[ObsUx] [Infra] Change container details view with asset details view (#180436)

Part of https://github.com/elastic/kibana/issues/179844

### In this PR
- From Inventory, open asset details page view for Containers
- Show overview tab with CPU and Memory KPIs and metric charts
- Metadata tab with old fields, more metadata fields will be shown in
follow-up PR
- Added links to container metrics documentation, currently there are no
docs for K8s metrics just for docker containers

#### How to test
- The feature is under a FF, on inventory page go to settings and enable
`Container view`
- In containers inventory, select a container and click on 'Docker
container metrics' link (there's an
[issue](https://github.com/elastic/kibana/issues/180806) to reword this
links as K8s containers are also shown)
- Container details page should be shown with overview and metadata tabs
- On overview tab KPIs for CPU and Memory and Metrics section with CPU
and Memory charts should be displayed
<img width="937" alt="image"
src="d2f25f2a-ea4f-4516-b216-38464682fd14">
This commit is contained in:
Miriam 2024-05-16 15:56:20 +01:00 committed by GitHub
parent 2de84ce4dd
commit a4579a7e78
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
81 changed files with 1890 additions and 375 deletions

View file

@ -0,0 +1,39 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
/* eslint-disable max-classes-per-file */
import { Entity, Fields } from '../entity';
import { Serializable } from '../serializable';
interface DockerContainerDocument extends Fields {
'container.id': string;
'metricset.name'?: string;
}
export class DockerContainer extends Entity<DockerContainerDocument> {
metrics() {
return new DockerContainerMetrics({
...this.fields,
'docker.cpu.total.pct': 25,
'docker.memory.usage.pct': 20,
});
}
}
export interface DockerContainerMetricsDocument extends DockerContainerDocument {
'docker.cpu.total.pct': number;
'docker.memory.usage.pct': number;
}
class DockerContainerMetrics extends Serializable<DockerContainerMetricsDocument> {}
export function dockerContainer(id: string): DockerContainer {
return new DockerContainer({
'container.id': id,
});
}

View file

@ -6,14 +6,20 @@
* Side Public License, v 1.
*/
import { container, ContainerMetricsDocument } from './container';
import { dockerContainer, DockerContainerMetricsDocument } from './docker_container';
import { host, HostMetricsDocument } from './host';
import { k8sContainer, K8sContainerMetricsDocument } from './k8s_container';
import { pod, PodMetricsDocument } from './pod';
export type InfraDocument = HostMetricsDocument | PodMetricsDocument | ContainerMetricsDocument;
export type InfraDocument =
| HostMetricsDocument
| PodMetricsDocument
| DockerContainerMetricsDocument
| K8sContainerMetricsDocument;
export const infra = {
host,
pod,
container,
dockerContainer,
k8sContainer,
};

View file

@ -10,30 +10,32 @@
import { Entity, Fields } from '../entity';
import { Serializable } from '../serializable';
interface ContainerDocument extends Fields {
interface K8sContainerDocument extends Fields {
'container.id': string;
'kubernetes.pod.uid': string;
'kubernetes.node.name': string;
'metricset.name'?: string;
}
export class Container extends Entity<ContainerDocument> {
export class K8sContainer extends Entity<K8sContainerDocument> {
metrics() {
return new ContainerMetrics({
return new K8sContainerMetrics({
...this.fields,
'kubernetes.container.cpu.usage.limit.pct': 46,
'kubernetes.container.memory.usage.limit.pct': 30,
});
}
}
export interface ContainerMetricsDocument extends ContainerDocument {
export interface K8sContainerMetricsDocument extends K8sContainerDocument {
'kubernetes.container.cpu.usage.limit.pct': number;
'kubernetes.container.memory.usage.limit.pct': number;
}
class ContainerMetrics extends Serializable<ContainerMetricsDocument> {}
class K8sContainerMetrics extends Serializable<K8sContainerMetricsDocument> {}
export function container(id: string, uid: string, nodeName: string) {
return new Container({
export function k8sContainer(id: string, uid: string, nodeName: string): K8sContainer {
return new K8sContainer({
'container.id': id,
'kubernetes.pod.uid': uid,
'kubernetes.node.name': nodeName,

View file

@ -9,7 +9,7 @@
/* eslint-disable max-classes-per-file */
import { Entity, Fields } from '../entity';
import { Serializable } from '../serializable';
import { container } from './container';
import { k8sContainer } from './k8s_container';
interface PodDocument extends Fields {
'kubernetes.pod.uid': string;
@ -26,7 +26,7 @@ export class Pod extends Entity<PodDocument> {
}
container(id: string) {
return container(id, this.fields['kubernetes.pod.uid'], this.fields['kubernetes.node.name']);
return k8sContainer(id, this.fields['kubernetes.pod.uid'], this.fields['kubernetes.node.name']);
}
}

View file

@ -0,0 +1,41 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { InfraDocument, infra } from '@kbn/apm-synthtrace-client';
import { Scenario } from '../cli/scenario';
import { withClient } from '../lib/utils/with_client';
const scenario: Scenario<InfraDocument> = async (runOptions) => {
return {
generate: ({ range, clients: { infraEsClient } }) => {
const { numContainers = 5 } = runOptions.scenarioOpts || {};
const { logger } = runOptions;
const CONTAINERS = Array(numContainers)
.fill(0)
.map((_, idx) => infra.dockerContainer(`container-${idx}`));
const containers = range
.interval('30s')
.rate(1)
.generator((timestamp) =>
CONTAINERS.flatMap((container) => [container.metrics().timestamp(timestamp)])
);
return [
withClient(
infraEsClient,
logger.perf('generating_infra_docker_containers', () => containers)
),
];
},
};
};
export default scenario;

View file

@ -0,0 +1,41 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { InfraDocument, infra } from '@kbn/apm-synthtrace-client';
import { Scenario } from '../cli/scenario';
import { withClient } from '../lib/utils/with_client';
const scenario: Scenario<InfraDocument> = async (runOptions) => {
return {
generate: ({ range, clients: { infraEsClient } }) => {
const { numContainers = 5 } = runOptions.scenarioOpts || {};
const { logger } = runOptions;
const CONTAINERS = Array(numContainers)
.fill(0)
.map((_, idx) => infra.k8sContainer(`container-${idx}`, `pod-${idx}`, `node-${idx}`));
const containers = range
.interval('30s')
.rate(1)
.generator((timestamp) =>
CONTAINERS.flatMap((container) => [container.metrics().timestamp(timestamp)])
);
return [
withClient(
infraEsClient,
logger.perf('generating_infra_containers', () => containers)
),
];
},
};
};
export default scenario;

View file

@ -123,6 +123,8 @@ export const OBSERVABILITY_ENABLE_COMPARISON_BY_DEFAULT_ID =
'observability:enableComparisonByDefault';
export const OBSERVABILITY_ENABLE_INFRASTRUCTURE_HOSTS_VIEW_ID =
'observability:enableInfrastructureHostsView';
export const OBSERVABILITY_ENABLE_CONTAINER_ASSET_VIEW_ID =
'observability:enableContainerAssetView';
export const OBSERVABILITY_ENABLE_INFRASTRUCTURE_ASSET_CUSTOM_DASHBOARDS_ID =
'observability:enableInfrastructureAssetCustomDashboards';
export const OBSERVABILITY_ENABLE_INSPECT_ES_QUERIES_ID = 'observability:enableInspectEsQueries';

View file

@ -627,6 +627,10 @@ export const stackManagementSchema: MakeSchemaFrom<UsageStats> = {
type: 'boolean',
_meta: { description: 'Non-default value of setting.' },
},
'observability:enableInfrastructureContainerAssetView': {
type: 'boolean',
_meta: { description: 'Non-default value of setting.' },
},
'observability:enableInfrastructureProfilingIntegration': {
type: 'boolean',
_meta: { description: 'Non-default value of setting.' },

View file

@ -46,6 +46,7 @@ export interface UsageStats {
'observability:apmAWSLambdaPriceFactor': string;
'observability:apmAWSLambdaRequestCostPerMillion': number;
'observability:enableInfrastructureHostsView': boolean;
'observability:enableInfrastructureContainerAssetView': boolean;
'observability:enableInfrastructureProfilingIntegration': boolean;
'observability:enableInfrastructureAssetCustomDashboards': boolean;
'observability:apmAgentExplorerView': boolean;

View file

@ -10404,6 +10404,12 @@
"description": "Non-default value of setting."
}
},
"observability:enableInfrastructureContainerAssetView":{
"type": "boolean",
"_meta": {
"description": "Non-default value of setting."
}
},
"observability:enableInfrastructureProfilingIntegration": {
"type": "boolean",
"_meta": {

View file

@ -10,76 +10,107 @@ import { i18n } from '@kbn/i18n';
import React from 'react';
import { ContentTabIds, type Tab } from '../../components/asset_details/types';
export const commonFlyoutTabs: Tab[] = [
{
id: ContentTabIds.OVERVIEW,
name: i18n.translate('xpack.infra.assetDetails.tabs.overview', {
defaultMessage: 'Overview',
}),
},
{
id: ContentTabIds.METADATA,
name: i18n.translate('xpack.infra.assetDetails.tabs.metadata', {
defaultMessage: 'Metadata',
}),
},
{
id: ContentTabIds.METRICS,
name: i18n.translate('xpack.infra.assetDetails.tabs.metrics', {
defaultMessage: 'Metrics',
}),
},
{
id: ContentTabIds.PROCESSES,
name: i18n.translate('xpack.infra.assetDetails.tabs.processes', {
defaultMessage: 'Processes',
}),
},
{
id: ContentTabIds.PROFILING,
name: i18n.translate('xpack.infra.assetDetails.tabs.profiling', {
defaultMessage: 'Universal Profiling',
}),
},
{
id: ContentTabIds.LOGS,
name: i18n.translate('xpack.infra.assetDetails.tabs.logs', {
defaultMessage: 'Logs',
}),
},
{
id: ContentTabIds.ANOMALIES,
name: i18n.translate('xpack.infra.assetDetails.tabs.anomalies', {
defaultMessage: 'Anomalies',
}),
},
{
id: ContentTabIds.OSQUERY,
name: i18n.translate('xpack.infra.assetDetails.tabs.osquery', {
defaultMessage: 'Osquery',
}),
},
{
id: ContentTabIds.DASHBOARDS,
name: i18n.translate('xpack.infra.infra.nodeDetails.tabs.dashboards', {
defaultMessage: 'Dashboards',
}),
append: (
<EuiBetaBadge
label={i18n.translate('xpack.infra.customDashboards.technicalPreviewBadgeLabel', {
defaultMessage: 'Technical preview',
})}
tooltipContent={i18n.translate(
'xpack.infra.customDashboards.technicalPreviewBadgeDescription',
{
defaultMessage:
'This functionality is in technical preview and may be changed or removed completely in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.',
}
)}
iconType="beaker"
size="s"
style={{ verticalAlign: 'middle' }}
/>
),
},
const overviewTab: Tab = {
id: ContentTabIds.OVERVIEW,
name: i18n.translate('xpack.infra.nodeDetails.tabs.overview.title', {
defaultMessage: 'Overview',
}),
};
const metadataTab: Tab = {
id: ContentTabIds.METADATA,
name: i18n.translate('xpack.infra.nodeDetails.tabs.metadata.title', {
defaultMessage: 'Metadata',
}),
};
const metricsTab: Tab = {
id: ContentTabIds.METRICS,
name: i18n.translate('xpack.infra.nodeDetails.tabs.metrics.title', {
defaultMessage: 'Metrics',
}),
};
const processesTab: Tab = {
id: ContentTabIds.PROCESSES,
name: i18n.translate('xpack.infra.metrics.nodeDetails.tabs.processes', {
defaultMessage: 'Processes',
}),
};
const profilingTab: Tab = {
id: ContentTabIds.PROFILING,
name: i18n.translate('xpack.infra.metrics.nodeDetails.tabs.profiling', {
defaultMessage: 'Universal Profiling',
}),
};
const logsTab: Tab = {
id: ContentTabIds.LOGS,
name: i18n.translate('xpack.infra.nodeDetails.tabs.logs.title', {
defaultMessage: 'Logs',
}),
};
const anomaliesTab: Tab = {
id: ContentTabIds.ANOMALIES,
name: i18n.translate('xpack.infra.nodeDetails.tabs.anomalies', {
defaultMessage: 'Anomalies',
}),
};
const osqueryTab: Tab = {
id: ContentTabIds.OSQUERY,
name: i18n.translate('xpack.infra.nodeDetails.tabs.osquery', {
defaultMessage: 'Osquery',
}),
};
const dashboardsTab: Tab = {
id: ContentTabIds.DASHBOARDS,
name: i18n.translate('xpack.infra.infra.nodeDetails.tabs.dashboards', {
defaultMessage: 'Dashboards',
}),
append: (
<EuiBetaBadge
label={i18n.translate('xpack.infra.customDashboards.technicalPreviewBadgeLabel', {
defaultMessage: 'Technical preview',
})}
tooltipContent={i18n.translate(
'xpack.infra.customDashboards.technicalPreviewBadgeDescription',
{
defaultMessage:
'This functionality is in technical preview and may be changed or removed completely in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.',
}
)}
iconType="beaker"
size="s"
style={{ verticalAlign: 'middle' }}
/>
),
};
export const hostDetailsTabs: Tab[] = [
overviewTab,
metadataTab,
metricsTab,
processesTab,
profilingTab,
logsTab,
anomaliesTab,
osqueryTab,
dashboardsTab,
];
// Profiling and Logs tab would be added in next iteration
export const containerDetailsTabs: Tab[] = [overviewTab, metadataTab];
export const getAssetDetailsTabs = (type: string): Tab[] => {
switch (type) {
case 'host':
return hostDetailsTabs;
case 'container':
return containerDetailsTabs;
default:
return [];
}
};

View file

@ -7,6 +7,7 @@
export const HOST_METRICS_DOC_HREF = 'https://ela.st/docs-infra-host-metrics';
export const HOST_METRICS_DOTTED_LINES_DOC_HREF = 'https://ela.st/docs-infra-why-dotted';
export const CONTAINER_METRICS_DOC_HREF = 'https://ela.st/docs-infra-docker-container-metrics';
export const KPI_CHART_HEIGHT = 150;
export const METRIC_CHART_HEIGHT = 300;

View file

@ -10,7 +10,7 @@ import type { LensConfig, LensDataviewDataset } from '@kbn/lens-embeddable-utils
import type { TimeRange } from '@kbn/es-query';
import { useDataView } from '../../../hooks/use_data_view';
import { METRIC_CHART_HEIGHT } from '../../../common/visualizations/constants';
import { buildCombinedHostsFilter } from '../../../utils/filters/build';
import { buildCombinedAssetFilter } from '../../../utils/filters/build';
import { type BrushEndArgs, LensChart, type OnFilterEvent, LensChartProps } from '../../lens';
import { useDatePickerContext } from '../hooks/use_date_picker';
import { extractRangeFromChartFilterEvent } from './chart_utils';
@ -31,7 +31,7 @@ export const Chart = ({ id, queryField, overrides, dateRange, assetId, ...props
const filters = useMemo(() => {
return [
buildCombinedHostsFilter({
buildCombinedAssetFilter({
field: queryField,
values: [assetId],
dataView,

View file

@ -0,0 +1,87 @@
/*
* 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 React from 'react';
import { findInventoryFields } from '@kbn/metrics-data-access-plugin/common';
import { FormattedMessage } from '@kbn/i18n-react';
import { css, cx } from '@emotion/css';
import { EuiText, EuiLink } from '@elastic/eui';
import { useDockerContainerPageViewMetricsCharts } from '../hooks/use_container_metrics_charts';
import { Section } from '../components/section';
import { ChartsGrid } from '../charts_grid/charts_grid';
import { Chart } from './chart';
import { TitleWithTooltip } from '../components/section_title';
import { CONTAINER_METRIC_GROUP_TITLES } from '../translations';
import { CONTAINER_METRICS_DOC_HREF } from '../../../common/visualizations/constants';
import { MetricsChartsFields, ContainerMetricTypes } from './types';
interface Props extends MetricsChartsFields {
metric: ContainerMetricTypes;
}
const FRAGMENT_BASE = 'key-metrics';
export const DockerCharts = React.forwardRef<HTMLDivElement, Props>(
({ assetId, dataView, dateRange, metric }, ref) => {
const { charts } = useDockerContainerPageViewMetricsCharts({
metric,
metricsDataViewId: dataView?.id,
});
return (
<Section
title={
<TitleWithTooltip
title={CONTAINER_METRIC_GROUP_TITLES[metric]}
tooltipContent={
<EuiText size="xs">
<FormattedMessage
id="xpack.infra.assetDetails.charts.container.toolTip"
defaultMessage="See container-related {link} for more information"
values={{
link: (
<EuiLink
data-test-subj="infraAssetDetailsViewContainerMetricsDocumentationLink"
href={`${CONTAINER_METRICS_DOC_HREF}#${FRAGMENT_BASE}-${metric}`}
target="_blank"
className={cx({
[css`
text-transform: lowercase;
`]: metric !== 'cpu',
})}
>
<FormattedMessage
id="xpack.infra.assetDetails.charts.container.toolTip.linkText"
defaultMessage="{metric} metrics"
values={{ metric: CONTAINER_METRIC_GROUP_TITLES[metric] }}
/>
</EuiLink>
),
}}
/>
</EuiText>
}
/>
}
data-test-subj={`infraAssetDetailsDockerChartsSection${metric}`}
id="dockerContainerCharts"
ref={ref}
>
<ChartsGrid columns={2}>
{charts.map((chart) => (
<Chart
key={chart.id}
{...chart}
assetId={assetId}
dateRange={dateRange}
queryField={findInventoryFields('container').id}
/>
))}
</ChartsGrid>
</Section>
);
}
);

View file

@ -5,8 +5,6 @@
* 2.0.
*/
import React from 'react';
import type { DataView } from '@kbn/data-views-plugin/public';
import type { TimeRange } from '@kbn/es-query';
import { EuiText, EuiLink, EuiButtonEmpty } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { findInventoryFields } from '@kbn/metrics-data-access-plugin/common';
@ -16,16 +14,12 @@ import { HOST_METRIC_GROUP_TITLES } from '../translations';
import { Section } from '../components/section';
import { ChartsGrid } from '../charts_grid/charts_grid';
import { Chart } from './chart';
import { type HostMetricTypes, useHostCharts } from '../hooks/use_metrics_charts';
import { useHostCharts } from '../hooks/use_host_metrics_charts';
import { TitleWithTooltip } from '../components/section_title';
import { MetricsChartsFields, HostMetricTypes } from './types';
interface Props {
assetId: string;
dateRange: TimeRange;
dataView?: DataView;
overview?: boolean;
interface Props extends MetricsChartsFields {
metric: Exclude<HostMetricTypes, 'kpi'>;
onShowAll?: (metric: string) => void;
}
const FRAGMENT_BASE = 'key-metrics';

View file

@ -6,4 +6,5 @@
*/
export { HostCharts } from './host_charts';
export { KubernetesCharts } from './kubernetes_charts';
export { KubernetesNodeCharts, KubernetesContainerCharts } from './kubernetes_charts';
export { DockerCharts } from './docker_charts';

View file

@ -5,66 +5,124 @@
* 2.0.
*/
import React from 'react';
import { EuiButtonEmpty } from '@elastic/eui';
import { EuiButtonEmpty, EuiText, EuiLink } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import type { TimeRange } from '@kbn/es-query';
import type { DataView } from '@kbn/data-views-plugin/public';
import { css, cx } from '@emotion/css';
import { findInventoryFields } from '@kbn/metrics-data-access-plugin/common';
import { useKubernetesCharts } from '../hooks/use_metrics_charts';
import { useKubernetesCharts } from '../hooks/use_host_metrics_charts';
import { Section } from '../components/section';
import { SectionTitle } from '../components/section_title';
import { HOST_METRIC_GROUP_TITLES } from '../translations';
import { SectionTitle, TitleWithTooltip } from '../components/section_title';
import { CONTAINER_METRIC_GROUP_TITLES, HOST_METRIC_GROUP_TITLES } from '../translations';
import { INTEGRATIONS } from '../constants';
import { ChartsGrid } from '../charts_grid/charts_grid';
import { Chart } from './chart';
import { useIntegrationCheck } from '../hooks/use_integration_check';
import { useK8sContainerPageViewMetricsCharts } from '../hooks/use_container_metrics_charts';
import { CONTAINER_METRICS_DOC_HREF } from '../../../common/visualizations/constants';
import { ContainerMetricTypes, MetricsChartsFields } from './types';
interface Props {
assetId: string;
dateRange: TimeRange;
dataView?: DataView;
overview?: boolean;
onShowAll?: (metric: string) => void;
}
const FRAGMENT_BASE = 'key-metrics';
export const KubernetesCharts = React.forwardRef<
HTMLDivElement,
Props & { onShowAll?: (metric: string) => void }
>(({ assetId, dataView, dateRange, onShowAll, overview }, ref) => {
const { charts } = useKubernetesCharts({
dataViewId: dataView?.id,
options: { overview },
});
export const KubernetesNodeCharts = React.forwardRef<HTMLDivElement, MetricsChartsFields>(
({ assetId, dataView, dateRange, onShowAll, overview }, ref) => {
const { charts } = useKubernetesCharts({
dataViewId: dataView?.id,
options: { overview },
});
const hasIntegration = useIntegrationCheck({ dependsOn: INTEGRATIONS.kubernetes });
const hasIntegration = useIntegrationCheck({ dependsOn: INTEGRATIONS.kubernetesNode });
if (!hasIntegration) {
return null;
if (!hasIntegration) {
return null;
}
return (
<Section
title={<SectionTitle title={HOST_METRIC_GROUP_TITLES.kubernetes} />}
data-test-subj="infraAssetDetailsKubernetesChartsSection"
id="kubernetes"
ref={ref}
extraAction={
onShowAll ? (
<EuiButtonEmpty
data-test-subj="infraAssetDetailsKubernetesChartsShowAllButton"
onClick={() => onShowAll('kubernetes')}
size="xs"
flush="both"
iconSide="right"
iconType="sortRight"
>
<FormattedMessage
id="xpack.infra.assetDetails.charts.kubernetes.showAllButton"
defaultMessage="Show all"
/>
</EuiButtonEmpty>
) : null
}
>
<ChartsGrid columns={2}>
{charts.map((chart) => (
<Chart
key={chart.id}
{...chart}
assetId={assetId}
dateRange={dateRange}
queryField={findInventoryFields('host').id}
/>
))}
</ChartsGrid>
</Section>
);
}
);
export const KubernetesContainerCharts = React.forwardRef<
HTMLDivElement,
MetricsChartsFields & { metric: ContainerMetricTypes }
>(({ assetId, dataView, dateRange, metric }, ref) => {
const { charts } = useK8sContainerPageViewMetricsCharts({
metric,
metricsDataViewId: dataView?.id,
});
return (
<Section
title={<SectionTitle title={HOST_METRIC_GROUP_TITLES.kubernetes} />}
data-test-subj="infraAssetDetailsKubernetesChartsSection"
id="kubernetes"
ref={ref}
extraAction={
onShowAll ? (
<EuiButtonEmpty
data-test-subj="infraAssetDetailsKubernetesChartsShowAllButton"
onClick={() => onShowAll('kubernetes')}
size="xs"
flush="both"
iconSide="right"
iconType="sortRight"
>
<FormattedMessage
id="xpack.infra.assetDetails.charts.kubernetes.showAllButton"
defaultMessage="Show all"
/>
</EuiButtonEmpty>
) : null
title={
<TitleWithTooltip
title={CONTAINER_METRIC_GROUP_TITLES[metric]}
tooltipContent={
<EuiText size="xs">
<FormattedMessage
id="xpack.infra.assetDetails.charts.container.k8s.toolTip"
defaultMessage="See container-related {link} for more information"
values={{
link: (
// confirm the link, there's not documentation for k8s container metrics
<EuiLink
data-test-subj="infraAssetDetailsViewContainerK8sMetricsDocumentationLink"
href={`${CONTAINER_METRICS_DOC_HREF}#${FRAGMENT_BASE}-${metric}`}
target="_blank"
className={cx({
[css`
text-transform: lowercase;
`]: metric !== 'cpu',
})}
>
<FormattedMessage
id="xpack.infra.assetDetails.charts.container.k8s.toolTip.linkText"
defaultMessage="{metric} metrics"
values={{ metric: CONTAINER_METRIC_GROUP_TITLES[metric] }}
/>
</EuiLink>
),
}}
/>
</EuiText>
}
/>
}
data-test-subj="infraAssetDetailsK8ContainerChartsSection"
id="k8sContainerCharts"
ref={ref}
>
<ChartsGrid columns={2}>
{charts.map((chart) => (
@ -73,7 +131,7 @@ export const KubernetesCharts = React.forwardRef<
{...chart}
assetId={assetId}
dateRange={dateRange}
queryField={findInventoryFields('host').id}
queryField={findInventoryFields('container').id}
/>
))}
</ChartsGrid>

View file

@ -0,0 +1,22 @@
/*
* 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 type { DataView } from '@kbn/data-views-plugin/public';
import type { TimeRange } from '@kbn/es-query';
export type HostMetricTypes = 'cpu' | 'memory' | 'network' | 'disk' | 'log' | 'kpi';
export type KubernetesContainerMetrics = 'cpu' | 'memory';
export type DockerContainerMetrics = 'cpu' | 'memory' | 'network' | 'disk';
export type ContainerMetricTypes = KubernetesContainerMetrics | DockerContainerMetrics;
export interface MetricsChartsFields {
assetId: string;
dateRange: TimeRange;
dataView?: DataView;
overview?: boolean;
onShowAll?: (metric: string) => void;
}

View file

@ -0,0 +1,136 @@
/*
* 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 React from 'react';
import { EuiFlexItem, useEuiTheme } from '@elastic/eui';
import type { DataView } from '@kbn/data-views-plugin/public';
import type { Filter, Query, TimeRange } from '@kbn/es-query';
import { Kpi } from './kpi';
import {
useK8sContainerKpiCharts,
useDockerContainerKpiCharts,
} from '../../hooks/use_container_metrics_charts';
import { useIntegrationCheck } from '../../hooks/use_integration_check';
import { INTEGRATIONS } from '../../constants';
export interface ContainerKpiChartsProps {
dataView?: DataView;
dateRange: TimeRange;
query?: Query;
filters?: Filter[];
searchSessionId?: string;
options?: {
getSubtitle?: (formulaValue: string) => string;
};
loading?: boolean;
}
export const ContainerKpiCharts = ({
dateRange,
dataView,
filters,
options,
query,
searchSessionId,
loading = false,
}: ContainerKpiChartsProps) => {
const isK8Container = useIntegrationCheck({ dependsOn: INTEGRATIONS.kubernetesContainer });
return isK8Container ? (
<KubernetesKpiCharts
dateRange={dateRange}
dataView={dataView}
filters={filters}
options={options}
query={query}
searchSessionId={searchSessionId}
loading={loading}
/>
) : (
<DockerKpiCharts
dateRange={dateRange}
dataView={dataView}
filters={filters}
options={options}
query={query}
searchSessionId={searchSessionId}
loading={loading}
/>
);
};
const DockerKpiCharts = ({
dateRange,
dataView,
filters,
options,
query,
searchSessionId,
loading = false,
}: ContainerKpiChartsProps) => {
const { euiTheme } = useEuiTheme();
const charts = useDockerContainerKpiCharts({
dataViewId: dataView?.id,
options: {
getSubtitle: options?.getSubtitle,
seriesColor: euiTheme.colors.lightestShade,
},
});
return (
<>
{charts.map((chartProps, index) => (
<EuiFlexItem key={index}>
<Kpi
{...chartProps}
dateRange={dateRange}
filters={filters}
query={query}
searchSessionId={searchSessionId}
loading={loading}
/>
</EuiFlexItem>
))}
</>
);
};
const KubernetesKpiCharts = ({
dateRange,
dataView,
filters,
options,
query,
searchSessionId,
loading = false,
}: ContainerKpiChartsProps) => {
const { euiTheme } = useEuiTheme();
const charts = useK8sContainerKpiCharts({
dataViewId: dataView?.id,
options: {
getSubtitle: options?.getSubtitle,
seriesColor: euiTheme.colors.lightestShade,
},
});
return (
<>
{charts.map((chartProps, index) => (
<EuiFlexItem key={index}>
<Kpi
{...chartProps}
dateRange={dateRange}
filters={filters}
query={query}
searchSessionId={searchSessionId}
loading={loading}
/>
</EuiFlexItem>
))}
</>
);
};

View file

@ -10,7 +10,7 @@ import { EuiFlexItem, useEuiTheme } from '@elastic/eui';
import type { DataView } from '@kbn/data-views-plugin/public';
import type { Filter, Query, TimeRange } from '@kbn/es-query';
import { Kpi } from './kpi';
import { useHostKpiCharts } from '../../hooks/use_metrics_charts';
import { useHostKpiCharts } from '../../hooks/use_host_metrics_charts';
export interface HostKpiChartsProps {
dataView?: DataView;

View file

@ -15,5 +15,7 @@ export const APM_HOST_FILTER_FIELD = 'host.hostname';
export const ASSET_DETAILS_URL_STATE_KEY = 'assetDetails';
export const INTEGRATIONS = {
[INTEGRATION_NAME.kubernetes]: 'kubernetes.node',
[INTEGRATION_NAME.kubernetesNode]: 'kubernetes.node',
[INTEGRATION_NAME.kubernetesContainer]: 'kubernetes.container',
[INTEGRATION_NAME.docker]: 'docker',
};

View file

@ -0,0 +1,114 @@
/*
* 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 { renderHook } from '@testing-library/react-hooks';
import { ContainerMetricTypes } from '../charts/types';
import {
useK8sContainerPageViewMetricsCharts,
useDockerContainerPageViewMetricsCharts,
useDockerContainerKpiCharts,
useK8sContainerKpiCharts,
} from './use_container_metrics_charts';
const metricsDataViewId = 'metricsDataViewId';
const getContainerChartsExpectedOrder = (metric: ContainerMetricTypes): string[] => {
switch (metric) {
case 'cpu':
return ['cpuUsage'];
case 'memory':
return ['memoryUsage'];
default:
return [];
}
};
const getK8sContainerChartsExpectedOrder = (metric: ContainerMetricTypes): string[] => {
switch (metric) {
case 'cpu':
return ['k8sCpuUsage'];
case 'memory':
return ['k8sMemoryUsage'];
default:
return [];
}
};
describe('useDockerContainerCharts', () => {
describe.each<[ContainerMetricTypes]>([['cpu'], ['memory']])('%s', (item) => {
test.each<[ContainerMetricTypes]>([[item]])(
'should return an array of charts with correct order for metric "%s"',
async (metric) => {
const expectedOrder = getContainerChartsExpectedOrder(metric);
const { result, waitForNextUpdate } = renderHook(() =>
useDockerContainerPageViewMetricsCharts({ metricsDataViewId, metric })
);
await waitForNextUpdate();
const { charts } = result.current;
expect(charts).toHaveLength(expectedOrder.length);
charts.forEach((chart, index) => {
expect(chart).toHaveProperty('id', expectedOrder[index]);
});
}
);
});
});
describe('useDockerKPIMetricsCharts', () => {
it('should return an array of charts with correct order', async () => {
const expectedOrder = ['cpuUsage', 'memoryUsage'];
const { result, waitForNextUpdate } = renderHook(() =>
useDockerContainerKpiCharts({ dataViewId: metricsDataViewId })
);
await waitForNextUpdate();
expect(result.current).toHaveLength(expectedOrder.length);
result.current.forEach((chart, index) => {
expect(chart).toHaveProperty('id', expectedOrder[index]);
});
});
});
describe('useK8sContainerCharts', () => {
describe.each<[ContainerMetricTypes]>([['cpu'], ['memory']])('%s', (item) => {
test.each<[ContainerMetricTypes]>([[item]])(
'should return an array of charts with correct order for metric "%s"',
async (metric) => {
const expectedOrder = getK8sContainerChartsExpectedOrder(metric);
const { result, waitForNextUpdate } = renderHook(() =>
useK8sContainerPageViewMetricsCharts({ metricsDataViewId, metric })
);
await waitForNextUpdate();
const { charts } = result.current;
expect(charts).toHaveLength(expectedOrder.length);
charts.forEach((chart, index) => {
expect(chart).toHaveProperty('id', expectedOrder[index]);
});
}
);
});
});
describe('useK8sContainerKPIMetricsCharts', () => {
it('should return an array of charts with correct order', async () => {
const expectedOrder = ['k8sCpuUsage', 'k8sMemoryUsage'];
const { result, waitForNextUpdate } = renderHook(() =>
useK8sContainerKpiCharts({ dataViewId: metricsDataViewId })
);
await waitForNextUpdate();
expect(result.current).toHaveLength(expectedOrder.length);
result.current.forEach((chart, index) => {
expect(chart).toHaveProperty('id', expectedOrder[index]);
});
});
});

View file

@ -0,0 +1,163 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { findInventoryModel } from '@kbn/metrics-data-access-plugin/common';
import useAsync from 'react-use/lib/useAsync';
import { ContainerMetricTypes } from '../charts/types';
const getSubtitleFromFormula = (value: string) =>
value.startsWith('max')
? i18n.translate('xpack.infra.containerViewPage.kpi.subtitle.max', { defaultMessage: 'Max' })
: i18n.translate('xpack.infra.assetDetails.kpi.subtitle.average', {
defaultMessage: 'Average',
});
export const useDockerContainerPageViewMetricsCharts = ({
metric,
metricsDataViewId,
}: {
metric: ContainerMetricTypes;
metricsDataViewId?: string;
}) => {
const { value: charts = [], error } = useAsync(async () => {
const containerCharts = await getDockerContainerCharts(metric);
return containerCharts.map((chart) => {
return {
...chart,
...(metricsDataViewId && {
dataset: {
index: metricsDataViewId,
},
}),
};
});
}, [metricsDataViewId]);
return { charts, error };
};
const getDockerContainerCharts = async (metric: ContainerMetricTypes) => {
const model = findInventoryModel('container');
const { cpu, memory } = await model.metrics.getCharts();
switch (metric) {
case 'cpu':
return [cpu.xy.dockerContainerCpuUsage];
case 'memory':
return [memory.xy.dockerContainerMemoryUsage];
default:
return [];
}
};
export const useK8sContainerPageViewMetricsCharts = ({
metric,
metricsDataViewId,
}: {
metric: ContainerMetricTypes;
metricsDataViewId?: string;
}) => {
const { value: charts = [], error } = useAsync(async () => {
const containerK8sCharts = await getK8sContainerCharts(metric);
return containerK8sCharts.map((chart) => {
return {
...chart,
...(metricsDataViewId && {
dataset: {
index: metricsDataViewId,
},
}),
};
});
}, [metricsDataViewId]);
return { charts, error };
};
const getK8sContainerCharts = async (metric: ContainerMetricTypes) => {
const model = findInventoryModel('container');
const { cpu, memory } = await model.metrics.getCharts();
switch (metric) {
case 'cpu':
return [cpu.xy.k8sContainerCpuUsage];
case 'memory':
return [memory.xy.k8sContainerMemoryUsage];
default:
return [];
}
};
export const useDockerContainerKpiCharts = ({
dataViewId,
options,
}: {
dataViewId?: string;
options?: { seriesColor: string; getSubtitle?: (formulaValue: string) => string };
}) => {
const { value: charts = [] } = useAsync(async () => {
const model = findInventoryModel('container');
const { cpu, memory } = await model.metrics.getCharts();
return [cpu.metric.dockerContainerCpuUsage, memory.metric.dockerContainerMemoryUsage].map(
(chart) => ({
...chart,
seriesColor: options?.seriesColor,
decimals: 1,
subtitle: getSubtitle(options, chart),
...(dataViewId && {
dataset: {
index: dataViewId,
},
}),
})
);
}, [dataViewId, options?.seriesColor, options?.getSubtitle]);
return charts;
};
export const useK8sContainerKpiCharts = ({
dataViewId,
options,
}: {
dataViewId?: string;
options?: { seriesColor: string; getSubtitle?: (formulaValue: string) => string };
}) => {
const { value: charts = [] } = useAsync(async () => {
const model = findInventoryModel('container');
const { cpu, memory } = await model.metrics.getCharts();
return [cpu.metric.k8sContainerCpuUsage, memory.metric.k8sContainerMemoryUsage].map(
(chart) => ({
...chart,
seriesColor: options?.seriesColor,
decimals: 1,
subtitle: getSubtitle(options, chart),
...(dataViewId && {
dataset: {
index: dataViewId,
},
}),
})
);
}, [dataViewId, options?.seriesColor, options?.getSubtitle]);
return charts;
};
function getSubtitle(
options: { getSubtitle?: ((formulaValue: string) => string) | undefined } | undefined,
chart: { value: string }
) {
return options?.getSubtitle
? options?.getSubtitle(chart.value)
: getSubtitleFromFormula(chart.value);
}

View file

@ -6,12 +6,8 @@
*/
import { renderHook } from '@testing-library/react-hooks';
import {
useHostKpiCharts,
useHostCharts,
useKubernetesCharts,
type HostMetricTypes,
} from './use_metrics_charts';
import { HostMetricTypes } from '../charts/types';
import { useHostKpiCharts, useHostCharts, useKubernetesCharts } from './use_host_metrics_charts';
const dataViewId = 'metricsDataViewId';
const getHostChartsExpectedOrder = (metric: HostMetricTypes, overview: boolean): string[] => {

View file

@ -8,8 +8,8 @@
import { i18n } from '@kbn/i18n';
import { findInventoryModel } from '@kbn/metrics-data-access-plugin/common';
import useAsync from 'react-use/lib/useAsync';
import { HostMetricTypes } from '../charts/types';
export type HostMetricTypes = 'cpu' | 'memory' | 'network' | 'disk' | 'log' | 'kpi';
interface UseChartsOptions {
overview?: boolean;
}
@ -101,9 +101,7 @@ export const useHostKpiCharts = ({
...chart,
seriesColor: options?.seriesColor,
decimals: 1,
subtitle: options?.getSubtitle
? options?.getSubtitle(chart.value)
: getSubtitleFromFormula(chart.value),
subtitle: getSubtitle(options, chart),
...(dataViewId && {
dataset: {
index: dataViewId,
@ -151,3 +149,12 @@ const getHostsCharts = async ({
return [];
}
};
function getSubtitle(
options: { getSubtitle?: ((formulaValue: string) => string) | undefined } | undefined,
chart: { value: string }
) {
return options?.getSubtitle
? options?.getSubtitle(chart.value)
: getSubtitleFromFormula(chart.value);
}

View file

@ -6,13 +6,13 @@
*/
import React, { useRef } from 'react';
import { HostMetricTypes } from '../../hooks/use_metrics_charts';
import { useDatePickerContext } from '../../hooks/use_date_picker';
import { useAssetDetailsRenderPropsContext } from '../../hooks/use_asset_details_render_props';
import { useDataViewsContext } from '../../hooks/use_data_views';
import { useIntersectingState } from '../../hooks/use_intersecting_state';
import { MetricsTemplate } from './metrics_template';
import { HostCharts, KubernetesCharts } from '../../charts';
import { HostCharts, KubernetesNodeCharts } from '../../charts';
import { HostMetricTypes } from '../../charts/types';
const METRIC_TYPES: Array<Exclude<HostMetricTypes, 'kpi'>> = [
'cpu',
@ -41,7 +41,7 @@ export const HostMetrics = () => {
metric={metric}
/>
))}
<KubernetesCharts
<KubernetesNodeCharts
assetId={asset.id}
dataView={metrics.dataView}
dateRange={state.dateRange}

View file

@ -9,38 +9,52 @@ import React, { useMemo } from 'react';
import type { DataView } from '@kbn/data-views-plugin/public';
import type { TimeRange } from '@kbn/es-query';
import { EuiFlexGroup } from '@elastic/eui';
import { findInventoryFields } from '@kbn/metrics-data-access-plugin/common';
import { buildCombinedHostsFilter } from '../../../../../utils/filters/build';
import {
findInventoryFields,
type InventoryItemType,
} from '@kbn/metrics-data-access-plugin/common';
import { buildCombinedAssetFilter } from '../../../../../utils/filters/build';
import { HostKpiCharts } from '../../../components/kpis/host_kpi_charts';
import { useLoadingStateContext } from '../../../hooks/use_loading_state';
import { ContainerKpiCharts } from '../../../components/kpis/container_kpi_charts';
interface Props {
dataView?: DataView;
assetId: string;
assetType: InventoryItemType;
dateRange: TimeRange;
}
export const KPIGrid = ({ assetId, dataView, dateRange }: Props) => {
export const KPIGrid = ({ assetId, assetType, dataView, dateRange }: Props) => {
const { searchSessionId } = useLoadingStateContext();
const filters = useMemo(() => {
return [
buildCombinedHostsFilter({
field: findInventoryFields('host').id,
buildCombinedAssetFilter({
field: findInventoryFields(assetType).id,
values: [assetId],
dataView,
}),
];
}, [dataView, assetId]);
}, [dataView, assetId, assetType]);
return (
<EuiFlexGroup direction="row" gutterSize="s" data-test-subj="infraAssetDetailsKPIGrid">
<HostKpiCharts
dataView={dataView}
filters={filters}
dateRange={dateRange}
searchSessionId={searchSessionId}
/>
{assetType === 'host' ? (
<HostKpiCharts
dataView={dataView}
filters={filters}
dateRange={dateRange}
searchSessionId={searchSessionId}
/>
) : (
<ContainerKpiCharts
dataView={dataView}
filters={filters}
dateRange={dateRange}
searchSessionId={searchSessionId}
/>
)}
</EuiFlexGroup>
);
};

View file

@ -0,0 +1,42 @@
/*
* 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 React from 'react';
import { EuiFlexGroup, EuiFlexGrid } from '@elastic/eui';
import type { TimeRange } from '@kbn/es-query';
import type { DataView } from '@kbn/data-views-plugin/public';
import { DockerCharts } from '../../../charts/docker_charts';
import { INTEGRATIONS } from '../../../constants';
import { useIntegrationCheck } from '../../../hooks/use_integration_check';
import { KubernetesContainerCharts } from '../../../charts/kubernetes_charts';
interface Props {
assetId: string;
dateRange: TimeRange;
dataView?: DataView;
}
export const ContainerMetrics = (props: Props) => {
const isK8sContainer = useIntegrationCheck({ dependsOn: INTEGRATIONS.kubernetesContainer });
return (
<EuiFlexGroup gutterSize="m" direction="column">
<EuiFlexGrid columns={2} gutterSize="s">
{isK8sContainer ? (
<>
<KubernetesContainerCharts {...props} metric="cpu" />
<KubernetesContainerCharts {...props} metric="memory" />
</>
) : (
<>
<DockerCharts {...props} metric="cpu" />
<DockerCharts {...props} metric="memory" />
</>
)}
</EuiFlexGrid>
</EuiFlexGroup>
);
};

View file

@ -9,7 +9,7 @@ import { EuiFlexGroup, EuiFlexGrid } from '@elastic/eui';
import type { TimeRange } from '@kbn/es-query';
import type { DataView } from '@kbn/data-views-plugin/public';
import { useTabSwitcherContext } from '../../../hooks/use_tab_switcher';
import { HostCharts, KubernetesCharts } from '../../../charts';
import { HostCharts, KubernetesNodeCharts } from '../../../charts';
import { ContentTabIds } from '../../../types';
interface Props {
@ -33,7 +33,7 @@ export const HostMetrics = (props: Props) => {
<HostCharts {...props} metric="network" onShowAll={onClick} overview />
</EuiFlexGrid>
<HostCharts {...props} metric="disk" onShowAll={onClick} overview />
<KubernetesCharts {...props} onShowAll={onClick} overview />
<KubernetesNodeCharts {...props} onShowAll={onClick} overview />
</EuiFlexGroup>
);
};

View file

@ -9,6 +9,7 @@ import type { TimeRange } from '@kbn/es-query';
import type { DataView } from '@kbn/data-views-plugin/public';
import { InventoryItemType } from '@kbn/metrics-data-access-plugin/common';
import { HostMetrics } from './host_metrics';
import { ContainerMetrics } from './container_metrics';
import { Section } from '../../../components/section';
import { MetricsSectionTitle } from '../section_titles';
@ -24,6 +25,8 @@ export const MetricsContent = ({ assetType, ...props }: Props) => {
switch (assetType) {
case 'host':
return <HostMetrics {...props} />;
case 'container':
return <ContainerMetrics {...props} />;
default:
return null;
}
@ -31,7 +34,7 @@ export const MetricsContent = ({ assetType, ...props }: Props) => {
return (
<Section
title={<MetricsSectionTitle />}
title={<MetricsSectionTitle assetType={assetType} />}
data-test-subj="infraAssetDetailsMetricsCollapsible"
id="metrics"
collapsible

View file

@ -48,21 +48,29 @@ export const Overview = () => {
return (
<EuiFlexGroup direction="column" gutterSize="m" ref={ref}>
<EuiFlexItem grow={false}>
<KPIGrid assetId={asset.id} dateRange={state.dateRange} dataView={metrics.dataView} />
<CpuProfilingPrompt />
<KPIGrid
assetId={asset.id}
assetType={asset.type}
dateRange={state.dateRange}
dataView={metrics.dataView}
/>
{asset.type === 'host' ? <CpuProfilingPrompt /> : null}
</EuiFlexItem>
<EuiFlexItem grow={false}>
{fetchMetadataError && !metadataLoading ? <MetadataErrorCallout /> : metadataSummarySection}
<SectionSeparator />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<AlertsSummaryContent
assetId={asset.id}
assetType={asset.type}
dateRange={state.dateRange}
/>
<SectionSeparator />
</EuiFlexItem>
{asset.type === 'host' ? (
<EuiFlexItem grow={false}>
<AlertsSummaryContent
assetId={asset.id}
assetType={asset.type}
dateRange={state.dateRange}
/>
<SectionSeparator />
</EuiFlexItem>
) : null}
{asset.type === 'host' ? (
<EuiFlexItem grow={false}>
<ServicesContent hostName={asset.id} dateRange={state.dateRange} />

View file

@ -6,12 +6,14 @@
*/
import React from 'react';
import { i18n } from '@kbn/i18n';
import type { InventoryItemType } from '@kbn/metrics-data-access-plugin/common';
import { HostMetricsExplanationContent } from '../../../lens';
import { TitleWithTooltip } from '../../components/section_title';
import { AlertsTooltipContent } from '../../components/alerts_tooltip_content';
import { ServicesTooltipContent } from '../../components/services_tooltip_content';
import { ContainerMetricsExplanationContent } from '../../../lens/metric_explanation/container_metrics_explanation_content';
export const MetricsSectionTitle = () => {
export const MetricsSectionTitle = ({ assetType }: { assetType: InventoryItemType }) => {
return (
<TitleWithTooltip
title={i18n.translate('xpack.infra.assetDetails.overview.metricsSectionTitle', {
@ -19,7 +21,7 @@ export const MetricsSectionTitle = () => {
})}
data-test-subj="infraAssetDetailsMetricsTitle"
tooltipTestSubj="infraAssetDetailsMetricsPopoverButton"
tooltipContent={<HostMetricsExplanationContent />}
tooltipContent={getTooltipContent(assetType)}
/>
);
};
@ -47,3 +49,12 @@ export const ServicesSectionTitle = () => (
tooltipContent={<ServicesTooltipContent />}
/>
);
function getTooltipContent(assetType: InventoryItemType) {
switch (assetType) {
case 'host':
return <HostMetricsExplanationContent />;
default:
return <ContainerMetricsExplanationContent />;
}
}

View file

@ -31,3 +31,18 @@ export const HOST_METRIC_GROUP_TITLES = {
defaultMessage: 'Kubernetes',
}),
};
export const CONTAINER_METRIC_GROUP_TITLES = {
cpu: i18n.translate('xpack.infra.metricsGroup.containerCpu', {
defaultMessage: 'CPU',
}),
memory: i18n.translate('xpack.infra.metricsGroup.containerMemory', {
defaultMessage: 'Memory',
}),
network: i18n.translate('xpack.infra.metricsGroup.containerNetwork', {
defaultMessage: 'Network',
}),
disk: i18n.translate('xpack.infra.metricsGroup.containerDisk', {
defaultMessage: 'Disk',
}),
};

View file

@ -98,5 +98,7 @@ export interface RouteState {
export type DataViewOrigin = 'logs' | 'metrics';
export enum INTEGRATION_NAME {
kubernetes = 'kubernetes',
kubernetesNode = 'kubernetesNode',
kubernetesContainer = 'kubernetesContainer',
docker = 'docker',
}

View file

@ -0,0 +1,54 @@
/*
* 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 React from 'react';
import { EuiText, EuiLink } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import {
CONTAINER_METRICS_DOC_HREF,
HOST_METRICS_DOTTED_LINES_DOC_HREF,
} from '../../../common/visualizations/constants';
export const ContainerMetricsExplanationContent = () => {
return (
<EuiText size="xs">
<p>
<FormattedMessage
id="xpack.infra.containerViewPage.metricsExplanation"
defaultMessage="Showing metrics for your container(s)"
/>
</p>
<p>
<EuiText size="xs">
<EuiLink
data-test-subj="containerViewMetricsDocumentationLink"
href={CONTAINER_METRICS_DOC_HREF}
target="_blank"
>
{i18n.translate('xpack.infra.containerViewPage.tooltip.whatAreTheseMetricsLink', {
defaultMessage: 'What are these metrics?',
})}
</EuiLink>
</EuiText>
</p>
<p>
<EuiText size="xs">
<EuiLink
data-test-subj="containerViewMetricsDocumentationLink"
href={HOST_METRICS_DOTTED_LINES_DOC_HREF}
target="_blank"
>
{i18n.translate('xpack.infra.hostsViewPage.tooltip.whyAmISeeingDottedLines', {
defaultMessage: 'Why am I seeing dotted lines?',
})}
</EuiLink>
</EuiText>
</p>
</EuiText>
);
};

View file

@ -10,7 +10,7 @@ import { useSourceContext } from '../../../../../containers/metrics_source';
import { useUnifiedSearchContext } from '../../hooks/use_unified_search';
import type { HostNodeRow } from '../../hooks/use_hosts_table';
import { AssetDetails } from '../../../../../components/asset_details';
import { commonFlyoutTabs } from '../../../../../common/asset_details_config/asset_details_tabs';
import { hostDetailsTabs } from '../../../../../common/asset_details_config/asset_details_tabs';
export interface Props {
node: HostNodeRow;
@ -32,7 +32,7 @@ export const FlyoutWrapper = ({ node: { name }, closeFlyout }: Props) => {
showActionsColumn: true,
},
}}
tabs={commonFlyoutTabs}
tabs={hostDetailsTabs}
links={['nodeDetails']}
renderMode={{
mode: 'flyout',

View file

@ -7,7 +7,7 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import { HostKpiCharts } from '../../../../../components/asset_details';
import { buildCombinedHostsFilter } from '../../../../../utils/filters/build';
import { buildCombinedAssetFilter } from '../../../../../utils/filters/build';
import { useUnifiedSearchContext } from '../../hooks/use_unified_search';
import { useHostsViewContext } from '../../hooks/use_hosts_view';
import { useHostCountContext } from '../../hooks/use_host_count';
@ -26,7 +26,7 @@ export const KpiCharts = () => {
const filters = shouldUseSearchCriteria
? [...searchCriteria.filters, ...(searchCriteria.panelFilters ?? [])]
: [
buildCombinedHostsFilter({
buildCombinedAssetFilter({
field: 'host.name',
values: hostNodes.map((p) => p.name),
dataView,

View file

@ -16,7 +16,7 @@ import { useUnifiedSearchContext } from '../../../hooks/use_unified_search';
import { useLogsSearchUrlState } from '../../../hooks/use_logs_search_url_state';
import { LogsLinkToStream } from './logs_link_to_stream';
import { LogsSearchBar } from './logs_search_bar';
import { buildCombinedHostsFilter } from '../../../../../../utils/filters/build';
import { buildCombinedAssetFilter } from '../../../../../../utils/filters/build';
import { useLogViewReference } from '../../../../../../hooks/use_log_view_reference';
export const LogsTabContent = () => {
@ -27,7 +27,7 @@ export const LogsTabContent = () => {
const hostsFilterQuery = useMemo(
() =>
buildCombinedHostsFilter({
buildCombinedAssetFilter({
field: 'host.name',
values: hostNodes.map((p) => p.name),
}),

View file

@ -11,7 +11,7 @@ import { METRIC_CHART_HEIGHT } from '../../../../../../common/visualizations/con
import { LensChart } from '../../../../../../components/lens';
import { useUnifiedSearchContext } from '../../../hooks/use_unified_search';
import { useHostsViewContext } from '../../../hooks/use_hosts_view';
import { buildCombinedHostsFilter } from '../../../../../../utils/filters/build';
import { buildCombinedAssetFilter } from '../../../../../../utils/filters/build';
import { useHostsTableContext } from '../../../hooks/use_hosts_table';
import { useAfterLoadedState } from '../../../hooks/use_after_loaded_state';
@ -40,7 +40,7 @@ export const Chart = ({ id, ...chartProps }: ChartProps) => {
return shouldUseSearchCriteria
? [...searchCriteria.filters, ...(searchCriteria.panelFilters ?? [])]
: [
buildCombinedHostsFilter({
buildCombinedAssetFilter({
field: 'host.name',
values: currentPage.map((p) => p.name),
dataView,

View file

@ -29,7 +29,7 @@ import { useMetricsDataViewContext } from './use_metrics_data_view';
import { ColumnHeader } from '../components/table/column_header';
import { TABLE_COLUMN_LABEL, TABLE_CONTENT_LABEL } from '../translations';
import { METRICS_TOOLTIP } from '../../../../common/visualizations';
import { buildCombinedHostsFilter } from '../../../../utils/filters/build';
import { buildCombinedAssetFilter } from '../../../../utils/filters/build';
/**
* Columns and items types
@ -151,7 +151,7 @@ export const useHostsTable = () => {
return [];
}
const selectedHostNames = selectedItems.map(({ name }) => name);
const newFilter = buildCombinedHostsFilter({
const newFilter = buildCombinedAssetFilter({
field: 'host.name',
values: selectedHostNames,
dataView,

View file

@ -12,7 +12,7 @@ import type { InfraWaffleMapOptions } from '../../../../../lib/lib';
import { ContentTabIds } from '../../../../../components/asset_details/types';
import { AssetDetails } from '../../../../../components/asset_details';
import { useSourceContext } from '../../../../../containers/metrics_source';
import { commonFlyoutTabs } from '../../../../../common/asset_details_config/asset_details_tabs';
import { hostDetailsTabs } from '../../../../../common/asset_details_config/asset_details_tabs';
interface Props {
assetName: string;
@ -25,7 +25,7 @@ interface Props {
}
const flyoutTabs = [
...commonFlyoutTabs,
...hostDetailsTabs,
{
id: ContentTabIds.LINK_TO_APM,
name: i18n.translate('xpack.infra.assetDetails.tabs.linkToApm', {

View file

@ -57,7 +57,7 @@ export const WaffleInventorySwitcher: React.FC = () => {
);
const goToHost = useCallback(() => goToNodeType('host'), [goToNodeType]);
const goToK8 = useCallback(() => goToNodeType('pod'), [goToNodeType]);
const goToDocker = useCallback(() => goToNodeType('container'), [goToNodeType]);
const goToContainer = useCallback(() => goToNodeType('container'), [goToNodeType]);
const goToAwsEC2 = useCallback(() => goToNodeType('awsEC2'), [goToNodeType]);
const goToAwsS3 = useCallback(() => goToNodeType('awsS3'), [goToNodeType]);
const goToAwsRDS = useCallback(() => goToNodeType('awsRDS'), [goToNodeType]);
@ -79,9 +79,9 @@ export const WaffleInventorySwitcher: React.FC = () => {
onClick: goToK8,
},
{
'data-test-subj': 'goToDocker',
'data-test-subj': 'goToContainer',
name: getDisplayNameForType('container'),
onClick: goToDocker,
onClick: goToContainer,
},
{
name: 'AWS',
@ -117,7 +117,7 @@ export const WaffleInventorySwitcher: React.FC = () => {
],
},
] as EuiContextMenuPanelDescriptor[],
[goToAwsEC2, goToAwsRDS, goToAwsS3, goToAwsSQS, goToDocker, goToHost, goToK8]
[goToAwsEC2, goToAwsRDS, goToAwsS3, goToAwsSQS, goToContainer, goToHost, goToK8]
);
const selectedText = useMemo(() => {

View file

@ -14,7 +14,7 @@ import { SourceLoadingPage } from '../../../components/source_loading_page';
import { useSourceContext } from '../../../containers/metrics_source';
import { AssetDetails } from '../../../components/asset_details';
import { MetricsPageTemplate } from '../page_template';
import { commonFlyoutTabs } from '../../../common/asset_details_config/asset_details_tabs';
import { getAssetDetailsTabs } from '../../../common/asset_details_config/asset_details_tabs';
export const AssetDetailPage = () => {
const { isLoading, loadSourceFailureMessage, loadSource, source } = useSourceContext();
@ -43,7 +43,7 @@ export const AssetDetailPage = () => {
<AssetDetails
assetId={nodeId}
assetType={nodeType}
tabs={commonFlyoutTabs}
tabs={getAssetDetailsTabs(nodeType)}
renderMode={{
mode: 'page',
}}

View file

@ -8,7 +8,9 @@
import { EuiErrorBoundary } from '@elastic/eui';
import React from 'react';
import { useRouteMatch } from 'react-router-dom';
import { useUiSetting } from '@kbn/kibana-react-plugin/public';
import type { InventoryItemType } from '@kbn/metrics-data-access-plugin/common';
import { enableInfrastructureContainerAssetView } from '@kbn/observability-plugin/common';
import { AssetDetailPage } from './asset_detail_page';
import { MetricDetailPage } from './metric_detail_page';
import { MetricsTimeProvider } from './hooks/use_metrics_time';
@ -18,9 +20,12 @@ export const NodeDetail = () => {
params: { type: nodeType },
} = useRouteMatch<{ type: InventoryItemType; node: string }>();
const isContainerAssetViewEnabled = useUiSetting(enableInfrastructureContainerAssetView);
const showContainerAssetDetailPage = nodeType === 'container' && isContainerAssetViewEnabled;
return (
<EuiErrorBoundary>
{nodeType === 'host' ? (
{nodeType === 'host' || showContainerAssetDetailPage ? (
<AssetDetailPage />
) : (
<MetricsTimeProvider>

View file

@ -13,6 +13,7 @@ import React from 'react';
import {
enableInfrastructureHostsView,
enableInfrastructureProfilingIntegration,
enableInfrastructureContainerAssetView,
} from '@kbn/observability-plugin/common';
import { useEditableSettings } from '@kbn/observability-shared-plugin/public';
import { withSuspense } from '@kbn/shared-ux-utility';
@ -83,6 +84,12 @@ export function FeaturesConfigurationPanel({
unsavedChange={unsavedChanges[enableInfrastructureProfilingIntegration]}
/>
)}
<FieldRow
field={fields[enableInfrastructureContainerAssetView]}
isSavingEnabled={true}
onFieldChange={handleFieldChange}
unsavedChange={unsavedChanges[enableInfrastructureContainerAssetView]}
/>
</FieldRowProvider>
</EuiForm>
);

View file

@ -14,6 +14,7 @@ import {
useEditableSettings,
} from '@kbn/observability-shared-plugin/public';
import {
enableInfrastructureContainerAssetView,
enableInfrastructureHostsView,
enableInfrastructureProfilingIntegration,
} from '@kbn/observability-plugin/common';
@ -90,6 +91,7 @@ export const SourceConfigurationSettings = ({
const infraUiSettings = useEditableSettings([
enableInfrastructureHostsView,
enableInfrastructureProfilingIntegration,
enableInfrastructureContainerAssetView,
]);
const resetAllUnsavedChanges = useCallback(() => {

View file

@ -16,7 +16,7 @@ import type { DataView } from '@kbn/data-views-plugin/common';
import { findInventoryFields } from '@kbn/metrics-data-access-plugin/common';
import type { InfraCustomDashboardAssetType } from '../../../common/custom_dashboards';
export const buildCombinedHostsFilter = ({
export const buildCombinedAssetFilter = ({
field,
values,
dataView,

View file

@ -9,7 +9,7 @@ import { ALERT_TIME_RANGE } from '@kbn/rule-data-utils';
import { BoolQuery, buildEsQuery, Filter, type TimeRange } from '@kbn/es-query';
import type { AlertStatus } from '@kbn/observability-plugin/common/typings';
import { findInventoryFields } from '@kbn/metrics-data-access-plugin/common';
import { buildCombinedHostsFilter } from './build';
import { buildCombinedAssetFilter } from './build';
import { ALERT_STATUS_QUERY } from '../../components/shared/alerts/constants';
export interface AlertsEsQuery {
@ -28,7 +28,7 @@ export const createAlertsEsQuery = ({
const alertStatusFilter = createAlertStatusFilter(status);
const dateFilter = createDateFilter(dateRange);
const hostsFilter = buildCombinedHostsFilter({
const hostsFilter = buildCombinedAssetFilter({
field: findInventoryFields('host').id,
values: assetIds,
});

View file

@ -11,7 +11,7 @@ import { InventoryModel } from '../types';
export { containerSnapshotMetricTypes } from './metrics';
export const container: InventoryModel = {
export const container: InventoryModel<typeof metrics> = {
id: 'container',
displayName: i18n.translate('xpack.metricsData.inventoryModel.container.displayName', {
defaultMessage: 'Docker Containers',

View file

@ -0,0 +1,79 @@
/*
* 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 {
CPU_USAGE_LABEL,
DEFAULT_XY_FITTING_FUNCTION,
DEFAULT_XY_HIDDEN_AXIS_TITLE,
DEFAULT_XY_HIDDEN_LEGEND,
DEFAULT_XY_YBOUNDS,
} from '../../../shared/charts/constants';
import { LensConfigWithId } from '../../../types';
import { formulas } from '../formulas';
const dockerContainerCpuUsageXY: LensConfigWithId = {
id: 'cpuUsage',
chartType: 'xy',
title: CPU_USAGE_LABEL,
layers: [
{
seriesType: 'line',
type: 'series',
xAxis: '@timestamp',
yAxis: [formulas.dockerContainerCpuUsage],
},
],
...DEFAULT_XY_FITTING_FUNCTION,
...DEFAULT_XY_HIDDEN_LEGEND,
...DEFAULT_XY_HIDDEN_AXIS_TITLE,
...DEFAULT_XY_YBOUNDS,
};
const k8sContainerCpuUsageXY: LensConfigWithId = {
id: 'k8sCpuUsage',
chartType: 'xy',
title: CPU_USAGE_LABEL,
layers: [
{
seriesType: 'line',
type: 'series',
xAxis: '@timestamp',
yAxis: [formulas.k8sContainerCpuUsage],
},
],
...DEFAULT_XY_FITTING_FUNCTION,
...DEFAULT_XY_HIDDEN_LEGEND,
...DEFAULT_XY_HIDDEN_AXIS_TITLE,
...DEFAULT_XY_YBOUNDS,
};
const dockerContainerCpuUsageMetric: LensConfigWithId = {
id: 'cpuUsage',
chartType: 'metric',
title: CPU_USAGE_LABEL,
trendLine: true,
...formulas.dockerContainerCpuUsage,
};
const containerK8sCpuUsageMetric: LensConfigWithId = {
id: 'k8sCpuUsage',
chartType: 'metric',
title: CPU_USAGE_LABEL,
trendLine: true,
...formulas.k8sContainerCpuUsage,
};
export const cpu = {
xy: {
dockerContainerCpuUsage: dockerContainerCpuUsageXY,
k8sContainerCpuUsage: k8sContainerCpuUsageXY,
},
metric: {
dockerContainerCpuUsage: dockerContainerCpuUsageMetric,
k8sContainerCpuUsage: containerK8sCpuUsageMetric,
},
} as const;

View file

@ -0,0 +1,16 @@
/*
* 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 { cpu } from './cpu';
import { memory } from './memory';
export const charts = {
cpu,
memory,
} as const;
export type ContainerCharts = typeof charts;

View file

@ -0,0 +1,79 @@
/*
* 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 { LensConfigWithId } from '../../../types';
import {
DEFAULT_XY_FITTING_FUNCTION,
DEFAULT_XY_HIDDEN_AXIS_TITLE,
DEFAULT_XY_HIDDEN_LEGEND,
DEFAULT_XY_YBOUNDS,
MEMORY_USAGE_LABEL,
} from '../../../shared/charts/constants';
import { formulas } from '../formulas';
const dockerContainerMemoryUsageXY: LensConfigWithId = {
id: 'memoryUsage',
chartType: 'xy',
title: MEMORY_USAGE_LABEL,
layers: [
{
seriesType: 'line',
type: 'series',
xAxis: '@timestamp',
yAxis: [formulas.dockerContainerMemoryUsage],
},
],
...DEFAULT_XY_FITTING_FUNCTION,
...DEFAULT_XY_HIDDEN_LEGEND,
...DEFAULT_XY_YBOUNDS,
...DEFAULT_XY_HIDDEN_AXIS_TITLE,
};
const k8sContainerMemoryUsageXY: LensConfigWithId = {
id: 'k8sMemoryUsage',
chartType: 'xy',
title: MEMORY_USAGE_LABEL,
layers: [
{
seriesType: 'line',
type: 'series',
xAxis: '@timestamp',
yAxis: [formulas.k8sContainerMemoryUsage],
},
],
...DEFAULT_XY_FITTING_FUNCTION,
...DEFAULT_XY_HIDDEN_LEGEND,
...DEFAULT_XY_YBOUNDS,
...DEFAULT_XY_HIDDEN_AXIS_TITLE,
};
const dockerContainerMemoryUsageMetric: LensConfigWithId = {
id: 'memoryUsage',
chartType: 'metric',
title: MEMORY_USAGE_LABEL,
trendLine: true,
...formulas.dockerContainerMemoryUsage,
};
const k8sContainerMemoryUsageMetric: LensConfigWithId = {
id: 'k8sMemoryUsage',
chartType: 'metric',
title: MEMORY_USAGE_LABEL,
trendLine: true,
...formulas.k8sContainerMemoryUsage,
};
export const memory = {
xy: {
dockerContainerMemoryUsage: dockerContainerMemoryUsageXY,
k8sContainerMemoryUsage: k8sContainerMemoryUsageXY,
},
metric: {
dockerContainerMemoryUsage: dockerContainerMemoryUsageMetric,
k8sContainerMemoryUsage: k8sContainerMemoryUsageMetric,
},
};

View file

@ -0,0 +1,23 @@
/*
* 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 type { LensBaseLayer } from '@kbn/lens-embeddable-utils/config_builder';
import { CPU_USAGE_LABEL } from '../../../shared/charts/constants';
export const dockerContainerCpuUsage: LensBaseLayer = {
label: CPU_USAGE_LABEL,
value: 'average(docker.cpu.total.pct)',
format: 'percent',
decimals: 1,
};
export const k8sContainerCpuUsage: LensBaseLayer = {
label: CPU_USAGE_LABEL,
value: 'average(kubernetes.container.cpu.usage.limit.pct)',
format: 'percent',
decimals: 1,
};

View file

@ -0,0 +1,18 @@
/*
* 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 { dockerContainerCpuUsage, k8sContainerCpuUsage } from './cpu';
import { dockerContainerMemoryUsage, k8sContainerMemoryUsage } from './memory';
export const formulas = {
dockerContainerCpuUsage,
dockerContainerMemoryUsage,
k8sContainerCpuUsage,
k8sContainerMemoryUsage,
} as const;
export type ContainerFormulas = typeof formulas;

View file

@ -0,0 +1,22 @@
/*
* 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 type { LensBaseLayer } from '@kbn/lens-embeddable-utils/config_builder';
import { MEMORY_USAGE_LABEL } from '../../../shared/charts/constants';
export const dockerContainerMemoryUsage: LensBaseLayer = {
label: MEMORY_USAGE_LABEL,
value: 'average(docker.memory.usage.pct)',
format: 'percent',
decimals: 1,
};
export const k8sContainerMemoryUsage: LensBaseLayer = {
label: MEMORY_USAGE_LABEL,
value: 'average(kubernetes.container.memory.usage.limit.pct)',
format: 'percent',
decimals: 1,
};

View file

@ -5,22 +5,23 @@
* 2.0.
*/
import { InventoryMetrics } from '../../types';
import { InventoryMetricsWithCharts } from '../../types';
import { cpu } from './snapshot/cpu';
import { memory } from './snapshot/memory';
import { rx } from './snapshot/rx';
import { tx } from './snapshot/tx';
import { containerOverview } from './tsvb/container_overview';
import { containerCpuUsage } from './tsvb/container_cpu_usage';
import { containerCpuKernel } from './tsvb/container_cpu_kernel';
import { containerCpuUsage } from './tsvb/container_cpu_usage';
import { containerDiskIOOps } from './tsvb/container_diskio_ops';
import { containerDiskIOBytes } from './tsvb/container_disk_io_bytes';
import { containerMemory } from './tsvb/container_memory';
import { containerNetworkTraffic } from './tsvb/container_network_traffic';
import { containerK8sOverview } from './tsvb/container_k8s_overview';
import { containerK8sCpuUsage } from './tsvb/container_k8s_cpu_usage';
import { containerK8sMemoryUsage } from './tsvb/container_k8s_memory_usage';
import { containerK8sOverview } from './tsvb/container_k8s_overview';
import { containerMemory } from './tsvb/container_memory';
import { containerNetworkTraffic } from './tsvb/container_network_traffic';
import { containerOverview } from './tsvb/container_overview';
import type { ContainerFormulas } from './formulas';
import { ContainerCharts } from './charts';
const containerSnapshotMetrics = { cpu, memory, rx, tx };
@ -28,7 +29,7 @@ export const containerSnapshotMetricTypes = Object.keys(containerSnapshotMetrics
keyof typeof containerSnapshotMetrics
>;
export const metrics: InventoryMetrics = {
export const metrics: InventoryMetricsWithCharts<ContainerFormulas, ContainerCharts> = {
tsvb: {
containerOverview,
containerCpuUsage,
@ -42,6 +43,8 @@ export const metrics: InventoryMetrics = {
containerK8sMemoryUsage,
},
snapshot: containerSnapshotMetrics,
getFormulas: async () => await import('./formulas').then(({ formulas }) => formulas),
getCharts: async () => await import('./charts').then(({ charts }) => charts),
defaultSnapshot: 'cpu',
defaultTimeRangeInSeconds: 3600, // 1 hour
};

View file

@ -5,8 +5,9 @@
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import {
CPU_USAGE_LABEL,
LOAD_LABEL,
DEFAULT_XY_FITTING_FUNCTION,
DEFAULT_XY_HIDDEN_AXIS_TITLE,
DEFAULT_XY_HIDDEN_LEGEND,
@ -19,9 +20,7 @@ import { formulas } from '../formulas';
const cpuUsageBreakdown: LensConfigWithId = {
id: 'cpuUsageBreakdown',
chartType: 'xy',
title: i18n.translate('xpack.metricsData.assetDetails.metricsCharts.cpuUsage', {
defaultMessage: 'CPU Usage',
}),
title: CPU_USAGE_LABEL,
layers: [
{
seriesType: 'area',
@ -47,9 +46,7 @@ const cpuUsageBreakdown: LensConfigWithId = {
const loadBreakdown: LensConfigWithId = {
id: 'loadBreakdown',
chartType: 'xy',
title: i18n.translate('xpack.metricsData.assetDetails.metricsCharts.load', {
defaultMessage: 'Load',
}),
title: LOAD_LABEL,
layers: [
{
seriesType: 'area',

View file

@ -14,14 +14,15 @@ import {
DEFAULT_XY_HIDDEN_LEGEND,
DEFAULT_XY_LEGEND,
DEFAULT_XY_YBOUNDS,
DISK_IOPS_LABEL,
DISK_THROUGHPUT_LABEL,
DISK_USAGE_BY_MOUNT_POINT_LABEL,
} from '../../../shared/charts/constants';
const diskIOReadWrite: LensConfigWithId = {
id: 'diskIOReadWrite',
chartType: 'xy',
title: i18n.translate('xpack.metricsData.assetDetails.metricsCharts.diskIOPS', {
defaultMessage: 'Disk IOPS',
}),
title: DISK_IOPS_LABEL,
layers: [
{
seriesType: 'area',
@ -51,9 +52,7 @@ const diskIOReadWrite: LensConfigWithId = {
const diskUsageByMountPoint: LensConfigWithId = {
id: 'diskUsageByMountPoint',
chartType: 'xy',
title: i18n.translate('xpack.metricsData.assetDetails.metricsCharts.diskUsageByMountingPoint', {
defaultMessage: 'Disk Usage by Mount Point',
}),
title: DISK_USAGE_BY_MOUNT_POINT_LABEL,
layers: [
{
seriesType: 'area',
@ -86,9 +85,7 @@ const diskUsageByMountPoint: LensConfigWithId = {
const diskThroughputReadWrite: LensConfigWithId = {
id: 'diskThroughputReadWrite',
chartType: 'xy',
title: i18n.translate('xpack.metricsData.assetDetails.metricsCharts.diskThroughput', {
defaultMessage: 'Disk Throughput',
}),
title: DISK_THROUGHPUT_LABEL,
layers: [
{
seriesType: 'area',

View file

@ -14,14 +14,13 @@ import {
DEFAULT_XY_HIDDEN_LEGEND,
DEFAULT_XY_LEGEND,
DEFAULT_XY_YBOUNDS,
MEMORY_USAGE_LABEL,
} from '../../../shared/charts/constants';
const memoryUsageBreakdown: LensConfigWithId = {
id: 'memoryUsageBreakdown',
chartType: 'xy',
title: i18n.translate('xpack.metricsData.assetDetails.metricsCharts.memoryUsage', {
defaultMessage: 'Memory Usage',
}),
title: MEMORY_USAGE_LABEL,
layers: [
{
seriesType: 'area',

View file

@ -13,14 +13,13 @@ import {
DEFAULT_XY_HIDDEN_AXIS_TITLE,
DEFAULT_XY_HIDDEN_LEGEND,
DEFAULT_XY_LEGEND,
NETWORK_LABEL,
} from '../../../shared/charts/constants';
const rxTx: LensConfigWithId = {
id: 'rxTx',
chartType: 'xy',
title: i18n.translate('xpack.metricsData.assetDetails.metricsCharts.network', {
defaultMessage: 'Network',
}),
title: NETWORK_LABEL,
layers: [
{
seriesType: 'area',

View file

@ -7,6 +7,13 @@
import { i18n } from '@kbn/i18n';
import type { LensBaseLayer } from '@kbn/lens-embeddable-utils/config_builder';
import {
CPU_USAGE_LABEL,
LOAD_15M_LABEL,
LOAD_1M_LABEL,
LOAD_5M_LABEL,
NORMALIZED_LOAD_LABEL,
} from '../../../shared/charts/constants';
export const cpuUsageIowait: LensBaseLayer = {
label: i18n.translate('xpack.metricsData.assetDetails.formulas.cpuUsage.iowaitLabel', {
@ -72,45 +79,35 @@ export const cpuUsageUser: LensBaseLayer = {
};
export const cpuUsage: LensBaseLayer = {
label: i18n.translate('xpack.metricsData.assetDetails.formulas.cpuUsage', {
defaultMessage: 'CPU Usage',
}),
label: CPU_USAGE_LABEL,
value: '(average(system.cpu.user.pct) + average(system.cpu.system.pct)) / max(system.cpu.cores)',
format: 'percent',
decimals: 0,
};
export const load1m: LensBaseLayer = {
label: i18n.translate('xpack.metricsData.assetDetails.formulas.load1m', {
defaultMessage: 'Load (1m)',
}),
label: LOAD_1M_LABEL,
value: 'average(system.load.1)',
format: 'number',
decimals: 1,
};
export const load5m: LensBaseLayer = {
label: i18n.translate('xpack.metricsData.assetDetails.formulas.load5m', {
defaultMessage: 'Load (5m)',
}),
label: LOAD_5M_LABEL,
value: 'average(system.load.5)',
format: 'number',
decimals: 1,
};
export const load15m: LensBaseLayer = {
label: i18n.translate('xpack.metricsData.assetDetails.formulas.load15m', {
defaultMessage: 'Load (15m)',
}),
label: LOAD_15M_LABEL,
value: 'average(system.load.15)',
format: 'number',
decimals: 1,
};
export const normalizedLoad1m: LensBaseLayer = {
label: i18n.translate('xpack.metricsData.assetDetails.formulas.normalizedLoad1m', {
defaultMessage: 'Normalized Load',
}),
label: NORMALIZED_LOAD_LABEL,
value: 'average(system.load.1) / max(system.load.cores)',
format: 'percent',
decimals: 0,

View file

@ -5,13 +5,20 @@
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import type { LensBaseLayer } from '@kbn/lens-embeddable-utils/config_builder';
import {
DISK_READ_IOPS_LABEL,
DISK_READ_THROUGHPUT_LABEL,
DISK_SPACE_AVAILABILITY_LABEL,
DISK_SPACE_AVAILABLE_LABEL,
DISK_USAGE_AVERAGE_LABEL,
DISK_USAGE_LABEL,
DISK_WRITE_IOPS_LABEL,
DISK_WRITE_THROUGHPUT_LABEL,
} from '../../../shared/charts/constants';
export const diskIORead: LensBaseLayer = {
label: i18n.translate('xpack.metricsData.assetDetails.formulas.diskIORead', {
defaultMessage: 'Disk Read IOPS',
}),
label: DISK_READ_IOPS_LABEL,
value: "counter_rate(max(system.diskio.read.count), kql='system.diskio.read.count: *')",
format: 'number',
decimals: 0,
@ -19,9 +26,7 @@ export const diskIORead: LensBaseLayer = {
};
export const diskReadThroughput: LensBaseLayer = {
label: i18n.translate('xpack.metricsData.assetDetails.formulas.diskReadThroughput', {
defaultMessage: 'Disk Read Throughput',
}),
label: DISK_READ_THROUGHPUT_LABEL,
value: "counter_rate(max(system.diskio.read.bytes), kql='system.diskio.read.bytes: *')",
format: 'bytes',
decimals: 1,
@ -29,45 +34,35 @@ export const diskReadThroughput: LensBaseLayer = {
};
export const diskSpaceAvailable: LensBaseLayer = {
label: i18n.translate('xpack.metricsData.assetDetails.formulas.diskSpaceAvailable', {
defaultMessage: 'Disk Space Available',
}),
label: DISK_SPACE_AVAILABLE_LABEL,
value: 'average(system.filesystem.free)',
format: 'bytes',
decimals: 0,
};
export const diskSpaceAvailability: LensBaseLayer = {
label: i18n.translate('xpack.metricsData.assetDetails.formulas.diskSpaceAvailability', {
defaultMessage: 'Disk Space Availability',
}),
label: DISK_SPACE_AVAILABILITY_LABEL,
value: '1 - average(system.filesystem.used.pct)',
format: 'percent',
decimals: 0,
};
export const diskUsage: LensBaseLayer = {
label: i18n.translate('xpack.metricsData.assetDetails.formulas.diskUsage', {
defaultMessage: 'Disk Usage',
}),
label: DISK_USAGE_LABEL,
value: 'max(system.filesystem.used.pct)',
format: 'percent',
decimals: 0,
};
export const diskUsageAverage: LensBaseLayer = {
label: i18n.translate('xpack.metricsData.assetDetails.formulas.diskUsageAverage', {
defaultMessage: 'Disk Usage Average',
}),
label: DISK_USAGE_AVERAGE_LABEL,
value: 'average(system.filesystem.used.pct)',
format: 'percent',
decimals: 0,
};
export const diskIOWrite: LensBaseLayer = {
label: i18n.translate('xpack.metricsData.assetDetails.formulas.diskIOWrite', {
defaultMessage: 'Disk Write IOPS',
}),
label: DISK_WRITE_IOPS_LABEL,
value: "counter_rate(max(system.diskio.write.count), kql='system.diskio.write.count: *')",
format: 'number',
decimals: 0,
@ -75,9 +70,7 @@ export const diskIOWrite: LensBaseLayer = {
};
export const diskWriteThroughput: LensBaseLayer = {
label: i18n.translate('xpack.metricsData.assetDetails.formulas.diskWriteThroughput', {
defaultMessage: 'Disk Write Throughput',
}),
label: DISK_WRITE_THROUGHPUT_LABEL,
value: "counter_rate(max(system.diskio.write.bytes), kql='system.diskio.write.bytes: *')",
format: 'bytes',
decimals: 1,

View file

@ -7,6 +7,7 @@
import { i18n } from '@kbn/i18n';
import type { LensBaseLayer } from '@kbn/lens-embeddable-utils/config_builder';
import { MEMORY_FREE_LABEL, MEMORY_USAGE_LABEL } from '../../../shared/charts/constants';
export const memoryCache: LensBaseLayer = {
label: i18n.translate('xpack.metricsData.assetDetails.formulas.metric.label.cache', {
@ -18,9 +19,7 @@ export const memoryCache: LensBaseLayer = {
};
export const memoryFree: LensBaseLayer = {
label: i18n.translate('xpack.metricsData.assetDetails.formulas.memoryFree', {
defaultMessage: 'Memory Free',
}),
label: MEMORY_FREE_LABEL,
value: 'max(system.memory.total) - average(system.memory.actual.used.bytes)',
format: 'bytes',
decimals: 1,
@ -36,9 +35,7 @@ export const memoryFreeExcludingCache: LensBaseLayer = {
};
export const memoryUsage: LensBaseLayer = {
label: i18n.translate('xpack.metricsData.assetDetails.formulas.memoryUsage', {
defaultMessage: 'Memory Usage',
}),
label: MEMORY_USAGE_LABEL,
value: 'average(system.memory.actual.used.pct)',
format: 'percent',

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import type { LensXYConfigBase } from '@kbn/lens-embeddable-utils/config_builder';
export const DEFAULT_XY_FITTING_FUNCTION: Pick<LensXYConfigBase, 'fittingFunction'> = {
@ -38,3 +39,134 @@ export const DEFAULT_XY_HIDDEN_AXIS_TITLE: Pick<LensXYConfigBase, 'axisTitleVisi
showYAxisTitle: false,
},
};
export const CPU_USAGE_LABEL = i18n.translate(
'xpack.metricsData.assetDetails.metrics.label.cpuUsage',
{
defaultMessage: 'CPU Usage',
}
);
export const MEMORY_USAGE_LABEL = i18n.translate(
'xpack.metricsData.assetDetails.metrics.label.memoryUsage',
{
defaultMessage: 'Memory Usage',
}
);
export const MEMORY_FREE_LABEL = i18n.translate(
'xpack.metricsData.assetDetails.metrics.label.memoryFree',
{
defaultMessage: 'Memory Free',
}
);
export const LOAD_LABEL = i18n.translate('xpack.metricsData.assetDetails.metrics.label.load', {
defaultMessage: 'Load',
});
export const LOAD_1M_LABEL = i18n.translate('xpack.metricsData.assetDetails.metrics.label.load1m', {
defaultMessage: 'Load (1m)',
});
export const LOAD_5M_LABEL = i18n.translate('xpack.metricsData.assetDetails.metrics.label.load5m', {
defaultMessage: 'Load (5m)',
});
export const LOAD_15M_LABEL = i18n.translate(
'xpack.metricsData.assetDetails.metrics.label.load15m',
{
defaultMessage: 'Load (15m)',
}
);
export const NORMALIZED_LOAD_LABEL = i18n.translate(
'xpack.metricsData.assetDetails.metrics.label.normalizedLoad',
{
defaultMessage: 'Normalized Load',
}
);
export const DISK_USAGE_LABEL = i18n.translate(
'xpack.metricsData.assetDetails.metrics.label.diskUsage',
{
defaultMessage: 'Disk Usage',
}
);
export const DISK_USAGE_AVERAGE_LABEL = i18n.translate(
'xpack.metricsData.assetDetails.metrics.label.diskUsageAverage',
{
defaultMessage: 'Disk Usage Average',
}
);
export const DISK_IOPS_LABEL = i18n.translate(
'xpack.metricsData.assetDetails.metrics.label.diskIOPS',
{
defaultMessage: 'Disk IOPS',
}
);
export const DISK_READ_IOPS_LABEL = i18n.translate(
'xpack.metricsData.assetDetails.metrics.label.diskReadIOPS',
{
defaultMessage: 'Disk Read IOPS',
}
);
export const DISK_WRITE_IOPS_LABEL = i18n.translate(
'xpack.metricsData.assetDetails.metrics.label.diskWriteIOPS',
{
defaultMessage: 'Disk Write IOPS',
}
);
export const DISK_USAGE_BY_MOUNT_POINT_LABEL = i18n.translate(
'xpack.metricsData.assetDetails.metrics.label.diskUsageByMountingPoint',
{
defaultMessage: 'Disk Usage by Mount Point',
}
);
export const DISK_THROUGHPUT_LABEL = i18n.translate(
'xpack.metricsData.assetDetails.metrics.label.diskThroughput',
{
defaultMessage: 'Disk Throughput',
}
);
export const DISK_READ_THROUGHPUT_LABEL = i18n.translate(
'xpack.metricsData.assetDetails.metrics.label.diskReadThroughput',
{
defaultMessage: 'Disk Read Throughput',
}
);
export const DISK_WRITE_THROUGHPUT_LABEL = i18n.translate(
'xpack.metricsData.assetDetails.metrics.label.diskWriteThroughput',
{
defaultMessage: 'Disk Write Throughput',
}
);
export const DISK_SPACE_AVAILABLE_LABEL = i18n.translate(
'xpack.metricsData.assetDetails.metrics.label.diskSpaceAvailable',
{
defaultMessage: 'Disk Space Available',
}
);
export const DISK_SPACE_AVAILABILITY_LABEL = i18n.translate(
'xpack.metricsData.assetDetails.metrics.label.diskSpaceAvailablity',
{
defaultMessage: 'Disk Space Availability',
}
);
export const NETWORK_LABEL = i18n.translate(
'xpack.metricsData.assetDetails.metrics.label.network',
{
defaultMessage: 'Network',
}
);

View file

@ -32,6 +32,7 @@ export {
apmTraceExplorerTab,
apmLabsButton,
enableInfrastructureHostsView,
enableInfrastructureContainerAssetView,
enableInfrastructureProfilingIntegration,
enableInfrastructureAssetCustomDashboards,
enableAwsLambdaMetrics,

View file

@ -21,6 +21,8 @@ export const enableInfrastructureProfilingIntegration =
'observability:enableInfrastructureProfilingIntegration';
export const enableInfrastructureAssetCustomDashboards =
'observability:enableInfrastructureAssetCustomDashboards';
export const enableInfrastructureContainerAssetView =
'observability:enableInfrastructureContainerAssetView';
export const enableAwsLambdaMetrics = 'observability:enableAwsLambdaMetrics';
export const enableAgentExplorerView = 'observability:apmAgentExplorerView';
export const apmEnableTableSearchBar = 'observability:apmEnableTableSearchBar';

View file

@ -41,6 +41,7 @@ export {
enableComparisonByDefault,
apmServiceGroupMaxNumberOfServices,
enableInfrastructureHostsView,
enableInfrastructureContainerAssetView,
enableAgentExplorerView,
apmEnableTableSearchBar,
} from '../common/ui_settings_keys';

View file

@ -45,6 +45,7 @@ import {
enableInfrastructureAssetCustomDashboards,
apmEnableServiceInventoryTableSearchBar,
profilingFetchTopNFunctionsFromStacktraces,
enableInfrastructureContainerAssetView,
} from '../common/ui_settings_keys';
const betaLabel = i18n.translate('xpack.observability.uiSettings.betaLabel', {
@ -244,6 +245,20 @@ export const uiSettings: Record<string, UiSettings> = {
}),
schema: schema.boolean(),
},
[enableInfrastructureContainerAssetView]: {
category: [observabilityFeatureId],
name: i18n.translate('xpack.observability.enableInfrastructureContainerAssetView', {
defaultMessage: 'Container view',
}),
value: false,
description: i18n.translate(
'xpack.observability.enableInfrastructureContainerAssetViewDescription',
{
defaultMessage: 'Enable the Container asset view in the Infrastructure app.',
}
),
schema: schema.boolean(),
},
[enableInfrastructureProfilingIntegration]: {
category: [observabilityFeatureId],
name: i18n.translate('xpack.observability.enableInfrastructureProfilingIntegration', {

View file

@ -21088,10 +21088,8 @@
"xpack.infra.assetDetails.tabs.metadata.seeLess": "Afficher moins",
"xpack.infra.assetDetails.tabs.metadata.seeMore": "+{count} de plus",
"xpack.infra.assetDetails.tabs.metadata": "Métadonnées",
"xpack.infra.assetDetails.tabs.osquery": "Osquery",
"xpack.infra.assetDetails.tabs.overview": "Aperçu",
"xpack.infra.assetDetails.tabs.processes": "Processus",
"xpack.infra.assetDetails.tabs.profiling": "Universal Profiling",
"xpack.infra.homePage.toolbar.showingLastOneMinuteDataText": "Dernières {duration} de données pour l'heure sélectionnée",
"xpack.infra.hostsViewPage.errorOnCreateOrLoadDataview": "Une erreur s'est produite lors de la création d'une vue de données : {metricAlias}. Essayez de recharger la page.",
"xpack.infra.hostsViewPage.kpi.subtitle.average.limit": "Moyenne (de {limit} hôtes)",
@ -44918,7 +44916,6 @@
"xpack.features.savedQueryManagementFeatureName": "Gestion des requêtes enregistrées",
"xpack.features.savedQueryManagementTooltip": "Si \"All\" (Tout) est défini, les requêtes enregistrées peuvent être gérées grâce à Kibana dans toutes les applications compatibles. Si \"None\" est défini, les privilèges relatifs aux requêtes enregistrées sont fixés indépendamment pour chaque application.",
"xpack.features.visualizeFeatureName": "Bibliothèque Visualize",
"xpack.metricsData.assetDetails.formulas.cpuUsage": "Utilisation CPU",
"xpack.metricsData.assetDetails.formulas.cpuUsage.iowaitLabel": "iowait",
"xpack.metricsData.assetDetails.formulas.cpuUsage.irqLabel": "irq",
"xpack.metricsData.assetDetails.formulas.cpuUsage.niceLabel": "nice",
@ -44926,45 +44923,25 @@
"xpack.metricsData.assetDetails.formulas.cpuUsage.stealLabel": "steal",
"xpack.metricsData.assetDetails.formulas.cpuUsage.systemLabel": "system",
"xpack.metricsData.assetDetails.formulas.cpuUsage.userLabel": "utilisateur",
"xpack.metricsData.assetDetails.formulas.diskIORead": "Entrées et sorties par seconde en lecture sur le disque",
"xpack.metricsData.assetDetails.formulas.diskIOWrite": "Entrées et sorties par seconde en écriture sur le disque",
"xpack.metricsData.assetDetails.formulas.diskReadThroughput": "Rendement de lecture du disque",
"xpack.metricsData.assetDetails.formulas.diskSpaceAvailability": "Disponibilité de l'espace disque",
"xpack.metricsData.assetDetails.formulas.diskSpaceAvailable": "Espace disque disponible",
"xpack.metricsData.assetDetails.formulas.diskUsage": "Utilisation du disque",
"xpack.metricsData.assetDetails.formulas.diskWriteThroughput": "Rendement décriture du disque",
"xpack.metricsData.assetDetails.formulas.hostCount.hostsLabel": "Hôtes",
"xpack.metricsData.assetDetails.formulas.kubernetes.capacity": "Capacité",
"xpack.metricsData.assetDetails.formulas.kubernetes.used": "Utilisé",
"xpack.metricsData.assetDetails.formulas.load15m": "Charge (15 min)",
"xpack.metricsData.assetDetails.formulas.load1m": "Charge (1 min)",
"xpack.metricsData.assetDetails.formulas.load5m": "Charge (5 min)",
"xpack.metricsData.assetDetails.formulas.logRate": "Taux de log",
"xpack.metricsData.assetDetails.formulas.memoryFree": "Sans mémoire",
"xpack.metricsData.assetDetails.formulas.memoryUsage": "Utilisation mémoire",
"xpack.metricsData.assetDetails.formulas.metric.label.cache": "cache",
"xpack.metricsData.assetDetails.formulas.metric.label.free": "gratuit",
"xpack.metricsData.assetDetails.formulas.metric.label.used": "utilisé",
"xpack.metricsData.assetDetails.formulas.normalizedLoad1m": "Charge normalisée",
"xpack.metricsData.assetDetails.formulas.rx": "Réseau entrant (RX)",
"xpack.metricsData.assetDetails.formulas.tx": "Réseau sortant (TX)",
"xpack.metricsData.assetDetails.metricsCharts.cpuUsage": "Utilisation CPU",
"xpack.metricsData.assetDetails.metricsCharts.diskIOPS": "Entrées et sorties par seconde (IOPS) sur le disque",
"xpack.metricsData.assetDetails.metricsCharts.diskThroughput": "Rendement du disque",
"xpack.metricsData.assetDetails.metricsCharts.diskUsage.label.used": "Utilisé",
"xpack.metricsData.assetDetails.metricsCharts.diskUsageByMountingPoint": "Utilisation du disque par point de montage",
"xpack.metricsData.assetDetails.metricsCharts.kubernetes.nodeCpuCapacity": "Capacité CPU du nœud",
"xpack.metricsData.assetDetails.metricsCharts.kubernetes.nodeDiskCapacity": "Capacité du disque du nœud",
"xpack.metricsData.assetDetails.metricsCharts.kubernetes.nodeMemoryCapacity": "Capacité de mémoire du nœud",
"xpack.metricsData.assetDetails.metricsCharts.kubernetes.nodePodCapacity": "Capacité de pod du nœud",
"xpack.metricsData.assetDetails.metricsCharts.load": "Charge",
"xpack.metricsData.assetDetails.metricsCharts.memoryUsage": "Utilisation mémoire",
"xpack.metricsData.assetDetails.metricsCharts.metric.label.cache": "Cache",
"xpack.metricsData.assetDetails.metricsCharts.metric.label.free": "Gratuit",
"xpack.metricsData.assetDetails.metricsCharts.metric.label.read": "Lire",
"xpack.metricsData.assetDetails.metricsCharts.metric.label.used": "Utilisé",
"xpack.metricsData.assetDetails.metricsCharts.metric.label.write": "Écrire",
"xpack.metricsData.assetDetails.metricsCharts.network": "Réseau",
"xpack.metricsData.assetDetails.metricsCharts.network.label.rx": "Entrant (RX)",
"xpack.metricsData.assetDetails.metricsCharts.network.label.tx": "Sortant (TX)",
"xpack.metricsData.hostsPage.goToMetricsSettings": "Vérifier les paramètres",

View file

@ -21228,10 +21228,8 @@
"xpack.infra.assetDetails.tabs.metadata.seeLess": "簡易表示",
"xpack.infra.assetDetails.tabs.metadata.seeMore": "他 {count} 件",
"xpack.infra.assetDetails.tabs.metadata": "メタデータ",
"xpack.infra.assetDetails.tabs.osquery": "Osquery",
"xpack.infra.assetDetails.tabs.overview": "概要",
"xpack.infra.assetDetails.tabs.processes": "プロセス",
"xpack.infra.assetDetails.tabs.profiling": "ユニバーサルプロファイリング",
"xpack.infra.assetDetails.tooltip.activeAlertsExplanation": "アクティブアラート",
"xpack.infra.bottomDrawer.kubernetesDashboardsLink": "Kubernetesダッシュボード",
"xpack.infra.chartSection.missingMetricDataBody": "このチャートはデータが欠けています。",
@ -44888,7 +44886,6 @@
"xpack.features.savedQueryManagementFeatureName": "保存されたクエリ管理",
"xpack.features.savedQueryManagementTooltip": "すべてに設定すると、保存されたクエリは、クエリをサポートするすべてのアプリケーションのKibana全体で管理できます。なしに設定すると、保存されたクエリ権限は各アプリケーションで独自に決定されます。",
"xpack.features.visualizeFeatureName": "Visualizeライブラリ",
"xpack.metricsData.assetDetails.formulas.cpuUsage": "CPU使用状況",
"xpack.metricsData.assetDetails.formulas.cpuUsage.iowaitLabel": "iowait",
"xpack.metricsData.assetDetails.formulas.cpuUsage.irqLabel": "irq",
"xpack.metricsData.assetDetails.formulas.cpuUsage.niceLabel": "nice",
@ -44896,45 +44893,25 @@
"xpack.metricsData.assetDetails.formulas.cpuUsage.stealLabel": "steal",
"xpack.metricsData.assetDetails.formulas.cpuUsage.systemLabel": "システム",
"xpack.metricsData.assetDetails.formulas.cpuUsage.userLabel": "ユーザー",
"xpack.metricsData.assetDetails.formulas.diskIORead": "ディスク読み取りIOPS",
"xpack.metricsData.assetDetails.formulas.diskIOWrite": "ディスク書き込みIOPS",
"xpack.metricsData.assetDetails.formulas.diskReadThroughput": "ディスク読み取りスループット",
"xpack.metricsData.assetDetails.formulas.diskSpaceAvailability": "空きディスク容量",
"xpack.metricsData.assetDetails.formulas.diskSpaceAvailable": "空きディスク容量",
"xpack.metricsData.assetDetails.formulas.diskUsage": "ディスク使用量",
"xpack.metricsData.assetDetails.formulas.diskWriteThroughput": "ディスク書き込みスループット",
"xpack.metricsData.assetDetails.formulas.hostCount.hostsLabel": "ホスト",
"xpack.metricsData.assetDetails.formulas.kubernetes.capacity": "容量",
"xpack.metricsData.assetDetails.formulas.kubernetes.used": "使用中",
"xpack.metricsData.assetDetails.formulas.load15m": "読み込み15m",
"xpack.metricsData.assetDetails.formulas.load1m": "読み込み1m",
"xpack.metricsData.assetDetails.formulas.load5m": "読み込み5m",
"xpack.metricsData.assetDetails.formulas.logRate": "ログレート",
"xpack.metricsData.assetDetails.formulas.memoryFree": "空きメモリー",
"xpack.metricsData.assetDetails.formulas.memoryUsage": "メモリー使用状況",
"xpack.metricsData.assetDetails.formulas.metric.label.cache": "キャッシュ",
"xpack.metricsData.assetDetails.formulas.metric.label.free": "空き",
"xpack.metricsData.assetDetails.formulas.metric.label.used": "使用中",
"xpack.metricsData.assetDetails.formulas.normalizedLoad1m": "正規化された負荷",
"xpack.metricsData.assetDetails.formulas.rx": "ネットワーク受信RX",
"xpack.metricsData.assetDetails.formulas.tx": "ネットワーク送信TX",
"xpack.metricsData.assetDetails.metricsCharts.cpuUsage": "CPU使用状況",
"xpack.metricsData.assetDetails.metricsCharts.diskIOPS": "Disk IOPS",
"xpack.metricsData.assetDetails.metricsCharts.diskThroughput": "Disk Throughput",
"xpack.metricsData.assetDetails.metricsCharts.diskUsage.label.used": "使用中",
"xpack.metricsData.assetDetails.metricsCharts.diskUsageByMountingPoint": "マウントポイント別ディスク使用量",
"xpack.metricsData.assetDetails.metricsCharts.kubernetes.nodeCpuCapacity": "ノード CPU 処理能力",
"xpack.metricsData.assetDetails.metricsCharts.kubernetes.nodeDiskCapacity": "ノードディスク容量",
"xpack.metricsData.assetDetails.metricsCharts.kubernetes.nodeMemoryCapacity": "ノードメモリー容量",
"xpack.metricsData.assetDetails.metricsCharts.kubernetes.nodePodCapacity": "ノードポッド容量",
"xpack.metricsData.assetDetails.metricsCharts.load": "読み込み",
"xpack.metricsData.assetDetails.metricsCharts.memoryUsage": "メモリー使用状況",
"xpack.metricsData.assetDetails.metricsCharts.metric.label.cache": "キャッシュ",
"xpack.metricsData.assetDetails.metricsCharts.metric.label.free": "空き",
"xpack.metricsData.assetDetails.metricsCharts.metric.label.read": "読み取り",
"xpack.metricsData.assetDetails.metricsCharts.metric.label.used": "使用中",
"xpack.metricsData.assetDetails.metricsCharts.metric.label.write": "書き込み",
"xpack.metricsData.assetDetails.metricsCharts.network": "ネットワーク",
"xpack.metricsData.assetDetails.metricsCharts.network.label.rx": "受信RX",
"xpack.metricsData.assetDetails.metricsCharts.network.label.tx": "送信TX",
"xpack.metricsData.hostsPage.goToMetricsSettings": "設定を確認",

View file

@ -21094,10 +21094,8 @@
"xpack.infra.assetDetails.tabs.metadata.seeLess": "显示更少",
"xpack.infra.assetDetails.tabs.metadata.seeMore": "另外 {count} 个",
"xpack.infra.assetDetails.tabs.metadata": "元数据",
"xpack.infra.assetDetails.tabs.osquery": "Osquery",
"xpack.infra.assetDetails.tabs.overview": "概览",
"xpack.infra.assetDetails.tabs.processes": "进程",
"xpack.infra.assetDetails.tabs.profiling": "Universal Profiling",
"xpack.infra.homePage.toolbar.showingLastOneMinuteDataText": "选定时间过去 {duration}的数据",
"xpack.infra.hostsViewPage.errorOnCreateOrLoadDataview": "尝试创建数据视图时出错:{metricAlias}。尝试重新加载该页面。",
"xpack.infra.hostsViewPage.kpi.subtitle.average.limit": "平均值(属于 {limit} 台主机)",
@ -44936,7 +44934,6 @@
"xpack.features.savedQueryManagementFeatureName": "已保存查询管理",
"xpack.features.savedQueryManagementTooltip": "如果设置为“全部”,可以在支持已保存查询的所有应用程序中管理整个 Kibana 中的已保存查询。如果设置为“无”,将由每个应用程序单独确定已保存查询权限。",
"xpack.features.visualizeFeatureName": "Visualize 库",
"xpack.metricsData.assetDetails.formulas.cpuUsage": "CPU 使用率",
"xpack.metricsData.assetDetails.formulas.cpuUsage.iowaitLabel": "iowait",
"xpack.metricsData.assetDetails.formulas.cpuUsage.irqLabel": "irq",
"xpack.metricsData.assetDetails.formulas.cpuUsage.niceLabel": "nice",
@ -44944,45 +44941,25 @@
"xpack.metricsData.assetDetails.formulas.cpuUsage.stealLabel": "steal",
"xpack.metricsData.assetDetails.formulas.cpuUsage.systemLabel": "system",
"xpack.metricsData.assetDetails.formulas.cpuUsage.userLabel": "用户",
"xpack.metricsData.assetDetails.formulas.diskIORead": "磁盘读取 IOPS",
"xpack.metricsData.assetDetails.formulas.diskIOWrite": "磁盘写入 IOPS",
"xpack.metricsData.assetDetails.formulas.diskReadThroughput": "磁盘读取吞吐量",
"xpack.metricsData.assetDetails.formulas.diskSpaceAvailability": "磁盘空间可用性",
"xpack.metricsData.assetDetails.formulas.diskSpaceAvailable": "可用磁盘空间",
"xpack.metricsData.assetDetails.formulas.diskUsage": "磁盘使用率",
"xpack.metricsData.assetDetails.formulas.diskWriteThroughput": "磁盘写入吞吐量",
"xpack.metricsData.assetDetails.formulas.hostCount.hostsLabel": "主机",
"xpack.metricsData.assetDetails.formulas.kubernetes.capacity": "容量",
"xpack.metricsData.assetDetails.formulas.kubernetes.used": "已使用",
"xpack.metricsData.assetDetails.formulas.load15m": "负载15 分钟)",
"xpack.metricsData.assetDetails.formulas.load1m": "负载1 分钟)",
"xpack.metricsData.assetDetails.formulas.load5m": "负载5 分钟)",
"xpack.metricsData.assetDetails.formulas.logRate": "日志速率",
"xpack.metricsData.assetDetails.formulas.memoryFree": "可用内存",
"xpack.metricsData.assetDetails.formulas.memoryUsage": "内存利用率",
"xpack.metricsData.assetDetails.formulas.metric.label.cache": "缓存",
"xpack.metricsData.assetDetails.formulas.metric.label.free": "可用",
"xpack.metricsData.assetDetails.formulas.metric.label.used": "已使用",
"xpack.metricsData.assetDetails.formulas.normalizedLoad1m": "标准化负载",
"xpack.metricsData.assetDetails.formulas.rx": "网络入站数据 (RX)",
"xpack.metricsData.assetDetails.formulas.tx": "网络出站数据 (TX)",
"xpack.metricsData.assetDetails.metricsCharts.cpuUsage": "CPU 使用率",
"xpack.metricsData.assetDetails.metricsCharts.diskIOPS": "磁盘 IOPS",
"xpack.metricsData.assetDetails.metricsCharts.diskThroughput": "磁盘吞吐量",
"xpack.metricsData.assetDetails.metricsCharts.diskUsage.label.used": "已使用",
"xpack.metricsData.assetDetails.metricsCharts.diskUsageByMountingPoint": "磁盘使用率(按装载点)",
"xpack.metricsData.assetDetails.metricsCharts.kubernetes.nodeCpuCapacity": "节点 CPU 容量",
"xpack.metricsData.assetDetails.metricsCharts.kubernetes.nodeDiskCapacity": "节点磁盘容量",
"xpack.metricsData.assetDetails.metricsCharts.kubernetes.nodeMemoryCapacity": "节点内存容量",
"xpack.metricsData.assetDetails.metricsCharts.kubernetes.nodePodCapacity": "节点 Pod 容量",
"xpack.metricsData.assetDetails.metricsCharts.load": "加载",
"xpack.metricsData.assetDetails.metricsCharts.memoryUsage": "内存利用率",
"xpack.metricsData.assetDetails.metricsCharts.metric.label.cache": "缓存",
"xpack.metricsData.assetDetails.metricsCharts.metric.label.free": "可用",
"xpack.metricsData.assetDetails.metricsCharts.metric.label.read": "读取",
"xpack.metricsData.assetDetails.metricsCharts.metric.label.used": "已使用",
"xpack.metricsData.assetDetails.metricsCharts.metric.label.write": "写入",
"xpack.metricsData.assetDetails.metricsCharts.network": "网络",
"xpack.metricsData.assetDetails.metricsCharts.network.label.rx": "入站 (RX)",
"xpack.metricsData.assetDetails.metricsCharts.network.label.tx": "出站 (TX)",
"xpack.metricsData.hostsPage.goToMetricsSettings": "检查设置",

View file

@ -23,8 +23,8 @@ export default function ({ getService }: FtrProviderContext) {
await synthtrace.clean();
});
it('should return container assets', async () => {
await synthtrace.index(generateContainersData({ from, to, count: 5 }));
it('should return docker container assets', async () => {
await synthtrace.index(generateDockerContainersData({ from, to, count: 5 }));
const response = await supertest
.get(routePaths.GET_CONTAINERS)
@ -38,8 +38,8 @@ export default function ({ getService }: FtrProviderContext) {
expect(response.body.containers.length).to.equal(5);
});
it('should return a specific container asset by EAN', async () => {
await synthtrace.index(generateContainersData({ from, to, count: 5 }));
it('should return a specific docker container asset by EAN', async () => {
await synthtrace.index(generateDockerContainersData({ from, to, count: 5 }));
const testEan = 'container:container-id-1';
const response = await supertest
@ -56,8 +56,68 @@ export default function ({ getService }: FtrProviderContext) {
expect(response.body.containers[0]['asset.ean']).to.equal(testEan);
});
it('should return a filtered list of container assets by ID wildcard pattern', async () => {
await synthtrace.index(generateContainersData({ from, to, count: 15 }));
it('should return a filtered list of docker container assets by ID wildcard pattern', async () => {
await synthtrace.index(generateDockerContainersData({ from, to, count: 15 }));
const testIdPattern = '*id-1*';
const response = await supertest
.get(routePaths.GET_CONTAINERS)
.query({
from,
to,
stringFilters: JSON.stringify({ id: testIdPattern }),
})
.expect(200);
expect(response.body).to.have.property('containers');
expect(response.body.containers.length).to.equal(6);
const ids = response.body.containers.map((result: Asset) => result['asset.id']);
expect(ids).to.eql([
'container-id-1',
'container-id-10',
'container-id-11',
'container-id-12',
'container-id-13',
'container-id-14',
]);
});
it('should return k8s container assets', async () => {
await synthtrace.index(generateK8sContainersData({ from, to, count: 5 }));
const response = await supertest
.get(routePaths.GET_CONTAINERS)
.query({
from,
to,
})
.expect(200);
expect(response.body).to.have.property('containers');
expect(response.body.containers.length).to.equal(5);
});
it('should return a specific k8s container asset by EAN', async () => {
await synthtrace.index(generateK8sContainersData({ from, to, count: 5 }));
const testEan = 'container:container-id-1';
const response = await supertest
.get(routePaths.GET_CONTAINERS)
.query({
from,
to,
stringFilters: JSON.stringify({ ean: testEan }),
})
.expect(200);
expect(response.body).to.have.property('containers');
expect(response.body.containers.length).to.equal(1);
expect(response.body.containers[0]['asset.ean']).to.equal(testEan);
});
it('should return a filtered list of k8s container assets by ID wildcard pattern', async () => {
await synthtrace.index(generateK8sContainersData({ from, to, count: 15 }));
const testIdPattern = '*id-1*';
const response = await supertest
@ -86,7 +146,30 @@ export default function ({ getService }: FtrProviderContext) {
});
}
function generateContainersData({
function generateDockerContainersData({
from,
to,
count = 1,
}: {
from: string;
to: string;
count: number;
}) {
const range = timerange(from, to);
const containers = Array(count)
.fill(0)
.map((_, idx) => infra.dockerContainer(`container-id-${idx}`));
return range
.interval('1m')
.rate(1)
.generator((timestamp) =>
containers.map((container) => container.metrics().timestamp(timestamp))
);
}
function generateK8sContainersData({
from,
to,
count = 1,
@ -100,7 +183,7 @@ function generateContainersData({
const containers = Array(count)
.fill(0)
.map((_, idx) =>
infra.container(`container-id-${idx}`, `container-uid-${idx + 1000}`, `node-name-${idx}`)
infra.k8sContainer(`container-id-${idx}`, `container-uid-${idx + 1000}`, `node-name-${idx}`)
);
return range

View file

@ -11,6 +11,7 @@ import { InfraLogViewsServiceProvider } from './infra_log_views';
import { SpacesServiceProvider } from './spaces';
import { BsearchSecureService } from './bsearch_secure';
import { ApmSynthtraceKibanaClientProvider } from './apm_synthtrace_kibana_client';
import { InfraSynthtraceKibanaClientProvider } from './infra_synthtrace_kibana_client';
export const services = {
...kibanaCommonServices,
@ -19,4 +20,5 @@ export const services = {
spaces: SpacesServiceProvider,
secureBsearch: BsearchSecureService,
apmSynthtraceKibanaClient: ApmSynthtraceKibanaClientProvider,
infraSynthtraceKibanaClient: InfraSynthtraceKibanaClientProvider,
};

View file

@ -0,0 +1,37 @@
/*
* 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 url from 'url';
import { kbnTestConfig } from '@kbn/test';
import { InfraSynthtraceKibanaClient, createLogger, LogLevel } from '@kbn/apm-synthtrace';
const getKibanaServerUrlWithAuth = () => {
const kibanaServerUrl = url.format(kbnTestConfig.getUrlParts() as url.UrlObject);
const kibanaServerUrlWithAuth = url
.format({
...url.parse(kibanaServerUrl),
auth: `elastic:${kbnTestConfig.getUrlParts().password}`,
})
.slice(0, -1);
return kibanaServerUrlWithAuth;
};
export function InfraSynthtraceKibanaClientProvider() {
const kibanaServerUrlWithAuth = getKibanaServerUrlWithAuth();
const target = kibanaServerUrlWithAuth;
const logger = createLogger(LogLevel.debug);
const username = 'elastic';
const password = kbnTestConfig.getUrlParts().password || 'changeme';
const kibanaClient = new InfraSynthtraceKibanaClient({
target,
logger,
username,
password,
});
return kibanaClient;
}

View file

@ -0,0 +1,17 @@
/*
* 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 { Client } from '@elastic/elasticsearch';
import { InfraSynthtraceEsClient, createLogger, LogLevel } from '@kbn/apm-synthtrace';
export async function getInfraSynthtraceEsClient(client: Client) {
return new InfraSynthtraceEsClient({
client,
logger: createLogger(LogLevel.info),
refreshAfterIndex: true,
});
}

View file

@ -51,7 +51,11 @@ export const ML_JOB_IDS = [
export const HOSTS_LINK_LOCAL_STORAGE_KEY = 'inventoryUI:hostsLinkClicked';
export const INVENTORY_PATH = 'metrics/inventory';
export const NODE_DETAILS_PATH = 'detail/host';
export const NODE_DETAILS_PATH = 'detail';
export const HOSTS_VIEW_PATH = 'metrics/hosts';
export const DATE_PICKER_FORMAT = 'MMM D, YYYY @ HH:mm:ss.SSS';
export const DATE_WITH_DOCKER_DATA_FROM = '2023-03-28T18:20:00.000Z';
export const DATE_WITH_DOCKER_DATA_TO = '2023-03-28T18:21:00.000Z';
export const DATE_WITH_DOCKER_DATA = '03/28/2023 6:20:59 PM';

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { apm, timerange } from '@kbn/apm-synthtrace-client';
import { apm, timerange, infra } from '@kbn/apm-synthtrace-client';
const SERVICE_PREFIX = 'service';
// generates traces, metrics for services
@ -46,3 +46,26 @@ export function generateAddServicesToExistingHost({
)
);
}
export function generateDockerContainersData({
from,
to,
count = 1,
}: {
from: string;
to: string;
count?: number;
}) {
const range = timerange(from, to);
const containers = Array(count)
.fill(0)
.map((_, idx) => infra.dockerContainer(`container-id-${idx}`));
return range
.interval('30s')
.rate(1)
.generator((timestamp) =>
containers.flatMap((container) => container.metrics().timestamp(timestamp))
);
}

View file

@ -8,17 +8,25 @@
import expect from '@kbn/expect';
import { parse } from 'url';
import { KUBERNETES_TOUR_STORAGE_KEY } from '@kbn/infra-plugin/public/pages/metrics/inventory_view/components/kubernetes_tour';
import { InfraSynthtraceEsClient } from '@kbn/apm-synthtrace';
import { FtrProviderContext } from '../../ftr_provider_context';
import { DATES, INVENTORY_PATH } from './constants';
import { generateDockerContainersData } from './helpers';
import { getInfraSynthtraceEsClient } from '../../../common/utils/synthtrace/infra_es_client';
const DATE_WITH_DATA = DATES.metricsAndLogs.hosts.withData;
const DATE_WITHOUT_DATA = DATES.metricsAndLogs.hosts.withoutData;
const DATE_WITH_POD_WITH_DATA = DATES.metricsAndLogs.pods.withData;
const DATE_WITH_DOCKER_DATA_FROM = '2023-03-28T18:20:00.000Z';
const DATE_WITH_DOCKER_DATA_TO = '2023-03-28T18:21:00.000Z';
const DATE_WITH_DOCKER_DATA = '03/28/2023 6:20:00 PM';
export default ({ getPageObjects, getService }: FtrProviderContext) => {
const esArchiver = getService('esArchiver');
const browser = getService('browser');
const retry = getService('retry');
const esClient = getService('es');
const infraSynthtraceKibanaClient = getService('infraSynthtraceKibanaClient');
const pageObjects = getPageObjects([
'common',
'header',
@ -355,7 +363,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await returnTo(INVENTORY_PATH);
});
it('Should redirect to Node Details page', async () => {
it('Should redirect to Pod Details page', async () => {
await pageObjects.infraHome.goToPods();
await pageObjects.infraHome.goToTime(DATE_WITH_POD_WITH_DATA);
await pageObjects.infraHome.clickOnFirstNode();
@ -370,6 +378,41 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await returnTo(INVENTORY_PATH);
});
describe('Redirect to Container Details page', () => {
let synthEsClient: InfraSynthtraceEsClient;
before(async () => {
const version = await infraSynthtraceKibanaClient.fetchLatestSystemPackageVersion();
await infraSynthtraceKibanaClient.installSystemPackage(version);
synthEsClient = await getInfraSynthtraceEsClient(esClient);
await synthEsClient.index(
generateDockerContainersData({
from: DATE_WITH_DOCKER_DATA_FROM,
to: DATE_WITH_DOCKER_DATA_TO,
count: 5,
})
);
});
after(async () => {
return await synthEsClient.clean();
});
it('Should redirect to Container Details page', async () => {
await pageObjects.infraHome.goToContainer();
await pageObjects.infraHome.goToTime(DATE_WITH_DOCKER_DATA);
await pageObjects.infraHome.clickOnFirstNode();
await pageObjects.infraHome.clickOnGoToNodeDetails();
await retry.try(async () => {
const documentTitle = await browser.getTitle();
expect(documentTitle).to.contain(
'container-id-4 - Inventory - Infrastructure - Observability - Elastic'
);
});
await returnTo(INVENTORY_PATH);
});
});
});
});

View file

@ -7,14 +7,26 @@
import moment from 'moment';
import expect from '@kbn/expect';
import { enableInfrastructureProfilingIntegration } from '@kbn/observability-plugin/common';
import { InfraSynthtraceEsClient } from '@kbn/apm-synthtrace';
import {
enableInfrastructureContainerAssetView,
enableInfrastructureProfilingIntegration,
} from '@kbn/observability-plugin/common';
import {
ALERT_STATUS_ACTIVE,
ALERT_STATUS_RECOVERED,
ALERT_STATUS_UNTRACKED,
} from '@kbn/rule-data-utils';
import { FtrProviderContext } from '../../ftr_provider_context';
import { DATES, NODE_DETAILS_PATH, DATE_PICKER_FORMAT } from './constants';
import {
DATES,
NODE_DETAILS_PATH,
DATE_PICKER_FORMAT,
DATE_WITH_DOCKER_DATA_FROM,
DATE_WITH_DOCKER_DATA_TO,
} from './constants';
import { getInfraSynthtraceEsClient } from '../../../common/utils/synthtrace/infra_es_client';
import { generateDockerContainersData } from './helpers';
const START_HOST_ALERTS_DATE = moment.utc(DATES.metricsAndLogs.hosts.min);
const END_HOST_ALERTS_DATE = moment.utc(DATES.metricsAndLogs.hosts.max);
@ -32,6 +44,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const browser = getService('browser');
const kibanaServer = getService('kibanaServer');
const esArchiver = getService('esArchiver');
const infraSynthtraceKibanaClient = getService('infraSynthtraceKibanaClient');
const esClient = getService('es');
const retry = getService('retry');
const testSubjects = getService('testSubjects');
const pageObjects = getPageObjects([
@ -50,10 +64,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
return queryParams.toString();
};
const navigateToNodeDetails = async (assetId: string, assetName: string) => {
const navigateToNodeDetails = async (assetId: string, assetName: string, assetType: string) => {
await pageObjects.common.navigateToUrlWithBrowserHistory(
'infraOps',
`/${NODE_DETAILS_PATH}/${assetId}`,
`/${NODE_DETAILS_PATH}/${assetType}/${assetId}`,
getNodeDetailsUrl(assetName),
{
insertTimestamp: false,
@ -79,6 +93,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await pageObjects.header.waitUntilLoadingHasFinished();
};
const setInfrastructureContainerAssetViewUiSetting = async (value: boolean = true) => {
await kibanaServer.uiSettings.update({ [enableInfrastructureContainerAssetView]: value });
await browser.refresh();
await pageObjects.header.waitUntilLoadingHasFinished();
};
describe('Node Details', () => {
describe('#With Asset Details', () => {
before(async () => {
@ -90,7 +110,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
]);
await browser.setWindowSize(1600, 1200);
await navigateToNodeDetails('Jennys-MBP.fritz.box', 'Jennys-MBP.fritz.box');
await navigateToNodeDetails('Jennys-MBP.fritz.box', 'Jennys-MBP.fritz.box', 'host');
await pageObjects.header.waitUntilLoadingHasFinished();
});
@ -102,7 +122,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
]);
});
describe('#Date picker', () => {
describe('#Date picker: host', () => {
before(async () => {
await pageObjects.assetDetails.clickOverviewTab();
@ -247,7 +267,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const ALL_ALERTS = ACTIVE_ALERTS + RECOVERED_ALERTS;
const COLUMNS = 11;
before(async () => {
await navigateToNodeDetails('demo-stack-apache-01', 'demo-stack-apache-01');
await navigateToNodeDetails('demo-stack-apache-01', 'demo-stack-apache-01', 'host');
await pageObjects.header.waitUntilLoadingHasFinished();
await pageObjects.timePicker.setAbsoluteRange(
@ -259,7 +279,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});
after(async () => {
await navigateToNodeDetails('Jennys-MBP.fritz.box', 'Jennys-MBP.fritz.box');
await navigateToNodeDetails('Jennys-MBP.fritz.box', 'Jennys-MBP.fritz.box', 'host');
await pageObjects.header.waitUntilLoadingHasFinished();
await pageObjects.timePicker.setAbsoluteRange(
@ -482,7 +502,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
describe('Host with alerts and no processes', () => {
before(async () => {
await navigateToNodeDetails('demo-stack-mysql-01', 'demo-stack-mysql-01');
await navigateToNodeDetails('demo-stack-mysql-01', 'demo-stack-mysql-01', 'host');
await pageObjects.timePicker.setAbsoluteRange(
START_HOST_ALERTS_DATE.format(DATE_PICKER_FORMAT),
END_HOST_ALERTS_DATE.format(DATE_PICKER_FORMAT)
@ -516,7 +536,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
describe('#With Kubernetes section', () => {
before(async () => {
await navigateToNodeDetails('demo-stack-kubernetes-01', 'demo-stack-kubernetes-01');
await navigateToNodeDetails(
'demo-stack-kubernetes-01',
'demo-stack-kubernetes-01',
'host'
);
await pageObjects.header.waitUntilLoadingHasFinished();
});
@ -597,6 +621,57 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});
});
});
describe('#Asset Type: container', () => {
let synthEsClient: InfraSynthtraceEsClient;
before(async () => {
const version = await infraSynthtraceKibanaClient.fetchLatestSystemPackageVersion();
await infraSynthtraceKibanaClient.installSystemPackage(version);
synthEsClient = await getInfraSynthtraceEsClient(esClient);
await synthEsClient.index(
generateDockerContainersData({
from: DATE_WITH_DOCKER_DATA_FROM,
to: DATE_WITH_DOCKER_DATA_TO,
count: 1,
})
);
});
after(async () => {
await synthEsClient.clean();
});
describe('when container asset view is disabled', () => {
it('should show old view of container details', async () => {
await setInfrastructureContainerAssetViewUiSetting(false);
await navigateToNodeDetails('container-id-0', 'container-id-0', 'container');
await pageObjects.header.waitUntilLoadingHasFinished();
await testSubjects.find('metricsEmptyViewState');
});
});
describe('when container asset view is enabled', () => {
it('should show asset container details page', async () => {
await setInfrastructureContainerAssetViewUiSetting(true);
await navigateToNodeDetails('container-id-0', 'container-id-0', 'container');
await pageObjects.header.waitUntilLoadingHasFinished();
await pageObjects.assetDetails.getOverviewTab();
});
[
{ metric: 'cpu', chartsCount: 1 },
{ metric: 'memory', chartsCount: 1 },
].forEach(({ metric, chartsCount }) => {
it(`should render ${chartsCount} ${metric} chart(s) in the Metrics section`, async () => {
const charts = await pageObjects.assetDetails.getOverviewTabDockerMetricCharts(
metric
);
expect(charts.length).to.equal(chartsCount);
});
});
});
});
});
});
};

View file

@ -30,6 +30,10 @@ export function AssetDetailsProvider({ getService }: FtrProviderContext) {
return testSubjects.click('infraAssetDetailsOverviewTab');
},
async getOverviewTab() {
return testSubjects.find('infraAssetDetailsOverviewTab');
},
async getAssetDetailsKPITileValue(type: string) {
const element = await testSubjects.find(`infraAssetDetailsKPI${type}`);
const div = await element.findByClassName('echMetricText__value');
@ -103,6 +107,15 @@ export function AssetDetailsProvider({ getService }: FtrProviderContext) {
return section.findAllByCssSelector('[data-test-subj*="infraAssetDetailsMetricChart"]');
},
async getOverviewTabDockerMetricCharts(metric: string) {
const container = await testSubjects.find('infraAssetDetailsOverviewTabContent');
const section = await container.findByTestSubject(
`infraAssetDetailsDockerChartsSection${metric}`
);
return section.findAllByCssSelector('[data-test-subj*="infraAssetDetailsMetricChart"]');
},
async getOverviewTabKubernetesMetricCharts() {
const container = await testSubjects.find('infraAssetDetailsOverviewTabContent');
const section = await container.findByTestSubject(`infraAssetDetailsKubernetesChartsSection`);

View file

@ -212,10 +212,10 @@ export function InfraHomePageProvider({ getService, getPageObjects }: FtrProvide
return testSubjects.click('goToPods');
},
async goToDocker() {
async goToContainer() {
await testSubjects.click('openInventorySwitcher');
await testSubjects.find('goToHost');
return testSubjects.click('goToDocker');
return testSubjects.click('goToContainer');
},
async goToSettings() {