diff --git a/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/container/container_metrics_configs.test.ts b/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/container/container_metrics_configs.test.ts index 1c79161d3c18e..1de3f8d532887 100644 --- a/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/container/container_metrics_configs.test.ts +++ b/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/container/container_metrics_configs.test.ts @@ -8,10 +8,11 @@ import { ECS_CONTAINER_CPU_USAGE_LIMIT_PCT, ECS_CONTAINER_MEMORY_USAGE_BYTES, + otelDatasetFilter, SEMCONV_DOCKER_CONTAINER_CPU_UTILIZATION, SEMCONV_DOCKER_CONTAINER_MEMORY_PERCENT, - SEMCONV_K8S_CONTAINER_CPU_LIMIT_UTILIZATION, - SEMCONV_K8S_CONTAINER_MEMORY_LIMIT_UTILIZATION, + SEMCONV_CONTAINER_CPU_USAGE, + SEMCONV_CONTAINER_MEMORY_WORKING_SET, } from '../shared/constants'; import { getOptionsForSchema } from './container_metrics_configs'; @@ -35,7 +36,7 @@ describe('container_metrics_configs', () => { const { options } = getOptionsForSchema(true, false); expect(options.groupBy).toBe('container.id'); - expect(options.kuery).toBe('event.dataset: "dockerstatsreceiver.otel"'); + expect(options.kuery).toBe(otelDatasetFilter('dockerstatsreceiver.otel')); expect(options.metrics).toEqual( expect.arrayContaining([ { field: SEMCONV_DOCKER_CONTAINER_CPU_UTILIZATION, aggregation: 'avg' }, @@ -49,7 +50,7 @@ describe('container_metrics_configs', () => { const { options } = getOptionsForSchema(true); expect(options.groupBy).toBe('container.id'); - expect(options.kuery).toBe('event.dataset: "dockerstatsreceiver.otel"'); + expect(options.kuery).toBe(otelDatasetFilter('dockerstatsreceiver.otel')); expect(options.metrics).toEqual( expect.arrayContaining([ { field: SEMCONV_DOCKER_CONTAINER_CPU_UTILIZATION, aggregation: 'avg' }, @@ -63,11 +64,11 @@ describe('container_metrics_configs', () => { const { options } = getOptionsForSchema(true, true); expect(options.groupBy).toBe('container.id'); - expect(options.kuery).toBe('event.dataset: "kubeletstatsreceiver.otel"'); + expect(options.kuery).toBe(otelDatasetFilter('kubeletstatsreceiver.otel')); expect(options.metrics).toEqual( expect.arrayContaining([ - { field: SEMCONV_K8S_CONTAINER_CPU_LIMIT_UTILIZATION, aggregation: 'avg' }, - { field: SEMCONV_K8S_CONTAINER_MEMORY_LIMIT_UTILIZATION, aggregation: 'avg' }, + { field: SEMCONV_CONTAINER_CPU_USAGE, aggregation: 'avg' }, + { field: SEMCONV_CONTAINER_MEMORY_WORKING_SET, aggregation: 'avg' }, ]) ); expect(options.metrics).toHaveLength(2); @@ -84,14 +85,16 @@ describe('container_metrics_configs', () => { const kuery = 'container.id: "abc-123"'; const { options } = getOptionsForSchema(true, false, kuery); - expect(options.kuery).toBe(`event.dataset: "dockerstatsreceiver.otel" AND (${kuery})`); + expect(options.kuery).toBe(`${otelDatasetFilter('dockerstatsreceiver.otel')} AND (${kuery})`); }); it('combines kuery with SemConv K8s when isOtel is true, isK8sContainer true, and kuery is provided', () => { const kuery = 'container.id: "abc-123"'; const { options } = getOptionsForSchema(true, true, kuery); - expect(options.kuery).toBe(`event.dataset: "kubeletstatsreceiver.otel" AND (${kuery})`); + expect(options.kuery).toBe( + `${otelDatasetFilter('kubeletstatsreceiver.otel')} AND (${kuery})` + ); }); }); }); diff --git a/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/container/container_metrics_configs.ts b/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/container/container_metrics_configs.ts index 547a461d27fca..6286c852667a0 100644 --- a/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/container/container_metrics_configs.ts +++ b/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/container/container_metrics_configs.ts @@ -10,10 +10,11 @@ import { createMetricByFieldLookup, makeUnpackMetric, metricsToApiOptions } from import { ECS_CONTAINER_CPU_USAGE_LIMIT_PCT, ECS_CONTAINER_MEMORY_USAGE_BYTES, + otelDatasetFilter, SEMCONV_DOCKER_CONTAINER_MEMORY_PERCENT, SEMCONV_DOCKER_CONTAINER_CPU_UTILIZATION, - SEMCONV_K8S_CONTAINER_CPU_LIMIT_UTILIZATION, - SEMCONV_K8S_CONTAINER_MEMORY_LIMIT_UTILIZATION, + SEMCONV_CONTAINER_CPU_USAGE, + SEMCONV_CONTAINER_MEMORY_WORKING_SET, } from '../shared/constants'; // --- ECS (Elastic Common Schema) --- @@ -43,7 +44,7 @@ type ContainerMetricsFieldSemconvDocker = const containerMetricsQueryConfigSemconvDocker: MetricsQueryOptions = { - sourceFilter: 'event.dataset: "dockerstatsreceiver.otel"', + sourceFilter: otelDatasetFilter('dockerstatsreceiver.otel'), groupByField: 'container.id', metricsMap: { [SEMCONV_DOCKER_CONTAINER_CPU_UTILIZATION]: { @@ -58,22 +59,24 @@ const containerMetricsQueryConfigSemconvDocker: MetricsQueryOptions = { - sourceFilter: 'event.dataset: "kubeletstatsreceiver.otel"', + sourceFilter: otelDatasetFilter('kubeletstatsreceiver.otel'), groupByField: 'container.id', metricsMap: { - [SEMCONV_K8S_CONTAINER_CPU_LIMIT_UTILIZATION]: { + [SEMCONV_CONTAINER_CPU_USAGE]: { aggregation: 'avg', - field: SEMCONV_K8S_CONTAINER_CPU_LIMIT_UTILIZATION, + field: SEMCONV_CONTAINER_CPU_USAGE, }, - [SEMCONV_K8S_CONTAINER_MEMORY_LIMIT_UTILIZATION]: { + [SEMCONV_CONTAINER_MEMORY_WORKING_SET]: { aggregation: 'avg', - field: SEMCONV_K8S_CONTAINER_MEMORY_LIMIT_UTILIZATION, + field: SEMCONV_CONTAINER_MEMORY_WORKING_SET, }, }, }; @@ -88,7 +91,7 @@ const metricByFieldSemconvDocker = createMetricByFieldLookup( ); export const unpackMetricSemconvDocker = makeUnpackMetric(metricByFieldSemconvDocker); -const metricByFieldSemconvK8s = createMetricByFieldLookup( +export const metricByFieldSemconvK8s = createMetricByFieldLookup( containerMetricsQueryConfigSemconvK8s.metricsMap ); export const unpackMetricSemconvK8s = makeUnpackMetric(metricByFieldSemconvK8s); diff --git a/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/container/container_metrics_table.tsx b/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/container/container_metrics_table.tsx index 1c2ad91fb404e..2a9ad87c3b765 100644 --- a/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/container/container_metrics_table.tsx +++ b/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/container/container_metrics_table.tsx @@ -138,7 +138,7 @@ function containerNodeColumns({ ContainerMetricsTableProps, 'timerange' | 'isOtel' | 'metricsIndices' | 'isK8sContainer' >): Array> { - const memoryUnit = isOtel ? '%' : ' MB'; + const memoryUnit = isOtel && !isK8sContainer ? '%' : ' MB'; return [ { name: i18n.translate('xpack.metricsData.metricsTable.container.idColumnHeader', { diff --git a/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/container/use_container_metrics_table.test.ts b/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/container/use_container_metrics_table.test.ts index 82d6d7bc43e81..7f6b326780f39 100644 --- a/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/container/use_container_metrics_table.test.ts +++ b/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/container/use_container_metrics_table.test.ts @@ -8,10 +8,11 @@ import { ECS_CONTAINER_CPU_USAGE_LIMIT_PCT, ECS_CONTAINER_MEMORY_USAGE_BYTES, + otelDatasetFilter, SEMCONV_DOCKER_CONTAINER_CPU_UTILIZATION, SEMCONV_DOCKER_CONTAINER_MEMORY_PERCENT, - SEMCONV_K8S_CONTAINER_CPU_LIMIT_UTILIZATION, - SEMCONV_K8S_CONTAINER_MEMORY_LIMIT_UTILIZATION, + SEMCONV_CONTAINER_CPU_USAGE, + SEMCONV_CONTAINER_MEMORY_WORKING_SET, } from '../shared/constants'; import { useContainerMetricsTable } from './use_container_metrics_table'; import { useInfrastructureNodeMetrics } from '../shared'; @@ -78,7 +79,7 @@ describe('useContainerMetricsTable hook', () => { expect(useInfrastructureNodeMetricsMock).toHaveBeenCalledWith( expect.objectContaining({ metricsExplorerOptions: expect.objectContaining({ - kuery: `event.dataset: "dockerstatsreceiver.otel" AND (${kuery})`, + kuery: `${otelDatasetFilter('dockerstatsreceiver.otel')} AND (${kuery})`, metrics: expect.arrayContaining([ expect.objectContaining({ field: SEMCONV_DOCKER_CONTAINER_CPU_UTILIZATION }), expect.objectContaining({ field: SEMCONV_DOCKER_CONTAINER_MEMORY_PERCENT }), @@ -110,13 +111,13 @@ describe('useContainerMetricsTable hook', () => { expect(useInfrastructureNodeMetricsMock).toHaveBeenCalledWith( expect.objectContaining({ metricsExplorerOptions: expect.objectContaining({ - kuery: `event.dataset: "kubeletstatsreceiver.otel" AND (${kuery})`, + kuery: `${otelDatasetFilter('kubeletstatsreceiver.otel')} AND (${kuery})`, metrics: expect.arrayContaining([ expect.objectContaining({ - field: SEMCONV_K8S_CONTAINER_CPU_LIMIT_UTILIZATION, + field: SEMCONV_CONTAINER_CPU_USAGE, }), expect.objectContaining({ - field: SEMCONV_K8S_CONTAINER_MEMORY_LIMIT_UTILIZATION, + field: SEMCONV_CONTAINER_MEMORY_WORKING_SET, }), ]), }), diff --git a/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/container/use_container_metrics_table.ts b/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/container/use_container_metrics_table.ts index 48f9db5e4130b..d0eee38cdc88c 100644 --- a/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/container/use_container_metrics_table.ts +++ b/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/container/use_container_metrics_table.ts @@ -23,8 +23,8 @@ import { ECS_CONTAINER_MEMORY_USAGE_BYTES, SEMCONV_DOCKER_CONTAINER_CPU_UTILIZATION, SEMCONV_DOCKER_CONTAINER_MEMORY_PERCENT, - SEMCONV_K8S_CONTAINER_CPU_LIMIT_UTILIZATION, - SEMCONV_K8S_CONTAINER_MEMORY_LIMIT_UTILIZATION, + SEMCONV_CONTAINER_CPU_USAGE, + SEMCONV_CONTAINER_MEMORY_WORKING_SET, } from '../shared/constants'; export { metricByFieldEcs, metricByField } from './container_metrics_configs'; @@ -52,12 +52,11 @@ const semconvDockerUnpackMetrics = (row: MetricsExplorerRow) => { }; const semconvK8sUnpackMetrics = (row: MetricsExplorerRow) => { - // semconv k8s unpack metrics - const cpuUtilization = unpackMetricSemconvK8s(row, SEMCONV_K8S_CONTAINER_CPU_LIMIT_UTILIZATION); - const memoryUsage = unpackMetricSemconvK8s(row, SEMCONV_K8S_CONTAINER_MEMORY_LIMIT_UTILIZATION); + const cpuUsage = unpackMetricSemconvK8s(row, SEMCONV_CONTAINER_CPU_USAGE); + const memoryBytes = unpackMetricSemconvK8s(row, SEMCONV_CONTAINER_MEMORY_WORKING_SET); return { - averageCpuUsage: cpuUtilization !== null ? scaleUpPercentage(cpuUtilization) : null, - averageMemoryUsage: memoryUsage !== null ? scaleUpPercentage(memoryUsage) : null, + averageCpuUsage: cpuUsage !== null ? scaleUpPercentage(cpuUsage) : null, + averageMemoryUsage: memoryBytes !== null ? Math.floor(memoryBytes / 1_000_000) : null, }; }; diff --git a/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/host/use_host_metrics_table.test.ts b/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/host/use_host_metrics_table.test.ts index 4140cd8a4c669..a2dd9eaef490e 100644 --- a/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/host/use_host_metrics_table.test.ts +++ b/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/host/use_host_metrics_table.test.ts @@ -6,9 +6,10 @@ */ import { + otelDatasetFilter, SEMCONV_SYSTEM_CPU_LOGICAL_COUNT, SEMCONV_SYSTEM_CPU_UTILIZATION, - SEMCONV_SYSTEM_MEMORY_LIMIT, + SEMCONV_SYSTEM_MEMORY_TOTAL, SEMCONV_SYSTEM_MEMORY_UTILIZATION, SYSTEM_CPU_CORES, SYSTEM_CPU_TOTAL_NORM_PCT, @@ -81,11 +82,11 @@ describe('useHostMetricsTable hook', () => { expect(useInfrastructureNodeMetricsMock).toHaveBeenCalledWith( expect.objectContaining({ metricsExplorerOptions: expect.objectContaining({ - kuery: `event.dataset: "hostmetricsreceiver.otel" AND (${kuery})`, + kuery: `${otelDatasetFilter('hostmetricsreceiver.otel')} AND (${kuery})`, metrics: expect.arrayContaining([ expect.objectContaining({ field: SEMCONV_SYSTEM_CPU_LOGICAL_COUNT }), expect.objectContaining({ field: SEMCONV_SYSTEM_CPU_UTILIZATION }), - expect.objectContaining({ field: SEMCONV_SYSTEM_MEMORY_LIMIT }), + expect.objectContaining({ field: SEMCONV_SYSTEM_MEMORY_TOTAL }), expect.objectContaining({ field: SEMCONV_SYSTEM_MEMORY_UTILIZATION }), ]), }), @@ -93,6 +94,103 @@ describe('useHostMetricsTable hook', () => { ); }); + it('should transform OTel rows into populated host metrics', () => { + const kuery = `host.name: "gke-edge-oblt-pool-1-9a60016d-lgg9"`; + + useInfrastructureNodeMetricsMock.mockClear(); + useInfrastructureNodeMetricsMock.mockReturnValue({ + isLoading: true, + data: { state: 'empty-indices' }, + metricIndices: 'test-index', + }); + + renderHook(() => + useHostMetricsTable({ + timerange: { from: 'now-30d', to: 'now' }, + kuery, + metricsClient: createMetricsClientMock({}), + isOtel: true, + }) + ); + + const lastCallArgs = useInfrastructureNodeMetricsMock.mock.calls.at(-1); + const { transform, metricsExplorerOptions } = lastCallArgs?.[0] ?? {}; + expect(transform).toBeDefined(); + + const metrics = metricsExplorerOptions?.metrics ?? []; + const metricIndexByField = new Map(metrics.map((metric, index) => [metric.field, index])); + + const row = transform!({ + id: 'otel-host-1', + columns: [], + rows: [ + { + timestamp: Date.now(), + [`metric_${metricIndexByField.get(SEMCONV_SYSTEM_CPU_LOGICAL_COUNT)}`]: 8, + [`metric_${metricIndexByField.get(SEMCONV_SYSTEM_CPU_UTILIZATION)}`]: 0.42, + [`metric_${metricIndexByField.get(SEMCONV_SYSTEM_MEMORY_TOTAL)}`]: 16_000_000_000, + [`metric_${metricIndexByField.get(SEMCONV_SYSTEM_MEMORY_UTILIZATION)}`]: 0.25, + }, + ], + }); + + expect(row).toEqual({ + name: 'otel-host-1', + cpuCount: 8, + averageCpuUsagePercent: 42, + totalMemoryMegabytes: 16000, + averageMemoryUsagePercent: 25, + }); + }); + + it('should return null totalMemoryMegabytes when memory total metric is absent (B=0 in equation)', () => { + const kuery = `host.name: "gke-edge-oblt-pool-1-9a60016d-lgg9"`; + + useInfrastructureNodeMetricsMock.mockClear(); + useInfrastructureNodeMetricsMock.mockReturnValue({ + isLoading: true, + data: { state: 'empty-indices' }, + metricIndices: 'test-index', + }); + + renderHook(() => + useHostMetricsTable({ + timerange: { from: 'now-30d', to: 'now' }, + kuery, + metricsClient: createMetricsClientMock({}), + isOtel: true, + }) + ); + + const lastCallArgs = useInfrastructureNodeMetricsMock.mock.calls.at(-1); + const { transform, metricsExplorerOptions } = lastCallArgs?.[0] ?? {}; + expect(transform).toBeDefined(); + + const metrics = metricsExplorerOptions?.metrics ?? []; + const metricIndexByField = new Map(metrics.map((metric, index) => [metric.field, index])); + + const row = transform!({ + id: 'otel-host-2', + columns: [], + rows: [ + { + timestamp: Date.now(), + [`metric_${metricIndexByField.get(SEMCONV_SYSTEM_CPU_LOGICAL_COUNT)}`]: 4, + [`metric_${metricIndexByField.get(SEMCONV_SYSTEM_CPU_UTILIZATION)}`]: 0.1, + [`metric_${metricIndexByField.get(SEMCONV_SYSTEM_MEMORY_UTILIZATION)}`]: 0.5, + }, + ], + }); + + expect(row).toEqual({ + name: 'otel-host-2', + cpuCount: 4, + averageCpuUsagePercent: 10, + totalMemoryMegabytes: null, + averageMemoryUsagePercent: 50, + }); + }); + it('should call useInfrastructureNodeMetrics with ECS metrics when isOtel is false', () => { const kuery = `host.name: "gke-edge-oblt-pool-1-9a60016d-lgg9"`; diff --git a/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/host/use_host_metrics_table.ts b/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/host/use_host_metrics_table.ts index c5a426c7ac570..f42bea4a6fe3b 100644 --- a/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/host/use_host_metrics_table.ts +++ b/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/host/use_host_metrics_table.ts @@ -20,9 +20,11 @@ import { useInfrastructureNodeMetrics, } from '../shared'; import { + otelDatasetFilter, SEMCONV_SYSTEM_CPU_LOGICAL_COUNT, SEMCONV_SYSTEM_CPU_UTILIZATION, - SEMCONV_SYSTEM_MEMORY_LIMIT, + SEMCONV_SYSTEM_MEMORY_TOTAL, + SEMCONV_SYSTEM_MEMORY_USAGE, SEMCONV_SYSTEM_MEMORY_UTILIZATION, SYSTEM_CPU_CORES, SYSTEM_CPU_TOTAL_NORM_PCT, @@ -56,11 +58,11 @@ const hostsMetricsQueryConfig: MetricsQueryOptions = { type HostMetricsFieldsOtel = | typeof SEMCONV_SYSTEM_CPU_LOGICAL_COUNT | typeof SEMCONV_SYSTEM_CPU_UTILIZATION - | typeof SEMCONV_SYSTEM_MEMORY_LIMIT + | typeof SEMCONV_SYSTEM_MEMORY_TOTAL | typeof SEMCONV_SYSTEM_MEMORY_UTILIZATION; const hostsMetricsQueryConfigOtel: MetricsQueryOptions = { - sourceFilter: 'event.dataset: "hostmetricsreceiver.otel"', + sourceFilter: otelDatasetFilter('hostmetricsreceiver.otel'), groupByField: 'host.name', metricsMap: { [SEMCONV_SYSTEM_CPU_LOGICAL_COUNT]: { @@ -71,9 +73,14 @@ const hostsMetricsQueryConfigOtel: MetricsQueryOptions = aggregation: 'avg', field: SEMCONV_SYSTEM_CPU_UTILIZATION, }, - [SEMCONV_SYSTEM_MEMORY_LIMIT]: { - aggregation: 'max', - field: SEMCONV_SYSTEM_MEMORY_LIMIT, + [SEMCONV_SYSTEM_MEMORY_TOTAL]: { + aggregation: 'custom', + field: SEMCONV_SYSTEM_MEMORY_TOTAL, + custom_metrics: [ + { name: 'A', aggregation: 'avg', field: SEMCONV_SYSTEM_MEMORY_USAGE }, + { name: 'B', aggregation: 'avg', field: SEMCONV_SYSTEM_MEMORY_UTILIZATION }, + ], + equation: 'B > 0 ? A / B : null', }, [SEMCONV_SYSTEM_MEMORY_UTILIZATION]: { aggregation: 'avg', @@ -83,6 +90,8 @@ const hostsMetricsQueryConfigOtel: MetricsQueryOptions = }; export const metricByField = createMetricByFieldLookup(hostsMetricsQueryConfig.metricsMap); const unpackMetric = makeUnpackMetric(metricByField); +const metricByFieldOtel = createMetricByFieldLookup(hostsMetricsQueryConfigOtel.metricsMap); +const unpackMetricOtel = makeUnpackMetric(metricByFieldOtel); export interface HostNodeMetricsRow { name: string; @@ -113,10 +122,16 @@ export function useHostMetricsTable({ () => metricsToApiOptions(hostsMetricsQueryConfigOtel, kuery), [kuery] ); + + const transform = useMemo( + () => (series: MetricsExplorerSeries) => seriesToHostNodeMetricsRow(series, isOtel), + [isOtel] + ); + const { data, isLoading, metricIndices } = useInfrastructureNodeMetrics({ metricsExplorerOptions: isOtel ? hostMetricsOptionsOtel : hostMetricsOptions, timerange, - transform: seriesToHostNodeMetricsRow, + transform, sortState, currentPageIndex, metricsClient, @@ -133,14 +148,17 @@ export function useHostMetricsTable({ }; } -function seriesToHostNodeMetricsRow(series: MetricsExplorerSeries): HostNodeMetricsRow { +function seriesToHostNodeMetricsRow( + series: MetricsExplorerSeries, + isOtel?: boolean +): HostNodeMetricsRow { if (series.rows.length === 0) { return rowWithoutMetrics(series.id); } return { name: series.id, - ...calculateMetricAverages(series.rows), + ...calculateMetricAverages(series.rows, isOtel), }; } @@ -154,13 +172,13 @@ function rowWithoutMetrics(name: string) { }; } -function calculateMetricAverages(rows: MetricsExplorerRow[]) { +function calculateMetricAverages(rows: MetricsExplorerRow[], isOtel?: boolean) { const { cpuCountValues, averageCpuUsagePercentValues, totalMemoryMegabytesValues, averageMemoryUsagePercentValues, - } = collectMetricValues(rows); + } = collectMetricValues(rows, isOtel); let cpuCount = null; if (cpuCountValues.length !== 0) { @@ -192,7 +210,7 @@ function calculateMetricAverages(rows: MetricsExplorerRow[]) { }; } -function collectMetricValues(rows: MetricsExplorerRow[]) { +function collectMetricValues(rows: MetricsExplorerRow[], isOtel?: boolean) { const cpuCountValues: number[] = []; const averageCpuUsagePercentValues: number[] = []; const totalMemoryMegabytesValues: number[] = []; @@ -200,7 +218,7 @@ function collectMetricValues(rows: MetricsExplorerRow[]) { rows.forEach((row) => { const { cpuCount, averageCpuUsagePercent, totalMemoryMegabytes, averageMemoryUsagePercent } = - unpackMetrics(row); + unpackMetrics(row, isOtel); if (cpuCount !== null) { cpuCountValues.push(cpuCount); @@ -227,7 +245,19 @@ function collectMetricValues(rows: MetricsExplorerRow[]) { }; } -function unpackMetrics(row: MetricsExplorerRow): Omit { +function unpackMetrics( + row: MetricsExplorerRow, + isOtel?: boolean +): Omit { + if (isOtel) { + return { + cpuCount: unpackMetricOtel(row, SEMCONV_SYSTEM_CPU_LOGICAL_COUNT), + averageCpuUsagePercent: unpackMetricOtel(row, SEMCONV_SYSTEM_CPU_UTILIZATION), + totalMemoryMegabytes: unpackMetricOtel(row, SEMCONV_SYSTEM_MEMORY_TOTAL), + averageMemoryUsagePercent: unpackMetricOtel(row, SEMCONV_SYSTEM_MEMORY_UTILIZATION), + }; + } + return { cpuCount: unpackMetric(row, SYSTEM_CPU_CORES), averageCpuUsagePercent: unpackMetric(row, SYSTEM_CPU_TOTAL_NORM_PCT), diff --git a/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/pod/pod_metrics_table.stories.tsx b/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/pod/pod_metrics_table.stories.tsx index 4bf8c5980ce90..ba14d52531c92 100644 --- a/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/pod/pod_metrics_table.stories.tsx +++ b/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/pod/pod_metrics_table.stories.tsx @@ -69,30 +69,35 @@ const loadedPods: PodNodeMetricsRow[] = [ name: 'gke-edge-oblt-pool-1-9a60016d-lgg1', averageCpuUsagePercent: 99, averageMemoryUsagePercent: 34, + memoryUnit: '%', }, { id: '358d96e3-026f-4440-a487-f6c2301884c1', name: 'gke-edge-oblt-pool-1-9a60016d-lgg2', averageCpuUsagePercent: 72, averageMemoryUsagePercent: 68, + memoryUnit: '%', }, { id: '358d96e3-026f-4440-a487-f6c2301884c0', name: 'gke-edge-oblt-pool-1-9a60016d-lgg3', averageCpuUsagePercent: 54, averageMemoryUsagePercent: 132, + memoryUnit: '%', }, { id: '358d96e3-026f-4440-a487-f6c2301884c0', name: 'gke-edge-oblt-pool-1-9a60016d-lgg4', averageCpuUsagePercent: 34, averageMemoryUsagePercent: 264, + memoryUnit: '%', }, { id: '358d96e3-026f-4440-a487-f6c2301884c0', name: 'gke-edge-oblt-pool-1-9a60016d-lgg5', averageCpuUsagePercent: 13, averageMemoryUsagePercent: 512, + memoryUnit: '%', }, ]; diff --git a/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/pod/pod_metrics_table.tsx b/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/pod/pod_metrics_table.tsx index 5741b10665ffc..19ba32bdfae85 100644 --- a/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/pod/pod_metrics_table.tsx +++ b/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/pod/pod_metrics_table.tsx @@ -24,8 +24,8 @@ import { StepwisePagination, } from '../shared'; import { - SEMCONV_K8S_POD_CPU_LIMIT_UTILIZATION, SEMCONV_K8S_POD_MEMORY_LIMIT_UTILIZATION, + SEMCONV_K8S_POD_MEMORY_WORKING_SET, } from '../shared/constants'; import type { PodNodeMetricsRow } from './use_pod_metrics_table'; @@ -152,33 +152,11 @@ function podNodeColumns( }, }, { - name: ( - - - {i18n.translate( - 'xpack.metricsData.metricsTable.pod.averageCpuUsagePercentColumnHeader', - { - defaultMessage: 'CPU usage (avg.)', - } - )} - - {isOtel ? ( - - - - ) : null} - + name: i18n.translate( + 'xpack.metricsData.metricsTable.pod.averageCpuUsagePercentColumnHeader', + { + defaultMessage: 'CPU usage (avg.)', + } ), field: 'averageCpuUsagePercent', align: 'right', @@ -201,12 +179,13 @@ function podNodeColumns( ( - + render: (averageMemoryUsagePercent: number, row: PodNodeMetricsRow) => ( + ), }, ]; diff --git a/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/pod/use_pod_metrics_table.test.ts b/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/pod/use_pod_metrics_table.test.ts index 7ce4b12a4d9e8..0d8379325b72e 100644 --- a/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/pod/use_pod_metrics_table.test.ts +++ b/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/pod/use_pod_metrics_table.test.ts @@ -8,7 +8,10 @@ import { ECS_POD_CPU_USAGE_LIMIT_PCT, MEMORY_LIMIT_UTILIZATION, - SEMCONV_K8S_POD_CPU_LIMIT_UTILIZATION, + otelDatasetFilter, + SEMCONV_K8S_POD_CPU_NODE_UTILIZATION, + SEMCONV_K8S_POD_MEMORY_LIMIT_UTILIZATION, + SEMCONV_K8S_POD_MEMORY_WORKING_SET, } from '../shared/constants'; import { usePodMetricsTable } from './use_pod_metrics_table'; import { useInfrastructureNodeMetrics } from '../shared'; @@ -76,19 +79,118 @@ describe('usePodMetricsTable hook', () => { expect(useInfrastructureNodeMetricsMock).toHaveBeenCalledWith( expect.objectContaining({ metricsExplorerOptions: expect.objectContaining({ - kuery: `event.dataset: "kubeletstatsreceiver.otel" AND (${kuery})`, + kuery: `${otelDatasetFilter('kubeletstatsreceiver.otel')} AND (${kuery})`, metrics: expect.arrayContaining([ - expect.objectContaining({ field: SEMCONV_K8S_POD_CPU_LIMIT_UTILIZATION }), - expect.objectContaining({ field: MEMORY_LIMIT_UTILIZATION }), + expect.objectContaining({ field: SEMCONV_K8S_POD_CPU_NODE_UTILIZATION }), + expect.objectContaining({ field: SEMCONV_K8S_POD_MEMORY_LIMIT_UTILIZATION }), + expect.objectContaining({ field: SEMCONV_K8S_POD_MEMORY_WORKING_SET }), ]), }), }) ); }); + it('should use memory_limit_utilization as % when available', () => { + const kuery = 'k8s.pod.name: "test-pod"'; + + useInfrastructureNodeMetricsMock.mockClear(); + useInfrastructureNodeMetricsMock.mockReturnValue({ + isLoading: true, + data: { state: 'empty-indices' }, + metricIndices: 'test-index', + }); + + renderHook(() => + usePodMetricsTable({ + timerange: { from: 'now-30d', to: 'now' }, + kuery, + metricsClient: createMetricsClientMock({}), + isOtel: true, + }) + ); + + const lastCallArgs = useInfrastructureNodeMetricsMock.mock.calls.at(-1); + const { transform, metricsExplorerOptions } = lastCallArgs?.[0] ?? {}; + expect(transform).toBeDefined(); + + const metrics = metricsExplorerOptions?.metrics ?? []; + const metricIndexByField = new Map(metrics.map((metric, index) => [metric.field, index])); + + const row = transform!({ + id: 'test-pod-uid', + columns: [], + keys: ['test-pod-uid', 'test-pod-name'], + rows: [ + { + timestamp: Date.now(), + [`metric_${metricIndexByField.get(SEMCONV_K8S_POD_CPU_NODE_UTILIZATION)}`]: 0.05, + [`metric_${metricIndexByField.get(SEMCONV_K8S_POD_MEMORY_LIMIT_UTILIZATION)}`]: 0.3, + [`metric_${metricIndexByField.get(SEMCONV_K8S_POD_MEMORY_WORKING_SET)}`]: 512_000_000, + }, + ], + }); + + expect(row).toEqual({ + id: 'test-pod-uid', + name: 'test-pod-name', + averageCpuUsagePercent: 5, + averageMemoryUsagePercent: 30, + memoryUnit: '%', + }); + }); + + it('should fall back to memory.working_set as MB when limit_utilization is absent', () => { + const kuery = 'k8s.pod.name: "test-pod"'; + + useInfrastructureNodeMetricsMock.mockClear(); + useInfrastructureNodeMetricsMock.mockReturnValue({ + isLoading: true, + data: { state: 'empty-indices' }, + metricIndices: 'test-index', + }); + + renderHook(() => + usePodMetricsTable({ + timerange: { from: 'now-30d', to: 'now' }, + kuery, + metricsClient: createMetricsClientMock({}), + isOtel: true, + }) + ); + + const lastCallArgs = useInfrastructureNodeMetricsMock.mock.calls.at(-1); + const { transform, metricsExplorerOptions } = lastCallArgs?.[0] ?? {}; + expect(transform).toBeDefined(); + + const metrics = metricsExplorerOptions?.metrics ?? []; + const metricIndexByField = new Map(metrics.map((metric, index) => [metric.field, index])); + + const row = transform!({ + id: 'test-pod-uid', + columns: [], + keys: ['test-pod-uid', 'test-pod-name'], + rows: [ + { + timestamp: Date.now(), + [`metric_${metricIndexByField.get(SEMCONV_K8S_POD_CPU_NODE_UTILIZATION)}`]: 0.05, + [`metric_${metricIndexByField.get(SEMCONV_K8S_POD_MEMORY_WORKING_SET)}`]: 512_000_000, + }, + ], + }); + + expect(row).toEqual({ + id: 'test-pod-uid', + name: 'test-pod-name', + averageCpuUsagePercent: 5, + averageMemoryUsagePercent: 512, + memoryUnit: ' MB', + }); + }); + it('should call useInfrastructureNodeMetrics with ECS metrics when isOtel is false', () => { const kuery = 'container.id: "gke-edge-oblt-pool-1-9a60016d-lgg9"'; + useInfrastructureNodeMetricsMock.mockClear(); // include this to prevent rendering error in test useInfrastructureNodeMetricsMock.mockReturnValue({ isLoading: true, @@ -99,7 +201,7 @@ describe('usePodMetricsTable hook', () => { renderHook(() => usePodMetricsTable({ timerange: { from: 'now-30d', to: 'now' }, - kuery: `(${kuery})`, + kuery, metricsClient: createMetricsClientMock({}), isOtel: false, }) diff --git a/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/pod/use_pod_metrics_table.ts b/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/pod/use_pod_metrics_table.ts index db1b5f6493649..64fe5ef440f25 100644 --- a/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/pod/use_pod_metrics_table.ts +++ b/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/pod/use_pod_metrics_table.ts @@ -24,8 +24,10 @@ import { KUBERNETES_NODE_MEMORY_ALLOCATABLE_BYTES, KUBERNETES_NODE_MEMORY_USAGE_BYTES, MEMORY_LIMIT_UTILIZATION, - SEMCONV_K8S_POD_CPU_LIMIT_UTILIZATION, + otelDatasetFilter, + SEMCONV_K8S_POD_CPU_NODE_UTILIZATION, SEMCONV_K8S_POD_MEMORY_LIMIT_UTILIZATION, + SEMCONV_K8S_POD_MEMORY_WORKING_SET, } from '../shared/constants'; type PodMetricsField = typeof ECS_POD_CPU_USAGE_LIMIT_PCT | typeof MEMORY_LIMIT_UTILIZATION; @@ -59,30 +61,25 @@ const podMetricsQueryConfig: MetricsQueryOptions = { }; type PodMetricsFieldsOtel = - | typeof SEMCONV_K8S_POD_CPU_LIMIT_UTILIZATION - | typeof MEMORY_LIMIT_UTILIZATION; + | typeof SEMCONV_K8S_POD_CPU_NODE_UTILIZATION + | typeof SEMCONV_K8S_POD_MEMORY_LIMIT_UTILIZATION + | typeof SEMCONV_K8S_POD_MEMORY_WORKING_SET; const podMetricsQueryConfigOtel: MetricsQueryOptions = { - sourceFilter: 'event.dataset: "kubeletstatsreceiver.otel"', + sourceFilter: otelDatasetFilter('kubeletstatsreceiver.otel'), groupByField: ['k8s.pod.uid', 'k8s.pod.name'], metricsMap: { - // this is an optional field and wont populate unless specifically enabled in kubeletstatreceiver. - // There are not pod metrics that can derive this value. - [SEMCONV_K8S_POD_CPU_LIMIT_UTILIZATION]: { + [SEMCONV_K8S_POD_CPU_NODE_UTILIZATION]: { aggregation: 'avg', - field: SEMCONV_K8S_POD_CPU_LIMIT_UTILIZATION, // this is an opt-in field. + field: SEMCONV_K8S_POD_CPU_NODE_UTILIZATION, }, - [MEMORY_LIMIT_UTILIZATION]: { - field: MEMORY_LIMIT_UTILIZATION, - aggregation: 'custom', - custom_metrics: [ - { - name: 'A', - aggregation: 'avg', - field: SEMCONV_K8S_POD_MEMORY_LIMIT_UTILIZATION, - }, - ], - equation: 'A', + [SEMCONV_K8S_POD_MEMORY_LIMIT_UTILIZATION]: { + aggregation: 'avg', + field: SEMCONV_K8S_POD_MEMORY_LIMIT_UTILIZATION, + }, + [SEMCONV_K8S_POD_MEMORY_WORKING_SET]: { + aggregation: 'avg', + field: SEMCONV_K8S_POD_MEMORY_WORKING_SET, }, }, }; @@ -90,11 +87,15 @@ const podMetricsQueryConfigOtel: MetricsQueryOptions = { export const metricByField = createMetricByFieldLookup(podMetricsQueryConfig.metricsMap); const unpackMetric = makeUnpackMetric(metricByField); +const metricByFieldOtel = createMetricByFieldLookup(podMetricsQueryConfigOtel.metricsMap); +const unpackMetricOtel = makeUnpackMetric(metricByFieldOtel); + export interface PodNodeMetricsRow { id: string; name: string; averageCpuUsagePercent: number | null; averageMemoryUsagePercent: number | null; + memoryUnit: '%' | ' MB'; } export function usePodMetricsTable({ @@ -119,10 +120,15 @@ export function usePodMetricsTable({ [kuery] ); + const transform = useMemo( + () => (series: MetricsExplorerSeries) => seriesToPodNodeMetricsRow(series, isOtel ?? false), + [isOtel] + ); + const { data, isLoading, metricIndices } = useInfrastructureNodeMetrics({ metricsExplorerOptions: isOtel ? podMetricsOptionsOtel : podMetricsOptions, timerange, - transform: seriesToPodNodeMetricsRow, + transform, sortState, currentPageIndex, metricsClient, @@ -140,31 +146,51 @@ export function usePodMetricsTable({ }; } -function seriesToPodNodeMetricsRow(series: MetricsExplorerSeries): PodNodeMetricsRow { +function seriesToPodNodeMetricsRow( + series: MetricsExplorerSeries, + isOtel: boolean +): PodNodeMetricsRow { const [id, name] = series.keys ?? []; if (series.rows.length === 0) { - return rowWithoutMetrics(id, name); + return rowWithoutMetrics(id, name, isOtel); } return { id, name, - ...calculateMetricAverages(series.rows), + ...calculateMetricAverages(series.rows, isOtel), }; } -function rowWithoutMetrics(id: string, name: string) { +function rowWithoutMetrics(id: string, name: string, isOtel: boolean) { return { id, name, averageCpuUsagePercent: null, averageMemoryUsagePercent: null, + memoryUnit: (isOtel ? ' MB' : '%') as PodNodeMetricsRow['memoryUnit'], }; } -function calculateMetricAverages(rows: MetricsExplorerRow[]) { - const { averageCpuUsagePercentValues, averageMemoryUsagePercentValues } = - collectMetricValues(rows); +function calculateMetricAverages( + rows: MetricsExplorerRow[], + isOtel: boolean +): Omit { + const averageCpuUsagePercentValues: number[] = []; + const averageMemoryUsagePercentValues: number[] = []; + let memoryUnit: PodNodeMetricsRow['memoryUnit'] = '%'; + + rows.forEach((row) => { + const unpacked = isOtel ? unpackMetricsOtel(row) : unpackMetrics(row); + + if (unpacked.averageCpuUsagePercent !== null) { + averageCpuUsagePercentValues.push(unpacked.averageCpuUsagePercent); + } + if (unpacked.averageMemoryUsagePercent !== null) { + averageMemoryUsagePercentValues.push(unpacked.averageMemoryUsagePercent); + } + memoryUnit = unpacked.memoryUnit; + }); let averageCpuUsagePercent = null; if (averageCpuUsagePercentValues.length !== 0) { @@ -173,39 +199,37 @@ function calculateMetricAverages(rows: MetricsExplorerRow[]) { let averageMemoryUsagePercent = null; if (averageMemoryUsagePercentValues.length !== 0) { - averageMemoryUsagePercent = scaleUpPercentage(averageOfValues(averageMemoryUsagePercentValues)); + const avg = averageOfValues(averageMemoryUsagePercentValues); + averageMemoryUsagePercent = memoryUnit === '%' ? scaleUpPercentage(avg) : Math.floor(avg); } - return { - averageCpuUsagePercent, - averageMemoryUsagePercent, - }; -} - -function collectMetricValues(rows: MetricsExplorerRow[]) { - const averageCpuUsagePercentValues: number[] = []; - const averageMemoryUsagePercentValues: number[] = []; - - rows.forEach((row) => { - const { averageCpuUsagePercent, averageMemoryUsagePercent } = unpackMetrics(row); - - if (averageCpuUsagePercent !== null) { - averageCpuUsagePercentValues.push(averageCpuUsagePercent); - } - - if (averageMemoryUsagePercent !== null) { - averageMemoryUsagePercentValues.push(averageMemoryUsagePercent); - } - }); - return { - averageCpuUsagePercentValues, - averageMemoryUsagePercentValues, - }; + return { averageCpuUsagePercent, averageMemoryUsagePercent, memoryUnit }; } function unpackMetrics(row: MetricsExplorerRow): Omit { return { averageCpuUsagePercent: unpackMetric(row, ECS_POD_CPU_USAGE_LIMIT_PCT), averageMemoryUsagePercent: unpackMetric(row, MEMORY_LIMIT_UTILIZATION), + memoryUnit: '%', + }; +} + +function unpackMetricsOtel(row: MetricsExplorerRow): Omit { + const cpuUtilization = unpackMetricOtel(row, SEMCONV_K8S_POD_CPU_NODE_UTILIZATION); + const memLimitUtil = unpackMetricOtel(row, SEMCONV_K8S_POD_MEMORY_LIMIT_UTILIZATION); + + if (memLimitUtil != null) { + return { + averageCpuUsagePercent: cpuUtilization, + averageMemoryUsagePercent: memLimitUtil, + memoryUnit: '%', + }; + } + + const memoryBytes = unpackMetricOtel(row, SEMCONV_K8S_POD_MEMORY_WORKING_SET); + return { + averageCpuUsagePercent: cpuUtilization, + averageMemoryUsagePercent: memoryBytes != null ? memoryBytes / 1_000_000 : null, + memoryUnit: ' MB', }; } diff --git a/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/shared/constants.ts b/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/shared/constants.ts index 216311342bc79..fcf93ad85d489 100644 --- a/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/shared/constants.ts +++ b/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/shared/constants.ts @@ -11,12 +11,16 @@ export const ECS_CONTAINER_CPU_USAGE_LIMIT_PCT = 'kubernetes.container.cpu.usage.limit.pct'; export const ECS_CONTAINER_MEMORY_USAGE_BYTES = 'kubernetes.container.memory.usage.bytes'; -/** SemConv K8s (Kubernetes container) metric field names */ +/** SemConv K8s (Kubernetes container) metric field names — require resource limits to be set */ export const SEMCONV_K8S_CONTAINER_CPU_LIMIT_UTILIZATION = 'metrics.k8s.container.cpu_limit_utilization'; export const SEMCONV_K8S_CONTAINER_MEMORY_LIMIT_UTILIZATION = 'metrics.k8s.container.memory_limit_utilization'; +/** SemConv container metrics always emitted by kubeletstats (no limits required) */ +export const SEMCONV_CONTAINER_CPU_USAGE = 'metrics.container.cpu.usage'; +export const SEMCONV_CONTAINER_MEMORY_WORKING_SET = 'metrics.container.memory.working_set'; + /** SemConv container metric names used in UI tooltips (generic / display) */ export const SEMCONV_DOCKER_CONTAINER_CPU_UTILIZATION = 'metrics.container.cpu.utilization'; export const SEMCONV_DOCKER_CONTAINER_MEMORY_PERCENT = 'metrics.container.memory.percent'; @@ -32,7 +36,8 @@ export const SYSTEM_MEMORY_USED_PCT = 'system.memory.used.pct'; /** SemConv (OpenTelemetry) host metric field names */ export const SEMCONV_SYSTEM_CPU_LOGICAL_COUNT = 'metrics.system.cpu.logical.count'; export const SEMCONV_SYSTEM_CPU_UTILIZATION = 'metrics.system.cpu.utilization'; -export const SEMCONV_SYSTEM_MEMORY_LIMIT = 'metrics.system.memory.limit'; +export const SEMCONV_SYSTEM_MEMORY_TOTAL = 'metrics.system.memory.total'; +export const SEMCONV_SYSTEM_MEMORY_USAGE = 'metrics.system.memory.usage'; export const SEMCONV_SYSTEM_MEMORY_UTILIZATION = 'metrics.system.memory.utilization'; // --- Pod metric field names --- @@ -47,6 +52,19 @@ export const MEMORY_LIMIT_UTILIZATION = 'memory_limit_utilization'; export const KUBERNETES_NODE_MEMORY_ALLOCATABLE_BYTES = 'kubernetes.node.memory.allocatable.bytes'; export const KUBERNETES_NODE_MEMORY_USAGE_BYTES = 'kubernetes.node.memory.usage.bytes'; -/** SemConv K8s pod metric field names */ +/** SemConv K8s pod metric field names — require resource limits to be set */ export const SEMCONV_K8S_POD_CPU_LIMIT_UTILIZATION = 'metrics.k8s.pod.cpu_limit_utilization'; export const SEMCONV_K8S_POD_MEMORY_LIMIT_UTILIZATION = 'metrics.k8s.pod.memory_limit_utilization'; + +/** SemConv K8s pod metrics always emitted by kubeletstats (no limits required) */ +export const SEMCONV_K8S_POD_CPU_NODE_UTILIZATION = 'metrics.k8s.pod.cpu.node.utilization'; +export const SEMCONV_K8S_POD_MEMORY_WORKING_SET = 'metrics.k8s.pod.memory.working_set'; + +// --- OTel dataset filter helper --- + +/** + * Builds a KQL source filter that matches OTel data regardless of whether the + * dataset value is stored under `data_stream.dataset` or `event.dataset`. + */ +export const otelDatasetFilter = (dataset: string) => + `(data_stream.dataset: "${dataset}" OR event.dataset: "${dataset}")`; diff --git a/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/shared/hooks/helpers.ts b/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/shared/hooks/helpers.ts index 4c475300658b7..5df3e0ab97fcb 100644 --- a/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/shared/hooks/helpers.ts +++ b/x-pack/solutions/observability/plugins/metrics_data_access/public/components/infrastructure_node_metrics_tables/shared/hooks/helpers.ts @@ -15,7 +15,8 @@ export function averageOfValues(values: number[]) { export function makeUnpackMetric(metricByField: Record) { // Make sure metrics are accessed as row[metricByField['FIELD_NAME']] // Not row['FIELD_NAME'] by accident - return (row: MetricsExplorerRow, field: T) => row[metricByField[field]] as number | null; + return (row: MetricsExplorerRow, field: T) => + (row[metricByField[field]] as number | null) ?? null; } export function scaleUpPercentage(unscaled: number) {