2021-08-19 06:38:31 +02:00
import { lastValueFrom , Observable , of } from 'rxjs' ;
2022-04-22 14:33:13 +01:00
import { createFetchResponse } from 'test/helpers/createFetchResponse' ;
2021-08-05 15:13:44 +02:00
import {
2022-06-09 17:56:15 +01:00
ArrayVector ,
2021-08-05 15:13:44 +02:00
DataFrame ,
dataFrameToJSON ,
DataSourceInstanceSettings ,
FieldType ,
2021-08-17 15:48:29 +02:00
getDefaultTimeRange ,
LoadingState ,
2021-08-05 15:13:44 +02:00
MutableDataFrame ,
PluginType ,
} from '@grafana/data' ;
2021-08-19 06:38:31 +02:00
import { BackendDataSourceResponse , FetchResponse , setBackendSrv , setDataSourceSrv } from '@grafana/runtime' ;
2022-06-09 17:56:15 +01:00
import config from 'app/core/config' ;
2022-04-22 14:33:13 +01:00
2022-06-09 17:56:15 +01:00
import {
DEFAULT_LIMIT ,
TempoDatasource ,
buildExpr ,
buildLinkExpr ,
getRateAlignedValues ,
makeApmRequest ,
makeTempoLink ,
2022-07-25 10:03:57 +01:00
getFieldConfig ,
2022-06-09 17:56:15 +01:00
} from './datasource' ;
2021-08-05 15:13:44 +02:00
import mockJson from './mockJsonResponse.json' ;
2022-06-07 06:37:19 -06:00
import mockServiceGraph from './mockServiceGraph.json' ;
2022-09-05 15:46:10 +01:00
import { TempoJsonData , TempoQuery } from './types' ;
2021-03-05 14:28:17 +01:00
2022-09-02 11:17:36 +01:00
let mockObservable : ( ) = > Observable < any > ;
2022-06-01 10:28:45 +01:00
jest . mock ( '@grafana/runtime' , ( ) = > {
return {
. . . jest . requireActual ( '@grafana/runtime' ) ,
reportInteraction : jest.fn ( ) ,
2022-09-02 11:17:36 +01:00
getBackendSrv : ( ) = > ( {
fetch : mockObservable ,
_request : mockObservable ,
} ) ,
2022-06-01 10:28:45 +01:00
} ;
} ) ;
2021-03-05 14:28:17 +01:00
describe ( 'Tempo data source' , ( ) = > {
2022-02-17 08:34:16 -07:00
// Mock the console error so that running the test suite doesnt throw the error
const origError = console . error ;
const consoleErrorMock = jest . fn ( ) ;
afterEach ( ( ) = > ( console . error = origError ) ) ;
beforeEach ( ( ) = > ( console . error = consoleErrorMock ) ) ;
2022-01-05 19:34:09 +01:00
it ( 'returns empty response when traceId is empty' , async ( ) = > {
const ds = new TempoDatasource ( defaultSettings ) ;
const response = await lastValueFrom (
ds . query ( { targets : [ { refId : 'refid1' , queryType : 'traceId' , query : '' } as Partial < TempoQuery > ] } as any ) ,
{ defaultValue : 'empty' }
) ;
expect ( response ) . toBe ( 'empty' ) ;
} ) ;
2022-05-03 17:42:36 +01:00
describe ( 'Variables should be interpolated correctly' , ( ) = > {
function getQuery ( ) : TempoQuery {
return {
refId : 'x' ,
queryType : 'traceId' ,
linkedQuery : {
refId : 'linked' ,
expr : '{instance="$interpolationVar"}' ,
} ,
query : '$interpolationVar' ,
search : '$interpolationVar' ,
minDuration : '$interpolationVar' ,
maxDuration : '$interpolationVar' ,
} ;
}
it ( 'when traceId query for dashboard->explore' , async ( ) = > {
const templateSrv : any = { replace : jest.fn ( ) } ;
const ds = new TempoDatasource ( defaultSettings , templateSrv ) ;
const text = 'interpolationText' ;
templateSrv . replace . mockReturnValue ( text ) ;
const queries = ds . interpolateVariablesInQueries ( [ getQuery ( ) ] , {
interpolationVar : { text : text , value : text } ,
} ) ;
2022-07-18 08:08:35 +01:00
expect ( templateSrv . replace ) . toBeCalledTimes ( 7 ) ;
2022-05-03 17:42:36 +01:00
expect ( queries [ 0 ] . linkedQuery ? . expr ) . toBe ( text ) ;
expect ( queries [ 0 ] . query ) . toBe ( text ) ;
2022-07-18 08:08:35 +01:00
expect ( queries [ 0 ] . serviceName ) . toBe ( text ) ;
expect ( queries [ 0 ] . spanName ) . toBe ( text ) ;
2022-05-03 17:42:36 +01:00
expect ( queries [ 0 ] . search ) . toBe ( text ) ;
expect ( queries [ 0 ] . minDuration ) . toBe ( text ) ;
expect ( queries [ 0 ] . maxDuration ) . toBe ( text ) ;
} ) ;
it ( 'when traceId query for template variable' , async ( ) = > {
const templateSrv : any = { replace : jest.fn ( ) } ;
const ds = new TempoDatasource ( defaultSettings , templateSrv ) ;
const text = 'interpolationText' ;
templateSrv . replace . mockReturnValue ( text ) ;
const resp = ds . applyTemplateVariables ( getQuery ( ) , {
interpolationVar : { text : text , value : text } ,
} ) ;
2022-07-18 08:08:35 +01:00
expect ( templateSrv . replace ) . toBeCalledTimes ( 7 ) ;
2022-05-03 17:42:36 +01:00
expect ( resp . linkedQuery ? . expr ) . toBe ( text ) ;
expect ( resp . query ) . toBe ( text ) ;
2022-07-18 08:08:35 +01:00
expect ( resp . serviceName ) . toBe ( text ) ;
expect ( resp . spanName ) . toBe ( text ) ;
2022-05-03 17:42:36 +01:00
expect ( resp . search ) . toBe ( text ) ;
expect ( resp . minDuration ) . toBe ( text ) ;
expect ( resp . maxDuration ) . toBe ( text ) ;
} ) ;
} ) ;
2021-03-22 19:09:15 +01:00
it ( 'parses json fields from backend' , async ( ) = > {
setupBackendSrv (
new MutableDataFrame ( {
fields : [
2021-05-05 20:51:43 +02:00
{ name : 'traceID' , values : [ '04450900759028499335' ] } ,
{ name : 'spanID' , values : [ '4322526419282105830' ] } ,
{ name : 'parentSpanID' , values : [ '' ] } ,
{ name : 'operationName' , values : [ 'store.validateQueryTimeRange' ] } ,
{ name : 'startTime' , values : [ 1619712655875.4539 ] } ,
{ name : 'duration' , values : [ 14.984 ] } ,
2022-07-21 10:00:47 -06:00
{ name : 'serviceTags' , values : [ { key : 'servicetag1' , value : 'service' } ] } ,
{ name : 'logs' , values : [ { timestamp : 12345 , fields : [ { key : 'count' , value : 1 } ] } ] } ,
{ name : 'tags' , values : [ { key : 'tag1' , value : 'val1' } ] } ,
2021-03-22 19:09:15 +01:00
{ name : 'serviceName' , values : [ 'service' ] } ,
] ,
} )
) ;
2022-05-03 17:42:36 +01:00
const templateSrv : any = { replace : jest.fn ( ) } ;
const ds = new TempoDatasource ( defaultSettings , templateSrv ) ;
2022-01-05 19:34:09 +01:00
const response = await lastValueFrom ( ds . query ( { targets : [ { refId : 'refid1' , query : '12345' } ] } as any ) ) ;
2021-05-05 20:51:43 +02:00
expect (
( response . data [ 0 ] as DataFrame ) . fields . map ( ( f ) = > ( {
name : f.name ,
values : f.values.toArray ( ) ,
} ) )
) . toMatchObject ( [
{ name : 'traceID' , values : [ '04450900759028499335' ] } ,
{ name : 'spanID' , values : [ '4322526419282105830' ] } ,
{ name : 'parentSpanID' , values : [ '' ] } ,
{ name : 'operationName' , values : [ 'store.validateQueryTimeRange' ] } ,
{ name : 'startTime' , values : [ 1619712655875.4539 ] } ,
{ name : 'duration' , values : [ 14.984 ] } ,
{ name : 'serviceTags' , values : [ { key : 'servicetag1' , value : 'service' } ] } ,
{ name : 'logs' , values : [ { timestamp : 12345 , fields : [ { key : 'count' , value : 1 } ] } ] } ,
{ name : 'tags' , values : [ { key : 'tag1' , value : 'val1' } ] } ,
{ name : 'serviceName' , values : [ 'service' ] } ,
] ) ;
expect (
( response . data [ 1 ] as DataFrame ) . fields . map ( ( f ) = > ( {
name : f.name ,
values : f.values.toArray ( ) ,
} ) )
) . toMatchObject ( [
{ name : 'id' , values : [ '4322526419282105830' ] } ,
{ name : 'title' , values : [ 'service' ] } ,
2022-04-13 08:18:40 -07:00
{ name : 'subtitle' , values : [ 'store.validateQueryTimeRange' ] } ,
{ name : 'mainstat' , values : [ '14.98ms (100%)' ] } ,
{ name : 'secondarystat' , values : [ '14.98ms (100%)' ] } ,
2021-05-05 20:51:43 +02:00
{ name : 'color' , values : [ 1.000007560204647 ] } ,
] ) ;
expect (
( response . data [ 2 ] as DataFrame ) . fields . map ( ( f ) = > ( {
name : f.name ,
values : f.values.toArray ( ) ,
} ) )
) . toMatchObject ( [
{ name : 'id' , values : [ ] } ,
{ name : 'target' , values : [ ] } ,
{ name : 'source' , values : [ ] } ,
] ) ;
2020-10-13 19:12:49 +02:00
} ) ;
2021-08-05 15:13:44 +02:00
it ( 'should handle json file upload' , async ( ) = > {
const ds = new TempoDatasource ( defaultSettings ) ;
ds . uploadedJson = JSON . stringify ( mockJson ) ;
2021-08-19 06:38:31 +02:00
const response = await lastValueFrom (
ds . query ( {
2021-08-05 15:13:44 +02:00
targets : [ { queryType : 'upload' , refId : 'A' } ] ,
} as any )
2021-08-19 06:38:31 +02:00
) ;
2021-08-05 15:13:44 +02:00
const field = response . data [ 0 ] . fields [ 0 ] ;
expect ( field . name ) . toBe ( 'traceID' ) ;
expect ( field . type ) . toBe ( FieldType . string ) ;
expect ( field . values . get ( 0 ) ) . toBe ( '60ba2abb44f13eae' ) ;
expect ( field . values . length ) . toBe ( 6 ) ;
} ) ;
2021-08-19 09:15:46 -06:00
2021-08-25 10:08:46 -06:00
it ( 'should fail on invalid json file upload' , async ( ) = > {
const ds = new TempoDatasource ( defaultSettings ) ;
ds . uploadedJson = JSON . stringify ( mockInvalidJson ) ;
const response = await lastValueFrom (
ds . query ( {
targets : [ { queryType : 'upload' , refId : 'A' } ] ,
} as any )
) ;
expect ( response . error ? . message ) . toBeDefined ( ) ;
expect ( response . data . length ) . toBe ( 0 ) ;
} ) ;
2022-06-07 06:37:19 -06:00
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' ) ;
} ) ;
2021-08-19 09:15:46 -06:00
it ( 'should build search query correctly' , ( ) = > {
2022-05-03 17:42:36 +01:00
const templateSrv : any = { replace : jest.fn ( ) } ;
const ds = new TempoDatasource ( defaultSettings , templateSrv ) ;
const duration = '10ms' ;
templateSrv . replace . mockReturnValue ( duration ) ;
2021-08-19 09:15:46 -06:00
const tempoQuery : TempoQuery = {
queryType : 'search' ,
refId : 'A' ,
query : '' ,
serviceName : 'frontend' ,
spanName : '/config' ,
search : 'root.http.status_code=500' ,
2022-05-03 17:42:36 +01:00
minDuration : '$interpolationVar' ,
maxDuration : '$interpolationVar' ,
2021-08-19 09:15:46 -06:00
limit : 10 ,
} ;
const builtQuery = ds . buildSearchQuery ( tempoQuery ) ;
expect ( builtQuery ) . toStrictEqual ( {
2021-12-14 07:41:46 -07:00
tags : 'root.http.status_code=500 service.name="frontend" name="/config"' ,
2022-05-03 17:42:36 +01:00
minDuration : duration ,
maxDuration : duration ,
2021-08-19 09:15:46 -06:00
limit : 10 ,
} ) ;
} ) ;
2021-10-06 08:43:13 -06:00
it ( 'should include a default limit' , ( ) = > {
2021-09-28 10:52:57 -06:00
const ds = new TempoDatasource ( defaultSettings ) ;
const tempoQuery : TempoQuery = {
queryType : 'search' ,
refId : 'A' ,
query : '' ,
search : '' ,
} ;
const builtQuery = ds . buildSearchQuery ( tempoQuery ) ;
expect ( builtQuery ) . toStrictEqual ( {
2021-12-14 07:41:46 -07:00
tags : '' ,
2021-10-06 08:43:13 -06:00
limit : DEFAULT_LIMIT ,
2021-09-28 10:52:57 -06:00
} ) ;
} ) ;
2022-01-10 07:38:40 -07:00
it ( 'should include time range if provided' , ( ) = > {
const ds = new TempoDatasource ( defaultSettings ) ;
const tempoQuery : TempoQuery = {
queryType : 'search' ,
refId : 'A' ,
query : '' ,
search : '' ,
} ;
const timeRange = { startTime : 0 , endTime : 1000 } ;
const builtQuery = ds . buildSearchQuery ( tempoQuery , timeRange ) ;
expect ( builtQuery ) . toStrictEqual ( {
tags : '' ,
limit : DEFAULT_LIMIT ,
start : timeRange.startTime ,
end : timeRange.endTime ,
} ) ;
} ) ;
2021-09-16 08:04:15 -06:00
it ( 'formats native search query history correctly' , ( ) = > {
const ds = new TempoDatasource ( defaultSettings ) ;
const tempoQuery : TempoQuery = {
queryType : 'nativeSearch' ,
refId : 'A' ,
query : '' ,
serviceName : 'frontend' ,
spanName : '/config' ,
search : 'root.http.status_code=500' ,
minDuration : '1ms' ,
maxDuration : '100s' ,
limit : 10 ,
} ;
const result = ds . getQueryDisplayText ( tempoQuery ) ;
expect ( result ) . toBe (
'Service Name: frontend, Span Name: /config, Search: root.http.status_code=500, Min Duration: 1ms, Max Duration: 100s, Limit: 10'
) ;
} ) ;
2022-03-17 11:23:15 -06:00
it ( 'should get loki search datasource' , ( ) = > {
// 1. Get lokiSearch.datasource if present
const ds1 = new TempoDatasource ( {
. . . defaultSettings ,
jsonData : {
lokiSearch : {
datasourceUid : 'loki-1' ,
} ,
} ,
} ) ;
const lokiDS1 = ds1 . getLokiSearchDS ( ) ;
expect ( lokiDS1 ) . toBe ( 'loki-1' ) ;
// 2. Get traceToLogs.datasource
const ds2 = new TempoDatasource ( {
. . . defaultSettings ,
jsonData : {
tracesToLogs : {
lokiSearch : true ,
datasourceUid : 'loki-2' ,
} ,
} ,
} ) ;
const lokiDS2 = ds2 . getLokiSearchDS ( ) ;
expect ( lokiDS2 ) . toBe ( 'loki-2' ) ;
// 3. Return undefined if neither is available
const ds3 = new TempoDatasource ( defaultSettings ) ;
const lokiDS3 = ds3 . getLokiSearchDS ( ) ;
expect ( lokiDS3 ) . toBe ( undefined ) ;
// 4. Return undefined if lokiSearch is undefined, even if traceToLogs is present
// since this indicates the user cleared the fallback setting
const ds4 = new TempoDatasource ( {
. . . defaultSettings ,
jsonData : {
tracesToLogs : {
lokiSearch : true ,
datasourceUid : 'loki-2' ,
} ,
lokiSearch : {
datasourceUid : undefined ,
} ,
} ,
} ) ;
const lokiDS4 = ds4 . getLokiSearchDS ( ) ;
expect ( lokiDS4 ) . toBe ( undefined ) ;
} ) ;
2022-09-02 11:17:36 +01:00
describe ( 'test the testDatasource function' , ( ) = > {
it ( 'should return a success msg if response.ok is true' , async ( ) = > {
mockObservable = ( ) = > of ( { ok : true } ) ;
const ds = new TempoDatasource ( defaultSettings ) ;
const response = await ds . testDatasource ( ) ;
expect ( response . status ) . toBe ( 'success' ) ;
} ) ;
} ) ;
describe ( 'test the metadataRequest function' , ( ) = > {
it ( 'should return the last value from the observed stream' , async ( ) = > {
mockObservable = ( ) = > of ( '321' , '123' , '456' ) ;
const ds = new TempoDatasource ( defaultSettings ) ;
const response = await ds . metadataRequest ( '/api/search/tags' ) ;
expect ( response ) . toBe ( '456' ) ;
} ) ;
} ) ;
2020-10-13 19:12:49 +02:00
} ) ;
2022-06-09 17:56:15 +01:00
describe ( 'Tempo apm table' , ( ) = > {
it ( 'runs service graph queries' , async ( ) = > {
const ds = new TempoDatasource ( {
. . . defaultSettings ,
jsonData : {
serviceMap : {
datasourceUid : 'prom' ,
} ,
} ,
} ) ;
config . featureToggles . tempoApmTable = true ;
setDataSourceSrv ( backendSrvWithPrometheus as any ) ;
const response = await lastValueFrom (
ds . query ( { targets : [ { queryType : 'serviceMap' } ] , range : getDefaultTimeRange ( ) } as any )
) ;
expect ( response . data ) . toHaveLength ( 3 ) ;
expect ( response . state ) . toBe ( LoadingState . Done ) ;
// APM table
expect ( response . data [ 0 ] . fields [ 0 ] . name ) . toBe ( 'Name' ) ;
expect ( response . data [ 0 ] . fields [ 0 ] . values . toArray ( ) . length ) . toBe ( 2 ) ;
expect ( response . data [ 0 ] . fields [ 0 ] . values . toArray ( ) [ 0 ] ) . toBe ( 'HTTP Client' ) ;
expect ( response . data [ 0 ] . fields [ 0 ] . values . toArray ( ) [ 1 ] ) . toBe ( 'HTTP GET - root' ) ;
expect ( response . data [ 0 ] . fields [ 1 ] . name ) . toBe ( 'Rate' ) ;
expect ( response . data [ 0 ] . fields [ 1 ] . values . toArray ( ) . length ) . toBe ( 2 ) ;
expect ( response . data [ 0 ] . fields [ 1 ] . values . toArray ( ) [ 0 ] ) . toBe ( 12.75164671814457 ) ;
expect ( response . data [ 0 ] . fields [ 1 ] . values . toArray ( ) [ 1 ] ) . toBe ( 12.121331111401608 ) ;
expect ( response . data [ 0 ] . fields [ 1 ] . config . decimals ) . toBe ( 2 ) ;
expect ( response . data [ 0 ] . fields [ 1 ] . config . links [ 0 ] . title ) . toBe ( 'Rate' ) ;
expect ( response . data [ 0 ] . fields [ 1 ] . config . links [ 0 ] . internal . query . expr ) . toBe (
2022-06-20 09:56:45 +01:00
'sum(rate(traces_spanmetrics_calls_total{span_name="${__data.fields[0]}"}[$__rate_interval]))'
2022-06-09 17:56:15 +01:00
) ;
expect ( response . data [ 0 ] . fields [ 1 ] . config . links [ 0 ] . internal . query . range ) . toBe ( true ) ;
expect ( response . data [ 0 ] . fields [ 1 ] . config . links [ 0 ] . internal . query . exemplar ) . toBe ( true ) ;
expect ( response . data [ 0 ] . fields [ 1 ] . config . links [ 0 ] . internal . query . instant ) . toBe ( false ) ;
expect ( response . data [ 0 ] . fields [ 2 ] . values . toArray ( ) . length ) . toBe ( 2 ) ;
expect ( response . data [ 0 ] . fields [ 2 ] . values . toArray ( ) [ 0 ] ) . toBe ( 12.75164671814457 ) ;
expect ( response . data [ 0 ] . fields [ 2 ] . values . toArray ( ) [ 1 ] ) . toBe ( 12.121331111401608 ) ;
expect ( response . data [ 0 ] . fields [ 2 ] . config . color . mode ) . toBe ( 'continuous-BlPu' ) ;
expect ( response . data [ 0 ] . fields [ 2 ] . config . custom . displayMode ) . toBe ( 'lcd-gauge' ) ;
expect ( response . data [ 0 ] . fields [ 2 ] . config . decimals ) . toBe ( 3 ) ;
expect ( response . data [ 0 ] . fields [ 3 ] . name ) . toBe ( 'Error Rate' ) ;
expect ( response . data [ 0 ] . fields [ 3 ] . values . length ) . toBe ( 2 ) ;
expect ( response . data [ 0 ] . fields [ 3 ] . values [ 0 ] ) . toBe ( 3.75164671814457 ) ;
expect ( response . data [ 0 ] . fields [ 3 ] . values [ 1 ] ) . toBe ( 3.121331111401608 ) ;
expect ( response . data [ 0 ] . fields [ 3 ] . config . decimals ) . toBe ( 2 ) ;
expect ( response . data [ 0 ] . fields [ 3 ] . config . links [ 0 ] . title ) . toBe ( 'Error Rate' ) ;
expect ( response . data [ 0 ] . fields [ 3 ] . config . links [ 0 ] . internal . query . expr ) . toBe (
2022-06-24 10:19:26 +01:00
'sum(rate(traces_spanmetrics_calls_total{status_code="STATUS_CODE_ERROR",span_name="${__data.fields[0]}"}[$__rate_interval]))'
2022-06-09 17:56:15 +01:00
) ;
expect ( response . data [ 0 ] . fields [ 3 ] . config . links [ 0 ] . internal . query . range ) . toBe ( true ) ;
expect ( response . data [ 0 ] . fields [ 3 ] . config . links [ 0 ] . internal . query . exemplar ) . toBe ( true ) ;
expect ( response . data [ 0 ] . fields [ 3 ] . config . links [ 0 ] . internal . query . instant ) . toBe ( false ) ;
expect ( response . data [ 0 ] . fields [ 4 ] . values . length ) . toBe ( 2 ) ;
expect ( response . data [ 0 ] . fields [ 4 ] . values [ 0 ] ) . toBe ( 3.75164671814457 ) ;
expect ( response . data [ 0 ] . fields [ 4 ] . values [ 1 ] ) . toBe ( 3.121331111401608 ) ;
expect ( response . data [ 0 ] . fields [ 4 ] . config . color . mode ) . toBe ( 'continuous-RdYlGr' ) ;
expect ( response . data [ 0 ] . fields [ 4 ] . config . custom . displayMode ) . toBe ( 'lcd-gauge' ) ;
expect ( response . data [ 0 ] . fields [ 4 ] . config . decimals ) . toBe ( 3 ) ;
expect ( response . data [ 0 ] . fields [ 5 ] . name ) . toBe ( 'Duration (p90)' ) ;
expect ( response . data [ 0 ] . fields [ 5 ] . values . length ) . toBe ( 2 ) ;
expect ( response . data [ 0 ] . fields [ 5 ] . values [ 0 ] ) . toBe ( '0' ) ;
expect ( response . data [ 0 ] . fields [ 5 ] . values [ 1 ] ) . toBe ( 0.12003505696757232 ) ;
expect ( response . data [ 0 ] . fields [ 5 ] . config . unit ) . toBe ( 's' ) ;
expect ( response . data [ 0 ] . fields [ 5 ] . config . links [ 0 ] . title ) . toBe ( 'Duration' ) ;
expect ( response . data [ 0 ] . fields [ 5 ] . config . links [ 0 ] . internal . query . expr ) . toBe (
2022-06-24 10:19:26 +01:00
'histogram_quantile(.9, sum(rate(traces_spanmetrics_latency_bucket{span_name="${__data.fields[0]}"}[$__rate_interval])) by (le))'
2022-06-09 17:56:15 +01:00
) ;
expect ( response . data [ 0 ] . fields [ 5 ] . config . links [ 0 ] . internal . query . range ) . toBe ( true ) ;
expect ( response . data [ 0 ] . fields [ 5 ] . config . links [ 0 ] . internal . query . exemplar ) . toBe ( true ) ;
expect ( response . data [ 0 ] . fields [ 5 ] . config . links [ 0 ] . internal . query . instant ) . toBe ( false ) ;
expect ( response . data [ 0 ] . fields [ 6 ] . config . links [ 0 ] . url ) . toBe ( '' ) ;
expect ( response . data [ 0 ] . fields [ 6 ] . config . links [ 0 ] . title ) . toBe ( 'Tempo' ) ;
expect ( response . data [ 0 ] . fields [ 6 ] . config . links [ 0 ] . internal . query . queryType ) . toBe ( 'nativeSearch' ) ;
expect ( response . data [ 0 ] . fields [ 6 ] . config . links [ 0 ] . internal . query . spanName ) . toBe ( '${__data.fields[0]}' ) ;
// Service graph
expect ( response . data [ 1 ] . name ) . toBe ( 'Nodes' ) ;
expect ( response . data [ 1 ] . fields [ 0 ] . values . length ) . toBe ( 3 ) ;
expect ( response . data [ 1 ] . fields [ 0 ] . config . links . length ) . toBeGreaterThan ( 0 ) ;
expect ( response . data [ 1 ] . fields [ 0 ] . config . links ) . toEqual ( serviceGraphLinks ) ;
expect ( response . data [ 2 ] . name ) . toBe ( 'Edges' ) ;
expect ( response . data [ 2 ] . fields [ 0 ] . values . length ) . toBe ( 2 ) ;
} ) ;
it ( 'should build expr correctly' , ( ) = > {
let targets = { targets : [ { queryType : 'serviceMap' } ] } as any ;
let builtQuery = buildExpr (
{ expr : 'topk(5, sum(rate(traces_spanmetrics_calls_total{}[$__range])) by (span_name))' , params : [ ] } ,
'' ,
targets
) ;
expect ( builtQuery ) . toBe ( 'topk(5, sum(rate(traces_spanmetrics_calls_total{}[$__range])) by (span_name))' ) ;
builtQuery = buildExpr (
{
expr : 'topk(5, sum(rate(traces_spanmetrics_calls_total{}[$__range])) by (span_name))' ,
2022-06-24 10:19:26 +01:00
params : [ 'status_code="STATUS_CODE_ERROR"' ] ,
2022-06-09 17:56:15 +01:00
} ,
'span_name=~"HTTP Client|HTTP GET|HTTP GET - root|HTTP POST|HTTP POST - post"' ,
targets
) ;
expect ( builtQuery ) . toBe (
2022-06-24 10:19:26 +01:00
'topk(5, sum(rate(traces_spanmetrics_calls_total{status_code="STATUS_CODE_ERROR",span_name=~"HTTP Client|HTTP GET|HTTP GET - root|HTTP POST|HTTP POST - post"}[$__range])) by (span_name))'
2022-06-09 17:56:15 +01:00
) ;
builtQuery = buildExpr (
{
2022-06-24 10:19:26 +01:00
expr : 'histogram_quantile(.9, sum(rate(traces_spanmetrics_latency_bucket{}[$__range])) by (le))' ,
params : [ 'status_code="STATUS_CODE_ERROR"' ] ,
2022-06-09 17:56:15 +01:00
} ,
'span_name=~"HTTP Client"' ,
targets
) ;
expect ( builtQuery ) . toBe (
2022-06-24 10:19:26 +01:00
'histogram_quantile(.9, sum(rate(traces_spanmetrics_latency_bucket{status_code="STATUS_CODE_ERROR",span_name=~"HTTP Client"}[$__range])) by (le))'
2022-06-09 17:56:15 +01:00
) ;
targets = { targets : [ { queryType : 'serviceMap' , serviceMapQuery : '{client="app",service="app"}' } ] } as any ;
builtQuery = buildExpr (
{ expr : 'topk(5, sum(rate(traces_spanmetrics_calls_total{}[$__range])) by (span_name))' , params : [ ] } ,
'' ,
targets
) ;
expect ( builtQuery ) . toBe (
'topk(5, sum(rate(traces_spanmetrics_calls_total{service="app",service="app"}[$__range])) by (span_name))'
) ;
} ) ;
it ( 'should build link expr correctly' , ( ) = > {
let builtQuery = buildLinkExpr ( 'topk(5, sum(rate(traces_spanmetrics_calls_total{}[$__range])) by (span_name))' ) ;
2022-06-20 09:56:45 +01:00
expect ( builtQuery ) . toBe ( 'sum(rate(traces_spanmetrics_calls_total{}[$__rate_interval]))' ) ;
2022-06-09 17:56:15 +01:00
} ) ;
2022-07-27 15:48:09 +01:00
it ( 'should get field config correctly' , ( ) = > {
2022-07-25 10:03:57 +01:00
let datasourceUid = 's4Jvz8Qnk' ;
let tempoDatasourceUid = 'EbPO1fYnz' ;
let targetField = '__data.fields.target' ;
let tempoField = '__data.fields.target' ;
let sourceField = '__data.fields.source' ;
let fieldConfig = getFieldConfig ( datasourceUid , tempoDatasourceUid , targetField , tempoField , sourceField ) ;
let resultObj = {
links : [
{
url : '' ,
title : 'Request rate' ,
internal : {
query : {
expr : 'sum by (client, server)(rate(traces_service_graph_request_total{client="${__data.fields.source}",server="${__data.fields.target}"}[$__rate_interval]))' ,
range : true ,
exemplar : true ,
instant : false ,
} ,
datasourceUid : 's4Jvz8Qnk' ,
datasourceName : '' ,
} ,
} ,
{
url : '' ,
title : 'Request histogram' ,
internal : {
query : {
expr : 'histogram_quantile(0.9, sum(rate(traces_service_graph_request_server_seconds_bucket{client="${__data.fields.source}",server="${__data.fields.target}"}[$__rate_interval])) by (le, client, server))' ,
range : true ,
exemplar : true ,
instant : false ,
} ,
datasourceUid : 's4Jvz8Qnk' ,
datasourceName : '' ,
} ,
} ,
{
url : '' ,
title : 'Failed request rate' ,
internal : {
query : {
expr : 'sum by (client, server)(rate(traces_service_graph_request_failed_total{client="${__data.fields.source}",server="${__data.fields.target}"}[$__rate_interval]))' ,
range : true ,
exemplar : true ,
instant : false ,
} ,
datasourceUid : 's4Jvz8Qnk' ,
datasourceName : '' ,
} ,
} ,
{
url : '' ,
title : 'View traces' ,
internal : {
query : {
queryType : 'nativeSearch' ,
serviceName : '${__data.fields.target}' ,
} ,
datasourceUid : 'EbPO1fYnz' ,
datasourceName : '' ,
} ,
} ,
] ,
} ;
expect ( fieldConfig ) . toStrictEqual ( resultObj ) ;
} ) ;
2022-06-09 17:56:15 +01:00
it ( 'should get rate aligned values correctly' , ( ) = > {
const resp = [
{
refId :
'topk(5, sum(rate(traces_spanmetrics_calls_total{service="app",service="app"}[$__range])) by (span_name))' ,
fields : [
{
name : 'Time' ,
type : 'time' ,
config : { } ,
values : [ 1653828275000 , 1653828275000 , 1653828275000 , 1653828275000 , 1653828275000 ] ,
} ,
{
name : 'span_name' ,
config : {
filterable : true ,
} ,
type : 'string' ,
values : new ArrayVector ( [ 'HTTP Client' , 'HTTP GET' , 'HTTP GET - root' , 'HTTP POST' , 'HTTP POST - post' ] ) ,
} ,
] ,
} ,
] ;
const objToAlign = {
'HTTP GET - root' : {
2022-07-25 18:59:43 +01:00
value : 0.1234 ,
2022-06-09 17:56:15 +01:00
} ,
'HTTP GET' : {
2022-07-25 18:59:43 +01:00
value : 0.6789 ,
2022-06-09 17:56:15 +01:00
} ,
'HTTP POST - post' : {
2022-07-25 18:59:43 +01:00
value : 0.4321 ,
2022-06-09 17:56:15 +01:00
} ,
} ;
let value = getRateAlignedValues ( resp , objToAlign as any ) ;
2022-07-25 18:59:43 +01:00
expect ( value . toString ( ) ) . toBe ( '0,0.6789,0.1234,0,0.4321' ) ;
2022-06-09 17:56:15 +01:00
} ) ;
it ( 'should make apm request correctly' , ( ) = > {
const apmRequest = makeApmRequest ( [
'topk(5, sum(rate(traces_spanmetrics_calls_total{service="app"}[$__range])) by (span_name))"' ,
2022-06-24 10:19:26 +01:00
'histogram_quantile(.9, sum(rate(traces_spanmetrics_latency_bucket{status_code="STATUS_CODE_ERROR",service="app",service="app",span_name=~"HTTP Client"}[$__range])) by (le))' ,
2022-06-09 17:56:15 +01:00
] ) ;
expect ( apmRequest ) . toEqual ( [
{
refId : 'topk(5, sum(rate(traces_spanmetrics_calls_total{service="app"}[$__range])) by (span_name))"' ,
expr : 'topk(5, sum(rate(traces_spanmetrics_calls_total{service="app"}[$__range])) by (span_name))"' ,
instant : true ,
} ,
{
refId :
2022-06-24 10:19:26 +01:00
'histogram_quantile(.9, sum(rate(traces_spanmetrics_latency_bucket{status_code="STATUS_CODE_ERROR",service="app",service="app",span_name=~"HTTP Client"}[$__range])) by (le))' ,
expr : 'histogram_quantile(.9, sum(rate(traces_spanmetrics_latency_bucket{status_code="STATUS_CODE_ERROR",service="app",service="app",span_name=~"HTTP Client"}[$__range])) by (le))' ,
2022-06-09 17:56:15 +01:00
instant : true ,
} ,
] ) ;
} ) ;
it ( 'should make tempo link correctly' , ( ) = > {
const tempoLink = makeTempoLink ( 'Tempo' , '' , '"${__data.fields[0]}"' , 'gdev-tempo' ) ;
expect ( tempoLink ) . toEqual ( {
url : '' ,
title : 'Tempo' ,
internal : {
query : {
queryType : 'nativeSearch' ,
spanName : '"${__data.fields[0]}"' ,
} ,
datasourceUid : 'gdev-tempo' ,
datasourceName : 'Tempo' ,
} ,
} ) ;
} ) ;
} ) ;
2021-08-17 15:48:29 +02:00
const backendSrvWithPrometheus = {
async get ( uid : string ) {
if ( uid === 'prom' ) {
return {
query() {
2022-06-09 17:56:15 +01:00
return of ( {
data : [ rateMetric , errorRateMetric , durationMetric , totalsPromMetric , secondsPromMetric , failedPromMetric ] ,
} ) ;
2021-08-17 15:48:29 +02:00
} ,
} ;
}
throw new Error ( 'unexpected uid' ) ;
} ,
2022-07-18 15:36:16 +01:00
getDataSourceSettingsByUid ( uid : string ) {
if ( uid === 'prom' ) {
return { name : 'Prometheus' } ;
} else if ( uid === 'gdev-tempo' ) {
return { name : 'Tempo' } ;
}
return '' ;
} ,
2021-08-17 15:48:29 +02:00
} ;
2021-03-22 19:09:15 +01:00
function setupBackendSrv ( frame : DataFrame ) {
setBackendSrv ( {
fetch ( ) : Observable < FetchResponse < BackendDataSourceResponse > > {
return of (
createFetchResponse ( {
results : {
refid1 : {
2021-04-01 10:30:08 -07:00
frames : [ dataFrameToJSON ( frame ) ] ,
2021-03-22 19:09:15 +01:00
} ,
} ,
} )
) ;
} ,
} as any ) ;
}
2020-10-13 19:12:49 +02:00
2021-10-06 13:39:14 -06:00
const defaultSettings : DataSourceInstanceSettings < TempoJsonData > = {
2020-10-13 19:12:49 +02:00
id : 0 ,
2022-07-18 15:36:16 +01:00
uid : 'gdev-tempo' ,
2020-10-13 19:12:49 +02:00
type : 'tracing' ,
2021-08-17 15:48:29 +02:00
name : 'tempo' ,
2021-08-06 03:24:35 -04:00
access : 'proxy' ,
2020-10-13 19:12:49 +02:00
meta : {
2021-08-17 15:48:29 +02:00
id : 'tempo' ,
name : 'tempo' ,
2020-10-13 19:12:49 +02:00
type : PluginType . datasource ,
info : { } as any ,
module : '' ,
baseUrl : '' ,
} ,
2021-10-06 13:39:14 -06:00
jsonData : {
nodeGraph : {
enabled : true ,
} ,
} ,
2022-08-26 11:27:28 +01:00
readOnly : false ,
2020-10-13 19:12:49 +02:00
} ;
2021-08-17 15:48:29 +02:00
2022-06-09 17:56:15 +01:00
const rateMetric = new MutableDataFrame ( {
2022-07-27 15:48:09 +01:00
refId : 'topk(5, sum(rate(traces_spanmetrics_calls_total{span_kind="SPAN_KIND_SERVER"}[$__range])) by (span_name))' ,
2022-06-09 17:56:15 +01:00
fields : [
{ name : 'Time' , values : [ 1653725618609 , 1653725618609 ] } ,
{ name : 'span_name' , values : [ 'HTTP Client' , 'HTTP GET - root' ] } ,
{
2022-07-27 15:48:09 +01:00
name : 'Value #topk(5, sum(rate(traces_spanmetrics_calls_total{span_kind="SPAN_KIND_SERVER"}[$__range])) by (span_name))' ,
2022-06-09 17:56:15 +01:00
values : [ 12.75164671814457 , 12.121331111401608 ] ,
} ,
] ,
} ) ;
const errorRateMetric = new MutableDataFrame ( {
refId :
2022-06-24 10:19:26 +01:00
'topk(5, sum(rate(traces_spanmetrics_calls_total{status_code="STATUS_CODE_ERROR",span_name=~"HTTP Client|HTTP GET - root"}[$__range])) by (span_name))' ,
2022-06-09 17:56:15 +01:00
fields : [
{ name : 'Time' , values : [ 1653725618609 , 1653725618609 ] } ,
{ name : 'span_name' , values : [ 'HTTP Client' , 'HTTP GET - root' ] } ,
{
2022-06-24 10:19:26 +01:00
name : 'Value #topk(5, sum(rate(traces_spanmetrics_calls_total{status_code="STATUS_CODE_ERROR"}[$__range])) by (span_name))' ,
2022-06-09 17:56:15 +01:00
values : [ 3.75164671814457 , 3.121331111401608 ] ,
} ,
] ,
} ) ;
const durationMetric = new MutableDataFrame ( {
refId :
2022-06-24 10:19:26 +01:00
'histogram_quantile(.9, sum(rate(traces_spanmetrics_latency_bucket{span_name=~"HTTP GET - root"}[$__range])) by (le))' ,
2022-06-09 17:56:15 +01:00
fields : [
{ name : 'Time' , values : [ 1653725618609 ] } ,
{
2022-06-24 10:19:26 +01:00
name : 'Value #histogram_quantile(.9, sum(rate(traces_spanmetrics_latency_bucket{span_name=~"HTTP GET - root"}[$__range])) by (le))' ,
2022-06-09 17:56:15 +01:00
values : [ 0.12003505696757232 ] ,
} ,
] ,
} ) ;
2021-08-17 15:48:29 +02:00
const totalsPromMetric = new MutableDataFrame ( {
2021-10-19 11:58:21 +02:00
refId : 'traces_service_graph_request_total' ,
2021-08-17 15:48:29 +02:00
fields : [
{ name : 'Time' , values : [ 1628169788000 , 1628169788000 ] } ,
{ name : 'client' , values : [ 'app' , 'lb' ] } ,
{ name : 'instance' , values : [ '127.0.0.1:12345' , '127.0.0.1:12345' ] } ,
{ name : 'job' , values : [ 'local_scrape' , 'local_scrape' ] } ,
{ name : 'server' , values : [ 'db' , 'app' ] } ,
{ name : 'tempo_config' , values : [ 'default' , 'default' ] } ,
2021-10-19 11:58:21 +02:00
{ name : 'Value #traces_service_graph_request_total' , values : [ 10 , 20 ] } ,
2021-08-17 15:48:29 +02:00
] ,
} ) ;
const secondsPromMetric = new MutableDataFrame ( {
2021-10-19 11:58:21 +02:00
refId : 'traces_service_graph_request_server_seconds_sum' ,
2021-08-17 15:48:29 +02:00
fields : [
{ name : 'Time' , values : [ 1628169788000 , 1628169788000 ] } ,
{ name : 'client' , values : [ 'app' , 'lb' ] } ,
{ name : 'instance' , values : [ '127.0.0.1:12345' , '127.0.0.1:12345' ] } ,
{ name : 'job' , values : [ 'local_scrape' , 'local_scrape' ] } ,
{ name : 'server' , values : [ 'db' , 'app' ] } ,
{ name : 'tempo_config' , values : [ 'default' , 'default' ] } ,
2021-10-19 11:58:21 +02:00
{ name : 'Value #traces_service_graph_request_server_seconds_sum' , values : [ 10 , 40 ] } ,
2021-08-17 15:48:29 +02:00
] ,
} ) ;
2021-08-25 10:08:46 -06:00
2021-10-22 16:37:41 +02:00
const failedPromMetric = new MutableDataFrame ( {
refId : 'traces_service_graph_request_failed_total' ,
fields : [
{ name : 'Time' , values : [ 1628169788000 , 1628169788000 ] } ,
{ name : 'client' , values : [ 'app' , 'lb' ] } ,
{ name : 'instance' , values : [ '127.0.0.1:12345' , '127.0.0.1:12345' ] } ,
{ name : 'job' , values : [ 'local_scrape' , 'local_scrape' ] } ,
{ name : 'server' , values : [ 'db' , 'app' ] } ,
{ name : 'tempo_config' , values : [ 'default' , 'default' ] } ,
{ name : 'Value #traces_service_graph_request_failed_total' , values : [ 2 , 15 ] } ,
] ,
} ) ;
2021-08-25 10:08:46 -06:00
const mockInvalidJson = {
batches : [
{
resource : {
attributes : [ ] ,
} ,
instrumentation_library_spans : [
{
instrumentation_library : { } ,
spans : [
{
trace_id : 'AAAAAAAAAABguiq7RPE+rg==' ,
span_id : 'cmteMBAvwNA=' ,
parentSpanId : 'OY8PIaPbma4=' ,
name : 'HTTP GET - root' ,
kind : 'SPAN_KIND_SERVER' ,
startTimeUnixNano : '1627471657255809000' ,
endTimeUnixNano : '1627471657256268000' ,
attributes : [
{ key : 'http.status_code' , value : { intValue : '200' } } ,
{ key : 'http.method' , value : { stringValue : 'GET' } } ,
{ key : 'http.url' , value : { stringValue : '/' } } ,
{ key : 'component' , value : { stringValue : 'net/http' } } ,
] ,
status : { } ,
} ,
] ,
} ,
] ,
} ,
] ,
} ;
2022-01-13 08:28:20 -07:00
const serviceGraphLinks = [
{
url : '' ,
title : 'Request rate' ,
internal : {
query : {
2022-06-19 19:11:59 -06:00
expr : 'sum by (client, server)(rate(traces_service_graph_request_total{server="${__data.fields.id}"}[$__rate_interval]))' ,
2022-06-09 17:56:15 +01:00
instant : false ,
range : true ,
exemplar : true ,
2022-01-13 08:28:20 -07:00
} ,
datasourceUid : 'prom' ,
datasourceName : 'Prometheus' ,
} ,
} ,
2022-02-01 10:41:14 -07:00
{
url : '' ,
title : 'Request histogram' ,
internal : {
query : {
2022-02-11 07:58:10 -07:00
expr : 'histogram_quantile(0.9, sum(rate(traces_service_graph_request_server_seconds_bucket{server="${__data.fields.id}"}[$__rate_interval])) by (le, client, server))' ,
2022-06-09 17:56:15 +01:00
instant : false ,
range : true ,
exemplar : true ,
2022-02-01 10:41:14 -07:00
} ,
datasourceUid : 'prom' ,
datasourceName : 'Prometheus' ,
} ,
} ,
2022-01-13 08:28:20 -07:00
{
url : '' ,
title : 'Failed request rate' ,
internal : {
query : {
2022-06-19 19:11:59 -06:00
expr : 'sum by (client, server)(rate(traces_service_graph_request_failed_total{server="${__data.fields.id}"}[$__rate_interval]))' ,
2022-06-09 17:56:15 +01:00
instant : false ,
range : true ,
exemplar : true ,
2022-01-13 08:28:20 -07:00
} ,
datasourceUid : 'prom' ,
datasourceName : 'Prometheus' ,
} ,
} ,
2022-06-03 10:38:13 +01:00
{
url : '' ,
title : 'View traces' ,
internal : {
query : {
queryType : 'nativeSearch' ,
serviceName : '${__data.fields[0]}' ,
} as TempoQuery ,
2022-07-18 15:36:16 +01:00
datasourceUid : 'gdev-tempo' ,
2022-06-03 10:38:13 +01:00
datasourceName : 'Tempo' ,
} ,
} ,
2022-01-13 08:28:20 -07:00
] ;