Skip to content

Commit

Permalink
[Metrics UI] Add Process tab to Enhanced Node Details (elastic#83477)
Browse files Browse the repository at this point in the history
  • Loading branch information
Zacqary committed Nov 18, 2020
1 parent d6d73ef commit aa3b663
Show file tree
Hide file tree
Showing 20 changed files with 1,163 additions and 48 deletions.
7 changes: 7 additions & 0 deletions x-pack/plugins/infra/common/http_api/host_details/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export * from './process_list';
20 changes: 20 additions & 0 deletions x-pack/plugins/infra/common/http_api/host_details/process_list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import * as rt from 'io-ts';
import { MetricsAPITimerangeRT, MetricsAPISeriesRT } from '../metrics_api';

export const ProcessListAPIRequestRT = rt.type({
hostTerm: rt.record(rt.string, rt.string),
timerange: MetricsAPITimerangeRT,
indexPattern: rt.string,
});

export const ProcessListAPIResponseRT = rt.array(MetricsAPISeriesRT);

export type ProcessListAPIRequest = rt.TypeOf<typeof ProcessListAPIRequestRT>;

export type ProcessListAPIResponse = rt.TypeOf<typeof ProcessListAPIResponseRT>;
1 change: 1 addition & 0 deletions x-pack/plugins/infra/common/http_api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * from './metrics_explorer';
export * from './metrics_api';
export * from './log_alerts';
export * from './snapshot_api';
export * from './host_details';
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ export const BottomDrawer: React.FC<{
<BottomActionContainer ref={isOpen ? measureRef : null} isOpen={isOpen}>
<BottomActionTopBar ref={isOpen ? null : measureRef}>
<EuiFlexItem grow={false}>
<ShowHideButton iconType={isOpen ? 'arrowDown' : 'arrowRight'} onClick={onClick}>
<ShowHideButton
aria-expanded={isOpen}
iconType={isOpen ? 'arrowDown' : 'arrowRight'}
onClick={onClick}
>
{isOpen ? hideHistory : showHistory}
</ShowHideButton>
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { EuiTabbedContent } from '@elastic/eui';
import { EuiPortal, EuiTabs, EuiTab, EuiPanel, EuiTitle } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiPanel } from '@elastic/eui';
import React, { CSSProperties, useMemo } from 'react';
import { EuiText } from '@elastic/eui';
import React, { CSSProperties, useMemo, useState } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui';
import { euiStyled } from '../../../../../../../observability/public';
import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../../../../lib/lib';
Expand All @@ -17,6 +15,7 @@ import { MetricsTab } from './tabs/metrics';
import { LogsTab } from './tabs/logs';
import { ProcessesTab } from './tabs/processes';
import { PropertiesTab } from './tabs/properties';
import { OVERLAY_Y_START, OVERLAY_BOTTOM_MARGIN, OVERLAY_HEADER_SIZE } from './tabs/shared';

interface Props {
isOpen: boolean;
Expand Down Expand Up @@ -48,46 +47,63 @@ export const NodeContextPopover = ({
});
}, [tabConfigs, node, nodeType, currentTime, options]);

const [selectedTab, setSelectedTab] = useState(0);

if (!isOpen) {
return null;
}

return (
<EuiPanel hasShadow={true} paddingSize={'none'} style={panelStyle}>
<OverlayHeader>
<EuiFlexGroup alignItems={'center'}>
<EuiFlexItem grow={true}>
<EuiText>
<h4>{node.name}</h4>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty onClick={onClose} iconType={'cross'}>
<FormattedMessage id="xpack.infra.infra.nodeDetails.close" defaultMessage="Close" />
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
</OverlayHeader>
<EuiTabbedContent tabs={tabs} />
</EuiPanel>
<EuiPortal>
<EuiPanel hasShadow={true} paddingSize={'none'} style={panelStyle}>
<OverlayHeader>
<OverlayHeaderTitleWrapper>
<EuiFlexItem grow={true}>
<EuiTitle size="s">
<h4>{node.name}</h4>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty onClick={onClose} iconType={'cross'}>
<FormattedMessage id="xpack.infra.infra.nodeDetails.close" defaultMessage="Close" />
</EuiButtonEmpty>
</EuiFlexItem>
</OverlayHeaderTitleWrapper>
<EuiTabs>
{tabs.map((tab, i) => (
<EuiTab key={tab.id} isSelected={i === selectedTab} onClick={() => setSelectedTab(i)}>
{tab.name}
</EuiTab>
))}
</EuiTabs>
</OverlayHeader>
{tabs[selectedTab].content}
</EuiPanel>
</EuiPortal>
);
};

const OverlayHeader = euiStyled.div`
border-color: ${(props) => props.theme.eui.euiBorderColor};
border-bottom-width: ${(props) => props.theme.eui.euiBorderWidthThick};
padding: ${(props) => props.theme.eui.euiSizeS};
padding-bottom: 0;
overflow: hidden;
background-color: ${(props) => props.theme.eui.euiColorLightestShade};
height: ${OVERLAY_HEADER_SIZE}px;
`;

const OverlayHeaderTitleWrapper = euiStyled(EuiFlexGroup).attrs({ alignItems: 'center' })`
padding: ${(props) => props.theme.eui.paddingSizes.s} ${(props) =>
props.theme.eui.paddingSizes.m} 0;
`;

const panelStyle: CSSProperties = {
position: 'absolute',
right: 10,
top: -100,
top: OVERLAY_Y_START,
width: '50%',
maxWidth: 600,
maxWidth: 730,
zIndex: 2,
height: '50vh',
height: `calc(100vh - ${OVERLAY_Y_START + OVERLAY_BOTTOM_MARGIN}px)`,
overflow: 'hidden',
};

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React, { useMemo, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiSearchBar, EuiSpacer, EuiEmptyPrompt, EuiButton, Query } from '@elastic/eui';
import { useProcessList } from '../../../../hooks/use_process_list';
import { TabContent, TabProps } from '../shared';
import { STATE_NAMES } from './states';
import { SummaryTable } from './summary_table';
import { ProcessesTable } from './processes_table';

const TabComponent = ({ currentTime, node, nodeType, options }: TabProps) => {
const [searchFilter, setSearchFilter] = useState<Query>(EuiSearchBar.Query.MATCH_ALL);

const hostTerm = useMemo(() => {
const field =
options.fields && Reflect.has(options.fields, nodeType)
? Reflect.get(options.fields, nodeType)
: nodeType;
return { [field]: node.name };
}, [options, node, nodeType]);

const { loading, error, response, makeRequest: reload } = useProcessList(
hostTerm,
'metricbeat-*',
options.fields!.timestamp,
currentTime
);

if (error) {
return (
<TabContent>
<EuiEmptyPrompt
iconType="tableDensityNormal"
title={
<h4>
{i18n.translate('xpack.infra.metrics.nodeDetails.processListError', {
defaultMessage: 'Unable to show process data',
})}
</h4>
}
actions={
<EuiButton color="primary" fill onClick={reload}>
{i18n.translate('xpack.infra.metrics.nodeDetails.processListRetry', {
defaultMessage: 'Try again',
})}
</EuiButton>
}
/>
</TabContent>
);
}

return (
<TabContent>
<SummaryTable isLoading={loading} processList={response ?? []} />
<EuiSpacer size="m" />
<EuiSearchBar
query={searchFilter}
onChange={({ query }) => setSearchFilter(query ?? EuiSearchBar.Query.MATCH_ALL)}
box={{
incremental: true,
placeholder: i18n.translate('xpack.infra.metrics.nodeDetails.searchForProcesses', {
defaultMessage: 'Search for processes…',
}),
}}
filters={[
{
type: 'field_value_selection',
field: 'state',
name: 'State',
operator: 'exact',
multiSelect: false,
options: Object.entries(STATE_NAMES).map(([value, view]: [string, string]) => ({
value,
view,
})),
},
]}
/>
<EuiSpacer size="m" />
<ProcessesTable
currentTime={currentTime}
isLoading={loading || !response}
processList={response ?? []}
searchFilter={searchFilter}
/>
</TabContent>
);
};

export const ProcessesTab = {
id: 'processes',
name: i18n.translate('xpack.infra.metrics.nodeDetails.tabs.processes', {
defaultMessage: 'Processes',
}),
content: TabComponent,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { ProcessListAPIResponse } from '../../../../../../../../common/http_api';
import { Process } from './types';

export const parseProcessList = (processList: ProcessListAPIResponse) =>
processList.map((process) => {
const command = process.id;
let mostRecentPoint;
for (let i = process.rows.length - 1; i >= 0; i--) {
const point = process.rows[i];
if (point && Array.isArray(point.meta) && point.meta?.length) {
mostRecentPoint = point;
break;
}
}
if (!mostRecentPoint) return { command, cpu: null, memory: null, startTime: null, state: null };

const { cpu, memory } = mostRecentPoint;
const { system, process: processMeta, user } = (mostRecentPoint.meta as any[])[0];
const startTime = system.process.cpu.start_time;
const state = system.process.state;

const timeseries = {
cpu: pickTimeseries(process.rows, 'cpu'),
memory: pickTimeseries(process.rows, 'memory'),
};

return {
command,
cpu,
memory,
startTime,
state,
pid: processMeta.pid,
user: user.name,
timeseries,
} as Process;
});

const pickTimeseries = (rows: any[], metricID: string) => ({
rows: rows.map((row) => ({
timestamp: row.timestamp,
metric_0: row[metricID],
})),
columns: [
{ name: 'timestamp', type: 'date' },
{ name: 'metric_0', type: 'number' },
],
id: metricID,
});
Loading

0 comments on commit aa3b663

Please sign in to comment.