Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/intl/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2665,6 +2665,8 @@
},
"metrics": {
"title": "Metrics",
"timeFrame": "Time frame",
"step": "Step",
"cpu": {
"label": "CPU usage",
"tooltip": "Percentage of used CPU for the selected instance"
Expand Down
31 changes: 31 additions & 0 deletions src/modules/metrics/metrics-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,37 @@ import { DataPoint, GraphDataPoint } from './metrics-types';
export const metricsTimeFrames = ['5m', '15m', '1h', '6h', '1d', '2d', '7d'] as const;
export type MetricsTimeFrame = (typeof metricsTimeFrames)[number];

export const metricsSteps = ['1m', '5m', '15m', '30m', '1h', '2h', '3h', '6h', '12h'] as const;
export type MetricsStep = (typeof metricsSteps)[number];

export function getDefaultStep(timeFrame: MetricsTimeFrame): MetricsStep {
const defaultSteps: Record<MetricsTimeFrame, MetricsStep> = {
'5m': '1m',
'15m': '1m',
'1h': '1m',
'6h': '5m',
'1d': '30m',
'2d': '1h',
'7d': '3h',
};

return defaultSteps[timeFrame];
}

export function getValidStepsForTimeFrame(timeFrame: MetricsTimeFrame): MetricsStep[] {
const validSteps: Record<MetricsTimeFrame, MetricsStep[]> = {
'5m': ['1m'],
'15m': ['1m', '5m'],
'1h': ['1m', '5m'],
'6h': ['1m', '5m', '15m', '30m'],
'1d': ['5m', '15m', '30m', '1h'],
'2d': ['15m', '30m', '1h', '2h'],
'7d': ['30m', '1h', '2h', '3h', '6h', '12h'],
};

return validSteps[timeFrame];
}

export function toGraph({ date, value }: DataPoint): GraphDataPoint {
const result: GraphDataPoint = { x: date, y: null };

Expand Down
19 changes: 6 additions & 13 deletions src/modules/metrics/use-metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { getApiQueryKey, refetchInterval, useApi } from 'src/api';
import { identity } from 'src/utils/generic';
import { toObject } from 'src/utils/object';

import { MetricsTimeFrame } from './metrics-helpers';
import { MetricsStep, MetricsTimeFrame, getDefaultStep } from './metrics-helpers';
import { DataPoint, Metric } from './metrics-types';

const timeFrameToDuration: Record<MetricsTimeFrame, Duration> = {
Expand All @@ -20,34 +20,27 @@ const timeFrameToDuration: Record<MetricsTimeFrame, Duration> = {
'7d': { days: 7 },
};

const timeFrameToStep: Record<MetricsTimeFrame, string> = {
'5m': '1m',
'15m': '1m',
'1h': '1m',
'6h': '5m',
'1d': '30m',
'2d': '1h',
'7d': '3h',
};

type UseMetricsOptions = {
serviceId?: string;
instanceId?: string;
metrics: API.MetricName[];
timeFrame: MetricsTimeFrame;
step?: MetricsStep;
};

export function useMetricsQueries({ serviceId, instanceId, metrics, timeFrame }: UseMetricsOptions) {
export function useMetricsQueries({ serviceId, instanceId, metrics, timeFrame, step }: UseMetricsOptions) {
const api = useApi();
const { getAccessToken } = useAuth();

const resolvedStep = step ?? getDefaultStep(timeFrame);

return useQueries({
queries: metrics.map((name) => {
const query = {
name,
service_id: serviceId,
instance_id: instanceId,
step: timeFrameToStep[timeFrame],
step: resolvedStep,
time_frame: timeFrame,
};

Expand Down
85 changes: 64 additions & 21 deletions src/pages/service/metrics/service-metrics.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ import { HttpThroughputGraph } from 'src/modules/metrics/graphs/http-throughput-
import { MemoryGraph } from 'src/modules/metrics/graphs/memory-graph';
import { PublicDataTransferGraph } from 'src/modules/metrics/graphs/public-data-transfer-graph';
import { ResponseTimeGraph } from 'src/modules/metrics/graphs/response-time-graph';
import { MetricsTimeFrame, metricsTimeFrames } from 'src/modules/metrics/metrics-helpers';
import {
MetricsStep,
MetricsTimeFrame,
getDefaultStep,
getValidStepsForTimeFrame,
metricsTimeFrames,
} from 'src/modules/metrics/metrics-helpers';
import { useMetricsQueries } from 'src/modules/metrics/use-metrics';
import { inArray } from 'src/utils/arrays';

Expand All @@ -22,31 +28,60 @@ const T = createTranslate('pages.service.metrics');
type MetricsPageProps = {
serviceId: string;
timeFrame: MetricsTimeFrame;
step?: MetricsStep;
};

export function ServiceMetricsPage({ serviceId, timeFrame }: MetricsPageProps) {
export function ServiceMetricsPage({ serviceId, timeFrame, step: stepProp }: MetricsPageProps) {
const validSteps = getValidStepsForTimeFrame(timeFrame);
const step: MetricsStep = stepProp && validSteps.includes(stepProp) ? stepProp : getDefaultStep(timeFrame);

return (
<>
<Title
title="Metrics"
end={
<ButtonGroup>
{metricsTimeFrames.map((s) => (
<LinkButton
key={s}
from="/services/$serviceId/metrics"
search={{ 'time-frame': s }}
type="button"
variant={s === timeFrame ? 'solid' : 'outline'}
>
{s.toUpperCase()}
</LinkButton>
))}
</ButtonGroup>
<div className="col gap-2">
<div className="col gap-1">
<span className="px-1 text-xs text-dim">
<T id="timeFrame" />
</span>
<ButtonGroup>
{metricsTimeFrames.map((s) => (
<LinkButton
key={s}
from="/services/$serviceId/metrics"
search={{ 'time-frame': s }}
type="button"
variant={s === timeFrame ? 'solid' : 'outline'}
>
{s.toUpperCase()}
</LinkButton>
))}
</ButtonGroup>
</div>
<div className="col gap-1">
<span className="px-1 text-xs text-dim">
<T id="step" />
</span>
<ButtonGroup>
{validSteps.map((s) => (
<LinkButton
key={s}
from="/services/$serviceId/metrics"
search={{ 'time-frame': timeFrame, step: s }}
type="button"
variant={s === step ? 'solid' : 'outline'}
>
{s.toUpperCase()}
</LinkButton>
))}
</ButtonGroup>
</div>
</div>
}
/>

<ServiceMetrics serviceId={serviceId} timeFrame={timeFrame} />
<ServiceMetrics serviceId={serviceId} timeFrame={timeFrame} step={step} />
</>
);
}
Expand All @@ -63,23 +98,31 @@ const metrics: API.MetricName[] = [
'PUBLIC_DATA_TRANSFER_OUT',
] as const;

function ServiceMetrics({ serviceId, timeFrame }: { serviceId: string; timeFrame: MetricsTimeFrame }) {
function ServiceMetrics({
serviceId,
timeFrame,
step,
}: {
serviceId: string;
timeFrame: MetricsTimeFrame;
step: MetricsStep;
}) {
const service = useService(serviceId);
const queries = useMetricsQueries({ serviceId, metrics, timeFrame });
const queries = useMetricsQueries({ serviceId, metrics, timeFrame, step });
const instance = useServiceInstanceType(serviceId);

return (
<>
<div className="col gap-4 lg:row">
<GraphCard label={<T id="cpu.label" />} tooltip={<T id="cpu.tooltip" />} className="flex-1">
<div className="col gap-4">
<GraphCard label={<T id="cpu.label" />} tooltip={<T id="cpu.tooltip" />}>
<CpuGraph
loading={queries.isPending}
error={queries.error['CPU_TOTAL_PERCENT']}
data={queries.data['CPU_TOTAL_PERCENT']}
/>
</GraphCard>

<GraphCard label={<T id="memory.label" />} tooltip={<T id="memory.tooltip" />} className="flex-1">
<GraphCard label={<T id="memory.label" />} tooltip={<T id="memory.tooltip" />}>
<MemoryGraph
loading={queries.isPending}
error={queries.error['MEM_RSS']}
Expand Down
19 changes: 5 additions & 14 deletions src/routes/_main/services/$serviceId/metrics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,20 @@ import { createFileRoute } from '@tanstack/react-router';
import z from 'zod';

import { CrumbLink } from 'src/layouts/main/app-breadcrumbs';
import { metricsSteps, metricsTimeFrames } from 'src/modules/metrics/metrics-helpers';
import { ServiceMetricsPage } from 'src/pages/service/metrics/service-metrics.page';

export const Route = createFileRoute('/_main/services/$serviceId/metrics')({
component: function Component() {
const { serviceId } = Route.useParams();
const { 'time-frame': timeFrame } = Route.useSearch();
const { 'time-frame': timeFrame, step } = Route.useSearch();

return <ServiceMetricsPage serviceId={serviceId} timeFrame={timeFrame} />;
return <ServiceMetricsPage serviceId={serviceId} timeFrame={timeFrame} step={step} />;
},

validateSearch: z.object({
'time-frame': z
.union([
z.literal('5m'),
z.literal('15m'),
z.literal('1h'),
z.literal('6h'),
z.literal('1d'),
z.literal('2d'),
z.literal('7d'),
])
.default('5m')
.catch('5m'),
'time-frame': z.enum(metricsTimeFrames).default('5m').catch('5m'),
step: z.enum(metricsSteps).optional().catch(undefined),
}),

beforeLoad: ({ params }) => ({
Expand Down
Loading