mirror of https://github.com/grafana/grafana
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>pull/28231/head
parent
25393e20aa
commit
b7fee71049
@ -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. |
@ -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} |
||||||
|
/> |
||||||
|
); |
||||||
|
}; |
@ -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> |
||||||
|
} |
||||||
|
/> |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -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', |
||||||
|
}, |
||||||
|
], |
||||||
|
}; |
@ -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; |
||||||
|
} |
After Width: | Height: | Size: 1.3 KiB |
@ -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); |
@ -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" |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue