From c4b08628862d999e57092f76a086274274e281b4 Mon Sep 17 00:00:00 2001 From: resserops Date: Thu, 20 Aug 2026 16:24:49 +0800 Subject: [PATCH 1/2] chore(batch_ssh): modify output format --- batch_ssh/batch_ssh.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/batch_ssh/batch_ssh.py b/batch_ssh/batch_ssh.py index 927ddbf..6e29134 100755 --- a/batch_ssh/batch_ssh.py +++ b/batch_ssh/batch_ssh.py @@ -54,7 +54,7 @@ def load_config(path): def ssh(host: Host, command: str, timeout: int) -> Result: ssh_cmd = [ - "ssh", + "ssh", "-p", host.port, "-o", f"ConnectTimeout={timeout}", "-o", "StrictHostKeyChecking=no", @@ -113,19 +113,20 @@ def main(): max_prefix_len = max(max_prefix_len, len(prefix)) entry.append(prefix) - for i, entry in enumerate(res): - host, future, prefix = entry - + for host, future, prefix in res: future_res = future.result() output = future_res.output.strip() if future_res.returncode is None: assert("\n" not in output) output = f"exception: {output}" + suffix = "---" + else: + suffix = future_res.returncode output_lines = output.splitlines(keepends=True) # 处理缩进 - output = output_lines[0] + "".join(textwrap.indent("".join(output_lines[1:]), " " * (max_prefix_len + 13))) - print(f"[{prefix:<{max_prefix_len}}][ret: {future_res.returncode:>3}] {output}") + output = output_lines[0] + "".join(textwrap.indent("".join(output_lines[1:]), " " * (max_prefix_len + 8))) + print(f"[{prefix:<{max_prefix_len}}][{suffix:>3}] {output}") if __name__ == "__main__": main() From 602fa841b29b97f0a1a01dbd6cb2e4787ee975da Mon Sep 17 00:00:00 2001 From: resserops Date: Thu, 10 Sep 2026 14:23:57 +0800 Subject: [PATCH 2/2] add gantt --- gantt/big_input.png | Bin 0 -> 68551 bytes gantt/big_input.txt | 324 +++++++++++++++++++++++++++++++++++++++ gantt/gantt.py | 350 +++++++++++++++++++++++++++++++++++++++++++ gantt/input.txt | 7 + gantt/test_input.png | Bin 0 -> 20843 bytes gantt/test_input.txt | 16 ++ 6 files changed, 697 insertions(+) create mode 100644 gantt/big_input.png create mode 100644 gantt/big_input.txt create mode 100755 gantt/gantt.py create mode 100644 gantt/input.txt create mode 100644 gantt/test_input.png create mode 100644 gantt/test_input.txt diff --git a/gantt/big_input.png b/gantt/big_input.png new file mode 100644 index 0000000000000000000000000000000000000000..594123fff7be9d3e6edc61beb0cecbf2b7776f7b GIT binary patch literal 68551 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 + + gantt results: + #, begin, duration, , , ... + , , , , , ... + + - worker (the # column) is a string deciding which row the task is drawn on; + - begin / duration are seconds relative to timestamp (float); + - remaining columns are custom tags, rendered onto bars via --label format string. + +Usage examples: + python3 gantt.py input.txt + python3 gantt.py input.txt -o result.png --label '{task} ({duration}s)' + python3 gantt.py input.txt --color-by task --label '{task}' + +Placeholders available in the --label format string: + {tag_name} tag names defined in the header, e.g. {task}, {size} + {c} / {N} raw CSV column index (0=worker, 1=begin, 2=duration, tags from 3) + {worker} {begin} {duration} {end} built-in fields +""" +import argparse +import re +import sys +from dataclasses import dataclass +from pathlib import Path + +import matplotlib +# 使用Anti-Grain Geometry图像渲染后端,避免在无GUI环境运行时出现错误 +matplotlib.use("Agg") +import matplotlib.colors +import matplotlib.pyplot as plt + +# 全局配置 +BASE_COLORMAP = "Set3" # 基础配色方案(matplotlib定性色条) +ALT_LIGHTEN = 0.35 # 交替明度提升参数,控制同一行相邻任务深浅差异 +FIG_W = 16.0 # 输出图像宽度,单位:英寸 +FIG_ASPECT = 9 / 16 # 默认高度:宽度 +BASE_ROWS = 32 # 基准行数:不超过时维持默认比例,超过后保持每行高度按比例增高 + +# 由BASE_COLORMAP锚点插值构建连续渐变COLORMAP +COLORMAP = matplotlib.colors.LinearSegmentedColormap.from_list( + "gradient", matplotlib.colormaps[BASE_COLORMAP].colors) + + +# 公共函数 +# 从COLORMAP上均匀采样n个颜色 +def sample_colors(n): + if n <= 1: + return [COLORMAP(0.5)] + return [COLORMAP(i / (n - 1)) for i in range(n)] + + +# 提升颜色rgb明度,amount表示向白色混合的百分比,取值为0-1 +def lighten(rgb, amount): + """向白色混合 amount,略微提高明度。""" + return tuple(c + (1 - c) * amount for c in rgb[:3]) + + +# 自然排序:划分数字和文本段,每段逐次比较,数字段优先文本段,数字段按数值排序,文本段按字典序排序 +def natural_key(s): + return [(0, int(p)) if p.isdigit() else (1, p) for p in re.split(r"(\d+)", s) if p] + + +@dataclass(frozen=True) +class Task: + worker: str + begin: float + duration: float + tags: tuple + + +@dataclass(frozen=True) +class GanttData: + """输入文件解析结果。 + + meta: 头部 key: value(如 timestamp) + columns: tag 列名(无表头时为 c3/c4/... 形式的兜底命名) + tasks: 解析出的任务列表 + """ + meta: dict + columns: tuple + tasks: tuple + + +# ---------------------------------------------------------------- 解析输入 + +def _warn(lineno, reason, raw): + print(f"warning: line {lineno} {reason}, skipped: {raw!r}", file=sys.stderr) + + +def _parse_row(fields, lineno, raw): + """解析一条数据行为 Task,字段不足或数值非法时告警并返回 None。""" + if len(fields) < 3: + _warn(lineno, "has too few fields", raw) + return None + try: + begin, duration = float(fields[1]), float(fields[2]) + except ValueError: + _warn(lineno, "begin/duration not numeric", raw) + return None + return Task(fields[0], begin, duration, tuple(fields[3:])) + + +def parse_input(path): + """Parse the input file, returning a GanttData.""" + meta, columns, tasks = {}, [], [] + in_results = False + for lineno, raw in enumerate(path.read_text(encoding="utf-8").splitlines(), 1): + line = raw.strip() + if not line: + continue + if not in_results: + if line == "gantt results:": + in_results = True + else: + key, sep, value = line.partition(":") + if sep: + meta[key.strip()] = value.strip() + continue + fields = [f.strip() for f in line.split(",")] + if not columns: + # 表头行:第二列为 begin 则认为是表头,否则首行即数据,tag 按列号命名 + if len(fields) >= 3 and fields[1].lower() == "begin": + columns = fields[3:] + continue + columns = [f"c{3 + i}" for i in range(max(len(fields) - 3, 0))] + task = _parse_row(fields, lineno, raw) + if task: + tasks.append(task) + # 统一 tag 数量与列数一致 + if columns: + tasks = [Task(t.worker, t.begin, t.duration, + (t.tags + ("",) * len(columns))[: len(columns)]) for t in tasks] + return GanttData(meta, tuple(columns), tuple(tasks)) + + +# ---------------------------------------------------------------- 统计与校验 + +def print_stats(tasks): + """打印所有任务耗时总和,以及总耗时最长/最短的 worker。""" + total = sum(t.duration for t in tasks) + per_worker = {} + for t in tasks: + per_worker[t.worker] = per_worker.get(t.worker, 0.0) + t.duration + longest = max(per_worker.items(), key=lambda kv: kv[1]) + shortest = min(per_worker.items(), key=lambda kv: kv[1]) + print(f"total duration: {fmt_num(total)}s") + print(f"longest worker: {longest[0]} ({fmt_num(longest[1])}s), " + f"shortest worker: {shortest[0]} ({fmt_num(shortest[1])}s)") + + +def check_overlaps(tasks): + """同一 worker 的任务在时序上重叠时输出 warning。""" + by_worker = {} + for t in tasks: + by_worker.setdefault(t.worker, []).append(t) + for worker, ws in by_worker.items(): + ws.sort(key=lambda t: t.begin) + cur = None # 目前结束时间最晚的任务 + for t in ws: + if cur is not None and t.begin < cur.begin + cur.duration: + print(f"warning: worker '{worker}' tasks overlap: " + f"[{fmt_num(cur.begin)}, {fmt_num(cur.begin + cur.duration)}) " + f"and [{fmt_num(t.begin)}, {fmt_num(t.begin + t.duration)})", + file=sys.stderr) + if cur is None or t.begin + t.duration > cur.begin + cur.duration: + cur = t + + +def fmt_num(value): + return f"{value:.6g}" + + +def make_label(fmt, task, columns): + """Render the --label format string onto a bar. Unknown placeholders become empty.""" + ctx = {f"c{i + 3}": tag for i, tag in enumerate(task.tags)} + for i, name in enumerate(columns): + ctx[name] = task.tags[i] if i < len(task.tags) else "" + ctx.update( + c0=task.worker, c1=fmt_num(task.begin), c2=fmt_num(task.duration), + worker=task.worker, begin=fmt_num(task.begin), + duration=fmt_num(task.duration), end=fmt_num(task.begin + task.duration), + ) + + class _Ctx(dict): + def __missing__(self, key): + return "" + + # {N} 等价于 {cN} + return re.sub(r"\{(\d+)\}", r"{c\1}", fmt).format_map(_Ctx(ctx)) + + +def resolve_column(name, columns): + """Resolve the --color-by argument to a raw CSV column index; None if unresolvable.""" + if name in columns: + return 3 + columns.index(name) + if name in ("worker", "begin", "duration"): + return ("worker", "begin", "duration").index(name) + m = re.fullmatch(r"c?(\d+)", name) + return int(m.group(1)) if m else None + + +def field_at(task, col_idx): + if col_idx == 0: + return task.worker + if col_idx == 1: + return fmt_num(task.begin) + if col_idx == 2: + return fmt_num(task.duration) + return task.tags[col_idx - 3] if col_idx - 3 < len(task.tags) else "" + + +# ---------------------------------------------------------------- 绘图 + +def render(data, args): + meta, columns, tasks = data.meta, data.columns, data.tasks + # 行排序方式:natural / lex / appearance + if args.sort == "lex": + workers = sorted({t.worker for t in tasks}) + elif args.sort == "appearance": + workers = list(dict.fromkeys(t.worker for t in tasks)) + else: + workers = sorted({t.worker for t in tasks}, key=natural_key) + row_of = {w: i for i, w in enumerate(workers)} + + # --color-by:按列值从彩虹 colorbar 均匀采样分类色 + # 默认按 worker 着色,同一 worker 内相邻任务交替深浅,避免连在一起的段看不清 + has_legend = False + legend_items = [] + if args.color_by: + col_idx = resolve_column(args.color_by, columns) + if col_idx is None: + print(f"error: cannot resolve --color-by column '{args.color_by}', " + f"available: {', '.join(columns) or '(no tag columns)'}", file=sys.stderr) + sys.exit(1) + values = list(dict.fromkeys(field_at(t, col_idx) for t in tasks)) + value_color = dict(zip(values, sample_colors(len(values)))) + task_color = {id(t): value_color[field_at(t, col_idx)] for t in tasks} + legend_items = list(value_color.items()) + has_legend = len(legend_items) >= 2 + else: + worker_color = dict(zip(workers, sample_colors(len(workers)))) + seq = {} + task_color = {} + for t in tasks: + i = seq.get(t.worker, 0) + task_color[id(t)] = (worker_color[t.worker] if i % 2 == 0 + else lighten(worker_color[t.worker], ALT_LIGHTEN)) + # 宽度接近零的任务不参与同行颜色 toggle + if t.duration > 0: + seq[t.worker] = i + 1 + + # 默认 16:9;行数超过 BASE_ROWS 时保持每行高度不变,按比例增大图高 + fig_h = FIG_W * FIG_ASPECT * max(1.0, len(workers) / BASE_ROWS) + fig, ax = plt.subplots(figsize=(FIG_W, fig_h)) + labels = [] + for t in tasks: + y = row_of[t.worker] + ax.broken_barh([(t.begin, max(t.duration, 1e-9))], (y - 0.5, 1.0), + facecolors=task_color[id(t)], edgecolor="none") + if args.label: + artist = ax.text(t.begin + t.duration / 2, y, + make_label(args.label, t, columns), + ha="center", va="center", fontsize=8) + labels.append((artist, t.begin, t.begin + t.duration)) + + # label 文字宽度超过任务 bar 宽度则不显示 + if labels: + fig.canvas.draw() + renderer = fig.canvas.get_renderer() + for artist, x0, x1 in labels: + bar_px = abs(ax.transData.transform((x1, 0))[0] + - ax.transData.transform((x0, 0))[0]) + if artist.get_window_extent(renderer).width > bar_px: + artist.remove() + + ax.set_ylim(len(workers) - 0.5, -0.5) # 首个 worker 在最上方 + ax.margins(x=0) # 横坐标左右两端不留空隙 + # 横坐标从 0(或最早任务的负时刻)开始,不跟随最早任务边界 + ax.set_xlim(left=min(0.0, min((t.begin for t in tasks), default=0.0))) + ax.set_yticks(range(len(workers))) + ax.set_yticklabels(workers) + ax.tick_params(axis="y", length=0) + ax.grid(axis="x", linewidth=0.8, alpha=0.5) + ax.set_axisbelow(True) + ax.set_xlabel("time (s)") + + title = args.title or "Gantt Chart" + sub = meta.get("timestamp") + if sub: + ax.set_title(f"{title}\n{sub}", fontsize=12) + else: + ax.set_title(title, fontsize=12) + if has_legend: + ax.legend(handles=[plt.Rectangle((0, 0), 1, 1, color=c, label=v) + for v, c in legend_items], + loc="lower left", bbox_to_anchor=(0, 1.02), ncol=8, + frameon=False, fontsize=9) + + fig.savefig(args.output, bbox_inches="tight") + + +# ---------------------------------------------------------------- CLI + +def main(): + parser = argparse.ArgumentParser( + description="Draw a Gantt chart from program task timing data " + "(input format see input.txt)") + parser.add_argument("input", help="input file path") + parser.add_argument("-o", "--output", help="output image path, default .png") + parser.add_argument("--label", + help="format string for bar labels, e.g. '{task} ({duration}s)', " + "see module docstring") + parser.add_argument("--color-by", + help="color bars by this column (tag name or index like c3), " + "default single color") + parser.add_argument("--title", help="custom title") + parser.add_argument("--sort", choices=["natural", "lex", "appearance"], + default="natural", + help="worker row order: natural (Windows-style, default), " + "lex (lexicographic), appearance (first-seen order)") + args = parser.parse_args() + + path = Path(args.input) + if not path.is_file(): + print(f"error: input path '{args.input}' does not exist or is not a file", + file=sys.stderr) + sys.exit(1) + + data = parse_input(path) + + if not data.tasks: + print("error: no valid tasks parsed (missing 'gantt results:' section?)", + file=sys.stderr) + sys.exit(1) + + args.output = args.output or str(path.with_suffix(".png")) + check_overlaps(data.tasks) + render(data, args) + print(f"gantt saved to {args.output}") + print_stats(data.tasks) + + +if __name__ == "__main__": + main() diff --git a/gantt/input.txt b/gantt/input.txt new file mode 100644 index 0000000..88c23c3 --- /dev/null +++ b/gantt/input.txt @@ -0,0 +1,7 @@ +timestamp: 2026-09-08 19:38:07.431900 + +gantt results: +#, begin, duration, , +... + +此后每行,#打印worker_id,可以是string,表示任务在甘特图的哪个行上;begin表示任务开始时间距离timestamp的时间,浮点型,单位s;duration表示任务长度,浮点型,单位s;后面的tag则是任务的自定义字段,脚本支持输入一个包含tag的格式化字符串,将字符串内容输入到对应任务的甘特图bar上 \ No newline at end of file diff --git a/gantt/test_input.png b/gantt/test_input.png new file mode 100644 index 0000000000000000000000000000000000000000..11bc29f313c67753ae53827ec03e3fa481cc6a10 GIT binary patch literal 20843 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

TN$?2*pS&hd!Gmb^QukRRV5K!jyt!y?IFW!pq& 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 literal 0 HcmV?d00001 diff --git a/gantt/test_input.txt b/gantt/test_input.txt new file mode 100644 index 0000000..897b2fc --- /dev/null +++ b/gantt/test_input.txt @@ -0,0 +1,16 @@ +timestamp: 2026-09-08 19:38:07.431900 + +gantt results: +#, begin, duration, task, size +main, 0.0, 0.8, init, small +main, 0.8, 12.5, compute, large +main, 13.3, 0, sync, small +main, 13.3, 1.2, cleanup, small +io-worker, 1.0, 5.5, read, medium +io-worker, 7.0, 4.0, write, medium +io-worker, 11.5, 0.02, fsync, small +gpu, 1.5, 3.25, kernel-A, large +gpu, 5.0, 6.75, kernel-B, large +net, 2.0, 2.5, recv, medium +net, 9.0, 3.0, send, medium +net, 14.5, 0.001, ping, small