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 bc96784269757..5048a410b0fe7 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, + otelDatasetFilterDsl, 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'; @@ -39,14 +40,14 @@ describe('container_metrics_configs', () => { it('returns SemConv Docker options when isOtel is true and isK8sContainer is false', () => { const { options } = getOptionsForSchema(true, false); - const filterClauseDsl = { + const expectedFilter = { bool: { - filter: [{ term: { 'event.dataset': 'dockerstatsreceiver.otel' } }], + filter: [otelDatasetFilterDsl('dockerstatsreceiver.otel')], }, }; expect(options.groupBy).toBe('container.id'); - expect(options.filterQuery).toBe(JSON.stringify(filterClauseDsl)); + expect(options.filterQuery).toBe(JSON.stringify(expectedFilter)); expect(options.metrics).toEqual( expect.arrayContaining([ { field: SEMCONV_DOCKER_CONTAINER_CPU_UTILIZATION, aggregation: 'avg' }, @@ -58,13 +59,13 @@ describe('container_metrics_configs', () => { it('returns SemConv Docker options when isOtel is true and isK8sContainer is undefined', () => { const { options } = getOptionsForSchema(true); - const filterClauseDsl = { + const expectedFilter = { bool: { - filter: [{ term: { 'event.dataset': 'dockerstatsreceiver.otel' } }], + filter: [otelDatasetFilterDsl('dockerstatsreceiver.otel')], }, }; expect(options.groupBy).toBe('container.id'); - expect(options.filterQuery).toBe(JSON.stringify(filterClauseDsl)); + expect(options.filterQuery).toBe(JSON.stringify(expectedFilter)); expect(options.metrics).toEqual( expect.arrayContaining([ { field: SEMCONV_DOCKER_CONTAINER_CPU_UTILIZATION, aggregation: 'avg' }, @@ -76,17 +77,17 @@ describe('container_metrics_configs', () => { it('returns SemConv K8s options when isOtel is true and isK8sContainer is true', () => { const { options } = getOptionsForSchema(true, true); - const filterClauseDsl = { + const expectedFilter = { bool: { - filter: [{ term: { 'event.dataset': 'kubeletstatsreceiver.otel' } }], + filter: [otelDatasetFilterDsl('kubeletstatsreceiver.otel')], }, }; expect(options.groupBy).toBe('container.id'); - expect(options.filterQuery).toBe(JSON.stringify(filterClauseDsl)); + expect(options.filterQuery).toBe(JSON.stringify(expectedFilter)); 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); @@ -119,10 +120,7 @@ describe('container_metrics_configs', () => { const filterClauseWithEventModuleFilter = { bool: { - filter: [ - { term: { 'event.dataset': 'dockerstatsreceiver.otel' } }, - { ...filterClauseDsl }, - ], + filter: [otelDatasetFilterDsl('dockerstatsreceiver.otel'), { ...filterClauseDsl }], }, }; @@ -139,10 +137,7 @@ describe('container_metrics_configs', () => { }; const filterClauseWithEventModuleFilter = { bool: { - filter: [ - { term: { 'event.dataset': 'kubeletstatsreceiver.otel' } }, - { ...filterClauseDsl }, - ], + filter: [otelDatasetFilterDsl('kubeletstatsreceiver.otel'), { ...filterClauseDsl }], }, }; 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 40aef31480759..1b7cfb7736651 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 @@ -11,10 +11,11 @@ import { createMetricByFieldLookup, makeUnpackMetric, metricsToApiOptions } from import { ECS_CONTAINER_CPU_USAGE_LIMIT_PCT, ECS_CONTAINER_MEMORY_USAGE_BYTES, + otelDatasetFilterDsl, 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) --- @@ -48,11 +49,7 @@ type ContainerMetricsFieldSemconvDocker = const containerMetricsQueryConfigSemconvDocker: MetricsQueryOptions = { - sourceFilter: { - term: { - 'event.dataset': 'dockerstatsreceiver.otel', - }, - }, + sourceFilter: otelDatasetFilterDsl('dockerstatsreceiver.otel'), groupByField: 'container.id', metricsMap: { [SEMCONV_DOCKER_CONTAINER_CPU_UTILIZATION]: { @@ -67,26 +64,24 @@ const containerMetricsQueryConfigSemconvDocker: MetricsQueryOptions = { - sourceFilter: { - term: { - 'event.dataset': 'kubeletstatsreceiver.otel', - }, - }, + sourceFilter: otelDatasetFilterDsl('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, }, }, }; @@ -101,7 +96,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 bcb6c5849a3d3..28634e6704864 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, + otelDatasetFilterDsl, 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'; @@ -74,7 +75,7 @@ describe('useContainerMetricsTable hook', () => { const filterClauseWithEventModuleFilter = { bool: { - filter: [{ term: { 'event.dataset': 'dockerstatsreceiver.otel' } }, { ...filterClauseDsl }], + filter: [otelDatasetFilterDsl('dockerstatsreceiver.otel'), { ...filterClauseDsl }], }, }; useInfrastructureNodeMetricsMock.mockReturnValue({ @@ -120,10 +121,7 @@ describe('useContainerMetricsTable hook', () => { const filterClauseWithEventModuleFilter = { bool: { - filter: [ - { term: { 'event.dataset': 'kubeletstatsreceiver.otel' } }, - { ...filterClauseDsl }, - ], + filter: [otelDatasetFilterDsl('kubeletstatsreceiver.otel'), { ...filterClauseDsl }], }, }; @@ -143,10 +141,10 @@ describe('useContainerMetricsTable hook', () => { filterQuery: JSON.stringify(filterClauseWithEventModuleFilter), 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 4aa18778a9fe9..a0976e690c2b1 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 7ee0c07b92252..b94e90f95f011 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 { + otelDatasetFilterDsl, 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, @@ -83,7 +84,7 @@ describe('useHostMetricsTable hook', () => { const filterClauseWithEventModuleFilter = { bool: { - filter: [{ term: { 'event.dataset': 'hostmetricsreceiver.otel' } }, { ...filterClauseDsl }], + filter: [otelDatasetFilterDsl('hostmetricsreceiver.otel'), { ...filterClauseDsl }], }, }; @@ -110,7 +111,7 @@ describe('useHostMetricsTable hook', () => { 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 }), ]), }), @@ -118,6 +119,97 @@ describe('useHostMetricsTable hook', () => { ); }); + it('should transform OTel rows into populated host metrics', () => { + useInfrastructureNodeMetricsMock.mockClear(); + useInfrastructureNodeMetricsMock.mockReturnValue({ + isLoading: true, + data: { state: 'empty-indices' }, + metricIndices: 'test-index', + }); + + renderHook(() => + useHostMetricsTable({ + timerange: { from: 'now-30d', to: 'now' }, + 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)', () => { + useInfrastructureNodeMetricsMock.mockClear(); + useInfrastructureNodeMetricsMock.mockReturnValue({ + isLoading: true, + data: { state: 'empty-indices' }, + metricIndices: 'test-index', + }); + + renderHook(() => + useHostMetricsTable({ + timerange: { from: 'now-30d', to: 'now' }, + 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 filterClauseDsl = { bool: { 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 0fcbf60598f53..6807bc4d6a922 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 { + otelDatasetFilterDsl, 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, @@ -60,15 +62,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: { - term: { - 'event.dataset': 'hostmetricsreceiver.otel', - }, - }, + sourceFilter: otelDatasetFilterDsl('hostmetricsreceiver.otel'), groupByField: 'host.name', metricsMap: { [SEMCONV_SYSTEM_CPU_LOGICAL_COUNT]: { @@ -79,9 +77,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', @@ -91,6 +94,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; @@ -121,10 +126,16 @@ export function useHostMetricsTable({ () => metricsToApiOptions(hostsMetricsQueryConfigOtel, filterClauseDsl), [filterClauseDsl] ); + + 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, @@ -141,14 +152,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), }; } @@ -162,13 +176,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) { @@ -200,7 +214,7 @@ function calculateMetricAverages(rows: MetricsExplorerRow[]) { }; } -function collectMetricValues(rows: MetricsExplorerRow[]) { +function collectMetricValues(rows: MetricsExplorerRow[], isOtel?: boolean) { const cpuCountValues: number[] = []; const averageCpuUsagePercentValues: number[] = []; const totalMemoryMegabytesValues: number[] = []; @@ -208,7 +222,7 @@ function collectMetricValues(rows: MetricsExplorerRow[]) { rows.forEach((row) => { const { cpuCount, averageCpuUsagePercent, totalMemoryMegabytes, averageMemoryUsagePercent } = - unpackMetrics(row); + unpackMetrics(row, isOtel); if (cpuCount !== null) { cpuCountValues.push(cpuCount); @@ -235,7 +249,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 f715d9267063f..06f622a7428c1 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, + otelDatasetFilterDsl, + 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'; @@ -71,10 +74,7 @@ describe('usePodMetricsTable hook', () => { const filterClauseWithEventModuleFilter = { bool: { - filter: [ - { term: { 'event.dataset': 'kubeletstatsreceiver.otel' } }, - { ...filterClauseDsl }, - ], + filter: [otelDatasetFilterDsl('kubeletstatsreceiver.otel'), { ...filterClauseDsl }], }, }; @@ -99,14 +99,106 @@ describe('usePodMetricsTable hook', () => { metricsExplorerOptions: expect.objectContaining({ filterQuery: JSON.stringify(filterClauseWithEventModuleFilter), 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', () => { + useInfrastructureNodeMetricsMock.mockClear(); + useInfrastructureNodeMetricsMock.mockReturnValue({ + isLoading: true, + data: { state: 'empty-indices' }, + metricIndices: 'test-index', + }); + + renderHook(() => + usePodMetricsTable({ + timerange: { from: 'now-30d', to: 'now' }, + 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', () => { + useInfrastructureNodeMetricsMock.mockClear(); + useInfrastructureNodeMetricsMock.mockReturnValue({ + isLoading: true, + data: { state: 'empty-indices' }, + metricIndices: 'test-index', + }); + + renderHook(() => + usePodMetricsTable({ + timerange: { from: 'now-30d', to: 'now' }, + 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 filterClauseDsl = { bool: { @@ -120,6 +212,7 @@ describe('usePodMetricsTable hook', () => { }, }; + useInfrastructureNodeMetricsMock.mockClear(); // include this to prevent rendering error in test useInfrastructureNodeMetricsMock.mockReturnValue({ isLoading: true, 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 c8408810d8fe6..cea66dcd315d7 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, + otelDatasetFilterDsl, + 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; @@ -63,34 +65,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: { - term: { - 'event.dataset': 'kubeletstatsreceiver.otel', - }, - }, + sourceFilter: otelDatasetFilterDsl('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, }, }, }; @@ -98,11 +91,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({ @@ -127,10 +124,15 @@ export function usePodMetricsTable({ [filterClauseDsl] ); + 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, @@ -148,31 +150,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) { @@ -181,39 +203,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..bf4590967acb1 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,32 @@ 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 helpers --- + +import type { QueryDslQueryContainer } from '@kbn/data-views-plugin/common/types'; + +/** + * 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}")`; + +/** + * DSL equivalent of otelDatasetFilter — returns a QueryDslQueryContainer + * that matches OTel data by both `data_stream.dataset` and `event.dataset`. + */ +export const otelDatasetFilterDsl = (dataset: string): QueryDslQueryContainer => ({ + bool: { + should: [{ term: { 'data_stream.dataset': dataset } }, { term: { 'event.dataset': dataset } }], + minimum_should_match: 1, + }, +}); 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) {