blob: 4710134a677103978edfa4d2db88a1dbb53243e9 [file] [log] [blame]
# Copyright 2025 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import argparse
import json
import os
import os.path
import sqlite3
import sys
from typing import Any, Dict
# This scripts creates a SQLite database with the following schema (expressed here in DBML):
#
# // Kernel system-level memory statistics.
# Table kernel_stats {
# name TEXT // Name of the statistic
# value INTEGER // Value of the statistic
# }
#
# // Principals on the system.
# Table principals {
# // ID of the principal.
# id INTEGER [PRIMARY KEY]
# // Human-readable name of the principal.
# name TEXT
# // Kind of the principal (e.g. Component or Part)
# principal_kind TEXT
# // Type of the principal (e.g. Runnable or Part)
# principal_type TEXT
# // Optional: parent (attributor) of the principal.
# parent INTEGER [ref: > principals.id]
# }
#
# // Names of resources
# Table resource_names {
# // Identifier of the resource name.
# id INTEGER [PRIMARY KEY]
# // Name of the resource
# name TEXT
# }
#
# // Memory resources
# Table resources {
# // Kernel object identifier of the resource
# koid INTEGER [PRIMARY KEY]
# // ID of the name of this resource
# name_id INTEGER [ref: > resource_names.id]
# // Type of this resource (e.g. job, process or vmo)
# type TEXT
# }
#
# // Virtual Memory Object (a type of resource)
# Table vmos {
# // Kernel object identifier of the VMO. Has a one-to-(zero or one) link with resources.koid.
# koid INTEGER [PRIMARY KEY]
# // Optional: parent of this VMO
# parent INTEGER [ref: > vmos.koid]
# // Memory usage of this VMO
# private_committed_bytes INTEGER
# private_populated_bytes INTEGER
# scaled_committed_bytes INTEGER
# scaled_populated_bytes INTEGER
# total_committed_bytes INTEGER
# total_populated_bytes INTEGER
# // Flags (bitfield) of this VMO
# flags INTEGER
# }
#
# // Associative table to join principals and resources.
# Table principals_resources {
# principal_id INTEGER [primary key, ref: > principals.id]
# resource_id INTEGER [ref: > resources.koid]
# }
#
# ref: vmos.koid - resources.koid [delete: cascade]
def main() -> int:
parser = argparse.ArgumentParser(
"components_sql",
description="This script converts a memory snapshot generated using `ffx --machine json profile memory components --detailed` into a SQLite database",
)
parser.add_argument(
"input",
help="Input memory profile: detailed machine JSON output of ffx profile memory components. Use '-' for stdin",
)
parser.add_argument("output", help="Output file")
args = parser.parse_args()
if args.input == "-":
json_input = json.load(sys.stdin)
else:
with open(args.input) as f:
json_input = json.load(f)
process_json_input(json_input, args.output)
return 0
def process_json_input(json_input: Dict[str, Any], output_path: str) -> None:
if "Detailed" not in json_input:
print(
"Error: The input JSON does not contain a 'Detailed' key. "
"Please ensure the input is generated using "
"`ffx --machine json profile memory components --detailed`.",
file=sys.stderr,
)
sys.exit(1)
else:
data = json_input["Detailed"]
if os.path.exists(output_path):
os.remove(output_path)
con = sqlite3.connect(output_path)
con.execute(
"""CREATE TABLE kernel_stats (
name TEXT PRIMARY KEY,
value INTEGER
)"""
)
con.executemany(
"""INSERT INTO kernel_stats VALUES (?, ?)""",
data["kernel"]["memory_statistics"].items(),
)
con.executemany(
"""INSERT INTO kernel_stats VALUES (?, ?)""",
[
(k, v)
for k, v in data["kernel"]["compression_statistics"].items()
if isinstance(v, int)
],
)
con.executemany(
"""INSERT INTO kernel_stats VALUES (?, ?)""",
data["performance"].items(),
)
con.execute(
"""CREATE TABLE principals (
id INTEGER PRIMARY KEY,
name TEXT,
principal_kind TEXT,
principal_type TEXT,
parent INTEGER,
FOREIGN KEY(parent) REFERENCES principals(id)
)"""
)
con.execute(
"""CREATE TABLE resource_names (
id INTEGER PRIMARY KEY,
name TEXT
)"""
)
con.executemany(
"INSERT INTO resource_names VALUES (?, ?)",
enumerate(data["resource_names"]),
)
con.execute(
"""CREATE TABLE resources (
koid INTEGER PRIMARY KEY,
name_id INTEGER,
type TEXT,
FOREIGN KEY(name_id) REFERENCES resource_names(id)
)"""
)
con.execute(
"""CREATE TABLE vmos (
koid INTEGER PRIMARY KEY,
parent INTEGER,
private_committed_bytes INTEGER,
private_populated_bytes INTEGER,
scaled_committed_bytes INTEGER,
scaled_populated_bytes INTEGER,
total_committed_bytes INTEGER,
total_populated_bytes INTEGER,
flags INTEGER,
FOREIGN KEY(parent) REFERENCES resources(koid),
FOREIGN KEY(koid) REFERENCES resources(koid) ON DELETE CASCADE
)"""
)
data_resources = data["resources"]
for data_resource in data_resources:
res = data_resource["resource"]
koid = res["koid"]
name_id = res["name_index"]
[(resource_type_name, resource_type_value)] = list(
res["resource_type"].items()
)
con.execute(
"INSERT INTO resources VALUES (?, ?, ?)",
(koid, name_id, resource_type_name),
)
if resource_type_name == "Vmo":
vmo = resource_type_value
con.execute(
"INSERT INTO vmos VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
(
koid,
vmo["parent"],
vmo["private_committed_bytes"],
vmo["private_populated_bytes"],
vmo["scaled_committed_bytes"],
vmo["scaled_populated_bytes"],
vmo["total_committed_bytes"],
vmo["total_populated_bytes"],
vmo["flags"],
),
)
con.execute(
"""CREATE TABLE principals_resources (
principal_id INTEGER,
resource_id INTEGER,
FOREIGN KEY(principal_id) REFERENCES principals(id),
FOREIGN KEY(resource_id) REFERENCES resources(koid)
)"""
)
principals = data["principals"]
for data_principal in principals:
principal = data_principal["principal"]
id = principal["identifier"]
[(kind, name)] = list(principal["description"].items())
principal_type = principal["principal_type"]
con.execute(
"INSERT INTO principals VALUES (?, ?, ?, ?, ?)",
(
id,
name,
kind,
principal_type,
principal["parent"],
),
)
for r in data_principal["resources"]:
con.execute(
"INSERT INTO principals_resources VALUES (?, ?)", (id, r)
)
con.commit()
con.close()
if __name__ == "__main__":
main()