mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[ObsUX][Infra] Enhance Metadata tab and metadata in containers overview (#185786)
Closes https://github.com/elastic/kibana/issues/183355 ### Summary This PR adds the metadata fields to the container views overview and metadata fields in synthtrace for testing <img width="1788" alt="Screenshot 2024-06-11 at 15 27 37" src="a6f1e09f
-9f34-4895-94a9-3197f8562e86"> <img width="1788" alt="Screenshot 2024-06-11 at 15 28 03" src="14311b25
-8a39-435f-894f-dd04e0707d5f">
This commit is contained in:
parent
04add9a1f1
commit
09e27bed08
13 changed files with 233 additions and 29 deletions
|
@ -13,14 +13,22 @@ import { Serializable } from '../serializable';
|
|||
interface DockerContainerDocument extends Fields {
|
||||
'container.id': string;
|
||||
'metricset.name'?: string;
|
||||
'container.name'?: string;
|
||||
'container.image.name'?: string;
|
||||
'container.runtime'?: string;
|
||||
'host.name'?: string;
|
||||
'cloud.provider'?: string;
|
||||
'cloud.instance.id'?: string;
|
||||
'cloud.image.id'?: string;
|
||||
'event.dataset'?: string;
|
||||
}
|
||||
|
||||
export class DockerContainer extends Entity<DockerContainerDocument> {
|
||||
metrics() {
|
||||
return new DockerContainerMetrics({
|
||||
...this.fields,
|
||||
'docker.cpu.total.pct': 25,
|
||||
'docker.memory.usage.pct': 20,
|
||||
'docker.cpu.total.pct': 0.25,
|
||||
'docker.memory.usage.pct': 0.2,
|
||||
'docker.network.inbound.bytes': 100,
|
||||
'docker.network.outbound.bytes': 200,
|
||||
'docker.diskio.read.ops': 10,
|
||||
|
|
|
@ -15,6 +15,14 @@ interface K8sContainerDocument extends Fields {
|
|||
'kubernetes.pod.uid': string;
|
||||
'kubernetes.node.name': string;
|
||||
'metricset.name'?: string;
|
||||
'container.name'?: string;
|
||||
'container.image.name'?: string;
|
||||
'container.runtime'?: string;
|
||||
'host.name'?: string;
|
||||
'cloud.provider'?: string;
|
||||
'cloud.instance.id'?: string;
|
||||
'cloud.image.id'?: string;
|
||||
'event.dataset'?: string;
|
||||
}
|
||||
|
||||
export class K8sContainer extends Entity<K8sContainerDocument> {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { InfraDocument, infra } from '@kbn/apm-synthtrace-client';
|
||||
import { InfraDocument, infra, generateShortId } from '@kbn/apm-synthtrace-client';
|
||||
|
||||
import { Scenario } from '../cli/scenario';
|
||||
import { withClient } from '../lib/utils/with_client';
|
||||
|
@ -19,7 +19,20 @@ const scenario: Scenario<InfraDocument> = async (runOptions) => {
|
|||
|
||||
const CONTAINERS = Array(numContainers)
|
||||
.fill(0)
|
||||
.map((_, idx) => infra.dockerContainer(`container-${idx}`));
|
||||
.map((_, idx) => {
|
||||
const id = generateShortId();
|
||||
return infra.dockerContainer(id).defaults({
|
||||
'container.name': `container-${idx}`,
|
||||
'container.id': id,
|
||||
'container.runtime': 'docker',
|
||||
'container.image.name': 'image-1',
|
||||
'host.name': 'host-1',
|
||||
'cloud.instance.id': 'instance-1',
|
||||
'cloud.image.id': 'image-1',
|
||||
'cloud.provider': 'aws',
|
||||
'event.dataset': 'docker.container',
|
||||
});
|
||||
});
|
||||
|
||||
const containers = range
|
||||
.interval('30s')
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { InfraDocument, infra } from '@kbn/apm-synthtrace-client';
|
||||
import { InfraDocument, infra, generateShortId } from '@kbn/apm-synthtrace-client';
|
||||
|
||||
import { Scenario } from '../cli/scenario';
|
||||
import { withClient } from '../lib/utils/with_client';
|
||||
|
@ -19,7 +19,22 @@ const scenario: Scenario<InfraDocument> = async (runOptions) => {
|
|||
|
||||
const CONTAINERS = Array(numContainers)
|
||||
.fill(0)
|
||||
.map((_, idx) => infra.k8sContainer(`container-${idx}`, `pod-${idx}`, `node-${idx}`));
|
||||
.map((_, idx) => {
|
||||
const id = generateShortId();
|
||||
return infra.k8sContainer(id, `pod-${idx}`, `node-${idx}`).defaults({
|
||||
'container.id': id,
|
||||
'kubernetes.pod.uid': `pod-${idx}`,
|
||||
'kubernetes.node.name': `node-${idx}`,
|
||||
'container.name': `container-${idx}`,
|
||||
'container.runtime': 'docker',
|
||||
'container.image.name': 'image-1',
|
||||
'host.name': 'host-1',
|
||||
'cloud.instance.id': 'instance-1',
|
||||
'cloud.image.id': 'image-1',
|
||||
'cloud.provider': 'aws',
|
||||
'event.dataset': 'kubernetes.container',
|
||||
});
|
||||
});
|
||||
|
||||
const containers = range
|
||||
.interval('30s')
|
||||
|
|
|
@ -44,6 +44,13 @@ export const InfraMetadataHostRT = rt.partial({
|
|||
containerized: rt.boolean,
|
||||
});
|
||||
|
||||
export const InfraMetadataContainerRT = rt.partial({
|
||||
name: rt.string,
|
||||
id: rt.string,
|
||||
runtime: rt.string,
|
||||
imageName: rt.string,
|
||||
});
|
||||
|
||||
export const InfraMetadataInstanceRT = rt.partial({
|
||||
id: rt.string,
|
||||
name: rt.string,
|
||||
|
@ -71,6 +78,7 @@ export const InfraMetadataCloudRT = rt.partial({
|
|||
project: InfraMetadataProjectRT,
|
||||
machine: InfraMetadataMachineRT,
|
||||
region: rt.string,
|
||||
imageId: rt.string,
|
||||
});
|
||||
|
||||
export const InfraMetadataAgentRT = rt.partial({
|
||||
|
@ -82,6 +90,7 @@ export const InfraMetadataAgentRT = rt.partial({
|
|||
export const InfraMetadataInfoRT = rt.partial({
|
||||
cloud: InfraMetadataCloudRT,
|
||||
host: InfraMetadataHostRT,
|
||||
container: InfraMetadataContainerRT,
|
||||
agent: InfraMetadataAgentRT,
|
||||
'@timestamp': rt.string,
|
||||
});
|
||||
|
@ -89,6 +98,7 @@ export const InfraMetadataInfoRT = rt.partial({
|
|||
export const InfraMetadataInfoResponseRT = rt.partial({
|
||||
cloud: InfraMetadataCloudRT,
|
||||
host: InfraMetadataHostRT,
|
||||
container: InfraMetadataContainerRT,
|
||||
agent: InfraMetadataAgentRT,
|
||||
timestamp: rt.string,
|
||||
});
|
||||
|
@ -123,4 +133,6 @@ export type InfraMetadataMachine = rt.TypeOf<typeof InfraMetadataMachineRT>;
|
|||
|
||||
export type InfraMetadataHost = rt.TypeOf<typeof InfraMetadataHostRT>;
|
||||
|
||||
export type InfraMetadataContainer = rt.TypeOf<typeof InfraMetadataContainerRT>;
|
||||
|
||||
export type InfraMetadataOS = rt.TypeOf<typeof InfraMetadataOSRT>;
|
||||
|
|
|
@ -18,7 +18,10 @@ interface FieldsByCategory {
|
|||
export const getAllFields = (metadata: InfraMetadata | null) => {
|
||||
if (!metadata?.info) return [];
|
||||
|
||||
const mapNestedProperties = (category: 'cloud' | 'host' | 'agent', property: string) => {
|
||||
const mapNestedProperties = (
|
||||
category: 'cloud' | 'host' | 'agent' | 'container',
|
||||
property: string
|
||||
) => {
|
||||
const fieldsByCategory: FieldsByCategory = metadata?.info?.[`${category}`] ?? {};
|
||||
if (fieldsByCategory.hasOwnProperty(property)) {
|
||||
const value = fieldsByCategory[property];
|
||||
|
@ -54,8 +57,11 @@ export const getAllFields = (metadata: InfraMetadata | null) => {
|
|||
const host = Object.keys(metadata?.info?.host ?? {}).flatMap((prop) =>
|
||||
mapNestedProperties('host', prop)
|
||||
);
|
||||
const container = Object.keys(metadata?.info?.container ?? {}).flatMap((prop) =>
|
||||
mapNestedProperties('container', prop)
|
||||
);
|
||||
|
||||
return prune([...host, ...agent, ...cloud]);
|
||||
return prune([...host, ...container, ...agent, ...cloud]);
|
||||
};
|
||||
|
||||
const prune = (fields: Field[]) => fields.filter((f) => !!f?.value);
|
||||
|
|
|
@ -21,15 +21,39 @@ const columnTitles = {
|
|||
hostOsVersion: i18n.translate('xpack.infra.assetDetails.overview.metadataHostOsVersionHeading', {
|
||||
defaultMessage: 'Host OS version',
|
||||
}),
|
||||
hostName: i18n.translate('xpack.infra.assetDetails.overview.metadataHostNameHeading', {
|
||||
defaultMessage: 'Host name',
|
||||
}),
|
||||
cloudProvider: i18n.translate('xpack.infra.assetDetails.overview.metadataCloudProviderHeading', {
|
||||
defaultMessage: 'Cloud provider',
|
||||
}),
|
||||
cloudInstanceId: i18n.translate(
|
||||
'xpack.infra.assetDetails.overview.metadataCloudInstanceIdHeading',
|
||||
{
|
||||
defaultMessage: 'Cloud instance ID',
|
||||
}
|
||||
),
|
||||
cloudImageId: i18n.translate('xpack.infra.assetDetails.overview.metadataCloudImageIdHeading', {
|
||||
defaultMessage: 'Cloud image ID',
|
||||
}),
|
||||
operatingSystem: i18n.translate(
|
||||
'xpack.infra.assetDetails.overview.metadataOperatingSystemHeading',
|
||||
{
|
||||
defaultMessage: 'Operating system',
|
||||
}
|
||||
),
|
||||
containerId: i18n.translate('xpack.infra.assetDetails.overview.metadataContainerIdHeading', {
|
||||
defaultMessage: 'Container ID',
|
||||
}),
|
||||
containerImageName: i18n.translate(
|
||||
'xpack.infra.assetDetails.overview.metadataContainerImageNameHeading',
|
||||
{
|
||||
defaultMessage: 'Container image name',
|
||||
}
|
||||
),
|
||||
runtime: i18n.translate('xpack.infra.assetDetails.overview.metadataRuntimeHeading', {
|
||||
defaultMessage: 'Runtime',
|
||||
}),
|
||||
};
|
||||
|
||||
type MetadataFields = 'hostIp' | 'hostOsVersion';
|
||||
|
|
|
@ -29,6 +29,7 @@ import { Section } from '../../../components/section';
|
|||
interface MetadataSummaryProps {
|
||||
metadata: InfraMetadata | null;
|
||||
loading: boolean;
|
||||
assetType: string;
|
||||
}
|
||||
interface MetadataSummaryWrapperProps {
|
||||
visibleMetadata: MetadataData[];
|
||||
|
@ -42,7 +43,7 @@ export interface MetadataData {
|
|||
tooltipLink?: string;
|
||||
}
|
||||
|
||||
const extendedMetadata = (metadataInfo: InfraMetadata['info']): MetadataData[] => [
|
||||
const hostExtendedMetadata = (metadataInfo: InfraMetadata['info']): MetadataData[] => [
|
||||
{
|
||||
field: 'cloudProvider',
|
||||
value: metadataInfo?.cloud?.provider,
|
||||
|
@ -56,7 +57,7 @@ const extendedMetadata = (metadataInfo: InfraMetadata['info']): MetadataData[] =
|
|||
},
|
||||
];
|
||||
|
||||
const metadataData = (metadataInfo: InfraMetadata['info']): MetadataData[] => [
|
||||
const hostMetadataData = (metadataInfo: InfraMetadata['info']): MetadataData[] => [
|
||||
{
|
||||
field: 'hostIp',
|
||||
value: metadataInfo?.host?.ip,
|
||||
|
@ -70,6 +71,47 @@ const metadataData = (metadataInfo: InfraMetadata['info']): MetadataData[] => [
|
|||
},
|
||||
];
|
||||
|
||||
const containerExtendedMetadata = (metadataInfo: InfraMetadata['info']): MetadataData[] => [
|
||||
{
|
||||
field: 'runtime',
|
||||
value: metadataInfo?.container?.runtime,
|
||||
tooltipFieldLabel: 'container.runtime',
|
||||
},
|
||||
{
|
||||
field: 'cloudInstanceId',
|
||||
value: metadataInfo?.cloud?.instance?.id,
|
||||
tooltipFieldLabel: 'cloud.instance.id',
|
||||
},
|
||||
{
|
||||
field: 'cloudImageId',
|
||||
value: metadataInfo?.cloud?.imageId,
|
||||
tooltipFieldLabel: 'cloud.image.id',
|
||||
},
|
||||
{
|
||||
field: 'cloudProvider',
|
||||
value: metadataInfo?.cloud?.provider,
|
||||
tooltipFieldLabel: 'cloud.provider',
|
||||
},
|
||||
];
|
||||
|
||||
const containerMetadataData = (metadataInfo: InfraMetadata['info']): MetadataData[] => [
|
||||
{
|
||||
field: 'containerId',
|
||||
value: metadataInfo?.container?.id,
|
||||
tooltipFieldLabel: 'container.id',
|
||||
},
|
||||
{
|
||||
field: 'containerImageName',
|
||||
value: metadataInfo?.container?.imageName,
|
||||
tooltipFieldLabel: 'container.image.name',
|
||||
},
|
||||
{
|
||||
field: 'hostName',
|
||||
value: metadataInfo?.host?.name,
|
||||
tooltipFieldLabel: 'host.name',
|
||||
},
|
||||
];
|
||||
|
||||
const MetadataSummaryListWrapper = ({
|
||||
loading: metadataLoading,
|
||||
visibleMetadata,
|
||||
|
@ -138,13 +180,54 @@ const MetadataSummaryListWrapper = ({
|
|||
</Section>
|
||||
);
|
||||
};
|
||||
export const MetadataSummaryList = ({ metadata, loading }: MetadataSummaryProps) => (
|
||||
<MetadataSummaryListWrapper
|
||||
visibleMetadata={[...metadataData(metadata?.info), ...extendedMetadata(metadata?.info)]}
|
||||
loading={loading}
|
||||
/>
|
||||
);
|
||||
export const MetadataSummaryList = ({ metadata, loading, assetType }: MetadataSummaryProps) => {
|
||||
switch (assetType) {
|
||||
case 'host':
|
||||
return (
|
||||
<MetadataSummaryListWrapper
|
||||
visibleMetadata={[
|
||||
...hostMetadataData(metadata?.info),
|
||||
...hostExtendedMetadata(metadata?.info),
|
||||
]}
|
||||
loading={loading}
|
||||
/>
|
||||
);
|
||||
case 'container':
|
||||
return (
|
||||
<MetadataSummaryListWrapper
|
||||
visibleMetadata={[
|
||||
...containerMetadataData(metadata?.info),
|
||||
...containerExtendedMetadata(metadata?.info),
|
||||
]}
|
||||
loading={loading}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return <MetadataSummaryListWrapper visibleMetadata={[]} loading={loading} />;
|
||||
}
|
||||
};
|
||||
|
||||
export const MetadataSummaryListCompact = ({ metadata, loading }: MetadataSummaryProps) => (
|
||||
<MetadataSummaryListWrapper visibleMetadata={metadataData(metadata?.info)} loading={loading} />
|
||||
);
|
||||
export const MetadataSummaryListCompact = ({
|
||||
metadata,
|
||||
loading,
|
||||
assetType,
|
||||
}: MetadataSummaryProps) => {
|
||||
switch (assetType) {
|
||||
case 'host':
|
||||
return (
|
||||
<MetadataSummaryListWrapper
|
||||
visibleMetadata={hostMetadataData(metadata?.info)}
|
||||
loading={loading}
|
||||
/>
|
||||
);
|
||||
case 'container':
|
||||
return (
|
||||
<MetadataSummaryListWrapper
|
||||
visibleMetadata={containerMetadataData(metadata?.info)}
|
||||
loading={loading}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return <MetadataSummaryListWrapper visibleMetadata={[]} loading={loading} />;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -40,9 +40,13 @@ export const Overview = () => {
|
|||
const state = useIntersectingState(ref, { dateRange });
|
||||
|
||||
const metadataSummarySection = isFullPageView ? (
|
||||
<MetadataSummaryList metadata={metadata} loading={metadataLoading} />
|
||||
<MetadataSummaryList metadata={metadata} loading={metadataLoading} assetType={asset.type} />
|
||||
) : (
|
||||
<MetadataSummaryListCompact metadata={metadata} loading={metadataLoading} />
|
||||
<MetadataSummaryListCompact
|
||||
metadata={metadata}
|
||||
loading={metadataLoading}
|
||||
assetType={asset.type}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -59,7 +59,7 @@ export const getNodeInfo = async (
|
|||
index: sourceConfiguration.metricAlias,
|
||||
body: {
|
||||
size: 1,
|
||||
_source: ['host.*', 'cloud.*', 'agent.*', TIMESTAMP_FIELD],
|
||||
_source: ['host.*', 'cloud.*', 'agent.*', 'container.*', TIMESTAMP_FIELD],
|
||||
sort: [{ [TIMESTAMP_FIELD]: 'desc' }],
|
||||
query: {
|
||||
bool: {
|
||||
|
|
|
@ -60,7 +60,19 @@ export function generateDockerContainersData({
|
|||
|
||||
const containers = Array(count)
|
||||
.fill(0)
|
||||
.map((_, idx) => infra.dockerContainer(`container-id-${idx}`));
|
||||
.map((_, idx) =>
|
||||
infra.dockerContainer(`container-id-${idx}`).defaults({
|
||||
'container.name': `container-id-${idx}`,
|
||||
'container.id': `container-id-${idx}`,
|
||||
'container.runtime': 'docker',
|
||||
'container.image.name': 'image-1',
|
||||
'host.name': 'host-1',
|
||||
'cloud.instance.id': 'instance-1',
|
||||
'cloud.image.id': 'image-1',
|
||||
'cloud.provider': 'aws',
|
||||
'event.dataset': 'docker.container',
|
||||
})
|
||||
);
|
||||
|
||||
return range
|
||||
.interval('30s')
|
||||
|
|
|
@ -292,10 +292,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
[
|
||||
{ metric: 'cpuUsage', value: '2,500.0%' },
|
||||
{ metric: 'memoryUsage', value: '2,000.0%' },
|
||||
{ metric: 'cpuUsage', value: '25.0%' },
|
||||
{ metric: 'memoryUsage', value: '20.0%' },
|
||||
].forEach(({ metric, value }) => {
|
||||
it.skip(`${metric} tile should show ${value}`, async () => {
|
||||
it(`${metric} tile should show ${value}`, async () => {
|
||||
await retry.tryForTime(3 * 1000, async () => {
|
||||
const tileValue = await pageObjects.assetDetails.getAssetDetailsKPITileValue(
|
||||
metric
|
||||
|
|
|
@ -38,6 +38,8 @@ const START_HOST_KUBERNETES_SECTION_DATE = moment.utc(
|
|||
const END_HOST_KUBERNETES_SECTION_DATE = moment.utc(
|
||||
DATES.metricsAndLogs.hosts.kubernetesSectionEndDate
|
||||
);
|
||||
const START_CONTAINER_DATE = moment.utc(DATE_WITH_DOCKER_DATA_FROM);
|
||||
const END_CONTAINER_DATE = moment.utc(DATE_WITH_DOCKER_DATA_TO);
|
||||
|
||||
export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
||||
const observability = getService('observability');
|
||||
|
@ -651,25 +653,42 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
describe('when container asset view is enabled', () => {
|
||||
it('should show asset container details page', async () => {
|
||||
before(async () => {
|
||||
await setInfrastructureContainerAssetViewUiSetting(true);
|
||||
await navigateToNodeDetails('container-id-0', 'container-id-0', 'container');
|
||||
await pageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
||||
await pageObjects.timePicker.setAbsoluteRange(
|
||||
START_CONTAINER_DATE.format(DATE_PICKER_FORMAT),
|
||||
END_CONTAINER_DATE.format(DATE_PICKER_FORMAT)
|
||||
);
|
||||
});
|
||||
it('should show asset container details page', async () => {
|
||||
await pageObjects.assetDetails.getOverviewTab();
|
||||
});
|
||||
|
||||
[
|
||||
{ metric: 'cpu', chartsCount: 1 },
|
||||
{ metric: 'memory', chartsCount: 1 },
|
||||
{ metric: 'disk', chartsCount: 1 },
|
||||
{ metric: 'network', chartsCount: 1 },
|
||||
].forEach(({ metric, chartsCount }) => {
|
||||
it.skip(`should render ${chartsCount} ${metric} chart(s) in the Metrics section`, async () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Metadata Tab', () => {
|
||||
before(async () => {
|
||||
await pageObjects.assetDetails.clickMetadataTab();
|
||||
});
|
||||
|
||||
it('should show metadata table', async () => {
|
||||
await pageObjects.assetDetails.metadataTableExists();
|
||||
});
|
||||
});
|
||||
describe('Logs Tab', () => {
|
||||
before(async () => {
|
||||
await pageObjects.assetDetails.clickLogsTab();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue