@perses-dev/tempo-plugin 0.46.0 → 0.47.0-rc0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/model/api-types.js +23 -1
- package/dist/cjs/model/tempo-client.js +51 -28
- package/dist/cjs/plugins/tempo-datasource.js +3 -3
- package/dist/cjs/plugins/tempo-trace-query/TempoTraceQuery.js +1 -1
- package/dist/cjs/plugins/tempo-trace-query/get-trace-data.js +128 -32
- package/dist/cjs/test/mock-data.js +2461 -796
- package/dist/model/api-types.d.ts +88 -51
- package/dist/model/api-types.d.ts.map +1 -1
- package/dist/model/api-types.js +4 -4
- package/dist/model/api-types.js.map +1 -1
- package/dist/model/tempo-client.d.ts +18 -16
- package/dist/model/tempo-client.d.ts.map +1 -1
- package/dist/model/tempo-client.js +54 -33
- package/dist/model/tempo-client.js.map +1 -1
- package/dist/plugins/tempo-datasource.d.ts.map +1 -1
- package/dist/plugins/tempo-datasource.js +4 -4
- package/dist/plugins/tempo-datasource.js.map +1 -1
- package/dist/plugins/tempo-trace-query/TempoTraceQuery.js +1 -1
- package/dist/plugins/tempo-trace-query/TempoTraceQuery.js.map +1 -1
- package/dist/plugins/tempo-trace-query/get-trace-data.d.ts.map +1 -1
- package/dist/plugins/tempo-trace-query/get-trace-data.js +128 -32
- package/dist/plugins/tempo-trace-query/get-trace-data.js.map +1 -1
- package/dist/test/mock-data.d.ts +7 -3
- package/dist/test/mock-data.d.ts.map +1 -1
- package/dist/test/mock-data.js +2444 -791
- package/dist/test/mock-data.js.map +1 -1
- package/package.json +4 -4
- package/dist/cjs/plugins/TempoTraceQuery.js +0 -30
- package/dist/cjs/plugins/get-trace-data.js +0 -69
- package/dist/plugins/TempoTraceQuery.d.ts +0 -11
- package/dist/plugins/TempoTraceQuery.d.ts.map +0 -1
- package/dist/plugins/TempoTraceQuery.js +0 -24
- package/dist/plugins/TempoTraceQuery.js.map +0 -1
- package/dist/plugins/get-trace-data.d.ts +0 -4
- package/dist/plugins/get-trace-data.d.ts.map +0 -1
- package/dist/plugins/get-trace-data.js +0 -61
- package/dist/plugins/get-trace-data.js.map +0 -1
|
@@ -10,7 +10,29 @@
|
|
|
10
10
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
11
|
// See the License for the specific language governing permissions and
|
|
12
12
|
// limitations under the License.
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Common types
|
|
15
|
+
*/ "use strict";
|
|
14
16
|
Object.defineProperty(exports, "__esModule", {
|
|
15
17
|
value: true
|
|
16
18
|
});
|
|
19
|
+
function _export(target, all) {
|
|
20
|
+
for(var name in all)Object.defineProperty(target, name, {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
get: all[name]
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
_export(exports, {
|
|
26
|
+
SpanStatusError: function() {
|
|
27
|
+
return SpanStatusError;
|
|
28
|
+
},
|
|
29
|
+
SpanStatusOk: function() {
|
|
30
|
+
return SpanStatusOk;
|
|
31
|
+
},
|
|
32
|
+
SpanStatusUnset: function() {
|
|
33
|
+
return SpanStatusUnset;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
const SpanStatusUnset = 'STATUS_CODE_UNSET';
|
|
37
|
+
const SpanStatusOk = 'STATUS_CODE_OK';
|
|
38
|
+
const SpanStatusError = 'STATUS_CODE_ERROR';
|
|
@@ -24,64 +24,87 @@ _export(exports, {
|
|
|
24
24
|
executeRequest: function() {
|
|
25
25
|
return executeRequest;
|
|
26
26
|
},
|
|
27
|
-
getEnrichedTraceQuery: function() {
|
|
28
|
-
return getEnrichedTraceQuery;
|
|
29
|
-
},
|
|
30
27
|
searchTraceID: function() {
|
|
31
28
|
return searchTraceID;
|
|
32
29
|
},
|
|
33
30
|
searchTraceQuery: function() {
|
|
34
31
|
return searchTraceQuery;
|
|
32
|
+
},
|
|
33
|
+
searchTraceQueryFallback: function() {
|
|
34
|
+
return searchTraceQueryFallback;
|
|
35
35
|
}
|
|
36
36
|
});
|
|
37
37
|
const _core = require("@perses-dev/core");
|
|
38
|
-
const
|
|
39
|
-
|
|
38
|
+
const _apitypes = require("./api-types");
|
|
39
|
+
const executeRequest = async (...args)=>{
|
|
40
|
+
const response = await (0, _core.fetch)(...args);
|
|
40
41
|
const jsonData = await response.json();
|
|
41
42
|
return jsonData;
|
|
42
43
|
};
|
|
43
|
-
function fetchWithGet(apiURI,
|
|
44
|
-
const
|
|
45
|
-
|
|
44
|
+
function fetchWithGet(apiURI, params, queryOptions) {
|
|
45
|
+
const { datasourceUrl, headers = {} } = queryOptions;
|
|
46
|
+
let url = `${datasourceUrl}${apiURI}`;
|
|
47
|
+
if (params) {
|
|
48
|
+
url += '?' + new URLSearchParams(params);
|
|
49
|
+
}
|
|
50
|
+
const init = {
|
|
51
|
+
method: 'GET',
|
|
52
|
+
headers
|
|
53
|
+
};
|
|
54
|
+
return executeRequest(url, init);
|
|
46
55
|
}
|
|
47
|
-
function searchTraceQuery(
|
|
48
|
-
return fetchWithGet(`/api/search
|
|
56
|
+
function searchTraceQuery(params, queryOptions) {
|
|
57
|
+
return fetchWithGet(`/api/search`, params, queryOptions);
|
|
49
58
|
}
|
|
50
|
-
function searchTraceID(traceID,
|
|
51
|
-
return fetchWithGet(`/api/traces/${traceID}`,
|
|
59
|
+
function searchTraceID(traceID, queryOptions) {
|
|
60
|
+
return fetchWithGet(`/api/traces/${traceID}`, null, queryOptions);
|
|
52
61
|
}
|
|
53
|
-
async function
|
|
62
|
+
async function searchTraceQueryFallback(params, queryOptions) {
|
|
63
|
+
var _searchResponse_traces_;
|
|
54
64
|
// Get a list of traces that satisfy the query.
|
|
55
|
-
const searchResponse = await searchTraceQuery(
|
|
56
|
-
if (!searchResponse.traces) {
|
|
65
|
+
const searchResponse = await searchTraceQuery(params, queryOptions);
|
|
66
|
+
if (!searchResponse.traces || searchResponse.traces.length === 0) {
|
|
57
67
|
return {
|
|
58
|
-
query,
|
|
59
68
|
traces: []
|
|
60
69
|
};
|
|
61
70
|
}
|
|
71
|
+
// exit early if fallback is not required (serviceStats are contained in the response)
|
|
72
|
+
if ((_searchResponse_traces_ = searchResponse.traces[0]) === null || _searchResponse_traces_ === void 0 ? void 0 : _searchResponse_traces_.serviceStats) {
|
|
73
|
+
return searchResponse;
|
|
74
|
+
}
|
|
75
|
+
// calculate serviceStats (number of spans and errors) per service
|
|
62
76
|
return {
|
|
63
|
-
query,
|
|
64
77
|
traces: await Promise.all(searchResponse.traces.map(async (trace)=>{
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
// For every trace, get the full trace, and find the total number of spans and errors.
|
|
78
|
+
const serviceStats = {};
|
|
79
|
+
const searchTraceIDResponse = await searchTraceID(trace.traceID, queryOptions);
|
|
80
|
+
// For every trace, get the full trace, and find the number of spans and errors.
|
|
69
81
|
for (const batch of searchTraceIDResponse.batches){
|
|
82
|
+
let serviceName = '?';
|
|
83
|
+
for (const attr of batch.resource.attributes){
|
|
84
|
+
if (attr.key === 'service.name' && 'stringValue' in attr.value) {
|
|
85
|
+
serviceName = attr.value.stringValue;
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
var _serviceStats_serviceName;
|
|
90
|
+
const stats = (_serviceStats_serviceName = serviceStats[serviceName]) !== null && _serviceStats_serviceName !== void 0 ? _serviceStats_serviceName : {
|
|
91
|
+
spanCount: 0
|
|
92
|
+
};
|
|
70
93
|
for (const scopeSpan of batch.scopeSpans){
|
|
71
|
-
spanCount += scopeSpan.spans.length;
|
|
94
|
+
stats.spanCount += scopeSpan.spans.length;
|
|
72
95
|
for (const span of scopeSpan.spans){
|
|
73
96
|
var _span_status;
|
|
74
|
-
if ((_span_status = span.status) === null || _span_status === void 0 ? void 0 : _span_status.code) {
|
|
75
|
-
|
|
97
|
+
if (((_span_status = span.status) === null || _span_status === void 0 ? void 0 : _span_status.code) === _apitypes.SpanStatusError) {
|
|
98
|
+
var _stats_errorCount;
|
|
99
|
+
stats.errorCount = ((_stats_errorCount = stats.errorCount) !== null && _stats_errorCount !== void 0 ? _stats_errorCount : 0) + 1;
|
|
76
100
|
}
|
|
77
101
|
}
|
|
78
102
|
}
|
|
103
|
+
serviceStats[serviceName] = stats;
|
|
79
104
|
}
|
|
80
105
|
return {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
spanCount,
|
|
84
|
-
errorCount
|
|
106
|
+
...trace,
|
|
107
|
+
serviceStats
|
|
85
108
|
};
|
|
86
109
|
}))
|
|
87
110
|
};
|
|
@@ -35,9 +35,9 @@ const _tempoclient = require("../model/tempo-client");
|
|
|
35
35
|
options: {
|
|
36
36
|
datasourceUrl
|
|
37
37
|
},
|
|
38
|
-
searchTraceQuery: (
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
searchTraceQuery: (params, queryOptions)=>(0, _tempoclient.searchTraceQuery)(params, queryOptions),
|
|
39
|
+
searchTraceQueryFallback: (params, queryOptions)=>(0, _tempoclient.searchTraceQueryFallback)(params, queryOptions),
|
|
40
|
+
searchTraceID: (traceID, queryOptions)=>(0, _tempoclient.searchTraceID)(traceID, queryOptions)
|
|
41
41
|
};
|
|
42
42
|
};
|
|
43
43
|
const TempoDatasource = {
|
|
@@ -28,7 +28,9 @@ _export(exports, {
|
|
|
28
28
|
return getUnixTimeRange;
|
|
29
29
|
}
|
|
30
30
|
});
|
|
31
|
+
const _core = require("@perses-dev/core");
|
|
31
32
|
const _datefns = require("date-fns");
|
|
33
|
+
const _lodash = require("lodash");
|
|
32
34
|
const _temposelectors = require("../../model/tempo-selectors");
|
|
33
35
|
function getUnixTimeRange(timeRange) {
|
|
34
36
|
const { start, end } = timeRange;
|
|
@@ -43,7 +45,7 @@ const getTraceData = async (spec, context)=>{
|
|
|
43
45
|
// Do not make a request to the backend, instead return an empty TraceData
|
|
44
46
|
console.error('TempoTraceQuery is undefined, null, or an empty string.');
|
|
45
47
|
return {
|
|
46
|
-
|
|
48
|
+
searchResult: []
|
|
47
49
|
};
|
|
48
50
|
}
|
|
49
51
|
const defaultTempoDatasource = {
|
|
@@ -55,47 +57,141 @@ const getTraceData = async (spec, context)=>{
|
|
|
55
57
|
if (datasourceUrl === undefined || datasourceUrl === null || datasourceUrl === '') {
|
|
56
58
|
console.error('TempoDatasource is undefined, null, or an empty string.');
|
|
57
59
|
return {
|
|
58
|
-
|
|
60
|
+
searchResult: []
|
|
59
61
|
};
|
|
60
62
|
}
|
|
61
63
|
const getQuery = ()=>{
|
|
62
64
|
// if time range not defined -- only return the query from the spec
|
|
63
65
|
if (context.absoluteTimeRange === undefined) {
|
|
64
|
-
return
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (spec.query.includes('start=') || spec.query.includes('end=')) {
|
|
68
|
-
return spec.query;
|
|
66
|
+
return {
|
|
67
|
+
q: spec.query
|
|
68
|
+
};
|
|
69
69
|
}
|
|
70
70
|
// handle time range selection from UI drop down (e.g. last 5 minutes, last 1 hour )
|
|
71
71
|
const { start, end } = getUnixTimeRange(context === null || context === void 0 ? void 0 : context.absoluteTimeRange);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
return {
|
|
73
|
+
q: spec.query,
|
|
74
|
+
start,
|
|
75
|
+
end
|
|
76
|
+
};
|
|
76
77
|
};
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
78
|
+
/**
|
|
79
|
+
* determine type of query:
|
|
80
|
+
* if the query is a valid traceId, fetch the trace by traceId
|
|
81
|
+
* otherwise, execute a TraceQL query
|
|
82
|
+
*/ if ((0, _core.isValidTraceId)(spec.query)) {
|
|
83
|
+
const response = await client.searchTraceID(spec.query, {
|
|
84
|
+
datasourceUrl
|
|
85
|
+
});
|
|
85
86
|
return {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
traceId,
|
|
91
|
-
name
|
|
87
|
+
trace: parseTraceResponse(response),
|
|
88
|
+
metadata: {
|
|
89
|
+
executedQueryString: spec.query
|
|
90
|
+
}
|
|
92
91
|
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
92
|
+
} else {
|
|
93
|
+
const response = await client.searchTraceQueryFallback(getQuery(), {
|
|
94
|
+
datasourceUrl
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
searchResult: parseSearchResponse(response),
|
|
98
|
+
metadata: {
|
|
99
|
+
executedQueryString: spec.query
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
function parseResource(resource) {
|
|
105
|
+
let serviceName = 'unknown';
|
|
106
|
+
for (const attr of resource.attributes){
|
|
107
|
+
if (attr.key === 'service.name' && 'stringValue' in attr.value) {
|
|
108
|
+
serviceName = attr.value.stringValue;
|
|
109
|
+
break;
|
|
98
110
|
}
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
serviceName,
|
|
114
|
+
attributes: resource.attributes
|
|
99
115
|
};
|
|
100
|
-
|
|
101
|
-
|
|
116
|
+
}
|
|
117
|
+
function parseEvent(event) {
|
|
118
|
+
return {
|
|
119
|
+
timeUnixMs: parseInt(event.timeUnixNano) * 1e-6,
|
|
120
|
+
name: event.name,
|
|
121
|
+
attributes: event.attributes || []
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* parseSpan parses the Span API type to the internal representation
|
|
126
|
+
* i.e. convert strings to numbers etc.
|
|
127
|
+
*/ function parseSpan(span) {
|
|
128
|
+
return {
|
|
129
|
+
traceId: span.traceId,
|
|
130
|
+
spanId: span.spanId,
|
|
131
|
+
parentSpanId: span.parentSpanId,
|
|
132
|
+
name: span.name,
|
|
133
|
+
kind: span.kind,
|
|
134
|
+
startTimeUnixMs: parseInt(span.startTimeUnixNano) * 1e-6,
|
|
135
|
+
endTimeUnixMs: parseInt(span.endTimeUnixNano) * 1e-6,
|
|
136
|
+
attributes: span.attributes || [],
|
|
137
|
+
events: (span.events || []).map(parseEvent),
|
|
138
|
+
status: span.status
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* parseTraceResponse builds a tree of spans from the Tempo API response
|
|
143
|
+
* time complexity: O(2n)
|
|
144
|
+
*/ function parseTraceResponse(response) {
|
|
145
|
+
// first pass: build lookup table <spanId, Span>
|
|
146
|
+
const lookup = new Map();
|
|
147
|
+
for (const batch of response.batches){
|
|
148
|
+
const resource = parseResource(batch.resource);
|
|
149
|
+
for (const scopeSpan of batch.scopeSpans){
|
|
150
|
+
const scope = scopeSpan.scope;
|
|
151
|
+
for (const tempoSpan of scopeSpan.spans){
|
|
152
|
+
const span = {
|
|
153
|
+
resource,
|
|
154
|
+
scope,
|
|
155
|
+
childSpans: [],
|
|
156
|
+
...parseSpan(tempoSpan)
|
|
157
|
+
};
|
|
158
|
+
lookup.set(tempoSpan.spanId, span);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// second pass: build tree based on parentSpanId property
|
|
163
|
+
let rootSpan = null;
|
|
164
|
+
for (const [, span] of lookup){
|
|
165
|
+
if (!span.parentSpanId) {
|
|
166
|
+
rootSpan = span;
|
|
167
|
+
} else {
|
|
168
|
+
const parent = lookup.get(span.parentSpanId);
|
|
169
|
+
if (!parent) {
|
|
170
|
+
console.error(`span ${span.spanId} has parent ${span.parentSpanId} which has not been received yet`);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
span.parentSpan = parent;
|
|
174
|
+
const insertChildSpanAt = (0, _lodash.sortedIndexBy)(parent.childSpans, span, (s)=>s.startTimeUnixMs);
|
|
175
|
+
parent.childSpans.splice(insertChildSpanAt, 0, span);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (!rootSpan) {
|
|
179
|
+
throw new Error('root span not found');
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
rootSpan
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function parseSearchResponse(response) {
|
|
186
|
+
return response.traces.map((trace)=>{
|
|
187
|
+
var _trace_durationMs;
|
|
188
|
+
return {
|
|
189
|
+
startTimeUnixMs: parseInt(trace.startTimeUnixNano) * 1e-6,
|
|
190
|
+
durationMs: (_trace_durationMs = trace.durationMs) !== null && _trace_durationMs !== void 0 ? _trace_durationMs : 0,
|
|
191
|
+
traceId: trace.traceID,
|
|
192
|
+
rootServiceName: trace.rootServiceName,
|
|
193
|
+
rootTraceName: trace.rootTraceName,
|
|
194
|
+
serviceStats: trace.serviceStats || {}
|
|
195
|
+
};
|
|
196
|
+
});
|
|
197
|
+
}
|