1ba991379Sopenharmony_ci#!/usr/bin/env python3
2ba991379Sopenharmony_ci# -*- coding: utf-8 -*-
3ba991379Sopenharmony_ci# Copyright (c) 2022 Huawei Device Co., Ltd.
4ba991379Sopenharmony_ci# Licensed under the Apache License, Version 2.0 (the "License");
5ba991379Sopenharmony_ci# you may not use this file except in compliance with the License.
6ba991379Sopenharmony_ci# You may obtain a copy of the License at
7ba991379Sopenharmony_ci#
8ba991379Sopenharmony_ci#     http://www.apache.org/licenses/LICENSE-2.0
9ba991379Sopenharmony_ci#
10ba991379Sopenharmony_ci# Unless required by applicable law or agreed to in writing, software
11ba991379Sopenharmony_ci# distributed under the License is distributed on an "AS IS" BASIS,
12ba991379Sopenharmony_ci# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13ba991379Sopenharmony_ci# See the License for the specific language governing permissions and
14ba991379Sopenharmony_ci# limitations under the License.
15ba991379Sopenharmony_ci
16ba991379Sopenharmony_ci# This file is to implement the rom analyzation of standard device.
17ba991379Sopenharmony_ci#
18ba991379Sopenharmony_ci
19ba991379Sopenharmony_ciimport argparse
20ba991379Sopenharmony_ciimport copy
21ba991379Sopenharmony_ciimport glob
22ba991379Sopenharmony_ciimport json
23ba991379Sopenharmony_ciimport os
24ba991379Sopenharmony_ciimport re
25ba991379Sopenharmony_ciimport sys
26ba991379Sopenharmony_ciimport subprocess
27ba991379Sopenharmony_ciimport typing
28ba991379Sopenharmony_ciimport xml.dom.minidom as dom
29ba991379Sopenharmony_cifrom typing import Dict
30ba991379Sopenharmony_cifrom pprint import pprint
31ba991379Sopenharmony_cifrom pkgs.basic_tool import unit_adaptive
32ba991379Sopenharmony_ci
33ba991379Sopenharmony_cifrom pkgs.simple_excel_writer import SimpleExcelWriter
34ba991379Sopenharmony_ci
35ba991379Sopenharmony_cidebug = True if sys.gettrace() else False
36ba991379Sopenharmony_ci
37ba991379Sopenharmony_ci
38ba991379Sopenharmony_ciclass HDCTool:
39ba991379Sopenharmony_ci    @classmethod
40ba991379Sopenharmony_ci    def verify_hdc(cls, verify_str: str = "OpenHarmony") -> bool:
41ba991379Sopenharmony_ci        """
42ba991379Sopenharmony_ci        验证hdc是否可用
43ba991379Sopenharmony_ci        True:可用
44ba991379Sopenharmony_ci        False:不可用
45ba991379Sopenharmony_ci        """
46ba991379Sopenharmony_ci        cp = subprocess.run(["hdc", "--help"], capture_output=True)
47ba991379Sopenharmony_ci        stdout = str(cp.stdout)
48ba991379Sopenharmony_ci        stderr = str(cp.stderr)
49ba991379Sopenharmony_ci        return verify_str in stdout or verify_str in stderr
50ba991379Sopenharmony_ci
51ba991379Sopenharmony_ci    @classmethod
52ba991379Sopenharmony_ci    def verify_device(cls, device_num: str) -> bool:
53ba991379Sopenharmony_ci        """
54ba991379Sopenharmony_ci        验证设备是否已经连接
55ba991379Sopenharmony_ci        True:已连接
56ba991379Sopenharmony_ci        False:未连接
57ba991379Sopenharmony_ci        """
58ba991379Sopenharmony_ci        cp = subprocess.run(["hdc", "list", "targets"], capture_output=True)
59ba991379Sopenharmony_ci        stdout = str(cp.stdout)
60ba991379Sopenharmony_ci        stderr = str(cp.stderr)
61ba991379Sopenharmony_ci        return device_num in stderr or device_num in stdout
62ba991379Sopenharmony_ci
63ba991379Sopenharmony_ci    @classmethod
64ba991379Sopenharmony_ci    def exec(cls, args: list, output_from: str = "stdout"):
65ba991379Sopenharmony_ci        cp = subprocess.run(args, capture_output=True)
66ba991379Sopenharmony_ci        if output_from == "stdout":
67ba991379Sopenharmony_ci            return cp.stdout.decode()
68ba991379Sopenharmony_ci        elif output_from == "stderr":
69ba991379Sopenharmony_ci            return cp.stderr.decode()
70ba991379Sopenharmony_ci        else:
71ba991379Sopenharmony_ci            print("error: 'output_from' must be stdout or stdin")
72ba991379Sopenharmony_ci
73ba991379Sopenharmony_ci
74ba991379Sopenharmony_cidef delete_values_from_dict(target_dict: typing.Dict, key_list: typing.Iterable):
75ba991379Sopenharmony_ci    for k in key_list:
76ba991379Sopenharmony_ci        if k not in target_dict.keys():
77ba991379Sopenharmony_ci            continue
78ba991379Sopenharmony_ci        del target_dict[k]
79ba991379Sopenharmony_ci
80ba991379Sopenharmony_ci
81ba991379Sopenharmony_ciclass RamAnalyzer:
82ba991379Sopenharmony_ci    @classmethod
83ba991379Sopenharmony_ci    def analysis(cls, cfg_path: str, json_path: str, rom_result_json: str, device_num: str,
84ba991379Sopenharmony_ci                 output_file: str, ss: str, output_excel: bool, baseline_file: str, unit_adapt: bool):
85ba991379Sopenharmony_ci        """
86ba991379Sopenharmony_ci        process size subsystem/component so so_size
87ba991379Sopenharmony_ci        """
88ba991379Sopenharmony_ci        if not HDCTool.verify_hdc():
89ba991379Sopenharmony_ci            print("error: Command 'hdc' not found")
90ba991379Sopenharmony_ci            return
91ba991379Sopenharmony_ci        if not HDCTool.verify_device(device_num):
92ba991379Sopenharmony_ci            print("error: {} is inaccessible or not found".format(device_num))
93ba991379Sopenharmony_ci            return
94ba991379Sopenharmony_ci        with open(rom_result_json, 'r', encoding='utf-8') as f:
95ba991379Sopenharmony_ci            rom_result_dict: typing.Dict = json.loads(f.read())
96ba991379Sopenharmony_ci        # 从rom的分析结果中将需要的elf信息重组
97ba991379Sopenharmony_ci        so_info_dict: typing.Dict[
98ba991379Sopenharmony_ci            str, typing.Dict[str["component_name|subsystem_name|size"], str]] = cls.get_elf_info_from_rom_result(
99ba991379Sopenharmony_ci            rom_result_json)
100ba991379Sopenharmony_ci        process_elf_dict: typing.Dict[str, typing.List[str]] = cls.get_process_so_relationship(cfg_path,
101ba991379Sopenharmony_ci                                                                                               json_path)
102ba991379Sopenharmony_ci        process_size_dict: typing.Dict[str, int] = cls.process_hidumper_info(
103ba991379Sopenharmony_ci            device_num, ss)
104ba991379Sopenharmony_ci        result_dict: typing.Dict[str, typing.Dict[str, typing.Any]] = dict()
105ba991379Sopenharmony_ci        result_dict = cls.result_process4(result_dict, process_size_dict, rom_result_dict, process_elf_dict,
106ba991379Sopenharmony_ci                                          so_info_dict)
107ba991379Sopenharmony_ci        base_dir, _ = os.path.split(output_file)
108ba991379Sopenharmony_ci        if len(base_dir) != 0 and not os.path.isdir(base_dir):
109ba991379Sopenharmony_ci            os.makedirs(base_dir, exist_ok=True)
110ba991379Sopenharmony_ci        with os.fdopen(os.open(output_file + ".json", os.O_WRONLY | os.O_CREAT, mode=0o640), 'w', encoding='utf-8') as f:
111ba991379Sopenharmony_ci            json.dump(result_dict, f, indent=4)
112ba991379Sopenharmony_ci        refactored_result: Dict[str, Dict] = refacotr_result(result_dict)
113ba991379Sopenharmony_ci        if unit_adapt:
114ba991379Sopenharmony_ci            cls.refactored_result_unit_adaptive(refactored_result)
115ba991379Sopenharmony_ci        if baseline_file:
116ba991379Sopenharmony_ci            cls.add_baseline(refactored_result, baseline_file)
117ba991379Sopenharmony_ci        with os.fdopen(os.open(f"refactored_{output_file}.json", os.O_WRONLY | os.O_CREAT, mode=0o640), 'w', encoding='utf-8') as f:
118ba991379Sopenharmony_ci            json.dump(refactored_result, f, indent=4)
119ba991379Sopenharmony_ci        if output_excel:
120ba991379Sopenharmony_ci            cls.__save_result_as_excel(
121ba991379Sopenharmony_ci                refactored_result, output_file + ".xls", ss, baseline_file, unit_adapt)
122ba991379Sopenharmony_ci
123ba991379Sopenharmony_ci    __ss_dict: typing.Dict[str, int] = {
124ba991379Sopenharmony_ci        "Pss": 2,
125ba991379Sopenharmony_ci        "Vss": 3,
126ba991379Sopenharmony_ci        "Rss": 4,
127ba991379Sopenharmony_ci        "Uss": 5
128ba991379Sopenharmony_ci    }
129ba991379Sopenharmony_ci
130ba991379Sopenharmony_ci    @classmethod
131ba991379Sopenharmony_ci    def process_hidumper_info(cls, device_num: str, ss: str) -> typing.Dict[str, int]:
132ba991379Sopenharmony_ci        """
133ba991379Sopenharmony_ci        处理进程名与对应进程大小
134ba991379Sopenharmony_ci        """
135ba991379Sopenharmony_ci
136ba991379Sopenharmony_ci        def exec_once() -> typing.Dict[str, int]:
137ba991379Sopenharmony_ci            stdout = HDCTool.exec(
138ba991379Sopenharmony_ci                ["hdc", "-t", device_num, "shell", "hidumper", "--mem"])
139ba991379Sopenharmony_ci            name_size_dict = cls.__parse_hidumper_mem(stdout, device_num, ss)
140ba991379Sopenharmony_ci            return name_size_dict
141ba991379Sopenharmony_ci
142ba991379Sopenharmony_ci        if not HDCTool.verify_hdc():
143ba991379Sopenharmony_ci            print("error: Command 'hdc' not found")
144ba991379Sopenharmony_ci            return dict()
145ba991379Sopenharmony_ci        if not HDCTool.verify_device(device_num):
146ba991379Sopenharmony_ci            print("error: {} is inaccessible or not found".format(device_num))
147ba991379Sopenharmony_ci            return dict()
148ba991379Sopenharmony_ci
149ba991379Sopenharmony_ci        return exec_once()
150ba991379Sopenharmony_ci
151ba991379Sopenharmony_ci    @classmethod
152ba991379Sopenharmony_ci    def get_elf_info_from_rom_result(cls, rom_result_json: str) -> typing.Dict[str, typing.Dict[str, str]]:
153ba991379Sopenharmony_ci        """
154ba991379Sopenharmony_ci        利用rom_analyzer.py的分析结果,重组成
155ba991379Sopenharmony_ci        {file_base_name: {"subsystem_name":subsystem_name, "component_name":component_name}}
156ba991379Sopenharmony_ci        的形式
157ba991379Sopenharmony_ci        """
158ba991379Sopenharmony_ci        with open(rom_result_json, 'r', encoding='utf-8') as f:
159ba991379Sopenharmony_ci            rom_info_dict = json.load(f)
160ba991379Sopenharmony_ci        elf_info_dict: typing.Dict[str, typing.Dict[str, str]] = dict()
161ba991379Sopenharmony_ci        for subsystem_name in rom_info_dict.keys():
162ba991379Sopenharmony_ci            sub_val_dict: typing.Dict[str, typing.Any] = rom_info_dict.get(
163ba991379Sopenharmony_ci                subsystem_name)
164ba991379Sopenharmony_ci            delete_values_from_dict(sub_val_dict, ["size", "file_count"])
165ba991379Sopenharmony_ci            for component_name in sub_val_dict.keys():
166ba991379Sopenharmony_ci                component_val_dict: typing.Dict[str, str] = sub_val_dict.get(
167ba991379Sopenharmony_ci                    component_name)
168ba991379Sopenharmony_ci                delete_values_from_dict(component_val_dict, [
169ba991379Sopenharmony_ci                    "size", "file_count"])
170ba991379Sopenharmony_ci                for file_name, size in component_val_dict.items():
171ba991379Sopenharmony_ci                    file_basename: str = os.path.split(file_name)[-1]
172ba991379Sopenharmony_ci                    elf_info_dict[file_basename] = {
173ba991379Sopenharmony_ci                        "subsystem_name": subsystem_name,
174ba991379Sopenharmony_ci                        "component_name": component_name,
175ba991379Sopenharmony_ci                        "size": size
176ba991379Sopenharmony_ci                    }
177ba991379Sopenharmony_ci
178ba991379Sopenharmony_ci        return elf_info_dict
179ba991379Sopenharmony_ci
180ba991379Sopenharmony_ci    @classmethod
181ba991379Sopenharmony_ci    def get_process_so_relationship(cls, cfg_path: str, profile_path: str) -> typing.Dict[
182ba991379Sopenharmony_ci        str, typing.List[str]]:
183ba991379Sopenharmony_ci        """
184ba991379Sopenharmony_ci        parse the relationship between process and elf file
185ba991379Sopenharmony_ci        """
186ba991379Sopenharmony_ci        # 从merged_sa里面收集
187ba991379Sopenharmony_ci        process_elf_dict: typing.Dict[str, typing.List[str]] = dict()
188ba991379Sopenharmony_ci        cfg_list = glob.glob(cfg_path + os.sep + "*.cfg", recursive=True)
189ba991379Sopenharmony_ci        for cfg in cfg_list:
190ba991379Sopenharmony_ci            if debug:
191ba991379Sopenharmony_ci                print("parsing: ", cfg)
192ba991379Sopenharmony_ci            try:
193ba991379Sopenharmony_ci                cls.__parse_process_cfg(cfg, profile_path, process_elf_dict)
194ba991379Sopenharmony_ci            except:
195ba991379Sopenharmony_ci                print("parse '{}' failed".format(cfg))
196ba991379Sopenharmony_ci            finally:
197ba991379Sopenharmony_ci                ...
198ba991379Sopenharmony_ci        return process_elf_dict
199ba991379Sopenharmony_ci
200ba991379Sopenharmony_ci    @classmethod
201ba991379Sopenharmony_ci    def find_elf_size_from_rom_result(cls, service_name: str, subsystem_name: str, component_name: str,
202ba991379Sopenharmony_ci                                      evaluator: typing.Callable, rom_result_dict: typing.Dict[str, typing.Dict]) -> \
203ba991379Sopenharmony_ci            typing.Tuple[
204ba991379Sopenharmony_ci                bool, str, str, int]:
205ba991379Sopenharmony_ci        """
206ba991379Sopenharmony_ci        全局查找进程的相关elf文件
207ba991379Sopenharmony_ci        subsystem_name与component_name可明确指定,或为*以遍历整个dict
208ba991379Sopenharmony_ci        evaluator:评估elf文件的从phone下面开始的路径与service_name的关系,评判如何才是找到了
209ba991379Sopenharmony_ci        returns: 是否查找到,elf文件名,部件名,size
210ba991379Sopenharmony_ci        """
211ba991379Sopenharmony_ci        subsystem_name_list = [
212ba991379Sopenharmony_ci            subsystem_name] if subsystem_name != "*" else rom_result_dict.keys()
213ba991379Sopenharmony_ci        for sn in subsystem_name_list:
214ba991379Sopenharmony_ci            sub_val_dict = rom_result_dict.get(sn)
215ba991379Sopenharmony_ci            component_name_list = [
216ba991379Sopenharmony_ci                component_name] if component_name != '*' else sub_val_dict.keys()
217ba991379Sopenharmony_ci            for cn in component_name_list:
218ba991379Sopenharmony_ci                if cn == "size" or cn == "file_count":
219ba991379Sopenharmony_ci                    continue
220ba991379Sopenharmony_ci                component_val_dict: typing.Dict[str,
221ba991379Sopenharmony_ci                int] = sub_val_dict.get(cn)
222ba991379Sopenharmony_ci                for k, v in component_val_dict.items():
223ba991379Sopenharmony_ci                    if k == "size" or k == "file_count":
224ba991379Sopenharmony_ci                        continue
225ba991379Sopenharmony_ci                    if not evaluator(service_name, k):
226ba991379Sopenharmony_ci                        continue
227ba991379Sopenharmony_ci                    return True, os.path.split(k)[-1], sn, cn, v
228ba991379Sopenharmony_ci        return False, str(), str(), str(), int()
229ba991379Sopenharmony_ci
230ba991379Sopenharmony_ci    @classmethod
231ba991379Sopenharmony_ci    def add_baseline(self, refactored_result_dict: Dict, baseline_file: str) -> None:
232ba991379Sopenharmony_ci        with open(baseline_file, 'r', encoding='utf-8') as f:
233ba991379Sopenharmony_ci            baseline_dict = json.load(f)
234ba991379Sopenharmony_ci        for subsystem_name, subsystem_info in refactored_result_dict.items():
235ba991379Sopenharmony_ci            for component_name, component_info in subsystem_info.items():
236ba991379Sopenharmony_ci                if component_name == "size":
237ba991379Sopenharmony_ci                    continue
238ba991379Sopenharmony_ci                if not baseline_dict.get(subsystem_name):
239ba991379Sopenharmony_ci                    continue
240ba991379Sopenharmony_ci                if not baseline_dict[subsystem_name].get(component_name):
241ba991379Sopenharmony_ci                    continue
242ba991379Sopenharmony_ci                component_info["baseline"] = baseline_dict[subsystem_name][component_name].get(
243ba991379Sopenharmony_ci                    "ram")
244ba991379Sopenharmony_ci
245ba991379Sopenharmony_ci    @classmethod
246ba991379Sopenharmony_ci    def inside_refactored_result_unit_adaptive(cls, process_info):
247ba991379Sopenharmony_ci        for elf_name, elf_size in process_info["elf"].items():
248ba991379Sopenharmony_ci            process_info["elf"][elf_name] = unit_adaptive(elf_size)
249ba991379Sopenharmony_ci        return process_info
250ba991379Sopenharmony_ci
251ba991379Sopenharmony_ci    @classmethod
252ba991379Sopenharmony_ci    def refactored_result_unit_adaptive(cls, result_dict: Dict[str, Dict]) -> None:
253ba991379Sopenharmony_ci        for subsystem_name, subsystem_info in result_dict.items():
254ba991379Sopenharmony_ci            sub_size = unit_adaptive(subsystem_info["size"])
255ba991379Sopenharmony_ci            del subsystem_info["size"]
256ba991379Sopenharmony_ci            for component_name, component_info in subsystem_info.items():
257ba991379Sopenharmony_ci                com_size = unit_adaptive(component_info["size"])
258ba991379Sopenharmony_ci                del component_info["size"]
259ba991379Sopenharmony_ci                for process_name, process_info in component_info.items():
260ba991379Sopenharmony_ci                    pro_size = unit_adaptive(process_info["size"])
261ba991379Sopenharmony_ci                    del process_info["size"]
262ba991379Sopenharmony_ci                    process_info = cls.inside_refactored_result_unit_adaptive(process_info)
263ba991379Sopenharmony_ci                    process_info["size"] = pro_size
264ba991379Sopenharmony_ci                component_info["size"] = com_size
265ba991379Sopenharmony_ci            subsystem_info["size"] = sub_size
266ba991379Sopenharmony_ci
267ba991379Sopenharmony_ci    @classmethod
268ba991379Sopenharmony_ci    def result_process1(cls, result_dict, process_name, process_size, elf, size):
269ba991379Sopenharmony_ci        result_dict[process_name] = dict()
270ba991379Sopenharmony_ci        result_dict[process_name]["size"] = process_size
271ba991379Sopenharmony_ci        result_dict[process_name]["startup"] = dict()
272ba991379Sopenharmony_ci        result_dict[process_name]["startup"]["init"] = dict()
273ba991379Sopenharmony_ci        result_dict[process_name]["startup"]["init"][elf if len(
274ba991379Sopenharmony_ci            elf) != 0 else "UNKNOWN"] = size
275ba991379Sopenharmony_ci        return result_dict
276ba991379Sopenharmony_ci
277ba991379Sopenharmony_ci    @classmethod
278ba991379Sopenharmony_ci    def result_process2(cls, result_dict, process_name, subsystem_name, process_size, component_name, hap_name, size):
279ba991379Sopenharmony_ci        result_dict[process_name] = dict()
280ba991379Sopenharmony_ci        result_dict[process_name]["size"] = process_size
281ba991379Sopenharmony_ci        result_dict[process_name][subsystem_name] = dict()
282ba991379Sopenharmony_ci        result_dict[process_name][subsystem_name][component_name] = dict()
283ba991379Sopenharmony_ci        result_dict[process_name][subsystem_name][component_name][hap_name if len(
284ba991379Sopenharmony_ci            hap_name) != 0 else "UNKNOWN"] = size
285ba991379Sopenharmony_ci        return result_dict
286ba991379Sopenharmony_ci
287ba991379Sopenharmony_ci    @classmethod
288ba991379Sopenharmony_ci    def result_process3(cls, result_dict, process_name, process_size):
289ba991379Sopenharmony_ci        result_dict[process_name] = dict()
290ba991379Sopenharmony_ci        result_dict[process_name]["size"] = process_size
291ba991379Sopenharmony_ci        result_dict[process_name]["UNKNOWN"] = dict()
292ba991379Sopenharmony_ci        result_dict[process_name]["UNKNOWN"]["UNKNOWN"] = dict()
293ba991379Sopenharmony_ci        result_dict[process_name]["UNKNOWN"]["UNKNOWN"]["UNKNOWN"] = int()
294ba991379Sopenharmony_ci        return result_dict
295ba991379Sopenharmony_ci
296ba991379Sopenharmony_ci    @classmethod
297ba991379Sopenharmony_ci    def result_process4(cls, result_dict, process_size_dict, rom_result_dict, process_elf_dict, so_info_dict):
298ba991379Sopenharmony_ci        def get(key: typing.Any, dt: typing.Dict[str, typing.Any]):
299ba991379Sopenharmony_ci            for k, v in dt.items():
300ba991379Sopenharmony_ci                if k.startswith(key) or (len(v) > 0 and key == v[0]):
301ba991379Sopenharmony_ci                    # 要么uinput_inject的对应key为mmi_uinput_inject。对于此类特殊处理,即:如果service_name找不到,但是直接执行的bin等于这个名字,也认为找到
302ba991379Sopenharmony_ci                    return v
303ba991379Sopenharmony_ci
304ba991379Sopenharmony_ci        for process_name, process_size in process_size_dict.items():  # 从进程出发
305ba991379Sopenharmony_ci            if not process_name:
306ba991379Sopenharmony_ci                print("warning: an empty 'process_name' has been found.")
307ba991379Sopenharmony_ci                continue
308ba991379Sopenharmony_ci            # 如果部件是init,特殊处理
309ba991379Sopenharmony_ci            if process_name == "init":
310ba991379Sopenharmony_ci                _, elf, _, _, size = cls.find_elf_size_from_rom_result(process_name, "startup", "init",
311ba991379Sopenharmony_ci                                                                       lambda x, y: os.path.split(y)[
312ba991379Sopenharmony_ci                                                                                        -1].lower() == x.lower(),
313ba991379Sopenharmony_ci                                                                       rom_result_dict)
314ba991379Sopenharmony_ci                result_dict = cls.result_process1(result_dict, process_name, process_size, elf, size)
315ba991379Sopenharmony_ci                continue
316ba991379Sopenharmony_ci            # 如果是hap,特殊处理
317ba991379Sopenharmony_ci            if (process_name.startswith("com.") or process_name.startswith("ohos.")):
318ba991379Sopenharmony_ci                _, hap_name, subsystem_name, component_name, size = cls.find_elf_size_from_rom_result(process_name, "*",
319ba991379Sopenharmony_ci                                                                                                      "*",
320ba991379Sopenharmony_ci                                                                                                      lambda x, y: len(
321ba991379Sopenharmony_ci                                                                                                          y.split(
322ba991379Sopenharmony_ci                                                                                                              '/')) >= 3 and x.lower().startswith(
323ba991379Sopenharmony_ci                                                                                                          y.split('/')[
324ba991379Sopenharmony_ci                                                                                                              2].lower()),
325ba991379Sopenharmony_ci                                                                                                      rom_result_dict)
326ba991379Sopenharmony_ci                result_dict = cls.result_process2(result_dict, process_name, subsystem_name, process_size,
327ba991379Sopenharmony_ci                                                  component_name, hap_name, size)
328ba991379Sopenharmony_ci                continue
329ba991379Sopenharmony_ci            # 得到进程相关的elf文件list
330ba991379Sopenharmony_ci            so_list: list = get(process_name, process_elf_dict)
331ba991379Sopenharmony_ci            if so_list is None:
332ba991379Sopenharmony_ci                print("warning: process '{}' not found in .json or .cfg".format(
333ba991379Sopenharmony_ci                    process_name))
334ba991379Sopenharmony_ci                result_dict = cls.result_process3(result_dict, process_name, process_size)
335ba991379Sopenharmony_ci                continue
336ba991379Sopenharmony_ci            result_dict[process_name] = dict()
337ba991379Sopenharmony_ci            result_dict[process_name]["size"] = process_size
338ba991379Sopenharmony_ci            for so in so_list:
339ba991379Sopenharmony_ci                unit = so_info_dict.get(so)
340ba991379Sopenharmony_ci                if unit is None:
341ba991379Sopenharmony_ci                    result_dict[process_name]["UNKNOWN"] = dict()
342ba991379Sopenharmony_ci                    result_dict[process_name]["UNKNOWN"]["UNKNOWN"] = dict()
343ba991379Sopenharmony_ci                    result_dict[process_name]["UNKNOWN"]["UNKNOWN"][so] = int()
344ba991379Sopenharmony_ci                    print("warning: '{}' in {} not found in json from rom analysis result".format(
345ba991379Sopenharmony_ci                        so, process_name))
346ba991379Sopenharmony_ci                    continue
347ba991379Sopenharmony_ci                component_name = unit.get("component_name")
348ba991379Sopenharmony_ci                subsystem_name = unit.get("subsystem_name")
349ba991379Sopenharmony_ci                so_size = unit.get("size")
350ba991379Sopenharmony_ci                if result_dict.get(process_name).get(subsystem_name) is None:
351ba991379Sopenharmony_ci                    result_dict[process_name][subsystem_name] = dict()
352ba991379Sopenharmony_ci                if result_dict.get(process_name).get(subsystem_name).get(component_name) is None:
353ba991379Sopenharmony_ci                    result_dict[process_name][subsystem_name][component_name] = dict()
354ba991379Sopenharmony_ci                result_dict[process_name][subsystem_name][component_name][so] = so_size
355ba991379Sopenharmony_ci        return result_dict
356ba991379Sopenharmony_ci
357ba991379Sopenharmony_ci    @classmethod
358ba991379Sopenharmony_ci    def __hidumper_mem_line_process(cls, content: typing.Text) -> typing.List[typing.Text]:
359ba991379Sopenharmony_ci        """
360ba991379Sopenharmony_ci        将hidumper的拥有的数据行进行分割,得到
361ba991379Sopenharmony_ci        [pid, name, pss, vss, rss, uss]格式的list
362ba991379Sopenharmony_ci        """
363ba991379Sopenharmony_ci        trival_pattern = re.compile(r"kB|\(.*\)(?#去除单位kB以及小括号内的任意数据,包括小括号)")
364ba991379Sopenharmony_ci        content = re.sub(trival_pattern, "", content)
365ba991379Sopenharmony_ci        blank_pattern = re.compile(r"\s+(?#匹配一个或多个空格)")
366ba991379Sopenharmony_ci        return re.sub(blank_pattern, ' ', content.strip()).split()
367ba991379Sopenharmony_ci
368ba991379Sopenharmony_ci    @classmethod
369ba991379Sopenharmony_ci    def __parse_hidumper_mem(cls, content: typing.Text, device_num: str, ss: str = "Pss") -> typing.Dict[
370ba991379Sopenharmony_ci        typing.Text, int]:
371ba991379Sopenharmony_ci        """
372ba991379Sopenharmony_ci        解析:hidumper --meme的结果
373ba991379Sopenharmony_ci        返回{process_name: pss}形式的字典
374ba991379Sopenharmony_ci        '248  	samgr              1464(0 in SwapPss) kB    15064 kB     6928 kB     1072 kB\r'
375ba991379Sopenharmony_ci        """
376ba991379Sopenharmony_ci
377ba991379Sopenharmony_ci        def find_full_process_name(hname: str) -> str:
378ba991379Sopenharmony_ci            for lname in __process_name_list:
379ba991379Sopenharmony_ci                if lname.startswith(hname):
380ba991379Sopenharmony_ci                    return lname
381ba991379Sopenharmony_ci            return str()
382ba991379Sopenharmony_ci
383ba991379Sopenharmony_ci        def process_ps_ef(content: str) -> list:
384ba991379Sopenharmony_ci            line_list = content.strip().split("\n")[1:]
385ba991379Sopenharmony_ci            process_name_list = list()
386ba991379Sopenharmony_ci            for line in line_list:
387ba991379Sopenharmony_ci                process_name = line.split()[7]
388ba991379Sopenharmony_ci                if process_name.startswith('['):
389ba991379Sopenharmony_ci                    continue
390ba991379Sopenharmony_ci                process_name_list.append(process_name)
391ba991379Sopenharmony_ci            return process_name_list
392ba991379Sopenharmony_ci
393ba991379Sopenharmony_ci        if ss not in cls.__ss_dict.keys():
394ba991379Sopenharmony_ci            print("error: {} is not a valid parameter".format(ss))
395ba991379Sopenharmony_ci            return dict()
396ba991379Sopenharmony_ci        output = content.split('\n')
397ba991379Sopenharmony_ci        process_pss_dict = dict()
398ba991379Sopenharmony_ci        __process_name_list: typing.List[str] = process_ps_ef(
399ba991379Sopenharmony_ci            HDCTool.exec(["hdc", "-t", device_num, "shell", "ps", "-ef"]))
400ba991379Sopenharmony_ci        for line in output:
401ba991379Sopenharmony_ci            if "Total Memory Usage by Size" in line:
402ba991379Sopenharmony_ci                break
403ba991379Sopenharmony_ci            if line.isspace():
404ba991379Sopenharmony_ci                continue
405ba991379Sopenharmony_ci            processed: typing.List[typing.Text] = cls.__hidumper_mem_line_process(
406ba991379Sopenharmony_ci                line)
407ba991379Sopenharmony_ci            # 如果第一列不是数字(pid),就过
408ba991379Sopenharmony_ci            if not processed or not processed[0].isnumeric():
409ba991379Sopenharmony_ci                continue
410ba991379Sopenharmony_ci            name = processed[1]  # 否则的话就取名字,和对应的size
411ba991379Sopenharmony_ci            size = int(processed[cls.__ss_dict.get(ss)]) * \
412ba991379Sopenharmony_ci                   1024  # kilo byte to byte
413ba991379Sopenharmony_ci            full_process_name = find_full_process_name(name)
414ba991379Sopenharmony_ci            if not full_process_name:
415ba991379Sopenharmony_ci                print(
416ba991379Sopenharmony_ci                    f"warning: process \"{full_process_name}\" not found in the result of command \"ps -ef\"")
417ba991379Sopenharmony_ci                continue
418ba991379Sopenharmony_ci            process_pss_dict[full_process_name] = size
419ba991379Sopenharmony_ci        return process_pss_dict
420ba991379Sopenharmony_ci
421ba991379Sopenharmony_ci    @classmethod
422ba991379Sopenharmony_ci    def __parse_process_json(cls, file_path: str, result_dict: typing.Dict[str, typing.List[str]]):
423ba991379Sopenharmony_ci        """
424ba991379Sopenharmony_ci        解析json文件,结存存入 result_dict中,格式:{process_name: os_list}
425ba991379Sopenharmony_ci        其中,so_list中是so的base_name
426ba991379Sopenharmony_ci        """
427ba991379Sopenharmony_ci        if not (os.path.isfile(file_path) and file_path.endswith(".json")):
428ba991379Sopenharmony_ci            print("warning: {} not exist or not a json file".format(file_path))
429ba991379Sopenharmony_ci            return
430ba991379Sopenharmony_ci        with open(file_path, 'r', encoding='utf-8') as f:
431ba991379Sopenharmony_ci            j_content: typing.Dict[str, typing.Any] = json.load(f)
432ba991379Sopenharmony_ci        if "process" not in j_content.keys() or "systemability" not in j_content.keys():
433ba991379Sopenharmony_ci            print(
434ba991379Sopenharmony_ci                f"warning: {file_path} has no field 'process' or 'systemability'")
435ba991379Sopenharmony_ci            return
436ba991379Sopenharmony_ci        process_name: str = j_content.get("process")
437ba991379Sopenharmony_ci        elf_list: typing.List[str] = list()
438ba991379Sopenharmony_ci        for sa in j_content.get("systemability"):
439ba991379Sopenharmony_ci            libpath: str = sa.get("libpath")
440ba991379Sopenharmony_ci            if not libpath:
441ba991379Sopenharmony_ci                continue
442ba991379Sopenharmony_ci            elf_list.append(libpath)
443ba991379Sopenharmony_ci        result_dict[process_name] = elf_list
444ba991379Sopenharmony_ci
445ba991379Sopenharmony_ci    @classmethod
446ba991379Sopenharmony_ci    def __parse_process_cfg(cls, cfg_path: str, profile_path: str, result_dict: dict):
447ba991379Sopenharmony_ci        """
448ba991379Sopenharmony_ci        解析cfg,因为有的cfg会拉起xml中的进程,所以也可能会去解析xml
449ba991379Sopenharmony_ci        """
450ba991379Sopenharmony_ci        with open(cfg_path, 'r', encoding='utf-8') as f:
451ba991379Sopenharmony_ci            cfg_dict = json.loads(f.read())
452ba991379Sopenharmony_ci        services = cfg_dict.get("services")
453ba991379Sopenharmony_ci        if services is None:
454ba991379Sopenharmony_ci            print("warning: 'services' not in {}".format(cfg_path))
455ba991379Sopenharmony_ci            return
456ba991379Sopenharmony_ci        for service in services:
457ba991379Sopenharmony_ci            process_name = service.get("name")
458ba991379Sopenharmony_ci            first, *path_list = service.get("path")
459ba991379Sopenharmony_ci            if first.endswith("sa_main"):
460ba991379Sopenharmony_ci                # 由sa_main去来起进程
461ba991379Sopenharmony_ci                xml_base_name = os.path.split(path_list[0])[-1]
462ba991379Sopenharmony_ci                cls.__parse_process_json(os.path.join(
463ba991379Sopenharmony_ci                    profile_path, xml_base_name), result_dict)
464ba991379Sopenharmony_ci            else:
465ba991379Sopenharmony_ci                # 直接执行
466ba991379Sopenharmony_ci                if result_dict.get(process_name) is None:
467ba991379Sopenharmony_ci                    result_dict[process_name] = list()
468ba991379Sopenharmony_ci                result_dict.get(process_name).append(os.path.split(first)[-1])
469ba991379Sopenharmony_ci
470ba991379Sopenharmony_ci    @classmethod
471ba991379Sopenharmony_ci    def __inside_save_result_as_excel(cls, baseline_file, subsystem_name, component_name, component_size,
472ba991379Sopenharmony_ci                                      component_baseline, process_name, process_size, elf_name, elf_size):
473ba991379Sopenharmony_ci        if baseline_file:
474ba991379Sopenharmony_ci            return [subsystem_name, component_name, component_size,
475ba991379Sopenharmony_ci                    component_baseline, process_name, process_size, elf_name, elf_size]
476ba991379Sopenharmony_ci        else:
477ba991379Sopenharmony_ci            return [subsystem_name, component_name, component_size,
478ba991379Sopenharmony_ci                    process_name, process_size, elf_name, elf_size]
479ba991379Sopenharmony_ci
480ba991379Sopenharmony_ci    @classmethod
481ba991379Sopenharmony_ci    def __save_result_as_excel(cls, data_dict: dict, filename: str, ss: str, baseline_file: str, unit_adapt: bool):
482ba991379Sopenharmony_ci        """
483ba991379Sopenharmony_ci        保存结果到excel中
484ba991379Sopenharmony_ci        子系统:{
485ba991379Sopenharmony_ci            "size": 1234,
486ba991379Sopenharmony_ci            部件:{
487ba991379Sopenharmony_ci                "size":123,
488ba991379Sopenharmony_ci                "base_line":124,
489ba991379Sopenharmony_ci                进程:{
490ba991379Sopenharmony_ci                    "size":12,
491ba991379Sopenharmony_ci                    "elf":{
492ba991379Sopenharmony_ci                        "elf_file_1":elf_size,
493ba991379Sopenharmony_ci                        ...
494ba991379Sopenharmony_ci                    }
495ba991379Sopenharmony_ci                }
496ba991379Sopenharmony_ci            }
497ba991379Sopenharmony_ci        }
498ba991379Sopenharmony_ci        """
499ba991379Sopenharmony_ci        tmp_dict = copy.deepcopy(data_dict)
500ba991379Sopenharmony_ci        writer = SimpleExcelWriter("ram_info")
501ba991379Sopenharmony_ci        header_unit = "" if unit_adapt else ", Byte"
502ba991379Sopenharmony_ci        header = [
503ba991379Sopenharmony_ci            "subsystem_name", "component_name", f"component_size(ram{header_unit})", "process_name",
504ba991379Sopenharmony_ci            f"process_size({ss}{header_unit})", "elf", f"elf_size{'' if unit_adapt else '(Byte)'}"
505ba991379Sopenharmony_ci        ]
506ba991379Sopenharmony_ci        if baseline_file:
507ba991379Sopenharmony_ci            header = [
508ba991379Sopenharmony_ci                "subsystem_name", "component_name", f"component_size(ram{header_unit})", "baseline", "process_name",
509ba991379Sopenharmony_ci                f"process_size({ss}{header_unit})", "elf", f"elf_size{'' if unit_adapt else '(Byte)'}"
510ba991379Sopenharmony_ci            ]
511ba991379Sopenharmony_ci        writer.set_sheet_header(header)
512ba991379Sopenharmony_ci        subsystem_c = 0
513ba991379Sopenharmony_ci        subsystem_start_r = 1
514ba991379Sopenharmony_ci        subsystem_end_r = 0
515ba991379Sopenharmony_ci
516ba991379Sopenharmony_ci        component_c = 1
517ba991379Sopenharmony_ci        component_start_r = 1
518ba991379Sopenharmony_ci        component_end_r = 0
519ba991379Sopenharmony_ci        component_size_c = 2
520ba991379Sopenharmony_ci        baseline_c = 3
521ba991379Sopenharmony_ci
522ba991379Sopenharmony_ci        process_start_r = 1
523ba991379Sopenharmony_ci        process_end_r = 0
524ba991379Sopenharmony_ci        process_c = 4
525ba991379Sopenharmony_ci        process_size_c = 5
526ba991379Sopenharmony_ci        if not baseline_file:
527ba991379Sopenharmony_ci            process_c -= 1
528ba991379Sopenharmony_ci            process_size_c -= 1
529ba991379Sopenharmony_ci        for subsystem_name, subsystem_info in tmp_dict.items():
530ba991379Sopenharmony_ci            subsystem_size = subsystem_info.get("size")
531ba991379Sopenharmony_ci            if subsystem_size:
532ba991379Sopenharmony_ci                del subsystem_info["size"]
533ba991379Sopenharmony_ci            for component_name, component_info in subsystem_info.items():
534ba991379Sopenharmony_ci                component_size = component_info.get("size")
535ba991379Sopenharmony_ci                component_baseline = component_info.get("baseline")
536ba991379Sopenharmony_ci                if "size" in component_info.keys():
537ba991379Sopenharmony_ci                    del component_info["size"]
538ba991379Sopenharmony_ci                if "baseline" in component_info.keys():
539ba991379Sopenharmony_ci                    del component_info["baseline"]
540ba991379Sopenharmony_ci                for process_name, process_info in component_info.items():
541ba991379Sopenharmony_ci                    process_size = process_info.get("size")
542ba991379Sopenharmony_ci                    elf_info = process_info.get("elf")
543ba991379Sopenharmony_ci                    for elf_name, elf_size in elf_info.items():
544ba991379Sopenharmony_ci                        line = cls.__inside_save_result_as_excel(baseline_file, subsystem_name, component_name,
545ba991379Sopenharmony_ci                                                                 component_size,
546ba991379Sopenharmony_ci                                                                 component_baseline, process_name, process_size,
547ba991379Sopenharmony_ci                                                                 elf_name, elf_size)
548ba991379Sopenharmony_ci                        writer.append_line(line)
549ba991379Sopenharmony_ci                    elf_count = len(elf_info)
550ba991379Sopenharmony_ci                    process_end_r += elf_count
551ba991379Sopenharmony_ci                    component_end_r += elf_count
552ba991379Sopenharmony_ci                    subsystem_end_r += elf_count
553ba991379Sopenharmony_ci                    writer.write_merge(
554ba991379Sopenharmony_ci                        process_start_r, process_c, process_end_r, process_c, process_name)
555ba991379Sopenharmony_ci                    writer.write_merge(
556ba991379Sopenharmony_ci                        process_start_r, process_size_c, process_end_r, process_size_c, process_size)
557ba991379Sopenharmony_ci                    process_start_r = process_end_r + 1
558ba991379Sopenharmony_ci                writer.write_merge(component_start_r, component_c,
559ba991379Sopenharmony_ci                                   component_end_r, component_c, component_name)
560ba991379Sopenharmony_ci                writer.write_merge(component_start_r, component_size_c,
561ba991379Sopenharmony_ci                                   component_end_r, component_size_c, component_size)
562ba991379Sopenharmony_ci                if baseline_file:
563ba991379Sopenharmony_ci                    writer.write_merge(component_start_r, baseline_c,
564ba991379Sopenharmony_ci                                       component_end_r, baseline_c, component_baseline)
565ba991379Sopenharmony_ci                component_start_r = component_end_r + 1
566ba991379Sopenharmony_ci            writer.write_merge(subsystem_start_r, subsystem_c,
567ba991379Sopenharmony_ci                               subsystem_end_r, subsystem_c, subsystem_name)
568ba991379Sopenharmony_ci            subsystem_start_r = subsystem_end_r + 1
569ba991379Sopenharmony_ci        writer.save(filename)
570ba991379Sopenharmony_ci
571ba991379Sopenharmony_ci
572ba991379Sopenharmony_cidef inside_refacotr_result(component_info, refactored_ram_dict, subsystem_name, component_name, process_name,
573ba991379Sopenharmony_ci                           process_size):
574ba991379Sopenharmony_ci    for elf_name, elf_size in component_info.items():
575ba991379Sopenharmony_ci        if not refactored_ram_dict.get(subsystem_name):
576ba991379Sopenharmony_ci            refactored_ram_dict[subsystem_name] = dict()
577ba991379Sopenharmony_ci            refactored_ram_dict[subsystem_name]["size"] = 0
578ba991379Sopenharmony_ci        if not refactored_ram_dict[subsystem_name].get(component_name):
579ba991379Sopenharmony_ci            refactored_ram_dict[subsystem_name][component_name] = dict(
580ba991379Sopenharmony_ci            )
581ba991379Sopenharmony_ci            refactored_ram_dict[subsystem_name][component_name]["size"] = 0
582ba991379Sopenharmony_ci        refactored_ram_dict[subsystem_name][component_name][process_name] = dict(
583ba991379Sopenharmony_ci        )
584ba991379Sopenharmony_ci        refactored_ram_dict[subsystem_name][component_name][process_name]["size"] = process_size
585ba991379Sopenharmony_ci        refactored_ram_dict[subsystem_name][component_name][process_name]["elf"] = dict(
586ba991379Sopenharmony_ci        )
587ba991379Sopenharmony_ci        refactored_ram_dict[subsystem_name][component_name][process_name]["elf"][elf_name] = elf_size
588ba991379Sopenharmony_ci        refactored_ram_dict[subsystem_name]["size"] += process_size
589ba991379Sopenharmony_ci        refactored_ram_dict[subsystem_name][component_name]["size"] += process_size
590ba991379Sopenharmony_ci    return refactored_ram_dict
591ba991379Sopenharmony_ci
592ba991379Sopenharmony_ci
593ba991379Sopenharmony_cidef refacotr_result(ram_result: Dict[str, Dict]) -> Dict[str, Dict]:
594ba991379Sopenharmony_ci    refactored_ram_dict: Dict[str, Dict] = dict()
595ba991379Sopenharmony_ci    for process_name, process_info in ram_result.items():
596ba991379Sopenharmony_ci        process_size = process_info.get("size")
597ba991379Sopenharmony_ci        del process_info["size"]
598ba991379Sopenharmony_ci        for subsystem_name, subsystem_info in process_info.items():
599ba991379Sopenharmony_ci            for component_name, component_info in subsystem_info.items():
600ba991379Sopenharmony_ci                refactored_ram_dict = inside_refacotr_result(component_info, refactored_ram_dict, subsystem_name,
601ba991379Sopenharmony_ci                                                             component_name, process_name, process_size)
602ba991379Sopenharmony_ci    return refactored_ram_dict
603ba991379Sopenharmony_ci
604ba991379Sopenharmony_ci
605ba991379Sopenharmony_cidef get_args():
606ba991379Sopenharmony_ci    version_num = 1.0
607ba991379Sopenharmony_ci    parser = argparse.ArgumentParser(
608ba991379Sopenharmony_ci        description="analyze ram size of component"
609ba991379Sopenharmony_ci    )
610ba991379Sopenharmony_ci    parser.add_argument("-v", "-version", action="version",
611ba991379Sopenharmony_ci                        version=f"version {version_num}")
612ba991379Sopenharmony_ci    parser.add_argument("-s", "--json_path", type=str, required=True,
613ba991379Sopenharmony_ci                        help="path of sa json file. eg: -x ~/openharmony/out/rk3568/packages/phone/system/profile")
614ba991379Sopenharmony_ci    parser.add_argument("-c", "--cfg_path", type=str, required=True,
615ba991379Sopenharmony_ci                        help="path of cfg files. eg: -c ./cfgs/")
616ba991379Sopenharmony_ci    parser.add_argument("-j", "--rom_result", type=str, default="./rom_analysis_result.json",
617ba991379Sopenharmony_ci                        help="json file produced by rom_analyzer_v1.0.py, default: ./rom_analysis_result.json."
618ba991379Sopenharmony_ci                             "eg: -j ./demo/rom_analysis_result.json")
619ba991379Sopenharmony_ci    parser.add_argument("-n", "--device_num", type=str, required=True,
620ba991379Sopenharmony_ci                        help="device number to be collect hidumper info. eg: -n 7001005458323933328a01fce16d3800")
621ba991379Sopenharmony_ci    parser.add_argument("-b", "--baseline_file", type=str, default="",
622ba991379Sopenharmony_ci                        help="baseline file of rom and ram generated by rom analysis.")
623ba991379Sopenharmony_ci    parser.add_argument("-o", "--output_filename", default="ram_analysis_result", type=str,
624ba991379Sopenharmony_ci                        help="base name of output file, default: ram_analysis_result. eg: -o ram_analysis_result")
625ba991379Sopenharmony_ci    parser.add_argument("-u", "--unit_adaptive",
626ba991379Sopenharmony_ci                        action="store_true", help="unit adaptive")
627ba991379Sopenharmony_ci    parser.add_argument("-e", "--excel", type=bool, default=False,
628ba991379Sopenharmony_ci                        help="if output result as excel, default: False. eg: -e True")
629ba991379Sopenharmony_ci    args = parser.parse_args()
630ba991379Sopenharmony_ci    return args
631ba991379Sopenharmony_ci
632ba991379Sopenharmony_ci
633ba991379Sopenharmony_cidef abspath(path: str) -> str:
634ba991379Sopenharmony_ci    return os.path.abspath(os.path.expanduser(path))
635ba991379Sopenharmony_ci
636ba991379Sopenharmony_ci
637ba991379Sopenharmony_ciif __name__ == '__main__':
638ba991379Sopenharmony_ci    args = get_args()
639ba991379Sopenharmony_ci    cfg_path_name = abspath(args.cfg_path)
640ba991379Sopenharmony_ci    profile_path_name = abspath(args.json_path)
641ba991379Sopenharmony_ci    rom_result = args.rom_result
642ba991379Sopenharmony_ci    device = args.device_num
643ba991379Sopenharmony_ci    output_filename = args.output_filename
644ba991379Sopenharmony_ci    baseline = args.baseline_file
645ba991379Sopenharmony_ci    output_excel_path = args.excel
646ba991379Sopenharmony_ci    unit_adaptiv = args.unit_adaptive
647ba991379Sopenharmony_ci    RamAnalyzer.analysis(cfg_path_name, profile_path_name, rom_result,
648ba991379Sopenharmony_ci                         device_num=device, output_file=output_filename, ss="Pss", output_excel=output_excel_path,
649ba991379Sopenharmony_ci                         baseline_file=baseline, unit_adapt=unit_adaptiv)