Farseerfc的小窩 - pacgraph//farseerfc.me/2016-07-31T03:52:00+09:00PacVis: 可視化 pacman 本地數據庫2016-07-31T03:52:00+09:002016-07-31T03:52:00+09:00farseerfctag:farseerfc.me,2016-07-31:/pacvis.html
<div class="panel panel-default">
<div class="panel-heading">
PacVis</div>
<div class="panel-body">
<img alt="Demo of PacVis" class="img-responsive" src="//farseerfc.me/images/pacvis-first.png"/>
</div>
</div>
<div class="section" id="pacvis">
<h2><a class="toc-backref" href="#id11">我爲什麼要做 PacVis</a></h2>
<p>我喜歡 Arch Linux ,大概是因爲唯有 Arch Linux 能給我對整個系統「瞭如指掌」的感覺。
在 Arch Linux 裏我能清楚地知道我安裝的每一個包,能知道系統裏任何一個文件是來自哪個包,
以及我爲什麼要裝它。或許對 Debian/Fedora/openSUSE 足夠熟悉了之後也能做到這兩點,
不過他們的細緻打包的結果通常是包的數量比 Arch 要多個 3 到 10 倍,並且打包的細節也比 Arch
Linux 簡單的 PKGBUILD 要複雜一個數量級。</p>
<p>每一個裝過 Arch Linux 的人大概都知道,裝了 Arch Linux 之後得到的系統非常樸素,按照
ArchWiki 上的流程一路走下來的話,最關鍵的一條命令就是 <code class="code">
pacstrap /mnt …</code></p></div>
<div class="panel panel-default">
<div class="panel-heading">
PacVis</div>
<div class="panel-body">
<img alt="Demo of PacVis" class="img-responsive" src="//farseerfc.me/images/pacvis-first.png"/>
</div>
</div>
<div class="section" id="pacvis">
<h2><a class="toc-backref" href="#id11">我爲什麼要做 PacVis</a></h2>
<p>我喜歡 Arch Linux ,大概是因爲唯有 Arch Linux 能給我對整個系統「瞭如指掌」的感覺。
在 Arch Linux 裏我能清楚地知道我安裝的每一個包,能知道系統裏任何一個文件是來自哪個包,
以及我爲什麼要裝它。或許對 Debian/Fedora/openSUSE 足夠熟悉了之後也能做到這兩點,
不過他們的細緻打包的結果通常是包的數量比 Arch 要多個 3 到 10 倍,並且打包的細節也比 Arch
Linux 簡單的 PKGBUILD 要複雜一個數量級。</p>
<p>每一個裝過 Arch Linux 的人大概都知道,裝了 Arch Linux 之後得到的系統非常樸素,按照
ArchWiki 上的流程一路走下來的話,最關鍵的一條命令就是 <code class="code">
pacstrap /mnt base</code>
,
它在 <code class="code">
/mnt</code>
裏作爲根調用 <code class="code">
pacman -S base</code>
裝上了整個 base 組,
然後就沒有然後了。這個系統一開始空無一物,你需要的任何東西都是後來一點點用
<code class="code">
pacman</code>
手動裝出來的,沒有累贅,按你所需。</p>
<p>然而時間長了,系統中難免會有一些包,是你裝過用過然後忘記了,
然後這些包就堆在系統的角落裏,就像家裏陳年的老傢俱,佔着地,落着灰。雖然
<code class="code">
pacman -Qtd</code>
能方便地幫你找出所有
<strong>曾經作爲依賴被裝進來,而現在不被任何包依賴</strong> 的包,但是對於那些你手動指定的包,
它就無能爲力了。</p>
<p>於是我就一直在找一個工具能幫我梳理系統中包的關係,方便我:</p>
<ol class="arabic simple">
<li>找出那些曾經用過而現在不需要的包</li>
<li>找出那些體積大而且佔地方的包</li>
<li>釐清系統中安裝了的包之間的關係</li>
</ol>
<div class="figure">
<img alt="Android System Architecture" class="img-responsive" src="//farseerfc.me/images/Android-System-Architecture.jpg"/>
<p class="caption"><a class="reference external" href="https://en.wikipedia.org/wiki/Android_(operating_system)">Android 系統架構</a></p>
</div>
<p>關於最後一點「釐清包的關係」,我曾經看到過
<a class="reference external" href="https://en.wikipedia.org/wiki/Architecture_of_OS_X">macOS 系統架構圖</a>
和 Android 的系統架構圖,對其中的層次化架構印象深刻,之後就一直在想,是否能畫出現代
Linux 桌面系統上類似的架構圖呢?又或者 Linux 桌面系統是否會展現完全不同的樣貌?
從維基百科或者別的渠道能找到 Linux 內核、或者 Linux 圖形棧,
或者某個桌面環境的架構,但是沒有找到覆蓋一整個發行版的樣貌的。
於是我便想,能不能從包的依賴關係中自動生成這樣一張圖呢。</p>
</div>
<div class="section" id="id1">
<h2><a class="toc-backref" href="#id12">PacVis的老前輩們</a></h2>
<p>在開始寫 PacVis 之前,我試過一些類似的工具,他們都或多或少能解決一部分我的需要,
又在某些方面有所不足。這些工具成爲了 PacVis 的雛形,啓發了 PacVis
應該做成什麼樣子。</p>
<div class="section" id="pactree">
<h3><a class="toc-backref" href="#id13">pactree</a></h3>
<p>pactree 曾經是一個
<a class="reference external" href="https://bbs.archlinux.org/viewtopic.php?id=51795">獨立的項目</a> ,現在則是
<a class="reference external" href="https://www.archlinux.org/pacman/pactree.8.html">pacman 的一部分</a> 了。
從手冊頁可以看出, pactree 的輸出是由某個包開始的依賴樹。
加上 <code class="code">
--graph</code>
參數之後 pactree 還能輸出
<a class="reference external" href="http://www.graphviz.org/">dot</a> 格式的矢量圖描述,然後可以用 dot 畫出依賴圖:</p>
<div class="panel panel-default">
<div class="panel-heading">
<code class="code">
pactree pacvis-git -d3 --graph | dot -Tpng >pacvis-pactree.png</code>
</div>
<div class="panel-body">
<img alt="pactree --graph" class="img-responsive" src="//farseerfc.me/images/pacvis-pactree.png"/>
</div>
</div>
<div class="highlight"><pre><span class="code-line"><span></span><span class="gp">$</span> pactree pacvis-git -d3</span>
<span class="code-line"><span class="go">pacvis-git</span></span>
<span class="code-line"><span class="go">├─python-tornado</span></span>
<span class="code-line"><span class="go">│ └─python</span></span>
<span class="code-line"><span class="go">│ ├─expat</span></span>
<span class="code-line"><span class="go">│ ├─bzip2</span></span>
<span class="code-line"><span class="go">│ ├─gdbm</span></span>
<span class="code-line"><span class="go">│ ├─openssl</span></span>
<span class="code-line"><span class="go">│ ├─libffi</span></span>
<span class="code-line"><span class="go">│ └─zlib</span></span>
<span class="code-line"><span class="go">├─pyalpm</span></span>
<span class="code-line"><span class="go">│ ├─python</span></span>
<span class="code-line"><span class="go">│ └─pacman</span></span>
<span class="code-line"><span class="go">│ ├─bash</span></span>
<span class="code-line"><span class="go">│ ├─glibc</span></span>
<span class="code-line"><span class="go">│ ├─libarchive</span></span>
<span class="code-line"><span class="go">│ ├─curl</span></span>
<span class="code-line"><span class="go">│ ├─gpgme</span></span>
<span class="code-line"><span class="go">│ ├─pacman-mirrorlist</span></span>
<span class="code-line"><span class="go">│ └─archlinux-keyring</span></span>
<span class="code-line"><span class="go">└─python-setuptools</span></span>
<span class="code-line"><span class="go"> └─python-packaging</span></span>
<span class="code-line"><span class="go"> ├─python-pyparsing</span></span>
<span class="code-line"><span class="go"> └─python-six</span></span>
<span class="code-line"><span class="gp"> $</span> pactree pacvis-git -d3 --graph <span class="p">|</span> dot -Tpng >pacvis-pactree.png</span>
</pre></div>
<p>從畫出的圖可以看出,因爲有共用的依賴,所以從一個包開始的依賴關係已經不再是一棵
<a class="reference external" href="https://zh.wikipedia.org/wiki/%E6%A8%B9%E7%8B%80%E7%B5%90%E6%A7%8B">圖論意義上的樹(Tree)</a>
了。最初嘗試做 PacVis 的早期實現的時候,就是試圖用 bash/python 腳本解析 pactree 和
pacman 的輸出,在 pactree 的基礎上把整個系統中所有安裝的包全都包含到一張圖裏。
當然後來畫出的結果並不那麼理想,首先由於圖非常巨大,導致 dot
的自動佈局要耗費數小時,最後畫出的圖也過於巨大基本上沒法看。</p>
<p>然而不得不說沒有 pactree 就不會有 PacVis ,甚至 pacman 被分離出 alpm
庫也和 pactree 用 C 重寫的過程有很大關係,而 PacVis 用來查詢 pacman 數據庫的庫
pyalpm 正是 alpm 的 Python 綁定。因爲 pactree 的需要而增加出的 alpm 庫奠定了 PacVis
實現的基石。</p>
</div>
<div class="section" id="pacgraph">
<h3><a class="toc-backref" href="#id14">pacgraph</a></h3>
<div class="panel panel-default">
<div class="panel-heading">
pacgraph 的輸出</div>
<div class="panel-body">
<img alt="pacgraph" class="img-responsive" src="//farseerfc.me/images/pacvis-pacgraph.png"/>
</div>
</div>
<p><a class="reference external" href="http://kmkeen.com/pacgraph/index.html">pacgraph</a> 是一位 Arch Linux 的
Trusted User <a class="reference external" href="http://kmkeen.com/">keenerd</a> 寫的程序,和
PacVis 一樣也是用 Python 實現的。
比起 pactree , pacgraph 明顯更接近我的需求,它默認繪製整個系統的所有安裝包,
並且用聰明的佈局算法解決了 dot 佈局的性能問題。</p>
<p>pacgraph 的輸出是一個富有藝術感的依賴圖,圖中用不同的字體大小表示出了每個包佔用
的磁盤空間。通過觀察 pacgraph 的輸出,我們可以清楚地把握系統全局的樣貌,
比如一眼看出這是個桌面系統還是個服務器系統,並且可以很容易地發現那些佔用磁盤空間
巨大的包,考慮清理這些包以節約空間。</p>
<p>更棒的是 pacgraph 還提供了一個交互性的 GUI 叫做 pacgraph-tk ,顯然通過 tk 實現。
用這個 GUI 可以縮放觀察整幅圖的細節,或者選中某個包觀察它和別的包的依賴關係。</p>
<p>pacgraph 還支持通過參數指定只繪製個別包的依賴關係,就像 pactree 那樣。</p>
<p>不過 pacgraph 也不是完全滿足我的需要。如我前面說過,我希望繪製出的圖能反應
<strong>這個發行版的架構面貌</strong> ,而 pacgraph 似乎並不區別「該包依賴的包」和「依賴該包的包」
這兩種截然相反的依賴關係。換句話說 pacgraph 畫出的是一張無向圖,
而我更想要一張有向圖,或者說是 <strong>有層次結構的依賴關係圖</strong> 。</p>
</div>
</div>
<div class="section" id="id4">
<h2><a class="toc-backref" href="#id15">於是就有了 PacVis</a></h2>
<div class="panel panel-default">
<div class="panel-heading">
PacVis 剛打開的樣子</div>
<div class="panel-body">
<img alt="PacVis on startup" class="img-responsive" src="//farseerfc.me/images/pacvis-second.png"/>
</div>
</div>
<p>總結了老前輩們的優勢與不足,我便開始利用空餘時間做我心目中的 PacVis 。
前後斷斷續續寫了兩個月,又分爲兩個階段,第一階段做了基本的功能和雛形,
第二階段套用上 <a class="reference external" href="https://getmdl.io/">https://getmdl.io/</a> 的模板,總算有了能拿得出手給別人看的樣子。</p>
<p>於是乎前兩天在 AUR 上給 pacvis 打了個
<a class="reference external" href="https://aur.archlinux.org/packages/pacvis-git/">pacvis-git</a>
包,現在想在本地跑 pacvis 應該很方便了,用任何你熟悉的 aurhelper
就可以安裝,也可以直接從 aur 下載 PKGBUILD 打包:</p>
<div class="highlight"><pre><span class="code-line"><span></span><span class="go">~$ git clone aur@aur.archlinux.org:pacvis-git.git</span></span>
<span class="code-line"><span class="go">~$ cd pacvis-git</span></span>
<span class="code-line"><span class="go">~/pacvis-git$ makepkg -si</span></span>
<span class="code-line"><span class="go">~/pacvis-git$ pacvis</span></span>
<span class="code-line"><span class="go">Start PacVis at http://localhost:8888/</span></span>
</pre></div>
<p>按照提示說的,接下來打開瀏覽器訪問 <a class="reference external" href="http://localhost:8888/">http://localhost:8888/</a> 就能看到 PacVis
的樣子了。僅僅作爲嘗試也可以直接打開跑在我的服務器上的 demo:
<a class="reference external" href="https://pacvis.farseerfc.me/">https://pacvis.farseerfc.me/</a> ,這個作爲最小安裝的服務器載入速度大概比普通的桌面系統快一點。</p>
<div class="panel panel-default">
<div class="panel-heading">
在 Windows msys2 跑 PacVis</div>
<div class="panel-body">
<img alt="PacVis on Windows msys2" class="img-responsive" src="//farseerfc.me/images/pacvis-msys2.png"/>
</div>
</div>
<p>另外補充一下,因爲 PacVis 只依賴 pyalpm 和 tornado ,所以在別的基於 pacman
的系統上跑它應該也沒有任何問題,包括
<a class="reference external" href="https://msys2.github.io/">Windows 上的 msys2</a> 裏(儘管在 msys2 上編譯
tornado 的包可能要花些功夫)。</p>
</div>
<div class="section" id="id5">
<h2><a class="toc-backref" href="#id16">PacVis 的圖例和用法</a></h2>
<p>操作上 PacVis 仿照地圖程序比如 Google Maps 的用法,可以用滾輪或者觸摸屏的手勢
縮放、拖拽,右上角有個側邊欄,不需要的話可以點叉隱藏掉,右下角有縮放的按鈕和
回到全局視圖的按鈕,用起來應該還算直觀。</p>
<div class="figure">
<img alt="PacVis showing pacvis-git" class="img-responsive" src="//farseerfc.me/images/pacvis-pacvis-git.png"/>
<p class="caption">pacvis-git 包的依賴</p>
</div>
<p>先解釋圖形本身,整張圖由很多小圓圈的節點,以及節點之間的箭頭組成。
一個圓圈就代表一個軟件包,而一條箭頭代表一個依賴關係。縮放到細節的話,
能看到每個小圓圈的下方標註了這個軟件包的名字,鼠標懸浮在圓圈上也會顯示相應信息。
還可以點開軟件包,在右側的邊欄裏會有更詳細的信息。</p>
<p>比如圖例中顯示了 pacvis-git 自己的依賴,它依賴 pyalpm, python-tornado 和
python-setuptools ,其中 pyalpm 又依賴 pacman 。圖中用
<span class="label label-primary">紫色</span> 表示手動安裝的包,
<span class="label label-warning">橙色</span> 表示被作爲依賴安裝的包,
箭頭的顏色也隨着包的顏色改變。</p>
<p>值得注意的是圖中大多數箭頭都是由下往上指的,這是因爲 PacVis 按照包的依賴關係做
了拓撲排序,並且給每個包賦予了一個拓撲層級。比如 pacvis-git 位於 39
層,那麼它依賴的 pyalpm 就位於 38 層,而 pyalpm 依賴的 pacman 就位於 37
層。根據層級關係排列包是 PacVis 於 pacgraph 之間最大的不同之處。</p>
<p>除了手動縮放, PacVis 還提供了搜索框,根據包名快速定位你感興趣的包。
以及在右側邊欄中的 Dep 和 Req-By 等頁中,包的依賴關係也是做成了按鈕的形式,
可以由此探索包和包之間的關聯。</p>
<p>最後稍微解釋一下兩個和實現相關的參數:</p>
<div class="label label-info">
Max Level</div>
<p>這是限制 PacVis 載入的最大拓撲層。系統包非常多的時候 PacVis
的佈局算法會顯得很慢,限制層數有助於加快載入,特別是在調試 PacVis 的時候比較有用。</p>
<div class="label label-info">
Max Required-By</div>
<p>這是限制 PacVis 繪製的最大被依賴關係。稍微把玩一下 PacVis 就會發現系統內絕大多數
的包都直接依賴了 glibc 或者 gcc-libs 等個別的幾個包,而要繪製這些依賴的話會導致
渲染出的圖中有大量長直的依賴線,不便觀察。於是可以通過限制這個值,使得 PacVis
不繪製被依賴太多的包的依賴關係,有助於讓渲染出的圖更易觀察。</p>
</div>
<div class="section" id="id6">
<h2><a class="toc-backref" href="#id17">從 PacVis 能瞭解到的一些事實</a></h2>
<div class="panel panel-default">
<div class="panel-heading">
一個 KDE 桌面的 PacVis 結果全圖, <a class="reference external" href="//farseerfc.me/images/pacvis-16384.png">放大(17M)</a></div>
<div class="panel-body">
<img alt="A normal KDE desktop in PacVis" class="img-responsive" src="//farseerfc.me/images/pacvis-4096-anno.png"/>
</div>
</div>
<p>稍微玩一下 PacVis 就能發現不少有趣現象,上述「絕大多數包依賴 glibc 」就是一例。
除此之外還有不少值得玩味的地方。</p>
<div class="section" id="id7">
<h3><a class="toc-backref" href="#id18">依賴層次</a></h3>
<p>系統中安裝的包被明顯地分成了這樣幾個層次:</p>
<ul class="simple">
<li>glibc 等 C 庫</li>
<li>Bash/Perl/Python 等腳本語言</li>
<li>coreutils/gcc/binutils 等核心工具</li>
<li>pacman / systemd 等較大的系統工具</li>
<li>gtk{2,3}/qt{4,5} 等 GUI toolkit</li>
<li>chromium 等 GUI 應用</li>
<li>Plasma/Gnome 等桌面環境</li>
</ul>
<p>大體上符合直觀的感受,不過細節上有很多有意思的地方,比如 zsh 因爲 gdbm
間接依賴了 bash,這也說明我們不可能在系統中用 zsh 完全替代掉 bash。
再比如 python (在 Arch Linux 中是 python3)和 python2 和 pypy
幾乎在同一個拓撲層級。</p>
<div class="figure">
<img alt="zsh depends on bash because of gdbm" class="img-responsive" src="//farseerfc.me/images/pacvis-zsh-bash.png" style="width: 45%;"/>
<p class="caption">zsh 因爲 gdbm 間接依賴了 bash</p>
</div>
<p>不過偶爾顯示的依賴層級不太符合直觀,比如 qt5-base < qt4 < gtk2 < gtk3 。
qt5 因爲被拆成了數個包所以比 qt4 更低級這可以理解,而 gtk 系比 qt
系更高級這一點是很多人(包括我)沒有預料到的吧。</p>
</div>
<div class="section" id="id8">
<h3><a class="toc-backref" href="#id19">循環依賴</a></h3>
<p>有些包的依賴關係形成了循環依賴,一個例子是 freetype2 和 harfbuzz,freetype2
是繪製字體的庫,harfbuzz 是解析 OpenType 字形的庫,兩者對對方互相依賴。
另一個例子是 KDE 的 kio 和 kinit,前者提供類似 FUSE 的資源訪問抽象層,
後者初始化 KDE 桌面環境。</p>
<div class="figure">
<img alt="freetype2 harfbuzz" class="img-responsive" src="//farseerfc.me/images/pacvis-freetype2-harfbuzz.png" style="width: 45%;"/>
<p class="caption">freetype2 和 harfbuzz 之間的循環依賴</p>
</div>
<p>因爲這些循環依賴的存在,使得 PacVis 在實現時不能直接拓撲排序,我採用環探測
算法找出有向圖中所有的環,並且打破這些環,然後再使用拓撲排序。
因此我在圖中用紅色的箭頭表示這些會導致環的依賴關係。</p>
</div>
<div class="section" id="id9">
<h3><a class="toc-backref" href="#id20">有些包沒有依賴關係</a></h3>
<div class="figure">
<img alt="PacVis Level 0" class="img-responsive" src="//farseerfc.me/images/pacvis-level0.png" style="width: 45%;"/>
<p class="caption">man-pages 和 licenses 沒有依賴關係</p>
</div>
<p>有些包既不被別的包依賴,也不依賴別的包,而是孤立在整張圖中,比如
man-pages 和 licenses 。這些包在圖中位於最頂端,拓撲層級是 0 ,我用
<span class="label label-info">藍色</span> 正方形特別繪製它們。</p>
</div>
<div class="section" id="linux">
<h3><a class="toc-backref" href="#id21">只看依賴關係的話 Linux 內核完全不重要</a></h3>
<p>所有用戶空間的程序都依賴着 glibc ,而 glibc 則從定義良好的 syscall 調用內核。
因此理所當然地,如果只看用戶空間的話, glibc 和別的 GNU 組件是整個 GNU/Linux
發行版的中心,而 Linux 則是位於依賴層次中很深的位置,甚至在我的 demo 服務器上
Linux 位於整個圖中的最底端,因爲它的安裝腳本依賴 mkinitcpio
而後者依賴了系統中的衆多組件。</p>
</div>
<div class="section" id="pacman-qtd">
<h3><a class="toc-backref" href="#id22">pacman -Qtd 不能找到帶有循環依賴的孤兒包</a></h3>
<div class="figure">
<img alt="pacman -Qtd cannot find packages with circle dependency" class="img-responsive" src="//farseerfc.me/images/pacvis-circledeps-Qtd.png" style="width: 45%;"/>
<p class="caption">msys2 中帶有循環依賴的孤兒包</p>
</div>
<p>這是我在 msys2 上測試 PacVis 的時候發現的,我看到在渲染的圖中有一片羣島,
沒有連上任何手動安裝的包。這種情況很不正常,因爲我一直在我的所有系統中跑
<code class="code">
pacman -Qtd</code>
找出孤兒包並刪掉他們。放大之後我發現這些包中有一條循環依賴,
這說明 <code class="code">
pacman -Qtd</code>
不能像語言的垃圾回收機制那樣找出有循環依賴的孤兒包。</p>
</div>
</div>
<div class="section" id="id10">
<h2><a class="toc-backref" href="#id23">PacVis 的未來</a></h2>
<p>目前的 PacVis 基本上是我最初開始做的時候設想的樣子,隨着開發逐漸又增加了不少功能。
一些是迫於佈局算法的性能而增加的(比如限制層數)。</p>
<p>今後準備再加入以下這些特性:</p>
<ol class="arabic simple">
<li>更合理的 optdeps 處理。目前只是把 optdeps 關係在圖上畫出來了。</li>
<li>更合理的 <strong>依賴關係抉擇</strong> 。有時候包的依賴關係並不是直接根據包名,而是
<code class="code">
provides</code>
由一個包提供另一個包的依賴。目前 PacVis 用 alpm
提供的方式抉擇這種依賴,於是這種關係並沒有記錄在圖上。</li>
<li>目前的層級關係沒有考慮包所在的倉庫 (core/extra/community/...) 或者包所屬的組。
加入這些關係能更清晰地表達依賴層次。</li>
<li>目前沒有辦法只顯示一部分包的關係。以後準備加入像 pactree/pacgraph 一樣顯示部分包。</li>
</ol>
<p>如果你希望 PacVis 出現某些有趣的用法和功能,也
<a class="reference external" href="https://github.com/farseerfc/pacvis/issues">請給我提 issue</a> 。</p>
</div>