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