• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3#
4# Copyright (c) 2025 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#
17
18from abc import ABCMeta, abstractmethod
19from enum import IntEnum, unique
20from typing import List, Any
21import optparse
22import os
23import re
24import stat
25import struct
26import sys
27
28import parse_functions
29
30TRACE_REGEX_ASYNC = "\s*(\d+)\s+(.*?)\|\d+\|[SFC]\s+:(.*?)\s+:(.*?)\s+(.*?)\s+\]\d+\[\s+\)(\d+)\s*\(\s+(\d+?)-(.*?)\s+"
31TRACE_REGEX_SYNC = "\s*\|\d+\|E\s+:(.*?)\s+:(.*?)\s+(.*?)\s+\]\d+\[\s+\)(\d+)\s*\(\s+(\d+?)-(.*?)\s+"
32text_file = ""
33binary_file = ""
34out_file = ""
35file_dir = ""
36
37TRACE_TXT_HEADER_FORMAT = """# tracer: nop
38#
39# entries-in-buffer/entries-written: %lu/%lu   #P:%d
40#
41#                                      _-----=> irqs-off
42#                                     / _----=> need-resched
43#                                    | / _---=> hardirq/softirq
44#                                    || / _--=> preempt-depth
45#                                    ||| /     delay
46#           TASK-PID    TGID   CPU#  ||||    TIMESTAMP  FUNCTION
47#              | |        |      |   ||||       |         |
48"""
49
50
51def parse_options() -> None:
52    global text_file
53    global binary_file
54    global out_file
55    global file_dir
56
57    usage = "Usage: %prog -t text_file -o out_file or\n%prog -b binary_file -o out_file"
58    desc = "Example: %prog -t my_trace_file.htrace -o my_trace_file.systrace"
59
60    parser = optparse.OptionParser(usage=usage, description=desc)
61    parser.add_option('-t', '--text_file', dest='text_file',
62        help='Name of the text file to be parsed.', metavar='FILE')
63    parser.add_option('-b', '--binary_file', dest='binary_file',
64        help='Name of the binary file to be parsed.', metavar='FILE')
65    parser.add_option('-o', '--out_file', dest='out_file',
66        help='File name after successful parsing.', metavar='FILE')
67    parser.add_option('-d', '--file_dir', dest='file_dir',
68        help='Folder to be parsed.', metavar='FILE')
69
70    options, args = parser.parse_args()
71
72    if options.file_dir is None:
73        if options.out_file is not None:
74            out_file = options.out_file
75        else:
76            print("Error: out_file must be specified")
77            exit(-1)
78        if options.text_file is not None:
79            text_file = options.text_file
80        if options.binary_file is not None:
81            binary_file = options.binary_file
82
83        if text_file == '' and binary_file == '':
84            print("Error: You must specify a text or binary file")
85            exit(-1)
86        if text_file != '' and binary_file != '':
87            print("Error: Only one parsed file can be specified")
88            exit(-1)
89    else:
90        folder = os.path.exists(options.file_dir)
91        if not folder:
92            print("Error: file_dir does not exist")
93            exit(-1)
94        else:
95            file_dir = options.file_dir
96
97
98def parse_text_trace_file() -> None:
99    print("start processing text trace file")
100    pattern_async = re.compile(TRACE_REGEX_ASYNC)
101    pattern_sync = re.compile(TRACE_REGEX_SYNC)
102    match_num = 0
103
104    infile_flags = os.O_RDONLY
105    infile_mode = stat.S_IRUSR
106    infile = os.fdopen(os.open(text_file, infile_flags, infile_mode), "r", encoding="utf-8")
107    outfile_flags = os.O_RDWR | os.O_CREAT
108    outfile_mode = stat.S_IRUSR | stat.S_IWUSR
109    outfile = os.fdopen(os.open(out_file, outfile_flags, outfile_mode), "w+", encoding="utf-8")
110
111    for line in infile:
112        reverse_line = line[::-1]
113        trace_match_async = pattern_async.match(reverse_line)
114        trace_match_sync = pattern_sync.match(reverse_line)
115        if trace_match_async:
116            line = line.rstrip(' ')
117            pos = line.rfind(' ')
118            line = "%s%s%s" % (line[:pos], '|', line[pos + 1:])
119            match_num += 1
120        elif trace_match_sync:
121            line = "%s\n" % (line.rstrip()[:-1])
122            match_num += 1
123        outfile.write(line)
124    infile.close()
125    outfile.close()
126    print("total matched and modified lines: ", match_num)
127
128
129class DataType:
130    @staticmethod
131    def get_data_bytes(data_types: str) -> int:
132        return struct.calcsize(data_types)
133
134
135@unique
136class FieldType(IntEnum):
137    # 对HiTrace文件中逻辑划分的域
138    TRACE_FILE_HEADER = 1001
139    TRACE_EVENT_PAGE_HEADER = 1002
140    TRACE_EVENT_HEADER = 1003
141    TRACE_EVENT_CONTENT = 1004
142
143    # HiTrace文件中定义的段类型取值
144    SEGMENT_SEGMENTS = 0
145    SEGMENT_EVENTS_FORMAT = 1
146    SEGMENT_CMDLINES = 2
147    SEGMENT_TGIDS = 3
148    SEGMENT_RAW_TRACE = 4
149    SEGMENT_HEADER_PAGE = 30
150    SEGMENT_PRINTK_FORMATS = 31
151    SEGMENT_KALLSYMS = 32
152    SEGMENT_UNSUPPORT = -1
153    pass
154
155
156class Field:
157    """
158    功能描述: field为一个复合的结构,该结构包含多少item,每个item都有自已的类型
159    """
160    def __init__(self, vtype: int, size: int, vformat: str, item_types: List) -> None:
161        self.field_type = vtype
162        self.field_size = size
163        self.format = vformat
164        self.item_types = item_types
165        pass
166
167
168class TraceParseContext:
169    """
170    功能描述: 解析过程的中间数据,需要缓存到上下文中,解决段与段间的数据依赖问题
171    """
172    CONTEXT_CPU_NUM = 1
173    CONTEXT_CMD_LINES = 2
174    CONTEXT_EVENT_SIZE = 3
175    CONTEXT_CORE_ID = 5
176    CONTEXT_TIMESTAMP = 6
177    CONTEXT_TIMESTAMP_OFFSET = 7
178    CONTEXT_TID_GROUPS = 8
179    CONTEXT_EVENT_FORMAT = 9
180
181    def __init__(self) -> None:
182        self.values = {}
183        self.default_values = {
184            TraceParseContext.CONTEXT_CPU_NUM: 1,
185            TraceParseContext.CONTEXT_CMD_LINES: {},
186            TraceParseContext.CONTEXT_EVENT_SIZE: 0,
187            TraceParseContext.CONTEXT_CORE_ID: 0,
188            TraceParseContext.CONTEXT_TIMESTAMP: 0,
189            TraceParseContext.CONTEXT_TIMESTAMP_OFFSET: 0,
190            TraceParseContext.CONTEXT_TID_GROUPS: {},
191            TraceParseContext.CONTEXT_EVENT_FORMAT: {},
192        }
193        pass
194
195    def set_param(self, key: int, value: Any) -> None:
196        self.values[key] = value
197        pass
198
199    def get_param(self, key: int) -> Any:
200        value = self.values.get(key)
201        if value is None:
202            return self.default_values.get(key)
203        return value
204
205
206class TraceViewerInterface(metaclass=ABCMeta):
207    """
208    功能描述: 声明解析HiTrace结果的接口
209    """
210    @abstractmethod
211    def append_trace_event(self, trace_event: List) -> None:
212        pass
213
214    @abstractmethod
215    def calculate(self, context: TraceParseContext) -> None:
216        pass
217
218
219class TraceFileParserInterface(metaclass=ABCMeta):
220    """
221    功能描述: 声明解析HiTrace文件的接口
222    """
223    @abstractmethod
224    def parse_simple_field(self, field: Field) -> tuple:
225        return ()
226
227    @abstractmethod
228    def parse_event_format(self, data: List) -> dict:
229        return {}
230
231    @abstractmethod
232    def parse_cmd_lines(self, data: List) -> dict:
233        return {}
234
235    @abstractmethod
236    def parse_tid_groups(self, data: List) -> dict:
237        return {}
238
239    @abstractmethod
240    def get_context(self) -> TraceParseContext:
241        return None
242
243    @abstractmethod
244    def get_viewer(self) -> TraceViewerInterface:
245        return None
246
247    @abstractmethod
248    def get_segment_data(self, segment_size) -> List:
249        return None
250
251
252class OperatorInterface(metaclass=ABCMeta):
253    @abstractmethod
254    def accept(self, parser: TraceFileParserInterface, segment=None) -> bool:
255        return True
256
257
258class FieldOperator(Field, OperatorInterface):
259    def __init__(self, vtype, size, vformat, item_types) -> None:
260        Field.__init__(self, vtype, size, vformat, item_types)
261        pass
262
263    def accept(self, parser: TraceFileParserInterface, segment=None) -> bool:
264        return True
265
266
267class SegmentOperator(Field, OperatorInterface):
268    def __init__(self, vtype) -> None:
269        Field.__init__(self, vtype, -1, "", [])
270        pass
271
272    def accept(self, parser: TraceFileParserInterface, segment=None) -> bool:
273        return True
274
275
276class FileHeader(FieldOperator):
277    """
278    功能描述: 声明HiTrace文件的头部格式
279    """
280    # 描述HiTrace文件头的pack格式
281    FORMAT = "HBHI"
282
283    # HiTrace文件头包含的字段
284    ITEM_MAGIC_NUMBER = 0
285    ITEM_FILE_TYPE = 1
286    ITEM_VERSION_NUMBER = 2
287    ITEM_RESERVED = 3
288
289    def __init__(self, item_types=None) -> None:
290        item_types = item_types or []
291        super().__init__(
292            FieldType.TRACE_FILE_HEADER,
293            DataType.get_data_bytes(FileHeader.FORMAT),
294            FileHeader.FORMAT,
295            item_types
296        )
297
298    def accept(self, parser: TraceFileParserInterface, segment=None) -> bool:
299        data = parser.get_segment_data(self.field_size)
300        values = parser.parse_simple_field(self.format, data)
301        if len(values) == 0:
302            return False
303        cpu_num = (values[FileHeader.ITEM_RESERVED] >> 1) & 0b00011111
304        context = parser.get_context()
305        context.set_param(TraceParseContext.CONTEXT_CPU_NUM, cpu_num)
306
307        version = values[FileHeader.ITEM_VERSION_NUMBER]
308        file_type = values[FileHeader.ITEM_FILE_TYPE]
309        print("version is %d, file type is %d, cpu number %d" % (version, file_type, cpu_num))
310        return True
311
312
313class PageHeader(FieldOperator):
314    """
315    功能描述: 声明HiTrace文件的第个 raw event 页头部格式
316    """
317    # 描述HiTrace文件的page header的pack格式
318    FORMAT = "QQB"
319
320    # HiTrace文件的page header包含的字段,每一页是同一个CPU核产生的数据,每一页都是固定大小4096字节
321    # page内trace event的时间基准点
322    ITEM_TIMESTAMP = 0
323    ITEM_LENGTH = 1
324    # page所属的CPU核
325    ITEM_CORE_ID = 2
326
327    def __init__(self, item_types: List) -> None:
328        super().__init__(
329            FieldType.TRACE_EVENT_PAGE_HEADER,
330            DataType.get_data_bytes(PageHeader.FORMAT),
331            PageHeader.FORMAT,
332            item_types
333        )
334
335    def accept(self, parser: TraceFileParserInterface, segment=None) -> bool:
336        segment = segment or []
337        value = parser.parse_simple_field(self.format, segment)
338        context = parser.get_context()
339        context.set_param(TraceParseContext.CONTEXT_TIMESTAMP, value[PageHeader.ITEM_TIMESTAMP])
340        context.set_param(TraceParseContext.CONTEXT_CORE_ID, value[PageHeader.ITEM_CORE_ID])
341        return True
342    pass
343
344
345class TraceEventHeader(FieldOperator):
346    """
347    功能描述: 声明HiTrace文件的trace event头部格式
348    """
349    # 描述HiTrace文件trace event的pack格式
350    FORMAT = "IH"
351    RMQ_ENTRY_ALIGN_MASK = 3
352
353    # HiTrace文件的trace event header包含的字段,事件的打点时间是基于page header的时间做偏移可计算出来
354    ITEM_TIMESTAMP_OFFSET = 0
355    # trace event的大小,实际占用内存的大小还需要考虑内存对齐的因素
356    ITEM_EVENT_SIZE = 1
357
358    def __init__(self, item_types: List) -> None:
359        super().__init__(
360            FieldType.TRACE_EVENT_HEADER,
361            DataType.get_data_bytes(TraceEventHeader.FORMAT),
362            TraceEventHeader.FORMAT,
363            item_types
364        )
365
366    def accept(self, parser: TraceFileParserInterface, segment=None) -> bool:
367        segment = segment or []
368        value = parser.parse_simple_field(self.format, segment)
369        if len(value) == 0:
370            return False
371        event_size = value[TraceEventHeader.ITEM_EVENT_SIZE]
372        align_event_size = ((event_size + TraceEventHeader.RMQ_ENTRY_ALIGN_MASK) & (~TraceEventHeader.RMQ_ENTRY_ALIGN_MASK))
373
374        context = parser.get_context()
375        context.set_param(TraceParseContext.CONTEXT_EVENT_SIZE, (event_size, align_event_size))
376        context.set_param(TraceParseContext.CONTEXT_TIMESTAMP_OFFSET, value[TraceEventHeader.ITEM_TIMESTAMP_OFFSET])
377        return True
378    pass
379
380
381class TraceEventContent(SegmentOperator):
382    """
383    功能描述: 声明HiTrace文件的trace event的内容格式
384    """
385    def __init__(self) -> None:
386        super().__init__(FieldType.TRACE_EVENT_CONTENT)
387        pass
388
389    def accept(self, parser: TraceFileParserInterface, segment=None) -> bool:
390        segment = segment or []
391        context = parser.get_context()
392        timestamp = context.get_param(TraceParseContext.CONTEXT_TIMESTAMP)
393        timestamp = timestamp + context.get_param(TraceParseContext.CONTEXT_TIMESTAMP_OFFSET)
394        core_id = context.get_param(TraceParseContext.CONTEXT_CORE_ID)
395
396        vformat = 'H'
397        data_bytes = DataType.get_data_bytes(vformat)
398        event_id = parser.parse_simple_field(vformat, segment[0:data_bytes])
399        if len(event_id) == 0:
400            return False
401        trace_event = [timestamp, core_id, event_id[0], segment]
402        viewer = parser.get_viewer()
403        viewer.append_trace_event(trace_event)
404        return True
405    pass
406
407
408class TraceEventWrapper(OperatorInterface):
409    """
410    功能描述: 声明HiTrace文件的1条trace event的格式
411    """
412    def __init__(self, event_header: TraceEventHeader, event_content: TraceEventContent) -> None:
413        self.event_header = event_header
414        self.event_content = event_content
415        pass
416
417    def accept(self, parser: TraceFileParserInterface, segment=None) -> bool:
418        segment = segment or []
419        cur_post = 0
420        while cur_post < len(segment):
421            (cur_post, data) = self.next_event_header(cur_post, segment)
422            if self.event_header.accept(parser, data) is False:
423                break
424            (cur_post, data) = self.next_event_context(cur_post, segment, parser)
425            if self.event_content.accept(parser, data) is False:
426                break
427        return True
428
429    def next_event_context(self, cur_post: int, segment: List, parser: TraceFileParserInterface) -> tuple:
430        context = parser.get_context()
431        (event_size, align_event_size) = context.get_param(TraceParseContext.CONTEXT_EVENT_SIZE)
432        data = segment[cur_post: cur_post + event_size]
433        next_post = cur_post + align_event_size
434        return (next_post, data)
435
436    def next_event_header(self, cur_post: int, segment: List) -> tuple:
437        data = segment[cur_post: cur_post + self.event_header.field_size]
438        next_post = cur_post + self.event_header.field_size
439        return (next_post, data)
440    pass
441
442
443class PageWrapper(OperatorInterface):
444    """
445    功能描述: 声明HiTrace文件的1个page的格式
446    """
447    # trace event的page是固定大小, trace event的内容大小,应该是4096的整数倍,trace event需要按页进行解析
448    TRACE_PAGE_SIZE = 4096
449
450    def __init__(self, page_header: PageHeader, trace_event: TraceEventWrapper) -> None:
451        self.page_header = page_header
452        self.trace_event = trace_event
453        pass
454
455    def next(self, cur_post: int, segment: List) -> tuple:
456        if segment is None:
457            return (-1, None)
458
459        if (cur_post + PageWrapper.TRACE_PAGE_SIZE) > len(segment):
460            return (-1, None)
461
462        data = segment[cur_post: cur_post + PageWrapper.TRACE_PAGE_SIZE]
463        next_post = cur_post + PageWrapper.TRACE_PAGE_SIZE
464        return (next_post, data)
465
466    def accept(self, parser: TraceFileParserInterface, segment=None) -> bool:
467        segment = segment or []
468        cur_post = 0
469        while True:
470            (cur_post, data) = self.next(cur_post, segment)
471            if data is None:
472                break
473
474            self.page_header.accept(parser, data[0: self.page_header.field_size])
475            self.trace_event.accept(parser, data[self.page_header.field_size:])
476        return True
477    pass
478
479
480class RawTraceSegment(SegmentOperator):
481    """
482    功能描述: 声明HiTrace文件trace_pipe_raw内容的段格式
483    """
484    def __init__(self) -> None:
485        super().__init__(FieldType.SEGMENT_RAW_TRACE)
486        self.field = PageWrapper(
487            PageHeader([
488                PageHeader.ITEM_TIMESTAMP,
489                PageHeader.ITEM_LENGTH,
490                PageHeader.ITEM_CORE_ID,
491            ]),
492            TraceEventWrapper(
493                TraceEventHeader([
494                    TraceEventHeader.ITEM_TIMESTAMP_OFFSET,
495                    TraceEventHeader.ITEM_EVENT_SIZE
496                ]),
497                TraceEventContent(),
498            )
499        )
500        pass
501
502    def accept(self, parser: TraceFileParserInterface, segment=None) -> bool:
503        segment = segment or []
504        self.field.accept(parser, segment)
505        return True
506
507
508class EventFormatSegment(SegmentOperator):
509    """
510    功能描述: 声明HiTrace文件event/format内容的段格式
511    """
512    def __init__(self) -> None:
513        super().__init__(FieldType.SEGMENT_EVENTS_FORMAT)
514        pass
515
516    def accept(self, parser: TraceFileParserInterface, segment=None) -> bool:
517        segment = segment or []
518        events_format = parser.parse_event_format(segment)
519        context = parser.get_context()
520        context.set_param(TraceParseContext.CONTEXT_EVENT_FORMAT, events_format)
521        return True
522
523
524class CmdLinesSegment(SegmentOperator):
525    """
526    功能描述: 声明HiTrace文件/sys/kernel/tracing/saved_cmdlines内容的段格式
527    """
528    def __init__(self) -> None:
529        super().__init__(FieldType.SEGMENT_CMDLINES)
530        pass
531
532    def accept(self, parser: TraceFileParserInterface, segment=None) -> bool:
533        segment = segment or []
534        cmd_lines = parser.parse_cmd_lines(segment)
535        context = parser.get_context()
536        context.set_param(TraceParseContext.CONTEXT_CMD_LINES, cmd_lines)
537        return True
538
539
540class TidGroupsSegment(SegmentOperator):
541    """
542    功能描述: 声明HiTrace文件/sys/kernel/tracing/saved_tgids内容的段格式
543    """
544    def __init__(self) -> None:
545        super().__init__(FieldType.SEGMENT_TGIDS)
546        pass
547
548    def accept(self, parser: TraceFileParserInterface, segment=None) -> bool:
549        segment = segment or []
550        tid_groups = parser.parse_tid_groups(segment)
551        context = parser.get_context()
552        context.set_param(TraceParseContext.CONTEXT_TID_GROUPS, tid_groups)
553        return True
554
555
556class PrintkFormatSegment(SegmentOperator):
557    """
558    功能描述: 声明HiTrace文件/sys/kernel/tracing/printk_formats内容的段格式
559    """
560    def __init__(self) -> None:
561        super().__init__(FieldType.SEGMENT_PRINTK_FORMATS)
562        pass
563
564    def accept(self, parser: TraceFileParserInterface, segment=None) -> bool:
565        print("in parse_printk_formats")
566        return True
567
568
569class KallSymsSegment(SegmentOperator):
570    """
571    功能描述: 声明HiTrace文件Kall Sysms内容的段格式
572    """
573    def __init__(self) -> None:
574        super().__init__(FieldType.SEGMENT_KALLSYMS)
575        pass
576
577    def accept(self, parser: TraceFileParserInterface, segment=None) -> bool:
578        print("in parse_kallsyms")
579        return True
580
581
582class HeaderPageSegment(SegmentOperator):
583    """
584    功能描述: 声明HiTrace文件header page内容的段格式
585    """
586    def __init__(self) -> None:
587        super().__init__(FieldType.SEGMENT_HEADER_PAGE)
588        pass
589
590    def accept(self, parser: TraceFileParserInterface, segment=None) -> bool:
591        print("in parse_header_page")
592        return True
593
594
595class UnSupportSegment(FieldOperator):
596    """
597    功能描述: 声明HiTrace文件还不支持解析的段
598    """
599    def __init__(self) -> None:
600        super().__init__(FieldType.SEGMENT_UNSUPPORT, -1, "", [])
601        pass
602
603    def accept(self, parser: TraceFileParserInterface, segment=None) -> bool:
604        print("unsupport segment")
605        return True
606
607
608class SegmentWrapper(FieldOperator):
609    """
610    功能描述: 声明HiTrace文件包含所有段的格式
611    """
612    # 描述段的pack的格式
613    FORMAT = "II"
614
615    ITEM_SEGMENT_TYPE = 0
616    ITEM_SEGMENT_SIZE = 1
617
618    def __init__(self, fields: List) -> None:
619        super().__init__(
620            FieldType.SEGMENT_SEGMENTS,
621            DataType.get_data_bytes(SegmentWrapper.FORMAT),
622            SegmentWrapper.FORMAT,
623            [
624                SegmentWrapper.ITEM_SEGMENT_TYPE,
625                SegmentWrapper.ITEM_SEGMENT_SIZE,
626            ]
627        )
628        self.fields = fields
629        pass
630
631    def accept(self, parser: TraceFileParserInterface, segment=None) -> bool:
632        segment = segment or []
633        while True:
634            data = parser.get_segment_data(self.field_size)
635            values = parser.parse_simple_field(self.format, data)
636            if len(values) == 0:
637                break
638            segment_type = values[SegmentWrapper.ITEM_SEGMENT_TYPE]
639            segment_size = values[SegmentWrapper.ITEM_SEGMENT_SIZE]
640            conext = parser.get_context()
641
642            segment = self.get_segment(segment_type, conext.get_param(TraceParseContext.CONTEXT_CPU_NUM))
643            segment_data = parser.get_segment_data(segment_size)
644            segment.accept(parser, segment_data)
645            pass
646        return True
647
648    def get_segment(self, segment_type: int, cpu_num: int) -> FieldOperator:
649        for field in self.fields:
650            if field.field_type == segment_type:
651                return field
652
653            if field.field_type < FieldType.SEGMENT_RAW_TRACE:
654                continue
655
656            if field.field_type > FieldType.SEGMENT_RAW_TRACE + cpu_num:
657                continue
658            return field
659        return UnSupportSegment()
660
661
662class TraceFile:
663    """
664    功能描述: 读取hitrace的二进制文件
665    """
666    def __init__(self, name: str) -> None:
667        self.name = name
668        flags = os.O_RDONLY
669        if sys.platform == 'win32':
670            flags = os.O_RDONLY | os.O_BINARY
671        mode = stat.S_IRUSR
672        self.file = os.fdopen(os.open(name, flags, mode), 'rb')
673        self.size = os.path.getsize(name)
674        self.cur_post = 0
675        pass
676
677    def read_data(self, block_size: int) -> List:
678        """
679        功能描述: 从文件当前位置读取blockSize字节的内容
680        参数: 读取字节数
681        返回值: 文件内容
682        """
683        if (self.cur_post + block_size) > self.size:
684            return None
685        self.cur_post = self.cur_post + block_size
686        return self.file.read(block_size)
687
688
689class TraceFileFormat(OperatorInterface):
690    """
691    功能描述: 声明整个HiTrace文件的二进制格式
692    """
693    def __init__(self) -> None:
694        self.fields = [
695            FileHeader([
696                FileHeader.ITEM_MAGIC_NUMBER,
697                FileHeader.ITEM_FILE_TYPE,
698                FileHeader.ITEM_VERSION_NUMBER,
699                FileHeader.ITEM_RESERVED
700            ]),
701            SegmentWrapper([
702                CmdLinesSegment(),
703                TidGroupsSegment(),
704                EventFormatSegment(),
705                RawTraceSegment(),
706                PrintkFormatSegment(),
707                KallSymsSegment(),
708                HeaderPageSegment(),
709            ])
710        ]
711        pass
712
713    def accept(self, parser: TraceFileParserInterface, segment=None) -> bool:
714        for field in self.fields:
715            if field.accept(parser) is False:
716                return False
717        return True
718
719
720class Viewer:
721    @abstractmethod
722    def set_context(self, context: TraceParseContext) -> None:
723        pass
724
725    @abstractmethod
726    def calculate(self, timestamp: int, core_id: int, event_id: int, segment: List) -> None:
727        pass
728
729    @abstractmethod
730    def show(self) -> None:
731        pass
732
733
734class SysTraceViewer(Viewer):
735    COMM_STR_MAX = 16
736    PID_STR_MAX = 6
737    TGID_STR_MAX = 5
738    CPU_STR_MAX = 3
739    TS_SECS_MIN = 5
740    TS_MICRO_SECS = 6
741
742    TRACE_FLAG_IRQS_OFF = 0x01
743    TRACE_FLAG_IRQS_NOSUPPORT = 0x02
744    TRACE_FLAG_NEED_RESCHED = 0x04
745    TRACE_FLAG_HARDIRQ = 0x08
746    TRACE_FLAG_SOFTIRQ = 0x10
747    TRACE_FLAG_PREEMPT_RESCHED = 0x20
748    TRACE_FLAG_NMI = 0x40
749
750    def __init__(self, output_file: str):
751        self.out_file = output_file
752        self.format_miss_cnt = 0
753        self.format_miss_set = set()
754        self.trace_event_count_dict = {} # trace event count dict
755        self.trace_event_mem_dict = {} # trace event mem dict
756        self.get_not_found_format = set()
757        self.systrace = []
758        pass
759
760    def set_context(self, context: TraceParseContext) -> None:
761        self.context = context
762        self.events_format = context.get_param(TraceParseContext.CONTEXT_EVENT_FORMAT)
763        self.cmd_lines = context.get_param(TraceParseContext.CONTEXT_CMD_LINES)
764        self.tgids = context.get_param(TraceParseContext.CONTEXT_TID_GROUPS)
765        pass
766
767    def calculate(self, timestamp: int, core_id: int, event_id: int, segment: List, context: TraceParseContext) -> None:
768        if event_id in self.trace_event_count_dict:
769            self.trace_event_count_dict[event_id] += 1
770        else:
771            self.trace_event_count_dict[event_id] = 1
772
773        if event_id in self.trace_event_mem_dict:
774            self.trace_event_mem_dict[event_id] += len(segment)
775        else:
776            self.trace_event_mem_dict[event_id] = len(segment)
777
778        event_format = self.events_format.get(event_id, "")
779        if event_format == "":
780            # current event format is not found in trace file format data.
781            self.format_miss_cnt = self.format_miss_cnt + 1
782            self.format_miss_set.add(event_id)
783            return
784
785        fields = event_format["fields"]
786        one_event = {}
787        one_event["id"] = event_id
788        one_event["name"] = event_format["name"]
789        one_event["print_fmt"] = event_format["print_fmt"]
790        one_event["fields"] = {}
791        for field in fields:
792            offset = field["offset"]
793            size = field["size"]
794            one_event["fields"][field["name"]] = segment[offset:offset + size]
795
796        systrace = self.generate_one_event_str(segment, core_id, timestamp, one_event)
797        self.systrace.append([timestamp, systrace])
798        pass
799
800    def generate_one_event_str(self, data: List, cpu_id: int, time_stamp: int, one_event: dict) -> str:
801        pid = int.from_bytes(one_event["fields"]["common_pid"], byteorder='little')
802        event_str = ""
803
804        cmd_line = self.cmd_lines.get(pid, "")
805        if pid == 0:
806            event_str += "<idle>"
807        elif cmd_line != "":
808            event_str += cmd_line
809        else:
810            event_str += "<...>"
811        event_str = event_str.rjust(SysTraceViewer.COMM_STR_MAX)
812        event_str += "-"
813
814        event_str += str(pid).ljust(SysTraceViewer.PID_STR_MAX)
815
816        tgid = self.tgids.get(pid)
817        if tgid is not None:
818            tgid = "%d" % tgid
819            event_str += "(" + tgid.rjust(SysTraceViewer.TGID_STR_MAX) + ")"
820        else:
821            event_str += "(-----)"
822
823        event_str += " [" + str(cpu_id).zfill(SysTraceViewer.CPU_STR_MAX) + "] "
824
825        flags = int.from_bytes(one_event["fields"]["common_flags"], byteorder='little')
826        preempt_count = int.from_bytes(one_event["fields"]["common_preempt_count"], byteorder='little')
827        if flags | preempt_count != 0:
828            event_str += self.trace_flags_to_str(flags, preempt_count) + " "
829        else:
830            event_str += ".... "
831
832        if time_stamp % 1000 >= 500:
833            time_stamp_str = str((time_stamp // 1000) + 1)
834        else:
835            time_stamp_str = str(time_stamp // 1000)
836        ts_secs = time_stamp_str[:-6].rjust(SysTraceViewer.TS_SECS_MIN)
837        ts_micro_secs = time_stamp_str[-6:]
838        event_str += ts_secs + "." + ts_micro_secs + ": "
839
840        parse_result = parse_functions.parse(one_event["print_fmt"], data, one_event)
841        if parse_result is None:
842            self.get_not_found_format.add(str(one_event["name"]))
843        else:
844            event_str += str(one_event["name"]) + ": " + parse_result
845
846        return event_str
847
848    def trace_flags_to_str(self, flags: int, preempt_count: int) -> str:
849        result = ""
850        irqs_off = '.'
851        if flags & SysTraceViewer.TRACE_FLAG_IRQS_OFF != 0:
852            irqs_off = 'd'
853        elif flags & SysTraceViewer.TRACE_FLAG_IRQS_NOSUPPORT != 0:
854            irqs_off = 'X'
855        result += irqs_off
856
857        need_resched = '.'
858        is_need_resched = flags & SysTraceViewer.TRACE_FLAG_NEED_RESCHED
859        is_preempt_resched = flags & SysTraceViewer.TRACE_FLAG_PREEMPT_RESCHED
860        if is_need_resched != 0 and is_preempt_resched != 0:
861            need_resched = 'N'
862        elif is_need_resched != 0:
863            need_resched = 'n'
864        elif is_preempt_resched != 0:
865            need_resched = 'p'
866        result += need_resched
867
868        nmi_flag = flags & SysTraceViewer.TRACE_FLAG_NMI
869        hard_irq = flags & SysTraceViewer.TRACE_FLAG_HARDIRQ
870        soft_irq = flags & SysTraceViewer.TRACE_FLAG_SOFTIRQ
871        irq_char = '.'
872        if nmi_flag != 0 and hard_irq != 0:
873            irq_char = 'Z'
874        elif nmi_flag != 0:
875            irq_char = 'z'
876        elif hard_irq != 0 and soft_irq != 0:
877            irq_char = 'H'
878        elif hard_irq != 0:
879            irq_char = 'h'
880        elif soft_irq != 0:
881            irq_char = 's'
882        result += irq_char
883
884        if preempt_count != 0:
885            result += "0123456789abcdef"[preempt_count & 0x0F]
886        else:
887            result += "."
888
889        return result
890
891    def show(self) -> None:
892        self.save_systrace()
893        self.show_stat()
894        pass
895
896    def save_systrace(self) -> None:
897        outfile_flags = os.O_RDWR | os.O_CREAT
898        outfile_mode = stat.S_IRUSR | stat.S_IWUSR
899
900        result = sorted(self.systrace, key=lambda x: x[0])
901        outfile = os.fdopen(os.open(self.out_file, outfile_flags, outfile_mode), 'w', encoding="utf-8")
902        outfile.write(TRACE_TXT_HEADER_FORMAT)
903        for line in result:
904            outfile.write("{}\n".format(line[1]))
905        outfile.close()
906
907    def show_stat(self) -> None:
908        for name in self.get_not_found_format:
909            print("Error: function parse_%s not found" % name)
910        print("Trace format miss count: %d" % self.format_miss_cnt)
911        print("Trace format id missed set:")
912        print(self.format_miss_set)
913
914        count_total = sum(self.trace_event_count_dict.values())
915        mem_total = sum(self.trace_event_mem_dict.values()) / 1024 # KB
916        print(f"Trace counter: total count({count_total}), total mem({mem_total:.3f}KB)")
917        for format_id, count in self.trace_event_count_dict.items():
918            count_percentage = count / count_total * 100
919            mem = self.trace_event_mem_dict.get(format_id, 0) / 1024
920            mem_percentage = mem / mem_total * 100
921            print(f"ID {format_id}: count={count}, count percentage={count_percentage:.5f}%, mem={mem:.3f}KB, mem percentage={mem_percentage:.5f}%")
922
923
924class TraceViewer(TraceViewerInterface):
925    def __init__(self, viewers: List) -> None:
926        self.trace_events = []
927        self.viewers = viewers
928        pass
929
930    def append_trace_event(self, trace_event: List) -> None:
931        self.trace_events.append(trace_event)
932        pass
933
934    def calculate(self, context: TraceParseContext):
935        for viewer in self.viewers:
936            viewer.set_context(context)
937
938        for timestamp, core_id, event_id, segment in self.trace_events:
939            for viewer in self.viewers:
940                viewer.calculate(timestamp, core_id, event_id, segment, context)
941
942        for viewer in self.viewers:
943            viewer.show()
944        pass
945
946
947class TraceFileParser(TraceFileParserInterface):
948    def __init__(self, file: TraceFile, vformat: TraceFileFormat, viewer: TraceViewer, context: TraceParseContext) -> None:
949        self.trace_file = file
950        self.trace_format = vformat
951        self.trace_viewer = viewer
952        self.context = context
953        pass
954
955    def parse_simple_field(self, vformat: str, data: List) -> tuple:
956        if data is None:
957            return {}
958        if DataType.get_data_bytes(vformat) != len(data):
959            return {}
960        unpack_value = struct.unpack(vformat, data)
961        return unpack_value
962
963    def parse_event_format(self, data: List) -> dict:
964        def parse_events_format_field(field_line):
965            field_info = field_line.split(";")
966            field_info[0] = field_info[0].lstrip()
967            field_info[1] = field_info[1].lstrip()
968            field_info[2] = field_info[2].lstrip()
969            field_info[3] = field_info[3].lstrip()
970
971            field = {}
972            type_name_pos = field_info[0].rfind(" ")
973            field["type"] = field_info[0][len("field:"):type_name_pos]
974            field["name"] = field_info[0][type_name_pos + 1:]
975            field["offset"] = int(field_info[1][len("offset:"):])
976            field["size"] = int(field_info[2][len("size:"):])
977            field["signed"] = field_info[3][len("signed:"):]
978            return field
979
980        events_format = {}
981        name_line_prefix = "name: "
982        id_line_prefix = "ID: "
983        field_line_prefix = "field:"
984        print_fmt_line_prefix = "print fmt: "
985
986        events_format_lines = data.decode('utf-8').split("\n")
987        event_format = {"fields": []}
988        for line in events_format_lines:
989            line = line.lstrip()
990            if line.startswith(name_line_prefix):
991                event_format["name"] = line[len(name_line_prefix):]
992            elif line.startswith(id_line_prefix):
993                event_format["id"] = int(line[len(id_line_prefix):])
994            elif line.startswith(field_line_prefix):
995                event_format["fields"].append(parse_events_format_field(line))
996            elif line.startswith(print_fmt_line_prefix):
997                event_format["print_fmt"] = line[len(print_fmt_line_prefix):]
998                events_format[event_format["id"]] = event_format
999                event_format = {"fields": []}
1000        return events_format
1001
1002    def parse_cmd_lines(self, data: List) -> dict:
1003        cmd_lines = {}
1004
1005        if isinstance(data, bytes):
1006            cmd_lines_list = data.decode('utf-8').split("\n")
1007        elif isinstance(data, list):
1008            if all(isinstance(item, bytes) for item in data):
1009                cmd_lines_list = b''.join(data).decode('utf-8').split("\n")
1010            else:
1011                cmd_lines_list = ''.join(data).split("\n")
1012        else:
1013            raise ValueError("Unsupported data type: must be bytes or list of bytes/str")
1014
1015        for cmd_line in cmd_lines_list:
1016            pos = cmd_line.find(" ")
1017            if pos == -1:
1018                continue
1019            cmd_lines[int(cmd_line[:pos])] = cmd_line[pos + 1:]
1020        return cmd_lines
1021
1022    def parse_tid_groups(self, data: List) -> dict:
1023        tgids = {}
1024        tgids_lines_list = data.decode('utf-8').split("\n")
1025        for tgids_line in tgids_lines_list:
1026            pos = tgids_line.find(" ")
1027            if pos == -1:
1028                continue
1029            tgids[int(tgids_line[:pos])] = int(tgids_line[pos + 1:])
1030        return tgids
1031
1032    def get_segment_data(self, segment_size) -> List:
1033        return self.trace_file.read_data(segment_size)
1034
1035    def get_context(self) -> TraceParseContext:
1036        return self.context
1037
1038    def get_viewer(self) -> TraceViewerInterface:
1039        return self.trace_viewer
1040
1041    def parse(self) -> None:
1042        self.trace_format.accept(self)
1043        self.trace_viewer.calculate(self.context)
1044        pass
1045
1046
1047def parse_binary_trace_file() -> None:
1048    global binary_file
1049    global out_file
1050    file = TraceFile(binary_file)
1051    vformat = TraceFileFormat()
1052    viewer = TraceViewer([
1053        SysTraceViewer(out_file)
1054    ])
1055    context = TraceParseContext()
1056    parser = TraceFileParser(file, vformat, viewer, context)
1057    parser.parse()
1058    pass
1059
1060
1061def main() -> None:
1062    parse_options()
1063
1064    if file_dir == '':
1065        if text_file != '':
1066            parse_text_trace_file()
1067        else:
1068            parse_binary_trace_file()
1069    else:
1070        for file in os.listdir(file_dir):
1071            if file.find('.sys') != -1:
1072                print(file)
1073                global binary_file
1074                global out_file
1075                binary_file = os.path.join(file_dir, file)
1076                out_file = os.path.join(os.path.split(binary_file)[0],
1077                                        '%s%s' % (os.path.split(binary_file)[-1].split('.')[0], '.ftrace'))
1078                parse_binary_trace_file()
1079
1080
1081if __name__ == '__main__':
1082    main()
1083