1/* 2 * Copyright (C) 2023 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 { LitButton } from '../../../../../base-ui/button/LitButton'; 18import { LitTable, RedrawTreeForm } from '../../../../../base-ui/table/lit-table'; 19import { SelectionData, SelectionParam } from '../../../../bean/BoxSelection'; 20import { 21 querySchedThreadStates, 22 querySingleCutData, 23 queryLoopCutData, 24} from '../../../../database/sql/ProcessThread.sql'; 25import { Utils } from '../../base/Utils'; 26import { resizeObserver } from '../SheetUtils'; 27import { LitChartColumn } from '../../../../../base-ui/chart/column/LitChartColumn'; 28import { SpSegmentationChart } from '../../../../../trace/component/chart/SpSegmentationChart'; 29import { 30 type TreeSwitchConfig, 31 HistogramSourceConfig, 32 ThreadInitConfig, 33 CutDataObjConfig, 34 SchedSwitchCountBean, 35} from '../../../../bean/SchedSwitchStruct'; 36const UNIT: number = 1000000.0; 37const NUM_DIGITS: number = 3; 38const SINGLE_BUTTON_TEXT: string = 'Single'; 39const LOOP_BUTTON_TEXT: string = 'Loop'; 40@element('tabpane-schedswitch') 41export class TabPaneSchedSwitch extends BaseElement { 42 private schedSwitchTbl: LitTable | null | undefined; 43 private threadQueryDIV: Element | null | undefined; 44 private rightDIV: HTMLDivElement | null | undefined; 45 private threadIdInput: HTMLInputElement | null | undefined; 46 private funcNameInput: HTMLInputElement | null | undefined; 47 private singleBtn: LitButton | null | undefined; 48 private loopBtn: LitButton | null | undefined; 49 private cycleALeftInput: HTMLInputElement | null | undefined; 50 private cycleARightInput: HTMLInputElement | null | undefined; 51 private cycleBLeftInput: HTMLInputElement | null | undefined; 52 private cycleBRightInput: HTMLInputElement | null | undefined; 53 private queryButton: LitButton | null | undefined; 54 private selectionParam: SelectionParam | undefined; 55 private canvansName: HTMLDivElement | null | undefined; 56 private threadMap: Map<string, Array<ThreadInitConfig>> = new Map<string, Array<ThreadInitConfig>>(); 57 private chartTotal: LitChartColumn | null | undefined; 58 private histogramSource: Array<HistogramSourceConfig> = []; 59 private rangeA: HistogramSourceConfig = new HistogramSourceConfig(); 60 private rangeB: HistogramSourceConfig = new HistogramSourceConfig(); 61 private rangeTotal: HistogramSourceConfig = new HistogramSourceConfig(); 62 private getThreadChildren: Array<TreeSwitchConfig> = []; 63 private hasThreadStatesData: boolean = false; 64 private clickThreadName: string = ''; 65 66 set data(threadStatesParam: SelectionParam) { 67 if (this.selectionParam === threadStatesParam) { 68 return; 69 } 70 let tabpaneSwitch = this.parentElement as HTMLElement; 71 tabpaneSwitch.style.overflow = 'hidden'; 72 this.schedSwitchTbl!.recycleDataSource = []; 73 this.queryButton!.style.pointerEvents = 'none'; 74 this.schedSwitchTbl!.loading = false; 75 this.hasThreadStatesData = false; 76 // @ts-ignore 77 this.schedSwitchTbl!.value = []; 78 this.funcNameInput!.style.border = '1px solid rgb(151,151,151)'; 79 this.threadIdInput!.style.border = '1px solid rgb(151,151,151)'; 80 SpSegmentationChart.setChartData('SCHED-SWITCH', []); 81 SpSegmentationChart.tabHover('SCHED-SWITCH', false, -1); 82 this.canvansName!.textContent = 'sched switch平均分布图'; 83 this.selectionParam = threadStatesParam; 84 this.canQueryButtonClick(false); 85 this.isSingleBtnColor(false); 86 this.isLoopBtnColor(false); 87 this.isCanvansHidden(true); 88 //当Tab页发生变化时,ResizeObserver会被通知并且检测到Tab页布局和大小的变化,可以按需求调整页面UI 89 new ResizeObserver((entries) => { 90 // @ts-ignore 91 let lastHeight = this.schedSwitchTbl!.tableElement!.offsetHeight; 92 //控制右侧区域高度实时变化与左侧区域Div保持一致,避免滚动滚动条时出现空白的情况 93 this.rightDIV!.style.height = String(lastHeight) + 'px'; 94 }).observe(this.schedSwitchTbl!); 95 } 96 initElements(): void { 97 this.schedSwitchTbl = this.shadowRoot!.querySelector<LitTable>('#tb-running'); 98 this.threadQueryDIV = this.shadowRoot?.querySelector('#data-cut'); 99 this.rightDIV = this.shadowRoot?.querySelector('#right'); 100 this.canvansName = this.shadowRoot!.querySelector<HTMLDivElement>('.sched-subheading'); 101 this.chartTotal = this.shadowRoot!.querySelector<LitChartColumn>('#chart_total'); 102 this.threadIdInput = this.shadowRoot?.getElementById('cut-threadid') as HTMLInputElement; 103 this.funcNameInput = this.shadowRoot?.querySelector('#cut-thread-func') as HTMLInputElement; 104 this.singleBtn = this.shadowRoot?.querySelector<LitButton>('.single-btn'); 105 this.loopBtn = this.shadowRoot?.querySelector<LitButton>('.loop-btn'); 106 this.cycleALeftInput = this.shadowRoot?.getElementById('leftA') as HTMLInputElement; 107 this.cycleARightInput = this.shadowRoot?.getElementById('rightA') as HTMLInputElement; 108 this.cycleBLeftInput = this.shadowRoot?.getElementById('leftB') as HTMLInputElement; 109 this.cycleBRightInput = this.shadowRoot?.getElementById('rightB') as HTMLInputElement; 110 this.queryButton = this.shadowRoot?.querySelector<LitButton>('.query-btn'); 111 this.singleBtn?.addEventListener('click', (e) => { 112 this.queryCutInfoFn(this.singleBtn!.innerHTML); 113 }); 114 this.loopBtn?.addEventListener('click', (e) => { 115 this.queryCutInfoFn(this.loopBtn!.innerHTML); 116 }); 117 this.queryButton!.addEventListener('click', (e) => { 118 this.queryCycleRangeData(); 119 }); 120 this.threadIdInput!.addEventListener('keyup', (ev) => { 121 if (ev.key.toLocaleLowerCase() === String.fromCharCode(47)) { 122 ev.stopPropagation(); 123 } 124 }); 125 this.funcNameInput!.addEventListener('keyup', (ev) => { 126 if (ev.key.toLocaleLowerCase() === String.fromCharCode(47)) { 127 ev.stopPropagation(); 128 } 129 }); 130 this.schedSwitchTbl!.addEventListener('row-click', (evt) => { 131 this.clickTreeRowEvent(evt); 132 }); 133 this.listenInputEvent(); 134 } 135 //监听周期A、B对应输入框的值 136 listenInputEvent(): void { 137 this.cycleALeftInput!.addEventListener('input', (evt) => { 138 this.checkInputRangeFn( 139 this.cycleALeftInput, 140 this.cycleARightInput, 141 this.cycleBLeftInput, 142 this.cycleBRightInput, 143 this.cycleALeftInput!.value, 144 this.cycleARightInput!.value 145 ); 146 }); 147 this.cycleARightInput!.addEventListener('input', (evt) => { 148 this.checkInputRangeFn( 149 this.cycleARightInput, 150 this.cycleALeftInput, 151 this.cycleBLeftInput, 152 this.cycleBRightInput, 153 this.cycleALeftInput!.value, 154 this.cycleARightInput!.value 155 ); 156 }); 157 this.cycleBLeftInput!.addEventListener('input', (evt) => { 158 this.checkInputRangeFn( 159 this.cycleBLeftInput, 160 this.cycleBRightInput, 161 this.cycleALeftInput, 162 this.cycleARightInput, 163 this.cycleBLeftInput!.value, 164 this.cycleBRightInput!.value 165 ); 166 }); 167 this.cycleBRightInput!.addEventListener('input', (evt) => { 168 this.checkInputRangeFn( 169 this.cycleBRightInput, 170 this.cycleBLeftInput, 171 this.cycleALeftInput, 172 this.cycleARightInput, 173 this.cycleBLeftInput!.value, 174 this.cycleBRightInput!.value 175 ); 176 }); 177 } 178 //校验周期A、B对应输入框的值是否符合要求,不符合时给出相应提示 179 checkInputRangeFn( 180 firstInput: HTMLInputElement | null | undefined, 181 secondInput: HTMLInputElement | null | undefined, 182 thirdInput: HTMLInputElement | null | undefined, 183 fourInput: HTMLInputElement | null | undefined, 184 lVal: string, 185 rVal: string 186 ): void { 187 let leftVal: number = Number(lVal); 188 let rightVal: number = Number(rVal); 189 if (firstInput!.value !== '' && secondInput!.value !== '') { 190 if (firstInput!.value !== '' && secondInput!.value !== '') { 191 if (leftVal >= rightVal) { 192 firstInput!.style.color = 'red'; 193 this.queryButton!.style.pointerEvents = 'none'; 194 this.canQueryButtonClick(false); 195 } else if (leftVal < rightVal) { 196 firstInput!.style.color = 'black'; 197 secondInput!.style.color = 'black'; 198 this.queryButton!.style.pointerEvents = 'auto'; 199 this.canQueryButtonClick(true); 200 } 201 } 202 } else if ( 203 (firstInput!.value === '' && secondInput!.value !== '') || 204 (firstInput!.value !== '' && secondInput!.value === '') 205 ) { 206 this.queryButton!.style.pointerEvents = 'none'; 207 this.canQueryButtonClick(false); 208 } else if ( 209 firstInput!.value === '' && 210 secondInput!.value === '' && 211 thirdInput!.value !== '' && 212 fourInput!.value !== '' 213 ) { 214 this.queryButton!.style.pointerEvents = 'auto'; 215 this.canQueryButtonClick(true); 216 } 217 if ( 218 (thirdInput!.value === '' && fourInput!.value !== '') || 219 (thirdInput!.value !== '' && fourInput!.value === '') 220 ) { 221 this.queryButton!.style.pointerEvents = 'none'; 222 this.canQueryButtonClick(false); 223 } 224 } 225 //点击树节点不同层级触发相应的功能 226 clickTreeRowEvent(evt: Event): void { 227 //@ts-ignore 228 let data = evt.detail.data; 229 if (data.level === 'process') { 230 //点击树节点时节点高亮 231 data.isSelected = true; 232 this.schedSwitchTbl!.clearAllSelection(data); 233 this.schedSwitchTbl!.setCurrentSelection(data); 234 //点击进程canvans相关内容隐藏 235 this.isCanvansHidden(true); 236 SpSegmentationChart.setChartData('SCHED-SWITCH', []); 237 SpSegmentationChart.tabHover('SCHED-SWITCH', false, -1); 238 this.clickThreadName = data.nodeFlag; 239 } else if (data.level === 'thread') { 240 //点击线程绘制canvans,相同线程canvans不会重新绘制,切换线程时清空周期输入框等相关内容 241 if (this.clickThreadName !== data.nodeFlag) { 242 this.cycleALeftInput!.value = ''; 243 this.cycleARightInput!.value = ''; 244 this.cycleBLeftInput!.value = ''; 245 this.cycleBRightInput!.value = ''; 246 this.histogramSource = []; 247 SpSegmentationChart.tabHover('SCHED-SWITCH', false, -1); //清空上一次的泳道高亮 248 this.getThreadChildren = data.children; 249 this.queryButton!.style.pointerEvents = 'none'; 250 this.isCanvansHidden(false); 251 this.canQueryButtonClick(false); 252 this.clickThreadName = data.nodeFlag; 253 //绘制canvans柱状图需要的数据 254 this.rangeTotal = { 255 value: data.value, 256 cycleNum: data.children.length, 257 average: data.children.length ? Math.ceil(data.value / data.children.length) : 0, 258 size: 'Total', 259 isHover: false, 260 color: '#2f72f8', 261 }; 262 this.histogramSource.push(this.rangeTotal); 263 //清空高亮的树节点 264 this.schedSwitchTbl!.clearAllSelection(data); 265 this.drawHistogramChart(); 266 } 267 //点击树节点时高亮 268 data.isSelected = true; 269 this.schedSwitchTbl!.setCurrentSelection(data); 270 //点击线程绘制对应泳道图 271 SpSegmentationChart.setChartData('SCHED-SWITCH', data.children); 272 } else if (data.level === 'cycle') { 273 if (this.clickThreadName === data.nodeFlag) { 274 data.isSelected = true; 275 this.schedSwitchTbl!.clearAllSelection(data); 276 this.schedSwitchTbl!.setCurrentSelection(data); 277 SpSegmentationChart.tabHover('SCHED-SWITCH', true, data!.cycle); 278 } 279 } 280 } 281 //点击single或loop按钮时触发 282 async queryCutInfoFn(btnHtml: string): Promise<void> { 283 let funcData: Array<ThreadInitConfig> = []; 284 let threadId: string = this.threadIdInput!.value.trim(); 285 let threadFunName: string = this.funcNameInput!.value.trim(); 286 let leftStartNs: number = this.selectionParam!.leftNs + this.selectionParam!.recordStartNs; 287 let rightEndNs: number = this.selectionParam!.rightNs + this.selectionParam!.recordStartNs; 288 if (threadId !== '' && threadFunName !== '') { 289 this.threadIdInput!.style.border = '1px solid rgb(151,151,151)'; 290 this.funcNameInput!.style.border = '1px solid rgb(151,151,151)'; 291 //@ts-ignore 292 this.schedSwitchTbl!.value = []; 293 this.isCanvansHidden(true); 294 this.schedSwitchTbl!.loading = true; 295 this.clickThreadName = ''; 296 SpSegmentationChart.setChartData('SCHED-SWITCH', []); 297 SpSegmentationChart.tabHover('SCHED-SWITCH', false, -1); 298 //首次点击single或loop时去查询数据 299 if (!this.hasThreadStatesData) { 300 this.initThreadStateData(this.selectionParam); 301 } 302 if (btnHtml === SINGLE_BUTTON_TEXT) { 303 this.isSingleBtnColor(true); 304 this.isLoopBtnColor(false); //@ts-ignore 305 funcData = await querySingleCutData(threadFunName, threadId, leftStartNs, rightEndNs); 306 } 307 if (btnHtml === LOOP_BUTTON_TEXT) { 308 this.isSingleBtnColor(false); 309 this.isLoopBtnColor(true); //@ts-ignore 310 funcData = await queryLoopCutData(threadFunName, threadId, leftStartNs, rightEndNs); 311 } 312 //获取到线程数据和方法数据,处理周期 313 this.handleCycleLogic(funcData, btnHtml); 314 } else { 315 if (threadId === '') { 316 this.threadIdInput!.style.border = '2px solid rgb(255,0,0)'; 317 this.threadIdInput!.setAttribute('placeholder', 'Please input thread id'); 318 } else { 319 this.threadIdInput!.style.border = '1px solid rgb(151,151,151)'; 320 } 321 if (threadFunName === '') { 322 this.funcNameInput!.style.border = '2px solid rgb(255,0,0)'; 323 this.funcNameInput!.setAttribute('placeholder', 'Please input function name'); 324 } else { 325 this.funcNameInput!.style.border = '1px solid rgb(151,151,151)'; 326 } 327 } 328 } 329 330 //获取每次被框选线程对应的state数据 331 async initThreadStateData(threadParam: SelectionParam | null | undefined): Promise<void> { 332 let threadSourceData: Array<ThreadInitConfig> = []; 333 let leftStartNs: number = threadParam!.leftNs + threadParam!.recordStartNs; 334 let rightEndNs: number = threadParam!.rightNs + threadParam!.recordStartNs; 335 let processIds: Array<number> = [...new Set(threadParam!.processIds)]; //@ts-ignore 336 let res: Array<ThreadInitConfig> = await querySchedThreadStates( 337 processIds, 338 threadParam!.threadIds, 339 leftStartNs, 340 rightEndNs 341 ); 342 threadSourceData = JSON.parse(JSON.stringify(res)); 343 //每次新款选线程时清空Map对象 344 this.threadMap.clear(); 345 //state数据转换成以pid+tid为key值的Map对象 346 for (let i = 0; i < threadSourceData.length; i++) { 347 let stateItem = threadSourceData[i]; 348 if (this.threadMap.has(`${stateItem.pid} - ${stateItem.tid}`)) { 349 let obj = this.threadMap.get(`${stateItem.pid} - ${stateItem.tid}`); 350 obj!.push(stateItem); 351 } else { 352 this.threadMap.set(`${stateItem.pid} - ${stateItem.tid}`, [stateItem]); 353 } 354 } 355 this.hasThreadStatesData = true; 356 } 357 358 //线程下周期的处理逻辑 359 handleCycleLogic(res: Array<ThreadInitConfig>, btnHtml: string): void { 360 //处理sql查询数据为0条,或者当loop切割获取的数据时1条 361 if (res.length === 0 || this.threadMap.size === 0 || (btnHtml === LOOP_BUTTON_TEXT && res.length === 1)) { 362 this.schedSwitchTbl!.recycleDataSource = []; 363 this.schedSwitchTbl!.loading = false; // @ts-ignore 364 this.clickTableLabel(this.schedSwitchTbl!.recycleDataSource); 365 return; 366 } 367 let group: { [key: number]: SchedSwitchCountBean } = {}; 368 for (let value of this.threadMap.values()) { 369 let cyclesArr: Array<SchedSwitchCountBean> = []; 370 let processInfo: string | undefined = Utils.getInstance().getProcessMap().get(value[0].pid); 371 let process: string | undefined = processInfo === null || processInfo!.length === 0 ? '[NULL]' : processInfo; 372 let threadInfo: string | undefined = Utils.getInstance().getThreadMap().get(value[0].tid); 373 let thread: string | undefined = threadInfo === null || threadInfo!.length === 0 ? '[NULL]' : threadInfo; 374 //整理出一个对象并将周期空数组放进对象里 375 let cutDataObj: CutDataObjConfig = { 376 cyclesArr: cyclesArr, 377 pid: value[0].pid, 378 tid: value[0].tid, 379 process: process, 380 thread: thread, 381 processTitle: `${process}[${value[0].pid}]`, 382 threadTitle: `${thread}[${[value[0].tid]}]`, 383 threadCountTotal: 0, 384 threadDurTotal: 0, 385 }; 386 //此处根据切割方法不同处理一下方法循环长度 387 for (let idx = 0; idx < (btnHtml === LOOP_BUTTON_TEXT ? res.length - 1 : res.length); idx++) { 388 if (btnHtml === LOOP_BUTTON_TEXT) { 389 res[idx].cycleEndTime = res[idx + 1].cycleStartTime; 390 } //当切割方法为loop时,处理出周期结束时间 391 let duration = ((res[idx].cycleEndTime - res[idx].cycleStartTime) / UNIT).toFixed(NUM_DIGITS); 392 let dur = Number(duration) * UNIT; 393 value.map((item: ThreadInitConfig) => { 394 //当idx变化时添加周期 395 if (cyclesArr.length !== idx + 1) { 396 let nodeFlag = `${process} - ${item.pid} - ${thread} - ${item.tid}`; 397 let startNS = res[idx].cycleStartTime - this.selectionParam!.recordStartNs; 398 let cycleStartTime = (startNS / UNIT).toFixed(NUM_DIGITS); 399 let title = `cycle ${idx + 1}-` + thread; //周期名称 400 cyclesArr.push( 401 new SchedSwitchCountBean( 402 nodeFlag, 403 startNS, 404 cycleStartTime, 405 dur, 406 duration, 407 idx + 1, 408 title, 409 0, 410 'cycle', 411 9, 412 [] 413 ) 414 ); 415 cutDataObj!.threadDurTotal = (Number(duration) + Number(cutDataObj.threadDurTotal)).toFixed(NUM_DIGITS); //本次线程下所有周期的dur和 416 } 417 //判断数据是否符合这个周期,符合的进入判断累加count 418 if (res[idx].cycleEndTime > item.endTs && item.endTs > res[idx].cycleStartTime) { 419 let index = cyclesArr.length - 1; 420 if (index === idx) { 421 cyclesArr[index].value += 1; 422 } 423 cutDataObj.threadCountTotal += 1; 424 } 425 }); 426 } 427 //本轮线程处理过的数据传入并处理成树结构,放入group对象中 428 this.translateIntoTree(cutDataObj, group); 429 } 430 this.schedSwitchTbl!.recycleDataSource = Object.values(group); 431 this.schedSwitchTbl!.loading = false; // @ts-ignore 432 this.clickTableLabel(this.schedSwitchTbl!.recycleDataSource); 433 } 434 //根据处理好的单个线程对应的周期数据、count总数、dur总数以及所属进程、线程相关信息,转换成树结构数据 435 translateIntoTree(data: CutDataObjConfig, group: { [key: number]: SchedSwitchCountBean }): void { 436 //给进程节点、线程节点添加节点标志 437 let nodeFlag = `${data.process} - ${data.pid} - ${data.thread} - ${data.tid}`; 438 //将线程放到对应的进程节点下 439 let dur = Number(data.threadDurTotal) * UNIT; 440 if (group[data.pid]) { 441 let process = group[data.pid]; 442 process.value += data.threadCountTotal; 443 process.duration = (Number(data.threadDurTotal) + Number(process.duration)).toFixed(NUM_DIGITS); 444 process.children.push( 445 new SchedSwitchCountBean( 446 nodeFlag, 447 -1, 448 '', 449 dur, 450 data.threadDurTotal, 451 -1, 452 data.threadTitle, 453 data.threadCountTotal, 454 'thread', 455 9, 456 data.cyclesArr 457 ) 458 ); 459 } else { 460 group[data.pid] = new SchedSwitchCountBean( 461 '', 462 -1, 463 '', 464 dur, 465 data.threadDurTotal, 466 -1, 467 data.processTitle, 468 data.threadCountTotal, 469 'process', 470 9, 471 [ 472 new SchedSwitchCountBean( 473 nodeFlag, 474 -1, 475 '', 476 dur, 477 data.threadDurTotal, 478 -1, 479 data.threadTitle, 480 data.threadCountTotal, 481 'thread', 482 9, 483 data.cyclesArr 484 ), 485 ] 486 ); 487 } 488 } 489 //点击表格标签,判断点击的是那个标签,进而展开或收缩对应的节点 490 clickTableLabel(data: Array<TreeSwitchConfig>): void { 491 let labelList = this.schedSwitchTbl!.shadowRoot?.querySelector('.th > .td')!.querySelectorAll('label'); 492 if (labelList) { 493 for (let i = 0; i < labelList.length; i++) { 494 let lable = labelList[i].innerHTML; 495 labelList[i].addEventListener('click', (e) => { 496 if (lable.includes('Process')) { 497 this.schedSwitchTbl!.setStatus(data, false); 498 this.schedSwitchTbl!.recycleDs = this.schedSwitchTbl!.meauseTreeRowElement(data, RedrawTreeForm.Retract); 499 } else if (lable.includes('Thread')) { 500 for (let item of data) { 501 item.status = true; 502 if (item.children !== undefined && item.children.length > 0) { 503 this.schedSwitchTbl!.setStatus(item.children, false); 504 } 505 } 506 this.schedSwitchTbl!.recycleDs = this.schedSwitchTbl!.meauseTreeRowElement(data, RedrawTreeForm.Retract); 507 } else if (lable.includes('Cycle')) { 508 this.schedSwitchTbl!.setStatus(data, true); 509 this.schedSwitchTbl!.recycleDs = this.schedSwitchTbl!.meauseTreeRowElement(data, RedrawTreeForm.Expand); 510 } 511 }); 512 } 513 } 514 } 515 //根据A、B周期输入的dur的范围值,计算对应周期的数据 516 queryCycleRangeData(): void { 517 let cycleALeft: string = this.cycleALeftInput!.value.trim(); 518 let cycleARight: string = this.cycleARightInput!.value.trim(); 519 let cycleBLeft: string = this.cycleBLeftInput!.value.trim(); 520 let cycleBRight: string = this.cycleBRightInput!.value.trim(); 521 this.histogramSource = []; 522 this.histogramSource.push(this.rangeTotal); 523 //周期A处理 524 if (cycleALeft !== '' && cycleARight !== '' && cycleALeft !== cycleARight) { 525 let countA: number = 0; 526 let rangeFilterA: Array<TreeSwitchConfig> = this.getThreadChildren.filter( 527 (it: TreeSwitchConfig) => Number(it.duration) >= Number(cycleALeft) && Number(it.duration) < Number(cycleARight) 528 ); 529 rangeFilterA.forEach((item: TreeSwitchConfig) => { 530 countA += item.value; 531 }); 532 this.rangeA = { 533 value: countA, 534 cycleNum: rangeFilterA.length, 535 average: rangeFilterA.length ? Math.ceil(countA / rangeFilterA.length) : 0, 536 size: 'Cycle A', 537 isHover: false, 538 color: '#ffab67', 539 }; 540 this.histogramSource.push(this.rangeA); 541 } 542 //周期B处理 543 if (cycleBLeft !== '' && cycleBRight !== '' && cycleBLeft !== cycleBRight) { 544 let countB: number = 0; 545 let rangeFilterB: Array<TreeSwitchConfig> = this.getThreadChildren.filter( 546 (it: TreeSwitchConfig) => Number(it.duration) >= Number(cycleBLeft) && Number(it.duration) < Number(cycleBRight) 547 ); 548 rangeFilterB.forEach((item: TreeSwitchConfig) => { 549 countB += item.value; 550 }); 551 this.rangeB = { 552 value: countB, 553 cycleNum: rangeFilterB.length, 554 average: rangeFilterB.length ? Math.ceil(countB / rangeFilterB.length) : 0, 555 size: 'Cycle B', 556 isHover: false, 557 color: '#a285d2', 558 }; 559 this.histogramSource.push(this.rangeB); 560 } 561 this.drawHistogramChart(); 562 } 563 //绘制柱状图 564 drawHistogramChart(): void { 565 let source = []; 566 source = this.histogramSource.map((it: HistogramSourceConfig, index: number) => { 567 let data = { 568 cycle: it.size, 569 average: it.average, 570 visible: 1, 571 color: it.color, 572 }; 573 return data; 574 }); 575 this.chartTotal!.config = { 576 data: source, //画柱状图的数据源 577 appendPadding: 10, 578 xField: 'cycle', //x轴代表属于那个周期 579 yField: 'average', //y轴代表count/周期个数的值 580 notSort: true, //绘制的柱状图不排序 581 removeUnit: true, //移除单位换算 582 seriesField: '', 583 //设置柱状图的颜色 584 color(a): string { 585 //@ts-ignore 586 if (a.cycle === 'Total') { 587 return '#2f72f8'; //@ts-ignore 588 } else if (a.cycle === 'Cycle A') { 589 return '#ffab67'; //@ts-ignore 590 } else if (a.cycle === 'Cycle B') { 591 return '#a285d2'; 592 } else { 593 return '#0a59f7'; 594 } 595 }, 596 //鼠标悬浮柱状图上方时显示对应的提示信息 597 tip(a): string { 598 //@ts-ignore 599 if (a && a[0]) { 600 let tip = ''; //@ts-ignore 601 for (let obj of a) { 602 tip = `${tip} 603 <div>Average count:${obj.obj.average}</div> 604 `; 605 } 606 return tip; 607 } else { 608 return ''; 609 } 610 }, 611 label: null, 612 }; 613 } 614 //canvans涉及的区域是否被隐藏 615 isCanvansHidden(flag: boolean): void { 616 if (flag) { 617 this.setAttribute('canvans-hidden', ''); 618 } else { 619 this.removeAttribute('canvans-hidden'); 620 } 621 } 622 //切换single按钮时颜色是否变化 623 isSingleBtnColor(flag: boolean): void { 624 if (flag) { 625 this.setAttribute('single', ''); 626 } else { 627 this.removeAttribute('single'); 628 } 629 } 630 //切换single按钮时颜色是否变化 631 isLoopBtnColor(flag: boolean): void { 632 if (flag) { 633 this.setAttribute('loop', ''); 634 } else { 635 this.removeAttribute('loop'); 636 } 637 } 638 //Query按钮能不能被点击,即在此处设置符合条件时鼠标箭头为手的样式表示可点击,反之表示禁止触发点击事件 639 canQueryButtonClick(flag: boolean): void { 640 if (flag) { 641 this.setAttribute('query-button', ''); 642 } else { 643 this.removeAttribute('query-button'); 644 } 645 } 646 //回调函数,this.schedSwitchTbl首次插入DOM时执行的初始化回调 647 connectedCallback(): void { 648 super.connectedCallback(); 649 resizeObserver(this.parentElement!, this.schedSwitchTbl!); 650 } 651 initHtml(): string { 652 return ( 653 ` 654 <style> 655 :host{ 656 padding: 10px 10px; 657 display: flex; 658 flex-direction: column; 659 } 660 #data-cut{ 661 display: flex; 662 justify-content: space-between; 663 width:100%; 664 height:20px; 665 margin-bottom:2px; 666 align-items:center; 667 } 668 button{ 669 width:40%; 670 height:100%; 671 border: solid 1px #666666; 672 background-color: rgba(0,0,0,0); 673 border-radius:10px; 674 } 675 #content-section{ 676 display: flex; 677 width: 100%; 678 } 679 #query-section{ 680 height: 78px; 681 display: flex; 682 flex-direction: column; 683 justify-content: space-evenly; 684 padding: 20px 0px 10px 20px; 685 } 686 .sched-subheading{ 687 font-weight: bold; 688 text-align: center; 689 } 690 .labels{ 691 display: flex; 692 flex-direction: row; 693 align-items: center; 694 justify-content: center; 695 font-size: 9pt; 696 padding-right: 15px; 697 } 698 ` + 699 this.initStyleContent() + 700 this.initTopContent() + 701 this.initMainContent() 702 ); 703 } 704 initStyleContent(): string { 705 return ` 706 #right { 707 padding-right: 10px; 708 flex-grow: 1; 709 overflow: auto; 710 } 711 .range-input { 712 width: 120px; 713 height: 18px; 714 border-radius:10px; 715 border:solid 1px #979797; 716 font-size:15px; 717 text-indent:3%; 718 } 719 #cut-threadid { 720 width: 15%; 721 height:90%; 722 border-radius:10px; 723 border:solid 1px #979797; 724 font-size:15px; 725 text-indent:3% 726 } 727 #cut-thread-func { 728 width: 20%; 729 height:90%; 730 border-radius:10px; 731 border:solid 1px #979797; 732 font-size:15px; 733 text-indent:3% 734 } 735 .hint-label { 736 width: 20px; 737 height: 10px; 738 margin-right: 5px 739 } 740 .cycle-title { 741 display: inline-block; 742 width: 61px; 743 height: 100%; 744 } 745 .range { 746 display:flex; 747 align-items: center; 748 } 749 `; 750 } 751 initTopContent(): string { 752 return ` 753 .query-btn{ 754 height: 20px; 755 width: 90px; 756 border: solid 1px #666666; 757 background-color: rgba(0,0,0,0); 758 border-radius:10px; 759 } 760 button:hover{ 761 background-color:#666666; 762 color:white; 763 } 764 :host([canvans-hidden]) #right { 765 display: none 766 } 767 :host([single]) .single-btn { 768 background-color: #666666; 769 color: white 770 } 771 :host([loop]) .loop-btn { 772 background-color: #666666; 773 color: white 774 } 775 :host([query-button]) .query-btn:hover { 776 cursor: pointer; 777 } 778 </style> 779 <div id='data-cut'> 780 <input id="cut-threadid" type="text" placeholder="Please input threadId" value='' onblur="this.value=this.value.replace(/\\D/g,'')"/> 781 <input id="cut-thread-func" type="text" placeholder="Please input funcName" value='' /> 782 <div style="width:20%;height: 100%;display:flex;justify-content: space-around;"> 783 <button class="single-btn cut-button">Single</button> 784 <button class="loop-btn cut-button">Loop</button> 785 </div> 786 </div> 787 `; 788 } 789 initMainContent(): string { 790 return ` 791 <div id="content-section"> 792 <div style="height: auto; width: 60%; overflow: auto"> 793 <lit-table id="tb-running" style="min-height: 380px; width: 100%" tree> 794 <lit-table-column class="running-percent-column" width="300px" title="Process/Thread/Cycle" data-index="title" key="title" align="flex-start" width="27%" retract> 795 </lit-table-column> 796 <lit-table-column class="running-percent-column" width="160px" title="Cycle start time(ms)" data-index="cycleStartTime" key="cycleStartTime" align="flex-start"> 797 </lit-table-column> 798 <lit-table-column class="running-percent-column" width="130px" title="duration(ms)" data-index="duration" key="duration" align="flex-start"> 799 </lit-table-column> 800 <lit-table-column class="running-percent-column" width="1fr" title="value" data-index="value" key="value" align="flex-start"> 801 </lit-table-column> 802 </lit-table> 803 </div> 804 <lit-slicer-track ></lit-slicer-track> 805 <div id="right"> 806 <div id="query-section"> 807 <div> 808 <span class="cycle-title">Cycle A:</span> 809 <input id="leftA" type="text" class="range-input" value='' oninput="this.value=this.value.replace(/[^0-9\.]/g,'')" placeholder="duration(ms)"/> 810 <span>~</span> 811 <input id="rightA" type="text" class="range-input" value='' oninput="this.value=this.value.replace(/[^0-9\.]/g,'')" placeholder="duration(ms)"/> 812 </div> 813 <div style="display: flex; justify-content: space-between"> 814 <div> 815 <span class="cycle-title">Cycle B:</span> 816 <input id="leftB" type="text" class="range-input" value='' oninput="this.value=this.value.replace(/[^0-9\.]/g,'')" placeholder="duration(ms)"/> 817 <span>~</span> 818 <input id="rightB" type="text" class="range-input" value='' oninput="this.value=this.value.replace(/[^0-9\.]/g,'')" placeholder="duration(ms)"/> 819 </div> 820 <button class="query-btn">Query</button> 821 </div> 822 </div> 823 <div class="sched-subheading"></div> 824 <lit-chart-column id="chart_total" style="width:100%;height:300px"></lit-chart-column> 825 <div style="height: 30px;width: 100%;display: flex;flex-direction: row;align-items: center;justify-content: center"> 826 <div class="labels"><div class="hint-label" style="background-color: #2f72f8"></div>Total</div> 827 <div class="labels"><div class="hint-label" style="background-color: #ffab67"></div>Cycle A</div> 828 <div class="labels"><div class="hint-label" style="background-color: #a285d2"></div>Cycle B</div> 829 </div> 830 </div> 831 </div> 832 `; 833 } 834} 835