Skip to content

Commit d1f791c

Browse files
Jaeger: Decouple Jaeger plugin (#81377)
1 parent 83597e6 commit d1f791c

27 files changed

+2189
-381
lines changed

.betterer.results

+10
Original file line numberDiff line numberDiff line change
@@ -5008,6 +5008,12 @@ exports[`better eslint`] = {
50085008
[0, 0, 0, "Styles should be written using objects.", "0"],
50095009
[0, 0, 0, "Styles should be written using objects.", "1"]
50105010
],
5011+
"public/app/plugins/datasource/jaeger/_importedDependencies/model/transform-trace-data.tsx:5381": [
5012+
[0, 0, 0, "Do not use any type assertions.", "0"]
5013+
],
5014+
"public/app/plugins/datasource/jaeger/_importedDependencies/types/trace.ts:5381": [
5015+
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
5016+
],
50115017
"public/app/plugins/datasource/jaeger/configuration/ConfigEditor.tsx:5381": [
50125018
[0, 0, 0, "Styles should be written using objects.", "0"]
50135019
],
@@ -5019,6 +5025,10 @@ exports[`better eslint`] = {
50195025
[0, 0, 0, "Do not use any type assertions.", "0"],
50205026
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
50215027
],
5028+
"public/app/plugins/datasource/jaeger/helpers/createFetchResponse.ts:5381": [
5029+
[0, 0, 0, "Do not use any type assertions.", "0"],
5030+
[0, 0, 0, "Do not use any type assertions.", "1"]
5031+
],
50225032
"public/app/plugins/datasource/jaeger/testResponse.ts:5381": [
50235033
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
50245034
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]

.eslintrc

+10-4
Original file line numberDiff line numberDiff line change
@@ -96,16 +96,22 @@
9696
},
9797
{
9898
"files": [
99+
"public/app/plugins/datasource/azuremonitor/*.{ts,tsx}",
100+
"public/app/plugins/datasource/azuremonitor/**/*.{ts,tsx}",
101+
"public/app/plugins/datasource/cloud-monitoring/*.{ts,tsx}",
102+
"public/app/plugins/datasource/cloud-monitoring/**/*.{ts,tsx}",
103+
"public/app/plugins/datasource/elasticsearch/*.{ts,tsx}",
104+
"public/app/plugins/datasource/elasticsearch/**/*.{ts,tsx}",
99105
"public/app/plugins/datasource/grafana-postgresql-datasource/*.{ts,tsx}",
100106
"public/app/plugins/datasource/grafana-postgresql-datasource/**/*.{ts,tsx}",
101107
"public/app/plugins/datasource/grafana-pyroscope-datasource/*.{ts,tsx}",
102108
"public/app/plugins/datasource/grafana-pyroscope-datasource/**/*.{ts,tsx}",
103109
"public/app/plugins/datasource/grafana-testdata-datasource/*.{ts,tsx}",
104110
"public/app/plugins/datasource/grafana-testdata-datasource/**/*.{ts,tsx}",
105-
"public/app/plugins/datasource/azuremonitor/*.{ts,tsx}",
106-
"public/app/plugins/datasource/azuremonitor/**/*.{ts,tsx}",
107-
"public/app/plugins/datasource/cloud-monitoring/*.{ts,tsx}",
108-
"public/app/plugins/datasource/cloud-monitoring/**/*.{ts,tsx}",
111+
"public/app/plugins/datasource/jaeger/*.{ts,tsx}",
112+
"public/app/plugins/datasource/jaeger/**/*.{ts,tsx}",
113+
"public/app/plugins/datasource/loki/*.{ts,tsx}",
114+
"public/app/plugins/datasource/loki/**/*.{ts,tsx}",
109115
"public/app/plugins/datasource/mysql/*.{ts,tsx}",
110116
"public/app/plugins/datasource/mysql/**/*.{ts,tsx}",
111117
"public/app/plugins/datasource/parca/*.{ts,tsx}",

.github/workflows/core-plugins-build-and-release.yml

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ on:
1111
- grafana-azure-monitor-datasource
1212
- grafana-pyroscope-datasource
1313
- grafana-testdata-datasource
14+
- jaeger
1415
- parca
1516
- stackdriver
1617
- tempo

pkg/tests/api/plugins/data/expectedListResp.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -961,7 +961,7 @@
961961
"keywords": null
962962
},
963963
"dependencies": {
964-
"grafanaDependency": "",
964+
"grafanaDependency": "\u003e=10.3.0-0",
965965
"grafanaVersion": "*",
966966
"plugins": []
967967
},

public/app/features/plugins/built_in_plugins.ts

-3
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ const grafanaPlugin = async () =>
1313
const influxdbPlugin = async () =>
1414
await import(/* webpackChunkName: "influxdbPlugin" */ 'app/plugins/datasource/influxdb/module');
1515
const lokiPlugin = async () => await import(/* webpackChunkName: "lokiPlugin" */ 'app/plugins/datasource/loki/module');
16-
const jaegerPlugin = async () =>
17-
await import(/* webpackChunkName: "jaegerPlugin" */ 'app/plugins/datasource/jaeger/module');
1816
const mixedPlugin = async () =>
1917
await import(/* webpackChunkName: "mixedPlugin" */ 'app/plugins/datasource/mixed/module');
2018
const mysqlPlugin = async () =>
@@ -77,7 +75,6 @@ const builtInPlugins: Record<string, System.Module | (() => Promise<System.Modul
7775
'core:plugin/grafana': grafanaPlugin,
7876
'core:plugin/influxdb': influxdbPlugin,
7977
'core:plugin/loki': lokiPlugin,
80-
'core:plugin/jaeger': jaegerPlugin,
8178
'core:plugin/mixed': mixedPlugin,
8279
'core:plugin/mysql': mysqlPlugin,
8380
'core:plugin/grafana-postgresql-datasource': postgresPlugin,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Changelog
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
# Jaeger Data Source - Native Plugin
1+
# Grafana Jaeger Data Source - Native Plugin
22

3-
Grafana ships with **built in** support for Jaeger, an open source, end-to-end distributed tracing system.
4-
5-
Read more about it here:
6-
7-
[https://docs.grafana.org/datasources/jaeger/](https://docs.grafana.org/datasources/jaeger/)
3+
[https://docs.grafana.org/datasources/jaeger/](Grafana plugin for the Jaeger data source).
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
This directory contains dependencies that we duplicated from Grafana core while working on the decoupling of Jaeger from such core.
2+
The long-term goal is to move these files away from here by replacing them with packages.
3+
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.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright (c) 2020 The Jaeger Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import { memoize } from 'lodash';
16+
17+
import { TraceSpan } from '../types';
18+
19+
function _getTraceNameImpl(spans: TraceSpan[]) {
20+
// Use a span with no references to another span in given array
21+
// prefering the span with the fewest references
22+
// using start time as a tie breaker
23+
let candidateSpan: TraceSpan | undefined;
24+
const allIDs: Set<string> = new Set(spans.map(({ spanID }) => spanID));
25+
26+
for (let i = 0; i < spans.length; i++) {
27+
const hasInternalRef =
28+
spans[i].references &&
29+
spans[i].references.some(({ traceID, spanID }) => traceID === spans[i].traceID && allIDs.has(spanID));
30+
if (hasInternalRef) {
31+
continue;
32+
}
33+
34+
if (!candidateSpan) {
35+
candidateSpan = spans[i];
36+
continue;
37+
}
38+
39+
const thisRefLength = (spans[i].references && spans[i].references.length) || 0;
40+
const candidateRefLength = (candidateSpan.references && candidateSpan.references.length) || 0;
41+
42+
if (
43+
thisRefLength < candidateRefLength ||
44+
(thisRefLength === candidateRefLength && spans[i].startTime < candidateSpan.startTime)
45+
) {
46+
candidateSpan = spans[i];
47+
}
48+
}
49+
return candidateSpan ? `${candidateSpan.process.serviceName}: ${candidateSpan.operationName}` : '';
50+
}
51+
52+
export const getTraceName = memoize(_getTraceNameImpl, (spans: TraceSpan[]) => {
53+
if (!spans.length) {
54+
return 0;
55+
}
56+
return spans[0].traceID;
57+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
// Copyright (c) 2017 Uber Technologies, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import { isEqual as _isEqual } from 'lodash';
16+
17+
import { getTraceSpanIdsAsTree } from '../selectors/trace';
18+
import { TraceKeyValuePair, TraceSpan, Trace, TraceResponse, TraceProcess } from '../types';
19+
import TreeNode from '../utils/TreeNode';
20+
import { getConfigValue } from '../utils/config/get-config';
21+
22+
import { getTraceName } from './trace-viewer';
23+
24+
function deduplicateTags(tags: TraceKeyValuePair[]) {
25+
const warningsHash: Map<string, string> = new Map<string, string>();
26+
const dedupedTags: TraceKeyValuePair[] = tags.reduce<TraceKeyValuePair[]>((uniqueTags, tag) => {
27+
if (!uniqueTags.some((t) => t.key === tag.key && t.value === tag.value)) {
28+
uniqueTags.push(tag);
29+
} else {
30+
warningsHash.set(`${tag.key}:${tag.value}`, `Duplicate tag "${tag.key}:${tag.value}"`);
31+
}
32+
return uniqueTags;
33+
}, []);
34+
const warnings = Array.from(warningsHash.values());
35+
return { dedupedTags, warnings };
36+
}
37+
38+
function orderTags(tags: TraceKeyValuePair[], topPrefixes?: string[]) {
39+
const orderedTags: TraceKeyValuePair[] = tags?.slice() ?? [];
40+
const tp = (topPrefixes || []).map((p: string) => p.toLowerCase());
41+
42+
orderedTags.sort((a, b) => {
43+
const aKey = a.key.toLowerCase();
44+
const bKey = b.key.toLowerCase();
45+
46+
for (let i = 0; i < tp.length; i++) {
47+
const p = tp[i];
48+
if (aKey.startsWith(p) && !bKey.startsWith(p)) {
49+
return -1;
50+
}
51+
if (!aKey.startsWith(p) && bKey.startsWith(p)) {
52+
return 1;
53+
}
54+
}
55+
56+
if (aKey > bKey) {
57+
return 1;
58+
}
59+
if (aKey < bKey) {
60+
return -1;
61+
}
62+
return 0;
63+
});
64+
65+
return orderedTags;
66+
}
67+
68+
/**
69+
* NOTE: Mutates `data` - Transform the HTTP response data into the form the app
70+
* generally requires.
71+
*/
72+
export default function transformTraceData(data: TraceResponse | undefined): Trace | null {
73+
if (!data?.traceID) {
74+
return null;
75+
}
76+
const traceID = data.traceID.toLowerCase();
77+
78+
let traceEndTime = 0;
79+
let traceStartTime = Number.MAX_SAFE_INTEGER;
80+
const spanIdCounts = new Map();
81+
const spanMap = new Map<string, TraceSpan>();
82+
// filter out spans with empty start times
83+
// eslint-disable-next-line no-param-reassign
84+
data.spans = data.spans.filter((span) => Boolean(span.startTime));
85+
86+
// Sort process tags
87+
data.processes = Object.entries(data.processes).reduce<Record<string, TraceProcess>>((processes, [id, process]) => {
88+
processes[id] = {
89+
...process,
90+
tags: orderTags(process.tags),
91+
};
92+
return processes;
93+
}, {});
94+
95+
const max = data.spans.length;
96+
for (let i = 0; i < max; i++) {
97+
const span: TraceSpan = data.spans[i] as TraceSpan;
98+
const { startTime, duration, processID } = span;
99+
100+
let spanID = span.spanID;
101+
// check for start / end time for the trace
102+
if (startTime < traceStartTime) {
103+
traceStartTime = startTime;
104+
}
105+
if (startTime + duration > traceEndTime) {
106+
traceEndTime = startTime + duration;
107+
}
108+
// make sure span IDs are unique
109+
const idCount = spanIdCounts.get(spanID);
110+
if (idCount != null) {
111+
// eslint-disable-next-line no-console
112+
console.warn(`Dupe spanID, ${idCount + 1} x ${spanID}`, span, spanMap.get(spanID));
113+
if (_isEqual(span, spanMap.get(spanID))) {
114+
// eslint-disable-next-line no-console
115+
console.warn('\t two spans with same ID have `isEqual(...) === true`');
116+
}
117+
spanIdCounts.set(spanID, idCount + 1);
118+
spanID = `${spanID}_${idCount}`;
119+
span.spanID = spanID;
120+
} else {
121+
spanIdCounts.set(spanID, 1);
122+
}
123+
span.process = data.processes[processID];
124+
spanMap.set(spanID, span);
125+
}
126+
// tree is necessary to sort the spans, so children follow parents, and
127+
// siblings are sorted by start time
128+
const tree = getTraceSpanIdsAsTree(data, spanMap);
129+
const spans: TraceSpan[] = [];
130+
const svcCounts: Record<string, number> = {};
131+
132+
tree.walk((spanID: string, node: TreeNode<string>, depth = 0) => {
133+
if (spanID === '__root__') {
134+
return;
135+
}
136+
if (typeof spanID !== 'string') {
137+
return;
138+
}
139+
const span = spanMap.get(spanID);
140+
if (!span) {
141+
return;
142+
}
143+
const { serviceName } = span.process;
144+
svcCounts[serviceName] = (svcCounts[serviceName] || 0) + 1;
145+
span.relativeStartTime = span.startTime - traceStartTime;
146+
span.depth = depth - 1;
147+
span.hasChildren = node.children.length > 0;
148+
span.childSpanCount = node.children.length;
149+
span.warnings = span.warnings || [];
150+
span.tags = span.tags || [];
151+
span.references = span.references || [];
152+
153+
span.childSpanIds = node.children
154+
.slice()
155+
.sort((a, b) => {
156+
const spanA = spanMap.get(a.value)!;
157+
const spanB = spanMap.get(b.value)!;
158+
return spanB.startTime + spanB.duration - (spanA.startTime + spanA.duration);
159+
})
160+
.map((each) => each.value);
161+
162+
const tagsInfo = deduplicateTags(span.tags);
163+
span.tags = orderTags(tagsInfo.dedupedTags, getConfigValue('topTagPrefixes'));
164+
span.warnings = span.warnings.concat(tagsInfo.warnings);
165+
span.references.forEach((ref, index) => {
166+
const refSpan = spanMap.get(ref.spanID);
167+
if (refSpan) {
168+
// eslint-disable-next-line no-param-reassign
169+
ref.span = refSpan;
170+
if (index > 0) {
171+
// Don't take into account the parent, just other references.
172+
refSpan.subsidiarilyReferencedBy = refSpan.subsidiarilyReferencedBy || [];
173+
refSpan.subsidiarilyReferencedBy.push({
174+
spanID,
175+
traceID,
176+
span,
177+
refType: ref.refType,
178+
});
179+
}
180+
}
181+
});
182+
spans.push(span);
183+
});
184+
const traceName = getTraceName(spans);
185+
const services = Object.keys(svcCounts).map((name) => ({ name, numberOfSpans: svcCounts[name] }));
186+
return {
187+
services,
188+
spans,
189+
traceID,
190+
traceName,
191+
// can't use spread operator for intersection types
192+
// repl: https://goo.gl/4Z23MJ
193+
// issue: https://github.com/facebook/flow/issues/1511
194+
processes: data.processes,
195+
duration: traceEndTime - traceStartTime,
196+
startTime: traceStartTime,
197+
endTime: traceEndTime,
198+
};
199+
}

0 commit comments

Comments
 (0)