1717const prettyPrintDuration = require ( 'pretty-ms' ) ,
1818 { sort, sortActivations, startTimeSorter, nameSorter, countSorter } = require ( './sorting' ) ,
1919 { drilldownWith } = require ( './drilldown' ) ,
20- { groupByAction } = require ( './grouping' ) ,
20+ { groupByAction, groupByTimeBucket } = require ( './grouping' ) ,
2121 { drawLegend } = require ( './legend' ) ,
2222 { renderCell } = require ( './cell' ) ,
2323 { modes } = require ( './modes' ) ,
2424 { grid :usage } = require ( '../usage' ) ,
25- { nbsp, optionsToString, isSuccess, titleWhenNothingSelected, latencyBucket,
25+ { nbsp, optionsToString, isSuccess, titleWhenNothingSelected, latencyBucket, nLatencyBuckets ,
2626 displayTimeRange, prepareHeader, visualize } = require ( './util' )
2727
2828const viewName = 'Grid'
@@ -152,7 +152,9 @@ const drawGrid = (options, header) => activations => {
152152 redraw = ! ! existingContent
153153
154154 content . className = css . content
155- _drawGrid ( options , header , content , groupByAction ( activations , options ) , undefined , undefined , redraw )
155+ _drawGrid ( options , header , content ,
156+ groupByAction ( activations , options ) ,
157+ undefined , undefined , redraw )
156158
157159 //injectHTML(content, 'grid/bottom-bar.html', 'bottom-bar')
158160
@@ -178,8 +180,10 @@ const drawGrid = (options, header) => activations => {
178180 width = gridDom . getAttribute ( 'data-width' ) ,
179181 vws = newZoom === 0 ? 2.75 : newZoom === 1 ? 3 : newZoom === 2 ? 4 : 0.75
180182
181- gridLabel . style . maxWidth = `${ Math . max ( 8 , width * vws * 1.1 ) } vw`
182183 gridRow . style . maxWidth = `${ Math . max ( 8 , width * vws ) } vw`
184+ if ( gridLabel ) {
185+ gridLabel . style . maxWidth = `${ Math . max ( 8 , width * vws * 1.1 ) } vw`
186+ }
183187 }
184188
185189 if ( newZoom === zoomMax ) {
@@ -207,15 +211,31 @@ const drawGrid = (options, header) => activations => {
207211 flush : 'right' ,
208212 actAsButton : true ,
209213 direct : rezoom ( _ => Math . max ( - 2 , _ - 1 ) )
210- }
214+ } ,
215+ asTimeline = { mode : 'as-timeline' ,
216+ fontawesome : 'fas fa-chart-bar' ,
217+ balloon : 'Display as timeline' ,
218+ flush : 'right' ,
219+ actAsButton : true ,
220+ direct : ( ) => repl . pexec ( `grid ${ optionsToString ( options ) } -t` )
221+ } ,
222+ asGrid = { mode : 'as-grid' ,
223+ fontawesome : 'fas fa-th' ,
224+ balloon : 'Display as grid' ,
225+ flush : 'right' ,
226+ actAsButton : true ,
227+ direct : ( ) => repl . pexec ( `grid ${ optionsToString ( options , [ 'timeline' , 't' ] ) } ` )
228+ } ,
229+
230+ switcher = options . timeline ? asGrid : asTimeline // switch between timeline and grid mode
211231
212232 return {
213233 type : 'custom' ,
214234 content,
215235 controlHeaders : true ,
216236
217237 // add zoom buttons to the mode button model
218- modes : modes ( 'grid' , options ) . concat ( [ zoomIn , zoomOut ] )
238+ modes : modes ( 'grid' , options ) . concat ( [ switcher , zoomIn , zoomOut ] )
219239 }
220240}
221241
@@ -241,7 +261,7 @@ const smartZoom = numCells => {
241261 *
242262 */
243263const _drawGrid = ( options , { sidecar, leftHeader, rightHeader} , content , groupData , sorter = countSorter , sortDir = + 1 , redraw ) => {
244- const { groups, summary } = groupData
264+ const { groups, summary, timeline } = groupData
245265
246266 sort ( groups , sorter , sortDir )
247267 sortActivations ( groups , startTimeSorter , + 1 )
@@ -251,7 +271,7 @@ const _drawGrid = (options, {sidecar, leftHeader, rightHeader}, content, groupDa
251271 gridGrid = redraw ? content . querySelector ( `.${ css . gridGrid } ` ) : document . createElement ( 'div' ) ,
252272 totalCount = groupData . totalCount ,
253273 zoomLevel = options . zoom || smartZoom ( totalCount ) ,
254- zoomLevelForDisplay = totalCount > 1000 ? - 2 : totalCount <= 100 ? zoomLevel : 0 // don't zoom in too far, if there are many cells to display
274+ zoomLevelForDisplay = options . timeline ? - 1 : totalCount > 1000 ? - 2 : totalCount <= 100 ? zoomLevel : 0 // don't zoom in too far, if there are many cells to display
255275
256276 gridGrid . className = `${ css . gridGrid } cell-container zoom_${ zoomLevelForDisplay } `
257277 gridGrid . setAttribute ( 'data-zoom-level' , zoomLevelForDisplay )
@@ -287,6 +307,11 @@ const _drawGrid = (options, {sidecar, leftHeader, rightHeader}, content, groupDa
287307 // add time range to the sidecar header
288308 displayTimeRange ( groupData , leftHeader )
289309
310+ if ( options . timeline ) {
311+ drawAsTimeline ( timeline , content , gridGrid , zoomLevelForDisplay , options )
312+ return
313+ }
314+
290315 groups . forEach ( ( group , groupIdx ) => {
291316 // prepare the grid structure
292317 const gridDom = redraw ? gridGrid . querySelector ( `.grid[data-action-path="${ group . path } "]` ) : document . createElement ( 'div' )
@@ -372,7 +397,9 @@ const _drawGrid = (options, {sidecar, leftHeader, rightHeader}, content, groupDa
372397 const cell = makeCellDom ( )
373398 cellContainer . appendChild ( cell )
374399 cell . classList . add ( 'grid-cell-newly-created' )
375- renderCell ( viewName , cell , activation , ! isSuccess ( activation ) , undefined , undefined ,
400+ renderCell ( viewName , cell , activation , ! isSuccess ( activation ) ,
401+ options . full ? activation . _duration : activation . executionTime ,
402+ undefined ,
376403 { zoom : zoomLevelForDisplay } )
377404 }
378405 } catch ( e ) {
@@ -381,8 +408,114 @@ const _drawGrid = (options, {sidecar, leftHeader, rightHeader}, content, groupDa
381408 } )
382409 }
383410 } )
411+ } // _drawGrid
412+
413+ /**
414+ * Return the minimum timestamp in the given list of activations
415+ *
416+ */
417+ const minTimestamp = activations => {
418+ return activations . reduce ( ( min , activation ) => {
419+ if ( min === 0 ) {
420+ return activation . start
421+ } else {
422+ return Math . min ( min , activation . start )
423+ }
424+ } , 0 )
384425}
385426
427+ /**
428+ * Render the grid as a timeline
429+ *
430+ */
431+ const drawAsTimeline = ( timelineData , content , gridGrid , zoomLevelForDisplay , options ) => {
432+ const { failure, activations, nBuckets } = timelineData
433+
434+ content . classList . add ( 'grid-as-timeline' )
435+
436+ const grid = document . createElement ( 'div' )
437+ grid . className = 'grid'
438+ gridGrid . appendChild ( grid )
439+
440+ const makeColumn = ( ) => {
441+ const gridRow = document . createElement ( 'div' )
442+ gridRow . className = 'grid-row'
443+ grid . appendChild ( gridRow )
444+
445+ return gridRow
446+ }
447+
448+ // for each column in the timeline... idx here is a column index
449+ for ( let idx = 0 , currentEmptyRunLength = 0 , currentRunMinTime ; idx < nBuckets ; idx ++ ) {
450+ if ( activations [ idx ] . length === 0 ) {
451+ // empty column
452+ if ( currentEmptyRunLength ++ === 0 && idx > 0 ) {
453+ // start of empty run; remember the timestamp
454+ currentRunMinTime = minTimestamp ( activations [ idx - 1 ] )
455+ }
456+
457+ continue
458+
459+ } else if ( currentEmptyRunLength > 5 ) {
460+ console . error ( 'EMPTY SWATH' )
461+ const currentRunMaxTime = minTimestamp ( activations [ idx ] ) ,
462+ swath = makeColumn ( )
463+
464+ swath . classList . add ( 'grid-timeline-empty-swath' )
465+
466+ if ( currentRunMinTime && currentRunMaxTime ) {
467+ const swathInner = document . createElement ( 'div' )
468+ swathInner . classList . add ( 'grid-timeline-empty-swath-inner' )
469+ swathInner . innerText = `${ prettyPrintDuration ( currentRunMaxTime - currentRunMinTime , { compact : true } ) } gap`
470+
471+ swath . appendChild ( swathInner )
472+ }
473+
474+ currentEmptyRunLength = 0
475+ }
476+
477+ const gridRow = makeColumn ( )
478+
479+ // sort the activations in the column, according to the user's desire
480+ if ( options . timeline === true || options . timeline === 'latency' ) {
481+ // default sort order
482+ activations [ idx ] . sort ( ( a , b ) => {
483+ const successA = isSuccess ( a ) ,
484+ successB = isSuccess ( b ) ,
485+ nA = options . full ? a . _duration : a . executionTime ,
486+ nB = options . full ? b . _duration : b . executionTime
487+ return ! successA && ! successB || successA && successB ? nA - nB
488+ : ! successA ? 1 : - 1
489+ } )
490+ } else if ( options . timeline === 'time' ) {
491+ activations [ idx ] . sort ( ( a , b ) => a . start - b . start )
492+ }
493+
494+ // now render the cells in the column; jdx here is a row index
495+ // within the current column's stack of cells
496+ activations [ idx ] . forEach ( ( activation , jdx ) => {
497+ const success = isSuccess ( activation ) ,
498+ latBucket = success && latencyBucket ( options . full ? activation . _duration : activation . executionTime )
499+
500+ const cell = makeCellDom ( ) ,
501+ isFailure = false ,
502+ duration = 0 ,
503+ nameInTooltip = true ,
504+ balloonPos = jdx >= 25 ? idx < 5 ? 'down-left' : 'down'
505+ : idx < 10 ? jdx < 5 ? 'up-left' : 'up' : jdx < 5 ? 'up-right' : 'up'
506+
507+ renderCell ( viewName , cell ,
508+ activation ,
509+ ! success ,
510+ options . full ? activation . _duration : activation . executionTime ,
511+ latBucket ,
512+ { zoom : zoomLevelForDisplay , balloonPos, nameInTooltip } )
513+
514+ gridRow . appendChild ( cell )
515+ } )
516+ }
517+ } // drawAsTimeline
518+
386519/**
387520 * This is the module
388521 *
0 commit comments