<img src="./etc/assets/disk_space_logo.png" width="100" height="100">
# DiskSpace
A small Elixir library with a NIF in Rust for getting disk usage statistics for a given filesystem path.
It returns information about total, used, free, and available disk space, by using native system calls. Optionally converts the results into human-readable strings with kibibytes etc. or kilobytes etc.
## Features
- Returns disk space metrics as a map with keys:
- `:total` — total size of the filesystem
- `:used` — bytes currently used
- `:free` — bytes free on the filesystem
- `:available` — bytes available to the current user (may be less than `:free` due to permissions)
- Provides both safe ([`stat/2`](https://hexdocs.pm/disk_space/DiskSpace.html#stat/2)) and bang ([`stat!/2`](https://hexdocs.pm/disk_space/DiskSpace.html#stat!/2)) functions, the latter raising [`DiskSpace.Error`](https://hexdocs.pm/disk_space/DiskSpace.Error.html) on errors
- Optional conversion of results from bytes into human-readable strings (in kB, KiB, etc.) with a keyword-list option that calls [`humanize/2`](https://hexdocs.pm/disk_space/DiskSpace.html#humanize/2)
- Supports Linux, macOS, Windows, NetBSD, FreeBSD, OpenBSD, DragonFlyBSD
## Installation
Add [`disk_space`](https://hex.pm/packages/disk_space) to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:disk_space, "~> 1.0.0"}
]
end
```
## Usage examples
```elixir
iex(1)> DiskSpace.stat!("/tmp")
%{
free: 39917449216,
total: 225035927552,
used: 185118478336,
available: 28411740160
}
iex(2)> DiskSpace.stat("/tmp")
{:ok,
%{
free: 39917436928,
total: 225035927552,
used: 185118490624,
available: 28411727872
}}
iex(3)> DiskSpace.stat("/tmp", humanize: :binary)
{:ok,
%{
free: "37.18 GiB",
total: "209.58 GiB",
used: "172.41 GiB",
available: "26.46 GiB"
}}
iex(4)> DiskSpace.stat("/tmp", humanize: :decimal)
{:ok,
%{
free: "39.92 GB",
total: "225.04 GB",
used: "185.12 GB",
available: "28.41 GB"
}}
iex(5)> DiskSpace.stat("/home/tisaak") |> DiskSpace.humanize()
{:ok,
%{
free: "37.18 GiB",
total: "209.58 GiB",
used: "172.41 GiB",
available: "26.46 GiB"
}}
iex(6)> DiskSpace.stat("/home/tisaak") |> DiskSpace.humanize(:decimal)
{:ok,
%{
free: "39.92 GB",
total: "225.04 GB",
used: "185.12 GB",
available: "28.41 GB"
}}
iex(7)> DiskSpace.stat("/yolo/swag")
{:error,
%{
info: %{errno: 2, errstr: "No such file or directory (os error 2)"},
reason: :not_directory
}}
iex(8)> DiskSpace.stat!("/yolo/swag")
** (DiskSpace.Error) DiskSpace error: %{info: %{errno: 2, errstr: "No such file or directory (os error 2)"}, reason: :not_directory}
(disk_space 1.0.0) lib/disk_space.ex:84: DiskSpace.stat!/2
iex:8: (file)
```
## Usage trick
In case you want to get results for a path that doesn't yet exist:
```elixir
def recursively_check_disk_space(local_dir) when is_binary(local_dir) do
local_dir |> Path.split() |> Enum.reduce_while(local_dir, fn _, acc ->
case DiskSpace.stat(acc) do
{:ok, info} -> {:halt, {:ok, info}}
{:error, _} ->
{:cont, acc |> Path.split() |> Enum.reverse() |> tl |> Enum.reverse() |> Path.join()}
end
end)
end
```
This is pulled from my book [**Elixir File Browsing**](https://overbring.com/books/elixir-file-browsing/), in which the API client for the undocumented REST API of [File Browser](https://filebrowser.org) uses `DiskSpace` to check whether there is enough space on the target local path's mount point before downloading a resource from the server.
## Error handling
- [`stat/2`](https://hexdocs.pm/disk_space/DiskSpace.html#stat/2) returns `{:ok, stats_map}` or `{:error, info}`, where `info` is a map with populated `:reason` (atom) and `:info` (map or `nil`) with more information, if provided by the NIF.
- [`stat!/2`](https://hexdocs.pm/disk_space/DiskSpace.html#stat!/2) returns `stats_map` or raises [`DiskSpace.Error`](https://hexdocs.pm/disk_space/DiskSpace.Error.html) with the `{:error, info}` of [`stat/2`](https://hexdocs.pm/disk_space/DiskSpace.html#stat/2) as the message.
## Supported Elixir and OTP versions
In short:
- Tested and confirmed working on Elixir 1.14 (OTP 25) to 1.18 (OTP 27)
- Tested and confirmed working on Linux, Windows and the BSDs (amd64)
- Tested and confirmed working on macOS (arm64)
- Reported as also working on Elixir 1.18.4 (OTP 28), at least on macOS/arm64
### Build & test matrix
| OS | Arch. | Elixir | OTP | Builds and `mix test` passes? |
| ------------------------------------------------------------------------- | ----- | ------ | --- | ------------------------------ |
| Linux (Ubuntu/Debian) | amd64 | 1.14 | 25 | ✅ |
| Linux (Ubuntu/Debian) | amd64 | 1.15 | 26 | ✅ |
| Linux (Ubuntu/Debian) | amd64 | 1.16 | 26 | ✅ |
| Linux (Ubuntu/Debian) | amd64 | 1.17 | 27 | ✅ |
| Linux (Ubuntu/Debian) | amd64 | 1.18 | 27 | ✅ |
| Linux (Ubuntu/Debian) | amd64 | 1.18.4 | 28 | ❔ Not tested, but should work |
| macOS | arm64 | 1.14 | 25 | ✅ |
| macOS | arm64 | 1.15 | 26 | ✅ |
| macOS | arm64 | 1.16 | 26 | ✅ |
| macOS | arm64 | 1.17 | 27 | ✅ |
| macOS | arm64 | 1.18 | 27 | ✅ |
| macOS | arm64 | 1.18.4 | 28 | ✅ reported as working |
| Windows | amd64 | 1.14 | 25 | ✅ |
| Windows | amd64 | 1.15 | 26 | ✅ |
| Windows | amd64 | 1.16 | 26 | ✅ |
| Windows | amd64 | 1.17 | 27 | ✅ |
| Windows | amd64 | 1.18 | 27 | ✅ |
| Windows | amd64 | 1.18.4 | 28 | ❔ Not tested, but should work |
| [NetBSD 10.1](https://www.netbsd.org/releases/formal-10/NetBSD-10.1.html) | amd64 | 1.17.2 | 27 | ✅ |
| [FreeBSD 14.3](https://www.freebsd.org/releases/14.3R/announce/) | amd64 | 1.17.3 | 26 | ✅ |
| [OpenBSD 7.7](https://www.openbsd.org/77.html) | amd64 | 1.18.3 | 27 | ✅ |
| [DragonFlyBSD 6.4.2](https://www.dragonflybsd.org/release64/) | amd64 | 1.16.3 | 25 | ✅ |
See also: [GitHub Actions](https://github.com/waseigo/disk_space/actions) for Linux, macOS, Windows.
## Build requirements
Generally: Erlang development headers (for `erl_nif` functions), Rust.
### Linux (amd64)
- `erlang-dev` or `erlang-erts-dev` (Erlang development headers)
- `libc` development headers (usually installed by default)
- `rustc`
Example on Debian and its derivatives:
```
sudo apt-get install elixir erlang-dev rustc
```
### macOS (arm64)
```
xcode-select --install
```
- `clang`
- Erlang installed via [Homebrew](https://brew.sh/) or other means
### NetBSD (amd64)
✅ Tested on [version 10.1](https://www.netbsd.org/releases/formal-10/NetBSD-10.1.html) with Elixir 1.17.2, OTP 27.
```
pkgin update
pkgin install erlang elixir rust
```
### FreeBSD (amd64)
✅ Tested on [version 14.3](https://www.freebsd.org/releases/14.3R/announce/) with Elixir 1.17.3, OTP 26.
```
pkg update
pkg install erlang elixir ca_root_nss rust
```
### DragonFlyBSD (amd64)
✅ Tested on [version 6.4.2](https://www.dragonflybsd.org/release64/) with Elixir 1.16.3, OTP 25.
```
pkg update
pkg install erlang elixir rust
```
### OpenBSD (amd64)
✅ Tested on [version 7.7](https://www.openbsd.org/77.html) with Elixir 1.18.3, OTP 27.
```
pkg_add erlang-27.3.3v0 elixir-1.18.3 rust
```
### Windows (amd64)
1. Install Erlang/OTP with development headers:
- Download the official installer from https://www.erlang.org/downloads (choose the latest stable version).
- During installation, ensure "Development and debugging tools" is selected (this includes headers like `erl_nif.h`).
- Add the Erlang bin directory to your `PATH` (e.g., `C:\Program Files\erl-27.0\bin`).
2. Install Elixir:
- Download the installer from https://elixir-lang.org/install.html#windows.
- Follow the instructions to add Elixir to your `PATH`.
3. Install Visual Studio Build Tools (required for Rust's MSVC toolchain):
- Download from https://visualstudio.microsoft.com/downloads/ (under "Tools for Visual Studio", select "Build Tools for Visual Studio").
- Run the installer and select the "C++ build tools" workload (includes MSVC compiler and linker).
- No full Visual Studio IDE is needed—just the build tools.
4. Install Rust:
- Download `rustup-init.exe` from https://www.rust-lang.org/tools/install.
- Run it and select the default options, which install the stable MSVC toolchain (`stable-x86_64-pc-windows-msvc`).
- Add Rust to your `PATH` if prompted (`cargo` and `rustc` should be accessible from the command line).
## Alternatives
Add [`:os_mon`](https://erlang.org/documentation/doc-16-rc2/lib/os_mon-2.11/doc/html/os_mon_app.html) in `:extra_applications` in `mix.exs`, then use [`get_disk_info/1`](https://erlang.org/documentation/doc-16-rc2/lib/os_mon-2.11/doc/html/disksup.html#get_disk_info/1) of [`disksup`](https://erlang.org/documentation/doc-16-rc2/lib/os_mon-2.11/doc/html/disksup.html) service.
### Comparison to alternatives
| Criterion | `:disksup.get_disk_info/1` | `disk_space.stat/2` and `stat!/2` |
| ------------------------------------ | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| What it is | Function of a supervised process (`:os_mon`'s `disksup`) | Function relying on a NIF |
| Runtime requirements | `:os_mon` in `:extra_applications` in `mix.exs` | None |
| Compile-time requirements | No, part of Erlang/OTP | Yes (Rust) |
| Returns | Total space, available space, and capacity (% of disk space used) | Returns total, used, free, available space |
| Return value type | 4-element tuple in list; first element: path as charlist; other elements: integers | 2-element tagged tuple; first element: `:ok` or `:error`; second element: map with atom keys and integer (bytes) or string (kB, KiB, etc.) values if `:ok`, map with `:reason` and OS `:info` if `:error` |
| Return units | kibibytes, percentage (as integers) | bytes (integers) or human-readable strings |
| Optional conversion to KiB, kB, etc. | No | Yes (through `humanize/2`) |
| Works with UNCs on Windows? | Probably not (_"On WIN32 - All logical drives of type "FIXED_DISK" are checked."_) | Should work (not tested / cannot test) |
| Well tested? | Yes | Yes, according to [GitHub Actions](https://github.com/waseigo/disk_space/actions) |
## Use of GenAI
The following files were incrementally generated/adapted by xAI's Grok 4 model over multiple rounds of prompting for reviews and improvements that were suggested by Grok 4, GPT-5 and Gemini 2.5 Pro, and according to the warnings/errors of the GitHub Actions workflow across Linux, macOS, and Windows:
- [`lib.rs`](https://github.com/waseigo/disk_space/blob/main/native/diskspace/src/lib.rs)
- [`Cargo.toml`](https://github.com/waseigo/disk_space/blob/main/native/diskspace/Cargo.toml)
- [`build.yml`](https://github.com/waseigo/disk_space/blob/main/.github/workflows/build.yml)
## License
Apache-2.0
## Documentation
For more details, see the documentation at https://hexdocs.pm/disk_space.
## Related
- [DiskSpace - retrieve disk usage statistics for a given filesystem path](https://elixirforum.com/t/diskspace-retrieve-disk-usage-statistics-for-a-given-filesystem-path/72064) -- thread on ElixirForum
- [I let LLMs write an Elixir NIF in C; it mostly worked](https://overbring.com/blog/2025-08-13-writing-an-elixir-nif-with-genai/) -- blog post related to versions up to 0.4.0 that relied on C code generated by Grok 4 with code reviews by GPT-5 and Gemini 2.5 Flash
- [Comments on HackerNews](https://news.ycombinator.com/item?id=44914040) that spurred me to re-vibe-code it in Rust with Grok 4, as running [`splint`](https://splint.org) on the original C code revealed memory-safety issues