mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Tracing: Specify type of the data frame that is expected for TraceView (#31465)
* Use dataframe API for jeager * Move types around * Fix imports * Simplify the data frame type * Add comment * Move the transform to separate file * Fix logs timestamp * Add/update tests for trace view * Fix lint * Add test to compare old and new format rendering * Fix test imports * Update data source tests
This commit is contained in:
parent
d84cfdbb0f
commit
c113d3ce72
@ -191,7 +191,7 @@ export class MutableDataFrame<T = any> extends FunctionalVector<T> implements Da
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add all properties of the value as fields on the frame
|
* Add values from an object to corresponding fields. Similar to appendRow but does not create new fields.
|
||||||
*/
|
*/
|
||||||
add(value: T) {
|
add(value: T) {
|
||||||
// Will add one value for every field
|
// Will add one value for every field
|
||||||
|
@ -1,94 +1,36 @@
|
|||||||
// Copyright (c) 2017 Uber Technologies, Inc.
|
type TraceKeyValuePair = {
|
||||||
//
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All timestamps are in microseconds
|
|
||||||
*/
|
|
||||||
|
|
||||||
// TODO: Everett Tech Debt: Fix KeyValuePair types
|
|
||||||
export type TraceKeyValuePair = {
|
|
||||||
key: string;
|
key: string;
|
||||||
type?: string;
|
|
||||||
value: any;
|
value: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TraceLink = {
|
type TraceLog = {
|
||||||
url: string;
|
// Millisecond epoch time
|
||||||
text: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TraceLog = {
|
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
fields: TraceKeyValuePair[];
|
fields: TraceKeyValuePair[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TraceProcess = {
|
/**
|
||||||
serviceName: string;
|
* This describes the structure of the dataframe that should be returned from a tracing data source to show trace
|
||||||
tags: TraceKeyValuePair[];
|
* in a TraceView component.
|
||||||
};
|
*/
|
||||||
|
export interface TraceSpanRow {
|
||||||
export type TraceSpanReference = {
|
|
||||||
refType: 'CHILD_OF' | 'FOLLOWS_FROM';
|
|
||||||
// eslint-disable-next-line no-use-before-define
|
|
||||||
span?: TraceSpan | null | undefined;
|
|
||||||
spanID: string;
|
|
||||||
traceID: string;
|
traceID: string;
|
||||||
};
|
|
||||||
|
|
||||||
export type TraceSpanData = {
|
|
||||||
spanID: string;
|
spanID: string;
|
||||||
traceID: string;
|
parentSpanID?: string;
|
||||||
processID: string;
|
|
||||||
operationName: string;
|
operationName: string;
|
||||||
// Times are in microseconds
|
serviceName: string;
|
||||||
|
serviceTags: TraceKeyValuePair[];
|
||||||
|
// Millisecond epoch time
|
||||||
startTime: number;
|
startTime: number;
|
||||||
|
// Milliseconds
|
||||||
duration: number;
|
duration: number;
|
||||||
logs: TraceLog[];
|
logs?: TraceLog[];
|
||||||
|
|
||||||
|
// Note: To mark spen as having error add tag error: true
|
||||||
tags?: TraceKeyValuePair[];
|
tags?: TraceKeyValuePair[];
|
||||||
references?: TraceSpanReference[];
|
warnings?: string[];
|
||||||
warnings?: string[] | null;
|
|
||||||
stackTraces?: string[];
|
stackTraces?: string[];
|
||||||
flags: number;
|
|
||||||
|
// Specify custom color of the error icon
|
||||||
errorIconColor?: string;
|
errorIconColor?: string;
|
||||||
};
|
}
|
||||||
|
|
||||||
export type TraceSpan = TraceSpanData & {
|
|
||||||
depth: number;
|
|
||||||
hasChildren: boolean;
|
|
||||||
process: TraceProcess;
|
|
||||||
relativeStartTime: number;
|
|
||||||
tags: NonNullable<TraceSpanData['tags']>;
|
|
||||||
references: NonNullable<TraceSpanData['references']>;
|
|
||||||
warnings: NonNullable<TraceSpanData['warnings']>;
|
|
||||||
subsidiarilyReferencedBy: TraceSpanReference[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TraceData = {
|
|
||||||
processes: Record<string, TraceProcess>;
|
|
||||||
traceID: string;
|
|
||||||
warnings?: string[] | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TraceViewData = TraceData & {
|
|
||||||
spans: TraceSpanData[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Trace = TraceData & {
|
|
||||||
duration: number;
|
|
||||||
endTime: number;
|
|
||||||
spans: TraceSpan[];
|
|
||||||
startTime: number;
|
|
||||||
traceName: string;
|
|
||||||
services: Array<{ name: string; numberOfSpans: number }>;
|
|
||||||
};
|
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { TNil } from './types';
|
import { TNil } from './types';
|
||||||
import { TraceSpan, TraceSpanReference, Trace } from '@grafana/data';
|
import { TraceSpan, TraceSpanReference, Trace } from './types/trace';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `Accessors` is necessary because `ScrollManager` needs to be created by
|
* `Accessors` is necessary because `ScrollManager` needs to be created by
|
||||||
|
@ -19,7 +19,7 @@ import CanvasSpanGraph from './CanvasSpanGraph';
|
|||||||
import TickLabels from './TickLabels';
|
import TickLabels from './TickLabels';
|
||||||
import ViewingLayer from './ViewingLayer';
|
import ViewingLayer from './ViewingLayer';
|
||||||
import { TUpdateViewRangeTimeFunction, ViewRange, ViewRangeTimeUpdate } from '../..';
|
import { TUpdateViewRangeTimeFunction, ViewRange, ViewRangeTimeUpdate } from '../..';
|
||||||
import { TraceSpan, Trace } from '@grafana/data';
|
import { TraceSpan, Trace } from '../../types/trace';
|
||||||
import { ubPb2, ubPx2, ubRelative } from '../../uberUtilityStyles';
|
import { ubPb2, ubPx2, ubRelative } from '../../uberUtilityStyles';
|
||||||
|
|
||||||
const DEFAULT_HEIGHT = 60;
|
const DEFAULT_HEIGHT = 60;
|
||||||
|
@ -27,7 +27,7 @@ import LabeledList from '../common/LabeledList';
|
|||||||
import TraceName from '../common/TraceName';
|
import TraceName from '../common/TraceName';
|
||||||
import { getTraceName } from '../model/trace-viewer';
|
import { getTraceName } from '../model/trace-viewer';
|
||||||
import { TNil } from '../types';
|
import { TNil } from '../types';
|
||||||
import { Trace } from '@grafana/data';
|
import { Trace } from '../types/trace';
|
||||||
import { formatDatetime, formatDuration } from '../utils/date';
|
import { formatDatetime, formatDuration } from '../utils/date';
|
||||||
import { getTraceLinks } from '../model/link-patterns';
|
import { getTraceLinks } from '../model/link-patterns';
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { css } from 'emotion';
|
import { css } from 'emotion';
|
||||||
import NewWindowIcon from '../common/NewWindowIcon';
|
import NewWindowIcon from '../common/NewWindowIcon';
|
||||||
import { TraceSpanReference } from '@grafana/data';
|
import { TraceSpanReference } from '../types/trace';
|
||||||
import { UITooltip, UIDropdown, UIMenuItem, UIMenu, TooltipPlacement } from '../uiElementsContext';
|
import { UITooltip, UIDropdown, UIMenuItem, UIMenu, TooltipPlacement } from '../uiElementsContext';
|
||||||
|
|
||||||
import ReferenceLink from '../url/ReferenceLink';
|
import ReferenceLink from '../url/ReferenceLink';
|
||||||
|
@ -12,13 +12,13 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
import { TraceSpan } from '@grafana/data';
|
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import { css } from 'emotion';
|
import { css } from 'emotion';
|
||||||
import _groupBy from 'lodash/groupBy';
|
import _groupBy from 'lodash/groupBy';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { compose, onlyUpdateForKeys, withProps, withState } from 'recompose';
|
import { compose, onlyUpdateForKeys, withProps, withState } from 'recompose';
|
||||||
import { autoColor, createStyle, Theme } from '../Theme';
|
import { autoColor, createStyle, Theme } from '../Theme';
|
||||||
|
import { TraceSpan } from '../types/trace';
|
||||||
import { TNil } from '../types';
|
import { TNil } from '../types';
|
||||||
import { UIPopover } from '../uiElementsContext';
|
import { UIPopover } from '../uiElementsContext';
|
||||||
import AccordianLogs from './SpanDetail/AccordianLogs';
|
import AccordianLogs from './SpanDetail/AccordianLogs';
|
||||||
|
@ -28,7 +28,7 @@ import SpanBar from './SpanBar';
|
|||||||
import Ticks from './Ticks';
|
import Ticks from './Ticks';
|
||||||
|
|
||||||
import { TNil } from '../types';
|
import { TNil } from '../types';
|
||||||
import { TraceSpan } from '@grafana/data';
|
import { TraceSpan } from '../types/trace';
|
||||||
import { autoColor, createStyle, Theme, withTheme } from '../Theme';
|
import { autoColor, createStyle, Theme, withTheme } from '../Theme';
|
||||||
|
|
||||||
const getStyles = createStyle((theme: Theme) => {
|
const getStyles = createStyle((theme: Theme) => {
|
||||||
|
@ -21,7 +21,7 @@ import cx from 'classnames';
|
|||||||
import * as markers from './AccordianKeyValues.markers';
|
import * as markers from './AccordianKeyValues.markers';
|
||||||
import KeyValuesTable from './KeyValuesTable';
|
import KeyValuesTable from './KeyValuesTable';
|
||||||
import { TNil } from '../../types';
|
import { TNil } from '../../types';
|
||||||
import { TraceKeyValuePair, TraceLink } from '@grafana/data';
|
import { TraceKeyValuePair, TraceLink } from '../../types/trace';
|
||||||
import { autoColor, createStyle, Theme, useTheme } from '../../Theme';
|
import { autoColor, createStyle, Theme, useTheme } from '../../Theme';
|
||||||
import { uAlignIcon, uTxEllipsis } from '../../uberUtilityStyles';
|
import { uAlignIcon, uTxEllipsis } from '../../uberUtilityStyles';
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ import { css } from 'emotion';
|
|||||||
import AccordianKeyValues from './AccordianKeyValues';
|
import AccordianKeyValues from './AccordianKeyValues';
|
||||||
import { formatDuration } from '../utils';
|
import { formatDuration } from '../utils';
|
||||||
import { TNil } from '../../types';
|
import { TNil } from '../../types';
|
||||||
import { TraceLog, TraceKeyValuePair, TraceLink } from '@grafana/data';
|
import { TraceLog, TraceKeyValuePair, TraceLink } from '../../types/trace';
|
||||||
import { autoColor, createStyle, Theme, useTheme } from '../../Theme';
|
import { autoColor, createStyle, Theme, useTheme } from '../../Theme';
|
||||||
import { uAlignIcon, ubMb1 } from '../../uberUtilityStyles';
|
import { uAlignIcon, ubMb1 } from '../../uberUtilityStyles';
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ import cx from 'classnames';
|
|||||||
|
|
||||||
import IoIosArrowDown from 'react-icons/lib/io/ios-arrow-down';
|
import IoIosArrowDown from 'react-icons/lib/io/ios-arrow-down';
|
||||||
import IoIosArrowRight from 'react-icons/lib/io/ios-arrow-right';
|
import IoIosArrowRight from 'react-icons/lib/io/ios-arrow-right';
|
||||||
import { TraceSpanReference } from '@grafana/data';
|
import { TraceSpanReference } from '../../types/trace';
|
||||||
import ReferenceLink from '../../url/ReferenceLink';
|
import ReferenceLink from '../../url/ReferenceLink';
|
||||||
|
|
||||||
import { createStyle } from '../../Theme';
|
import { createStyle } from '../../Theme';
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
import { TraceLog } from '@grafana/data';
|
import { TraceLog } from '../../types/trace';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Which items of a {@link SpanDetail} component are expanded.
|
* Which items of a {@link SpanDetail} component are expanded.
|
||||||
|
@ -20,7 +20,7 @@ import cx from 'classnames';
|
|||||||
import CopyIcon from '../../common/CopyIcon';
|
import CopyIcon from '../../common/CopyIcon';
|
||||||
|
|
||||||
import { TNil } from '../../types';
|
import { TNil } from '../../types';
|
||||||
import { TraceKeyValuePair, TraceLink } from '@grafana/data';
|
import { TraceKeyValuePair, TraceLink } from '../../types/trace';
|
||||||
import { UIDropdown, UIIcon, UIMenu, UIMenuItem } from '../../uiElementsContext';
|
import { UIDropdown, UIIcon, UIMenu, UIMenuItem } from '../../uiElementsContext';
|
||||||
import { autoColor, createStyle, Theme, useTheme } from '../../Theme';
|
import { autoColor, createStyle, Theme, useTheme } from '../../Theme';
|
||||||
import { ubInlineBlock, uWidth100 } from '../../uberUtilityStyles';
|
import { ubInlineBlock, uWidth100 } from '../../uberUtilityStyles';
|
||||||
|
@ -25,7 +25,7 @@ import CopyIcon from '../../common/CopyIcon';
|
|||||||
import LabeledList from '../../common/LabeledList';
|
import LabeledList from '../../common/LabeledList';
|
||||||
|
|
||||||
import { TNil } from '../../types';
|
import { TNil } from '../../types';
|
||||||
import { TraceKeyValuePair, TraceLink, TraceLog, TraceSpan } from '@grafana/data';
|
import { TraceKeyValuePair, TraceLink, TraceLog, TraceSpan } from '../../types/trace';
|
||||||
import AccordianReferences from './AccordianReferences';
|
import AccordianReferences from './AccordianReferences';
|
||||||
import { autoColor, createStyle, Theme, useTheme } from '../../Theme';
|
import { autoColor, createStyle, Theme, useTheme } from '../../Theme';
|
||||||
import { UIDivider } from '../../uiElementsContext';
|
import { UIDivider } from '../../uiElementsContext';
|
||||||
|
@ -21,7 +21,7 @@ import SpanTreeOffset from './SpanTreeOffset';
|
|||||||
import TimelineRow from './TimelineRow';
|
import TimelineRow from './TimelineRow';
|
||||||
import { autoColor, createStyle, Theme, withTheme } from '../Theme';
|
import { autoColor, createStyle, Theme, withTheme } from '../Theme';
|
||||||
|
|
||||||
import { TraceLog, TraceSpan, TraceKeyValuePair, TraceLink } from '@grafana/data';
|
import { TraceLog, TraceSpan, TraceKeyValuePair, TraceLink } from '../types/trace';
|
||||||
import { CreateSpanLink } from './types';
|
import { CreateSpanLink } from './types';
|
||||||
|
|
||||||
const getStyles = createStyle((theme: Theme) => {
|
const getStyles = createStyle((theme: Theme) => {
|
||||||
|
@ -19,7 +19,7 @@ import IoIosArrowDown from 'react-icons/lib/io/ios-arrow-down';
|
|||||||
import { css } from 'emotion';
|
import { css } from 'emotion';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
|
|
||||||
import { TraceSpan } from '@grafana/data';
|
import { TraceSpan } from '../types/trace';
|
||||||
import spanAncestorIds from '../utils/span-ancestor-ids';
|
import spanAncestorIds from '../utils/span-ancestor-ids';
|
||||||
|
|
||||||
import { autoColor, createStyle, Theme, withTheme } from '../Theme';
|
import { autoColor, createStyle, Theme, withTheme } from '../Theme';
|
||||||
|
@ -29,7 +29,7 @@ import {
|
|||||||
import { Accessors } from '../ScrollManager';
|
import { Accessors } from '../ScrollManager';
|
||||||
import { getColorByKey } from '../utils/color-generator';
|
import { getColorByKey } from '../utils/color-generator';
|
||||||
import { TNil } from '../types';
|
import { TNil } from '../types';
|
||||||
import { TraceLog, TraceSpan, Trace, TraceKeyValuePair, TraceLink } from '@grafana/data';
|
import { TraceLog, TraceSpan, Trace, TraceKeyValuePair, TraceLink } from '../types/trace';
|
||||||
import TTraceTimeline from '../types/TTraceTimeline';
|
import TTraceTimeline from '../types/TTraceTimeline';
|
||||||
|
|
||||||
import { createStyle, Theme, withTheme } from '../Theme';
|
import { createStyle, Theme, withTheme } from '../Theme';
|
||||||
|
@ -21,7 +21,7 @@ import { merge as mergeShortcuts } from '../keyboard-shortcuts';
|
|||||||
import { Accessors } from '../ScrollManager';
|
import { Accessors } from '../ScrollManager';
|
||||||
import { TUpdateViewRangeTimeFunction, ViewRange, ViewRangeTimeUpdate } from './types';
|
import { TUpdateViewRangeTimeFunction, ViewRange, ViewRangeTimeUpdate } from './types';
|
||||||
import { TNil } from '../types';
|
import { TNil } from '../types';
|
||||||
import { TraceSpan, Trace, TraceLog, TraceKeyValuePair, TraceLink } from '@grafana/data';
|
import { TraceSpan, Trace, TraceLog, TraceKeyValuePair, TraceLink } from '../types/trace';
|
||||||
import TTraceTimeline from '../types/TTraceTimeline';
|
import TTraceTimeline from '../types/TTraceTimeline';
|
||||||
import { autoColor, createStyle, Theme, withTheme } from '../Theme';
|
import { autoColor, createStyle, Theme, withTheme } from '../Theme';
|
||||||
import ExternalLinkContext from '../url/externalLinkContext';
|
import ExternalLinkContext from '../url/externalLinkContext';
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
import { TraceSpan } from '@grafana/data';
|
import { TraceSpan } from '../types/trace';
|
||||||
import { TNil } from '../types';
|
import { TNil } from '../types';
|
||||||
|
|
||||||
interface TimeCursorUpdate {
|
interface TimeCursorUpdate {
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
import { TraceSpan } from '@grafana/data';
|
import { TraceSpan } from '../types/trace';
|
||||||
|
|
||||||
export type ViewedBoundsFunctionType = (start: number, end: number) => { start: number; end: number };
|
export type ViewedBoundsFunctionType = (start: number, end: number) => { start: number; end: number };
|
||||||
/**
|
/**
|
||||||
|
@ -17,7 +17,7 @@ import memoize from 'lru-memoize';
|
|||||||
import { getConfigValue } from '../utils/config/get-config';
|
import { getConfigValue } from '../utils/config/get-config';
|
||||||
import { getParent } from './span';
|
import { getParent } from './span';
|
||||||
import { TNil } from '../types';
|
import { TNil } from '../types';
|
||||||
import { TraceSpan, TraceLink, TraceKeyValuePair, Trace } from '@grafana/data';
|
import { TraceSpan, TraceLink, TraceKeyValuePair, Trace } from '../types/trace';
|
||||||
|
|
||||||
const parameterRegExp = /#\{([^{}]*)\}/g;
|
const parameterRegExp = /#\{([^{}]*)\}/g;
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
import { TraceSpan } from '@grafana/data';
|
import { TraceSpan } from '../types/trace';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Searches the span.references to find 'CHILD_OF' reference type or returns null.
|
* Searches the span.references to find 'CHILD_OF' reference type or returns null.
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
import { TraceSpan } from '@grafana/data';
|
import { TraceSpan } from '../types/trace';
|
||||||
|
|
||||||
export function getTraceName(spans: TraceSpan[]): string {
|
export function getTraceName(spans: TraceSpan[]): string {
|
||||||
const span = spans.filter((sp) => !sp.references || !sp.references.length)[0];
|
const span = spans.filter((sp) => !sp.references || !sp.references.length)[0];
|
||||||
|
@ -17,7 +17,7 @@ import _isEqual from 'lodash/isEqual';
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { getTraceSpanIdsAsTree } from '../selectors/trace';
|
import { getTraceSpanIdsAsTree } from '../selectors/trace';
|
||||||
import { getConfigValue } from '../utils/config/get-config';
|
import { getConfigValue } from '../utils/config/get-config';
|
||||||
import { TraceKeyValuePair, TraceSpan, Trace, TraceViewData } from '@grafana/data';
|
import { TraceKeyValuePair, TraceSpan, Trace, TraceResponse } from '../types/trace';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import TreeNode from '../utils/TreeNode';
|
import TreeNode from '../utils/TreeNode';
|
||||||
|
|
||||||
@ -71,7 +71,7 @@ export function orderTags(spanTags: TraceKeyValuePair[], topPrefixes?: string[])
|
|||||||
* NOTE: Mutates `data` - Transform the HTTP response data into the form the app
|
* NOTE: Mutates `data` - Transform the HTTP response data into the form the app
|
||||||
* generally requires.
|
* generally requires.
|
||||||
*/
|
*/
|
||||||
export default function transformTraceData(data: TraceViewData | undefined): Trace | null {
|
export default function transformTraceData(data: TraceResponse | undefined): Trace | null {
|
||||||
if (!data?.traceID) {
|
if (!data?.traceID) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,9 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { ApiError } from './api-error';
|
import { ApiError } from './api-error';
|
||||||
import { Trace } from '@grafana/data';
|
import { Trace } from './trace';
|
||||||
|
|
||||||
|
export { TraceSpan, TraceResponse, Trace, TraceProcess, TraceKeyValuePair, TraceLink } from './trace';
|
||||||
|
|
||||||
export { default as TTraceTimeline } from './TTraceTimeline';
|
export { default as TTraceTimeline } from './TTraceTimeline';
|
||||||
export { default as TNil } from './TNil';
|
export { default as TNil } from './TNil';
|
||||||
|
94
packages/jaeger-ui-components/src/types/trace.ts
Normal file
94
packages/jaeger-ui-components/src/types/trace.ts
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All timestamps are in microseconds
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO: Everett Tech Debt: Fix KeyValuePair types
|
||||||
|
export type TraceKeyValuePair = {
|
||||||
|
key: string;
|
||||||
|
type?: string;
|
||||||
|
value: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TraceLink = {
|
||||||
|
url: string;
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TraceLog = {
|
||||||
|
timestamp: number;
|
||||||
|
fields: TraceKeyValuePair[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TraceProcess = {
|
||||||
|
serviceName: string;
|
||||||
|
tags: TraceKeyValuePair[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TraceSpanReference = {
|
||||||
|
refType: 'CHILD_OF' | 'FOLLOWS_FROM';
|
||||||
|
// eslint-disable-next-line no-use-before-define
|
||||||
|
span?: TraceSpan | null | undefined;
|
||||||
|
spanID: string;
|
||||||
|
traceID: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TraceSpanData = {
|
||||||
|
spanID: string;
|
||||||
|
traceID: string;
|
||||||
|
processID: string;
|
||||||
|
operationName: string;
|
||||||
|
// Times are in microseconds
|
||||||
|
startTime: number;
|
||||||
|
duration: number;
|
||||||
|
logs: TraceLog[];
|
||||||
|
tags?: TraceKeyValuePair[];
|
||||||
|
references?: TraceSpanReference[];
|
||||||
|
warnings?: string[] | null;
|
||||||
|
stackTraces?: string[];
|
||||||
|
flags: number;
|
||||||
|
errorIconColor?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TraceSpan = TraceSpanData & {
|
||||||
|
depth: number;
|
||||||
|
hasChildren: boolean;
|
||||||
|
process: TraceProcess;
|
||||||
|
relativeStartTime: number;
|
||||||
|
tags: NonNullable<TraceSpanData['tags']>;
|
||||||
|
references: NonNullable<TraceSpanData['references']>;
|
||||||
|
warnings: NonNullable<TraceSpanData['warnings']>;
|
||||||
|
subsidiarilyReferencedBy: TraceSpanReference[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TraceData = {
|
||||||
|
processes: Record<string, TraceProcess>;
|
||||||
|
traceID: string;
|
||||||
|
warnings?: string[] | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TraceResponse = TraceData & {
|
||||||
|
spans: TraceSpanData[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Trace = TraceData & {
|
||||||
|
duration: number;
|
||||||
|
endTime: number;
|
||||||
|
spans: TraceSpan[];
|
||||||
|
startTime: number;
|
||||||
|
traceName: string;
|
||||||
|
services: Array<{ name: string; numberOfSpans: number }>;
|
||||||
|
};
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { TraceSpanReference } from '@grafana/data';
|
import { TraceSpanReference } from '../types/trace';
|
||||||
import ExternalLinkContext from './externalLinkContext';
|
import ExternalLinkContext from './externalLinkContext';
|
||||||
|
|
||||||
type ReferenceLinkProps = {
|
type ReferenceLinkProps = {
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
import { TraceKeyValuePair, TraceSpan } from '@grafana/data';
|
import { TraceKeyValuePair, TraceSpan } from '../types/trace';
|
||||||
import { TNil } from '../types';
|
import { TNil } from '../types';
|
||||||
|
|
||||||
export default function filterSpans(textFilter: string, spans: TraceSpan[] | TNil) {
|
export default function filterSpans(textFilter: string, spans: TraceSpan[] | TNil) {
|
||||||
|
@ -16,7 +16,7 @@ import _find from 'lodash/find';
|
|||||||
import _get from 'lodash/get';
|
import _get from 'lodash/get';
|
||||||
|
|
||||||
import { TNil } from '../types';
|
import { TNil } from '../types';
|
||||||
import { TraceSpan } from '@grafana/data';
|
import { TraceSpan } from '../types/trace';
|
||||||
|
|
||||||
function getFirstAncestor(span: TraceSpan): TraceSpan | TNil {
|
function getFirstAncestor(span: TraceSpan): TraceSpan | TNil {
|
||||||
return _get(
|
return _get(
|
||||||
|
@ -17,7 +17,6 @@ import {
|
|||||||
RawTimeRange,
|
RawTimeRange,
|
||||||
TimeZone,
|
TimeZone,
|
||||||
LogsModel,
|
LogsModel,
|
||||||
TraceViewData,
|
|
||||||
DataFrame,
|
DataFrame,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
|
|
||||||
@ -292,15 +291,8 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
const dataFrames = queryResponse.series.filter((series) => series.meta?.preferredVisualisationType === 'trace');
|
const dataFrames = queryResponse.series.filter((series) => series.meta?.preferredVisualisationType === 'trace');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// We expect only one trace at the moment to be in the dataframe
|
|
||||||
// If there is no data (like 404) we show a separate error so no need to show anything here
|
// If there is no data (like 404) we show a separate error so no need to show anything here
|
||||||
dataFrames[0] && (
|
dataFrames.length && <TraceView exploreId={exploreId} dataFrames={dataFrames} splitOpenFn={splitOpen} />
|
||||||
<TraceView
|
|
||||||
exploreId={exploreId}
|
|
||||||
trace={dataFrames[0].fields[0].values.get(0) as TraceViewData | undefined}
|
|
||||||
splitOpenFn={splitOpen}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,18 +1,28 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import { render } from '@testing-library/react';
|
import { render, prettyDOM } from '@testing-library/react';
|
||||||
import { TraceView } from './TraceView';
|
import { TraceView } from './TraceView';
|
||||||
import { TracePageHeader, TraceTimelineViewer } from '@jaegertracing/jaeger-ui-components';
|
import { TracePageHeader, TraceTimelineViewer } from '@jaegertracing/jaeger-ui-components';
|
||||||
import { TraceSpanData, TraceData } from '@grafana/data';
|
|
||||||
import { setDataSourceSrv } from '@grafana/runtime';
|
import { setDataSourceSrv } from '@grafana/runtime';
|
||||||
import { ExploreId } from 'app/types';
|
import { ExploreId } from 'app/types';
|
||||||
|
import { TraceData, TraceSpanData } from '@jaegertracing/jaeger-ui-components/src/types/trace';
|
||||||
|
import { MutableDataFrame } from '@grafana/data';
|
||||||
|
|
||||||
jest.mock('react-redux', () => ({
|
jest.mock('react-redux', () => ({
|
||||||
useSelector: jest.fn(() => undefined),
|
useSelector: jest.fn(() => undefined),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function renderTraceView() {
|
function renderTraceView() {
|
||||||
const wrapper = shallow(<TraceView exploreId={ExploreId.left} trace={response} splitOpenFn={() => {}} />);
|
const wrapper = shallow(<TraceView exploreId={ExploreId.left} dataFrames={[frameOld]} splitOpenFn={() => {}} />);
|
||||||
|
return {
|
||||||
|
timeline: wrapper.find(TraceTimelineViewer),
|
||||||
|
header: wrapper.find(TracePageHeader),
|
||||||
|
wrapper,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTraceViewNew() {
|
||||||
|
const wrapper = shallow(<TraceView exploreId={ExploreId.left} dataFrames={[frameNew]} splitOpenFn={() => {}} />);
|
||||||
return {
|
return {
|
||||||
timeline: wrapper.find(TraceTimelineViewer),
|
timeline: wrapper.find(TraceTimelineViewer),
|
||||||
header: wrapper.find(TracePageHeader),
|
header: wrapper.find(TracePageHeader),
|
||||||
@ -35,15 +45,30 @@ describe('TraceView', () => {
|
|||||||
expect(header).toHaveLength(1);
|
expect(header).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders TraceTimelineViewer with new format', () => {
|
||||||
|
const { timeline, header } = renderTraceViewNew();
|
||||||
|
expect(timeline).toHaveLength(1);
|
||||||
|
expect(header).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders renders the same for old and new format', () => {
|
||||||
|
const { baseElement } = render(
|
||||||
|
<TraceView exploreId={ExploreId.left} dataFrames={[frameNew]} splitOpenFn={() => {}} />
|
||||||
|
);
|
||||||
|
const { baseElement: baseElementOld } = render(
|
||||||
|
<TraceView exploreId={ExploreId.left} dataFrames={[frameOld]} splitOpenFn={() => {}} />
|
||||||
|
);
|
||||||
|
expect(prettyDOM(baseElement)).toEqual(prettyDOM(baseElementOld));
|
||||||
|
});
|
||||||
|
|
||||||
it('does not render anything on missing trace', () => {
|
it('does not render anything on missing trace', () => {
|
||||||
// Simulating Explore's access to empty response data
|
// Simulating Explore's access to empty response data
|
||||||
const trace = [][0];
|
const { container } = render(<TraceView exploreId={ExploreId.left} dataFrames={[]} splitOpenFn={() => {}} />);
|
||||||
const { container } = render(<TraceView exploreId={ExploreId.left} trace={trace} splitOpenFn={() => {}} />);
|
|
||||||
expect(container.hasChildNodes()).toBeFalsy();
|
expect(container.hasChildNodes()).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('toggles detailState', () => {
|
it('toggles detailState', () => {
|
||||||
let { timeline, wrapper } = renderTraceView();
|
let { timeline, wrapper } = renderTraceViewNew();
|
||||||
expect(timeline.props().traceTimeline.detailStates.size).toBe(0);
|
expect(timeline.props().traceTimeline.detailStates.size).toBe(0);
|
||||||
|
|
||||||
timeline.props().detailToggle('1');
|
timeline.props().detailToggle('1');
|
||||||
@ -57,7 +82,7 @@ describe('TraceView', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('toggles children visibility', () => {
|
it('toggles children visibility', () => {
|
||||||
let { timeline, wrapper } = renderTraceView();
|
let { timeline, wrapper } = renderTraceViewNew();
|
||||||
expect(timeline.props().traceTimeline.childrenHiddenIDs.size).toBe(0);
|
expect(timeline.props().traceTimeline.childrenHiddenIDs.size).toBe(0);
|
||||||
|
|
||||||
timeline.props().childrenToggle('1');
|
timeline.props().childrenToggle('1');
|
||||||
@ -71,7 +96,7 @@ describe('TraceView', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('toggles adds and removes hover indent guides', () => {
|
it('toggles adds and removes hover indent guides', () => {
|
||||||
let { timeline, wrapper } = renderTraceView();
|
let { timeline, wrapper } = renderTraceViewNew();
|
||||||
expect(timeline.props().traceTimeline.hoverIndentGuideIds.size).toBe(0);
|
expect(timeline.props().traceTimeline.hoverIndentGuideIds.size).toBe(0);
|
||||||
|
|
||||||
timeline.props().addHoverIndentGuideId('1');
|
timeline.props().addHoverIndentGuideId('1');
|
||||||
@ -85,7 +110,7 @@ describe('TraceView', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('toggles collapses and expands one level of spans', () => {
|
it('toggles collapses and expands one level of spans', () => {
|
||||||
let { timeline, wrapper } = renderTraceView();
|
let { timeline, wrapper } = renderTraceViewNew();
|
||||||
expect(timeline.props().traceTimeline.childrenHiddenIDs.size).toBe(0);
|
expect(timeline.props().traceTimeline.childrenHiddenIDs.size).toBe(0);
|
||||||
const spans = timeline.props().trace.spans;
|
const spans = timeline.props().trace.spans;
|
||||||
|
|
||||||
@ -100,7 +125,7 @@ describe('TraceView', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('toggles collapses and expands all levels', () => {
|
it('toggles collapses and expands all levels', () => {
|
||||||
let { timeline, wrapper } = renderTraceView();
|
let { timeline, wrapper } = renderTraceViewNew();
|
||||||
expect(timeline.props().traceTimeline.childrenHiddenIDs.size).toBe(0);
|
expect(timeline.props().traceTimeline.childrenHiddenIDs.size).toBe(0);
|
||||||
const spans = timeline.props().trace.spans;
|
const spans = timeline.props().trace.spans;
|
||||||
|
|
||||||
@ -116,7 +141,7 @@ describe('TraceView', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('searches for spans', () => {
|
it('searches for spans', () => {
|
||||||
let { wrapper, header } = renderTraceView();
|
let { wrapper, header } = renderTraceViewNew();
|
||||||
header.props().onSearchValueChange('HTTP POST - api_prom_push');
|
header.props().onSearchValueChange('HTTP POST - api_prom_push');
|
||||||
|
|
||||||
const timeline = wrapper.find(TraceTimelineViewer);
|
const timeline = wrapper.find(TraceTimelineViewer);
|
||||||
@ -124,7 +149,7 @@ describe('TraceView', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('change viewRange', () => {
|
it('change viewRange', () => {
|
||||||
let { header, timeline, wrapper } = renderTraceView();
|
let { header, timeline, wrapper } = renderTraceViewNew();
|
||||||
const defaultRange = { time: { current: [0, 1] } };
|
const defaultRange = { time: { current: [0, 1] } };
|
||||||
expect(timeline.props().viewRange).toEqual(defaultRange);
|
expect(timeline.props().viewRange).toEqual(defaultRange);
|
||||||
expect(header.props().viewRange).toEqual(defaultRange);
|
expect(header.props().viewRange).toEqual(defaultRange);
|
||||||
@ -237,3 +262,107 @@ const response: TraceData & { spans: TraceSpanData[] } = {
|
|||||||
},
|
},
|
||||||
warnings: null as any,
|
warnings: null as any,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const frameOld = new MutableDataFrame({
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'trace',
|
||||||
|
values: [response],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
preferredVisualisationType: 'trace',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const frameNew = new MutableDataFrame({
|
||||||
|
fields: [
|
||||||
|
{ name: 'traceID', values: ['1ed38015486087ca', '1ed38015486087ca', '1ed38015486087ca'] },
|
||||||
|
{ name: 'spanID', values: ['1ed38015486087ca', '3fb050342773d333', '35118c298fc91f68'] },
|
||||||
|
{ name: 'parentSpanID', values: [undefined, '1ed38015486087ca', '3fb050342773d333'] },
|
||||||
|
{ name: 'operationName', values: ['HTTP POST - api_prom_push', '/logproto.Pusher/Push', '/logproto.Pusher/Push'] },
|
||||||
|
{ name: 'serviceName', values: ['loki-all', 'loki-all', 'loki-all'] },
|
||||||
|
{
|
||||||
|
name: 'serviceTags',
|
||||||
|
values: [
|
||||||
|
[
|
||||||
|
{ key: 'client-uuid', value: '2a59d08899ef6a8a' },
|
||||||
|
{ key: 'hostname', value: '0080b530fae3' },
|
||||||
|
{ key: 'ip', value: '172.18.0.6' },
|
||||||
|
{ key: 'jaeger.version', value: 'Go-2.20.1' },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ key: 'client-uuid', value: '2a59d08899ef6a8a' },
|
||||||
|
{ key: 'hostname', value: '0080b530fae3' },
|
||||||
|
{ key: 'ip', value: '172.18.0.6' },
|
||||||
|
{ key: 'jaeger.version', value: 'Go-2.20.1' },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ key: 'client-uuid', value: '2a59d08899ef6a8a' },
|
||||||
|
{ key: 'hostname', value: '0080b530fae3' },
|
||||||
|
{ key: 'ip', value: '172.18.0.6' },
|
||||||
|
{ key: 'jaeger.version', value: 'Go-2.20.1' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ name: 'startTime', values: [1585244579835.187, 1585244579835.341, 1585244579836.04] },
|
||||||
|
{ name: 'duration', values: [1.098, 0.921, 0.036] },
|
||||||
|
{
|
||||||
|
name: 'logs',
|
||||||
|
values: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
timestamp: 1585244579835.229,
|
||||||
|
fields: [{ key: 'event', value: 'util.ParseProtoRequest[start reading]' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamp: 1585244579835.241,
|
||||||
|
fields: [
|
||||||
|
{ key: 'event', value: 'util.ParseProtoRequest[decompress]' },
|
||||||
|
{ key: 'size', value: 315 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamp: 1585244579835.245,
|
||||||
|
fields: [
|
||||||
|
{ key: 'event', value: 'util.ParseProtoRequest[unmarshal]' },
|
||||||
|
{ key: 'size', value: 446 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tags',
|
||||||
|
values: [
|
||||||
|
[
|
||||||
|
{ key: 'sampler.type', value: 'const' },
|
||||||
|
{ key: 'sampler.param', value: true },
|
||||||
|
{ key: 'span.kind', value: 'server' },
|
||||||
|
{ key: 'http.method', value: 'POST' },
|
||||||
|
{ key: 'http.url', value: '/api/prom/push' },
|
||||||
|
{ key: 'component', value: 'net/http' },
|
||||||
|
{ key: 'http.status_code', value: 204 },
|
||||||
|
{ key: 'internal.span.format', value: 'proto' },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ key: 'span.kind', value: 'client' },
|
||||||
|
{ key: 'component', value: 'gRPC' },
|
||||||
|
{ key: 'internal.span.format', value: 'proto' },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ key: 'span.kind', value: 'server' },
|
||||||
|
{ key: 'component', value: 'gRPC' },
|
||||||
|
{ key: 'internal.span.format', value: 'proto' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ name: 'warnings', values: [undefined, undefined] },
|
||||||
|
{ name: 'stackTraces', values: [undefined, undefined] },
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
preferredVisualisationType: 'trace',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
@ -3,7 +3,13 @@ import {
|
|||||||
ThemeOptions,
|
ThemeOptions,
|
||||||
ThemeProvider,
|
ThemeProvider,
|
||||||
ThemeType,
|
ThemeType,
|
||||||
|
Trace,
|
||||||
|
TraceKeyValuePair,
|
||||||
|
TraceLink,
|
||||||
TracePageHeader,
|
TracePageHeader,
|
||||||
|
TraceProcess,
|
||||||
|
TraceResponse,
|
||||||
|
TraceSpan,
|
||||||
TraceTimelineViewer,
|
TraceTimelineViewer,
|
||||||
transformTraceData,
|
transformTraceData,
|
||||||
TTraceTimeline,
|
TTraceTimeline,
|
||||||
@ -16,7 +22,7 @@ import { useChildrenState } from './useChildrenState';
|
|||||||
import { useDetailState } from './useDetailState';
|
import { useDetailState } from './useDetailState';
|
||||||
import { useHoverIndentGuide } from './useHoverIndentGuide';
|
import { useHoverIndentGuide } from './useHoverIndentGuide';
|
||||||
import { colors, useTheme } from '@grafana/ui';
|
import { colors, useTheme } from '@grafana/ui';
|
||||||
import { TraceViewData, Trace, TraceSpan, TraceKeyValuePair, TraceLink } from '@grafana/data';
|
import { DataFrame, DataFrameView, TraceSpanRow } from '@grafana/data';
|
||||||
import { createSpanLinkFactory } from './createSpanLink';
|
import { createSpanLinkFactory } from './createSpanLink';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { StoreState } from 'app/types';
|
import { StoreState } from 'app/types';
|
||||||
@ -25,13 +31,13 @@ import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
|||||||
import { TraceToLogsData } from 'app/core/components/TraceToLogsSettings';
|
import { TraceToLogsData } from 'app/core/components/TraceToLogsSettings';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
trace?: TraceViewData;
|
dataFrames: DataFrame[];
|
||||||
splitOpenFn: SplitOpen;
|
splitOpenFn: SplitOpen;
|
||||||
exploreId: ExploreId;
|
exploreId: ExploreId;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function TraceView(props: Props) {
|
export function TraceView(props: Props) {
|
||||||
if (!props.trace?.traceID) {
|
if (!props.dataFrames.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { expandOne, collapseOne, childrenToggle, collapseAll, childrenHiddenIDs, expandAll } = useChildrenState();
|
const { expandOne, collapseOne, childrenToggle, collapseAll, childrenHiddenIDs, expandAll } = useChildrenState();
|
||||||
@ -58,7 +64,7 @@ export function TraceView(props: Props) {
|
|||||||
*/
|
*/
|
||||||
const [slim, setSlim] = useState(false);
|
const [slim, setSlim] = useState(false);
|
||||||
|
|
||||||
const traceProp = useMemo(() => transformTraceData(props.trace), [props.trace]);
|
const traceProp = useMemo(() => transformDataFrames(props.dataFrames), [props.dataFrames]);
|
||||||
const { search, setSearch, spanFindMatches } = useSearch(traceProp?.spans);
|
const { search, setSearch, spanFindMatches } = useSearch(traceProp?.spans);
|
||||||
const dataSourceName = useSelector((state: StoreState) => state.explore[props.exploreId]?.datasourceInstance?.name);
|
const dataSourceName = useSelector((state: StoreState) => state.explore[props.exploreId]?.datasourceInstance?.name);
|
||||||
const traceToLogsOptions = (getDatasourceSrv().getInstanceSettings(dataSourceName)?.jsonData as TraceToLogsData)
|
const traceToLogsOptions = (getDatasourceSrv().getInstanceSettings(dataSourceName)?.jsonData as TraceToLogsData)
|
||||||
@ -167,3 +173,44 @@ export function TraceView(props: Props) {
|
|||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function transformDataFrames(frames: DataFrame[]): Trace | null {
|
||||||
|
// At this point we only show single trace.
|
||||||
|
const frame = frames[0];
|
||||||
|
let data: TraceResponse =
|
||||||
|
frame.fields.length === 1
|
||||||
|
? // For backward compatibility when we sent whole json response in a single field/value
|
||||||
|
frame.fields[0].values.get(0)
|
||||||
|
: transformTraceDataFrame(frame);
|
||||||
|
return transformTraceData(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformTraceDataFrame(frame: DataFrame): TraceResponse {
|
||||||
|
const view = new DataFrameView<TraceSpanRow>(frame);
|
||||||
|
const processes: Record<string, TraceProcess> = {};
|
||||||
|
for (let i = 0; i < view.length; i++) {
|
||||||
|
const span = view.get(i);
|
||||||
|
if (!processes[span.serviceName]) {
|
||||||
|
processes[span.serviceName] = {
|
||||||
|
serviceName: span.serviceName,
|
||||||
|
tags: span.serviceTags,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
traceID: view.get(0).traceID,
|
||||||
|
processes,
|
||||||
|
spans: view.toArray().map((s) => {
|
||||||
|
return {
|
||||||
|
...s,
|
||||||
|
duration: s.duration * 1000,
|
||||||
|
startTime: s.startTime * 1000,
|
||||||
|
processID: s.serviceName,
|
||||||
|
flags: 0,
|
||||||
|
references: s.parentSpanID ? [{ refType: 'CHILD_OF', spanID: s.parentSpanID, traceID: s.traceID }] : undefined,
|
||||||
|
logs: s.logs?.map((l) => ({ ...l, timestamp: l.timestamp * 1000 })) || [],
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { DataLink, dateTime, Field, mapInternalLinkToExplore, TimeRange, TraceSpan } from '@grafana/data';
|
import { DataLink, dateTime, Field, mapInternalLinkToExplore, TimeRange } from '@grafana/data';
|
||||||
import { getTemplateSrv } from '@grafana/runtime';
|
import { getTemplateSrv } from '@grafana/runtime';
|
||||||
import { Icon } from '@grafana/ui';
|
import { Icon } from '@grafana/ui';
|
||||||
import { SplitOpen } from 'app/types/explore';
|
import { SplitOpen } from 'app/types/explore';
|
||||||
@ -6,6 +6,7 @@ import { TraceToLogsOptions } from 'app/core/components/TraceToLogsSettings';
|
|||||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { LokiQuery } from '../../../plugins/datasource/loki/types';
|
import { LokiQuery } from '../../../plugins/datasource/loki/types';
|
||||||
|
import { TraceSpan } from '@jaegertracing/jaeger-ui-components';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a factory for the link creator. It returns the function mainly so it can return undefined in which case
|
* This is a factory for the link creator. It returns the function mainly so it can return undefined in which case
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { renderHook, act } from '@testing-library/react-hooks';
|
import { renderHook, act } from '@testing-library/react-hooks';
|
||||||
import { useChildrenState } from './useChildrenState';
|
import { useChildrenState } from './useChildrenState';
|
||||||
import { TraceSpan } from '@grafana/data';
|
import { TraceSpan } from '@jaegertracing/jaeger-ui-components';
|
||||||
|
|
||||||
describe('useChildrenState', () => {
|
describe('useChildrenState', () => {
|
||||||
describe('childrenToggle', () => {
|
describe('childrenToggle', () => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { TraceSpan } from '@grafana/data';
|
import { TraceSpan } from '@jaegertracing/jaeger-ui-components';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Children state means whether spans are collapsed or not. Also provides some functions to manipulate that state.
|
* Children state means whether spans are collapsed or not. Also provides some functions to manipulate that state.
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { act, renderHook } from '@testing-library/react-hooks';
|
import { act, renderHook } from '@testing-library/react-hooks';
|
||||||
import { TraceLog } from '@grafana/data';
|
|
||||||
import { useDetailState } from './useDetailState';
|
import { useDetailState } from './useDetailState';
|
||||||
|
import { TraceLog } from '@jaegertracing/jaeger-ui-components/src/types/trace';
|
||||||
|
|
||||||
describe('useDetailState', () => {
|
describe('useDetailState', () => {
|
||||||
it('toggles detail', async () => {
|
it('toggles detail', async () => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { DetailState } from '@jaegertracing/jaeger-ui-components';
|
import { DetailState } from '@jaegertracing/jaeger-ui-components';
|
||||||
import { TraceLog } from '@grafana/data';
|
import { TraceLog } from '@jaegertracing/jaeger-ui-components/src/types/trace';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keeps state of the span detail. This means whether span details are open but also state of each detail subitem
|
* Keeps state of the span detail. This means whether span details are open but also state of each detail subitem
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { act, renderHook } from '@testing-library/react-hooks';
|
import { act, renderHook } from '@testing-library/react-hooks';
|
||||||
import { useSearch } from './useSearch';
|
import { useSearch } from './useSearch';
|
||||||
import { TraceSpan } from '@grafana/data';
|
import { TraceSpan } from '@jaegertracing/jaeger-ui-components';
|
||||||
|
|
||||||
describe('useSearch', () => {
|
describe('useSearch', () => {
|
||||||
it('returns matching span IDs', async () => {
|
it('returns matching span IDs', async () => {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { filterSpans } from '@jaegertracing/jaeger-ui-components';
|
import { filterSpans, TraceSpan } from '@jaegertracing/jaeger-ui-components';
|
||||||
import { TraceSpan } from '@grafana/data';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controls the state of search input that highlights spans if they match the search string.
|
* Controls the state of search input that highlights spans if they match the search string.
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { AppEvents, ExploreQueryFieldProps, TraceData, TraceSpan } from '@grafana/data';
|
import { AppEvents, ExploreQueryFieldProps } from '@grafana/data';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { ButtonCascader, CascaderOption } from '@grafana/ui';
|
import { ButtonCascader, CascaderOption } from '@grafana/ui';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { appEvents } from '../../../core/core';
|
import { appEvents } from '../../../core/core';
|
||||||
import { JaegerDatasource, JaegerQuery } from './datasource';
|
import { JaegerDatasource, JaegerQuery } from './datasource';
|
||||||
|
import { Span, TraceResponse } from './types';
|
||||||
|
|
||||||
const ALL_OPERATIONS_KEY = '__ALL__';
|
const ALL_OPERATIONS_KEY = '__ALL__';
|
||||||
const NO_TRACES_KEY = '__NO_TRACES__';
|
const NO_TRACES_KEY = '__NO_TRACES__';
|
||||||
@ -13,11 +14,11 @@ interface State {
|
|||||||
serviceOptions: CascaderOption[];
|
serviceOptions: CascaderOption[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function findRootSpan(spans: TraceSpan[]): TraceSpan | undefined {
|
function findRootSpan(spans: Span[]): Span | undefined {
|
||||||
return spans.find((s) => !s.references?.length);
|
return spans.find((s) => !s.references?.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLabelFromTrace(trace: TraceData & { spans: TraceSpan[] }): string {
|
function getLabelFromTrace(trace: TraceResponse): string {
|
||||||
const rootSpan = findRootSpan(trace.spans);
|
const rootSpan = findRootSpan(trace.spans);
|
||||||
if (rootSpan) {
|
if (rootSpan) {
|
||||||
return `${rootSpan.operationName} [${rootSpan.duration / 1000} ms]`;
|
return `${rootSpan.operationName} [${rootSpan.duration / 1000} ms]`;
|
||||||
|
@ -1,18 +1,60 @@
|
|||||||
import { JaegerDatasource, JaegerQuery } from './datasource';
|
import { JaegerDatasource, JaegerQuery } from './datasource';
|
||||||
import { DataQueryRequest, DataSourceInstanceSettings, FieldType, PluginType, dateTime } from '@grafana/data';
|
import {
|
||||||
|
DataQueryRequest,
|
||||||
|
DataSourceInstanceSettings,
|
||||||
|
FieldType,
|
||||||
|
PluginType,
|
||||||
|
dateTime,
|
||||||
|
ArrayVector,
|
||||||
|
} from '@grafana/data';
|
||||||
import { BackendSrv, BackendSrvRequest, getBackendSrv, setBackendSrv } from '@grafana/runtime';
|
import { BackendSrv, BackendSrvRequest, getBackendSrv, setBackendSrv } from '@grafana/runtime';
|
||||||
|
import { testResponse } from './testResponse';
|
||||||
|
|
||||||
describe('JaegerDatasource', () => {
|
describe('JaegerDatasource', () => {
|
||||||
it('returns trace when queried', async () => {
|
it('returns trace when queried', async () => {
|
||||||
await withMockedBackendSrv(makeBackendSrvMock('12345'), async () => {
|
await withMockedBackendSrv(makeBackendSrvMock('12345'), async () => {
|
||||||
const ds = new JaegerDatasource(defaultSettings);
|
const ds = new JaegerDatasource(defaultSettings);
|
||||||
const response = await ds.query(defaultQuery).toPromise();
|
const response = await ds.query(defaultQuery).toPromise();
|
||||||
const field = response.data[0].fields[0];
|
expect(response.data[0].fields).toMatchObject(
|
||||||
expect(field.name).toBe('trace');
|
[
|
||||||
expect(field.type).toBe(FieldType.trace);
|
{ name: 'traceID', values: ['3fa414edcef6ad90', '3fa414edcef6ad90'] },
|
||||||
expect(field.values.get(0)).toEqual({
|
{ name: 'spanID', values: ['3fa414edcef6ad90', '0f5c1808567e4403'] },
|
||||||
traceId: '12345',
|
{ name: 'parentSpanID', values: [undefined, '3fa414edcef6ad90'] },
|
||||||
});
|
{ name: 'operationName', values: ['HTTP GET - api_traces_traceid', '/tempopb.Querier/FindTraceByID'] },
|
||||||
|
{ name: 'serviceName', values: ['tempo-querier', 'tempo-querier'] },
|
||||||
|
{
|
||||||
|
name: 'serviceTags',
|
||||||
|
values: [
|
||||||
|
[
|
||||||
|
{ key: 'cluster', type: 'string', value: 'ops-tools1' },
|
||||||
|
{ key: 'container', type: 'string', value: 'tempo-query' },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ key: 'cluster', type: 'string', value: 'ops-tools1' },
|
||||||
|
{ key: 'container', type: 'string', value: 'tempo-query' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ name: 'startTime', values: [1605873894680.409, 1605873894680.587] },
|
||||||
|
{ name: 'duration', values: [1049.141, 1.847] },
|
||||||
|
{ name: 'logs', values: [[], []] },
|
||||||
|
{
|
||||||
|
name: 'tags',
|
||||||
|
values: [
|
||||||
|
[
|
||||||
|
{ key: 'sampler.type', type: 'string', value: 'probabilistic' },
|
||||||
|
{ key: 'sampler.param', type: 'float64', value: 1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ key: 'component', type: 'string', value: 'gRPC' },
|
||||||
|
{ key: 'span.kind', type: 'string', value: 'client' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ name: 'warnings', values: [undefined, undefined] },
|
||||||
|
{ name: 'stackTraces', values: [undefined, undefined] },
|
||||||
|
].map((f) => ({ ...f, values: new ArrayVector<any>(f.values) }))
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -28,13 +70,8 @@ describe('JaegerDatasource', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
const response = await ds.query(query).toPromise();
|
await ds.query(query).toPromise();
|
||||||
const field = response.data[0].fields[0];
|
// there is expect makeBackendSrvMock checking correct encoding
|
||||||
expect(field.name).toBe('trace');
|
|
||||||
expect(field.type).toBe(FieldType.trace);
|
|
||||||
expect(field.values.get(0)).toEqual({
|
|
||||||
traceId: 'a/b',
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -158,11 +195,7 @@ function makeBackendSrvMock(traceId: string) {
|
|||||||
);
|
);
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: {
|
data: {
|
||||||
data: [
|
data: [testResponse],
|
||||||
{
|
|
||||||
traceId,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -1,20 +1,21 @@
|
|||||||
import {
|
import {
|
||||||
dateMath,
|
DataQuery,
|
||||||
DateTime,
|
|
||||||
MutableDataFrame,
|
|
||||||
DataSourceApi,
|
|
||||||
DataSourceInstanceSettings,
|
|
||||||
DataQueryRequest,
|
DataQueryRequest,
|
||||||
DataQueryResponse,
|
DataQueryResponse,
|
||||||
DataQuery,
|
DataSourceApi,
|
||||||
|
DataSourceInstanceSettings,
|
||||||
|
dateMath,
|
||||||
|
DateTime,
|
||||||
FieldType,
|
FieldType,
|
||||||
|
MutableDataFrame,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { getBackendSrv, BackendSrvRequest } from '@grafana/runtime';
|
import { BackendSrvRequest, getBackendSrv } from '@grafana/runtime';
|
||||||
import { Observable, from, of } from 'rxjs';
|
import { from, Observable, of } from 'rxjs';
|
||||||
import { catchError, map } from 'rxjs/operators';
|
import { catchError, map } from 'rxjs/operators';
|
||||||
|
|
||||||
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||||
import { serializeParams } from 'app/core/utils/fetch';
|
import { serializeParams } from 'app/core/utils/fetch';
|
||||||
|
import { createTraceFrame } from './responseTransform';
|
||||||
|
|
||||||
export type JaegerQuery = {
|
export type JaegerQuery = {
|
||||||
query: string;
|
query: string;
|
||||||
@ -34,46 +35,18 @@ export class JaegerDatasource extends DataSourceApi<JaegerQuery> {
|
|||||||
// At this moment we expect only one target. In case we somehow change the UI to be able to show multiple
|
// At this moment we expect only one target. In case we somehow change the UI to be able to show multiple
|
||||||
// traces at one we need to change this.
|
// traces at one we need to change this.
|
||||||
const id = options.targets[0]?.query;
|
const id = options.targets[0]?.query;
|
||||||
if (id) {
|
if (!id) {
|
||||||
// TODO: this api is internal, used in jaeger ui. Officially they have gRPC api that should be used.
|
return of({ data: [emptyTraceDataFrame] });
|
||||||
return this._request(`/api/traces/${encodeURIComponent(id)}`).pipe(
|
|
||||||
map((response) => {
|
|
||||||
return {
|
|
||||||
data: [
|
|
||||||
new MutableDataFrame({
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: 'trace',
|
|
||||||
type: FieldType.trace,
|
|
||||||
values: response?.data?.data || [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
meta: {
|
|
||||||
preferredVisualisationType: 'trace',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return of({
|
|
||||||
data: [
|
|
||||||
new MutableDataFrame({
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: 'trace',
|
|
||||||
type: FieldType.trace,
|
|
||||||
values: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
meta: {
|
|
||||||
preferredVisualisationType: 'trace',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: this api is internal, used in jaeger ui. Officially they have gRPC api that should be used.
|
||||||
|
return this._request(`/api/traces/${encodeURIComponent(id)}`).pipe(
|
||||||
|
map((response) => {
|
||||||
|
return {
|
||||||
|
data: [createTraceFrame(response?.data?.data?.[0] || [])],
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async testDatasource(): Promise<any> {
|
async testDatasource(): Promise<any> {
|
||||||
@ -146,3 +119,16 @@ function getTime(date: string | DateTime, roundUp: boolean) {
|
|||||||
}
|
}
|
||||||
return date.valueOf() * 1000;
|
return date.valueOf() * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const emptyTraceDataFrame = new MutableDataFrame({
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'trace',
|
||||||
|
type: FieldType.trace,
|
||||||
|
values: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
preferredVisualisationType: 'trace',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
import { createTraceFrame } from './responseTransform';
|
||||||
|
import { ArrayVector } from '@grafana/data';
|
||||||
|
import { testResponse } from './testResponse';
|
||||||
|
|
||||||
|
describe('createTraceFrame', () => {
|
||||||
|
it('creates data frame from jaeger response', () => {
|
||||||
|
const dataFrame = createTraceFrame(testResponse);
|
||||||
|
expect(dataFrame.fields).toMatchObject(
|
||||||
|
[
|
||||||
|
{ name: 'traceID', values: ['3fa414edcef6ad90', '3fa414edcef6ad90'] },
|
||||||
|
{ name: 'spanID', values: ['3fa414edcef6ad90', '0f5c1808567e4403'] },
|
||||||
|
{ name: 'parentSpanID', values: [undefined, '3fa414edcef6ad90'] },
|
||||||
|
{ name: 'operationName', values: ['HTTP GET - api_traces_traceid', '/tempopb.Querier/FindTraceByID'] },
|
||||||
|
{ name: 'serviceName', values: ['tempo-querier', 'tempo-querier'] },
|
||||||
|
{
|
||||||
|
name: 'serviceTags',
|
||||||
|
values: [
|
||||||
|
[
|
||||||
|
{ key: 'cluster', type: 'string', value: 'ops-tools1' },
|
||||||
|
{ key: 'container', type: 'string', value: 'tempo-query' },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ key: 'cluster', type: 'string', value: 'ops-tools1' },
|
||||||
|
{ key: 'container', type: 'string', value: 'tempo-query' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ name: 'startTime', values: [1605873894680.409, 1605873894680.587] },
|
||||||
|
{ name: 'duration', values: [1049.141, 1.847] },
|
||||||
|
{ name: 'logs', values: [[], []] },
|
||||||
|
{
|
||||||
|
name: 'tags',
|
||||||
|
values: [
|
||||||
|
[
|
||||||
|
{ key: 'sampler.type', type: 'string', value: 'probabilistic' },
|
||||||
|
{ key: 'sampler.param', type: 'float64', value: 1 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ key: 'component', type: 'string', value: 'gRPC' },
|
||||||
|
{ key: 'span.kind', type: 'string', value: 'client' },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ name: 'warnings', values: [undefined, undefined] },
|
||||||
|
{ name: 'stackTraces', values: [undefined, undefined] },
|
||||||
|
].map((f) => ({ ...f, values: new ArrayVector<any>(f.values) }))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
54
public/app/plugins/datasource/jaeger/responseTransform.ts
Normal file
54
public/app/plugins/datasource/jaeger/responseTransform.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { DataFrame, FieldType, MutableDataFrame, TraceSpanRow } from '@grafana/data';
|
||||||
|
|
||||||
|
import { Span, TraceProcess, TraceResponse } from './types';
|
||||||
|
|
||||||
|
export function createTraceFrame(data: TraceResponse): DataFrame {
|
||||||
|
const spans = data.spans.map((s) => toSpanRow(s, data.processes));
|
||||||
|
|
||||||
|
const frame = new MutableDataFrame({
|
||||||
|
fields: [
|
||||||
|
{ name: 'traceID', type: FieldType.string },
|
||||||
|
{ name: 'spanID', type: FieldType.string },
|
||||||
|
{ name: 'parentSpanID', type: FieldType.string },
|
||||||
|
{ name: 'operationName', type: FieldType.string },
|
||||||
|
{ name: 'serviceName', type: FieldType.string },
|
||||||
|
{ name: 'serviceTags', type: FieldType.other },
|
||||||
|
{ name: 'startTime', type: FieldType.number },
|
||||||
|
{ name: 'duration', type: FieldType.number },
|
||||||
|
{ name: 'logs', type: FieldType.other },
|
||||||
|
{ name: 'tags', type: FieldType.other },
|
||||||
|
{ name: 'warnings', type: FieldType.other },
|
||||||
|
{ name: 'stackTraces', type: FieldType.other },
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
preferredVisualisationType: 'trace',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const span of spans) {
|
||||||
|
frame.add(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toSpanRow(span: Span, processes: Record<string, TraceProcess>): TraceSpanRow {
|
||||||
|
return {
|
||||||
|
spanID: span.spanID,
|
||||||
|
traceID: span.traceID,
|
||||||
|
parentSpanID: span.references?.find((r) => r.refType === 'CHILD_OF')?.spanID,
|
||||||
|
operationName: span.operationName,
|
||||||
|
// from micro to millis
|
||||||
|
startTime: span.startTime / 1000,
|
||||||
|
duration: span.duration / 1000,
|
||||||
|
logs: span.logs.map((l) => ({
|
||||||
|
...l,
|
||||||
|
timestamp: l.timestamp / 1000,
|
||||||
|
})),
|
||||||
|
tags: span.tags,
|
||||||
|
warnings: span.warnings ?? undefined,
|
||||||
|
stackTraces: span.stackTraces,
|
||||||
|
serviceName: processes[span.processID].serviceName,
|
||||||
|
serviceTags: processes[span.processID].tags,
|
||||||
|
};
|
||||||
|
}
|
49
public/app/plugins/datasource/jaeger/testResponse.ts
Normal file
49
public/app/plugins/datasource/jaeger/testResponse.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { TraceResponse } from './types';
|
||||||
|
|
||||||
|
export const testResponse: TraceResponse = {
|
||||||
|
traceID: '3fa414edcef6ad90',
|
||||||
|
spans: [
|
||||||
|
{
|
||||||
|
traceID: '3fa414edcef6ad90',
|
||||||
|
spanID: '3fa414edcef6ad90',
|
||||||
|
operationName: 'HTTP GET - api_traces_traceid',
|
||||||
|
references: [],
|
||||||
|
startTime: 1605873894680409,
|
||||||
|
duration: 1049141,
|
||||||
|
tags: [
|
||||||
|
{ key: 'sampler.type', type: 'string', value: 'probabilistic' },
|
||||||
|
{ key: 'sampler.param', type: 'float64', value: 1 },
|
||||||
|
],
|
||||||
|
logs: [],
|
||||||
|
processID: 'p1',
|
||||||
|
warnings: null,
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
traceID: '3fa414edcef6ad90',
|
||||||
|
spanID: '0f5c1808567e4403',
|
||||||
|
operationName: '/tempopb.Querier/FindTraceByID',
|
||||||
|
references: [{ refType: 'CHILD_OF', traceID: '3fa414edcef6ad90', spanID: '3fa414edcef6ad90' }],
|
||||||
|
startTime: 1605873894680587,
|
||||||
|
duration: 1847,
|
||||||
|
tags: [
|
||||||
|
{ key: 'component', type: 'string', value: 'gRPC' },
|
||||||
|
{ key: 'span.kind', type: 'string', value: 'client' },
|
||||||
|
],
|
||||||
|
logs: [],
|
||||||
|
processID: 'p1',
|
||||||
|
warnings: null,
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
processes: {
|
||||||
|
p1: {
|
||||||
|
serviceName: 'tempo-querier',
|
||||||
|
tags: [
|
||||||
|
{ key: 'cluster', type: 'string', value: 'ops-tools1' },
|
||||||
|
{ key: 'container', type: 'string', value: 'tempo-query' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
warnings: null,
|
||||||
|
};
|
49
public/app/plugins/datasource/jaeger/types.ts
Normal file
49
public/app/plugins/datasource/jaeger/types.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
export type TraceKeyValuePair = {
|
||||||
|
key: string;
|
||||||
|
type?: string;
|
||||||
|
value: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TraceLink = {
|
||||||
|
url: string;
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TraceLog = {
|
||||||
|
timestamp: number;
|
||||||
|
fields: TraceKeyValuePair[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TraceProcess = {
|
||||||
|
serviceName: string;
|
||||||
|
tags: TraceKeyValuePair[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TraceSpanReference = {
|
||||||
|
refType: 'CHILD_OF' | 'FOLLOWS_FROM';
|
||||||
|
spanID: string;
|
||||||
|
traceID: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Span = {
|
||||||
|
spanID: string;
|
||||||
|
traceID: string;
|
||||||
|
processID: string;
|
||||||
|
operationName: string;
|
||||||
|
// Times are in microseconds
|
||||||
|
startTime: number;
|
||||||
|
duration: number;
|
||||||
|
logs: TraceLog[];
|
||||||
|
tags?: TraceKeyValuePair[];
|
||||||
|
references?: TraceSpanReference[];
|
||||||
|
warnings?: string[] | null;
|
||||||
|
stackTraces?: string[];
|
||||||
|
flags: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TraceResponse = {
|
||||||
|
processes: Record<string, TraceProcess>;
|
||||||
|
traceID: string;
|
||||||
|
warnings?: string[] | null;
|
||||||
|
spans: Span[];
|
||||||
|
};
|
@ -1,5 +1,5 @@
|
|||||||
import { TraceSpanData, TraceData } from '@grafana/data';
|
|
||||||
import { ZipkinSpan } from '../types';
|
import { ZipkinSpan } from '../types';
|
||||||
|
import { TraceResponse } from '../../jaeger/types';
|
||||||
|
|
||||||
export const zipkinResponse: ZipkinSpan[] = [
|
export const zipkinResponse: ZipkinSpan[] = [
|
||||||
{
|
{
|
||||||
@ -59,7 +59,7 @@ export const zipkinResponse: ZipkinSpan[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const jaegerTrace: TraceData & { spans: TraceSpanData[] } = {
|
export const jaegerTrace: TraceResponse = {
|
||||||
processes: {
|
processes: {
|
||||||
'service 1': {
|
'service 1': {
|
||||||
serviceName: 'service 1',
|
serviceName: 'service 1',
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { identity, keyBy } from 'lodash';
|
import { identity, keyBy } from 'lodash';
|
||||||
import { ZipkinAnnotation, ZipkinEndpoint, ZipkinSpan } from '../types';
|
import { ZipkinAnnotation, ZipkinEndpoint, ZipkinSpan } from '../types';
|
||||||
import { TraceKeyValuePair, TraceLog, TraceProcess, TraceSpanData, TraceData } from '@grafana/data';
|
import * as Jaeger from '../../jaeger/types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms response to format similar to Jaegers as we use Jaeger ui on the frontend.
|
* Transforms response to format similar to Jaegers as we use Jaeger ui on the frontend.
|
||||||
*/
|
*/
|
||||||
export function transformResponse(zSpans: ZipkinSpan[]): TraceData & { spans: TraceSpanData[] } {
|
export function transformResponse(zSpans: ZipkinSpan[]): Jaeger.TraceResponse {
|
||||||
return {
|
return {
|
||||||
processes: gatherProcesses(zSpans),
|
processes: gatherProcesses(zSpans),
|
||||||
traceID: zSpans[0].traceId,
|
traceID: zSpans[0].traceId,
|
||||||
@ -14,8 +14,8 @@ export function transformResponse(zSpans: ZipkinSpan[]): TraceData & { spans: Tr
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function transformSpan(span: ZipkinSpan): TraceSpanData {
|
function transformSpan(span: ZipkinSpan): Jaeger.Span {
|
||||||
const jaegerSpan: TraceSpanData = {
|
const jaegerSpan: Jaeger.Span = {
|
||||||
duration: span.duration,
|
duration: span.duration,
|
||||||
// TODO: not sure what this is
|
// TODO: not sure what this is
|
||||||
flags: 1,
|
flags: 1,
|
||||||
@ -62,7 +62,7 @@ function transformSpan(span: ZipkinSpan): TraceSpanData {
|
|||||||
* Maps annotations as a Jaeger log as that seems to be the closest thing.
|
* Maps annotations as a Jaeger log as that seems to be the closest thing.
|
||||||
* See https://zipkin.io/zipkin-api/#/default/get_trace__traceId_
|
* See https://zipkin.io/zipkin-api/#/default/get_trace__traceId_
|
||||||
*/
|
*/
|
||||||
function transformAnnotation(annotation: ZipkinAnnotation): TraceLog {
|
function transformAnnotation(annotation: ZipkinAnnotation): Jaeger.TraceLog {
|
||||||
return {
|
return {
|
||||||
timestamp: annotation.timestamp,
|
timestamp: annotation.timestamp,
|
||||||
fields: [
|
fields: [
|
||||||
@ -75,7 +75,7 @@ function transformAnnotation(annotation: ZipkinAnnotation): TraceLog {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function gatherProcesses(zSpans: ZipkinSpan[]): Record<string, TraceProcess> {
|
function gatherProcesses(zSpans: ZipkinSpan[]): Record<string, Jaeger.TraceProcess> {
|
||||||
const processes = zSpans.reduce((acc, span) => {
|
const processes = zSpans.reduce((acc, span) => {
|
||||||
if (span.localEndpoint) {
|
if (span.localEndpoint) {
|
||||||
acc.push(endpointToProcess(span.localEndpoint));
|
acc.push(endpointToProcess(span.localEndpoint));
|
||||||
@ -84,22 +84,26 @@ function gatherProcesses(zSpans: ZipkinSpan[]): Record<string, TraceProcess> {
|
|||||||
acc.push(endpointToProcess(span.remoteEndpoint));
|
acc.push(endpointToProcess(span.remoteEndpoint));
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}, [] as TraceProcess[]);
|
}, [] as Jaeger.TraceProcess[]);
|
||||||
return keyBy(processes, 'serviceName');
|
return keyBy(processes, 'serviceName');
|
||||||
}
|
}
|
||||||
|
|
||||||
function endpointToProcess(endpoint: ZipkinEndpoint): TraceProcess {
|
function endpointToProcess(endpoint: ZipkinEndpoint): Jaeger.TraceProcess {
|
||||||
return {
|
return {
|
||||||
serviceName: endpoint.serviceName,
|
serviceName: endpoint.serviceName,
|
||||||
tags: [
|
tags: [
|
||||||
valueToTag('ipv4', endpoint.ipv4, 'string'),
|
valueToTag('ipv4', endpoint.ipv4, 'string'),
|
||||||
valueToTag('ipv6', endpoint.ipv6, 'string'),
|
valueToTag('ipv6', endpoint.ipv6, 'string'),
|
||||||
valueToTag('port', endpoint.port, 'number'),
|
valueToTag('port', endpoint.port, 'number'),
|
||||||
].filter(identity) as TraceKeyValuePair[],
|
].filter(identity) as Jaeger.TraceKeyValuePair[],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function valueToTag(key: string, value: string | number | undefined, type: string): TraceKeyValuePair | undefined {
|
function valueToTag(
|
||||||
|
key: string,
|
||||||
|
value: string | number | undefined,
|
||||||
|
type: string
|
||||||
|
): Jaeger.TraceKeyValuePair | undefined {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user