昨天把圖畫出來了。節點會變色、會閃、會告訴你誰在 LSF 排隊。還有一個真正花時間的,是看你的工作,到底跑得如何 ?
綠了,只代表你設定要做的工作順利完成了。
日常大概長這樣:EDA 跟驗證工具都會吐一份幾十萬行的 log。JOB 一旦多起來,如果還是在用 gvim log/xxx.log 一個一個開,那是真的會累死
讓顏色告訴你「誰出事了」;讓 log 告訴你「出什麼事」。WinFlow 做的,是讓你不必離開畫面,就能把後者讀完。
很多人會把「程式幹的活」跟「LSF job 幹的活」搞混
WinFlow 把這兩者拆成兩個目錄、兩個分頁:
bsub -o log/{lsf_name}.log Job 的 stdout(工具自己印的)
-e log/{lsf_name}.err Job 的 stderr
Runner 自己記的:
logs/flow_YYYYMMDD_HHMMSS.log GUI 這次 session
logs/flow_runner.log CLI 模式
| 分頁 | 目錄 | 在說什麼 |
|---|---|---|
| Runner Log | logs/* |
誰被 bsub、現在 PEND 還是 RUN、哪一顆 EXIT |
| Job Log | log/* |
那顆 job 真正跑出來的 Innovus / Calibre / script 輸出 |

Runner Log 管「排程發生了什麼」;Job Log 管「那個程式在做什麼」
注意 lsf_name 不是畫面上的 job 名。Runner 送 LSF 時會再加使用者與時間戳,避免撞名。檔案因此長這樣:
log/lee_Place_20260815_140211.log
log/lee_Place_20260815_140211.err
路徑可改。config.json 的 runner.job_log_dir / runner.session_log_dir 就是這兩間倉庫的位置
LSF 一收下 job,bsub -o / -e 就把檔案路徑訂死。GUI 收到 job_submitted 之後,會做三件事:
lsf_name(檔名就是它)它不會把整份 log 讀進 Tk。EDA 工具的 log 動輒數十 MB,Text widget 吃進去會卡死。實際做法是:
[ 檔案從頭 ........ 還沒載入 ........ | 最後 100 行 | 之後新寫的 ]
↑
job_log_view_lines
job_log_io.py 從檔案尾巴往回讀,一次 8KB,湊滿設定的行數(預設 100)就停。回傳三樣東西:這 100 行、它們從哪個 byte 開始、更前面還有沒有內容。
之後 JobLogTailer 每 0.5 秒(log_tail_interval_sec)只讀「上次看到的 offset 之後新長出來的 bytes」。你人如果已經捲在最底,畫面會跟著往下走;你如果正在上面翻舊帳,它不會把你拽回去。
這就是為什麼畫面上有時會出現一行:
[...] earlier log not loaded — scroll up or search to load more
往上捲、或搜尋時目前這窗找不到,會再用 read_lines_before() 把更前面的 100 行接上來。舊內容插在頂部,你原本看的那一行會盡量留在原位——不然每次「再載一點」都跳到檔案頭,比直接開 gvim 還暈。
每一行長這樣:
[14:03:11] [STDOUT] [lee_Place_....log] 工具印的字
[14:03:12] [STDERR] [lee_Place_....err] 錯誤流
.log 跟 .err 合在同一個視窗,但用顏色分開:stdout 偏綠,stderr 偏紅。前面掛檔名,是因為你之後可能從下拉選單跳去看另一顆,不能讓行看起來像沒標籤的匿名字。
下拉視窗中的預設 log 視窗會看哪一顆剛被 bsub。想回看已經 DONE 的,自己選就好;選完會先載入那份檔的最後 100 行。若那顆還在跑、而且正好是目前 active job,tail 會從檔案結尾接著跟,不會把你剛載入的內容再灌一次。
真的要整份檔、要跳行號、要跟同事對同一份 evidence,工具列有 Open log file。預設叫 gvim(可在runner.log_viewer 改)
背景執行緒不准直接碰 Tk。tail 進來的資料先丟進 queue,主執行緒每 50ms 再畫。這是 Tk 的規矩;破了,畫面會在你最需要看 log 的時候凍結。
顏色如果只分 stdout / stderr,還是不夠。很多 EDA 工具把失敗寫在 stdout 裡,一行 ERROR 埋在十萬行 INFO 中間。
所以插入每一行時,會再掃一次關鍵字(不分大小寫)。它標的是詞,不是整行。你還是看得到前後文,只是那個字會自己發光:
| 關鍵字 | 標記 | 看起來 |
|---|---|---|
| FATAL / CRITICAL | sev_fatal | 紅字、深紅底、粗體 |
| ERROR / FAILED / EXCEPTION / TRACEBACK | sev_error | 紅字、暗底、粗體 |
| WARNING / WARN | sev_warning | 黃字、暗黃底 |
| MISSING / NOT FOUND / TIMEOUT / ABORT / DENIED | sev_alert | 橘色、提醒用 |
再加一個按鈕:Issues only。打開之後,只留 stderr,以及命中上面那些關鍵字的行。十萬行變成幾十行。把人眼不該逐行做的過濾,先做掉。
另外我也順手把 LSF 常見的廢話濾掉。像沒有 tty 的 batch job 很愛印:
TERM environment variable not set.
這種也對除錯沒有任何資訊量,is_job_log_noise() 會在進畫面之前丟掉。空行也丟
工具列有 Search、Find Next、Find Prev。狀態列會顯示 3 / 17。目前命中用亮黃,其他命中用暗橘。Enter 等於找下一筆。
比較少被注意到的是:如果目前載入的這 100 行裡沒有,搜尋會自動載入更舊的內容再找,最多試好幾輪。也就是你不用先把檔案捲完,才知道這個 timing violation 到底在不在。
它不是全文索引。目標是「在 GUI 裡夠用」;如果有要 grep 整份 800MB 的 log,還是 Open log file 會比較好
另一個分頁在解決另一種 log
本來 LSF 輪詢時,應該會一直印:
[12345] Status: PEND
[12345] Status: PEND
[12345] Status: RUN
[12345] Job completed successfully
如果每則都新開一行,就會變得很長,我讓 GUI 認得出這些句型,同一顆 job 永遠只佔一行,內容就地變成:
[14:03:11] [JOB] lee_Place_... id=12345 PEND
PEND 淺藍、RUN 藍、DONE 綠、EXIT 紅。要的是「現在這顆活著還是死了」,不是「它已經回報 PEND 二十次」。
其他真正的事件 —— 缺 input、bkill、驗證失敗還是一行一行往下長。該吵的還是要吵。
LSF 機器上的 job
| stdout / stderr
v
log/{lsf_name}.log
log/{lsf_name}.err
|
+-- JobLogTailer(背景,0.5s)
| 先 tail 100 行,再只讀新 bytes
v
Job Log 分頁(深色)
|-- STDOUT 綠 / STDERR 紅
|-- ERROR / WARN 關鍵字 highlight
|-- Issues only
|-- Search(不夠就再載舊的)
|-- 往上捲 → 再載 100 行
+-- Open log file → gvim(整份檔)
Runner 自己的事件
|
v
logs/flow_*.log
|
v
Runner Log 分頁(淺色)
+-- 同一顆 job 的狀態,疊在同一行
我都把他們放在 config.json 裡的 runner:
| 欄位 | 預設 | 用途 |
|---|---|---|
job_log_dir |
log |
LSF -o / -e 的目錄 |
session_log_dir |
logs |
Runner 自己的 session |
job_log_view_lines |
100 |
一開始載幾行、往上捲一次補幾行 |
log_tail_interval_sec |
0.5 |
多久去看檔案長了沒 |
log_viewer |
gvim |
Open log file 叫誰 |
Reset Flow 會把兩個目錄裡的檔清掉,DAG 回到 waiting。它不會幫你 bkill。Log 跟 LSF 行程是兩件事,清畫面不等於殺 job。
這篇沒有新的排程演算法。只是在解決一個問題:
一個個開 log 來看實在是太累了
WinFlow 能做到一些改善:尾巴先看、關鍵字自己亮、廢話先丟掉,需要整份檔的時候,也可以把 gvim 請回來。