mirror of https://github.com/grafana/grafana.git
Jaeger: remove unused code (#108612)
* remove isSearchFormValid * remove getTimeRange * remove convertTagsLogfmt * remove createTableFrame * remove transformToLogfmt * remove types * remove mapJaegerDependenciesResponse * trigger workflow
This commit is contained in:
parent
a3e1920983
commit
04a563cb09
|
|
@ -3498,12 +3498,6 @@ exports[`better eslint`] = {
|
|||
"public/app/plugins/datasource/influxdb/response_parser.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"public/app/plugins/datasource/jaeger/_importedDependencies/model/transform-trace-data.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
],
|
||||
"public/app/plugins/datasource/jaeger/_importedDependencies/types/index.tsx:5381": [
|
||||
[0, 0, 0, "Do not re-export imported variable (\`./trace\`)", "0"]
|
||||
],
|
||||
"public/app/plugins/datasource/jaeger/datasource.ts:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
This directory contains dependencies that we duplicated from Grafana core while working on the decoupling of Jaeger from such core.
|
||||
The long-term goal is to move these files away from here by replacing them with packages.
|
||||
As such, they are only temporary and meant to be used internally to this package, please avoid using them for example as dependencies (imports) in other data source plugins.
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
// Copyright (c) 2020 The Jaeger Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { memoize } from 'lodash';
|
||||
|
||||
import { TraceSpan } from '../types';
|
||||
|
||||
function _getTraceNameImpl(spans: TraceSpan[]) {
|
||||
// Use a span with no references to another span in given array
|
||||
// prefering the span with the fewest references
|
||||
// using start time as a tie breaker
|
||||
let candidateSpan: TraceSpan | undefined;
|
||||
const allIDs: Set<string> = new Set(spans.map(({ spanID }) => spanID));
|
||||
|
||||
for (let i = 0; i < spans.length; i++) {
|
||||
const hasInternalRef =
|
||||
spans[i].references &&
|
||||
spans[i].references.some(({ traceID, spanID }) => traceID === spans[i].traceID && allIDs.has(spanID));
|
||||
if (hasInternalRef) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!candidateSpan) {
|
||||
candidateSpan = spans[i];
|
||||
continue;
|
||||
}
|
||||
|
||||
const thisRefLength = (spans[i].references && spans[i].references.length) || 0;
|
||||
const candidateRefLength = (candidateSpan.references && candidateSpan.references.length) || 0;
|
||||
|
||||
if (
|
||||
thisRefLength < candidateRefLength ||
|
||||
(thisRefLength === candidateRefLength && spans[i].startTime < candidateSpan.startTime)
|
||||
) {
|
||||
candidateSpan = spans[i];
|
||||
}
|
||||
}
|
||||
return candidateSpan ? `${candidateSpan.process.serviceName}: ${candidateSpan.operationName}` : '';
|
||||
}
|
||||
|
||||
export const getTraceName = memoize(_getTraceNameImpl, (spans: TraceSpan[]) => {
|
||||
if (!spans.length) {
|
||||
return 0;
|
||||
}
|
||||
return spans[0].traceID;
|
||||
});
|
||||
|
|
@ -1,201 +0,0 @@
|
|||
// 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.
|
||||
|
||||
import { isEqual as _isEqual } from 'lodash';
|
||||
|
||||
import { TraceKeyValuePair } from '@grafana/data';
|
||||
|
||||
import { getTraceSpanIdsAsTree } from '../selectors/trace';
|
||||
import { TraceSpan, Trace, TraceResponse, TraceProcess } from '../types';
|
||||
import TreeNode from '../utils/TreeNode';
|
||||
import { getConfigValue } from '../utils/config/get-config';
|
||||
|
||||
import { getTraceName } from './trace-viewer';
|
||||
|
||||
function deduplicateTags(tags: TraceKeyValuePair[]) {
|
||||
const warningsHash: Map<string, string> = new Map<string, string>();
|
||||
const dedupedTags: TraceKeyValuePair[] = tags.reduce<TraceKeyValuePair[]>((uniqueTags, tag) => {
|
||||
if (!uniqueTags.some((t) => t.key === tag.key && t.value === tag.value)) {
|
||||
uniqueTags.push(tag);
|
||||
} else {
|
||||
warningsHash.set(`${tag.key}:${tag.value}`, `Duplicate tag "${tag.key}:${tag.value}"`);
|
||||
}
|
||||
return uniqueTags;
|
||||
}, []);
|
||||
const warnings = Array.from(warningsHash.values());
|
||||
return { dedupedTags, warnings };
|
||||
}
|
||||
|
||||
function orderTags(tags: TraceKeyValuePair[], topPrefixes?: string[]) {
|
||||
const orderedTags: TraceKeyValuePair[] = tags?.slice() ?? [];
|
||||
const tp = (topPrefixes || []).map((p: string) => p.toLowerCase());
|
||||
|
||||
orderedTags.sort((a, b) => {
|
||||
const aKey = a.key.toLowerCase();
|
||||
const bKey = b.key.toLowerCase();
|
||||
|
||||
for (let i = 0; i < tp.length; i++) {
|
||||
const p = tp[i];
|
||||
if (aKey.startsWith(p) && !bKey.startsWith(p)) {
|
||||
return -1;
|
||||
}
|
||||
if (!aKey.startsWith(p) && bKey.startsWith(p)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (aKey > bKey) {
|
||||
return 1;
|
||||
}
|
||||
if (aKey < bKey) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
return orderedTags;
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: Mutates `data` - Transform the HTTP response data into the form the app
|
||||
* generally requires.
|
||||
*/
|
||||
export default function transformTraceData(data: TraceResponse | undefined): Trace | null {
|
||||
if (!data?.traceID) {
|
||||
return null;
|
||||
}
|
||||
const traceID = data.traceID.toLowerCase();
|
||||
|
||||
let traceEndTime = 0;
|
||||
let traceStartTime = Number.MAX_SAFE_INTEGER;
|
||||
const spanIdCounts = new Map();
|
||||
const spanMap = new Map<string, TraceSpan>();
|
||||
// filter out spans with empty start times
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
data.spans = data.spans.filter((span) => Boolean(span.startTime));
|
||||
|
||||
// Sort process tags
|
||||
data.processes = Object.entries(data.processes).reduce<Record<string, TraceProcess>>((processes, [id, process]) => {
|
||||
processes[id] = {
|
||||
...process,
|
||||
tags: orderTags(process.tags),
|
||||
};
|
||||
return processes;
|
||||
}, {});
|
||||
|
||||
const max = data.spans.length;
|
||||
for (let i = 0; i < max; i++) {
|
||||
const span: TraceSpan = data.spans[i] as TraceSpan;
|
||||
const { startTime, duration, processID } = span;
|
||||
|
||||
let spanID = span.spanID;
|
||||
// check for start / end time for the trace
|
||||
if (startTime < traceStartTime) {
|
||||
traceStartTime = startTime;
|
||||
}
|
||||
if (startTime + duration > traceEndTime) {
|
||||
traceEndTime = startTime + duration;
|
||||
}
|
||||
// make sure span IDs are unique
|
||||
const idCount = spanIdCounts.get(spanID);
|
||||
if (idCount != null) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(`Dupe spanID, ${idCount + 1} x ${spanID}`, span, spanMap.get(spanID));
|
||||
if (_isEqual(span, spanMap.get(spanID))) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('\t two spans with same ID have `isEqual(...) === true`');
|
||||
}
|
||||
spanIdCounts.set(spanID, idCount + 1);
|
||||
spanID = `${spanID}_${idCount}`;
|
||||
span.spanID = spanID;
|
||||
} else {
|
||||
spanIdCounts.set(spanID, 1);
|
||||
}
|
||||
span.process = data.processes[processID];
|
||||
spanMap.set(spanID, span);
|
||||
}
|
||||
// tree is necessary to sort the spans, so children follow parents, and
|
||||
// siblings are sorted by start time
|
||||
const tree = getTraceSpanIdsAsTree(data, spanMap);
|
||||
const spans: TraceSpan[] = [];
|
||||
const svcCounts: Record<string, number> = {};
|
||||
|
||||
tree.walk((spanID: string, node: TreeNode<string>, depth = 0) => {
|
||||
if (spanID === '__root__') {
|
||||
return;
|
||||
}
|
||||
if (typeof spanID !== 'string') {
|
||||
return;
|
||||
}
|
||||
const span = spanMap.get(spanID);
|
||||
if (!span) {
|
||||
return;
|
||||
}
|
||||
const { serviceName } = span.process;
|
||||
svcCounts[serviceName] = (svcCounts[serviceName] || 0) + 1;
|
||||
span.relativeStartTime = span.startTime - traceStartTime;
|
||||
span.depth = depth - 1;
|
||||
span.hasChildren = node.children.length > 0;
|
||||
span.childSpanCount = node.children.length;
|
||||
span.warnings = span.warnings || [];
|
||||
span.tags = span.tags || [];
|
||||
span.references = span.references || [];
|
||||
|
||||
span.childSpanIds = node.children
|
||||
.slice()
|
||||
.sort((a, b) => {
|
||||
const spanA = spanMap.get(a.value)!;
|
||||
const spanB = spanMap.get(b.value)!;
|
||||
return spanB.startTime + spanB.duration - (spanA.startTime + spanA.duration);
|
||||
})
|
||||
.map((each) => each.value);
|
||||
|
||||
const tagsInfo = deduplicateTags(span.tags);
|
||||
span.tags = orderTags(tagsInfo.dedupedTags, getConfigValue('topTagPrefixes'));
|
||||
span.warnings = span.warnings.concat(tagsInfo.warnings);
|
||||
span.references.forEach((ref, index) => {
|
||||
const refSpan = spanMap.get(ref.spanID);
|
||||
if (refSpan) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
ref.span = refSpan;
|
||||
if (index > 0) {
|
||||
// Don't take into account the parent, just other references.
|
||||
refSpan.subsidiarilyReferencedBy = refSpan.subsidiarilyReferencedBy || [];
|
||||
refSpan.subsidiarilyReferencedBy.push({
|
||||
spanID,
|
||||
traceID,
|
||||
span,
|
||||
refType: ref.refType,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
spans.push(span);
|
||||
});
|
||||
const traceName = getTraceName(spans);
|
||||
const services = Object.keys(svcCounts).map((name) => ({ name, numberOfSpans: svcCounts[name] }));
|
||||
return {
|
||||
services,
|
||||
spans,
|
||||
traceID,
|
||||
traceName,
|
||||
// can't use spread operator for intersection types
|
||||
// repl: https://goo.gl/4Z23MJ
|
||||
// issue: https://github.com/facebook/flow/issues/1511
|
||||
processes: data.processes,
|
||||
duration: traceEndTime - traceStartTime,
|
||||
startTime: traceStartTime,
|
||||
endTime: traceEndTime,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
// 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.
|
||||
|
||||
import { TraceResponse, TraceSpanData } from '../types/trace';
|
||||
import TreeNode from '../utils/TreeNode';
|
||||
|
||||
const TREE_ROOT_ID = '__root__';
|
||||
|
||||
/**
|
||||
* Build a tree of { value: spanID, children } items derived from the
|
||||
* `span.references` information. The tree represents the grouping of parent /
|
||||
* child relationships. The root-most node is nominal in that
|
||||
* `.value === TREE_ROOT_ID`. This is done because a root span (the main trace
|
||||
* span) is not always included with the trace data. Thus, there can be
|
||||
* multiple top-level spans, and the root node acts as their common parent.
|
||||
*
|
||||
* The children are sorted by `span.startTime` after the tree is built.
|
||||
*
|
||||
* @param {Trace} trace The trace to build the tree of spanIDs.
|
||||
* @return {TreeNode} A tree of spanIDs derived from the relationships
|
||||
* between spans in the trace.
|
||||
*/
|
||||
export function getTraceSpanIdsAsTree(trace: TraceResponse, spanMap: Map<string, TraceSpanData> | null = null) {
|
||||
const nodesById = new Map(trace.spans.map((span: TraceSpanData) => [span.spanID, new TreeNode(span.spanID)]));
|
||||
const spansById = spanMap ?? new Map(trace.spans.map((span: TraceSpanData) => [span.spanID, span]));
|
||||
const root = new TreeNode(TREE_ROOT_ID);
|
||||
trace.spans.forEach((span: TraceSpanData) => {
|
||||
const node = nodesById.get(span.spanID)!;
|
||||
if (Array.isArray(span.references) && span.references.length) {
|
||||
const { refType, spanID: parentID } = span.references[0];
|
||||
if (refType === 'CHILD_OF' || refType === 'FOLLOWS_FROM') {
|
||||
const parent = nodesById.get(parentID) || root;
|
||||
parent.children?.push(node);
|
||||
} else {
|
||||
throw new Error(`Unrecognized ref type: ${refType}`);
|
||||
}
|
||||
} else {
|
||||
root.children.push(node);
|
||||
}
|
||||
});
|
||||
const comparator = (nodeA: TreeNode<string>, nodeB: TreeNode<string>) => {
|
||||
const a: TraceSpanData | undefined = nodeA?.value ? spansById.get(nodeA.value.toString()) : undefined;
|
||||
const b: TraceSpanData | undefined = nodeB?.value ? spansById.get(nodeB.value.toString()) : undefined;
|
||||
return +(a?.startTime! > b?.startTime!) || +(a?.startTime === b?.startTime) - 1;
|
||||
};
|
||||
trace.spans.forEach((span: TraceSpanData) => {
|
||||
const node = nodesById.get(span.spanID);
|
||||
if (node!.children.length > 1) {
|
||||
node?.children.sort(comparator);
|
||||
}
|
||||
});
|
||||
root.children.sort(comparator);
|
||||
return root;
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
// 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.
|
||||
|
||||
export type { TraceSpan, TraceResponse, Trace, TraceProcess, TraceLink, CriticalPathSection } from './trace';
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
// 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.
|
||||
|
||||
import { TraceKeyValuePair, TraceLog } from '@grafana/data';
|
||||
|
||||
/**
|
||||
* All timestamps are in microseconds
|
||||
*/
|
||||
|
||||
export type TraceLink = {
|
||||
url: string;
|
||||
text: string;
|
||||
};
|
||||
|
||||
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;
|
||||
tags?: TraceKeyValuePair[];
|
||||
};
|
||||
|
||||
export type TraceSpanData = {
|
||||
spanID: string;
|
||||
traceID: string;
|
||||
processID: string;
|
||||
operationName: string;
|
||||
// Times are in microseconds
|
||||
startTime: number;
|
||||
duration: number;
|
||||
logs: TraceLog[];
|
||||
tags?: TraceKeyValuePair[];
|
||||
kind?: string;
|
||||
statusCode?: number;
|
||||
statusMessage?: string;
|
||||
instrumentationLibraryName?: string;
|
||||
instrumentationLibraryVersion?: string;
|
||||
traceState?: string;
|
||||
references?: TraceSpanReference[];
|
||||
warnings?: string[] | null;
|
||||
stackTraces?: string[];
|
||||
flags: number;
|
||||
errorIconColor?: string;
|
||||
dataFrameRowIndex?: number;
|
||||
childSpanIds?: string[];
|
||||
};
|
||||
|
||||
export type TraceSpan = TraceSpanData & {
|
||||
depth: number;
|
||||
hasChildren: boolean;
|
||||
childSpanCount: number;
|
||||
process: TraceProcess;
|
||||
relativeStartTime: number;
|
||||
tags: NonNullable<TraceSpanData['tags']>;
|
||||
references: NonNullable<TraceSpanData['references']>;
|
||||
warnings: NonNullable<TraceSpanData['warnings']>;
|
||||
childSpanIds: NonNullable<TraceSpanData['childSpanIds']>;
|
||||
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 }>;
|
||||
};
|
||||
|
||||
// It is a section of span that lies on critical path
|
||||
export type CriticalPathSection = {
|
||||
spanId: string;
|
||||
section_start: number;
|
||||
section_end: number;
|
||||
};
|
||||
|
|
@ -1,139 +0,0 @@
|
|||
// 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
|
||||
|
||||
export default class TreeNode<TValue> {
|
||||
value: TValue;
|
||||
children: Array<TreeNode<TValue>>;
|
||||
|
||||
static iterFunction<TValue>(
|
||||
fn: ((value: TValue, node: TreeNode<TValue>, depth: number) => TreeNode<TValue> | null) | Function,
|
||||
depth = 0
|
||||
) {
|
||||
return (node: TreeNode<TValue>) => fn(node.value, node, depth);
|
||||
}
|
||||
|
||||
static searchFunction<TValue>(search: Function | TreeNode<TValue>) {
|
||||
if (typeof search === 'function') {
|
||||
return search;
|
||||
}
|
||||
|
||||
return (value: TValue, node: TreeNode<TValue>) => (search instanceof TreeNode ? node === search : value === search);
|
||||
}
|
||||
|
||||
constructor(value: TValue, children: Array<TreeNode<TValue>> = []) {
|
||||
this.value = value;
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
get depth(): number {
|
||||
return this.children.reduce((depth, child) => Math.max(child.depth + 1, depth), 1);
|
||||
}
|
||||
|
||||
get size() {
|
||||
let i = 0;
|
||||
this.walk(() => i++);
|
||||
return i;
|
||||
}
|
||||
|
||||
addChild(child: TreeNode<TValue> | TValue) {
|
||||
this.children.push(child instanceof TreeNode ? child : new TreeNode(child));
|
||||
return this;
|
||||
}
|
||||
|
||||
find(search: Function | TreeNode<TValue>): TreeNode<TValue> | null {
|
||||
const searchFn = TreeNode.iterFunction(TreeNode.searchFunction(search));
|
||||
if (searchFn(this)) {
|
||||
return this;
|
||||
}
|
||||
for (let i = 0; i < this.children.length; i++) {
|
||||
const result = this.children[i].find(search);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getPath(search: Function | TreeNode<TValue>) {
|
||||
const searchFn = TreeNode.iterFunction(TreeNode.searchFunction(search));
|
||||
|
||||
const findPath = (
|
||||
currentNode: TreeNode<TValue>,
|
||||
currentPath: Array<TreeNode<TValue>>
|
||||
): Array<TreeNode<TValue>> | null => {
|
||||
// skip if we already found the result
|
||||
const attempt = currentPath.concat([currentNode]);
|
||||
// base case: return the array when there is a match
|
||||
if (searchFn(currentNode)) {
|
||||
return attempt;
|
||||
}
|
||||
for (let i = 0; i < currentNode.children.length; i++) {
|
||||
const child = currentNode.children[i];
|
||||
const match = findPath(child, attempt);
|
||||
if (match) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return findPath(this, []);
|
||||
}
|
||||
|
||||
walk(fn: (spanID: TValue, node: TreeNode<TValue>, depth: number) => void, startDepth = 0) {
|
||||
type StackEntry = {
|
||||
node: TreeNode<TValue>;
|
||||
depth: number;
|
||||
};
|
||||
const nodeStack: StackEntry[] = [];
|
||||
let actualDepth = startDepth;
|
||||
nodeStack.push({ node: this, depth: actualDepth });
|
||||
while (nodeStack.length) {
|
||||
const entry: StackEntry = nodeStack[nodeStack.length - 1];
|
||||
nodeStack.pop();
|
||||
const { node, depth } = entry;
|
||||
fn(node.value, node, depth);
|
||||
actualDepth = depth + 1;
|
||||
let i = node.children.length - 1;
|
||||
while (i >= 0) {
|
||||
nodeStack.push({ node: node.children[i], depth: actualDepth });
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
paths(fn: (pathIds: TValue[]) => void) {
|
||||
type StackEntry = {
|
||||
node: TreeNode<TValue>;
|
||||
childIndex: number;
|
||||
};
|
||||
const stack: StackEntry[] = [];
|
||||
stack.push({ node: this, childIndex: 0 });
|
||||
const paths: TValue[] = [];
|
||||
while (stack.length) {
|
||||
const { node, childIndex } = stack[stack.length - 1];
|
||||
if (node.children.length >= childIndex + 1) {
|
||||
stack[stack.length - 1].childIndex++;
|
||||
stack.push({ node: node.children[childIndex], childIndex: 0 });
|
||||
} else {
|
||||
if (node.children.length === 0) {
|
||||
const path = stack.map((item) => item.node.value);
|
||||
fn(path);
|
||||
}
|
||||
stack.pop();
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
// 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.
|
||||
|
||||
const FALLBACK_DAG_MAX_NUM_SERVICES = 100;
|
||||
|
||||
export default Object.defineProperty(
|
||||
{
|
||||
archiveEnabled: false,
|
||||
dependencies: {
|
||||
dagMaxNumServices: FALLBACK_DAG_MAX_NUM_SERVICES,
|
||||
menuEnabled: true,
|
||||
},
|
||||
linkPatterns: [],
|
||||
search: {
|
||||
maxLookback: {
|
||||
label: '2 Days',
|
||||
value: '2d',
|
||||
},
|
||||
maxLimit: 1500,
|
||||
},
|
||||
tracking: {
|
||||
gaID: null,
|
||||
trackErrors: true,
|
||||
},
|
||||
},
|
||||
// fields that should be individually merged vs wholesale replaced
|
||||
'__mergeFields',
|
||||
{ value: ['dependencies', 'search', 'tracking'] }
|
||||
);
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
// 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.
|
||||
|
||||
import { get as _get } from 'lodash';
|
||||
|
||||
import defaultConfig from './default-config';
|
||||
|
||||
/**
|
||||
* Merge the embedded config from the query service (if present) with the
|
||||
* default config from `../../constants/default-config`.
|
||||
*/
|
||||
export default function getConfig() {
|
||||
return defaultConfig;
|
||||
}
|
||||
|
||||
export function getConfigValue(path: string) {
|
||||
return _get(getConfig(), path);
|
||||
}
|
||||
|
|
@ -8,7 +8,6 @@ import { fuzzyMatch, InlineField, InlineFieldRow, Input, Select } from '@grafana
|
|||
|
||||
import { JaegerDatasource } from '../datasource';
|
||||
import { JaegerQuery } from '../types';
|
||||
import { transformToLogfmt } from '../util';
|
||||
|
||||
const durationPlaceholder = 'e.g. 1.2s, 100ms, 500us';
|
||||
|
||||
|
|
@ -150,7 +149,7 @@ export function SearchForm({ datasource, query, onChange }: Props) {
|
|||
<InlineField label="Tags" labelWidth={14} grow tooltip="Values should be in logfmt.">
|
||||
<Input
|
||||
id="tags"
|
||||
value={transformToLogfmt(query.tags)}
|
||||
value={query.tags}
|
||||
placeholder="http.status_code=200 error=true"
|
||||
onChange={(v) =>
|
||||
onChange({
|
||||
|
|
|
|||
|
|
@ -136,16 +136,6 @@ describe('node graph functionality', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('time range', () => {
|
||||
it('should calculate correct time range', async () => {
|
||||
const ds = new JaegerDatasource(defaultSettings);
|
||||
const timeRange = ds.getTimeRange();
|
||||
const now = Date.now();
|
||||
expect(timeRange.end).toBeCloseTo(now * 1000, -4);
|
||||
expect(timeRange.start).toBeCloseTo((now - 6 * 3600 * 1000) * 1000, -4);
|
||||
});
|
||||
});
|
||||
|
||||
function setupQueryMock(type: 'trace' | 'search') {
|
||||
return jest.spyOn(DataSourceWithBackend.prototype, 'query').mockImplementation(() => {
|
||||
if (type === 'search') {
|
||||
|
|
|
|||
|
|
@ -6,10 +6,7 @@ import {
|
|||
DataQueryResponse,
|
||||
DataSourceInstanceSettings,
|
||||
DataSourceJsonData,
|
||||
dateMath,
|
||||
DateTime,
|
||||
FieldType,
|
||||
getDefaultTimeRange,
|
||||
MutableDataFrame,
|
||||
ScopedVars,
|
||||
toDataFrame,
|
||||
|
|
@ -45,10 +42,6 @@ export class JaegerDatasource extends DataSourceWithBackend<JaegerQuery, JaegerJ
|
|||
return await this.getResource(url, params);
|
||||
}
|
||||
|
||||
isSearchFormValid(query: JaegerQuery): boolean {
|
||||
return !!query.service;
|
||||
}
|
||||
|
||||
query(options: DataQueryRequest<JaegerQuery>): Observable<DataQueryResponse> {
|
||||
// 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.
|
||||
|
|
@ -122,25 +115,11 @@ export class JaegerDatasource extends DataSourceWithBackend<JaegerQuery, JaegerJ
|
|||
return await super.testDatasource();
|
||||
}
|
||||
|
||||
getTimeRange(range = getDefaultTimeRange()): { start: number; end: number } {
|
||||
return {
|
||||
start: getTime(range.from, false),
|
||||
end: getTime(range.to, true),
|
||||
};
|
||||
}
|
||||
|
||||
getQueryDisplayText(query: JaegerQuery) {
|
||||
return query.query || '';
|
||||
}
|
||||
}
|
||||
|
||||
function getTime(date: string | DateTime, roundUp: boolean) {
|
||||
if (typeof date === 'string') {
|
||||
date = dateMath.parse(date, roundUp)!;
|
||||
}
|
||||
return date.valueOf() * 1000;
|
||||
}
|
||||
|
||||
const emptyTraceDataFrame = new MutableDataFrame({
|
||||
fields: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,106 +0,0 @@
|
|||
import { mapJaegerDependenciesResponse } from './dependencyGraphTransform';
|
||||
|
||||
describe('dependencyGraphTransform', () => {
|
||||
it('should transform Jaeger dependencies API response', () => {
|
||||
const data = {
|
||||
data: [
|
||||
{
|
||||
parent: 'serviceA',
|
||||
child: 'serviceB',
|
||||
callCount: 1,
|
||||
},
|
||||
{
|
||||
parent: 'serviceA',
|
||||
child: 'serviceC',
|
||||
callCount: 2,
|
||||
},
|
||||
{
|
||||
parent: 'serviceB',
|
||||
child: 'serviceC',
|
||||
callCount: 3,
|
||||
},
|
||||
],
|
||||
total: 0,
|
||||
limit: 0,
|
||||
offset: 0,
|
||||
};
|
||||
|
||||
const res = mapJaegerDependenciesResponse({ data });
|
||||
expect(res).toMatchObject({
|
||||
data: [
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
config: {},
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
values: ['serviceA', 'serviceB', 'serviceC'],
|
||||
},
|
||||
{
|
||||
config: {},
|
||||
name: 'title',
|
||||
type: 'string',
|
||||
values: ['serviceA', 'serviceB', 'serviceC'],
|
||||
},
|
||||
],
|
||||
meta: { preferredVisualisationType: 'nodeGraph' },
|
||||
},
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
config: {},
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
values: ['serviceA--serviceB', 'serviceA--serviceC', 'serviceB--serviceC'],
|
||||
},
|
||||
{
|
||||
config: {},
|
||||
name: 'target',
|
||||
type: 'string',
|
||||
values: ['serviceB', 'serviceC', 'serviceC'],
|
||||
},
|
||||
{
|
||||
config: {},
|
||||
name: 'source',
|
||||
type: 'string',
|
||||
values: ['serviceA', 'serviceA', 'serviceB'],
|
||||
},
|
||||
{
|
||||
config: { displayName: 'Call count' },
|
||||
name: 'mainstat',
|
||||
type: 'string',
|
||||
values: [1, 2, 3],
|
||||
},
|
||||
],
|
||||
meta: { preferredVisualisationType: 'nodeGraph' },
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should transform Jaeger API error', () => {
|
||||
const data = {
|
||||
total: 0,
|
||||
limit: 0,
|
||||
offset: 0,
|
||||
errors: [
|
||||
{
|
||||
code: 400,
|
||||
msg: 'unable to parse param \'endTs\': strconv.ParseInt: parsing "foo": invalid syntax',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const res = mapJaegerDependenciesResponse({ data });
|
||||
|
||||
expect(res).toEqual({
|
||||
data: [],
|
||||
errors: [
|
||||
{
|
||||
message: 'unable to parse param \'endTs\': strconv.ParseInt: parsing "foo": invalid syntax',
|
||||
status: 400,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
import {
|
||||
DataFrame,
|
||||
DataQueryResponse,
|
||||
FieldType,
|
||||
MutableDataFrame,
|
||||
NodeGraphDataFrameFieldNames as Fields,
|
||||
} from '@grafana/data';
|
||||
|
||||
import { JaegerServiceDependency } from './types';
|
||||
|
||||
interface Node {
|
||||
[Fields.id]: string;
|
||||
[Fields.title]: string;
|
||||
}
|
||||
|
||||
interface Edge {
|
||||
[Fields.id]: string;
|
||||
[Fields.target]: string;
|
||||
[Fields.source]: string;
|
||||
[Fields.mainStat]: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error schema used by the Jaeger dependencies API.
|
||||
*/
|
||||
interface JaegerDependenciesResponseError {
|
||||
code: number;
|
||||
msg: string;
|
||||
}
|
||||
|
||||
interface JaegerDependenciesResponse {
|
||||
data?: {
|
||||
errors?: JaegerDependenciesResponseError[];
|
||||
data?: JaegerServiceDependency[];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a Jaeger dependencies API response to a Grafana {@link DataQueryResponse}.
|
||||
* @param response Raw response data from the API proxy.
|
||||
*/
|
||||
export function mapJaegerDependenciesResponse(response: JaegerDependenciesResponse): DataQueryResponse {
|
||||
const errors = response?.data?.errors;
|
||||
if (errors) {
|
||||
return {
|
||||
data: [],
|
||||
errors: errors.map((e: JaegerDependenciesResponseError) => ({ message: e.msg, status: e.code })),
|
||||
};
|
||||
}
|
||||
const dependencies = response?.data?.data;
|
||||
if (dependencies) {
|
||||
return {
|
||||
data: convertDependenciesToGraph(dependencies),
|
||||
};
|
||||
}
|
||||
|
||||
return { data: [] };
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a list of Jaeger service dependencies to a Grafana {@link DataFrame} array suitable for the node graph panel.
|
||||
* @param dependencies List of Jaeger service dependencies as returned by the Jaeger dependencies API.
|
||||
*/
|
||||
function convertDependenciesToGraph(dependencies: JaegerServiceDependency[]): DataFrame[] {
|
||||
const servicesByName = new Map<string, Node>();
|
||||
const edges: Edge[] = [];
|
||||
|
||||
for (const dependency of dependencies) {
|
||||
addServiceNode(dependency.parent, servicesByName);
|
||||
addServiceNode(dependency.child, servicesByName);
|
||||
|
||||
edges.push({
|
||||
[Fields.id]: dependency.parent + '--' + dependency.child,
|
||||
[Fields.target]: dependency.child,
|
||||
[Fields.source]: dependency.parent,
|
||||
[Fields.mainStat]: dependency.callCount,
|
||||
});
|
||||
}
|
||||
|
||||
const nodesFrame = new MutableDataFrame({
|
||||
fields: [
|
||||
{ name: Fields.id, type: FieldType.string },
|
||||
{ name: Fields.title, type: FieldType.string },
|
||||
],
|
||||
meta: {
|
||||
preferredVisualisationType: 'nodeGraph',
|
||||
},
|
||||
});
|
||||
|
||||
const edgesFrame = new MutableDataFrame({
|
||||
fields: [
|
||||
{ name: Fields.id, type: FieldType.string },
|
||||
{ name: Fields.target, type: FieldType.string },
|
||||
{ name: Fields.source, type: FieldType.string },
|
||||
{ name: Fields.mainStat, type: FieldType.string, config: { displayName: 'Call count' } },
|
||||
],
|
||||
meta: {
|
||||
preferredVisualisationType: 'nodeGraph',
|
||||
},
|
||||
});
|
||||
|
||||
for (const node of servicesByName.values()) {
|
||||
nodesFrame.add(node);
|
||||
}
|
||||
|
||||
for (const edge of edges) {
|
||||
edgesFrame.add(edge);
|
||||
}
|
||||
|
||||
return [nodesFrame, edgesFrame];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to register a service node in the dependency graph.
|
||||
* @param service Name of the service to register.
|
||||
* @param servicesByName Map of service nodes keyed name.
|
||||
*/
|
||||
function addServiceNode(service: string, servicesByName: Map<string, Node>) {
|
||||
if (!servicesByName.has(service)) {
|
||||
servicesByName.set(service, {
|
||||
[Fields.id]: service,
|
||||
[Fields.title]: service,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,5 @@
|
|||
import {
|
||||
DataFrame,
|
||||
DataSourceInstanceSettings,
|
||||
FieldType,
|
||||
MutableDataFrame,
|
||||
TraceLog,
|
||||
TraceSpanRow,
|
||||
} from '@grafana/data';
|
||||
import { DataFrame, FieldType, MutableDataFrame, TraceLog, TraceSpanRow } from '@grafana/data';
|
||||
|
||||
import transformTraceData from './_importedDependencies/model/transform-trace-data';
|
||||
import { JaegerResponse, Span, TraceProcess, TraceResponse } from './types';
|
||||
|
||||
export function createTraceFrame(data: TraceResponse): DataFrame {
|
||||
|
|
@ -68,62 +60,6 @@ function toSpanRow(span: Span, processes: Record<string, TraceProcess>): TraceSp
|
|||
};
|
||||
}
|
||||
|
||||
export function createTableFrame(data: TraceResponse[], instanceSettings: DataSourceInstanceSettings): DataFrame {
|
||||
const frame = new MutableDataFrame({
|
||||
fields: [
|
||||
{
|
||||
name: 'traceID',
|
||||
type: FieldType.string,
|
||||
config: {
|
||||
unit: 'string',
|
||||
displayNameFromDS: 'Trace ID',
|
||||
links: [
|
||||
{
|
||||
title: 'Trace: ${__value.raw}',
|
||||
url: '',
|
||||
internal: {
|
||||
datasourceUid: instanceSettings.uid,
|
||||
datasourceName: instanceSettings.name,
|
||||
query: {
|
||||
query: '${__value.raw}',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{ name: 'traceName', type: FieldType.string, config: { displayNameFromDS: 'Trace name' } },
|
||||
{ name: 'startTime', type: FieldType.time, config: { displayNameFromDS: 'Start time' } },
|
||||
{ name: 'duration', type: FieldType.number, config: { displayNameFromDS: 'Duration', unit: 'µs' } },
|
||||
],
|
||||
meta: {
|
||||
preferredVisualisationType: 'table',
|
||||
},
|
||||
});
|
||||
// Show the most recent traces
|
||||
const traceData = data.map(transformToTraceData).sort((a, b) => b?.startTime! - a?.startTime!);
|
||||
|
||||
for (const trace of traceData) {
|
||||
frame.add(trace);
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
function transformToTraceData(data: TraceResponse) {
|
||||
const traceData = transformTraceData(data);
|
||||
if (!traceData) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
traceID: traceData.traceID,
|
||||
startTime: traceData.startTime / 1000,
|
||||
duration: traceData.duration,
|
||||
traceName: traceData.traceName,
|
||||
};
|
||||
}
|
||||
|
||||
export function transformToJaeger(data: MutableDataFrame): JaegerResponse {
|
||||
let traceResponse: TraceResponse = {
|
||||
traceID: '',
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
import { DataQuery, TraceKeyValuePair, TraceLog } from '@grafana/data';
|
||||
|
||||
export type TraceLink = {
|
||||
url: string;
|
||||
text: string;
|
||||
};
|
||||
import { TraceKeyValuePair, TraceLog } from '@grafana/data';
|
||||
import { DataQuery } from '@grafana/schema';
|
||||
|
||||
export type TraceProcess = {
|
||||
serviceName: string;
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
import logfmt from 'logfmt';
|
||||
|
||||
export function convertTagsLogfmt(tags: string | undefined) {
|
||||
if (!tags) {
|
||||
return '';
|
||||
}
|
||||
const data = logfmt.parse(tags);
|
||||
Object.keys(data).forEach((key) => {
|
||||
const value = data[key];
|
||||
if (typeof value !== 'string') {
|
||||
data[key] = String(value);
|
||||
}
|
||||
});
|
||||
return JSON.stringify(data);
|
||||
}
|
||||
|
||||
export function transformToLogfmt(tags: string | undefined) {
|
||||
if (!tags) {
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
return logfmt.stringify(JSON.parse(tags));
|
||||
} catch {
|
||||
return tags;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue