Skip to content

Commit

Permalink
feat: Log Side Panel Host Metrics (#164)
Browse files Browse the repository at this point in the history
Co-authored-by: Mike Shi <mike@deploysentinel.com>
  • Loading branch information
svc-shorpo and MikeShi42 committed Dec 29, 2023
1 parent 8e44260 commit af70f7d
Show file tree
Hide file tree
Showing 5 changed files with 254 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/dirty-ads-exist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hyperdx/app': patch
---

Link Infrastructure Metrics with Events
19 changes: 18 additions & 1 deletion packages/app/src/ChartUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down Expand Up @@ -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',
};
13 changes: 13 additions & 0 deletions packages/app/src/HDXLineChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const MemoChart = memo(function MemoChart({
groupKeys,
alertThreshold,
alertThresholdType,
logReferenceTimestamp,
displayType = 'line',
numberFormat,
}: {
Expand All @@ -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;

Expand Down Expand Up @@ -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>
);
Expand Down Expand Up @@ -249,6 +259,7 @@ const HDXLineChart = memo(
onSettled,
alertThreshold,
alertThresholdType,
logReferenceTimestamp,
}: {
config: {
table: string;
Expand All @@ -263,6 +274,7 @@ const HDXLineChart = memo(
onSettled?: () => void;
alertThreshold?: number;
alertThresholdType?: 'above' | 'below';
logReferenceTimestamp?: number;
}) => {
const { data, isError, isLoading } =
table === 'logs'
Expand Down Expand Up @@ -517,6 +529,7 @@ const HDXLineChart = memo(
alertThresholdType={alertThresholdType}
displayType={displayType}
numberFormat={numberFormat}
logReferenceTimestamp={logReferenceTimestamp}
/>
</div>
</div>
Expand Down
224 changes: 217 additions & 7 deletions packages/app/src/LogSidePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -2383,19 +2575,22 @@ 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();
const isSmallScreen = (width ?? 1000) < 900;

const drawerZIndex = contextZIndex + 1;

const hasK8sContext = useMemo(() => {
return (
checkKeyExistsInLogData('k8s.pod.uid', logData) != null ||
checkKeyExistsInLogData('k8s.node.name', logData) != null
);
}, [logData]);

return (
<Drawer
enableOverlay
Expand Down Expand Up @@ -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)}
Expand Down Expand Up @@ -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 />
</>
Expand Down
1 change: 1 addition & 0 deletions packages/app/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

0 comments on commit af70f7d

Please sign in to comment.