Skip to content

Commit

Permalink
[infra] hosts table metrics (elastic#173708)
Browse files Browse the repository at this point in the history
## Summary

Closes elastic#172475

- record performance event of the host table data loading
- infra synthtrace now installs `system` package and can produce all
datasets queried by host view
- [lens
viz](https://telemetry-v2-staging.elastic.dev/s/kibana-performance/app/visualize#/edit/f88d3180-9f10-11ee-b4ef-7533a045abfc?_g=h@97e8101&_a=h@a8f21d5)
(internal)

### Testing
- run journey with `PERFORMANCE_ENABLE_TELEMETRY=1 node
scripts/run_performance.js -v --journey-path
x-pack/performance/journeys/infra_hosts_view.ts`
- check data is ingested in lens

---------

Co-authored-by: kibanamachine <[email protected]>
Co-authored-by: Dzmitry Lemechko <[email protected]>
  • Loading branch information
3 people authored and CoenWarmer committed Feb 15, 2024
1 parent 82718b7 commit 585838c
Show file tree
Hide file tree
Showing 21 changed files with 352 additions and 32 deletions.
1 change: 1 addition & 0 deletions .buildkite/ftr_configs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@ enabled:
- x-pack/performance/journeys/tags_listing_page.ts
- x-pack/performance/journeys/cloud_security_dashboard.ts
- x-pack/performance/journeys/apm_service_inventory.ts
- x-pack/performance/journeys/infra_hosts_view.ts
- x-pack/test/custom_branding/config.ts
- x-pack/test/profiling_api_integration/cloud/config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/configs/serverless.config.ts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface ContainerDocument extends Fields {
'container.id': string;
'kubernetes.pod.uid': string;
'kubernetes.node.name': string;
'metricset.name'?: string;
}

export class Container extends Entity<ContainerDocument> {
Expand Down
93 changes: 90 additions & 3 deletions packages/kbn-apm-synthtrace-client/src/lib/infra/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,79 @@ import { Serializable } from '../serializable';
import { pod } from './pod';

interface HostDocument extends Fields {
'agent.id': string;
'host.hostname': string;
'host.name': string;
'metricset.name'?: string;
}

class Host extends Entity<HostDocument> {
metrics() {
cpu() {
return new HostMetrics({
...this.fields,
'system.cpu.total.norm.pct': 46,
'system.cpu.total.norm.pct': 0.094,
'system.cpu.user.pct': 0.805,
'system.cpu.system.pct': 0.704,
'system.cpu.cores': 16,
'metricset.period': 10000,
'metricset.name': 'cpu',
});
}

memory() {
return new HostMetrics({
...this.fields,
'system.memory.actual.free': 44704067584,
'system.memory.actual.used.bytes': 24015409152,
'system.memory.actual.used.pct': 0.35,
'system.memory.total': 68719476736,
'system.memory.used.bytes': 39964708864,
'system.memory.used.pct': 0.582,
'metricset.period': 10000,
'metricset.name': 'memory',
});
}

network() {
return new HostMetrics({
...this.fields,
'host.network.ingress.bytes': 2871285,
'host.network.egress.bytes': 2904987,
'metricset.period': 10000,
'metricset.name': 'network',
});
}

load() {
return new HostMetrics({
...this.fields,
'system.load': {
1: 3,
cores: 16,
},
'metricset.period': 10000,
'metricset.name': 'load',
});
}

filesystem() {
return new HostMetrics({
...this.fields,
'system.filesystem.used.pct': 12.23,
'metricset.period': 10000,
'metricset.name': 'filesystem',
});
}

diskio() {
return new HostMetrics({
...this.fields,
'system.diskio.read.count': 3538413,
'system.diskio.write.count': 4694333,
'system.diskio.read.bytes': 33147297792,
'system.diskio.write.bytes': 48595652608,
'metricset.period': 10000,
'metricset.name': 'diskio',
});
}

Expand All @@ -39,13 +104,35 @@ class Host extends Entity<HostDocument> {
}

export interface HostMetricsDocument extends HostDocument {
'system.cpu.total.norm.pct': number;
'agent.id': string;
'metricset.period'?: number;
'metricset.name'?: string;
'system.cpu.total.norm.pct'?: number;
'system.cpu.user.pct'?: number;
'system.cpu.system.pct'?: number;
'system.cpu.cores'?: number;
'system.diskio.read.count'?: number;
'system.diskio.write.count'?: number;
'system.diskio.read.bytes'?: number;
'system.diskio.write.bytes'?: number;
'system.filesystem.used.pct'?: number;
'system.memory.actual.used.pct'?: number;
'system.memory.total'?: number;
'system.memory.actual.used.bytes'?: number;
'system.memory.actual.free'?: number;
'system.memory.used.bytes'?: number;
'system.memory.used.pct'?: number;
'system.load'?: { 1: number; cores: number };
'host.network.ingress.bytes'?: number;
'host.network.egress.bytes'?: number;
}

class HostMetrics extends Serializable<HostMetricsDocument> {}

export function host(name: string): Host {
return new Host({
'agent.id': 'synthtrace',
'host.hostname': name,
'host.name': name,
});
}
1 change: 1 addition & 0 deletions packages/kbn-apm-synthtrace-client/src/lib/infra/pod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { container } from './container';
interface PodDocument extends Fields {
'kubernetes.pod.uid': string;
'kubernetes.node.name': string;
'metricset.name'?: string;
}

export class Pod extends Entity<PodDocument> {
Expand Down
1 change: 1 addition & 0 deletions packages/kbn-apm-synthtrace/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export { ApmSynthtraceEsClient } from './src/lib/apm/client/apm_synthtrace_es_cl
export { ApmSynthtraceKibanaClient } from './src/lib/apm/client/apm_synthtrace_kibana_client';

export { InfraSynthtraceEsClient } from './src/lib/infra/infra_synthtrace_es_client';
export { InfraSynthtraceKibanaClient } from './src/lib/infra/infra_synthtrace_kibana_client';

export { AssetsSynthtraceEsClient } from './src/lib/assets/assets_synthtrace_es_client';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import fetch from 'node-fetch';
import pRetry from 'p-retry';
import { Logger } from '../../utils/create_logger';
import { kibanaHeaders } from '../../shared/client_headers';

export class ApmSynthtraceKibanaClient {
private readonly logger: Logger;
Expand Down Expand Up @@ -63,12 +64,3 @@ export class ApmSynthtraceKibanaClient {
this.logger.info(`Installed APM package ${packageVersion}`);
}
}

function kibanaHeaders() {
return {
Accept: 'application/json',
'Content-Type': 'application/json',
'kbn-xsrf': 'kibana',
'elastic-api-version': '2023-10-31',
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,20 @@ function getRoutingTransform() {
return new Transform({
objectMode: true,
transform(document: ESDocumentWithOperation<InfraDocument>, encoding, callback) {
if ('host.hostname' in document) {
const metricset = document['metricset.name'];

if (metricset === 'cpu') {
document._index = 'metrics-system.cpu-default';
} else if (metricset === 'memory') {
document._index = 'metrics-system.memory-default';
} else if (metricset === 'network') {
document._index = 'metrics-system.network-default';
} else if (metricset === 'load') {
document._index = 'metrics-system.load-default';
} else if (metricset === 'filesystem') {
document._index = 'metrics-system.filesystem-default';
} else if (metricset === 'diskio') {
document._index = 'metrics-system.diskio-default';
} else if ('container.id' in document) {
document._index = 'metrics-kubernetes.container-default';
} else if ('kubernetes.pod.uid' in document) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* 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 { join } from 'path';
import fetch from 'node-fetch';
import pRetry from 'p-retry';
import { Logger } from '../utils/create_logger';
import { kibanaHeaders } from '../shared/client_headers';

export class InfraSynthtraceKibanaClient {
private readonly logger: Logger;
private target: string;

constructor(options: { logger: Logger; target: string; username: string; password: string }) {
this.logger = options.logger;
const url = new URL(options.target);
url.username = options.username;
url.password = options.password;
this.target = url.toString();
}

async fetchLatestSystemPackageVersion() {
const fleetPackageApiUrl = join(this.target, '/api/fleet/epm/packages/system?prerelease=true');
this.logger.debug(`Fetching latest System package version from ${fleetPackageApiUrl}`);
const response = await fetch(fleetPackageApiUrl, {
method: 'GET',
headers: kibanaHeaders(),
});

const responseJson = await response.json();

if (response.status !== 200) {
throw new Error(
`Failed to fetch latest System package version, received HTTP ${response.status} and message: ${responseJson.message}`
);
}

const { latestVersion } = responseJson.item;

return latestVersion as string;
}

async installSystemPackage(packageVersion: string) {
this.logger.debug(`Installing System package ${packageVersion}`);

const url = join(this.target, `/api/fleet/epm/packages/system/${packageVersion}`);
const response = await pRetry(() => {
return fetch(url, {
method: 'POST',
headers: kibanaHeaders(),
body: '{"force":true}',
});
});

const responseJson = await response.json();

if (!responseJson.items) {
throw new Error(
`Failed to install System package version ${packageVersion}, received HTTP ${response.status} and message: ${responseJson.message} for url ${url}`
);
}

this.logger.info(`Installed System package ${packageVersion}`);
}
}
16 changes: 16 additions & 0 deletions packages/kbn-apm-synthtrace/src/lib/shared/client_headers.ts
Original file line number Diff line number Diff line change
@@ -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 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.
*/

export function kibanaHeaders() {
return {
Accept: 'application/json',
'Content-Type': 'application/json',
'kbn-xsrf': 'kibana',
'elastic-api-version': '2023-10-31',
};
}
86 changes: 86 additions & 0 deletions x-pack/performance/journeys/infra_hosts_view.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* 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 { Journey } from '@kbn/journeys';
import {
createLogger,
InfraSynthtraceEsClient,
LogLevel,
InfraSynthtraceKibanaClient,
} from '@kbn/apm-synthtrace';
import { infra, timerange } from '@kbn/apm-synthtrace-client';
import { subj } from '@kbn/test-subj-selector';

export const journey = new Journey({
beforeSteps: async ({ kbnUrl, auth, es }) => {
const logger = createLogger(LogLevel.debug);
const synthKibanaClient = new InfraSynthtraceKibanaClient({
logger,
target: kbnUrl.get(),
username: auth.getUsername(),
password: auth.getPassword(),
});

const pkgVersion = await synthKibanaClient.fetchLatestSystemPackageVersion();
await synthKibanaClient.installSystemPackage(pkgVersion);

const synthEsClient = new InfraSynthtraceEsClient({
logger,
client: es,
refreshAfterIndex: true,
});

const start = Date.now() - 1000 * 60 * 10;
await synthEsClient.index(
generateHostsData({
from: new Date(start).toISOString(),
to: new Date().toISOString(),
count: 1000,
})
);
},
}).step('Navigate to Hosts view and load 500 hosts', async ({ page, kbnUrl, kibanaPage }) => {
await page.goto(
kbnUrl.get(
`app/metrics/hosts?_a=(dateRange:(from:now-15m,to:now),filters:!(),limit:500,panelFilters:!(),query:(language:kuery,query:''))`
)
);
// wait for table to be loaded
await page.waitForSelector(subj('hostsView-table-loaded'));
// wait for metric charts to be loaded
await kibanaPage.waitForCharts({ count: 5, timeout: 60000 });
});

export function generateHostsData({
from,
to,
count = 1,
}: {
from: string;
to: string;
count: number;
}) {
const range = timerange(from, to);

const hosts = Array(count)
.fill(0)
.map((_, idx) => infra.host(`my-host-${idx}`));

return range
.interval('30s')
.rate(1)
.generator((timestamp, index) =>
hosts.flatMap((host) => [
host.cpu().timestamp(timestamp),
host.memory().timestamp(timestamp),
host.network().timestamp(timestamp),
host.load().timestamp(timestamp),
host.filesystem().timestamp(timestamp),
host.diskio().timestamp(timestamp),
])
);
}
Loading

0 comments on commit 585838c

Please sign in to comment.