import math import os import re import subprocess import sys import time import matplotlib.pyplot as plt OUT_DIR = "out" os.makedirs(OUT_DIR, exist_ok=True) LOG_RE = re.compile( r"(PROC_START|PROC_END)\s+pid=(\d+)\s+ppid=(\d+)\s+depth=(\d+)\s+l=(\d+)\s+r=(\d+)\s+ts=([\d.]+)" ) class Proc: def __init__(self, pid, ppid, depth): self.pid = pid self.ppid = ppid self.depth = depth self.start = None self.end = None self.children = [] @property def duration(self): if self.start is None or self.end is None: return 0 return self.end - self.start def parse_log(path): procs = {} for line in open(path): m = LOG_RE.search(line) if not m: continue typ, pid, ppid, depth, l, r, ts = m.groups() pid, ppid, depth, ts = int(pid), int(ppid), int(depth), float(ts) if pid not in procs: procs[pid] = Proc(pid, ppid, depth) if typ == "PROC_START": procs[pid].start = ts else: procs[pid].end = ts return procs def build_tree(procs): root = None for p in procs.values(): if p.ppid in procs: procs[p.ppid].children.append(p) else: root = p return root def dfs_order(root): order = [] def dfs(p): order.append(p) for c in sorted(p.children, key=lambda x: x.start): dfs(c) dfs(root) return order def write_md(procs, ordered): out_path = os.path.join(OUT_DIR, "processes.md") first = min(p.start for p in procs.values()) with open(out_path, "w") as f: f.write("| PID | PPID | Depth | Start | End | Offset | Duration |\n") f.write("|---|---|---|---|---|---|---|\n") for p in ordered: f.write( f"| {p.pid} | {p.ppid} | {p.depth} | " f"{p.start:.6f} | {p.end:.6f} | " f"{p.start - first:.6f} | {p.duration:.6f} |\n" ) def draw_gantt(ordered): out_tree = os.path.join(OUT_DIR, "gantt_tree.png") out_pid = os.path.join(OUT_DIR, "gantt_pid.png") first = min(p.start for p in ordered) cmap = plt.get_cmap("tab20") color_map = {p.pid: cmap(i % 20) for i, p in enumerate(ordered)} # ------------------------- # 1) TREE ORDER # ------------------------- fig, ax = plt.subplots(figsize=(12, 6)) for i, p in enumerate(ordered): ax.barh( i, p.duration, left=p.start - first, height=0.6, color=color_map[p.pid], ) ax.text( p.start - first, i, f"PID {p.pid} ({p.depth})", va="center", ) ax.set_yticks(range(len(ordered))) ax.set_yticklabels([f"{p.pid} ({p.depth})" for p in ordered]) ax.set_xlabel("Секунд с начала первого процесса") ax.set_title("Диаграмма процессов (в виде дерева)") ax.invert_yaxis() plt.tight_layout() plt.savefig(out_tree) plt.close() # ------------------------- # 2) PID ORDER # ------------------------- pid_order = sorted(ordered, key=lambda p: p.pid) fig, ax = plt.subplots(figsize=(12, 6)) for i, p in enumerate(pid_order): ax.barh( i, p.duration, left=p.start - first, height=0.6, color=color_map[p.pid], ) ax.text( p.start - first, i, f"PID {p.pid} ({p.depth})", va="center", ) ax.set_yticks(range(len(pid_order))) ax.set_yticklabels([f"{p.pid} ({p.depth})" for p in pid_order]) ax.set_xlabel("Секунд с начала первого процесса") ax.set_title("Диаграмма процессов (в порядке PID)") ax.invert_yaxis() plt.tight_layout() plt.savefig(out_pid) plt.close() # ============================================================ # PERFORMANCE GRAPH # ============================================================ def depth_for_process_count(proc_count): """ Your C++ program creates processes by recursion depth. depth=0 -> 1 process depth=1 -> 3 processes depth=2 -> 7 processes depth=3 -> 15 processes depth=4 -> 31 processes depth=5 -> 63 processes This function chooses the closest depth for requested process count. """ if proc_count <= 1: return 0 return max(0, math.ceil(math.log2(proc_count + 1)) - 1) def run_process_test(n, proc_count): max_depth = depth_for_process_count(proc_count) cmd = ["./lab1", str(n), str(max_depth)] start = time.perf_counter() subprocess.run( cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False, ) end = time.perf_counter() return end - start def draw_performance_graph(): data_sizes = { "16": 16, "64": 64, "256": 256, } proc_counts = list(range(1, 129)) print("\nBenchmarking process count performance:") for label, n in data_sizes.items(): times = [] print(f"\nTesting N = {n}:") for p in proc_counts: t = run_process_test(n, p) times.append(t) print(f"N={n:3d} | Processes: {p:3d} | Time: {t:.6f} sec") base_time = times[0] speedup = [base_time / t if t > 0 else 0 for t in times] out_path = os.path.join( OUT_DIR, f"performance_N{n}.png", ) fig, (ax1, ax2) = plt.subplots( 1, 2, figsize=(14, 6), ) # -------------------------------- # EXECUTION TIME # -------------------------------- ax1.plot(proc_counts, times, "o-") ax1.set_xlabel("Количество процессов") ax1.set_ylabel("Время (сек)") ax1.set_title(f"Время выполнения (N = {n})") ax1.grid(True) # -------------------------------- # SPEEDUP # -------------------------------- ax2.plot( proc_counts, speedup, "s-", label="Реальное ускорение", ) ax2.plot( proc_counts, proc_counts, "--", color="black", alpha=0.3, label="Идеал", ) ax2.set_xlabel("Количество процессов") ax2.set_ylabel("Ускорение") ax2.set_title(f"Масштабируемость (N = {n})") ax2.legend() ax2.grid(True) plt.tight_layout() plt.savefig(out_path) plt.close() print(f"Saved: {out_path}") def main(): if len(sys.argv) > 1: procs = parse_log(sys.argv[1]) root = build_tree(procs) ordered = dfs_order(root) write_md(procs, ordered) draw_gantt(ordered) draw_performance_graph() print(f"\nOutputs saved to: {OUT_DIR}/") if __name__ == "__main__": main()