mirror of
https://github.com/grafana/grafana.git
synced 2025-08-02 23:12:54 +08:00
Tracing: Add Tempo data source (#28204)
* Add tempo datasource, mostly copy of jaeger datasource code * Add label to input field * Add logo * Remove access option from configuration * Add white space to field label * Add documentation * Fix link in docs * Update public/app/plugins/datasource/tempo/ConfigEditor.tsx Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com> * Update public/app/plugins/datasource/tempo/QueryField.tsx Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com> * Add data source to the docs menu * Add simple implementation for testDatasource * Wording updates to the docs. Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com>
This commit is contained in:
@ -33,6 +33,9 @@ The following data sources are officially supported:
|
||||
- [OpenTSDB]({{< relref "opentsdb.md" >}})
|
||||
- [PostgreSQL]({{< relref "postgres.md" >}})
|
||||
- [Prometheus]({{< relref "prometheus.md" >}})
|
||||
- [Jaeger]({{< relref "jaeger.md" >}})
|
||||
- [Zipkin]({{< relref "zipkin.md" >}})
|
||||
- [Tempo]({{< relref "tempo.md" >}})
|
||||
- [Testdata]({{< relref "testdata.md" >}})
|
||||
|
||||
In addition to the data sources that you have configured in your Grafana, there are three special data sources available:
|
||||
|
38
docs/sources/datasources/tempo.md
Normal file
38
docs/sources/datasources/tempo.md
Normal file
@ -0,0 +1,38 @@
|
||||
+++
|
||||
title = "Tempo"
|
||||
description = "High volume, minimal dependency trace storage. OSS tracing solution from Grafana Labs."
|
||||
keywords = ["grafana", "tempo", "guide", "tracing"]
|
||||
type = "docs"
|
||||
aliases = ["/docs/grafana/latest/features/datasources/tempo"]
|
||||
[menu.docs]
|
||||
name = "Tempo"
|
||||
parent = "datasources"
|
||||
weight = 800
|
||||
+++
|
||||
|
||||
# Tempo data source
|
||||
|
||||
Grafana ships with built-in support for Tempo a high volume, minimal dependency trace storage, OSS tracing solution from Grafana Labs. Add it as a data source, and you are ready to query your traces in [Explore]({{< relref "../explore/index.md" >}}).
|
||||
|
||||
## Adding the data source
|
||||
To access Tempo settings, click the **Configuration** (gear) icon, then click **Data Sources** > **Tempo**.
|
||||
|
||||
| Name | Description |
|
||||
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| _Name_ | The data source name using which you will refer to the data source in panels, queries, and Explore. |
|
||||
| _Default_ | The default data source will be pre-selected for new panels. |
|
||||
| _URL_ | The URL of the Tempo instance, e.g., `http://localhost:16686` |
|
||||
| _Basic Auth_ | Enable basic authentication to the Tempo data source. |
|
||||
| _User_ | User name for basic authentication. |
|
||||
| _Password_ | Password for basic authentication. |
|
||||
|
||||
## Query traces
|
||||
|
||||
You can query and display traces from Tempo via [Explore]({{< relref "../explore/index.md" >}}).
|
||||
To query a particular trace, insert its trace ID into the query text input.
|
||||
|
||||
{{< docs-imagebox img="/img/docs/v73/tempo-query-editor.png" class="docs-image--no-shadow" caption="Screenshot of the Tempo query editor" >}}
|
||||
|
||||
## Linking Trace ID from logs
|
||||
|
||||
You can link to Tempo trace from logs in Loki or Elastic by configuring an internal link. See the [Derived fields]({{< relref "loki.md#derived-fields" >}}) section in the [Loki data source]({{< relref "loki.md" >}}) or [Data links]({{< relref "elasticsearch.md#data-links" >}}) section in the [Elastic data source]({{< relref "elasticsearch.md" >}}) for configuration instructions.
|
@ -156,6 +156,8 @@
|
||||
name: PostgreSQL
|
||||
- link: /datasources/prometheus/
|
||||
name: Prometheus
|
||||
- link: /datasources/tempo/
|
||||
name: Tempo
|
||||
- link: /datasources/testdata/
|
||||
name: TestData DB
|
||||
- link: /datasources/zipkin/
|
||||
|
@ -35,6 +35,8 @@ const azureMonitorPlugin = async () =>
|
||||
await import(
|
||||
/* webpackChunkName: "azureMonitorPlugin" */ 'app/plugins/datasource/grafana-azure-monitor-datasource/module'
|
||||
);
|
||||
const tempoPlugin = async () =>
|
||||
await import(/* webpackChunkName: "tempoPlugin" */ 'app/plugins/datasource/tempo/module');
|
||||
|
||||
import * as textPanel from 'app/plugins/panel/text/module';
|
||||
import * as graph2Panel from 'app/plugins/panel/graph2/module';
|
||||
@ -78,6 +80,7 @@ const builtInPlugins: any = {
|
||||
'app/plugins/datasource/testdata/module': testDataDSPlugin,
|
||||
'app/plugins/datasource/cloud-monitoring/module': cloudMonitoringPlugin,
|
||||
'app/plugins/datasource/grafana-azure-monitor-datasource/module': azureMonitorPlugin,
|
||||
'app/plugins/datasource/tempo/module': tempoPlugin,
|
||||
|
||||
'app/plugins/panel/text/module': textPanel,
|
||||
'app/plugins/panel/graph2/module': graph2Panel,
|
||||
|
16
public/app/plugins/datasource/tempo/ConfigEditor.tsx
Normal file
16
public/app/plugins/datasource/tempo/ConfigEditor.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import { DataSourcePluginOptionsEditorProps } from '@grafana/data';
|
||||
import { DataSourceHttpSettings } from '@grafana/ui';
|
||||
|
||||
export type Props = DataSourcePluginOptionsEditorProps;
|
||||
|
||||
export const ConfigEditor: React.FC<Props> = ({ options, onOptionsChange }) => {
|
||||
return (
|
||||
<DataSourceHttpSettings
|
||||
defaultUrl="http://localhost:16686"
|
||||
dataSourceConfig={options}
|
||||
showAccessOptions={false}
|
||||
onChange={onOptionsChange}
|
||||
/>
|
||||
);
|
||||
};
|
35
public/app/plugins/datasource/tempo/QueryField.tsx
Normal file
35
public/app/plugins/datasource/tempo/QueryField.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import { TempoDatasource, TempoQuery } from './datasource';
|
||||
|
||||
import { ExploreQueryFieldProps } from '@grafana/data';
|
||||
import { LegacyForms } from '@grafana/ui';
|
||||
|
||||
type Props = ExploreQueryFieldProps<TempoDatasource, TempoQuery>;
|
||||
export class TempoQueryField extends React.PureComponent<Props> {
|
||||
render() {
|
||||
const { query, onChange } = this.props;
|
||||
|
||||
return (
|
||||
<LegacyForms.FormField
|
||||
label="Trace ID"
|
||||
labelWidth={4}
|
||||
inputEl={
|
||||
<div className="slate-query-field__wrapper">
|
||||
<div className="slate-query-field">
|
||||
<input
|
||||
style={{ width: '100%' }}
|
||||
value={query.query || ''}
|
||||
onChange={e =>
|
||||
onChange({
|
||||
...query,
|
||||
query: e.currentTarget.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
119
public/app/plugins/datasource/tempo/datasource.test.ts
Normal file
119
public/app/plugins/datasource/tempo/datasource.test.ts
Normal file
@ -0,0 +1,119 @@
|
||||
import { TempoDatasource, TempoQuery } from './datasource';
|
||||
import { DataQueryRequest, DataSourceInstanceSettings, FieldType, PluginType, dateTime } from '@grafana/data';
|
||||
import { BackendSrv, BackendSrvRequest, getBackendSrv, setBackendSrv } from '@grafana/runtime';
|
||||
|
||||
describe('JaegerDatasource', () => {
|
||||
it('returns trace when queried', async () => {
|
||||
await withMockedBackendSrv(makeBackendSrvMock('12345'), async () => {
|
||||
const ds = new TempoDatasource(defaultSettings);
|
||||
const response = await ds.query(defaultQuery).toPromise();
|
||||
const field = response.data[0].fields[0];
|
||||
expect(field.name).toBe('trace');
|
||||
expect(field.type).toBe(FieldType.trace);
|
||||
expect(field.values.get(0)).toEqual({
|
||||
traceId: '12345',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('returns trace when traceId with special characters is queried', async () => {
|
||||
await withMockedBackendSrv(makeBackendSrvMock('a/b'), async () => {
|
||||
const ds = new TempoDatasource(defaultSettings);
|
||||
const query = {
|
||||
...defaultQuery,
|
||||
targets: [
|
||||
{
|
||||
query: 'a/b',
|
||||
refId: '1',
|
||||
},
|
||||
],
|
||||
};
|
||||
const response = await ds.query(query).toPromise();
|
||||
const field = response.data[0].fields[0];
|
||||
expect(field.name).toBe('trace');
|
||||
expect(field.type).toBe(FieldType.trace);
|
||||
expect(field.values.get(0)).toEqual({
|
||||
traceId: 'a/b',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('returns empty response if trace id is not specified', async () => {
|
||||
const ds = new TempoDatasource(defaultSettings);
|
||||
const response = await ds
|
||||
.query({
|
||||
...defaultQuery,
|
||||
targets: [],
|
||||
})
|
||||
.toPromise();
|
||||
const field = response.data[0].fields[0];
|
||||
expect(field.name).toBe('trace');
|
||||
expect(field.type).toBe(FieldType.trace);
|
||||
expect(field.values.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
function makeBackendSrvMock(traceId: string) {
|
||||
return {
|
||||
datasourceRequest(options: BackendSrvRequest): Promise<any> {
|
||||
expect(options.url.substr(options.url.length - 17, options.url.length)).toBe(
|
||||
`/api/traces/${encodeURIComponent(traceId)}`
|
||||
);
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
data: [
|
||||
{
|
||||
traceId,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
},
|
||||
} as any;
|
||||
}
|
||||
|
||||
async function withMockedBackendSrv(srv: BackendSrv, fn: () => Promise<void>) {
|
||||
const oldSrv = getBackendSrv();
|
||||
setBackendSrv(srv);
|
||||
await fn();
|
||||
setBackendSrv(oldSrv);
|
||||
}
|
||||
|
||||
const defaultSettings: DataSourceInstanceSettings = {
|
||||
id: 0,
|
||||
uid: '0',
|
||||
type: 'tracing',
|
||||
name: 'jaeger',
|
||||
meta: {
|
||||
id: 'jaeger',
|
||||
name: 'jaeger',
|
||||
type: PluginType.datasource,
|
||||
info: {} as any,
|
||||
module: '',
|
||||
baseUrl: '',
|
||||
},
|
||||
jsonData: {},
|
||||
};
|
||||
|
||||
const defaultQuery: DataQueryRequest<TempoQuery> = {
|
||||
requestId: '1',
|
||||
dashboardId: 0,
|
||||
interval: '0',
|
||||
intervalMs: 10,
|
||||
panelId: 0,
|
||||
scopedVars: {},
|
||||
range: {
|
||||
from: dateTime().subtract(1, 'h'),
|
||||
to: dateTime(),
|
||||
raw: { from: '1h', to: 'now' },
|
||||
},
|
||||
timezone: 'browser',
|
||||
app: 'explore',
|
||||
startTime: 0,
|
||||
targets: [
|
||||
{
|
||||
query: '12345',
|
||||
refId: '1',
|
||||
},
|
||||
],
|
||||
};
|
122
public/app/plugins/datasource/tempo/datasource.ts
Normal file
122
public/app/plugins/datasource/tempo/datasource.ts
Normal file
@ -0,0 +1,122 @@
|
||||
import {
|
||||
dateMath,
|
||||
DateTime,
|
||||
MutableDataFrame,
|
||||
DataSourceApi,
|
||||
DataSourceInstanceSettings,
|
||||
DataQueryRequest,
|
||||
DataQueryResponse,
|
||||
DataQuery,
|
||||
FieldType,
|
||||
} from '@grafana/data';
|
||||
import { getBackendSrv, BackendSrvRequest } from '@grafana/runtime';
|
||||
import { Observable, from, of } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
import { serializeParams } from 'app/core/utils/fetch';
|
||||
|
||||
export type TempoQuery = {
|
||||
query: string;
|
||||
} & DataQuery;
|
||||
|
||||
export class TempoDatasource extends DataSourceApi<TempoQuery> {
|
||||
constructor(private instanceSettings: DataSourceInstanceSettings, private readonly timeSrv: TimeSrv = getTimeSrv()) {
|
||||
super(instanceSettings);
|
||||
}
|
||||
|
||||
async metadataRequest(url: string, params?: Record<string, any>): Promise<any> {
|
||||
const res = await this._request(url, params, { hideFromInspector: true }).toPromise();
|
||||
return res.data.data;
|
||||
}
|
||||
|
||||
query(options: DataQueryRequest<TempoQuery>): Observable<DataQueryResponse> {
|
||||
// At this moment we expect only one target. In case we somehow change the UI to be able to show multiple
|
||||
// traces at one we need to change this.
|
||||
const id = options.targets[0]?.query;
|
||||
if (id) {
|
||||
return this._request(`/api/traces/${encodeURIComponent(id)}`).pipe(
|
||||
map(response => {
|
||||
return {
|
||||
data: [
|
||||
new MutableDataFrame({
|
||||
fields: [
|
||||
{
|
||||
name: 'trace',
|
||||
type: FieldType.trace,
|
||||
values: response?.data?.data || [],
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
preferredVisualisationType: 'trace',
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
})
|
||||
);
|
||||
} else {
|
||||
return of({
|
||||
data: [
|
||||
new MutableDataFrame({
|
||||
fields: [
|
||||
{
|
||||
name: 'trace',
|
||||
type: FieldType.trace,
|
||||
values: [],
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
preferredVisualisationType: 'trace',
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async testDatasource(): Promise<any> {
|
||||
try {
|
||||
await this._request(`/api/traces/random`).toPromise();
|
||||
} catch (e) {
|
||||
// As we are not searching for a valid trace here this will definitely fail but we should return 502 if it's
|
||||
// unreachable. 500 should otherwise be from tempo it self but probably makes sense to report them here.
|
||||
if (e?.status >= 500 && e?.status < 600) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
getTimeRange(): { start: number; end: number } {
|
||||
const range = this.timeSrv.timeRange();
|
||||
return {
|
||||
start: getTime(range.from, false),
|
||||
end: getTime(range.to, true),
|
||||
};
|
||||
}
|
||||
|
||||
getQueryDisplayText(query: TempoQuery) {
|
||||
return query.query;
|
||||
}
|
||||
|
||||
private _request(apiUrl: string, data?: any, options?: Partial<BackendSrvRequest>): Observable<Record<string, any>> {
|
||||
// Hack for proxying metadata requests
|
||||
const baseUrl = `/api/datasources/proxy/${this.instanceSettings.id}`;
|
||||
const params = data ? serializeParams(data) : '';
|
||||
const url = `${baseUrl}${apiUrl}${params.length ? `?${params}` : ''}`;
|
||||
const req = {
|
||||
...options,
|
||||
url,
|
||||
};
|
||||
|
||||
return from(getBackendSrv().datasourceRequest(req));
|
||||
}
|
||||
}
|
||||
|
||||
function getTime(date: string | DateTime, roundUp: boolean) {
|
||||
if (typeof date === 'string') {
|
||||
date = dateMath.parse(date, roundUp)!;
|
||||
}
|
||||
return date.valueOf() * 1000;
|
||||
}
|
1
public/app/plugins/datasource/tempo/img/tempo_logo.svg
Normal file
1
public/app/plugins/datasource/tempo/img/tempo_logo.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 121.85 99.17"><defs><style>.cls-1{fill:url(#linear-gradient);}</style><linearGradient id="linear-gradient" x1="168.55" y1="13.4" x2="27.2" y2="57" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fff100"/><stop offset="1" stop-color="#f05a28"/></linearGradient></defs><g id="Layer_6" data-name="Layer 6"><path class="cls-1" d="M4.66,25H2.37a2.37,2.37,0,0,0,0,4.74H4.66a2.37,2.37,0,1,0,0-4.74ZM48.48,59.57H46.19a2.37,2.37,0,1,0,0,4.74h2.29a2.37,2.37,0,1,0,0-4.74ZM46.56,37a2.37,2.37,0,0,0-2.37-2.37H36.27a2.37,2.37,0,0,0,0,4.74h7.92A2.37,2.37,0,0,0,46.56,37ZM121.73,22.1,119.32,8.56A9.88,9.88,0,0,0,109.07,0H16.24A6.28,6.28,0,0,0,9.9,7.7l2.54,14.4a3.38,3.38,0,0,0,.08.34v0c.3,1.76-.59,2.47-1.39,2.73h0a2.37,2.37,0,0,0,.79,4.6H115.39A6.28,6.28,0,0,0,121.73,22.1ZM90.15,76.42c-1-5.25-4-7.2-7.39-7.2H58.24a2.39,2.39,0,0,0-2.37,2.4A2.37,2.37,0,0,0,58,74h0c.78.14,1.62,1.16,2.15,3.68l2.52,14a9.59,9.59,0,0,0,9.19,7.54l14.63-.07a6.28,6.28,0,0,0,6.44-7.61ZM57.73,64.48H84.34a2.27,2.27,0,0,0,.59-.09c2.46-.52,2.58-2.52,2.26-4.51L83.8,41.27c-.93-4.84-3.74-6.75-7.43-6.75H52a2.37,2.37,0,0,0-.28,4.72h0c.81.15,1.7,1.24,2.22,4l2.57,14.24v0A1.92,1.92,0,0,1,55,59.87h0a2.36,2.36,0,0,0,.79,4.59h1.9Z"/></g></svg>
|
After Width: | Height: | Size: 1.3 KiB |
8
public/app/plugins/datasource/tempo/module.ts
Normal file
8
public/app/plugins/datasource/tempo/module.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { DataSourcePlugin } from '@grafana/data';
|
||||
import { TempoDatasource } from './datasource';
|
||||
import { TempoQueryField } from './QueryField';
|
||||
import { ConfigEditor } from './ConfigEditor';
|
||||
|
||||
export const plugin = new DataSourcePlugin(TempoDatasource)
|
||||
.setConfigEditor(ConfigEditor)
|
||||
.setExploreQueryField(TempoQueryField);
|
31
public/app/plugins/datasource/tempo/plugin.json
Normal file
31
public/app/plugins/datasource/tempo/plugin.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"type": "datasource",
|
||||
"name": "Tempo",
|
||||
"id": "tempo",
|
||||
"category": "tracing",
|
||||
|
||||
"metrics": false,
|
||||
"alerting": false,
|
||||
"annotations": false,
|
||||
"logs": false,
|
||||
"streaming": false,
|
||||
"tracing": true,
|
||||
|
||||
"info": {
|
||||
"description": "High volume, minimal dependency trace storage. OSS tracing solution from Grafana Labs.",
|
||||
"author": {
|
||||
"name": "Grafana Labs",
|
||||
"url": "https://grafana.com"
|
||||
},
|
||||
"logos": {
|
||||
"small": "img/tempo_logo.svg",
|
||||
"large": "img/tempo_logo.svg"
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"name": "GitHub Project",
|
||||
"url": "https://github.com/grafana/tempo"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user