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);
|
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', () => {
|
it('should get rate aligned values correctly', () => {
|
||||||
const resp = [
|
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
|
// No handling of multiple targets assume just one. NodeGraph does not support it anyway, but still should be
|
||||||
// fixed at some point.
|
// fixed at some point.
|
||||||
nodes.refId = request.targets[0].refId;
|
const { serviceMapIncludeNamespace, refId } = request.targets[0];
|
||||||
edges.refId = request.targets[0].refId;
|
nodes.refId = refId;
|
||||||
|
edges.refId = refId;
|
||||||
|
|
||||||
nodes.fields[0].config = getFieldConfig(
|
if (serviceMapIncludeNamespace) {
|
||||||
datasourceUid,
|
nodes.fields[0].config = getFieldConfig(
|
||||||
tempoDatasourceUid,
|
datasourceUid, // datasourceUid
|
||||||
'__data.fields.id',
|
tempoDatasourceUid, // tempoDatasourceUid
|
||||||
'__data.fields[0]'
|
'__data.fields.title', // targetField
|
||||||
);
|
'__data.fields[0]', // tempoField
|
||||||
edges.fields[0].config = getFieldConfig(
|
undefined, // sourceField
|
||||||
datasourceUid,
|
{ targetNamespace: '__data.fields.subtitle' }
|
||||||
tempoDatasourceUid,
|
);
|
||||||
'__data.fields.target',
|
|
||||||
'__data.fields.target',
|
edges.fields[0].config = getFieldConfig(
|
||||||
'__data.fields.source'
|
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 {
|
return {
|
||||||
data: [nodes, edges],
|
data: [nodes, edges],
|
||||||
@ -948,26 +969,42 @@ export function getFieldConfig(
|
|||||||
tempoDatasourceUid: string,
|
tempoDatasourceUid: string,
|
||||||
targetField: string,
|
targetField: string,
|
||||||
tempoField: 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 {
|
return {
|
||||||
links: [
|
links: [
|
||||||
makePromLink(
|
makePromLink(
|
||||||
'Request rate',
|
'Request rate',
|
||||||
`sum by (client, server)(rate(${totalsMetric}{${sourceField}server="\${${targetField}}"}[$__rate_interval]))`,
|
`sum by (client, ${serverSumBy})(rate(${totalsMetric}{${source}${target}}[$__rate_interval]))`,
|
||||||
datasourceUid,
|
datasourceUid,
|
||||||
false
|
false
|
||||||
),
|
),
|
||||||
makePromLink(
|
makePromLink(
|
||||||
'Request histogram',
|
'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,
|
datasourceUid,
|
||||||
false
|
false
|
||||||
),
|
),
|
||||||
makePromLink(
|
makePromLink(
|
||||||
'Failed request rate',
|
'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,
|
datasourceUid,
|
||||||
false
|
false
|
||||||
),
|
),
|
||||||
|
@ -106,13 +106,17 @@ describe('mapPromMetricsToServiceMap', () => {
|
|||||||
expect(edges.fields).toMatchObject([
|
expect(edges.fields).toMatchObject([
|
||||||
{ name: 'id', values: ['app_db', 'lb_app'] },
|
{ name: 'id', values: ['app_db', 'lb_app'] },
|
||||||
{ name: 'source', values: ['app', 'lb'] },
|
{ name: 'source', values: ['app', 'lb'] },
|
||||||
|
{ name: 'sourceName', values: ['app', 'lb'] },
|
||||||
|
{ name: 'sourceNamespace', values: [undefined, undefined] },
|
||||||
{ name: 'target', values: ['db', 'app'] },
|
{ name: 'target', values: ['db', 'app'] },
|
||||||
|
{ name: 'targetName', values: ['db', 'app'] },
|
||||||
|
{ name: 'targetNamespace', values: [undefined, undefined] },
|
||||||
{ name: 'mainstat', values: [1000, 2000] },
|
{ name: 'mainstat', values: [1000, 2000] },
|
||||||
{ name: 'secondarystat', values: [10, 20] },
|
{ 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 = {
|
const range = {
|
||||||
from: dateTime('2000-01-01T00:00:00'),
|
from: dateTime('2000-01-01T00:00:00'),
|
||||||
to: dateTime('2000-01-01T00:01:00'),
|
to: dateTime('2000-01-01T00:01:00'),
|
||||||
@ -137,7 +141,11 @@ describe('mapPromMetricsToServiceMap', () => {
|
|||||||
expect(edges.fields).toMatchObject([
|
expect(edges.fields).toMatchObject([
|
||||||
{ name: 'id', values: ['ns1/app_ns3/db', 'ns2/lb_ns1/app'] },
|
{ name: 'id', values: ['ns1/app_ns3/db', 'ns2/lb_ns1/app'] },
|
||||||
{ name: 'source', values: ['ns1/app', 'ns2/lb'] },
|
{ 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: 'target', values: ['ns3/db', 'ns1/app'] },
|
||||||
|
{ name: 'targetName', values: ['db', 'app'] },
|
||||||
|
{ name: 'targetNamespace', values: ['ns3', 'ns1'] },
|
||||||
{ name: 'mainstat', values: [1000, 2000] },
|
{ name: 'mainstat', values: [1000, 2000] },
|
||||||
{ name: 'secondarystat', values: [10, 20] },
|
{ name: 'secondarystat', values: [10, 20] },
|
||||||
]);
|
]);
|
||||||
|
@ -212,7 +212,11 @@ function createServiceMapDataFrames() {
|
|||||||
const edges = createDF('Edges', [
|
const edges = createDF('Edges', [
|
||||||
{ name: Fields.id, type: FieldType.string },
|
{ name: Fields.id, type: FieldType.string },
|
||||||
{ name: Fields.source, 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: 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.mainStat, type: FieldType.number, config: { unit: 'ms/r', displayName: 'Average response time' } },
|
||||||
{
|
{
|
||||||
name: Fields.secondaryStat,
|
name: Fields.secondaryStat,
|
||||||
@ -249,9 +253,22 @@ type NodeObject = ServiceMapStatistics & {
|
|||||||
|
|
||||||
type EdgeObject = ServiceMapStatistics & {
|
type EdgeObject = ServiceMapStatistics & {
|
||||||
source: string;
|
source: string;
|
||||||
|
sourceName: string;
|
||||||
|
sourceNamespace: string;
|
||||||
target: 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
|
* 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
|
* 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
|
// Create edge as it does not exist yet
|
||||||
edgesMap[edgeId] = {
|
edgesMap[edgeId] = {
|
||||||
target: serverId,
|
target: serverId,
|
||||||
|
targetName: row.server,
|
||||||
|
targetNamespace: row.server_service_namespace,
|
||||||
source: clientId,
|
source: clientId,
|
||||||
|
sourceName: row.client,
|
||||||
|
sourceNamespace: row.client_service_namespace,
|
||||||
[stat]: row[valueName],
|
[stat]: row[valueName],
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
@ -348,7 +369,11 @@ function convertToDataFrames(
|
|||||||
edges.add({
|
edges.add({
|
||||||
[Fields.id]: edgeId,
|
[Fields.id]: edgeId,
|
||||||
[Fields.source]: edge.source,
|
[Fields.source]: edge.source,
|
||||||
|
[AdditionalEdgeFields.sourceName]: edge.sourceName,
|
||||||
|
[AdditionalEdgeFields.sourceNamespace]: edge.sourceNamespace,
|
||||||
[Fields.target]: edge.target,
|
[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.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)
|
[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