diff --git a/x-pack/plugins/infra/common/http_api/snapshot_api.ts b/x-pack/plugins/infra/common/http_api/snapshot_api.ts index 9c4fd65ce3fec..eb39f6a440a79 100644 --- a/x-pack/plugins/infra/common/http_api/snapshot_api.ts +++ b/x-pack/plugins/infra/common/http_api/snapshot_api.ts @@ -17,6 +17,9 @@ export const SnapshotNodePathRT = rt.intersection([ rt.partial({ ip: rt.union([rt.string, rt.null]), }), + rt.partial({ + os: rt.union([rt.string, rt.null]), + }), ]); const SnapshotNodeMetricOptionalRT = rt.partial({ diff --git a/x-pack/plugins/infra/common/inventory_models/host/index.ts b/x-pack/plugins/infra/common/inventory_models/host/index.ts index 8e2582c5e1f6f..7420ca3f15939 100644 --- a/x-pack/plugins/infra/common/inventory_models/host/index.ts +++ b/x-pack/plugins/infra/common/inventory_models/host/index.ts @@ -33,6 +33,7 @@ export const host: InventoryModel = { fields: { id: 'host.name', name: 'host.name', + os: 'host.os.name', ip: 'host.ip', }, metrics, diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/index.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/index.ts index 2190140cc0a5e..8a60c4a066502 100644 --- a/x-pack/plugins/infra/common/inventory_models/host/metrics/index.ts +++ b/x-pack/plugins/infra/common/inventory_models/host/metrics/index.ts @@ -6,10 +6,12 @@ */ import { cpu } from './snapshot/cpu'; +import { cpuCores } from './snapshot/cpu_cores'; import { count } from '../../shared/metrics/snapshot/count'; import { load } from './snapshot/load'; import { logRate } from './snapshot/log_rate'; import { memory } from './snapshot/memory'; +import { memoryTotal } from './snapshot/memory_total'; import { rx } from './snapshot/rx'; import { tx } from './snapshot/tx'; @@ -33,7 +35,7 @@ import { hostDockerInfo } from './tsvb/host_docker_info'; import { InventoryMetrics } from '../../types'; -const exposedHostSnapshotMetrics = { cpu, load, logRate, memory, rx, tx }; +const exposedHostSnapshotMetrics = { cpu, load, logRate, memory, rx, tx, cpuCores, memoryTotal }; // not sure why this is the only model with "count" const hostSnapshotMetrics = { count, ...exposedHostSnapshotMetrics }; diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/snapshot/cpu_cores.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/snapshot/cpu_cores.ts new file mode 100644 index 0000000000000..3cc90ebfdca95 --- /dev/null +++ b/x-pack/plugins/infra/common/inventory_models/host/metrics/snapshot/cpu_cores.ts @@ -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 { MetricsUIAggregation } from '../../../types'; + +export const cpuCores: MetricsUIAggregation = { + cpuCores: { + max: { + field: 'system.cpu.cores', + }, + }, +}; diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/snapshot/memory_total.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/snapshot/memory_total.ts new file mode 100644 index 0000000000000..c3d0a8063b93f --- /dev/null +++ b/x-pack/plugins/infra/common/inventory_models/host/metrics/snapshot/memory_total.ts @@ -0,0 +1,28 @@ +/* + * 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 { MetricsUIAggregation } from '../../../types'; + +export const memoryTotal: MetricsUIAggregation = { + memory_total: { + avg: { + field: 'system.memory.total', + }, + }, + memoryTotal: { + bucket_script: { + buckets_path: { + memoryTotal: 'memory_total', + }, + script: { + source: 'params.memoryTotal / 1000000', // Convert to MB + lang: 'painless', + }, + gap_policy: 'skip', + }, + }, +}; diff --git a/x-pack/plugins/infra/common/inventory_models/types.ts b/x-pack/plugins/infra/common/inventory_models/types.ts index 9459933a489d3..8c0118d40fa4b 100644 --- a/x-pack/plugins/infra/common/inventory_models/types.ts +++ b/x-pack/plugins/infra/common/inventory_models/types.ts @@ -339,8 +339,10 @@ export type MetricsUIAggregation = rt.TypeOf; export const SnapshotMetricTypeKeys = { count: null, cpu: null, + cpuCores: null, load: null, memory: null, + memoryTotal: null, tx: null, rx: null, logRate: null, @@ -382,6 +384,7 @@ export interface InventoryModel { fields: { id: string; name: string; + os?: string; ip?: string; }; crosslinkSupport: { diff --git a/x-pack/plugins/infra/kibana.json b/x-pack/plugins/infra/kibana.json index 4290fb0382cc6..2226c89ab90f6 100644 --- a/x-pack/plugins/infra/kibana.json +++ b/x-pack/plugins/infra/kibana.json @@ -15,8 +15,7 @@ "triggersActionsUi", "observability", "ruleRegistry", - "unifiedSearch", - "lens" + "unifiedSearch" ], "optionalPlugins": ["ml", "home", "embeddable", "osquery"], "server": true, diff --git a/x-pack/plugins/infra/public/apps/metrics_app.tsx b/x-pack/plugins/infra/public/apps/metrics_app.tsx index ce8123b5f2223..159c6dab7a66e 100644 --- a/x-pack/plugins/infra/public/apps/metrics_app.tsx +++ b/x-pack/plugins/infra/public/apps/metrics_app.tsx @@ -47,7 +47,6 @@ export const renderApp = ( return () => { core.chrome.docTitle.reset(); ReactDOM.unmountComponentAtNode(element); - plugins.data.search.session.clear(); }; }; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx index 143f7fedb1420..f81f80faca73e 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx @@ -5,549 +5,24 @@ * 2.0. */ -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { TypedLensByValueInput } from '@kbn/lens-plugin/public'; -import type { Query, TimeRange } from '@kbn/es-query'; -import React, { useState } from 'react'; -import type { DataView } from '@kbn/data-views-plugin/public'; -import { i18n } from '@kbn/i18n'; -import { NoData } from '../../../../components/empty_states'; -import { InfraClientStartDeps } from '../../../../types'; - -const getLensHostsTable = ( - metricsDataView: DataView, - query: Query -): TypedLensByValueInput['attributes'] => - ({ - visualizationType: 'lnsDatatable', - title: 'Lens visualization', - references: [ - { - id: metricsDataView.id, - name: 'indexpattern-datasource-current-indexpattern', - type: 'index-pattern', - }, - { - id: metricsDataView.id, - name: 'indexpattern-datasource-layer-cbe5d8a0-381d-49bf-b8ac-f8f312ec7129', - type: 'index-pattern', - }, - ], - state: { - datasourceStates: { - formBased: { - layers: { - 'cbe5d8a0-381d-49bf-b8ac-f8f312ec7129': { - columns: { - '8d17458d-31af-41d1-a23c-5180fd960bee': { - label: 'Name', - dataType: 'string', - operationType: 'terms', - scale: 'ordinal', - sourceField: 'host.name', - isBucketed: true, - params: { - size: 10000, - orderBy: { - type: 'column', - columnId: '467de550-9186-4e18-8cfa-bce07087801a', - }, - orderDirection: 'desc', - otherBucket: true, - missingBucket: false, - parentFormat: { - id: 'terms', - }, - }, - customLabel: true, - }, - '155fc728-d010-498e-8126-0bc46cad2be2': { - label: 'Operating system', - dataType: 'string', - operationType: 'terms', - scale: 'ordinal', - sourceField: 'host.os.type', - isBucketed: true, - params: { - size: 10000, - orderBy: { - type: 'column', - columnId: '467de550-9186-4e18-8cfa-bce07087801a', - }, - orderDirection: 'desc', - otherBucket: false, - missingBucket: false, - parentFormat: { - id: 'terms', - }, - }, - customLabel: true, - }, - '467de550-9186-4e18-8cfa-bce07087801a': { - label: '# of CPUs', - dataType: 'number', - operationType: 'max', - sourceField: 'system.cpu.cores', - isBucketed: false, - scale: 'ratio', - params: { - emptyAsNull: true, - }, - customLabel: true, - }, - '3eca2307-228e-4842-a023-57e15c8c364d': { - label: 'Disk latency (avg.)', - dataType: 'number', - operationType: 'formula', - isBucketed: false, - scale: 'ratio', - params: { - formula: 'average(system.diskio.io.time) / 1000', - isFormulaBroken: false, - format: { - id: 'number', - params: { - decimals: 0, - suffix: 'ms', - }, - }, - }, - references: ['3eca2307-228e-4842-a023-57e15c8c364dX1'], - customLabel: true, - }, - '0a9bd0fa-9966-489b-8c95-70997a7aad4c': { - label: 'Memory total (avg.)', - dataType: 'number', - operationType: 'formula', - isBucketed: false, - scale: 'ratio', - params: { - formula: 'average(system.memory.total)', - isFormulaBroken: false, - format: { - id: 'bytes', - params: { - decimals: 0, - }, - }, - }, - references: ['0a9bd0fa-9966-489b-8c95-70997a7aad4cX0'], - customLabel: true, - }, - 'fe5a4d7d-6f48-45ab-974c-96bc864ac36f': { - label: 'Memory usage (avg.)', - dataType: 'number', - operationType: 'formula', - isBucketed: false, - scale: 'ratio', - params: { - formula: 'average(system.memory.used.pct)', - isFormulaBroken: false, - format: { - id: 'percent', - params: { - decimals: 0, - }, - }, - }, - references: ['fe5a4d7d-6f48-45ab-974c-96bc864ac36fX0'], - customLabel: true, - }, - '0a9bd0fa-9966-489b-8c95-70997a7aad4cX0': { - label: 'Part of Memory Total (avg)', - dataType: 'number', - operationType: 'average', - sourceField: 'system.memory.total', - isBucketed: false, - scale: 'ratio', - params: { - emptyAsNull: false, - }, - customLabel: true, - }, - 'fe5a4d7d-6f48-45ab-974c-96bc864ac36fX0': { - label: 'Part of Memory Usage (avg)', - dataType: 'number', - operationType: 'average', - sourceField: 'system.memory.used.pct', - isBucketed: false, - scale: 'ratio', - params: { - emptyAsNull: false, - }, - customLabel: true, - }, - '3eca2307-228e-4842-a023-57e15c8c364dX0': { - label: 'Part of Disk Latency (avg ms)', - dataType: 'number', - operationType: 'average', - sourceField: 'system.diskio.io.time', - isBucketed: false, - scale: 'ratio', - params: { - emptyAsNull: false, - }, - customLabel: true, - }, - '3eca2307-228e-4842-a023-57e15c8c364dX1': { - label: 'Part of Disk Latency (avg ms)', - dataType: 'number', - operationType: 'math', - isBucketed: false, - scale: 'ratio', - params: { - tinymathAst: { - type: 'function', - name: 'divide', - args: ['3eca2307-228e-4842-a023-57e15c8c364dX0', 1000], - location: { - min: 0, - max: 37, - }, - text: 'average(system.diskio.io.time) / 1000', - }, - }, - references: ['3eca2307-228e-4842-a023-57e15c8c364dX0'], - customLabel: true, - }, - '02e9d54c-bbe0-42dc-839c-55080a29838dX0': { - label: 'Part of RX (avg)', - dataType: 'number', - operationType: 'average', - sourceField: 'host.network.ingress.bytes', - isBucketed: false, - scale: 'ratio', - filter: { - query: 'host.network.ingress.bytes: *', - language: 'kuery', - }, - params: { - emptyAsNull: false, - }, - customLabel: true, - }, - '02e9d54c-bbe0-42dc-839c-55080a29838dX1': { - label: 'Part of RX (avg)', - dataType: 'number', - operationType: 'max', - sourceField: 'metricset.period', - isBucketed: false, - scale: 'ratio', - filter: { - query: 'host.network.ingress.bytes: *', - language: 'kuery', - }, - params: { - emptyAsNull: false, - }, - customLabel: true, - }, - '02e9d54c-bbe0-42dc-839c-55080a29838dX2': { - label: 'Part of RX (avg)', - dataType: 'number', - operationType: 'math', - isBucketed: false, - scale: 'ratio', - params: { - tinymathAst: { - type: 'function', - name: 'divide', - args: [ - { - type: 'function', - name: 'multiply', - args: ['02e9d54c-bbe0-42dc-839c-55080a29838dX0', 8], - location: { - min: 1, - max: 40, - }, - text: 'average(host.network.ingress.bytes) * 8', - }, - { - type: 'function', - name: 'divide', - args: ['02e9d54c-bbe0-42dc-839c-55080a29838dX1', 1000], - location: { - min: 45, - max: 73, - }, - text: 'max(metricset.period) / 1000', - }, - ], - location: { - min: 0, - max: 75, - }, - text: '(average(host.network.ingress.bytes) * 8) / (max(metricset.period) / 1000)\n', - }, - }, - references: [ - '02e9d54c-bbe0-42dc-839c-55080a29838dX0', - '02e9d54c-bbe0-42dc-839c-55080a29838dX1', - ], - customLabel: true, - }, - '02e9d54c-bbe0-42dc-839c-55080a29838d': { - label: 'RX (avg.)', - dataType: 'number', - operationType: 'formula', - isBucketed: false, - scale: 'ratio', - params: { - formula: - '(average(host.network.ingress.bytes) * 8) / (max(metricset.period) / 1000)\n', - isFormulaBroken: false, - format: { - id: 'bits', - params: { - decimals: 0, - suffix: '/s', - }, - }, - }, - references: ['02e9d54c-bbe0-42dc-839c-55080a29838dX2'], - filter: { - query: 'host.network.ingress.bytes: *', - language: 'kuery', - }, - customLabel: true, - }, - '7802ef93-622c-44df-94fa-03eec01bb7b6X0': { - label: 'Part of TX', - dataType: 'number', - operationType: 'average', - sourceField: 'host.network.egress.bytes', - isBucketed: false, - scale: 'ratio', - filter: { - query: 'host.network.egress.bytes: *', - language: 'kuery', - }, - params: { - emptyAsNull: false, - }, - customLabel: true, - }, - '7802ef93-622c-44df-94fa-03eec01bb7b6X1': { - label: 'Part of TX', - dataType: 'number', - operationType: 'max', - sourceField: 'metricset.period', - isBucketed: false, - scale: 'ratio', - filter: { - query: 'host.network.egress.bytes: *', - language: 'kuery', - }, - params: { - emptyAsNull: false, - }, - customLabel: true, - }, - '7802ef93-622c-44df-94fa-03eec01bb7b6X2': { - label: 'Part of TX', - dataType: 'number', - operationType: 'math', - isBucketed: false, - scale: 'ratio', - params: { - tinymathAst: { - type: 'function', - name: 'divide', - args: [ - { - type: 'function', - name: 'multiply', - args: ['7802ef93-622c-44df-94fa-03eec01bb7b6X0', 8], - location: { - min: 1, - max: 39, - }, - text: 'average(host.network.egress.bytes) * 8', - }, - { - type: 'function', - name: 'divide', - args: ['7802ef93-622c-44df-94fa-03eec01bb7b6X1', 1000], - location: { - min: 44, - max: 72, - }, - text: 'max(metricset.period) / 1000', - }, - ], - location: { - min: 0, - max: 74, - }, - text: '(average(host.network.egress.bytes) * 8) / (max(metricset.period) / 1000)\n', - }, - }, - references: [ - '7802ef93-622c-44df-94fa-03eec01bb7b6X0', - '7802ef93-622c-44df-94fa-03eec01bb7b6X1', - ], - customLabel: true, - }, - '7802ef93-622c-44df-94fa-03eec01bb7b6': { - label: 'TX (avg.)', - dataType: 'number', - operationType: 'formula', - isBucketed: false, - scale: 'ratio', - params: { - formula: - '(average(host.network.egress.bytes) * 8) / (max(metricset.period) / 1000)\n', - isFormulaBroken: false, - format: { - id: 'bits', - params: { - decimals: 0, - suffix: '/s', - }, - }, - }, - references: ['7802ef93-622c-44df-94fa-03eec01bb7b6X2'], - filter: { - query: 'host.network.egress.bytes: *', - language: 'kuery', - }, - customLabel: true, - }, - }, - columnOrder: [ - '8d17458d-31af-41d1-a23c-5180fd960bee', - '155fc728-d010-498e-8126-0bc46cad2be2', - '467de550-9186-4e18-8cfa-bce07087801a', - '3eca2307-228e-4842-a023-57e15c8c364d', - '02e9d54c-bbe0-42dc-839c-55080a29838d', - '7802ef93-622c-44df-94fa-03eec01bb7b6', - '0a9bd0fa-9966-489b-8c95-70997a7aad4c', - 'fe5a4d7d-6f48-45ab-974c-96bc864ac36f', - '0a9bd0fa-9966-489b-8c95-70997a7aad4cX0', - 'fe5a4d7d-6f48-45ab-974c-96bc864ac36fX0', - '3eca2307-228e-4842-a023-57e15c8c364dX0', - '3eca2307-228e-4842-a023-57e15c8c364dX1', - '02e9d54c-bbe0-42dc-839c-55080a29838dX0', - '02e9d54c-bbe0-42dc-839c-55080a29838dX1', - '02e9d54c-bbe0-42dc-839c-55080a29838dX2', - '7802ef93-622c-44df-94fa-03eec01bb7b6X0', - '7802ef93-622c-44df-94fa-03eec01bb7b6X1', - '7802ef93-622c-44df-94fa-03eec01bb7b6X2', - ], - incompleteColumns: {}, - indexPatternId: '305688db-9e02-4046-acc1-5d0d8dd73ef6', - }, - }, - }, - }, - visualization: { - layerId: 'cbe5d8a0-381d-49bf-b8ac-f8f312ec7129', - layerType: 'data', - columns: [ - { - columnId: '8d17458d-31af-41d1-a23c-5180fd960bee', - width: 296.16666666666663, - isTransposed: false, - }, - { - columnId: '155fc728-d010-498e-8126-0bc46cad2be2', - isTransposed: false, - width: 152.36666666666667, - }, - { - columnId: '467de550-9186-4e18-8cfa-bce07087801a', - isTransposed: false, - width: 121.11666666666667, - }, - { - columnId: '3eca2307-228e-4842-a023-57e15c8c364d', - isTransposed: false, - }, - { - columnId: '0a9bd0fa-9966-489b-8c95-70997a7aad4c', - isTransposed: false, - }, - { - columnId: 'fe5a4d7d-6f48-45ab-974c-96bc864ac36f', - isTransposed: false, - }, - { - isTransposed: false, - columnId: '02e9d54c-bbe0-42dc-839c-55080a29838d', - }, - { - isTransposed: false, - columnId: '7802ef93-622c-44df-94fa-03eec01bb7b6', - }, - ], - paging: { - size: 10, - enabled: true, - }, - headerRowHeight: 'custom', - headerRowHeightLines: 2, - rowHeight: 'single', - rowHeightLines: 1, - }, - filters: [], - query, - }, - } as TypedLensByValueInput['attributes']); +import React from 'react'; +import { EuiBasicTable } from '@elastic/eui'; +import type { SnapshotNode } from '../../../../../common/http_api'; +import { HostsTableColumns } from './hosts_table_columns'; +import { useHostTable } from '../hooks/use_host_table'; interface Props { - dataView: DataView; - timeRange: TimeRange; - query: Query; - searchSessionId: string; - onRefetch: () => void; - onLoading: (isLoading: boolean) => void; - isLensLoading: boolean; + nodes: SnapshotNode[]; } -export const HostsTable: React.FunctionComponent = ({ - dataView, - timeRange, - query, - searchSessionId, - onRefetch, - onLoading, - isLensLoading, -}) => { - const { - services: { lens }, - } = useKibana(); - const LensComponent = lens?.EmbeddableComponent; - const [noData, setNoData] = useState(false); - if (noData && !isLensLoading) { - return ( - - ); - } +export const HostsTable: React.FunctionComponent = ({ nodes }) => { + const items = useHostTable(nodes); + return ( - { - if (!isLoading && adapters?.tables) { - setNoData(adapters?.tables.tables.default?.rows.length === 0); - onLoading(false); - } - }} + ); }; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table_columns.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table_columns.tsx new file mode 100644 index 0000000000000..7bd8184b0e464 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table_columns.tsx @@ -0,0 +1,99 @@ +/* + * 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 { EuiBasicTableColumn } from '@elastic/eui'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiText } from '@elastic/eui'; +import { scaleUpPercentage } from '../../../../components/infrastructure_node_metrics_tables/shared/hooks'; +import type { SnapshotNodeMetric } from '../../../../../common/http_api/snapshot_api'; +import { NumberCell } from '../../../../components/infrastructure_node_metrics_tables/shared/components'; + +interface HostNodeRow extends HostMetics { + os?: string | null | undefined; + servicesOnHost?: number | null | undefined; +} + +export interface HostMetics { + cpuCores: SnapshotNodeMetric; + rx: SnapshotNodeMetric; + tx: SnapshotNodeMetric; + memory: SnapshotNodeMetric; + memoryTotal: SnapshotNodeMetric; +} + +export const HostsTableColumns: Array> = [ + { + name: i18n.translate('xpack.infra.hostsTable.nameColumnHeader', { + defaultMessage: 'Name', + }), + field: 'label', + truncateText: true, + textOnly: true, + render: (name: string) => {name}, + }, + { + name: i18n.translate('xpack.infra.hostsTable.operatingSystemColumnHeader', { + defaultMessage: 'Operating System', + }), + field: 'os', + render: (os: string) => {os ?? '-'}, + }, + { + name: i18n.translate('xpack.infra.hostsTable.numberOfCpusColumnHeader', { + defaultMessage: '# of CPUs', + }), + field: 'cpuCores', + render: (cpuCores: { value: number }) => , + }, + { + name: i18n.translate('xpack.infra.hostsTable.diskLatencyColumnHeader', { + defaultMessage: 'Disk Latency', + }), + field: 'diskLatency', + render: (ds: number) => , + }, + { + name: i18n.translate('xpack.infra.hostsTable.averageTxColumnHeader', { + defaultMessage: 'TX (avg.)', + }), + field: 'tx', + render: (tx: { avg: number }) => , + }, + { + name: i18n.translate('xpack.infra.hostsTable.averageRxColumnHeader', { + defaultMessage: 'RX (avg.)', + }), + field: 'rx', + render: (rx: { avg: number }) => , + }, + { + name: i18n.translate('xpack.infra.hostsTable.averageMemoryTotalColumnHeader', { + defaultMessage: 'Memory total (avg.)', + }), + field: 'memoryTotal', + render: (memoryTotal: { avg: number }) => ( + + ), + }, + { + name: i18n.translate('xpack.infra.hostsTable.servicesOnHostColumnHeader', { + defaultMessage: 'Services on Host', + }), + field: 'servicesOnHost', + render: (servicesOnHost: number) => , + }, + { + name: i18n.translate('xpack.infra.hostsTable.averageMemoryUsageColumnHeader', { + defaultMessage: 'Memory usage (avg.)', + }), + field: 'memory', + render: (memory: { avg: number }) => ( + + ), + }, +]; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_table.test.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_table.test.ts new file mode 100644 index 0000000000000..e0d26413f0453 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_table.test.ts @@ -0,0 +1,129 @@ +/* + * 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 { useHostTable } from './use_host_table'; +import { renderHook } from '@testing-library/react-hooks'; + +describe('useHostTable hook', () => { + it('it should map the nodes returned from the snapshot api to a format matching eui table items', () => { + const nodes = [ + { + metrics: [ + { + name: 'rx', + avg: 252456.92916666667, + }, + { + name: 'tx', + avg: 252758.425, + }, + { + name: 'memory', + avg: 0.94525, + }, + { + name: 'cpuCores', + value: 10, + }, + { + name: 'memoryTotal', + avg: 34359.738368, + }, + ], + path: [{ value: 'host-0', label: 'host-0', os: null }], + name: 'host-0', + }, + { + metrics: [ + { + name: 'rx', + avg: 95.86339715321859, + }, + { + name: 'tx', + avg: 110.38566859563191, + }, + { + name: 'memory', + avg: 0.5400000214576721, + }, + { + name: 'cpuCores', + value: 8, + }, + { + name: 'memoryTotal', + avg: 9.194304, + }, + ], + path: [ + { value: 'host-1', label: 'host-1' }, + { value: 'host-1', label: 'host-1', ip: '243.86.94.22', os: 'macOS' }, + ], + name: 'host-1', + }, + ]; + + const items = [ + { + rx: { + name: 'rx', + avg: 252456.92916666667, + }, + tx: { + name: 'tx', + avg: 252758.425, + }, + memory: { + name: 'memory', + avg: 0.94525, + }, + cpuCores: { + name: 'cpuCores', + value: 10, + }, + memoryTotal: { + name: 'memoryTotal', + + avg: 34359.738368, + }, + value: 'host-0', + label: 'host-0', + os: null, + }, + { + rx: { + name: 'rx', + avg: 95.86339715321859, + }, + tx: { + name: 'tx', + avg: 110.38566859563191, + }, + memory: { + name: 'memory', + avg: 0.5400000214576721, + }, + cpuCores: { + name: 'cpuCores', + value: 8, + }, + memoryTotal: { + name: 'memoryTotal', + avg: 9.194304, + }, + value: 'host-1', + label: 'host-1', + ip: '243.86.94.22', + os: 'macOS', + }, + ]; + const result = renderHook(() => useHostTable(nodes)); + + expect(result.result.current).toStrictEqual(items); + }); +}); diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_table.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_table.ts new file mode 100644 index 0000000000000..4c604b2a27217 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_table.ts @@ -0,0 +1,27 @@ +/* + * 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 { last } from 'lodash'; +import { useMemo } from 'react'; +import type { SnapshotNode, SnapshotNodeMetric } from '../../../../../common/http_api'; +import { HostMetics } from '../components/hosts_table_columns'; + +type MappedMetrics = Record; + +export const useHostTable = (nodes: SnapshotNode[]) => { + const items: MappedMetrics[] = useMemo(() => { + return nodes.map(({ metrics, path }) => ({ + ...last(path), + ...metrics.reduce((data, metric) => { + data[metric.name as keyof HostMetics] = metric; + return data; + }, {} as MappedMetrics), + })); + }, [nodes]); + + return items; +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx index 2fc841d651e21..25aa168ca8dc0 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx @@ -6,28 +6,25 @@ */ import type { Query, TimeRange } from '@kbn/es-query'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; import { i18n } from '@kbn/i18n'; import React, { useState, useCallback } from 'react'; import { SearchBar } from '@kbn/unified-search-plugin/public'; +import { EuiSpacer } from '@elastic/eui'; +import { NoData } from '../../../components/empty_states'; import { InfraLoadingPanel } from '../../../components/loading'; import { useMetricsDataViewContext } from './hooks/use_data_view'; import { HostsTable } from './components/hosts_table'; -import { InfraClientStartDeps } from '../../../types'; import { useSourceContext } from '../../../containers/metrics_source'; +import { useSnapshot } from '../inventory_view/hooks/use_snaphot'; +import type { SnapshotMetricType } from '../../../../common/inventory_models/types'; export const HostsContent: React.FunctionComponent = () => { - const { - services: { data }, - } = useKibana(); - const { source } = useSourceContext(); + const { source, sourceId } = useSourceContext(); const [dateRange, setDateRange] = useState({ from: 'now-15m', to: 'now' }); const [query, setQuery] = useState({ query: '', language: 'kuery' }); const { metricsDataView, hasFailedCreatingDataView, hasFailedFetchingDataView } = useMetricsDataViewContext(); // needed to refresh the lens table when filters havent changed - const [searchSessionId, setSearchSessionId] = useState(data.search.session.start()); - const [isLensLoading, setIsLensLoading] = useState(false); const onQuerySubmit = useCallback( (payload: { dateRange: TimeRange; query?: Query }) => { @@ -35,52 +32,75 @@ export const HostsContent: React.FunctionComponent = () => { if (payload.query) { setQuery(payload.query); } - setIsLensLoading(true); - setSearchSessionId(data.search.session.start()); }, - [setDateRange, setQuery, data.search.session] + [setDateRange, setQuery] ); - const onLoading = useCallback( - (isLoading: boolean) => { - if (isLensLoading) { - setIsLensLoading(isLoading); - } - }, - [setIsLensLoading, isLensLoading] + const hostMetrics: Array<{ type: SnapshotMetricType }> = [ + { type: 'rx' }, + { type: 'tx' }, + { type: 'memory' }, + { type: 'cpuCores' }, + { type: 'memoryTotal' }, + ]; + + const { loading, nodes, reload } = useSnapshot( + '', // use the unified search query, supported type? + hostMetrics, + [], + 'host', + sourceId, + 1666081614879, // currentTime. need to add support for TimeRange? + '', + '', + true, + { + from: 1666081614879, // dynamic time range needs to be supported + interval: '1m', + lookbackSize: 5, + to: 1666082814879, + } ); - const onRefetch = useCallback(() => { - setIsLensLoading(true); - setSearchSessionId(data.search.session.start()); - }, [data.search.session]); + const noData = !loading && nodes && nodes.length === 0; return (
- {metricsDataView ? ( - <> - - { + reload(); + }} + testString="noMetricsDataPrompt" /> - + ) : ( + <> + + + + + ) ) : hasFailedCreatingDataView || hasFailedFetchingDataView ? (
There was an error trying to load or create the Data View:
diff --git a/x-pack/plugins/infra/public/types.ts b/x-pack/plugins/infra/public/types.ts index 461ed1261233a..be8c4d877c61c 100644 --- a/x-pack/plugins/infra/public/types.ts +++ b/x-pack/plugins/infra/public/types.ts @@ -28,7 +28,6 @@ import type { } from '@kbn/observability-plugin/public'; // import type { OsqueryPluginStart } from '../../osquery/public'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; -import type { LensPublicStart } from '@kbn/lens-plugin/public'; import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { UnwrapPromise } from '../common/utility_types'; import type { @@ -75,7 +74,6 @@ export interface InfraClientStartDeps { embeddable?: EmbeddableStart; osquery?: unknown; // OsqueryPluginStart; share: SharePluginStart; - lens: LensPublicStart; storage: IStorageWrapper; } diff --git a/x-pack/plugins/infra/server/routes/snapshot/lib/apply_metadata_to_last_path.ts b/x-pack/plugins/infra/server/routes/snapshot/lib/apply_metadata_to_last_path.ts index 2931555fc06b0..07fa1ac9cd1cf 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/lib/apply_metadata_to_last_path.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/lib/apply_metadata_to_last_path.ts @@ -46,7 +46,7 @@ export const applyMetadataToLastPath = ( // Set the label as the name and fallback to the id OR path.value lastPath.label = (firstMetaDoc[inventoryFields.name] ?? lastPath.value) as string; // If the inventory fields contain an ip address, we need to try and set that - // on the path object. IP addersses are typically stored as multiple fields. We will + // on the path object. IP addresses are typically stored as multiple fields. We will // use the first IPV4 address we find. if (inventoryFields.ip) { const ipAddresses = get(firstMetaDoc, inventoryFields.ip) as string[]; @@ -56,6 +56,9 @@ export const applyMetadataToLastPath = ( lastPath.ip = ipAddresses; } } + if (inventoryFields.os) { + lastPath.os = get(firstMetaDoc, inventoryFields.os) as string; + } return [...node.path.slice(0, node.path.length - 1), lastPath]; } } diff --git a/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts b/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts index b7e389cae9126..87fbbdac9a701 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts @@ -93,6 +93,11 @@ export const transformRequestToMetricsAPIRequest = async ({ if (inventoryFields.ip) { metaAggregation.aggregations[META_KEY].top_metrics.metrics.push({ field: inventoryFields.ip }); } + + if (inventoryFields.os) { + metaAggregation.aggregations[META_KEY].top_metrics.metrics.push({ field: inventoryFields.os }); + } + metricsApiRequest.metrics.push(metaAggregation); if (filters.length) { diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index ead29476ba7f2..58f4b34475d72 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -15597,6 +15597,15 @@ "xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel": "Voir les instructions de configuration", "xpack.infra.homePage.settingsTabTitle": "Paramètres", "xpack.infra.homePage.toolbar.kqlSearchFieldPlaceholder": "Rechercher des données d'infrastructure… (par exemple host.name:host-1)", + "xpack.infra.hostsTable.nameColumnHeader": "Nom", + "xpack.infra.hostsTable.operatingSystemColumnHeader": "Système d'exploitation", + "xpack.infra.hostsTable.numberOfCpusColumnHeader": "Nombre de processeurs", + "xpack.infra.hostsTable.diskLatencyColumnHeader": "", + "xpack.infra.hostsTable.averageTxColumnHeader": "", + "xpack.infra.hostsTable.averageRxColumnHeader": "", + "xpack.infra.hostsTable.averageMemoryTotalColumnHeader": "Total de la mémoire (moy.)", + "xpack.infra.hostsTable.servicesOnHostColumnHeader": "", + "xpack.infra.hostsTable.averageMemoryUsageColumnHeader": "Utilisation de la mémoire (moy.)", "xpack.infra.infra.nodeDetails.apmTabLabel": "APM", "xpack.infra.infra.nodeDetails.createAlertLink": "Créer une règle d'inventaire", "xpack.infra.infra.nodeDetails.openAsPage": "Ouvrir en tant que page", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 5f867500f94b7..362d61a868dfa 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -15582,6 +15582,15 @@ "xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel": "セットアップの手順を表示", "xpack.infra.homePage.settingsTabTitle": "設定", "xpack.infra.homePage.toolbar.kqlSearchFieldPlaceholder": "インフラストラクチャデータを検索…(例:host.name:host-1)", + "xpack.infra.hostsTable.nameColumnHeader": "名前", + "xpack.infra.hostsTable.operatingSystemColumnHeader": "オペレーティングシステム", + "xpack.infra.hostsTable.numberOfCpusColumnHeader": "CPU数", + "xpack.infra.hostsTable.diskLatencyColumnHeader": "", + "xpack.infra.hostsTable.averageTxColumnHeader": "", + "xpack.infra.hostsTable.averageRxColumnHeader": "", + "xpack.infra.hostsTable.averageMemoryTotalColumnHeader": "メモリ合計 (平均) ", + "xpack.infra.hostsTable.servicesOnHostColumnHeader": "", + "xpack.infra.hostsTable.averageMemoryUsageColumnHeader": "メモリー使用状況(平均)", "xpack.infra.infra.nodeDetails.apmTabLabel": "APM", "xpack.infra.infra.nodeDetails.createAlertLink": "インベントリルールの作成", "xpack.infra.infra.nodeDetails.openAsPage": "ページとして開く", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 002fdcf382100..e9b782af7c1a7 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -15603,6 +15603,15 @@ "xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel": "查看设置说明", "xpack.infra.homePage.settingsTabTitle": "设置", "xpack.infra.homePage.toolbar.kqlSearchFieldPlaceholder": "搜索基础设施数据……(例如 host.name:host-1)", + "xpack.infra.hostsTable.nameColumnHeader": "名称", + "xpack.infra.hostsTable.operatingSystemColumnHeader": "操作系统", + "xpack.infra.hostsTable.numberOfCpusColumnHeader": "# 个 CPU", + "xpack.infra.hostsTable.diskLatencyColumnHeader": "", + "xpack.infra.hostsTable.averageTxColumnHeader": "", + "xpack.infra.hostsTable.averageRxColumnHeader": "", + "xpack.infra.hostsTable.averageMemoryTotalColumnHeader": "内存合计 (平均值)", + "xpack.infra.hostsTable.servicesOnHostColumnHeader": "", + "xpack.infra.hostsTable.averageMemoryUsageColumnHeader": "内存使用率(平均值)", "xpack.infra.infra.nodeDetails.apmTabLabel": "APM", "xpack.infra.infra.nodeDetails.createAlertLink": "创建库存规则", "xpack.infra.infra.nodeDetails.openAsPage": "以页面形式打开",