From 559ef427673357756a0bbac8a259899b153c3b29 Mon Sep 17 00:00:00 2001 From: Andre Pereira Date: Fri, 21 Oct 2022 16:51:48 +0100 Subject: [PATCH] Tempo: Add start time and end time parameters while querying traces (#48068) (#57459) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add start time and end time parameters while querying tempo traces * Added configurable time shift to query by trace id * Test that the URL is formatted correctly * Added test to check for time shift * Improved label and tooltip of new time shift settings Co-authored-by: André Pereira (cherry picked from commit 98053cfde815f588e1c96ed51f225ea1cf14a508) Co-authored-by: bikashmishra100 --- pkg/tsdb/tempo/tempo.go | 13 +++- pkg/tsdb/tempo/tempo_test.go | 12 +++- .../tempo/configuration/ConfigEditor.tsx | 5 ++ .../tempo/configuration/QuerySettings.tsx | 72 +++++++++++++++++++ .../datasource/tempo/datasource.test.ts | 30 ++++++++ .../plugins/datasource/tempo/datasource.ts | 29 ++++++-- public/app/plugins/datasource/tempo/types.ts | 4 ++ 7 files changed, 155 insertions(+), 10 deletions(-) create mode 100644 public/app/plugins/datasource/tempo/configuration/QuerySettings.tsx diff --git a/pkg/tsdb/tempo/tempo.go b/pkg/tsdb/tempo/tempo.go index 6dee8319a97..7f60389dba9 100644 --- a/pkg/tsdb/tempo/tempo.go +++ b/pkg/tsdb/tempo/tempo.go @@ -73,7 +73,7 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) return nil, err } - request, err := s.createRequest(ctx, dsInfo, model.TraceID) + request, err := s.createRequest(ctx, dsInfo, model.TraceID, req.Queries[0].TimeRange.From.Unix(), req.Queries[0].TimeRange.To.Unix()) if err != nil { return result, err } @@ -117,8 +117,15 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) return result, nil } -func (s *Service) createRequest(ctx context.Context, dsInfo *datasourceInfo, traceID string) (*http.Request, error) { - req, err := http.NewRequestWithContext(ctx, "GET", dsInfo.URL+"/api/traces/"+traceID, nil) +func (s *Service) createRequest(ctx context.Context, dsInfo *datasourceInfo, traceID string, start int64, end int64) (*http.Request, error) { + var tempoQuery string + if start == 0 || end == 0 { + tempoQuery = fmt.Sprintf("%s/api/traces/%s", dsInfo.URL, traceID) + } else { + tempoQuery = fmt.Sprintf("%s/api/traces/%s?start=%d&end=%d", dsInfo.URL, traceID, start, end) + } + + req, err := http.NewRequestWithContext(ctx, "GET", tempoQuery, nil) if err != nil { return nil, err } diff --git a/pkg/tsdb/tempo/tempo_test.go b/pkg/tsdb/tempo/tempo_test.go index 741a7bd3e40..a037d8b4777 100644 --- a/pkg/tsdb/tempo/tempo_test.go +++ b/pkg/tsdb/tempo/tempo_test.go @@ -10,10 +10,18 @@ import ( ) func TestTempo(t *testing.T) { - t.Run("createRequest - success", func(t *testing.T) { + t.Run("createRequest without time range - success", func(t *testing.T) { service := &Service{tlog: log.New("tempo-test")} - req, err := service.createRequest(context.Background(), &datasourceInfo{}, "traceID") + req, err := service.createRequest(context.Background(), &datasourceInfo{}, "traceID", 0, 0) require.NoError(t, err) assert.Equal(t, 1, len(req.Header)) }) + + t.Run("createRequest with time range - success", func(t *testing.T) { + service := &Service{tlog: log.New("tempo-test")} + req, err := service.createRequest(context.Background(), &datasourceInfo{}, "traceID", 1, 2) + require.NoError(t, err) + assert.Equal(t, 1, len(req.Header)) + assert.Equal(t, "/api/traces/traceID?start=1&end=2", req.URL.String()) + }) } diff --git a/public/app/plugins/datasource/tempo/configuration/ConfigEditor.tsx b/public/app/plugins/datasource/tempo/configuration/ConfigEditor.tsx index 10b85be9e49..32f633a5414 100644 --- a/public/app/plugins/datasource/tempo/configuration/ConfigEditor.tsx +++ b/public/app/plugins/datasource/tempo/configuration/ConfigEditor.tsx @@ -9,6 +9,7 @@ import { TraceToLogsSettings } from 'app/core/components/TraceToLogs/TraceToLogs import { TraceToMetricsSettings } from 'app/core/components/TraceToMetrics/TraceToMetricsSettings'; import { LokiSearchSettings } from './LokiSearchSettings'; +import { QuerySettings } from './QuerySettings'; import { SearchSettings } from './SearchSettings'; import { ServiceGraphSettings } from './ServiceGraphSettings'; @@ -50,6 +51,10 @@ export const ConfigEditor = ({ options, onOptionsChange }: Props) => { +
+ +
+
diff --git a/public/app/plugins/datasource/tempo/configuration/QuerySettings.tsx b/public/app/plugins/datasource/tempo/configuration/QuerySettings.tsx new file mode 100644 index 00000000000..a8d82af8479 --- /dev/null +++ b/public/app/plugins/datasource/tempo/configuration/QuerySettings.tsx @@ -0,0 +1,72 @@ +import { css } from '@emotion/css'; +import React from 'react'; + +import { DataSourcePluginOptionsEditorProps, updateDatasourcePluginJsonDataOption } from '@grafana/data'; +import { InlineField, InlineFieldRow, Input, useStyles } from '@grafana/ui'; + +import { TempoJsonData } from '../types'; + +interface Props extends DataSourcePluginOptionsEditorProps {} + +export function QuerySettings({ options, onOptionsChange }: Props) { + const styles = useStyles(getStyles); + + return ( +
+

TraceID Query

+ + + + updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'traceQuery', { + ...options.jsonData.traceQuery, + spanStartTimeShift: v.currentTarget.value, + }) + } + value={options.jsonData.traceQuery?.spanStartTimeShift || ''} + /> + + + + + + updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'traceQuery', { + ...options.jsonData.traceQuery, + spanEndTimeShift: v.currentTarget.value, + }) + } + value={options.jsonData.traceQuery?.spanEndTimeShift || ''} + /> + + +
+ ); +} + +const getStyles = () => ({ + container: css` + label: container; + width: 100%; + `, + row: css` + label: row; + align-items: baseline; + `, +}); diff --git a/public/app/plugins/datasource/tempo/datasource.test.ts b/public/app/plugins/datasource/tempo/datasource.test.ts index 99c46aeb309..0a3c84a2ed4 100644 --- a/public/app/plugins/datasource/tempo/datasource.test.ts +++ b/public/app/plugins/datasource/tempo/datasource.test.ts @@ -6,6 +6,7 @@ import { DataFrame, dataFrameToJSON, DataSourceInstanceSettings, + dateTime, FieldType, getDefaultTimeRange, LoadingState, @@ -365,6 +366,35 @@ describe('Tempo data source', () => { expect(response).toBe('456'); }); }); + + it('should include time shift when querying for traceID', () => { + const ds = new TempoDatasource({ + ...defaultSettings, + jsonData: { traceQuery: { spanStartTimeShift: '2m', spanEndTimeShift: '4m' } }, + }); + + const request = ds.traceIdQueryRequest( + { + requestId: 'test', + interval: '', + intervalMs: 5, + scopedVars: {}, + targets: [], + timezone: '', + app: '', + startTime: 0, + range: { + from: dateTime(new Date(2022, 8, 13, 16, 0, 0, 0)), + to: dateTime(new Date(2022, 8, 13, 16, 15, 0, 0)), + raw: { from: '15m', to: 'now' }, + }, + }, + [{ refId: 'refid1', queryType: 'traceId', query: '' } as TempoQuery] + ); + + expect(request.range.from.unix()).toBe(dateTime(new Date(2022, 8, 13, 15, 58, 0, 0)).unix()); + expect(request.range.to.unix()).toBe(dateTime(new Date(2022, 8, 13, 16, 19, 0, 0)).unix()); + }); }); describe('Tempo apm table', () => { diff --git a/public/app/plugins/datasource/tempo/datasource.ts b/public/app/plugins/datasource/tempo/datasource.ts index 4338784e06a..b7e9ce3e5da 100644 --- a/public/app/plugins/datasource/tempo/datasource.ts +++ b/public/app/plugins/datasource/tempo/datasource.ts @@ -11,6 +11,7 @@ import { FieldType, isValidGoDuration, LoadingState, + rangeUtil, ScopedVars, } from '@grafana/data'; import { @@ -68,6 +69,10 @@ export class TempoDatasource extends DataSourceWithBackend, - targets: TempoQuery[] - ): Observable { + handleTraceIdQuery(options: DataQueryRequest, targets: TempoQuery[]): Observable { const validTargets = targets.filter((t) => t.query).map((t) => ({ ...t, query: t.query.trim() })); if (!validTargets.length) { return EMPTY; } - const traceRequest: DataQueryRequest = { ...options, targets: validTargets }; + const traceRequest = this.traceIdQueryRequest(options, validTargets); + return super.query(traceRequest).pipe( map((response) => { if (response.error) { @@ -309,6 +313,21 @@ export class TempoDatasource extends DataSourceWithBackend, targets: TempoQuery[]): DataQueryRequest { + return { + ...options, + range: options.range && { + ...options.range, + from: options.range.from.subtract( + rangeUtil.intervalToMs(this.traceQuery?.spanStartTimeShift || '30m'), + 'milliseconds' + ), + to: options.range.to.add(rangeUtil.intervalToMs(this.traceQuery?.spanEndTimeShift || '30m'), 'milliseconds'), + }, + targets, + }; + } + async metadataRequest(url: string, params = {}) { return await lastValueFrom(this._request(url, params, { method: 'GET', hideFromInspector: true })); } diff --git a/public/app/plugins/datasource/tempo/types.ts b/public/app/plugins/datasource/tempo/types.ts index e36cf93486b..6fca0f681df 100644 --- a/public/app/plugins/datasource/tempo/types.ts +++ b/public/app/plugins/datasource/tempo/types.ts @@ -29,6 +29,10 @@ export interface TempoJsonData extends DataSourceJsonData { spanBar?: { tag: string; }; + traceQuery?: { + spanStartTimeShift?: string; + spanEndTimeShift?: string; + }; } // search = Loki search, nativeSearch = Tempo search for backwards compatibility