Tempo: Fix Spans table format (#79938)

This commit is contained in:
Fabrizio 2024-01-03 11:28:13 +01:00 committed by GitHub
parent e372b54722
commit ed5c90a8b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 198 additions and 29 deletions

View File

@ -15,6 +15,7 @@ import {
transformFromOTLP,
createTableFrameFromSearch,
createTableFrameFromTraceQlQuery,
createTableFrameFromTraceQlQueryAsSpans,
} from './resultTransformer';
import {
badOTLPResponse,
@ -198,6 +199,152 @@ describe('createTableFrameFromTraceQlQuery()', () => {
});
});
describe('createTableFrameFromTraceQlQueryAsSpans()', () => {
test('transforms TraceQL legacy response to DataFrame for Spans table type', () => {
const traces = [
{
traceID: '1',
rootServiceName: 'prometheus',
rootTraceName: 'POST /api/v1/write',
startTimeUnixNano: '1702984850354934104',
durationMs: 1,
spanSet: {
spans: [
{
spanID: '11',
startTimeUnixNano: '1702984850354934104',
durationNanos: '1377608',
},
],
matched: 1,
},
},
{
traceID: '2',
rootServiceName: 'prometheus',
rootTraceName: 'GET /api/v1/status/config',
startTimeUnixNano: '1702984840786143459',
spanSet: {
spans: [
{
spanID: '21',
startTimeUnixNano: '1702984840786143459',
durationNanos: '542316',
},
],
matched: 1,
},
},
];
const frameList = createTableFrameFromTraceQlQueryAsSpans(traces, defaultSettings);
const frame = frameList[0];
// Trace ID field
expect(frame.fields[0].name).toBe('traceIdHidden');
expect(frame.fields[0].values[0]).toBe('1');
// Trace service field
expect(frame.fields[1].name).toBe('traceService');
expect(frame.fields[1].type).toBe('string');
expect(frame.fields[1].values[0]).toBe('prometheus');
// Trace name field
expect(frame.fields[2].name).toBe('traceName');
expect(frame.fields[2].type).toBe('string');
expect(frame.fields[2].values[0]).toBe('POST /api/v1/write');
// Span ID field
expect(frame.fields[3].name).toBe('spanID');
expect(frame.fields[3].type).toBe('string');
expect(frame.fields[3].values[0]).toBe('11');
// Time field
expect(frame.fields[4].name).toBe('time');
expect(frame.fields[4].type).toBe('time');
expect(frame.fields[4].values[0]).toBe(1702984850354.934);
// Name field
expect(frame.fields[5].name).toBe('name');
expect(frame.fields[5].type).toBe('string');
expect(frame.fields[5].values[0]).toBe(undefined);
// Duration field
expect(frame.fields[6].name).toBe('duration');
expect(frame.fields[6].type).toBe('number');
expect(frame.fields[6].values[0]).toBe(1377608);
// No more fields
expect(frame.fields.length).toBe(7);
});
test('transforms TraceQL response to DataFrame for Spans table type', () => {
const traces = [
{
traceID: '1',
rootServiceName: 'prometheus',
rootTraceName: 'POST /api/v1/write',
startTimeUnixNano: '1702984850354934104',
durationMs: 1,
spanSets: [
{
spans: [
{
spanID: '11',
startTimeUnixNano: '1702984850354934104',
durationNanos: '1377608',
},
],
matched: 1,
},
],
},
{
traceID: '2',
rootServiceName: 'prometheus',
rootTraceName: 'GET /api/v1/status/config',
startTimeUnixNano: '1702984840786143459',
spanSets: [
{
spans: [
{
spanID: '21',
startTimeUnixNano: '1702984840786143459',
durationNanos: '542316',
},
],
matched: 1,
},
],
},
];
const frameList = createTableFrameFromTraceQlQueryAsSpans(traces, defaultSettings);
const frame = frameList[0];
// Trace ID field
expect(frame.fields[0].name).toBe('traceIdHidden');
expect(frame.fields[0].values[0]).toBe('1');
// Trace service field
expect(frame.fields[1].name).toBe('traceService');
expect(frame.fields[1].type).toBe('string');
expect(frame.fields[1].values[0]).toBe('prometheus');
// Trace name field
expect(frame.fields[2].name).toBe('traceName');
expect(frame.fields[2].type).toBe('string');
expect(frame.fields[2].values[0]).toBe('POST /api/v1/write');
// Span ID field
expect(frame.fields[3].name).toBe('spanID');
expect(frame.fields[3].type).toBe('string');
expect(frame.fields[3].values[0]).toBe('11');
// Time field
expect(frame.fields[4].name).toBe('time');
expect(frame.fields[4].type).toBe('time');
expect(frame.fields[4].values[0]).toBe(1702984850354.934);
// Name field
expect(frame.fields[5].name).toBe('name');
expect(frame.fields[5].type).toBe('string');
expect(frame.fields[5].values[0]).toBe(undefined);
// Duration field
expect(frame.fields[6].name).toBe('duration');
expect(frame.fields[6].type).toBe('number');
expect(frame.fields[6].values[0]).toBe(1377608);
// No more fields
expect(frame.fields.length).toBe(7);
});
});
describe('transformFromOTLP()', () => {
// Mock the console error so that running the test suite doesnt throw the error
const origError = console.error;

View File

@ -739,32 +739,6 @@ export function createTableFrameFromTraceQlQueryAsSpans(
): DataFrame[] {
const spanDynamicAttrs: Record<string, FieldDTO> = {};
let hasNameAttribute = false;
data?.forEach(
(t) =>
t.spanSets?.forEach((ss) => {
ss.attributes?.forEach((attr) => {
spanDynamicAttrs[attr.key] = {
name: attr.key,
type: FieldType.string,
config: { displayNameFromDS: attr.key },
};
});
ss.spans.forEach((span) => {
if (span.name) {
hasNameAttribute = true;
}
span.attributes?.forEach((attr) => {
spanDynamicAttrs[attr.key] = {
name: attr.key,
type: FieldType.string,
config: { displayNameFromDS: attr.key },
};
});
});
})
);
const frame = new MutableDataFrame({
name: 'Spans',
refId: 'traces',
@ -856,15 +830,46 @@ export function createTableFrameFromTraceQlQueryAsSpans(
},
});
if (!data?.length) {
// According to the parameter type, `data` should never be undefined of null, but the old code had
// entries such as `!data` or `data?`, so we keep this check just for safety
if (data === undefined || data === null) {
console.error(`Unexpected ${data} value for \`data\``);
return [frame];
}
if (!data.length) {
return [frame];
}
data.forEach((trace) =>
getSpanSets(trace).forEach((ss) => {
ss.attributes?.forEach((attr) => {
spanDynamicAttrs[attr.key] = {
name: attr.key,
type: FieldType.string,
config: { displayNameFromDS: attr.key },
};
});
ss.spans.forEach((span) => {
if (span.name) {
hasNameAttribute = true;
}
span.attributes?.forEach((attr) => {
spanDynamicAttrs[attr.key] = {
name: attr.key,
type: FieldType.string,
config: { displayNameFromDS: attr.key },
};
});
});
})
);
data
// Show the most recent traces
.sort((a, b) => parseInt(b?.startTimeUnixNano!, 10) / 1000000 - parseInt(a?.startTimeUnixNano!, 10) / 1000000)
.forEach((trace) => {
trace.spanSets?.forEach((spanSet) => {
getSpanSets(trace).forEach((spanSet) => {
spanSet.spans.forEach((span) => {
frame.add(transformSpanToTraceData(span, spanSet, trace));
});
@ -874,6 +879,23 @@ export function createTableFrameFromTraceQlQueryAsSpans(
return [frame];
}
/**
* Get the spansets of a trace.
*
* Field `spanSets` is preferred to `spanSet` since the latter is deprecated in Tempo, but we
* support both for backward compatibility.
*
* @param trace a trace
* @returns the spansets of the trace, if existing
*/
const getSpanSets = (trace: TraceSearchMetadata): Spanset[] => {
if (trace.spanSets && trace.spanSet) {
console.warn('Both `spanSets` and `spanSet` are set. `spanSet` will be ignored');
}
return trace.spanSets || (trace.spanSet ? [trace.spanSet] : []);
};
const traceSubFrame = (
trace: TraceSearchMetadata,
spanSet: Spanset,

View File

@ -55,7 +55,7 @@ export type TraceSearchMetadata = {
rootTraceName: string;
startTimeUnixNano?: string;
durationMs?: number;
spanSet?: Spanset;
spanSet?: Spanset; // deprecated in Tempo, https://github.com/grafana/tempo/blob/3cc44fca03ba7d676dc77da6a18b8222546ede3c/docs/sources/tempo/api_docs/_index.md?plain=1#L619
spanSets?: Spanset[];
};