• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# Copyright (C) 2021 Huawei Device Co., Ltd.
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            return ProtoType(ProtoType.INTEGER, 8, False)
157        elif 0 < self.size <= 4:
158            if self.size in [1, 2, 4]:
159                if "*" in self.field:
160                    return ProtoType(ProtoType.INTEGER, 8, False)
161                return ProtoType(ProtoType.INTEGER, 4, self.signed)
162        elif 4 < self.size <= 8:
163            if self.size in [8]:
164                return ProtoType(ProtoType.INTEGER, 8, self.signed)
165        else:
166            Common.logger.fatal("can not convert {} to proto type.".format(str(self)))
167
168
169class FtraceEventFormat(object):
170    def __init__(self):
171        self.category = ""
172        self.path = ""
173        self.name = ""
174        self.event_id = ""
175        self.common_fields = []
176        self.remain_fields = []
177        self.print_fmt = ""
178
179    def __str__(self):
180        return '{{ name: "{}", ID: {}, common: {}, remain: {}" }}'.format(
181            self.name, self.event_id, self.common_fields, self.remain_fields
182        )
183
184    def __repr__(self):
185        return self.__str__()
186
187
188class FtraceEventFormatParser(object):
189    def __init__(self):
190        self.text = []
191        self.line = None
192
193    def _parse_name(self, type_name):
194        if len(type_name) > 0:
195            start_idx = type_name.rfind(" ")
196            remain = type_name[start_idx + 1 :]
197            if "[" not in remain:
198                return remain
199            return remain[: remain.rfind("[")]
200
201    def _parse_field(self, event):
202        fields = [f.strip().split(":") for f in self.line.split(";")]
203        if fields[0][0] == "field" and fields[1][0] == "offset":
204            type_name, offset = fields[0][1], int(fields[1][1])
205            name = self._parse_name(type_name)
206            if fields[2][0] == "size" and fields[3][0] == "signed":
207                size, signed = int(fields[2][1]), int(fields[3][1]) == 1
208                return FtraceEventField(event, type_name, name, offset, size, signed)
209
210    def parse(self, ftrace_info):
211        self.text = []
212        with open(ftrace_info.format_path) as f:
213            self.text = f.readlines()
214        event_format = FtraceEventFormat()
215        event_format.category = ftrace_info.event_category
216        event_format.path = ftrace_info.format_path
217        event = (ftrace_info.event_category, ftrace_info.event_name)
218        cursor_line = 0
219        field_list = []
220        while cursor_line < len(self.text):
221            self.line = self.text[cursor_line].strip()
222            if self.line.startswith("name:"):
223                event_format.name = self.line.split(":")[1].strip()
224                cursor_line += 1
225            elif self.line.startswith("ID:"):
226                event_format.event_id = int(self.line.split(":")[1])
227                cursor_line += 1
228            elif self.line.startswith("format:"):
229                field_list = event_format.common_fields
230                cursor_line += 1
231            elif self.line.startswith("field:"):
232                field_list.append(self._parse_field(event))
233                cursor_line += 1
234            elif self.line.startswith("print fmt:"):
235                event_format.print_fmt = self.line[len("print fmt:") :]
236                cursor_line += 1
237            elif len(self.line) == 0:
238                field_list = event_format.remain_fields
239                cursor_line += 1
240            else:
241                Common.logger.warning(
242                    "ignore unknow line at {}:{}".format(
243                        ftrace_info.format_path, cursor_line
244                    )
245                )
246                cursor_line += 1
247        return event_format
248
249
250class FtraceEventCodeGenerator(object):
251    def __init__(self, events_dir, allow_list):
252        self.events_dir = events_dir
253        self.allow_list = allow_list
254        self.output_dir = None
255        self.allowed_events = set()
256        self.available_events = set()
257        self.category_to_info = {}
258        self.grouped_event_formats = {}
259        self.target_event_list = []
260        self.target_event_formats = []
261        self.parser = FtraceEventFormatParser()
262        self.text = None
263
264    def _load_allow_list(self):
265        self.text = []
266        with open(self.allow_list) as f:
267            self.text = [line.strip() for line in f.readlines()]
268        for i in self.text:
269            if i.startswith("removed") or i.startswith("#"):
270                continue
271            class_type = tuple(i.split("/"))  # event class and event type
272            if len(class_type) == 2:
273                self.allowed_events.add(class_type)
274
275    def _get_platform_event_list(self):
276        files = enumerate_files(self.events_dir)
277        is_format = lambda p: os.path.basename(p) == "format"
278        format_files = list(filter(is_format, files))
279        event_names = set()
280        for path in format_files:
281            parts = path.split(os.path.sep)
282            if parts[-1] == "format":
283                category_type = tuple(parts[-3:-1])  # category/type
284                self.available_events.add(category_type)
285                event_category, event_name = parts[-3], parts[-2]
286                event_info = FtraceEvent(path, event_category, event_name)
287                self.category_to_info[category_type] = event_info
288                event_names.add(parts[-2])
289        Common.logger.info("platform events: %d", len(self.available_events))
290        Common.logger.info("allowed events: %d", len(self.allowed_events))
291
292    def _get_target_event_list(self):
293        targets = list(self.allowed_events & self.available_events)
294        Common.logger.info("target events: %d", len(targets))
295        targets.sort()
296        self.target_event_list = [self.category_to_info.get(c) for c in targets]
297        Common.logger.info("target_event_list: %d", len(self.target_event_list))
298
299    def _parse_ftrace_formats(self):
300        for info in self.target_event_list:
301            event_format = self.parser.parse(info)
302            event_category = event_format.category
303            if event_category not in self.grouped_event_formats:
304                self.grouped_event_formats[event_category] = []
305            self.grouped_event_formats[event_category].append(event_format)
306            self.target_event_formats.append(event_format)
307
308    def generate_code(self):
309        print("FATAL: subclass must implements generate_code_files method!")
310        os.abort()
311
312    def generate(self, output_dir):
313        self.output_dir = output_dir
314        self._load_allow_list()
315        self._get_platform_event_list()
316        self._get_target_event_list()
317        self._parse_ftrace_formats()
318        self.generate_code()
319