1// Copyright (C) 2019 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 {searchEq, searchRange, searchSegment} from '../../base/binary_search'; 16import {assertTrue} from '../../base/logging'; 17import {Actions} from '../../common/actions'; 18import {colorForThread} from '../../common/colorizer'; 19import {checkerboardExcept} from '../../frontend/checkerboard'; 20import {globals} from '../../frontend/globals'; 21import {NewTrackArgs, Track} from '../../frontend/track'; 22import {trackRegistry} from '../../frontend/track_registry'; 23 24import { 25 Config, 26 Data, 27 PROCESS_SCHEDULING_TRACK_KIND, 28} from './common'; 29 30const MARGIN_TOP = 5; 31const RECT_HEIGHT = 30; 32const TRACK_HEIGHT = MARGIN_TOP * 2 + RECT_HEIGHT; 33 34class ProcessSchedulingTrack extends Track<Config, Data> { 35 static readonly kind = PROCESS_SCHEDULING_TRACK_KIND; 36 static create(args: NewTrackArgs): ProcessSchedulingTrack { 37 return new ProcessSchedulingTrack(args); 38 } 39 40 private mousePos?: {x: number, y: number}; 41 private utidHoveredInThisTrack = -1; 42 43 constructor(args: NewTrackArgs) { 44 super(args); 45 } 46 47 getHeight(): number { 48 return TRACK_HEIGHT; 49 } 50 51 renderCanvas(ctx: CanvasRenderingContext2D): void { 52 // TODO: fonts and colors should come from the CSS and not hardcoded here. 53 const {timeScale, visibleWindowTime} = globals.frontendLocalState; 54 const data = this.data(); 55 56 if (data === undefined) return; // Can't possibly draw anything. 57 58 // If the cached trace slices don't fully cover the visible time range, 59 // show a gray rectangle with a "Loading..." label. 60 checkerboardExcept( 61 ctx, 62 this.getHeight(), 63 timeScale.timeToPx(visibleWindowTime.start), 64 timeScale.timeToPx(visibleWindowTime.end), 65 timeScale.timeToPx(data.start), 66 timeScale.timeToPx(data.end)); 67 68 assertTrue(data.starts.length === data.ends.length); 69 assertTrue(data.starts.length === data.utids.length); 70 71 const rawStartIdx = 72 data.ends.findIndex(end => end >= visibleWindowTime.start); 73 const startIdx = rawStartIdx === -1 ? data.starts.length : rawStartIdx; 74 75 const [, rawEndIdx] = searchSegment(data.starts, visibleWindowTime.end); 76 const endIdx = rawEndIdx === -1 ? data.starts.length : rawEndIdx; 77 78 const cpuTrackHeight = Math.floor(RECT_HEIGHT / data.maxCpu); 79 80 for (let i = startIdx; i < endIdx; i++) { 81 const tStart = data.starts[i]; 82 const tEnd = data.ends[i]; 83 const utid = data.utids[i]; 84 const cpu = data.cpus[i]; 85 86 const rectStart = timeScale.timeToPx(tStart); 87 const rectEnd = timeScale.timeToPx(tEnd); 88 const rectWidth = rectEnd - rectStart; 89 if (rectWidth < 0.3) continue; 90 91 const threadInfo = globals.threads.get(utid); 92 const pid = (threadInfo ? threadInfo.pid : -1) || -1; 93 94 const isHovering = globals.state.hoveredUtid !== -1; 95 const isThreadHovered = globals.state.hoveredUtid === utid; 96 const isProcessHovered = globals.state.hoveredPid === pid; 97 const color = colorForThread(threadInfo); 98 if (isHovering && !isThreadHovered) { 99 if (!isProcessHovered) { 100 color.l = 90; 101 color.s = 0; 102 } else { 103 color.l = Math.min(color.l + 30, 80); 104 color.s -= 20; 105 } 106 } else { 107 color.l = Math.min(color.l + 10, 60); 108 color.s -= 20; 109 } 110 ctx.fillStyle = `hsl(${color.h}, ${color.s}%, ${color.l}%)`; 111 const y = MARGIN_TOP + cpuTrackHeight * cpu + cpu; 112 ctx.fillRect(rectStart, y, rectEnd - rectStart, cpuTrackHeight); 113 } 114 115 const hoveredThread = globals.threads.get(this.utidHoveredInThisTrack); 116 if (hoveredThread !== undefined && this.mousePos !== undefined) { 117 const tidText = `T: ${hoveredThread.threadName} [${hoveredThread.tid}]`; 118 if (hoveredThread.pid) { 119 const pidText = `P: ${hoveredThread.procName} [${hoveredThread.pid}]`; 120 this.drawTrackHoverTooltip(ctx, this.mousePos, pidText, tidText); 121 } else { 122 this.drawTrackHoverTooltip(ctx, this.mousePos, tidText); 123 } 124 } 125 } 126 127 onMouseMove(pos: {x: number, y: number}) { 128 const data = this.data(); 129 this.mousePos = pos; 130 if (data === undefined) return; 131 if (pos.y < MARGIN_TOP || pos.y > MARGIN_TOP + RECT_HEIGHT) { 132 this.utidHoveredInThisTrack = -1; 133 globals.dispatch(Actions.setHoveredUtidAndPid({utid: -1, pid: -1})); 134 return; 135 } 136 137 const cpuTrackHeight = Math.floor(RECT_HEIGHT / data.maxCpu); 138 const cpu = Math.floor((pos.y - MARGIN_TOP) / (cpuTrackHeight + 1)); 139 const {timeScale} = globals.frontendLocalState; 140 const t = timeScale.pxToTime(pos.x); 141 142 const [i, j] = searchRange(data.starts, t, searchEq(data.cpus, cpu)); 143 if (i === j || i >= data.starts.length || t > data.ends[i]) { 144 this.utidHoveredInThisTrack = -1; 145 globals.dispatch(Actions.setHoveredUtidAndPid({utid: -1, pid: -1})); 146 return; 147 } 148 149 const utid = data.utids[i]; 150 this.utidHoveredInThisTrack = utid; 151 const threadInfo = globals.threads.get(utid); 152 const pid = threadInfo ? (threadInfo.pid ? threadInfo.pid : -1) : -1; 153 globals.dispatch(Actions.setHoveredUtidAndPid({utid, pid})); 154 } 155 156 onMouseOut() { 157 this.utidHoveredInThisTrack = -1; 158 globals.dispatch(Actions.setHoveredUtidAndPid({utid: -1, pid: -1})); 159 this.mousePos = undefined; 160 } 161} 162 163trackRegistry.register(ProcessSchedulingTrack); 164