1#!/usr/bin/env python3
2# -*- coding: UTF-8 -*-
3
4# Copyright (c) 2023 Huawei Device Co., Ltd.
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17import copy
18import os
19import stat
20import re
21import argparse
22import csv
23import logging
24
25# global variables
26WORKER_KEY = ["ffrt_worker-", "ffrtwk", "ffrt_rtg-"]
27
28
29def extract_thread_name(log):
30    """
31    extract thread name from trace line
32    """
33    return log.strip().split(' ')[0]
34
35
36def extract_thread_id(log):
37    """
38    extract tid from trace line
39    """
40    if len(log.strip().split(' ')[0].split('-')) <= 1:
41        return 0
42
43    return int(log.strip().split(' ')[0].split('-')[-1])
44
45
46def extract_process_id(log):
47    """
48    extract pid from trace line
49    """
50    m = re.search(r"\(\s*\d+\) \[", log)
51    if m is None:
52        return 0
53
54    match = m.group()
55    if '-' in match:
56        return 0
57
58    return int(match.split(')')[0].lstrip('('))
59
60
61def extract_cpu_id(log):
62    """
63    extract #cpu from trace line
64    """
65    m = re.search(r"\) \[.*\]", log)
66    if m is None:
67        return -1
68
69    match = m.group()
70
71    return int(match.split(']')[0].split('[')[-1])
72
73
74def extract_timestamp(log):
75    """
76    extract timestamp(us) from trace line
77    """
78    m = re.search(r" (\d+)\.(\d+): ", log)
79    if m is None:
80        return 0
81
82    match = m.group()
83
84    return int(match.strip().split('.')[0]) * int(1e6) + int(match.strip().rstrip(':').split('.')[-1])
85
86
87def extract_trace_marker_task(log):
88    """
89    extract ffrt task marker from trace line
90    """
91    m = re.search(r" [FB]\|(\d+)\|(.+)\|(\d+)", log)
92
93    if m is not None:
94        match = m.group()
95
96        return (match.split('|')[-2], int(match.split('|')[-1]))
97
98    m = re.search(r" F\|(\d+)\|(\S+)\s(\d+)", log)
99
100    if m is not None:
101        match = m.group()
102
103        return (match.split('|')[-1].split(' ')[0], int(match.split(' ')[-1]))
104
105    return (False, False)
106
107
108def extract_switch_info(log):
109    """
110    parse sched_switch log
111    """
112    switch_info = {}
113
114    switch_info["cpu"] = extract_cpu_id(log)
115    switch_info["timestamp"] = extract_timestamp(log)
116
117    index = log.index("prev_comm=")
118    switch_info["prev_tname"] = log[index:].split("prev_pid=")[0].split('=')[-1].rstrip()
119
120    index = log.index("prev_pid=")
121    switch_info["prev_tid"] = int(log[index:].split(' ')[0].split('=')[-1])
122
123    index = log.index("prev_state=")
124    switch_info["prev_state"] = log[index:].split(' ')[0].split('=')[-1]
125
126    index = log.index("next_comm=")
127    switch_info["next_tname"] = log[index:].split("next_pid=")[0].split('=')[-1].rstrip()
128
129    index = log.index("next_pid=")
130    switch_info["next_tid"] = int(log[index:].split(' ')[0].split('=')[-1])
131
132    return switch_info
133
134
135def extract_active_pid_and_switch_log(logs):
136    """
137    extract active processes in trace with corresponding switch logs
138    """
139    active_process_map = {}
140    switch_log_map = {}
141    ffrt_process = []
142
143    for log in logs:
144        if " sched_" in log or " tracing_mark_write" in log:
145            pid = extract_process_id(log)
146            if pid != 0 and pid not in active_process_map.keys():
147                active_process_map[pid] = {}
148
149            tn = log[:log.find(" (")].strip()
150            ti = int(tn.split('-')[-1])
151            if ti != 0 and ti not in active_process_map[pid].keys():
152                active_process_map[pid][ti] = tn
153
154            if "sched_switch:" in log:
155                switch_info = extract_switch_info(log)
156                if switch_info["prev_tid"] not in switch_log_map.keys():
157                    switch_log_map[switch_info["prev_tid"]] = []
158                switch_log_map[switch_info["prev_tid"]].append(switch_info)
159
160                if switch_info["next_tid"] not in switch_log_map.keys():
161                    switch_log_map[switch_info["next_tid"]] = []
162                switch_log_map[switch_info["next_tid"]].append(switch_info)
163
164                if "ffrt" in switch_info["prev_tname"] and pid not in ffrt_process:
165                    ffrt_process.append(pid)
166
167                if pid != 0 and switch_info["prev_tname"] not in active_process_map[pid][ti]:
168                    active_process_map[pid][ti] = \
169                        "%s-%d" % (switch_info["prev_tname"], switch_info["prev_tid"])
170
171    return ffrt_process, active_process_map, switch_log_map
172
173
174def parse_thread_trace(switch_logs, tid):
175    """
176    parser trace record of specific thread:
177        1)sched_waking:waking up thread
178        2)sched_blocked_reason:uninterruptible sleep
179        3)sched_wakeup:thread waked up
180        4)sched_switch:thread switch out/in
181    note that trace file may lose some logs during recording
182    therefore approximate esimation is used in statistics
183    """
184    statistics = {
185        "running": {
186            "duration": 0, "occurrence": 0, "average": 0.
187        },
188        "cpu": {},
189        "switch_out": {}
190    }
191
192    prev_timestamp = None
193    prev_running = None
194
195    for switch_log in switch_logs:
196        if switch_log["next_tid"] == tid:
197            statistics["running"]["occurrence"] += 1
198            if prev_running == "running":
199                continue
200            prev_timestamp = switch_log["timestamp"]
201            prev_running = "running"
202        elif switch_log["prev_tid"] == tid:
203            curr_timestamp = switch_log["timestamp"]
204            if prev_running == "running":
205                statistics["running"]["duration"] += curr_timestamp - prev_timestamp
206                if switch_log["cpu"] not in statistics["cpu"].keys():
207                    statistics["cpu"][switch_log["cpu"]] = 0
208                statistics["cpu"][switch_log["cpu"]] += curr_timestamp - prev_timestamp
209            prev_timestamp = curr_timestamp
210            prev_running = "idle"
211            if switch_log["prev_state"] not in statistics["switch_out"].keys():
212                statistics["switch_out"][switch_log["prev_state"]] = 0
213            statistics["switch_out"][switch_log["prev_state"]] += 1
214
215    statistics["running"]["average"] = float(statistics["running"]["duration"]) / float(
216        statistics["running"]["occurrence"]) if statistics["running"]["occurrence"] != 0 else 0.
217
218    return statistics
219
220
221def generate_counter_info(suffix, task_records, next_status, gid, pid_counters_dict, pid):
222    """
223    generate trace counter marker based on task status transition
224    """
225    infos = []
226    prev_counter = task_records[gid]["status"] + "_task"
227    next_counter = next_status + "_task"
228
229    if next_status != "finish":
230        pid_counters_dict[next_counter] += 1
231        info = suffix + "C|" + str(pid) + "|" + next_counter + "|" + str(pid_counters_dict[next_counter]) + '\n'
232        infos.append(info)
233
234    if next_status != "pending":
235        pid_counters_dict[prev_counter] -= 1
236        info = suffix + "C|" + str(pid) + "|" + prev_counter + "|" + str(pid_counters_dict[prev_counter]) + '\n'
237        infos.append(info)
238
239    task_records[gid]["status"] = next_status
240
241    return infos
242
243
244def parse_and_convert_task_trace(logs, pid):
245    """
246    parser trace record of ffrt tasks:
247        P:task submit
248        R:task ready
249        E:task execute
250        B:task block
251        F:task execute finished
252    convert task execute record from async marker to sync marker
253    """
254    task_records = {}
255    task_infos = {}
256    submit_no_ready_tasks = {}
257    ready_no_exec_tasks = {}
258    exec_no_done_tasks = {}
259    pid_counters_dict = {
260        'total_task': 0,
261        'pending_task': 0,
262        'ready_task': 0,
263        'running_task': 0,
264        'blocked_task': 0
265    }
266
267    pid_keyword = "%d) " % pid
268    trace_end_keyword = " E|%d" % pid
269
270    lineno = 0
271    remove_trace_end = False
272    logs_supplement = []
273    for log in logs:
274        lineno += 1
275
276        if pid_keyword not in log or "tracing_mark_write: " not in log:
277            logs_supplement.append(log)
278            continue
279
280        if remove_trace_end is True and trace_end_keyword in log:
281            remove_trace_end = False
282            continue
283
284        task_marker = extract_trace_marker_task(log)
285        if len(task_marker) == 0 or task_marker[0] is False:
286            logs_supplement.append(log)
287            continue
288
289        state = task_marker[0]
290        if "H:" in state:
291            state = state[2:]
292        gid = task_marker[1]
293        suffix = log[:log.find("tracing_mark_write: ") + len("tracing_mark_write: ")]
294
295        if "P[" in state:
296            if gid not in task_records.keys():
297                tag = state.split('[')[-1].split(']')[0]
298                task_records[gid] = {
299                    "gid": gid,
300                    "tag": tag,
301                    "submit": extract_timestamp(log),
302                    "ready": None,
303                    "exec": None,
304                    "cowait": [],
305                    "costart": [],
306                    "done": None,
307                    "exec_duration": 0,
308                    "cowait_duration": 0,
309                    "exec_pids": [],
310                    "prev_tname": None,
311                    "status": "pending",
312                }
313
314            # replace async trace begin with trace counter
315            pid_counters_dict['total_task'] += 1
316            line_total_task = "%sC|%d|total_task|%s\n" % (suffix, pid, str(pid_counters_dict['total_task']))
317            logs_supplement.append(line_total_task)
318
319            infos = generate_counter_info(suffix, task_records, "pending", gid, pid_counters_dict, pid)
320            for info in infos:
321                logs_supplement.append(info)
322
323            remove_trace_end = True
324
325            continue
326
327        if state == "R":
328            if gid in task_records.keys():
329                if task_records[gid]["ready"] is None:
330                    task_records[gid]["ready"] = extract_timestamp(log)
331
332                # replace async trace begin with trace counter
333                infos = generate_counter_info(suffix, task_records, "ready", gid, pid_counters_dict, pid)
334                for info in infos:
335                    logs_supplement.append(info)
336
337            continue
338
339        if "FFRT::[" in state:
340            if gid in task_records.keys():
341                timestamp = extract_timestamp(log)
342                tid = extract_thread_id(log)
343                if task_records[gid]["exec"] is None:
344                    task_records[gid]["exec"] = timestamp
345                task_records[gid]["costart"].append(timestamp)
346                task_records[gid]["exec_pids"].append(tid)
347                task_records[gid]["prev_tname"] = extract_thread_name(log)
348                if len(task_records[gid]["cowait"]) > 0:
349                    task_records[gid]["cowait_duration"] += task_records[gid]["costart"][-1] - task_records[gid]["cowait"][-1]
350
351                # replace async trace begin with trace counter
352                infos = generate_counter_info(suffix, task_records, "running", gid, pid_counters_dict, pid)
353                for info in infos:
354                    logs_supplement.append(info)
355            logs_supplement.append(log)
356
357            continue
358
359        if state == "B":
360            if gid in task_records.keys():
361                task_records[gid]["cowait"].append(extract_timestamp(log))
362                if len(task_records[gid]["costart"]) > 0:
363                    task_records[gid]["exec_duration"] += task_records[gid]["cowait"][-1] - task_records[gid]["costart"][-1]
364
365                # replace async trace begin with trace counter
366                infos = generate_counter_info(suffix, task_records, "blocked", gid, pid_counters_dict, pid)
367                for info in infos:
368                    logs_supplement.append(info)
369
370            continue
371
372        if state == "F":
373            if gid in task_records.keys():
374                timestamp = extract_timestamp(log)
375                task_records[gid]["done"] = timestamp
376                if len(task_records[gid]["costart"]) > 0:
377                    task_records[gid]["exec_duration"] += timestamp - task_records[gid]["costart"][-1]
378
379                # replace async trace begin with trace counter
380                infos = generate_counter_info(suffix, task_records, "finish", gid, pid_counters_dict, pid)
381                for info in infos:
382                    logs_supplement.append(info)
383
384            continue
385
386        logs_supplement.append(log)
387
388    for task in task_records.values():
389        if task["tag"] not in task_infos.keys():
390            task_infos[task["tag"]] = []
391
392        # check suspect tasks, i.e. not ready, not execute, not finish
393        if task["ready"] is None:
394            if task["exec"] is not None or task["done"] is not None:
395                task["submit_ready"] = "lost"
396            else:
397                task["submit_ready"] = None
398                if task["tag"] not in submit_no_ready_tasks.keys():
399                    submit_no_ready_tasks[task["tag"]] = []
400                submit_no_ready_tasks[task["tag"]].append(task["gid"])
401        else:
402            task["submit_ready"] = task["ready"] - task["submit"]
403
404        if task["exec"] is None:
405            if task["ready"] is None:
406                task["ready_exec"] = None
407            elif task["done"] is not None:
408                task["ready_exec"] = "lost"
409            else:
410                task["ready_exec"] = None
411                if task["tag"] not in ready_no_exec_tasks.keys():
412                    ready_no_exec_tasks[task["tag"]] = []
413                ready_no_exec_tasks[task["tag"]].append(task["gid"])
414        else:
415            if task["ready"] is None:
416                task["ready_exec"] = "lost"
417            else:
418                task["ready_exec"] = task["exec"] - task["ready"]
419
420        if task["done"] is None:
421            task["exec_done"] = None
422            if task["exec"] is not None:
423                if task["tag"] not in exec_no_done_tasks.keys():
424                    exec_no_done_tasks[task["tag"]] = []
425                exec_no_done_tasks[task["tag"]].append(task["gid"])
426        else:
427            if task["exec"] is None:
428                task["exec_done"] = "lost"
429            else:
430                task["exec_done"] = task["done"] - task["exec"]
431
432        task_infos[task["tag"]].append(task)
433
434    return task_infos, submit_no_ready_tasks, ready_no_exec_tasks, exec_no_done_tasks, logs_supplement
435
436
437def process_trace(logs, pid, active_process_map, switch_log_map):
438    """
439    process trace data, generate thread info and task info
440    """
441    tids = list(active_process_map[pid].keys())
442    tnames = list(active_process_map[pid].values())
443
444    data = {
445        "total": {},
446        "switch": {
447            "worker": {},
448            "non-worker": {}
449        },
450        "cpu": {
451            "worker": {},
452            "non-worker": {}
453        },
454        "thread": {
455            "worker": {
456                "S": {}, "T": {}
457            },
458            "non-worker": {
459                "S": {}, "T": {}
460            },
461        },
462        "task": {
463            "infos": None,
464            "not_ready_tasks": None,
465            "not_exec_tasks": None,
466            "not_finish_taks": None
467        }
468    }
469
470    for i in range(len(tids)):
471        statistics = parse_thread_trace(switch_log_map[tids[i]], tids[i])
472
473        tname = tnames[i]
474        ttype = "worker" if any([k in tname for k in WORKER_KEY]) else "non-worker"
475
476        # save thread slices
477        data["thread"][ttype]["S"][tname] = {
478            "statistics": statistics,
479        }
480
481        for cpu, duration in statistics["cpu"].items():
482            if cpu not in data["cpu"][ttype].keys():
483                data["cpu"][ttype][cpu] = 0
484            data["cpu"][ttype][cpu] += duration
485
486            # thread running distribution
487            if tname not in data["thread"][ttype]["T"].keys():
488                data["thread"][ttype]["T"][tname] = 0
489            data["thread"][ttype]["T"][tname] += duration
490
491        for state, count in statistics["switch_out"].items():
492            if state not in data["switch"][ttype].keys():
493                data["switch"][ttype][state] = 0
494            data["switch"][ttype][state] += count
495
496    data["total"]["all_load"] = sum(data["thread"]["worker"]["T"].values()) + sum(
497        data["thread"]["non-worker"]["T"].values())
498    data["total"]["worker_load"] = sum(data["thread"]["worker"]["T"].values())
499    data["total"]["all_switch"] = sum(data["switch"]["worker"].values()) + sum(
500        data["switch"]["non-worker"].values())
501    data["total"]["worker_switch"] = sum(data["switch"]["worker"].values())
502
503    task_infos, submit_no_ready_tasks, ready_no_exec_tasks, exec_no_done_tasks, logs_supplement = \
504        parse_and_convert_task_trace(logs, pid)
505
506    data["task"]["infos"] = task_infos
507    data["task"]["not_ready_tasks"] = submit_no_ready_tasks
508    data["task"]["not_exec_tasks"] = ready_no_exec_tasks
509    data["task"]["not_finish_taks"] = exec_no_done_tasks
510
511    return data, logs_supplement
512
513
514def write_infos(out_dir, logs, data):
515    """
516    write process results
517    """
518    if not os.path.exists(out_dir):
519        os.mkdir(out_dir)
520    else:
521        del_list = os.listdir(out_dir)
522        for f in del_list:
523            file_path = os.path.join(out_dir, f)
524            if os.path.isfile(file_path):
525                os.remove(file_path)
526
527    # write recovered trace
528    if logs is not None:
529        with os.fdopen(
530                os.open(out_dir + "/trace_refine.ftrace", os.O_WRONLY | os.O_CREAT | os.O_EXCL, stat.S_IWUSR | stat.S_IRUSR),
531                'w') as file:
532            file.writelines(logs)
533            file.close()
534
535    # write summary info
536    with os.fdopen(
537            os.open(out_dir + "/summary.txt", os.O_WRONLY | os.O_CREAT | os.O_EXCL,
538                    stat.S_IWUSR | stat.S_IRUSR), 'w') as file:
539        lines = print_summary(data)
540        file.writelines(lines)
541        file.close()
542
543    # write thread info
544    for tname in data["thread"]["worker"]["S"].keys():
545        with os.fdopen(
546                os.open("%s/%s.txt" % (out_dir, tname.replace("/", "_")), os.O_WRONLY | os.O_CREAT | os.O_EXCL,
547                        stat.S_IWUSR | stat.S_IRUSR), 'w') as file:
548            statistics = data["thread"]["worker"]["S"][tname]["statistics"]
549            lines = print_hist(statistics)
550            file.writelines(lines)
551
552            switch = data["thread"]["worker"]["S"][tname]["statistics"]["switch_out"]
553            lines = print_switch(switch)
554            file.writelines(lines)
555            file.close()
556
557    # write task info
558    with os.fdopen(os.open(out_dir + "/task_info.csv", os.O_WRONLY | os.O_CREAT | os.O_EXCL, stat.S_IWUSR | stat.S_IRUSR), 'w', newline="") as file:
559        writer = csv.writer(file)
560        writer.writerow(
561            ["Label", "UID", "Submit_Timestamp", "Ready_Timestamp", "Exec_Timestamp", "Done_Timestamp", "Submit->Ready(us)", "Ready->Exec(us)",
562             "Exec->Done(us)", "Cowait_Occurence", "Cowait_Duration(us)", "Exec_Duration(us)", "Workers"])
563        for task_name, task_info in data["task"]["infos"].items():
564            for task in task_info:
565                writer.writerow([task_name, task["gid"], "%s.%s" % (str(task["submit"])[:-6], str(task["submit"])[-6:]),
566                                 "%s.%s" % (str(task["ready"])[:-6], str(task["ready"])[-6:]),
567                                 "%s.%s" % (str(task["exec"])[:-6], str(task["exec"])[-6:]),
568                                 "%s.%s" % (str(task["done"])[:-6], str(task["done"])[-6:]), str(task["submit_ready"]),
569                                 str(task["ready_exec"]), str(task["exec_done"]), len(task["cowait"]),
570                                 str(task["cowait_duration"]), str(task["exec_duration"]),
571                                 str(list(set(task["exec_pids"])))])
572        file.close()
573
574    return
575
576
577def print_hist(s):
578    lines = []
579    lines.append("State                 |  Duration        |  Occurrence      |  Average\n")
580    lines.append("------------------------------------------------------------------------\n")
581    for itm in ["running"]:
582        if s[itm]["occurrence"] > 0:
583            lines.append("%-22s|  %-13s us|  %-16d|  %.0f us\n" % (
584                itm, str(s[itm]["duration"]), s[itm]["occurrence"], s[itm]["average"]))
585    lines.append("------------------------------------------------------------------------\n\n\n")
586    return lines
587
588
589def print_switch(w):
590    lines = []
591    lines.append("Switch Type           |  Switch Times / Total Times\n")
592    lines.append("------------------------------------------------------\n")
593    t = sum(w.values())
594    for k in w.keys():
595        lines.append("%-22s|    %10d / %-6d\n" % (k, w[k], t))
596    lines.append("------------------------------------------------------\n\n\n")
597    return lines
598
599
600def print_task_info(task_name, tasks):
601    lines = []
602    lines.append("Task Label: " + task_name + ", Total Count: " + str(len(tasks)) + "\n\n")
603    lines.append(" gid     |  submit_tstamp |  submit_ready  |  ready_exec  |  exec_done   | cowait_cnt |  cowait_duration  |  exec_duration  |  exec_tids                              \n")
604    lines.append("-------------------------------------------------------------------------------------------------------------------------------------------------------------------\n")
605
606    for task in tasks:
607        timestamp = str(task["submit"])
608        lines.append(" %-6d  |  %-12s  |  %-8sus    |  %-8sus  |  %-8sus  |  %-8d  |  %-10dus     |  %-10dus   | %-48s\n" % (
609                        task["gid"], "%s.%s" % (timestamp[:-6], timestamp[-6:]),
610                        str(task["submit_ready"]), str(task["ready_exec"]), str(task["exec_done"]),
611                        len(task["cowait"]), task["cowait_duration"], task["exec_duration"],
612                        str(list(set(task["exec_pids"])))))
613    lines.append("-------------------------------------------------------------------------------------------------------------------------------------------------------------------")
614    return lines
615
616
617def print_summary(data):
618    lines = []
619
620    lines.append("Summary:\n")
621    lines.append("\n1) Suspect Tasks:\n\n")
622    lines.append("tasks_not_ready:" + str(data["task"]["not_ready_tasks"]) + "\n")
623    lines.append("tasks_not_execute:" + str(data["task"]["not_exec_tasks"]) + "\n")
624    lines.append("tasks_not_finish:" + str(data["task"]["not_finish_taks"]) + "\n")
625    lines.append("\n------------------------------------------------------------------------------------------\n\n")
626    lines.append("2) Thread Overview:\n\n")
627    lines.append("%-16s |   %3d worker    | %3d non-worker  | total            |\n" % (
628        "thread num", len(data["thread"]["worker"]["T"].keys()), len(data["thread"]["non-worker"]["T"].keys())))
629    lines.append("%-16s |      %3.0f%%       |      %3.0f%%       | %-13d us |\n" % (
630        "load ratio",
631        100.0 * data["total"]["worker_load"] / data["total"]["all_load"] if
632        data["total"]["all_load"] != 0 else 0,
633        100.0 * (data["total"]["all_load"] - data["total"]["worker_load"]) / data["total"]["all_load"] if
634        data["total"]["all_load"] != 0 else 0,
635        data["total"]["all_load"]))
636    lines.append("%-16s |      %3.0f%%       |      %3.0f%%       | %-14d   |\n" % (
637        "context switch",
638        100.0 * data["total"]["worker_switch"] / data["total"]["all_switch"] if
639        data["total"]["all_switch"] != 0 else 0,
640        100.0 * (data["total"]["all_switch"] - data["total"]["worker_switch"]) / data["total"]["all_switch"] if
641        data["total"]["all_switch"] != 0 else 0,
642        data["total"]["all_switch"]))
643    lines.append("\n------------------------------------------------------------------------------------------\n\n")
644    lines.append("3) CPU Usage:\n\n")
645
646    lines.append("cpu usage: worker\n")
647    for i in sorted(data["cpu"]["worker"].items(), key=lambda kv: (kv[1], kv[0]), reverse=True):
648        lines.append("%3.0f%% core %-3d %8d us\n" % (
649            100.0 * i[1] / data["total"]["all_load"] if data["total"]["all_load"] != 0 else 0, i[0], i[1]))
650    lines.append("\ncpu usage: non-worker\n")
651    for i in sorted(data["cpu"]["non-worker"].items(), key=lambda kv: (kv[1], kv[0]), reverse=True):
652        lines.append("%3.0f%% core %-3d %8d us\n" % (
653            100.0 * i[1] / data["total"]["all_load"] if data["total"]["all_load"] != 0 else 0, i[0], i[1]))
654    lines.append("\n------------------------------------------------------------------------------------------\n\n")
655
656    lines.append("4) Thread Distribution:\n\n")
657    lines.append("thread info: %d worker load distribution\n" % len(data["thread"]["worker"]["T"].keys()))
658    for i in sorted(data["thread"]["worker"]["T"].items(), key=lambda kv: (kv[1], kv[0]), reverse=True):
659        lines.append("%3.0f%% %-24s %8d us\n" % (
660            100.0 * i[1] / data["total"]["all_load"] if data["total"]["all_load"] != 0 else 0, i[0], i[1]))
661
662    lines.append("\nthread info: %d non-worker load distribution\n" % len(data["thread"]["non-worker"]["T"].keys()))
663    for i in sorted(data["thread"]["non-worker"]["T"].items(), key=lambda kv: (kv[1], kv[0]), reverse=True):
664        lines.append("%3.0f%% %-24s %8d us\n" % (
665            100.0 * i[1] / data["total"]["all_load"] if data["total"]["all_load"] != 0 else 0, i[0], i[1]))
666    lines.append("\n------------------------------------------------------------------------------------------\n\n")
667
668    return lines
669
670
671def clean_logs(logs):
672    """
673    split logs that are mixed in same line
674    """
675    num_line = len(logs)
676
677    i = 0
678    while i < num_line:
679        log = logs[i]
680
681        if " sched_" in log or " tracing_mark_write" in log:
682            match = re.finditer("\S+-\d+\s+\(", log)
683            indexs = []
684            for m in match:
685                indexs.append(m.span()[0])
686            if len(indexs) > 1:
687                del logs[i]
688                for j in range(len(indexs)):
689                    begin = indexs[j]
690                    end = indexs[j + 1] if j + 1 < len(indexs) else len(log)
691                    logs.insert(i + j, log[begin:end])
692
693                num_line += len(indexs) - 1
694                i += len(indexs) - 1
695
696        i += 1
697
698    return
699
700
701def main():
702    parser = argparse.ArgumentParser(description="parse")
703    parser.add_argument('--file', '-f', type=str, required=True, help="input trace file path")
704    parser.add_argument('--pid', '-p', type=int, default=None, help="specify process id for trace analyzing")
705
706    args = parser.parse_args()
707
708    logging.basicConfig(filename="./ffrt_trace_process_log.txt", level=logging.DEBUG,
709                        format="%(asctime)s - %(levelname)s - %(message)s", datefmt="%m/%d/%Y %H:%M:%S %p")
710
711    if not os.path.isfile(args.file):
712        logging.error("file: %s not exist", args.file)
713        exit(1)
714
715    with open(args.file, 'r', encoding="gb18030", errors="ignore") as file:
716        logs = file.readlines()
717        clean_logs(logs)
718
719        ffrt_process, active_process_map, switch_log_map = extract_active_pid_and_switch_log(logs)
720
721        if args.pid is None:
722            if len(ffrt_process) == 0:
723                logging.error("not find any process used ffrt automatically, plz assign -pid or -p in args")
724                exit(1)
725            pid = ffrt_process[0]
726        else:
727            pid = args.pid
728
729        if pid not in active_process_map.keys():
730            logging.error("pid %d is not active in trace", pid)
731            exit(1)
732
733        data, logs_supplement = process_trace(logs, pid, active_process_map, switch_log_map)
734        logging.info("trace process done")
735        write_infos(args.file + "_result", logs_supplement, data)
736        logging.info("result saved in directory: %s", args.file)
737
738        file.close()
739
740
741if __name__ == '__main__':
742    main()