• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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) or (not subsystem_name):
116                    cn, sn = GnCommonTool.find_part_subsystem(
117                        gn_path, project_path)
118                    component_name = cn if not component_name else component_name
119                    subsystem_name = sn if not subsystem_name else subsystem_name
120            else:
121                print("warning: keyword 'label' not found in {}".format(unit))
122            for target in dest:
123                if cs_flag:
124                    product_info_dict[target] = {
125                        "component_name": component_name,
126                        "subsystem_name": subsystem_name,
127                        "gn_path": gn_path,
128                    }
129                else:
130                    tmp = target.split('/')[-1]
131                    pre_info = extra_info.get(tmp)
132                    if not pre_info:
133                        continue
134                    product_info_dict[target] = pre_info
135        return product_info_dict
136
137    @classmethod
138    def __save_result_as_excel(cls, result_dict: dict, output_name: str, add_baseline: bool):
139        header = ["subsystem_name", "component_name",
140                  "output_file", "size(Byte)"]
141        if add_baseline:
142            header = ["subsystem_name", "component_name", "baseline",
143                      "output_file", "size(Byte)"]
144        tmp_dict = deepcopy(result_dict)
145        excel_writer = SimpleExcelWriter("rom")
146        excel_writer.set_sheet_header(headers=header)
147        subsystem_start_row = 1
148        subsystem_end_row = 0
149        subsystem_col = 0
150        component_start_row = 1
151        component_end_row = 0
152        component_col = 1
153        if add_baseline:
154            baseline_col = 2
155        for subsystem_name in tmp_dict.keys():
156            subsystem_dict = tmp_dict.get(subsystem_name)
157            subsystem_size = subsystem_dict.get("size")
158            subsystem_file_count = subsystem_dict.get("file_count")
159            del subsystem_dict["file_count"]
160            del subsystem_dict["size"]
161            subsystem_end_row += subsystem_file_count
162
163            for component_name in subsystem_dict.keys():
164                component_dict: Dict[str, int] = subsystem_dict.get(
165                    component_name)
166                component_size = component_dict.get("size")
167                component_file_count = component_dict.get("file_count")
168                baseline = component_dict.get("baseline")
169                del component_dict["file_count"]
170                del component_dict["size"]
171                if add_baseline:
172                    del component_dict["baseline"]
173                component_end_row += component_file_count
174
175                for file_name, size in component_dict.items():
176                    line = [subsystem_name, component_name, file_name, size]
177                    if add_baseline:
178                        line = [subsystem_name, component_name,
179                                baseline, file_name, size]
180                    excel_writer.append_line(line)
181                excel_writer.write_merge(component_start_row, component_col, component_end_row, component_col,
182                                         component_name)
183                if add_baseline:
184                    excel_writer.write_merge(component_start_row, baseline_col, component_end_row, baseline_col,
185                                             baseline)
186                component_start_row = component_end_row + 1
187            excel_writer.write_merge(subsystem_start_row, subsystem_col, subsystem_end_row, subsystem_col,
188                                     subsystem_name)
189            subsystem_start_row = subsystem_end_row + 1
190        excel_writer.save(output_name + ".xls")
191
192    @classmethod
193    def __put(cls, unit: typing.Dict[Text, Any], result_dict: typing.Dict[Text, Dict], baseline_dict: Dict[str, Any], add_baseline: bool):
194
195        component_name = NOTFOUND if unit.get(
196            "component_name") is None else unit.get("component_name")
197        subsystem_name = NOTFOUND if unit.get(
198            "subsystem_name") is None else unit.get("subsystem_name")
199
200        def get_rom_baseline():
201            if (not baseline_dict.get(subsystem_name)) or (not baseline_dict.get(subsystem_name).get(component_name)):
202                return str()
203            return baseline_dict.get(subsystem_name).get(component_name).get("rom")
204        size = unit.get("size")
205        relative_filepath = unit.get("relative_filepath")
206        if result_dict.get(subsystem_name) is None:  # 子系统
207            result_dict[subsystem_name] = dict()
208            result_dict[subsystem_name]["size"] = 0
209            result_dict[subsystem_name]["file_count"] = 0
210
211        if result_dict.get(subsystem_name).get(component_name) is None:  # 部件
212            result_dict[subsystem_name][component_name] = dict()
213            result_dict[subsystem_name][component_name]["size"] = 0
214            result_dict[subsystem_name][component_name]["file_count"] = 0
215            if add_baseline:
216                result_dict[subsystem_name][component_name]["baseline"] = get_rom_baseline(
217                )
218
219        result_dict[subsystem_name]["size"] += size
220        result_dict[subsystem_name]["file_count"] += 1
221        result_dict[subsystem_name][component_name]["size"] += size
222        result_dict[subsystem_name][component_name]["file_count"] += 1
223        result_dict[subsystem_name][component_name][relative_filepath] = size
224
225    @classmethod
226    def result_unit_adaptive(self, result_dict: Dict[str, Dict]) -> None:
227        for subsystem_name, subsystem_info in result_dict.items():
228            size = unit_adaptive(subsystem_info["size"])
229            count = subsystem_info["file_count"]
230            if "size" in subsystem_info.keys():
231                del subsystem_info["size"]
232            if "file_count" in subsystem_info.keys():
233                del subsystem_info["file_count"]
234            for component_name, component_info in subsystem_info.items():
235                component_info["size"] = unit_adaptive(component_info["size"])
236            subsystem_info["size"] = size
237            subsystem_info["file_count"] = count
238
239    @classmethod
240    def analysis(cls, system_module_info_json: Text, product_dirs: List[str],
241                 project_path: Text, product_name: Text, output_file: Text, output_execel: bool, add_baseline: bool, unit_adapt: bool):
242        """
243        system_module_info_json: json文件
244        product_dirs:要处理的产物的路径列表如["vendor", "system/"]
245        project_path: 项目根路径
246        product_name: eg,rk3568
247        output_file: basename of output file
248        """
249        project_path = BasicTool.get_abs_path(project_path)
250        rom_baseline_dict: Dict[str, Any] = RomRamBaselineCollector.collect(
251            project_path)
252        with open("rom_ram_baseline.json", 'w', encoding='utf-8') as f:
253            json.dump(rom_baseline_dict, f, indent=4)
254        phone_dir = os.path.join(
255            project_path, "out", product_name, "packages", "phone")
256        product_dirs = [os.path.join(phone_dir, d) for d in product_dirs]
257        pre_collector = PreCollector(project_path)
258        pre_collector.collect_sa_profile()
259        extra_product_info_dict: Dict[str, Dict] = pre_collector.result_dict
260        product_info_dict = cls.__collect_product_info(
261            system_module_info_json, project_path, extra_info=extra_product_info_dict)  # collect product info from json file
262        result_dict: Dict[Text:Dict] = dict()
263        for d in product_dirs:
264            file_list: List[Text] = BasicTool.find_all_files(d)
265            for f in file_list:
266                size = os.path.getsize(f)
267                relative_filepath = f.replace(phone_dir, "").lstrip(os.sep)
268                unit: Dict[Text, Any] = product_info_dict.get(
269                    relative_filepath)
270                if not unit:
271                    bf = f.split('/')[-1]
272                    unit: Dict[Text, Any] = product_info_dict.get(bf)
273                if not unit:
274                    unit = dict()
275                unit["size"] = size
276                unit["relative_filepath"] = relative_filepath
277                cls.__put(unit, result_dict, rom_baseline_dict, add_baseline)
278        output_dir, _ = os.path.split(output_file)
279        if len(output_dir) != 0:
280            os.makedirs(output_dir, exist_ok=True)
281        if unit_adapt:
282            cls.result_unit_adaptive(result_dict)
283        with open(output_file + ".json", 'w', encoding='utf-8') as f:
284            f.write(json.dumps(result_dict, indent=4))
285        if output_execel:
286            cls.__save_result_as_excel(result_dict, output_file, add_baseline)
287
288
289def get_args():
290    VERSION = 2.0
291    parser = argparse.ArgumentParser(
292        description=f"analyze rom size of component.\n")
293    parser.add_argument("-v", "-version", action="version",
294                        version=f"version {VERSION}")
295    parser.add_argument("-p", "--project_path", type=str, required=True,
296                        help="root path of openharmony. eg: -p ~/openharmony")
297    parser.add_argument("-j", "--module_info_json", required=True, type=str,
298                        help="path of out/{product_name}/packages/phone/system_module_info.json")
299    parser.add_argument("-n", "--product_name", required=True,
300                        type=str, help="product name. eg: -n rk3568")
301    parser.add_argument("-d", "--product_dir", required=True, action="append",
302                        help="subdirectories of out/{product_name}/packages/phone to be counted."
303                             "eg: -d system -d vendor")
304    parser.add_argument("-b", "--baseline", action="store_true",
305                        help="add baseline of component to the result(-b) or not.")
306    parser.add_argument("-o", "--output_file", type=str, default="rom_analysis_result",
307                        help="basename of output file, default: rom_analysis_result. eg: demo/rom_analysis_result")
308    parser.add_argument("-u", "--unit_adaptive",
309                        action="store_true", help="unit adaptive")
310    parser.add_argument("-e", "--excel", type=bool, default=False,
311                        help="if output result as excel, default: False. eg: -e True")
312    args = parser.parse_args()
313    return args
314
315
316if __name__ == '__main__':
317    args = get_args()
318    module_info_json = args.module_info_json
319    project_path = args.project_path
320    product_name = args.product_name
321    product_dirs = args.product_dir
322    output_file = args.output_file
323    output_excel = args.excel
324    add_baseline = args.baseline
325    unit_adapt = args.unit_adaptive
326    RomAnalyzer.analysis(module_info_json, product_dirs,
327                         project_path, product_name, output_file, output_excel, add_baseline, unit_adapt)