Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 19 additions & 27 deletions .github/workflows/hotpath-comment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,45 +13,37 @@ 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"
run: |
hotpath profile-pr \
--head-metrics head-timing.json \
--base-metrics base-timing.json \
--github-token ${{ secrets.GITHUB_TOKEN }} \
--pr-number ${{ steps.pr.outputs.number }}

- name: Post allocation comparison comment
run: |
hotpath profile-pr \
--head-metrics head-alloc.json \
--base-metrics base-alloc.json \
--github-token ${{ secrets.GITHUB_TOKEN }} \
--pr-number ${{ steps.pr.outputs.number }}
19 changes: 0 additions & 19 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ path = "src/main.rs"
[dependencies]
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"
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ on your system: it is pretty _[fast](#benchmarks)_...
- Fast
- Really fast
- Minimal dependencies
- Tiny binary (~410kb)
- Tiny binary (~370kb)
- Actually really fast
- Cool NixOS logo (other, inferior, distros are not supported)
- Reliable detection of following info:
Expand Down Expand Up @@ -160,7 +160,8 @@ performance regressions.

> [!NOTE]
> You will need a Nerdfonts patched font installed, and for your terminal
> emulator to support said font. Microfetch uses nerdfonts glyphs by default.
> emulator to support said font. Microfetch uses nerdfonts glyphs by default,
> but this can be changed by [patching the program](#customizing).

Microfetch is packaged in [nixpkgs](https://github.com/nixos/nixpkgs). It can be
installed by adding `pkgs.microfetch` to your `environment.systemPackages`.
Expand Down
3 changes: 2 additions & 1 deletion benches/benchmark.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use criterion::{Criterion, criterion_group, criterion_main};
use microfetch_lib::{
UtsName,
colors::print_dots,
desktop::get_desktop_info,
release::{get_os_pretty_name, get_system_info},
Expand All @@ -13,7 +14,7 @@ use microfetch_lib::{
};

fn main_benchmark(c: &mut Criterion) {
let utsname = nix::sys::utsname::uname().expect("lol");
let utsname = UtsName::uname().expect("Failed to get uname");
c.bench_function("user_info", |b| {
b.iter(|| get_username_and_hostname(&utsname));
});
Expand Down
1 change: 1 addition & 0 deletions nix/package.nix
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ in
(fs.fileFilter (file: builtins.any file.hasExt ["rs"]) (s + /src))
(s + /Cargo.lock)
(s + /Cargo.toml)
(s + /benches)
];
};

Expand Down
40 changes: 40 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,45 @@
pub mod colors;
pub mod desktop;
pub mod release;
pub mod syscall;
pub mod system;
pub mod uptime;

use std::mem::MaybeUninit;

/// Wrapper for `libc::utsname` with safe accessor methods
pub struct UtsName(libc::utsname);

impl UtsName {
/// Calls `uname` syscall and returns a `UtsName` wrapper
///
/// # Errors
/// Returns an error if the uname syscall fails
pub fn uname() -> Result<Self, std::io::Error> {
let mut uts = MaybeUninit::uninit();
if unsafe { libc::uname(uts.as_mut_ptr()) } != 0 {
return Err(std::io::Error::last_os_error());
}
Ok(Self(unsafe { uts.assume_init() }))
}

#[must_use]
pub const fn nodename(&self) -> &std::ffi::CStr {
unsafe { std::ffi::CStr::from_ptr(self.0.nodename.as_ptr()) }
}

#[must_use]
pub const fn sysname(&self) -> &std::ffi::CStr {
unsafe { std::ffi::CStr::from_ptr(self.0.sysname.as_ptr()) }
}

#[must_use]
pub const fn release(&self) -> &std::ffi::CStr {
unsafe { std::ffi::CStr::from_ptr(self.0.release.as_ptr()) }
}

#[must_use]
pub const fn machine(&self) -> &std::ffi::CStr {
unsafe { std::ffi::CStr::from_ptr(self.0.machine.as_ptr()) }
}
}
5 changes: 4 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
mod colors;
mod desktop;
mod release;
mod syscall;
mod system;
mod uptime;

use std::io::{Write, stdout};

pub use microfetch_lib::UtsName;

use crate::{
colors::print_dots,
desktop::get_desktop_info,
Expand All @@ -24,7 +27,7 @@ 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 utsname = UtsName::uname()?;
let fields = Fields {
user_info: get_username_and_hostname(&utsname),
os_name: get_os_pretty_name()?,
Expand Down
59 changes: 40 additions & 19 deletions src/release.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
use std::{
fmt::Write as _,
fs::File,
io::{self, Read},
};
use std::{fmt::Write as _, io};

use nix::sys::utsname::UtsName;
use crate::{UtsName, syscall::read_file_fast};

#[must_use]
#[cfg_attr(feature = "hotpath", hotpath::measure)]
Expand All @@ -28,21 +24,46 @@ pub fn get_system_info(utsname: &UtsName) -> String {
/// 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> {
// 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 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('"'))
// Fast byte-level scanning for PRETTY_NAME=
const PREFIX: &[u8] = b"PRETTY_NAME=";

let mut buffer = [0u8; 1024];

// Use fast syscall-based file reading
let bytes_read = read_file_fast("/etc/os-release", &mut buffer)?;
let content = &buffer[..bytes_read];

let mut offset = 0;

while offset < content.len() {
let remaining = &content[offset..];

// Find newline or end
let line_end = remaining
.iter()
.position(|&b| b == b'\n')
.unwrap_or(remaining.len());
let line = &remaining[..line_end];

if line.starts_with(PREFIX) {
let value = &line[PREFIX.len()..];

// Strip quotes if present
let trimmed = if value.len() >= 2
&& value[0] == b'"'
&& value[value.len() - 1] == b'"'
{
return Ok(trimmed.to_owned());
}
return Ok(pretty_name.to_owned());
&value[1..value.len() - 1]
} else {
value
};

// Convert to String - should be valid UTF-8
return Ok(String::from_utf8_lossy(trimmed).into_owned());
}

offset += line_end + 1;
}

Ok("Unknown".to_owned())
}
Loading