mirror of https://github.com/grafana/grafana
Explore Metrics: Get OTel resources and filter metrics and labels (#91221)
* add OTel filter in metric select scene * add resource query to get matching OTEL job&instance * filter metrics by OTEL resources * only add otel select if DS has OTEL matching job and instance * add folder for otel resources * upate metric select for new otel folder * move otel api call * get otel resources for labels for single series job/instance target_info * add otel resources to adhoc variable dropdown * update otel api to check for standardization and return labels * label types for api * check standardization, show otel variable, select depenv, update other variables * remove otel target list from metric select scene * load resources if dep_env label has already been selected * exclude previously used filters * do not check standardization if there are already otel filters * drop filters when switching data sources * add experience var for switching to otel experience * remove otel from variables and place near settings * add error for non-standard prom with otel resources * fix typescript errors, remove ts-ignores * add custom variable for deployment environment like app-olly * fix name of otel variable * add function for getting otel resources from variables * add otel join query const * update standard check to be simpler * allow for unstandard otel data sources but give warning * add otelJoinQuery to the base query and clean up variables when state changes * refactor otel functions to return filters for targets, use targets to filter metrics * update metric names on otel target filter change * when no otel targets for otel resource filter, show no metrics * move switch to settings, default to use experience, refactor otel checks * clean code * fix refactor to add hasOtelResources for showing the switch in settings * sort otel resources by blessed list * reset otel when data source is changed * move otel experience toggle back outside settings * move showPreviews into settings * do not re-add otel resources from blessed list to filters when already selected * add otel join query variable to histogram base query * only show settings for appropriate scenes * show info tooltip the same but show error on hover for disabling otel exp for unstandard DS * refactor tagKeys and tagValues for otel resources variable, fix promoted list ordering, fix dep env state bug * default dep env value * apply var filters only where they are using VAR_FILTER_EXPR in queryies * change copy for labels to attributes * do not group_left job label when already joining by job * update copy for label variable when using otel * remove isStandard check for now because of data staleness in Prometheus * default to showing heatmap for histograms * add trail history for selecting dep env and otel resources * add otel resource attributes tests for DataTrail * move otel functions to utils * write tests for otel api calls * write tests for otel utils functions * fix history * standard otel has target_info metric and deployment_environment resource attributes * fix tests * refactor otel functions for updating state and variables * clean code * fix tests * fix tests * mock checkDataSourceForOtelResources * fix tests * update query tests with otelJoinQuery and default to heatmap for _bucket metrics * fix tests for otel api * fix trail history test * fix trail store tests for missing otel variables * make i18n-extract * handle target_info with inconsistent job and instance labels * fix otel copy and <Trans> component * fix custom variable deployment environment bug when switchiing data sources from non otel to otel * fix linting error for trans component * format i18nKey correctly * clean up old comments * add frontend hardening for OTel job and instance metric list filtering * fix test for deployment environment custom variable to use changeValueTo * fix i18n * remove comments for fixed bug * edit skipped testspull/93302/head
parent
542105b680
commit
4d1adf9db4
@ -1,8 +1,10 @@ |
||||
import { VAR_METRIC_EXPR, VAR_FILTERS_EXPR } from 'app/features/trails/shared'; |
||||
import { VAR_METRIC_EXPR, VAR_FILTERS_EXPR, VAR_OTEL_JOIN_QUERY_EXPR } from 'app/features/trails/shared'; |
||||
|
||||
const GENERAL_BASE_QUERY = `${VAR_METRIC_EXPR}${VAR_FILTERS_EXPR}`; |
||||
const GENERAL_RATE_BASE_QUERY = `rate(${GENERAL_BASE_QUERY}[$__rate_interval])`; |
||||
|
||||
export function getGeneralBaseQuery(rate: boolean) { |
||||
return rate ? GENERAL_RATE_BASE_QUERY : GENERAL_BASE_QUERY; |
||||
return rate |
||||
? `${GENERAL_RATE_BASE_QUERY} ${VAR_OTEL_JOIN_QUERY_EXPR}` |
||||
: `${GENERAL_BASE_QUERY} ${VAR_OTEL_JOIN_QUERY_EXPR}`; |
||||
} |
||||
|
||||
@ -0,0 +1,90 @@ |
||||
import { RawTimeRange } from '@grafana/data'; |
||||
import { BackendSrvRequest } from '@grafana/runtime'; |
||||
|
||||
import { getOtelResources, totalOtelResources, isOtelStandardization, getDeploymentEnvironments } from './api'; |
||||
|
||||
jest.mock('@grafana/runtime', () => ({ |
||||
getBackendSrv: () => { |
||||
return { |
||||
get: ( |
||||
url: string, |
||||
params?: Record<string, string | number>, |
||||
requestId?: string, |
||||
options?: Partial<BackendSrvRequest> |
||||
) => { |
||||
if (requestId === 'explore-metrics-otel-resources') { |
||||
return Promise.resolve({ data: ['job', 'instance', 'deployment_environment'] }); |
||||
} else if (requestId === 'explore-metrics-otel-check-total') { |
||||
return Promise.resolve({ |
||||
data: { |
||||
result: [ |
||||
{ metric: { job: 'job1', instance: 'instance1' } }, |
||||
{ metric: { job: 'job2', instance: 'instance2' } }, |
||||
], |
||||
}, |
||||
}); |
||||
} else if (requestId === 'explore-metrics-otel-check-standard') { |
||||
return Promise.resolve({ |
||||
data: { |
||||
result: [{ metric: { job: 'job1', instance: 'instance1' } }], |
||||
}, |
||||
}); |
||||
} else if (requestId === 'explore-metrics-otel-resources-deployment-env') { |
||||
return Promise.resolve({ data: ['env1', 'env2'] }); |
||||
} |
||||
return []; |
||||
}, |
||||
}; |
||||
}, |
||||
})); |
||||
|
||||
describe('OTEL API', () => { |
||||
const dataSourceUid = 'test-uid'; |
||||
const timeRange: RawTimeRange = { |
||||
from: 'now-1h', |
||||
to: 'now', |
||||
}; |
||||
|
||||
afterAll(() => { |
||||
jest.clearAllMocks(); |
||||
}); |
||||
|
||||
describe('getOtelResources', () => { |
||||
it('should fetch and filter OTEL resources', async () => { |
||||
const resources = await getOtelResources(dataSourceUid, timeRange); |
||||
|
||||
expect(resources).toEqual(['job', 'instance']); |
||||
}); |
||||
}); |
||||
|
||||
describe('totalOtelResources', () => { |
||||
it('should fetch total OTEL resources', async () => { |
||||
const result = await totalOtelResources(dataSourceUid, timeRange); |
||||
|
||||
expect(result).toEqual({ |
||||
jobs: ['job1', 'job2'], |
||||
instances: ['instance1', 'instance2'], |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('isOtelStandardization', () => { |
||||
// keeping for reference because standardization for OTel by series on target_info for job&instance is not consistent
|
||||
// There is a bug currently where there is stale data in Prometheus resulting in duplicate series for job&instance at random times
|
||||
// When this is resolved, we can check for standardization again
|
||||
xit('should check if OTEL standardization is met when there are no duplicate series on target_info for job&instance', async () => { |
||||
// will return duplicates, see mock above
|
||||
const isStandard = await isOtelStandardization(dataSourceUid, timeRange); |
||||
|
||||
expect(isStandard).toBe(false); |
||||
}); |
||||
}); |
||||
|
||||
describe('getDeploymentEnvironments', () => { |
||||
it('should fetch deployment environments', async () => { |
||||
const environments = await getDeploymentEnvironments(dataSourceUid, timeRange); |
||||
|
||||
expect(environments).toEqual(['env1', 'env2']); |
||||
}); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,166 @@ |
||||
import { RawTimeRange } from '@grafana/data'; |
||||
import { getPrometheusTime } from '@grafana/prometheus/src/language_utils'; |
||||
import { getBackendSrv } from '@grafana/runtime'; |
||||
|
||||
import { OtelResponse, LabelResponse, OtelTargetType } from './types'; |
||||
|
||||
const OTEL_RESOURCE_EXCLUDED_FILTERS = ['__name__', 'deployment_environment']; // name is handled by metric search metrics bar
|
||||
/** |
||||
* Function used to test for OTEL |
||||
* When filters are added, we can also get a list of otel targets used to reduce the metric list |
||||
* */ |
||||
const otelTargetInfoQuery = (filters?: string) => `count(target_info{${filters ?? ''}}) by (job, instance)`; |
||||
|
||||
export const TARGET_INFO_FILTER = { key: '__name__', value: 'target_info', operator: '=' }; |
||||
|
||||
/** |
||||
* Query the DS for target_info matching job and instance. |
||||
* Parse the results to get label filters. |
||||
* @param dataSourceUid |
||||
* @param timeRange |
||||
* @returns OtelResourcesType[], labels for the query result requesting matching job and instance on target_info metric |
||||
*/ |
||||
export async function getOtelResources( |
||||
dataSourceUid: string, |
||||
timeRange: RawTimeRange, |
||||
excludedFilters?: string[], |
||||
matchFilters?: string |
||||
): Promise<string[]> { |
||||
const allExcludedFilters = (excludedFilters ?? []).concat(OTEL_RESOURCE_EXCLUDED_FILTERS); |
||||
|
||||
const start = getPrometheusTime(timeRange.from, false); |
||||
const end = getPrometheusTime(timeRange.to, true); |
||||
|
||||
const url = `/api/datasources/uid/${dataSourceUid}/resources/api/v1/labels`; |
||||
const params: Record<string, string | number> = { |
||||
start, |
||||
end, |
||||
'match[]': `{__name__="target_info"${matchFilters ? `,${matchFilters}` : ''}}`, |
||||
}; |
||||
|
||||
const response = await getBackendSrv().get<LabelResponse>(url, params, 'explore-metrics-otel-resources'); |
||||
|
||||
// exclude __name__ or deployment_environment or previously chosen filters
|
||||
const resources = response.data?.filter((resource) => !allExcludedFilters.includes(resource)).map((el: string) => el); |
||||
|
||||
return resources; |
||||
} |
||||
|
||||
/** |
||||
* Get the total amount of job/instance pairs on target info metric |
||||
* |
||||
* @param dataSourceUid |
||||
* @param timeRange |
||||
* @param expr |
||||
* @returns |
||||
*/ |
||||
export async function totalOtelResources( |
||||
dataSourceUid: string, |
||||
timeRange: RawTimeRange, |
||||
filters?: string |
||||
): Promise<OtelTargetType> { |
||||
const start = getPrometheusTime(timeRange.from, false); |
||||
const end = getPrometheusTime(timeRange.to, true); |
||||
|
||||
const url = `/api/datasources/uid/${dataSourceUid}/resources/api/v1/query`; |
||||
const paramsTotalTargets: Record<string, string | number> = { |
||||
start, |
||||
end, |
||||
query: otelTargetInfoQuery(filters), |
||||
}; |
||||
|
||||
const responseTotal = await getBackendSrv().get<OtelResponse>( |
||||
url, |
||||
paramsTotalTargets, |
||||
'explore-metrics-otel-check-total' |
||||
); |
||||
|
||||
let jobs: string[] = []; |
||||
let instances: string[] = []; |
||||
|
||||
responseTotal.data.result.forEach((result) => { |
||||
// NOTE: sometimes there are target_info series with
|
||||
// - both job and instance labels
|
||||
// - only job label
|
||||
// - only instance label
|
||||
// Here we make sure both of them are present
|
||||
// because we use this collection to filter metric names
|
||||
if (result.metric.job && result.metric.instance) { |
||||
jobs.push(result.metric.job); |
||||
instances.push(result.metric.instance); |
||||
} |
||||
}); |
||||
|
||||
const otelTargets: OtelTargetType = { |
||||
jobs, |
||||
instances, |
||||
}; |
||||
|
||||
return otelTargets; |
||||
} |
||||
|
||||
/** |
||||
* Look for duplicated series in target_info metric by job and instance labels |
||||
* If each job&instance combo is unique, the data source is otel standardized. |
||||
* If there is a count by job&instance on target_info greater than one, |
||||
* the data source is not standardized |
||||
* |
||||
* @param dataSourceUid |
||||
* @param timeRange |
||||
* @param expr |
||||
* @returns |
||||
*/ |
||||
export async function isOtelStandardization( |
||||
dataSourceUid: string, |
||||
timeRange: RawTimeRange, |
||||
expr?: string |
||||
): Promise<boolean> { |
||||
const url = `/api/datasources/uid/${dataSourceUid}/resources/api/v1/query`; |
||||
|
||||
const start = getPrometheusTime(timeRange.from, false); |
||||
const end = getPrometheusTime(timeRange.to, true); |
||||
|
||||
const paramsTargets: Record<string, string | number> = { |
||||
start, |
||||
end, |
||||
// any data source with duplicated series will have a count > 1
|
||||
query: `${otelTargetInfoQuery()} > 1`, |
||||
}; |
||||
|
||||
const response = await getBackendSrv().get<OtelResponse>(url, paramsTargets, 'explore-metrics-otel-check-standard'); |
||||
|
||||
// the response should be not greater than zero if it is standard
|
||||
const checkStandard = !(response.data.result.length > 0); |
||||
|
||||
return checkStandard; |
||||
} |
||||
|
||||
/** |
||||
* Query the DS for deployment environment label values. |
||||
* |
||||
* @param dataSourceUid |
||||
* @param timeRange |
||||
* @returns string[], values for the deployment_environment label |
||||
*/ |
||||
export async function getDeploymentEnvironments(dataSourceUid: string, timeRange: RawTimeRange): Promise<string[]> { |
||||
const start = getPrometheusTime(timeRange.from, false); |
||||
const end = getPrometheusTime(timeRange.to, true); |
||||
|
||||
const url = `/api/datasources/uid/${dataSourceUid}/resources/api/v1/label/deployment_environment/values`; |
||||
const params: Record<string, string | number> = { |
||||
start, |
||||
end, |
||||
'match[]': '{__name__="target_info"}', |
||||
}; |
||||
|
||||
const response = await getBackendSrv().get<LabelResponse>( |
||||
url, |
||||
params, |
||||
'explore-metrics-otel-resources-deployment-env' |
||||
); |
||||
|
||||
// exclude __name__ or deployment_environment or previously chosen filters
|
||||
const resources = response.data; |
||||
|
||||
return resources; |
||||
} |
||||
@ -0,0 +1,32 @@ |
||||
export type OtelResponse = { |
||||
data: { |
||||
result: [ |
||||
{ |
||||
metric: { |
||||
job: string; |
||||
instance: string; |
||||
}; |
||||
}, |
||||
]; |
||||
}; |
||||
status: 'success' | 'error'; |
||||
error?: 'string'; |
||||
warnings?: string[]; |
||||
}; |
||||
|
||||
export type LabelResponse = { |
||||
data: string[]; |
||||
status: 'success' | 'error'; |
||||
error?: 'string'; |
||||
warnings?: string[]; |
||||
}; |
||||
|
||||
export type OtelTargetType = { |
||||
jobs: string[]; |
||||
instances: string[]; |
||||
}; |
||||
|
||||
export type OtelResourcesObject = { |
||||
filters: string; |
||||
labels: string; |
||||
}; |
||||
@ -0,0 +1,195 @@ |
||||
import { MetricFindValue } from '@grafana/data'; |
||||
import { AdHocFiltersVariable, CustomVariable, sceneGraph, SceneObject } from '@grafana/scenes'; |
||||
|
||||
import { VAR_OTEL_DEPLOYMENT_ENV, VAR_OTEL_RESOURCES } from '../shared'; |
||||
|
||||
import { OtelResourcesObject } from './types'; |
||||
|
||||
export const blessedList = (): Record<string, number> => { |
||||
return { |
||||
cloud_availability_zone: 0, |
||||
cloud_region: 0, |
||||
container_name: 0, |
||||
k8s_cluster_name: 0, |
||||
k8s_container_name: 0, |
||||
k8s_cronjob_name: 0, |
||||
k8s_daemonset_name: 0, |
||||
k8s_deployment_name: 0, |
||||
k8s_job_name: 0, |
||||
k8s_namespace_name: 0, |
||||
k8s_pod_name: 0, |
||||
k8s_replicaset_name: 0, |
||||
k8s_statefulset_name: 0, |
||||
service_instance_id: 0, |
||||
service_name: 0, |
||||
service_namespace: 0, |
||||
}; |
||||
}; |
||||
|
||||
export function sortResources(resources: MetricFindValue[], excluded: string[]) { |
||||
// these may be filtered
|
||||
const promotedList = blessedList(); |
||||
|
||||
const blessed = Object.keys(promotedList); |
||||
|
||||
resources = resources.filter((resource) => { |
||||
// if not in the list keep it
|
||||
const val = (resource.value ?? '').toString(); |
||||
|
||||
if (!blessed.includes(val)) { |
||||
return true; |
||||
} |
||||
// remove blessed filters
|
||||
// but indicate which are available
|
||||
promotedList[val] = 1; |
||||
return false; |
||||
}); |
||||
|
||||
const promotedResources = Object.keys(promotedList) |
||||
.filter((resource) => promotedList[resource] && !excluded.includes(resource)) |
||||
.map((v) => ({ text: v })); |
||||
|
||||
// put the filters first
|
||||
return promotedResources.concat(resources); |
||||
} |
||||
|
||||
/** |
||||
* Return a collection of labels and labels filters. |
||||
* This data is used to build the join query to filter with otel resources |
||||
* |
||||
* @param otelResourcesObject |
||||
* @returns a string that is used to add a join query to filter otel resources |
||||
*/ |
||||
export function getOtelJoinQuery(otelResourcesObject: OtelResourcesObject): string { |
||||
let otelResourcesJoinQuery = ''; |
||||
if (otelResourcesObject.filters && otelResourcesObject.labels) { |
||||
// add support for otel data sources that are not standardized, i.e., have non unique target_info series by job, instance
|
||||
otelResourcesJoinQuery = `* on (job, instance) group_left(${otelResourcesObject.labels}) topk by (job, instance) (1, target_info{${otelResourcesObject.filters}})`; |
||||
} |
||||
|
||||
return otelResourcesJoinQuery; |
||||
} |
||||
|
||||
/** |
||||
* Returns an object containing all the filters for otel resources as well as a list of labels |
||||
* |
||||
* @param scene |
||||
* @param firstQueryVal |
||||
* @returns |
||||
*/ |
||||
export function getOtelResourcesObject(scene: SceneObject, firstQueryVal?: string): OtelResourcesObject { |
||||
const otelResources = sceneGraph.lookupVariable(VAR_OTEL_RESOURCES, scene); |
||||
// add deployment env to otel resource filters
|
||||
const otelDepEnv = sceneGraph.lookupVariable(VAR_OTEL_DEPLOYMENT_ENV, scene); |
||||
|
||||
let otelResourcesObject = { labels: '', filters: '' }; |
||||
|
||||
if (otelResources instanceof AdHocFiltersVariable && otelDepEnv instanceof CustomVariable) { |
||||
// get the collection of adhoc filters
|
||||
const otelFilters = otelResources.state.filters; |
||||
|
||||
// get the value for deployment_environment variable
|
||||
let otelDepEnvValue = String(otelDepEnv.getValue()); |
||||
// check if there are multiple environments
|
||||
const isMulti = otelDepEnvValue.includes(','); |
||||
// start with the default label filters for deployment_environment
|
||||
let op = '='; |
||||
let val = firstQueryVal ? firstQueryVal : otelDepEnvValue; |
||||
// update the filters if multiple deployment environments selected
|
||||
if (isMulti) { |
||||
op = '=~'; |
||||
val = val.split(',').join('|'); |
||||
} |
||||
|
||||
// start with the deployment environment
|
||||
let allFilters = `deployment_environment${op}"${val}"`; |
||||
let allLabels = 'deployment_environment'; |
||||
|
||||
// add the other OTEL resource filters
|
||||
for (let i = 0; i < otelFilters?.length; i++) { |
||||
const labelName = otelFilters[i].key; |
||||
const op = otelFilters[i].operator; |
||||
const labelValue = otelFilters[i].value; |
||||
|
||||
allFilters += `,${labelName}${op}"${labelValue}"`; |
||||
|
||||
const addLabelToGroupLeft = labelName !== 'job' && labelName !== 'instance'; |
||||
|
||||
if (addLabelToGroupLeft) { |
||||
allLabels += `,${labelName}`; |
||||
} |
||||
} |
||||
|
||||
otelResourcesObject.labels = allLabels; |
||||
otelResourcesObject.filters = allFilters; |
||||
|
||||
return otelResourcesObject; |
||||
} |
||||
return otelResourcesObject; |
||||
} |
||||
|
||||
/** |
||||
* This function checks that when adding OTel job and instance filters |
||||
* to the label values request for a list of metrics, |
||||
* the total character count of the request does not exceed 2000 characters |
||||
* |
||||
* @param matchTerms __name__ and other Prom filters |
||||
* @param jobsList list of jobs in target_info |
||||
* @param instancesList list of instances in target_info |
||||
* @param missingOtelTargets flag to indicate truncated job and instance filters |
||||
* @returns |
||||
*/ |
||||
export function limitOtelMatchTerms( |
||||
matchTerms: string[], |
||||
jobsList: string[], |
||||
instancesList: string[], |
||||
missingOtelTargets: boolean |
||||
): { missingOtelTargets: boolean; jobsRegex: string; instancesRegex: string } { |
||||
const charLimit = 2000; |
||||
|
||||
let initialCharAmount = matchTerms.join(',').length; |
||||
|
||||
// start to add values to the regex and start quote
|
||||
let jobsRegex = 'job=~"'; |
||||
let instancesRegex = 'instance=~"'; |
||||
|
||||
// iterate through the jobs and instances,
|
||||
// count the chars as they are added,
|
||||
// stop before the total count reaches 2000
|
||||
// show a warning that there are missing OTel targets and
|
||||
// the user must select more OTel resource attributes
|
||||
for (let i = 0; i < jobsList.length; i++) { |
||||
// use or character for the count
|
||||
const orChars = i === 0 ? 0 : 2; |
||||
// count all the characters that will go into the match terms
|
||||
const checkCharAmount = |
||||
initialCharAmount + |
||||
jobsRegex.length + |
||||
jobsList[i].length + |
||||
instancesRegex.length + |
||||
instancesList[i].length + |
||||
orChars; |
||||
|
||||
if (checkCharAmount <= charLimit) { |
||||
if (i === 0) { |
||||
jobsRegex += `${jobsList[i]}`; |
||||
instancesRegex += `${instancesList[i]}`; |
||||
} else { |
||||
jobsRegex += `|${jobsList[i]}`; |
||||
instancesRegex += `|${instancesList[i]}`; |
||||
} |
||||
} else { |
||||
missingOtelTargets = true; |
||||
break; |
||||
} |
||||
} |
||||
// complete the quote after values have been added
|
||||
jobsRegex += '"'; |
||||
instancesRegex += '"'; |
||||
|
||||
return { |
||||
missingOtelTargets, |
||||
jobsRegex, |
||||
instancesRegex, |
||||
}; |
||||
} |
||||
@ -0,0 +1,191 @@ |
||||
import { MetricFindValue } from '@grafana/data'; |
||||
|
||||
import { sortResources, getOtelJoinQuery, blessedList, limitOtelMatchTerms } from './util'; |
||||
|
||||
describe('sortResources', () => { |
||||
it('should sort and filter resources correctly', () => { |
||||
const resources: MetricFindValue[] = [ |
||||
{ text: 'cloud_region', value: 'cloud_region' }, |
||||
{ text: 'custom_resource', value: 'custom_resource' }, |
||||
]; |
||||
const excluded: string[] = ['cloud_region']; |
||||
|
||||
const result = sortResources(resources, excluded); |
||||
|
||||
expect(result).toEqual([{ text: 'custom_resource', value: 'custom_resource' }]); |
||||
}); |
||||
}); |
||||
|
||||
describe('getOtelJoinQuery', () => { |
||||
it('should return the correct join query', () => { |
||||
const otelResourcesObject = { |
||||
filters: 'job="test-job",instance="test-instance"', |
||||
labels: 'deployment_environment,custom_label', |
||||
}; |
||||
|
||||
const result = getOtelJoinQuery(otelResourcesObject); |
||||
|
||||
expect(result).toBe( |
||||
'* on (job, instance) group_left(deployment_environment,custom_label) topk by (job, instance) (1, target_info{job="test-job",instance="test-instance"})' |
||||
); |
||||
}); |
||||
|
||||
it('should return an empty string if filters or labels are missing', () => { |
||||
const otelResourcesObject = { |
||||
filters: '', |
||||
labels: '', |
||||
}; |
||||
|
||||
const result = getOtelJoinQuery(otelResourcesObject); |
||||
|
||||
expect(result).toBe(''); |
||||
}); |
||||
}); |
||||
|
||||
describe('blessedList', () => { |
||||
it('should return the correct blessed list', () => { |
||||
const result = blessedList(); |
||||
expect(result).toEqual({ |
||||
cloud_availability_zone: 0, |
||||
cloud_region: 0, |
||||
container_name: 0, |
||||
k8s_cluster_name: 0, |
||||
k8s_container_name: 0, |
||||
k8s_cronjob_name: 0, |
||||
k8s_daemonset_name: 0, |
||||
k8s_deployment_name: 0, |
||||
k8s_job_name: 0, |
||||
k8s_namespace_name: 0, |
||||
k8s_pod_name: 0, |
||||
k8s_replicaset_name: 0, |
||||
k8s_statefulset_name: 0, |
||||
service_instance_id: 0, |
||||
service_name: 0, |
||||
service_namespace: 0, |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('sortResources', () => { |
||||
it('should sort and filter resources correctly', () => { |
||||
const resources: MetricFindValue[] = [ |
||||
{ text: 'cloud_region', value: 'cloud_region' }, |
||||
{ text: 'custom_resource', value: 'custom_resource' }, |
||||
]; |
||||
const excluded: string[] = ['cloud_region']; |
||||
|
||||
const result = sortResources(resources, excluded); |
||||
|
||||
expect(result).toEqual([{ text: 'custom_resource', value: 'custom_resource' }]); |
||||
}); |
||||
|
||||
it('should promote blessed resources and exclude specified ones', () => { |
||||
const resources: MetricFindValue[] = [ |
||||
{ text: 'custom_resource', value: 'custom_resource' }, |
||||
{ text: 'k8s_cluster_name', value: 'k8s_cluster_name' }, |
||||
]; |
||||
const excluded: string[] = ['k8s_cluster_name']; |
||||
|
||||
const result = sortResources(resources, excluded); |
||||
|
||||
expect(result).toEqual([{ text: 'custom_resource', value: 'custom_resource' }]); |
||||
}); |
||||
}); |
||||
|
||||
describe('getOtelJoinQuery', () => { |
||||
it('should return the correct join query', () => { |
||||
const otelResourcesObject = { |
||||
filters: 'job="test-job",instance="test-instance"', |
||||
labels: 'deployment_environment,custom_label', |
||||
}; |
||||
|
||||
const result = getOtelJoinQuery(otelResourcesObject); |
||||
|
||||
expect(result).toBe( |
||||
'* on (job, instance) group_left(deployment_environment,custom_label) topk by (job, instance) (1, target_info{job="test-job",instance="test-instance"})' |
||||
); |
||||
}); |
||||
|
||||
it('should return an empty string if filters or labels are missing', () => { |
||||
const otelResourcesObject = { |
||||
filters: '', |
||||
labels: '', |
||||
}; |
||||
|
||||
const result = getOtelJoinQuery(otelResourcesObject); |
||||
|
||||
expect(result).toBe(''); |
||||
}); |
||||
}); |
||||
|
||||
describe('limitOtelMatchTerms', () => { |
||||
it('should limit the OTel match terms if the total match term character count exceeds 2000', () => { |
||||
// the initial match is 1980 characters
|
||||
const promMatchTerms: string[] = [ |
||||
`${[...Array(1979).keys()] |
||||
.map((el) => { |
||||
return '0'; |
||||
}) |
||||
.join('')}"`,
|
||||
]; |
||||
// job=~"" is 7 chars
|
||||
// instance=~"" is 12 characters
|
||||
|
||||
// 7 + 12 + 1979 = 1998
|
||||
// so we have room to add 2 more characters
|
||||
// attribute values that are b will be left out
|
||||
const jobs = ['a', 'b', 'c']; |
||||
const instances = ['d', 'e', 'f']; |
||||
|
||||
const missingOtelTargets = false; |
||||
|
||||
const result = limitOtelMatchTerms(promMatchTerms, jobs, instances, missingOtelTargets); |
||||
|
||||
expect(result.missingOtelTargets).toEqual(true); |
||||
expect(result.jobsRegex).toEqual('job=~"a"'); |
||||
expect(result.instancesRegex).toEqual('instance=~"d"'); |
||||
}); |
||||
|
||||
it('should include | char in the count', () => { |
||||
// the initial match is 1980 characters
|
||||
const promMatchTerms: string[] = [ |
||||
`${[...Array(1975).keys()] |
||||
.map((el) => { |
||||
return '0'; |
||||
}) |
||||
.join('')}"`,
|
||||
]; |
||||
// job=~"" is 7 chars
|
||||
// instance=~"" is 12 characters
|
||||
|
||||
// 7 + 12 + 1975 = 1994
|
||||
// so we have room to add 6 more characters
|
||||
// the extra 6 characters will be 'a|b' and 'd|e'
|
||||
const jobs = ['a', 'b', 'c']; |
||||
const instances = ['d', 'e', 'f']; |
||||
|
||||
const missingOtelTargets = false; |
||||
|
||||
const result = limitOtelMatchTerms(promMatchTerms, jobs, instances, missingOtelTargets); |
||||
|
||||
expect(result.missingOtelTargets).toEqual(true); |
||||
expect(result.jobsRegex).toEqual('job=~"a|b"'); |
||||
expect(result.instancesRegex).toEqual('instance=~"d|e"'); |
||||
}); |
||||
|
||||
it('should add all OTel job and instance matches if the character count is less that 2000', () => { |
||||
const promMatchTerms: string[] = []; |
||||
|
||||
const jobs = ['job1', 'job2', 'job3', 'job4', 'job5']; |
||||
|
||||
const instances = ['instance1', 'instance2', 'instance3', 'instance4', 'instance5']; |
||||
|
||||
const missingOtelTargets = false; |
||||
|
||||
const result = limitOtelMatchTerms(promMatchTerms, jobs, instances, missingOtelTargets); |
||||
|
||||
expect(result.missingOtelTargets).toEqual(false); |
||||
expect(result.jobsRegex).toEqual('job=~"job1|job2|job3|job4|job5"'); |
||||
expect(result.instancesRegex).toEqual('instance=~"instance1|instance2|instance3|instance4|instance5"'); |
||||
}); |
||||
}); |
||||
Loading…
Reference in new issue