• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 { LitTable } from '../../../../../base-ui/table/lit-table';
18import { HeapDataInterface } from '../../../../../js-heap/HeapDataInterface';
19import {
20  ConstructorComparison,
21  ConstructorItem,
22  ConstructorType
23} from '../../../../../js-heap/model/UiStruct';
24import '../../../../../base-ui/table/lit-table-column';
25import { TabPaneJsMemoryFilter } from '../TabPaneJsMemoryFilter';
26import '../TabPaneJsMemoryFilter';
27import { HeapSnapshotStruct } from '../../../../database/ui-worker/ProcedureWorkerHeapSnapshot';
28import { LitSelectOption } from '../../../../../base-ui/select/LitSelectOption';
29import { LitSelect } from '../../../../../base-ui/select/LitSelect';
30import { TabPaneComparisonHtml } from './TabPaneComparison.html';
31
32@element('tabpane-comparison')
33export class TabPaneComparison extends BaseElement {
34  private comparisonTableEl: LitTable | undefined | null;
35  private retainerTableEl: LitTable | undefined | null;
36  private comparisonsData: Array<ConstructorComparison> = [];
37  private retainsData: Array<ConstructorItem> = [];
38  private baseFileId: number | undefined | null;
39  private targetFileId: number | undefined | null;
40  private filterEl: TabPaneJsMemoryFilter | undefined | null;
41  private selectEl: LitSelect | undefined | null;
42  private search: HTMLInputElement | undefined | null;
43  private comparisonData: Array<ConstructorItem> = [];
44  private comparisonFilter: Array<ConstructorComparison> = [];
45  private leftArray: Array<ConstructorComparison> = [];
46  private rightArray: Array<ConstructorItem> = [];
47  private rightTheadTable: HTMLDivElement | undefined | null;
48  private leftTheadTable: HTMLDivElement | undefined | null;
49  private comparisonTable: HTMLDivElement | undefined | null;
50  private fileSize: number = 0;
51
52  initElements(): void {
53    this.comparisonTableEl = this.shadowRoot!.querySelector<LitTable>('#tb-comparison') as LitTable;
54    this.retainerTableEl = this.shadowRoot!.querySelector<LitTable>('#tb-retainer') as LitTable;
55    this.filterEl = this.shadowRoot!.querySelector<TabPaneJsMemoryFilter>('#filter');
56    this.selectEl = this.filterEl?.shadowRoot?.querySelector<LitSelect>('lit-select');
57    this.search = this.filterEl?.shadowRoot?.querySelector('#js-memory-filter-input') as HTMLInputElement;
58    this.rightTheadTable = this.retainerTableEl!.shadowRoot?.querySelector('.thead') as HTMLDivElement;
59    this.leftTheadTable = this.comparisonTableEl!.shadowRoot?.querySelector('.thead') as HTMLDivElement;
60    this.comparisonTable = this.comparisonTableEl.shadowRoot?.querySelector('.table') as HTMLDivElement;
61    this.classFilter();
62  }
63
64  initComparison(data: HeapSnapshotStruct, dataListCache: Array<HeapSnapshotStruct>): void {
65    this.clear();
66    this.retainerTableEl!.snapshotDataSource = [];
67    let fileArr: HeapSnapshotStruct[] = [];
68    let that = this;
69    for (let file of dataListCache) {
70      if (file.id !== data.id) {
71        fileArr.push(file);
72      }
73    }
74    fileArr = fileArr.sort();
75    this.fileSize = data.size;
76    this.initSelect(data.id, fileArr);
77    this.baseFileId = data.id;
78    this.targetFileId = fileArr[0].id;
79    that.updateComparisonData(data.id, fileArr[0].id);
80    new ResizeObserver(() => {
81      this.comparisonTableEl!.style.height = '100%';
82      this.comparisonTableEl!.reMeauseHeight();
83    }).observe(this.parentElement!);
84  }
85
86  updateComparisonData(baseFileId: number, targetFileId: number): void {
87    this.comparisonsData = HeapDataInterface.getInstance().getClassListForComparison(baseFileId, targetFileId);
88    this.comparisonsData.forEach((dataList) => {
89      dataList.objectName = dataList.nodeName;
90    });
91    if (this.comparisonsData.length > 0) {
92      this.comparisonData = this.comparisonsData;
93      this.comparisonTableEl!.snapshotDataSource = this.comparisonsData;
94    } else {
95      this.comparisonTableEl!.snapshotDataSource = [];
96    }
97    this.comparisonTableEl!.reMeauseHeight();
98  }
99
100  initSelect(fileId: number, comFileArr: Array<HeapSnapshotStruct>): void {
101    let that = this;
102    let input = this.selectEl!.shadowRoot?.querySelector('input') as HTMLInputElement;
103    this.selectEl!.innerHTML = '';
104    let option = new LitSelectOption();
105    option.innerHTML = 'File Name';
106    option.setAttribute('disabled', 'disabled');
107    this.selectEl?.appendChild(option);
108    if (comFileArr[0].name) {
109      option.setAttribute('value', comFileArr[0].name);
110    }
111    this.selectEl!.defaultValue = comFileArr[0].name || '';
112    this.selectEl!.placeholder = comFileArr[0].name || '';
113    this.selectEl!.dataSource = comFileArr;
114    this.selectEl!.querySelectorAll('lit-select-option').forEach((a) => {
115      a.addEventListener('onSelected', (e) => {
116        this.comparisonTable!.scrollTop = 0;
117        this.retainerTableEl!.snapshotDataSource = [];
118        for (let f of comFileArr) {
119          if (input.value === f.name) {
120            that.updateComparisonData(fileId, f.id);
121          }
122        }
123        e.stopPropagation();
124      });
125    });
126  }
127
128  sortComprisonByColumn(column: string, sort: number): void {
129    switch (sort) {
130      case 0:
131        if (this.search!.value === '') {
132          this.comparisonTableEl!.snapshotDataSource = this.comparisonsData;
133        } else {
134          this.comparisonTableEl!.snapshotDataSource = this.comparisonFilter;
135        }
136        break;
137      default:
138        if (this.search!.value === '') {
139          this.leftArray = [...this.comparisonsData];
140        } else {
141          this.leftArray = [...this.comparisonFilter];
142        }
143        this.sortComprisonByColumnExtend(column, sort);
144        break;
145    }
146  }
147
148  private sortComprisonByColumnExtend(column: string, sort: number): void{
149    switch (column) {
150      case 'addedCount':
151        this.comparisonTableEl!.snapshotDataSource = this.leftArray.sort((a, b) => {
152          return sort === 1 ? a.addedCount - b.addedCount : b.addedCount - a.addedCount;
153        });
154        break;
155      case 'removedCount':
156        this.comparisonTableEl!.snapshotDataSource = this.leftArray.sort((a, b) => {
157          return sort === 1 ? a.removedCount - b.removedCount : b.removedCount - a.removedCount;
158        });
159        break;
160      case 'deltaCount':
161        this.comparisonTableEl!.snapshotDataSource = this.leftArray.sort((a, b) => {
162          return sort === 1 ? a.deltaCount - b.deltaCount : b.deltaCount - a.deltaCount;
163        });
164        break;
165      case 'objectName':
166        this.comparisonTableEl!.snapshotDataSource = this.leftArray.sort((a, b) => {
167          return sort === 1 ?
168            (`${a.objectName  }`).localeCompare(`${b.objectName  }`) :
169            (`${b.objectName  }`).localeCompare(`${a.objectName  }`);
170        });
171        break;
172      case 'addedSize':
173        this.comparisonTableEl!.snapshotDataSource = this.leftArray.sort((a, b) => {
174          return sort === 1 ? a.addedSize - b.addedSize : b.addedSize - a.addedSize;
175        });
176        break;
177      case 'removedSize':
178        this.comparisonTableEl!.snapshotDataSource = this.leftArray.sort((a, b) => {
179          return sort === 1 ? a.removedSize - b.removedSize : b.removedSize - a.removedSize;
180        });
181        break;
182      case 'deltaSize':
183        this.comparisonTableEl!.snapshotDataSource = this.leftArray.sort((a, b) => {
184          return sort === 1 ? a.deltaSize - b.deltaSize : b.deltaSize - a.deltaSize;
185        });
186        break;
187    }
188  }
189
190  sortRetainerByColumn(column: string, sort: number): void {
191    switch (sort) {
192      case 0:
193        this.retainerTableEl!.snapshotDataSource = this.retainsData;
194        break;
195      default:
196        this.rightArray = [...this.retainsData];
197        switch (column) {
198          case 'distance':
199            this.sortRetainerByDistanceType(sort);
200            break;
201          case 'shallowSize':
202            this.sortRetainerByShallowSizeType(sort);
203            break;
204          case 'retainedSize':
205            this.sortRetainerByRetainedSizeType(sort);
206            break;
207          case 'objectName':
208            this.sortRetainerByObjectNameType(sort);
209            break;
210        }
211        break;
212    }
213  }
214
215  private sortRetainerByObjectNameType(sort: number): void {
216    this.retainerTableEl!.snapshotDataSource = this.rightArray.sort((rightArrA, rightArrB) => {
217      return sort === 1 ?
218        (`${rightArrA.objectName  }`).localeCompare(`${rightArrB.objectName  }`) :
219        (`${rightArrB.objectName  }`).localeCompare(`${rightArrA.objectName  }`);
220    });
221    this.rightArray.forEach((list) => {
222      let retainsTable = (): void => {
223        const getList = (listArr: Array<ConstructorItem>): void => {
224          listArr.sort((listArrA, listArrB) => {
225            return sort === 1 ?
226              (`${listArrA.objectName  }`).localeCompare(`${listArrB.objectName  }`) :
227              (`${listArrB.objectName  }`).localeCompare(`${listArrA.objectName  }`);
228          });
229          listArr.forEach(function (currentRow) {
230            if (currentRow.children.length > 0) {
231              getList(currentRow.children);
232            }
233          });
234        };
235        getList(list.children);
236      };
237      retainsTable();
238    });
239    this.retainerTableEl!.snapshotDataSource = this.rightArray;
240  }
241
242  private sortRetainerByRetainedSizeType(sort: number): void {
243    this.retainerTableEl!.snapshotDataSource = this.rightArray.sort((rightArrA, rightArrB) => {
244      return sort === 1 ?
245        rightArrA.retainedSize - rightArrB.retainedSize :
246        rightArrB.retainedSize - rightArrA.retainedSize;
247    });
248    this.rightArray.forEach((list) => {
249      let retainsTable = (): void => {
250        const getList = (listArr: Array<ConstructorItem>): void => {
251          listArr.sort((listArrA, listArrB) => {
252            return sort === 1 ?
253              listArrA.retainedSize - listArrB.retainedSize :
254              listArrB.retainedSize - listArrA.retainedSize;
255          });
256          listArr.forEach(function (row) {
257            if (row.children.length > 0) {
258              getList(row.children);
259            }
260          });
261        };
262        getList(list.children);
263      };
264      retainsTable();
265    });
266    this.retainerTableEl!.snapshotDataSource = this.rightArray;
267  }
268
269  private sortRetainerByShallowSizeType(sort: number): void {
270    this.retainerTableEl!.snapshotDataSource = this.rightArray.sort((rightArrA, rightArrB) => {
271      return sort === 1 ?
272        rightArrA.shallowSize - rightArrB.shallowSize :
273        rightArrB.shallowSize - rightArrA.shallowSize;
274    });
275    this.rightArray.forEach((list) => {
276      let retainsTable = (): void => {
277        const getList = (listArr: Array<ConstructorItem>): void => {
278          listArr.sort((listArrA, listArrB) => {
279            return sort === 1 ?
280              listArrA.shallowSize - listArrB.shallowSize :
281              listArrB.shallowSize - listArrA.shallowSize;
282          });
283          listArr.forEach(function (rowEl) {
284            if (rowEl.children.length > 0) {
285              getList(rowEl.children);
286            }
287          });
288        };
289        getList(list.children);
290      };
291      retainsTable();
292    });
293    this.retainerTableEl!.snapshotDataSource = this.rightArray;
294  }
295
296  private sortRetainerByDistanceType(sort: number): void {
297    this.retainerTableEl!.snapshotDataSource = this.rightArray.sort((a, b) => {
298      return sort === 1 ? a.distance - b.distance : b.distance - a.distance;
299    });
300    this.rightArray.forEach((list) => {
301      let retainsTable =  (): void => {
302        const getList = (currentList: Array<ConstructorItem>): void => {
303          currentList.sort((a, b) => {
304            return sort === 1 ? a.distance - b.distance : b.distance - a.distance;
305          });
306          currentList.forEach(function (currentRow) {
307            if (currentRow.children.length > 0) {
308              getList(currentRow.children);
309            }
310          });
311        };
312        getList(list.children);
313      };
314      retainsTable();
315    });
316    this.retainerTableEl!.snapshotDataSource = this.rightArray;
317  }
318
319  classFilter(): void {
320    this.search!.addEventListener('keyup', () => {
321      this.comparisonFilter = [];
322      this.comparisonData.forEach((a: any) => {
323        if (a.objectName.toLowerCase().includes(this.search!.value.toLowerCase())) {
324          this.comparisonFilter.push(a);
325        } else {
326        }
327      });
328      this.comparisonTableEl!.snapshotDataSource = this.comparisonFilter;
329      let summaryTable = this.comparisonTableEl!.shadowRoot?.querySelector('.table') as HTMLDivElement;
330      summaryTable.scrollTop = 0;
331      this.comparisonTableEl!.reMeauseHeight();
332    });
333  }
334
335  clear(): void {
336    this.search!.value = '';
337    this.rightTheadTable!.removeAttribute('sort');
338    this.leftTheadTable!.removeAttribute('sort');
339    this.comparisonTable!.scrollTop = 0;
340  }
341
342  connectedCallback(): void {
343    super.connectedCallback();
344    let filterHeight = 0;
345    new ResizeObserver(() => {
346      let comparisonPanelFilter = this.shadowRoot!.querySelector('#filter') as HTMLElement;
347      if (comparisonPanelFilter.clientHeight > 0) {filterHeight = comparisonPanelFilter.clientHeight}
348      if (this.parentElement!.clientHeight > filterHeight) {
349        comparisonPanelFilter.style.display = 'flex';
350      } else {
351        comparisonPanelFilter.style.display = 'none';
352      }
353    }).observe(this.parentElement!);
354    this.comparisonTableEl!.addEventListener('icon-click', this.comparisonTblIconClickHandler);
355    this.retainerTableEl!.addEventListener('icon-click', this.retainerTblIconClickHandler);
356    this.comparisonTableEl!.addEventListener('column-click', this.comparisonTblColumnClickHandler);
357    this.retainerTableEl!.addEventListener('column-click', this.retainerTblColumnClickHandler);
358    this.comparisonTableEl!.addEventListener('row-click', this.comparisonTblRowClickHandler);
359    this.retainerTableEl!.addEventListener('row-click', this.retainerTblRowClickHandler);
360  }
361
362  disconnectedCallback(): void {
363    super.disconnectedCallback();
364    this.comparisonTableEl!.removeEventListener('icon-click', this.comparisonTblIconClickHandler);
365    this.retainerTableEl!.removeEventListener('icon-click', this.retainerTblIconClickHandler);
366    this.comparisonTableEl!.removeEventListener('column-click', this.comparisonTblColumnClickHandler);
367    this.retainerTableEl!.removeEventListener('column-click', this.retainerTblColumnClickHandler);
368    this.comparisonTableEl!.removeEventListener('row-click', this.comparisonTblRowClickHandler);
369    this.retainerTableEl!.removeEventListener('row-click', this.retainerTblRowClickHandler);
370  }
371
372  private comparisonTblRowClickHandler = (e: Event): void => {
373    this.rightTheadTable!.removeAttribute('sort');
374    // @ts-ignore
375    let item = e.detail.data as ConstructorItem;
376    (item as any).isSelected = true;
377    this.retainsData = HeapDataInterface.getInstance().getRetains(item);
378    if (this.retainsData && this.retainsData.length > 0) {
379      this.retainsDataInit();
380      let i = 0;
381      let that = this;
382      if (this.retainsData[0].distance > 1) {
383        this.retainsData[0].getChildren();
384        this.retainsData[0].expanded = false;
385      }
386      let retainsTable = (): void => {
387        const getList = (list: Array<ConstructorItem>): void => {
388          list.forEach((structRow) => {
389            let shallow = `${Math.round((structRow.shallowSize / this.fileSize) * 100)  }%`;
390            let retained = `${Math.round((structRow.retainedSize / this.fileSize) * 100)  }%`;
391            structRow.shallowPercent = shallow;
392            structRow.retainedPercent = retained;
393            let nodeId = `${structRow.nodeName  } @${structRow.id}`;
394            structRow.objectName = `${structRow.edgeName  }\xa0` + 'in' + `\xa0${  nodeId}`;
395            if (structRow.distance >= 100000000 || structRow.distance === -5) {
396              // @ts-ignore
397              structRow.distance = '-';
398            }
399            i++;
400            // @ts-ignore
401            if (i < that.retainsData[0].distance - 1 && list[0].distance !== '-') {
402              list[0].getChildren();
403              list[0].expanded = false;
404              if (structRow.hasNext) {
405                getList(structRow.children);
406              }
407            } else {
408              return;
409            }
410          });
411        };
412        getList(that.retainsData[0].children);
413      };
414      retainsTable();
415      this.retainerTableEl!.snapshotDataSource = this.retainsData;
416    } else {
417      this.retainerTableEl!.snapshotDataSource = [];
418    }
419    this.resizeObserverObserve();
420    // @ts-ignore
421    if ((e.detail as any).callBack) {
422      // @ts-ignore
423      (e.detail as any).callBack(true);
424    }
425  };
426
427  private resizeObserverObserve(){
428    new ResizeObserver(() => {
429      this.retainerTableEl!.style.height = 'calc(100% - 21px)';
430      this.retainerTableEl!.reMeauseHeight();
431    }).observe(this.parentElement!);
432  }
433
434  private retainsDataInit(): void{
435    this.retainsData.forEach((comparisonRetainEl) => {
436      let shallow = `${Math.round((comparisonRetainEl.shallowSize / this.fileSize) * 100)  }%`;
437      let retained = `${Math.round((comparisonRetainEl.retainedSize / this.fileSize) * 100)  }%`;
438      comparisonRetainEl.shallowPercent = shallow;
439      comparisonRetainEl.retainedPercent = retained;
440      if (comparisonRetainEl.distance >= 100000000 || comparisonRetainEl.distance === -5) {
441        // @ts-ignore
442        comparisonRetainEl.distance = '-';
443      }
444      let nodeId = `${comparisonRetainEl.nodeName  } @${comparisonRetainEl.id}`;
445      comparisonRetainEl.objectName = `${comparisonRetainEl.edgeName  }\xa0` + 'in' + `\xa0${  nodeId}`;
446    });
447  }
448
449  private retainerTblRowClickHandler = (evt: Event): void => {
450    // @ts-ignore
451    let data = evt.detail.data as ConstructorItem;
452    (data as any).isSelected = true;
453    // @ts-ignore
454    if ((evt.detail as any).callBack) {
455      // @ts-ignore
456      (evt.detail as any).callBack(true);
457    }
458  };
459
460  private comparisonTblColumnClickHandler = (e: Event): void => {
461    // @ts-ignore
462    this.sortComprisonByColumn(e.detail.key, e.detail.sort);
463    this.comparisonTableEl!.reMeauseHeight();
464  };
465
466  private retainerTblColumnClickHandler = (e: Event): void => {
467    // @ts-ignore
468    this.sortRetainerByColumn(e.detail.key, e.detail.sort);
469    this.retainerTableEl!.reMeauseHeight();
470  };
471
472  retainerTblIconClickHandler = (e: Event): void => {
473    // @ts-ignore
474    let retainerNext = e.detail.data as ConstructorItem;
475    if (retainerNext) {
476      if (this.retainsData.length > 0) {
477        if (retainerNext.status) {
478          retainerNext.getChildren();
479          let i = 0;
480          let that = this;
481          let retainsTable = (): void => {
482            const getList = (comList: Array<ConstructorItem>): void => {
483              comList.forEach((row) => {
484                let shallow = `${Math.round((row.shallowSize / this.fileSize) * 100)  }%`;
485                let retained = `${Math.round((row.retainedSize / this.fileSize) * 100)  }%`;
486                row.shallowPercent = shallow;
487                row.retainedPercent = retained;
488                let nodeId = `${row.nodeName  } @${row.id}`;
489                row.objectName = `${row.edgeName  }\xa0` + 'in' + `\xa0${  nodeId}`;
490                if (row.distance >= 100000000 || row.distance === -5) {
491                  // @ts-ignore
492                  row.distance = '-';
493                }
494                i++;
495                // @ts-ignore
496                if (i < that.retainsData[0].distance - 1 && comList[0].distance !== '-') {
497                  comList[0].getChildren();
498                  comList[0].expanded = false;
499                  if (row.hasNext) {
500                    getList(row.children);
501                  }
502                } else {
503                  return;
504                }
505              });
506            };
507            getList(retainerNext.children);
508          };
509          retainsTable();
510        } else {
511          retainerNext.status = true;
512        }
513        if (this.rightTheadTable!.hasAttribute('sort')) {
514          this.retainerTableEl!.snapshotDataSource = this.rightArray;
515        } else {
516          this.retainerTableEl!.snapshotDataSource = this.retainsData;
517        }
518      } else {
519        this.retainerTableEl!.snapshotDataSource = [];
520      }
521      this.resizeObserverObserve();
522    }
523  };
524
525  comparisonTblIconClickHandler = (e: Event): void => {
526    // @ts-ignore
527    let clickRow = e.detail.data;
528    if (clickRow.status) {
529      clickRow.targetFileId = this.targetFileId;
530      clickRow.children = HeapDataInterface.getInstance().getNextForComparison(clickRow);
531      if (clickRow.children.length > 0) {
532        for (let item of clickRow.children) {
533          let nodeName = `${item.nodeName  } @${item.id}`;
534          item.nodeId = ` @${item.id}`;
535          if (item.isString()) {
536            item.objectName = `"${  item.nodeName}"` + ` @${item.id}`;
537          } else {
538            item.objectName = nodeName;
539          }
540          item.deltaCount = '-';
541          item.deltaSize = '-';
542          if (item.edgeName !== '') {
543            item.objectName = `${item.edgeName  }\xa0` + '::' + `\xa0${  nodeName}`;
544          } else {
545            if (item.fileId === this.baseFileId) {
546              item.addedCount = '•';
547              item.addedSize = item.shallowSize;
548              item.removedCount = '-';
549              item.removedSize = '-';
550            } else if (item.fileId) {
551              item.removedCount = '•';
552              item.removedSize = item.shallowSize;
553              item.addedCount = '-';
554              item.addedSize = '-';
555            }
556          }
557          if (item.type === ConstructorType.FiledType) {
558            item.removedCount = '-';
559            item.removedSize = '-';
560            item.addedCount = '-';
561            item.addedSize = '-';
562          }
563        }
564      } else {
565        this.comparisonTableEl!.snapshotDataSource = [];
566      }
567    } else {
568      clickRow.status = true;
569    }
570    this.comparisonTblIconClickData();
571  };
572
573  private comparisonTblIconClickData(): void{
574    if (this.search!.value !== '') {
575      if (this.leftTheadTable!.hasAttribute('sort')) {
576        this.comparisonTableEl!.snapshotDataSource = this.leftArray;
577      } else {
578        this.comparisonTableEl!.snapshotDataSource = this.comparisonFilter;
579      }
580    } else {
581      if (this.leftTheadTable!.hasAttribute('sort')) {
582        this.comparisonTableEl!.snapshotDataSource = this.leftArray;
583      } else {
584        this.comparisonTableEl!.snapshotDataSource = this.comparisonsData;
585      }
586    }
587    new ResizeObserver(() => {
588      this.comparisonTableEl!.style.height = '100%';
589      this.comparisonTableEl!.reMeauseHeight();
590    }).observe(this.parentElement!);
591  }
592
593  initHtml(): string {
594    return TabPaneComparisonHtml;
595  }
596}
597