@@ -22,9 +22,19 @@ import { getInstanceSelector, useInstanceSelector } from 'app/hooks'
22
22
23
23
const TimeSeriesChart = React . lazy ( ( ) => import ( 'app/components/TimeSeriesChart' ) )
24
24
25
+ export function getCycleCount ( num : number , base : number ) {
26
+ let cycleCount = 0
27
+ let transformedValue = num
28
+ while ( transformedValue > base ) {
29
+ transformedValue = transformedValue / base
30
+ cycleCount ++
31
+ }
32
+ return cycleCount
33
+ }
34
+
25
35
type DiskMetricParams = {
26
36
title : string
27
- unit ?: string
37
+ unit : 'Bytes' | 'Count'
28
38
startTime : Date
29
39
endTime : Date
30
40
metric : DiskMetricName
@@ -54,27 +64,77 @@ function DiskMetric({
54
64
{ placeholderData : ( x ) => x }
55
65
)
56
66
57
- const data = ( metrics ?. items || [ ] ) . map ( ( { datum, timestamp } ) => ( {
58
- timestamp : timestamp . getTime ( ) ,
59
- // all of these metrics are cumulative ints
60
- value : ( datum . datum as Cumulativeint64 ) . value ,
61
- } ) )
67
+ const isBytesChart = unit === 'Bytes'
68
+
69
+ const largestValue = useMemo ( ( ) => {
70
+ if ( ! metrics || metrics . items . length === 0 ) return 0
71
+ return Math . max ( ...metrics . items . map ( ( m ) => ( m . datum . datum as Cumulativeint64 ) . value ) )
72
+ } , [ metrics ] )
73
+
74
+ // We'll need to divide each number in the set by a consistent exponent
75
+ // of 1024 (for Bytes) or 1000 (for Counts)
76
+ const base = isBytesChart ? 1024 : 1000
77
+ // Figure out what that exponent is:
78
+ const cycleCount = getCycleCount ( largestValue , base )
79
+
80
+ // Now that we know how many cycles of "divide by 1024 || 1000" to run through
81
+ // (via cycleCount), we can determine the proper unit for the set
82
+ let unitForSet = ''
83
+ let label = '(COUNT)'
84
+ if ( isBytesChart ) {
85
+ const byteUnits = [ 'Bytes' , 'KiB' , 'MiB' , 'GiB' , 'TiB' ]
86
+ unitForSet = byteUnits [ cycleCount ]
87
+ label = `(${ unitForSet } )`
88
+ }
89
+
90
+ const divisor = base ** cycleCount
91
+
92
+ const data = useMemo (
93
+ ( ) =>
94
+ ( metrics ?. items || [ ] ) . map ( ( { datum, timestamp } ) => ( {
95
+ timestamp : timestamp . getTime ( ) ,
96
+ // All of these metrics are cumulative ints.
97
+ // The value passed in is what will render in the tooltip.
98
+ value : isBytesChart
99
+ ? // We pass a pre-divided value to the chart if the unit is Bytes
100
+ ( datum . datum as Cumulativeint64 ) . value / divisor
101
+ : // If the unit is Count, we pass the raw value
102
+ ( datum . datum as Cumulativeint64 ) . value ,
103
+ } ) ) ,
104
+ [ metrics , isBytesChart , divisor ]
105
+ )
106
+
107
+ // Create a label for the y-axis ticks. "Count" charts will be
108
+ // abbreviated and will have a suffix (e.g. "k") appended. Because
109
+ // "Bytes" charts will have already been divided by the divisor
110
+ // before the yAxis is created, we can use their given value.
111
+ const yAxisTickFormatter = ( val : number ) => {
112
+ if ( isBytesChart ) {
113
+ return val . toLocaleString ( )
114
+ }
115
+ const tickValue = ( val / divisor ) . toFixed ( 2 )
116
+ const countUnits = [ '' , 'k' , 'M' , 'B' , 'T' ]
117
+ const unitForTick = countUnits [ cycleCount ]
118
+ return `${ tickValue } ${ unitForTick } `
119
+ }
62
120
63
121
return (
64
122
< div className = "flex w-1/2 flex-grow flex-col" >
65
- < h2 className = "ml-3 flex items-center text-mono-xs text-secondary" >
66
- { title } { unit && < div className = "ml-1 text-quaternary" > { unit } </ div > }
123
+ < h2 className = "ml-3 flex items-center text-mono-xs text-secondary " >
124
+ { title } < div className = "ml-1 normal-case text-quaternary" > { label } </ div >
67
125
{ isLoading && < Spinner className = "ml-2" /> }
68
126
</ h2 >
69
127
< Suspense fallback = { < div className = "mt-3 h-[300px]" /> } >
70
128
< TimeSeriesChart
71
129
className = "mt-3"
72
130
data = { data }
73
131
title = { title }
132
+ unit = { unitForSet }
74
133
width = { 480 }
75
134
height = { 240 }
76
135
startTime = { startTime }
77
136
endTime = { endTime }
137
+ yAxisTickFormatter = { yAxisTickFormatter }
78
138
/>
79
139
</ Suspense >
80
140
</ div >
@@ -151,17 +211,17 @@ export function MetricsTab() {
151
211
{ /* see the following link for the source of truth on what these mean
152
212
https://github.com/oxidecomputer/crucible/blob/258f162b/upstairs/src/stats.rs#L9-L50 */ }
153
213
< div className = "flex w-full space-x-4" >
154
- < DiskMetric { ...commonProps } title = "Reads" unit = "( Count) " metric = "read" />
155
- < DiskMetric { ...commonProps } title = "Read" unit = "( Bytes) " metric = "read_bytes" />
214
+ < DiskMetric { ...commonProps } title = "Reads" unit = "Count" metric = "read" />
215
+ < DiskMetric { ...commonProps } title = "Read" unit = "Bytes" metric = "read_bytes" />
156
216
</ div >
157
217
158
218
< div className = "flex w-full space-x-4" >
159
- < DiskMetric { ...commonProps } title = "Writes" unit = "( Count) " metric = "write" />
160
- < DiskMetric { ...commonProps } title = "Write" unit = "( Bytes) " metric = "write_bytes" />
219
+ < DiskMetric { ...commonProps } title = "Writes" unit = "Count" metric = "write" />
220
+ < DiskMetric { ...commonProps } title = "Write" unit = "Bytes" metric = "write_bytes" />
161
221
</ div >
162
222
163
223
< div className = "flex w-full space-x-4" >
164
- < DiskMetric { ...commonProps } title = "Flushes" unit = "( Count) " metric = "flush" />
224
+ < DiskMetric { ...commonProps } title = "Flushes" unit = "Count" metric = "flush" />
165
225
</ div >
166
226
</ div >
167
227
</ >
0 commit comments