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