@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.
Files changed (37) hide show
  1. package/dist/cjs/model/api-types.js +23 -1
  2. package/dist/cjs/model/tempo-client.js +51 -28
  3. package/dist/cjs/plugins/tempo-datasource.js +3 -3
  4. package/dist/cjs/plugins/tempo-trace-query/TempoTraceQuery.js +1 -1
  5. package/dist/cjs/plugins/tempo-trace-query/get-trace-data.js +128 -32
  6. package/dist/cjs/test/mock-data.js +2461 -796
  7. package/dist/model/api-types.d.ts +88 -51
  8. package/dist/model/api-types.d.ts.map +1 -1
  9. package/dist/model/api-types.js +4 -4
  10. package/dist/model/api-types.js.map +1 -1
  11. package/dist/model/tempo-client.d.ts +18 -16
  12. package/dist/model/tempo-client.d.ts.map +1 -1
  13. package/dist/model/tempo-client.js +54 -33
  14. package/dist/model/tempo-client.js.map +1 -1
  15. package/dist/plugins/tempo-datasource.d.ts.map +1 -1
  16. package/dist/plugins/tempo-datasource.js +4 -4
  17. package/dist/plugins/tempo-datasource.js.map +1 -1
  18. package/dist/plugins/tempo-trace-query/TempoTraceQuery.js +1 -1
  19. package/dist/plugins/tempo-trace-query/TempoTraceQuery.js.map +1 -1
  20. package/dist/plugins/tempo-trace-query/get-trace-data.d.ts.map +1 -1
  21. package/dist/plugins/tempo-trace-query/get-trace-data.js +128 -32
  22. package/dist/plugins/tempo-trace-query/get-trace-data.js.map +1 -1
  23. package/dist/test/mock-data.d.ts +7 -3
  24. package/dist/test/mock-data.d.ts.map +1 -1
  25. package/dist/test/mock-data.js +2444 -791
  26. package/dist/test/mock-data.js.map +1 -1
  27. package/package.json +4 -4
  28. package/dist/cjs/plugins/TempoTraceQuery.js +0 -30
  29. package/dist/cjs/plugins/get-trace-data.js +0 -69
  30. package/dist/plugins/TempoTraceQuery.d.ts +0 -11
  31. package/dist/plugins/TempoTraceQuery.d.ts.map +0 -1
  32. package/dist/plugins/TempoTraceQuery.js +0 -24
  33. package/dist/plugins/TempoTraceQuery.js.map +0 -1
  34. package/dist/plugins/get-trace-data.d.ts +0 -4
  35. package/dist/plugins/get-trace-data.d.ts.map +0 -1
  36. package/dist/plugins/get-trace-data.js +0 -61
  37. 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
- "use strict";
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 executeRequest = async (url)=>{
39
- const response = await (0, _core.fetch)(url);
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, datasourceUrl) {
44
- const url = `${datasourceUrl}${apiURI}`;
45
- return executeRequest(url);
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(query, datasourceUrl) {
48
- return fetchWithGet(`/api/search?q=${query}`, datasourceUrl);
56
+ function searchTraceQuery(params, queryOptions) {
57
+ return fetchWithGet(`/api/search`, params, queryOptions);
49
58
  }
50
- function searchTraceID(traceID, datasourceUrl) {
51
- return fetchWithGet(`/api/traces/${traceID}`, datasourceUrl);
59
+ function searchTraceID(traceID, queryOptions) {
60
+ return fetchWithGet(`/api/traces/${traceID}`, null, queryOptions);
52
61
  }
53
- async function getEnrichedTraceQuery(query, datasourceUrl) {
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(query, datasourceUrl);
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
- let spanCount = 0;
66
- let errorCount = 0;
67
- const searchTraceIDResponse = await searchTraceID(trace.traceID, datasourceUrl);
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
- errorCount++;
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
- summary: trace,
82
- traceDetails: searchTraceIDResponse,
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: (query)=>(0, _tempoclient.searchTraceQuery)(query, datasourceUrl),
39
- searchTraceID: (traceID)=>(0, _tempoclient.searchTraceID)(traceID, datasourceUrl),
40
- getEnrichedTraceQuery: (query)=>(0, _tempoclient.getEnrichedTraceQuery)(query, datasourceUrl)
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 = {
@@ -26,7 +26,7 @@ const TempoTraceQuery = {
26
26
  getTraceData: _gettracedata.getTraceData,
27
27
  OptionsEditorComponent: _TempoTraceQueryEditor.TempoTraceQueryEditor,
28
28
  createInitialOptions: ()=>({
29
- query: '{}',
29
+ query: '',
30
30
  datasource: undefined
31
31
  })
32
32
  };
@@ -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
- traces: []
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
- traces: []
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 spec.query;
65
- }
66
- // if the query already contains a time range (i.e.start and end times)
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
- const queryStartTime = '&start=' + start;
73
- const queryEndTime = '&end=' + end;
74
- const queryWithTimeRange = encodeURI(spec.query) + queryStartTime + queryEndTime;
75
- return queryWithTimeRange;
72
+ return {
73
+ q: spec.query,
74
+ start,
75
+ end
76
+ };
76
77
  };
77
- const enrichedTraceResponse = await client.getEnrichedTraceQuery(getQuery(), datasourceUrl);
78
- const traces = enrichedTraceResponse.traces.map((traceValue)=>{
79
- const startTimeUnixMs = parseInt(traceValue.summary.startTimeUnixNano) * 1e-6; // convert to millisecond for eChart time format
80
- const durationMs = traceValue.summary.durationMs;
81
- const spanCount = traceValue.spanCount;
82
- const errorCount = traceValue.errorCount;
83
- const traceId = traceValue.summary.traceID;
84
- const name = `rootServiceName="${traceValue.summary.rootServiceName.trim()}", rootTraceName="${traceValue.summary.rootServiceName.trim()}"`;
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
- startTimeUnixMs,
87
- durationMs,
88
- spanCount,
89
- errorCount,
90
- traceId,
91
- name
87
+ trace: parseTraceResponse(response),
88
+ metadata: {
89
+ executedQueryString: spec.query
90
+ }
92
91
  };
93
- });
94
- const traceData = {
95
- traces,
96
- metadata: {
97
- executedQueryString: spec.query
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
- return traceData;
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
+ }