2013年10月27日 星期日

用 graphviz 繪製 debian 套件相依關係圖

「最佔空間的一些套件」 之間的相依關係 到底我的 debian (或 ubuntu/antix/...) 系統裡面, 哪些套件最佔用空間? 它們彼此之間的相依關係 (dependency) 如何呢? 其實 apt-* 系列指令可以部分回答這兩個問題。 不過畫出來的圖大到完全無法理解。 這篇就來示範一下如何畫出類似右圖的結果, 並且順便練習一下 grep、 graphviz、 輸入輸出重新導向、 pipe、 命令結果代換等等, 再次展示 「組合的力量」。

一、 列出最佔空間的 20 個套件

根據 dpkg-query 的手冊, 可以這樣查詢套件名稱及佔用空間: dpkg-query -W -f '${Installed-Size;6} ${Package}\n'

我們先把結果存起來:

dpkg-query -W -f '${Installed-Size;6} ${Package}\n' > package-size.txt

只要按照檔案大小排序一下, 再截頭或去尾, 就可找出最佔空間的套件:

sort -n -k 1 package-size.txt | tail -n 20
sort -nr -k 1 package-size.txt | head -n 20

二、 用 grep 列出 14MB 以上的套件

[這一節的目的其實只是在練習 regexp 以及提醒 grep 的新功能而已。] 如果 「大套件」 的條件不是排名, 而是超過某個預先指定的數字, 那該如何篩選呢? 比方想列出 「佔用空間 14MB 或以上的所有套件」。 這對會寫 perl 或 awk 小程式的人來說很簡單。 但有沒有可能只靠著 regexp -- 例如只靠著 grep 指令 -- 完成呢? 因為 regexp 看不見數字 -- 在它眼裡, 所有東西都是字串。 所以必須把條件敘述翻譯成三種可能性:

  1. 1 開頭, 次位是 4-9 的 (至少) 五位數字
  2. 2-9 開頭的 (至少) 五位數字
  3. 六位或更大的數字

(因為 dpkg-query 印出來的數字以 K 為單位。) 先只看第一類的 數字 字串。 可以這樣下:
grep '1[4-9][0-9][0-9][0-9] ' package-size.txt
還好現代的 grep 提供 -P 選項, 讓我們可以改用比較強大的 PCRE 語法:
grep -P '1[4-9]\d{3} ' package-size.txt
加上顏色會看得更清楚一點:
grep -P --color=auto '1[4-9]\d{3} ' package-size.txt
同樣的方法可以找出第二類和第三類:
grep -P --color=auto '[2-9]\d{4} ' package-size.txt
grep -P --color=auto '\d{6} ' package-size.txt
合併在一起就變成:
grep -P --color=auto '(\d\d\d|[2-9]\d|1[4-9])\d{3} ' package-size.txt

順便一提: -P 跟 --color=auto 這兩個選項太好用了。 建議在 ~/.bashrc 裡面加上這一句:
alias pgrep='/bin/grep -P --color=auto'
這樣以後可以用 pgrep 指令做 PCRE 搜尋, 而且它會用不同的顏色顯示找到的字串, 有助於 regexp 除錯。

三、 列出與某個特定套件直接相關的那些套件

如果想查詢 「某個特定套件 (比方說 vim 好了) 相依於哪些套件?」 那麼可以這樣下:

apt-cache depends vim

列出來的結果顯示: vim 套件必須有 vim-common, vim-runtime, libacl1, libc6, libgpm2, libselinux1, libtinfo5 等等套件 "撐著" (也就是說, vim 需要用到這些套件) 才能運作。

反過來說, 如果想查詢 「有哪些套件會需要用到某個特定套件 (比方說 coreutils 好了)?」 那麼可以這樣下:

apt-cache rdepends coreutils

列出來的結果顯示: 包含 initscripts, xinit, mktemp, 各版本的核心 (linux-image-*) 等等三五十個套件都靠 coreutils 撐著 (都需要用到 coreutils)。

以上只顯示一層的相依關係。 如果要遞迴地列出所有下層的 depends 或所有上層的 rdepends, 可以加上 --recurse 選項。 不過那樣印出來的資料量多到爆, 顯然不是給人看的。

四、 畫出所有套件相依圖

以上都是文字資訊; 可是... 沒圖沒真相, 一張圖勝過千言萬語啊! 其實 apt-cache 有一個很強大的功能可以畫出整個系統內所有套件的相依圖:

apt-cache dotty > ~/system-dependency.dot

這在我的 antix 底下會產生一個 8MB 的大檔。 它是 graphviz 格式的原始碼, 所以可以這樣產生 svg 向量圖檔:

dot system-dependency.dot -T svg -o system-dependency.svg

如果你的電腦比較慢的話, 會當住好久。 可以開另外的分頁, 用 top 看一下, 它確實還在執行, 而且幾乎吃掉所有的 CPU 時間。 我還不曾在手邊的任何電腦成功地完成這個指令 -- 每次都是以 「Killed」 或 「已砍掉」 的殘念收場。 冏rz ... 就算你的電腦夠強, 成功地產生一個超大的 system-dependency.svg 而且 firefox 或 chrome 成功地開啟這個圖檔, (我不用看也知道) 你也會被它的複雜度完全打敗。 就算有 google 地圖那種等級的縮放功能 (而不是只有 firefox 的小級距 「ctrl +」 跟 「ctrl -」) 你也還是看不懂套件之間的關係 :-)

五、 畫出某些特定套件的相依圖

apt-cache dotty 有一個選項可以 「只畫出你所指定的幾個套件 (以及他們第一層支撐者) 之間的相依關係」。 例如可以這樣畫出 gimp, inkscape, digikam 這幾個套件及其第一層支撐者之間的相依關係:

apt-cache -o APT::Cache::GivenOnly=1 dotty gimp inkscape digikam > 3pkgs.dot
dot 3pkgs.dot -T svg -o 3pkgs.svg

請用(任何有水準的) 瀏覽器打開來看看。 啊, 終於產生一個勉強可以看的圖了!

六、 過濾和補資訊

不過這樣畫出來的相依套件既太多 (不分大小一律印出) 又太少 (只有一層)。 於是我寫了一支 小小的 perl 程式。 請把它的內容剪貼 (不要 「另存新檔」) 並重新命名為 pick-large-add-size.pl 。 然後 chmod a+x pick-large-add-size.pl 變成可執行檔。 它把命令列上第一個參數視為對照表的檔名, 用以過濾及編修 standard input 的資料。 它假設對照表採用很簡單的一列列 「大小 套件名稱」 格式 -- 就是上面 dpkg-query 輸出的 package-size.txt 格式; 又假設 stdin 的資料是 graphviz 的 dot 格式。 它會過濾 dot 內容, 只留下對照表裡面提及的套件, 又會把每個套件的大小補進 dot 檔裡面。 例如這樣做:

grep -P '(\d\d|[3-9])\d{3} ' package-size.txt > large-pkg.txt
./pick-large-add-size.pl large-pkg.txt < 3pkgs.dot | dot -T svg -o 3pkgs.svg

所畫出來的圖只包含 「上述三套件第一層相依套件當中大小至少 3M 者」。

再進一步, 用 grep 的 -o 選項 ("只印出比對到的字串; 不要印同一列上的前後文") 挑出套件名稱, 再用 命令結果代換 把這些 「夠大的套件」 (這次改成 「至少 14M」) 的名稱放在 apt-cache dotty 的命令列上。 然後用 pick-large-add-size.pl 重新處理一次:

grep -P '(\d\d\d|[2-9]\d|1[4-9])\d{3} ' package-size.txt > large-pkg.txt
apt-cache -o APT::Cache::GivenOnly=1 dotty $(grep -Po ' \S+$' large-pkg.txt) > large-pkg-dep.dot
./pick-large-add-size.pl large-pkg.txt < large-pkg-dep.dot | dot -T svg -o large-pkg-dep.svg

就可以看到一個比較簡潔的相依圖。 最後手動編輯一下 large-pkg-dep.dot 在大括弧之後加上一句 rankdir=LR; 讓相依順序方向改成由左而右排列。 重新執行上面最後一句, 然後用 inkscape 打開最終成品 large-pkg-dep.svg 略微手工調整, 就得到本文最開始的插圖。

七、 組合的力量

用 debtree 指令繪製出來的 debconf 套件相依圖 本文寫到一半才發現有個好物 debtree 也在做有點類似但不又不盡相同的事, 方便/強大/實用。 可惜它只查詢一個套件的相依關係。

每一塊單獨的積木看起來都很單調乏味;
但它最好玩也最強大的地方在於彼此可以用各種排列組合產生無窮盡的可能性 不過當初要寫這篇的動機其實也並非為了實用, 只是想在 linux 課堂上展現 組合的力量。 請深呼吸, 讓我們用 pipe 跟 command substitution 這兩個最重要的機制, 一氣呵成 (省略大部分的中間檔) 產生最終的成果:

apt-cache -o APT::Cache::GivenOnly=1 dotty $(dpkg-query -W -f '${Installed-Size;6} ${Package}\n' | grep -P '(\d\d\d|[2-9]\d|1[4-9])\d{3} ' | tee large-pkg.txt | grep -Po ' \S+$') | ./pick-large-add-size.pl large-pkg.txt | dot -T svg -o large-pkg-dep.svg

實際執行時, 必須執行第二次才會成功, 因為 shell 執行 pick-large-add-size.pl 的時間出乎意外地早, 當時 tee 指令還來不及產生 large-pkg.txt。

在 linux 底下, 每一個指令就像一塊塊單獨的積木一樣, 看起來單調無聊乏善可陳。 但它最好玩也最強大的地方在於: 許多積木放在一起時, 彼此可以用各種不同的排列組合, 產生奇妙的花樣, 以及無窮盡的可能性。 這種以簡馭繁的快感, 就是小時候被電腦吸引的真正原因啊!

所有指令一氣呵成的圖解

沒有留言:

張貼留言