Browse Source

include model statistics

Snej69420 5 months ago
parent
commit
13c71fb522
1 changed files with 74 additions and 122 deletions
  1. 74 122
      benchmarks/compareMemory.py

+ 74 - 122
benchmarks/compareMemory.py

@@ -5,6 +5,7 @@ import matplotlib.pyplot as plt
 import argparse
 from pathlib import Path
 from itertools import cycle
+from model_properties import model_properties
 
 # Standard Linux page size in bytes
 PAGE_SIZE = 4096
@@ -16,170 +17,121 @@ SIMULATOR_MAPPING = {
     "dynamic": "(d)",
     "dynamic-classic": "(dc)",
     "Memory": "(a)",
+    "Agents": "(a)",
 }
 
-TIME_UNITS = {
-    "ms": 0.001,
-    "s": 1,
-    "m": 60,
-    "h": 3600,
-}
-
+TIME_UNITS = {"ms": 0.001, "s": 1, "m": 60, "h": 3600}
 MEMORY_UNITS = {
-    "Bytes": 1,
-    "KB": 1000,
-    "MB": 1000 ** 2,
-    "GB": 1000 ** 3,
-    "KiB": 1024,
-    "MiB": 1024 ** 2,
-    "GiB": 1024 ** 3
+    "Bytes": 1, "KB": 1000, "MB": 1000 ** 2, "GB": 1000 ** 3,
+    "KiB": 1024, "MiB": 1024 ** 2, "GiB": 1024 ** 3
 }
 
 
-def get_memory_trace(db_path):
-    """
-    Connects to the SQLite database and retrieves the memory trace
-    for processes named 'python3'.
-    """
-
-    if not db_path.exists():
-        print(f"Error: File {db_path} does not exist.")
-        return None
-
-    if db_path.stat().st_size == 0:
-        print(f"Error: File {db_path} is empty (0 bytes).")
-        return None
-
+def normalize_database(db_path):
+    """Normalizes the database so that every process starts at 0 RSS (Net Growth)."""
     try:
         with sqlite3.connect(db_path) as conn:
             cursor = conn.cursor()
-
             cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='record';")
-            if not cursor.fetchone():
-                print(f"Warning: Table 'record' not found in {db_path} Skipping.")
-                return None
-
-            # Query data: timestamp and RSS for python3 processes
-            query = """
-                    SELECT ts, stat_rss
-                    FROM record
-                    WHERE stat_comm IN ('python3', 'julia')
-                    ORDER BY ts ASC \
-                    """
-            df = pd.read_sql_query(query, conn)
+            if not cursor.fetchone(): return
+
+            cursor.execute("PRAGMA table_info(record)")
+            columns = [info[1] for info in cursor.fetchall()]
+
+            if 'raw_rss' not in columns:
+                cursor.execute("ALTER TABLE record ADD COLUMN raw_rss INTEGER")
+                cursor.execute("UPDATE record SET raw_rss = stat_rss")
+                cursor.execute("""
+                               UPDATE record
+                               SET stat_rss = raw_rss - (SELECT r2.raw_rss
+                                                         FROM record r2
+                                                         WHERE r2.stat_pid = record.stat_pid
+                                                         ORDER BY r2.record_id ASC
+                                                         LIMIT 1)
+                               """)
+                conn.commit()
+                print(f"Normalized {db_path.name}: Processes now start at 0 (Net Growth).")
+    except sqlite3.Error as e:
+        print(f"Error normalizing {db_path}: {e}")
 
-            if df.empty:
-                print(f"Warning: No 'python3' or 'julia' records found in {db_path}.")
-                return None
 
-            return df
+def get_memory_trace(db_path):
+    if not db_path.exists() or db_path.stat().st_size == 0: return None
+    normalize_database(db_path)
+    try:
+        with sqlite3.connect(db_path) as conn:
+            query = "SELECT ts, stat_rss FROM record WHERE stat_comm IN ('python3', 'julia') ORDER BY ts ASC"
+            df = pd.read_sql_query(query, conn)
+            return df if not df.empty else None
     except sqlite3.Error as e:
-        print(f"Error reading {db_path}: {e}")
         return None
 
 
 def process_data(df, mem_unit, time_unit):
-    """
-    Converts timestamps to relative time and RSS to the specified units.
-    """
-    # Convert timestamps to relative time in seconds first
     start_time = df["ts"].iloc[0]
-    relative_seconds = df["ts"] - start_time
-
-    # Scale time to the target unit
-    df["relative_time"] = relative_seconds / TIME_UNITS[time_unit]
-
-    # Convert RSS from Pages to the target memory unit
-    # stat_rss is in pages (usually 4KB)
+    df["relative_time"] = (df["ts"] - start_time) / TIME_UNITS[time_unit]
     df["memory"] = (df["stat_rss"] * PAGE_SIZE) / MEMORY_UNITS[mem_unit]
-
     return df
 
 
-def plot_traces(files, mem_unit="MB", time_unit="s", path="", name="", title=None, bw=False):
+def plot_traces(files, width=None, depth=None, mem_unit="MB", time_unit="s", path="", name="", title=None, bw=False):
     plt.figure(figsize=(10, 6))
+    style_cycle = cycle(zip(cycle(plt.rcParams['axes.prop_cycle'].by_key()['color'] if not bw else ['black']),
+                            cycle(['-', '--', '-.', ':'])))
+    plotted_any, models, main_model_type = False, [], None
 
-    # Styling
-    colors = plt.rcParams['axes.prop_cycle'].by_key()['color'] if not bw else ['black']
-    line_styles = ['-', '--', '-.', ':']
-
-    style_cycle = cycle(zip(cycle(colors), cycle(line_styles)))
-
-    plotted_any = False
-
-    models = []
     for db_file in files:
         file = Path(db_file).resolve()
         df = get_memory_trace(file)
-        if df is None:
-            continue
-        models.append(file.stem)
-        df = process_data(df, mem_unit, time_unit)
+        if df is None: continue
 
+        model_type = file.stem
+        models.append(model_type)
+        if main_model_type is None: main_model_type = model_type
+        df = process_data(df, mem_unit, time_unit)
         color, linestyle = next(style_cycle)
-        label = file.stem + " " + SIMULATOR_MAPPING[file.parent.name]
-
-        plt.plot(
-            df["relative_time"],
-            df["memory"],
-            label=label,
-            color=color,
-            linestyle=linestyle,
-            linewidth=1.5
-        )
+        label = f"{model_type} {SIMULATOR_MAPPING.get(file.parent.name, f'({file.parent.name})')}"
+        plt.plot(df["relative_time"], df["memory"], label=label, color=color, linestyle=linestyle, linewidth=1.5)
         plotted_any = True
 
-    if not plotted_any:
-        print("No valid data found to plot.")
-        return
+    if not plotted_any: return
 
-    # Labels and Title
-    plt.xlabel(f"Time ({time_unit})")
-    plt.ylabel(f"Resident Set Size ({mem_unit})")
+    plt.xlabel(f"Time ({time_unit})");
+    plt.ylabel(f"Resident Set Size Net Growth ({mem_unit})")
 
-    unique_models = "|".join(set(models))
-    if not name:
-        final_name = f"{unique_models}"
-    else:
-        final_name = name
+    if main_model_type and width and depth:
+        props = model_properties(main_model_type, width, depth)
+        atomics = int(props.get("Atomic Models", 0))
+        events = int(props.get("Events", 0))
 
-    if title:
-        plt.title(title)
+        plt.suptitle(f"Memory Usage over Time (Net Growth): {main_model_type}", fontsize=12)
+        plt.title(f"Width: {width}, Depth: {depth} | #Atomic Models: {atomics:,} | #Events: {events:,}", fontsize=10)
     else:
-        plt.title("Memory Usage over Time")
+        plt.title("Memory Usage over Time (Net Growth)", fontsize=12)
 
     plt.legend()
     plt.grid(True, linestyle=':', alpha=0.6)
 
-    # Save output
-    script_dir = Path(__file__).resolve().parent
-    output_dir = script_dir / "MemoryPlots"
-    if path:
-        output_dir = output_dir / path
-    output_dir.mkdir(parents=True, exist_ok=True)
-    output_file = output_dir / f"{final_name}.png"
-
-    plt.tight_layout()
-    plt.savefig(output_file, dpi=300)
-    print(f"Saved plot to {output_file}")
+    out_dir = Path(__file__).resolve().parent / "MemoryPlots" / path
+    out_dir.mkdir(parents=True, exist_ok=True)
+    plt.tight_layout();
+    plt.savefig(out_dir / f"{name or '|'.join(set(models)) + '_memory'}.png", dpi=300)
 
 
 def main():
-    parser = argparse.ArgumentParser(description="Plot Resident Set Size (RSS) from SQLite memory traces.")
-
-    parser.add_argument("files", nargs="+", type=str, help="SQLite database files to plot.")
-    parser.add_argument("-mu", "--memory-unit", type=str, default="MB", choices=MEMORY_UNITS.keys(), help="Unit for memory usage (default: MB).")
-    parser.add_argument("-tu", "--time-unit", type=str, default="s", choices=TIME_UNITS.keys(), help="Unit for the time scale (default: s).")
-
-    parser.add_argument("-p", "--path", type=str, default="", help="Output image file path.")
-    parser.add_argument("-n", "--name", type=str, default="", help="Output image file name.")
-    parser.add_argument("-t", "--title", type=str, help="Custom title for the plot.")
-    parser.add_argument("--bw", action="store_true", help="Black and white mode.")
-
+    parser = argparse.ArgumentParser()
+    parser.add_argument("files", nargs="+")
+    parser.add_argument("-w", "--width", type=int)
+    parser.add_argument("-d", "--depth", type=int)
+    parser.add_argument("-mu", "--memory-unit", default="MB")
+    parser.add_argument("-tu", "--time-unit", default="s")
+    parser.add_argument("-p", "--path", default="")
+    parser.add_argument("-n", "--name", default="")
+    parser.add_argument("-t", "--title")
+    parser.add_argument("--bw", action="store_true")
     args = parser.parse_args()
-
-    plot_traces(args.files, args.memory_unit, args.time_unit, args.path, args.name, args.title, args.bw)
+    plot_traces(args.files, args.width, args.depth, args.memory_unit, args.time_unit, args.path, args.name, args.title,
+                args.bw)
 
 
-if __name__ == "__main__":
-    main()
+if __name__ == "__main__": main()