1*bb4ee6a4SAndroid Build Coastguard Worker#!/usr/bin/env python3 2*bb4ee6a4SAndroid Build Coastguard Worker 3*bb4ee6a4SAndroid Build Coastguard Worker# Copyright 2023 The ChromiumOS Authors 4*bb4ee6a4SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 5*bb4ee6a4SAndroid Build Coastguard Worker# found in the LICENSE file. 6*bb4ee6a4SAndroid Build Coastguard Worker 7*bb4ee6a4SAndroid Build Coastguard Workerfrom collections import defaultdict 8*bb4ee6a4SAndroid Build Coastguard Workerimport argparse 9*bb4ee6a4SAndroid Build Coastguard Workerimport subprocess 10*bb4ee6a4SAndroid Build Coastguard Workerimport os 11*bb4ee6a4SAndroid Build Coastguard Workerimport json 12*bb4ee6a4SAndroid Build Coastguard Workerimport pandas as pd 13*bb4ee6a4SAndroid Build Coastguard Workerimport plotly 14*bb4ee6a4SAndroid Build Coastguard Workerimport plotly.graph_objects as go 15*bb4ee6a4SAndroid Build Coastguard Workerimport plotly.express as px 16*bb4ee6a4SAndroid Build Coastguard Worker 17*bb4ee6a4SAndroid Build Coastguard Worker 18*bb4ee6a4SAndroid Build Coastguard Workerclass BalloonRecord: 19*bb4ee6a4SAndroid Build Coastguard Worker def __init__(self, rec): 20*bb4ee6a4SAndroid Build Coastguard Worker byte_to_gb = 1024.0**3 21*bb4ee6a4SAndroid Build Coastguard Worker self.total = rec["balloon_stats"]["stats"]["total_memory"] / byte_to_gb 22*bb4ee6a4SAndroid Build Coastguard Worker self.free = rec["balloon_stats"]["stats"]["free_memory"] / byte_to_gb 23*bb4ee6a4SAndroid Build Coastguard Worker self.disk_caches = rec["balloon_stats"]["stats"]["disk_caches"] / byte_to_gb 24*bb4ee6a4SAndroid Build Coastguard Worker self.avail = rec["balloon_stats"]["stats"]["available_memory"] / byte_to_gb 25*bb4ee6a4SAndroid Build Coastguard Worker 26*bb4ee6a4SAndroid Build Coastguard Worker self.shared_memory = (rec["balloon_stats"]["stats"]["shared_memory"] or 0.0) / byte_to_gb 27*bb4ee6a4SAndroid Build Coastguard Worker self.unevictable_memory = ( 28*bb4ee6a4SAndroid Build Coastguard Worker rec["balloon_stats"]["stats"]["unevictable_memory"] or 0.0 29*bb4ee6a4SAndroid Build Coastguard Worker ) / byte_to_gb 30*bb4ee6a4SAndroid Build Coastguard Worker self.balloon_actual = (rec["balloon_stats"]["balloon_actual"] or 0.0) / byte_to_gb 31*bb4ee6a4SAndroid Build Coastguard Worker 32*bb4ee6a4SAndroid Build Coastguard Worker 33*bb4ee6a4SAndroid Build Coastguard Workerclass Records: 34*bb4ee6a4SAndroid Build Coastguard Worker def __init__(self) -> None: 35*bb4ee6a4SAndroid Build Coastguard Worker self.data = [] 36*bb4ee6a4SAndroid Build Coastguard Worker 37*bb4ee6a4SAndroid Build Coastguard Worker def add(self, timestamp, name, mem_usage) -> None: 38*bb4ee6a4SAndroid Build Coastguard Worker self.data.append({"boot time (sec)": timestamp, "process": name, "PSS (GB)": mem_usage}) 39*bb4ee6a4SAndroid Build Coastguard Worker 40*bb4ee6a4SAndroid Build Coastguard Worker 41*bb4ee6a4SAndroid Build Coastguard Workerdef memstat_plot(data, args) -> str: 42*bb4ee6a4SAndroid Build Coastguard Worker names = set() 43*bb4ee6a4SAndroid Build Coastguard Worker for rec in data: 44*bb4ee6a4SAndroid Build Coastguard Worker for p in rec["stats"]: 45*bb4ee6a4SAndroid Build Coastguard Worker names.add(p["name"]) 46*bb4ee6a4SAndroid Build Coastguard Worker 47*bb4ee6a4SAndroid Build Coastguard Worker recs = Records() 48*bb4ee6a4SAndroid Build Coastguard Worker ballon_sizes = [[], []] 49*bb4ee6a4SAndroid Build Coastguard Worker 50*bb4ee6a4SAndroid Build Coastguard Worker total_memory_size = BalloonRecord(data[-1]).total 51*bb4ee6a4SAndroid Build Coastguard Worker for rec in data: 52*bb4ee6a4SAndroid Build Coastguard Worker timestamp = int(rec["timestamp"]) 53*bb4ee6a4SAndroid Build Coastguard Worker 54*bb4ee6a4SAndroid Build Coastguard Worker balloon = None 55*bb4ee6a4SAndroid Build Coastguard Worker if rec["balloon_stats"]: 56*bb4ee6a4SAndroid Build Coastguard Worker balloon = BalloonRecord(rec) 57*bb4ee6a4SAndroid Build Coastguard Worker 58*bb4ee6a4SAndroid Build Coastguard Worker # Dict: name -> (dict: field -> value) 59*bb4ee6a4SAndroid Build Coastguard Worker # Example: { "crosvm": {"Pss": 100, "Rss": 120, ...}, "virtio-blk": ... } 60*bb4ee6a4SAndroid Build Coastguard Worker proc_to_smaps = {name: defaultdict(int) for name in names} 61*bb4ee6a4SAndroid Build Coastguard Worker 62*bb4ee6a4SAndroid Build Coastguard Worker # Summarize multiple processes using the same name such as multiple virtiofs devices. 63*bb4ee6a4SAndroid Build Coastguard Worker for p in rec["stats"]: 64*bb4ee6a4SAndroid Build Coastguard Worker name = p["name"] 65*bb4ee6a4SAndroid Build Coastguard Worker for key in p["smaps"]: 66*bb4ee6a4SAndroid Build Coastguard Worker val = p["smaps"][key] 67*bb4ee6a4SAndroid Build Coastguard Worker # Convert the value from KB to GB. 68*bb4ee6a4SAndroid Build Coastguard Worker proc_to_smaps[name][key] += val / (1024.0**2) 69*bb4ee6a4SAndroid Build Coastguard Worker 70*bb4ee6a4SAndroid Build Coastguard Worker for p in rec["stats"]: 71*bb4ee6a4SAndroid Build Coastguard Worker name = p["name"] 72*bb4ee6a4SAndroid Build Coastguard Worker smaps = proc_to_smaps[name] 73*bb4ee6a4SAndroid Build Coastguard Worker 74*bb4ee6a4SAndroid Build Coastguard Worker if name != "crosvm": 75*bb4ee6a4SAndroid Build Coastguard Worker # TODO: We may want to track VmPTE too. 76*bb4ee6a4SAndroid Build Coastguard Worker # https://chromium-review.googlesource.com/c/crosvm/crosvm/+/4712086/comment/9e08afd5_2fd05550/ 77*bb4ee6a4SAndroid Build Coastguard Worker recs.add(timestamp, name, smaps["Private_Dirty"]) 78*bb4ee6a4SAndroid Build Coastguard Worker continue 79*bb4ee6a4SAndroid Build Coastguard Worker 80*bb4ee6a4SAndroid Build Coastguard Worker assert name == "crosvm" 81*bb4ee6a4SAndroid Build Coastguard Worker if not balloon: 82*bb4ee6a4SAndroid Build Coastguard Worker recs.add(timestamp, "crosvm (guest disk caches)", 0) 83*bb4ee6a4SAndroid Build Coastguard Worker recs.add(timestamp, "crosvm (guest shared memory)", 0) 84*bb4ee6a4SAndroid Build Coastguard Worker recs.add(timestamp, "crosvm (guest unevictable)", 0) 85*bb4ee6a4SAndroid Build Coastguard Worker recs.add(timestamp, "crosvm (guest used)", 0) 86*bb4ee6a4SAndroid Build Coastguard Worker 87*bb4ee6a4SAndroid Build Coastguard Worker recs.add(timestamp, "crosvm (host)", smaps["Rss"]) 88*bb4ee6a4SAndroid Build Coastguard Worker 89*bb4ee6a4SAndroid Build Coastguard Worker ballon_sizes[0].append(timestamp) 90*bb4ee6a4SAndroid Build Coastguard Worker ballon_sizes[1].append(total_memory_size) 91*bb4ee6a4SAndroid Build Coastguard Worker 92*bb4ee6a4SAndroid Build Coastguard Worker continue 93*bb4ee6a4SAndroid Build Coastguard Worker 94*bb4ee6a4SAndroid Build Coastguard Worker recs.add(timestamp, "crosvm (guest disk caches)", balloon.disk_caches) 95*bb4ee6a4SAndroid Build Coastguard Worker recs.add(timestamp, "crosvm (guest shared memory)", balloon.shared_memory) 96*bb4ee6a4SAndroid Build Coastguard Worker recs.add(timestamp, "crosvm (guest unevictable)", balloon.unevictable_memory) 97*bb4ee6a4SAndroid Build Coastguard Worker 98*bb4ee6a4SAndroid Build Coastguard Worker # (guest used) = (guest total = host's RSS) - (free + balloon_actual + disk caches + shared memory) 99*bb4ee6a4SAndroid Build Coastguard Worker guest_used = ( 100*bb4ee6a4SAndroid Build Coastguard Worker balloon.total 101*bb4ee6a4SAndroid Build Coastguard Worker - balloon.free 102*bb4ee6a4SAndroid Build Coastguard Worker - balloon.balloon_actual 103*bb4ee6a4SAndroid Build Coastguard Worker - balloon.disk_caches 104*bb4ee6a4SAndroid Build Coastguard Worker - balloon.shared_memory 105*bb4ee6a4SAndroid Build Coastguard Worker - balloon.unevictable_memory 106*bb4ee6a4SAndroid Build Coastguard Worker ) 107*bb4ee6a4SAndroid Build Coastguard Worker assert guest_used >= 0 108*bb4ee6a4SAndroid Build Coastguard Worker if guest_used > proc_to_smaps["crosvm"]["Rss"]: 109*bb4ee6a4SAndroid Build Coastguard Worker print( 110*bb4ee6a4SAndroid Build Coastguard Worker "WARNING: guest_used > crosvm RSS: {} > {}".format( 111*bb4ee6a4SAndroid Build Coastguard Worker guest_used, proc_to_smaps["crosvm"]["Rss"] 112*bb4ee6a4SAndroid Build Coastguard Worker ) 113*bb4ee6a4SAndroid Build Coastguard Worker ) 114*bb4ee6a4SAndroid Build Coastguard Worker 115*bb4ee6a4SAndroid Build Coastguard Worker recs.add(timestamp, "crosvm (guest used)", guest_used) 116*bb4ee6a4SAndroid Build Coastguard Worker crosvm_host = ( 117*bb4ee6a4SAndroid Build Coastguard Worker proc_to_smaps["crosvm"]["Rss"] 118*bb4ee6a4SAndroid Build Coastguard Worker - guest_used 119*bb4ee6a4SAndroid Build Coastguard Worker - balloon.disk_caches 120*bb4ee6a4SAndroid Build Coastguard Worker - balloon.shared_memory 121*bb4ee6a4SAndroid Build Coastguard Worker - balloon.unevictable_memory 122*bb4ee6a4SAndroid Build Coastguard Worker ) 123*bb4ee6a4SAndroid Build Coastguard Worker if crosvm_host < 0: 124*bb4ee6a4SAndroid Build Coastguard Worker print("WARNING: crosvm (host) < 0: {}".format(crosvm_host)) 125*bb4ee6a4SAndroid Build Coastguard Worker recs.add(timestamp, "crosvm (host)", crosvm_host) 126*bb4ee6a4SAndroid Build Coastguard Worker 127*bb4ee6a4SAndroid Build Coastguard Worker ballon_sizes[0].append(timestamp) 128*bb4ee6a4SAndroid Build Coastguard Worker ballon_sizes[1].append(balloon.total - balloon.balloon_actual) 129*bb4ee6a4SAndroid Build Coastguard Worker 130*bb4ee6a4SAndroid Build Coastguard Worker df = pd.DataFrame(recs.data) 131*bb4ee6a4SAndroid Build Coastguard Worker fig = px.area( 132*bb4ee6a4SAndroid Build Coastguard Worker df, 133*bb4ee6a4SAndroid Build Coastguard Worker x="boot time (sec)", 134*bb4ee6a4SAndroid Build Coastguard Worker y="Memory usage (GB)", 135*bb4ee6a4SAndroid Build Coastguard Worker color="process", 136*bb4ee6a4SAndroid Build Coastguard Worker ) 137*bb4ee6a4SAndroid Build Coastguard Worker fig.update_layout(title={"text": args.title}) 138*bb4ee6a4SAndroid Build Coastguard Worker fig.add_trace( 139*bb4ee6a4SAndroid Build Coastguard Worker go.Scatter( 140*bb4ee6a4SAndroid Build Coastguard Worker x=ballon_sizes[0], 141*bb4ee6a4SAndroid Build Coastguard Worker y=ballon_sizes[1], 142*bb4ee6a4SAndroid Build Coastguard Worker mode="lines", 143*bb4ee6a4SAndroid Build Coastguard Worker name="(total memory) - (balloon size)", 144*bb4ee6a4SAndroid Build Coastguard Worker ) 145*bb4ee6a4SAndroid Build Coastguard Worker ) 146*bb4ee6a4SAndroid Build Coastguard Worker 147*bb4ee6a4SAndroid Build Coastguard Worker base, _ = os.path.splitext(args.input) 148*bb4ee6a4SAndroid Build Coastguard Worker outname = base + "." + args.format 149*bb4ee6a4SAndroid Build Coastguard Worker if args.format == "html": 150*bb4ee6a4SAndroid Build Coastguard Worker fig.write_html(outname) 151*bb4ee6a4SAndroid Build Coastguard Worker else: 152*bb4ee6a4SAndroid Build Coastguard Worker plotly.io.write_image(fig, outname, format="png") 153*bb4ee6a4SAndroid Build Coastguard Worker print(f"{outname} is written") 154*bb4ee6a4SAndroid Build Coastguard Worker return outname 155*bb4ee6a4SAndroid Build Coastguard Worker 156*bb4ee6a4SAndroid Build Coastguard Worker 157*bb4ee6a4SAndroid Build Coastguard Workerdef main(): 158*bb4ee6a4SAndroid Build Coastguard Worker parser = argparse.ArgumentParser(description="Plot JSON generated by memstats_chart") 159*bb4ee6a4SAndroid Build Coastguard Worker parser.add_argument("-i", "--input", required=True, help="input JSON file path") 160*bb4ee6a4SAndroid Build Coastguard Worker parser.add_argument("--format", choices=["html", "png"], default="html") 161*bb4ee6a4SAndroid Build Coastguard Worker parser.add_argument("--title", default="crosvm memory usage") 162*bb4ee6a4SAndroid Build Coastguard Worker args = parser.parse_args() 163*bb4ee6a4SAndroid Build Coastguard Worker 164*bb4ee6a4SAndroid Build Coastguard Worker with open(args.input) as f: 165*bb4ee6a4SAndroid Build Coastguard Worker data = json.load(f) 166*bb4ee6a4SAndroid Build Coastguard Worker 167*bb4ee6a4SAndroid Build Coastguard Worker outfile = memstat_plot(data, args) 168*bb4ee6a4SAndroid Build Coastguard Worker 169*bb4ee6a4SAndroid Build Coastguard Worker try: 170*bb4ee6a4SAndroid Build Coastguard Worker subprocess.run(["google-chrome", outfile]) 171*bb4ee6a4SAndroid Build Coastguard Worker except Exception as e: 172*bb4ee6a4SAndroid Build Coastguard Worker print(f"Failed to open {outfile} with google-chrome: {e}") 173*bb4ee6a4SAndroid Build Coastguard Worker 174*bb4ee6a4SAndroid Build Coastguard Worker 175*bb4ee6a4SAndroid Build Coastguard Workermain() 176