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 { SpSystemTrace } from '../SpSystemTrace';
17import { TraceRow } from '../trace/base/TraceRow';
18import { renders } from '../../database/ui-worker/ProcedureWorker';
19import {
20  GpuCounterStruct,
21  MaleoonCounterObj,
22  GpuCounterType,
23  GpuCounterRender,
24} from '../../database/ui-worker/ProcedureWorkerGpuCounter';
25import { folderSupplier, folderThreadHandler } from './SpChartManager';
26import { queryRangeTime } from '../../database/sql/SqlLite.sql';
27
28export class SpGpuCounterChart {
29  trace: SpSystemTrace;
30  // @ts-ignore
31  private folderRow: TraceRow<unknown> | undefined;
32  constructor(trace: SpSystemTrace) {
33    this.trace = trace;
34  }
35
36  async init(res: Array<unknown>): Promise<void> {
37    if (res.length === 0) {
38      let startTime = await queryRangeTime();
39      this.initFolder(res, false);
40      //@ts-ignore
41      this.addTraceRowEventListener(startTime[0].start_ts, startTime[0].end_ts);
42    } else {
43      // @ts-ignore
44      const { gpuCounterType, start_time } = this.handleCsvData(res);
45      this.initFolder(res, true);
46      await this.initGpuCounters(gpuCounterType, start_time);
47    }
48  }
49
50  initFolder(res: Array<unknown>, isSimpleUpload: boolean): void {
51    this.folderRow = TraceRow.skeleton();
52    this.folderRow.rowId = 'GpuCounter';
53    this.folderRow.index = 0;
54    this.folderRow.rowType = TraceRow.ROW_TYPE_GPU_COUNTER_GROUP;
55    this.folderRow.rowParentId = '';
56    this.folderRow.style.height = '40px';
57    this.folderRow.rowHidden = this.folderRow!.expansion;
58    this.folderRow.setAttribute('children', '');
59    this.folderRow.folder = res.length > 0 ? true : false;
60    this.folderRow.name = 'Gpu counter';
61    //@ts-ignore
62    this.folderRow.supplier = folderSupplier();
63    // @ts-ignore
64    this.folderRow.onThreadHandler = folderThreadHandler(this.folderRow, this.trace);
65    if (!isSimpleUpload) {
66      this.folderRow.addRowSampleUpload('.csv');
67    }
68    this.folderRow.addEventListener('expansion-change', this.trace.extracted(this.folderRow));
69    this.trace.rowsEL?.appendChild(this.folderRow);
70  }
71
72  async initGpuCounters(gpuCounterType: unknown, start_time: number): Promise<void> {
73    // @ts-ignore
74    for (const key in gpuCounterType) {
75      let typeRows = TraceRow.skeleton();
76      typeRows.rowId = key;
77      typeRows.rowType = TraceRow.ROW_TYPE_GPU_COUNTER;
78      typeRows.rowParentId = this.folderRow?.rowId;
79      typeRows.folder = true;
80      typeRows.folderTextLeft = 20;
81      typeRows.rowHidden = !this.folderRow!.expansion;
82      typeRows.style.height = '40px';
83      typeRows.name = `${key}`;
84      typeRows.selectChangeHandler = this.trace.selectChangeHandler;
85      //@ts-ignore
86      typeRows.supplier = folderSupplier();
87      typeRows.onThreadHandler = folderThreadHandler(typeRows, this.trace);
88      this.folderRow!.addChildTraceRow(typeRows);
89      // @ts-ignore
90      this.initTypeRow(gpuCounterType[key], key, typeRows, start_time);
91    }
92  }
93
94  initTypeRow(rowList: unknown, key: string, parentRow: unknown, start_time: number): void {
95    const typeName = this.getKeyTypeName(key);
96    // @ts-ignore
97    for (let i = 0; i < rowList.length; i++) {
98      let typeRow = TraceRow.skeleton<GpuCounterStruct>();
99      // @ts-ignore
100      let maxValue = Math.max(...rowList[i].map((it: GpuCounterStruct) => Number(it.height)));
101      typeRow.rowId = `${typeName[i]}`;
102      typeRow.rowType = TraceRow.ROW_TYPE_GPU_COUNTER;
103      typeRow.rowParentId = key;
104      typeRow.folder = false;
105      typeRow.folderTextLeft = 40;
106      typeRow.style.height = '40px';
107      // @ts-ignore
108      typeRow.rowHidden = !parentRow.expansion;
109      typeRow.setAttribute('children', '');
110      typeRow.name = `${typeName[i]}`;
111      typeRow.selectChangeHandler = this.trace.selectChangeHandler;
112      typeRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
113      typeRow.focusHandler = (): void => this.focusHandler(typeRow, GpuCounterStruct.hoverGpuCounterStruct!);
114      typeRow.findHoverStruct = (): void => {
115        GpuCounterStruct.hoverGpuCounterStruct = typeRow.getHoverStruct(false);
116      };
117      typeRow.supplierFrame = (): Promise<GpuCounterStruct[]> =>
118        new Promise((resolve): void => {
119          // @ts-ignore
120          resolve(rowList[i]);
121        });
122      typeRow.onThreadHandler = (useCache): void => {
123        let context: CanvasRenderingContext2D;
124        if (typeRow.currentContext) {
125          context = typeRow.currentContext;
126        } else {
127          context = typeRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!;
128        }
129        typeRow.canvasSave(context);
130        (renders.gpuCounter as GpuCounterRender).renderMainThread(
131          {
132            context: context,
133            useCache: useCache,
134            type: `${typeName[i]}`,
135            startTime: start_time,
136            maxValue: maxValue,
137          },
138          typeRow
139        );
140        typeRow.canvasRestore(context);
141      };
142      // @ts-ignore
143      parentRow.addChildTraceRow(typeRow);
144    }
145  }
146
147  focusHandler(row: TraceRow<GpuCounterStruct>, struct: GpuCounterStruct): void {
148    let tip = '';
149    if (struct) {
150      tip = `
151      <span> ${struct.height}</span>
152      `;
153    }
154    this.trace?.displayTip(row, struct, tip);
155  }
156
157  getKeyTypeName(key: string): Array<string> {
158    const typeName: { [key: string]: Array<string> } = {
159      cycle: ['gpu clocks', 'tiler utilization', 'binning utilization', 'rendering utilization', 'compute utilization'],
160      drawcall: [
161        'drawcall count',
162        'vertex count',
163        'primitives count',
164        'visible primitives count',
165        'compute invocations count',
166      ],
167      shader_cycle: [
168        'shader utilization',
169        'eu utilization',
170        'eu stall utilization',
171        'eu idle utilization',
172        'control flow instr utilization',
173        'half float instr utilization',
174        'tu utilization',
175      ],
176      local_count: ['concurrent warps', 'instruction count', 'quads count', 'texels count'],
177      local_wr: ['memory read', 'memory write', 'memory traffic'],
178    };
179    return typeName[key];
180  }
181
182  handleCsvData(res: unknown, start_ts: number = 0, end_ts: number = 0): unknown {
183    // @ts-ignore
184    const minIndex = this.getMinData(res) + 1;
185    const gpuCounterMap = this.initGpuCounterMap();
186    // @ts-ignore
187    this.maleoon_gpu_counter_init(gpuCounterMap, res[0]);
188    // @ts-ignore
189    let start_time_data = res[minIndex].split(',');
190    // @ts-ignore
191    let start_time = Number(start_time_data[gpuCounterMap.timestamp]);
192    let last_record_time = 0;
193    // @ts-ignore
194    let maleoonCounter = new maleoon_counter_obj();
195    // @ts-ignore
196    let read_line_num = res.length - 1;
197    let utilization_array = [
198      'tiler_utilization',
199      'binning_utilization',
200      'rendering_utilization',
201      'compute_utilization',
202      'shader_utilization',
203      'eu_utilization',
204      'eu_stall_utilization',
205      'eu_idle_utilization',
206      'control_flow_instr_utilization',
207      'half_float_instr_utilization',
208      'tu_utilization',
209      'concurrent_warps',
210    ];
211
212    let count_array = [
213      'gpu_clocks',
214      'drawcall_count',
215      'vertex_count',
216      'primitives_count',
217      'visible_primitives_count',
218      'instruction_count',
219      'quads_count',
220      'texels_count',
221      'compute_invocations_count',
222      'memory_read',
223      'memory_write',
224      'memory_traffic',
225    ];
226
227    for (let i = minIndex; i < read_line_num; i++) {
228      // @ts-ignore
229      let datas = res[i].split(',');
230      if (datas.length !== 25) {
231        continue;
232      }
233      if (
234        // @ts-ignore
235        (start_ts > 0 && Number(datas[gpuCounterMap.timestamp]) < start_ts) ||
236        // @ts-ignore
237        (end_ts > 0 && Number(datas[gpuCounterMap.timestamp]) > end_ts)
238      ) {
239        continue;
240      }
241      // @ts-ignore
242      let time_passed = Number(datas[gpuCounterMap.timestamp]) - start_time;
243      //去重
244      if (time_passed <= last_record_time) {
245        continue;
246      }
247      time_passed -= last_record_time;
248      last_record_time += time_passed;
249      utilization_array.forEach((item) => {
250        maleoonCounter[item].push({
251          // @ts-ignore
252          startNS: Number(datas[gpuCounterMap.timestamp]),
253          // @ts-ignore
254          height: gpuCounterMap[item] === -1 ? 0 : Math.floor(Number(datas[gpuCounterMap[item]])),
255        });
256      });
257      count_array.forEach((item) => {
258        maleoonCounter[item].push({
259          // @ts-ignore
260          startNS: Number(datas[gpuCounterMap.timestamp]),
261          // @ts-ignore
262          height: gpuCounterMap[item] === -1 ? 0 : Math.floor(Number(datas[gpuCounterMap[item]])),
263        });
264      });
265    }
266    utilization_array.forEach((item) => {
267      for (let i = 0; i < maleoonCounter[item].length; i++) {
268        //@ts-ignore
269        maleoonCounter[item][i].dur = maleoonCounter[item][i + 1]?.startNS - maleoonCounter[item][i].startNS || 0;
270      }
271    });
272    count_array.forEach((item) => {
273      for (let i = 0; i < maleoonCounter[item].length; i++) {
274        //@ts-ignore
275        maleoonCounter[item][i].dur = maleoonCounter[item][i + 1]?.startNS - maleoonCounter[item][i].startNS || 0;
276      }
277    });
278    const gpuCounterType = this.groupByGpuCounterType(maleoonCounter);
279    return { gpuCounterType, start_time };
280  }
281
282  initGpuCounterMap(): unknown {
283    let gpu_counter_map = {
284      timestamp: -1,
285
286      gpu_clocks: -1,
287      tiler_utilization: -1,
288      binning_utilization: -1,
289      rendering_utilization: -1,
290      compute_utilization: -1,
291
292      drawcall_count: -1,
293      vertex_count: -1,
294      primitives_count: -1,
295      visible_primitives_count: -1,
296      compute_invocations_count: -1,
297
298      shader_utilization: -1,
299      eu_utilization: -1,
300      eu_stall_utilization: -1,
301      eu_idle_utilization: -1,
302      control_flow_instr_utilization: -1,
303      half_float_instr_utilization: -1,
304      tu_utilization: -1,
305
306      concurrent_warps: -1,
307      instruction_count: -1,
308      quads_count: -1,
309      texels_count: -1,
310
311      memory_read: -1,
312      memory_write: -1,
313      memory_traffic: -1,
314    };
315    return gpu_counter_map;
316  }
317
318  maleoon_gpu_counter_init(gpu_counter_map: unknown, head_line: string): void {
319    // @ts-ignore
320    gpu_counter_map.timestamp = -1;
321    // @ts-ignore
322    gpu_counter_map.gpu_clocks = -1;
323    // @ts-ignore
324    gpu_counter_map.tiler_utilization = -1;
325    // @ts-ignore
326    gpu_counter_map.binning_utilization = -1;
327    // @ts-ignore
328    gpu_counter_map.rendering_utilization = -1;
329    // @ts-ignore
330    gpu_counter_map.compute_utilization = -1;
331
332    // @ts-ignore
333    gpu_counter_map.drawcall_count = -1;
334    // @ts-ignore
335    gpu_counter_map.vertex_count = -1;
336    // @ts-ignore
337    gpu_counter_map.primitives_count = -1;
338    // @ts-ignore
339    gpu_counter_map.visible_primitives_count = -1;
340    // @ts-ignore
341    gpu_counter_map.compute_invocations_count = -1;
342
343    // @ts-ignore
344    gpu_counter_map.shader_utilization = -1;
345    // @ts-ignore
346    gpu_counter_map.eu_utilization = -1;
347    // @ts-ignore
348    gpu_counter_map.eu_stall_utilization = -1;
349    // @ts-ignore
350    gpu_counter_map.eu_idle_utilization = -1;
351    // @ts-ignore
352    gpu_counter_map.control_flow_instr_utilization = -1;
353    // @ts-ignore
354    gpu_counter_map.half_float_instr_utilization = -1;
355    // @ts-ignore
356    gpu_counter_map.tu_utilization = -1;
357
358    // @ts-ignore
359    gpu_counter_map.concurrent_warps = -1;
360    // @ts-ignore
361    gpu_counter_map.instruction_count = -1;
362    // @ts-ignore
363    gpu_counter_map.quads_count = -1;
364    // @ts-ignore
365    gpu_counter_map.texels_count = -1;
366
367    // @ts-ignore
368    gpu_counter_map.memory_read = -1;
369    // @ts-ignore
370    gpu_counter_map.memory_write = -1;
371    // @ts-ignore
372    gpu_counter_map.memory_traffic = -1;
373
374    let paras = head_line.split(',');
375    for (let i = 0; i < paras.length; i++) {
376      // @ts-ignore
377      if (paras[i] === 'TIMESTAMP') {
378        // @ts-ignore
379        gpu_counter_map.timestamp = i;
380      }
381
382      if (paras[i] === 'GPU Clocks') {
383        // @ts-ignore
384        gpu_counter_map.gpu_clocks = i;
385      }
386      if (paras[i] === 'Tiler Utilization') {
387        // @ts-ignore
388        gpu_counter_map.tiler_utilization = i;
389      }
390      if (paras[i] === 'Binning Queue Utilization') {
391        // @ts-ignore
392        gpu_counter_map.binning_utilization = i;
393      }
394      if (paras[i] === 'Rendering Queue Utilization') {
395        // @ts-ignore
396        gpu_counter_map.rendering_utilization = i;
397      }
398      if (paras[i] === 'Compute Queue Utilization') {
399        // @ts-ignore
400        gpu_counter_map.compute_utilization = i;
401      }
402
403      if (paras[i] === 'Drawcalls Count') {
404        // @ts-ignore
405        gpu_counter_map.drawcall_count = i;
406      }
407      if (paras[i] === 'Vertex Count') {
408        // @ts-ignore
409        gpu_counter_map.vertex_count = i;
410      }
411      if (paras[i] === 'Primitive Count') {
412        // @ts-ignore
413        gpu_counter_map.primitives_count = i;
414      }
415      if (paras[i] === 'Visible Primitive Count') {
416        // @ts-ignore
417        gpu_counter_map.visible_primitives_count = i;
418      }
419      if (paras[i] === 'Compute Shader Invocations') {
420        // @ts-ignore
421        gpu_counter_map.compute_invocations_count = i;
422      }
423
424      if (paras[i] === 'Shader Core Utilization') {
425        // @ts-ignore
426        gpu_counter_map.shader_utilization = i;
427      }
428      if (paras[i] === 'EU Utilization') {
429        // @ts-ignore
430        gpu_counter_map.eu_utilization = i;
431      }
432      if (paras[i] === 'EU Stall') {
433        // @ts-ignore
434        gpu_counter_map.eu_stall_utilization = i;
435      }
436      if (paras[i] === 'EU Idle') {
437        // @ts-ignore
438        gpu_counter_map.eu_idle_utilization = i;
439      }
440      if (paras[i] === 'Instructions Diverged') {
441        // @ts-ignore
442        gpu_counter_map.control_flow_instr_utilization = i;
443      }
444      if (paras[i] === 'Half-float Instructions') {
445        // @ts-ignore
446        gpu_counter_map.half_float_instr_utilization = i;
447      }
448      if (paras[i] === 'TU Utilization') {
449        // @ts-ignore
450        gpu_counter_map.tu_utilization = i;
451      }
452
453      if (paras[i] === 'Concurrent Warps') {
454        // @ts-ignore
455        gpu_counter_map.concurrent_warps = i;
456      }
457      if (paras[i] === 'Instructions Executed') {
458        // @ts-ignore
459        gpu_counter_map.instruction_count = i;
460      }
461      if (paras[i] === 'Quads Shaded') {
462        // @ts-ignore
463        gpu_counter_map.quads_count = i;
464      }
465      if (paras[i] === 'Texels Sampled') {
466        // @ts-ignore
467        gpu_counter_map.texels_count = i;
468      }
469
470      if (paras[i] === 'External Memory Read') {
471        // @ts-ignore
472        gpu_counter_map.memory_read = i;
473      }
474      if (paras[i] === 'External Memory Write') {
475        // @ts-ignore
476        gpu_counter_map.memory_write = i;
477      }
478      if (paras[i] === 'External Memory Traffic') {
479        // @ts-ignore
480        gpu_counter_map.memory_traffic = i;
481      }
482    }
483  }
484
485  groupByGpuCounterType(maleoonCounter: MaleoonCounterObj): GpuCounterType {
486    const gpuCounterType = new GpuCounterType();
487    let index = 0;
488    for (const key in maleoonCounter) {
489      if (index < 5) {
490        gpuCounterType.cycle.push(maleoonCounter[key]);
491      }
492      if (index >= 5 && index < 10) {
493        gpuCounterType.drawcall.push(maleoonCounter[key]);
494      }
495      if (index >= 10 && index < 17) {
496        gpuCounterType.shader_cycle.push(maleoonCounter[key]);
497      }
498      if (index >= 17 && index < 21) {
499        gpuCounterType.local_count.push(maleoonCounter[key]);
500      }
501      if (index >= 21 && index < 24) {
502        gpuCounterType.local_wr.push(maleoonCounter[key]);
503      }
504      index++;
505    }
506    return gpuCounterType;
507  }
508
509  /**
510   * 监听文件上传事件
511   * @param row
512   * @param start_ts
513   */
514  addTraceRowEventListener(startTime: number, endTime: number): void {
515    this.folderRow?.uploadEl?.addEventListener('sample-file-change', (e: unknown) => {
516      this.getCsvData(e).then((res: unknown) => {
517        this.resetChartData(this.folderRow!);
518        // @ts-ignore
519        const { gpuCounterType } = this.handleCsvData(res, startTime, endTime);
520        this.initGpuCounters(gpuCounterType, startTime);
521        if (!this.folderRow!.folder) {
522          this.folderRow!.folder = true;
523        }
524      });
525    });
526  }
527
528  /**
529   * 清空缓存
530   * @param row
531   */
532  // @ts-ignore
533  resetChartData(row: TraceRow<unknown>): void {
534    if (row.expansion) {
535      row.describeEl?.click();
536    }
537    row.childrenList = [];
538  }
539
540  getMinData(list: Array<unknown>): number {
541    // @ts-ignore
542    const sliceList = list.slice(1, 11).map((item) => Number(item.split(',')[0]));
543    const nonZeroList = sliceList.filter((item) => item !== 0).sort((a, b) => a - b);
544    const minIndex = sliceList.findIndex((item) => item === nonZeroList[0]);
545    return minIndex;
546  }
547
548  /**
549   * 获取上传的文件内容 转为json格式
550   * @param file
551   * @returns
552   */
553  getCsvData(file: unknown): Promise<unknown> {
554    return new Promise((resolve, reject) => {
555      let reader = new FileReader();
556      // @ts-ignore
557      reader.readAsText(file.detail || file);
558      reader.onloadend = (e: unknown): void => {
559        // @ts-ignore
560        const fileContent = e.target?.result.split(/[\r\n]/).filter(Boolean);
561        try {
562          resolve(fileContent);
563          document.dispatchEvent(new CustomEvent('file-correct'));
564        } catch (error) {
565          document.dispatchEvent(new CustomEvent('file-error'));
566        }
567      };
568    });
569  }
570}
571