Tempo: Add start time and end time parameters while querying traces (#48068)

* 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 <adrapereira@gmail.com>
This commit is contained in:
bikashmishra100
2022-10-21 20:08:10 +05:30
committed by GitHub
parent 245c1ee3d3
commit 98053cfde8
7 changed files with 155 additions and 10 deletions

View File

@ -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
}

View File

@ -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())
})
}

View File

@ -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) => {
<LokiSearchSettings options={options} onOptionsChange={onOptionsChange} />
</div>
<div className="gf-form-group">
<QuerySettings options={options} onOptionsChange={onOptionsChange} />
</div>
<div className="gf-form-group">
<SpanBarSettings options={options} onOptionsChange={onOptionsChange} />
</div>

View File

@ -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<TempoJsonData> {}
export function QuerySettings({ options, onOptionsChange }: Props) {
const styles = useStyles(getStyles);
return (
<div className={styles.container}>
<h3 className="page-heading">TraceID Query</h3>
<InlineFieldRow>
<InlineField
label="Time shift for start of search"
labelWidth={26}
grow
tooltip="Shifts the start of the time range when searching by trace ID. This is needed as searching for traces can return traces that do not fully fall into the search time range, so we recommend using higher time shifts for longer traces. Default 30m (Time units can be used here, for example: 5s, 1m, 3h)"
>
<Input
type="text"
placeholder="30m"
width={40}
onChange={(v) =>
updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'traceQuery', {
...options.jsonData.traceQuery,
spanStartTimeShift: v.currentTarget.value,
})
}
value={options.jsonData.traceQuery?.spanStartTimeShift || ''}
/>
</InlineField>
</InlineFieldRow>
<InlineFieldRow>
<InlineField
label="Time shift for end of search"
labelWidth={26}
grow
tooltip="Shifts the end of the time range when searching by trace ID. This is needed as searching for traces can return traces that do not fully fall into the search time range, so we recommend using higher time shifts for longer traces. Default 30m (Time units can be used here, for example: 5s, 1m, 3h)"
>
<Input
type="text"
placeholder="30m"
width={40}
onChange={(v) =>
updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'traceQuery', {
...options.jsonData.traceQuery,
spanEndTimeShift: v.currentTarget.value,
})
}
value={options.jsonData.traceQuery?.spanEndTimeShift || ''}
/>
</InlineField>
</InlineFieldRow>
</div>
);
}
const getStyles = () => ({
container: css`
label: container;
width: 100%;
`,
row: css`
label: row;
align-items: baseline;
`,
});

View File

@ -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', () => {

View File

@ -11,6 +11,7 @@ import {
FieldType,
isValidGoDuration,
LoadingState,
rangeUtil,
ScopedVars,
} from '@grafana/data';
import {
@ -67,6 +68,10 @@ export class TempoDatasource extends DataSourceWithBackend<TempoQuery, TempoJson
lokiSearch?: {
datasourceUid?: string;
};
traceQuery?: {
spanStartTimeShift?: string;
spanEndTimeShift?: string;
};
uploadedJson?: string | ArrayBuffer | null = null;
spanBar?: SpanBarOptions;
languageProvider: TempoLanguageProvider;
@ -81,6 +86,7 @@ export class TempoDatasource extends DataSourceWithBackend<TempoQuery, TempoJson
this.search = instanceSettings.jsonData.search;
this.nodeGraph = instanceSettings.jsonData.nodeGraph;
this.lokiSearch = instanceSettings.jsonData.lokiSearch;
this.traceQuery = instanceSettings.jsonData.traceQuery;
this.languageProvider = new TempoLanguageProvider(this);
}
@ -300,16 +306,14 @@ export class TempoDatasource extends DataSourceWithBackend<TempoQuery, TempoJson
* @param targets
* @private
*/
private handleTraceIdQuery(
options: DataQueryRequest<TempoQuery>,
targets: TempoQuery[]
): Observable<DataQueryResponse> {
handleTraceIdQuery(options: DataQueryRequest<TempoQuery>, targets: TempoQuery[]): Observable<DataQueryResponse> {
const validTargets = targets.filter((t) => t.query).map((t) => ({ ...t, query: t.query.trim() }));
if (!validTargets.length) {
return EMPTY;
}
const traceRequest: DataQueryRequest<TempoQuery> = { ...options, targets: validTargets };
const traceRequest = this.traceIdQueryRequest(options, validTargets);
return super.query(traceRequest).pipe(
map((response) => {
if (response.error) {
@ -320,6 +324,21 @@ export class TempoDatasource extends DataSourceWithBackend<TempoQuery, TempoJson
);
}
traceIdQueryRequest(options: DataQueryRequest<TempoQuery>, targets: TempoQuery[]): DataQueryRequest<TempoQuery> {
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 }));
}

View File

@ -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