diff --git a/packages/jaeger-ui/src/components/TracePage/CriticalPath/index.test.js b/packages/jaeger-ui/src/components/TracePage/CriticalPath/index.test.js new file mode 100644 index 0000000000..de447baf64 --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/CriticalPath/index.test.js @@ -0,0 +1,43 @@ +// 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 TraceCriticalPath, { computeCriticalPath } from './index'; +import test1 from './testCases/test1'; +import test2 from './testCases/test2'; +import test3 from './testCases/test3'; +import test4 from './testCases/test4'; +import getChildOfSpans from './utils/getChildOfSpans'; +import sanitizeOverFlowingChildren from './utils/sanitizeOverFlowingChildren'; +import test6 from './testCases/test6'; +import test7 from './testCases/test7'; +import test5 from './testCases/test5'; + +describe.each([[test1], [test2], [test3], [test4], [test5], [test6], [test7]])('Happy Path', testProps => { + it('Should find criticalPathSections correctly', () => { + const rootSpanId = testProps.trace.spans[0].spanID; + const spanMap = testProps.trace.spans.reduce((map, span) => { + map.set(span.spanID, span); + return map; + }, new Map()); + const refinedSpanMap = getChildOfSpans(spanMap); + const sanitizedSpanMap = sanitizeOverFlowingChildren(refinedSpanMap); + const criticalPath = computeCriticalPath(sanitizedSpanMap, rootSpanId, []); + expect(criticalPath).toStrictEqual(testProps.criticalPathSections); + }); + + it('Critical path sections', () => { + const criticalPath = TraceCriticalPath(testProps.trace); + expect(criticalPath).toStrictEqual(testProps.criticalPathSections); + }); +}); diff --git a/packages/jaeger-ui/src/components/TracePage/CriticalPath/index.tsx b/packages/jaeger-ui/src/components/TracePage/CriticalPath/index.tsx new file mode 100644 index 0000000000..16659bbde8 --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/CriticalPath/index.tsx @@ -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. + +import memoizeOne from 'memoize-one'; +import { Span, Trace, criticalPathSection } from '../../../types/trace'; +import getChildOfSpans from './utils/getChildOfSpans'; +import findLastFinishingChildSpan from './utils/findLastFinishingChildSpan'; +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. + */ +export const computeCriticalPath = ( + spanMap: Map, + spanId: string, + criticalPath: criticalPathSection[], + returningChildStartTime?: number +): criticalPathSection[] => { + const currentSpan: Span = spanMap.get(spanId)!; + + 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 TraceCriticalPath(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()); + const refinedSpanMap = getChildOfSpans(spanMap); + const sanitizedSpanMap = sanitizeOverFlowingChildren(refinedSpanMap); + criticalPath = computeCriticalPath(sanitizedSpanMap, rootSpanId, criticalPath); + } + return criticalPath; +} + +const memoizedTraceCriticalPath = memoizeOne(TraceCriticalPath); + +export default memoizedTraceCriticalPath; diff --git a/packages/jaeger-ui/src/components/TracePage/CriticalPath/testCases/test1.js b/packages/jaeger-ui/src/components/TracePage/CriticalPath/testCases/test1.js new file mode 100644 index 0000000000..d008cadfd0 --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/CriticalPath/testCases/test1.js @@ -0,0 +1,112 @@ +// 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 transformTraceData from '../../../../model/transform-trace-data'; + +/* + + ┌──────────────────────────────────────┐ | + │ Span C │ | + └──┬──────────▲─────────┬──────────▲───┘ | span C + +++│ │+++++++++│ │++++ | / \ + │ │ │ │ | / \ + ▼──────────┤ ▼──────────┤ | span D span E + │ Span D │ │ Span E │ | + └──────────┘ └──────────┘ | (parent-child tree) + +++++++++++ ++++++++++++ | + + +Here +++++ are critical path sections +*/ +const testTrace = { + traceID: 'test1-trace', + spans: [ + { + spanID: 'span-E', + operationName: 'operation E', + references: [ + { + refType: 'CHILD_OF', + spanID: 'span-C', + }, + ], + startTime: 50, + duration: 10, + processID: 'p1', + }, + { + spanID: 'span-C', + operationName: 'operation C', + references: [], + startTime: 1, + duration: 100, + processID: 'p1', + }, + { + spanID: 'span-D', + operationName: 'operation D', + references: [ + { + refType: 'CHILD_OF', + spanID: 'span-C', + }, + ], + startTime: 20, + duration: 20, + processID: 'p1', + }, + ], + processes: { + p1: { + serviceName: 'customers-service', + }, + }, +}; + +const transformedTrace = 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; diff --git a/packages/jaeger-ui/src/components/TracePage/CriticalPath/testCases/test2.js b/packages/jaeger-ui/src/components/TracePage/CriticalPath/testCases/test2.js new file mode 100644 index 0000000000..e97066c5ed --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/CriticalPath/testCases/test2.js @@ -0,0 +1,104 @@ +// 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 transformTraceData from '../../../../model/transform-trace-data'; + +/* | + ┌─────────────────────────────────────────────────┐ | + │ Span X │ | + └──────┬───────┬─────────────────▲──────▲─────────┘ | + +++++++│+++++++│ │ |++++++++++ | span X + ▼───────┼─────────────────┤ | | / \ + │ │ Span A │ | | / \ + └───────┼─────────────────┘ | | span A span C + │ | | + │ | | + ▼────────────────────────┤ | (parent-child tree) + │ Span C │ | + └────────────────────────┘ | + ++++++++++++++++++++++++++ | + | +Here ++++++ is critical path | +*/ +const happyTrace = { + traceID: 'trace-123', + spans: [ + { + spanID: 'span-X', + operationName: 'op1', + startTime: 1, + duration: 100, + references: [], + processID: 'p1', + }, + { + spanID: 'span-A', + operationName: 'op2', + startTime: 10, + duration: 40, + references: [ + { + refType: 'CHILD_OF', + spanID: 'span-X', + }, + ], + processID: 'p1', + }, + { + spanID: 'span-C', + operationName: 'op3', + startTime: 20, + duration: 40, + references: [ + { + refType: 'CHILD_OF', + spanID: 'span-X', + }, + ], + processID: 'p1', + }, + ], + processes: { + p1: { + serviceName: 'service1', + }, + }, +}; + +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; diff --git a/packages/jaeger-ui/src/components/TracePage/CriticalPath/testCases/test3.js b/packages/jaeger-ui/src/components/TracePage/CriticalPath/testCases/test3.js new file mode 100644 index 0000000000..22ab816a1f --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/CriticalPath/testCases/test3.js @@ -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 transformTraceData from '../../../../model/transform-trace-data'; +/* + | + ┌──────────┐ | + │ Span A │ | span A + └──────────┘ | / + ++++++++++++ ┌───────────────────┐ | / + │ Span B │ | span B + └───────────────────┘ | + | (parent-child tree) + | +Span B will be dropped. | +span A is on critical path(+++++) | +*/ + +const trace = require('../../TraceStatistics/tableValuesTestTrace/traceWithSingleChildSpanLongerThanParent.json'); + +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; diff --git a/packages/jaeger-ui/src/components/TracePage/CriticalPath/testCases/test4.js b/packages/jaeger-ui/src/components/TracePage/CriticalPath/testCases/test4.js new file mode 100644 index 0000000000..75df0dd26d --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/CriticalPath/testCases/test4.js @@ -0,0 +1,94 @@ +// 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 transformTraceData from '../../../../model/transform-trace-data'; + +/* + ┌──────────┐ | + │ 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(+++++) | +*/ + +const trace = { + traceID: 'trace-abc', + spans: [ + { + spanID: 'span-A', + operationName: 'op-A', + references: [], + startTime: 1, + duration: 30, + processID: 'p1', + }, + { + spanID: 'span-B', + operationName: 'op-B', + references: [ + { + refType: 'CHILD_OF', + spanID: 'span-A', + }, + ], + startTime: 40, + duration: 40, + processID: 'p1', + }, + { + spanID: 'span-c', + operationName: 'op-C', + references: [ + { + refType: 'CHILD_OF', + spanID: 'span-B', + }, + ], + startTime: 50, + duration: 10, + processID: 'p1', + }, + ], + processes: { + p1: { + serviceName: 'service-one', + }, + }, +}; + +const transformedTrace = transformTraceData(trace); + +const criticalPathSections = [ + { + spanId: 'span-A', + section_start: 1, + section_end: 31, + }, +]; + +const test4 = { + criticalPathSections, + trace: transformedTrace, +}; + +export default test4; diff --git a/packages/jaeger-ui/src/components/TracePage/CriticalPath/testCases/test5.js b/packages/jaeger-ui/src/components/TracePage/CriticalPath/testCases/test5.js new file mode 100644 index 0000000000..a0e80d979a --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/CriticalPath/testCases/test5.js @@ -0,0 +1,93 @@ +// 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 transformTraceData from '../../../../model/transform-trace-data'; + +/* + ┌────────────────────┐ | + │ 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' | +*/ + +const trace = { + traceID: 'trace-abc', + spans: [ + { + spanID: 'span-A', + operationName: 'op-A', + references: [], + startTime: 1, + duration: 30, + processID: 'p1', + }, + { + spanID: 'span-B', + operationName: 'op-B', + references: [ + { + refType: 'FOLLOWS_FROM', + spanID: 'span-A', + }, + ], + startTime: 10, + duration: 10, + processID: 'p1', + }, + { + spanID: 'span-C', + operationName: 'op-C', + references: [ + { + refType: 'CHILD_OF', + spanID: 'span-B', + }, + ], + startTime: 12, + duration: 2, + processID: 'p1', + }, + ], + processes: { + p1: { + serviceName: 'service-one', + }, + }, +}; + +const transformedTrace = transformTraceData(trace); + +const criticalPathSections = [ + { + spanId: 'span-A', + section_start: 1, + section_end: 31, + }, +]; + +const test5 = { + criticalPathSections, + trace: transformedTrace, +}; + +export default test5; diff --git a/packages/jaeger-ui/src/components/TracePage/CriticalPath/testCases/test6.js b/packages/jaeger-ui/src/components/TracePage/CriticalPath/testCases/test6.js new file mode 100644 index 0000000000..6a6250f4cd --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/CriticalPath/testCases/test6.js @@ -0,0 +1,101 @@ +// 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 transformTraceData from '../../../../model/transform-trace-data'; + +/* + ┌────────────────────────────┐ | + │ Span A │ | + └────────────┬───────────────┘ | span A + │ | / + ┌▼──────────────────────┐ | / + │ Span B │ | span B + └──────────▲────────────┘ | / + │ | / + ┌──────────────┤ | span C + │ Span C │ | + └──────────────┘ | (parent-child tree) +*/ + +const trace = { + traceID: 'trace-abc', + spans: [ + { + spanID: 'span-A', + operationName: 'op-A', + references: [], + startTime: 1, + duration: 29, + processID: 'p1', + }, + { + spanID: 'span-B', + operationName: 'op-B', + references: [ + { + refType: 'CHILD_OF', + spanID: 'span-A', + }, + ], + startTime: 15, + duration: 20, + processID: 'p1', + }, + { + spanID: 'span-C', + operationName: 'op-C', + references: [ + { + refType: 'CHILD_OF', + spanID: 'span-B', + }, + ], + startTime: 10, + duration: 15, + processID: 'p1', + }, + ], + processes: { + p1: { + serviceName: 'service-one', + }, + }, +}; + +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; diff --git a/packages/jaeger-ui/src/components/TracePage/CriticalPath/testCases/test7.js b/packages/jaeger-ui/src/components/TracePage/CriticalPath/testCases/test7.js new file mode 100644 index 0000000000..274ee31426 --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/CriticalPath/testCases/test7.js @@ -0,0 +1,101 @@ +// 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 transformTraceData from '../../../../model/transform-trace-data'; + +/* + ┌─────────────────┐ | + │ Span A │ | spanA + └───────┬─────────┘ | / + │ | / + ┌▼──────────────┐ | spanB + │ Span B │ | / + └─────┬─────────┘ | / + │ | spanC + ┌▼─────────────┐ | + │ Span C │ | ((parent-child tree)) + └──────────────┘ | +*/ + +const trace = { + traceID: 'trace-abc', + spans: [ + { + spanID: 'span-A', + operationName: 'op-A', + references: [], + startTime: 1, + duration: 29, + processID: 'p1', + }, + { + spanID: 'span-B', + operationName: 'op-B', + references: [ + { + refType: 'CHILD_OF', + spanID: 'span-A', + }, + ], + startTime: 15, + duration: 20, + processID: 'p1', + }, + { + spanID: 'span-C', + operationName: 'op-C', + references: [ + { + refType: 'CHILD_OF', + spanID: 'span-B', + }, + ], + startTime: 20, + duration: 20, + processID: 'p1', + }, + ], + processes: { + p1: { + serviceName: 'service-one', + }, + }, +}; + +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; diff --git a/packages/jaeger-ui/src/components/TracePage/CriticalPath/utils/findLastFinishingChildSpan.test.js b/packages/jaeger-ui/src/components/TracePage/CriticalPath/utils/findLastFinishingChildSpan.test.js new file mode 100644 index 0000000000..2ab5c91c67 --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/CriticalPath/utils/findLastFinishingChildSpan.test.js @@ -0,0 +1,55 @@ +// 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 getChildOfSpans from './getChildOfSpans'; +import findLastFinishingChildSpanId from './findLastFinishingChildSpan'; +import sanitizeOverFlowingChildren from './sanitizeOverFlowingChildren'; + +describe('findLastFinishingChildSpanId', () => { + it('Should find lfc of a span correctly', () => { + const refinedSpanData = getChildOfSpans(test1.trace.spans); + const spanMap = refinedSpanData.reduce((map, span) => { + map.set(span.spanID, span); + return map; + }, new Map()); + const sanitizedSpanMap = sanitizeOverFlowingChildren(spanMap); + + 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(test2.trace.spans); + const spanMap = refinedSpanData.reduce((map, span) => { + map.set(span.spanID, span); + return map; + }, new Map()); + const sanitizedSpanMap = sanitizeOverFlowingChildren(spanMap); + + 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(); + }); +}); diff --git a/packages/jaeger-ui/src/components/TracePage/CriticalPath/utils/findLastFinishingChildSpan.tsx b/packages/jaeger-ui/src/components/TracePage/CriticalPath/utils/findLastFinishingChildSpan.tsx new file mode 100644 index 0000000000..3564ed4b13 --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/CriticalPath/utils/findLastFinishingChildSpan.tsx @@ -0,0 +1,43 @@ +// 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 { Span } from '../../../../types/trace'; + +/** + * @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, + currentSpan: Span, + returningChildStartTime?: number +): Span | 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; diff --git a/packages/jaeger-ui/src/components/TracePage/CriticalPath/utils/getChildOfSpans.test.js b/packages/jaeger-ui/src/components/TracePage/CriticalPath/utils/getChildOfSpans.test.js new file mode 100644 index 0000000000..9974ed704b --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/CriticalPath/utils/getChildOfSpans.test.js @@ -0,0 +1,45 @@ +// 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); + }); +}); diff --git a/packages/jaeger-ui/src/components/TracePage/CriticalPath/utils/getChildOfSpans.tsx b/packages/jaeger-ui/src/components/TracePage/CriticalPath/utils/getChildOfSpans.tsx new file mode 100644 index 0000000000..d18967e62e --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/CriticalPath/utils/getChildOfSpans.tsx @@ -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 { Span } from '../../../../types/trace'; +/** + * 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): Map => { + 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; diff --git a/packages/jaeger-ui/src/components/TracePage/CriticalPath/utils/sanitizeOverFlowingChildren.test.js b/packages/jaeger-ui/src/components/TracePage/CriticalPath/utils/sanitizeOverFlowingChildren.test.js new file mode 100644 index 0000000000..58a8e00666 --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/CriticalPath/utils/sanitizeOverFlowingChildren.test.js @@ -0,0 +1,50 @@ +// 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 test3 from '../testCases/test3'; +import test4 from '../testCases/test4'; +import test6 from '../testCases/test6'; +import test7 from '../testCases/test7'; +import getChildOfSpans from './getChildOfSpans'; +import sanitizeOverFlowingChildren from './sanitizeOverFlowingChildren'; + +// Function to make expected data for test6 and test7 +function getExpectedSanitizedData(spans, test) { + 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 }], + }; + 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])], + [test4, new Map().set(test4.trace.spans[0].spanID, test4.trace.spans[0])], + [test6, getExpectedSanitizedData(test6.trace.spans, 'test6')], + [test7, getExpectedSanitizedData(test7.trace.spans, 'test7')], +])('sanitizeOverFlowingChildren', (testProps, expectedSanitizedData) => { + it('Should sanitize the data(overflowing spans) correctly', () => { + const refinedSpanData = getChildOfSpans(testProps.trace.spans); + const spanMap = refinedSpanData.reduce((map, span) => { + map.set(span.spanID, span); + return map; + }, new Map()); + const sanitizedSpanMap = sanitizeOverFlowingChildren(spanMap); + expect(sanitizedSpanMap).toStrictEqual(expectedSanitizedData); + }); +}); diff --git a/packages/jaeger-ui/src/components/TracePage/CriticalPath/utils/sanitizeOverFlowingChildren.tsx b/packages/jaeger-ui/src/components/TracePage/CriticalPath/utils/sanitizeOverFlowingChildren.tsx new file mode 100644 index 0000000000..80ae9c3f80 --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/CriticalPath/utils/sanitizeOverFlowingChildren.tsx @@ -0,0 +1,110 @@ +// 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 { Span } from '../../../../types/trace'; + +/** + * 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): Map => { + let spanIds: string[] = [...spanMap.keys()]; + + spanIds.forEach(spanId => { + const span = spanMap.get(spanId)!; + if (span && span.references.length) { + // parentSpan will be undefined when its parentSpan is dropped previously + const parentSpan = spanMap.get(span.references[0].spanID); + if (parentSpan) { + const childEndTime = span.startTime + span.duration; + const parentEndTime = parentSpan.startTime + parentSpan.duration; + switch (true) { + case span.startTime >= parentSpan.startTime && childEndTime <= parentEndTime: + // case 1: everything looks good + // |----parent----| + // |----child--| + break; + + case span.startTime < parentSpan.startTime && + childEndTime <= parentEndTime && + childEndTime > parentSpan.startTime: + // case 2: child start before parent, truncate is needed + // |----parent----| + // |----child--| + spanMap.set(span.spanID, { + ...span, + startTime: parentSpan.startTime, + duration: childEndTime - parentSpan.startTime, + }); + break; + + case span.startTime >= parentSpan.startTime && + childEndTime > parentEndTime && + span.startTime < parentEndTime: + // case 3: child end after parent, truncate is needed + // |----parent----| + // |----child--| + spanMap.set(span.spanID, { + ...span, + duration: parentEndTime - span.startTime, + }); + break; + + case span.startTime >= parentEndTime || childEndTime <= parentSpan.startTime: + // case 4: child outside of parent range => drop the child span + // |----parent----| + // |----child--| + // or + // |----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); + spanMap.set(parentSpan.spanID, { ...parentSpan }); + break; + + default: + // Never reaches to default + // Something unexpected happened + throw RangeError(`Error while computing Critical Path Algorithm.`); + } + } else { + // Drop the child spans of dropped parent span + spanMap.delete(span.spanID); + } + } + }); + + // 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; diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBar.css b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBar.css index 36da603c1c..3a0150dd30 100644 --- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBar.css +++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBar.css @@ -44,6 +44,13 @@ limitations under the License. z-index: 1; } +.SpanBar--criticalPath { + position: absolute; + top: 45%; + height: 11%; + z-index: 2; +} + .SpanBar--label { color: #aaa; font-size: 12px; diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBar.test.js b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBar.test.js index c45cff710f..46fcbada8b 100644 --- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBar.test.js +++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBar.test.js @@ -90,4 +90,20 @@ describe('', () => { render(); expect(screen.getAllByTestId('SpanBar--logMarker').length).toEqual(2); }); + + it('Critical Path is rendered', () => { + const newProps = { + ...props, + criticalPath: [ + { + spanId: 'Test-SpanId', + section_start: 10, + section_end: 20, + }, + ], + getViewedBounds: () => ({ start: 0.1, end: 0.5 }), + }; + const wrapper = render(); + expect(wrapper.getAllByTestId('SpanBar--criticalPath').length).toEqual(1); + }); }); diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBar.tsx b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBar.tsx index e68c4f16c9..9e27244747 100644 --- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBar.tsx +++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBar.tsx @@ -20,7 +20,7 @@ import AccordianLogs from './SpanDetail/AccordianLogs'; import { ViewedBoundsFunctionType } from './utils'; import { TNil } from '../../../types'; -import { Span } from '../../../types/trace'; +import { Span, criticalPathSection } from '../../../types/trace'; import './SpanBar.css'; @@ -29,6 +29,7 @@ type TCommonProps = { hintSide: string; // onClick: (evt: React.MouseEvent) => void; onClick?: (evt: React.MouseEvent) => void; + criticalPath: criticalPathSection[]; viewEnd: number; viewStart: number; getViewedBounds: ViewedBoundsFunctionType; @@ -49,8 +50,13 @@ function toPercent(value: number) { return `${(value * 100).toFixed(1)}%`; } +function toPercentInDecimal(value: number) { + return `${value * 100}%`; +} + function SpanBar(props: TCommonProps) { const { + criticalPath, viewEnd, viewStart, getViewedBounds, @@ -133,6 +139,24 @@ function SpanBar(props: TCommonProps) { }} /> )} + {criticalPath && + criticalPath.map((each, index) => { + const critcalPathViewBounds = getViewedBounds(each.section_start, each.section_end); + const criticalPathViewStart = critcalPathViewBounds.start; + const criticalPathViewEnd = critcalPathViewBounds.end; + return ( +
+ ); + })}
); } diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.tsx b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.tsx index c2acb7365f..928585ba53 100644 --- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.tsx +++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/SpanBarRow.tsx @@ -25,13 +25,14 @@ import SpanBar from './SpanBar'; import Ticks from './Ticks'; import { TNil } from '../../../types'; -import { Span } from '../../../types/trace'; +import { criticalPathSection, Span } from '../../../types/trace'; import './SpanBarRow.css'; type SpanBarRowProps = { className?: string; color: string; + criticalPath: criticalPathSection[]; columnDivision: number; isChildrenExpanded: boolean; isDetailExpanded: boolean; @@ -87,6 +88,7 @@ export default class SpanBarRow extends React.PureComponent { const { className, color, + criticalPath, columnDivision, isChildrenExpanded, isDetailExpanded, @@ -201,6 +203,7 @@ export default class SpanBarRow extends React.PureComponent { > ', () => { const focusUiFindMatchesMock = jest.fn(); const trace = transformTraceData(traceGenerator.trace({ numberOfSpans: 10 })); + const criticalPath = memoizedTraceCriticalPath(trace); + const props = { childrenHiddenIDs: new Set(), childrenToggle: jest.fn(), @@ -53,6 +56,7 @@ describe('', () => { shouldScrollToFirstUiFindMatch: false, spanNameColumnWidth: 0.5, trace, + criticalPath, uiFind: 'uiFind', history: { replace: () => {}, diff --git a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/VirtualizedTraceView.tsx b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/VirtualizedTraceView.tsx index 9d9644e3c7..a5af64cb0f 100644 --- a/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/VirtualizedTraceView.tsx +++ b/packages/jaeger-ui/src/components/TracePage/TraceTimelineViewer/VirtualizedTraceView.tsx @@ -41,7 +41,7 @@ import { extractUiFindFromState, TExtractUiFindFromStateReturn } from '../../com import getLinks from '../../../model/link-patterns'; import colorGenerator from '../../../utils/color-generator'; import { TNil, ReduxState } from '../../../types'; -import { Log, Span, Trace, KeyValuePair } from '../../../types/trace'; +import { Log, Span, Trace, KeyValuePair, criticalPathSection } from '../../../types/trace'; import TTraceTimeline from '../../../types/TTraceTimeline'; import './VirtualizedTraceView.css'; @@ -60,6 +60,7 @@ type TVirtualizedTraceViewOwnProps = { scrollToFirstVisibleSpan: () => void; registerAccessors: (accesors: Accessors) => void; trace: Trace; + criticalPath: criticalPathSection[]; }; type TDispatchProps = { @@ -330,7 +331,7 @@ export class VirtualizedTraceViewImpl extends React.Component { + if (isCollapsed) { + 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); + return allChildSpanIds.includes(each.spanId); + } + return each.spanId === spanID; + }); // Check for direct child "server" span if the span is a "client" span. let rpc = null; if (isCollapsed) { @@ -382,6 +401,7 @@ export class VirtualizedTraceViewImpl extends React.Component void; spanNameColumnWidth: number; trace: Trace; + criticalPath: criticalPathSection[]; updateNextViewRangeTime: (update: ViewRangeTimeUpdate) => void; updateViewRangeTime: TUpdateViewRangeTimeFunction; viewRange: IViewRange; diff --git a/packages/jaeger-ui/src/components/TracePage/index.test.js b/packages/jaeger-ui/src/components/TracePage/index.test.js index 91edf9f8ce..68d2d858a3 100644 --- a/packages/jaeger-ui/src/components/TracePage/index.test.js +++ b/packages/jaeger-ui/src/components/TracePage/index.test.js @@ -25,6 +25,7 @@ jest.mock('./TracePageHeader/SpanGraph'); jest.mock('./TracePageHeader/TracePageHeader.track'); jest.mock('./TracePageHeader/TracePageSearchBar'); jest.mock('./TraceTimelineViewer'); +jest.mock('./CriticalPath/index'); import React from 'react'; import sinon from 'sinon'; diff --git a/packages/jaeger-ui/src/components/TracePage/index.tsx b/packages/jaeger-ui/src/components/TracePage/index.tsx index 2d47420303..94e03714ef 100644 --- a/packages/jaeger-ui/src/components/TracePage/index.tsx +++ b/packages/jaeger-ui/src/components/TracePage/index.tsx @@ -61,6 +61,7 @@ import TraceFlamegraph from './TraceFlamegraph/index'; import { TraceGraphConfig } from '../../types/config'; import './index.css'; +import memoizedTraceCriticalPath from './CriticalPath/index'; type TDispatchProps = { acknowledgeArchive: (id: string) => void; @@ -78,6 +79,7 @@ type TOwnProps = { type TReduxProps = { archiveEnabled: boolean; archiveTraceState: TraceArchive | TNil; + criticalPathEnabled: boolean; embedded: null | EmbeddedState; id: string; searchUrl: null | string; @@ -325,6 +327,7 @@ export class TracePageImpl extends React.PureComponent { const { archiveEnabled, archiveTraceState, + criticalPathEnabled, embedded, id, uiFind, @@ -388,6 +391,7 @@ export class TracePageImpl extends React.PureComponent { }; let view; + const criticalPath = criticalPathEnabled ? memoizedTraceCriticalPath(data) : []; if (ETraceViewType.TraceTimelineViewer === viewType && headerHeight) { view = ( { scrollToFirstVisibleSpan={this._scrollManager.scrollToFirstVisibleSpan} findMatchesIDs={spanFindMatches} trace={data} + criticalPath={criticalPath} updateNextViewRangeTime={this.updateNextViewRangeTime} updateViewRangeTime={this.updateViewRangeTime} viewRange={viewRange} @@ -440,7 +445,7 @@ export function mapStateToProps(state: ReduxState, ownProps: TOwnProps): TReduxP const trace = id ? traces[id] : null; const archiveTraceState = id ? archive[id] : null; const archiveEnabled = Boolean(config.archiveEnabled); - const { disableJsonView } = config; + const { disableJsonView, criticalPathEnabled } = config; const { state: locationState } = router.location; const searchUrl = (locationState && locationState.fromSearch) || null; const { traceGraph: traceGraphConfig } = config; @@ -449,6 +454,7 @@ export function mapStateToProps(state: ReduxState, ownProps: TOwnProps): TReduxP ...extractUiFindFromState(state), archiveEnabled, archiveTraceState, + criticalPathEnabled, embedded, id, searchUrl, diff --git a/packages/jaeger-ui/src/constants/default-config.tsx b/packages/jaeger-ui/src/constants/default-config.tsx index 56a4708033..c5d4d53937 100644 --- a/packages/jaeger-ui/src/constants/default-config.tsx +++ b/packages/jaeger-ui/src/constants/default-config.tsx @@ -22,6 +22,7 @@ import { Config } from '../types/config'; const defaultConfig: Config = { archiveEnabled: false, + criticalPathEnabled: true, dependencies: { dagMaxNumServices: FALLBACK_DAG_MAX_NUM_SERVICES, menuEnabled: true, diff --git a/packages/jaeger-ui/src/model/transform-trace-data.tsx b/packages/jaeger-ui/src/model/transform-trace-data.tsx index eabdf75ea6..649b17d93f 100644 --- a/packages/jaeger-ui/src/model/transform-trace-data.tsx +++ b/packages/jaeger-ui/src/model/transform-trace-data.tsx @@ -135,6 +135,15 @@ export default function transformTraceData(data: TraceData & { spans: SpanData[] span.relativeStartTime = span.startTime - traceStartTime; span.depth = depth - 1; span.hasChildren = node.children.length > 0; + // Get the childSpanIds sorted based on endTime without changing tree structure + 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); span.warnings = span.warnings || []; span.tags = span.tags || []; span.references = span.references || []; diff --git a/packages/jaeger-ui/src/types/config.tsx b/packages/jaeger-ui/src/types/config.tsx index 1d3f909585..3f3fcc98a4 100644 --- a/packages/jaeger-ui/src/types/config.tsx +++ b/packages/jaeger-ui/src/types/config.tsx @@ -88,6 +88,9 @@ export type Config = { // Requires Query Service to be configured with "archive" storage backend. archiveEnabled?: boolean; + // criticalPath enables to show the criticalPath of each span in a trace view. + criticalPathEnabled: boolean; + // dependencies controls the behavior of System Architecture tab. dependencies?: { // menuEnabled enables or disables the System Architecture tab. diff --git a/packages/jaeger-ui/src/types/trace.tsx b/packages/jaeger-ui/src/types/trace.tsx index 2b30b9b6f6..0fca1b905f 100644 --- a/packages/jaeger-ui/src/types/trace.tsx +++ b/packages/jaeger-ui/src/types/trace.tsx @@ -56,6 +56,7 @@ export type SpanData = { tags?: Array; references?: Array; warnings?: Array | null; + childSpanIds?: Array; }; export type Span = SpanData & { @@ -66,6 +67,7 @@ export type Span = SpanData & { tags: NonNullable; references: NonNullable; warnings: NonNullable; + childSpanIds: NonNullable; subsidiarilyReferencedBy: Array; }; @@ -82,3 +84,10 @@ export type Trace = TraceData & { traceName: string; services: { 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; +}; diff --git a/yarn.lock b/yarn.lock index 9db14b2e10..1c48c25079 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2589,19 +2589,12 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== -"@types/react-dom@18.2.5": - version "18.2.5" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.5.tgz#5c5f13548bda23cd98f50ca4a59107238bfe18f3" - integrity sha512-sRQsOS/sCLnpQhR4DSKGTtWFE3FZjpQa86KPVbhUqdYMRZ9FEFcfAytKhR/vUG2rH1oFbOOej6cuD7MFSobDRQ== +"@types/react-dom@16.9.18", "@types/react-dom@18.2.5", "@types/react-dom@^18.0.0": + version "16.9.18" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.18.tgz#1fda8b84370b1339d639a797a84c16d5a195b419" + integrity sha512-lmNARUX3+rNF/nmoAFqasG0jAA7q6MeGZK/fdeLwY3kAA4NPgHHrG5bNQe2B5xmD4B+x6Z6h0rEJQ7MEEgQxsw== dependencies: - "@types/react" "*" - -"@types/react-dom@^18.0.0": - version "18.2.7" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.7.tgz#67222a08c0a6ae0a0da33c3532348277c70abb63" - integrity sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA== - dependencies: - "@types/react" "*" + "@types/react" "^16" "@types/react-helmet@^6.1.5": version "6.1.5" @@ -2639,7 +2632,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@16.14.35": +"@types/react@*", "@types/react@16.14.35", "@types/react@16.8.7", "@types/react@^16": version "16.14.35" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.35.tgz#9d3cf047d85aca8006c4776693124a5be90ee429" integrity sha512-NUEiwmSS1XXtmBcsm1NyRRPYjoZF2YTE89/5QiLt5mlGffYK9FQqOKuOLuXNrjPQV04oQgaZG+Yq02ZfHoFyyg== @@ -2648,14 +2641,6 @@ "@types/scheduler" "*" csstype "^3.0.2" -"@types/react@16.8.7": - version "16.8.7" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.8.7.tgz#7b1c0223dd5494f9b4501ad2a69aa6acb350a29b" - integrity sha512-0xbkIyrDNKUn4IJVf8JaCn+ucao/cq6ZB8O6kSzhrJub1cVSqgTArtG0qCfdERWKMEIvUbrwLXeQMqWEsyr9dA== - dependencies: - "@types/prop-types" "*" - csstype "^2.2.0" - "@types/redux-actions@2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@types/redux-actions/-/redux-actions-2.2.1.tgz#c1f4a7283ecd3cd696291550361e441bf9389370" @@ -4515,11 +4500,6 @@ cssstyle@^3.0.0: dependencies: rrweb-cssom "^0.6.0" -csstype@^2.2.0: - version "2.6.21" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.21.tgz#2efb85b7cc55c80017c66a5ad7cbd931fda3a90e" - integrity sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w== - csstype@^3.0.2: version "3.1.2" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" @@ -8334,7 +8314,7 @@ lodash.upperfirst@^4.3.1: resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce" integrity sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg== -lodash@^4.16.5, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.2.1: +lodash@4.17.21, lodash@^4.16.5, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.2.1: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==