From c2393f580a7cf941f44bef96ccffc238363dfab8 Mon Sep 17 00:00:00 2001 From: Naman Verma Date: Thu, 12 Mar 2026 14:19:29 +0530 Subject: [PATCH] chore: add last seen info in err message --- pkg/querier/querier.go | 26 ++++++++++---- pkg/telemetrymetadata/metadata.go | 34 +++++++++++++++++++ pkg/types/telemetrytypes/store.go | 2 ++ .../telemetrytypestest/metadata_store.go | 4 +++ 4 files changed, 60 insertions(+), 6 deletions(-) diff --git a/pkg/querier/querier.go b/pkg/querier/querier.go index fb17328497..80dab965ec 100644 --- a/pkg/querier/querier.go +++ b/pkg/querier/querier.go @@ -20,6 +20,7 @@ import ( "github.com/SigNoz/signoz/pkg/types/instrumentationtypes" "github.com/SigNoz/signoz/pkg/types/metrictypes" "github.com/SigNoz/signoz/pkg/types/telemetrytypes" + "github.com/dustin/go-humanize" "golang.org/x/exp/maps" qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5" @@ -279,6 +280,7 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtype queries := make(map[string]qbtypes.Query) steps := make(map[string]qbtypes.Step) + missingMetrics := []string{} for _, query := range req.CompositeQuery.Queries { var queryName string @@ -371,7 +373,6 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtype } q.logger.DebugContext(ctx, "fetched metric temporalities and types", "metric_temporality", metricTemporality, "metric_types", metricTypes) } - missingMetrics := []string{} for i := range spec.Aggregations { if spec.Aggregations[i].MetricName != "" && spec.Aggregations[i].Temporality == metrictypes.Unknown { if temp, ok := metricTemporality[spec.Aggregations[i].MetricName]; ok && temp != metrictypes.Unknown { @@ -389,11 +390,6 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtype } } } - if len(missingMetrics) == 1 { - return nil, errors.NewNotFoundf(errors.CodeNotFound, "no data found for the metric %s in the query time range", missingMetrics[0]) - } else if len(missingMetrics) > 1 { - return nil, errors.NewNotFoundf(errors.CodeNotFound, "no data found for the following metrics in the query time range: %s", strings.Join(missingMetrics, ", ")) - } spec.ShiftBy = extractShiftFromBuilderQuery(spec) timeRange := adjustTimeRangeForShift(spec, qbtypes.TimeRange{From: req.Start, To: req.End}, req.RequestType) var bq *builderQuery[qbtypes.MetricAggregation] @@ -412,6 +408,24 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtype } } } + if len(missingMetrics) > 0 { + lastSeenInfo, _ := q.metadataStore.FetchLastSeenInfoMulti(ctx, missingMetrics...) + lastSeenStr := func(name string) string { + if ts, ok := lastSeenInfo[name]; ok && ts > 0 { + ago := humanize.RelTime(time.UnixMilli(ts), time.Now(), "ago", "from now") + return fmt.Sprintf("%s (last seen %s)", name, ago) + } + return name + } + if len(missingMetrics) == 1 { + return nil, errors.NewNotFoundf(errors.CodeNotFound, "no data found for the metric %s in the query time range", lastSeenStr(missingMetrics[0])) + } + parts := make([]string, len(missingMetrics)) + for i, m := range missingMetrics { + parts[i] = lastSeenStr(m) + } + return nil, errors.NewNotFoundf(errors.CodeNotFound, "no data found for the following metrics in the query time range: %s", strings.Join(parts, ", ")) + } qbResp, qbErr := q.run(ctx, orgID, queries, req, steps, event) if qbResp != nil { qbResp.QBEvent = event diff --git a/pkg/telemetrymetadata/metadata.go b/pkg/telemetrymetadata/metadata.go index 5e4f9bb0f6..3a58ab5d0c 100644 --- a/pkg/telemetrymetadata/metadata.go +++ b/pkg/telemetrymetadata/metadata.go @@ -1928,3 +1928,37 @@ func (t *telemetryMetaStore) GetFirstSeenFromMetricMetadata(ctx context.Context, return result, nil } + +func (t *telemetryMetaStore) FetchLastSeenInfoMulti(ctx context.Context, metricNames ...string) (map[string]int64, error) { + sb := sqlbuilder.Select( + "metric_name", + "max(unix_milli)", + ). + From(t.metricsDBName + "." + telemetrymetrics.TimeseriesV4TableName) + sb.Where(sb.In("metric_name", metricNames)) + sb.GroupBy("metric_name") + + query, args := sb.BuildWithFlavor(sqlbuilder.ClickHouse) + + t.logger.DebugContext(ctx, "fetching metric last seen timestamp", "query", query, "args", args) + + rows, err := t.telemetrystore.ClickhouseDB().Query(ctx, query, args...) + if err != nil { + return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to fetch metric last seen info") + } + defer rows.Close() + + lastSeenInfo := make(map[string]int64) + for rows.Next() { + var metricName string + var unix_milli int64 + if err := rows.Scan(&metricName, &unix_milli); err != nil { + return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to scan last seen info result") + } + lastSeenInfo[metricName] = unix_milli + } + if err := rows.Err(); err != nil { + return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "error iterating over metrics temporality rows") + } + return lastSeenInfo, nil +} diff --git a/pkg/types/telemetrytypes/store.go b/pkg/types/telemetrytypes/store.go index 628a90d9a4..cda299033a 100644 --- a/pkg/types/telemetrytypes/store.go +++ b/pkg/types/telemetrytypes/store.go @@ -45,6 +45,8 @@ type MetadataStore interface { // GetFirstSeenFromMetricMetadata gets the first seen timestamp for a metric metadata lookup key. GetFirstSeenFromMetricMetadata(ctx context.Context, lookupKeys []MetricMetadataLookupKey) (map[MetricMetadataLookupKey]int64, error) + + FetchLastSeenInfoMulti(ctx context.Context, metricNames ...string) (map[string]int64, error) } type MetricMetadataLookupKey struct { diff --git a/pkg/types/telemetrytypes/telemetrytypestest/metadata_store.go b/pkg/types/telemetrytypes/telemetrytypestest/metadata_store.go index fed12b0832..c962933029 100644 --- a/pkg/types/telemetrytypes/telemetrytypestest/metadata_store.go +++ b/pkg/types/telemetrytypes/telemetrytypestest/metadata_store.go @@ -342,3 +342,7 @@ func (m *MockMetadataStore) SetFirstSeenFromMetricMetadata(firstSeenMap map[tele m.LookupKeysMap[key] = value } } + +func (m *MockMetadataStore) FetchLastSeenInfoMulti(ctx context.Context, metricNames ...string) (map[string]int64, error) { + return make(map[string]int64), nil +}