mirror of https://github.com/grafana/grafana
Jaeger: Decouple Jaeger plugin (#81377)
parent
83597e6c37
commit
d1f791cf1f
@ -0,0 +1 @@ |
|||||||
|
# Changelog |
@ -1,7 +1,3 @@ |
|||||||
# Jaeger Data Source - Native Plugin |
# Grafana Jaeger Data Source - Native Plugin |
||||||
|
|
||||||
Grafana ships with **built in** support for Jaeger, an open source, end-to-end distributed tracing system. |
[https://docs.grafana.org/datasources/jaeger/](Grafana plugin for the Jaeger data source). |
||||||
|
|
||||||
Read more about it here: |
|
||||||
|
|
||||||
[https://docs.grafana.org/datasources/jaeger/](https://docs.grafana.org/datasources/jaeger/) |
|
||||||
|
@ -0,0 +1,3 @@ |
|||||||
|
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. |
@ -0,0 +1,57 @@ |
|||||||
|
// 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; |
||||||
|
}); |
@ -0,0 +1,199 @@ |
|||||||
|
// 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 { getTraceSpanIdsAsTree } from '../selectors/trace'; |
||||||
|
import { TraceKeyValuePair, 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, |
||||||
|
}; |
||||||
|
} |
@ -0,0 +1,65 @@ |
|||||||
|
// 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; |
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
// 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 { |
||||||
|
TraceSpan, |
||||||
|
TraceResponse, |
||||||
|
Trace, |
||||||
|
TraceProcess, |
||||||
|
TraceKeyValuePair, |
||||||
|
TraceLink, |
||||||
|
CriticalPathSection, |
||||||
|
} from './trace'; |
@ -0,0 +1,112 @@ |
|||||||
|
// 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; |
||||||
|
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; |
||||||
|
}; |
@ -0,0 +1,139 @@ |
|||||||
|
// 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; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,40 @@ |
|||||||
|
// 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'] } |
||||||
|
); |
@ -0,0 +1,29 @@ |
|||||||
|
// 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); |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
import { FetchResponse } from '@grafana/runtime'; |
||||||
|
|
||||||
|
export function createFetchResponse<T>(data: T): FetchResponse<T> { |
||||||
|
return { |
||||||
|
data, |
||||||
|
status: 200, |
||||||
|
url: 'http://localhost:3000/api/ds/query', |
||||||
|
config: { url: 'http://localhost:3000/api/ds/query' }, |
||||||
|
type: 'basic', |
||||||
|
statusText: 'Ok', |
||||||
|
redirected: false, |
||||||
|
headers: {} as unknown as Headers, |
||||||
|
ok: true, |
||||||
|
}; |
||||||
|
} |
@ -0,0 +1,43 @@ |
|||||||
|
{ |
||||||
|
"name": "@grafana-plugins/jaeger", |
||||||
|
"description": "Jaeger plugin for Grafana", |
||||||
|
"private": true, |
||||||
|
"version": "10.4.0-pre", |
||||||
|
"dependencies": { |
||||||
|
"@emotion/css": "11.11.2", |
||||||
|
"@grafana/data": "workspace:*", |
||||||
|
"@grafana/experimental": "1.7.10", |
||||||
|
"@grafana/o11y-ds-frontend": "workspace:*", |
||||||
|
"@grafana/runtime": "workspace:*", |
||||||
|
"@grafana/ui": "workspace:*", |
||||||
|
"lodash": "4.17.21", |
||||||
|
"logfmt": "^1.3.2", |
||||||
|
"react-window": "1.8.10", |
||||||
|
"rxjs": "7.8.1", |
||||||
|
"stream-browserify": "3.0.0", |
||||||
|
"tslib": "2.6.2", |
||||||
|
"uuid": "9.0.1" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@grafana/plugin-configs": "workspace:*", |
||||||
|
"@testing-library/jest-dom": "6.4.2", |
||||||
|
"@testing-library/react": "14.2.1", |
||||||
|
"@testing-library/user-event": "14.5.2", |
||||||
|
"@types/jest": "29.5.12", |
||||||
|
"@types/lodash": "4.17.0", |
||||||
|
"@types/logfmt": "^1.2.3", |
||||||
|
"@types/react-window": "1.8.8", |
||||||
|
"@types/uuid": "9.0.8", |
||||||
|
"ts-node": "10.9.2", |
||||||
|
"webpack": "5.90.3" |
||||||
|
}, |
||||||
|
"peerDependencies": { |
||||||
|
"@grafana/runtime": "*" |
||||||
|
}, |
||||||
|
"scripts": { |
||||||
|
"build": "webpack -c ./webpack.config.ts --env production", |
||||||
|
"build:commit": "webpack -c ./webpack.config.ts --env production --env commit=$(git rev-parse --short HEAD)", |
||||||
|
"dev": "webpack -w -c ./webpack.config.ts --env development" |
||||||
|
}, |
||||||
|
"packageManager": "yarn@3.6.0" |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"types": ["jest", "@testing-library/jest-dom"] |
||||||
|
}, |
||||||
|
"extends": "@grafana/plugin-configs/tsconfig.json", |
||||||
|
"include": ["."] |
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
import config from '@grafana/plugin-configs/webpack.config'; |
||||||
|
|
||||||
|
const configWithFallback = async (env: Record<string, unknown>) => { |
||||||
|
const response = await config(env); |
||||||
|
if (response !== undefined && response.resolve !== undefined) { |
||||||
|
response.resolve.fallback = { |
||||||
|
...response.resolve.fallback, |
||||||
|
stream: require.resolve('stream-browserify'), |
||||||
|
}; |
||||||
|
} |
||||||
|
return response; |
||||||
|
}; |
||||||
|
|
||||||
|
export default configWithFallback; |
Loading…
Reference in new issue