mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Trace View: Critical path highlighting (#76857)
* Added critical path computation code. Refactor some trace view code * Refactor js to ts * First implementation of critical path working * Simplified code * Added filter to show only critical path spans * Lint and stuff * Fixes and moving styling to object * Betterer
This commit is contained in:
parent
4ed36cbc1d
commit
107cf0dc04
@ -3920,13 +3920,6 @@ exports[`better eslint`] = {
|
|||||||
[0, 0, 0, "Styles should be written using objects.", "11"],
|
[0, 0, 0, "Styles should be written using objects.", "11"],
|
||||||
[0, 0, 0, "Styles should be written using objects.", "12"]
|
[0, 0, 0, "Styles should be written using objects.", "12"]
|
||||||
],
|
],
|
||||||
"public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanBar.tsx:5381": [
|
|
||||||
[0, 0, 0, "Styles should be written using objects.", "0"],
|
|
||||||
[0, 0, 0, "Styles should be written using objects.", "1"],
|
|
||||||
[0, 0, 0, "Styles should be written using objects.", "2"],
|
|
||||||
[0, 0, 0, "Styles should be written using objects.", "3"],
|
|
||||||
[0, 0, 0, "Styles should be written using objects.", "4"]
|
|
||||||
],
|
|
||||||
"public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanBarRow.tsx:5381": [
|
"public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanBarRow.tsx:5381": [
|
||||||
[0, 0, 0, "Styles should be written using objects.", "0"],
|
[0, 0, 0, "Styles should be written using objects.", "0"],
|
||||||
[0, 0, 0, "Styles should be written using objects.", "1"],
|
[0, 0, 0, "Styles should be written using objects.", "1"],
|
||||||
|
@ -35,6 +35,7 @@ import {
|
|||||||
TraceTimelineViewer,
|
TraceTimelineViewer,
|
||||||
TTraceTimeline,
|
TTraceTimeline,
|
||||||
} from './components';
|
} from './components';
|
||||||
|
import memoizedTraceCriticalPath from './components/CriticalPath';
|
||||||
import SpanGraph from './components/TracePageHeader/SpanGraph';
|
import SpanGraph from './components/TracePageHeader/SpanGraph';
|
||||||
import { TopOfViewRefType } from './components/TraceTimelineViewer/VirtualizedTraceView';
|
import { TopOfViewRefType } from './components/TraceTimelineViewer/VirtualizedTraceView';
|
||||||
import { createSpanLinkFactory } from './createSpanLink';
|
import { createSpanLinkFactory } from './createSpanLink';
|
||||||
@ -99,6 +100,7 @@ export function TraceView(props: Props) {
|
|||||||
const [focusedSpanIdForSearch, setFocusedSpanIdForSearch] = useState('');
|
const [focusedSpanIdForSearch, setFocusedSpanIdForSearch] = useState('');
|
||||||
const [showSpanFilters, setShowSpanFilters] = useToggle(false);
|
const [showSpanFilters, setShowSpanFilters] = useToggle(false);
|
||||||
const [showSpanFilterMatchesOnly, setShowSpanFilterMatchesOnly] = useState(false);
|
const [showSpanFilterMatchesOnly, setShowSpanFilterMatchesOnly] = useState(false);
|
||||||
|
const [showCriticalPathSpansOnly, setShowCriticalPathSpansOnly] = useState(false);
|
||||||
const [headerHeight, setHeaderHeight] = useState(100);
|
const [headerHeight, setHeaderHeight] = useState(100);
|
||||||
|
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
@ -159,6 +161,8 @@ export function TraceView(props: Props) {
|
|||||||
? props.scrollElement
|
? props.scrollElement
|
||||||
: document.getElementsByClassName(props.scrollElementClass ?? '')[0];
|
: document.getElementsByClassName(props.scrollElementClass ?? '')[0];
|
||||||
|
|
||||||
|
const criticalPath = memoizedTraceCriticalPath(traceProp);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{props.dataFrames?.length && traceProp ? (
|
{props.dataFrames?.length && traceProp ? (
|
||||||
@ -173,6 +177,8 @@ export function TraceView(props: Props) {
|
|||||||
setShowSpanFilters={setShowSpanFilters}
|
setShowSpanFilters={setShowSpanFilters}
|
||||||
showSpanFilterMatchesOnly={showSpanFilterMatchesOnly}
|
showSpanFilterMatchesOnly={showSpanFilterMatchesOnly}
|
||||||
setShowSpanFilterMatchesOnly={setShowSpanFilterMatchesOnly}
|
setShowSpanFilterMatchesOnly={setShowSpanFilterMatchesOnly}
|
||||||
|
showCriticalPathSpansOnly={showCriticalPathSpansOnly}
|
||||||
|
setShowCriticalPathSpansOnly={setShowCriticalPathSpansOnly}
|
||||||
setFocusedSpanIdForSearch={setFocusedSpanIdForSearch}
|
setFocusedSpanIdForSearch={setFocusedSpanIdForSearch}
|
||||||
spanFilterMatches={spanFilterMatches}
|
spanFilterMatches={spanFilterMatches}
|
||||||
datasourceType={datasourceType}
|
datasourceType={datasourceType}
|
||||||
@ -218,10 +224,12 @@ export function TraceView(props: Props) {
|
|||||||
focusedSpanId={focusedSpanId}
|
focusedSpanId={focusedSpanId}
|
||||||
focusedSpanIdForSearch={focusedSpanIdForSearch}
|
focusedSpanIdForSearch={focusedSpanIdForSearch}
|
||||||
showSpanFilterMatchesOnly={showSpanFilterMatchesOnly}
|
showSpanFilterMatchesOnly={showSpanFilterMatchesOnly}
|
||||||
|
showCriticalPathSpansOnly={showCriticalPathSpansOnly}
|
||||||
createFocusSpanLink={createFocusSpanLink}
|
createFocusSpanLink={createFocusSpanLink}
|
||||||
topOfViewRef={topOfViewRef}
|
topOfViewRef={topOfViewRef}
|
||||||
topOfViewRefType={topOfViewRefType}
|
topOfViewRefType={topOfViewRefType}
|
||||||
headerHeight={headerHeight}
|
headerHeight={headerHeight}
|
||||||
|
criticalPath={criticalPath}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (c) 2023 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 test1 from './testCases/test1';
|
||||||
|
import test2 from './testCases/test2';
|
||||||
|
import test3 from './testCases/test3';
|
||||||
|
import test4 from './testCases/test4';
|
||||||
|
import test5 from './testCases/test5';
|
||||||
|
import test6 from './testCases/test6';
|
||||||
|
import test7 from './testCases/test7';
|
||||||
|
import test8 from './testCases/test8';
|
||||||
|
import test9 from './testCases/test9';
|
||||||
|
|
||||||
|
import TraceCriticalPath from './index';
|
||||||
|
|
||||||
|
describe.each([[test1], [test2], [test3], [test4], [test5], [test6], [test7], [test8], [test9]])(
|
||||||
|
'Happy Path',
|
||||||
|
(testProps) => {
|
||||||
|
it('should find criticalPathSections correctly', () => {
|
||||||
|
const criticalPath = TraceCriticalPath(testProps.trace);
|
||||||
|
expect(criticalPath).toStrictEqual(testProps.criticalPathSections);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -0,0 +1,115 @@
|
|||||||
|
// Copyright (c) 2023 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 memoizeOne from 'memoize-one';
|
||||||
|
|
||||||
|
import { CriticalPathSection, Trace, TraceSpan } from '../types';
|
||||||
|
|
||||||
|
import findLastFinishingChildSpan from './utils/findLastFinishingChildSpan';
|
||||||
|
import getChildOfSpans from './utils/getChildOfSpans';
|
||||||
|
import sanitizeOverFlowingChildren from './utils/sanitizeOverFlowingChildren';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the critical path sections of a Jaeger trace.
|
||||||
|
* The algorithm begins with the top-level span and iterates through the last finishing children (LFCs).
|
||||||
|
* It recursively computes the critical path for each LFC span.
|
||||||
|
* Upon return from recursion, the algorithm walks backward and picks another child that
|
||||||
|
* finished just before the LFC's start.
|
||||||
|
* @param spanMap - A map associating span IDs with spans.
|
||||||
|
* @param spanId - The ID of the current span.
|
||||||
|
* @param criticalPath - An array of critical path sections.
|
||||||
|
* @param returningChildStartTime - Optional parameter representing the span's start time.
|
||||||
|
* It is provided only during the recursive return phase.
|
||||||
|
* @returns - An array of critical path sections for the trace.
|
||||||
|
* @example -
|
||||||
|
* |-------------spanA--------------|
|
||||||
|
* |--spanB--| |--spanC--|
|
||||||
|
* The LFC of spanA is spanC, as it finishes last among its child spans.
|
||||||
|
* After invoking CP recursively on LFC, for spanC there is no LFC, so the algorithm walks backward.
|
||||||
|
* At this point, it uses returningChildStartTime (startTime of spanC) to select another child that finished
|
||||||
|
* immediately before the LFC's start.
|
||||||
|
*/
|
||||||
|
const computeCriticalPath = (
|
||||||
|
spanMap: Map<string, TraceSpan>,
|
||||||
|
spanId: string,
|
||||||
|
criticalPath: CriticalPathSection[],
|
||||||
|
returningChildStartTime?: number
|
||||||
|
): CriticalPathSection[] => {
|
||||||
|
const currentSpan = spanMap.get(spanId);
|
||||||
|
|
||||||
|
if (!currentSpan) {
|
||||||
|
return criticalPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastFinishingChildSpan = findLastFinishingChildSpan(spanMap, currentSpan, returningChildStartTime);
|
||||||
|
let spanCriticalSection: CriticalPathSection;
|
||||||
|
|
||||||
|
if (lastFinishingChildSpan) {
|
||||||
|
spanCriticalSection = {
|
||||||
|
spanId: currentSpan.spanID,
|
||||||
|
section_start: lastFinishingChildSpan.startTime + lastFinishingChildSpan.duration,
|
||||||
|
section_end: returningChildStartTime || currentSpan.startTime + currentSpan.duration,
|
||||||
|
};
|
||||||
|
if (spanCriticalSection.section_start !== spanCriticalSection.section_end) {
|
||||||
|
criticalPath.push(spanCriticalSection);
|
||||||
|
}
|
||||||
|
// Now focus shifts to the lastFinishingChildSpan of cuurent span
|
||||||
|
computeCriticalPath(spanMap, lastFinishingChildSpan.spanID, criticalPath);
|
||||||
|
} else {
|
||||||
|
// If there is no last finishing child then total section upto startTime of span is on critical path
|
||||||
|
spanCriticalSection = {
|
||||||
|
spanId: currentSpan.spanID,
|
||||||
|
section_start: currentSpan.startTime,
|
||||||
|
section_end: returningChildStartTime || currentSpan.startTime + currentSpan.duration,
|
||||||
|
};
|
||||||
|
if (spanCriticalSection.section_start !== spanCriticalSection.section_end) {
|
||||||
|
criticalPath.push(spanCriticalSection);
|
||||||
|
}
|
||||||
|
// Now as there are no lfc's focus shifts to parent span from startTime of span
|
||||||
|
// return from recursion and walk backwards to one level depth to parent span
|
||||||
|
// provide span's startTime as returningChildStartTime
|
||||||
|
if (currentSpan.references.length) {
|
||||||
|
const parentSpanId: string = currentSpan.references.filter((reference) => reference.refType === 'CHILD_OF')[0]
|
||||||
|
.spanID;
|
||||||
|
computeCriticalPath(spanMap, parentSpanId, criticalPath, currentSpan.startTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return criticalPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
function criticalPathForTrace(trace: Trace) {
|
||||||
|
let criticalPath: CriticalPathSection[] = [];
|
||||||
|
// As spans are already sorted based on startTime first span is always rootSpan
|
||||||
|
const rootSpanId = trace?.spans[0].spanID;
|
||||||
|
// If there is root span then algorithm implements
|
||||||
|
if (rootSpanId) {
|
||||||
|
const spanMap = trace.spans.reduce((map, span) => {
|
||||||
|
map.set(span.spanID, span);
|
||||||
|
return map;
|
||||||
|
}, new Map<string, TraceSpan>());
|
||||||
|
try {
|
||||||
|
const refinedSpanMap = getChildOfSpans(spanMap);
|
||||||
|
const sanitizedSpanMap = sanitizeOverFlowingChildren(refinedSpanMap);
|
||||||
|
criticalPath = computeCriticalPath(sanitizedSpanMap, rootSpanId, criticalPath);
|
||||||
|
} catch (error) {
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
console.log('error while computing critical path for a trace', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return criticalPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
const memoizedTraceCriticalPath = memoizeOne(criticalPathForTrace);
|
||||||
|
|
||||||
|
export default memoizedTraceCriticalPath;
|
@ -0,0 +1,126 @@
|
|||||||
|
// Copyright (c) 2023 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.
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
┌──────────────────────────────────────┐ |
|
||||||
|
│ Span C │ |
|
||||||
|
└──┬──────────▲─────────┬──────────▲───┘ | span C
|
||||||
|
+++│ │+++++++++│ │++++ | / \
|
||||||
|
│ │ │ │ | / \
|
||||||
|
▼──────────┤ ▼──────────┤ | span D span E
|
||||||
|
│ Span D │ │ Span E │ |
|
||||||
|
└──────────┘ └──────────┘ | (parent-child tree)
|
||||||
|
+++++++++++ ++++++++++++ |
|
||||||
|
|
||||||
|
|
||||||
|
Here +++++ are critical path sections
|
||||||
|
*/
|
||||||
|
import { Trace, TraceResponse, transformTraceData } from '../../index';
|
||||||
|
|
||||||
|
const testTrace: TraceResponse = {
|
||||||
|
traceID: 'test1-trace',
|
||||||
|
spans: [
|
||||||
|
{
|
||||||
|
traceID: 'test1-trace',
|
||||||
|
spanID: 'span-E',
|
||||||
|
operationName: 'operation E',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
refType: 'CHILD_OF',
|
||||||
|
spanID: 'span-C',
|
||||||
|
traceID: 'test1-trace',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
startTime: 50,
|
||||||
|
duration: 10,
|
||||||
|
processID: 'p1',
|
||||||
|
logs: [],
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
traceID: 'test1-trace',
|
||||||
|
|
||||||
|
spanID: 'span-C',
|
||||||
|
operationName: 'operation C',
|
||||||
|
references: [],
|
||||||
|
startTime: 1,
|
||||||
|
duration: 100,
|
||||||
|
processID: 'p1',
|
||||||
|
logs: [],
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
traceID: 'test1-trace',
|
||||||
|
|
||||||
|
spanID: 'span-D',
|
||||||
|
operationName: 'operation D',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
refType: 'CHILD_OF',
|
||||||
|
spanID: 'span-C',
|
||||||
|
traceID: 'test1-trace',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
startTime: 20,
|
||||||
|
duration: 20,
|
||||||
|
processID: 'p1',
|
||||||
|
logs: [],
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
processes: {
|
||||||
|
p1: {
|
||||||
|
serviceName: 'customers-service',
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const transformedTrace: Trace = transformTraceData(testTrace)!;
|
||||||
|
|
||||||
|
const criticalPathSections = [
|
||||||
|
{
|
||||||
|
spanId: 'span-C',
|
||||||
|
section_start: 60,
|
||||||
|
section_end: 101,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spanId: 'span-E',
|
||||||
|
section_start: 50,
|
||||||
|
section_end: 60,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spanId: 'span-C',
|
||||||
|
section_start: 40,
|
||||||
|
section_end: 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spanId: 'span-D',
|
||||||
|
section_start: 20,
|
||||||
|
section_end: 40,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spanId: 'span-C',
|
||||||
|
section_start: 1,
|
||||||
|
section_end: 20,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const test1 = {
|
||||||
|
criticalPathSections,
|
||||||
|
trace: transformedTrace,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default test1;
|
@ -0,0 +1,116 @@
|
|||||||
|
// Copyright (c) 2023 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.
|
||||||
|
|
||||||
|
/* |
|
||||||
|
┌─────────────────────────────────────────────────┐ |
|
||||||
|
│ Span X │ |
|
||||||
|
└──────┬───────┬─────────────────▲──────▲─────────┘ |
|
||||||
|
+++++++│+++++++│ │ |++++++++++ | span X
|
||||||
|
▼───────┼─────────────────┤ | | / \
|
||||||
|
│ │ Span A │ | | / \
|
||||||
|
└───────┼─────────────────┘ | | span A span C
|
||||||
|
│ | |
|
||||||
|
│ | |
|
||||||
|
▼────────────────────────┤ | (parent-child tree)
|
||||||
|
│ Span C │ |
|
||||||
|
└────────────────────────┘ |
|
||||||
|
++++++++++++++++++++++++++ |
|
||||||
|
|
|
||||||
|
Here ++++++ is critical path |
|
||||||
|
*/
|
||||||
|
import { TraceResponse, transformTraceData } from '../../index';
|
||||||
|
|
||||||
|
const happyTrace: TraceResponse = {
|
||||||
|
traceID: 'trace-123',
|
||||||
|
spans: [
|
||||||
|
{
|
||||||
|
traceID: 'trace-123',
|
||||||
|
spanID: 'span-X',
|
||||||
|
operationName: 'op1',
|
||||||
|
startTime: 1,
|
||||||
|
duration: 100,
|
||||||
|
references: [],
|
||||||
|
processID: 'p1',
|
||||||
|
logs: [],
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
traceID: 'trace-123',
|
||||||
|
spanID: 'span-A',
|
||||||
|
operationName: 'op2',
|
||||||
|
startTime: 10,
|
||||||
|
duration: 40,
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
refType: 'CHILD_OF',
|
||||||
|
spanID: 'span-X',
|
||||||
|
traceID: 'trace-123',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
processID: 'p1',
|
||||||
|
logs: [],
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
traceID: 'trace-123',
|
||||||
|
spanID: 'span-C',
|
||||||
|
operationName: 'op3',
|
||||||
|
startTime: 20,
|
||||||
|
duration: 40,
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
refType: 'CHILD_OF',
|
||||||
|
spanID: 'span-X',
|
||||||
|
traceID: 'trace-123',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
processID: 'p1',
|
||||||
|
logs: [],
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
processes: {
|
||||||
|
p1: {
|
||||||
|
serviceName: 'service1',
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const transformedTrace = transformTraceData(happyTrace)!;
|
||||||
|
|
||||||
|
const criticalPathSections = [
|
||||||
|
{
|
||||||
|
spanId: 'span-X',
|
||||||
|
section_start: 60,
|
||||||
|
section_end: 101,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spanId: 'span-C',
|
||||||
|
section_start: 20,
|
||||||
|
section_end: 60,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spanId: 'span-X',
|
||||||
|
section_start: 1,
|
||||||
|
section_end: 20,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const test2 = {
|
||||||
|
criticalPathSections,
|
||||||
|
trace: transformedTrace,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default test2;
|
@ -0,0 +1,113 @@
|
|||||||
|
// Copyright (c) 2023 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.
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
|
||||||
|
┌──────────┐ |
|
||||||
|
│ Span A │ | span A
|
||||||
|
└──────────┘ | /
|
||||||
|
++++++++++++ ┌───────────────────┐ | /
|
||||||
|
│ Span B │ | span B
|
||||||
|
└───────────────────┘ |
|
||||||
|
| (parent-child tree)
|
||||||
|
|
|
||||||
|
Span B will be dropped. |
|
||||||
|
span A is on critical path(+++++) |
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { TraceResponse, transformTraceData } from '../../index';
|
||||||
|
|
||||||
|
const trace: TraceResponse = {
|
||||||
|
traceID: '006c3cf93508f205',
|
||||||
|
spans: [
|
||||||
|
{
|
||||||
|
traceID: '006c3cf93508f205',
|
||||||
|
spanID: '006c3cf93508f205',
|
||||||
|
flags: 1,
|
||||||
|
operationName: 'send',
|
||||||
|
references: [],
|
||||||
|
startTime: 1679437737490189,
|
||||||
|
duration: 36,
|
||||||
|
tags: [
|
||||||
|
{
|
||||||
|
key: 'span.kind',
|
||||||
|
type: 'string',
|
||||||
|
value: 'producer',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
logs: [],
|
||||||
|
processID: 'p1',
|
||||||
|
warnings: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
traceID: '006c3cf93508f205',
|
||||||
|
spanID: '2dc4b796e2127e32',
|
||||||
|
flags: 1,
|
||||||
|
operationName: 'async task 1',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
refType: 'CHILD_OF',
|
||||||
|
traceID: '006c3cf93508f205',
|
||||||
|
spanID: '006c3cf93508f205',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
startTime: 1679437737491529,
|
||||||
|
duration: 79182,
|
||||||
|
tags: [
|
||||||
|
{
|
||||||
|
key: 'span.kind',
|
||||||
|
type: 'string',
|
||||||
|
value: 'client',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'http.method',
|
||||||
|
type: 'string',
|
||||||
|
value: 'POST',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
logs: [],
|
||||||
|
processID: 'p2',
|
||||||
|
warnings: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
processes: {
|
||||||
|
p1: {
|
||||||
|
serviceName: 'service-one',
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
p2: {
|
||||||
|
serviceName: 'service-two',
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
warnings: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const transformedTrace = transformTraceData(trace)!;
|
||||||
|
const traceStart = 1679437737490189;
|
||||||
|
|
||||||
|
const criticalPathSections = [
|
||||||
|
{
|
||||||
|
spanId: '006c3cf93508f205',
|
||||||
|
section_start: traceStart,
|
||||||
|
section_end: traceStart + 36,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const test3 = {
|
||||||
|
criticalPathSections,
|
||||||
|
trace: transformedTrace,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default test3;
|
@ -0,0 +1,106 @@
|
|||||||
|
// Copyright (c) 2023 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.
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌──────────┐ |
|
||||||
|
│ Span A │ |
|
||||||
|
└──────────┘ |
|
||||||
|
++++++++++++ ┌────────────┐ | span A
|
||||||
|
│ Span B │ | /
|
||||||
|
└┬───────▲───┘ | /
|
||||||
|
│ │ | span B
|
||||||
|
│ │ | /
|
||||||
|
▼───────┤ | /
|
||||||
|
│Span C │ | span C
|
||||||
|
└───────┘ |
|
||||||
|
| (parent-child tree)
|
||||||
|
Both spanB and spanC will be dropped. |
|
||||||
|
span A is on critical path(+++++) |
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { TraceResponse, transformTraceData } from '../../index';
|
||||||
|
|
||||||
|
const trace: TraceResponse = {
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
spans: [
|
||||||
|
{
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
spanID: 'span-A',
|
||||||
|
operationName: 'op-A',
|
||||||
|
references: [],
|
||||||
|
startTime: 1,
|
||||||
|
duration: 30,
|
||||||
|
processID: 'p1',
|
||||||
|
logs: [],
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
spanID: 'span-B',
|
||||||
|
operationName: 'op-B',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
refType: 'CHILD_OF',
|
||||||
|
spanID: 'span-A',
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
startTime: 40,
|
||||||
|
duration: 40,
|
||||||
|
processID: 'p1',
|
||||||
|
logs: [],
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
spanID: 'span-c',
|
||||||
|
operationName: 'op-C',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
refType: 'CHILD_OF',
|
||||||
|
spanID: 'span-B',
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
startTime: 50,
|
||||||
|
duration: 10,
|
||||||
|
processID: 'p1',
|
||||||
|
logs: [],
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
processes: {
|
||||||
|
p1: {
|
||||||
|
serviceName: 'service-one',
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const transformedTrace = transformTraceData(trace)!;
|
||||||
|
|
||||||
|
const criticalPathSections = [
|
||||||
|
{
|
||||||
|
spanId: 'span-A',
|
||||||
|
section_start: 1,
|
||||||
|
section_end: 31,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const test4 = {
|
||||||
|
criticalPathSections,
|
||||||
|
trace: transformedTrace,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default test4;
|
@ -0,0 +1,105 @@
|
|||||||
|
// Copyright (c) 2023 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.
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌────────────────────┐ |
|
||||||
|
│ Span A │ | span A
|
||||||
|
└───┬────────────────┘ | /
|
||||||
|
│ | /
|
||||||
|
▼────────────┐ | span B(FOLLOW_FROM)
|
||||||
|
│ Span B │ | /
|
||||||
|
└──────────▲─┘ | /
|
||||||
|
│ │ | span C(CHILD_OF)
|
||||||
|
▼────────┐ |
|
||||||
|
│ Span C │ |
|
||||||
|
└────────┘ | (parent-child tree)
|
||||||
|
|
|
||||||
|
Here span B is ref-type is 'FOLLOWS_FROM' |
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { TraceResponse, transformTraceData } from '../../index';
|
||||||
|
|
||||||
|
const trace: TraceResponse = {
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
spans: [
|
||||||
|
{
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
spanID: 'span-A',
|
||||||
|
operationName: 'op-A',
|
||||||
|
references: [],
|
||||||
|
startTime: 1,
|
||||||
|
duration: 30,
|
||||||
|
processID: 'p1',
|
||||||
|
logs: [],
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
spanID: 'span-B',
|
||||||
|
operationName: 'op-B',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
refType: 'FOLLOWS_FROM',
|
||||||
|
spanID: 'span-A',
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
startTime: 10,
|
||||||
|
duration: 10,
|
||||||
|
processID: 'p1',
|
||||||
|
logs: [],
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
spanID: 'span-C',
|
||||||
|
operationName: 'op-C',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
refType: 'CHILD_OF',
|
||||||
|
spanID: 'span-B',
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
startTime: 12,
|
||||||
|
duration: 2,
|
||||||
|
processID: 'p1',
|
||||||
|
logs: [],
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
processes: {
|
||||||
|
p1: {
|
||||||
|
serviceName: 'service-one',
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const transformedTrace = transformTraceData(trace)!;
|
||||||
|
|
||||||
|
const criticalPathSections = [
|
||||||
|
{
|
||||||
|
spanId: 'span-A',
|
||||||
|
section_start: 1,
|
||||||
|
section_end: 31,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const test5 = {
|
||||||
|
criticalPathSections,
|
||||||
|
trace: transformedTrace,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default test5;
|
@ -0,0 +1,113 @@
|
|||||||
|
// Copyright (c) 2023 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.
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌────────────────────────────┐ |
|
||||||
|
│ Span A │ |
|
||||||
|
└────────────┬───────────────┘ | span A
|
||||||
|
│ | /
|
||||||
|
┌▼──────────────────────┐ | /
|
||||||
|
│ Span B │ | span B
|
||||||
|
└──────────▲────────────┘ | /
|
||||||
|
│ | /
|
||||||
|
┌──────────────┤ | span C
|
||||||
|
│ Span C │ |
|
||||||
|
└──────────────┘ | (parent-child tree)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { TraceResponse, transformTraceData } from '../../index';
|
||||||
|
|
||||||
|
const trace: TraceResponse = {
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
spans: [
|
||||||
|
{
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
spanID: 'span-A',
|
||||||
|
operationName: 'op-A',
|
||||||
|
references: [],
|
||||||
|
startTime: 1,
|
||||||
|
duration: 29,
|
||||||
|
processID: 'p1',
|
||||||
|
logs: [],
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
spanID: 'span-B',
|
||||||
|
operationName: 'op-B',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
refType: 'CHILD_OF',
|
||||||
|
spanID: 'span-A',
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
startTime: 15,
|
||||||
|
duration: 20,
|
||||||
|
processID: 'p1',
|
||||||
|
logs: [],
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
spanID: 'span-C',
|
||||||
|
operationName: 'op-C',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
refType: 'CHILD_OF',
|
||||||
|
spanID: 'span-B',
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
startTime: 10,
|
||||||
|
duration: 15,
|
||||||
|
processID: 'p1',
|
||||||
|
logs: [],
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
processes: {
|
||||||
|
p1: {
|
||||||
|
serviceName: 'service-one',
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const transformedTrace = transformTraceData(trace)!;
|
||||||
|
|
||||||
|
const criticalPathSections = [
|
||||||
|
{
|
||||||
|
spanId: 'span-B',
|
||||||
|
section_start: 25,
|
||||||
|
section_end: 30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spanId: 'span-C',
|
||||||
|
section_start: 15,
|
||||||
|
section_end: 25,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spanId: 'span-A',
|
||||||
|
section_start: 1,
|
||||||
|
section_end: 15,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const test6 = {
|
||||||
|
criticalPathSections,
|
||||||
|
trace: transformedTrace,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default test6;
|
@ -0,0 +1,113 @@
|
|||||||
|
// Copyright (c) 2023 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.
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────┐ |
|
||||||
|
│ Span A │ | spanA
|
||||||
|
└───────┬─────────┘ | /
|
||||||
|
│ | /
|
||||||
|
┌▼──────────────┐ | spanB
|
||||||
|
│ Span B │ | /
|
||||||
|
└─────┬─────────┘ | /
|
||||||
|
│ | spanC
|
||||||
|
┌▼─────────────┐ |
|
||||||
|
│ Span C │ | ((parent-child tree))
|
||||||
|
└──────────────┘ |
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { TraceResponse, transformTraceData } from '../../index';
|
||||||
|
|
||||||
|
const trace: TraceResponse = {
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
spans: [
|
||||||
|
{
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
spanID: 'span-A',
|
||||||
|
operationName: 'op-A',
|
||||||
|
references: [],
|
||||||
|
startTime: 1,
|
||||||
|
duration: 29,
|
||||||
|
processID: 'p1',
|
||||||
|
logs: [],
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
spanID: 'span-B',
|
||||||
|
operationName: 'op-B',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
refType: 'CHILD_OF',
|
||||||
|
spanID: 'span-A',
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
startTime: 15,
|
||||||
|
duration: 20,
|
||||||
|
processID: 'p1',
|
||||||
|
logs: [],
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
spanID: 'span-C',
|
||||||
|
operationName: 'op-C',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
refType: 'CHILD_OF',
|
||||||
|
spanID: 'span-B',
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
startTime: 20,
|
||||||
|
duration: 20,
|
||||||
|
processID: 'p1',
|
||||||
|
logs: [],
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
processes: {
|
||||||
|
p1: {
|
||||||
|
serviceName: 'service-one',
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const transformedTrace = transformTraceData(trace)!;
|
||||||
|
|
||||||
|
const criticalPathSections = [
|
||||||
|
{
|
||||||
|
spanId: 'span-C',
|
||||||
|
section_start: 20,
|
||||||
|
section_end: 30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spanId: 'span-B',
|
||||||
|
section_start: 15,
|
||||||
|
section_end: 20,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spanId: 'span-A',
|
||||||
|
section_start: 1,
|
||||||
|
section_end: 15,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const test7 = {
|
||||||
|
criticalPathSections,
|
||||||
|
trace: transformedTrace,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default test7;
|
@ -0,0 +1,82 @@
|
|||||||
|
// Copyright (c) 2023 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 { TraceResponse, transformTraceData } from '../../index';
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌─────────────────┐ |
|
||||||
|
│ Span A │ | spanA
|
||||||
|
└─────────────────┘ | /
|
||||||
|
| /
|
||||||
|
┌──────────────────────┐ | spanB (CHILD_OF)
|
||||||
|
│ Span B │ |
|
||||||
|
└──────────────────────┘ | ((parent-child tree))
|
||||||
|
*/
|
||||||
|
|
||||||
|
const trace: TraceResponse = {
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
spans: [
|
||||||
|
{
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
spanID: 'span-A',
|
||||||
|
operationName: 'op-A',
|
||||||
|
references: [],
|
||||||
|
startTime: 10,
|
||||||
|
duration: 20,
|
||||||
|
processID: 'p1',
|
||||||
|
logs: [],
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
spanID: 'span-B',
|
||||||
|
operationName: 'op-B',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
refType: 'CHILD_OF',
|
||||||
|
spanID: 'span-A',
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
startTime: 5,
|
||||||
|
duration: 30,
|
||||||
|
processID: 'p1',
|
||||||
|
logs: [],
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
processes: {
|
||||||
|
p1: {
|
||||||
|
serviceName: 'service-one',
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const transformedTrace = transformTraceData(trace)!;
|
||||||
|
|
||||||
|
const criticalPathSections = [
|
||||||
|
{
|
||||||
|
spanId: 'span-B',
|
||||||
|
section_start: 10,
|
||||||
|
section_end: 30,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const test8 = {
|
||||||
|
criticalPathSections,
|
||||||
|
trace: transformedTrace,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default test8;
|
@ -0,0 +1,84 @@
|
|||||||
|
// Copyright (c) 2023 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 { TraceResponse, transformTraceData } from '../../index';
|
||||||
|
|
||||||
|
/*
|
||||||
|
┌──────────┐ |
|
||||||
|
│ Span A │ | span A
|
||||||
|
└──────────┘ | /
|
||||||
|
++++++++++++ | /
|
||||||
|
┌────────────┐ | span B
|
||||||
|
│ Span B │ |
|
||||||
|
└────────────┘ | (parent-child tree)
|
||||||
|
spanB will be dropped. |
|
||||||
|
span A is on critical path(+++++) |
|
||||||
|
*/
|
||||||
|
|
||||||
|
const trace: TraceResponse = {
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
spans: [
|
||||||
|
{
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
spanID: 'span-A',
|
||||||
|
operationName: 'op-A',
|
||||||
|
references: [],
|
||||||
|
startTime: 10,
|
||||||
|
duration: 20,
|
||||||
|
processID: 'p1',
|
||||||
|
logs: [],
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
spanID: 'span-B',
|
||||||
|
operationName: 'op-B',
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
refType: 'CHILD_OF',
|
||||||
|
spanID: 'span-A',
|
||||||
|
traceID: 'trace-abc',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
startTime: 1,
|
||||||
|
duration: 4,
|
||||||
|
processID: 'p1',
|
||||||
|
logs: [],
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
processes: {
|
||||||
|
p1: {
|
||||||
|
serviceName: 'service-one',
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const transformedTrace = transformTraceData(trace)!;
|
||||||
|
|
||||||
|
const criticalPathSections = [
|
||||||
|
{
|
||||||
|
spanId: 'span-A',
|
||||||
|
section_start: 10,
|
||||||
|
section_end: 30,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const test9 = {
|
||||||
|
criticalPathSections,
|
||||||
|
trace: transformedTrace,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default test9;
|
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (c) 2023 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 test1 from '../testCases/test1';
|
||||||
|
import test2 from '../testCases/test2';
|
||||||
|
|
||||||
|
import findLastFinishingChildSpanId from './findLastFinishingChildSpan';
|
||||||
|
import getChildOfSpans from './getChildOfSpans';
|
||||||
|
import sanitizeOverFlowingChildren from './sanitizeOverFlowingChildren';
|
||||||
|
|
||||||
|
describe('findLastFinishingChildSpanId', () => {
|
||||||
|
it('Should find lfc of a span correctly', () => {
|
||||||
|
const refinedSpanData = getChildOfSpans(new Map(test1.trace.spans.map((span) => [span.spanID, span])));
|
||||||
|
const sanitizedSpanMap = sanitizeOverFlowingChildren(refinedSpanData);
|
||||||
|
|
||||||
|
const currentSpan = sanitizedSpanMap.get('span-C')!;
|
||||||
|
let lastFinishingChildSpan = findLastFinishingChildSpanId(sanitizedSpanMap, currentSpan);
|
||||||
|
expect(lastFinishingChildSpan).toStrictEqual(sanitizedSpanMap.get('span-E'));
|
||||||
|
|
||||||
|
// Second Case to check if it works with spawn time or not
|
||||||
|
lastFinishingChildSpan = findLastFinishingChildSpanId(sanitizedSpanMap, currentSpan, 50);
|
||||||
|
expect(lastFinishingChildSpan).toStrictEqual(sanitizedSpanMap.get('span-D'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should find lfc of a span correctly', () => {
|
||||||
|
const refinedSpanData = getChildOfSpans(new Map(test2.trace.spans.map((span) => [span.spanID, span])));
|
||||||
|
const sanitizedSpanMap = sanitizeOverFlowingChildren(refinedSpanData);
|
||||||
|
|
||||||
|
const currentSpan = sanitizedSpanMap.get('span-X')!;
|
||||||
|
let lastFinishingChildSpanId = findLastFinishingChildSpanId(sanitizedSpanMap, currentSpan);
|
||||||
|
expect(lastFinishingChildSpanId).toStrictEqual(sanitizedSpanMap.get('span-C'));
|
||||||
|
|
||||||
|
// Second Case to check if it works with spawn time or not
|
||||||
|
lastFinishingChildSpanId = findLastFinishingChildSpanId(sanitizedSpanMap, currentSpan, 20);
|
||||||
|
expect(lastFinishingChildSpanId).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,42 @@
|
|||||||
|
// Copyright (c) 2023 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 { TraceSpan } from '../../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns - Returns the span that finished last among the remaining child spans.
|
||||||
|
* If a `returningChildStartTime` is provided as a parameter, it returns the child span that finishes
|
||||||
|
* just before the specified `returningChildStartTime`.
|
||||||
|
*/
|
||||||
|
const findLastFinishingChildSpan = (
|
||||||
|
spanMap: Map<string, TraceSpan>,
|
||||||
|
currentSpan: TraceSpan,
|
||||||
|
returningChildStartTime?: number
|
||||||
|
): TraceSpan | undefined => {
|
||||||
|
let lastFinishingChildSpanId: string | undefined;
|
||||||
|
if (returningChildStartTime) {
|
||||||
|
lastFinishingChildSpanId = currentSpan?.childSpanIds.find(
|
||||||
|
(each) =>
|
||||||
|
// Look up the span using the map
|
||||||
|
spanMap.has(each) && spanMap.get(each)!.startTime + spanMap.get(each)!.duration < returningChildStartTime
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// If `returningChildStartTime` is not provided, select the first child span.
|
||||||
|
// As they are sorted based on endTime
|
||||||
|
lastFinishingChildSpanId = currentSpan.childSpanIds[0];
|
||||||
|
}
|
||||||
|
return lastFinishingChildSpanId ? spanMap.get(lastFinishingChildSpanId) : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default findLastFinishingChildSpan;
|
@ -0,0 +1,46 @@
|
|||||||
|
// Copyright (c) 2023 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 test2 from '../testCases/test2';
|
||||||
|
import test5 from '../testCases/test5';
|
||||||
|
|
||||||
|
import getChildOfSpans from './getChildOfSpans';
|
||||||
|
|
||||||
|
describe('getChildOfSpans', () => {
|
||||||
|
it('Should not remove CHILD_OF child spans if there are any', () => {
|
||||||
|
const spanMap = test2.trace.spans.reduce((map, span) => {
|
||||||
|
map.set(span.spanID, span);
|
||||||
|
return map;
|
||||||
|
}, new Map());
|
||||||
|
const refinedSpanMap = getChildOfSpans(spanMap);
|
||||||
|
const expectedRefinedSpanMap = spanMap;
|
||||||
|
|
||||||
|
expect(refinedSpanMap.size).toBe(3);
|
||||||
|
expect(refinedSpanMap).toStrictEqual(expectedRefinedSpanMap);
|
||||||
|
});
|
||||||
|
it('Should remove FOLLOWS_FROM child spans if there are any', () => {
|
||||||
|
const spanMap = test5.trace.spans.reduce((map, span) => {
|
||||||
|
map.set(span.spanID, span);
|
||||||
|
return map;
|
||||||
|
}, new Map());
|
||||||
|
const refinedSpanMap = getChildOfSpans(spanMap);
|
||||||
|
const expectedRefinedSpanMap = new Map().set(test5.trace.spans[0].spanID, {
|
||||||
|
...test5.trace.spans[0],
|
||||||
|
childSpanIds: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(refinedSpanMap.size).toBe(1);
|
||||||
|
expect(refinedSpanMap).toStrictEqual(expectedRefinedSpanMap);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,54 @@
|
|||||||
|
// Copyright (c) 2023 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 { TraceSpan } from '../../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes child spans whose refType is FOLLOWS_FROM and their descendants.
|
||||||
|
* @param spanMap - The map containing spans.
|
||||||
|
* @returns - A map with spans whose refType is CHILD_OF.
|
||||||
|
*/
|
||||||
|
const getChildOfSpans = (spanMap: Map<string, TraceSpan>): Map<string, TraceSpan> => {
|
||||||
|
const followFromSpanIds: string[] = [];
|
||||||
|
const followFromSpansDescendantIds: string[] = [];
|
||||||
|
|
||||||
|
// First find all FOLLOWS_FROM refType spans
|
||||||
|
spanMap.forEach((each) => {
|
||||||
|
if (each.references[0]?.refType === 'FOLLOWS_FROM') {
|
||||||
|
followFromSpanIds.push(each.spanID);
|
||||||
|
// Remove the spanId from childSpanIds array of its parentSpan
|
||||||
|
const parentSpan = spanMap.get(each.references[0].spanID)!;
|
||||||
|
parentSpan.childSpanIds = parentSpan.childSpanIds.filter((a) => a !== each.spanID);
|
||||||
|
spanMap.set(parentSpan.spanID, { ...parentSpan });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Recursively find all Descendants of FOLLOWS_FROM spans
|
||||||
|
const findDescendantSpans = (spanIds: string[]) => {
|
||||||
|
spanIds.forEach((spanId) => {
|
||||||
|
const span = spanMap.get(spanId)!;
|
||||||
|
if (span.hasChildren) {
|
||||||
|
followFromSpansDescendantIds.push(...span.childSpanIds);
|
||||||
|
findDescendantSpans(span.childSpanIds);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
findDescendantSpans(followFromSpanIds);
|
||||||
|
// Delete all FOLLOWS_FROM spans and its descendants
|
||||||
|
const idsToBeDeleted = [...followFromSpanIds, ...followFromSpansDescendantIds];
|
||||||
|
idsToBeDeleted.forEach((id) => spanMap.delete(id));
|
||||||
|
|
||||||
|
return spanMap;
|
||||||
|
};
|
||||||
|
export default getChildOfSpans;
|
@ -0,0 +1,53 @@
|
|||||||
|
// Copyright (c) 2023 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 { TraceSpan } from '../../types';
|
||||||
|
import test3 from '../testCases/test3';
|
||||||
|
import test4 from '../testCases/test4';
|
||||||
|
import test6 from '../testCases/test6';
|
||||||
|
import test7 from '../testCases/test7';
|
||||||
|
import test8 from '../testCases/test8';
|
||||||
|
import test9 from '../testCases/test9';
|
||||||
|
|
||||||
|
import getChildOfSpans from './getChildOfSpans';
|
||||||
|
import sanitizeOverFlowingChildren from './sanitizeOverFlowingChildren';
|
||||||
|
|
||||||
|
// Function to make expected data for test6 and test7
|
||||||
|
function getExpectedSanitizedData(spans: TraceSpan[], test: 'test6' | 'test7' | 'test8') {
|
||||||
|
const testSanitizedData = {
|
||||||
|
test6: [spans[0], { ...spans[1], duration: 15 }, { ...spans[2], duration: 10, startTime: 15 }],
|
||||||
|
test7: [spans[0], { ...spans[1], duration: 15 }, { ...spans[2], duration: 10 }],
|
||||||
|
test8: [spans[0], { ...spans[1], startTime: 10, duration: 20 }],
|
||||||
|
};
|
||||||
|
const spanMap = testSanitizedData[test].reduce((map, span) => {
|
||||||
|
map.set(span.spanID, span);
|
||||||
|
return map;
|
||||||
|
}, new Map());
|
||||||
|
return spanMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe.each([
|
||||||
|
[test3, new Map().set(test3.trace.spans[0].spanID, { ...test3.trace.spans[0], childSpanIds: [] })],
|
||||||
|
[test4, new Map().set(test4.trace.spans[0].spanID, { ...test4.trace.spans[0], childSpanIds: [] })],
|
||||||
|
[test6, getExpectedSanitizedData(test6.trace.spans, 'test6')],
|
||||||
|
[test7, getExpectedSanitizedData(test7.trace.spans, 'test7')],
|
||||||
|
[test8, getExpectedSanitizedData(test8.trace.spans, 'test8')],
|
||||||
|
[test9, new Map().set(test9.trace.spans[0].spanID, { ...test9.trace.spans[0], childSpanIds: [] })],
|
||||||
|
])('sanitizeOverFlowingChildren', (testProps, expectedSanitizedData) => {
|
||||||
|
it('Should sanitize the data(overflowing spans) correctly', () => {
|
||||||
|
const refinedSpanData = getChildOfSpans(new Map(testProps.trace.spans.map((span) => [span.spanID, span])));
|
||||||
|
const sanitizedSpanMap = sanitizeOverFlowingChildren(refinedSpanData);
|
||||||
|
expect(sanitizedSpanMap).toStrictEqual(expectedSanitizedData);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,115 @@
|
|||||||
|
// Copyright (c) 2023 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 { TraceSpan } from '../../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function resolves overflowing child spans for each span.
|
||||||
|
* An overflowing child span is one whose time range falls outside its parent span's time range.
|
||||||
|
* The function adjusts the start time and duration of overflowing child spans
|
||||||
|
* to ensure they fit within the time range of their parent span.
|
||||||
|
* @param spanMap - A Map where span IDs are keys and the corresponding spans are values.
|
||||||
|
* @returns - A sanitized span Map.
|
||||||
|
*/
|
||||||
|
const sanitizeOverFlowingChildren = (spanMap: Map<string, TraceSpan>): Map<string, TraceSpan> => {
|
||||||
|
let spanIds: string[] = [...spanMap.keys()];
|
||||||
|
|
||||||
|
spanIds.forEach((spanId) => {
|
||||||
|
const span = spanMap.get(spanId)!;
|
||||||
|
if (!(span && span.references.length && span.depth)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// parentSpan will be undefined when its parentSpan is dropped previously
|
||||||
|
const parentSpan = spanMap.get(span.references[0].spanID);
|
||||||
|
|
||||||
|
if (!parentSpan) {
|
||||||
|
// Drop the child spans of dropped parent span
|
||||||
|
spanMap.delete(span.spanID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const childEndTime = span.startTime + span.duration;
|
||||||
|
const parentEndTime = parentSpan.startTime + parentSpan.duration;
|
||||||
|
if (span.startTime >= parentSpan.startTime) {
|
||||||
|
if (span.startTime >= parentEndTime) {
|
||||||
|
// child outside of parent range => drop the child span
|
||||||
|
// |----parent----|
|
||||||
|
// |----child--|
|
||||||
|
// Remove the childSpan from spanMap
|
||||||
|
spanMap.delete(span.spanID);
|
||||||
|
|
||||||
|
// Remove the childSpanId from its parent span
|
||||||
|
parentSpan.childSpanIds = parentSpan.childSpanIds.filter((id) => id !== span.spanID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (childEndTime > parentEndTime) {
|
||||||
|
// child end after parent, truncate is needed
|
||||||
|
// |----parent----|
|
||||||
|
// |----child--|
|
||||||
|
spanMap.set(span.spanID, {
|
||||||
|
...span,
|
||||||
|
duration: parentEndTime - span.startTime,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// everything looks good
|
||||||
|
// |----parent----|
|
||||||
|
// |----child--|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (childEndTime <= parentSpan.startTime) {
|
||||||
|
// child outside of parent range => drop the child span
|
||||||
|
// |----parent----|
|
||||||
|
// |----child--|
|
||||||
|
|
||||||
|
// Remove the childSpan from spanMap
|
||||||
|
spanMap.delete(span.spanID);
|
||||||
|
|
||||||
|
// Remove the childSpanId from its parent span
|
||||||
|
parentSpan.childSpanIds = parentSpan.childSpanIds.filter((id) => id !== span.spanID);
|
||||||
|
} else if (childEndTime <= parentEndTime) {
|
||||||
|
// child start before parent, truncate is needed
|
||||||
|
// |----parent----|
|
||||||
|
// |----child--|
|
||||||
|
spanMap.set(span.spanID, {
|
||||||
|
...span,
|
||||||
|
startTime: parentSpan.startTime,
|
||||||
|
duration: childEndTime - parentSpan.startTime,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// child start before parent and end after parent, truncate is needed
|
||||||
|
// |----parent----|
|
||||||
|
// |---------child---------|
|
||||||
|
spanMap.set(span.spanID, {
|
||||||
|
...span,
|
||||||
|
startTime: parentSpan.startTime,
|
||||||
|
duration: parentEndTime - parentSpan.startTime,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Updated spanIds to ensure to not include dropped spans
|
||||||
|
spanIds = [...spanMap.keys()];
|
||||||
|
// Update Child Span References with updated parent span
|
||||||
|
spanIds.forEach((spanId) => {
|
||||||
|
const span = spanMap.get(spanId)!;
|
||||||
|
if (span.references.length) {
|
||||||
|
const parentSpan = spanMap.get(span.references[0].spanID);
|
||||||
|
span.references[0].span = parentSpan;
|
||||||
|
spanMap.set(spanId, { ...span });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return spanMap;
|
||||||
|
};
|
||||||
|
export default sanitizeOverFlowingChildren;
|
@ -31,10 +31,12 @@ describe('<TracePageSearchBar>', () => {
|
|||||||
setFocusedSpanIdForSearch: jest.fn(),
|
setFocusedSpanIdForSearch: jest.fn(),
|
||||||
focusedSpanIndexForSearch: -1,
|
focusedSpanIndexForSearch: -1,
|
||||||
setFocusedSpanIndexForSearch: jest.fn(),
|
setFocusedSpanIndexForSearch: jest.fn(),
|
||||||
|
setShowCriticalPathSpansOnly: jest.fn(),
|
||||||
datasourceType: '',
|
datasourceType: '',
|
||||||
clear: jest.fn(),
|
clear: jest.fn(),
|
||||||
totalSpans: 100,
|
totalSpans: 100,
|
||||||
showSpanFilters: true,
|
showSpanFilters: true,
|
||||||
|
showCriticalPathSpansOnly: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
return <TracePageSearchBar {...searchBarProps} />;
|
return <TracePageSearchBar {...searchBarProps} />;
|
||||||
|
@ -31,6 +31,8 @@ export type TracePageSearchBarProps = {
|
|||||||
spanFilterMatches: Set<string> | undefined;
|
spanFilterMatches: Set<string> | undefined;
|
||||||
showSpanFilterMatchesOnly: boolean;
|
showSpanFilterMatchesOnly: boolean;
|
||||||
setShowSpanFilterMatchesOnly: (showMatchesOnly: boolean) => void;
|
setShowSpanFilterMatchesOnly: (showMatchesOnly: boolean) => void;
|
||||||
|
showCriticalPathSpansOnly: boolean;
|
||||||
|
setShowCriticalPathSpansOnly: (showCriticalPath: boolean) => void;
|
||||||
focusedSpanIndexForSearch: number;
|
focusedSpanIndexForSearch: number;
|
||||||
setFocusedSpanIndexForSearch: Dispatch<SetStateAction<number>>;
|
setFocusedSpanIndexForSearch: Dispatch<SetStateAction<number>>;
|
||||||
setFocusedSpanIdForSearch: Dispatch<SetStateAction<string>>;
|
setFocusedSpanIdForSearch: Dispatch<SetStateAction<string>>;
|
||||||
@ -46,6 +48,8 @@ export default memo(function TracePageSearchBar(props: TracePageSearchBarProps)
|
|||||||
spanFilterMatches,
|
spanFilterMatches,
|
||||||
showSpanFilterMatchesOnly,
|
showSpanFilterMatchesOnly,
|
||||||
setShowSpanFilterMatchesOnly,
|
setShowSpanFilterMatchesOnly,
|
||||||
|
showCriticalPathSpansOnly,
|
||||||
|
setShowCriticalPathSpansOnly,
|
||||||
focusedSpanIndexForSearch,
|
focusedSpanIndexForSearch,
|
||||||
setFocusedSpanIndexForSearch,
|
setFocusedSpanIndexForSearch,
|
||||||
setFocusedSpanIdForSearch,
|
setFocusedSpanIdForSearch,
|
||||||
@ -101,6 +105,21 @@ export default memo(function TracePageSearchBar(props: TracePageSearchBarProps)
|
|||||||
Show matches only
|
Show matches only
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
<div className={styles.matchesOnly}>
|
||||||
|
<Switch
|
||||||
|
value={showCriticalPathSpansOnly}
|
||||||
|
onChange={(value) => setShowCriticalPathSpansOnly(value.currentTarget.checked ?? false)}
|
||||||
|
label="Show critical path only switch"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={() => setShowCriticalPathSpansOnly(!showCriticalPathSpansOnly)}
|
||||||
|
className={styles.clearMatchesButton}
|
||||||
|
variant="secondary"
|
||||||
|
fill="text"
|
||||||
|
>
|
||||||
|
Show critical path only
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.nextPrevResult}>
|
<div className={styles.nextPrevResult}>
|
||||||
<NextPrevResult
|
<NextPrevResult
|
||||||
|
@ -52,12 +52,15 @@ describe('SpanFilters', () => {
|
|||||||
const SpanFiltersWithProps = ({ showFilters = true, matches }: { showFilters?: boolean; matches?: Set<string> }) => {
|
const SpanFiltersWithProps = ({ showFilters = true, matches }: { showFilters?: boolean; matches?: Set<string> }) => {
|
||||||
const [search, setSearch] = useState(defaultFilters);
|
const [search, setSearch] = useState(defaultFilters);
|
||||||
const [showSpanFilterMatchesOnly, setShowSpanFilterMatchesOnly] = useState(false);
|
const [showSpanFilterMatchesOnly, setShowSpanFilterMatchesOnly] = useState(false);
|
||||||
|
const [showCriticalPathSpansOnly, setShowCriticalPathSpansOnly] = useState(false);
|
||||||
const props = {
|
const props = {
|
||||||
trace: trace,
|
trace: trace,
|
||||||
showSpanFilters: showFilters,
|
showSpanFilters: showFilters,
|
||||||
setShowSpanFilters: jest.fn(),
|
setShowSpanFilters: jest.fn(),
|
||||||
showSpanFilterMatchesOnly,
|
showSpanFilterMatchesOnly,
|
||||||
setShowSpanFilterMatchesOnly,
|
setShowSpanFilterMatchesOnly,
|
||||||
|
showCriticalPathSpansOnly,
|
||||||
|
setShowCriticalPathSpansOnly,
|
||||||
search,
|
search,
|
||||||
setSearch,
|
setSearch,
|
||||||
spanFilterMatches: matches,
|
spanFilterMatches: matches,
|
||||||
|
@ -37,6 +37,8 @@ export type SpanFilterProps = {
|
|||||||
showSpanFilterMatchesOnly: boolean;
|
showSpanFilterMatchesOnly: boolean;
|
||||||
setShowSpanFilterMatchesOnly: (showMatchesOnly: boolean) => void;
|
setShowSpanFilterMatchesOnly: (showMatchesOnly: boolean) => void;
|
||||||
setFocusedSpanIdForSearch: React.Dispatch<React.SetStateAction<string>>;
|
setFocusedSpanIdForSearch: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
showCriticalPathSpansOnly: boolean;
|
||||||
|
setShowCriticalPathSpansOnly: (showCriticalPathSpansOnly: boolean) => void;
|
||||||
spanFilterMatches: Set<string> | undefined;
|
spanFilterMatches: Set<string> | undefined;
|
||||||
datasourceType: string;
|
datasourceType: string;
|
||||||
};
|
};
|
||||||
@ -50,6 +52,8 @@ export const SpanFilters = memo((props: SpanFilterProps) => {
|
|||||||
setShowSpanFilters,
|
setShowSpanFilters,
|
||||||
showSpanFilterMatchesOnly,
|
showSpanFilterMatchesOnly,
|
||||||
setShowSpanFilterMatchesOnly,
|
setShowSpanFilterMatchesOnly,
|
||||||
|
showCriticalPathSpansOnly,
|
||||||
|
setShowCriticalPathSpansOnly,
|
||||||
setFocusedSpanIdForSearch,
|
setFocusedSpanIdForSearch,
|
||||||
spanFilterMatches,
|
spanFilterMatches,
|
||||||
datasourceType,
|
datasourceType,
|
||||||
@ -456,6 +460,8 @@ export const SpanFilters = memo((props: SpanFilterProps) => {
|
|||||||
spanFilterMatches={spanFilterMatches}
|
spanFilterMatches={spanFilterMatches}
|
||||||
showSpanFilterMatchesOnly={showSpanFilterMatchesOnly}
|
showSpanFilterMatchesOnly={showSpanFilterMatchesOnly}
|
||||||
setShowSpanFilterMatchesOnly={setShowSpanFilterMatchesOnly}
|
setShowSpanFilterMatchesOnly={setShowSpanFilterMatchesOnly}
|
||||||
|
showCriticalPathSpansOnly={showCriticalPathSpansOnly}
|
||||||
|
setShowCriticalPathSpansOnly={setShowCriticalPathSpansOnly}
|
||||||
setFocusedSpanIdForSearch={setFocusedSpanIdForSearch}
|
setFocusedSpanIdForSearch={setFocusedSpanIdForSearch}
|
||||||
focusedSpanIndexForSearch={focusedSpanIndexForSearch}
|
focusedSpanIndexForSearch={focusedSpanIndexForSearch}
|
||||||
setFocusedSpanIndexForSearch={setFocusedSpanIndexForSearch}
|
setFocusedSpanIndexForSearch={setFocusedSpanIndexForSearch}
|
||||||
|
@ -31,6 +31,8 @@ const setup = () => {
|
|||||||
setShowSpanFilters: jest.fn(),
|
setShowSpanFilters: jest.fn(),
|
||||||
showSpanFilterMatchesOnly: false,
|
showSpanFilterMatchesOnly: false,
|
||||||
setShowSpanFilterMatchesOnly: jest.fn(),
|
setShowSpanFilterMatchesOnly: jest.fn(),
|
||||||
|
showCriticalPathSpansOnly: false,
|
||||||
|
setShowCriticalPathSpansOnly: jest.fn(),
|
||||||
spanFilterMatches: undefined,
|
spanFilterMatches: undefined,
|
||||||
setFocusedSpanIdForSearch: jest.fn(),
|
setFocusedSpanIdForSearch: jest.fn(),
|
||||||
datasourceType: 'tempo',
|
datasourceType: 'tempo',
|
||||||
@ -86,6 +88,7 @@ export const trace = {
|
|||||||
hasChildren: false,
|
hasChildren: false,
|
||||||
childSpanCount: 0,
|
childSpanCount: 0,
|
||||||
warnings: [],
|
warnings: [],
|
||||||
|
childSpanIds: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
traceID: '164afda25df92413',
|
traceID: '164afda25df92413',
|
||||||
@ -125,6 +128,7 @@ export const trace = {
|
|||||||
hasChildren: false,
|
hasChildren: false,
|
||||||
childSpanCount: 0,
|
childSpanCount: 0,
|
||||||
warnings: [],
|
warnings: [],
|
||||||
|
childSpanIds: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
traceID: '164afda25df92413',
|
traceID: '164afda25df92413',
|
||||||
@ -164,6 +168,7 @@ export const trace = {
|
|||||||
hasChildren: false,
|
hasChildren: false,
|
||||||
childSpanCount: 0,
|
childSpanCount: 0,
|
||||||
warnings: [],
|
warnings: [],
|
||||||
|
childSpanIds: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
traceID: '8bb35a31-eb64-512d-aaed-ddd61887bb2b',
|
traceID: '8bb35a31-eb64-512d-aaed-ddd61887bb2b',
|
||||||
|
@ -42,6 +42,8 @@ export type TracePageHeaderProps = {
|
|||||||
setShowSpanFilters: (isOpen: boolean) => void;
|
setShowSpanFilters: (isOpen: boolean) => void;
|
||||||
showSpanFilterMatchesOnly: boolean;
|
showSpanFilterMatchesOnly: boolean;
|
||||||
setShowSpanFilterMatchesOnly: (showMatchesOnly: boolean) => void;
|
setShowSpanFilterMatchesOnly: (showMatchesOnly: boolean) => void;
|
||||||
|
showCriticalPathSpansOnly: boolean;
|
||||||
|
setShowCriticalPathSpansOnly: (showCriticalPathSpansOnly: boolean) => void;
|
||||||
setFocusedSpanIdForSearch: React.Dispatch<React.SetStateAction<string>>;
|
setFocusedSpanIdForSearch: React.Dispatch<React.SetStateAction<string>>;
|
||||||
spanFilterMatches: Set<string> | undefined;
|
spanFilterMatches: Set<string> | undefined;
|
||||||
datasourceType: string;
|
datasourceType: string;
|
||||||
@ -60,6 +62,8 @@ export const TracePageHeader = memo((props: TracePageHeaderProps) => {
|
|||||||
setShowSpanFilters,
|
setShowSpanFilters,
|
||||||
showSpanFilterMatchesOnly,
|
showSpanFilterMatchesOnly,
|
||||||
setShowSpanFilterMatchesOnly,
|
setShowSpanFilterMatchesOnly,
|
||||||
|
showCriticalPathSpansOnly,
|
||||||
|
setShowCriticalPathSpansOnly,
|
||||||
setFocusedSpanIdForSearch,
|
setFocusedSpanIdForSearch,
|
||||||
spanFilterMatches,
|
spanFilterMatches,
|
||||||
datasourceType,
|
datasourceType,
|
||||||
@ -152,6 +156,8 @@ export const TracePageHeader = memo((props: TracePageHeaderProps) => {
|
|||||||
setShowSpanFilters={setShowSpanFilters}
|
setShowSpanFilters={setShowSpanFilters}
|
||||||
showSpanFilterMatchesOnly={showSpanFilterMatchesOnly}
|
showSpanFilterMatchesOnly={showSpanFilterMatchesOnly}
|
||||||
setShowSpanFilterMatchesOnly={setShowSpanFilterMatchesOnly}
|
setShowSpanFilterMatchesOnly={setShowSpanFilterMatchesOnly}
|
||||||
|
showCriticalPathSpansOnly={showCriticalPathSpansOnly}
|
||||||
|
setShowCriticalPathSpansOnly={setShowCriticalPathSpansOnly}
|
||||||
search={search}
|
search={search}
|
||||||
setSearch={setSearch}
|
setSearch={setSearch}
|
||||||
spanFilterMatches={spanFilterMatches}
|
spanFilterMatches={spanFilterMatches}
|
||||||
|
@ -19,76 +19,85 @@ import React, { useState } from 'react';
|
|||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { useStyles2 } from '@grafana/ui';
|
import { Tooltip, useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
import { autoColor } from '../Theme';
|
import { autoColor } from '../Theme';
|
||||||
import { Popover } from '../common/Popover';
|
import { Popover } from '../common/Popover';
|
||||||
import { TraceSpan, TNil } from '../types';
|
import { TraceSpan, TNil, CriticalPathSection } from '../types';
|
||||||
|
|
||||||
import AccordianLogs from './SpanDetail/AccordianLogs';
|
import AccordianLogs from './SpanDetail/AccordianLogs';
|
||||||
import { ViewedBoundsFunctionType } from './utils';
|
import { ViewedBoundsFunctionType } from './utils';
|
||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme2) => {
|
const getStyles = (theme: GrafanaTheme2) => {
|
||||||
return {
|
return {
|
||||||
wrapper: css`
|
wrapper: css({
|
||||||
label: wrapper;
|
label: 'wrapper',
|
||||||
bottom: 0;
|
bottom: 0,
|
||||||
left: 0;
|
left: 0,
|
||||||
position: absolute;
|
position: 'absolute',
|
||||||
right: 0;
|
right: 0,
|
||||||
top: 0;
|
top: 0,
|
||||||
overflow: hidden;
|
overflow: 'hidden',
|
||||||
z-index: 0;
|
zIndex: 0,
|
||||||
`,
|
}),
|
||||||
bar: css`
|
bar: css({
|
||||||
label: bar;
|
label: 'bar',
|
||||||
border-radius: 3px;
|
borderRadius: theme.shape.radius.default,
|
||||||
min-width: 2px;
|
minWidth: '2px',
|
||||||
position: absolute;
|
position: 'absolute',
|
||||||
height: 36%;
|
height: '36%',
|
||||||
top: 32%;
|
top: '32%',
|
||||||
`,
|
}),
|
||||||
rpc: css`
|
rpc: css({
|
||||||
label: rpc;
|
label: 'rpc',
|
||||||
position: absolute;
|
position: 'absolute',
|
||||||
top: 35%;
|
top: '35%',
|
||||||
bottom: 35%;
|
bottom: '35%',
|
||||||
z-index: 1;
|
zIndex: 1,
|
||||||
`,
|
}),
|
||||||
label: css`
|
label: css({
|
||||||
label: label;
|
label: 'label',
|
||||||
color: #aaa;
|
color: '#aaa',
|
||||||
font-size: 12px;
|
fontSize: '12px',
|
||||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
fontFamily: "'Helvetica Neue', Helvetica, Arial, sans - serif",
|
||||||
line-height: 1em;
|
lineHeight: '1em',
|
||||||
white-space: nowrap;
|
whiteSpace: 'nowrap',
|
||||||
padding: 0 0.5em;
|
padding: '0 0.5em',
|
||||||
position: absolute;
|
position: 'absolute',
|
||||||
`,
|
}),
|
||||||
logMarker: css`
|
logMarker: css({
|
||||||
label: logMarker;
|
label: 'logMarker',
|
||||||
background-color: ${autoColor(theme, '#2c3235')};
|
backgroundColor: autoColor(theme, '#2c3235'),
|
||||||
cursor: pointer;
|
cursor: 'pointer',
|
||||||
height: 60%;
|
height: '60%',
|
||||||
min-width: 1px;
|
minWidth: '1px',
|
||||||
position: absolute;
|
position: 'absolute',
|
||||||
top: 20%;
|
top: '20%',
|
||||||
&:hover {
|
'&:hover': {
|
||||||
background-color: ${autoColor(theme, '#464c54')};
|
backgroundColor: autoColor(theme, '#464c54'),
|
||||||
}
|
},
|
||||||
&::before,
|
'&::before, &::after': {
|
||||||
&::after {
|
content: "''",
|
||||||
content: '';
|
position: 'absolute',
|
||||||
position: absolute;
|
top: 0,
|
||||||
top: 0;
|
bottom: 0,
|
||||||
bottom: 0;
|
right: 0,
|
||||||
right: 0;
|
border: '1px solid transparent',
|
||||||
border: 1px solid transparent;
|
},
|
||||||
}
|
'&::after': {
|
||||||
&::after {
|
left: 0,
|
||||||
left: 0;
|
},
|
||||||
}
|
}),
|
||||||
`,
|
criticalPath: css({
|
||||||
|
position: 'absolute',
|
||||||
|
top: '45%',
|
||||||
|
height: '11%',
|
||||||
|
zIndex: 2,
|
||||||
|
overflow: 'hidden',
|
||||||
|
background: autoColor(theme, '#f1f1f1'),
|
||||||
|
borderLeft: `1px solid ${autoColor(theme, '#2c3235')}`,
|
||||||
|
borderRight: `1px solid ${autoColor(theme, '#2c3235')}`,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -111,13 +120,19 @@ export type Props = {
|
|||||||
labelClassName?: string;
|
labelClassName?: string;
|
||||||
longLabel: string;
|
longLabel: string;
|
||||||
shortLabel: string;
|
shortLabel: string;
|
||||||
|
criticalPath: CriticalPathSection[];
|
||||||
};
|
};
|
||||||
|
|
||||||
function toPercent(value: number) {
|
function toPercent(value: number) {
|
||||||
return `${(value * 100).toFixed(1)}%`;
|
return `${(value * 100).toFixed(1)}%`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toPercentInDecimal(value: number) {
|
||||||
|
return `${value * 100}%`;
|
||||||
|
}
|
||||||
|
|
||||||
function SpanBar({
|
function SpanBar({
|
||||||
|
criticalPath,
|
||||||
viewEnd,
|
viewEnd,
|
||||||
viewStart,
|
viewStart,
|
||||||
getViewedBounds,
|
getViewedBounds,
|
||||||
@ -156,7 +171,7 @@ function SpanBar({
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
className={styles.bar}
|
className={cx(styles.bar)}
|
||||||
style={{
|
style={{
|
||||||
background: color,
|
background: color,
|
||||||
left: toPercent(viewStart),
|
left: toPercent(viewStart),
|
||||||
@ -175,13 +190,13 @@ function SpanBar({
|
|||||||
<AccordianLogs interactive={false} isOpen logs={logGroups[positionKey]} timestamp={traceStartTime} />
|
<AccordianLogs interactive={false} isOpen logs={logGroups[positionKey]} timestamp={traceStartTime} />
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div data-testid="SpanBar--logMarker" className={styles.logMarker} style={{ left: positionKey }} />
|
<div data-testid="SpanBar--logMarker" className={cx(styles.logMarker)} style={{ left: positionKey }} />
|
||||||
</Popover>
|
</Popover>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{rpc && (
|
{rpc && (
|
||||||
<div
|
<div
|
||||||
className={styles.rpc}
|
className={cx(styles.rpc)}
|
||||||
style={{
|
style={{
|
||||||
background: rpc.color,
|
background: rpc.color,
|
||||||
left: toPercent(rpc.viewStart),
|
left: toPercent(rpc.viewStart),
|
||||||
@ -189,6 +204,32 @@ function SpanBar({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{criticalPath?.map((each, index) => {
|
||||||
|
const critcalPathViewBounds = getViewedBounds(each.section_start, each.section_end);
|
||||||
|
const criticalPathViewStart = critcalPathViewBounds.start;
|
||||||
|
const criticalPathViewEnd = critcalPathViewBounds.end;
|
||||||
|
const key = `${each.spanId}-${index}`;
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
key={key}
|
||||||
|
placement="top"
|
||||||
|
content={
|
||||||
|
<div>
|
||||||
|
A segment on the <em>critical path</em> of the overall trace/request/workflow.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="SpanBar--criticalPath"
|
||||||
|
className={styles.criticalPath}
|
||||||
|
style={{
|
||||||
|
left: toPercentInDecimal(criticalPathViewStart),
|
||||||
|
width: toPercentInDecimal(criticalPathViewEnd - criticalPathViewStart),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ import { Icon, stylesFactory, withTheme2 } from '@grafana/ui';
|
|||||||
|
|
||||||
import { autoColor } from '../Theme';
|
import { autoColor } from '../Theme';
|
||||||
import { DURATION, NONE, TAG } from '../settings/SpanBarSettings';
|
import { DURATION, NONE, TAG } from '../settings/SpanBarSettings';
|
||||||
import { SpanBarOptions, SpanLinkFunc, TraceSpan, TNil } from '../types';
|
import { SpanBarOptions, SpanLinkFunc, TraceSpan, TNil, CriticalPathSection } from '../types';
|
||||||
|
|
||||||
import SpanBar from './SpanBar';
|
import SpanBar from './SpanBar';
|
||||||
import { SpanLinksMenu } from './SpanLinks';
|
import { SpanLinksMenu } from './SpanLinks';
|
||||||
@ -324,6 +324,7 @@ export type SpanBarRowProps = {
|
|||||||
createSpanLink?: SpanLinkFunc;
|
createSpanLink?: SpanLinkFunc;
|
||||||
datasourceType: string;
|
datasourceType: string;
|
||||||
visibleSpanIds: string[];
|
visibleSpanIds: string[];
|
||||||
|
criticalPath: CriticalPathSection[];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -377,6 +378,7 @@ export class UnthemedSpanBarRow extends React.PureComponent<SpanBarRowProps> {
|
|||||||
datasourceType,
|
datasourceType,
|
||||||
showServiceName,
|
showServiceName,
|
||||||
visibleSpanIds,
|
visibleSpanIds,
|
||||||
|
criticalPath,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const {
|
const {
|
||||||
duration,
|
duration,
|
||||||
@ -530,6 +532,7 @@ export class UnthemedSpanBarRow extends React.PureComponent<SpanBarRowProps> {
|
|||||||
>
|
>
|
||||||
<Ticks numTicks={numTicks} />
|
<Ticks numTicks={numTicks} />
|
||||||
<SpanBar
|
<SpanBar
|
||||||
|
criticalPath={criticalPath}
|
||||||
rpc={rpc}
|
rpc={rpc}
|
||||||
viewStart={viewStart}
|
viewStart={viewStart}
|
||||||
viewEnd={viewEnd}
|
viewEnd={viewEnd}
|
||||||
|
@ -23,7 +23,7 @@ import { config, reportInteraction } from '@grafana/runtime';
|
|||||||
import { stylesFactory, withTheme2, ToolbarButton } from '@grafana/ui';
|
import { stylesFactory, withTheme2, ToolbarButton } from '@grafana/ui';
|
||||||
|
|
||||||
import { PEER_SERVICE } from '../constants/tag-keys';
|
import { PEER_SERVICE } from '../constants/tag-keys';
|
||||||
import { SpanBarOptions, SpanLinkFunc, TNil } from '../types';
|
import { CriticalPathSection, SpanBarOptions, SpanLinkFunc, TNil } from '../types';
|
||||||
import TTraceTimeline from '../types/TTraceTimeline';
|
import TTraceTimeline from '../types/TTraceTimeline';
|
||||||
import { TraceLog, TraceSpan, Trace, TraceKeyValuePair, TraceLink, TraceSpanReference } from '../types/trace';
|
import { TraceLog, TraceSpan, Trace, TraceKeyValuePair, TraceLink, TraceSpanReference } from '../types/trace';
|
||||||
import { getColorByKey } from '../utils/color-generator';
|
import { getColorByKey } from '../utils/color-generator';
|
||||||
@ -105,11 +105,13 @@ type TVirtualizedTraceViewOwnProps = {
|
|||||||
focusedSpanId?: string;
|
focusedSpanId?: string;
|
||||||
focusedSpanIdForSearch: string;
|
focusedSpanIdForSearch: string;
|
||||||
showSpanFilterMatchesOnly: boolean;
|
showSpanFilterMatchesOnly: boolean;
|
||||||
|
showCriticalPathSpansOnly: boolean;
|
||||||
createFocusSpanLink: (traceId: string, spanId: string) => LinkModel;
|
createFocusSpanLink: (traceId: string, spanId: string) => LinkModel;
|
||||||
topOfViewRef?: RefObject<HTMLDivElement>;
|
topOfViewRef?: RefObject<HTMLDivElement>;
|
||||||
topOfViewRefType?: TopOfViewRefType;
|
topOfViewRefType?: TopOfViewRefType;
|
||||||
datasourceType: string;
|
datasourceType: string;
|
||||||
headerHeight: number;
|
headerHeight: number;
|
||||||
|
criticalPath: CriticalPathSection[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type VirtualizedTraceViewProps = TVirtualizedTraceViewOwnProps & TTraceTimeline;
|
export type VirtualizedTraceViewProps = TVirtualizedTraceViewOwnProps & TTraceTimeline;
|
||||||
@ -129,7 +131,9 @@ function generateRowStates(
|
|||||||
childrenHiddenIDs: Set<string>,
|
childrenHiddenIDs: Set<string>,
|
||||||
detailStates: Map<string, DetailState | TNil>,
|
detailStates: Map<string, DetailState | TNil>,
|
||||||
findMatchesIDs: Set<string> | TNil,
|
findMatchesIDs: Set<string> | TNil,
|
||||||
showSpanFilterMatchesOnly: boolean
|
showSpanFilterMatchesOnly: boolean,
|
||||||
|
showCriticalPathSpansOnly: boolean,
|
||||||
|
criticalPath: CriticalPathSection[]
|
||||||
): RowState[] {
|
): RowState[] {
|
||||||
if (!spans) {
|
if (!spans) {
|
||||||
return [];
|
return [];
|
||||||
@ -138,6 +142,10 @@ function generateRowStates(
|
|||||||
spans = spans.filter((span) => findMatchesIDs.has(span.spanID));
|
spans = spans.filter((span) => findMatchesIDs.has(span.spanID));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showCriticalPathSpansOnly && criticalPath) {
|
||||||
|
spans = spans.filter((span) => criticalPath.find((section) => section.spanId === span.spanID));
|
||||||
|
}
|
||||||
|
|
||||||
let collapseDepth = null;
|
let collapseDepth = null;
|
||||||
const rowStates = [];
|
const rowStates = [];
|
||||||
for (let i = 0; i < spans.length; i++) {
|
for (let i = 0; i < spans.length; i++) {
|
||||||
@ -186,10 +194,20 @@ function generateRowStatesFromTrace(
|
|||||||
childrenHiddenIDs: Set<string>,
|
childrenHiddenIDs: Set<string>,
|
||||||
detailStates: Map<string, DetailState | TNil>,
|
detailStates: Map<string, DetailState | TNil>,
|
||||||
findMatchesIDs: Set<string> | TNil,
|
findMatchesIDs: Set<string> | TNil,
|
||||||
showSpanFilterMatchesOnly: boolean
|
showSpanFilterMatchesOnly: boolean,
|
||||||
|
showCriticalPathSpansOnly: boolean,
|
||||||
|
criticalPath: CriticalPathSection[]
|
||||||
): RowState[] {
|
): RowState[] {
|
||||||
return trace
|
return trace
|
||||||
? generateRowStates(trace.spans, childrenHiddenIDs, detailStates, findMatchesIDs, showSpanFilterMatchesOnly)
|
? generateRowStates(
|
||||||
|
trace.spans,
|
||||||
|
childrenHiddenIDs,
|
||||||
|
detailStates,
|
||||||
|
findMatchesIDs,
|
||||||
|
showSpanFilterMatchesOnly,
|
||||||
|
showCriticalPathSpansOnly,
|
||||||
|
criticalPath
|
||||||
|
)
|
||||||
: [];
|
: [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,8 +253,24 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
|||||||
}
|
}
|
||||||
|
|
||||||
getRowStates(): RowState[] {
|
getRowStates(): RowState[] {
|
||||||
const { childrenHiddenIDs, detailStates, trace, findMatchesIDs, showSpanFilterMatchesOnly } = this.props;
|
const {
|
||||||
return memoizedGenerateRowStates(trace, childrenHiddenIDs, detailStates, findMatchesIDs, showSpanFilterMatchesOnly);
|
childrenHiddenIDs,
|
||||||
|
detailStates,
|
||||||
|
trace,
|
||||||
|
findMatchesIDs,
|
||||||
|
showSpanFilterMatchesOnly,
|
||||||
|
showCriticalPathSpansOnly,
|
||||||
|
criticalPath,
|
||||||
|
} = this.props;
|
||||||
|
return memoizedGenerateRowStates(
|
||||||
|
trace,
|
||||||
|
childrenHiddenIDs,
|
||||||
|
detailStates,
|
||||||
|
findMatchesIDs,
|
||||||
|
showSpanFilterMatchesOnly,
|
||||||
|
showCriticalPathSpansOnly,
|
||||||
|
criticalPath
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getClipping(): { left: boolean; right: boolean } {
|
getClipping(): { left: boolean; right: boolean } {
|
||||||
@ -361,7 +395,7 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
|||||||
attrs: {},
|
attrs: {},
|
||||||
visibleSpanIds: string[]
|
visibleSpanIds: string[]
|
||||||
) {
|
) {
|
||||||
const { spanID } = span;
|
const { spanID, childSpanIds } = span;
|
||||||
const { serviceName } = span.process;
|
const { serviceName } = span.process;
|
||||||
const {
|
const {
|
||||||
childrenHiddenIDs,
|
childrenHiddenIDs,
|
||||||
@ -381,6 +415,7 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
|||||||
showSpanFilterMatchesOnly,
|
showSpanFilterMatchesOnly,
|
||||||
theme,
|
theme,
|
||||||
datasourceType,
|
datasourceType,
|
||||||
|
criticalPath,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
// to avert flow error
|
// to avert flow error
|
||||||
if (!trace) {
|
if (!trace) {
|
||||||
@ -422,6 +457,25 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
|||||||
|
|
||||||
const prevSpan = spanIndex > 0 ? trace.spans[spanIndex - 1] : null;
|
const prevSpan = spanIndex > 0 ? trace.spans[spanIndex - 1] : null;
|
||||||
|
|
||||||
|
const allChildSpanIds = [spanID, ...childSpanIds];
|
||||||
|
// This function called recursively to find all descendants of a span
|
||||||
|
const findAllDescendants = (currentChildSpanIds: string[]) => {
|
||||||
|
currentChildSpanIds.forEach((eachId) => {
|
||||||
|
const currentChildSpan = trace.spans.find((a) => a.spanID === eachId)!;
|
||||||
|
if (currentChildSpan.hasChildren) {
|
||||||
|
allChildSpanIds.push(...currentChildSpan.childSpanIds);
|
||||||
|
findAllDescendants(currentChildSpan.childSpanIds);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
findAllDescendants(childSpanIds);
|
||||||
|
const criticalPathSections = criticalPath?.filter((each) => {
|
||||||
|
if (isCollapsed) {
|
||||||
|
return allChildSpanIds.includes(each.spanId);
|
||||||
|
}
|
||||||
|
return each.spanId === spanID;
|
||||||
|
});
|
||||||
|
|
||||||
const styles = getStyles(this.props);
|
const styles = getStyles(this.props);
|
||||||
return (
|
return (
|
||||||
<div className={styles.row} key={key} style={style} {...attrs}>
|
<div className={styles.row} key={key} style={style} {...attrs}>
|
||||||
@ -452,6 +506,7 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
|||||||
datasourceType={datasourceType}
|
datasourceType={datasourceType}
|
||||||
showServiceName={prevSpan === null || prevSpan.process.serviceName !== span.process.serviceName}
|
showServiceName={prevSpan === null || prevSpan.process.serviceName !== span.process.serviceName}
|
||||||
visibleSpanIds={visibleSpanIds}
|
visibleSpanIds={visibleSpanIds}
|
||||||
|
criticalPath={criticalPathSections}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -22,7 +22,7 @@ import { stylesFactory, withTheme2 } from '@grafana/ui';
|
|||||||
import { autoColor } from '../Theme';
|
import { autoColor } from '../Theme';
|
||||||
import { merge as mergeShortcuts } from '../keyboard-shortcuts';
|
import { merge as mergeShortcuts } from '../keyboard-shortcuts';
|
||||||
import { SpanBarOptions } from '../settings/SpanBarSettings';
|
import { SpanBarOptions } from '../settings/SpanBarSettings';
|
||||||
import { SpanLinkFunc, TNil } from '../types';
|
import { CriticalPathSection, SpanLinkFunc, TNil } from '../types';
|
||||||
import TTraceTimeline from '../types/TTraceTimeline';
|
import TTraceTimeline from '../types/TTraceTimeline';
|
||||||
import { TraceSpan, Trace, TraceLog, TraceKeyValuePair, TraceLink, TraceSpanReference } from '../types/trace';
|
import { TraceSpan, Trace, TraceLog, TraceKeyValuePair, TraceLink, TraceSpanReference } from '../types/trace';
|
||||||
|
|
||||||
@ -102,10 +102,12 @@ export type TProps = {
|
|||||||
focusedSpanId?: string;
|
focusedSpanId?: string;
|
||||||
focusedSpanIdForSearch: string;
|
focusedSpanIdForSearch: string;
|
||||||
showSpanFilterMatchesOnly: boolean;
|
showSpanFilterMatchesOnly: boolean;
|
||||||
|
showCriticalPathSpansOnly: boolean;
|
||||||
createFocusSpanLink: (traceId: string, spanId: string) => LinkModel;
|
createFocusSpanLink: (traceId: string, spanId: string) => LinkModel;
|
||||||
topOfViewRef?: RefObject<HTMLDivElement>;
|
topOfViewRef?: RefObject<HTMLDivElement>;
|
||||||
topOfViewRefType?: TopOfViewRefType;
|
topOfViewRefType?: TopOfViewRefType;
|
||||||
headerHeight: number;
|
headerHeight: number;
|
||||||
|
criticalPath: CriticalPathSection[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
|
@ -129,13 +129,11 @@ export default function transformTraceData(data: TraceResponse | undefined): Tra
|
|||||||
}
|
}
|
||||||
// tree is necessary to sort the spans, so children follow parents, and
|
// tree is necessary to sort the spans, so children follow parents, and
|
||||||
// siblings are sorted by start time
|
// siblings are sorted by start time
|
||||||
const tree = getTraceSpanIdsAsTree(data);
|
const tree = getTraceSpanIdsAsTree(data, spanMap);
|
||||||
const spans: TraceSpan[] = [];
|
const spans: TraceSpan[] = [];
|
||||||
const svcCounts: Record<string, number> = {};
|
const svcCounts: Record<string, number> = {};
|
||||||
|
|
||||||
// Eslint complains about number type not needed but then TS complains it is implicitly any.
|
tree.walk((spanID: string, node: TreeNode<string>, depth = 0) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-inferrable-types
|
|
||||||
tree.walk((spanID: string | number | undefined, node: TreeNode, depth: number = 0) => {
|
|
||||||
if (spanID === '__root__') {
|
if (spanID === '__root__') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -155,6 +153,16 @@ export default function transformTraceData(data: TraceResponse | undefined): Tra
|
|||||||
span.warnings = span.warnings || [];
|
span.warnings = span.warnings || [];
|
||||||
span.tags = span.tags || [];
|
span.tags = span.tags || [];
|
||||||
span.references = span.references || [];
|
span.references = span.references || [];
|
||||||
|
|
||||||
|
span.childSpanIds = node.children
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) => {
|
||||||
|
const spanA = spanMap.get(a.value)!;
|
||||||
|
const spanB = spanMap.get(b.value)!;
|
||||||
|
return spanB.startTime + spanB.duration - (spanA.startTime + spanA.duration);
|
||||||
|
})
|
||||||
|
.map((each) => each.value);
|
||||||
|
|
||||||
const tagsInfo = deduplicateTags(span.tags);
|
const tagsInfo = deduplicateTags(span.tags);
|
||||||
span.tags = orderTags(tagsInfo.dedupedTags, getConfigValue('topTagPrefixes'));
|
span.tags = orderTags(tagsInfo.dedupedTags, getConfigValue('topTagPrefixes'));
|
||||||
span.warnings = span.warnings.concat(tagsInfo.warnings);
|
span.warnings = span.warnings.concat(tagsInfo.warnings);
|
||||||
|
@ -37,7 +37,7 @@ describe('getTraceSpanIdsAsTree()', () => {
|
|||||||
const tree = traceSelectors.getTraceSpanIdsAsTree(generatedTrace);
|
const tree = traceSelectors.getTraceSpanIdsAsTree(generatedTrace);
|
||||||
const spanMap = traceSelectors.getTraceSpansAsMap(generatedTrace);
|
const spanMap = traceSelectors.getTraceSpansAsMap(generatedTrace);
|
||||||
|
|
||||||
tree.walk((value: string | number | undefined, node: TreeNode) => {
|
tree.walk((value: string, node: TreeNode<string>) => {
|
||||||
const expectedParentValue = value === traceSelectors.TREE_ROOT_ID ? null : value;
|
const expectedParentValue = value === traceSelectors.TREE_ROOT_ID ? null : value;
|
||||||
node.children.forEach((childNode) => {
|
node.children.forEach((childNode) => {
|
||||||
expect(getSpanParentId(spanMap.get(childNode.value))).toBe(expectedParentValue);
|
expect(getSpanParentId(spanMap.get(childNode.value))).toBe(expectedParentValue);
|
||||||
|
@ -41,9 +41,9 @@ export const TREE_ROOT_ID = '__root__';
|
|||||||
* @return {TreeNode} A tree of spanIDs derived from the relationships
|
* @return {TreeNode} A tree of spanIDs derived from the relationships
|
||||||
* between spans in the trace.
|
* between spans in the trace.
|
||||||
*/
|
*/
|
||||||
export function getTraceSpanIdsAsTree(trace: TraceResponse) {
|
export function getTraceSpanIdsAsTree(trace: TraceResponse, spanMap: Map<string, TraceSpanData> | null = null) {
|
||||||
const nodesById = new Map(trace.spans.map((span: TraceSpanData) => [span.spanID, new TreeNode(span.spanID)]));
|
const nodesById = new Map(trace.spans.map((span: TraceSpanData) => [span.spanID, new TreeNode(span.spanID)]));
|
||||||
const spansById = new Map(trace.spans.map((span: TraceSpanData) => [span.spanID, span]));
|
const spansById = spanMap ?? new Map(trace.spans.map((span: TraceSpanData) => [span.spanID, span]));
|
||||||
const root = new TreeNode(TREE_ROOT_ID);
|
const root = new TreeNode(TREE_ROOT_ID);
|
||||||
trace.spans.forEach((span: TraceSpanData) => {
|
trace.spans.forEach((span: TraceSpanData) => {
|
||||||
const node = nodesById.get(span.spanID)!;
|
const node = nodesById.get(span.spanID)!;
|
||||||
@ -59,13 +59,13 @@ export function getTraceSpanIdsAsTree(trace: TraceResponse) {
|
|||||||
root.children.push(node);
|
root.children.push(node);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const comparator = (nodeA: TreeNode | undefined, nodeB: TreeNode | undefined) => {
|
const comparator = (nodeA: TreeNode<string>, nodeB: TreeNode<string>) => {
|
||||||
const a: TraceSpanData | undefined = nodeA?.value ? spansById.get(nodeA.value.toString()) : undefined;
|
const a: TraceSpanData | undefined = nodeA?.value ? spansById.get(nodeA.value.toString()) : undefined;
|
||||||
const b: TraceSpanData | undefined = nodeB?.value ? spansById.get(nodeB.value.toString()) : undefined;
|
const b: TraceSpanData | undefined = nodeB?.value ? spansById.get(nodeB.value.toString()) : undefined;
|
||||||
return +(a?.startTime! > b?.startTime!) || +(a?.startTime === b?.startTime) - 1;
|
return +(a?.startTime! > b?.startTime!) || +(a?.startTime === b?.startTime) - 1;
|
||||||
};
|
};
|
||||||
trace.spans.forEach((span: TraceSpanData) => {
|
trace.spans.forEach((span: TraceSpanData) => {
|
||||||
const node: TreeNode | undefined = nodesById.get(span.spanID);
|
const node = nodesById.get(span.spanID);
|
||||||
if (node!.children.length > 1) {
|
if (node!.children.length > 1) {
|
||||||
node?.children.sort(comparator);
|
node?.children.sort(comparator);
|
||||||
}
|
}
|
||||||
@ -73,17 +73,3 @@ export function getTraceSpanIdsAsTree(trace: TraceResponse) {
|
|||||||
root.children.sort(comparator);
|
root.children.sort(comparator);
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const omitCollapsedSpans = createSelector(
|
|
||||||
({ spans }: { spans: TraceSpanData[] }) => spans,
|
|
||||||
createSelector(({ trace }: { trace: TraceResponse }) => trace, getTraceSpanIdsAsTree),
|
|
||||||
({ collapsed }: { collapsed: string[] }) => collapsed,
|
|
||||||
(spans, tree, collapse) => {
|
|
||||||
const hiddenSpanIds = collapse.reduce((result, collapsedSpanId) => {
|
|
||||||
tree.find(collapsedSpanId)!.walk((id: string | number | undefined) => id !== collapsedSpanId && result.add(id));
|
|
||||||
return result;
|
|
||||||
}, new Set());
|
|
||||||
|
|
||||||
return hiddenSpanIds.size > 0 ? spans.filter((span) => !hiddenSpanIds.has(getSpanId(span))) : spans;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
@ -12,7 +12,15 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
export { TraceSpan, TraceResponse, Trace, TraceProcess, TraceKeyValuePair, TraceLink } from './trace';
|
export {
|
||||||
|
TraceSpan,
|
||||||
|
TraceResponse,
|
||||||
|
Trace,
|
||||||
|
TraceProcess,
|
||||||
|
TraceKeyValuePair,
|
||||||
|
TraceLink,
|
||||||
|
CriticalPathSection,
|
||||||
|
} from './trace';
|
||||||
export { SpanBarOptions, SpanBarOptionsData } from '../settings/SpanBarSettings';
|
export { SpanBarOptions, SpanBarOptionsData } from '../settings/SpanBarSettings';
|
||||||
export { default as TTraceTimeline } from './TTraceTimeline';
|
export { default as TTraceTimeline } from './TTraceTimeline';
|
||||||
export { default as TNil } from './TNil';
|
export { default as TNil } from './TNil';
|
||||||
|
@ -69,6 +69,7 @@ export type TraceSpanData = {
|
|||||||
flags: number;
|
flags: number;
|
||||||
errorIconColor?: string;
|
errorIconColor?: string;
|
||||||
dataFrameRowIndex?: number;
|
dataFrameRowIndex?: number;
|
||||||
|
childSpanIds?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TraceSpan = TraceSpanData & {
|
export type TraceSpan = TraceSpanData & {
|
||||||
@ -80,6 +81,7 @@ export type TraceSpan = TraceSpanData & {
|
|||||||
tags: NonNullable<TraceSpanData['tags']>;
|
tags: NonNullable<TraceSpanData['tags']>;
|
||||||
references: NonNullable<TraceSpanData['references']>;
|
references: NonNullable<TraceSpanData['references']>;
|
||||||
warnings: NonNullable<TraceSpanData['warnings']>;
|
warnings: NonNullable<TraceSpanData['warnings']>;
|
||||||
|
childSpanIds: NonNullable<TraceSpanData['childSpanIds']>;
|
||||||
subsidiarilyReferencedBy: TraceSpanReference[];
|
subsidiarilyReferencedBy: TraceSpanReference[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -101,3 +103,10 @@ export type Trace = TraceData & {
|
|||||||
traceName: string;
|
traceName: string;
|
||||||
services: Array<{ name: string; numberOfSpans: number }>;
|
services: Array<{ name: string; numberOfSpans: number }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// It is a section of span that lies on critical path
|
||||||
|
export type CriticalPathSection = {
|
||||||
|
spanId: string;
|
||||||
|
section_start: number;
|
||||||
|
section_end: number;
|
||||||
|
};
|
||||||
|
@ -29,7 +29,7 @@ it('TreeNode constructor should return a tree node', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('depth should work for a single node', () => {
|
it('depth should work for a single node', () => {
|
||||||
expect(new TreeNode().depth).toBe(1);
|
expect(new TreeNode<number>(0).depth).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('depth should caluclate the depth', () => {
|
it('depth should caluclate the depth', () => {
|
||||||
@ -119,8 +119,8 @@ it('find() should return the found item for a function', () => {
|
|||||||
treeRoot.addChild(11);
|
treeRoot.addChild(11);
|
||||||
treeRoot.addChild(12);
|
treeRoot.addChild(12);
|
||||||
|
|
||||||
expect(treeRoot.find((value) => value === 6)).toEqual(secondChildNode);
|
expect(treeRoot.find((value: number) => value === 6)).toEqual(secondChildNode);
|
||||||
expect(treeRoot.find(12)).toEqual(new TreeNode(12));
|
expect(treeRoot.find((value: number) => value === 12)).toEqual(new TreeNode(12));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('find() should return the found item for a value', () => {
|
it('find() should return the found item for a value', () => {
|
||||||
@ -140,8 +140,8 @@ it('find() should return the found item for a value', () => {
|
|||||||
treeRoot.addChild(11);
|
treeRoot.addChild(11);
|
||||||
treeRoot.addChild(12);
|
treeRoot.addChild(12);
|
||||||
|
|
||||||
expect(treeRoot.find(7)).toEqual(thirdDeepestChildNode);
|
expect(treeRoot.find((value: number) => value === 7)).toEqual(thirdDeepestChildNode);
|
||||||
expect(treeRoot.find(12)).toEqual(new TreeNode(12));
|
expect(treeRoot.find((value: number) => value === 12)).toEqual(new TreeNode(12));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('find() should return the found item for a treenode', () => {
|
it('find() should return the found item for a treenode', () => {
|
||||||
@ -182,8 +182,8 @@ it('find() should return null for none found', () => {
|
|||||||
treeRoot.addChild(11);
|
treeRoot.addChild(11);
|
||||||
treeRoot.addChild(12);
|
treeRoot.addChild(12);
|
||||||
|
|
||||||
expect(treeRoot.find(13)).toBe(null);
|
expect(treeRoot.find((value: number) => value === 13)).toBe(null);
|
||||||
expect(treeRoot.find((value) => value === 'foo')).toBe(null);
|
expect(treeRoot.find((value: string) => value === 'foo')).toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getPath() should return the path to the node', () => {
|
it('getPath() should return the path to the node', () => {
|
||||||
@ -225,7 +225,7 @@ it('getPath() should return null if the node is not in the tree', () => {
|
|||||||
|
|
||||||
const exteriorNode = new TreeNode(15);
|
const exteriorNode = new TreeNode(15);
|
||||||
|
|
||||||
expect(treeRoot.getPath(exteriorNode)).toEqual(null);
|
expect(treeRoot.getPath((value: TreeNode<number>) => value === exteriorNode)).toEqual(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('walk() should iterate over every item once in the right order', () => {
|
it('walk() should iterate over every item once in the right order', () => {
|
||||||
|
@ -10,34 +10,34 @@
|
|||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License
|
||||||
|
|
||||||
type SearchFn = (value: string | number | undefined, node: TreeNode, depth?: number) => boolean;
|
export default class TreeNode<TValue> {
|
||||||
|
value: TValue;
|
||||||
|
children: Array<TreeNode<TValue>>;
|
||||||
|
|
||||||
export default class TreeNode {
|
static iterFunction<TValue>(
|
||||||
value: string | number | undefined;
|
fn: ((value: TValue, node: TreeNode<TValue>, depth: number) => TreeNode<TValue> | null) | Function,
|
||||||
children: TreeNode[];
|
depth = 0
|
||||||
|
) {
|
||||||
static iterFunction(fn: SearchFn, depth = 0) {
|
return (node: TreeNode<TValue>) => fn(node.value, node, depth);
|
||||||
return (node: TreeNode) => fn(node.value, node, depth);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static searchFunction(search: TreeNode | number | SearchFn | string) {
|
static searchFunction<TValue>(search: Function | TreeNode<TValue>) {
|
||||||
if (typeof search === 'function') {
|
if (typeof search === 'function') {
|
||||||
return search;
|
return search;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (value: string | number | undefined, node: TreeNode) =>
|
return (value: TValue, node: TreeNode<TValue>) => (search instanceof TreeNode ? node === search : value === search);
|
||||||
search instanceof TreeNode ? node === search : value === search;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(value?: string | number, children: TreeNode[] = []) {
|
constructor(value: TValue, children: Array<TreeNode<TValue>> = []) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.children = children;
|
this.children = children;
|
||||||
}
|
}
|
||||||
|
|
||||||
get depth(): number {
|
get depth(): number {
|
||||||
return this.children?.reduce((depth: number, child: { depth: number }) => Math.max(child.depth + 1, depth), 1);
|
return this.children.reduce((depth, child) => Math.max(child.depth + 1, depth), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
get size() {
|
get size() {
|
||||||
@ -46,12 +46,12 @@ export default class TreeNode {
|
|||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
addChild(child: string | number | TreeNode) {
|
addChild(child: TreeNode<TValue> | TValue) {
|
||||||
this.children?.push(child instanceof TreeNode ? child : new TreeNode(child));
|
this.children.push(child instanceof TreeNode ? child : new TreeNode(child));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
find(search: TreeNode | number | SearchFn | string): TreeNode | null {
|
find(search: Function | TreeNode<TValue>): TreeNode<TValue> | null {
|
||||||
const searchFn = TreeNode.iterFunction(TreeNode.searchFunction(search));
|
const searchFn = TreeNode.iterFunction(TreeNode.searchFunction(search));
|
||||||
if (searchFn(this)) {
|
if (searchFn(this)) {
|
||||||
return this;
|
return this;
|
||||||
@ -65,10 +65,13 @@ export default class TreeNode {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getPath(search: TreeNode | string) {
|
getPath(search: Function | TreeNode<TValue>) {
|
||||||
const searchFn = TreeNode.iterFunction(TreeNode.searchFunction(search));
|
const searchFn = TreeNode.iterFunction(TreeNode.searchFunction(search));
|
||||||
|
|
||||||
const findPath = (currentNode: TreeNode, currentPath: TreeNode[]): TreeNode[] | null => {
|
const findPath = (
|
||||||
|
currentNode: TreeNode<TValue>,
|
||||||
|
currentPath: Array<TreeNode<TValue>>
|
||||||
|
): Array<TreeNode<TValue>> | null => {
|
||||||
// skip if we already found the result
|
// skip if we already found the result
|
||||||
const attempt = currentPath.concat([currentNode]);
|
const attempt = currentPath.concat([currentNode]);
|
||||||
// base case: return the array when there is a match
|
// base case: return the array when there is a match
|
||||||
@ -88,16 +91,20 @@ export default class TreeNode {
|
|||||||
return findPath(this, []);
|
return findPath(this, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
walk(fn: (value: string | number | undefined, node: TreeNode, depth?: number) => void, depth = 0) {
|
walk(fn: (spanID: TValue, node: TreeNode<TValue>, depth: number) => void, startDepth = 0) {
|
||||||
const nodeStack: Array<{ node: TreeNode; depth?: number }> = [];
|
type StackEntry = {
|
||||||
let actualDepth = depth;
|
node: TreeNode<TValue>;
|
||||||
|
depth: number;
|
||||||
|
};
|
||||||
|
const nodeStack: StackEntry[] = [];
|
||||||
|
let actualDepth = startDepth;
|
||||||
nodeStack.push({ node: this, depth: actualDepth });
|
nodeStack.push({ node: this, depth: actualDepth });
|
||||||
while (nodeStack.length) {
|
while (nodeStack.length) {
|
||||||
const popped = nodeStack.pop();
|
const entry: StackEntry = nodeStack[nodeStack.length - 1];
|
||||||
if (popped) {
|
nodeStack.pop();
|
||||||
const { node, depth: nodeDepth } = popped;
|
const { node, depth } = entry;
|
||||||
fn(node.value, node, nodeDepth);
|
fn(node.value, node, depth);
|
||||||
actualDepth = (nodeDepth || 0) + 1;
|
actualDepth = depth + 1;
|
||||||
let i = node.children.length - 1;
|
let i = node.children.length - 1;
|
||||||
while (i >= 0) {
|
while (i >= 0) {
|
||||||
nodeStack.push({ node: node.children[i], depth: actualDepth });
|
nodeStack.push({ node: node.children[i], depth: actualDepth });
|
||||||
@ -105,5 +112,28 @@ export default class TreeNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
paths(fn: (pathIds: TValue[]) => void) {
|
||||||
|
type StackEntry = {
|
||||||
|
node: TreeNode<TValue>;
|
||||||
|
childIndex: number;
|
||||||
|
};
|
||||||
|
const stack: StackEntry[] = [];
|
||||||
|
stack.push({ node: this, childIndex: 0 });
|
||||||
|
const paths: TValue[] = [];
|
||||||
|
while (stack.length) {
|
||||||
|
const { node, childIndex } = stack[stack.length - 1];
|
||||||
|
if (node.children.length >= childIndex + 1) {
|
||||||
|
stack[stack.length - 1].childIndex++;
|
||||||
|
stack.push({ node: node.children[childIndex], childIndex: 0 });
|
||||||
|
} else {
|
||||||
|
if (node.children.length === 0) {
|
||||||
|
const path = stack.map((item) => item.node.value);
|
||||||
|
fn(path);
|
||||||
|
}
|
||||||
|
stack.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return paths;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user