Tempo: Display start time in search results as relative time (#44568)

* Explore: modify tempo trace start time to human readable format in table output

Signed-off-by: tharun <rajendrantharun@live.com>

* add unit tests

Signed-off-by: tharun <rajendrantharun@live.com>
This commit is contained in:
Tharun Rajendran 2022-01-31 20:37:43 +05:30 committed by GitHub
parent 76603b93d6
commit de1661e877
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 91 additions and 7 deletions

View File

@ -1,6 +1,12 @@
import { FieldType, MutableDataFrame } from '@grafana/data';
import { createTableFrame, transformToOTLP, transformFromOTLP } from './resultTransformer';
import { otlpDataFrameToResponse, otlpDataFrameFromResponse, otlpResponse } from './testResponse';
import { FieldType, MutableDataFrame, PluginType, DataSourceInstanceSettings, dateTime } from '@grafana/data';
import {
SearchResponse,
createTableFrame,
transformToOTLP,
transformFromOTLP,
createTableFrameFromSearch,
} from './resultTransformer';
import { otlpDataFrameToResponse, otlpDataFrameFromResponse, otlpResponse, tempoSearchResponse } from './testResponse';
import { collectorTypes } from '@opentelemetry/exporter-collector';
describe('transformTraceList()', () => {
@ -54,3 +60,43 @@ describe('transformFromOTLP()', () => {
expect(res.data[0]).toMatchObject(otlpDataFrameFromResponse);
});
});
describe('createTableFrameFromSearch()', () => {
const mockTimeUnix = dateTime(1643357709095).valueOf();
global.Date.now = jest.fn(() => mockTimeUnix);
test('transforms search response to dataFrame', () => {
const frame = createTableFrameFromSearch(tempoSearchResponse.traces as SearchResponse[], defaultSettings);
expect(frame.fields[0].name).toBe('traceID');
expect(frame.fields[0].values.get(0)).toBe('e641dcac1c3a0565');
expect(frame.fields[1].name).toBe('traceName');
expect(frame.fields[1].values.get(0)).toBe('c10d7ca4e3a00354 ');
// expect time in ago format if startTime less than 1 hour
expect(frame.fields[2].name).toBe('startTime');
expect(frame.fields[2].values.get(0)).toBe('15 minutes ago');
// expect time in format if startTime greater than 1 hour
expect(frame.fields[2].values.get(1)).toBe('2022-01-27 22:56:06');
expect(frame.fields[3].name).toBe('duration');
expect(frame.fields[3].values.get(0)).toBe(65);
});
});
const defaultSettings: DataSourceInstanceSettings = {
id: 0,
uid: '0',
type: 'tracing',
name: 'tempo',
access: 'proxy',
meta: {
id: 'tempo',
name: 'tempo',
type: PluginType.datasource,
info: {} as any,
module: '',
baseUrl: '',
},
jsonData: {},
};

View File

@ -9,9 +9,12 @@ import {
TraceKeyValuePair,
TraceLog,
TraceSpanRow,
dateTimeFormat,
} from '@grafana/data';
import { SpanStatus, SpanStatusCode } from '@opentelemetry/api';
import { collectorTypes } from '@opentelemetry/exporter-collector';
import formatDistance from 'date-fns/formatDistance';
import differenceInHours from 'date-fns/differenceInHours';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
import { createGraphFrames } from './graphTransform';
@ -525,7 +528,7 @@ function parseJsonFields(frame: DataFrame) {
}
}
type SearchResponse = {
export type SearchResponse = {
traceID: string;
rootServiceName: string;
rootTraceName: string;
@ -558,7 +561,7 @@ export function createTableFrameFromSearch(data: SearchResponse[], instanceSetti
},
},
{ name: 'traceName', type: FieldType.string, config: { displayNameFromDS: 'Trace name' } },
{ name: 'startTime', type: FieldType.time, config: { displayNameFromDS: 'Start time' } },
{ name: 'startTime', type: FieldType.string, config: { displayNameFromDS: 'Start time' } },
{ name: 'duration', type: FieldType.number, config: { displayNameFromDS: 'Duration', unit: 'ms' } },
],
meta: {
@ -569,7 +572,9 @@ export function createTableFrameFromSearch(data: SearchResponse[], instanceSetti
return frame;
}
// Show the most recent traces
const traceData = data.map(transformToTraceData).sort((a, b) => b?.startTime! - a?.startTime!);
const traceData = data
.sort((a, b) => parseInt(b?.startTimeUnixNano!, 10) / 1000000 - parseInt(a?.startTimeUnixNano!, 10) / 1000000)
.map(transformToTraceData);
for (const trace of traceData) {
frame.add(trace);
@ -586,9 +591,21 @@ function transformToTraceData(data: SearchResponse) {
if (data.rootTraceName) {
traceName += data.rootTraceName;
}
const traceStartTime = parseInt(data.startTimeUnixNano!, 10) / 1000000;
let startTime = dateTimeFormat(traceStartTime);
if (Math.abs(differenceInHours(new Date(traceStartTime), Date.now())) <= 1) {
startTime = formatDistance(new Date(traceStartTime), Date.now(), {
addSuffix: true,
includeSeconds: true,
});
}
return {
traceID: data.traceID,
startTime: parseInt(data.startTimeUnixNano, 10) / 1000 / 1000,
startTime: startTime,
duration: data.durationMs,
traceName,
};

View File

@ -2202,3 +2202,24 @@ export const otlpResponse = {
},
],
};
export const tempoSearchResponse = {
traces: [
{
traceID: 'e641dcac1c3a0565',
rootServiceName: 'c10d7ca4e3a00354',
startTimeUnixNano: '1643356828724000000',
durationMs: 65,
},
{
traceID: 'c2983496a2b12544',
rootServiceName: '<root span not yet received>',
startTimeUnixNano: '1643342166678000000',
durationMs: 93,
},
],
metrics: {
inspectedTraces: 2,
inspectedBytes: '83720',
},
};