diff --git a/public/app/features/explore/TraceView/components/selectors/trace.test.ts b/public/app/features/explore/TraceView/components/selectors/trace.test.ts index 1aec2d75aa8..13346e359ce 100644 --- a/public/app/features/explore/TraceView/components/selectors/trace.test.ts +++ b/public/app/features/explore/TraceView/components/selectors/trace.test.ts @@ -17,35 +17,14 @@ import { values as _values } from 'lodash'; import TreeNode from 'app/features/explore/TraceView/components/utils/TreeNode'; import traceGenerator from '../demo/trace-generators'; -import { TraceResponse, TraceSpan, TraceSpanData } from '../types/trace'; -import { numberSortComparator } from '../utils/sort'; +import { TraceResponse } from '../types/trace'; -import { - getSpanId, - getSpanName, - getSpanParentId, - getSpanProcess, - getSpanProcessId, - getSpanServiceName, - getSpanTimestamp, -} from './span'; +import { getSpanId, getSpanParentId } from './span'; import * as traceSelectors from './trace'; import { followsFromRef } from './trace.fixture'; const generatedTrace: TraceResponse = traceGenerator.trace({ numberOfSpans: 45 }); -it('getTraceId() should return the traceID', () => { - expect(traceSelectors.getTraceId(generatedTrace)).toBe(generatedTrace.traceID); -}); - -it('hydrateSpansWithProcesses() should return the trace with processes on each span', () => { - const hydratedTrace = traceSelectors.hydrateSpansWithProcesses(generatedTrace); - - hydratedTrace.spans.forEach((span) => - expect(getSpanProcess(span as TraceSpan)).toBe(generatedTrace.processes[getSpanProcessId(span)]) - ); -}); - it('getTraceSpansAsMap() should return a map of all of the spans', () => { const spanMap = traceSelectors.getTraceSpansAsMap(generatedTrace); [...spanMap.entries()].forEach((pair) => { @@ -70,300 +49,3 @@ describe('getTraceSpanIdsAsTree()', () => { expect(() => traceSelectors.getTraceSpanIdsAsTree(followsFromRef)).not.toThrow(); }); }); - -it('getParentSpan() should return the parent span of the tree', () => { - expect(traceSelectors.getParentSpan(generatedTrace)).toBe( - traceSelectors - .getTraceSpansAsMap(generatedTrace) - .get(traceSelectors.getTraceSpanIdsAsTree(generatedTrace).children[0].value) - ); -}); - -it('getParentSpan() should return the first span if there are multiple parents', () => { - const initialTimestamp = new Date().getTime() * 1000; - const firstSpan = { - startTime: initialTimestamp, - spanID: 'my-span-1', - references: [], - }; - - const trace = { - spans: [ - { - startTime: initialTimestamp + 2000, - spanID: 'my-span-3', - references: [], - }, - firstSpan, - { - startTime: initialTimestamp + 1000, - spanID: 'my-span-2', - references: [], - }, - ], - } as unknown as TraceResponse; - - expect(traceSelectors.getParentSpan(trace)).toBe(firstSpan); -}); - -it('getTraceName() should return a formatted name for the first span', () => { - const hydratedTrace = traceSelectors.hydrateSpansWithProcesses(generatedTrace); - const parentSpan = traceSelectors.getParentSpan(hydratedTrace); - - expect(traceSelectors.getTraceName(hydratedTrace)).toBe( - `${getSpanServiceName(parentSpan)}: ${getSpanName(parentSpan)}` - ); -}); - -it('getTraceSpanCount() should return the length of the spans array', () => { - expect(traceSelectors.getTraceSpanCount(generatedTrace)).toBe(generatedTrace.spans.length); -}); - -it('getTraceDuration() should return the duration for the span', () => { - expect(traceSelectors.getTraceDuration(generatedTrace)).toBe(generatedTrace.spans[0].duration); -}); - -it('getTraceTimestamp() should return the first timestamp for the conventional trace', () => { - expect(traceSelectors.getTraceTimestamp(generatedTrace)).toBe(generatedTrace.spans[0].startTime); -}); - -it('getTraceDepth() should determine the total depth of the trace tree', () => { - expect(traceSelectors.getTraceDepth(generatedTrace)).toBe( - traceSelectors.getTraceSpanIdsAsTree(generatedTrace).depth - 1 - ); -}); - -it('getSpanDepthForTrace() should determine the depth of a given span in the parent', () => { - function testDepthCalc(span: TraceSpanData) { - let depth = 2; - let currentId = getSpanParentId(span); - - const findCurrentSpanById = (item: TraceSpanData) => getSpanId(item) === currentId; - while (currentId !== getSpanId(generatedTrace.spans[0])) { - depth++; - currentId = getSpanParentId(generatedTrace.spans.find(findCurrentSpanById)!); - } - - // console.log('hypothetical depth', depth); - - expect( - traceSelectors.getSpanDepthForTrace({ - trace: generatedTrace, - span, - }) - ).toBe(depth); - } - - // test depth calculations for a few random spans - testDepthCalc(generatedTrace.spans[1]); - testDepthCalc(generatedTrace.spans[Math.floor(generatedTrace.spans.length / 2)]); - testDepthCalc(generatedTrace.spans[Math.floor(generatedTrace.spans.length / 4)]); - testDepthCalc(generatedTrace.spans[Math.floor(generatedTrace.spans.length * 0.75)]); -}); - -it('getTraceServices() should return an unique array of all services in the trace', () => { - const svcs = [...traceSelectors.getTraceServices(generatedTrace)].sort(); - const set = new Set(_values(generatedTrace.processes).map((v) => v.serviceName)); - const setSvcs = [...set.values()].sort(); - expect(svcs).toEqual(setSvcs); -}); - -it('getTraceServiceCount() should return the length of the service list', () => { - expect(traceSelectors.getTraceServiceCount(generatedTrace)).toBe( - Object.values(generatedTrace.processes).reduce((results, process) => results.add(process.serviceName), new Set()) - .size - ); -}); - -it('formatDurationForUnit() should use the formatters to return the proper value', () => { - expect(traceSelectors.formatDurationForUnit({ duration: 302000, unit: 'ms' })).toBe('302ms'); - - expect(traceSelectors.formatDurationForUnit({ duration: 1302000, unit: 'ms' })).toBe('1302ms'); - - expect(traceSelectors.formatDurationForUnit({ duration: 1302000, unit: 's' })).toBe('1.302s'); - - expect(traceSelectors.formatDurationForUnit({ duration: 90000, unit: 's' })).toBe('0.09s'); -}); - -it('formatDurationForTrace() should return a ms value for traces shorter than a second', () => { - const firstSpan = generatedTrace.spans[0]; - firstSpan.duration = 600000; - expect( - traceSelectors.formatDurationForTrace({ - trace: { - ...generatedTrace, - spans: [firstSpan], - }, - duration: 302000, - }) - ).toBe('302ms'); -}); - -it('formatDurationForTrace() should return a s value for traces longer than a second', () => { - expect( - traceSelectors.formatDurationForTrace({ - trace: { - ...generatedTrace, - spans: generatedTrace.spans.concat([ - { - ...generatedTrace.spans[0], - duration: 1000000, - }, - ]), - }, - duration: 302000, - }) - ).toBe('0.302s'); - - expect( - traceSelectors.formatDurationForTrace({ - trace: { - ...generatedTrace, - spans: generatedTrace.spans.concat([ - { - ...generatedTrace.spans[0], - duration: 1200000, - }, - ]), - }, - duration: 302000, - }) - ).toBe('0.302s'); -}); - -it('getSortedSpans() should sort spans given a sort object', () => { - expect( - traceSelectors.getSortedSpans({ - trace: generatedTrace, - spans: generatedTrace.spans, - sort: { - dir: 1, - comparator: numberSortComparator, - selector: getSpanTimestamp, - }, - }) - ).toEqual([...generatedTrace.spans].sort((spanA, spanB) => spanA.startTime - spanB.startTime)); - - expect( - traceSelectors.getSortedSpans({ - trace: generatedTrace, - spans: generatedTrace.spans, - sort: { - dir: -1, - comparator: numberSortComparator, - selector: getSpanTimestamp, - }, - }) - ).toEqual([...generatedTrace.spans].sort((spanA, spanB) => spanB.startTime - spanA.startTime)); -}); - -it('getTreeSizeForTraceSpan() should return the size for the parent span', () => { - expect( - traceSelectors.getTreeSizeForTraceSpan({ - trace: generatedTrace, - span: generatedTrace.spans[0], - }) - ).toBe(generatedTrace.spans.length - 1); -}); - -it('getTreeSizeForTraceSpan() should return the size for a child span', () => { - expect( - traceSelectors.getTreeSizeForTraceSpan({ - trace: generatedTrace, - span: generatedTrace.spans[1], - }) - ).toBe(traceSelectors.getTraceSpanIdsAsTree(generatedTrace).find(generatedTrace.spans[1].spanID)!.size - 1); -}); - -it('getTreeSizeForTraceSpan() should return -1 for an absent span', () => { - const absentSpan = generatedTrace.spans[0]; - absentSpan.spanID = 'whatever'; - - expect( - traceSelectors.getTreeSizeForTraceSpan({ - trace: generatedTrace, - span: absentSpan, - }) - ).toBe(-1); -}); - -it('getTraceName() should return the trace name based on the parentSpan', () => { - const serviceName = generatedTrace.processes[generatedTrace.spans[0].processID].serviceName; - const operationName = generatedTrace.spans[0].operationName; - - expect(traceSelectors.getTraceName(generatedTrace)).toBe(`${serviceName}: ${operationName}`); -}); - -it('omitCollapsedSpans() should filter out collapsed spans', () => { - const span = generatedTrace.spans[1]; - const size = traceSelectors.getTraceSpanIdsAsTree(generatedTrace).find(span.spanID)!.size - 1; - - expect( - traceSelectors.omitCollapsedSpans({ - trace: generatedTrace, - spans: generatedTrace.spans, - collapsed: [span.spanID], - }).length - ).toBe(generatedTrace.spans.length - size); -}); - -it('getTicksForTrace() should return a list of ticks given interval parameters', () => { - const trace = generatedTrace; - const timestamp = new Date().getTime() * 1000; - - trace.spans.forEach((span) => { - span.duration = 3000000; - span.startTime = timestamp; - }); - - expect( - traceSelectors.getTicksForTrace({ - trace, - interval: 3, - width: 10, - }) - ).toEqual([ - { timestamp, width: 10 }, - { timestamp: timestamp + 1000000, width: 10 }, - { timestamp: timestamp + 2000000, width: 10 }, - { timestamp: timestamp + 3000000, width: 10 }, - ]); -}); - -it('getTicksForTrace() should use defaults', () => { - const timestamp = new Date().getTime() * 1000; - const trace = traceGenerator.trace({ numberOfSpans: 1 }); - - trace.spans = [ - { - traceID: '5031233a-d0b5-5d41-9b4b-4c072bcf5020', - processID: 'b5f4e0ff-7318-5017-a3f3-9c7b423a82aa', - spanID: 'e871771f-f1b4-54af-9e9d-826259c2915e', - flags: 0, - logs: [], - operationName: 'POST', - startTime: timestamp, - duration: 4000000, - }, - ]; - - expect(traceSelectors.getTicksForTrace({ trace })).toEqual([ - { timestamp, width: traceSelectors.DEFAULT_TICK_WIDTH }, - { - timestamp: timestamp + 1000000, - width: traceSelectors.DEFAULT_TICK_WIDTH, - }, - { - timestamp: timestamp + 2000000, - width: traceSelectors.DEFAULT_TICK_WIDTH, - }, - { - timestamp: timestamp + 3000000, - width: traceSelectors.DEFAULT_TICK_WIDTH, - }, - { - timestamp: timestamp + 4000000, - width: traceSelectors.DEFAULT_TICK_WIDTH, - }, - ]); -}); diff --git a/public/app/features/explore/TraceView/components/selectors/trace.ts b/public/app/features/explore/TraceView/components/selectors/trace.ts index 288910b07e6..ca944ba67ba 100644 --- a/public/app/features/explore/TraceView/components/selectors/trace.ts +++ b/public/app/features/explore/TraceView/components/selectors/trace.ts @@ -12,35 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { createSelector, createStructuredSelector } from 'reselect'; +import { createSelector } from 'reselect'; -import { Trace, TraceData, TraceProcess, TraceResponse, TraceSpanData } from '../types/trace'; +import { TraceResponse, TraceSpanData } from '../types/trace'; import TreeNode from '../utils/TreeNode'; -import { formatMillisecondTime, formatSecondTime, ONE_SECOND } from '../utils/date'; -import { numberSortComparator } from '../utils/sort'; -import { getProcessServiceName } from './process'; -import { - getSpanId, - getSpanName, - getSpanServiceName, - getSpanTimestamp, - getSpanDuration, - getSpanProcessId, -} from './span'; +import { getSpanId } from './span'; -export const getTraceId = (trace: TraceData) => trace.traceID; export const getTraceSpans = (trace: TraceResponse) => trace.spans; -const getTraceProcesses = (trace: TraceData | Trace) => trace.processes; - -const getSpanWithProcess = createSelector( - (state: { span: TraceSpanData; processes: Record }) => state.span, - (state: { span: TraceSpanData; processes: Record }) => state.processes, - (span, processes) => ({ - ...span, - process: processes[getSpanProcessId(span)], - }) -); export const getTraceSpansAsMap = createSelector(getTraceSpans, (spans) => spans.reduce((map, span: TraceSpanData) => map.set(getSpanId(span), span), new Map()) @@ -95,127 +74,6 @@ export function getTraceSpanIdsAsTree(trace: TraceResponse) { return root; } -// attach "process" as an object to each span. -export const hydrateSpansWithProcesses = (trace: TraceResponse) => { - const spans = getTraceSpans(trace); - const processes = getTraceProcesses(trace); - - return { - ...trace, - spans: spans.map((span: TraceSpanData) => getSpanWithProcess({ span, processes })), - }; -}; - -export const getTraceSpanCount = createSelector(getTraceSpans, (spans) => spans.length); - -export const getTraceTimestamp = createSelector(getTraceSpans, (spans) => - spans.reduce( - (prevTimestamp: number, span: TraceSpanData) => - prevTimestamp ? Math.min(prevTimestamp, getSpanTimestamp(span)) : getSpanTimestamp(span), - 0 - ) -); - -export const getTraceDuration = createSelector(getTraceSpans, getTraceTimestamp, (spans, timestamp) => - spans.reduce( - (prevDuration: number, span: TraceSpanData) => - prevDuration - ? Math.max(getSpanTimestamp(span) - timestamp! + getSpanDuration(span), prevDuration) - : getSpanDuration(span), - 0 - ) -); - -export const getParentSpan = createSelector( - getTraceSpanIdsAsTree, - getTraceSpansAsMap, - (tree, spanMap) => - tree.children - .map((node: TreeNode) => spanMap.get(node.value)) - .sort((spanA: TraceSpanData, spanB: TraceSpanData) => - numberSortComparator(getSpanTimestamp(spanA), getSpanTimestamp(spanB)) - )[0] -); - -export const getTraceDepth = createSelector(getTraceSpanIdsAsTree, (spanTree) => spanTree.depth - 1); - -export const getSpanDepthForTrace = createSelector( - createSelector((state: { trace: TraceResponse }) => state.trace, getTraceSpanIdsAsTree), - createSelector((state: { span: TraceSpanData }) => state.span, getSpanId), - (node, spanID) => node.getPath(spanID)!.length - 1 -); - -export const getTraceServices = createSelector(getTraceProcesses, (processes) => - Object.keys(processes).reduce( - (services, processID) => services.add(getProcessServiceName(processes[processID])), - new Set() - ) -); - -export const getTraceServiceCount = createSelector(getTraceServices, (services) => services.size); - -// establish constants to determine how math should be handled -// for nanosecond-to-millisecond conversions. -export const DURATION_FORMATTERS = { - ms: formatMillisecondTime, - s: formatSecondTime, -}; - -const getDurationFormatterForTrace = createSelector(getTraceDuration, (totalDuration: number) => - totalDuration >= ONE_SECOND ? DURATION_FORMATTERS.s : DURATION_FORMATTERS.ms -); - -export const formatDurationForUnit = createSelector( - ({ duration }: { duration: number }) => duration, - ({ unit }: { unit: 'ms' | 's' }) => DURATION_FORMATTERS[unit], - (duration, formatter) => formatter(duration) -); - -export const formatDurationForTrace = createSelector( - ({ duration }: { duration: number }) => duration, - createSelector(({ trace }: { trace: TraceResponse }) => trace, getDurationFormatterForTrace), - (duration, formatter) => formatter(duration) -); - -export const getSortedSpans = createSelector( - ({ trace }: { trace: TraceResponse }) => trace, - ({ spans }: { spans: TraceSpanData[] }) => spans, - ({ - sort, - }: { - sort: { - dir: number; - comparator: (itemA: number, itemB: number) => number; - selector: (itemA: TraceSpanData, itemB: TraceResponse) => number; - }; - }) => sort, - (trace, spans, { dir, comparator, selector }) => - [...spans].sort((spanA, spanB) => dir * comparator(selector(spanA, trace), selector(spanB, trace))) -); - -export const getTreeSizeForTraceSpan = createSelector( - createSelector((state: { trace: TraceResponse }) => state.trace, getTraceSpanIdsAsTree), - createSelector((state: { span: TraceSpanData }) => state.span, getSpanId), - (tree, spanID) => { - const node = tree.find(spanID); - if (!node) { - return -1; - } - return node.size - 1; - } -); - -export const getTraceName = createSelector( - createSelector( - createSelector(hydrateSpansWithProcesses, getParentSpan), - createStructuredSelector({ - name: getSpanName, - serviceName: getSpanServiceName, - }) - ), - ({ name, serviceName }: { name: string; serviceName: string }) => `${serviceName}: ${name}` -); - export const omitCollapsedSpans = createSelector( ({ spans }: { spans: TraceSpanData[] }) => spans, createSelector(({ trace }: { trace: TraceResponse }) => trace, getTraceSpanIdsAsTree), @@ -229,21 +87,3 @@ export const omitCollapsedSpans = createSelector( return hiddenSpanIds.size > 0 ? spans.filter((span) => !hiddenSpanIds.has(getSpanId(span))) : spans; } ); - -export const DEFAULT_TICK_INTERVAL = 4; -export const DEFAULT_TICK_WIDTH = 3; -export const getTicksForTrace = createSelector( - ({ trace }: { trace: TraceResponse }) => trace, - ({ interval = DEFAULT_TICK_INTERVAL }: { interval?: number }) => interval, - ({ width = DEFAULT_TICK_WIDTH }: { width?: number }) => width, - ( - trace, - interval: number, - width: number - // timestamps will be spaced over the interval, starting from the initial timestamp - ) => - [...Array(interval + 1).keys()].map((num) => ({ - timestamp: getTraceTimestamp(trace) + getTraceDuration(trace) * (num / interval), - width, - })) -); diff --git a/public/app/features/explore/TraceView/components/utils/sort.test.ts b/public/app/features/explore/TraceView/components/utils/sort.test.ts index 1f427425072..470f3b7f0d1 100644 --- a/public/app/features/explore/TraceView/components/utils/sort.test.ts +++ b/public/app/features/explore/TraceView/components/utils/sort.test.ts @@ -36,12 +36,6 @@ it('localeStringComparator() should properly sort a list of strings', () => { ]); }); -it('numberSortComparator() should properly sort a list of numbers', () => { - const arr = [3, -1.1, 4, -1, 9, 4, 2, Infinity, 0, 0]; - - expect(arr.sort(sortUtils.numberSortComparator)).toEqual([-1.1, -1, 0, 0, 2, 3, 4, 4, 9, Infinity]); -}); - it('classNameForSortDir() should return the proper asc classes', () => { expect(sortUtils.classNameForSortDir(1)).toBe('sorted ascending'); }); diff --git a/public/app/features/explore/TraceView/components/utils/sort.ts b/public/app/features/explore/TraceView/components/utils/sort.ts index 5636fdc977f..0618f491a9a 100644 --- a/public/app/features/explore/TraceView/components/utils/sort.ts +++ b/public/app/features/explore/TraceView/components/utils/sort.ts @@ -16,10 +16,6 @@ export function localeStringComparator(itemA: string, itemB: string) { return itemA.localeCompare(itemB); } -export function numberSortComparator(itemA: number, itemB: number) { - return itemA - itemB; -} - export function classNameForSortDir(dir: number) { return `sorted ${dir === 1 ? 'ascending' : 'descending'}`; }