mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Explore: Download and upload service graphs for Tempo (#50260)
* Download service graph from inspect data tab * Upload service graph * Fix tests
This commit is contained in:
parent
cbfac157fb
commit
f9ddb8bf86
@ -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:
|
||||
|
||||
|
@ -147,5 +147,31 @@ describe('InspectDataTab', () => {
|
||||
render(<InspectDataTab {...createProps()} />);
|
||||
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(
|
||||
<InspectDataTab
|
||||
{...createProps({
|
||||
data: sgFrames,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByText(/Download service graph/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -172,6 +172,20 @@ export class InspectDataTab extends PureComponent<Props, State> {
|
||||
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<DataTransformerID | number>) => {
|
||||
this.setState({
|
||||
transformId:
|
||||
@ -232,6 +246,7 @@ export class InspectDataTab extends PureComponent<Props, State> {
|
||||
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 (
|
||||
<div className={styles.wrap} aria-label={selectors.components.PanelInspector.Data.content}>
|
||||
@ -282,6 +297,18 @@ export class InspectDataTab extends PureComponent<Props, State> {
|
||||
Download traces
|
||||
</Button>
|
||||
)}
|
||||
{hasServiceGraph && (
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={this.exportServiceGraph}
|
||||
className={css`
|
||||
margin-bottom: 10px;
|
||||
margin-left: 10px;
|
||||
`}
|
||||
>
|
||||
Download service graph
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<AutoSizer>
|
||||
|
@ -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);
|
||||
|
@ -190,11 +190,17 @@ export class TempoDatasource extends DataSourceWithBackend<TempoQuery, TempoJson
|
||||
|
||||
if (targets.upload?.length) {
|
||||
if (this.uploadedJson) {
|
||||
const otelTraceData = JSON.parse(this.uploadedJson as string);
|
||||
if (!otelTraceData.batches) {
|
||||
subQueries.push(of({ error: { message: 'JSON is not valid OpenTelemetry format' }, data: [] }));
|
||||
const jsonData = JSON.parse(this.uploadedJson as string);
|
||||
const isTraceData = jsonData.batches;
|
||||
const isServiceGraphData =
|
||||
Array.isArray(jsonData) && jsonData.some((df) => 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 }));
|
||||
|
315
public/app/plugins/datasource/tempo/mockServiceGraph.json
Normal file
315
public/app/plugins/datasource/tempo/mockServiceGraph.json
Normal file
@ -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
|
||||
}
|
||||
]
|
Loading…
Reference in New Issue
Block a user