feat: Log Side Panel Host Metrics (#164)

Co-authored-by: Mike Shi <mike@deploysentinel.com>
This commit is contained in:
Shorpo 2023-12-28 22:27:56 -07:00 committed by GitHub
parent 8e44260f59
commit af70f7d2b1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 254 additions and 8 deletions

View file

@ -0,0 +1,5 @@
---
'@hyperdx/app': patch
---
Link Infrastructure Metrics with Events

View file

@ -13,8 +13,8 @@ export const SORT_ORDER = [
{ value: 'asc' as const, label: 'Ascending' },
{ value: 'desc' as const, label: 'Descending' },
];
import { NumberFormat } from './types';
export type SortOrder = (typeof SORT_ORDER)[number]['value'];
import type { NumberFormat } from './types';
export const TABLES = [
{ value: 'logs' as const, label: 'Logs / Spans' },
@ -759,3 +759,20 @@ export function timeBucketByGranularity(
return buckets;
}
export const K8S_CPU_PERCENTAGE_NUMBER_FORMAT: NumberFormat = {
output: 'percent',
mantissa: 0,
};
export const K8S_FILESYSTEM_NUMBER_FORMAT: NumberFormat = {
output: 'byte',
};
export const K8S_MEM_NUMBER_FORMAT: NumberFormat = {
output: 'byte',
};
export const K8S_NETWORK_NUMBER_FORMAT: NumberFormat = {
output: 'byte',
};

View file

@ -55,6 +55,7 @@ const MemoChart = memo(function MemoChart({
groupKeys,
alertThreshold,
alertThresholdType,
logReferenceTimestamp,
displayType = 'line',
numberFormat,
}: {
@ -67,6 +68,7 @@ const MemoChart = memo(function MemoChart({
alertThresholdType?: 'above' | 'below';
displayType?: 'stacked_bar' | 'line';
numberFormat?: NumberFormat;
logReferenceTimestamp?: number;
}) {
const ChartComponent = displayType === 'stacked_bar' ? BarChart : LineChart;
@ -208,6 +210,14 @@ const MemoChart = memo(function MemoChart({
{isClickActive != null ? (
<ReferenceLine x={isClickActive.activeLabel} stroke="#ccc" />
) : null}
{logReferenceTimestamp != null ? (
<ReferenceLine
x={logReferenceTimestamp}
stroke="#ff5d5b"
strokeDasharray="3 3"
label="Event"
/>
) : null}
</ChartComponent>
</ResponsiveContainer>
);
@ -249,6 +259,7 @@ const HDXLineChart = memo(
onSettled,
alertThreshold,
alertThresholdType,
logReferenceTimestamp,
}: {
config: {
table: string;
@ -263,6 +274,7 @@ const HDXLineChart = memo(
onSettled?: () => void;
alertThreshold?: number;
alertThresholdType?: 'above' | 'below';
logReferenceTimestamp?: number;
}) => {
const { data, isError, isLoading } =
table === 'logs'
@ -517,6 +529,7 @@ const HDXLineChart = memo(
alertThresholdType={alertThresholdType}
displayType={displayType}
numberFormat={numberFormat}
logReferenceTimestamp={logReferenceTimestamp}
/>
</div>
</div>

View file

@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import Link from 'next/link';
import { useRouter } from 'next/router';
import cx from 'classnames';
import { add, format } from 'date-fns';
import { add, format, sub } from 'date-fns';
import Fuse from 'fuse.js';
import get from 'lodash/get';
import isPlainObject from 'lodash/isPlainObject';
@ -30,7 +30,15 @@ import {
import HyperJson, { GetLineActions, LineAction } from './components/HyperJson';
import { Table } from './components/Table';
import api from './api';
import {
K8S_CPU_PERCENTAGE_NUMBER_FORMAT,
K8S_FILESYSTEM_NUMBER_FORMAT,
K8S_MEM_NUMBER_FORMAT,
K8S_NETWORK_NUMBER_FORMAT,
} from './ChartUtils';
import { K8S_METRICS_ENABLED } from './config';
import { CurlGenerator } from './curlGenerator';
import HDXLineChart from './HDXLineChart';
import LogLevel from './LogLevel';
import {
breadcrumbColumns,
@ -2276,6 +2284,190 @@ const ExceptionSubpanel = ({
</div>
);
};
import { Card, SimpleGrid, Stack } from '@mantine/core';
import { convertDateRangeToGranularityString, Granularity } from './ChartUtils';
const MetricsSubpanelGroup = ({
timestamp,
where,
fieldPrefix,
title,
}: {
timestamp: any;
where: string;
fieldPrefix: string;
title: string;
}) => {
const [range, setRange] = useState<'30m' | '1h' | '1d'>('30m');
const [size, setSize] = useState<'sm' | 'md' | 'lg'>('sm');
const dateRange = useMemo<[Date, Date]>(() => {
const duration = {
'30m': { minutes: 15 },
'1h': { minutes: 30 },
'1d': { hours: 12 },
}[range];
return [
sub(new Date(timestamp), duration),
add(new Date(timestamp), duration),
];
}, [timestamp, range]);
const { cols, height } = useMemo(() => {
switch (size) {
case 'sm':
return { cols: 3, height: 200 };
case 'md':
return { cols: 2, height: 250 };
case 'lg':
return { cols: 1, height: 320 };
}
}, [size]);
const granularity = useMemo<Granularity>(() => {
return convertDateRangeToGranularityString(dateRange, 60);
}, [dateRange]);
return (
<div>
<Group position="apart" align="center">
<Group align="center">
<h4 className="text-slate-300 fs-6 m-0">{title}</h4>
<SegmentedControl
bg="dark.7"
color="dark.5"
size="xs"
data={[
{ label: '30m', value: '30m' },
{ label: '1h', value: '1h' },
{ label: '1d', value: '1d' },
]}
value={range}
onChange={value => setRange(value as any)}
/>
</Group>
<Group align="center">
<SegmentedControl
bg="dark.7"
color="dark.5"
size="xs"
data={[
{ label: 'SM', value: 'sm' },
{ label: 'MD', value: 'md' },
{ label: 'LG', value: 'lg' },
]}
value={size}
onChange={value => setSize(value as any)}
/>
</Group>
</Group>
<SimpleGrid mt="md" cols={cols}>
<Card p="md">
<Card.Section p="md" py="xs" withBorder>
CPU Usage (%)
</Card.Section>
<Card.Section py={8} px={4} h={height}>
<HDXLineChart
config={{
dateRange,
granularity,
where,
groupBy: '',
aggFn: 'avg',
field: `${fieldPrefix}cpu.utilization - Gauge`,
table: 'metrics',
numberFormat: K8S_CPU_PERCENTAGE_NUMBER_FORMAT,
}}
logReferenceTimestamp={timestamp / 1000}
/>
</Card.Section>
</Card>
<Card p="md">
<Card.Section p="md" py="xs" withBorder>
Memory Used
</Card.Section>
<Card.Section py={8} px={4} h={height}>
<HDXLineChart
config={{
dateRange,
granularity,
where,
groupBy: '',
aggFn: 'avg',
field: `${fieldPrefix}memory.usage - Gauge`,
table: 'metrics',
numberFormat: K8S_MEM_NUMBER_FORMAT,
}}
logReferenceTimestamp={timestamp / 1000}
/>
</Card.Section>
</Card>
<Card p="md">
<Card.Section p="md" py="xs" withBorder>
Disk Available
</Card.Section>
<Card.Section py={8} px={4} h={height}>
<HDXLineChart
config={{
dateRange,
granularity,
where,
groupBy: '',
aggFn: 'avg',
field: `${fieldPrefix}filesystem.available - Gauge`,
table: 'metrics',
numberFormat: K8S_FILESYSTEM_NUMBER_FORMAT,
}}
logReferenceTimestamp={timestamp / 1000}
/>
</Card.Section>
</Card>
</SimpleGrid>
</div>
);
};
const MetricsSubpanel = ({ logData }: { logData?: any }) => {
const podUid = useMemo(() => {
return logData?.['string.values']?.[
logData?.['string.names']?.indexOf('k8s.pod.uid')
];
}, [logData]);
const nodeName = useMemo(() => {
return logData?.['string.values']?.[
logData?.['string.names']?.indexOf('k8s.node.name')
];
}, [logData]);
const timestamp = new Date(logData?.timestamp).getTime();
return (
<Stack my="md" spacing={40}>
{podUid && (
<MetricsSubpanelGroup
title="Pod Metrics"
where={`k8s.pod.uid:"${podUid}"`}
fieldPrefix="k8s.pod."
timestamp={timestamp}
/>
)}
{nodeName && (
<MetricsSubpanelGroup
title="Node Metrics"
where={`k8s.node.name:"${nodeName}"`}
fieldPrefix="k8s.node."
timestamp={timestamp}
/>
)}
</Stack>
);
};
const checkKeyExistsInLogData = (key: string, logData: any) => {
return logData?.['string.values']?.[logData?.['string.names']?.indexOf(key)];
};
export default function LogSidePanel({
logId,
@ -2383,12 +2575,8 @@ export default function LogSidePanel({
// TODO: use rum_session_id instead ?
const rumSessionId: string | undefined =
logData?.['string.values']?.[
logData?.['string.names']?.indexOf('rum.sessionId')
] ??
logData?.['string.values']?.[
logData?.['string.names']?.indexOf('process.tag.rum.sessionId')
] ??
checkKeyExistsInLogData('rum.sessionId', logData) ??
checkKeyExistsInLogData('process.tag.rum.sessionId', logData) ??
sessionId;
const { width } = useWindowSize();
@ -2396,6 +2584,13 @@ export default function LogSidePanel({
const drawerZIndex = contextZIndex + 1;
const hasK8sContext = useMemo(() => {
return (
checkKeyExistsInLogData('k8s.pod.uid', logData) != null ||
checkKeyExistsInLogData('k8s.node.name', logData) != null
);
}, [logData]);
return (
<Drawer
enableOverlay
@ -2459,6 +2654,14 @@ export default function LogSidePanel({
},
] as const)
: []),
...(K8S_METRICS_ENABLED && hasK8sContext
? ([
{
text: 'Metrics',
value: 'metrics',
},
] as const)
: []),
]}
activeItem={displayedTab}
onClick={(v: any) => setTab(v)}
@ -2553,6 +2756,13 @@ export default function LogSidePanel({
)}
</div>
) : null}
{/* Metrics */}
{displayedTab === 'metrics' ? (
<div className="px-4 overflow-auto">
<MetricsSubpanel logData={logData} />
</div>
) : null}
</ErrorBoundary>
<LogSidePanelKbdShortcuts />
</>

View file

@ -12,3 +12,4 @@ export const IS_OSS = process.env.NEXT_PUBLIC_IS_OSS ?? 'true' === 'true';
// Features in development
export const METRIC_ALERTS_ENABLED = process.env.NODE_ENV === 'development';
export const K8S_METRICS_ENABLED = process.env.NODE_ENV === 'development';