tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

parse_resource_usage.py (8417B)


      1 # This Source Code Form is subject to the terms of the Mozilla Public
      2 # License, v. 2.0. If a copy of the MPL was not distributed with this
      3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
      4 import pathlib
      5 import re
      6 import sys
      7 from datetime import datetime
      8 
      9 MEM_MATCHER = re.compile("([\\d,]*)K:\\s([\\S]*)\\s\\(")
     10 
     11 
     12 def make_differential_metrics(
     13    differential_name, base_measures, mem_measures, cpu_measures
     14 ):
     15    metrics = []
     16 
     17    # Setup memory differentials
     18    metrics.extend([
     19        {
     20            "name": f"{mem_type}-{category}-{differential_name}",
     21            "unit": "Kb",
     22            "values": [round(mem_usage - base_measures["mem"][mem_type][category], 2)],
     23        }
     24        for mem_type, mem_info in mem_measures.items()
     25        for category, mem_usage in mem_info.items()
     26        if category in base_measures["mem"].get(mem_type, {})
     27    ])
     28    metrics.extend([
     29        {
     30            "name": f"{mem_type}-total-{differential_name}",
     31            "unit": "Kb",
     32            "values": [
     33                round(
     34                    sum(mem_info.values())
     35                    - sum(base_measures["mem"][mem_type].values()),
     36                    2,
     37                )
     38            ],
     39        }
     40        for mem_type, mem_info in mem_measures.items()
     41    ])
     42 
     43    # Setup cpuTime differentials
     44    metrics.extend([
     45        {
     46            "name": f"cpuTime-{category}-{differential_name}",
     47            "unit": "ms",
     48            "values": [cpu_time - base_measures["cpu"][category]],
     49        }
     50        for category, cpu_time in cpu_measures.items()
     51        if category in base_measures.get("cpu", {})
     52    ])
     53    metrics.append({
     54        "name": f"cpuTime-total-{differential_name}",
     55        "unit": "ms",
     56        "values": [
     57            round(sum(cpu_measures.values()) - sum(base_measures["cpu"].values()), 2)
     58        ],
     59    })
     60 
     61    return metrics
     62 
     63 
     64 def get_chrome_process_category(process, binary):
     65    if "privileged_process" in process:
     66        return "gpu"
     67    elif "sandboxed_process" in process:
     68        return "tab"
     69    elif "zygote" in process:
     70        return "zygote"
     71    return "main"
     72 
     73 
     74 def get_fenix_process_category(process, binary):
     75    # In the future, we'll also need to catch media/utility procs
     76    if "tab" in process:
     77        return "tab"
     78    elif f"{binary}" in process:
     79        return "main"
     80    elif "zygote" in process:
     81        return "zygote"
     82    return process
     83 
     84 
     85 def get_category_for_process(process, binary):
     86    if "fenix" in binary:
     87        return get_fenix_process_category(process, binary)
     88    elif "chrome" in binary:
     89        return get_chrome_process_category(process, binary)
     90    raise Exception("Unknown binary for determining process category")
     91 
     92 
     93 def parse_memory_usage(mem_file, binary):
     94    mem_info = []
     95    with mem_file.open() as f:
     96        mem_info = f.readlines()
     97 
     98    curr_mem = ""
     99    final_mems = {"rss": {}, "pss": {}}
    100    for line in mem_info:
    101        if not line.strip():
    102            # Anytime a blank line is hit, the current
    103            # memory type being tracked changes
    104            curr_mem = ""
    105            continue
    106        if not curr_mem:
    107            if "Total RSS by process:" in line:
    108                curr_mem = "rss"
    109            elif "Total PSS by process:" in line:
    110                curr_mem = "pss"
    111            continue
    112 
    113        match = MEM_MATCHER.search(line.strip())
    114        if not match:
    115            continue
    116 
    117        mem_usage, binary_name = match.groups()
    118        if binary not in binary_name:
    119            continue
    120 
    121        name_split = binary_name.split(f"{binary}:")
    122        if len(name_split) == 1:
    123            name = name_split[0]
    124        else:
    125            name = name_split[-1]
    126 
    127        final_mems[curr_mem][name] = round(float(mem_usage.replace(",", "")), 2)
    128 
    129    measurements = {
    130        "rss": {"tab": 0, "gpu": 0, "main": 0, "crashhelper": 0},
    131        "pss": {"tab": 0, "gpu": 0, "main": 0, "crashhelper": 0},
    132    }
    133    for mem_type, mem_info in final_mems.items():
    134        for name, mem_usage in mem_info.items():
    135            final_name = get_category_for_process(name, binary)
    136            if (
    137                final_name == "zygote"
    138                and measurements[mem_type].get("zygote", None) is None
    139            ):
    140                # Only add this process if it exists (it doesn't exist on fenix)
    141                measurements[mem_type]["zygote"] = 0
    142            measurements[mem_type][final_name] += mem_usage
    143 
    144    return measurements
    145 
    146 
    147 def parse_cpu_usage(cpu_file, binary):
    148    cpu_info = []
    149    with cpu_file.open() as f:
    150        cpu_info = f.readlines()
    151 
    152    # Gather all the final cpu times for the processes
    153    final_times = {}
    154    for line in cpu_info:
    155        if not line.strip():
    156            continue
    157        vals = line.split()
    158 
    159        name = vals[0]
    160        if f"{binary}" not in name:
    161            # Sometimes the PID catches the wrong process
    162            continue
    163 
    164        name_split = name.split(f"{binary}:")
    165        if len(name_split) == 1:
    166            name = name_split[0]
    167        else:
    168            name = name_split[-1]
    169 
    170        final_times[name] = vals[-2]
    171 
    172    # Convert the final times to milliseconds
    173    cpu_times = {"tab": 0, "gpu": 0, "main": 0, "crashhelper": 0}
    174    for name, time in final_times.items():
    175        # adb shell ps -o time+= gives us MIN:SEC.HUNDREDTHS.
    176        # That's why we divide dt.microseconds by 1000 for measuring in milliseconds.
    177        dt = datetime.strptime(time, "%M:%S.%f")
    178        milliseconds = (((dt.minute * 60) + dt.second) * 1000) + (dt.microsecond / 1000)
    179 
    180        final_name = get_category_for_process(name, binary)
    181        if final_name == "zygote" and cpu_times.get("zygote", None) is None:
    182            # Only add this process if it exists (it doesn't exist on fenix)
    183            cpu_times["zygote"] = 0
    184 
    185        cpu_times[final_name] += milliseconds
    186 
    187    return cpu_times
    188 
    189 
    190 def main():
    191    args = sys.argv[1:]
    192    binary = args[1]
    193    testing_dir = pathlib.Path(args[0])
    194    run_background = True if args[2] == "True" else False
    195 
    196    cpu_info_files = sorted(testing_dir.glob("cpu_info*"))
    197    mem_info_files = sorted(testing_dir.glob("mem_info*"))
    198 
    199    perf_metrics = []
    200    base_measures = {}
    201    measuring_intervals = ("start", "10%", "50%", "end")
    202    if binary == "com.android.chrome":
    203        measuring_intervals = ("start", "10%", "50%")
    204 
    205    for i, measurement_time in enumerate(measuring_intervals):
    206        cpu_measures = parse_cpu_usage(cpu_info_files[i], binary)
    207        mem_measures = parse_memory_usage(mem_info_files[i], binary)
    208 
    209        if not base_measures:
    210            base_measures["cpu"] = cpu_measures
    211            base_measures["mem"] = mem_measures
    212 
    213        perf_metrics.extend([
    214            {
    215                "name": f"cpuTime-{category}-{measurement_time}",
    216                "unit": "ms",
    217                "values": [cpu_time],
    218            }
    219            for category, cpu_time in cpu_measures.items()
    220        ])
    221        perf_metrics.append({
    222            "name": f"cpuTime-total-{measurement_time}",
    223            "unit": "ms",
    224            "values": [round(sum(cpu_measures.values()), 2)],
    225        })
    226 
    227        perf_metrics.extend([
    228            {
    229                "name": f"{mem_type}-{category}-{measurement_time}",
    230                "unit": "Kb",
    231                "values": [round(mem_usage, 2)],
    232            }
    233            for mem_type, mem_info in mem_measures.items()
    234            for category, mem_usage in mem_info.items()
    235        ])
    236        perf_metrics.extend([
    237            {
    238                "name": f"{mem_type}-total-{measurement_time}",
    239                "unit": "Kb",
    240                "values": [round(sum(mem_info.values()), 2)],
    241            }
    242            for mem_type, mem_info in mem_measures.items()
    243        ])
    244 
    245        if base_measures and run_background:
    246            if measurement_time == "10%":
    247                perf_metrics.extend(
    248                    make_differential_metrics(
    249                        "backgrounding-diff", base_measures, mem_measures, cpu_measures
    250                    )
    251                )
    252            elif measurement_time == "end":
    253                perf_metrics.extend(
    254                    make_differential_metrics(
    255                        "background-diff", base_measures, mem_measures, cpu_measures
    256                    )
    257                )
    258 
    259    print(
    260        "perfMetrics: "
    261        + str(perf_metrics).replace("{", "{{").replace("}", "}}").replace("'", '"')
    262    )
    263 
    264 
    265 if __name__ == "__main__":
    266    main()