@@ -22,9 +22,19 @@ import { getInstanceSelector, useInstanceSelector } from 'app/hooks'
2222
2323const TimeSeriesChart = React . lazy ( ( ) => import ( 'app/components/TimeSeriesChart' ) )
2424
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+
2535type DiskMetricParams = {
2636 title : string
27- unit ?: string
37+ unit : 'Bytes' | 'Count'
2838 startTime : Date
2939 endTime : Date
3040 metric : DiskMetricName
@@ -54,27 +64,77 @@ function DiskMetric({
5464 { placeholderData : ( x ) => x }
5565 )
5666
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+ }
62120
63121 return (
64122 < 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 >
67125 { isLoading && < Spinner className = "ml-2" /> }
68126 </ h2 >
69127 < Suspense fallback = { < div className = "mt-3 h-[300px]" /> } >
70128 < TimeSeriesChart
71129 className = "mt-3"
72130 data = { data }
73131 title = { title }
132+ unit = { unitForSet }
74133 width = { 480 }
75134 height = { 240 }
76135 startTime = { startTime }
77136 endTime = { endTime }
137+ yAxisTickFormatter = { yAxisTickFormatter }
78138 />
79139 </ Suspense >
80140 </ div >
@@ -151,17 +211,17 @@ export function MetricsTab() {
151211 { /* see the following link for the source of truth on what these mean
152212 https://github.com/oxidecomputer/crucible/blob/258f162b/upstairs/src/stats.rs#L9-L50 */ }
153213 < 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" />
156216 </ div >
157217
158218 < 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" />
161221 </ div >
162222
163223 < 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" />
165225 </ div >
166226 </ div >
167227 </ >
0 commit comments