-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Infra Monitoring UI] Add event module filter to metrics table #133872
Changes from 9 commits
116bea2
8dee579
47298ca
e744e0f
9663f86
7ed4669
7d7868d
5c4b324
620a2cd
568ba85
46a6da7
4047ce0
6e30166
1bbdae0
da0ca31
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { useContainerMetricsTable } from './use_container_metrics_table'; | ||
import { useInfrastructureNodeMetrics } from '../shared'; | ||
import { renderHook } from '@testing-library/react-hooks'; | ||
|
||
jest.mock('../shared', () => ({ | ||
...jest.requireActual('../shared'), | ||
useInfrastructureNodeMetrics: jest.fn(), | ||
})); | ||
|
||
describe('useContainerMetricsTable hook', () => { | ||
const useInfrastructureNodeMetricsMock = useInfrastructureNodeMetrics as jest.MockedFunction< | ||
typeof useInfrastructureNodeMetrics | ||
>; | ||
|
||
it('should call useInfrastructureNodeMetrics hook with event.module filter in filterClauseDsl query', () => { | ||
const filterClauseDsl = { | ||
bool: { | ||
filter: [{ terms: { 'container.id': 'gke-edge-oblt-pool-1-9a60016d-lgg9' } }], | ||
}, | ||
}; | ||
|
||
const filterClauseWithEventModuleFilter = { | ||
bool: { | ||
filter: [{ term: { 'event.module': 'kubernetes' } }, { ...filterClauseDsl }], | ||
}, | ||
}; | ||
|
||
renderHook(() => | ||
useContainerMetricsTable({ | ||
timerange: { from: 'now-30d', to: 'now' }, | ||
filterClauseDsl, | ||
}) | ||
); | ||
|
||
expect(useInfrastructureNodeMetricsMock).toHaveBeenCalledWith( | ||
expect.objectContaining({ filterClauseDsl: filterClauseWithEventModuleFilter }) | ||
); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,39 +5,45 @@ | |
* 2.0. | ||
*/ | ||
|
||
import { useState } from 'react'; | ||
import { useEffect, useState } from 'react'; | ||
import type { | ||
MetricsExplorerRow, | ||
MetricsExplorerSeries, | ||
} from '../../../../common/http_api/metrics_explorer'; | ||
import type { MetricsMap, SortState, UseNodeMetricsTableOptions } from '../shared'; | ||
import type { MetricsQueryOptions, SortState, UseNodeMetricsTableOptions } from '../shared'; | ||
import { metricsToApiOptions, useInfrastructureNodeMetrics } from '../shared'; | ||
import { createMetricByFieldLookup } from '../shared/hooks/metrics_to_api_options'; | ||
import type { MetricsExplorerOptions } from '../../../pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options'; | ||
|
||
type ContainerMetricsField = | ||
| 'kubernetes.container.start_time' | ||
| 'kubernetes.container.cpu.usage.node.pct' | ||
| 'kubernetes.container.memory.usage.bytes'; | ||
|
||
const containerMetricsMap: MetricsMap<ContainerMetricsField> = { | ||
'kubernetes.container.start_time': { | ||
aggregation: 'max', | ||
field: 'kubernetes.container.start_time', | ||
const containerMetricsQueryConfig: MetricsQueryOptions<ContainerMetricsField> = { | ||
sourceFilter: { | ||
term: { | ||
'event.dataset': 'kubernetes.container', | ||
}, | ||
}, | ||
'kubernetes.container.cpu.usage.node.pct': { | ||
aggregation: 'avg', | ||
field: 'kubernetes.container.cpu.usage.node.pct', | ||
}, | ||
'kubernetes.container.memory.usage.bytes': { | ||
aggregation: 'avg', | ||
field: 'kubernetes.container.memory.usage.bytes', | ||
groupByField: 'kubernetes.pod.name', | ||
metricsMap: { | ||
'kubernetes.container.start_time': { | ||
aggregation: 'max', | ||
field: 'kubernetes.container.start_time', | ||
}, | ||
'kubernetes.container.cpu.usage.node.pct': { | ||
aggregation: 'avg', | ||
field: 'kubernetes.container.cpu.usage.node.pct', | ||
}, | ||
'kubernetes.container.memory.usage.bytes': { | ||
aggregation: 'avg', | ||
field: 'kubernetes.container.memory.usage.bytes', | ||
}, | ||
}, | ||
}; | ||
|
||
const { options: containerMetricsOptions, metricByField } = metricsToApiOptions( | ||
containerMetricsMap, | ||
'container.id' | ||
); | ||
export { metricByField }; | ||
export const metricByField = createMetricByFieldLookup(containerMetricsQueryConfig.metricsMap); | ||
|
||
export interface ContainerNodeMetricsRow { | ||
name: string; | ||
|
@@ -56,14 +62,22 @@ export function useContainerMetricsTable({ | |
direction: 'desc', | ||
}); | ||
|
||
const [containerMetricsOptions, setContainerMetricsOptions] = useState<MetricsExplorerOptions>(); | ||
|
||
useEffect(() => { | ||
if (!containerMetricsOptions) { | ||
const { options } = metricsToApiOptions(containerMetricsQueryConfig, filterClauseDsl); | ||
setContainerMetricsOptions(options); | ||
} | ||
}, [filterClauseDsl, containerMetricsOptions]); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this have to be mutable state? Or could this be computed state (e.g. with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmm, indeed. I think when I first decided to go with |
||
const { | ||
isLoading, | ||
nodes: containers, | ||
pageCount, | ||
} = useInfrastructureNodeMetrics<ContainerNodeMetricsRow>({ | ||
metricsExplorerOptions: containerMetricsOptions, | ||
timerange, | ||
filterClauseDsl, | ||
transform: seriesToContainerNodeMetricsRow, | ||
sortState, | ||
currentPageIndex, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { useHostMetricsTable } from './use_host_metrics_table'; | ||
import { useInfrastructureNodeMetrics } from '../shared'; | ||
import { renderHook } from '@testing-library/react-hooks'; | ||
|
||
jest.mock('../shared', () => ({ | ||
...jest.requireActual('../shared'), | ||
useInfrastructureNodeMetrics: jest.fn(), | ||
})); | ||
|
||
describe('useHostMetricsTable hook', () => { | ||
const useInfrastructureNodeMetricsMock = useInfrastructureNodeMetrics as jest.MockedFunction< | ||
typeof useInfrastructureNodeMetrics | ||
>; | ||
|
||
it('should call useInfrastructureNodeMetrics hook with event.module filter in filterClauseDsl query', () => { | ||
const filterClauseDsl = { | ||
bool: { | ||
should: [ | ||
{ | ||
terms: { | ||
'host.name': 'gke-edge-oblt-pool-1-9a60016d-lgg9', | ||
}, | ||
}, | ||
], | ||
minimum_should_match: 1, | ||
}, | ||
}; | ||
|
||
const filterClauseWithEventModuleFilter = { | ||
bool: { | ||
filter: [{ term: { 'event.module': 'system' } }, { ...filterClauseDsl }], | ||
}, | ||
}; | ||
|
||
renderHook(() => | ||
useHostMetricsTable({ | ||
timerange: { from: 'now-30d', to: 'now' }, | ||
filterClauseDsl, | ||
}) | ||
); | ||
|
||
expect(useInfrastructureNodeMetricsMock).toHaveBeenCalledWith( | ||
expect.objectContaining({ filterClauseDsl: filterClauseWithEventModuleFilter }) | ||
); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,38 +5,44 @@ | |
* 2.0. | ||
*/ | ||
|
||
import { useState } from 'react'; | ||
import { useEffect, useState } from 'react'; | ||
import type { | ||
MetricsExplorerRow, | ||
MetricsExplorerSeries, | ||
} from '../../../../common/http_api/metrics_explorer'; | ||
import type { MetricsMap, SortState, UseNodeMetricsTableOptions } from '../shared'; | ||
import type { MetricsQueryOptions, SortState, UseNodeMetricsTableOptions } from '../shared'; | ||
import { metricsToApiOptions, useInfrastructureNodeMetrics } from '../shared'; | ||
import { createMetricByFieldLookup } from '../shared/hooks/metrics_to_api_options'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. tiny nit: looks like this could be imported from |
||
import type { MetricsExplorerOptions } from '../../../pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options'; | ||
|
||
type HostMetricsField = | ||
| 'system.cpu.cores' | ||
| 'system.cpu.total.norm.pct' | ||
| 'system.memory.total' | ||
| 'system.memory.used.pct'; | ||
|
||
const hostMetricsMap: MetricsMap<HostMetricsField> = { | ||
'system.cpu.cores': { aggregation: 'max', field: 'system.cpu.cores' }, | ||
'system.cpu.total.norm.pct': { | ||
aggregation: 'avg', | ||
field: 'system.cpu.total.norm.pct', | ||
const hostsMetricsQueryConfig: MetricsQueryOptions<HostMetricsField> = { | ||
sourceFilter: { | ||
term: { | ||
'event.module': 'system', | ||
}, | ||
}, | ||
'system.memory.total': { aggregation: 'max', field: 'system.memory.total' }, | ||
'system.memory.used.pct': { | ||
aggregation: 'avg', | ||
field: 'system.memory.used.pct', | ||
groupByField: 'host.name', | ||
metricsMap: { | ||
'system.cpu.cores': { aggregation: 'max', field: 'system.cpu.cores' }, | ||
'system.cpu.total.norm.pct': { | ||
aggregation: 'avg', | ||
field: 'system.cpu.total.norm.pct', | ||
}, | ||
'system.memory.total': { aggregation: 'max', field: 'system.memory.total' }, | ||
'system.memory.used.pct': { | ||
aggregation: 'avg', | ||
field: 'system.memory.used.pct', | ||
}, | ||
}, | ||
}; | ||
|
||
const { options: hostMetricsOptions, metricByField } = metricsToApiOptions( | ||
hostMetricsMap, | ||
'host.name' | ||
); | ||
export { metricByField }; | ||
export const metricByField = createMetricByFieldLookup(hostsMetricsQueryConfig.metricsMap); | ||
|
||
export interface HostNodeMetricsRow { | ||
name: string; | ||
|
@@ -53,14 +59,22 @@ export function useHostMetricsTable({ timerange, filterClauseDsl }: UseNodeMetri | |
direction: 'desc', | ||
}); | ||
|
||
const [hostMetricsOptions, setHostsMetricsOptions] = useState<MetricsExplorerOptions>(); | ||
|
||
useEffect(() => { | ||
if (!hostMetricsOptions) { | ||
const { options } = metricsToApiOptions(hostsMetricsQueryConfig, filterClauseDsl); | ||
setHostsMetricsOptions(options); | ||
} | ||
}, [filterClauseDsl, hostMetricsOptions]); | ||
|
||
const { | ||
isLoading, | ||
nodes: hosts, | ||
pageCount, | ||
} = useInfrastructureNodeMetrics<HostNodeMetricsRow>({ | ||
metricsExplorerOptions: hostMetricsOptions, | ||
timerange, | ||
filterClauseDsl, | ||
transform: seriesToHostNodeMetricsRow, | ||
sortState, | ||
currentPageIndex, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { usePodMetricsTable } from './use_pod_metrics_table'; | ||
import { useInfrastructureNodeMetrics } from '../shared'; | ||
import { renderHook } from '@testing-library/react-hooks'; | ||
|
||
jest.mock('../shared', () => ({ | ||
...jest.requireActual('../shared'), | ||
useInfrastructureNodeMetrics: jest.fn(), | ||
})); | ||
|
||
describe('usePodMetricsTable hook', () => { | ||
const useInfrastructureNodeMetricsMock = useInfrastructureNodeMetrics as jest.MockedFunction< | ||
typeof useInfrastructureNodeMetrics | ||
>; | ||
|
||
it('should call useInfrastructureNodeMetrics hook with event.module filter in filterClauseDsl query', () => { | ||
const filterClauseDsl = { | ||
bool: { | ||
filter: [{ terms: { 'container.id': 'gke-edge-oblt-pool-1-9a60016d-lgg9' } }], | ||
}, | ||
}; | ||
|
||
const filterClauseWithEventModuleFilter = { | ||
bool: { | ||
filter: [{ term: { 'event.module': 'kubernetes' } }, { ...filterClauseDsl }], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This expected value might be outdated. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it sure is! |
||
}, | ||
}; | ||
|
||
renderHook(() => | ||
usePodMetricsTable({ | ||
timerange: { from: 'now-30d', to: 'now' }, | ||
filterClauseDsl, | ||
}) | ||
); | ||
|
||
expect(useInfrastructureNodeMetricsMock).toHaveBeenCalledWith( | ||
expect.objectContaining({ filterClauseDsl: filterClauseWithEventModuleFilter }) | ||
); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this incorrectly mix the metrics of multiple containers since a pod might have multiple containers? Would
kubernetes.container.id
be more suitable?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. It should indeed be
container.id
.