1/* 2 * Copyright (C) 2022 Huawei Device Co., Ltd. 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 */ 15 16import { RangeSelectStruct, TraceRow } from './TraceRow'; 17import { Rect } from '../timer-shaft/Rect'; 18import { TimerShaftElement } from '../TimerShaftElement'; 19import { info } from '../../../../log/Log'; 20import './Extension'; 21import { SpSystemTrace } from '../../SpSystemTrace'; 22import { fuzzyQueryFuncRowData, queryFuncRowData } from '../../../database/sql/Func.sql'; 23import { SpLtpoChart } from '../../chart/SpLTPO'; 24import { isEmpty, isNotEmpty } from './Extension'; 25 26export class RangeSelect { 27 private rowsEL: HTMLDivElement | undefined | null; 28 private rowsPaneEL: HTMLDivElement | undefined | null; 29 isMouseDown: boolean = false; // @ts-ignore 30 public rangeTraceRow: Array<TraceRow<unknown>> | undefined; // @ts-ignore 31 public selectHandler: ((ds: Array<TraceRow<unknown>>, refreshCheckBox: boolean) => void) | undefined; 32 private startPageX: number = 0; 33 private startPageY: number = 0; 34 private endPageX: number = 0; 35 private endPageY: number = 0; 36 private timerShaftEL: TimerShaftElement | null | undefined; 37 private isHover: boolean = false; 38 private movingMark: string = ''; 39 private mark: { startMark: number; endMark: number } = { 40 startMark: 0, 41 endMark: 0, 42 }; 43 private trace: SpSystemTrace | null | undefined; 44 drag = false; 45 docomList: Array<number> = []; 46 repaintList: Array<number> = []; 47 presentList: Array<number> = []; 48 49 constructor(trace: SpSystemTrace | null | undefined) { 50 this.trace = trace; 51 this.timerShaftEL = trace?.timerShaftEL; 52 this.rowsEL = trace?.rowsEL; 53 this.rowsPaneEL = trace?.rowsPaneEL; 54 } 55 56 isInRowsEl(ev: MouseEvent): boolean { 57 return this.rowsPaneEL!.containPoint(ev, { left: 248 }); 58 } 59 60 isInSpacerEL(ev: MouseEvent): boolean { 61 return this.trace!.favoriteChartListEL!.containPoint(ev, { left: 248 }); 62 } 63 64 mouseDown(eventDown: MouseEvent): void { 65 this.startPageX = eventDown.pageX; 66 this.startPageY = eventDown.pageY; 67 if (TraceRow.rangeSelectObject) { 68 this.handleTouchMark(eventDown); 69 } else { 70 this.isHover = false; 71 } 72 if (this.isHover) { 73 this.isMouseDown = true; 74 return; 75 } 76 this.rangeTraceRow = []; 77 this.isMouseDown = true; 78 TraceRow.rangeSelectObject = undefined; 79 // 遍历当前可视区域所有的泳道,如果有render_service进程,查询该进程下对应泳道的方法存起来,以便框选时直接使用 80 this.trace?.visibleRows.forEach((row) => { 81 if (row.getAttribute('name')?.startsWith('render_service')) { 82 if (row.getAttribute('row-type') === 'process') { 83 this.queryRowsData(row.childrenList); 84 } else { 85 this.queryRowsData(row.parentRowEl!.childrenList); 86 } 87 return; 88 } 89 }); 90 } 91 92 // 对应查询方法行所有的数据 93 // @ts-ignore 94 queryRowsData(rowList: Array<TraceRow<unknown>>): void { 95 rowList.forEach((row): void => { 96 if (row.getAttribute('row-type') === 'func') { 97 if (row.getAttribute('name')?.startsWith('render_service')) { 98 this.saveFrameRateData(row, 'H:RSMainThread::DoComposition'); 99 } else if (row.getAttribute('name')?.startsWith('RSHardwareThrea')) { 100 this.saveFrameRateData(row, 'H:Repaint'); 101 } else if (row.getAttribute('name')?.startsWith('Present')) { 102 this.savePresentData(row, 'H:Waiting for Present Fence'); 103 } 104 } 105 }); 106 } 107 108 // 查到所有的数据存储起来 109 // @ts-ignore 110 saveFrameRateData(row: TraceRow<unknown>, funcName: string): void { 111 let dataList: unknown = []; 112 queryFuncRowData(funcName, Number(row?.getAttribute('row-id'))).then((res): void => { 113 if (res.length) { 114 res.forEach((item): void => { 115 // @ts-ignore 116 dataList?.push({ startTime: item.startTime!, tid: item.tid }); 117 }); 118 if (funcName === 'H:RSMainThread::DoComposition') { 119 // @ts-ignore 120 this.docomList = dataList; 121 } else { 122 // @ts-ignore 123 this.repaintList = dataList; 124 } 125 } 126 }); 127 } 128 // 查到present泳道所有的数据存储起来 129 // @ts-ignore 130 savePresentData(row: TraceRow<unknown>, funcName: string): void { 131 let dataList: unknown = []; 132 fuzzyQueryFuncRowData(funcName, Number(row?.getAttribute('row-id'))).then((res): void => { 133 if (res.length) { 134 res.forEach((item): void => { 135 // @ts-ignore 136 dataList?.push({ endTime: item.endTime!, tid: item.tid }); 137 }); // @ts-ignore 138 this.presentList = dataList; 139 } 140 }); 141 } 142 143 mouseUp(mouseEventUp?: MouseEvent): void { 144 if (mouseEventUp) { 145 this.endPageX = mouseEventUp.pageX; 146 this.endPageY = mouseEventUp.pageY; 147 } 148 if (this.drag) { 149 if (this.selectHandler) { 150 this.selectHandler(this.rangeTraceRow || [], !this.isHover); 151 } 152 //查询render_service数据 153 if (this.rangeTraceRow?.length) { 154 this.checkRowsName(this.rangeTraceRow); 155 } 156 } 157 this.isMouseDown = false; 158 this.isHover = false; 159 } 160 // @ts-ignore 161 checkRowsName(rowList: Array<TraceRow<unknown>>): void { 162 rowList.forEach((row): void => { 163 if ( 164 row.getAttribute('row-type') === 'func' && 165 row.parentRowEl?.getAttribute('name')?.startsWith('render_service') 166 ) { 167 row.frameRateList = []; 168 if (row.getAttribute('name')?.startsWith('render_service')) { 169 this.filterRateData(row, this.docomList); 170 } else if (row.getAttribute('name')?.startsWith('RSHardwareThrea')) { 171 this.filterRateData(row, this.repaintList); 172 } else if (row.getAttribute('name')?.startsWith('Present')) { 173 this.filterPresentData(row, this.presentList); 174 } 175 } 176 }); 177 } 178 179 // 过滤处理数据 180 // @ts-ignore 181 filterRateData(row: TraceRow<unknown>, data: unknown): void { 182 // @ts-ignore 183 data.forEach((it: unknown): void => { 184 if ( 185 // @ts-ignore 186 it.startTime >= TraceRow.rangeSelectObject!.startNS! && // @ts-ignore 187 it.startTime <= TraceRow.rangeSelectObject!.endNS! && // @ts-ignore 188 Number(row.rowId) === Number(it.tid) 189 ) { 190 // @ts-ignore 191 row.frameRateList?.push(it.startTime); 192 } 193 }); 194 if (row.frameRateList?.length) { 195 if (row.frameRateList.length < 2) { 196 row.frameRateList = []; 197 } else { 198 const CONVERT_SECONDS = 1000000000; 199 let cutres: number = row.frameRateList[row.frameRateList.length - 1] - row.frameRateList[0]; 200 row.avgRateTxt = `${(((row.frameRateList.length - 1) / cutres) * CONVERT_SECONDS).toFixed(1)}fps`; 201 } 202 } 203 } 204 205 // 过滤并处理present数据 206 // @ts-ignore 207 filterPresentData(row: TraceRow<unknown>, data: unknown): void { 208 // @ts-ignore 209 data.forEach((it: unknown): void => { 210 if ( 211 // @ts-ignore 212 it.endTime >= TraceRow.rangeSelectObject!.startNS! && // @ts-ignore 213 it.endTime <= TraceRow.rangeSelectObject!.endNS! && // @ts-ignore 214 Number(row.rowId) === Number(it.tid) 215 ) { 216 // @ts-ignore 217 row.frameRateList?.push(it.endTime); 218 } 219 }); 220 if (row.frameRateList?.length) { 221 if (row.frameRateList?.length < 2) { 222 row.frameRateList = []; 223 } else { 224 let hitchTimeList: Array<number> = []; 225 for (let i = 0; i < SpLtpoChart.sendHitchDataArr.length; i++) { 226 if ( 227 SpLtpoChart.sendHitchDataArr[i].startTs! >= row.frameRateList[0]! && 228 SpLtpoChart.sendHitchDataArr[i].startTs! < row.frameRateList[row.frameRateList.length - 1]! 229 ) { 230 hitchTimeList.push(SpLtpoChart.sendHitchDataArr[i].value!); 231 } else if (SpLtpoChart.sendHitchDataArr[i].startTs! >= row.frameRateList[row.frameRateList.length - 1]!) { 232 break; 233 } 234 } 235 const CONVERT_SECONDS = 1000000000; 236 let cutres: number = row.frameRateList[row.frameRateList.length - 1] - row.frameRateList[0]; 237 let avgRate: string = `${(((row.frameRateList.length - 1) / cutres) * CONVERT_SECONDS).toFixed(1)}fps`; 238 let sum: number = hitchTimeList.reduce((accumulator, currentValue) => accumulator + currentValue, 0); // ∑hitchTimeData 239 let hitchRate: number = 240 sum / ((TraceRow.rangeSelectObject!.endNS! - TraceRow.rangeSelectObject!.startNS!) / 1000000000); 241 row.avgRateTxt = 242 `${avgRate} ` + ',' + ' ' + 'HitchTime:' + ` ${sum.toFixed(1)}ms` + ' ' + ',' + ` ${hitchRate.toFixed(2)}ms/s`; 243 } 244 } 245 } 246 247 isDrag(): boolean { 248 return this.startPageX !== this.endPageX; 249 } 250 251 isTouchMark(ev: MouseEvent): boolean { 252 let notTimeHeight: boolean = this.rowsPaneEL!.containPoint(ev, { 253 left: 248, 254 top: -45, 255 }); 256 if (!notTimeHeight) { 257 return false; 258 } 259 if ((isEmpty(this.rangeTraceRow) ?? false) && !this.isMouseDown) { 260 this.isHover = false; 261 } 262 return notTimeHeight && (isNotEmpty(this.rangeTraceRow) ?? false) && !this.isMouseDown; 263 } 264 265 mouseOut(mouseEventOut: MouseEvent): void { 266 this.endPageX = mouseEventOut.pageX; 267 this.endPageY = mouseEventOut.pageY; 268 if (this.drag) { 269 if (this.selectHandler && this.isMouseDown) { 270 this.selectHandler(this.rangeTraceRow || [], !this.isHover); 271 } 272 } 273 document.getSelection()?.removeAllRanges(); 274 this.isMouseDown = false; 275 this.isHover = false; 276 } 277 // @ts-ignore 278 mouseMove(rows: Array<TraceRow<unknown>>, ev: MouseEvent): void { 279 this.endPageX = ev.pageX; 280 this.endPageY = ev.pageY; 281 if (this.isTouchMark(ev) && TraceRow.rangeSelectObject) { 282 this.handleTouchMark(ev); 283 } else { 284 document.body.style.cursor = 'default'; 285 } 286 if (this.isHover && this.isMouseDown) { 287 this.handleRangeSelectAndDraw(rows, ev); 288 return; 289 } 290 if (!this.isMouseDown) { 291 this.isHover = false; 292 this.handleDrawForNotMouseDown(); 293 return; 294 } 295 this.handleRangeSelect(rows); 296 this.timerShaftEL!.sportRuler!.isRangeSelect = this.rangeTraceRow!.length > 0; 297 this.timerShaftEL!.sportRuler!.draw(); 298 } 299 // @ts-ignore 300 private handleRangeSelect(rows: Array<TraceRow<unknown>>): void { 301 let rangeSelect: RangeSelectStruct | undefined; 302 let favoriteRect = this.trace?.favoriteChartListEL?.getBoundingClientRect(); 303 let favoriteLimit = favoriteRect!.top + favoriteRect!.height; 304 this.rangeTraceRow = rows.filter((it): boolean => { 305 let domRect = it.getBoundingClientRect(); 306 let itRect = { x: domRect.x, y: domRect.y, width: domRect.width, height: domRect.height }; 307 if (itRect.y < favoriteLimit && !it.collect) { 308 let offset = favoriteLimit - itRect.y; 309 itRect.y = itRect.y + offset; 310 itRect.height = itRect.height - offset; 311 } 312 if (it.sticky) { 313 itRect.y = 0; 314 itRect.height = 0; 315 } 316 let result: boolean; 317 if ( 318 Rect.intersect( 319 itRect as Rect, 320 { 321 x: Math.min(this.startPageX, this.endPageX), 322 y: Math.min(this.startPageY, this.endPageY), 323 width: Math.abs(this.startPageX - this.endPageX), 324 height: Math.abs(this.startPageY - this.endPageY), 325 } as Rect 326 ) 327 ) { 328 if (!rangeSelect) { 329 it.setTipLeft(0, null); 330 rangeSelect = new RangeSelectStruct(); 331 let startX = Math.min(this.startPageX, this.endPageX) - it.describeEl!.getBoundingClientRect().right; 332 let endX = Math.max(this.startPageX, this.endPageX) - it.describeEl!.getBoundingClientRect().right; 333 if (startX <= 0) { 334 startX = 0; 335 } 336 if (endX > it.frame.width) { 337 endX = it.frame.width; 338 } 339 rangeSelect.startX = startX; 340 rangeSelect.endX = endX; 341 rangeSelect.startNS = RangeSelect.SetNS(it, startX); 342 rangeSelect.endNS = RangeSelect.SetNS(it, endX); 343 } 344 TraceRow.rangeSelectObject = rangeSelect; 345 it.rangeSelect = true; 346 result = true; 347 } else { 348 it.rangeSelect = false; 349 result = false; 350 } 351 return result; 352 }); 353 if (this.rangeTraceRow && this.rangeTraceRow.length) { 354 if (this.rangeTraceRow[0].parentRowEl) { 355 for (let i = 0; i < this.rangeTraceRow[0].parentRowEl.childrenList.length; i++) { 356 this.rangeTraceRow[0].parentRowEl.childrenList[i].frameRateList = []; 357 this.rangeTraceRow[0].parentRowEl.childrenList[i].avgRateTxt = null; 358 } 359 } 360 } 361 } 362 363 private handleDrawForNotMouseDown(): void { 364 this.timerShaftEL!.sportRuler!.isRangeSelect = isNotEmpty(this.rangeTraceRow) ?? false; 365 this.timerShaftEL!.sportRuler!.draw(); 366 } 367 // @ts-ignore 368 private handleRangeSelectAndDraw(rows: Array<TraceRow<unknown>>, ev: MouseEvent): void { 369 let rangeSelect: RangeSelectStruct | undefined; 370 let result: boolean; 371 this.rangeTraceRow = rows.filter((it) => { 372 if (it.rangeSelect) { 373 if (!rangeSelect) { 374 rangeSelect = new RangeSelectStruct(); 375 let mouseX = ev.pageX - this.rowsEL!.getBoundingClientRect().left - 248; 376 mouseX = mouseX < 0 ? 0 : mouseX; 377 let markA = this.movingMark === 'markA' ? mouseX : this.mark.startMark; 378 let markB = this.movingMark === 'markB' ? mouseX : this.mark.endMark; 379 let startX = markA < markB ? markA : markB; 380 let endX = markB < markA ? markA : markB; 381 rangeSelect.startX = startX; 382 rangeSelect.endX = endX; 383 rangeSelect.startNS = RangeSelect.SetNS(it, startX); 384 rangeSelect.endNS = RangeSelect.SetNS(it, endX); 385 if (rangeSelect.startNS <= TraceRow.range!.startNS) { 386 rangeSelect.startNS = TraceRow.range!.startNS; 387 } 388 if (rangeSelect.endNS >= TraceRow.range!.endNS) { 389 rangeSelect.endNS = TraceRow.range!.endNS; 390 } 391 if (startX < 0) { 392 rangeSelect.startNS = TraceRow.rangeSelectObject!.startNS!; 393 } // @ts-ignore 394 if (endX > it.frame.width) { 395 rangeSelect.endNS = TraceRow.rangeSelectObject!.endNS!; 396 } 397 } 398 TraceRow.rangeSelectObject = rangeSelect; 399 result = true; 400 } else { 401 result = false; 402 } 403 return result; 404 }); 405 this.timerShaftEL!.sportRuler!.isRangeSelect = (this.rangeTraceRow?.length || 0) > 0; 406 this.timerShaftEL!.sportRuler!.draw(); 407 } 408 409 private handleTouchMark(ev: MouseEvent): void { 410 info('isTouchMark'); 411 let x1 = 412 ((TraceRow.rangeSelectObject!.startNS! - TraceRow.range!.startNS) * 413 (this.timerShaftEL?.canvas?.clientWidth || 0)) / 414 (TraceRow.range!.endNS - TraceRow.range!.startNS); 415 let x2 = 416 ((TraceRow.rangeSelectObject!.endNS! - TraceRow.range!.startNS) * (this.timerShaftEL?.canvas?.clientWidth || 0)) / 417 (TraceRow.range!.endNS - TraceRow.range!.startNS); 418 this.mark = { startMark: x1, endMark: x2 }; 419 let mouseX = ev.pageX - this.rowsPaneEL!.getBoundingClientRect().left - 248; 420 if (mouseX > x1 - 5 && mouseX < x1 + 5) { 421 this.isHover = true; 422 document.body.style.cursor = 'ew-resize'; 423 this.movingMark = x1 < x2 ? 'markA' : 'markB'; 424 } else if (mouseX > x2 - 5 && mouseX < x2 + 5) { 425 this.isHover = true; 426 document.body.style.cursor = 'ew-resize'; 427 this.movingMark = x2 < x1 ? 'markA' : 'markB'; 428 } else { 429 this.isHover = false; 430 document.body.style.cursor = 'default'; 431 } 432 } 433 // @ts-ignore 434 static SetNS(row: TraceRow<unknown>, num: number): number { 435 return Math.floor( 436 // @ts-ignore 437 ((TraceRow.range!.endNS - TraceRow.range!.startNS) * num) / row.frame.width + TraceRow.range!.startNS! 438 ); 439 } 440} 441