1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# Copyright (c) 2022 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. 15 16# This file contains a RomAnalyzer for rom analyzation of standard device. 17 18import argparse 19import json 20import os 21import sys 22import typing 23from copy import deepcopy 24from typing import * 25import re 26import subprocess 27from pkgs.rom_ram_baseline_collector import RomRamBaselineCollector 28from pkgs.basic_tool import BasicTool, unit_adaptive 29from pkgs.gn_common_tool import GnCommonTool, GnVariableParser 30from pkgs.simple_excel_writer import SimpleExcelWriter 31 32debug = bool(sys.gettrace()) 33 34NOTFOUND = "NOTFOUND" 35 36 37class PreCollector: 38 """ 39 collect some info that system_module_info.json dosn't contains 40 """ 41 42 def __init__(self, project_path: str) -> None: 43 self.info_dict: Dict[str, Any] = dict() 44 self.project_path = BasicTool.get_abs_path(project_path) 45 self.result_dict = dict() 46 47 def _process_single_sa(self, item: str, start_pattern: str): 48 gn, _, _ = item.split(':') 49 with open(gn, 'r', encoding='utf-8') as f: 50 content = f.read() 51 p_itr: Iterator[re.Match] = BasicTool.match_paragraph( 52 content=content, start_pattern=start_pattern) 53 for p in p_itr: 54 p_content = p.group() 55 files: List[str] = GnVariableParser.list_parser( 56 "sources", p_content) 57 component_name, subsystem_name = GnCommonTool.find_part_subsystem( 58 gn, self.project_path) 59 for f in files: 60 f = f.split('/')[-1] 61 self.result_dict[f] = { 62 "subsystem_name": subsystem_name, 63 "component_name": component_name, 64 "gn_path": gn 65 } 66 67 def collect_sa_profile(self): 68 grep_kw = r"ohos_sa_profile" 69 grep_cmd = f"grep -rn '{grep_kw}' --include=BUILD.gn {self.project_path}" 70 content = BasicTool.execute( 71 grep_cmd, post_processor=lambda x: x.split('\n')) 72 for item in content: 73 if not item: 74 continue 75 self._process_single_sa(item, start_pattern=grep_kw) 76 77 78class RomAnalyzer: 79 80 @classmethod 81 def __collect_product_info(cls, system_module_info_json: Text, 82 project_path: Text, extra_info: Dict[str, Dict]) -> Dict[Text, Dict[Text, Text]]: 83 """ 84 根据system_module_info.json生成target字典 85 format: 86 { 87 "{file_name}":{ 88 "{subsytem_name}": abc, 89 "{component_name}": def, 90 "{gn_path}": ghi 91 } 92 } 93 if the unit of system_module_info.json has not field "label" and the "type" is "sa_profile", 94 find the subsystem_name and component_name in the BUILD.gn 95 """ 96 with open(system_module_info_json, 'r', encoding='utf-8') as f: 97 product_list = json.loads(f.read()) 98 project_path = BasicTool.get_abs_path(project_path) 99 product_info_dict: Dict[Text, Dict[Text, Text]] = dict() 100 for unit in product_list: 101 cs_flag = False 102 dest: List = unit.get("dest") 103 if not dest: 104 print("warning: keyword 'dest' not found in {}".format( 105 system_module_info_json)) 106 continue 107 label: Text = unit.get("label") 108 gn_path = component_name = subsystem_name = None 109 if label: 110 cs_flag = True 111 gn_path = os.path.join(project_path, label.split(':')[ 112 0].lstrip('/'), "BUILD.gn") 113 component_name = unit.get("part_name") 114 subsystem_name = unit.get("subsystem_name") 115 if not component_name: 116 cn, sn = GnCommonTool.find_part_subsystem( 117 gn_path, project_path) 118 component_name = cn 119 if not subsystem_name: 120 cn, sn = GnCommonTool.find_part_subsystem( 121 gn_path, project_path) 122 subsystem_name = sn 123 else: 124 print("warning: keyword 'label' not found in {}".format(unit)) 125 for target in dest: 126 if cs_flag: 127 product_info_dict[target] = { 128 "component_name": component_name, 129 "subsystem_name": subsystem_name, 130 "gn_path": gn_path, 131 } 132 continue 133 tmp = target.split('/')[-1] 134 pre_info = extra_info.get(tmp) 135 if not pre_info: 136 continue 137 else: 138 product_info_dict[target] = pre_info 139 return product_info_dict 140 141 @classmethod 142 def __inside_save_result_as_excel(cls, add_baseline, subsystem_name, component_name, 143 baseline, file_name, size): 144 if add_baseline: 145 return [subsystem_name, component_name, 146 baseline, file_name, size] 147 else: 148 return [subsystem_name, component_name, file_name, size] 149 150 @classmethod 151 def __save_result_as_excel(cls, result_dict: dict, output_name: str, add_baseline: bool): 152 header = ["subsystem_name", "component_name", 153 "output_file", "size(Byte)"] 154 if add_baseline: 155 header = ["subsystem_name", "component_name", "baseline", 156 "output_file", "size(Byte)"] 157 tmp_dict = deepcopy(result_dict) 158 excel_writer = SimpleExcelWriter("rom") 159 excel_writer.set_sheet_header(headers=header) 160 subsystem_start_row = 1 161 subsystem_end_row = 0 162 subsystem_col = 0 163 component_start_row = 1 164 component_end_row = 0 165 component_col = 1 166 if add_baseline: 167 baseline_col = 2 168 for subsystem_name in tmp_dict.keys(): 169 subsystem_dict = tmp_dict.get(subsystem_name) 170 subsystem_size = subsystem_dict.get("size") 171 subsystem_file_count = subsystem_dict.get("file_count") 172 del subsystem_dict["file_count"] 173 del subsystem_dict["size"] 174 subsystem_end_row += subsystem_file_count 175 176 for component_name in subsystem_dict.keys(): 177 component_dict: Dict[str, int] = subsystem_dict.get( 178 component_name) 179 component_size = component_dict.get("size") 180 component_file_count = component_dict.get("file_count") 181 baseline = component_dict.get("baseline") 182 del component_dict["file_count"] 183 del component_dict["size"] 184 if add_baseline: 185 del component_dict["baseline"] 186 component_end_row += component_file_count 187 188 for file_name, size in component_dict.items(): 189 line = cls.__inside_save_result_as_excel(add_baseline, subsystem_name, component_name, 190 baseline, file_name, size) 191 excel_writer.append_line(line) 192 excel_writer.write_merge(component_start_row, component_col, component_end_row, component_col, 193 component_name) 194 if add_baseline: 195 excel_writer.write_merge(component_start_row, baseline_col, component_end_row, baseline_col, 196 baseline) 197 component_start_row = component_end_row + 1 198 excel_writer.write_merge(subsystem_start_row, subsystem_col, subsystem_end_row, subsystem_col, 199 subsystem_name) 200 subsystem_start_row = subsystem_end_row + 1 201 excel_writer.save(output_name + ".xls") 202 203 @classmethod 204 def __put(cls, unit: typing.Dict[Text, Any], result_dict: typing.Dict[Text, Dict], baseline_dict: Dict[str, Any], 205 baseline: bool): 206 207 component_name = NOTFOUND if unit.get( 208 "component_name") is None else unit.get("component_name") 209 subsystem_name = NOTFOUND if unit.get( 210 "subsystem_name") is None else unit.get("subsystem_name") 211 212 def get_rom_baseline(): 213 if (not baseline_dict.get(subsystem_name)) or (not baseline_dict.get(subsystem_name).get(component_name)): 214 return str() 215 return baseline_dict.get(subsystem_name).get(component_name).get("rom") 216 217 size = unit.get("size") 218 relative_filepath = unit.get("relative_filepath") 219 if result_dict.get(subsystem_name) is None: # 子系统 220 result_dict[subsystem_name] = dict() 221 result_dict[subsystem_name]["size"] = 0 222 result_dict[subsystem_name]["file_count"] = 0 223 224 if result_dict.get(subsystem_name).get(component_name) is None: # 部件 225 result_dict[subsystem_name][component_name] = dict() 226 result_dict[subsystem_name][component_name]["size"] = 0 227 result_dict[subsystem_name][component_name]["file_count"] = 0 228 if baseline: 229 result_dict[subsystem_name][component_name]["baseline"] = get_rom_baseline( 230 ) 231 232 result_dict[subsystem_name]["size"] += size 233 result_dict[subsystem_name]["file_count"] += 1 234 result_dict[subsystem_name][component_name]["size"] += size 235 result_dict[subsystem_name][component_name]["file_count"] += 1 236 result_dict[subsystem_name][component_name][relative_filepath] = size 237 238 @classmethod 239 def result_unit_adaptive(self, result_dict: Dict[str, Dict]) -> None: 240 for subsystem_name, subsystem_info in result_dict.items(): 241 size = unit_adaptive(subsystem_info["size"]) 242 count = subsystem_info["file_count"] 243 if "size" in subsystem_info.keys(): 244 del subsystem_info["size"] 245 if "file_count" in subsystem_info.keys(): 246 del subsystem_info["file_count"] 247 for component_name, component_info in subsystem_info.items(): 248 component_info["size"] = unit_adaptive(component_info["size"]) 249 subsystem_info["size"] = size 250 subsystem_info["file_count"] = count 251 252 @classmethod 253 def analysis(cls, system_module_info_json: Text, product_dirs: List[str], 254 project_path: Text, product_name: Text, output_file: Text, output_execel: bool, add_baseline: bool, 255 unit_adapt: bool): 256 """ 257 system_module_info_json: json文件 258 product_dirs:要处理的产物的路径列表如["vendor", "system/"] 259 project_path: 项目根路径 260 product_name: eg,rk3568 261 output_file: basename of output file 262 """ 263 project_path = BasicTool.get_abs_path(project_path) 264 rom_baseline_dict: Dict[str, Any] = RomRamBaselineCollector.collect( 265 project_path) 266 with open("rom_ram_baseline.json", 'w', encoding='utf-8') as f: 267 json.dump(rom_baseline_dict, f, indent=4) 268 phone_dir = os.path.join( 269 project_path, "out", product_name, "packages", "phone") 270 product_dirs = [os.path.join(phone_dir, d) for d in product_dirs] 271 pre_collector = PreCollector(project_path) 272 pre_collector.collect_sa_profile() 273 extra_product_info_dict: Dict[str, Dict] = pre_collector.result_dict 274 product_info_dict = cls.__collect_product_info( 275 system_module_info_json, project_path, 276 extra_info=extra_product_info_dict) # collect product info from json file 277 result_dict: Dict[Text:Dict] = dict() 278 for d in product_dirs: 279 file_list: List[Text] = BasicTool.find_all_files(d) 280 for f in file_list: 281 size = os.path.getsize(f) 282 relative_filepath = f.replace(phone_dir, "").lstrip(os.sep) 283 unit: Dict[Text, Any] = product_info_dict.get( 284 relative_filepath) 285 if not unit: 286 bf = f.split('/')[-1] 287 unit: Dict[Text, Any] = product_info_dict.get(bf) 288 if not unit: 289 unit = dict() 290 unit["size"] = size 291 unit["relative_filepath"] = relative_filepath 292 cls.__put(unit, result_dict, rom_baseline_dict, add_baseline) 293 output_dir, _ = os.path.split(output_file) 294 if len(output_dir) != 0: 295 os.makedirs(output_dir, exist_ok=True) 296 if unit_adapt: 297 cls.result_unit_adaptive(result_dict) 298 with open(output_file + ".json", 'w', encoding='utf-8') as f: 299 f.write(json.dumps(result_dict, indent=4)) 300 if output_execel: 301 cls.__save_result_as_excel(result_dict, output_file, add_baseline) 302 303 304def get_args(): 305 VERSION = 2.0 306 parser = argparse.ArgumentParser( 307 description=f"analyze rom size of component.\n") 308 parser.add_argument("-v", "-version", action="version", 309 version=f"version {VERSION}") 310 parser.add_argument("-p", "--project_path", type=str, required=True, 311 help="root path of openharmony. eg: -p ~/openharmony") 312 parser.add_argument("-j", "--module_info_json", required=True, type=str, 313 help="path of out/{product_name}/packages/phone/system_module_info.json") 314 parser.add_argument("-n", "--product_name", required=True, 315 type=str, help="product name. eg: -n rk3568") 316 parser.add_argument("-d", "--product_dir", required=True, action="append", 317 help="subdirectories of out/{product_name}/packages/phone to be counted." 318 "eg: -d system -d vendor") 319 parser.add_argument("-b", "--baseline", action="store_true", 320 help="add baseline of component to the result(-b) or not.") 321 parser.add_argument("-o", "--output_file", type=str, default="rom_analysis_result", 322 help="basename of output file, default: rom_analysis_result. eg: demo/rom_analysis_result") 323 parser.add_argument("-u", "--unit_adaptive", 324 action="store_true", help="unit adaptive") 325 parser.add_argument("-e", "--excel", type=bool, default=False, 326 help="if output result as excel, default: False. eg: -e True") 327 args = parser.parse_args() 328 return args 329 330 331if __name__ == '__main__': 332 args = get_args() 333 module_info_json = args.module_info_json 334 project_origin_path = args.project_path 335 product = args.product_name 336 product_dirs = args.product_dir 337 output_file_name = args.output_file 338 output_excel = args.excel 339 baseline_path = args.baseline 340 unit_adaptiv = args.unit_adaptive 341 RomAnalyzer.analysis(module_info_json, product_dirs, 342 project_origin_path, product, output_file_name, output_excel, baseline_path, unit_adaptiv) 343