nix-du
nix-du 是一个旨在帮助回答以下问题的工具:
我应该从我的 nix store 中移除哪些 gc-roots 以释放一些空间? 我应该从我的 profile 中移除哪些包以释放一些空间?
入门
安装
nix-du 可在 nixpkgs >= 18.09-pre 中获取,请参阅下方的徽章。
在这种情况下,安装过程很简单。
nix-env -f <nixpkgs> -iA nix-du
有关其他安装方法,请参阅 INSTALL.md。
运行
nix-du 将其分析结果以 DOT 格式输出为有向图(稍后会有更多介绍)。
因此,您需要安装 dot(通常以包名 graphviz 提供)。
然后,您可以将该图转换为各种更“传统”的图像格式。
例如:
# to svg
nix-du -s=500MB | dot -Tsvg > store.svg
# to png
nix-du -s=500MB | dot -Tpng > store.png
另一个选项是使用交互式查看器,例如 zgrviewer
nix-du -s=500MB > store.dot
zgrviewer -f store.dot
解读结果
哪些 gc-roots 占用了空间?
作为一个示例,想象以下场景。
您从一个全新的、空的 nix 安装开始。您安装
nix-index。然后您运行 nix-du:
nix-du | dot -Tsvg > result.svg
左侧是 gc-roots。其他节点标注了包名,
但这几乎没有意义。重要的是它们的大小。蓝色表示“最轻”;
红色表示“最重”。从 A 到 B 的边意味着“只要 A 还活着,
你就无法移除 B”。如果你移除一个节点的所有入边,当你运行 nix-collect-garbage 时它_应该_
消失,并且这_应该_释放大约
显示的空间量。
在此实例中,我们看到 root 和 coucou 共享同一个 channel,其
重量约为 50Mo。从 channel 指向 user-environment
的箭头象征着,如果你想摆脱这 50 MB,你必须删除
root 和 coucou 的 channel。
另一方面,nix-index 不出现在图中:如果你移除你的(唯一的)
profile(黄色),那么 nix-index 就会消失。因此节点 nix-index 已经
与 profile 的节点合并。总之,如果你移除你的 profile,你将
节省大约 27 MB。
现在,你安装 graphviz。
如果你删除第二个配置文件,你可以节省 graphviz 及其依赖项(140 MB)
但如果你删除第一个配置文件,你不会节省任何空间:nix-index 是两个配置文件的依赖项,
因此它现在拥有自己的节点。
如果你使用 nix-env -e graphviz 删除 graphviz
... 你意识到它仍然在你的第二个配置文件中。 所以如果你想节省空间,删除红色节点 :)
过滤器
在现实中,一个存储的依赖图往往非常大(并且 dot 会
在计算其布局时遇到困难),因此你可以要求 nix-du 对其进行简化:
- 仅保留权重至少为 500 MB 的节点(即我只对节省至少 500 MB 感兴趣):
nix-du -s=500MB | dot -Tsvg > store.svg
- 仅保留 50 个最重的内部节点
nix-du -n=50 | dot -Tsvg > store.svg
请注意,使用这些选项时:
- 即使某些根节点不够重,它们也会被保留。
- 节点的大小变为近似值,因此如果删除一个 500 MB 的根节点只节省了 450 MB, 请不要感到惊讶。
我的配置文件中的哪个元素占用了空间?
nix-du 也可以用于分析存储
路径的哪些依赖项导致了磁盘占用。为此,请传递 --root /nix/store/hash-foo。特别是,以下是一些有用的用例:
- 在
/etc/nixos/configuration.nix中使用environment.systemPackages安装的哪些包占用的空间最多?(忽略所有小于 500 MB 的内容)
nix-du --root /run/current-system/sw/ -s 500MB > result.dot
- 使用
nix-env安装的哪些包占用的空间最多?
nix-du --root ~/.nix-profile > result.dot
限制
请注意,nix-du 主要关注根节点的直接依赖项;如果你希望传递依赖项清晰可见,请查看 nix-tree。
示例
八边形框是根存储路径的子项(此处为我的 配置文件中的元素)。
例如,你可以看到 nix 在图中没有依赖项:它很可能是
从与配置文件其余部分不同的 nixpkgs 提交安装的,因此不
与它们共享依赖项。让我们解决这种情况:
nix-env -f "<nixpkgs>" -iA nix
现在,nix 的占用更小(因为它与我的配置中的其他部分共享依赖项),
并且你可以看到,如果不考虑它对 nix 的依赖,nix-du 仅占用几兆字节。
注意事项
--root 与外部引用者
请注意,当传入 --root 时,nix-du 将忽略指定 store 路径的传递闭包之外的所有内容。这可能导致意外的行为。
示例:如果你有如下 NixOS 配置:
{ pkgs, config, ... }:
{
services.openssh.enable = true;
environment.systemPackages = with pkgs; [ openssh ];
}
然后你运行
nix-du --root /run/current-system/sw/ > result.dot
并看到 openssh 占用了 123 MB 的空间。然后你从
你的 systemPackages 中移除 openssh,删除旧版本,但并未释放 123 MB。这是因为
其他某些内容(此处为单元文件 sshd.service)也依赖于 openssh,
从而阻止其删除,但被 nix-du 完全忽略了。
Store 优化
如果你使用了 store 优化(参见 nix-store --optimise 的文档),那么
不相关的 store 路径中的相同文件会被去重,并替换为硬链接
以节省空间。为了报告准确的大小,nix-du 需要扫描你的整个 store
以查找去重后的文件,这需要相当长的时间(尤其是在旋转硬盘上)。
(顺便说一句,这也是 nix-collect-garbage 耗时如此之长的原因)。
因此,默认情况下,nix-du 仅会在
活动路径中查找去重后的文件(选项 -O1)。你可以使用 -O2 获取完全精确的报告,或者使用
-O0 选择禁用去重文件检测。在后一种情况下,如果去重后的
文件出现在两个 store 路径中,它将被计算两次,并且大小将被
高估。
FAQ
这个图 究竟 是什么?
如果你既不使用 -s 也不使用 -n,那么输出图是根据你存储的参考图按如下方式派生的
- 节点集是原始节点集在关系“这两个节点被(递归地)同一组 gc-roots 引用”下的商集
- 如果两个类之间不是自环,并且在原始图中这两个类的任意元素之间存在边,则这两个类之间存在一条边
类的代表继承该类的总大小和一个任意成员的名称。大小是最重要的
信息。节点的名称有时有用,但通常也无意义。例如,我已经看到一个巨大的节点 glibc-locales 与
texlive 组件之间存在一条边,这令人惊讶,因为 glibc-locales 没有任何引用...
如果你使用 -s(仅保留大于给定大小的节点)或 -n(仅保留最大的 n 个节点)中的任何一个,则会进行近似
处理,因此结果可能不太准确(但可读性要好得多!)
nix-du 是否取代了 nix-collect-garbage?
不,nix-du 只是一个诊断工具,它不会修改任何内容。
我的存储比图的总大小还要大
仅显示活动路径。或者参见关于优化的部分。
我的存储比显示的轻得多!
这可能与存储优化有关。参见相关部分。
或者如果你使用 --root,则仅显示根的传递闭包。
我删除了一个巨大的节点,但 nix-collect-garbage 只释放了很少的空间!
如果没有 --root,这可能与优化有关。请参阅相关章节。
如果有 --root,可能发生的情况是,你删除的节点在根节点的传递闭包之外有一个引用者。请参阅上方关于 --root 的章节中的示例。
我使用 -n 60 请求了 60 个节点,但我得到了 120 个!
当你使用 -n 或 -s 应用过滤器时,所有具有被过滤器保留的(传递)子节点的根节点也会被保留。
剩余的根节点会被合并到 {filtered out} 节点中。
什么是 {transient} 节点?
这是一个合并所有内存和临时根节点的节点,用 nix 的术语来说。 内存根节点代表一个通过 mmap 映射了存储路径的进程,而临时根节点 是由 nix 构建机制为当前正在运行的构建的依赖项创建的根节点。 简而言之:此节点表示依赖于存储但会在重启后消失的活跃内容。