mirror of
https://github.com/NotAShelf/microfetch.git
synced 2025-12-09 14:33: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]
|
[package]
|
||||||
name = "microfetch"
|
name = "microfetch"
|
||||||
version = "0.4.9"
|
version = "0.4.11"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
|
@ -12,28 +12,59 @@ name = "microfetch"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nix = { version = "0.30", features = ["fs", "hostname", "feature"] }
|
hotpath = { optional = true, version = "0.6.0" }
|
||||||
libc = "0.2"
|
libc = "0.2.177"
|
||||||
|
nix = { default-features = false, features = [ "fs", "hostname", "feature" ], version = "0.30.1" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.7"
|
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]]
|
[[bench]]
|
||||||
name = "benchmark"
|
|
||||||
harness = false
|
harness = false
|
||||||
|
name = "benchmark"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 3
|
opt-level = 1
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
strip = true
|
|
||||||
opt-level = "s"
|
|
||||||
lto = true
|
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
panic = "abort"
|
lto = true
|
||||||
|
opt-level = "s"
|
||||||
|
panic = "abort"
|
||||||
|
strip = true
|
||||||
|
|
||||||
[profile.profiler]
|
[profile.profiler]
|
||||||
inherits = "release"
|
debug = true
|
||||||
debug = true
|
inherits = "release"
|
||||||
split-debuginfo = "unpacked"
|
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">
|
<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://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" alt="stars">
|
||||||
<img src="https://img.shields.io/github/stars/notashelf/microfetch?label=stars&color=DEA584">
|
|
||||||
</div>
|
</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
|
## Synopsis
|
||||||
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
|
[fastfetch]: https://github.com/fastfetch-cli/fastfetch
|
||||||
to replace [fastfetch](https://github.com/fastfetch-cli/fastfetch) on my
|
|
||||||
personal system, but [probably not yours](#customizing). Though, you are more
|
Stupidly small and simple, laughably fast and pretty fetch tool. Written in Rust
|
||||||
than welcome to use it on your system: it's pretty [fast](#benchmarks)...
|
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">
|
<p align="center">
|
||||||
<img
|
<img
|
||||||
|
|
@ -26,6 +43,7 @@ than welcome to use it on your system: it's pretty [fast](#benchmarks)...
|
||||||
- Fast
|
- Fast
|
||||||
- Really fast
|
- Really fast
|
||||||
- Minimal dependencies
|
- Minimal dependencies
|
||||||
|
- Tiny binary (~410kb)
|
||||||
- Actually really fast
|
- Actually really fast
|
||||||
- Cool NixOS logo (other, inferior, distros are not supported)
|
- Cool NixOS logo (other, inferior, distros are not supported)
|
||||||
- Reliable detection of following info:
|
- Reliable detection of following info:
|
||||||
|
|
@ -44,39 +62,64 @@ than welcome to use it on your system: it's pretty [fast](#benchmarks)...
|
||||||
|
|
||||||
## Motivation
|
## Motivation
|
||||||
|
|
||||||
Fastfetch, as its name indicates, a very fast fetch tool written in C, however,
|
Fastfetch, as its name probably hinted, is a very fast fetch tool written in C.
|
||||||
I am not interested in any of its additional features, such as customization,
|
However, I am not interested in _any_ of its additional features, and I'm not
|
||||||
and I very much dislike the defaults. Microfetch is my response to this problem,
|
interested in its configuration options. Sure I can _configure_ it when I
|
||||||
a _very fast_ fetch tool that you would normally write in Bash and put in your
|
dislike the defaults, but how often would I really change the configuration...
|
||||||
`~/.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.
|
|
||||||
|
|
||||||
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
|
## Benchmarks
|
||||||
|
|
||||||
The performance may be sometimes influenced by hardware-specific race
|
Below are the benchmarks that I've used to back up my claims of Microfetch's
|
||||||
conditions, or even your kernel configuration meaning it may (at times) depend
|
speed. It is fast, it is _very_ fast and that is the point of its existence. It
|
||||||
on your hardware. However, the overall trend appears to be less than 1.3ms on
|
_could_ be faster, and it will be. Eventually.
|
||||||
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.
|
|
||||||
|
|
||||||
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative | Written by raf? |
|
At this point in time, the performance may be sometimes influenced by
|
||||||
| :----------- | -----------: | -------: | -------: | -------------: | --------------: |
|
hardware-specific race conditions or even your kernel configuration. Which means
|
||||||
| `microfetch` | 1.0 ± 0.1 | 0.9 | 1.7 | 1.00 | yes |
|
that Microfetch's speed may (at times) depend on your hardware setup. However,
|
||||||
| `fastfetch` | 48.6 ± 1.6 | 45.8 | 61.3 | 46.65 ± 4.75 | no |
|
even with the worst possible hardware I could find in my house, I've achieved a
|
||||||
| `pfetch` | 206.0 ± 4.5 | 198.0 | 241.4 | 197.50 ± 19.53 | no |
|
nice less-than-1ms invocation time. Which is pretty good. While Microfetch
|
||||||
| `neofetch` | 689.1 ± 29.1 | 637.7 | 811.2 | 660.79 ± 69.56 | no |
|
_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
|
Below are the actual benchmarks with Hyperfine measured on my Desktop system.
|
||||||
fetch tool that I have tried. The only downsides of using Rust for the project
|
The benchmarks were performed under medium system load, and may not be the same
|
||||||
(in exchange for speed and maintainability) is the slightly "bloated" dependency
|
on your system. Please _also_ note that those benchmarks will not be always kept
|
||||||
trees, and the increased build times. The latter is very easily mitigated with
|
up to date, but I will try to update the numbers as I make Microfetch faster.
|
||||||
Nix's binary cache. Since Microfetch is already in Nixpkgs, you are recommended
|
|
||||||
to use it to utilize the binary cache properly
|
| 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
|
### 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
|
[Getting Started Guide] for details or just run `cargo bench` to benchmark all
|
||||||
features of Microfetch.
|
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
|
## Installation
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,31 @@
|
||||||
use criterion::{Criterion, criterion_group, criterion_main};
|
use criterion::{Criterion, criterion_group, criterion_main};
|
||||||
use microfetch_lib::colors::print_dots;
|
use microfetch_lib::{
|
||||||
use microfetch_lib::desktop::get_desktop_info;
|
colors::print_dots,
|
||||||
use microfetch_lib::release::{get_os_pretty_name, get_system_info};
|
desktop::get_desktop_info,
|
||||||
use microfetch_lib::system::{
|
release::{get_os_pretty_name, get_system_info},
|
||||||
get_memory_usage, get_root_disk_usage, get_shell, get_username_and_hostname,
|
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) {
|
fn main_benchmark(c: &mut Criterion) {
|
||||||
let utsname = nix::sys::utsname::uname().expect("lol");
|
let utsname = nix::sys::utsname::uname().expect("lol");
|
||||||
c.bench_function("user_info", |b| {
|
c.bench_function("user_info", |b| {
|
||||||
b.iter(|| get_username_and_hostname(&utsname));
|
b.iter(|| get_username_and_hostname(&utsname));
|
||||||
});
|
});
|
||||||
c.bench_function("os_name", |b| b.iter(get_os_pretty_name));
|
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("kernel_version", |b| b.iter(|| get_system_info(&utsname)));
|
||||||
c.bench_function("shell", |b| b.iter(get_shell));
|
c.bench_function("shell", |b| b.iter(get_shell));
|
||||||
|
|
||||||
c.bench_function("desktop", |b| b.iter(get_desktop_info));
|
c.bench_function("desktop", |b| b.iter(get_desktop_info));
|
||||||
c.bench_function("uptime", |b| b.iter(get_current));
|
c.bench_function("uptime", |b| b.iter(get_current));
|
||||||
c.bench_function("memory_usage", |b| b.iter(get_memory_usage));
|
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("storage", |b| b.iter(get_root_disk_usage));
|
||||||
c.bench_function("colors", |b| b.iter(print_dots));
|
c.bench_function("colors", |b| b.iter(print_dots));
|
||||||
}
|
}
|
||||||
|
|
||||||
criterion_group!(benches, main_benchmark);
|
criterion_group!(benches, main_benchmark);
|
||||||
|
|
|
||||||
9
flake.lock
generated
9
flake.lock
generated
|
|
@ -2,11 +2,14 @@
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1754214453,
|
"lastModified": 1743359643,
|
||||||
"narHash": "sha256-Q/I2xJn/j1wpkGhWkQnm20nShYnG7TI99foDBpXm1SY=",
|
"narHash": "sha256-RkyJ9a67s0zEIz4O66TyZOIGh4TFZ4dKHKMgnxZCh2I=",
|
||||||
|
"lastModified": 1763381801,
|
||||||
|
"narHash": "sha256-325fR0JmHW7B74/gHPv/S9w1Rfj/M2HniwQFUwdrZ9k=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "5b09dc45f24cf32316283e62aec81ffee3c3e376",
|
"rev": "ca77b4bc80e558ce59f2712fdb276f90c0ee309a",
|
||||||
|
"rev": "46931757ea8bdbba25c076697f8e73b8dc39fef5",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
||||||
|
|
@ -9,23 +9,29 @@
|
||||||
inherit (toml) version;
|
inherit (toml) version;
|
||||||
in
|
in
|
||||||
rustPlatform.buildRustPackage.override {stdenv = stdenvAdapters.useMoldLinker llvm.stdenv;} {
|
rustPlatform.buildRustPackage.override {stdenv = stdenvAdapters.useMoldLinker llvm.stdenv;} {
|
||||||
RUSTFLAGS = "-C link-arg=-fuse-ld=mold";
|
|
||||||
|
|
||||||
inherit pname version;
|
inherit pname version;
|
||||||
|
src = let
|
||||||
src = builtins.path {
|
fs = lib.fileset;
|
||||||
name = "${pname}-${version}";
|
s = ../.;
|
||||||
path = lib.sources.cleanSource ../.;
|
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;
|
cargoLock.lockFile = ../Cargo.lock;
|
||||||
enableParallelBuilding = true;
|
enableParallelBuilding = true;
|
||||||
|
env.RUSTFLAGS = "-C link-arg=-fuse-ld=mold";
|
||||||
|
|
||||||
meta = {
|
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";
|
homepage = "https://github.com/NotAShelf/microfetch";
|
||||||
license = lib.licenses.gpl3Only;
|
license = lib.licenses.gpl3Only;
|
||||||
maintainers = with lib.maintainers; [NotAShelf];
|
maintainers = [lib.maintainers.NotAShelf];
|
||||||
mainProgram = "microfetch";
|
mainProgram = "microfetch";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
rustfmt,
|
rustfmt,
|
||||||
clippy,
|
clippy,
|
||||||
cargo,
|
cargo,
|
||||||
|
taplo,
|
||||||
rustc,
|
rustc,
|
||||||
rustPlatform,
|
rustPlatform,
|
||||||
gnuplot,
|
gnuplot,
|
||||||
|
|
@ -16,8 +17,9 @@ mkShell {
|
||||||
rustc
|
rustc
|
||||||
|
|
||||||
rust-analyzer-unwrapped
|
rust-analyzer-unwrapped
|
||||||
rustfmt
|
(rustfmt.override {asNightly = true;})
|
||||||
clippy
|
clippy
|
||||||
|
taplo
|
||||||
|
|
||||||
gnuplot # For Criterion.rs plots
|
gnuplot # For Criterion.rs plots
|
||||||
];
|
];
|
||||||
|
|
|
||||||
112
src/colors.rs
112
src/colors.rs
|
|
@ -1,57 +1,81 @@
|
||||||
use std::env;
|
use std::{env, sync::LazyLock};
|
||||||
use std::sync::LazyLock;
|
|
||||||
|
|
||||||
pub struct Colors {
|
pub struct Colors {
|
||||||
pub reset: &'static str,
|
pub reset: &'static str,
|
||||||
pub blue: &'static str,
|
pub blue: &'static str,
|
||||||
pub cyan: &'static str,
|
pub cyan: &'static str,
|
||||||
pub green: &'static str,
|
pub green: &'static str,
|
||||||
pub yellow: &'static str,
|
pub yellow: &'static str,
|
||||||
pub red: &'static str,
|
pub red: &'static str,
|
||||||
pub magenta: &'static str,
|
pub magenta: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Colors {
|
impl Colors {
|
||||||
const fn new(is_no_color: bool) -> Self {
|
const fn new(is_no_color: bool) -> Self {
|
||||||
if is_no_color {
|
if is_no_color {
|
||||||
Self {
|
Self {
|
||||||
reset: "",
|
reset: "",
|
||||||
blue: "",
|
blue: "",
|
||||||
cyan: "",
|
cyan: "",
|
||||||
green: "",
|
green: "",
|
||||||
yellow: "",
|
yellow: "",
|
||||||
red: "",
|
red: "",
|
||||||
magenta: "",
|
magenta: "",
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Self {
|
Self {
|
||||||
reset: "\x1b[0m",
|
reset: "\x1b[0m",
|
||||||
blue: "\x1b[34m",
|
blue: "\x1b[34m",
|
||||||
cyan: "\x1b[36m",
|
cyan: "\x1b[36m",
|
||||||
green: "\x1b[32m",
|
green: "\x1b[32m",
|
||||||
yellow: "\x1b[33m",
|
yellow: "\x1b[33m",
|
||||||
red: "\x1b[31m",
|
red: "\x1b[31m",
|
||||||
magenta: "\x1b[35m",
|
magenta: "\x1b[35m",
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static COLORS: LazyLock<Colors> = LazyLock::new(|| {
|
pub static COLORS: LazyLock<Colors> = LazyLock::new(|| {
|
||||||
// check for NO_COLOR once at startup
|
// Check for NO_COLOR once at startup
|
||||||
let is_no_color = env::var("NO_COLOR").is_ok();
|
let is_no_color = env::var("NO_COLOR").is_ok();
|
||||||
Colors::new(is_no_color)
|
Colors::new(is_no_color)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
#[cfg_attr(feature = "hotpath", hotpath::measure)]
|
||||||
pub fn print_dots() -> String {
|
pub fn print_dots() -> String {
|
||||||
format!(
|
// Pre-calculate capacity: 6 color codes + " " (glyph + 2 spaces) per color
|
||||||
"{} {} {} {} {} {} {}",
|
const GLYPH: &str = "";
|
||||||
COLORS.blue,
|
let capacity = COLORS.blue.len()
|
||||||
COLORS.cyan,
|
+ COLORS.cyan.len()
|
||||||
COLORS.green,
|
+ COLORS.green.len()
|
||||||
COLORS.yellow,
|
+ COLORS.yellow.len()
|
||||||
COLORS.red,
|
+ COLORS.red.len()
|
||||||
COLORS.magenta,
|
+ COLORS.magenta.len()
|
||||||
COLORS.reset,
|
+ 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 {
|
pub fn get_desktop_info() -> String {
|
||||||
// Retrieve the environment variables and handle Result types
|
// Retrieve the environment variables and handle Result types
|
||||||
let desktop_env = std::env::var("XDG_CURRENT_DESKTOP");
|
let desktop_env = std::env::var("XDG_CURRENT_DESKTOP");
|
||||||
let display_backend_result = std::env::var("XDG_SESSION_TYPE");
|
let display_backend = std::env::var("XDG_SESSION_TYPE");
|
||||||
|
|
||||||
// Capitalize the first letter of the display backend value
|
let desktop_str = match desktop_env {
|
||||||
let mut display_backend = display_backend_result.unwrap_or_default();
|
Err(_) => "Unknown",
|
||||||
if let Some(c) = display_backend.as_mut_str().get_mut(0..1) {
|
Ok(ref s) if s.starts_with("none+") => &s[5..],
|
||||||
c.make_ascii_uppercase();
|
Ok(ref s) => s.as_str(),
|
||||||
}
|
};
|
||||||
|
|
||||||
// Trim "none+" from the start of desktop_env if present
|
let backend_str = match display_backend {
|
||||||
// Use "Unknown" if desktop_env is empty or has an error
|
Err(_) => "Unknown",
|
||||||
let desktop_env = match desktop_env {
|
Ok(ref s) if s.is_empty() => "Unknown",
|
||||||
Err(_) => "Unknown".to_string(),
|
Ok(ref s) => s.as_str(),
|
||||||
Ok(s) => s.trim_start_matches("none+").to_string(),
|
};
|
||||||
};
|
|
||||||
|
|
||||||
// Handle the case where display_backend might be empty after capitalization
|
// Pre-calculate capacity: desktop_len + " (" + backend_len + ")"
|
||||||
let display_backend = if display_backend.is_empty() {
|
// Capitalize first char needs temporary allocation only if backend exists
|
||||||
"Unknown"
|
let mut result =
|
||||||
} else {
|
String::with_capacity(desktop_str.len() + backend_str.len() + 3);
|
||||||
&display_backend
|
result.push_str(desktop_str);
|
||||||
}
|
result.push_str(" (");
|
||||||
.to_string();
|
|
||||||
|
|
||||||
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 system;
|
||||||
mod uptime;
|
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};
|
use std::io::{Write, stdout};
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
use crate::{
|
||||||
if Some("--version") == std::env::args().nth(1).as_deref() {
|
colors::print_dots,
|
||||||
println!("Microfetch {}", env!("CARGO_PKG_VERSION"));
|
desktop::get_desktop_info,
|
||||||
} else {
|
release::{get_os_pretty_name, get_system_info},
|
||||||
let utsname = nix::sys::utsname::uname()?;
|
system::{
|
||||||
let fields = Fields {
|
get_memory_usage,
|
||||||
user_info: get_username_and_hostname(&utsname),
|
get_root_disk_usage,
|
||||||
os_name: get_os_pretty_name()?,
|
get_shell,
|
||||||
kernel_version: get_system_info(&utsname),
|
get_username_and_hostname,
|
||||||
shell: get_shell(),
|
},
|
||||||
desktop: get_desktop_info(),
|
uptime::get_current,
|
||||||
uptime: get_current()?,
|
};
|
||||||
memory_usage: get_memory_usage()?,
|
|
||||||
storage: get_root_disk_usage()?,
|
|
||||||
colors: print_dots(),
|
|
||||||
};
|
|
||||||
print_system_info(&fields)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
// 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
|
// 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.
|
// easier to pass data around. Though, it is not like we really need to.
|
||||||
struct Fields {
|
struct Fields {
|
||||||
user_info: String,
|
user_info: String,
|
||||||
os_name: String,
|
os_name: String,
|
||||||
kernel_version: String,
|
kernel_version: String,
|
||||||
shell: String,
|
shell: String,
|
||||||
uptime: String,
|
uptime: String,
|
||||||
desktop: String,
|
desktop: String,
|
||||||
memory_usage: String,
|
memory_usage: String,
|
||||||
storage: String,
|
storage: String,
|
||||||
colors: String,
|
colors: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_system_info(fields: &Fields) -> Result<(), Box<dyn std::error::Error>> {
|
#[cfg_attr(feature = "hotpath", hotpath::measure)]
|
||||||
use crate::colors::COLORS;
|
fn print_system_info(
|
||||||
|
fields: &Fields,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
use crate::colors::COLORS;
|
||||||
|
|
||||||
let Fields {
|
let Fields {
|
||||||
user_info,
|
user_info,
|
||||||
os_name,
|
os_name,
|
||||||
kernel_version,
|
kernel_version,
|
||||||
shell,
|
shell,
|
||||||
uptime,
|
uptime,
|
||||||
desktop,
|
desktop,
|
||||||
memory_usage,
|
memory_usage,
|
||||||
storage,
|
storage,
|
||||||
colors,
|
colors,
|
||||||
} = fields;
|
} = fields;
|
||||||
|
|
||||||
let cyan = COLORS.cyan;
|
let cyan = COLORS.cyan;
|
||||||
let blue = COLORS.blue;
|
let blue = COLORS.blue;
|
||||||
let reset = COLORS.reset;
|
let reset = COLORS.reset;
|
||||||
let system_info = format!("
|
let system_info = format!("
|
||||||
{cyan} ▟█▖ {blue}▝█▙ ▗█▛ {user_info} ~{reset}
|
{cyan} ▟█▖ {blue}▝█▙ ▗█▛ {user_info} ~{reset}
|
||||||
{cyan} ▗▄▄▟██▄▄▄▄▄{blue}▝█▙█▛ {cyan}▖ {cyan} {blue}System{reset} {os_name}
|
{cyan} ▗▄▄▟██▄▄▄▄▄{blue}▝█▙█▛ {cyan}▖ {cyan} {blue}System{reset} {os_name}
|
||||||
{cyan} ▀▀▀▀▀▀▀▀▀▀▀▘{blue}▝██ {cyan}▟█▖ {cyan} {blue}Kernel{reset} {kernel_version}
|
{cyan} ▀▀▀▀▀▀▀▀▀▀▀▘{blue}▝██ {cyan}▟█▖ {cyan} {blue}Kernel{reset} {kernel_version}
|
||||||
{blue} ▟█▛ {blue}▝█▘{cyan}▟█▛ {cyan} {blue}Shell{reset} {shell}
|
{blue} ▟█▛ {blue}▝█▘{cyan}▟█▛ {cyan} {blue}Shell{reset} {shell}
|
||||||
{blue}▟█████▛ {cyan}▟█████▛ {cyan} {blue}Uptime{reset} {uptime}
|
{blue}▟█████▛ {cyan}▟█████▛ {cyan} {blue}Uptime{reset} {uptime}
|
||||||
{blue} ▟█▛{cyan}▗█▖ {cyan}▟█▛ {cyan} {blue}Desktop{reset} {desktop}
|
{blue} ▟█▛{cyan}▗█▖ {cyan}▟█▛ {cyan} {blue}Desktop{reset} {desktop}
|
||||||
{blue} ▝█▛ {cyan}██▖{blue}▗▄▄▄▄▄▄▄▄▄▄▄ {cyan} {blue}Memory{reset} {memory_usage}
|
{blue} ▝█▛ {cyan}██▖{blue}▗▄▄▄▄▄▄▄▄▄▄▄ {cyan} {blue}Memory{reset} {memory_usage}
|
||||||
{blue} ▝ {cyan}▟█▜█▖{blue}▀▀▀▀▀██▛▀▀▘ {cyan} {blue}Storage (/){reset} {storage}
|
{blue} ▝ {cyan}▟█▜█▖{blue}▀▀▀▀▀██▛▀▀▘ {cyan} {blue}Storage (/){reset} {storage}
|
||||||
{cyan} ▟█▘ ▜█▖ {blue}▝█▛ {cyan} {blue}Colors{reset} {colors}\n");
|
{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::{
|
use std::{
|
||||||
fs::File,
|
fmt::Write as _,
|
||||||
io::{self, BufRead, BufReader},
|
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 {
|
pub fn get_system_info(utsname: &UtsName) -> String {
|
||||||
format!(
|
let sysname = utsname.sysname().to_str().unwrap_or("Unknown");
|
||||||
"{} {} ({})",
|
let release = utsname.release().to_str().unwrap_or("Unknown");
|
||||||
utsname.sysname().to_str().unwrap_or("Unknown"),
|
let machine = utsname.machine().to_str().unwrap_or("Unknown");
|
||||||
utsname.release().to_str().unwrap_or("Unknown"),
|
|
||||||
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> {
|
pub fn get_os_pretty_name() -> Result<String, io::Error> {
|
||||||
let file = File::open("/etc/os-release")?;
|
// We use a stack-allocated buffer here, which seems to perform MUCH better
|
||||||
let reader = BufReader::new(file);
|
// 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() {
|
for line in buffer.lines() {
|
||||||
let line = line?;
|
if let Some(pretty_name) = line.strip_prefix("PRETTY_NAME=") {
|
||||||
if let Some(pretty_name) = line.strip_prefix("PRETTY_NAME=") {
|
if let Some(trimmed) = pretty_name
|
||||||
if let Some(trimmed) = pretty_name
|
.strip_prefix('"')
|
||||||
.strip_prefix('"')
|
.and_then(|s| s.strip_suffix('"'))
|
||||||
.and_then(|s| s.strip_suffix('"'))
|
{
|
||||||
{
|
return Ok(trimmed.to_owned());
|
||||||
return Ok(trimmed.to_string());
|
}
|
||||||
}
|
return Ok(pretty_name.to_owned());
|
||||||
return Ok(pretty_name.to_string());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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::{
|
use std::{
|
||||||
env,
|
env,
|
||||||
fs::File,
|
fmt::Write as _,
|
||||||
io::{self, Read},
|
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 {
|
pub fn get_username_and_hostname(utsname: &UtsName) -> String {
|
||||||
let username = env::var("USER").unwrap_or_else(|_| "unknown_user".to_string());
|
let username = env::var("USER").unwrap_or_else(|_| "unknown_user".to_owned());
|
||||||
let hostname = utsname
|
let hostname = utsname.nodename().to_str().unwrap_or("unknown_host");
|
||||||
.nodename()
|
|
||||||
.to_str()
|
let capacity = COLORS.yellow.len()
|
||||||
.unwrap_or("unknown_host")
|
+ username.len()
|
||||||
.to_string();
|
+ COLORS.red.len()
|
||||||
format!(
|
+ 1
|
||||||
"{yellow}{username}{red}@{green}{hostname}{reset}",
|
+ COLORS.green.len()
|
||||||
yellow = COLORS.yellow,
|
+ hostname.len()
|
||||||
red = COLORS.red,
|
+ COLORS.reset.len();
|
||||||
green = COLORS.green,
|
let mut result = String::with_capacity(capacity);
|
||||||
reset = COLORS.reset,
|
|
||||||
)
|
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 {
|
pub fn get_shell() -> String {
|
||||||
let shell_path = env::var("SHELL").unwrap_or_else(|_| "unknown_shell".to_string());
|
let shell_path =
|
||||||
let shell_name = shell_path.rsplit('/').next().unwrap_or("unknown_shell");
|
env::var("SHELL").unwrap_or_else(|_| "unknown_shell".to_owned());
|
||||||
shell_name.to_string()
|
|
||||||
|
// 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> {
|
pub fn get_root_disk_usage() -> Result<String, io::Error> {
|
||||||
let vfs = statvfs("/")?;
|
let vfs = statvfs("/")?;
|
||||||
let block_size = vfs.block_size() as u64;
|
let block_size = vfs.block_size() as u64;
|
||||||
let total_blocks = vfs.blocks();
|
let total_blocks = vfs.blocks();
|
||||||
let available_blocks = vfs.blocks_available();
|
let available_blocks = vfs.blocks_available();
|
||||||
|
|
||||||
let total_size = block_size * total_blocks;
|
let total_size = block_size * total_blocks;
|
||||||
let used_size = total_size - (block_size * available_blocks);
|
let used_size = total_size - (block_size * available_blocks);
|
||||||
|
|
||||||
let total_size = total_size as f64 / (1024.0 * 1024.0 * 1024.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 used_size = used_size as f64 / (1024.0 * 1024.0 * 1024.0);
|
||||||
let usage = (used_size / total_size) * 100.0;
|
let usage = (used_size / total_size) * 100.0;
|
||||||
|
|
||||||
Ok(format!(
|
let mut result = String::with_capacity(64);
|
||||||
"{used_size:.2} GiB / {total_size:.2} GiB ({cyan}{usage:.0}%{reset})",
|
write!(
|
||||||
cyan = COLORS.cyan,
|
result,
|
||||||
reset = COLORS.reset,
|
"{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> {
|
pub fn get_memory_usage() -> Result<String, io::Error> {
|
||||||
fn parse_memory_info() -> Result<(f64, f64), io::Error> {
|
#[cfg_attr(feature = "hotpath", hotpath::measure)]
|
||||||
let mut total_memory_kb = 0.0;
|
fn parse_memory_info() -> Result<(f64, f64), io::Error> {
|
||||||
let mut available_memory_kb = 0.0;
|
let mut total_memory_kb = 0.0;
|
||||||
let mut meminfo = String::with_capacity(2048);
|
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() {
|
for line in meminfo.lines() {
|
||||||
let mut split = line.split_whitespace();
|
let mut split = line.split_whitespace();
|
||||||
match split.next().unwrap_or_default() {
|
match split.next().unwrap_or_default() {
|
||||||
"MemTotal:" => total_memory_kb = split.next().unwrap_or("0").parse().unwrap_or(0.0),
|
"MemTotal:" => {
|
||||||
"MemAvailable:" => {
|
total_memory_kb = split.next().unwrap_or("0").parse().unwrap_or(0.0);
|
||||||
available_memory_kb = split.next().unwrap_or("0").parse().unwrap_or(0.0);
|
},
|
||||||
// MemTotal comes before MemAvailable, stop parsing
|
"MemAvailable:" => {
|
||||||
break;
|
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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let (used_memory, total_memory) = parse_memory_info()?;
|
let total_memory_gb = total_memory_kb / 1024.0 / 1024.0;
|
||||||
let percentage_used = (used_memory / total_memory * 100.0).round() as u64;
|
let available_memory_gb = available_memory_kb / 1024.0 / 1024.0;
|
||||||
|
let used_memory_gb = total_memory_gb - available_memory_gb;
|
||||||
|
|
||||||
Ok(format!(
|
Ok((used_memory_gb, total_memory_gb))
|
||||||
"{used_memory:.2} GiB / {total_memory:.2} GiB ({cyan}{percentage_used}%{reset})",
|
}
|
||||||
cyan = COLORS.cyan,
|
|
||||||
reset = COLORS.reset,
|
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> {
|
pub fn get_current() -> Result<String, io::Error> {
|
||||||
let uptime_seconds = {
|
let uptime_seconds = {
|
||||||
let mut info = MaybeUninit::uninit();
|
let mut info = MaybeUninit::uninit();
|
||||||
if unsafe { libc::sysinfo(info.as_mut_ptr()) } != 0 {
|
if unsafe { libc::sysinfo(info.as_mut_ptr()) } != 0 {
|
||||||
return Err(io::Error::last_os_error());
|
return Err(io::Error::last_os_error());
|
||||||
}
|
}
|
||||||
unsafe { info.assume_init().uptime as u64 }
|
#[allow(clippy::cast_sign_loss)]
|
||||||
};
|
unsafe {
|
||||||
|
info.assume_init().uptime as u64
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let days = uptime_seconds / 86400;
|
let days = uptime_seconds / 86400;
|
||||||
let hours = (uptime_seconds / 3600) % 24;
|
let hours = (uptime_seconds / 3600) % 24;
|
||||||
let minutes = (uptime_seconds / 60) % 60;
|
let minutes = (uptime_seconds / 60) % 60;
|
||||||
|
|
||||||
let mut result = String::with_capacity(32);
|
let mut result = String::with_capacity(32);
|
||||||
if days > 0 {
|
if days > 0 {
|
||||||
result.push_str(&days.to_string());
|
let _ = write!(result, "{days}");
|
||||||
result.push_str(if days == 1 { " day" } else { " days" });
|
result.push_str(if days == 1 { " day" } else { " days" });
|
||||||
|
}
|
||||||
|
if hours > 0 {
|
||||||
|
if !result.is_empty() {
|
||||||
|
result.push_str(", ");
|
||||||
}
|
}
|
||||||
if hours > 0 {
|
let _ = write!(result, "{hours}");
|
||||||
if !result.is_empty() {
|
result.push_str(if hours == 1 { " hour" } else { " hours" });
|
||||||
result.push_str(", ");
|
}
|
||||||
}
|
if minutes > 0 {
|
||||||
result.push_str(&hours.to_string());
|
if !result.is_empty() {
|
||||||
result.push_str(if hours == 1 { " hour" } else { " hours" });
|
result.push_str(", ");
|
||||||
}
|
|
||||||
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, "{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