From f9ddb8bf860da5b363f391e65621454122f9f81a Mon Sep 17 00:00:00 2001 From: Connor Lindsey Date: Tue, 7 Jun 2022 06:37:19 -0600 Subject: [PATCH] Explore: Download and upload service graphs for Tempo (#50260) * Download service graph from inspect data tab * Upload service graph * Fix tests --- docs/sources/datasources/tempo.md | 4 +- .../inspector/InspectDataTab.test.tsx | 26 ++ .../app/features/inspector/InspectDataTab.tsx | 27 ++ .../datasource/tempo/datasource.test.ts | 19 ++ .../plugins/datasource/tempo/datasource.ts | 14 +- .../datasource/tempo/mockServiceGraph.json | 315 ++++++++++++++++++ 6 files changed, 400 insertions(+), 5 deletions(-) create mode 100644 public/app/plugins/datasource/tempo/mockServiceGraph.json diff --git a/docs/sources/datasources/tempo.md b/docs/sources/datasources/tempo.md index 9b8ba97158d..63c03286255 100644 --- a/docs/sources/datasources/tempo.md +++ b/docs/sources/datasources/tempo.md @@ -116,7 +116,9 @@ To query a particular trace, select the **TraceID** query type, and then put the ## Upload JSON trace file -You can upload a JSON file that contains a single trace to visualize it. If the file has multiple traces then the first trace is used for visualization. +You can upload a JSON file that contains a single trace or service graph to visualize it. If the file has multiple traces, the first trace is used for visualization. + +You can download a trace or service graph through the inspector. Open the inspector, navigate to the 'Data' tab, and click 'Download traces' or 'Download service graph'. Here is an example JSON: diff --git a/public/app/features/inspector/InspectDataTab.test.tsx b/public/app/features/inspector/InspectDataTab.test.tsx index 273fd164696..3355a8b8cef 100644 --- a/public/app/features/inspector/InspectDataTab.test.tsx +++ b/public/app/features/inspector/InspectDataTab.test.tsx @@ -147,5 +147,31 @@ describe('InspectDataTab', () => { render(); expect(screen.queryByText(/Download traces/i)).not.toBeInTheDocument(); }); + it('should show download service graph button', () => { + const sgFrames = [ + { + name: 'Nodes', + fields: [], + meta: { + preferredVisualisationType: 'nodeGraph', + }, + }, + { + name: 'Edges', + fields: [], + meta: { + preferredVisualisationType: 'nodeGraph', + }, + }, + ] as unknown as DataFrame[]; + render( + + ); + expect(screen.getByText(/Download service graph/i)).toBeInTheDocument(); + }); }); }); diff --git a/public/app/features/inspector/InspectDataTab.tsx b/public/app/features/inspector/InspectDataTab.tsx index 6fed6dbfdce..8e30ce87e50 100644 --- a/public/app/features/inspector/InspectDataTab.tsx +++ b/public/app/features/inspector/InspectDataTab.tsx @@ -172,6 +172,20 @@ export class InspectDataTab extends PureComponent { saveAs(blob, fileName); }; + exportServiceGraph = () => { + const { data, panel } = this.props; + if (!data) { + return; + } + + const blob = new Blob([JSON.stringify(data)], { + type: 'application/json', + }); + const displayTitle = panel ? panel.getDisplayTitle() : 'Explore'; + const fileName = `${displayTitle}-service-graph-${dateTimeFormat(new Date())}.json`; + saveAs(blob, fileName); + }; + onDataFrameChange = (item: SelectableValue) => { this.setState({ transformId: @@ -232,6 +246,7 @@ export class InspectDataTab extends PureComponent { const dataFrame = dataFrames[index]; const hasLogs = dataFrames.some((df) => df?.meta?.preferredVisualisationType === 'logs'); const hasTraces = dataFrames.some((df) => df?.meta?.preferredVisualisationType === 'trace'); + const hasServiceGraph = dataFrames.some((df) => df?.meta?.preferredVisualisationType === 'nodeGraph'); return (
@@ -282,6 +297,18 @@ export class InspectDataTab extends PureComponent { Download traces )} + {hasServiceGraph && ( + + )}
diff --git a/public/app/plugins/datasource/tempo/datasource.test.ts b/public/app/plugins/datasource/tempo/datasource.test.ts index b43ae4a4822..35f39b654ba 100644 --- a/public/app/plugins/datasource/tempo/datasource.test.ts +++ b/public/app/plugins/datasource/tempo/datasource.test.ts @@ -15,6 +15,7 @@ import { BackendDataSourceResponse, FetchResponse, setBackendSrv, setDataSourceS import { DEFAULT_LIMIT, TempoJsonData, TempoDatasource, TempoQuery } from './datasource'; import mockJson from './mockJsonResponse.json'; +import mockServiceGraph from './mockServiceGraph.json'; jest.mock('@grafana/runtime', () => { return { @@ -210,6 +211,24 @@ describe('Tempo data source', () => { expect(response.data.length).toBe(0); }); + it('should handle service graph upload', async () => { + const ds = new TempoDatasource(defaultSettings); + ds.uploadedJson = JSON.stringify(mockServiceGraph); + const response = await lastValueFrom( + ds.query({ + targets: [{ queryType: 'upload', refId: 'A' }], + } as any) + ); + expect(response.data).toHaveLength(2); + const nodesFrame = response.data[0]; + expect(nodesFrame.name).toBe('Nodes'); + expect(nodesFrame.meta.preferredVisualisationType).toBe('nodeGraph'); + + const edgesFrame = response.data[1]; + expect(edgesFrame.name).toBe('Edges'); + expect(edgesFrame.meta.preferredVisualisationType).toBe('nodeGraph'); + }); + it('should build search query correctly', () => { const templateSrv: any = { replace: jest.fn() }; const ds = new TempoDatasource(defaultSettings, templateSrv); diff --git a/public/app/plugins/datasource/tempo/datasource.ts b/public/app/plugins/datasource/tempo/datasource.ts index 7287ab709a1..b85f9314f35 100644 --- a/public/app/plugins/datasource/tempo/datasource.ts +++ b/public/app/plugins/datasource/tempo/datasource.ts @@ -190,11 +190,17 @@ export class TempoDatasource extends DataSourceWithBackend df?.meta?.preferredVisualisationType === 'nodeGraph'); + + if (isTraceData) { + subQueries.push(of(transformFromOTEL(jsonData.batches, this.nodeGraph?.enabled))); + } else if (isServiceGraphData) { + subQueries.push(of({ data: jsonData, state: LoadingState.Done })); } else { - subQueries.push(of(transformFromOTEL(otelTraceData.batches, this.nodeGraph?.enabled))); + subQueries.push(of({ error: { message: 'Unable to parse uploaded data.' }, data: [] })); } } else { subQueries.push(of({ data: [], state: LoadingState.Done })); diff --git a/public/app/plugins/datasource/tempo/mockServiceGraph.json b/public/app/plugins/datasource/tempo/mockServiceGraph.json new file mode 100644 index 00000000000..abdd24a0d18 --- /dev/null +++ b/public/app/plugins/datasource/tempo/mockServiceGraph.json @@ -0,0 +1,315 @@ +[ + { + "name": "Nodes", + "meta": { + "preferredVisualisationType": "nodeGraph" + }, + "fields": [ + { + "name": "id", + "type": "string", + "config": { + "links": [ + { + "url": "", + "title": "Request rate", + "internal": { + "query": { + "expr": "rate(traces_service_graph_request_total{server=\"${__data.fields.id}\"}[$__rate_interval])" + }, + "datasourceUid": "7_o0Ho87z", + "datasourceName": "Prometheus" + } + }, + { + "url": "", + "title": "Request histogram", + "internal": { + "query": { + "expr": "histogram_quantile(0.9, sum(rate(traces_service_graph_request_server_seconds_bucket{server=\"${__data.fields.id}\"}[$__rate_interval])) by (le, client, server))" + }, + "datasourceUid": "7_o0Ho87z", + "datasourceName": "Prometheus" + } + }, + { + "url": "", + "title": "Failed request rate", + "internal": { + "query": { + "expr": "rate(traces_service_graph_request_failed_total{server=\"${__data.fields.id}\"}[$__rate_interval])" + }, + "datasourceUid": "7_o0Ho87z", + "datasourceName": "Prometheus" + } + }, + { + "url": "", + "title": "View traces", + "internal": { + "query": { + "queryType": "nativeSearch", + "serviceName": "${__data.fields[0]}" + }, + "datasourceUid": "TNS Tempo", + "datasourceName": "Tempo" + } + } + ] + }, + "values": ["db", "app", "lb"], + "state": null + }, + { + "name": "title", + "config": { + "displayName": "Service name" + }, + "type": "string", + "values": ["db", "app", "lb"], + "state": null + }, + { + "name": "mainstat", + "config": { + "unit": "ms/r", + "displayName": "Average response time" + }, + "type": "number", + "values": [8.817128259611174, 51.8661058283908, null], + "state": { + "calcs": { + "sum": null, + "max": 51.8661058283908, + "min": 8.817128259611174, + "logmin": 8.817128259611174, + "mean": null, + "last": null, + "first": 8.817128259611174, + "lastNotNull": null, + "firstNotNull": 8.817128259611174, + "count": 3, + "nonNullCount": 3, + "allIsNull": false, + "allIsZero": false, + "range": 43.04897756877963, + "diff": null, + "delta": null, + "step": 43.04897756877963, + "diffperc": null, + "previousDeltaUp": true + }, + "displayName": "Average response time", + "multipleFrames": false + } + }, + { + "name": "secondarystat", + "config": { + "unit": "r/sec", + "displayName": "Requests per second" + }, + "type": "number", + "values": [10.11, 10.16, null], + "state": { + "calcs": { + "sum": null, + "max": 10.16, + "min": 10.11, + "logmin": 10.11, + "mean": null, + "last": null, + "first": 10.11, + "lastNotNull": null, + "firstNotNull": 10.11, + "count": 3, + "nonNullCount": 3, + "allIsNull": false, + "allIsZero": false, + "range": 0.05000000000000071, + "diff": null, + "delta": null, + "step": 0.05000000000000071, + "diffperc": null, + "previousDeltaUp": true + }, + "displayName": "Requests per second", + "multipleFrames": false + } + }, + { + "name": "arc__success", + "config": { + "displayName": "Success", + "color": { + "fixedColor": "green", + "mode": "fixed" + } + }, + "type": "number", + "values": [0.9223488506857254, 0.9141946824873654, 1], + "state": { + "calcs": { + "sum": 2.836543533173091, + "max": 1, + "min": 0.9141946824873654, + "logmin": 0.9141946824873654, + "mean": 0.9455145110576969, + "last": 1, + "first": 0.9223488506857254, + "lastNotNull": 1, + "firstNotNull": 0.9223488506857254, + "count": 3, + "nonNullCount": 3, + "allIsNull": false, + "allIsZero": false, + "range": 0.08580531751263465, + "diff": 0.07765114931427464, + "delta": 1, + "step": -0.008154168198360012, + "diffperc": 0.08418848167539263, + "previousDeltaUp": true + }, + "displayName": "Success", + "multipleFrames": false + } + }, + { + "name": "arc__failed", + "config": { + "displayName": "Failed", + "color": { + "fixedColor": "red", + "mode": "fixed" + } + }, + "type": "number", + "values": [0.07765114931427468, 0.08580531751263458, 0], + "state": { + "calcs": { + "sum": 0.16345646682690926, + "max": 0.08580531751263458, + "min": 0, + "logmin": 0.07765114931427468, + "mean": 0.05448548894230309, + "last": 0, + "first": 0.07765114931427468, + "lastNotNull": 0, + "firstNotNull": 0.07765114931427468, + "count": 3, + "nonNullCount": 3, + "allIsNull": false, + "allIsZero": false, + "range": 0.08580531751263458, + "diff": -0.07765114931427468, + "delta": 0.008154168198359901, + "step": -0.08580531751263458, + "diffperc": -1, + "previousDeltaUp": false + }, + "displayName": "Failed", + "multipleFrames": false + } + } + ], + "first": ["db", "app", "lb"], + "parsers": {}, + "length": 3 + }, + { + "name": "Edges", + "meta": { + "preferredVisualisationType": "nodeGraph" + }, + "fields": [ + { + "name": "id", + "type": "string", + "config": {}, + "values": ["app_db", "lb_app"], + "state": null + }, + { + "name": "source", + "type": "string", + "config": {}, + "values": ["app", "lb"], + "state": null + }, + { + "name": "target", + "type": "string", + "config": {}, + "values": ["db", "app"], + "state": null + }, + { + "name": "mainstat", + "config": { + "unit": "r", + "displayName": "Requests" + }, + "type": "number", + "values": [36389.368959065294, 36559.070202313786], + "state": { + "calcs": { + "sum": 72948.43916137908, + "max": 36559.070202313786, + "min": 36389.368959065294, + "logmin": 36389.368959065294, + "mean": 36474.21958068954, + "last": 36559.070202313786, + "first": 36389.368959065294, + "lastNotNull": 36559.070202313786, + "firstNotNull": 36389.368959065294, + "count": 2, + "nonNullCount": 2, + "allIsNull": false, + "allIsZero": false, + "range": 169.70124324849166, + "diff": 169.70124324849166, + "delta": 169.70124324849166, + "step": 169.70124324849166, + "diffperc": 0.0046634840917244265, + "previousDeltaUp": true + } + } + }, + { + "name": "secondarystat", + "config": { + "unit": "ms/r", + "displayName": "Average response time" + }, + "type": "number", + "values": [8.817128259611174, 51.8661058283908], + "state": { + "calcs": { + "sum": 60.683234088001974, + "max": 51.8661058283908, + "min": 8.817128259611174, + "logmin": 8.817128259611174, + "mean": 30.341617044000987, + "last": 51.8661058283908, + "first": 8.817128259611174, + "lastNotNull": 51.8661058283908, + "firstNotNull": 8.817128259611174, + "count": 2, + "nonNullCount": 2, + "allIsNull": false, + "allIsZero": false, + "range": 43.04897756877963, + "diff": 43.04897756877963, + "delta": 43.04897756877963, + "step": 43.04897756877963, + "diffperc": 4.882426148429198, + "previousDeltaUp": true + } + } + } + ], + "first": ["app_db", "lb_app"], + "parsers": {}, + "length": 2 + } +]