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 { SelectionParam } from '../../../../bean/BoxSelection'; 18import { HiSysEventStruct } from '../../../../database/ui-worker/ProcedureWorkerHiSysEvent'; 19import { ns2x, Rect } from '../../../../database/ui-worker/ProcedureWorkerCommon'; 20import { LitPageTable } from '../../../../../base-ui/table/LitPageTable'; 21import { LitTable } from '../../../../../base-ui/table/lit-table'; 22import { LitSlicerTrack } from '../../../../../base-ui/slicer/lit-slicer'; 23import { TraceRow } from '../../base/TraceRow'; 24import { Flag } from '../../timer-shaft/Flag'; 25import { TraceSheet } from '../../base/TraceSheet'; 26import { SpSystemTrace } from '../../../SpSystemTrace'; 27import { ColorUtils } from '../../base/ColorUtils'; 28import {queryHiSysEventTabData} from "../../../../database/sql/Perf.sql"; 29import {queryRealTime} from "../../../../database/sql/Clock.sql"; 30import { TabPaneHiSysEventsHtml } from './TabPaneHisysEvents.html'; 31 32@element('tab-hisysevents') 33export class TabPaneHisysEvents extends BaseElement { 34 private hisysEventSource: Array<HiSysEventStruct> = []; 35 private filterDataList: HiSysEventStruct[] = []; 36 private hiSysEventTable: LitPageTable | undefined | null; 37 private currentSelection: SelectionParam | undefined; 38 private domainFilterInput: HTMLInputElement | undefined | null; 39 private eventNameFilterInput: HTMLInputElement | undefined | null; 40 private levelFilter: HTMLSelectElement | undefined | null; 41 private contentFilterInput: HTMLInputElement | undefined | null; 42 private domainTag: Set<string> = new Set(); 43 private domainTagDiv: HTMLDivElement | undefined | null; 44 private eventNameTag: Set<string> = new Set(); 45 private eventNameTagDiv: HTMLDivElement | undefined | null; 46 private traceSheetEl: TraceSheet | undefined | null; 47 private spSystemTrace: SpSystemTrace | undefined | null; 48 private detailsTbl: LitTable | null | undefined; 49 private boxDetails: HTMLDivElement | null | undefined; 50 private slicerTrack: LitSlicerTrack | null | undefined; 51 private tableElement: HTMLDivElement | undefined | null; 52 private detailbox: HTMLDivElement | null | undefined; 53 private changeInput: HTMLInputElement | null | undefined; 54 private eventTableTitle: HTMLLabelElement | undefined | null; 55 tableTitleTimeHandle: (() => void) | undefined; 56 private currentDetailList: Array<{ key: string; value: string }> = []; 57 private realTime: number = -1; 58 private baseTime: string = ''; 59 60 set data(systemEventParam: SelectionParam) { 61 if (systemEventParam === this.currentSelection) { 62 return; 63 } 64 if (this.hiSysEventTable) { 65 this.hiSysEventTable.recycleDataSource = []; 66 this.filterDataList = []; 67 } 68 if (this.detailsTbl) { 69 this.detailsTbl!.recycleDataSource = []; 70 } 71 this.initTabSheetEl(); 72 queryRealTime().then((result) => { 73 if (result && result.length > 0) { 74 this.realTime = Math.floor(result[0].ts / millisecond); 75 } 76 queryHiSysEventTabData(systemEventParam.leftNs, systemEventParam.rightNs).then((res) => { 77 this.currentSelection = systemEventParam; 78 systemEventParam.sysAllEventsData = res; 79 this.hiSysEventTable!.recycleDataSource = res; 80 this.hisysEventSource = res; 81 this.updateData(); 82 }); 83 }); 84 } 85 86 queryElements(): void { 87 this.boxDetails = this.shadowRoot?.querySelector<HTMLDivElement>('.box-details'); 88 this.hiSysEventTable = this.shadowRoot?.querySelector<LitPageTable>('#tb-hisysevent'); 89 this.hiSysEventTable!.getItemTextColor = (data) => { 90 return ColorUtils.getHisysEventColor(data.level); 91 }; 92 this.domainTagDiv = this.shadowRoot?.querySelector<HTMLDivElement>('#domainTagFilter'); 93 this.eventNameTagDiv = this.shadowRoot?.querySelector<HTMLDivElement>('#eventNameTagFilter'); 94 this.domainFilterInput = this.shadowRoot?.querySelector<HTMLInputElement>('#domain-filter'); 95 this.eventNameFilterInput = this.shadowRoot?.querySelector<HTMLInputElement>('#event-name-filter'); 96 this.levelFilter = this.shadowRoot?.querySelector<HTMLSelectElement>('#level-filter'); 97 this.spSystemTrace = document.querySelector('body > sp-application') 98 ?.shadowRoot?.querySelector<SpSystemTrace>('#sp-system-trace'); 99 this.traceSheetEl = this.spSystemTrace?.shadowRoot?.querySelector('.trace-sheet'); 100 this.contentFilterInput = this.shadowRoot?.querySelector<HTMLInputElement>('#contents-filter'); 101 this.changeInput = this.shadowRoot?.querySelector<HTMLInputElement>('#contents-change'); 102 this.detailsTbl = this.shadowRoot?.querySelector<LitTable>('#tb-hisysevent-data'); 103 this.slicerTrack = this.shadowRoot?.querySelector<LitSlicerTrack>('lit-slicer-track'); 104 this.detailbox = this.shadowRoot?.querySelector<HTMLDivElement>('.detail-content'); 105 this.tableElement = this.hiSysEventTable?.shadowRoot?.querySelector('.table') as HTMLDivElement; 106 this.eventTableTitle = this.shadowRoot?.querySelector<HTMLLabelElement>('#event-title'); 107 this.tableTitleTimeHandle = this.delayedRefresh(this.refreshEventsTitle); 108 this.initHiSysEventListener(); 109 } 110 111 private initHiSysEventListener(): void { 112 this.hiSysEventTable!.addEventListener('row-click', (event) => { 113 this.changeInput!.value = ''; 114 // @ts-ignore 115 const data = event.detail.data; 116 this.convertData(data); 117 this.hiSysEventTable?.clearAllSelection(); 118 data.isSelected = true; 119 this.hiSysEventTable?.setCurrentSelection(data); 120 this.updateDetail(this.baseTime); 121 }); 122 this.boxDetails!.addEventListener('click', (ev) => { 123 if (ev.target !== this.hiSysEventTable) { 124 this.hiSysEventTable?.clearAllSelection(); 125 this.detailsTbl!.dataSource = []; 126 this.boxDetails!.style.width = '100%'; 127 this.detailbox!.style.display = 'none'; 128 this.slicerTrack!.style.visibility = 'hidden'; 129 this.detailsTbl!.style.paddingLeft = '0px'; 130 } 131 }); 132 this.hiSysEventTable!.addEventListener('row-hover', (e) => { 133 // @ts-ignore 134 let data = e.detail.data; 135 if (data) { 136 this.drawFlag(data.startTs, '#999999'); 137 } 138 }); 139 this.hiSysEventTable!.addEventListener('column-click', (evt) => { 140 // @ts-ignore 141 this.sortByColumn(evt.detail); 142 }); 143 this.tableElement?.addEventListener('mouseout', () => { 144 this.traceSheetEl!.systemLogFlag = undefined; 145 this.spSystemTrace?.refreshCanvas(false); 146 }); 147 } 148 149 initElements(): void { 150 this.queryElements(); 151 this.detailsTbl!.addEventListener('row-hover', (e) => { 152 // @ts-ignore 153 let data = e.detail.data; 154 if (data && data.key && this.realTime >= 0) { 155 if (data.key.endsWith('_TIME') || data.key.endsWith('_LATENCY')) { 156 this.drawFlag(data.value, '#999999'); 157 return; 158 } 159 } 160 this.traceSheetEl!.systemLogFlag = undefined; 161 this.spSystemTrace?.refreshCanvas(false); 162 }); 163 } 164 165 private delayedRefresh(optionFn: Function, dur: number = 50): () => void { 166 let timeOutId: number; 167 return (...args: []): void => { 168 window.clearTimeout(timeOutId); 169 timeOutId = window.setTimeout((): void => { 170 optionFn.apply(this, ...args); 171 }, dur); 172 }; 173 } 174 175 private refreshEventsTitle() { 176 let tbl = this.hiSysEventTable?.shadowRoot?.querySelector<HTMLDivElement>('.table'); 177 let height = 0; 178 let firstRowHeight = 27; 179 let tableHeadHeight = 26; 180 if (this.hiSysEventTable && this.hiSysEventTable.currentRecycleList.length > 0) { 181 let startDataIndex = this.hiSysEventTable.startSkip + 1; 182 let endDataIndex = startDataIndex; 183 if (height < firstRowHeight * 0.3) { 184 startDataIndex++; 185 } 186 let tableHeight = Number(tbl!.style.height.replace('px', '')) - tableHeadHeight; 187 while (height < tableHeight) { 188 if (firstRowHeight <= 0 || height + firstRowHeight > tableHeight) { 189 break; 190 } 191 height += firstRowHeight; 192 endDataIndex++; 193 } 194 if (tableHeight - height > firstRowHeight * 0.3) { 195 endDataIndex++; 196 } 197 if (endDataIndex >= this.filterDataList.length) { 198 endDataIndex = this.filterDataList.length; 199 } else { 200 endDataIndex = this.hiSysEventTable.startSkip === 0 ? endDataIndex - 1 : endDataIndex; 201 } 202 this.eventTableTitle!.textContent = `HisysEvents [${this.hiSysEventTable.startSkip === 0 ? 1 : startDataIndex}, 203 ${endDataIndex}] / ${this.filterDataList.length || 0}`; 204 } else { 205 this.eventTableTitle!.textContent = 'HisysEvents [0, 0] / 0'; 206 } 207 } 208 209 initTabSheetEl(): void { 210 this.levelFilter!.selectedIndex = 0; 211 this.domainFilterInput!.value = ''; 212 this.domainTagDiv!.innerHTML = ''; 213 this.domainTag.clear(); 214 this.eventNameFilterInput!.value = ''; 215 this.eventNameTagDiv!.innerHTML = ''; 216 this.eventNameTag.clear(); 217 this.contentFilterInput!.value = ''; 218 this.boxDetails!.style.width = '100%'; 219 this.detailbox!.style.display = 'none'; 220 this.slicerTrack!.style.visibility = 'hidden'; 221 this.detailsTbl!.style.paddingLeft = '0px'; 222 } 223 224 initHtml(): string { 225 return TabPaneHiSysEventsHtml; 226 } 227 228 connectedCallback(): void { 229 this.domainFilterInput?.addEventListener('keyup', this.domainKeyEvent); 230 this.eventNameFilterInput?.addEventListener('keyup', this.eventNameKeyEvent); 231 this.contentFilterInput?.addEventListener('input', this.filterInputEvent); 232 this.levelFilter?.addEventListener('change', this.filterInputEvent); 233 this.domainTagDiv?.addEventListener('click', this.domainDivClickEvent); 234 this.eventNameTagDiv?.addEventListener('click', this.eventNameDivClickEvent); 235 this.changeInput?.addEventListener('input', this.changeInputEvent); 236 new ResizeObserver(() => { 237 this.tableElement!.style.height = `${this.parentElement!.clientHeight - 20 - 35}px`; 238 this.hiSysEventTable?.reMeauseHeight(); 239 this.detailsTbl!.style.height = `${this.parentElement!.clientHeight - 30}px`; 240 this.parentElement!.style.overflow = 'hidden'; 241 this.detailsTbl?.reMeauseHeight(); 242 this.updateData(); 243 this.tableTitleTimeHandle?.(); 244 }).observe(this.parentElement!); 245 let tbl = this.hiSysEventTable?.shadowRoot?.querySelector<HTMLDivElement>('.table'); 246 tbl!.addEventListener('scroll', () => { 247 this.tableTitleTimeHandle?.(); 248 }); 249 } 250 251 disconnectedCallback(): void { 252 super.disconnectedCallback(); 253 this.domainFilterInput?.removeEventListener('keyup', this.domainKeyEvent); 254 this.eventNameFilterInput?.removeEventListener('keyup', this.eventNameKeyEvent); 255 this.contentFilterInput?.removeEventListener('input', this.filterInputEvent); 256 this.levelFilter?.addEventListener('change', this.filterInputEvent); 257 this.changeInput?.addEventListener('input', this.changeInputEvent); 258 this.domainTagDiv?.removeEventListener('click', this.domainDivClickEvent); 259 this.eventNameTagDiv?.removeEventListener('click', this.eventNameDivClickEvent); 260 } 261 262 filterInputEvent = (): void => { 263 this.updateData(); 264 }; 265 266 domainDivClickEvent = (ev: Event): void => { 267 // @ts-ignore 268 let parentNode = ev.target.parentNode; 269 if (parentNode && this.domainTagDiv!.contains(parentNode)) { 270 this.domainTagDiv!.removeChild(parentNode); 271 this.domainTag['delete'](parentNode.textContent.trim().toLowerCase()); 272 } 273 this.updateData(); 274 }; 275 276 eventNameDivClickEvent = (ev: Event): void => { 277 // @ts-ignore 278 let parentNode = ev.target.parentNode; 279 if (parentNode && this.eventNameTagDiv!.contains(parentNode)) { 280 this.eventNameTagDiv!.removeChild(parentNode); 281 this.eventNameTag['delete'](parentNode.textContent.trim().toLowerCase()); 282 } 283 this.updateData(); 284 }; 285 286 domainKeyEvent = (e: KeyboardEvent): void => { 287 let domainValue = this.domainFilterInput!.value.trim(); 288 if (e.key === 'Enter') { 289 if (domainValue !== '' && !this.domainTag.has(domainValue.toLowerCase()) && this.domainTag.size < 10) { 290 let tagElement = this.buildTag(domainValue); 291 this.domainTag.add(domainValue.toLowerCase()); 292 this.domainTagDiv!.append(tagElement); 293 this.domainFilterInput!.value = ''; 294 } 295 } else if (e.key === 'Backspace') { 296 let index = this.domainTagDiv!.childNodes.length - 1; 297 if (index >= 0 && domainValue === '') { 298 let childNode = this.domainTagDiv!.childNodes[index]; 299 this.domainTagDiv!.removeChild(childNode); 300 this.domainTag['delete'](childNode.textContent!.trim().toLowerCase()); 301 } 302 } 303 this.updateData(); 304 }; 305 306 private buildTag(domainValue: string): HTMLDivElement { 307 let tagElement = document.createElement('div'); 308 tagElement.className = 'tagElement'; 309 tagElement.id = domainValue; 310 let tag = document.createElement('div'); 311 tag.className = 'tag'; 312 tag.innerHTML = domainValue; 313 let closeButton = document.createElement('lit-icon'); 314 closeButton.setAttribute('name', 'close-light'); 315 closeButton.style.color = '#FFFFFF'; 316 tagElement.append(tag); 317 tagElement.append(closeButton); 318 return tagElement; 319 } 320 321 eventNameKeyEvent = (e: KeyboardEvent): void => { 322 let eventNameValue = this.eventNameFilterInput!.value.trim(); 323 if (e.key === 'Enter') { 324 if (eventNameValue !== '' && !this.eventNameTag.has(eventNameValue.toLowerCase()) && this.eventNameTag.size < 10) { 325 let tagElement = this.buildTag(eventNameValue); 326 this.eventNameTag!.add(eventNameValue.toLowerCase()); 327 this.eventNameTagDiv!.append(tagElement); 328 this.eventNameFilterInput!.value = ''; 329 } 330 } else if (e.key === 'Backspace') { 331 let index = this.eventNameTagDiv!.childNodes.length - 1; 332 if (index >= 0 && eventNameValue === '') { 333 let childNode = this.eventNameTagDiv!.childNodes[index]; 334 this.eventNameTagDiv!.removeChild(childNode); 335 this.eventNameTag['delete'](childNode.textContent!.trim().toLowerCase()); 336 } 337 } 338 this.updateData(); 339 }; 340 341 updateData(): void { 342 if (this.hisysEventSource.length > 0) { 343 this.filterDataList = this.hisysEventSource.filter((data) => this.filterData(data)); 344 } 345 if (this.filterDataList.length > 0) { 346 this.hiSysEventTable!.recycleDataSource = this.filterDataList; 347 } else { 348 this.hiSysEventTable!.recycleDataSource = []; 349 } 350 this.refreshEventsTitle(); 351 } 352 353 filterData(data: HiSysEventStruct): boolean { 354 let level = this.levelFilter?.value; 355 let contentsValue = this.contentFilterInput?.value.toLowerCase() || ''; 356 contentsValue = contentsValue.replace(/\s/g, ''); 357 return ( 358 (level === 'ALL' || data.level! === level) && 359 (this.domainTag.size === 0 || this.domainTag.has(data.domain!.toLowerCase())) && 360 (this.eventNameTag.size === 0 || this.eventNameTag.has(data.eventName!.toLowerCase())) && 361 (contentsValue === '' || data.contents!.toLowerCase().replace(/\s/g, '').indexOf(contentsValue) >= 0) 362 ); 363 } 364 365 changeInputEvent = (): void => { 366 const changeValue = this.changeInput!.value; 367 const currentValue = changeValue; 368 if (!/^[0-9]*$/.test(changeValue) || isNaN(Number(currentValue))) { 369 this.changeInput!.value = ''; 370 this.updateDetail(this.baseTime); 371 } else { 372 this.updateDetail(this.changeInput!.value); 373 } 374 }; 375 376 updateDetail(baseTime: string): void { 377 const latencySuffix = '_LATENCY'; 378 let detailList: Array<{ key: string; value: string }> = []; 379 this.currentDetailList.forEach((item) => { 380 const latencyValue = item.key && item.key.endsWith(latencySuffix) ? item.value + Number(baseTime) : item.value; 381 detailList.push({ 382 key: item.key, 383 value: latencyValue, 384 }); 385 }); 386 this.detailsTbl!.recycleDataSource = detailList; 387 } 388 389 convertData = (data: HiSysEventStruct): void => { 390 this.baseTime = ''; 391 this.currentDetailList = [ 392 { 393 key: 'key', 394 value: 'value', 395 }, 396 ]; 397 const content = JSON.parse(data.contents ?? '{}'); 398 if (content && typeof content === 'object') { 399 let isFirstTime = true; 400 let keyList = Object.keys(content); 401 keyList.forEach((key) => { 402 const value: string = content[key]; 403 let contentValue = value; 404 if (key.endsWith('_TIME')) { 405 if (!isNaN(Number(value))) { 406 contentValue = ((Number(value) - this.realTime) * millisecond).toString(); 407 if (this.realTime < 0) { 408 contentValue = value; 409 } 410 if (isFirstTime) { 411 this.baseTime = contentValue; 412 isFirstTime = false; 413 } 414 } 415 if (key === 'INPUT_TIME') { 416 this.baseTime = contentValue; 417 isFirstTime = false; 418 } 419 } 420 this.currentDetailList.push({ 421 key: key, 422 value: contentValue, 423 }); 424 }); 425 } 426 this.changeInput!.value = `${this.baseTime}`; 427 this.detailsTbl!.recycleDataSource = this.currentDetailList; 428 this.slicerTrack!.style.visibility = 'visible'; 429 this.detailsTbl!.style.paddingLeft = '20px'; 430 this.boxDetails!.style.width = '65%'; 431 this.detailbox!.style.display = 'block'; 432 }; 433 434 sortByColumn(framesDetail: { sort: number; key: string }): void { 435 let compare = function (property: string, sort: number, type: string) { 436 return function (eventLeftData: HiSysEventStruct, eventRightData: HiSysEventStruct): number { 437 let firstSortNumber: number = -1; 438 let SecondSortNumber: number = 1; 439 let thirdSortNumber: number = 2; 440 // @ts-ignore 441 let rightEventData = eventRightData[property]; 442 // @ts-ignore 443 let leftEventData = eventLeftData[property]; 444 if (type === 'number') { 445 return sort === thirdSortNumber 446 ? parseFloat(rightEventData) - parseFloat(leftEventData) 447 : parseFloat(leftEventData) - parseFloat(rightEventData); 448 } else { 449 if (rightEventData > leftEventData) { 450 return sort === thirdSortNumber ? SecondSortNumber : firstSortNumber; 451 } else { 452 if (rightEventData === leftEventData) { 453 return 0; 454 } else { 455 return sort === thirdSortNumber ? firstSortNumber : SecondSortNumber; 456 } 457 } 458 } 459 }; 460 }; 461 this.hisysEventSource.sort(compare(framesDetail.key, framesDetail.sort, 'number')); 462 this.hiSysEventTable!.recycleDataSource = this.hisysEventSource; 463 } 464 465 drawFlag(value: number, color: string): void { 466 let pointX: number = ns2x( 467 value || 0, 468 TraceRow.range!.startNS, 469 TraceRow.range!.endNS, 470 TraceRow.range!.totalNS, 471 new Rect(0, 0, TraceRow.FRAME_WIDTH, 0) 472 ); 473 this.traceSheetEl!.systemLogFlag = new Flag(Math.floor(pointX), 0, 0, 0, value!, color, '', true, ''); 474 this.spSystemTrace?.refreshCanvas(false); 475 } 476} 477 478const millisecond = 1000_000; 479