1// Copyright (C) 2018 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import * as m from 'mithril'; 16 17import {assertExists} from '../base/logging'; 18import {TimeSpan, timeToString} from '../common/time'; 19 20import {hueForCpu} from './colorizer'; 21import {DragGestureHandler} from './drag_gesture_handler'; 22import {globals} from './globals'; 23import {Panel, PanelSize} from './panel'; 24import {TimeScale} from './time_scale'; 25 26export class OverviewTimelinePanel extends Panel { 27 private width = 0; 28 private dragStartPx = 0; 29 private gesture?: DragGestureHandler; 30 private timeScale?: TimeScale; 31 private totTime = new TimeSpan(0, 0); 32 33 // Must explicitly type now; arguments types are no longer auto-inferred. 34 // https://github.com/Microsoft/TypeScript/issues/1373 35 onupdate({dom}: m.CVnodeDOM) { 36 this.width = dom.getBoundingClientRect().width; 37 this.totTime = new TimeSpan( 38 globals.state.traceTime.startSec, globals.state.traceTime.endSec); 39 this.timeScale = new TimeScale(this.totTime, [0, assertExists(this.width)]); 40 41 if (this.gesture === undefined) { 42 this.gesture = new DragGestureHandler( 43 dom as HTMLElement, 44 this.onDrag.bind(this), 45 this.onDragStart.bind(this), 46 this.onDragEnd.bind(this)); 47 } 48 } 49 50 oncreate(vnode: m.CVnodeDOM) { 51 this.onupdate(vnode); 52 } 53 54 view() { 55 return m('.overview-timeline'); 56 } 57 58 renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) { 59 if (this.width === undefined) return; 60 if (this.timeScale === undefined) return; 61 const headerHeight = 25; 62 const tracksHeight = size.height - headerHeight; 63 64 // Draw time labels on the top header. 65 ctx.font = '10px Google Sans'; 66 ctx.fillStyle = '#999'; 67 for (let i = 0; i < 100; i++) { 68 const xPos = i * this.width / 100; 69 const t = this.timeScale.pxToTime(xPos); 70 if (xPos <= 0) continue; 71 if (xPos > this.width) break; 72 if (i % 10 === 0) { 73 ctx.fillRect(xPos, 0, 1, headerHeight - 5); 74 ctx.fillText(timeToString(t - this.totTime.start), xPos + 5, 18); 75 } else { 76 ctx.fillRect(xPos, 0, 1, 5); 77 } 78 } 79 80 // Draw mini-tracks with quanitzed density for each process. 81 if (globals.overviewStore.size > 0) { 82 const numTracks = globals.overviewStore.size; 83 let y = 0; 84 const trackHeight = (tracksHeight - 1) / numTracks; 85 for (const key of globals.overviewStore.keys()) { 86 const loads = globals.overviewStore.get(key)!; 87 for (let i = 0; i < loads.length; i++) { 88 const xStart = Math.floor(this.timeScale.timeToPx(loads[i].startSec)); 89 const xEnd = Math.ceil(this.timeScale.timeToPx(loads[i].endSec)); 90 const yOff = Math.floor(headerHeight + y * trackHeight); 91 const lightness = Math.ceil((1 - loads[i].load * 0.7) * 100); 92 ctx.fillStyle = `hsl(${hueForCpu(y)}, 50%, ${lightness}%)`; 93 ctx.fillRect(xStart, yOff, xEnd - xStart, Math.ceil(trackHeight)); 94 } 95 y++; 96 } 97 } 98 99 // Draw bottom border. 100 ctx.fillStyle = 'hsl(219, 40%, 50%)'; 101 ctx.fillRect(0, size.height - 1, this.width, 1); 102 103 // Draw semi-opaque rects that occlude the non-visible time range. 104 const vizTime = globals.frontendLocalState.visibleWindowTime; 105 const vizStartPx = Math.floor(this.timeScale.timeToPx(vizTime.start)); 106 const vizEndPx = Math.ceil(this.timeScale.timeToPx(vizTime.end)); 107 108 ctx.fillStyle = 'rgba(200, 200, 200, 0.8)'; 109 ctx.fillRect(0, headerHeight, vizStartPx, tracksHeight); 110 ctx.fillRect(vizEndPx, headerHeight, this.width - vizEndPx, tracksHeight); 111 112 // Draw brushes. 113 ctx.fillStyle = '#333'; 114 ctx.fillRect(vizStartPx - 1, headerHeight, 1, tracksHeight); 115 ctx.fillRect(vizEndPx, headerHeight, 1, tracksHeight); 116 } 117 118 onDrag(x: number) { 119 // Set visible time limits from selection. 120 if (this.timeScale === undefined) return; 121 let tStart = this.timeScale.pxToTime(this.dragStartPx); 122 let tEnd = this.timeScale.pxToTime(x); 123 if (tStart > tEnd) [tStart, tEnd] = [tEnd, tStart]; 124 const vizTime = new TimeSpan(tStart, tEnd); 125 globals.frontendLocalState.updateVisibleTime(vizTime); 126 globals.rafScheduler.scheduleRedraw(); 127 } 128 129 onDragStart(x: number) { 130 this.dragStartPx = x; 131 } 132 133 onDragEnd() { 134 this.dragStartPx = 0; 135 } 136} 137