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 {TRACK_SHELL_WIDTH} from './css_constants'; 22import {DragGestureHandler} from './drag_gesture_handler'; 23import {globals} from './globals'; 24import {Panel, PanelSize} from './panel'; 25import {TimeScale} from './time_scale'; 26 27export class OverviewTimelinePanel extends Panel { 28 private width = 0; 29 private dragStartPx = 0; 30 private gesture?: DragGestureHandler; 31 private timeScale?: TimeScale; 32 private totTime = new TimeSpan(0, 0); 33 34 // Must explicitly type now; arguments types are no longer auto-inferred. 35 // https://github.com/Microsoft/TypeScript/issues/1373 36 onupdate({dom}: m.CVnodeDOM) { 37 this.width = dom.getBoundingClientRect().width; 38 this.totTime = new TimeSpan( 39 globals.state.traceTime.startSec, globals.state.traceTime.endSec); 40 this.timeScale = new TimeScale( 41 this.totTime, [TRACK_SHELL_WIDTH, assertExists(this.width)]); 42 43 if (this.gesture === undefined) { 44 this.gesture = new DragGestureHandler( 45 dom as HTMLElement, 46 this.onDrag.bind(this), 47 this.onDragStart.bind(this), 48 this.onDragEnd.bind(this)); 49 } 50 } 51 52 oncreate(vnode: m.CVnodeDOM) { 53 this.onupdate(vnode); 54 } 55 56 view() { 57 return m('.overview-timeline'); 58 } 59 60 renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) { 61 if (this.width === undefined) return; 62 if (this.timeScale === undefined) return; 63 const headerHeight = 25; 64 const tracksHeight = size.height - headerHeight; 65 66 // Draw time labels on the top header. 67 ctx.font = '10px Roboto Condensed'; 68 ctx.fillStyle = '#999'; 69 for (let i = 0; i < 100; i++) { 70 const xPos = 71 (i * (this.width - TRACK_SHELL_WIDTH) / 100) + TRACK_SHELL_WIDTH; 72 const t = this.timeScale.pxToTime(xPos); 73 if (xPos <= 0) continue; 74 if (xPos > this.width) break; 75 if (i % 10 === 0) { 76 ctx.fillRect(xPos - 1, 0, 1, headerHeight - 5); 77 ctx.fillText(timeToString(t - this.totTime.start), xPos + 5, 18); 78 } else { 79 ctx.fillRect(xPos - 1, 0, 1, 5); 80 } 81 } 82 83 // Draw mini-tracks with quanitzed density for each process. 84 if (globals.overviewStore.size > 0) { 85 const numTracks = globals.overviewStore.size; 86 let y = 0; 87 const trackHeight = (tracksHeight - 1) / numTracks; 88 for (const key of globals.overviewStore.keys()) { 89 const loads = globals.overviewStore.get(key)!; 90 for (let i = 0; i < loads.length; i++) { 91 const xStart = Math.floor(this.timeScale.timeToPx(loads[i].startSec)); 92 const xEnd = Math.ceil(this.timeScale.timeToPx(loads[i].endSec)); 93 const yOff = Math.floor(headerHeight + y * trackHeight); 94 const lightness = Math.ceil((1 - loads[i].load * 0.7) * 100); 95 ctx.fillStyle = `hsl(${hueForCpu(y)}, 50%, ${lightness}%)`; 96 ctx.fillRect(xStart, yOff, xEnd - xStart, Math.ceil(trackHeight)); 97 } 98 y++; 99 } 100 } 101 102 // Draw bottom border. 103 ctx.fillStyle = '#dadada'; 104 ctx.fillRect(0, size.height - 1, this.width, 1); 105 106 // Draw semi-opaque rects that occlude the non-visible time range. 107 const vizTime = globals.frontendLocalState.visibleWindowTime; 108 const vizStartPx = Math.floor(this.timeScale.timeToPx(vizTime.start)); 109 const vizEndPx = Math.ceil(this.timeScale.timeToPx(vizTime.end)); 110 111 ctx.fillStyle = 'rgba(200, 200, 200, 0.8)'; 112 ctx.fillRect( 113 TRACK_SHELL_WIDTH - 1, 114 headerHeight, 115 vizStartPx - TRACK_SHELL_WIDTH, 116 tracksHeight); 117 ctx.fillRect(vizEndPx, headerHeight, this.width - vizEndPx, tracksHeight); 118 119 // Draw brushes. 120 ctx.fillStyle = '#999'; 121 ctx.fillRect(vizStartPx - 1, headerHeight, 1, tracksHeight); 122 ctx.fillRect(vizEndPx, headerHeight, 1, tracksHeight); 123 } 124 125 onDrag(x: number) { 126 // Set visible time limits from selection. 127 if (this.timeScale === undefined) return; 128 let tStart = this.timeScale.pxToTime(this.dragStartPx); 129 let tEnd = this.timeScale.pxToTime(x); 130 if (tStart > tEnd) [tStart, tEnd] = [tEnd, tStart]; 131 const vizTime = new TimeSpan(tStart, tEnd); 132 globals.frontendLocalState.updateVisibleTime(vizTime); 133 globals.rafScheduler.scheduleRedraw(); 134 } 135 136 onDragStart(x: number) { 137 this.dragStartPx = x; 138 } 139 140 onDragEnd() { 141 this.dragStartPx = 0; 142 } 143} 144