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