Source code
Revision control
Copy as Markdown
Other Tools
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
import pathlib
import re
import sys
from datetime import timedelta
from time import strptime
MEM_MATCHER = re.compile("([\\d,]*)K:\\s([\\S]*)\\s\\(")
def make_differential_metrics(
differential_name, base_measures, mem_measures, cpu_measures
):
metrics = []
# Setup memory differentials
metrics.extend(
[
{
"name": f"{mem_type}-{category}-{differential_name}",
"unit": "Kb",
"values": [
round(mem_usage - base_measures["mem"][mem_type][category], 2)
],
}
for mem_type, mem_info in mem_measures.items()
for category, mem_usage in mem_info.items()
]
)
metrics.extend(
[
{
"name": f"{mem_type}-total-{differential_name}",
"unit": "Kb",
"values": [
round(
sum(mem_info.values())
- sum(base_measures["mem"][mem_type].values()),
2,
)
],
}
for mem_type, mem_info in mem_measures.items()
]
)
# Setup cpuTime differentials
metrics.extend(
[
{
"name": f"cpuTime-{category}-{differential_name}",
"unit": "s",
"values": [cpu_time - base_measures["cpu"][category]],
}
for category, cpu_time in cpu_measures.items()
]
)
metrics.append(
{
"name": f"cpuTime-total-{differential_name}",
"unit": "s",
"values": [
round(
sum(cpu_measures.values()) - sum(base_measures["cpu"].values()), 2
)
],
}
)
return metrics
def get_chrome_process_category(process, binary):
if "privileged_process" in process:
return "gpu"
elif "sandboxed_process" in process:
return "tab"
elif "zygote" in process:
return "zygote"
return "main"
def get_fenix_process_category(process, binary):
# In the future, we'll also need to catch media/utility procs
if "tab" in process:
return "tab"
elif f"{binary}" in process:
return "main"
elif "zygote" in process:
return "zygote"
return process
def get_category_for_process(process, binary):
if "fenix" in binary:
return get_fenix_process_category(process, binary)
elif "chrome" in binary:
return get_chrome_process_category(process, binary)
raise Exception("Unknown binary for determining process category")
def parse_memory_usage(mem_file, binary):
mem_info = []
with mem_file.open() as f:
mem_info = f.readlines()
curr_mem = ""
final_mems = {"rss": {}, "pss": {}}
for line in mem_info:
if not line.strip():
# Anytime a blank line is hit, the current
# memory type being tracked changes
curr_mem = ""
continue
if not curr_mem:
if "Total RSS by process:" in line:
curr_mem = "rss"
elif "Total PSS by process:" in line:
curr_mem = "pss"
continue
match = MEM_MATCHER.search(line.strip())
if not match:
continue
mem_usage, binary_name = match.groups()
if binary not in binary_name:
continue
name_split = binary_name.split(f"{binary}:")
if len(name_split) == 1:
name = name_split[0]
else:
name = name_split[-1]
final_mems[curr_mem][name] = round(float(mem_usage.replace(",", "")), 2)
measurements = {
"rss": {"tab": 0, "gpu": 0, "main": 0},
"pss": {"tab": 0, "gpu": 0, "main": 0},
}
for mem_type, mem_info in final_mems.items():
for name, mem_usage in mem_info.items():
final_name = get_category_for_process(name, binary)
if (
final_name == "zygote"
and measurements[mem_type].get("zygote", None) is None
):
# Only add this process if it exists (it doesn't exist on fenix)
measurements[mem_type]["zygote"] = 0
measurements[mem_type][final_name] += mem_usage
return measurements
def parse_cpu_usage(cpu_file, binary):
cpu_info = []
with cpu_file.open() as f:
cpu_info = f.readlines()
# Gather all the final cpu times for the processes
final_times = {}
for line in cpu_info:
if not line.strip():
continue
vals = line.split()
name = vals[0]
if f"{binary}" not in name:
# Sometimes the PID catches the wrong process
continue
name_split = name.split(f"{binary}:")
if len(name_split) == 1:
name = name_split[0]
else:
name = name_split[-1]
final_times[name] = vals[-2]
# Convert the final times to seconds
cpu_times = {"tab": 0, "gpu": 0, "main": 0}
for name, time in final_times.items():
dt = strptime(time, "%H:%M:%S")
seconds = timedelta(
hours=dt.tm_hour, minutes=dt.tm_min, seconds=dt.tm_sec
).total_seconds()
final_name = get_category_for_process(name, binary)
if final_name == "zygote" and cpu_times.get("zygote", None) is None:
# Only add this process if it exists (it doesn't exist on fenix)
cpu_times["zygote"] = 0
cpu_times[final_name] += seconds
return cpu_times
def main():
args = sys.argv[1:]
binary = args[1]
testing_dir = pathlib.Path(args[0])
run_background = True if args[2] == "True" else False
cpu_info_files = sorted(testing_dir.glob("cpu_info*"))
mem_info_files = sorted(testing_dir.glob("mem_info*"))
perf_metrics = []
base_measures = {}
for i, measurement_time in enumerate(("start", "10%", "50%", "end")):
cpu_measures = parse_cpu_usage(cpu_info_files[i], binary)
mem_measures = parse_memory_usage(mem_info_files[i], binary)
if not base_measures:
base_measures["cpu"] = cpu_measures
base_measures["mem"] = mem_measures
perf_metrics.extend(
[
{
"name": f"cpuTime-{category}-{measurement_time}",
"unit": "s",
"values": [cpu_time],
}
for category, cpu_time in cpu_measures.items()
]
)
perf_metrics.append(
{
"name": f"cpuTime-total-{measurement_time}",
"unit": "s",
"values": [round(sum(cpu_measures.values()), 2)],
}
)
perf_metrics.extend(
[
{
"name": f"{mem_type}-{category}-{measurement_time}",
"unit": "Kb",
"values": [round(mem_usage, 2)],
}
for mem_type, mem_info in mem_measures.items()
for category, mem_usage in mem_info.items()
]
)
perf_metrics.extend(
[
{
"name": f"{mem_type}-total-{measurement_time}",
"unit": "Kb",
"values": [round(sum(mem_info.values()), 2)],
}
for mem_type, mem_info in mem_measures.items()
]
)
if base_measures and run_background:
if measurement_time == "10%":
perf_metrics.extend(
make_differential_metrics(
"backgrounding-diff", base_measures, mem_measures, cpu_measures
)
)
elif measurement_time == "end":
perf_metrics.extend(
make_differential_metrics(
"background-diff", base_measures, mem_measures, cpu_measures
)
)
print(
"perfMetrics: "
+ str(perf_metrics).replace("{", "{{").replace("}", "}}").replace("'", '"')
)
if __name__ == "__main__":
main()