Commit b7fee710 by Andrej Ocenas Committed by GitHub

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>
parent 25393e20
......@@ -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:
......
+++
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,
......
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}
/>
);
};
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>
}
/>
);
}
}
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',
},
],
};
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;
}
<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>
\ No newline at end of file
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);
{
"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"
}
]
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment