|
1 | | -import { addHours } from 'date-fns' |
| 1 | +import { useMemo, useState } from 'react' |
2 | 2 | import { Area, CartesianGrid, ComposedChart, XAxis, YAxis } from 'recharts' |
3 | 3 |
|
4 | | -import { datumToValue, useApiQuery } from '@oxide/api' |
| 4 | +import type { Cumulativeint64, DiskMetricName } from '@oxide/api' |
| 5 | +import { useApiQuery } from '@oxide/api' |
5 | 6 |
|
6 | 7 | import { useRequiredParams } from 'app/hooks' |
7 | 8 |
|
| 9 | +type DiskMetricParams = { |
| 10 | + title: string |
| 11 | + startTime: Date |
| 12 | + endTime: Date |
| 13 | + metricName: DiskMetricName |
| 14 | + diskParams: { orgName: string; projectName: string; diskName: string } |
| 15 | + // TODO: specify bytes or count |
| 16 | +} |
| 17 | + |
| 18 | +function DiskMetric({ |
| 19 | + title, |
| 20 | + startTime, |
| 21 | + endTime, |
| 22 | + metricName, |
| 23 | + diskParams, |
| 24 | +}: DiskMetricParams) { |
| 25 | + // TODO: we're only pulling the first page. Should we bump the cap to 10k? |
| 26 | + // Fetch multiple pages if 10k is not enough? That's a bit much. |
| 27 | + const { data: metrics } = useApiQuery('diskMetricsList', { |
| 28 | + ...diskParams, |
| 29 | + metricName, |
| 30 | + startTime: startTime.toISOString(), |
| 31 | + endTime: endTime.toISOString(), |
| 32 | + limit: 1000, |
| 33 | + }) |
| 34 | + |
| 35 | + // console.log(metrics) |
| 36 | + |
| 37 | + const data = (metrics?.items || []).map(({ datum, timestamp }) => ({ |
| 38 | + timestamp: new Date(timestamp).toLocaleString(), |
| 39 | + // all of these metrics are cumulative ints |
| 40 | + value: (datum.datum as Cumulativeint64).value, |
| 41 | + })) |
| 42 | + |
| 43 | + // if (data.length > 0) { |
| 44 | + // console.log('time range:', data[0].timestamp, data[data.length - 1].timestamp) |
| 45 | + // } |
| 46 | + |
| 47 | + return ( |
| 48 | + <div> |
| 49 | + <h2 className="text-mono-sm text-secondary">{title}</h2> |
| 50 | + <ComposedChart |
| 51 | + width={480} |
| 52 | + height={240} |
| 53 | + data={data} |
| 54 | + margin={{ top: 5, right: 20, bottom: 5, left: 0 }} |
| 55 | + className="mt-4" |
| 56 | + > |
| 57 | + {/* TODO: pull these colors from TW config */} |
| 58 | + <CartesianGrid stroke="#1D2427" vertical={false} /> |
| 59 | + <Area |
| 60 | + dataKey="value" |
| 61 | + stroke="#2F8865" |
| 62 | + fillOpacity={1} |
| 63 | + fill="#112725" |
| 64 | + isAnimationActive={false} |
| 65 | + /> |
| 66 | + <XAxis dataKey="timestamp" /> |
| 67 | + <YAxis orientation="right" /> |
| 68 | + </ComposedChart> |
| 69 | + </div> |
| 70 | + ) |
| 71 | +} |
| 72 | + |
8 | 73 | export function MetricsTab() { |
9 | 74 | const instanceParams = useRequiredParams('orgName', 'projectName', 'instanceName') |
10 | 75 | const { orgName, projectName } = instanceParams |
11 | 76 |
|
| 77 | + const { data: instance } = useApiQuery('instanceView', instanceParams) |
12 | 78 | const { data: disks } = useApiQuery('instanceDiskList', instanceParams) |
13 | 79 | const diskName = disks?.items[0].name |
14 | | - const startTime = new Date(2022, 7, 18, 0) |
15 | | - const endTime = addHours(startTime, 24) |
16 | | - const { data: metrics } = useApiQuery( |
17 | | - 'diskMetricsList', |
18 | | - { |
19 | | - orgName, |
20 | | - projectName, |
21 | | - diskName: diskName!, // force it because this only runs when diskName is there |
22 | | - metricName: 'read', |
23 | | - startTime: startTime.toISOString(), |
24 | | - endTime: endTime.toISOString(), |
25 | | - limit: 1000, |
26 | | - }, |
27 | | - { enabled: !!diskName } |
28 | | - ) |
29 | | - console.log(metrics) |
30 | 80 |
|
31 | | - const data = (metrics?.items || []).map(({ datum, timestamp }) => ({ |
32 | | - timestamp: new Date(timestamp).toLocaleString(), |
33 | | - value: datumToValue(datum), |
34 | | - })) |
| 81 | + const [startTime] = useState(instance?.timeCreated) |
| 82 | + // TODO: add date picker |
| 83 | + |
| 84 | + // endTime is now, i.e., mount time |
| 85 | + const endTime = useMemo(() => new Date(), []) |
| 86 | + |
| 87 | + if (!startTime) return 'loading' |
| 88 | + if (!diskName) return 'loading' |
| 89 | + |
| 90 | + const commonProps = { |
| 91 | + startTime, |
| 92 | + endTime, |
| 93 | + diskParams: { orgName, projectName, diskName }, |
| 94 | + } |
| 95 | + |
35 | 96 | return ( |
36 | | - <ComposedChart |
37 | | - width={600} |
38 | | - height={300} |
39 | | - data={data} |
40 | | - margin={{ top: 5, right: 20, bottom: 5, left: 0 }} |
41 | | - className="mt-16" |
42 | | - > |
43 | | - {/* TODO: pull these colors from TW config */} |
44 | | - <CartesianGrid stroke="#1D2427" vertical={false} /> |
45 | | - <Area |
46 | | - dataKey="value" |
47 | | - stroke="#2F8865" |
48 | | - fillOpacity={1} |
49 | | - fill="#112725" |
50 | | - isAnimationActive={false} |
51 | | - /> |
52 | | - <XAxis dataKey="timestamp" /> |
53 | | - <YAxis orientation="right" /> |
54 | | - </ComposedChart> |
| 97 | + <> |
| 98 | + <h2 className="text-sans-xl"> |
| 99 | + {/* TODO: need a nicer way of saying what the boot disk is */} |
| 100 | + Boot disk ( <code>{diskName}</code> ) |
| 101 | + </h2> |
| 102 | + <div className="flex flex-wrap gap-8 mt-8"> |
| 103 | + <DiskMetric {...commonProps} title="Activations (count)" metricName="activated" /> |
| 104 | + <DiskMetric {...commonProps} title="Reads (count)" metricName="read" /> |
| 105 | + <DiskMetric {...commonProps} title="Read (bytes)" metricName="read_bytes" /> |
| 106 | + <DiskMetric {...commonProps} title="Writes (count)" metricName="write" /> |
| 107 | + <DiskMetric {...commonProps} title="Write (bytes)" metricName="write_bytes" /> |
| 108 | + <DiskMetric {...commonProps} title="Flushes (count)" metricName="flush" /> |
| 109 | + </div> |
| 110 | + </> |
55 | 111 | ) |
56 | 112 | } |
0 commit comments