From de1661e87721487e38e701a2871f5f571b7c32ef Mon Sep 17 00:00:00 2001 From: Tharun Rajendran Date: Mon, 31 Jan 2022 20:37:43 +0530 Subject: [PATCH] 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 * add unit tests Signed-off-by: tharun --- .../tempo/resultTransformer.test.ts | 52 +++++++++++++++++-- .../datasource/tempo/resultTransformer.ts | 25 +++++++-- .../plugins/datasource/tempo/testResponse.ts | 21 ++++++++ 3 files changed, 91 insertions(+), 7 deletions(-) diff --git a/public/app/plugins/datasource/tempo/resultTransformer.test.ts b/public/app/plugins/datasource/tempo/resultTransformer.test.ts index 01909d72a34..175efdcfabb 100644 --- a/public/app/plugins/datasource/tempo/resultTransformer.test.ts +++ b/public/app/plugins/datasource/tempo/resultTransformer.test.ts @@ -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: {}, +}; diff --git a/public/app/plugins/datasource/tempo/resultTransformer.ts b/public/app/plugins/datasource/tempo/resultTransformer.ts index 9eb4a61442c..cba5d12db45 100644 --- a/public/app/plugins/datasource/tempo/resultTransformer.ts +++ b/public/app/plugins/datasource/tempo/resultTransformer.ts @@ -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, }; diff --git a/public/app/plugins/datasource/tempo/testResponse.ts b/public/app/plugins/datasource/tempo/testResponse.ts index 2596c5221fe..97f7242b53b 100644 --- a/public/app/plugins/datasource/tempo/testResponse.ts +++ b/public/app/plugins/datasource/tempo/testResponse.ts @@ -2202,3 +2202,24 @@ export const otlpResponse = { }, ], }; + +export const tempoSearchResponse = { + traces: [ + { + traceID: 'e641dcac1c3a0565', + rootServiceName: 'c10d7ca4e3a00354', + startTimeUnixNano: '1643356828724000000', + durationMs: 65, + }, + { + traceID: 'c2983496a2b12544', + rootServiceName: '', + startTimeUnixNano: '1643342166678000000', + durationMs: 93, + }, + ], + metrics: { + inspectedTraces: 2, + inspectedBytes: '83720', + }, +};