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 { BaseElement, element } from '../base-ui/BaseElement'; 17import '../base-ui/menu/LitMainMenu'; 18import '../base-ui/icon/LitIcon'; 19import { SpMetrics } from './component/SpMetrics'; 20import { SpHelp } from './component/SpHelp'; 21import './component/SpHelp'; 22import { SpQuerySQL } from './component/SpQuerySQL'; 23import './component/SpQuerySQL'; 24import { SpSystemTrace } from './component/SpSystemTrace'; 25import { LitMainMenu, MenuItem } from '../base-ui/menu/LitMainMenu'; 26import { SpInfoAndStats } from './component/SpInfoAndStas'; 27import '../base-ui/progress-bar/LitProgressBar'; 28import { LitProgressBar } from '../base-ui/progress-bar/LitProgressBar'; 29import { SpRecordTrace } from './component/SpRecordTrace'; 30import { SpWelcomePage } from './component/SpWelcomePage'; 31import { LitSearch } from './component/trace/search/Search'; 32import { DbPool, threadPool } from './database/SqlLite'; 33import './component/trace/search/Search'; 34import './component/SpWelcomePage'; 35import './component/SpSystemTrace'; 36import './component/SpRecordTrace'; 37import './component/SpMetrics'; 38import './component/SpInfoAndStas'; 39import './component/trace/base/TraceRow'; 40import './component/schedulingAnalysis/SpSchedulingAnalysis'; 41import { error, info, log } from '../log/Log'; 42import { LitMainMenuGroup } from '../base-ui/menu/LitMainMenuGroup'; 43import { LitMainMenuItem } from '../base-ui/menu/LitMainMenuItem'; 44import { LitIcon } from '../base-ui/icon/LitIcon'; 45import { TraceRow } from './component/trace/base/TraceRow'; 46import { SpSchedulingAnalysis } from './component/schedulingAnalysis/SpSchedulingAnalysis'; 47import './component/trace/base/TraceRowConfig'; 48import { TraceRowConfig } from './component/trace/base/TraceRowConfig'; 49import { ColorUtils } from './component/trace/base/ColorUtils'; 50import { SpStatisticsHttpUtil } from '../statistics/util/SpStatisticsHttpUtil'; 51import { FlagsConfig, SpFlags } from './component/SpFlags'; 52import './component/SpFlags'; 53import './component/trace/base/CustomThemeColor'; 54import { CustomThemeColor, Theme } from './component/trace/base/CustomThemeColor'; 55import { convertPool } from './database/Convert'; 56import { LongTraceDBUtils } from './database/LongTraceDBUtils'; 57import { type SpKeyboard } from './component/SpKeyboard'; 58import './component/SpKeyboard'; 59import { parseKeyPathJson } from './component/Utils'; 60import { Utils } from './component/trace/base/Utils'; 61import { 62 applicationHtml, 63 clearTraceFileCache, 64 findFreeSizeAlgorithm, 65 getCurrentDataTime, 66 indexedDataToBufferData, 67 postLog, 68 readTraceFileBuffer, 69} from './SpApplicationPublicFunc'; 70import { queryExistFtrace } from './database/sql/SqlLite.sql'; 71import '../base-ui/chart/scatter/LitChartScatter'; 72import { SpThirdParty } from './component/SpThirdParty'; 73import './component/SpThirdParty'; 74 75@element('sp-application') 76export class SpApplication extends BaseElement { 77 private static loadingProgress: number = 0; 78 private static progressStep: number = 2; 79 longTraceHeadMessageList: Array<{ 80 pageNum: number; 81 data: ArrayBuffer; 82 }> = []; 83 84 longTraceDataList: Array<{ 85 fileType: string; 86 index: number; 87 pageNum: number; 88 startOffsetSize: number; 89 endOffsetSize: number; 90 }> = []; 91 92 longTraceTypeMessageMap: 93 | Map< 94 number, 95 Array<{ 96 fileType: string; 97 startIndex: number; 98 endIndex: number; 99 size: number; 100 }> 101 > 102 | undefined 103 | null; 104 static skinChange: Function | null | undefined = null; 105 static skinChange2: Function | null | undefined = null; 106 skinChangeArray: Array<Function> = []; 107 private rootEL: HTMLDivElement | undefined | null; 108 private headerDiv: HTMLDivElement | undefined | null; 109 private spWelcomePage: SpWelcomePage | undefined | null; 110 private spMetrics: SpMetrics | undefined | null; 111 private spQuerySQL: SpQuerySQL | undefined | null; 112 private spInfoAndStats: SpInfoAndStats | undefined | null; 113 private spSystemTrace: SpSystemTrace | undefined | null; 114 private spHelp: SpHelp | undefined | null; 115 private spKeyboard: SpKeyboard | undefined | null; 116 private spFlags: SpFlags | undefined | null; 117 private spRecordTrace: SpRecordTrace | undefined | null; 118 private spRecordTemplate: SpRecordTrace | undefined | null; 119 private spSchedulingAnalysis: SpSchedulingAnalysis | undefined | null; 120 private mainMenu: LitMainMenu | undefined | null; 121 private menu: HTMLDivElement | undefined | null; 122 private progressEL: LitProgressBar | undefined | null; 123 private litSearch: LitSearch | undefined | null; 124 private litRecordSearch: LitSearch | undefined | null; 125 private sidebarButton: HTMLDivElement | undefined | null; 126 private chartFilter: TraceRowConfig | undefined | null; 127 private cutTraceFile: HTMLImageElement | undefined | null; 128 private exportRecord: LitIcon | undefined | null; 129 private longTracePage: HTMLDivElement | undefined | null; 130 private customColor: CustomThemeColor | undefined | null; 131 private filterConfig: LitIcon | undefined | null; 132 private configClose: LitIcon | undefined | null; 133 private spThirdParty: SpThirdParty | undefined | null; 134 // 关键路径标识 135 private importConfigDiv: HTMLInputElement | undefined | null; 136 private closeKeyPath: HTMLDivElement | undefined | null; 137 private importFileBt: HTMLInputElement | undefined | null; 138 private contentLeftOption: HTMLDivElement | undefined | null; 139 private contentCenterOption: HTMLDivElement | undefined | null; 140 private contentRightOption: HTMLDivElement | undefined | null; 141 private childComponent: Array<unknown> | undefined | null; 142 private keyCodeMap = { 143 61: true, 144 107: true, 145 109: true, 146 173: true, 147 187: true, 148 189: true, 149 }; 150 private traceFileName: string | undefined; 151 private markJson: string | undefined; 152 colorTransiton?: NodeJS.Timeout; 153 static isLongTrace: boolean = false; 154 fileTypeList: string[] = ['ebpf', 'arkts', 'hiperf']; 155 private pageTimStamp: number = 0; 156 private currentPageNum: number = 1; 157 private currentDataTime: string[] = []; 158 159 static get observedAttributes(): Array<string> { 160 return ['server', 'sqlite', 'wasm', 'dark', 'vs', 'query-sql', 'subsection']; 161 } 162 163 get dark(): boolean { 164 return this.hasAttribute('dark'); 165 } 166 167 set dark(value) { 168 if (value) { 169 this.rootEL!.classList.add('dark'); 170 this.setAttribute('dark', ''); 171 } else { 172 this.rootEL!.classList.remove('dark'); 173 this.removeAttribute('dark'); 174 } 175 if (this.skinChangeArray.length > 0) { 176 this.skinChangeArray.forEach((item) => item(value)); 177 } 178 if (SpApplication.skinChange) { 179 SpApplication.skinChange(value); 180 } 181 if (SpApplication.skinChange2) { 182 SpApplication.skinChange2(value); 183 } 184 185 if (this.spHelp) { 186 this.spHelp.dark = value; 187 } 188 } 189 190 get sqlite(): boolean { 191 return this.hasAttribute('sqlite'); 192 } 193 194 get wasm(): boolean { 195 return this.hasAttribute('wasm'); 196 } 197 198 set wasm(isWasm: boolean) { 199 if (isWasm) { 200 this.setAttribute('wasm', ''); 201 } else { 202 this.hasAttribute('wasm') && this.removeAttribute('wasm'); 203 } 204 } 205 206 get server(): boolean { 207 return this.hasAttribute('server'); 208 } 209 210 set server(s: boolean) { 211 if (s) { 212 this.setAttribute('server', ''); 213 } else { 214 this.removeAttribute('server'); 215 } 216 } 217 218 get querySql(): boolean { 219 return this.hasAttribute('query-sql'); 220 } 221 222 set querySql(isShowMetric) { 223 if (isShowMetric) { 224 this.setAttribute('query-sql', ''); 225 } else { 226 this.removeAttribute('query-sql'); 227 } 228 } 229 230 set search(search: boolean) { 231 if (search) { 232 this.setAttribute('search', ''); 233 } else { 234 this.removeAttribute('search'); 235 } 236 } 237 238 get search(): boolean { 239 return this.hasAttribute('search'); 240 } 241 242 addSkinListener(handler: Function): void { 243 this.skinChangeArray.push(handler); 244 } 245 246 removeSkinListener(handler: Function): void { 247 this.skinChangeArray.splice(this.skinChangeArray.indexOf(handler), 1); 248 } 249 250 initHtml(): string { 251 return applicationHtml; 252 } 253 254 initPlugin(): void { 255 SpStatisticsHttpUtil.initStatisticsServerConfig(); 256 SpStatisticsHttpUtil.addUserVisitAction('visit'); 257 LongTraceDBUtils.getInstance().createDBAndTable().then(); 258 } 259 260 initElements(): void { 261 this.wasm = true; 262 this.initPlugin(); 263 this.querySql = true; 264 this.rootEL = this.shadowRoot!.querySelector<HTMLDivElement>('.root'); 265 this.headerDiv = this.shadowRoot!.querySelector<HTMLDivElement>('.search-vessel'); 266 this.spWelcomePage = this.shadowRoot!.querySelector('#sp-welcome') as SpWelcomePage; 267 this.spMetrics = this.shadowRoot!.querySelector<SpMetrics>('#sp-metrics') as SpMetrics; // new SpMetrics(); 268 this.spQuerySQL = this.shadowRoot!.querySelector<SpQuerySQL>('#sp-query-sql') as SpQuerySQL; // new SpQuerySQL(); 269 this.spInfoAndStats = this.shadowRoot!.querySelector<SpInfoAndStats>('#sp-info-and-stats'); // new SpInfoAndStats(); 270 this.spSystemTrace = this.shadowRoot!.querySelector<SpSystemTrace>('#sp-system-trace'); 271 this.spHelp = this.shadowRoot!.querySelector<SpHelp>('#sp-help'); 272 this.spKeyboard = this.shadowRoot!.querySelector<SpKeyboard>('#sp-keyboard') as SpKeyboard; 273 this.spFlags = this.shadowRoot!.querySelector<SpFlags>('#sp-flags') as SpFlags; 274 this.spRecordTrace = this.shadowRoot!.querySelector<SpRecordTrace>('#sp-record-trace'); 275 this.spRecordTemplate = this.shadowRoot!.querySelector<SpRecordTrace>('#sp-record-template'); 276 this.spSchedulingAnalysis = this.shadowRoot!.querySelector<SpSchedulingAnalysis>('#sp-scheduling-analysis'); 277 this.mainMenu = this.shadowRoot?.querySelector('#main-menu') as LitMainMenu; 278 this.menu = this.mainMenu.shadowRoot?.querySelector('.menu-button') as HTMLDivElement; 279 this.progressEL = this.shadowRoot?.querySelector('.progress') as LitProgressBar; 280 this.litSearch = this.shadowRoot?.querySelector('#lit-search') as LitSearch; 281 this.litRecordSearch = this.shadowRoot?.querySelector('#lit-record-search') as LitSearch; 282 this.sidebarButton = this.shadowRoot?.querySelector('.sidebar-button'); 283 this.chartFilter = this.shadowRoot?.querySelector('.chart-filter') as TraceRowConfig; 284 this.cutTraceFile = this.shadowRoot?.querySelector('.cut-trace-file') as HTMLImageElement; 285 this.exportRecord = this.shadowRoot?.querySelector('.export-record') as LitIcon; 286 this.longTracePage = this.shadowRoot!.querySelector('.long_trace_page') as HTMLDivElement; 287 this.customColor = this.shadowRoot?.querySelector('.custom-color') as CustomThemeColor; 288 this.filterConfig = this.shadowRoot?.querySelector('.filter-config') as LitIcon; 289 this.spThirdParty = this.shadowRoot!.querySelector('#sp-third-party') as SpThirdParty; 290 this.configClose = this.shadowRoot 291 ?.querySelector<HTMLElement>('.chart-filter')! 292 .shadowRoot?.querySelector<LitIcon>('.config-close'); 293 this.importConfigDiv = this.shadowRoot?.querySelector<HTMLInputElement>('#import-key-path'); 294 this.closeKeyPath = this.shadowRoot?.querySelector<HTMLDivElement>('#close-key-path'); 295 this.importFileBt = this.shadowRoot?.querySelector<HTMLInputElement>('#import-config'); 296 this.contentRightOption = this.shadowRoot?.querySelector<HTMLDivElement>('.content-right-option'); 297 this.contentLeftOption = this.shadowRoot?.querySelector<HTMLDivElement>('.content-left-option'); 298 this.contentCenterOption = this.shadowRoot?.querySelector<HTMLDivElement>('.content-center-option'); 299 this.initElementsAttr(); 300 this.initEvents(); 301 this.initRecordEvents(); 302 this.initCustomEvents(); 303 this.initCustomColorHandler(); 304 this.initImportConfigEvent(); 305 this.initSlideMenuEvents(); 306 // @ts-ignore 307 this.mainMenu!.menus = [this.initNavigationMenu(), this.initSupportMenus()]; 308 this.initGlobalEvents(); 309 this.initDocumentListener(); 310 this.initElementsEnd(); 311 } 312 313 private initElementsEnd(): void { 314 let urlParams = new URL(window.location.href).searchParams; 315 if (urlParams && urlParams.get('trace') && urlParams.get('link')) { 316 this.openLineFileHandler(urlParams); 317 } else if (urlParams && urlParams.get('action')) { 318 this.helpClick(urlParams!); 319 } else { 320 this.openMenu(true); 321 } 322 } 323 324 private helpClick(urlParams: URLSearchParams): void { 325 if (urlParams.get('action') === 'help') { 326 SpStatisticsHttpUtil.addOrdinaryVisitAction({ 327 event: 'help_page', 328 action: 'help_doc', 329 }); 330 this.spHelp!.dark = this.dark; 331 this.showContent(this.spHelp!); 332 } else if (urlParams.get('action')!.length > 4) { 333 this.showContent(this.spHelp!); 334 } 335 } 336 337 private initElementsAttr(): void { 338 this.mainMenu!.setAttribute('main_menu', '1'); 339 this.chartFilter!.setAttribute('mode', ''); 340 this.chartFilter!.setAttribute('hidden', ''); 341 this.customColor!.setAttribute('mode', ''); 342 this.customColor!.setAttribute('hidden', ''); 343 this.childComponent = [ 344 this.spSystemTrace, 345 this.spRecordTrace, 346 this.spWelcomePage, 347 this.spMetrics, 348 this.spQuerySQL, 349 this.spSchedulingAnalysis, 350 this.spInfoAndStats, 351 this.spHelp, 352 this.spRecordTemplate, 353 this.spFlags, 354 this.spKeyboard, 355 this.spThirdParty, 356 ]; 357 } 358 359 private openLongTraceFile(ev: unknown, isRecordTrace: boolean = false): void { 360 this.returnOriginalUrl(); 361 this.wasm = true; 362 this.openFileInit(); 363 // @ts-ignore 364 let detail = ev.detail; 365 let initRes = this.longTraceFileInit(isRecordTrace, detail); 366 if (!isRecordTrace && initRes) { 367 let readSize = 0; 368 let timStamp = new Date().getTime(); 369 const { traceTypePage, allFileSize, normalTraceNames, specialTraceNames } = initRes; 370 if (normalTraceNames.length <= 0) { 371 return; 372 } 373 const readFiles = async ( 374 files: FileList, 375 traceTypePage: Array<number>, 376 normalNames: Array<string>, 377 specialNames: Array<string> 378 ): Promise<unknown> => { 379 const promises = Array.from(files).map((file) => { 380 if (normalNames.indexOf(file.name.toLowerCase()) >= 0) { 381 return this.longTraceFileRead(file, true, traceTypePage, readSize, timStamp, allFileSize); 382 } else if (specialNames.indexOf(file.name.toLowerCase()) >= 0) { 383 return this.longTraceFileRead(file, false, traceTypePage, readSize, timStamp, allFileSize); 384 } else { 385 return; 386 } 387 }); 388 return Promise.all(promises); 389 }; 390 this.litSearch!.setPercent('Read in file: ', 1); 391 readFiles(detail, traceTypePage, normalTraceNames, specialTraceNames).then(() => { 392 this.litSearch!.setPercent('Cut in file: ', 1); 393 this.sendCutFileMessage(timStamp); 394 }); 395 } 396 } 397 398 private longTraceFileRead = async ( 399 file: File, 400 isNormalType: boolean, 401 traceTypePage: Array<number>, 402 readSize: number, 403 timStamp: number, 404 allFileSize: number 405 ): Promise<boolean> => { 406 info('reading long trace file ', file.name); 407 return new Promise((resolve, reject) => { 408 let fr = new FileReader(); 409 let message = { fileType: '', startIndex: 0, endIndex: 0, size: 0 }; 410 info('Parse long trace using wasm mode '); 411 const { fileType, pageNumber } = this.getFileTypeAndPages(file.name, isNormalType, traceTypePage); 412 let chunk = 48 * 1024 * 1024; 413 let offset = 0; 414 let sliceLen = 0; 415 let index = 1; 416 fr.onload = (): void => { 417 let data = fr.result as ArrayBuffer; 418 LongTraceDBUtils.getInstance() 419 .addLongTableData(data, fileType, timStamp, pageNumber, index, offset, sliceLen) 420 .then(() => { 421 this.longTraceFileReadMessagePush(index, isNormalType, pageNumber, offset, sliceLen, fileType, data); 422 offset += sliceLen; 423 if (offset < file.size) { 424 index++; 425 } 426 continueReading(); 427 }); 428 }; 429 const continueReading = (): void => { 430 if (offset >= file.size) { 431 message.endIndex = index; 432 message.size = file.size; 433 this.longTraceFileReadMessageHandler(pageNumber, message); 434 resolve(true); 435 return; 436 } 437 if (index === 1) { 438 message.fileType = fileType; 439 message.startIndex = index; 440 } 441 sliceLen = Math.min(file.size - offset, chunk); 442 let slice = file.slice(offset, offset + sliceLen); 443 readSize += slice.size; 444 let percentValue = ((readSize * 100) / allFileSize).toFixed(2); 445 this.litSearch!.setPercent('Read in file: ', Number(percentValue)); 446 fr.readAsArrayBuffer(slice); 447 }; 448 continueReading(); 449 fr.onerror = (): void => reject(false); 450 info('read over long trace file ', file.name); 451 }); 452 }; 453 454 getFileTypeAndPages( 455 fileName: string, 456 isNormalType: boolean, 457 traceTypePage: Array<number> 458 ): { 459 fileType: string; 460 pageNumber: number; 461 } { 462 let fileType = 'trace'; 463 let pageNumber = 0; 464 let firstLastIndexOf = fileName.lastIndexOf('.'); 465 let firstText = fileName.slice(0, firstLastIndexOf); 466 let resultLastIndexOf = firstText.lastIndexOf('_'); 467 let searchResult = firstText.slice(resultLastIndexOf + 1, firstText.length); 468 if (isNormalType) { 469 pageNumber = traceTypePage.lastIndexOf(Number(searchResult)); 470 } else { 471 fileType = searchResult; 472 } 473 return { fileType, pageNumber }; 474 } 475 476 private longTraceFileInit( 477 isRecordTrace: boolean, 478 detail: unknown 479 ): 480 | { 481 traceTypePage: number[]; 482 allFileSize: number; 483 normalTraceNames: string[]; 484 specialTraceNames: string[]; 485 } 486 | undefined { 487 if (!this.wasm) { 488 this.progressEL!.loading = false; 489 return; 490 } 491 if (this.longTracePage) { 492 this.longTracePage.style.display = 'none'; 493 this.litSearch!.style.marginLeft = '0px'; 494 this.shadowRoot!.querySelector('.page-number-list')!.innerHTML = ''; 495 } 496 this.currentPageNum = 1; 497 if (isRecordTrace) { 498 //@ts-ignore 499 this.sendCutFileMessage(detail.timeStamp); 500 return undefined; 501 } else { 502 this.longTraceHeadMessageList = []; 503 this.longTraceTypeMessageMap = undefined; 504 this.longTraceDataList = []; 505 let traceTypePage: Array<number> = []; 506 let allFileSize = 0; 507 let normalTraceNames: Array<string> = []; 508 let specialTraceNames: Array<string> = []; 509 //@ts-ignore 510 for (let index = 0; index < detail.length; index++) { 511 //@ts-ignore 512 let file = detail[index]; 513 let fileName = file.name as string; 514 allFileSize += file.size; 515 let specialMatch = fileName.match(/_(arkts|ebpf|hiperf)\.htrace$/); 516 let normalMatch = fileName.match(/_\d{8}_\d{6}_\d+\.htrace$/); 517 if (normalMatch) { 518 normalTraceNames.push(fileName); 519 let fileNameStr = fileName.split('.')[0]; 520 let pageMatch = fileNameStr.match(/\d+$/); 521 if (pageMatch) { 522 traceTypePage.push(Number(pageMatch[0])); 523 } 524 } else if (specialMatch) { 525 specialTraceNames.push(fileName); 526 } 527 } 528 if (normalTraceNames.length <= 0) { 529 this.traceFileLoadFailedHandler('No large trace files exists in the folder!'); 530 } 531 traceTypePage.sort((leftNum: number, rightNum: number) => leftNum - rightNum); 532 return { traceTypePage, allFileSize, normalTraceNames, specialTraceNames }; 533 } 534 } 535 536 longTraceFileReadMessagePush( 537 index: number, 538 isNormalType: boolean, 539 pageNumber: number, 540 offset: number, 541 sliceLen: number, 542 fileType: string, 543 data: ArrayBuffer 544 ): void { 545 if (index === 1 && isNormalType) { 546 this.longTraceHeadMessageList.push({ 547 pageNum: pageNumber, 548 data: data.slice(offset, 1024), 549 }); 550 } 551 this.longTraceDataList.push({ 552 index: index, 553 fileType: fileType, 554 pageNum: pageNumber, 555 startOffsetSize: offset, 556 endOffsetSize: offset + sliceLen, 557 }); 558 } 559 560 longTraceFileReadMessageHandler(pageNumber: number, message: unknown): void { 561 if (this.longTraceTypeMessageMap) { 562 if (this.longTraceTypeMessageMap?.has(pageNumber)) { 563 let oldTypeList = this.longTraceTypeMessageMap?.get(pageNumber); 564 //@ts-ignore 565 oldTypeList?.push(message); 566 this.longTraceTypeMessageMap?.set(pageNumber, oldTypeList!); 567 } else { 568 //@ts-ignore 569 this.longTraceTypeMessageMap?.set(pageNumber, [message]); 570 } 571 } else { 572 this.longTraceTypeMessageMap = new Map(); 573 //@ts-ignore 574 this.longTraceTypeMessageMap.set(pageNumber, [message]); 575 } 576 } 577 578 private openTraceFile(ev: unknown, isClickHandle?: boolean): void { 579 this.returnOriginalUrl(); 580 this.removeAttribute('custom-color'); 581 this.customColor!.setAttribute('hidden', ''); 582 this.longTracePage!.style.display = 'none'; 583 this.litSearch!.style.marginLeft = '0px'; 584 let pageListDiv = this.shadowRoot?.querySelector('.page-number-list') as HTMLDivElement; 585 pageListDiv.innerHTML = ''; 586 this.openFileInit(); 587 if (this.importConfigDiv && this.closeKeyPath) { 588 this.importConfigDiv.style.display = 'none'; 589 this.closeKeyPath.style.display = 'none'; 590 } 591 //@ts-ignore 592 let fileName = ev.name; 593 this.traceFileName = fileName; 594 let showFileName = fileName.lastIndexOf('.') === -1 ? fileName : fileName.substring(0, fileName.lastIndexOf('.')); 595 TraceRow.rangeSelectObject = undefined; 596 //@ts-ignore 597 let typeHeader = ev.slice(0, 6); 598 let reader: FileReader | null = new FileReader(); 599 reader.readAsText(typeHeader); 600 reader.onloadend = (event): void => { 601 let headerStr: string = `${reader?.result}`; 602 if (headerStr.indexOf('SQLite') === 0) { 603 info('Parse trace headerStr sql mode'); 604 this.wasm = false; 605 //@ts-ignore 606 this.handleSqliteMode(ev, showFileName, ev.size, fileName); 607 } else { 608 info('Parse trace using wasm mode '); 609 this.wasm = true; 610 //@ts-ignore 611 this.handleWasmMode(ev, showFileName, ev.size, fileName); 612 } 613 }; 614 } 615 616 private openLineFileHandler(urlParams: URLSearchParams): void { 617 this.openFileInit(); 618 this.openMenu(false); 619 let downloadLineFile = urlParams.get('local') ? false : true; 620 this.setProgress(downloadLineFile ? 'download trace file' : 'open trace file'); 621 this.downloadOnLineFile( 622 urlParams.get('trace') as string, 623 downloadLineFile, 624 (arrayBuf, fileName, showFileName, fileSize) => { 625 this.handleWasmMode(new File([arrayBuf], fileName), showFileName, fileSize, fileName); 626 }, 627 (localPath) => { 628 let path = urlParams.get('trace') as string; 629 let fileName: string = ''; 630 let showFileName: string = ''; 631 if (urlParams.get('local')) { 632 this.openMenu(true); 633 fileName = urlParams.get('traceName') as string; 634 } else { 635 fileName = path.split('/').reverse()[0]; 636 } 637 this.traceFileName = fileName; 638 showFileName = fileName.lastIndexOf('.') === -1 ? fileName : fileName.substring(0, fileName.lastIndexOf('.')); 639 TraceRow.rangeSelectObject = undefined; 640 let localUrl = downloadLineFile ? `${window.location.origin}${localPath}` : urlParams.get('trace')!; 641 fetch(localUrl) 642 .then((res) => { 643 res.arrayBuffer().then((arrayBuf) => { 644 if (urlParams.get('local')) { 645 URL.revokeObjectURL(localUrl); 646 } 647 this.handleWasmMode(new File([arrayBuf], fileName), showFileName, arrayBuf.byteLength, fileName); 648 }); 649 }) 650 .catch((e) => { 651 if (!downloadLineFile) { 652 const firstQuestionMarkIndex = window.location.href.indexOf('?'); 653 location.replace(window.location.href.substring(0, firstQuestionMarkIndex)); 654 } 655 }); 656 } 657 ); 658 } 659 660 private openMenu(open: boolean): void { 661 if (this.mainMenu) { 662 this.mainMenu.style.width = open ? '248px' : '0px'; 663 this.mainMenu.style.zIndex = open ? '2000' : '0'; 664 } 665 if (this.sidebarButton) { 666 this.sidebarButton.style.width = open ? '0px' : '48px'; 667 this.importConfigDiv!.style.left = open ? '5px' : '45px'; 668 this.closeKeyPath!.style.left = open ? '25px' : '65px'; 669 } 670 } 671 672 private initGlobalDropEvents(): void { 673 let body = document.querySelector('body'); 674 body!.addEventListener( 675 'drop', 676 (e) => { 677 e.preventDefault(); 678 e.stopPropagation(); 679 if (this.rootEL!.classList.contains('filedrag')) { 680 this.rootEL!.classList.remove('filedrag'); 681 } 682 //@ts-ignore 683 if (e.dataTransfer.items !== undefined && e.dataTransfer.items.length > 0) { 684 //@ts-ignore 685 let item = e.dataTransfer.items[0]; 686 if (item.webkitGetAsEntry()?.isFile) { 687 this.openTraceFile(item.getAsFile()); 688 } else if (item.webkitGetAsEntry()?.isDirectory) { 689 this.litSearch!.setPercent('This File is not supported!', -1); 690 this.progressEL!.loading = false; 691 this.freshMenuDisable(false); 692 this.mainMenu!.menus!.splice(1, 1); 693 this.mainMenu!.menus = this.mainMenu!.menus!; 694 this.spSystemTrace!.reset(null); 695 } 696 } 697 }, 698 false 699 ); 700 } 701 private initGlobalEvents(): void { 702 let body = document.querySelector('body'); 703 body!.addEventListener( 704 'dragover', 705 (e) => { 706 e.preventDefault(); 707 e.stopPropagation(); 708 //@ts-ignore 709 if (e.dataTransfer.items.length > 0 && e.dataTransfer.items[0].kind === 'file') { 710 //@ts-ignore 711 e.dataTransfer.dropEffect = 'copy'; 712 if (!this.rootEL!.classList.contains('filedrag')) { 713 this.rootEL!.classList.add('filedrag'); 714 } 715 } 716 }, 717 false 718 ); 719 body!.addEventListener( 720 'dragleave', 721 (e) => { 722 e.stopPropagation(); 723 e.preventDefault(); 724 if (this.rootEL!.classList.contains('filedrag')) { 725 this.rootEL!.classList.remove('filedrag'); 726 } 727 }, 728 false 729 ); 730 this.initGlobalDropEvents(); 731 } 732 733 private initDocumentListener(): void { 734 document.addEventListener('file-error', () => { 735 this.litSearch!.setPercent('This File is Error!', -1); 736 }); 737 document.addEventListener('file-correct', () => { 738 this.litSearch!.setPercent('', 101); 739 }); 740 document.addEventListener('visibilitychange', () => { 741 if (document.visibilityState === 'visible') { 742 this.validateFileCacheLost(); 743 if (window.localStorage.getItem('Theme') === 'dark') { 744 this.changeTheme(Theme.DARK); 745 } else { 746 this.changeTheme(Theme.LIGHT); 747 } 748 } 749 }); 750 document.addEventListener('keydown', (event) => { 751 const e = event || window.event; 752 const ctrlKey = e.ctrlKey || e.metaKey; 753 //@ts-ignore 754 if (ctrlKey && this.keyCodeMap[e.keyCode]) { 755 e.preventDefault(); 756 } else if (e.detail) { 757 // Firefox 758 event.returnValue = false; 759 } 760 }); 761 document.body.addEventListener( 762 'wheel', 763 (e) => { 764 if (e.ctrlKey) { 765 if (e.deltaY < 0) { 766 e.preventDefault(); 767 return false; 768 } 769 if (e.deltaY > 0) { 770 e.preventDefault(); 771 return false; 772 } 773 } 774 }, 775 { passive: false } 776 ); 777 } 778 779 private initNavigationMenu(): unknown { 780 return { 781 collapsed: false, 782 title: 'Navigation', 783 second: false, 784 icon: '', 785 describe: 'Open or record a new trace', 786 children: [ 787 { 788 title: 'Open trace file', 789 icon: 'folder', 790 fileChoose: true, 791 fileHandler: (ev: InputEvent): void => { 792 this.openTraceFile(ev.detail); 793 }, 794 clickHandler: (hand: unknown): void => { 795 this.openTraceFile(hand, true); 796 }, 797 }, 798 { 799 title: 'Open long trace file', 800 icon: 'folder', 801 fileChoose: true, 802 fileHandler: (ev: InputEvent): void => { 803 this.openLongTraceFile(ev); 804 }, 805 clickHandler: (hand: InputEvent): void => { 806 this.openLongTraceFile(hand, true); 807 }, 808 }, 809 { 810 title: 'Record new trace', 811 icon: 'copyhovered', 812 clickHandler: (item: MenuItem): void => this.clickHandleByRecordNewTrace(), 813 }, 814 { 815 title: 'Record template', 816 icon: 'copyhovered', 817 clickHandler: (item: MenuItem): void => this.clickHandleByRecordTemplate(), 818 }, 819 ], 820 }; 821 } 822 823 private initSupportMenus(): unknown { 824 return { 825 collapsed: false, 826 title: 'Support', 827 second: false, 828 icon: '', 829 describe: 'Support', 830 children: [ 831 { 832 title: 'Help Documents', 833 icon: 'smart-help', 834 clickHandler: (item: MenuItem): void => this.clickHandleByHelpDocuments(), 835 }, 836 { 837 title: 'Flags', 838 icon: 'menu', 839 clickHandler: (item: MenuItem): void => this.clickHandleByFlags(), 840 }, 841 { 842 title: 'Keyboard Shortcuts', 843 icon: 'smart-help', 844 clickHandler: (item: MenuItem): void => this.clickHandleByKeyboardShortcuts(), 845 }, 846 { 847 title: 'Third File', 848 icon: 'file-fill', 849 fileModel: this.wasm ? 'wasm' : 'db', 850 clickHandler: (item: MenuItem): void => { 851 this.returnOriginalUrl(); 852 this.search = false; 853 this.showContent(this.spThirdParty!); 854 }, 855 }, 856 ], 857 }; 858 } 859 860 private clickHandleByHelpDocuments(): void { 861 this.spHelp!.dark = this.dark; 862 this.search = false; 863 this.showContent(this.spHelp!); 864 SpStatisticsHttpUtil.addOrdinaryVisitAction({ 865 event: 'help_page', 866 action: 'help_doc', 867 }); 868 this.changeUrl(); 869 } 870 871 private clickHandleByFlags(): void { 872 this.returnOriginalUrl(); 873 this.search = false; 874 this.showContent(this.spFlags!); 875 SpStatisticsHttpUtil.addOrdinaryVisitAction({ 876 event: 'flags', 877 action: 'flags', 878 }); 879 } 880 881 private clickHandleByKeyboardShortcuts(): void { 882 this.returnOriginalUrl(); 883 document 884 .querySelector('body > sp-application')! 885 .shadowRoot!.querySelector<HTMLDivElement>('#sp-keyboard')!.style.visibility = 'visible'; 886 SpSystemTrace.keyboardFlar = false; 887 SpStatisticsHttpUtil.addOrdinaryVisitAction({ 888 event: 'Keyboard Shortcuts', 889 action: 'Keyboard Shortcuts', 890 }); 891 } 892 893 private clickHandleByRecordNewTrace(): void { 894 this.returnOriginalUrl(); 895 this.spRecordTrace!.synchronizeDeviceList(); 896 this.spRecordTemplate!.record_template = false; 897 this.spRecordTrace!.refreshConfig(true); 898 this.showContent(this.spRecordTrace!); 899 } 900 901 private clickHandleByRecordTemplate(): void { 902 this.returnOriginalUrl(); 903 this.spRecordTemplate!.refreshHint(); 904 this.spRecordTemplate!.record_template = true; 905 this.spRecordTemplate!.refreshConfig(false); 906 this.spRecordTemplate!.synchronizeDeviceList(); 907 this.showContent(this.spRecordTemplate!); 908 } 909 910 private changeUrl(): void { 911 let url = new URL(window.location.href); 912 let actionParam = url.searchParams.get('action'); 913 let newActionValue = 'help'; 914 if (actionParam) { 915 url.searchParams.set('action', newActionValue); 916 } else { 917 url.searchParams.append('action', newActionValue); 918 } 919 let newURL = url.href; 920 history.pushState({}, '', newURL); 921 } 922 923 private returnOriginalUrl(): void { 924 history.pushState({}, '', window.location.origin + window.location.pathname); 925 } 926 927 private handleSqliteMode(ev: unknown, showFileName: string, fileSize: number, fileName: string): void { 928 let fileSizeStr = (fileSize / 1048576).toFixed(1); 929 postLog(fileName, fileSizeStr); 930 document.title = `${showFileName} (${fileSizeStr}M)`; 931 this.litSearch!.setPercent('', 0); 932 threadPool.init('sqlite').then((res) => { 933 let reader = new FileReader(); 934 reader.readAsArrayBuffer(ev as Blob); 935 reader.onloadend = (ev): void => { 936 SpApplication.loadingProgress = 0; 937 SpApplication.progressStep = 3; 938 this.spSystemTrace!.loadDatabaseArrayBuffer( 939 reader.result as ArrayBuffer, 940 '', 941 (command: string, _: number) => { 942 this.setProgress(command); 943 }, 944 () => { 945 this.mainMenu!.menus!.splice(1, this.mainMenu!.menus!.length > 2 ? 1 : 0, { 946 collapsed: false, 947 title: 'Current Trace', 948 second: false, 949 icon: '', 950 describe: 'Actions on the current trace', 951 children: this.getTraceOptionMenus(showFileName, fileSizeStr, fileName, true), 952 }); 953 this.mainMenu!.menus!.splice(2, 1, { 954 collapsed: false, 955 title: 'Support', 956 second: false, 957 icon: '', 958 describe: 'Support', 959 children: this.getTraceSupportMenus(), 960 }); 961 this.litSearch!.setPercent('', 101); 962 this.chartFilter!.setAttribute('mode', ''); 963 this.progressEL!.loading = false; 964 this.freshMenuDisable(false); 965 this.spInfoAndStats!.initInfoAndStatsData(); 966 this.cutTraceFile!.style.display = 'none'; 967 this.headerDiv!.style.pointerEvents = 'auto'; 968 } 969 ); 970 }; 971 }); 972 } 973 974 private handleWasmMode(ev: unknown, showFileName: string, fileSize: number, fileName: string): void { 975 this.litSearch!.setPercent('', 1); 976 if (fileName.endsWith('.json')) { 977 this.progressEL!.loading = true; 978 //@ts-ignore 979 self.spSystemTrace!.loadSample(ev).then(() => { 980 this.showContent(this.spSystemTrace!); 981 this.litSearch!.setPercent('', 101); 982 this.freshMenuDisable(false); 983 this.chartFilter!.setAttribute('mode', ''); 984 this.progressEL!.loading = false; 985 }); 986 } else { 987 let fileSizeStr = (fileSize / 1048576).toFixed(1); 988 postLog(fileName, fileSizeStr); 989 document.title = `${showFileName} (${fileSizeStr}M)`; 990 info('Parse trace using wasm mode '); 991 let completeHandler = async (res: unknown): Promise<void> => { 992 await this.traceLoadCompleteHandler(res, fileSizeStr, showFileName, fileName); 993 if (this.markJson) { 994 window.publish(window.SmartEvent.UI.ImportRecord, this.markJson); 995 } 996 }; 997 threadPool.init('wasm').then((res) => { 998 let reader: FileReader | null = new FileReader(); 999 //@ts-ignore 1000 reader.readAsArrayBuffer(ev); 1001 reader.onloadend = (ev): void => { 1002 info('read file onloadend'); 1003 this.litSearch!.setPercent('ArrayBuffer loaded ', 2); 1004 let wasmUrl = `https://${window.location.host.split(':')[0]}:${window.location.port}/application/wasm.json`; 1005 SpApplication.loadingProgress = 0; 1006 SpApplication.progressStep = 3; 1007 let data = this.markPositionHandler(reader.result as ArrayBuffer); 1008 info('initData start Parse Data'); 1009 this.spSystemTrace!.loadDatabaseArrayBuffer( 1010 data, 1011 wasmUrl, 1012 (command: string, _: number) => this.setProgress(command), 1013 completeHandler 1014 ); 1015 }; 1016 }); 1017 } 1018 } 1019 1020 private markPositionHandler(buf: ArrayBuffer): ArrayBuffer { 1021 const decoder = new TextDecoder('utf-8'); 1022 const headText = decoder.decode(buf.slice(0, 100)); 1023 let hasMark = headText.includes('MarkPositionJSON'); 1024 if (hasMark) { 1025 let markLength = headText.split('->')[0].replace('MarkPositionJSON', ''); 1026 let mark = decoder.decode(buf.slice(0, markLength.length + parseInt(markLength))); 1027 if (mark.includes('->')) { 1028 this.markJson = mark.split('->')[1]; 1029 } 1030 return buf.slice(markLength.length + parseInt(markLength)); 1031 } else { 1032 return buf; 1033 } 1034 } 1035 1036 private async traceLoadCompleteHandler( 1037 res: unknown, 1038 fileSize: string, 1039 showFileName: string, 1040 fileName: string 1041 ): Promise<void> { 1042 let existFtrace = await queryExistFtrace(); 1043 let isAllowTrace = true; 1044 if (DbPool.sharedBuffer) { 1045 let traceHeadData = new Uint8Array(DbPool.sharedBuffer!.slice(0, 10)); 1046 let enc = new TextDecoder(); 1047 let headerStr = enc.decode(traceHeadData); 1048 let rowTraceStr = Array.from(new Uint8Array(DbPool.sharedBuffer!.slice(0, 2))) 1049 .map((byte) => byte.toString(16).padStart(2, '0')) 1050 .join(''); 1051 if (headerStr.indexOf('OHOSPROF') !== 0 && rowTraceStr.indexOf('49df') !== 0) { 1052 isAllowTrace = false; 1053 } 1054 this.cutTraceFile!.style.display = 'block'; 1055 DbPool.sharedBuffer = null; 1056 } 1057 let index = 2; 1058 if (existFtrace.length > 0 && isAllowTrace) { 1059 this.showConvertTraceMenu(fileName); 1060 index = 3; 1061 } 1062 this.loadTraceCompleteMenuHandler(index); 1063 //@ts-ignore 1064 if (res.status) { 1065 info('loadDatabaseArrayBuffer success'); 1066 //@ts-ignore 1067 window.traceFileName = fileName; 1068 this.showCurrentTraceMenu(fileSize, showFileName, fileName); 1069 this.importConfigDiv!.style.display = Utils.SCHED_SLICE_MAP.size > 0 ? 'block' : 'none'; 1070 this.showContent(this.spSystemTrace!); 1071 this.litSearch!.setPercent('', 101); 1072 this.chartFilter!.setAttribute('mode', ''); 1073 this.freshMenuDisable(false); 1074 } else { 1075 info('loadDatabaseArrayBuffer failed'); 1076 //@ts-ignore 1077 this.litSearch!.setPercent(res.msg || 'This File is not supported!', -1); 1078 this.freshMenuDisable(false); 1079 this.mainMenu!.menus!.splice(1, 1); 1080 this.mainMenu!.menus = this.mainMenu!.menus!; 1081 } 1082 this.progressEL!.loading = false; 1083 this.headerDiv!.style.pointerEvents = 'auto'; 1084 this.spInfoAndStats!.initInfoAndStatsData(); 1085 } 1086 1087 private showConvertTraceMenu(fileName: string): void { 1088 this.mainMenu!.menus!.splice(2, 1, { 1089 collapsed: false, 1090 title: 'Convert trace', 1091 second: false, 1092 icon: '', 1093 describe: 'Convert to other formats', 1094 children: this.pushConvertTrace(fileName), 1095 }); 1096 } 1097 1098 private showCurrentTraceMenu(fileSize: string, showFileName: string, fileName: string): void { 1099 this.mainMenu!.menus!.splice(1, this.mainMenu!.menus!.length > 2 ? 1 : 0, { 1100 collapsed: false, 1101 title: 'Current Trace', 1102 second: false, 1103 icon: '', 1104 describe: 'Actions on the current trace', 1105 children: this.getTraceOptionMenus(showFileName, fileSize, fileName, false), 1106 }); 1107 } 1108 1109 private loadTraceCompleteMenuHandler(index: number): void { 1110 this.mainMenu!.menus!.splice(index, 1, { 1111 collapsed: false, 1112 title: 'Support', 1113 second: false, 1114 icon: '', 1115 describe: 'Support', 1116 children: [ 1117 { 1118 title: 'Help Documents', 1119 icon: 'smart-help', 1120 clickHandler: (item: MenuItem): void => this.clickHandleByHelpDocuments(), 1121 }, 1122 { 1123 title: 'Flags', 1124 icon: 'menu', 1125 clickHandler: (item: MenuItem): void => this.clickHandleByFlags(), 1126 }, 1127 { 1128 title: 'Keyboard Shortcuts', 1129 icon: 'smart-help', 1130 clickHandler: (item: MenuItem): void => this.clickHandleByKeyboardShortcuts(), 1131 }, 1132 { 1133 title: 'Third File', 1134 icon: 'file-fill', 1135 fileModel: this.wasm ? 'wasm' : 'db', 1136 clickHandler: (item: MenuItem): void => { 1137 this.returnOriginalUrl(); 1138 this.search = false; 1139 this.showContent(this.spThirdParty!); 1140 }, 1141 }, 1142 ], 1143 }); 1144 } 1145 1146 private validateGetTraceFileByPage(): boolean { 1147 if (!this.wasm) { 1148 this.progressEL!.loading = false; 1149 return false; 1150 } 1151 return this.pageTimStamp !== 0; 1152 } 1153 1154 private queryFileByPage( 1155 instance: LongTraceDBUtils, 1156 indexedDbPageNum: number, 1157 maxTraceFileLength: number, 1158 traceRange: IDBKeyRange 1159 ): void { 1160 instance.indexedDBHelp.get(instance.tableName, traceRange, 'QueryFileByPage').then((result) => { 1161 let traceData = indexedDataToBufferData(result); 1162 let ebpfRange = this.getIDBKeyRange(indexedDbPageNum, 'ebpf_new'); 1163 let arkTsRange = this.getIDBKeyRange(indexedDbPageNum, 'arkts_new'); 1164 let hiperfRange = this.getIDBKeyRange(indexedDbPageNum, 'hiperf_new'); 1165 Promise.all([ 1166 instance.getByRange(ebpfRange), 1167 instance.getByRange(arkTsRange), 1168 instance.getByRange(hiperfRange), 1169 ]).then((otherResult) => { 1170 let ebpfData = indexedDataToBufferData(otherResult[0]); 1171 let arkTsData = indexedDataToBufferData(otherResult[1]); 1172 let hiperfData = indexedDataToBufferData(otherResult[2]); 1173 let traceArray = new Uint8Array(traceData); 1174 let ebpfArray = new Uint8Array(ebpfData); 1175 let arkTsArray = new Uint8Array(arkTsData); 1176 let hiPerfArray = new Uint8Array(hiperfData); 1177 let allOtherData = [ebpfData, arkTsData, hiperfData]; 1178 let otherDataLength = traceData.byteLength + ebpfData.byteLength + arkTsData.byteLength + hiperfData.byteLength; 1179 let timeStamp = 1180 this.currentDataTime[0] + 1181 this.currentDataTime[1] + 1182 this.currentDataTime[2] + 1183 '_' + 1184 this.currentDataTime[3] + 1185 this.currentDataTime[4] + 1186 this.currentDataTime[5]; 1187 this.traceFileName = `hiprofiler_long_${timeStamp}_${indexedDbPageNum}.htrace`; 1188 if (otherDataLength > maxTraceFileLength) { 1189 if (traceData.byteLength > maxTraceFileLength) { 1190 this.traceFileLoadFailedHandler('hitrace file too big!'); 1191 } else { 1192 let freeDataLength = maxTraceFileLength - traceData.byteLength; 1193 let freeDataIndex = findFreeSizeAlgorithm( 1194 [ebpfData.byteLength, arkTsData.byteLength, hiperfData.byteLength], 1195 freeDataLength 1196 ); 1197 let finalData = [traceData]; 1198 freeDataIndex.forEach((dataIndex) => { 1199 finalData.push(allOtherData[dataIndex]); 1200 }); 1201 const file = new File([new Blob(finalData)], this.traceFileName); 1202 this.handleWasmMode(file, file.name, file.size, this.traceFileName); 1203 } 1204 } else { 1205 let fileBlob = new Blob([traceArray, ebpfArray, arkTsArray, hiPerfArray]); 1206 const file = new File([fileBlob], this.traceFileName); 1207 this.handleWasmMode(file, file.name, file.size, file.name); 1208 } 1209 }); 1210 }); 1211 } 1212 1213 private getTraceFileByPage(pageNumber: number): void { 1214 this.openFileInit(); 1215 if (this.validateGetTraceFileByPage()) { 1216 let indexedDbPageNum = pageNumber - 1; 1217 let maxTraceFileLength = 400 * 1024 * 1024; 1218 let traceRange = this.getIDBKeyRange(indexedDbPageNum, 'trace'); 1219 let instance = LongTraceDBUtils.getInstance(); 1220 this.queryFileByPage(instance, indexedDbPageNum, maxTraceFileLength, traceRange); 1221 } 1222 } 1223 1224 private traceFileLoadFailedHandler(reason: string): void { 1225 this.litSearch!.isLoading = false; 1226 this.litSearch!.setPercent(reason, -1); 1227 this.progressEL!.loading = false; 1228 this.freshMenuDisable(false); 1229 } 1230 1231 private getIDBKeyRange(indexedDbPageNum: number, key: string): IDBKeyRange { 1232 return IDBKeyRange.bound( 1233 [this.pageTimStamp, key, indexedDbPageNum], 1234 [this.pageTimStamp, key, indexedDbPageNum], 1235 false, 1236 false 1237 ); 1238 } 1239 1240 private refreshPageListHandler( 1241 pageListDiv: HTMLDivElement, 1242 previewButton: HTMLDivElement, 1243 nextButton: HTMLDivElement, 1244 pageInput: HTMLInputElement 1245 ): void { 1246 this.progressEL!.loading = true; 1247 this.refreshPageList( 1248 pageListDiv, 1249 previewButton!, 1250 nextButton!, 1251 pageInput!, 1252 this.currentPageNum, 1253 this.longTraceHeadMessageList.length 1254 ); 1255 this.getTraceFileByPage(this.currentPageNum); 1256 } 1257 1258 private sendCutFileMessage(timStamp: number): void { 1259 this.pageTimStamp = timStamp; 1260 threadPool.init('wasm').then(() => { 1261 let headUintArray = new Uint8Array(this.longTraceHeadMessageList.length * 1024); 1262 let headOffset = 0; 1263 this.longTraceHeadMessageList = this.longTraceHeadMessageList.sort( 1264 (leftMessage, rightMessage) => leftMessage.pageNum - rightMessage.pageNum 1265 ); 1266 for (let index = 0; index < this.longTraceHeadMessageList.length; index++) { 1267 let currentUintArray = new Uint8Array(this.longTraceHeadMessageList[index].data); 1268 headUintArray.set(currentUintArray, headOffset); 1269 headOffset += currentUintArray.length; 1270 } 1271 threadPool.submit( 1272 'ts-cut-file', 1273 '', 1274 { 1275 headArray: headUintArray, 1276 timeStamp: timStamp, 1277 splitFileInfo: this.longTraceTypeMessageMap?.get(0), 1278 splitDataList: this.longTraceDataList, 1279 }, 1280 (res: Array<unknown>) => { 1281 this.litSearch!.setPercent('Cut in file ', 100); 1282 this.currentDataTime = getCurrentDataTime(); 1283 if (this.longTraceHeadMessageList.length > 0) { 1284 this.getTraceFileByPage(this.currentPageNum); 1285 this.litSearch!.style.marginLeft = '80px'; 1286 this.longTracePage!.style.display = 'flex'; 1287 this.initCutFileEvent(); 1288 } else { 1289 this.progressEL!.loading = false; 1290 this.litSearch!.setPercent('Missing basic trace in the large-file scenario!', -1); 1291 this.freshMenuDisable(false); 1292 return; 1293 } 1294 }, 1295 'long_trace' 1296 ); 1297 }); 1298 } 1299 1300 private initCutFileEvent(): void { 1301 let pageListDiv = this.shadowRoot?.querySelector('.page-number-list') as HTMLDivElement; 1302 let previewButton: HTMLDivElement | null | undefined = 1303 this.shadowRoot?.querySelector<HTMLDivElement>('#preview-button'); 1304 let nextButton: HTMLDivElement | null | undefined = this.shadowRoot?.querySelector<HTMLDivElement>('#next-button'); 1305 let pageInput = this.shadowRoot?.querySelector<HTMLInputElement>('.page-input'); 1306 pageListDiv.innerHTML = ''; 1307 this.refreshPageList( 1308 pageListDiv, 1309 previewButton!, 1310 nextButton!, 1311 pageInput!, 1312 this.currentPageNum, 1313 this.longTraceHeadMessageList.length 1314 ); 1315 this.initCutFileNextOrPreEvents(previewButton!, nextButton!, pageListDiv, pageInput!); 1316 let nodeListOf = pageListDiv.querySelectorAll<HTMLDivElement>('div'); 1317 nodeListOf.forEach((divEL, index) => { 1318 divEL.addEventListener('click', () => { 1319 if (this.progressEL!.loading) { 1320 return; 1321 } 1322 if (divEL.textContent === '...') { 1323 let freeSize = Number(nodeListOf[index + 1].textContent) - Number(nodeListOf[index - 1].textContent); 1324 this.currentPageNum = Math.floor(freeSize / 2 + Number(nodeListOf[index - 1].textContent)); 1325 } else { 1326 this.currentPageNum = Number(divEL.textContent); 1327 } 1328 this.refreshPageListHandler(pageListDiv, previewButton!, nextButton!, pageInput!); 1329 }); 1330 }); 1331 pageInput!.addEventListener('input', () => { 1332 let value = pageInput!.value; 1333 value = value.replace(/\D/g, ''); 1334 if (value) { 1335 value = Math.min(this.longTraceHeadMessageList.length, parseInt(value)).toString(); 1336 } 1337 pageInput!.value = value; 1338 }); 1339 let pageConfirmEl = this.shadowRoot?.querySelector<HTMLDivElement>('.confirm-button'); 1340 pageConfirmEl!.addEventListener('click', () => { 1341 if (this.progressEL!.loading) { 1342 return; 1343 } 1344 this.currentPageNum = Number(pageInput!.value); 1345 this.refreshPageListHandler(pageListDiv, previewButton!, nextButton!, pageInput!); 1346 }); 1347 } 1348 1349 private initCutFileNextOrPreEvents( 1350 previewButton: HTMLDivElement, 1351 nextButton: HTMLDivElement, 1352 pageListDiv: HTMLDivElement, 1353 pageInput: HTMLInputElement 1354 ): void { 1355 if (previewButton) { 1356 previewButton.addEventListener('click', () => { 1357 if (this.progressEL!.loading || this.currentPageNum === 1) { 1358 return; 1359 } 1360 if (this.currentPageNum > 1) { 1361 this.currentPageNum--; 1362 this.refreshPageListHandler(pageListDiv, previewButton!, nextButton!, pageInput!); 1363 } 1364 }); 1365 } 1366 nextButton!.addEventListener('click', () => { 1367 if (this.progressEL!.loading || this.currentPageNum === this.longTraceHeadMessageList.length) { 1368 return; 1369 } 1370 if (this.currentPageNum < this.longTraceHeadMessageList.length) { 1371 this.currentPageNum++; 1372 this.refreshPageListHandler(pageListDiv, previewButton!, nextButton!, pageInput!); 1373 } 1374 }); 1375 } 1376 1377 private initCustomColorHandler(): void { 1378 let customColorShow = this.shadowRoot 1379 ?.querySelector('lit-main-menu')! 1380 .shadowRoot!.querySelector('.customColor') as HTMLDivElement; 1381 customColorShow.addEventListener('click', (ev) => { 1382 if (this!.hasAttribute('custom-color')) { 1383 this!.removeAttribute('custom-color'); 1384 this.customColor!.setAttribute('hidden', ''); 1385 this.customColor!.cancelOperate(); 1386 } else { 1387 this!.removeAttribute('chart_filter'); 1388 this.chartFilter!.setAttribute('hidden', ''); 1389 this!.setAttribute('custom-color', ''); 1390 this.customColor!.removeAttribute('hidden'); 1391 } 1392 }); 1393 } 1394 1395 private openFileInit(): void { 1396 clearTraceFileCache(); 1397 this.litSearch!.clear(); 1398 this.markJson = undefined; 1399 SpStatisticsHttpUtil.addOrdinaryVisitAction({ 1400 event: 'open_trace', 1401 action: 'open_trace', 1402 }); 1403 info('openTraceFile'); 1404 this.headerDiv!.style.pointerEvents = 'none'; 1405 this.spSystemTrace!.clearPointPair(); 1406 this.spSystemTrace!.reset((command: string, percent: number) => { 1407 this.setProgress(command); 1408 }); 1409 window.publish(window.SmartEvent.UI.MouseEventEnable, { 1410 mouseEnable: false, 1411 }); 1412 window.clearTraceRowComplete(); 1413 this.freshMenuDisable(true); 1414 SpSchedulingAnalysis.resetCpu(); 1415 if (this.mainMenu!.menus!.length > 3) { 1416 this.mainMenu!.menus!.splice(1, 2); 1417 this.mainMenu!.menus = this.mainMenu!.menus!; 1418 } else if (this.mainMenu!.menus!.length > 2) { 1419 this.mainMenu!.menus!.splice(1, 1); 1420 this.mainMenu!.menus = this.mainMenu!.menus!; 1421 } 1422 this.showContent(this.spSystemTrace!); 1423 this.progressEL!.loading = true; 1424 } 1425 1426 private restoreDownLoadIcons(): void { 1427 let querySelectorAll = this.mainMenu!.shadowRoot?.querySelectorAll<LitMainMenuGroup>('lit-main-menu-group'); 1428 querySelectorAll!.forEach((menuGroup) => { 1429 let attribute = menuGroup.getAttribute('title'); 1430 if (attribute === 'Convert trace') { 1431 let querySelectors = menuGroup.querySelectorAll<LitMainMenuItem>('lit-main-menu-item'); 1432 querySelectors.forEach((item) => { 1433 if (item.getAttribute('title') === 'Convert to .systrace') { 1434 item!.setAttribute('icon', 'download'); 1435 let querySelector = item!.shadowRoot?.querySelector('.icon') as LitIcon; 1436 querySelector.removeAttribute('spin'); 1437 } 1438 }); 1439 } 1440 }); 1441 } 1442 1443 private postConvert(fileName: string): void { 1444 let newFileName = fileName.substring(0, fileName.lastIndexOf('.')) + '.systrace'; 1445 let aElement = document.createElement('a'); 1446 convertPool.submitWithName('getConvertData', (status: boolean, msg: string, results: Blob) => { 1447 aElement.href = URL.createObjectURL(results); 1448 aElement.download = newFileName; 1449 let timeoutId = 0; 1450 aElement.addEventListener('click', (ev) => { 1451 clearTimeout(timeoutId); 1452 timeoutId = window.setTimeout(() => { 1453 this.restoreDownLoadIcons(); 1454 }, 2000); 1455 }); 1456 aElement.click(); 1457 window.URL.revokeObjectURL(aElement.href); 1458 }); 1459 } 1460 1461 private pushConvertTrace(fileName: string): Array<unknown> { 1462 let instance = this; 1463 let menus = []; 1464 menus.push({ 1465 title: 'Convert to .systrace', 1466 icon: 'download', 1467 clickHandler: function () { 1468 convertPool.init('convert').then((item) => { 1469 let querySelectorAll = 1470 instance.mainMenu!.shadowRoot?.querySelectorAll<LitMainMenuGroup>('lit-main-menu-group'); 1471 querySelectorAll!.forEach((menuGroup) => { 1472 let attribute = menuGroup.getAttribute('title'); 1473 if (attribute === 'Convert trace') { 1474 let querySelectors = menuGroup.querySelectorAll<LitMainMenuItem>('lit-main-menu-item'); 1475 querySelectors.forEach((item) => { 1476 if (item.getAttribute('title') === 'Convert to .systrace') { 1477 item!.setAttribute('icon', 'convert-loading'); 1478 item!.classList.add('pending'); 1479 item!.style.fontKerning = ''; 1480 let querySelector = item!.shadowRoot?.querySelector('.icon') as LitIcon; 1481 querySelector.setAttribute('spin', ''); 1482 } 1483 }); 1484 } 1485 }); 1486 instance.postConvert(fileName); 1487 }); 1488 }, 1489 }); 1490 return menus; 1491 } 1492 1493 private setProgress(command: string): void { 1494 if (command === 'database ready' && SpApplication.loadingProgress < 50) { 1495 SpApplication.progressStep = 6; 1496 } 1497 if (command === 'process' && SpApplication.loadingProgress < 92) { 1498 SpApplication.loadingProgress = 92 + Math.round(Math.random() * SpApplication.progressStep); 1499 } else { 1500 SpApplication.loadingProgress += Math.round(Math.random() * SpApplication.progressStep + Math.random()); 1501 } 1502 if (SpApplication.loadingProgress > 99) { 1503 SpApplication.loadingProgress = 99; 1504 } 1505 info('setPercent :' + command + 'percent :' + SpApplication.loadingProgress); 1506 this.litSearch!.setPercent(command + ' ', SpApplication.loadingProgress); 1507 } 1508 1509 private getTraceOptionMenus( 1510 showFileName: string, 1511 fileSize: string, 1512 fileName: string, 1513 isServer: boolean, 1514 dbName?: string 1515 ): Array<unknown> { 1516 let menus = [ 1517 { 1518 title: `${showFileName} (${fileSize}M)`, 1519 icon: 'file-fill', 1520 clickHandler: (): void => { 1521 this.search = true; 1522 this.showContent(this.spSystemTrace!); 1523 }, 1524 }, 1525 { 1526 title: 'Scheduling Analysis', 1527 icon: 'piechart-circle-fil', 1528 clickHandler: (): void => { 1529 SpStatisticsHttpUtil.addOrdinaryVisitAction({ 1530 event: 'Scheduling Analysis', 1531 action: 'scheduling_analysis', 1532 }); 1533 this.showContent(this.spSchedulingAnalysis!); 1534 this.spSchedulingAnalysis!.init(); 1535 }, 1536 }, 1537 { 1538 title: 'Download File', 1539 icon: 'download', 1540 fileModel: this.wasm ? 'wasm' : 'db', 1541 clickHandler: (): void => { 1542 this.download(this.mainMenu!, fileName, isServer, dbName); 1543 SpStatisticsHttpUtil.addOrdinaryVisitAction({ 1544 event: 'download', 1545 action: 'download', 1546 }); 1547 }, 1548 }, 1549 { 1550 title: 'Download Database', 1551 icon: 'download', 1552 fileModel: this.wasm ? 'wasm' : 'db', 1553 clickHandler: (): void => { 1554 this.downloadDB(this.mainMenu!, fileName); 1555 SpStatisticsHttpUtil.addOrdinaryVisitAction({ 1556 event: 'download_db', 1557 action: 'download', 1558 }); 1559 }, 1560 }, 1561 ]; 1562 this.getTraceQuerySqlMenus(menus); 1563 //@ts-ignore 1564 if (window.cpuCount === 0 || !FlagsConfig.getFlagsConfigEnableStatus('SchedulingAnalysis')) { 1565 menus.splice(1, 1); 1566 } 1567 return menus; 1568 } 1569 1570 private getTraceSupportMenus(): Array<unknown> { 1571 return [ 1572 { 1573 title: 'Help Documents', 1574 icon: 'smart-help', 1575 clickHandler: (item: MenuItem): void => { 1576 this.spHelp!.dark = this.dark; 1577 this.search = false; 1578 this.showContent(this.spHelp!); 1579 SpStatisticsHttpUtil.addOrdinaryVisitAction({ 1580 event: 'help_page', 1581 action: 'help_doc', 1582 }); 1583 }, 1584 }, 1585 { 1586 title: 'Flags', 1587 icon: 'menu', 1588 fileModel: this.wasm ? 'wasm' : 'db', 1589 clickHandler: (item: MenuItem): void => { 1590 this.search = false; 1591 this.showContent(this.spFlags!); 1592 SpStatisticsHttpUtil.addOrdinaryVisitAction({ 1593 event: 'flags', 1594 action: 'flags', 1595 }); 1596 }, 1597 }, 1598 { 1599 title: 'Keyboard Shortcuts', 1600 icon: 'smart-help', 1601 clickHandler: (item: MenuItem): void => { 1602 document 1603 .querySelector('body > sp-application')! 1604 .shadowRoot!.querySelector<HTMLDivElement>('#sp-keyboard')!.style.visibility = 'visible'; 1605 SpStatisticsHttpUtil.addOrdinaryVisitAction({ 1606 event: 'Keyboard Shortcuts', 1607 action: 'Keyboard Shortcuts', 1608 }); 1609 }, 1610 }, 1611 { 1612 title: '第三方文件', 1613 icon: 'file-fill', 1614 fileModel: this.wasm ? 'wasm' : 'db', 1615 clickHandler: (item: MenuItem): void => { 1616 this.search = false; 1617 this.showContent(this.spThirdParty!); 1618 }, 1619 }, 1620 ]; 1621 } 1622 1623 private getTraceQuerySqlMenus(menus: Array<unknown>): void { 1624 if (this.querySql) { 1625 if (this.spQuerySQL) { 1626 this.spQuerySQL!.reset(); 1627 menus.push({ 1628 title: 'Query (SQL)', 1629 icon: 'filesearch', 1630 clickHandler: () => { 1631 this.showContent(this.spQuerySQL!); 1632 }, 1633 }); 1634 } 1635 if (this.spMetrics) { 1636 this.spMetrics!.reset(); 1637 menus.push({ 1638 title: 'Metrics', 1639 icon: 'metric', 1640 fileModel: this.wasm ? 'wasm' : 'db', 1641 clickHandler: () => { 1642 this.showContent(this.spMetrics!); 1643 }, 1644 }); 1645 } 1646 if (this.spInfoAndStats) { 1647 menus.push({ 1648 title: 'Info and stats', 1649 icon: 'info', 1650 clickHandler: () => { 1651 SpStatisticsHttpUtil.addOrdinaryVisitAction({ 1652 event: 'info', 1653 action: 'info_stats', 1654 }); 1655 this.showContent(this.spInfoAndStats!); 1656 }, 1657 }); 1658 } 1659 } 1660 } 1661 1662 private initSlideMenuEvents(): void { 1663 //打开侧边栏 1664 this.sidebarButton!.onclick = (e): void => { 1665 if (this.sidebarButton) { 1666 this.sidebarButton.style.width = '0px'; 1667 this.importConfigDiv!.style.left = '5px'; 1668 this.closeKeyPath!.style.left = '25px'; 1669 } 1670 if (this.mainMenu) { 1671 this.mainMenu.style.width = '248px'; 1672 this.mainMenu.style.zIndex = '2000'; 1673 this.mainMenu.style.display = 'flex'; 1674 } 1675 }; 1676 let icon: HTMLDivElement | undefined | null = this.mainMenu?.shadowRoot?.querySelector('div.header > div'); 1677 icon!.style.pointerEvents = 'none'; 1678 icon!.onclick = (e): void => { 1679 if (this.mainMenu) { 1680 this.mainMenu.style.width = '0px'; 1681 this.mainMenu.style.display = 'flex'; 1682 this.mainMenu.style.zIndex = '0'; 1683 } 1684 if (this.sidebarButton) { 1685 this.sidebarButton.style.width = '48px'; 1686 this.importConfigDiv!.style.left = '45px'; 1687 this.closeKeyPath!.style.left = '65px'; 1688 } 1689 }; 1690 } 1691 1692 private initImportConfigEvent(): void { 1693 this.importFileBt?.addEventListener('change', (): void => { 1694 let files = this.importFileBt!.files; 1695 if (files && files.length === 1) { 1696 const reader = new FileReader(); 1697 reader.readAsText(files[0], 'UTF-8'); 1698 reader.onload = (e): void => { 1699 if (e.target?.result) { 1700 try { 1701 const result = parseKeyPathJson(e.target.result as string); 1702 window.publish(window.SmartEvent.UI.KeyPath, result); 1703 this.closeKeyPath!.style.display = 'block'; 1704 } catch { 1705 error('json Parse Failed'); 1706 this.litSearch!.setPercent('Json Parse Failed!', -1); 1707 window.setTimeout(() => { 1708 this.litSearch!.setPercent('Json Parse Failed!', 101); 1709 }, 1000); 1710 } 1711 } else { 1712 window.publish(window.SmartEvent.UI.KeyPath, []); 1713 this.closeKeyPath!.style.display = 'none'; 1714 } 1715 }; 1716 } 1717 this.importFileBt!.files = null; 1718 this.importFileBt!.value = ''; 1719 }); 1720 if (this.closeKeyPath) { 1721 this.closeKeyPath.addEventListener('click', (): void => { 1722 window.publish(window.SmartEvent.UI.KeyPath, []); 1723 this.closeKeyPath!.style.display = 'none'; 1724 }); 1725 } 1726 } 1727 1728 private initCustomEvents(): void { 1729 window.subscribe(window.SmartEvent.UI.MenuTrace, () => this.showContent(this.spSystemTrace!)); 1730 window.subscribe(window.SmartEvent.UI.Error, (err) => { 1731 //@ts-ignore 1732 this.litSearch!.setPercent(err, -1); 1733 this.progressEL!.loading = false; 1734 this.freshMenuDisable(false); 1735 }); //@ts-ignore 1736 window.subscribe(window.SmartEvent.UI.Loading, (arg: { loading: boolean; text?: string }) => { 1737 if (arg.text) { 1738 this.litSearch!.setPercent(arg.text || '', arg.loading ? -1 : 101); 1739 } 1740 if (this.headerDiv) { 1741 this.headerDiv.style.pointerEvents = arg.loading ? 'none' : 'auto'; 1742 } 1743 window.publish(window.SmartEvent.UI.MouseEventEnable, { 1744 mouseEnable: !arg.loading, 1745 }); 1746 this.progressEL!.loading = arg.loading; 1747 }); 1748 } 1749 1750 private initEvents(): void { 1751 this.addEventListener('copy', function (event) { 1752 SpSystemTrace.isMouseLeftDown = false; 1753 let clipdata = event.clipboardData; 1754 let value = clipdata!.getData('text/plain'); 1755 let searchValue = value.toString().trim(); 1756 clipdata!.setData('text/plain', searchValue); 1757 }); 1758 this.initSearchEvents(); 1759 this.initSystemTraceEvents(); 1760 this.filterConfig!.addEventListener('click', (ev) => { 1761 SpSystemTrace.isMouseLeftDown = false; 1762 this.filterRowConfigClickHandle(); 1763 }); 1764 this.configClose!.addEventListener('click', (ev) => { 1765 this.filterRowConfigClickHandle(); 1766 }); 1767 this.cutTraceFile!.addEventListener('click', (ev) => { 1768 this.croppingFile(this.progressEL!, this.litSearch!); 1769 }); 1770 } 1771 1772 private filterRowConfigClickHandle(): void { 1773 if (this!.hasAttribute('chart_filter')) { 1774 this!.removeAttribute('chart_filter'); 1775 this.chartFilter!.setAttribute('hidden', ''); 1776 } else { 1777 this!.removeAttribute('custom-color'); 1778 this.customColor!.setAttribute('hidden', ''); 1779 this.customColor!.cancelOperate(); 1780 this!.setAttribute('chart_filter', ''); 1781 this.chartFilter!.removeAttribute('hidden'); 1782 } 1783 } 1784 1785 private initRecordEvents(): void { 1786 this.exportRecord?.addEventListener('click', () => { 1787 this.headerDiv!.style.pointerEvents = 'none'; 1788 window.publish(window.SmartEvent.UI.Loading, { loading: true, text: 'Downloading trace file with mark' }); 1789 window.publish(window.SmartEvent.UI.ExportRecord, { bt: this.exportRecord }); 1790 }); 1791 } 1792 1793 private initSearchChangeEvents(): void { 1794 let timer: NodeJS.Timeout; 1795 this.litSearch!.valueChangeHandler = (value: string): void => { 1796 this.litSearch!.list = []; 1797 if (timer) { 1798 clearTimeout(timer); 1799 } 1800 timer = setTimeout(() => { 1801 this.litSearch!.isClearValue = false; 1802 if (value.length > 0) { 1803 let list = []; 1804 this.progressEL!.loading = true; 1805 this.spSystemTrace!.searchCPU(value).then((cpus) => { 1806 list = cpus; 1807 this.spSystemTrace!.searchFunction(list, value).then((mixedResults) => { 1808 if (this.litSearch!.searchValue !== '') { 1809 this.litSearch!.list = this.spSystemTrace!.searchSdk(mixedResults, value); 1810 this.litSearch!.index = this.spSystemTrace!.showStruct(false, -1, this.litSearch!.list); 1811 } 1812 this.progressEL!.loading = false; 1813 }); 1814 }); 1815 } else { 1816 let indexEL = this.litSearch!.shadowRoot!.querySelector<HTMLSpanElement>('#index'); 1817 indexEL!.textContent = '0'; 1818 this.litSearch!.list = []; 1819 this.spSystemTrace?.visibleRows.forEach((it) => { 1820 it.highlight = false; 1821 it.draw(); 1822 }); 1823 this.spSystemTrace?.timerShaftEL?.removeTriangle('inverted'); 1824 } 1825 }, 1000); 1826 }; 1827 } 1828 private initSearchEvents(): void { 1829 this.litSearch!.addEventListener('focus', () => { 1830 window.publish(window.SmartEvent.UI.KeyboardEnable, { 1831 enable: false, 1832 }); 1833 }); 1834 this.litSearch!.addEventListener('blur', () => { 1835 window.publish(window.SmartEvent.UI.KeyboardEnable, { 1836 enable: true, 1837 }); 1838 }); 1839 this.litSearch!.addEventListener('previous-data', (ev) => { 1840 this.litSearch!.index = this.spSystemTrace!.showStruct(true, this.litSearch!.index, this.litSearch!.list); 1841 this.litSearch!.blur(); 1842 }); 1843 this.litSearch!.addEventListener('next-data', (ev) => { 1844 this.litSearch!.index = this.spSystemTrace!.showStruct(false, this.litSearch!.index, this.litSearch!.list); 1845 this.litSearch!.blur(); 1846 }); 1847 // 翻页事件 1848 this.litSearch!.addEventListener('retarget-data', (ev) => { 1849 this.litSearch!.index = this.spSystemTrace!.showStruct( 1850 true, 1851 //@ts-ignore 1852 ev.detail.value, 1853 this.litSearch!.list, 1854 //@ts-ignore 1855 ev.detail.value 1856 ); 1857 this.litSearch!.blur(); 1858 }); 1859 this.initSearchChangeEvents(); 1860 } 1861 1862 private initSystemTraceEvents(): void { 1863 this.spSystemTrace?.addEventListener('trace-previous-data', (ev) => { 1864 this.litSearch!.index = this.spSystemTrace!.showStruct(true, this.litSearch!.index, this.litSearch!.list); 1865 }); 1866 this.spSystemTrace?.addEventListener('trace-next-data', (ev) => { 1867 this.litSearch!.index = this.spSystemTrace!.showStruct(false, this.litSearch!.index, this.litSearch!.list); 1868 }); 1869 } 1870 1871 private showContent(showNode: HTMLElement): void { 1872 if (showNode === this.spSystemTrace) { 1873 this.menu!.style.pointerEvents = 'auto'; 1874 this.sidebarButton!.style.pointerEvents = 'auto'; 1875 this.search = true; 1876 this.litRecordSearch!.style.display = 'none'; 1877 this.litSearch!.style.display = 'block'; 1878 this.contentRightOption!.style.display = 'flex'; 1879 this.contentLeftOption!.style.display = 'flex'; 1880 this.contentCenterOption!.style.display = 'flex'; 1881 window.publish(window.SmartEvent.UI.KeyboardEnable, { 1882 enable: true, 1883 }); 1884 this.filterConfig!.style.visibility = 'visible'; 1885 } else { 1886 this.removeAttribute('custom-color'); 1887 this.customColor!.setAttribute('hidden', ''); 1888 this.customColor!.cancelOperate(); 1889 this.menu!.style.pointerEvents = 'none'; 1890 this.sidebarButton!.style.pointerEvents = 'none'; 1891 this.search = this.litSearch!.isLoading; 1892 this.contentRightOption!.style.display = 'none'; 1893 this.contentLeftOption!.style.display = 'none'; 1894 this.contentCenterOption!.style.display = 'none'; 1895 if (!this.search) { 1896 this.litSearch!.style.display = 'none'; 1897 this.litRecordSearch!.style.display = 'block'; 1898 } 1899 window.publish(window.SmartEvent.UI.KeyboardEnable, { 1900 enable: false, 1901 }); 1902 this.filterConfig!.style.visibility = 'hidden'; 1903 } 1904 this.childComponent!.forEach((node) => { 1905 if (this.hasAttribute('chart_filter')) { 1906 this.removeAttribute('chart_filter'); 1907 } 1908 if (this.hasAttribute('custom-color')) { 1909 this.removeAttribute('custom-color'); 1910 this.customColor!.setAttribute('hidden', ''); 1911 this.customColor!.cancelOperate(); 1912 } 1913 if (node === showNode) { 1914 showNode.style.visibility = 'visible'; 1915 } else { 1916 (node! as HTMLElement).style.visibility = 'hidden'; 1917 } 1918 }); 1919 } 1920 1921 private validateFileCacheLost(): void { 1922 caches.has(DbPool.fileCacheKey).then((exist) => { 1923 if (!exist) { 1924 this.mainMenu!.menus?.forEach((mg) => { 1925 // @ts-ignore 1926 mg.children.forEach((mi: unknown) => { 1927 //@ts-ignore 1928 if (mi.title === 'Download File') { 1929 //@ts-ignore 1930 mi.disabled = true; 1931 } 1932 }); 1933 }); 1934 this.cutTraceFile!.style.display = 'none'; 1935 this.mainMenu!.menus = this.mainMenu!.menus; 1936 } 1937 }); 1938 } 1939 1940 private refreshPageList( 1941 pageListDiv: HTMLDivElement, 1942 previewButton: HTMLDivElement, 1943 nextButton: HTMLDivElement, 1944 pageInput: HTMLInputElement, 1945 currentPageNum: number, 1946 maxPageNumber: number 1947 ): void { 1948 if (pageInput) { 1949 pageInput.textContent = currentPageNum.toString(); 1950 pageInput.value = currentPageNum.toString(); 1951 } 1952 let pageText: string[] = []; 1953 if (maxPageNumber > 7) { 1954 pageText = this.largePageHandler(currentPageNum, maxPageNumber, previewButton, nextButton); 1955 } else { 1956 pageText = []; 1957 for (let index = 0; index < maxPageNumber; index++) { 1958 pageText.push((index + 1).toString()); 1959 } 1960 } 1961 this.pageNodeHandler(pageListDiv, pageText, currentPageNum); 1962 nextButton.style.pointerEvents = 'auto'; 1963 nextButton.style.opacity = '1'; 1964 previewButton.style.pointerEvents = 'auto'; 1965 previewButton.style.opacity = '1'; 1966 if (currentPageNum === 1) { 1967 previewButton.style.pointerEvents = 'none'; 1968 previewButton.style.opacity = '0.7'; 1969 } else if (currentPageNum === maxPageNumber) { 1970 nextButton.style.pointerEvents = 'none'; 1971 nextButton.style.opacity = '0.7'; 1972 } 1973 } 1974 1975 private pageNodeHandler(pageListDiv: HTMLDivElement, pageText: Array<string>, currentPageNum: number): void { 1976 let pageNodeList = pageListDiv.querySelectorAll<HTMLDivElement>('div'); 1977 if (pageNodeList.length > 0) { 1978 pageNodeList.forEach((page, index) => { 1979 page.textContent = pageText[index].toString(); 1980 page.title = pageText[index]; 1981 if (currentPageNum.toString() === pageText[index]) { 1982 page.setAttribute('selected', ''); 1983 } else { 1984 if (page.hasAttribute('selected')) { 1985 page.removeAttribute('selected'); 1986 } 1987 } 1988 }); 1989 } else { 1990 pageListDiv.innerHTML = ''; 1991 pageText.forEach((page) => { 1992 let element = document.createElement('div'); 1993 element.className = 'page-number pagination'; 1994 element.textContent = page.toString(); 1995 element.title = page.toString(); 1996 if (currentPageNum.toString() === page.toString()) { 1997 element.setAttribute('selected', ''); 1998 } 1999 pageListDiv.appendChild(element); 2000 }); 2001 } 2002 } 2003 2004 private largePageHandler( 2005 currentPageNum: number, 2006 maxPageNumber: number, 2007 previewButton: HTMLDivElement, 2008 nextButton: HTMLDivElement 2009 ): Array<string> { 2010 switch (currentPageNum) { 2011 case 1: 2012 case 2: 2013 case 3: 2014 case 4: 2015 case 5: 2016 return ['1', '2', '3', '4', '5', '...', maxPageNumber.toString()]; 2017 case maxPageNumber: 2018 case maxPageNumber - 1: 2019 case maxPageNumber - 2: 2020 case maxPageNumber - 3: 2021 case maxPageNumber - 4: 2022 return [ 2023 '1', 2024 '...', 2025 (maxPageNumber - 4).toString(), 2026 (maxPageNumber - 3).toString(), 2027 (maxPageNumber - 2).toString(), 2028 (maxPageNumber - 1).toString(), 2029 maxPageNumber.toString(), 2030 ]; 2031 default: 2032 nextButton.style.pointerEvents = 'auto'; 2033 previewButton!.style.pointerEvents = 'auto'; 2034 nextButton.style.opacity = '1'; 2035 previewButton!.style.opacity = '1'; 2036 return [ 2037 '1', 2038 '...', 2039 (currentPageNum - 1).toString(), 2040 currentPageNum.toString(), 2041 (currentPageNum + 1).toString(), 2042 '...', 2043 maxPageNumber.toString(), 2044 ]; 2045 } 2046 } 2047 2048 /** 2049 * 修改颜色或者主题,重新绘制侧边栏和泳道图 2050 * @param theme 当前主题(深色和浅色) 2051 * @param colorsArray 预览的情况下传入 2052 */ 2053 changeTheme(theme: Theme, colorsArray?: Array<string>): void { 2054 if (!colorsArray) { 2055 this.customColor!.setRadioChecked(theme); 2056 } 2057 if (theme === Theme.DARK) { 2058 this.changeDarkTheme(colorsArray); 2059 } else { 2060 this.changeLightTheme(colorsArray); 2061 } 2062 this.spSystemTrace!.timerShaftEL!.rangeRuler!.draw(); 2063 if (this.colorTransiton) { 2064 clearTimeout(this.colorTransiton); 2065 } 2066 this.colorTransiton = setTimeout(() => (this.mainMenu!.style.transition = '0s'), 1000); 2067 } 2068 2069 private changeDarkTheme(colorsArray?: Array<string>): void { 2070 let menuGroup = this.mainMenu!.shadowRoot?.querySelectorAll<LitMainMenuGroup>('lit-main-menu-group'); 2071 let menuItem = this.mainMenu!.shadowRoot?.querySelectorAll<LitMainMenuItem>('lit-main-menu-item'); 2072 this.mainMenu!.style.backgroundColor = '#262f3c'; 2073 this.mainMenu!.style.transition = '1s'; 2074 menuGroup!.forEach((item) => { 2075 let groupName = item!.shadowRoot!.querySelector('.group-name') as LitMainMenuGroup; 2076 let groupDescribe = item!.shadowRoot!.querySelector('.group-describe') as LitMainMenuGroup; 2077 groupName.style.color = 'white'; 2078 groupDescribe.style.color = 'white'; 2079 }); 2080 menuItem!.forEach((item) => { 2081 item.style.color = 'white'; 2082 }); 2083 if ( 2084 !colorsArray && 2085 window.localStorage.getItem('DarkThemeColors') && 2086 ColorUtils.FUNC_COLOR_B !== JSON.parse(window.localStorage.getItem('DarkThemeColors')!) 2087 ) { 2088 ColorUtils.MD_PALETTE = JSON.parse(window.localStorage.getItem('DarkThemeColors')!); 2089 ColorUtils.FUNC_COLOR = JSON.parse(window.localStorage.getItem('DarkThemeColors')!); 2090 } else if (colorsArray) { 2091 ColorUtils.MD_PALETTE = colorsArray; 2092 ColorUtils.FUNC_COLOR = colorsArray; 2093 } else { 2094 ColorUtils.MD_PALETTE = ColorUtils.FUNC_COLOR_B; 2095 ColorUtils.FUNC_COLOR = ColorUtils.FUNC_COLOR_B; 2096 } 2097 } 2098 2099 private changeLightTheme(colorsArray?: Array<string>): void { 2100 let menuGroup = this.mainMenu!.shadowRoot?.querySelectorAll<LitMainMenuGroup>('lit-main-menu-group'); 2101 let menuItem = this.mainMenu!.shadowRoot?.querySelectorAll<LitMainMenuItem>('lit-main-menu-item'); 2102 this.mainMenu!.style.backgroundColor = 'white'; 2103 this.mainMenu!.style.transition = '1s'; 2104 menuGroup!.forEach((item) => { 2105 let groupName = item!.shadowRoot!.querySelector('.group-name') as LitMainMenuGroup; 2106 let groupDescribe = item!.shadowRoot!.querySelector('.group-describe') as LitMainMenuGroup; 2107 groupName.style.color = 'black'; 2108 groupDescribe.style.color = '#92959b'; 2109 }); 2110 menuItem!.forEach((item) => { 2111 item.style.color = 'black'; 2112 }); 2113 if ( 2114 !colorsArray && 2115 window.localStorage.getItem('LightThemeColors') && 2116 ColorUtils.FUNC_COLOR_A !== JSON.parse(window.localStorage.getItem('LightThemeColors')!) 2117 ) { 2118 ColorUtils.MD_PALETTE = JSON.parse(window.localStorage.getItem('LightThemeColors')!); 2119 ColorUtils.FUNC_COLOR = JSON.parse(window.localStorage.getItem('LightThemeColors')!); 2120 } else if (colorsArray) { 2121 ColorUtils.MD_PALETTE = colorsArray; 2122 ColorUtils.FUNC_COLOR = colorsArray; 2123 } else { 2124 ColorUtils.MD_PALETTE = ColorUtils.FUNC_COLOR_A; 2125 ColorUtils.FUNC_COLOR = ColorUtils.FUNC_COLOR_A; 2126 } 2127 } 2128 2129 private downloadOnLineFile( 2130 url: string, 2131 download: boolean, 2132 openUrl: (buffer: ArrayBuffer, fileName: string, showFileName: string, fileSize: number) => void, 2133 openFileHandler: (path: string) => void 2134 ): void { 2135 if (download) { 2136 fetch(url) 2137 .then((res) => { 2138 res.arrayBuffer().then((arrayBuf) => { 2139 let fileName = url.split('/').reverse()[0]; 2140 let showFileName = 2141 fileName.lastIndexOf('.') === -1 ? fileName : fileName.substring(0, fileName.lastIndexOf('.')); 2142 openUrl(arrayBuf, fileName, showFileName, arrayBuf.byteLength); 2143 }); 2144 }) 2145 .catch((e) => { 2146 let api = `${window.location.origin}/application/download-file`; 2147 fetch(api, { 2148 method: 'POST', 2149 headers: { 2150 'Content-Type': 'application/x-www-form-urlencoded', 2151 }, 2152 body: new URLSearchParams({ 2153 url: url, 2154 }), 2155 }) 2156 .then((response) => response.json()) 2157 .then((res) => { 2158 if (res.code === 0 && res.success) { 2159 let resultUrl = res.data.url; 2160 if (resultUrl) { 2161 openFileHandler(resultUrl.toString().replace(/\\/g, '/')); 2162 } 2163 } 2164 }); 2165 }); 2166 } else { 2167 openFileHandler(url); 2168 } 2169 } 2170 2171 private croppingFile(progressEL: LitProgressBar, litSearch: LitSearch): void { 2172 let cutLeftNs = TraceRow.rangeSelectObject?.startNS || 0; 2173 let cutRightNs = TraceRow.rangeSelectObject?.endNS || 0; 2174 if (cutRightNs === cutLeftNs) { 2175 return; 2176 } 2177 let recordStartNS = window.recordStartNS; 2178 let cutLeftTs = recordStartNS + cutLeftNs; 2179 let cutRightTs = recordStartNS + cutRightNs; 2180 let minCutDur = 1_000_000; 2181 if (cutRightTs - cutLeftTs < minCutDur) { 2182 let unitTs = (cutRightTs - cutLeftTs) / 2; 2183 let midTs = cutLeftTs + unitTs; 2184 cutLeftTs = midTs - minCutDur / 2; 2185 cutRightTs = midTs + minCutDur / 2; 2186 } 2187 progressEL.loading = true; 2188 threadPool.cutFile(cutLeftTs, cutRightTs, (status: boolean, msg: string, cutBuffer?: ArrayBuffer) => { 2189 progressEL.loading = false; 2190 if (status) { 2191 FlagsConfig.updateFlagsConfig('FfrtConvert', 'Disabled'); 2192 let traceFileName = this.traceFileName as string; 2193 let cutIndex = traceFileName.indexOf('_cut_'); 2194 let fileType = traceFileName.substring(traceFileName.lastIndexOf('.')); 2195 let traceName = document.title.replace(/\s*\([^)]*\)/g, '').trim(); 2196 if (cutIndex !== -1) { 2197 traceName = traceName.substring(0, cutIndex); 2198 } 2199 if (cutBuffer !== undefined && cutBuffer.byteLength <= 12) { 2200 this.litSearch!.setPercent('The cut is empty data. Select a time range for valid data!', -1); 2201 this.progressEL!.loading = false; 2202 this.freshMenuDisable(false); 2203 return; 2204 } 2205 let blobUrl = URL.createObjectURL(new Blob([cutBuffer!])); 2206 window.open( 2207 `index.html?link=true&local=true&traceName=${encodeURIComponent( 2208 traceName 2209 )}_cut_${cutLeftTs}${fileType}&trace=${encodeURIComponent(blobUrl)}` 2210 ); 2211 } else { 2212 litSearch.setPercent(msg, -1); 2213 window.setTimeout(() => { 2214 litSearch.setPercent(msg, 101); 2215 }, 1000); 2216 } 2217 }); 2218 } 2219 2220 private downloadDB(mainMenu: LitMainMenu, fileDbName: string): void { 2221 let fileName = `${fileDbName?.substring(0, fileDbName?.lastIndexOf('.'))}.db`; 2222 threadPool.submit( 2223 'download-db', 2224 '', 2225 {}, 2226 (reqBufferDB: ArrayBuffer) => { 2227 let a = document.createElement('a'); 2228 a.href = URL.createObjectURL(new Blob([reqBufferDB])); 2229 a.download = fileName; 2230 a.click(); 2231 this.itemIconLoading(mainMenu, 'Current Trace', 'Download Database', true); 2232 let timer = setInterval(() => { 2233 this.itemIconLoading(mainMenu, 'Current Trace', 'Download Database', false); 2234 clearInterval(timer); 2235 }, 4000); 2236 }, 2237 'download-db' 2238 ); 2239 } 2240 2241 private async download(mainMenu: LitMainMenu, fileName: string, isServer: boolean, dbName?: string): Promise<void> { 2242 let a = document.createElement('a'); 2243 if (isServer) { 2244 if (dbName !== '') { 2245 let file = dbName?.substring(0, dbName?.lastIndexOf('.')) + fileName.substring(fileName.lastIndexOf('.')); 2246 a.href = `https://${window.location.host.split(':')[0]}:${window.location.port}${file}`; 2247 } else { 2248 return; 2249 } 2250 } else { 2251 let buffer = await readTraceFileBuffer(); 2252 if (buffer) { 2253 a.href = URL.createObjectURL(new Blob([buffer])); 2254 } 2255 } 2256 a.download = fileName; 2257 a.click(); 2258 window.URL.revokeObjectURL(a.href); 2259 this.itemIconLoading(mainMenu, 'Current Trace', 'Download File', true); 2260 let timer = setInterval(() => { 2261 this.itemIconLoading(mainMenu, 'Current Trace', 'Download File', false); 2262 clearInterval(timer); 2263 }, 4000); 2264 } 2265 2266 private itemIconLoading(mainMenu: LitMainMenu, groupName: string, itemName: string, start: boolean): void { 2267 let currentTraceGroup = mainMenu.shadowRoot?.querySelector<LitMainMenuGroup>( 2268 `lit-main-menu-group[title='${groupName}']` 2269 ); 2270 let downloadItem = currentTraceGroup!.querySelector<LitMainMenuItem>(`lit-main-menu-item[title='${itemName}']`); 2271 let downloadIcon = downloadItem!.shadowRoot?.querySelector('.icon') as LitIcon; 2272 if (start) { 2273 downloadItem!.setAttribute('icon', 'convert-loading'); 2274 downloadIcon.setAttribute('spin', ''); 2275 } else { 2276 downloadItem!.setAttribute('icon', 'download'); 2277 downloadIcon.removeAttribute('spin'); 2278 } 2279 } 2280 2281 freshMenuDisable(disable: boolean): void { 2282 // @ts-ignore 2283 this.mainMenu!.menus[0].children[0].disabled = disable; 2284 // @ts-ignore 2285 this.mainMenu!.menus[0].children[1].disabled = disable; 2286 if (this.mainMenu!.menus!.length > 2) { 2287 // @ts-ignore 2288 this.mainMenu!.menus[1].children.map((it) => (it.disabled = disable)); 2289 } 2290 this.mainMenu!.menus = this.mainMenu!.menus; 2291 this.filterConfig!.style.visibility = disable ? 'hidden' : 'visible'; 2292 } 2293} 2294