diff --git a/tempo/README.md b/tempo/README.md index 3649907ed..72ad39b40 100644 --- a/tempo/README.md +++ b/tempo/README.md @@ -1,5 +1,7 @@ # Tempo Datasource Plugin +This plugin supports Tempo v2.6.0 and later. + ### How to install This plugin requires react and react-dom 18 diff --git a/tempo/src/model/tempo-client.test.ts b/tempo/src/model/tempo-client.test.ts index 9598426d2..fba6b842b 100644 --- a/tempo/src/model/tempo-client.test.ts +++ b/tempo/src/model/tempo-client.test.ts @@ -12,13 +12,8 @@ // limitations under the License. import { UserFriendlyError } from '@perses-dev/client'; -import { - MOCK_SEARCH_RESPONSE_MIXED_VPARQUET3_AND_4, - MOCK_SEARCH_RESPONSE_VPARQUET3, - MOCK_SEARCH_RESPONSE_VPARQUET4, - MOCK_TRACE_RESPONSE, -} from '../test'; -import { query, search, searchTagValues, searchWithFallback } from './tempo-client'; +import { MOCK_TRACE_RESPONSE } from '../test'; +import { query, search, searchTagValues } from './tempo-client'; const fetchMock = (global.fetch = jest.fn()); @@ -40,43 +35,6 @@ describe('tempo-client', () => { fetchMock.mockReset(); }); - it('should return query results as-is when serviceStats are present', async () => { - fetchMock.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(MOCK_SEARCH_RESPONSE_VPARQUET4) }); - - const results = await searchWithFallback({ q: '{}' }, { datasourceUrl: '' }); - expect(results).toEqual(MOCK_SEARCH_RESPONSE_VPARQUET4); - }); - - it('should augment query results with serviceStats if they are not present', async () => { - fetchMock.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(MOCK_SEARCH_RESPONSE_VPARQUET3) }); - fetchMock.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(MOCK_TRACE_RESPONSE) }); - - const results = await searchWithFallback({ q: '{}' }, { datasourceUrl: '' }); - expect(results).toEqual(MOCK_SEARCH_RESPONSE_VPARQUET4); - }); - - it('should augment query results with serviceStats if they are partially present', async () => { - fetchMock.mockResolvedValueOnce({ - ok: true, - json: () => Promise.resolve(MOCK_SEARCH_RESPONSE_MIXED_VPARQUET3_AND_4), - }); - fetchMock.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(MOCK_TRACE_RESPONSE) }); - - const results = await searchWithFallback({ q: '{}' }, { datasourceUrl: '' }); - - // in the mock response, the first trace contains serviceStats but the second trace does not contain serviceStats - expect(results.traces[0]?.serviceStats).toEqual({ - telemetrygen: { spanCount: 2 }, - }); - expect(results.traces[1]?.serviceStats).toEqual({ - 'article-service': { spanCount: 2 }, - 'auth-service': { spanCount: 1 }, - 'cart-service': { spanCount: 2 }, - postgres: { spanCount: 1 }, - 'shop-backend': { spanCount: 4 }, - }); - }); - it('should return v2 query response', async () => { fetchMock.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(MOCK_TRACE_RESPONSE) }); diff --git a/tempo/src/model/tempo-client.ts b/tempo/src/model/tempo-client.ts index d8ddfc07a..e587a9f55 100644 --- a/tempo/src/model/tempo-client.ts +++ b/tempo/src/model/tempo-client.ts @@ -13,14 +13,12 @@ import { DatasourceClient } from '@perses-dev/plugin-system'; import { fetchJson, RequestHeaders, UserFriendlyError } from '@perses-dev/client'; -import * as otlptracev1 from '@perses-dev/spec/dist/dashboard/query-type/otlp/trace/v1/trace'; import { QueryRequestParameters, SearchRequestParameters, SearchTagsRequestParameters, SearchTagsResponse, QueryResponse, - ServiceStats, SearchResponse, SearchTagValuesRequestParameters, SearchTagValuesResponse, @@ -36,7 +34,6 @@ export interface TempoClient extends DatasourceClient { // https://grafana.com/docs/tempo/latest/api_docs/ query(params: QueryRequestParameters, headers?: RequestHeaders): Promise; search(params: SearchRequestParameters, headers?: RequestHeaders): Promise; - searchWithFallback(params: SearchRequestParameters, headers?: RequestHeaders): Promise; searchTags(params: SearchTagsRequestParameters, headers?: RequestHeaders): Promise; searchTagValues(params: SearchTagValuesRequestParameters, headers?: RequestHeaders): Promise; } @@ -123,73 +120,6 @@ export async function query(params: QueryRequestParameters, queryOptions: QueryO return response; } -/** - * Returns a summary report of traces that satisfy the query. - * - * If the serviceStats field is missing in the response, fetches all traces - * and calculates the serviceStats. - * - * Tempo computes the serviceStats field during ingestion since vParquet4, - * this fallback is required for older block formats. - */ -export async function searchWithFallback( - params: SearchRequestParameters, - queryOptions: QueryOptions -): Promise { - // Get a list of traces that satisfy the query. - const searchResponse = await search(params, queryOptions); - if (!searchResponse.traces || searchResponse.traces.length === 0) { - return { traces: [] }; - } - - // exit early if fallback is not required (serviceStats are contained in the response) - if (searchResponse.traces.every((t) => t.serviceStats)) { - return searchResponse; - } - - // calculate serviceStats (number of spans and errors) per service - return { - traces: await Promise.all( - searchResponse.traces.map(async (trace) => { - if (trace.serviceStats) { - // fallback not required, serviceStats are contained in the response - return trace; - } - - const serviceStats: Record = {}; - const searchTraceIDResponse = await query({ traceId: trace.traceID }, queryOptions); - - // For every trace, get the full trace, and find the number of spans and errors. - for (const batch of searchTraceIDResponse.trace.resourceSpans) { - let serviceName = 'unknown'; - for (const attr of batch.resource?.attributes ?? []) { - if (attr.key === 'service.name' && 'stringValue' in attr.value) { - serviceName = attr.value.stringValue; - break; - } - } - - const stats = serviceStats[serviceName] ?? { spanCount: 0 }; - for (const scopeSpan of batch.scopeSpans) { - stats.spanCount += scopeSpan.spans.length; - for (const span of scopeSpan.spans) { - if (span.status?.code === otlptracev1.StatusCodeError) { - stats.errorCount = (stats.errorCount ?? 0) + 1; - } - } - } - serviceStats[serviceName] = stats; - } - - return { - ...trace, - serviceStats, - }; - }) - ), - }; -} - /** * Returns a list of all tag names for a given scope. */ diff --git a/tempo/src/plugins/plugin.test.ts b/tempo/src/plugins/plugin.test.ts index 6d4ad9fd6..0a80ee18c 100644 --- a/tempo/src/plugins/plugin.test.ts +++ b/tempo/src/plugins/plugin.test.ts @@ -26,7 +26,7 @@ const datasource: TempoDatasourceSpec = { const tempoStubClient = TempoDatasource.createClient(datasource, {}); tempoStubClient.query = jest.fn(async () => MOCK_TRACE_RESPONSE_SMALL); -tempoStubClient.searchWithFallback = jest.fn(async () => MOCK_SEARCH_RESPONSE_VPARQUET4); +tempoStubClient.search = jest.fn(async () => MOCK_SEARCH_RESPONSE_VPARQUET4); const getDatasourceClient: jest.Mock = jest.fn(() => { return tempoStubClient; diff --git a/tempo/src/plugins/tempo-datasource.tsx b/tempo/src/plugins/tempo-datasource.tsx index e9df0eca0..15a2252dc 100644 --- a/tempo/src/plugins/tempo-datasource.tsx +++ b/tempo/src/plugins/tempo-datasource.tsx @@ -12,7 +12,7 @@ // limitations under the License. import { DatasourcePlugin } from '@perses-dev/plugin-system'; -import { TempoClient, query, search, searchTagValues, searchTags, searchWithFallback } from '../model/tempo-client'; +import { TempoClient, query, search, searchTagValues, searchTags } from '../model/tempo-client'; import { TempoDatasourceSpec } from './tempo-datasource-types'; import { TempoDatasourceEditor } from './TempoDatasourceEditor'; @@ -37,8 +37,6 @@ const createClient: DatasourcePlugin['createCl }, query: (params, headers) => query(params, { datasourceUrl, headers: headers ?? specHeaders }), search: (params, headers) => search(params, { datasourceUrl, headers: headers ?? specHeaders }), - searchWithFallback: (params, headers) => - searchWithFallback(params, { datasourceUrl, headers: headers ?? specHeaders }), searchTags: (params, headers) => searchTags(params, { datasourceUrl, headers: headers ?? specHeaders }), searchTagValues: (params, headers) => searchTagValues(params, { datasourceUrl, headers: headers ?? specHeaders }), }; diff --git a/tempo/src/plugins/tempo-trace-query/get-trace-data.test.ts b/tempo/src/plugins/tempo-trace-query/get-trace-data.test.ts index ea8f17105..14073a8c0 100644 --- a/tempo/src/plugins/tempo-trace-query/get-trace-data.test.ts +++ b/tempo/src/plugins/tempo-trace-query/get-trace-data.test.ts @@ -35,7 +35,7 @@ const datasource: TempoDatasourceSpec = { const createMockClient = (searchResponse: SearchResponse): TempoClient => { const client = TempoDatasource.createClient(datasource, {}); - client.searchWithFallback = jest.fn(async () => searchResponse); + client.search = jest.fn(async () => searchResponse); return client; }; @@ -99,7 +99,7 @@ describe('getTraceData', () => { const result = await getTraceData({ query: '{}' }, stubContext); // Verify client was called with limit+1 - expect(mockClient.searchWithFallback).toHaveBeenCalledWith( + expect(mockClient.search).toHaveBeenCalledWith( expect.objectContaining({ limit: DEFAULT_SEARCH_LIMIT + 1, }) @@ -130,7 +130,7 @@ describe('getTraceData', () => { const result = await getTraceData({ query: '{}', limit: customLimit }, stubContext); // Verify client was called with customLimit+1 - expect(mockClient.searchWithFallback).toHaveBeenCalledWith( + expect(mockClient.search).toHaveBeenCalledWith( expect.objectContaining({ limit: customLimit + 1, }) @@ -178,7 +178,7 @@ describe('getTraceData', () => { const result = await getTraceData({ query: '{resource.service.name="$serviceName"}' }, stubContext); - expect(mockClient.searchWithFallback).toHaveBeenCalledWith( + expect(mockClient.search).toHaveBeenCalledWith( expect.objectContaining({ q: '{resource.service.name="frontend"}', }) @@ -265,7 +265,7 @@ describe('getTraceData', () => { const result = await getTraceData({ query: rawQuery }, stubContext); expect(mockedReplaceVariables).toHaveBeenCalledWith(rawQuery, stubContext.variableState); - expect(mockClient.searchWithFallback).toHaveBeenCalledWith( + expect(mockClient.search).toHaveBeenCalledWith( expect.objectContaining({ q: replacedQuery, }) diff --git a/tempo/src/plugins/tempo-trace-query/get-trace-data.ts b/tempo/src/plugins/tempo-trace-query/get-trace-data.ts index 0c6520cb6..c09516596 100644 --- a/tempo/src/plugins/tempo-trace-query/get-trace-data.ts +++ b/tempo/src/plugins/tempo-trace-query/get-trace-data.ts @@ -91,7 +91,7 @@ export const getTraceData: TraceQueryPlugin['getTraceData'] const limit = spec.limit ?? DEFAULT_SEARCH_LIMIT; params.limit = limit + 1; - const response = await client.searchWithFallback(params); + const response = await client.search(params); const searchResult = parseSearchResponse(response); const hasMoreResults = searchResult.length > limit;