// Copyright (C) 2018 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import * as m from 'mithril'; import {assertExists} from '../base/logging'; import {TimeSpan, timeToString} from '../common/time'; import {hueForCpu} from './colorizer'; import {TRACK_SHELL_WIDTH} from './css_constants'; import {DragGestureHandler} from './drag_gesture_handler'; import {globals} from './globals'; import {Panel, PanelSize} from './panel'; import {TimeScale} from './time_scale'; export class OverviewTimelinePanel extends Panel { private width = 0; private dragStartPx = 0; private gesture?: DragGestureHandler; private timeScale?: TimeScale; private totTime = new TimeSpan(0, 0); // Must explicitly type now; arguments types are no longer auto-inferred. // https://github.com/Microsoft/TypeScript/issues/1373 onupdate({dom}: m.CVnodeDOM) { this.width = dom.getBoundingClientRect().width; this.totTime = new TimeSpan( globals.state.traceTime.startSec, globals.state.traceTime.endSec); this.timeScale = new TimeScale( this.totTime, [TRACK_SHELL_WIDTH, assertExists(this.width)]); if (this.gesture === undefined) { this.gesture = new DragGestureHandler( dom as HTMLElement, this.onDrag.bind(this), this.onDragStart.bind(this), this.onDragEnd.bind(this)); } } oncreate(vnode: m.CVnodeDOM) { this.onupdate(vnode); } view() { return m('.overview-timeline'); } renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) { if (this.width === undefined) return; if (this.timeScale === undefined) return; const headerHeight = 25; const tracksHeight = size.height - headerHeight; // Draw time labels on the top header. ctx.font = '10px Roboto Condensed'; ctx.fillStyle = '#999'; for (let i = 0; i < 100; i++) { const xPos = (i * (this.width - TRACK_SHELL_WIDTH) / 100) + TRACK_SHELL_WIDTH; const t = this.timeScale.pxToTime(xPos); if (xPos <= 0) continue; if (xPos > this.width) break; if (i % 10 === 0) { ctx.fillRect(xPos - 1, 0, 1, headerHeight - 5); ctx.fillText(timeToString(t - this.totTime.start), xPos + 5, 18); } else { ctx.fillRect(xPos - 1, 0, 1, 5); } } // Draw mini-tracks with quanitzed density for each process. if (globals.overviewStore.size > 0) { const numTracks = globals.overviewStore.size; let y = 0; const trackHeight = (tracksHeight - 1) / numTracks; for (const key of globals.overviewStore.keys()) { const loads = globals.overviewStore.get(key)!; for (let i = 0; i < loads.length; i++) { const xStart = Math.floor(this.timeScale.timeToPx(loads[i].startSec)); const xEnd = Math.ceil(this.timeScale.timeToPx(loads[i].endSec)); const yOff = Math.floor(headerHeight + y * trackHeight); const lightness = Math.ceil((1 - loads[i].load * 0.7) * 100); ctx.fillStyle = `hsl(${hueForCpu(y)}, 50%, ${lightness}%)`; ctx.fillRect(xStart, yOff, xEnd - xStart, Math.ceil(trackHeight)); } y++; } } // Draw bottom border. ctx.fillStyle = '#dadada'; ctx.fillRect(0, size.height - 1, this.width, 1); // Draw semi-opaque rects that occlude the non-visible time range. const vizTime = globals.frontendLocalState.visibleWindowTime; const vizStartPx = Math.floor(this.timeScale.timeToPx(vizTime.start)); const vizEndPx = Math.ceil(this.timeScale.timeToPx(vizTime.end)); ctx.fillStyle = 'rgba(200, 200, 200, 0.8)'; ctx.fillRect( TRACK_SHELL_WIDTH - 1, headerHeight, vizStartPx - TRACK_SHELL_WIDTH, tracksHeight); ctx.fillRect(vizEndPx, headerHeight, this.width - vizEndPx, tracksHeight); // Draw brushes. ctx.fillStyle = '#999'; ctx.fillRect(vizStartPx - 1, headerHeight, 1, tracksHeight); ctx.fillRect(vizEndPx, headerHeight, 1, tracksHeight); } onDrag(x: number) { // Set visible time limits from selection. if (this.timeScale === undefined) return; let tStart = this.timeScale.pxToTime(this.dragStartPx); let tEnd = this.timeScale.pxToTime(x); if (tStart > tEnd) [tStart, tEnd] = [tEnd, tStart]; const vizTime = new TimeSpan(tStart, tEnd); globals.frontendLocalState.updateVisibleTime(vizTime); globals.rafScheduler.scheduleRedraw(); } onDragStart(x: number) { this.dragStartPx = x; } onDragEnd() { this.dragStartPx = 0; } }