@@ -619,7 +619,9 @@ export function loadSeries(projList, t0, t1, keyOf, opts) {
619619 const bucketOf = { } ; for ( const p of projList ) bucketOf [ p . id ] = keyOf ( p )
620620 const set = new Set ( projList . map ( ( p ) => p . id ) )
621621 const buckets = [ ...new Set ( projList . map ( ( p ) => keyOf ( p ) ) ) ]
622- const emit = ( cj , cr ) => { const row = { } ; for ( const b of buckets ) { if ( split ) { row [ b ] = cj [ b ] ; row [ b + RESV_SFX ] = cr [ b ] } else { row [ b ] = cj [ b ] + cr [ b ] } } return row }
622+ // a reservation is a hold: usage under it is absorbed inside the reservation, and only
623+ // usage above it adds its own band. Job band = max(0, used - reserved); total = max(used, reserved).
624+ const emit = ( cj , cr ) => { const row = { } ; for ( const b of buckets ) { const over = Math . max ( 0 , cj [ b ] - cr [ b ] ) ; if ( split ) { row [ b ] = over ; row [ b + RESV_SFX ] = cr [ b ] } else { row [ b ] = cr [ b ] + over } } return row }
623625 const raw = ( t1 - t0 ) <= 14 * DAY
624626 if ( raw ) {
625627 const evts = [ ]
@@ -632,15 +634,38 @@ export function loadSeries(projList, t0, t1, keyOf, opts) {
632634 return { rows, buckets, mode : 'raw' }
633635 }
634636 const d0 = Math . floor ( t0 / DAY ) , d1 = Math . ceil ( t1 / DAY ) , nDays = d1 - d0
635- const gj = new Map ( ) , gr = new Map ( )
637+ const gj = new Map ( ) // jobs: mean GPUs over the day (GPU-hours / 24)
638+ const gr = new Map ( ) // reservations: the reserved level, kept as a rectangular block
636639 for ( let d = 0 ; d < nDays ; d ++ ) { gj . set ( d , Object . fromEntries ( buckets . map ( ( b ) => [ b , 0 ] ) ) ) ; gr . set ( d , Object . fromEntries ( buckets . map ( ( b ) => [ b , 0 ] ) ) ) }
640+ // jobs -> time-averaged GPUs per day (actual usage genuinely varies within a day)
637641 for ( const u of units ) {
638- if ( ! set . has ( u . projectId ) || u . end <= t0 || u . start >= t1 ) continue
639- const b = bucketOf [ u . projectId ] ; const grid = u . kind === 'resv' ? gr : gj ; let s = Math . max ( u . start , t0 ) ; const e = Math . min ( u . end , t1 )
640- while ( s < e ) { const di = Math . floor ( s / DAY ) - d0 ; const de = ( Math . floor ( s / DAY ) + 1 ) * DAY ; const seg = Math . min ( e , de ) - s ; if ( grid . has ( di ) ) grid . get ( di ) [ b ] += u . gpus * seg / HOUR ; s = de }
642+ if ( u . kind === 'resv' || ! set . has ( u . projectId ) || u . end <= t0 || u . start >= t1 ) continue
643+ const b = bucketOf [ u . projectId ] ; let s = Math . max ( u . start , t0 ) ; const e = Math . min ( u . end , t1 )
644+ while ( s < e ) { const di = Math . floor ( s / DAY ) - d0 ; const de = ( Math . floor ( s / DAY ) + 1 ) * DAY ; const seg = Math . min ( e , de ) - s ; if ( gj . has ( di ) ) gj . get ( di ) [ b ] += u . gpus * seg / HOUR ; s = de }
645+ }
646+ // reservations -> the reserved GPU level, not a time-average: a hold is a rectangular
647+ // block, so per day and bucket take the peak concurrent reserved GPUs. A single
648+ // reservation then reads flat at its size across every day it is active.
649+ const resvUnits = units . filter ( ( u ) => u . kind === 'resv' && set . has ( u . projectId ) && u . end > t0 && u . start < t1 )
650+ for ( let d = 0 ; d < nDays ; d ++ ) {
651+ const dayS = ( d0 + d ) * DAY , dayE = dayS + DAY
652+ for ( const b of buckets ) {
653+ const ev = [ ]
654+ for ( const u of resvUnits ) {
655+ if ( bucketOf [ u . projectId ] !== b ) continue
656+ const s = Math . max ( u . start , dayS , t0 ) , e = Math . min ( u . end , dayE , t1 )
657+ if ( e <= s ) continue
658+ ev . push ( [ s , u . gpus ] ) ; ev . push ( [ e , - u . gpus ] )
659+ }
660+ if ( ! ev . length ) continue
661+ ev . sort ( ( x , y ) => x [ 0 ] - y [ 0 ] )
662+ let cur = 0 , peak = 0
663+ for ( const [ , g ] of ev ) { cur += g ; if ( cur > peak ) peak = cur }
664+ gr . get ( d ) [ b ] = peak
665+ }
641666 }
642667 const rows = [ ]
643- for ( let d = 0 ; d < nDays ; d ++ ) { const cj = Object . fromEntries ( buckets . map ( ( b ) => [ b , + ( gj . get ( d ) [ b ] / 24 ) . toFixed ( 1 ) ] ) ) ; const cr = Object . fromEntries ( buckets . map ( ( b ) => [ b , + ( gr . get ( d ) [ b ] / 24 ) . toFixed ( 1 ) ] ) ) ; rows . push ( { t : ( d0 + d ) * DAY , ...emit ( cj , cr ) } ) }
668+ for ( let d = 0 ; d < nDays ; d ++ ) { const cj = Object . fromEntries ( buckets . map ( ( b ) => [ b , + ( gj . get ( d ) [ b ] / 24 ) . toFixed ( 1 ) ] ) ) ; const cr = Object . fromEntries ( buckets . map ( ( b ) => [ b , + gr . get ( d ) [ b ] . toFixed ( 1 ) ] ) ) ; rows . push ( { t : ( d0 + d ) * DAY , ...emit ( cj , cr ) } ) }
644669 return { rows, buckets, mode : 'daily' }
645670}
646671
0 commit comments