mirror of
https://github.com/grafana/grafana.git
synced 2025-08-03 04:22:13 +08:00
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:
@ -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
|
||||
}
|
||||
|
@ -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())
|
||||
})
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
`,
|
||||
});
|
@ -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', () => {
|
||||
|
@ -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 }));
|
||||
}
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user