• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 { LitTable, RedrawTreeForm } from '../../../../../base-ui/table/lit-table';
18import { SelectionParam } from '../../../../bean/BoxSelection';
19import '../../../StackBar';
20import { getTabRunningPercent } from '../../../../database/sql/ProcessThread.sql';
21import { queryCpuFreqUsageData, queryCpuFreqFilterId } from '../../../../database/sql/Cpu.sql';
22import { Utils } from '../../base/Utils';
23import { resizeObserver } from '../SheetUtils';
24import { SpSegmentationChart } from '../../../chart/SpSegmentationChart';
25import { type CpuFreqData, type RunningFreqData, type RunningData, type CpuFreqTd } from './TabPaneFreqUsageConfig';
26
27@element('tabpane-frequsage')
28export class TabPaneFreqUsage extends BaseElement {
29  private threadStatesTbl: LitTable | null | undefined;
30  private currentSelectionParam: SelectionParam | undefined;
31  private result: Array<RunningFreqData> = [];
32
33  set data(threadStatesParam: SelectionParam) {
34    if (this.currentSelectionParam === threadStatesParam) {
35      return;
36    }
37    this.threadStatesTbl!.loading = true;
38    this.currentSelectionParam = threadStatesParam;
39    this.threadStatesTbl!.recycleDataSource = [];
40    // @ts-ignore
41    this.threadStatesTbl.value = [];
42    this.result = [];
43    this.queryAllData(threadStatesParam);
44  }
45  async queryAllData(threadStatesParam: SelectionParam): Promise<void> {
46    let runningResult: Array<RunningData> = await getTabRunningPercent(
47      threadStatesParam.threadIds,
48      threadStatesParam.processIds,
49      threadStatesParam.leftNs,
50      threadStatesParam.rightNs
51    );
52    // 查询cpu及id信息
53    let cpuIdResult: Array<{ id: number; cpu: number }> = await queryCpuFreqFilterId();
54    // 以键值对形式将cpu及id进行对应,后续会将频点数据与其对应cpu进行整合
55    let IdMap: Map<number, number> = new Map();
56    let queryId: Array<number> = [];
57    let cpuArray: Array<number> = [];
58    for (let i = 0; i < cpuIdResult.length; i++) {
59      queryId.push(cpuIdResult[i].id);
60      IdMap.set(cpuIdResult[i].id, cpuIdResult[i].cpu);
61      cpuArray.push(cpuIdResult[i].cpu);
62    }
63    // 通过id去查询频点数据
64    let cpuFreqResult: Array<CpuFreqTd> = await queryCpuFreqUsageData(queryId);
65    let cpuFreqData: Array<CpuFreqData> = [];
66    for (let i of cpuFreqResult) {
67      cpuFreqData.push({
68        ts: i.startNS + threadStatesParam.recordStartNs,
69        cpu: IdMap.get(i.filter_id)!,
70        value: i.value,
71        dur: i.dur,
72      });
73    }
74    const LEFT_TIME: number = threadStatesParam.leftNs + threadStatesParam.recordStartNs;
75    const RIGHT_TIME: number = threadStatesParam.rightNs + threadStatesParam.recordStartNs;
76    const args = { leftNs: LEFT_TIME, rightNs: RIGHT_TIME, cpuArray: cpuArray };
77    let resultArr: Array<RunningFreqData> = orgnazitionMap(runningResult, cpuFreqData, args);
78    // 递归拿出来最底层的数据,并以进程层级的数据作为分割
79    this.recursion(resultArr);
80    this.result = JSON.parse(JSON.stringify(this.result));
81    mergeTotal(resultArr, fixTotal(this.result));
82    this.fixedDeal(resultArr, threadStatesParam.traceId);
83    this.threadClick(resultArr);
84    this.threadStatesTbl!.recycleDataSource = resultArr;
85    this.threadStatesTbl!.loading = false;
86  }
87
88  /**
89   * 递归整理数据小数位
90   */
91  fixedDeal(arr: Array<RunningFreqData>, traceId?: string | null): void {
92    if (arr == undefined) {
93      return;
94    }
95    const TIME_MUTIPLE: number = 1000000;
96    // KHz->MHz  *  ns->ms
97    const CONS_MUTIPLE: number = 1000000000;
98    const FREQ_MUTIPLE: number = 1000;
99    const MIN_PERCENT: number = 2;
100    const MIN_FREQ: number = 3;
101    for (let i = 0; i < arr.length; i++) {
102      let trackId: number;
103      // 若存在空位元素则进行删除处理
104      if (arr[i] === undefined) {
105        arr.splice(i, 1);
106        i--;
107        continue;
108      }
109      if (arr[i].thread?.indexOf('P') !== -1) {
110        trackId = Number(arr[i].thread?.slice(1)!);
111        arr[i].thread = `${Utils.getInstance().getProcessMap(traceId).get(trackId) || 'Process'} ${trackId}`;
112      } else if (arr[i].thread === 'summary data') {
113      } else {
114        trackId = Number(arr[i].thread!.split('_')[1]);
115        arr[i].thread = `${Utils.getInstance().getThreadMap(traceId).get(trackId) || 'Thread'} ${trackId}`;
116      }
117      if (arr[i].cpu < 0) {
118        // @ts-ignore
119        arr[i].cpu = '';
120      }
121      // @ts-ignore
122      if (arr[i].frequency < 0) {
123        arr[i].frequency = '';
124      }
125      // @ts-ignore
126      arr[i].percent = arr[i].percent.toFixed(MIN_PERCENT);
127      // @ts-ignore
128      arr[i].dur = (arr[i].dur / TIME_MUTIPLE).toFixed(MIN_FREQ);
129      // @ts-ignore
130      arr[i].consumption = (arr[i].consumption / CONS_MUTIPLE).toFixed(MIN_FREQ);
131      if (arr[i].frequency !== '') {
132        if (arr[i].frequency === 'unknown') {
133          arr[i].frequency = 'unknown';
134        } else {
135          arr[i].frequency = Number(arr[i].frequency) / FREQ_MUTIPLE;
136        }
137      }
138      this.fixedDeal(arr[i].children!, traceId);
139    }
140  }
141
142  /**
143   * 表头点击事件
144   */
145  private threadClick(data: Array<RunningFreqData>): void {
146    let labels = this.threadStatesTbl?.shadowRoot?.querySelector('.th > .td')!.querySelectorAll('label');
147    if (labels) {
148      for (let i = 0; i < labels.length; i++) {
149        let label = labels[i].innerHTML;
150        labels[i].addEventListener('click', (e) => {
151          if (label.includes('Process') && i === 0) {
152            this.threadStatesTbl!.setStatus(data, false);
153            this.threadStatesTbl!.recycleDs = this.threadStatesTbl!.meauseTreeRowElement(data, RedrawTreeForm.Retract);
154          } else if (label.includes('Thread') && i === 1) {
155            for (let item of data) {
156              // @ts-ignore
157              item.status = true;
158              if (item.children !== undefined && item.children.length > 0) {
159                this.threadStatesTbl!.setStatus(item.children, false);
160              }
161            }
162            this.threadStatesTbl!.recycleDs = this.threadStatesTbl!.meauseTreeRowElement(data, RedrawTreeForm.Retract);
163          } else if (label.includes('CPU') && i === 2) {
164            this.threadStatesTbl!.setStatus(data, true);
165            this.threadStatesTbl!.recycleDs = this.threadStatesTbl!.meauseTreeRowElement(data, RedrawTreeForm.Expand);
166          }
167        });
168      }
169    }
170  }
171
172  /**
173   *
174   * @param arr 待整理的数组,会经过递归取到最底层的数据
175   */
176  recursion(arr: Array<RunningFreqData>): void {
177    for (let idx = 0; idx < arr.length; idx++) {
178      if (arr[idx].cpu === -1) {
179        this.result.push(arr[idx]);
180      }
181      if (arr[idx].children) {
182        this.recursion(arr[idx].children!);
183      } else {
184        this.result.push(arr[idx]);
185      }
186    }
187  }
188
189  initElements(): void {
190    this.threadStatesTbl = this.shadowRoot?.querySelector<LitTable>('#tb-running-percent');
191  }
192  connectedCallback(): void {
193    super.connectedCallback();
194    resizeObserver(this.parentElement!, this.threadStatesTbl!);
195  }
196  initHtml(): string {
197    return `
198        <style>
199        :host{
200            padding: 10px 10px;
201            display: flex;
202            flex-direction: column;
203        }
204        </style>
205        <lit-table id="tb-running-percent" style="height: auto; overflow-x:auto;width:calc(100vw - 270px)" tree>
206            <lit-table-column class="running-percent-column" width="320px" title="Process/Thread/CPU" data-index="thread" key="thread" align="flex-start" retract>
207            </lit-table-column>
208            <lit-table-column class="running-percent-column" width="120px" title="CPU" data-index="cpu" key="cpu" align="flex-start">
209            </lit-table-column>
210            <lit-table-column class="running-percent-column" width="240px" title="Consumption" data-index="consumption" key="consumption" align="flex-start">
211            </lit-table-column>
212            <lit-table-column class="running-percent-column" width="200px" title="Freq(MHz)" data-index="frequency" key="frequency" align="flex-start">
213            </lit-table-column>
214            <lit-table-column class="running-percent-column" width="200px" title="duration(ms)" data-index="dur" key="dur" align="flex-start">
215            </lit-table-column>
216            <lit-table-column class="running-percent-column" width="240px" title="Percent(%)" data-index="percent" key="percent" align="flex-start">
217            </lit-table-column>
218        </lit-table>
219        `;
220  }
221}
222
223/**
224 *
225 * @param runData 数据库查询上来的running数据,此函数会将数据整理成map结构,分组规则:'pid_tid'为键,running数据数字为值
226 * @returns 返回map对象及所有running数据的dur和,后续会依此计算百分比
227 */
228function orgnazitionMap(
229  runData: Array<RunningData>,
230  cpuFreqData: Array<CpuFreqData>,
231  args: {
232    leftNs: number,
233    rightNs: number,
234    cpuArray: number[]
235  }
236): Array<RunningFreqData> {
237  let result: Map<string, Array<RunningData>> = new Map();
238  let sum: number = 0;
239  // 循环分组
240  for (let i = 0; i < runData.length; i++) {
241    let mapKey: string = runData[i].pid + '_' + runData[i].tid;
242    // 该running数据若在map对象中不包含其'pid_tid'构成的键,则新加key-value值
243    if (!result.has(mapKey)) {
244      result.set(mapKey, new Array());
245    }
246    // 整理左右边界数据问题, 因为涉及多线程,所以必须放在循环里
247    if (runData[i].ts < args.leftNs && runData[i].ts + runData[i].dur > args.leftNs) {
248      runData[i].dur = runData[i].ts + runData[i].dur - args.leftNs;
249      runData[i].ts = args.leftNs;
250    }
251    if (runData[i].ts + runData[i].dur > args.rightNs) {
252      runData[i].dur = args.rightNs - runData[i].ts;
253    }
254    // 特殊处理数据表中dur为负值的情况
255    if (runData[i].dur < 0) {
256      runData[i].dur = 0;
257    }
258    // 分组整理数据
259    result.get(mapKey)?.push({
260      pid: runData[i].pid,
261      tid: runData[i].tid,
262      cpu: runData[i].cpu,
263      dur: runData[i].dur,
264      ts: runData[i].ts,
265    });
266    sum += runData[i].dur;
267  }
268  return dealCpuFreqData(cpuFreqData, result, sum, args.cpuArray);
269}
270
271/**
272 *
273 * @param cpuFreqData cpu频点数据的数组
274 * @param result running数据的map对象
275 * @param sum running数据的时间和
276 * @returns 返回cpu频点数据map,'pid_tid'为键,频点算力值数据的数组为值
277 */
278function dealCpuFreqData(
279  cpuFreqData: Array<CpuFreqData>,
280  result: Map<string, Array<RunningData>>,
281  sum: number,
282  cpuList: number[]
283): Array<RunningFreqData> {
284  let runningFreqData: Map<string, Array<RunningFreqData>> = new Map();
285  result.forEach((item, key) => {
286    let resultList: Array<RunningFreqData> = new Array();
287    for (let i = 0; i < item.length; i++) {
288      for (let j = 0; j < cpuFreqData.length; j++) {
289        let flag: number;
290        if (item[i].cpu === cpuFreqData[j].cpu) {
291          // 当running状态数据的开始时间大于频点数据开始时间,小于频点结束时间。且running数据的持续时间小于频点结束时间减去running数据开始时间的差值的情况
292          if (
293            item[i].ts > cpuFreqData[j].ts &&
294            item[i].ts < cpuFreqData[j].ts + cpuFreqData[j].dur &&
295            item[i].dur < cpuFreqData[j].ts + cpuFreqData[j].dur - item[i].ts
296          ) {
297            resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 1))!);
298            item.splice(i, 1);
299            i--;
300            break;
301          }
302          if (
303            item[i].ts > cpuFreqData[j].ts &&
304            item[i].ts < cpuFreqData[j].ts + cpuFreqData[j].dur &&
305            item[i].dur >= cpuFreqData[j].ts + cpuFreqData[j].dur - item[i].ts
306          ) {
307            // 当running状态数据的开始时间大于频点数据开始时间,小于频点结束时间。且running数据的持续时间大于等于频点结束时间减去running数据开始时间的差值的情况
308            resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 2))!);
309          }
310          // 当running状态数据的开始时间小于等于频点数据开始时间,结束时间大于频点开始时间。且running数据的持续时间减去频点数据开始时间的差值小于频点数据持续时间的情况
311          if (
312            item[i].ts <= cpuFreqData[j].ts &&
313            item[i].ts + item[i].dur > cpuFreqData[j].ts &&
314            item[i].dur + item[i].ts - cpuFreqData[j].ts < cpuFreqData[j].dur
315          ) {
316            resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 3))!);
317            item.splice(i, 1);
318            i--;
319            break;
320          }
321          if (
322            item[i].ts <= cpuFreqData[j].ts &&
323            item[i].ts + item[i].dur > cpuFreqData[j].ts &&
324            item[i].dur + item[i].ts - cpuFreqData[j].ts >= cpuFreqData[j].dur
325          ) {
326            // 当running状态数据的开始时间小于等于频点数据开始时间,结束时间大于频点开始时间。且running数据的持续时间减去频点数据开始时间的差值大于等于频点数据持续时间的情况
327            resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 4))!);
328          }
329          if (item[i].ts <= cpuFreqData[j].ts && item[i].ts + item[i].dur <= cpuFreqData[j].ts) {
330            // 当running状态数据的开始时间小于等于频点数据开始时间,结束时间小于等于频点开始时间的情况
331            resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 5))!);
332            item.splice(i, 1);
333            i--;
334            break;
335          }
336        } else {
337          if (!cpuList.includes(item[i].cpu)) {
338            resultList.push(returnObj(item[i], cpuFreqData[j], sum, (flag = 5))!);
339            item.splice(i, 1);
340            i--;
341            break;
342          }
343        }
344      }
345    }
346    runningFreqData.set(key, mergeSameData(resultList));
347  });
348  return dealTree(runningFreqData);
349}
350
351/**
352 *
353 * @param item running数据
354 * @param cpuFreqData 频点数据
355 * @param sum running总和
356 * @param flag 标志位,根据不同值返回不同结果
357 * @returns 返回新的对象
358 */
359function returnObj(
360  item: RunningData,
361  cpuFreqData: CpuFreqData,
362  sum: number,
363  flag: number
364): RunningFreqData | undefined {
365  const PERCENT: number = 100;
366  const consumption: number = (
367    SpSegmentationChart.freqInfoMapData.size > 0
368      ? SpSegmentationChart.freqInfoMapData.get(item.cpu)?.get(cpuFreqData.value)
369      : cpuFreqData.value
370  )!;
371  let result: RunningFreqData | undefined;
372  switch (flag) {
373    case 1:
374      result = {
375        thread: item.pid + '_' + item.tid,
376        consumption: consumption * item.dur,
377        cpu: item.cpu,
378        frequency: cpuFreqData.value,
379        dur: item.dur,
380        percent: (item.dur / sum) * PERCENT,
381      };
382    case 2:
383      result = {
384        thread: item.pid + '_' + item.tid,
385        consumption: consumption * (cpuFreqData.ts + cpuFreqData.dur - item.ts),
386        cpu: item.cpu,
387        frequency: cpuFreqData.value,
388        dur: cpuFreqData.ts + cpuFreqData.dur - item.ts,
389        percent: ((cpuFreqData.ts + cpuFreqData.dur - item.ts) / sum) * PERCENT,
390      };
391    case 3:
392      result = {
393        thread: item.pid + '_' + item.tid,
394        consumption: consumption * (item.dur + item.ts - cpuFreqData.ts),
395        cpu: item.cpu,
396        frequency: cpuFreqData.value,
397        dur: item.dur + item.ts - cpuFreqData.ts,
398        percent: ((item.dur + item.ts - cpuFreqData.ts) / sum) * PERCENT,
399      };
400    case 4:
401      result = {
402        thread: item.pid + '_' + item.tid,
403        consumption: consumption * cpuFreqData.dur,
404        cpu: item.cpu,
405        frequency: cpuFreqData.value,
406        dur: cpuFreqData.dur,
407        percent: (cpuFreqData.dur / sum) * PERCENT,
408      };
409    case 5:
410      result = {
411        thread: item.pid + '_' + item.tid,
412        consumption: 0,
413        cpu: item.cpu,
414        frequency: 'unknown',
415        dur: item.dur,
416        percent: (item.dur / sum) * PERCENT,
417      };
418  }
419  return result;
420}
421
422/**
423 *
424 * @param resultList 单线程内running数据与cpu频点数据整合成的数组
425 */
426function mergeSameData(resultList: Array<RunningFreqData>): Array<RunningFreqData> {
427  let cpuFreqArr: Array<RunningFreqData> = [];
428  let cpuArr: Array<number> = [];
429  //合并同一线程内,当运行所在cpu和频点相同时,dur及percent进行累加求和
430  for (let i = 0; i < resultList.length; i++) {
431    if (!cpuArr.includes(resultList[i].cpu)) {
432      cpuArr.push(resultList[i].cpu);
433      cpuFreqArr.push(creatNewObj(resultList[i].cpu));
434    }
435    for (let j = i + 1; j < resultList.length; j++) {
436      if (resultList[i].cpu === resultList[j].cpu && resultList[i].frequency === resultList[j].frequency) {
437        resultList[i].dur += resultList[j].dur;
438        resultList[i].percent += resultList[j].percent;
439        resultList[i].consumption += resultList[j].consumption;
440        resultList.splice(j, 1);
441        j--;
442      }
443    }
444    cpuFreqArr.find(function (item) {
445      if (item.cpu === resultList[i].cpu) {
446        item.children?.push(resultList[i]);
447        item.children?.sort((a, b) => b.consumption - a.consumption);
448        item.dur += resultList[i].dur;
449        item.percent += resultList[i].percent;
450        item.consumption += resultList[i].consumption;
451        item.thread = resultList[i].thread;
452      }
453    });
454  }
455  cpuFreqArr.sort((a, b) => a.cpu - b.cpu);
456  return cpuFreqArr;
457}
458
459/**
460 *
461 * @param params cpu层级的数据
462 * @returns 整理好的进程级数据
463 */
464function dealTree(params: Map<string, Array<RunningFreqData>>): Array<RunningFreqData> {
465  let result: Array<RunningFreqData> = [];
466  params.forEach((item, key) => {
467    let process: RunningFreqData = creatNewObj(-1, false);
468    let thread: RunningFreqData = creatNewObj(-2);
469    for (let i = 0; i < item.length; i++) {
470      thread.children?.push(item[i]);
471      thread.dur += item[i].dur;
472      thread.percent += item[i].percent;
473      thread.consumption += item[i].consumption;
474      thread.thread = item[i].thread;
475    }
476    process.children?.push(thread);
477    process.dur += thread.dur;
478    process.percent += thread.percent;
479    process.consumption += thread.consumption;
480    process.thread = process.thread! + key.split('_')[0];
481    result.push(process);
482  });
483  for (let i = 0; i < result.length; i++) {
484    for (let j = i + 1; j < result.length; j++) {
485      if (result[i].thread === result[j].thread) {
486        result[i].children?.push(result[j].children![0]);
487        result[i].dur += result[j].dur;
488        result[i].percent += result[j].percent;
489        result[i].consumption += result[j].consumption;
490        result.splice(j, 1);
491        j--;
492      }
493    }
494  }
495  return result;
496}
497
498/**
499 *
500 * @param cpu 根据cpu值创建层级结构,cpu < 0为线程、进程层级,其余为cpu层级
501 * @returns
502 */
503function creatNewObj(cpu: number, flag: boolean = true): RunningFreqData {
504  return {
505    thread: flag ? '' : 'P',
506    consumption: 0,
507    cpu: cpu,
508    frequency: -1,
509    dur: 0,
510    percent: 0,
511    children: [],
512  };
513}
514
515/**
516 *
517 * @param arr 需要整理汇总的频点级数据
518 * @returns 返回一个total->cpu->频点的三级树结构数组
519 */
520function fixTotal(arr: Array<RunningFreqData>): Array<RunningFreqData> {
521  let result: Array<RunningFreqData> = [];
522  let flag: number = -1;
523  // 数据入参的情况是,第一条为进程数据,其后是该进程下所有线程的数据。以进程数据做分割
524  for (let i = 0; i < arr.length; i++) {
525    // 判断如果是进程数据,则将其children的数组清空,并以其作为最顶层数据
526    if (arr[i].thread?.indexOf('P') !== -1) {
527      arr[i].children = [];
528      arr[i].thread = arr[i].thread + '-summary data';
529      result.push(arr[i]);
530      // 标志判定当前数组的长度,也可用.length判断
531      flag++;
532    } else {
533      // 非进程数据会进入到else中,去判断当前线程数据的cpu分组是否存在,不存在则进行创建
534      if (result[flag].children![arr[i].cpu] === undefined) {
535        result[flag].children![arr[i].cpu] = {
536          thread: 'summary data',
537          consumption: 0,
538          cpu: arr[i].cpu,
539          frequency: -1,
540          dur: 0,
541          percent: 0,
542          children: [],
543        };
544      }
545      // 每有一条数据要放到cpu分组下时,则将该cpu分组的各项数据累和
546      result[flag].children![arr[i].cpu].consumption += arr[i].consumption;
547      result[flag].children![arr[i].cpu].dur += arr[i].dur;
548      result[flag].children![arr[i].cpu].percent += arr[i].percent;
549      // 查找当前cpu分组下是否存在与当前数据的频点相同的数据,返回相同数据的索引值
550      let index: number = result[flag].children![arr[i].cpu].children?.findIndex(
551        (item) => item.frequency === arr[i].frequency
552      )!;
553      // 若存在相同频点的数据,则进行合并,不同直接push
554      if (index === -1) {
555        arr[i].thread = 'summary data';
556        result[flag].children![arr[i].cpu].children?.push(arr[i]);
557      } else {
558        result[flag].children![arr[i].cpu].children![index].consumption += arr[i].consumption;
559        result[flag].children![arr[i].cpu].children![index].dur += arr[i].dur;
560        result[flag].children![arr[i].cpu].children![index].percent += arr[i].percent;
561      }
562    }
563  }
564  return result;
565}
566
567/**
568 *
569 * @param arr1 前次整理好的区分线程的数据
570 * @param arr2 不区分线程的Total数据
571 */
572function mergeTotal(arr1: Array<RunningFreqData>, arr2: Array<RunningFreqData>): void {
573  for (let i = 0; i < arr1.length; i++) {
574    const num: number = arr2.findIndex((item) => item.thread?.includes(arr1[i].thread!));
575    arr2[num].thread = 'summary data';
576    arr1[i].children?.unshift(arr2[num]);
577    arr2.splice(num, 1);
578  }
579}
580