diff --git a/public/app/plugins/datasource/tempo/datasource.test.ts b/public/app/plugins/datasource/tempo/datasource.test.ts index f494401a1c8..91c8db8bd50 100644 --- a/public/app/plugins/datasource/tempo/datasource.test.ts +++ b/public/app/plugins/datasource/tempo/datasource.test.ts @@ -695,6 +695,87 @@ describe('Tempo service graph view', () => { expect(fieldConfig).toStrictEqual(resultObj); }); + it('should get field config correctly when namespaces are present', () => { + let datasourceUid = 's4Jvz8Qnk'; + let tempoDatasourceUid = 'EbPO1fYnz'; + let targetField = '__data.fields.targetName'; + let tempoField = '__data.fields.target'; + let sourceField = '__data.fields.sourceName'; + let namespaceFields = { + targetNamespace: '__data.fields.targetNamespace', + sourceNamespace: '__data.fields.sourceNamespace', + }; + + let fieldConfig = getFieldConfig( + datasourceUid, + tempoDatasourceUid, + targetField, + tempoField, + sourceField, + namespaceFields + ); + + let resultObj = { + links: [ + { + url: '', + title: 'Request rate', + internal: { + query: { + expr: 'sum by (client, server, server_service_namespace, client_service_namespace)(rate(traces_service_graph_request_total{client="${__data.fields.sourceName}",client_service_namespace="${__data.fields.sourceNamespace}",server="${__data.fields.targetName}",server_service_namespace="${__data.fields.targetNamespace}"}[$__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.sourceName}",client_service_namespace="${__data.fields.sourceNamespace}",server="${__data.fields.targetName}",server_service_namespace="${__data.fields.targetNamespace}"}[$__rate_interval])) by (le, client, server, server_service_namespace, client_service_namespace))', + range: true, + exemplar: true, + instant: false, + }, + datasourceUid: 's4Jvz8Qnk', + datasourceName: '', + }, + }, + { + url: '', + title: 'Failed request rate', + internal: { + query: { + expr: 'sum by (client, server, server_service_namespace, client_service_namespace)(rate(traces_service_graph_request_failed_total{client="${__data.fields.sourceName}",client_service_namespace="${__data.fields.sourceNamespace}",server="${__data.fields.targetName}",server_service_namespace="${__data.fields.targetNamespace}"}[$__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); + }); + it('should get rate aligned values correctly', () => { const resp = [ { diff --git a/public/app/plugins/datasource/tempo/datasource.ts b/public/app/plugins/datasource/tempo/datasource.ts index 81b8bb6e321..3ac0ddded6c 100644 --- a/public/app/plugins/datasource/tempo/datasource.ts +++ b/public/app/plugins/datasource/tempo/datasource.ts @@ -799,22 +799,43 @@ function serviceMapQuery(request: DataQueryRequest, datasourceUid: s // No handling of multiple targets assume just one. NodeGraph does not support it anyway, but still should be // fixed at some point. - nodes.refId = request.targets[0].refId; - edges.refId = request.targets[0].refId; + const { serviceMapIncludeNamespace, refId } = request.targets[0]; + nodes.refId = refId; + edges.refId = refId; - nodes.fields[0].config = getFieldConfig( - datasourceUid, - tempoDatasourceUid, - '__data.fields.id', - '__data.fields[0]' - ); - edges.fields[0].config = getFieldConfig( - datasourceUid, - tempoDatasourceUid, - '__data.fields.target', - '__data.fields.target', - '__data.fields.source' - ); + if (serviceMapIncludeNamespace) { + nodes.fields[0].config = getFieldConfig( + datasourceUid, // datasourceUid + tempoDatasourceUid, // tempoDatasourceUid + '__data.fields.title', // targetField + '__data.fields[0]', // tempoField + undefined, // sourceField + { targetNamespace: '__data.fields.subtitle' } + ); + + edges.fields[0].config = getFieldConfig( + datasourceUid, // datasourceUid + tempoDatasourceUid, // tempoDatasourceUid + '__data.fields.targetName', // targetField + '__data.fields.target', // tempoField + '__data.fields.sourceName', // sourceField + { targetNamespace: '__data.fields.targetNamespace', sourceNamespace: '__data.fields.sourceNamespace' } + ); + } else { + nodes.fields[0].config = getFieldConfig( + datasourceUid, + tempoDatasourceUid, + '__data.fields.id', + '__data.fields[0]' + ); + edges.fields[0].config = getFieldConfig( + datasourceUid, + tempoDatasourceUid, + '__data.fields.target', + '__data.fields.target', + '__data.fields.source' + ); + } return { data: [nodes, edges], @@ -948,26 +969,42 @@ export function getFieldConfig( tempoDatasourceUid: string, targetField: string, tempoField: string, - sourceField?: string + sourceField?: string, + namespaceFields?: { targetNamespace: string; sourceNamespace?: string } ) { - sourceField = sourceField ? `client="\${${sourceField}}",` : ''; + let source = sourceField ? `client="\${${sourceField}}",` : ''; + let target = `server="\${${targetField}}"`; + let serverSumBy = 'server'; + + if (namespaceFields !== undefined) { + const { targetNamespace } = namespaceFields; + target += `,server_service_namespace="\${${targetNamespace}}"`; + serverSumBy += ', server_service_namespace'; + + if (source) { + const { sourceNamespace } = namespaceFields; + source += `client_service_namespace="\${${sourceNamespace}}",`; + serverSumBy += ', client_service_namespace'; + } + } + return { links: [ makePromLink( 'Request rate', - `sum by (client, server)(rate(${totalsMetric}{${sourceField}server="\${${targetField}}"}[$__rate_interval]))`, + `sum by (client, ${serverSumBy})(rate(${totalsMetric}{${source}${target}}[$__rate_interval]))`, datasourceUid, false ), makePromLink( 'Request histogram', - `histogram_quantile(0.9, sum(rate(${histogramMetric}{${sourceField}server="\${${targetField}}"}[$__rate_interval])) by (le, client, server))`, + `histogram_quantile(0.9, sum(rate(${histogramMetric}{${source}${target}}[$__rate_interval])) by (le, client, ${serverSumBy}))`, datasourceUid, false ), makePromLink( 'Failed request rate', - `sum by (client, server)(rate(${failedMetric}{${sourceField}server="\${${targetField}}"}[$__rate_interval]))`, + `sum by (client, ${serverSumBy})(rate(${failedMetric}{${source}${target}}[$__rate_interval]))`, datasourceUid, false ), diff --git a/public/app/plugins/datasource/tempo/graphTransform.test.ts b/public/app/plugins/datasource/tempo/graphTransform.test.ts index 7411e9a75c9..e6acd91ddd1 100644 --- a/public/app/plugins/datasource/tempo/graphTransform.test.ts +++ b/public/app/plugins/datasource/tempo/graphTransform.test.ts @@ -106,13 +106,17 @@ describe('mapPromMetricsToServiceMap', () => { expect(edges.fields).toMatchObject([ { name: 'id', values: ['app_db', 'lb_app'] }, { name: 'source', values: ['app', 'lb'] }, + { name: 'sourceName', values: ['app', 'lb'] }, + { name: 'sourceNamespace', values: [undefined, undefined] }, { name: 'target', values: ['db', 'app'] }, + { name: 'targetName', values: ['db', 'app'] }, + { name: 'targetNamespace', values: [undefined, undefined] }, { name: 'mainstat', values: [1000, 2000] }, { name: 'secondarystat', values: [10, 20] }, ]); }); - it('transforms prom metrics to service graph inlucding namespace', async () => { + it('transforms prom metrics to service graph including namespace', async () => { const range = { from: dateTime('2000-01-01T00:00:00'), to: dateTime('2000-01-01T00:01:00'), @@ -137,7 +141,11 @@ describe('mapPromMetricsToServiceMap', () => { expect(edges.fields).toMatchObject([ { name: 'id', values: ['ns1/app_ns3/db', 'ns2/lb_ns1/app'] }, { name: 'source', values: ['ns1/app', 'ns2/lb'] }, + { name: 'sourceName', values: ['app', 'lb'] }, + { name: 'sourceNamespace', values: ['ns1', 'ns2'] }, { name: 'target', values: ['ns3/db', 'ns1/app'] }, + { name: 'targetName', values: ['db', 'app'] }, + { name: 'targetNamespace', values: ['ns3', 'ns1'] }, { name: 'mainstat', values: [1000, 2000] }, { name: 'secondarystat', values: [10, 20] }, ]); diff --git a/public/app/plugins/datasource/tempo/graphTransform.ts b/public/app/plugins/datasource/tempo/graphTransform.ts index 8c540eb66c6..181225e4351 100644 --- a/public/app/plugins/datasource/tempo/graphTransform.ts +++ b/public/app/plugins/datasource/tempo/graphTransform.ts @@ -212,7 +212,11 @@ function createServiceMapDataFrames() { const edges = createDF('Edges', [ { name: Fields.id, type: FieldType.string }, { name: Fields.source, type: FieldType.string }, + { name: AdditionalEdgeFields.sourceName, type: FieldType.string }, + { name: AdditionalEdgeFields.sourceNamespace, type: FieldType.string }, { name: Fields.target, type: FieldType.string }, + { name: AdditionalEdgeFields.targetName, type: FieldType.string }, + { name: AdditionalEdgeFields.targetNamespace, type: FieldType.string }, { name: Fields.mainStat, type: FieldType.number, config: { unit: 'ms/r', displayName: 'Average response time' } }, { name: Fields.secondaryStat, @@ -249,9 +253,22 @@ type NodeObject = ServiceMapStatistics & { type EdgeObject = ServiceMapStatistics & { source: string; + sourceName: string; + sourceNamespace: string; target: string; + targetName: string; + targetNamespace: string; }; +// These fields are not necessary for rendering, so not available from the Fields enum +// Will be used for linking when namespace is present +enum AdditionalEdgeFields { + sourceName = 'sourceName', + sourceNamespace = 'sourceNamespace', + targetName = 'targetName', + targetNamespace = 'targetNamespace', +} + /** * Collect data from a metric into a map of nodes and edges. The metric data is modeled as counts of metric per edge * which is a pair of client-server nodes. This means we convert each row of the metric 1-1 to edges and than we assign @@ -289,7 +306,11 @@ function collectMetricData( // Create edge as it does not exist yet edgesMap[edgeId] = { target: serverId, + targetName: row.server, + targetNamespace: row.server_service_namespace, source: clientId, + sourceName: row.client, + sourceNamespace: row.client_service_namespace, [stat]: row[valueName], }; } else { @@ -348,7 +369,11 @@ function convertToDataFrames( edges.add({ [Fields.id]: edgeId, [Fields.source]: edge.source, + [AdditionalEdgeFields.sourceName]: edge.sourceName, + [AdditionalEdgeFields.sourceNamespace]: edge.sourceNamespace, [Fields.target]: edge.target, + [AdditionalEdgeFields.targetName]: edge.targetName, + [AdditionalEdgeFields.targetNamespace]: edge.targetNamespace, [Fields.mainStat]: edge.total ? (edge.seconds! / edge.total) * 1000 : Number.NaN, // Average response time [Fields.secondaryStat]: edge.total ? Math.round(edge.total * 100) / 100 : Number.NaN, // Request per second (to 2 decimals) });