mirror of
https://github.com/NotAShelf/microfetch.git
synced 2025-12-09 06:23:52 +00:00
Compare commits
18 commits
9d8905354e
...
b24e720dd8
| Author | SHA1 | Date | |
|---|---|---|---|
|
b24e720dd8 |
|||
|
ca76a6e1bd |
|||
|
00159d6454 |
|||
|
11a726428b |
|||
|
789ece866b |
|||
|
f4f3385ff7 |
|||
|
2ad765ef98 |
|||
|
325ec69024 |
|||
|
2a6fe2a3f1 |
|||
|
9bd4c9a70a |
|||
|
af8031f9ec |
|||
|
4c22cf5d2a |
|||
|
d438800738 |
|||
|
1d69d3107c |
|||
|
dab8f556af |
|||
|
6150e55ba5 |
|||
|
8800b69ef3 |
|||
|
e355ddc517 |
17 changed files with 1781 additions and 334 deletions
57
.github/workflows/hotpath-comment.yml
vendored
Normal file
57
.github/workflows/hotpath-comment.yml
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
name: Hotpath Comment
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Hotpath Profile"]
|
||||
types:
|
||||
- completed
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
comment:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
|
||||
steps:
|
||||
- name: Download profiling results
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: hotpath-results
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
|
||||
- name: Read PR number
|
||||
id: pr
|
||||
run: echo "number=$(cat pr_number.txt)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Setup Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
||||
- name: Install hotpath CLI
|
||||
run: cargo install hotpath
|
||||
|
||||
- name: Post timing comparison comment
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
hotpath profile-pr \
|
||||
--repo ${{ github.repository }} \
|
||||
--pr-number ${{ steps.pr.outputs.number }} \
|
||||
--head-json head-timing.json \
|
||||
--base-json base-timing.json \
|
||||
--mode timing \
|
||||
--title "⏱️ Hotpath Timing Profile"
|
||||
|
||||
- name: Post allocation comparison comment
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
hotpath profile-pr \
|
||||
--repo ${{ github.repository }} \
|
||||
--pr-number ${{ steps.pr.outputs.number }} \
|
||||
--head-json head-alloc.json \
|
||||
--base-json base-alloc.json \
|
||||
--mode alloc \
|
||||
--title "📊 Hotpath Allocation Profile"
|
||||
63
.github/workflows/hotpath-profile.yml
vendored
Normal file
63
.github/workflows/hotpath-profile.yml
vendored
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
name: Hotpath Profile
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
profile:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout PR HEAD
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
||||
- name: Run timing profiling on HEAD
|
||||
env:
|
||||
HOTPATH_JSON: "true"
|
||||
run: |
|
||||
cargo run --features='hotpath' 2>&1 | grep '^{"hotpath_profiling_mode"' > head-timing.json
|
||||
|
||||
- name: Run allocation profiling on HEAD
|
||||
env:
|
||||
HOTPATH_JSON: "true"
|
||||
run: |
|
||||
cargo run --features='hotpath,hotpath-alloc-count-total' 2>&1 | grep '^{"hotpath_profiling_mode"' > head-alloc.json
|
||||
|
||||
- name: Checkout base branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.base.sha }}
|
||||
|
||||
- name: Run timing profiling on base
|
||||
env:
|
||||
HOTPATH_JSON: "true"
|
||||
run: |
|
||||
cargo run --features='hotpath' 2>&1 | grep '^{"hotpath_profiling_mode"' > base-timing.json
|
||||
|
||||
- name: Run allocation profiling on base
|
||||
env:
|
||||
HOTPATH_JSON: "true"
|
||||
run: |
|
||||
cargo run --features='hotpath,hotpath-alloc-count-total' 2>&1 | grep '^{"hotpath_profiling_mode"' > base-alloc.json
|
||||
|
||||
- name: Save PR number
|
||||
run: echo "${{ github.event.number }}" > pr_number.txt
|
||||
|
||||
- name: Upload profiling results
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: hotpath-results
|
||||
path: |
|
||||
head-timing.json
|
||||
head-alloc.json
|
||||
base-timing.json
|
||||
base-alloc.json
|
||||
pr_number.txt
|
||||
retention-days: 1
|
||||
26
.rustfmt.toml
Normal file
26
.rustfmt.toml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
condense_wildcard_suffixes = true
|
||||
doc_comment_code_block_width = 80
|
||||
edition = "2024" # Keep in sync with Cargo.toml.
|
||||
enum_discrim_align_threshold = 60
|
||||
force_explicit_abi = false
|
||||
force_multiline_blocks = true
|
||||
format_code_in_doc_comments = true
|
||||
format_macro_matchers = true
|
||||
format_strings = true
|
||||
group_imports = "StdExternalCrate"
|
||||
hex_literal_case = "Upper"
|
||||
imports_granularity = "Crate"
|
||||
imports_layout = "HorizontalVertical"
|
||||
inline_attribute_width = 60
|
||||
match_block_trailing_comma = true
|
||||
max_width = 80
|
||||
newline_style = "Unix"
|
||||
normalize_comments = true
|
||||
normalize_doc_attributes = true
|
||||
overflow_delimited_expr = true
|
||||
struct_field_align_threshold = 60
|
||||
tab_spaces = 2
|
||||
unstable_features = true
|
||||
use_field_init_shorthand = true
|
||||
use_try_shorthand = true
|
||||
wrap_comments = true
|
||||
13
.taplo.toml
Normal file
13
.taplo.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[formatting]
|
||||
align_entries = true
|
||||
column_width = 100
|
||||
compact_arrays = false
|
||||
reorder_inline_tables = true
|
||||
reorder_keys = true
|
||||
|
||||
[[rule]]
|
||||
include = [ "**/Cargo.toml" ]
|
||||
keys = [ "package" ]
|
||||
|
||||
[rule.formatting]
|
||||
reorder_keys = false
|
||||
1069
Cargo.lock
generated
1069
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
57
Cargo.toml
57
Cargo.toml
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "microfetch"
|
||||
version = "0.4.9"
|
||||
name = "microfetch"
|
||||
version = "0.4.11"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
|
|
@ -12,28 +12,59 @@ name = "microfetch"
|
|||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
nix = { version = "0.30", features = ["fs", "hostname", "feature"] }
|
||||
libc = "0.2"
|
||||
hotpath = { optional = true, version = "0.6.0" }
|
||||
libc = "0.2.177"
|
||||
nix = { default-features = false, features = [ "fs", "hostname", "feature" ], version = "0.30.1" }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.7"
|
||||
|
||||
[features]
|
||||
hotpath = [ "dep:hotpath", "hotpath/hotpath" ]
|
||||
hotpath-alloc-bytes-total = [ "hotpath/hotpath-alloc-bytes-total" ]
|
||||
hotpath-alloc-count-total = [ "hotpath/hotpath-alloc-count-total" ]
|
||||
hotpath-off = [ "hotpath/hotpath-off" ]
|
||||
|
||||
[[bench]]
|
||||
name = "benchmark"
|
||||
harness = false
|
||||
name = "benchmark"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 3
|
||||
opt-level = 1
|
||||
|
||||
[profile.release]
|
||||
strip = true
|
||||
opt-level = "s"
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
lto = true
|
||||
opt-level = "s"
|
||||
panic = "abort"
|
||||
strip = true
|
||||
|
||||
[profile.profiler]
|
||||
inherits = "release"
|
||||
debug = true
|
||||
debug = true
|
||||
inherits = "release"
|
||||
split-debuginfo = "unpacked"
|
||||
strip = "none"
|
||||
strip = "none"
|
||||
|
||||
[lints.clippy]
|
||||
complexity = { level = "warn", priority = -1 }
|
||||
nursery = { level = "warn", priority = -1 }
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
perf = { level = "warn", priority = -1 }
|
||||
style = { level = "warn", priority = -1 }
|
||||
|
||||
# The lint groups above enable some less-than-desirable rules, we should manually
|
||||
# enable those to keep our sanity.
|
||||
absolute_paths = "allow"
|
||||
arbitrary_source_item_ordering = "allow"
|
||||
implicit_return = "allow"
|
||||
missing_docs_in_private_items = "allow"
|
||||
non_ascii_literal = "allow"
|
||||
pattern_type_mismatch = "allow"
|
||||
print_stdout = "allow"
|
||||
question_mark_used = "allow"
|
||||
similar_names = "allow"
|
||||
single_call_fn = "allow"
|
||||
std_instead_of_core = "allow"
|
||||
too_long_first_doc_paragraph = "allow"
|
||||
too_many_lines = "allow"
|
||||
unused_trait_names = "allow"
|
||||
|
|
|
|||
141
README.md
141
README.md
|
|
@ -1,17 +1,34 @@
|
|||
<!-- markdownlint-disable MD013 MD033 MD041 -->
|
||||
|
||||
<div align="center">
|
||||
<img src="https://deps.rs/repo/github/notashelf/microfetch/status.svg" alt="https://deps.rs/repo/github/notashelf/microfetch">
|
||||
<!-- <img src="https://img.shields.io/github/v/release/notashelf/microfetch?display_name=tag&color=DEA584"> -->
|
||||
<img src="https://img.shields.io/github/stars/notashelf/microfetch?label=stars&color=DEA584">
|
||||
<img src="https://img.shields.io/github/stars/notashelf/microfetch?label=stars&color=DEA584" alt="stars">
|
||||
</div>
|
||||
|
||||
<h1 align="center">Microfetch</h1>
|
||||
<div id="doc-begin" align="center">
|
||||
<h1 id="header">
|
||||
Microfetch
|
||||
</h1>
|
||||
<p>
|
||||
Microscopic fetch tool in Rust, for NixOS systems, with special emphasis on speed
|
||||
</p>
|
||||
<br/>
|
||||
<a href="#synopsis">Synopsis</a><br/>
|
||||
<a href="#features">Features</a> | <a href="#motivation">Motivation</a><br/>
|
||||
<a href="#installation">Installation</a>
|
||||
<br/>
|
||||
</div>
|
||||
|
||||
Stupidly simple, laughably fast fetch tool. Written in Rust for speed and ease
|
||||
of maintainability. Runs in a _fraction of a millisecond_ and displays _most_ of
|
||||
the nonsense you'd see posted on r/unixporn or other internet communities. Aims
|
||||
to replace [fastfetch](https://github.com/fastfetch-cli/fastfetch) on my
|
||||
personal system, but [probably not yours](#customizing). Though, you are more
|
||||
than welcome to use it on your system: it's pretty [fast](#benchmarks)...
|
||||
## Synopsis
|
||||
|
||||
[fastfetch]: https://github.com/fastfetch-cli/fastfetch
|
||||
|
||||
Stupidly small and simple, laughably fast and pretty fetch tool. Written in Rust
|
||||
for speed and ease of maintainability. Runs in a _fraction of a millisecond_ and
|
||||
displays _most_ of the nonsense you'd see posted on r/unixporn or other internet
|
||||
communities. Aims to replace [fastfetch] on my personal system, but
|
||||
[probably not yours](#customizing). Though, you are more than welcome to use it
|
||||
on your system: it is pretty _[fast](#benchmarks)_...
|
||||
|
||||
<p align="center">
|
||||
<img
|
||||
|
|
@ -26,6 +43,7 @@ than welcome to use it on your system: it's pretty [fast](#benchmarks)...
|
|||
- Fast
|
||||
- Really fast
|
||||
- Minimal dependencies
|
||||
- Tiny binary (~410kb)
|
||||
- Actually really fast
|
||||
- Cool NixOS logo (other, inferior, distros are not supported)
|
||||
- Reliable detection of following info:
|
||||
|
|
@ -44,39 +62,64 @@ than welcome to use it on your system: it's pretty [fast](#benchmarks)...
|
|||
|
||||
## Motivation
|
||||
|
||||
Fastfetch, as its name indicates, a very fast fetch tool written in C, however,
|
||||
I am not interested in any of its additional features, such as customization,
|
||||
and I very much dislike the defaults. Microfetch is my response to this problem,
|
||||
a _very fast_ fetch tool that you would normally write in Bash and put in your
|
||||
`~/.bashrc` but actually _really_ fast because it opts-out of all customization
|
||||
options provided by Fastfetch, and is written in Rust. Why? Because I can, and
|
||||
because I prefer Rust for "structured" Bash scripts.
|
||||
Fastfetch, as its name probably hinted, is a very fast fetch tool written in C.
|
||||
However, I am not interested in _any_ of its additional features, and I'm not
|
||||
interested in its configuration options. Sure I can _configure_ it when I
|
||||
dislike the defaults, but how often would I really change the configuration...
|
||||
|
||||
I cannot re-iterate it enough, Microfetch is _annoyingly fast_.
|
||||
Microfetch is my response to this problem. It is an _even faster_ fetch tool
|
||||
that I would've written in Bash and put in my `~/.bashrc` but is _actually_
|
||||
incredibly fast because it opts out of all the customization options provided by
|
||||
tools such as Fastfetch. Ultimately, it's a small, opinionated binary with a
|
||||
nice size that doesn't bother me, and incredible speed. Customization? No thank
|
||||
you. I cannot re-iterate it enough, Microfetch is _annoyingly fast_.
|
||||
|
||||
The project is written in Rust, which comes at the cost of "bloated" dependency
|
||||
trees and the increased build times, but we make an extended effort to keep the
|
||||
dependencies minimal and build times managable. The latter is also very easily
|
||||
mitigated with Nix's binary cache systems. Since Microfetch is already in
|
||||
Nixpkgs, you are recommended to use it to utilize the binary cache properly. The
|
||||
usage of Rust _is_ nice, however, since it provides us with incredible tooling
|
||||
and a very powerful language that allows for Microfetch to be as fast as
|
||||
possible. Sure C could've been used here as well, but do you think I hate
|
||||
myself? [^1]
|
||||
|
||||
[^1]: Okay, maybe a little bit. One of the future goals of Microfetch is to
|
||||
defer to inline Assembly for the costliest functions, but that's for a
|
||||
future date and until I do that I can pretend to be sane.
|
||||
|
||||
## Benchmarks
|
||||
|
||||
The performance may be sometimes influenced by hardware-specific race
|
||||
conditions, or even your kernel configuration meaning it may (at times) depend
|
||||
on your hardware. However, the overall trend appears to be less than 1.3ms on
|
||||
any modern (2015 and after) CPU that I own. Below are the benchmarks with
|
||||
Hyperfine on my desktop system. Please note that those benchmarks will not be
|
||||
always kept up to date, but I will try to update the numbers as I make
|
||||
Microfetch faster.
|
||||
Below are the benchmarks that I've used to back up my claims of Microfetch's
|
||||
speed. It is fast, it is _very_ fast and that is the point of its existence. It
|
||||
_could_ be faster, and it will be. Eventually.
|
||||
|
||||
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative | Written by raf? |
|
||||
| :----------- | -----------: | -------: | -------: | -------------: | --------------: |
|
||||
| `microfetch` | 1.0 ± 0.1 | 0.9 | 1.7 | 1.00 | yes |
|
||||
| `fastfetch` | 48.6 ± 1.6 | 45.8 | 61.3 | 46.65 ± 4.75 | no |
|
||||
| `pfetch` | 206.0 ± 4.5 | 198.0 | 241.4 | 197.50 ± 19.53 | no |
|
||||
| `neofetch` | 689.1 ± 29.1 | 637.7 | 811.2 | 660.79 ± 69.56 | no |
|
||||
At this point in time, the performance may be sometimes influenced by
|
||||
hardware-specific race conditions or even your kernel configuration. Which means
|
||||
that Microfetch's speed may (at times) depend on your hardware setup. However,
|
||||
even with the worst possible hardware I could find in my house, I've achieved a
|
||||
nice less-than-1ms invocation time. Which is pretty good. While Microfetch
|
||||
_could_ be made faster, we're in the territory of environmental bottlenecks
|
||||
given how little Microfetch actually allocates.
|
||||
|
||||
As far as I'm concerned, Microfetch is significantly faster than every other
|
||||
fetch tool that I have tried. The only downsides of using Rust for the project
|
||||
(in exchange for speed and maintainability) is the slightly "bloated" dependency
|
||||
trees, and the increased build times. The latter is very easily mitigated with
|
||||
Nix's binary cache. Since Microfetch is already in Nixpkgs, you are recommended
|
||||
to use it to utilize the binary cache properly
|
||||
Below are the actual benchmarks with Hyperfine measured on my Desktop system.
|
||||
The benchmarks were performed under medium system load, and may not be the same
|
||||
on your system. Please _also_ note that those benchmarks will not be always kept
|
||||
up to date, but I will try to update the numbers as I make Microfetch faster.
|
||||
|
||||
| Command | Mean [µs] | Min [µs] | Max [µs] | Relative | Written by raf? |
|
||||
| :----------- | ----------------: | -------: | -------: | -------------: | --------------: |
|
||||
| `microfetch` | 604.4 ± 64.2 | 516.0 | 1184.6 | 1.00 | Yes |
|
||||
| `fastfetch` | 140836.6 ± 1258.6 | 139204.7 | 143299.4 | 233.00 ± 24.82 | No |
|
||||
| `pfetch` | 177036.6 ± 1614.3 | 174199.3 | 180830.2 | 292.89 ± 31.20 | No |
|
||||
| `neofetch` | 406309.9 ± 1810.0 | 402757.3 | 409526.3 | 672.20 ± 71.40 | No |
|
||||
| `nitch` | 127743.7 ± 1391.7 | 123933.5 | 130451.2 | 211.34 ± 22.55 | No |
|
||||
| `macchina` | 13603.7 ± 339.7 | 12642.9 | 14701.4 | 22.51 ± 2.45 | No |
|
||||
|
||||
The point stands that Microfetch is significantly faster than every other fetch
|
||||
tool I have tried. This is to be expected, of course, since Microfetch is
|
||||
designed _explicitly_ for speed and makes some tradeoffs to achieve it's
|
||||
signature speed.
|
||||
|
||||
### Benchmarking Individual Functions
|
||||
|
||||
|
|
@ -87,6 +130,32 @@ To benchmark individual functions, [Criterion.rs] is used. See Criterion's
|
|||
[Getting Started Guide] for details or just run `cargo bench` to benchmark all
|
||||
features of Microfetch.
|
||||
|
||||
### Profiling Allocations and Timing
|
||||
|
||||
[Hotpath]: https://github.com/pawurb/hotpath
|
||||
|
||||
Microfetch uses [Hotpath] for profiling function execution timing and heap
|
||||
allocations. This helps identify performance bottlenecks and track optimization
|
||||
progress. It is so effective that thanks to Hotpath, Microfetch has seen a 60%
|
||||
reduction in the number of allocations.
|
||||
|
||||
To profile timing:
|
||||
|
||||
```bash
|
||||
HOTPATH_JSON=true cargo run --features=hotpath
|
||||
```
|
||||
|
||||
To profile allocations:
|
||||
|
||||
```bash
|
||||
HOTPATH_JSON=true cargo run --features=hotpath,hotpath-alloc-count-total
|
||||
```
|
||||
|
||||
The JSON output can be analyzed with the `hotpath` CLI tool for detailed
|
||||
performance metrics. On pull requests, GitHub Actions automatically profiles
|
||||
both timing and allocations, posting comparison comments to help catch
|
||||
performance regressions.
|
||||
|
||||
## Installation
|
||||
|
||||
> [!NOTE]
|
||||
|
|
|
|||
|
|
@ -1,26 +1,31 @@
|
|||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use microfetch_lib::colors::print_dots;
|
||||
use microfetch_lib::desktop::get_desktop_info;
|
||||
use microfetch_lib::release::{get_os_pretty_name, get_system_info};
|
||||
use microfetch_lib::system::{
|
||||
get_memory_usage, get_root_disk_usage, get_shell, get_username_and_hostname,
|
||||
use microfetch_lib::{
|
||||
colors::print_dots,
|
||||
desktop::get_desktop_info,
|
||||
release::{get_os_pretty_name, get_system_info},
|
||||
system::{
|
||||
get_memory_usage,
|
||||
get_root_disk_usage,
|
||||
get_shell,
|
||||
get_username_and_hostname,
|
||||
},
|
||||
uptime::get_current,
|
||||
};
|
||||
use microfetch_lib::uptime::get_current;
|
||||
|
||||
fn main_benchmark(c: &mut Criterion) {
|
||||
let utsname = nix::sys::utsname::uname().expect("lol");
|
||||
c.bench_function("user_info", |b| {
|
||||
b.iter(|| get_username_and_hostname(&utsname));
|
||||
});
|
||||
c.bench_function("os_name", |b| b.iter(get_os_pretty_name));
|
||||
c.bench_function("kernel_version", |b| b.iter(|| get_system_info(&utsname)));
|
||||
c.bench_function("shell", |b| b.iter(get_shell));
|
||||
let utsname = nix::sys::utsname::uname().expect("lol");
|
||||
c.bench_function("user_info", |b| {
|
||||
b.iter(|| get_username_and_hostname(&utsname));
|
||||
});
|
||||
c.bench_function("os_name", |b| b.iter(get_os_pretty_name));
|
||||
c.bench_function("kernel_version", |b| b.iter(|| get_system_info(&utsname)));
|
||||
c.bench_function("shell", |b| b.iter(get_shell));
|
||||
|
||||
c.bench_function("desktop", |b| b.iter(get_desktop_info));
|
||||
c.bench_function("uptime", |b| b.iter(get_current));
|
||||
c.bench_function("memory_usage", |b| b.iter(get_memory_usage));
|
||||
c.bench_function("storage", |b| b.iter(get_root_disk_usage));
|
||||
c.bench_function("colors", |b| b.iter(print_dots));
|
||||
c.bench_function("desktop", |b| b.iter(get_desktop_info));
|
||||
c.bench_function("uptime", |b| b.iter(get_current));
|
||||
c.bench_function("memory_usage", |b| b.iter(get_memory_usage));
|
||||
c.bench_function("storage", |b| b.iter(get_root_disk_usage));
|
||||
c.bench_function("colors", |b| b.iter(print_dots));
|
||||
}
|
||||
|
||||
criterion_group!(benches, main_benchmark);
|
||||
|
|
|
|||
9
flake.lock
generated
9
flake.lock
generated
|
|
@ -2,11 +2,14 @@
|
|||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1754214453,
|
||||
"narHash": "sha256-Q/I2xJn/j1wpkGhWkQnm20nShYnG7TI99foDBpXm1SY=",
|
||||
"lastModified": 1743359643,
|
||||
"narHash": "sha256-RkyJ9a67s0zEIz4O66TyZOIGh4TFZ4dKHKMgnxZCh2I=",
|
||||
"lastModified": 1763381801,
|
||||
"narHash": "sha256-325fR0JmHW7B74/gHPv/S9w1Rfj/M2HniwQFUwdrZ9k=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "5b09dc45f24cf32316283e62aec81ffee3c3e376",
|
||||
"rev": "ca77b4bc80e558ce59f2712fdb276f90c0ee309a",
|
||||
"rev": "46931757ea8bdbba25c076697f8e73b8dc39fef5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
|||
|
|
@ -9,23 +9,29 @@
|
|||
inherit (toml) version;
|
||||
in
|
||||
rustPlatform.buildRustPackage.override {stdenv = stdenvAdapters.useMoldLinker llvm.stdenv;} {
|
||||
RUSTFLAGS = "-C link-arg=-fuse-ld=mold";
|
||||
|
||||
inherit pname version;
|
||||
|
||||
src = builtins.path {
|
||||
name = "${pname}-${version}";
|
||||
path = lib.sources.cleanSource ../.;
|
||||
};
|
||||
src = let
|
||||
fs = lib.fileset;
|
||||
s = ../.;
|
||||
in
|
||||
fs.toSource {
|
||||
root = s;
|
||||
fileset = fs.unions [
|
||||
(fs.fileFilter (file: builtins.any file.hasExt ["rs"]) (s + /src))
|
||||
(s + /Cargo.lock)
|
||||
(s + /Cargo.toml)
|
||||
];
|
||||
};
|
||||
|
||||
cargoLock.lockFile = ../Cargo.lock;
|
||||
enableParallelBuilding = true;
|
||||
env.RUSTFLAGS = "-C link-arg=-fuse-ld=mold";
|
||||
|
||||
meta = {
|
||||
description = "A microscopic fetch script in Rust, for NixOS systems";
|
||||
description = "Microscopic fetch script in Rust, for NixOS systems";
|
||||
homepage = "https://github.com/NotAShelf/microfetch";
|
||||
license = lib.licenses.gpl3Only;
|
||||
maintainers = with lib.maintainers; [NotAShelf];
|
||||
maintainers = [lib.maintainers.NotAShelf];
|
||||
mainProgram = "microfetch";
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
rustfmt,
|
||||
clippy,
|
||||
cargo,
|
||||
taplo,
|
||||
rustc,
|
||||
rustPlatform,
|
||||
gnuplot,
|
||||
|
|
@ -16,8 +17,9 @@ mkShell {
|
|||
rustc
|
||||
|
||||
rust-analyzer-unwrapped
|
||||
rustfmt
|
||||
(rustfmt.override {asNightly = true;})
|
||||
clippy
|
||||
taplo
|
||||
|
||||
gnuplot # For Criterion.rs plots
|
||||
];
|
||||
|
|
|
|||
112
src/colors.rs
112
src/colors.rs
|
|
@ -1,57 +1,81 @@
|
|||
use std::env;
|
||||
use std::sync::LazyLock;
|
||||
use std::{env, sync::LazyLock};
|
||||
|
||||
pub struct Colors {
|
||||
pub reset: &'static str,
|
||||
pub blue: &'static str,
|
||||
pub cyan: &'static str,
|
||||
pub green: &'static str,
|
||||
pub yellow: &'static str,
|
||||
pub red: &'static str,
|
||||
pub magenta: &'static str,
|
||||
pub reset: &'static str,
|
||||
pub blue: &'static str,
|
||||
pub cyan: &'static str,
|
||||
pub green: &'static str,
|
||||
pub yellow: &'static str,
|
||||
pub red: &'static str,
|
||||
pub magenta: &'static str,
|
||||
}
|
||||
|
||||
impl Colors {
|
||||
const fn new(is_no_color: bool) -> Self {
|
||||
if is_no_color {
|
||||
Self {
|
||||
reset: "",
|
||||
blue: "",
|
||||
cyan: "",
|
||||
green: "",
|
||||
yellow: "",
|
||||
red: "",
|
||||
magenta: "",
|
||||
}
|
||||
} else {
|
||||
Self {
|
||||
reset: "\x1b[0m",
|
||||
blue: "\x1b[34m",
|
||||
cyan: "\x1b[36m",
|
||||
green: "\x1b[32m",
|
||||
yellow: "\x1b[33m",
|
||||
red: "\x1b[31m",
|
||||
magenta: "\x1b[35m",
|
||||
}
|
||||
}
|
||||
const fn new(is_no_color: bool) -> Self {
|
||||
if is_no_color {
|
||||
Self {
|
||||
reset: "",
|
||||
blue: "",
|
||||
cyan: "",
|
||||
green: "",
|
||||
yellow: "",
|
||||
red: "",
|
||||
magenta: "",
|
||||
}
|
||||
} else {
|
||||
Self {
|
||||
reset: "\x1b[0m",
|
||||
blue: "\x1b[34m",
|
||||
cyan: "\x1b[36m",
|
||||
green: "\x1b[32m",
|
||||
yellow: "\x1b[33m",
|
||||
red: "\x1b[31m",
|
||||
magenta: "\x1b[35m",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub static COLORS: LazyLock<Colors> = LazyLock::new(|| {
|
||||
// check for NO_COLOR once at startup
|
||||
let is_no_color = env::var("NO_COLOR").is_ok();
|
||||
Colors::new(is_no_color)
|
||||
// Check for NO_COLOR once at startup
|
||||
let is_no_color = env::var("NO_COLOR").is_ok();
|
||||
Colors::new(is_no_color)
|
||||
});
|
||||
|
||||
#[must_use]
|
||||
#[cfg_attr(feature = "hotpath", hotpath::measure)]
|
||||
pub fn print_dots() -> String {
|
||||
format!(
|
||||
"{} {} {} {} {} {} {}",
|
||||
COLORS.blue,
|
||||
COLORS.cyan,
|
||||
COLORS.green,
|
||||
COLORS.yellow,
|
||||
COLORS.red,
|
||||
COLORS.magenta,
|
||||
COLORS.reset,
|
||||
)
|
||||
// Pre-calculate capacity: 6 color codes + " " (glyph + 2 spaces) per color
|
||||
const GLYPH: &str = "";
|
||||
let capacity = COLORS.blue.len()
|
||||
+ COLORS.cyan.len()
|
||||
+ COLORS.green.len()
|
||||
+ COLORS.yellow.len()
|
||||
+ COLORS.red.len()
|
||||
+ COLORS.magenta.len()
|
||||
+ COLORS.reset.len()
|
||||
+ (GLYPH.len() + 2) * 6;
|
||||
|
||||
let mut result = String::with_capacity(capacity);
|
||||
result.push_str(COLORS.blue);
|
||||
result.push_str(GLYPH);
|
||||
result.push_str(" ");
|
||||
result.push_str(COLORS.cyan);
|
||||
result.push_str(GLYPH);
|
||||
result.push_str(" ");
|
||||
result.push_str(COLORS.green);
|
||||
result.push_str(GLYPH);
|
||||
result.push_str(" ");
|
||||
result.push_str(COLORS.yellow);
|
||||
result.push_str(GLYPH);
|
||||
result.push_str(" ");
|
||||
result.push_str(COLORS.red);
|
||||
result.push_str(GLYPH);
|
||||
result.push_str(" ");
|
||||
result.push_str(COLORS.magenta);
|
||||
result.push_str(GLYPH);
|
||||
result.push_str(" ");
|
||||
result.push_str(COLORS.reset);
|
||||
|
||||
result
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,37 @@
|
|||
use std::fmt::Write;
|
||||
|
||||
#[must_use]
|
||||
#[cfg_attr(feature = "hotpath", hotpath::measure)]
|
||||
pub fn get_desktop_info() -> String {
|
||||
// Retrieve the environment variables and handle Result types
|
||||
let desktop_env = std::env::var("XDG_CURRENT_DESKTOP");
|
||||
let display_backend_result = std::env::var("XDG_SESSION_TYPE");
|
||||
// Retrieve the environment variables and handle Result types
|
||||
let desktop_env = std::env::var("XDG_CURRENT_DESKTOP");
|
||||
let display_backend = std::env::var("XDG_SESSION_TYPE");
|
||||
|
||||
// Capitalize the first letter of the display backend value
|
||||
let mut display_backend = display_backend_result.unwrap_or_default();
|
||||
if let Some(c) = display_backend.as_mut_str().get_mut(0..1) {
|
||||
c.make_ascii_uppercase();
|
||||
}
|
||||
let desktop_str = match desktop_env {
|
||||
Err(_) => "Unknown",
|
||||
Ok(ref s) if s.starts_with("none+") => &s[5..],
|
||||
Ok(ref s) => s.as_str(),
|
||||
};
|
||||
|
||||
// Trim "none+" from the start of desktop_env if present
|
||||
// Use "Unknown" if desktop_env is empty or has an error
|
||||
let desktop_env = match desktop_env {
|
||||
Err(_) => "Unknown".to_string(),
|
||||
Ok(s) => s.trim_start_matches("none+").to_string(),
|
||||
};
|
||||
let backend_str = match display_backend {
|
||||
Err(_) => "Unknown",
|
||||
Ok(ref s) if s.is_empty() => "Unknown",
|
||||
Ok(ref s) => s.as_str(),
|
||||
};
|
||||
|
||||
// Handle the case where display_backend might be empty after capitalization
|
||||
let display_backend = if display_backend.is_empty() {
|
||||
"Unknown"
|
||||
} else {
|
||||
&display_backend
|
||||
}
|
||||
.to_string();
|
||||
// Pre-calculate capacity: desktop_len + " (" + backend_len + ")"
|
||||
// Capitalize first char needs temporary allocation only if backend exists
|
||||
let mut result =
|
||||
String::with_capacity(desktop_str.len() + backend_str.len() + 3);
|
||||
result.push_str(desktop_str);
|
||||
result.push_str(" (");
|
||||
|
||||
format!("{desktop_env} ({display_backend})")
|
||||
// Capitalize first character of backend
|
||||
if let Some(first_char) = backend_str.chars().next() {
|
||||
let _ = write!(result, "{}", first_char.to_ascii_uppercase());
|
||||
result.push_str(&backend_str[first_char.len_utf8()..]);
|
||||
}
|
||||
|
||||
result.push(')');
|
||||
result
|
||||
}
|
||||
|
|
|
|||
132
src/main.rs
132
src/main.rs
|
|
@ -4,78 +4,90 @@ mod release;
|
|||
mod system;
|
||||
mod uptime;
|
||||
|
||||
use crate::colors::print_dots;
|
||||
use crate::desktop::get_desktop_info;
|
||||
use crate::release::{get_os_pretty_name, get_system_info};
|
||||
use crate::system::{get_memory_usage, get_root_disk_usage, get_shell, get_username_and_hostname};
|
||||
use crate::uptime::get_current;
|
||||
use std::io::{Write, stdout};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
if Some("--version") == std::env::args().nth(1).as_deref() {
|
||||
println!("Microfetch {}", env!("CARGO_PKG_VERSION"));
|
||||
} else {
|
||||
let utsname = nix::sys::utsname::uname()?;
|
||||
let fields = Fields {
|
||||
user_info: get_username_and_hostname(&utsname),
|
||||
os_name: get_os_pretty_name()?,
|
||||
kernel_version: get_system_info(&utsname),
|
||||
shell: get_shell(),
|
||||
desktop: get_desktop_info(),
|
||||
uptime: get_current()?,
|
||||
memory_usage: get_memory_usage()?,
|
||||
storage: get_root_disk_usage()?,
|
||||
colors: print_dots(),
|
||||
};
|
||||
print_system_info(&fields)?;
|
||||
}
|
||||
use crate::{
|
||||
colors::print_dots,
|
||||
desktop::get_desktop_info,
|
||||
release::{get_os_pretty_name, get_system_info},
|
||||
system::{
|
||||
get_memory_usage,
|
||||
get_root_disk_usage,
|
||||
get_shell,
|
||||
get_username_and_hostname,
|
||||
},
|
||||
uptime::get_current,
|
||||
};
|
||||
|
||||
Ok(())
|
||||
#[cfg_attr(feature = "hotpath", hotpath::main)]
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
if Some("--version") == std::env::args().nth(1).as_deref() {
|
||||
println!("Microfetch {}", env!("CARGO_PKG_VERSION"));
|
||||
} else {
|
||||
let utsname = nix::sys::utsname::uname()?;
|
||||
let fields = Fields {
|
||||
user_info: get_username_and_hostname(&utsname),
|
||||
os_name: get_os_pretty_name()?,
|
||||
kernel_version: get_system_info(&utsname),
|
||||
shell: get_shell(),
|
||||
desktop: get_desktop_info(),
|
||||
uptime: get_current()?,
|
||||
memory_usage: get_memory_usage()?,
|
||||
storage: get_root_disk_usage()?,
|
||||
colors: print_dots(),
|
||||
};
|
||||
print_system_info(&fields)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Struct to hold all the fields we need in order to print the fetch. This
|
||||
// helps avoid Clippy warnings about argument count, and makes it slightly
|
||||
// easier to pass data around. Though, it is not like we really need to.
|
||||
struct Fields {
|
||||
user_info: String,
|
||||
os_name: String,
|
||||
kernel_version: String,
|
||||
shell: String,
|
||||
uptime: String,
|
||||
desktop: String,
|
||||
memory_usage: String,
|
||||
storage: String,
|
||||
colors: String,
|
||||
user_info: String,
|
||||
os_name: String,
|
||||
kernel_version: String,
|
||||
shell: String,
|
||||
uptime: String,
|
||||
desktop: String,
|
||||
memory_usage: String,
|
||||
storage: String,
|
||||
colors: String,
|
||||
}
|
||||
|
||||
fn print_system_info(fields: &Fields) -> Result<(), Box<dyn std::error::Error>> {
|
||||
use crate::colors::COLORS;
|
||||
#[cfg_attr(feature = "hotpath", hotpath::measure)]
|
||||
fn print_system_info(
|
||||
fields: &Fields,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
use crate::colors::COLORS;
|
||||
|
||||
let Fields {
|
||||
user_info,
|
||||
os_name,
|
||||
kernel_version,
|
||||
shell,
|
||||
uptime,
|
||||
desktop,
|
||||
memory_usage,
|
||||
storage,
|
||||
colors,
|
||||
} = fields;
|
||||
let Fields {
|
||||
user_info,
|
||||
os_name,
|
||||
kernel_version,
|
||||
shell,
|
||||
uptime,
|
||||
desktop,
|
||||
memory_usage,
|
||||
storage,
|
||||
colors,
|
||||
} = fields;
|
||||
|
||||
let cyan = COLORS.cyan;
|
||||
let blue = COLORS.blue;
|
||||
let reset = COLORS.reset;
|
||||
let system_info = format!("
|
||||
{cyan} ▟█▖ {blue}▝█▙ ▗█▛ {user_info} ~{reset}
|
||||
{cyan} ▗▄▄▟██▄▄▄▄▄{blue}▝█▙█▛ {cyan}▖ {cyan} {blue}System{reset} {os_name}
|
||||
{cyan} ▀▀▀▀▀▀▀▀▀▀▀▘{blue}▝██ {cyan}▟█▖ {cyan} {blue}Kernel{reset} {kernel_version}
|
||||
{blue} ▟█▛ {blue}▝█▘{cyan}▟█▛ {cyan} {blue}Shell{reset} {shell}
|
||||
{blue}▟█████▛ {cyan}▟█████▛ {cyan} {blue}Uptime{reset} {uptime}
|
||||
{blue} ▟█▛{cyan}▗█▖ {cyan}▟█▛ {cyan} {blue}Desktop{reset} {desktop}
|
||||
{blue} ▝█▛ {cyan}██▖{blue}▗▄▄▄▄▄▄▄▄▄▄▄ {cyan} {blue}Memory{reset} {memory_usage}
|
||||
{blue} ▝ {cyan}▟█▜█▖{blue}▀▀▀▀▀██▛▀▀▘ {cyan} {blue}Storage (/){reset} {storage}
|
||||
{cyan} ▟█▘ ▜█▖ {blue}▝█▛ {cyan} {blue}Colors{reset} {colors}\n");
|
||||
let cyan = COLORS.cyan;
|
||||
let blue = COLORS.blue;
|
||||
let reset = COLORS.reset;
|
||||
let system_info = format!("
|
||||
{cyan} ▟█▖ {blue}▝█▙ ▗█▛ {user_info} ~{reset}
|
||||
{cyan} ▗▄▄▟██▄▄▄▄▄{blue}▝█▙█▛ {cyan}▖ {cyan} {blue}System{reset} {os_name}
|
||||
{cyan} ▀▀▀▀▀▀▀▀▀▀▀▘{blue}▝██ {cyan}▟█▖ {cyan} {blue}Kernel{reset} {kernel_version}
|
||||
{blue} ▟█▛ {blue}▝█▘{cyan}▟█▛ {cyan} {blue}Shell{reset} {shell}
|
||||
{blue}▟█████▛ {cyan}▟█████▛ {cyan} {blue}Uptime{reset} {uptime}
|
||||
{blue} ▟█▛{cyan}▗█▖ {cyan}▟█▛ {cyan} {blue}Desktop{reset} {desktop}
|
||||
{blue} ▝█▛ {cyan}██▖{blue}▗▄▄▄▄▄▄▄▄▄▄▄ {cyan} {blue}Memory{reset} {memory_usage}
|
||||
{blue} ▝ {cyan}▟█▜█▖{blue}▀▀▀▀▀██▛▀▀▘ {cyan} {blue}Storage (/){reset} {storage}
|
||||
{cyan} ▟█▘ ▜█▖ {blue}▝█▛ {cyan} {blue}Colors{reset} {colors}\n");
|
||||
|
||||
Ok(stdout().write_all(system_info.as_bytes())?)
|
||||
Ok(stdout().write_all(system_info.as_bytes())?)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,48 @@
|
|||
use nix::sys::utsname::UtsName;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{self, BufRead, BufReader},
|
||||
fmt::Write as _,
|
||||
fs::File,
|
||||
io::{self, Read},
|
||||
};
|
||||
|
||||
use nix::sys::utsname::UtsName;
|
||||
|
||||
#[must_use]
|
||||
#[cfg_attr(feature = "hotpath", hotpath::measure)]
|
||||
pub fn get_system_info(utsname: &UtsName) -> String {
|
||||
format!(
|
||||
"{} {} ({})",
|
||||
utsname.sysname().to_str().unwrap_or("Unknown"),
|
||||
utsname.release().to_str().unwrap_or("Unknown"),
|
||||
utsname.machine().to_str().unwrap_or("Unknown")
|
||||
)
|
||||
let sysname = utsname.sysname().to_str().unwrap_or("Unknown");
|
||||
let release = utsname.release().to_str().unwrap_or("Unknown");
|
||||
let machine = utsname.machine().to_str().unwrap_or("Unknown");
|
||||
|
||||
// Pre-allocate capacity: sysname + " " + release + " (" + machine + ")"
|
||||
let capacity = sysname.len() + 1 + release.len() + 2 + machine.len() + 1;
|
||||
let mut result = String::with_capacity(capacity);
|
||||
|
||||
write!(result, "{sysname} {release} ({machine})").unwrap();
|
||||
result
|
||||
}
|
||||
|
||||
/// Gets the pretty name of the OS from `/etc/os-release`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if `/etc/os-release` cannot be read.
|
||||
#[cfg_attr(feature = "hotpath", hotpath::measure)]
|
||||
pub fn get_os_pretty_name() -> Result<String, io::Error> {
|
||||
let file = File::open("/etc/os-release")?;
|
||||
let reader = BufReader::new(file);
|
||||
// We use a stack-allocated buffer here, which seems to perform MUCH better
|
||||
// than `BufReader`. In hindsight, I should've seen this coming.
|
||||
let mut buffer = String::with_capacity(1024);
|
||||
File::open("/etc/os-release")?.read_to_string(&mut buffer)?;
|
||||
|
||||
for line in reader.lines() {
|
||||
let line = line?;
|
||||
if let Some(pretty_name) = line.strip_prefix("PRETTY_NAME=") {
|
||||
if let Some(trimmed) = pretty_name
|
||||
.strip_prefix('"')
|
||||
.and_then(|s| s.strip_suffix('"'))
|
||||
{
|
||||
return Ok(trimmed.to_string());
|
||||
}
|
||||
return Ok(pretty_name.to_string());
|
||||
}
|
||||
for line in buffer.lines() {
|
||||
if let Some(pretty_name) = line.strip_prefix("PRETTY_NAME=") {
|
||||
if let Some(trimmed) = pretty_name
|
||||
.strip_prefix('"')
|
||||
.and_then(|s| s.strip_suffix('"'))
|
||||
{
|
||||
return Ok(trimmed.to_owned());
|
||||
}
|
||||
return Ok(pretty_name.to_owned());
|
||||
}
|
||||
Ok("Unknown".to_string())
|
||||
}
|
||||
Ok("Unknown".to_owned())
|
||||
}
|
||||
|
|
|
|||
180
src/system.rs
180
src/system.rs
|
|
@ -1,87 +1,137 @@
|
|||
use crate::colors::COLORS;
|
||||
use nix::sys::{statvfs::statvfs, utsname::UtsName};
|
||||
use std::{
|
||||
env,
|
||||
fs::File,
|
||||
io::{self, Read},
|
||||
env,
|
||||
fmt::Write as _,
|
||||
fs::File,
|
||||
io::{self, Read},
|
||||
};
|
||||
|
||||
use nix::sys::{statvfs::statvfs, utsname::UtsName};
|
||||
|
||||
use crate::colors::COLORS;
|
||||
|
||||
#[must_use]
|
||||
#[cfg_attr(feature = "hotpath", hotpath::measure)]
|
||||
pub fn get_username_and_hostname(utsname: &UtsName) -> String {
|
||||
let username = env::var("USER").unwrap_or_else(|_| "unknown_user".to_string());
|
||||
let hostname = utsname
|
||||
.nodename()
|
||||
.to_str()
|
||||
.unwrap_or("unknown_host")
|
||||
.to_string();
|
||||
format!(
|
||||
"{yellow}{username}{red}@{green}{hostname}{reset}",
|
||||
yellow = COLORS.yellow,
|
||||
red = COLORS.red,
|
||||
green = COLORS.green,
|
||||
reset = COLORS.reset,
|
||||
)
|
||||
let username = env::var("USER").unwrap_or_else(|_| "unknown_user".to_owned());
|
||||
let hostname = utsname.nodename().to_str().unwrap_or("unknown_host");
|
||||
|
||||
let capacity = COLORS.yellow.len()
|
||||
+ username.len()
|
||||
+ COLORS.red.len()
|
||||
+ 1
|
||||
+ COLORS.green.len()
|
||||
+ hostname.len()
|
||||
+ COLORS.reset.len();
|
||||
let mut result = String::with_capacity(capacity);
|
||||
|
||||
result.push_str(COLORS.yellow);
|
||||
result.push_str(&username);
|
||||
result.push_str(COLORS.red);
|
||||
result.push('@');
|
||||
result.push_str(COLORS.green);
|
||||
result.push_str(hostname);
|
||||
result.push_str(COLORS.reset);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[cfg_attr(feature = "hotpath", hotpath::measure)]
|
||||
pub fn get_shell() -> String {
|
||||
let shell_path = env::var("SHELL").unwrap_or_else(|_| "unknown_shell".to_string());
|
||||
let shell_name = shell_path.rsplit('/').next().unwrap_or("unknown_shell");
|
||||
shell_name.to_string()
|
||||
let shell_path =
|
||||
env::var("SHELL").unwrap_or_else(|_| "unknown_shell".to_owned());
|
||||
|
||||
// Find last '/' and get the part after it, avoiding allocation
|
||||
shell_path
|
||||
.rsplit('/')
|
||||
.next()
|
||||
.unwrap_or("unknown_shell")
|
||||
.to_owned()
|
||||
}
|
||||
|
||||
/// Gets the root disk usage information.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the filesystem information cannot be retrieved.
|
||||
#[cfg_attr(feature = "hotpath", hotpath::measure)]
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
pub fn get_root_disk_usage() -> Result<String, io::Error> {
|
||||
let vfs = statvfs("/")?;
|
||||
let block_size = vfs.block_size() as u64;
|
||||
let total_blocks = vfs.blocks();
|
||||
let available_blocks = vfs.blocks_available();
|
||||
let vfs = statvfs("/")?;
|
||||
let block_size = vfs.block_size() as u64;
|
||||
let total_blocks = vfs.blocks();
|
||||
let available_blocks = vfs.blocks_available();
|
||||
|
||||
let total_size = block_size * total_blocks;
|
||||
let used_size = total_size - (block_size * available_blocks);
|
||||
let total_size = block_size * total_blocks;
|
||||
let used_size = total_size - (block_size * available_blocks);
|
||||
|
||||
let total_size = total_size as f64 / (1024.0 * 1024.0 * 1024.0);
|
||||
let used_size = used_size as f64 / (1024.0 * 1024.0 * 1024.0);
|
||||
let usage = (used_size / total_size) * 100.0;
|
||||
let total_size = total_size as f64 / (1024.0 * 1024.0 * 1024.0);
|
||||
let used_size = used_size as f64 / (1024.0 * 1024.0 * 1024.0);
|
||||
let usage = (used_size / total_size) * 100.0;
|
||||
|
||||
Ok(format!(
|
||||
"{used_size:.2} GiB / {total_size:.2} GiB ({cyan}{usage:.0}%{reset})",
|
||||
cyan = COLORS.cyan,
|
||||
reset = COLORS.reset,
|
||||
))
|
||||
let mut result = String::with_capacity(64);
|
||||
write!(
|
||||
result,
|
||||
"{used_size:.2} GiB / {total_size:.2} GiB ({cyan}{usage:.0}%{reset})",
|
||||
cyan = COLORS.cyan,
|
||||
reset = COLORS.reset,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Gets the system memory usage information.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if `/proc/meminfo` cannot be read.
|
||||
#[cfg_attr(feature = "hotpath", hotpath::measure)]
|
||||
pub fn get_memory_usage() -> Result<String, io::Error> {
|
||||
fn parse_memory_info() -> Result<(f64, f64), io::Error> {
|
||||
let mut total_memory_kb = 0.0;
|
||||
let mut available_memory_kb = 0.0;
|
||||
let mut meminfo = String::with_capacity(2048);
|
||||
#[cfg_attr(feature = "hotpath", hotpath::measure)]
|
||||
fn parse_memory_info() -> Result<(f64, f64), io::Error> {
|
||||
let mut total_memory_kb = 0.0;
|
||||
let mut available_memory_kb = 0.0;
|
||||
let mut meminfo = String::with_capacity(2048);
|
||||
|
||||
File::open("/proc/meminfo")?.read_to_string(&mut meminfo)?;
|
||||
File::open("/proc/meminfo")?.read_to_string(&mut meminfo)?;
|
||||
|
||||
for line in meminfo.lines() {
|
||||
let mut split = line.split_whitespace();
|
||||
match split.next().unwrap_or_default() {
|
||||
"MemTotal:" => total_memory_kb = split.next().unwrap_or("0").parse().unwrap_or(0.0),
|
||||
"MemAvailable:" => {
|
||||
available_memory_kb = split.next().unwrap_or("0").parse().unwrap_or(0.0);
|
||||
// MemTotal comes before MemAvailable, stop parsing
|
||||
break;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
let total_memory_gb = total_memory_kb / 1024.0 / 1024.0;
|
||||
let available_memory_gb = available_memory_kb / 1024.0 / 1024.0;
|
||||
let used_memory_gb = total_memory_gb - available_memory_gb;
|
||||
|
||||
Ok((used_memory_gb, total_memory_gb))
|
||||
for line in meminfo.lines() {
|
||||
let mut split = line.split_whitespace();
|
||||
match split.next().unwrap_or_default() {
|
||||
"MemTotal:" => {
|
||||
total_memory_kb = split.next().unwrap_or("0").parse().unwrap_or(0.0);
|
||||
},
|
||||
"MemAvailable:" => {
|
||||
available_memory_kb =
|
||||
split.next().unwrap_or("0").parse().unwrap_or(0.0);
|
||||
// MemTotal comes before MemAvailable, stop parsing
|
||||
break;
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
let (used_memory, total_memory) = parse_memory_info()?;
|
||||
let percentage_used = (used_memory / total_memory * 100.0).round() as u64;
|
||||
let total_memory_gb = total_memory_kb / 1024.0 / 1024.0;
|
||||
let available_memory_gb = available_memory_kb / 1024.0 / 1024.0;
|
||||
let used_memory_gb = total_memory_gb - available_memory_gb;
|
||||
|
||||
Ok(format!(
|
||||
"{used_memory:.2} GiB / {total_memory:.2} GiB ({cyan}{percentage_used}%{reset})",
|
||||
cyan = COLORS.cyan,
|
||||
reset = COLORS.reset,
|
||||
))
|
||||
Ok((used_memory_gb, total_memory_gb))
|
||||
}
|
||||
|
||||
let (used_memory, total_memory) = parse_memory_info()?;
|
||||
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
||||
let percentage_used = (used_memory / total_memory * 100.0).round() as u64;
|
||||
|
||||
let mut result = String::with_capacity(64);
|
||||
write!(
|
||||
result,
|
||||
"{used_memory:.2} GiB / {total_memory:.2} GiB \
|
||||
({cyan}{percentage_used}%{reset})",
|
||||
cyan = COLORS.cyan,
|
||||
reset = COLORS.reset,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,40 +1,49 @@
|
|||
use std::{io, mem::MaybeUninit};
|
||||
use std::{fmt::Write, io, mem::MaybeUninit};
|
||||
|
||||
/// Gets the current system uptime.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the system uptime cannot be retrieved.
|
||||
#[cfg_attr(feature = "hotpath", hotpath::measure)]
|
||||
pub fn get_current() -> Result<String, io::Error> {
|
||||
let uptime_seconds = {
|
||||
let mut info = MaybeUninit::uninit();
|
||||
if unsafe { libc::sysinfo(info.as_mut_ptr()) } != 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
unsafe { info.assume_init().uptime as u64 }
|
||||
};
|
||||
let uptime_seconds = {
|
||||
let mut info = MaybeUninit::uninit();
|
||||
if unsafe { libc::sysinfo(info.as_mut_ptr()) } != 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
#[allow(clippy::cast_sign_loss)]
|
||||
unsafe {
|
||||
info.assume_init().uptime as u64
|
||||
}
|
||||
};
|
||||
|
||||
let days = uptime_seconds / 86400;
|
||||
let hours = (uptime_seconds / 3600) % 24;
|
||||
let minutes = (uptime_seconds / 60) % 60;
|
||||
let days = uptime_seconds / 86400;
|
||||
let hours = (uptime_seconds / 3600) % 24;
|
||||
let minutes = (uptime_seconds / 60) % 60;
|
||||
|
||||
let mut result = String::with_capacity(32);
|
||||
if days > 0 {
|
||||
result.push_str(&days.to_string());
|
||||
result.push_str(if days == 1 { " day" } else { " days" });
|
||||
let mut result = String::with_capacity(32);
|
||||
if days > 0 {
|
||||
let _ = write!(result, "{days}");
|
||||
result.push_str(if days == 1 { " day" } else { " days" });
|
||||
}
|
||||
if hours > 0 {
|
||||
if !result.is_empty() {
|
||||
result.push_str(", ");
|
||||
}
|
||||
if hours > 0 {
|
||||
if !result.is_empty() {
|
||||
result.push_str(", ");
|
||||
}
|
||||
result.push_str(&hours.to_string());
|
||||
result.push_str(if hours == 1 { " hour" } else { " hours" });
|
||||
}
|
||||
if minutes > 0 {
|
||||
if !result.is_empty() {
|
||||
result.push_str(", ");
|
||||
}
|
||||
result.push_str(&minutes.to_string());
|
||||
result.push_str(if minutes == 1 { " minute" } else { " minutes" });
|
||||
}
|
||||
if result.is_empty() {
|
||||
result.push_str("less than a minute");
|
||||
let _ = write!(result, "{hours}");
|
||||
result.push_str(if hours == 1 { " hour" } else { " hours" });
|
||||
}
|
||||
if minutes > 0 {
|
||||
if !result.is_empty() {
|
||||
result.push_str(", ");
|
||||
}
|
||||
let _ = write!(result, "{minutes}");
|
||||
result.push_str(if minutes == 1 { " minute" } else { " minutes" });
|
||||
}
|
||||
if result.is_empty() {
|
||||
result.push_str("less than a minute");
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
Ok(result)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue