Jaeger: Sync getTraceName improvements from upstream (#37408)

* Improve trace name resolution

* Use getTraceName in transformTraceData

* Improve getTraceName performance

* Change trace-viewer extension back to ts
This commit is contained in:
Connor Lindsey 2021-08-02 08:23:43 -06:00 committed by GitHub
parent 0699a04dcd
commit 6906a74179
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 396 additions and 9 deletions

View File

@ -0,0 +1,244 @@
// Copyright (c) 2020 The Jaeger Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { _getTraceNameImpl as getTraceName } from './trace-viewer';
describe('getTraceName', () => {
const firstSpanId = 'firstSpanId';
const secondSpanId = 'secondSpanId';
const thirdSpanId = 'thirdSpanId';
const missingSpanId = 'missingSpanId';
const currentTraceId = 'currentTraceId';
const serviceName = 'serviceName';
const operationName = 'operationName';
const t = 1583758670000;
// Note: this trace has a loop S1 <- S2 <- S3 <- S1, which is the only way
// to make the algorithm return an empty string as trace name.
const spansWithNoRoots = [
{
spanID: firstSpanId,
traceID: currentTraceId,
startTime: t + 200,
process: {},
references: [
{
spanID: secondSpanId,
traceID: currentTraceId,
},
],
},
{
spanID: secondSpanId,
traceID: currentTraceId,
startTime: t + 100,
process: {},
references: [
{
spanID: thirdSpanId,
traceID: currentTraceId,
},
],
},
{
spanID: thirdSpanId,
traceID: currentTraceId,
startTime: t,
process: {},
references: [
{
spanID: firstSpanId,
traceID: currentTraceId,
},
],
},
];
const spansWithMultipleRootsDifferentByStartTime = [
{
spanID: firstSpanId,
traceID: currentTraceId,
startTime: t + 200,
process: {},
references: [
{
spanID: thirdSpanId,
traceID: currentTraceId,
},
],
},
{
spanID: secondSpanId, // may be a root span
traceID: currentTraceId,
startTime: t + 100,
process: {},
references: [
{
spanID: missingSpanId,
traceID: currentTraceId,
},
],
},
{
spanID: thirdSpanId, // root span (as the earliest)
traceID: currentTraceId,
startTime: t,
operationName,
process: {
serviceName,
},
references: [
{
spanID: missingSpanId,
traceID: currentTraceId,
},
],
},
];
const spansWithMultipleRootsWithOneWithoutRefs = [
{
spanID: firstSpanId,
traceID: currentTraceId,
startTime: t + 200,
process: {},
references: [
{
spanID: thirdSpanId,
traceID: currentTraceId,
},
],
},
{
spanID: secondSpanId, // root span (as a span without any refs)
traceID: currentTraceId,
startTime: t + 100,
operationName,
process: {
serviceName,
},
},
{
spanID: thirdSpanId, // may be a root span
traceID: currentTraceId,
startTime: t,
process: {},
references: [
{
spanID: missingSpanId,
traceID: currentTraceId,
},
],
},
];
const spansWithOneRootWithRemoteRef = [
{
spanID: firstSpanId,
traceID: currentTraceId,
startTime: t + 200,
process: {},
references: [
{
spanID: secondSpanId,
traceID: currentTraceId,
},
],
},
{
spanID: secondSpanId,
traceID: currentTraceId,
startTime: t + 100,
process: {},
references: [
{
spanID: thirdSpanId,
traceID: currentTraceId,
},
],
},
{
spanID: thirdSpanId, // effective root span, since its parent is missing
traceID: currentTraceId,
startTime: t,
operationName,
process: {
serviceName,
},
references: [
{
spanID: missingSpanId,
traceID: currentTraceId,
},
],
},
];
const spansWithOneRootWithNoRefs = [
{
spanID: firstSpanId,
traceID: currentTraceId,
startTime: t + 200,
process: {},
references: [
{
spanID: thirdSpanId,
traceID: currentTraceId,
},
],
},
{
spanID: secondSpanId, // root span
traceID: currentTraceId,
startTime: t + 100,
operationName,
process: {
serviceName,
},
},
{
spanID: thirdSpanId,
traceID: currentTraceId,
startTime: t,
process: {},
references: [
{
spanID: secondSpanId,
traceID: currentTraceId,
},
],
},
];
const fullTraceName = `${serviceName}: ${operationName}`;
it('returns an empty string if given spans with no root among them', () => {
expect(getTraceName(spansWithNoRoots)).toEqual('');
});
it('returns an id of root span with the earliest startTime', () => {
expect(getTraceName(spansWithMultipleRootsDifferentByStartTime)).toEqual(fullTraceName);
});
it('returns an id of root span without any refs', () => {
expect(getTraceName(spansWithMultipleRootsWithOneWithoutRefs)).toEqual(fullTraceName);
});
it('returns an id of root span with remote ref', () => {
expect(getTraceName(spansWithOneRootWithRemoteRef)).toEqual(fullTraceName);
});
it('returns an id of root span with no refs', () => {
expect(getTraceName(spansWithOneRootWithNoRefs)).toEqual(fullTraceName);
});
});

View File

@ -1,4 +1,4 @@
// Copyright (c) 2017 Uber Technologies, Inc.
// Copyright (c) 2020 The Jaeger Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -12,9 +12,46 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { memoize } from 'lodash';
import { TraceSpan } from '../types/trace';
export function getTraceName(spans: TraceSpan[]): string {
const span = spans.filter((sp) => !sp.references || !sp.references.length)[0];
return span ? `${span.process.serviceName}: ${span.operationName}` : '';
export function _getTraceNameImpl(spans: TraceSpan[]) {
// Use a span with no references to another span in given array
// prefering the span with the fewest references
// using start time as a tie breaker
let candidateSpan: TraceSpan | undefined;
const allIDs: Set<string> = new Set(spans.map(({ spanID }) => spanID));
for (let i = 0; i < spans.length; i++) {
const hasInternalRef =
spans[i].references &&
spans[i].references.some(({ traceID, spanID }) => traceID === spans[i].traceID && allIDs.has(spanID));
if (hasInternalRef) {
continue;
}
if (!candidateSpan) {
candidateSpan = spans[i];
continue;
}
const thisRefLength = (spans[i].references && spans[i].references.length) || 0;
const candidateRefLength = (candidateSpan.references && candidateSpan.references.length) || 0;
if (
thisRefLength < candidateRefLength ||
(thisRefLength === candidateRefLength && spans[i].startTime < candidateSpan.startTime)
) {
candidateSpan = spans[i];
}
}
return candidateSpan ? `${candidateSpan.process.serviceName}: ${candidateSpan.operationName}` : '';
}
export const getTraceName = memoize(_getTraceNameImpl, (spans: TraceSpan[]) => {
if (!spans.length) {
return 0;
}
return spans[0].traceID;
});

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { orderTags, deduplicateTags } from './transform-trace-data';
import transformTraceData, { orderTags, deduplicateTags } from './transform-trace-data';
describe('orderTags()', () => {
it('correctly orders tags', () => {
@ -53,3 +53,111 @@ describe('deduplicateTags()', () => {
expect(tagsInfo.warnings).toEqual(['Duplicate tag "b.ip:8.8.4.4"']);
});
});
describe('transformTraceData()', () => {
const startTime = 1586160015434000;
const duration = 34000;
const traceID = 'f77950feed55c1ce91dd8e87896623a6';
const rootSpanID = 'd4dcb46e95b781f5';
const rootOperationName = 'rootOperation';
const serviceName = 'serviceName';
const spans = [
{
traceID,
spanID: '41f71485ed2593e4',
operationName: 'someOperationName',
references: [
{
refType: 'CHILD_OF',
traceID,
spanID: rootSpanID,
},
],
startTime,
duration,
tags: [],
processID: 'p1',
},
{
traceID,
spanID: '4f623fd33c213cba',
operationName: 'anotherOperationName',
references: [
{
refType: 'CHILD_OF',
traceID,
spanID: rootSpanID,
},
],
startTime: startTime + 100,
duration,
tags: [],
processID: 'p1',
},
];
const rootSpanWithMissingRef = {
traceID,
spanID: rootSpanID,
operationName: rootOperationName,
references: [
{
refType: 'CHILD_OF',
traceID,
spanID: 'missingSpanId',
},
],
startTime: startTime + 50,
duration,
tags: [],
processID: 'p1',
};
const rootSpanWithoutRefs = {
traceID,
spanID: rootSpanID,
operationName: rootOperationName,
startTime: startTime + 50,
duration,
tags: [],
processID: 'p1',
};
const processes = {
p1: {
serviceName,
tags: [],
},
};
it('should return null for trace without traceID', () => {
const traceData = {
traceID: undefined,
processes,
spans,
};
expect(transformTraceData(traceData)).toEqual(null);
});
it('should return trace data with correct traceName based on root span with missing ref', () => {
const traceData = {
traceID,
processes,
spans: [...spans, rootSpanWithMissingRef],
};
expect(transformTraceData(traceData).traceName).toEqual(`${serviceName}: ${rootOperationName}`);
});
it('should return trace data with correct traceName based on root span without any refs', () => {
const traceData = {
traceID,
processes,
spans: [...spans, rootSpanWithoutRefs],
};
expect(transformTraceData(traceData).traceName).toEqual(`${serviceName}: ${rootOperationName}`);
});
});

View File

@ -20,6 +20,7 @@ import { getConfigValue } from '../utils/config/get-config';
import { TraceKeyValuePair, TraceSpan, Trace, TraceResponse } from '../types/trace';
// @ts-ignore
import TreeNode from '../utils/TreeNode';
import { getTraceName } from './trace-viewer';
// exported for tests
export function deduplicateTags(spanTags: TraceKeyValuePair[]) {
@ -121,7 +122,6 @@ export default function transformTraceData(data: TraceResponse | undefined): Tra
const tree = getTraceSpanIdsAsTree(data);
const spans: TraceSpan[] = [];
const svcCounts: Record<string, number> = {};
let traceName = '';
// Eslint complains about number type not needed but then TS complains it is implicitly any.
// eslint-disable-next-line @typescript-eslint/no-inferrable-types
@ -135,9 +135,6 @@ export default function transformTraceData(data: TraceResponse | undefined): Tra
}
const { serviceName } = span.process;
svcCounts[serviceName] = (svcCounts[serviceName] || 0) + 1;
if (!span.references || !span.references.length) {
traceName = `${serviceName}: ${span.operationName}`;
}
span.relativeStartTime = span.startTime - traceStartTime;
span.depth = depth - 1;
span.hasChildren = node.children.length > 0;
@ -166,6 +163,7 @@ export default function transformTraceData(data: TraceResponse | undefined): Tra
});
spans.push(span);
});
const traceName = getTraceName(spans);
const services = Object.keys(svcCounts).map((name) => ({ name, numberOfSpans: svcCounts[name] }));
return {
services,