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