• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# Copyright (c) Huawei Technologies Co., Ltd. 2021. All rights reserved.
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15import os
16import re
17import logging
18
19
20class Common:
21    this_file = os.path.basename(__file__)
22    logging.basicConfig(
23        format="%(asctime)s %(levelname)s %(message)s", level=logging.INFO
24    )
25    logger = logging.getLogger(this_file)
26
27
28def enumerate_files(top_dir):
29    files = []
30    stack = [top_dir]
31    while len(stack) > 0:
32        current = stack.pop()
33        for entry in os.listdir(current):
34            if entry.startswith("."):
35                continue
36            path = os.path.join(current, entry)
37            if os.path.isfile(path):
38                files.append(path)  # append to result list.
39            if os.path.isdir(path):
40                stack.append(path)  # continue to do depth first search.
41    files.sort()
42    return files
43
44
45class FtraceEvent(object):
46    def __init__(self, format_path, event_category, event_name):
47        self.format_path = format_path
48        self.event_category = event_category
49        self.event_name = event_name
50
51    def __str__(self):
52        return '{{ P: "{}", C: "{}", N: {} }}'.format(
53            self.format_path, self.event_category, self.event_name
54        )
55
56    def __repr__(self):
57        return self.__str__()
58
59
60class ProtoType(object):
61    INVALID, INTEGER, STRING, BYTES, ARRAY = 0, 1, 2, 3, 4
62
63    def __init__(self, tid, size=0, signed=False):
64        self.tid = tid  # type id
65        self.size = size
66        self.signed = signed
67
68    def __str__(self):
69        return "{{ T: {}, SZ: {}, SN: {} }}".format(self.tid, self.size, self.signed)
70
71    def __repr__(self):
72        return self.__str__()
73
74    def to_string(self):
75        if self.tid != ProtoType.INVALID:
76            if self.tid == ProtoType.STRING:
77                return "string"
78            elif self.tid == ProtoType.BYTES:
79                return "string"
80            elif self.tid == ProtoType.ARRAY:
81                return "repeated uint64"
82            elif self.tid == ProtoType.INTEGER:
83                t = ""
84                if not self.signed:
85                    t = "{}{}".format(t, "u")
86                if self.size <= 4:
87                    t = "{}{}".format(t, "int32")
88                elif self.size <= 8:
89                    t = "{}{}".format(t, "int64")
90                return t
91
92
93class FtraceEventField(object):
94    def __init__(self, event, field, name, offset, size, signed):
95        self.event = event
96        self.field = field
97        self.name = name
98        self.offset = offset
99        self.size = size
100        self.signed = signed
101
102    def __str__(self):
103        return '{{ E: {}, F: "{}", N: "{}", O: {}, SZ: {}, SN: {} }}'.format(
104            self.event, self.field, self.name, self.offset, self.size, self.signed
105        )
106
107    def __repr__(self):
108        return self.__str__()
109
110    def to_proto_type_special_case(self):
111        if self.event == ("ftrace", "print") and self.name == "buf":
112            # ftrace/print: char buf[];
113            return ProtoType(ProtoType.STRING)
114        if self.size == 0:
115            Common.logger.fatal("zero size field {}!".format(self))
116        return ProtoType.INVALID
117
118    def is_string_type(self):
119        return (
120            re.match(r"char\s+\w+\[\d*\]", self.field)
121            or re.match(r"char\s*\*\s*\w+", self.field)
122            or re.findall(r"char\s*\*\s*", self.field)
123            or re.findall(r"char\[\]", self.field)
124            or re.findall(r"char\s*\w+\[.*\]", self.field)
125            or re.match(r"__u8\s+\w+\[\d+\]", self.field)
126            or re.match(r"__u8\s+\w+\[.*]", self.field)
127            or re.match(r"u32\s+\w+\[\d+\]", self.field)
128        )
129
130    def is_array_type(self):
131        return re.match(r"unsigned long\s+\w+\[\d*\]", self.field)
132
133    def is_uintptr_type(self):
134        return (
135            self.field.startswith("ino_t ")
136            or self.field.startswith("i_ino ")
137            or self.field.startswith("dev_t ")
138            or self.field.startswith("loff_t ")
139            or self.field.startswith("sector_t ")
140            or self.field.startswith("ext4_fsblk_t ")
141            or "long" in self.field
142            or "size_t" in self.field
143            or "intptr_t" in self.field
144        )
145
146    def to_proto_type(self):
147        t = self.to_proto_type_special_case()
148        if t is not ProtoType.INVALID:
149            return t
150
151        if self.is_string_type():
152            return ProtoType(ProtoType.STRING)
153        if self.is_array_type():
154            return ProtoType(ProtoType.ARRAY)
155        elif self.is_uintptr_type():
156            if (self.field.startswith("ino_t ") and self.size == 4):
157                return ProtoType(ProtoType.INTEGER, 4, False)
158            return ProtoType(ProtoType.INTEGER, 8, False)
159        elif 0 < self.size <= 4:
160            if self.size in [1, 2, 4]:
161                if "*" in self.field:
162                    return ProtoType(ProtoType.INTEGER, 8, False)
163                return ProtoType(ProtoType.INTEGER, 4, self.signed)
164        elif 4 < self.size <= 8:
165            if self.size in [8]:
166                return ProtoType(ProtoType.INTEGER, 8, self.signed)
167        else:
168            Common.logger.fatal("can not convert {} to proto type.".format(str(self)))
169
170
171class FtraceEventFormat(object):
172    def __init__(self):
173        self.category = ""
174        self.path = ""
175        self.name = ""
176        self.event_id = ""
177        self.common_fields = []
178        self.remain_fields = []
179        self.print_fmt = ""
180
181    def __str__(self):
182        return '{{ name: "{}", ID: {}, common: {}, remain: {}" }}'.format(
183            self.name, self.event_id, self.common_fields, self.remain_fields
184        )
185
186    def __repr__(self):
187        return self.__str__()
188
189
190class FtraceEventFormatParser(object):
191    def __init__(self):
192        self.text = []
193        self.line = None
194
195    def _parse_name(self, type_name):
196        if len(type_name) > 0:
197            start_idx = type_name.rfind(" ")
198            remain = type_name[start_idx + 1 :]
199            if "[" not in remain:
200                return remain
201            return remain[: remain.rfind("[")]
202
203    def _parse_field(self, event):
204        fields = [f.strip().split(":") for f in self.line.split(";")]
205        if fields[0][0] == "field" and fields[1][0] == "offset":
206            type_name, offset = fields[0][1], int(fields[1][1])
207            name = self._parse_name(type_name)
208            if fields[2][0] == "size" and fields[3][0] == "signed":
209                size, signed = int(fields[2][1]), int(fields[3][1]) == 1
210                return FtraceEventField(event, type_name, name, offset, size, signed)
211
212    def parse(self, ftrace_info):
213        self.text = []
214        with open(ftrace_info.format_path) as f:
215            self.text = f.readlines()
216        event_format = FtraceEventFormat()
217        event_format.category = ftrace_info.event_category
218        event_format.path = ftrace_info.format_path
219        event = (ftrace_info.event_category, ftrace_info.event_name)
220        cursor_line = 0
221        field_list = []
222        while cursor_line < len(self.text):
223            self.line = self.text[cursor_line].strip()
224            if self.line.startswith("name:"):
225                event_format.name = self.line.split(":")[1].strip()
226                cursor_line += 1
227            elif self.line.startswith("ID:"):
228                event_format.event_id = int(self.line.split(":")[1])
229                cursor_line += 1
230            elif self.line.startswith("format:"):
231                field_list = event_format.common_fields
232                cursor_line += 1
233            elif self.line.startswith("field:"):
234                field_list.append(self._parse_field(event))
235                cursor_line += 1
236            elif self.line.startswith("print fmt:"):
237                event_format.print_fmt = self.line[len("print fmt:") :]
238                cursor_line += 1
239            elif len(self.line) == 0:
240                field_list = event_format.remain_fields
241                cursor_line += 1
242            else:
243                Common.logger.warning(
244                    "ignore unknow line at {}:{}".format(
245                        ftrace_info.format_path, cursor_line
246                    )
247                )
248                cursor_line += 1
249        return event_format
250
251
252class FtraceEventCodeGenerator(object):
253    def __init__(self, events_dir, allow_list):
254        self.events_dir = events_dir
255        self.allow_list = allow_list
256        self.output_dir = None
257        self.allowed_events = set()
258        self.available_events = set()
259        self.category_to_info = {}
260        self.grouped_event_formats = {}
261        self.target_event_list = []
262        self.target_event_formats = []
263        self.parser = FtraceEventFormatParser()
264        self.text = None
265
266    def _load_allow_list(self):
267        self.text = []
268        with open(self.allow_list) as f:
269            self.text = [line.strip() for line in f.readlines()]
270        for i in self.text:
271            if i.startswith("removed") or i.startswith("#"):
272                continue
273            class_type = tuple(i.split("/"))  # event class and event type
274            if len(class_type) == 2:
275                self.allowed_events.add(class_type)
276
277    def _get_platform_event_list(self):
278        files = enumerate_files(self.events_dir)
279        is_format = lambda p: os.path.basename(p) == "format"
280        format_files = list(filter(is_format, files))
281        event_names = set()
282        for path in format_files:
283            parts = path.split(os.path.sep)
284            if parts[-1] == "format":
285                category_type = tuple(parts[-3:-1])  # category/type
286                self.available_events.add(category_type)
287                event_category, event_name = parts[-3], parts[-2]
288                event_info = FtraceEvent(path, event_category, event_name)
289                self.category_to_info[category_type] = event_info
290                event_names.add(parts[-2])
291        Common.logger.info("platform events: %d", len(self.available_events))
292        Common.logger.info("allowed events: %d", len(self.allowed_events))
293
294    def _get_target_event_list(self):
295        targets = list(self.allowed_events & self.available_events)
296        Common.logger.info("target events: %d", len(targets))
297        targets.sort()
298        self.target_event_list = [self.category_to_info.get(c) for c in targets]
299        Common.logger.info("target_event_list: %d", len(self.target_event_list))
300
301    def _parse_ftrace_formats(self):
302        for info in self.target_event_list:
303            event_format = self.parser.parse(info)
304            event_category = event_format.category
305            if event_category not in self.grouped_event_formats:
306                self.grouped_event_formats[event_category] = []
307            self.grouped_event_formats[event_category].append(event_format)
308            self.target_event_formats.append(event_format)
309
310    def generate_code(self):
311        print("FATAL: subclass must implements generate_code_files method!")
312        os.abort()
313
314    def generate(self, output_dir):
315        self.output_dir = output_dir
316        self._load_allow_list()
317        self._get_platform_event_list()
318        self._get_target_event_list()
319        self._parse_ftrace_formats()
320        self.generate_code()
321