mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Tempo Service Map: Fix context menu links in service map when namespace is present (#74186)
* Add necessary fields to edges * Add information about name and namespace to edge when available * Use new fields to build search taking into account namespace * Remove new fields from NodeGraphDataFrameFieldNames, define them locally
This commit is contained in:
parent
0c44a6f9bb
commit
3bae1c564d
@ -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 = [
|
||||
{
|
||||
|
@ -799,22 +799,43 @@ function serviceMapQuery(request: DataQueryRequest<TempoQuery>, 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
|
||||
),
|
||||
|
@ -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] },
|
||||
]);
|
||||
|
@ -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)
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user