diff --git a/.clang-format b/.clang-format index e0941b9..bd9afd9 100644 --- a/.clang-format +++ b/.clang-format @@ -28,7 +28,8 @@ ContinuationIndentWidth: 8 AlignConsecutiveMacros: true AlignConsecutiveAssignments: false AlignTrailingComments: false -ColumnLimit: 80 +ColumnLimit: 100 +BreakStringLiterals: false MaxEmptyLinesToKeep: 1 KeepEmptyLinesAtTheStartOfBlocks: false SpaceAfterCStyleCast: false diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 1a7cffd..9d91db2 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,274 +1,207 @@ -# GitHub Copilot Instructions for Kernel Module Project +# GitHub Copilot Instructions for ProcLens -## Project Overview +## Project Snapshot -This is a Linux kernel module project that provides process information through `/proc/proclens_module/`. The module displays detailed process information including memory mappings, CPU usage, memory pressure statistics, and ELF binary details. +ProcLens is a hybrid project: -Current output includes memory pressure/layout, brief network stats, open sockets, -per-process I/O statistics, and thread information. +- Linux kernel module exposing process data via `/proc/proclens_module/` +- Userspace CLI (`proclens`) for one-shot and live monitoring views +- Unit, static-analysis, and QEMU-based end-to-end test workflows -## Code Style and Quality Standards +The module currently reports memory pressure/layout, network stats, open sockets, +per-process I/O statistics, ELF metadata, and thread information. -### Kernel Coding Style (HIGHEST PRIORITY) +## Architecture and Key Files -**CRITICAL**: All kernel module code MUST pass `make checkpatch` with zero errors and zero warnings. +### Kernel Module (`src/proclens_module.c`, `src/proclens_module.h`) -- Follow Linux kernel coding style strictly -- Use tabs for indentation (width: 8) -- Maximum line length: 100 characters (kernel relaxed standard) -- Opening braces on the same line for structs, functions use K&R style -- No spaces inside parentheses: `if (condition)` not `if ( condition )` -- Use `/* */` for comments, not `//` in kernel code -- Variable declarations at the beginning of code blocks +- `/proc/proclens_module/pid` accepts a PID selection +- `/proc/proclens_module/det` prints process details +- `/proc/proclens_module/threads` prints thread details +- Main render path: memory, network, sockets, I/O, and thread sections -### Special Formatting Rules +### Userspace CLI (`src/proclens.c`, `src/proclens.h`) -When using macros like `for_each_thread`, the opening brace style may conflict with `clang-format`. Use formatting disable pragmas when needed: +- One-shot mode for a specific PID +- Live mode dashboard with section filtering and refresh loop +- Uses procfs endpoints provided by the kernel module -```c -// clang-format off -for_each_thread(task, thread) { - // code here -} -// clang-format on -``` +### Tests -### Code Quality Tools +- `src/proclens_module_tests.c`: helper-focused unit tests for module helpers +- `src/proclens_tests.c`: userspace/helper unit tests +- `src/test_multithread.c`: multithreaded runtime behavior test -Run these before every commit: +### Documentation -1. `make format` - Auto-format code with clang-format -2. `make checkpatch` - Validate against kernel coding standards (MUST PASS) -3. `make sparse` - Static analysis for kernel code -4. `make cppcheck` - Additional static analysis +- `README.md`: user-facing overview, install, usage +- `docs/TECHNICAL.md`: implementation details +- `docs/TESTING.md`: test strategy and commands +- `docs/CODE_QUALITY.md`: quality tooling details +- `docs/SCRIPTS.md`: QEMU workflow details +- `docs/RELEASE.md`: release process and labels -The pre-commit hook automatically runs format-check and cppcheck. +## Code Style and Standards -## File Organization +### Kernel Coding Priority -### Source Files (`src/`) +**CRITICAL**: `src/proclens_module.c` and `src/proclens_module.h` must stay +checkpatch-clean (0 errors, 0 warnings) for release-quality changes. -- `proclens_module.c` / `proclens_module.h` - Main kernel module and shared helpers -- `proclens_module_tests.c` - Unit tests for proclens_module helpers -- `proclens.c` / `proclens.h` - Control utility and helpers -- `proclens_tests.c` - Unit tests for proclens helpers -- `test_multithread.c` - Multi-threaded test application +- Use Linux kernel coding style +- Use tabs for kernel C indentation (tab width 8) +- Treat 100 columns as the project baseline +- Keep kernel naming in snake_case +- Prefer `/* */` comments in kernel code -### Documentation (`docs/`) +### Header and Helper Rules -- `TECHNICAL.md` - Architecture and implementation details -- `TESTING.md` - Test strategy and coverage -- `CODE_QUALITY.md` - Code quality tools and standards -- `SCRIPTS.md` - E2E testing and QEMU workflows -- `RELEASE.md` - Release process and versioning +- `src/proclens_module.h` contains helpers used by kernel and userspace tests +- Keep helpers deterministic and side-effect free when possible +- Guard kernel-only functionality with `#ifdef __KERNEL__` +- Preserve API/format contracts used by unit tests (exact output strings matter) -## Kernel Module Development Guidelines +## Formatting and Checkpatch Interoperability -### Memory Access Patterns +This repository uses `clang-format`, but checkpatch has stricter opinions in some +areas. Preserve checkpatch-sensitive formatting in kernel files when needed. -- Always use `get_task_mm()` and `mmput()` when accessing task memory management -- Check for NULL before dereferencing mm pointers: `if (mm)` -- Use `get_mm_counter()` for RSS statistics (MM_FILEPAGES, MM_ANONPAGES, etc.) -- Lock appropriately when accessing task structures +Use local formatting guards for known conflict zones: -### String Formatting +```c +/* clang-format off */ +/* checkpatch-sensitive code */ +/* clang-format on */ +``` -- Use `seq_printf()` for /proc file output -- Use `snprintf()` for userspace utilities -- Always check buffer boundaries -- Format consistently: use tables, align columns with spacing +Examples of sensitive patterns: -### Error Handling +- String formatting lines where split literals trigger checkpatch warnings +- Multi-line call layout where checkpatch flags continuation style -- Return appropriate error codes (-ENOMEM, -EFAULT, -EINVAL, etc.) -- Clean up resources on error paths -- Use `pr_err()`, `pr_warn()`, `pr_info()` for kernel logging -- Check return values from all kernel API calls +## Build, Test, and Quality Commands -### Header Files +Use these commands during development: -- `proclens_module.h` contains helper functions that work in BOTH kernel and userspace -- Use `#ifdef __KERNEL__` to separate kernel-only code -- Keep helpers pure (no side effects) for testability -- Add comprehensive documentation comments for public functions +1. `make clean` +2. `make all` +3. `make unit` +4. `make checkpatch` +5. `make sparse` +6. `make cppcheck` +7. `make check` -## Testing Requirements +Additional useful targets: -### Unit Tests +- `make module` +- `make user` +- `make build-multithread` +- `make run-multithread` +- `make format` +- `make format-check` -When adding new helper functions to headers: -- Add corresponding unit tests in `*_tests.c` files -- Test edge cases: zero, negative, maximum values -- Test boundary conditions -- Aim for 100% coverage of helper functions -- Use descriptive test names: `test_calculate_rss_pages_zero_counters()` +## Enforcement Reality (Important) -### Integration Tests +`make checkpatch`, `make sparse`, and `make cppcheck` currently mask non-zero +exits in the Makefile. Also, CI marks checkpatch/cppcheck/sparse as +`continue-on-error`. -- Update `e2e/qemu-test.sh` when adding new output fields -- Test should validate presence of new sections in module output -- Use grep patterns to match expected output format +Implication: -### Multi-threaded Testing +- Green `make check` or CI does **not** automatically mean style/analysis clean +- Always read tool output directly for kernel-file regressions +- Treat kernel-file checkpatch findings as blocking even if command exits 0 -- Use `test_multithread.c` to validate behavior under concurrent access -- Build with `make build-multithread` -- Run with `make run-multithread` - -## Common Patterns +## Testing Requirements -### Adding New Statistics +### Unit Tests -1. Add calculation helper to header file (e.g., `proclens_module.h`) -2. Add unit tests for the helper -3. Call helper in kernel module print function -4. Update `e2e/qemu-test.sh` to validate output -5. Update `docs/TECHNICAL.md` with new field documentation -6. Update `docs/TESTING.md` with test coverage info -7. Update `README.md` to reflect new features in the features list and example output +When changing helper behavior or output formats: -### Memory Pressure Statistics +- Update/add tests in `src/proclens_module_tests.c` or `src/proclens_tests.c` +- Cover edge cases and division-by-zero behavior +- Keep expectations aligned with exact output formatting where asserted -Current implementation includes: -- RSS breakdown (anonymous, file-backed, shared memory pages) -- Virtual size (VSZ) -- Swap usage -- Page faults (major/minor) -- OOM score adjustment +### Integration / E2E -Use these as examples when adding similar statistics. +When adding output fields/sections: -### I/O Statistics +1. Update kernel output implementation +2. Update userspace filtering/rendering if applicable +3. Update `e2e/qemu-test.sh` assertions +4. Update docs (`README.md`, `docs/TECHNICAL.md`, `docs/TESTING.md`) -Current implementation includes: -- Syscall byte counters (`rchar`, `wchar`) and syscall counts (`syscr`, `syscw`) -- Storage byte counters (`read_bytes`, `write_bytes`, `cancelled_write_bytes`) -- Derived metrics (`avg_read_bytes_per_syscall`, - `avg_write_bytes_per_syscall`, `io_intensity`) +## Kernel Development Guidance -When extending I/O output: -1. Add or update helper functions in `proclens_module.h` -2. Keep division-by-zero handling explicit in helper logic -3. Update `e2e/qemu-test.sh` checks for new fields -4. Keep `proclens` view filtering in sync (section key `4` for I/O) +### Memory Access and Locking -## Build System (Makefile) +- Null-check task/mm pointers before dereference +- Follow existing codebase locking patterns when reading task memory maps +- If accessing mm beyond local locked scope, use proper lifetime management + (`get_task_mm()`/`mmput()`) and document why -### Target Categories +### Error Handling -- **Build**: `all`, `module`, `user`, `build-multithread` -- **Run**: `install`, `uninstall`, `test` -- **Test**: `unit`, `run-multithread` -- **Quality**: `check`, `format`, `format-check`, `checkpatch`, `sparse`, `cppcheck` -- **Cleanup**: `clean` +- Return appropriate kernel/user error codes +- Validate all kernel API return values +- Keep cleanup paths explicit and complete +- Use `pr_err()`, `pr_warn()`, `pr_info()` consistently -### Adding New Targets +## QEMU E2E Workflow -- Group by category in help output -- Use descriptive names with hyphens -- Add `@echo` for user feedback -- Check for required tools before running +Scripts in `e2e/`: -## Development Workflow +- `qemu-setup.sh`: prepares image and host dependencies +- `qemu-run.sh`: starts VM (KVM when available, TCG fallback) +- `qemu-test.sh`: copies source into VM, builds, and validates output -### Setting Up +Run sequence: -1. Dev container automatically installs kernel headers via `.devcontainer/post-create.sh` -2. Git hooks installed automatically (runs format-check and cppcheck) -3. Kernel headers match running kernel version +1. `sudo ./e2e/qemu-setup.sh` +2. `sudo ./e2e/qemu-run.sh` +3. `sudo ./e2e/qemu-test.sh` -### Before Committing +## Release Workflow -```bash -make clean -make all -make unit -make checkpatch # MUST PASS -make sparse -make cppcheck -``` +- Releases are label-driven on merged PRs or manual dispatch +- Exactly one release label: `release:major`, `release:minor`, or `release:patch` +- `not-a-release` skips release creation +- Use the PR template and provide clean release notes -### Creating Pull Requests +## Common Gotchas -- Use the PR template (`.github/PULL_REQUEST_TEMPLATE.md`) -- Fill out the "Release Notes" section with clean, user-facing descriptions -- DO NOT paste command output or test logs in release notes -- Add appropriate release label: `release:major`, `release:minor`, `release:patch`, or `not-a-release` -- Only one release label per PR +- `make format`/`format-check` intentionally skip generated `*.mod.c` files +- Checkpatch can warn on split string literals even if clang-format is happy +- Unit tests can fail on whitespace/spacing changes in formatted output strings +- Some tools print warnings but still return success due to Makefile design -## What to Avoid - -### DON'T - -- Mix kernel and userspace code without `#ifdef __KERNEL__` -- Use `//` comments in kernel code -- Access task structures without proper locking -- Dereference pointers without NULL checks -- Ignore compiler warnings -- Submit code that fails checkpatch -- Add TODO comments without tracking them -- Use floating point in kernel code (not allowed) -- Call userspace-only functions (printf, malloc, etc.) in kernel code -- Use camelCase - kernel uses snake_case +## Do / Don't ### DO -- Run all quality checks before committing -- Add tests for all new functionality -- Update documentation when adding features -- Keep functions small and focused -- Use descriptive variable names -- Comment complex algorithms -- Check return values -- Clean up resources on error paths - -## QEMU E2E Testing - -Located in `e2e/` directory: - -- `qemu-setup.sh` - Initialize QEMU VM -- `qemu-run.sh` - Start VM in background -- `qemu-test.sh` - Run tests in VM - -Tests validate actual kernel module behavior in isolated environment. +- Keep kernel-module files checkpatch-clean +- Run unit tests after changing helper format strings +- Update docs/tests alongside feature additions +- Keep userspace and kernel output expectations in sync -## Key Functions Reference - -### Kernel Module (`proclens_module.c`) - -- `print_memory_pressure()` - Display memory pressure metrics -- `print_memory_layout()` - Show heap, stack, code segments -- `print_memory_layout_visualization()` - Show proportional memory map bars -- `print_io_stats()` - Display per-process I/O counters and derived metrics -- `print_network_stats()` - Display brief per-process network statistics -- `print_sockets()` - Display open sockets and endpoint details -- `proclens_module_show()` - Main process information output handler -- `proclens_module_threads_show()` - Thread listing output handler - -### Helpers (`proclens_module.h`) - -- `calculate_rss_pages()` - Compute resident set size from mm counters -- `pages_to_kb()` - Convert pages to kilobytes -- `calculate_total_faults()` - Sum major and minor page faults -- `is_valid_oom_score_adj()` - Validate OOM score range -- `calculate_memory_usage_percent()` - Compute memory percentage -- `is_high_memory_pressure()` - Determine if memory pressure is high -- `calculate_avg_bytes_per_syscall()` - Compute average bytes per syscall safely -- `calculate_io_intensity()` - Compute read/write storage intensity - -## Version and Release +### DON'T -- Follow semantic versioning (MAJOR.MINOR.PATCH) -- Releases triggered by PR labels -- Release notes extracted from PR "Release Notes" section -- See `docs/RELEASE.md` for full release process +- Assume `make check` success means no warnings +- Introduce formatting-only changes that break asserted output strings +- Mix kernel and userspace code paths without proper guards +- Ignore lock/lifetime requirements around task/mm access -## Additional Resources +## Additional References -- Linux Kernel Coding Style: https://www.kernel.org/doc/html/latest/process/coding-style.html -- Kernel APIs: https://www.kernel.org/doc/html/latest/ -- Project Documentation: `/docs/*.md` +- Linux Kernel Coding Style: + https://www.kernel.org/doc/html/latest/process/coding-style.html +- Kernel API docs: + https://www.kernel.org/doc/html/latest/ +- Project docs: + `docs/*.md` --- -**Remember**: Kernel code quality is non-negotiable. Always run `make checkpatch` and ensure it passes with 0 errors and 0 warnings. +**Remember**: For this repository, release-quality changes require manual review +of tool output plus strict cleanliness in `src/proclens_module.c` and +`src/proclens_module.h`, regardless of masked exit codes. diff --git a/.github/scripts/release/build-target-binaries.sh b/.github/scripts/release/build-target-binaries.sh new file mode 100644 index 0000000..a68b858 --- /dev/null +++ b/.github/scripts/release/build-target-binaries.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail + +target_arch="${TARGET_ARCH:-}" +release_tag="${RELEASE_TAG:-}" +target_kernel_base="${TARGET_KERNEL_BASE:-}" + +if [[ -z "$target_arch" || -z "$release_tag" || -z "$target_kernel_base" ]]; then + echo "TARGET_ARCH, RELEASE_TAG, and TARGET_KERNEL_BASE are required" + exit 1 +fi + +kdir="$(ls -d /usr/src/linux-headers-${target_kernel_base}-*-generic 2>/dev/null | sort -V | tail -n 1)" + +if [[ -z "${kdir:-}" ]]; then + echo "No Ubuntu ${target_kernel_base}.x generic kernel headers found" + exit 1 +fi + +echo "Using kernel headers: $kdir" + +make KDIR="$kdir" PROCLENS_VERSION="$release_tag" clean +make KDIR="$kdir" PROCLENS_VERSION="$release_tag" module +make PROCLENS_VERSION="$release_tag" user + +mkdir -p "out/${target_arch}" +cp build/proclens_module.ko "out/${target_arch}/proclens_module.ko" +cp build/proclens "out/${target_arch}/proclens" diff --git a/.github/scripts/release/calculate-new-version.sh b/.github/scripts/release/calculate-new-version.sh new file mode 100644 index 0000000..65d932f --- /dev/null +++ b/.github/scripts/release/calculate-new-version.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euo pipefail + +current="${CURRENT_VERSION:-}" +type="${VERSION_TYPE:-}" + +if [[ -z "${GITHUB_OUTPUT:-}" ]]; then + echo "GITHUB_OUTPUT is required" + exit 1 +fi + +if [[ ! "$current" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Invalid current version: $current" + exit 1 +fi + +IFS='.' read -r major minor patch <<< "$current" + +case "$type" in + major) + major=$((major + 1)) + minor=0 + patch=0 + ;; + minor) + minor=$((minor + 1)) + patch=0 + ;; + patch) + patch=$((patch + 1)) + ;; + *) + echo "No valid version type found: $type" + exit 1 + ;; +esac + +new_version="$major.$minor.$patch" +echo "version=$new_version" >> "$GITHUB_OUTPUT" +echo "tag=v$new_version" >> "$GITHUB_OUTPUT" +echo "New version: $new_version" diff --git a/.github/scripts/release/create-and-push-tag.sh b/.github/scripts/release/create-and-push-tag.sh new file mode 100644 index 0000000..2b0231e --- /dev/null +++ b/.github/scripts/release/create-and-push-tag.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail + +tag="${TAG:-}" + +if [[ -z "$tag" ]]; then + echo "TAG is required" + exit 1 +fi + +if git rev-parse "$tag" >/dev/null 2>&1; then + echo "Tag $tag already exists, skipping tag creation" + exit 0 +fi + +git tag -a "$tag" -m "Release $tag" +git push origin "$tag" diff --git a/.github/scripts/release/create-bundle-package.sh b/.github/scripts/release/create-bundle-package.sh new file mode 100644 index 0000000..e0a2d6a --- /dev/null +++ b/.github/scripts/release/create-bundle-package.sh @@ -0,0 +1,231 @@ +#!/usr/bin/env bash +set -euo pipefail + +release_tag="${RELEASE_TAG:-}" +release_version="${RELEASE_VERSION:-}" +target_kernel_base="${TARGET_KERNEL_BASE:-}" + +if [[ -z "${GITHUB_OUTPUT:-}" ]]; then + echo "GITHUB_OUTPUT is required" + exit 1 +fi + +if [[ -z "$release_tag" || -z "$release_version" || -z "$target_kernel_base" ]]; then + echo "RELEASE_TAG, RELEASE_VERSION, and TARGET_KERNEL_BASE are required" + exit 1 +fi + +pkg_name="proclens_module-binaries-${release_tag}-linux-${target_kernel_base}" +pkg_dir="dist/${pkg_name}" +archive_path="dist/${pkg_name}.tar.gz" + +mkdir -p "$pkg_dir/amd64" "$pkg_dir/arm64" + +cp dist/artifacts/binaries-amd64/proclens_module.ko "$pkg_dir/amd64/" +cp dist/artifacts/binaries-amd64/proclens "$pkg_dir/amd64/" +cp dist/artifacts/binaries-arm64/proclens_module.ko "$pkg_dir/arm64/" +cp dist/artifacts/binaries-arm64/proclens "$pkg_dir/arm64/" + +chmod +x "$pkg_dir/amd64/proclens" "$pkg_dir/arm64/proclens" + +cat > "$pkg_dir/install.sh" << 'SCRIPT' +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat << 'USAGE' +Usage: sudo ./install.sh [--arch amd64|arm64] + +Installs or updates both components: + - Kernel module: /lib/modules/$(uname -r)/extra/proclens_module.ko + - User binary: /usr/local/bin/proclens + +Behavior: + - Detects architecture automatically (x86_64 -> amd64, aarch64 -> arm64) + - If module is already loaded, unloads it first + - Replaces previously installed binaries (update-in-place) + - Runs depmod and loads module with modprobe (fallback: insmod) +USAGE +} + +if [[ "${EUID}" -ne 0 ]]; then + echo "Please run as root: sudo ./install.sh" + exit 1 +fi + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +arch="" +if [[ $# -gt 0 ]]; then + case "$1" in + --arch) + arch="${2:-}" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" + usage + exit 1 + ;; + esac +fi + +if [[ -z "$arch" ]]; then + case "$(uname -m)" in + x86_64) + arch="amd64" + ;; + aarch64|arm64) + arch="arm64" + ;; + *) + echo "Unsupported architecture: $(uname -m)" + echo "Use --arch amd64|arm64" + exit 1 + ;; + esac +fi + +if [[ "$arch" != "amd64" && "$arch" != "arm64" ]]; then + echo "Invalid --arch value: $arch" + echo "Use amd64 or arm64" + exit 1 +fi + +src_mod="$script_dir/$arch/proclens_module.ko" +src_bin="$script_dir/$arch/proclens" + +if [[ ! -f "$src_mod" || ! -f "$src_bin" ]]; then + echo "Missing release binaries for arch '$arch'" + exit 1 +fi + +krel="$(uname -r)" +mod_dir="/lib/modules/$krel/extra" +mod_dst="$mod_dir/proclens_module.ko" +bin_dst="/usr/local/bin/proclens" + +echo "Installing ProcLens binaries for arch: $arch" +echo "Kernel release: $krel" + +if lsmod | grep -q '^proclens_module '; then + echo "Module is currently loaded; unloading old version..." + modprobe -r proclens_module || rmmod proclens_module +fi + +mkdir -p "$mod_dir" +install -m 0644 "$src_mod" "$mod_dst" +install -m 0755 "$src_bin" "$bin_dst" + +depmod "$krel" + +if ! insmod "$mod_dst"; then + echo "insmod failed; trying modprobe..." + modprobe proclens_module +fi + +echo "Install/update complete." +echo "User binary: $bin_dst" +echo "Module file: $mod_dst" +echo "Check module version: cat /sys/module/proclens_module/version" +echo "Check user version: sudo proclens --version" +SCRIPT + +cat > "$pkg_dir/uninstall.sh" << 'SCRIPT' +#!/usr/bin/env bash +set -euo pipefail + +if [[ "${EUID}" -ne 0 ]]; then + echo "Please run as root: sudo ./uninstall.sh" + exit 1 +fi + +krel="$(uname -r)" +mod_dst="/lib/modules/$krel/extra/proclens_module.ko" +bin_dst="/usr/local/bin/proclens" + +if lsmod | grep -q '^proclens_module '; then + echo "Unloading proclens_module module..." + modprobe -r proclens_module || rmmod proclens_module || true +fi + +rm -f "$mod_dst" +rm -f "$bin_dst" +depmod "$krel" + +echo "Uninstall complete." +SCRIPT + +chmod +x "$pkg_dir/install.sh" "$pkg_dir/uninstall.sh" + +cat > "$pkg_dir/README-QUICKSTART.md" << 'DOC' +# Quick Start (Linux kernel __TARGET_KERNEL_BASE__.x) + +This package contains prebuilt binaries for: +- amd64 (x86_64) +- arm64 (aarch64) + +Version: __RELEASE_VERSION__ + +## 1) Choose architecture + +On target machine: + +```bash +uname -m +``` + +Use: +- `amd64/` for `x86_64` +- `arm64/` for `aarch64` + +## 2) Install or update + +```bash +sudo ./install.sh +``` + +The installer: +- Installs/updates `proclens` to `/usr/local/bin/proclens` +- Installs/updates `proclens_module.ko` to `/lib/modules/$(uname -r)/extra/proclens_module.ko` +- Unloads an already-loaded `proclens_module` before update +- Runs `depmod` and loads the module + +Optional (override architecture selection): + +```bash +sudo ./install.sh --arch amd64 +# or +sudo ./install.sh --arch arm64 +``` + +If Secure Boot is enabled, module signing may be required. + +## 3) Run user program + +```bash +sudo proclens --version +sudo proclens +``` + +## 4) Uninstall + +```bash +sudo ./uninstall.sh +``` +DOC + +sed -i \ + -e "s/__TARGET_KERNEL_BASE__/${target_kernel_base}/g" \ + -e "s/__RELEASE_VERSION__/${release_version}/g" \ + "$pkg_dir/README-QUICKSTART.md" + +tar -czf "$archive_path" -C dist "$pkg_name" +sha256sum "$archive_path" > "${archive_path}.sha256" + +echo "archive_path=$archive_path" >> "$GITHUB_OUTPUT" +echo "checksum_path=${archive_path}.sha256" >> "$GITHUB_OUTPUT" diff --git a/.github/scripts/release/create-release-notes.sh b/.github/scripts/release/create-release-notes.sh new file mode 100644 index 0000000..695e110 --- /dev/null +++ b/.github/scripts/release/create-release-notes.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +set -euo pipefail + +version="${VERSION:-}" +bump_type="${BUMP_TYPE:-}" +event_name="${EVENT_NAME:-}" +target_kernel_base="${TARGET_KERNEL_BASE:-}" + +if [[ -z "$version" || -z "$bump_type" || -z "$event_name" || -z "$target_kernel_base" ]]; then + echo "VERSION, BUMP_TYPE, EVENT_NAME, and TARGET_KERNEL_BASE are required" + exit 1 +fi + +if [[ "$event_name" == "pull_request" ]]; then + pr_title="$(jq -r '.pull_request.title // ""' "$GITHUB_EVENT_PATH")" + pr_body="$(jq -r '.pull_request.body // ""' "$GITHUB_EVENT_PATH")" + + { + printf '## Changelog\n\n' + printf '### %s\n\n' "$pr_title" + printf '%s\n\n' "$pr_body" + printf '\n---\n' + printf '**Version**: %s | **Type**: %s\n\n' "$version" "$bump_type" + printf '## Binary package includes\n\n' + printf -- '- `amd64/proclens_module.ko`\n' + printf -- '- `amd64/proclens`\n' + printf -- '- `arm64/proclens_module.ko`\n' + printf -- '- `arm64/proclens`\n' + printf -- '- `install.sh`\n' + printf -- '- `uninstall.sh`\n' + printf -- '- `README-QUICKSTART.md`\n\n' + printf 'Quick Start summary:\n\n' + printf -- '- Supports Linux kernel `%s.x`\n' "$target_kernel_base" + printf -- '- Includes binaries for `amd64` (`x86_64`) and `arm64` (`aarch64`)\n' + printf -- '- Install or update: `sudo ./install.sh`\n' + printf -- '- Run user tool: `sudo proclens`\n' + printf -- '- Uninstall: `sudo ./uninstall.sh`\n' + } > /tmp/release_notes.md +else + { + printf '## Changelog\n\n' + printf -- '- Manual release triggered via workflow dispatch.\n\n' + printf '**Version**: %s | **Type**: %s\n\n' "$version" "$bump_type" + printf '## Binary package includes\n\n' + printf -- '- `amd64/proclens_module.ko`\n' + printf -- '- `amd64/proclens`\n' + printf -- '- `arm64/proclens_module.ko`\n' + printf -- '- `arm64/proclens`\n' + printf -- '- `install.sh`\n' + printf -- '- `uninstall.sh`\n' + printf -- '- `README-QUICKSTART.md`\n\n' + printf 'Quick Start summary:\n\n' + printf -- '- Supports Linux kernel `%s.x`\n' "$target_kernel_base" + printf -- '- Includes binaries for `amd64` (`x86_64`) and `arm64` (`aarch64`)\n' + printf -- '- Install or update: `sudo ./install.sh`\n' + printf -- '- Run user tool: `sudo proclens`\n' + printf -- '- Uninstall: `sudo ./uninstall.sh`\n' + } > /tmp/release_notes.md +fi + +cat /tmp/release_notes.md diff --git a/.github/scripts/release/determine-version-type.sh b/.github/scripts/release/determine-version-type.sh new file mode 100644 index 0000000..ba2699c --- /dev/null +++ b/.github/scripts/release/determine-version-type.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -euo pipefail + +event_name="${EVENT_NAME:-}" +input_version_type="${INPUT_VERSION_TYPE:-}" +major="${RELEASE_MAJOR:-false}" +minor="${RELEASE_MINOR:-false}" +patch="${RELEASE_PATCH:-false}" + +if [[ -z "${GITHUB_OUTPUT:-}" ]]; then + echo "GITHUB_OUTPUT is required" + exit 1 +fi + +if [[ "$event_name" == "workflow_dispatch" ]]; then + echo "type=$input_version_type" >> "$GITHUB_OUTPUT" +elif [[ "$major" == "true" ]]; then + echo "type=major" >> "$GITHUB_OUTPUT" +elif [[ "$minor" == "true" ]]; then + echo "type=minor" >> "$GITHUB_OUTPUT" +elif [[ "$patch" == "true" ]]; then + echo "type=patch" >> "$GITHUB_OUTPUT" +else + echo "type=none" >> "$GITHUB_OUTPUT" +fi diff --git a/.github/scripts/release/get-current-version.sh b/.github/scripts/release/get-current-version.sh new file mode 100644 index 0000000..710b567 --- /dev/null +++ b/.github/scripts/release/get-current-version.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ -z "${GITHUB_OUTPUT:-}" ]]; then + echo "GITHUB_OUTPUT is required" + exit 1 +fi + +latest_tag="$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")" +version="${latest_tag#v}" + +echo "tag=$latest_tag" >> "$GITHUB_OUTPUT" +echo "version=$version" >> "$GITHUB_OUTPUT" +echo "Current version: $version" diff --git a/.github/scripts/release/validate-release-labels.sh b/.github/scripts/release/validate-release-labels.sh new file mode 100644 index 0000000..1facc5e --- /dev/null +++ b/.github/scripts/release/validate-release-labels.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +major="${RELEASE_MAJOR:-false}" +minor="${RELEASE_MINOR:-false}" +patch="${RELEASE_PATCH:-false}" + +count=0 +[[ "$major" == "true" ]] && count=$((count + 1)) +[[ "$minor" == "true" ]] && count=$((count + 1)) +[[ "$patch" == "true" ]] && count=$((count + 1)) + +if [[ $count -gt 1 ]]; then + echo "Error: Multiple release labels detected. Only one release label (major, minor, or patch) is allowed." + exit 1 +fi + +echo "Label validation passed: $count release label(s) found" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7b57390..ffb6e3d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -50,141 +50,44 @@ jobs: - name: Validate release labels if: github.event_name == 'pull_request' + env: + RELEASE_MAJOR: ${{ contains(github.event.pull_request.labels.*.name, 'release:major') }} + RELEASE_MINOR: ${{ contains(github.event.pull_request.labels.*.name, 'release:minor') }} + RELEASE_PATCH: ${{ contains(github.event.pull_request.labels.*.name, 'release:patch') }} run: | - MAJOR="${{ contains(github.event.pull_request.labels.*.name, 'release:major') }}" - MINOR="${{ contains(github.event.pull_request.labels.*.name, 'release:minor') }}" - PATCH="${{ contains(github.event.pull_request.labels.*.name, 'release:patch') }}" - - COUNT=0 - [[ "$MAJOR" == "true" ]] && COUNT=$((COUNT + 1)) - [[ "$MINOR" == "true" ]] && COUNT=$((COUNT + 1)) - [[ "$PATCH" == "true" ]] && COUNT=$((COUNT + 1)) - - if [[ $COUNT -gt 1 ]]; then - echo "Error: Multiple release labels detected. Only one release label (major, minor, or patch) is allowed." - exit 1 - fi - echo "Label validation passed: $COUNT release label(s) found" + bash .github/scripts/release/validate-release-labels.sh - name: Determine version bump type id: version_type + env: + EVENT_NAME: ${{ github.event_name }} + INPUT_VERSION_TYPE: ${{ inputs.version_type }} + RELEASE_MAJOR: ${{ contains(github.event.pull_request.labels.*.name, 'release:major') }} + RELEASE_MINOR: ${{ contains(github.event.pull_request.labels.*.name, 'release:minor') }} + RELEASE_PATCH: ${{ contains(github.event.pull_request.labels.*.name, 'release:patch') }} run: | - if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then - echo "type=${{ inputs.version_type }}" >> $GITHUB_OUTPUT - elif [[ "${{ contains(github.event.pull_request.labels.*.name, 'release:major') }}" == "true" ]]; then - echo "type=major" >> $GITHUB_OUTPUT - elif [[ "${{ contains(github.event.pull_request.labels.*.name, 'release:minor') }}" == "true" ]]; then - echo "type=minor" >> $GITHUB_OUTPUT - elif [[ "${{ contains(github.event.pull_request.labels.*.name, 'release:patch') }}" == "true" ]]; then - echo "type=patch" >> $GITHUB_OUTPUT - else - echo "type=none" >> $GITHUB_OUTPUT - fi + bash .github/scripts/release/determine-version-type.sh - name: Get current version id: current_version run: | - # Try to get the latest tag, default to 0.0.0 if no tags exist - LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0") - echo "tag=$LATEST_TAG" >> $GITHUB_OUTPUT - # Remove 'v' prefix if present - VERSION=${LATEST_TAG#v} - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "Current version: $VERSION" + bash .github/scripts/release/get-current-version.sh - name: Calculate new version id: new_version + env: + CURRENT_VERSION: ${{ steps.current_version.outputs.version }} + VERSION_TYPE: ${{ steps.version_type.outputs.type }} run: | - CURRENT="${{ steps.current_version.outputs.version }}" - TYPE="${{ steps.version_type.outputs.type }}" - - # Parse current version - IFS='.' read -r -a VERSION_PARTS <<< "$CURRENT" - MAJOR="${VERSION_PARTS[0]}" - MINOR="${VERSION_PARTS[1]}" - PATCH="${VERSION_PARTS[2]}" - - # Bump version based on type - case "$TYPE" in - major) - MAJOR=$((MAJOR + 1)) - MINOR=0 - PATCH=0 - ;; - minor) - MINOR=$((MINOR + 1)) - PATCH=0 - ;; - patch) - PATCH=$((PATCH + 1)) - ;; - *) - echo "No valid version type found" - exit 1 - ;; - esac - - NEW_VERSION="$MAJOR.$MINOR.$PATCH" - echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT - echo "tag=v$NEW_VERSION" >> $GITHUB_OUTPUT - echo "New version: $NEW_VERSION" + bash .github/scripts/release/calculate-new-version.sh - name: Create release notes + env: + VERSION: ${{ steps.new_version.outputs.version }} + BUMP_TYPE: ${{ steps.version_type.outputs.type }} + EVENT_NAME: ${{ github.event_name }} run: | - VERSION="${{ steps.new_version.outputs.version }}" - BUMP_TYPE="${{ steps.version_type.outputs.type }}" - - # Create release notes - if [[ "${{ github.event_name }}" == "pull_request" ]]; then - # Use PR metadata from event payload to avoid shell parsing issues - PR_TITLE=$(jq -r '.pull_request.title // ""' "$GITHUB_EVENT_PATH") - PR_BODY=$(jq -r '.pull_request.body // ""' "$GITHUB_EVENT_PATH") - - { - printf '## Changelog\n\n' - printf '### %s\n\n' "$PR_TITLE" - printf '%s\n\n' "$PR_BODY" - printf '\n---\n' - printf '**Version**: %s | **Type**: %s\n\n' "$VERSION" "$BUMP_TYPE" - printf '## Binary package includes\n\n' - printf -- '- `amd64/proclens_module.ko`\n' - printf -- '- `amd64/proclens`\n' - printf -- '- `arm64/proclens_module.ko`\n' - printf -- '- `arm64/proclens`\n' - printf -- '- `install.sh`\n' - printf -- '- `uninstall.sh`\n' - printf -- '- `README-QUICKSTART.md`\n\n' - printf 'Quick Start summary:\n\n' - printf -- '- Supports Linux kernel `%s.x`\n' "${TARGET_KERNEL_BASE}" - printf -- '- Includes binaries for `amd64` (`x86_64`) and `arm64` (`aarch64`)\n' - printf -- '- Install or update: `sudo ./install.sh`\n' - printf -- '- Run user tool: `sudo proclens`\n' - printf -- '- Uninstall: `sudo ./uninstall.sh`\n' - } > /tmp/release_notes.md - else - # Manual workflow dispatch - { - printf '## Changelog\n\n' - printf -- '- Manual release triggered via workflow dispatch.\n\n' - printf '**Version**: %s | **Type**: %s\n\n' "$VERSION" "$BUMP_TYPE" - printf '## Binary package includes\n\n' - printf -- '- `amd64/proclens_module.ko`\n' - printf -- '- `amd64/proclens`\n' - printf -- '- `arm64/proclens_module.ko`\n' - printf -- '- `arm64/proclens`\n' - printf -- '- `install.sh`\n' - printf -- '- `uninstall.sh`\n' - printf -- '- `README-QUICKSTART.md`\n\n' - printf 'Quick Start summary:\n\n' - printf -- '- Supports Linux kernel `%s.x`\n' "${TARGET_KERNEL_BASE}" - printf -- '- Includes binaries for `amd64` (`x86_64`) and `arm64` (`aarch64`)\n' - printf -- '- Install or update: `sudo ./install.sh`\n' - printf -- '- Run user tool: `sudo proclens`\n' - printf -- '- Uninstall: `sudo ./uninstall.sh`\n' - } > /tmp/release_notes.md - fi - - cat /tmp/release_notes.md + bash .github/scripts/release/create-release-notes.sh - name: Upload release metadata uses: actions/upload-artifact@v4 @@ -221,25 +124,7 @@ jobs: TARGET_ARCH: ${{ matrix.arch }} RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }} run: | - set -euo pipefail - - KDIR=$(ls -d /usr/src/linux-headers-${TARGET_KERNEL_BASE}-*-generic 2>/dev/null \ - | sort -V | tail -n 1) - - if [[ -z "${KDIR:-}" ]]; then - echo "No Ubuntu ${TARGET_KERNEL_BASE}.x generic kernel headers found" - exit 1 - fi - - echo "Using kernel headers: $KDIR" - - make KDIR="$KDIR" PROCLENS_VERSION="$RELEASE_TAG" clean - make KDIR="$KDIR" PROCLENS_VERSION="$RELEASE_TAG" module - make PROCLENS_VERSION="$RELEASE_TAG" user - - mkdir -p "out/${TARGET_ARCH}" - cp build/proclens_module.ko "out/${TARGET_ARCH}/proclens_module.ko" - cp build/proclens "out/${TARGET_ARCH}/proclens" + bash .github/scripts/release/build-target-binaries.sh - name: Upload architecture binaries uses: actions/upload-artifact@v4 @@ -279,234 +164,13 @@ jobs: RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }} RELEASE_VERSION: ${{ needs.prepare_release.outputs.release_version }} run: | - set -euo pipefail - - PKG_NAME="proclens_module-binaries-${RELEASE_TAG}-linux-${TARGET_KERNEL_BASE}" - PKG_DIR="dist/${PKG_NAME}" - ARCHIVE_PATH="dist/${PKG_NAME}.tar.gz" - - mkdir -p "$PKG_DIR/amd64" "$PKG_DIR/arm64" - - cp dist/artifacts/binaries-amd64/proclens_module.ko "$PKG_DIR/amd64/" - cp dist/artifacts/binaries-amd64/proclens "$PKG_DIR/amd64/" - cp dist/artifacts/binaries-arm64/proclens_module.ko "$PKG_DIR/arm64/" - cp dist/artifacts/binaries-arm64/proclens "$PKG_DIR/arm64/" - - chmod +x "$PKG_DIR/amd64/proclens" "$PKG_DIR/arm64/proclens" - - cat > "$PKG_DIR/install.sh" << 'EOF' - #!/usr/bin/env bash - set -euo pipefail - - usage() { - cat << 'USAGE' - Usage: sudo ./install.sh [--arch amd64|arm64] - - Installs or updates both components: - - Kernel module: /lib/modules/$(uname -r)/extra/proclens_module.ko - - User binary: /usr/local/bin/proclens - - Behavior: - - Detects architecture automatically (x86_64 -> amd64, aarch64 -> arm64) - - If module is already loaded, unloads it first - - Replaces previously installed binaries (update-in-place) - - Runs depmod and loads module with modprobe (fallback: insmod) - USAGE - } - - if [[ "${EUID}" -ne 0 ]]; then - echo "Please run as root: sudo ./install.sh" - exit 1 - fi - - SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - - ARCH="" - if [[ $# -gt 0 ]]; then - case "$1" in - --arch) - ARCH="${2:-}" - shift 2 - ;; - -h|--help) - usage - exit 0 - ;; - *) - echo "Unknown option: $1" - usage - exit 1 - ;; - esac - fi - - if [[ -z "$ARCH" ]]; then - case "$(uname -m)" in - x86_64) - ARCH="amd64" - ;; - aarch64|arm64) - ARCH="arm64" - ;; - *) - echo "Unsupported architecture: $(uname -m)" - echo "Use --arch amd64|arm64" - exit 1 - ;; - esac - fi - - if [[ "$ARCH" != "amd64" && "$ARCH" != "arm64" ]]; then - echo "Invalid --arch value: $ARCH" - echo "Use amd64 or arm64" - exit 1 - fi - - SRC_MOD="$SCRIPT_DIR/$ARCH/proclens_module.ko" - SRC_BIN="$SCRIPT_DIR/$ARCH/proclens" - - if [[ ! -f "$SRC_MOD" || ! -f "$SRC_BIN" ]]; then - echo "Missing release binaries for arch '$ARCH'" - exit 1 - fi - - KREL="$(uname -r)" - MOD_DIR="/lib/modules/$KREL/extra" - MOD_DST="$MOD_DIR/proclens_module.ko" - BIN_DST="/usr/local/bin/proclens" - - echo "Installing ProcLens binaries for arch: $ARCH" - echo "Kernel release: $KREL" - - if lsmod | grep -q '^proclens_module '; then - echo "Module is currently loaded; unloading old version..." - modprobe -r proclens_module || rmmod proclens_module - fi - - mkdir -p "$MOD_DIR" - install -m 0644 "$SRC_MOD" "$MOD_DST" - install -m 0755 "$SRC_BIN" "$BIN_DST" - - depmod "$KREL" - - if ! insmod "$MOD_DST"; then - echo "insmod failed; trying modprobe..." - modprobe proclens_module - fi - - echo "Install/update complete." - echo "User binary: $BIN_DST" - echo "Module file: $MOD_DST" - echo "Check module version: cat /sys/module/proclens_module/version" - echo "Check user version: sudo proclens --version" - EOF - - cat > "$PKG_DIR/uninstall.sh" << 'EOF' - #!/usr/bin/env bash - set -euo pipefail - - if [[ "${EUID}" -ne 0 ]]; then - echo "Please run as root: sudo ./uninstall.sh" - exit 1 - fi - - KREL="$(uname -r)" - MOD_DST="/lib/modules/$KREL/extra/proclens_module.ko" - BIN_DST="/usr/local/bin/proclens" - - if lsmod | grep -q '^proclens_module '; then - echo "Unloading proclens_module module..." - modprobe -r proclens_module || rmmod proclens_module || true - fi - - rm -f "$MOD_DST" - rm -f "$BIN_DST" - depmod "$KREL" - - echo "Uninstall complete." - EOF - - chmod +x "$PKG_DIR/install.sh" "$PKG_DIR/uninstall.sh" - - cat > "$PKG_DIR/README-QUICKSTART.md" << 'EOF' - # Quick Start (Linux kernel __TARGET_KERNEL_BASE__.x) - - This package contains prebuilt binaries for: - - amd64 (x86_64) - - arm64 (aarch64) - - Version: __RELEASE_VERSION__ - - ## 1) Choose architecture - - On target machine: - - ```bash - uname -m - ``` - - Use: - - `amd64/` for `x86_64` - - `arm64/` for `aarch64` - - ## 2) Install or update - - ```bash - sudo ./install.sh - ``` - - The installer: - - Installs/updates `proclens` to `/usr/local/bin/proclens` - - Installs/updates `proclens_module.ko` to `/lib/modules/$(uname -r)/extra/proclens_module.ko` - - Unloads an already-loaded `proclens_module` before update - - Runs `depmod` and loads the module - - Optional (override architecture selection): - - ```bash - sudo ./install.sh --arch amd64 - # or - sudo ./install.sh --arch arm64 - ``` - - If Secure Boot is enabled, module signing may be required. - - ## 3) Run user program - - ```bash - sudo proclens --version - sudo proclens - ``` - - ## 4) Uninstall - - ```bash - sudo ./uninstall.sh - ``` - EOF - - sed -i \ - -e "s/__TARGET_KERNEL_BASE__/${TARGET_KERNEL_BASE}/g" \ - -e "s/__RELEASE_VERSION__/${RELEASE_VERSION}/g" \ - "$PKG_DIR/README-QUICKSTART.md" - - tar -czf "$ARCHIVE_PATH" -C dist "$PKG_NAME" - sha256sum "$ARCHIVE_PATH" > "${ARCHIVE_PATH}.sha256" - - echo "archive_path=$ARCHIVE_PATH" >> "$GITHUB_OUTPUT" - echo "checksum_path=${ARCHIVE_PATH}.sha256" >> "$GITHUB_OUTPUT" + bash .github/scripts/release/create-bundle-package.sh - name: Create and push tag env: TAG: ${{ needs.prepare_release.outputs.release_tag }} run: | - if git rev-parse "$TAG" >/dev/null 2>&1; then - echo "Tag $TAG already exists, skipping tag creation" - exit 0 - fi - - git tag -a "$TAG" -m "Release $TAG" - git push origin "$TAG" + bash .github/scripts/release/create-and-push-tag.sh - name: Create GitHub Release uses: softprops/action-gh-release@v2 diff --git a/Makefile b/Makefile index 72259a8..e715d58 100644 --- a/Makefile +++ b/Makefile @@ -163,10 +163,13 @@ format: @echo "Formatting code with clang-format..." @if command -v clang-format >/dev/null 2>&1; then \ for file in $(SRC_DIR)/*.c $(SRC_DIR)/*.h; do \ - if [ -f "$$file" ]; then \ - echo "Formatting $$file..."; \ - clang-format -i $$file; \ - fi \ + case "$$file" in \ + *.mod.c) ;; \ + *) if [ -f "$$file" ]; then \ + echo "Formatting $$file..."; \ + clang-format -i $$file; \ + fi ;; \ + esac; \ done; \ echo "Code formatting complete!"; \ else \ @@ -178,9 +181,12 @@ format-check: @echo "Checking code formatting..." @if command -v clang-format >/dev/null 2>&1; then \ UNFORMATTED=$$(for file in $(SRC_DIR)/*.c $(SRC_DIR)/*.h; do \ - if [ -f "$$file" ]; then \ - clang-format -output-replacements-xml $$file | grep -q "pid); - printf("%sCurrent section:%s %s\n", color_code(C_YELLOW), - color_code(C_RESET), view_name(snap->view)); - printf("%sSnapshot index:%s %d/%d\n", color_code(C_YELLOW), - color_code(C_RESET), history_count - browse_offset, - history_count); + printf("%sPID:%s %s\n", color_code(C_YELLOW), color_code(C_RESET), snap->pid); + printf("%sCurrent section:%s %s\n", color_code(C_YELLOW), color_code(C_RESET), + view_name(snap->view)); + printf("%sSnapshot index:%s %d/%d\n", color_code(C_YELLOW), color_code(C_RESET), + history_count - browse_offset, history_count); puts("Sections: [1] Memory [2] Network [3] Threads [4] I/O"); puts("History: [Up/k] older [Down/j] newer [f] follow live"); puts("Commands: 1/2/3/4 switch view, 0 change PID, Ctrl+C exit"); @@ -713,9 +678,8 @@ static void free_snapshot(struct live_snapshot *snap) snap->view = VIEW_MEMORY; } -static void clear_snapshot_history(struct live_snapshot *history, - int *history_count, - int *history_next) +static void +clear_snapshot_history(struct live_snapshot *history, int *history_count, int *history_next) { int i; @@ -742,11 +706,10 @@ static void append_snapshot(struct live_snapshot *history, (*history_count)++; } -static struct live_snapshot * -get_snapshot_by_offset(struct live_snapshot *history, - int history_count, - int history_next, - int browse_offset) +static struct live_snapshot *get_snapshot_by_offset(struct live_snapshot *history, + int history_count, + int history_next, + int browse_offset) { int newest_index; int target_index; @@ -758,21 +721,17 @@ get_snapshot_by_offset(struct live_snapshot *history, return NULL; newest_index = (history_next + MAX_SNAPSHOTS - 1) % MAX_SNAPSHOTS; - target_index = - (newest_index + MAX_SNAPSHOTS - browse_offset) % MAX_SNAPSHOTS; + target_index = (newest_index + MAX_SNAPSHOTS - browse_offset) % MAX_SNAPSHOTS; return &history[target_index]; } -static int capture_live_snapshot(const char *pid_str, - int view, - struct live_snapshot *snapshot) +static int capture_live_snapshot(const char *pid_str, int view, struct live_snapshot *snapshot) { size_t len; memset(snapshot, 0, sizeof(*snapshot)); snapshot->view = view; - format_current_time(snapshot->captured_at, - sizeof(snapshot->captured_at)); + format_current_time(snapshot->captured_at, sizeof(snapshot->captured_at)); len = strlen(pid_str); if (len >= sizeof(snapshot->pid)) @@ -786,8 +745,7 @@ static int capture_live_snapshot(const char *pid_str, if (read_proc_file_alloc("det", &snapshot->det_content) < 0) goto fail; - if (view == VIEW_THREADS && - read_proc_file_alloc("threads", &snapshot->threads_content) < 0) + if (view == VIEW_THREADS && read_proc_file_alloc("threads", &snapshot->threads_content) < 0) goto fail; return 0; @@ -912,17 +870,14 @@ static void run_live_mode(void) const struct live_snapshot *current; if (browse_offset == 0) { - if (capture_live_snapshot(pid_user, view, &snapshot) == - 0) - append_snapshot(history, &history_count, - &history_next, &snapshot); + if (capture_live_snapshot(pid_user, view, &snapshot) == 0) + append_snapshot(history, &history_count, &history_next, &snapshot); } - current = get_snapshot_by_offset(history, history_count, - history_next, browse_offset); + current = + get_snapshot_by_offset(history, history_count, history_next, browse_offset); if (current) { - print_live_header(current, browse_offset, - history_count); + print_live_header(current, browse_offset, history_count); print_live_snapshot(current); } @@ -937,23 +892,19 @@ static void run_live_mode(void) if (key == '1') { view = VIEW_MEMORY; browse_offset = 0; - clear_snapshot_history(history, &history_count, - &history_next); + clear_snapshot_history(history, &history_count, &history_next); } else if (key == '2') { view = VIEW_NETWORK; browse_offset = 0; - clear_snapshot_history(history, &history_count, - &history_next); + clear_snapshot_history(history, &history_count, &history_next); } else if (key == '3') { view = VIEW_THREADS; browse_offset = 0; - clear_snapshot_history(history, &history_count, - &history_next); + clear_snapshot_history(history, &history_count, &history_next); } else if (key == '4') { view = VIEW_IO; browse_offset = 0; - clear_snapshot_history(history, &history_count, - &history_next); + clear_snapshot_history(history, &history_count, &history_next); } else if (key == 'k') { if (browse_offset + 1 < history_count) browse_offset++; @@ -969,8 +920,7 @@ static void run_live_mode(void) fprintf(stderr, "invalid PID\n"); apply_raw_mode(); browse_offset = 0; - clear_snapshot_history(history, &history_count, - &history_next); + clear_snapshot_history(history, &history_count, &history_next); } } @@ -1006,14 +956,12 @@ int main(int argc, char **argv) { init_color_output(); - if (argc > 1 && - (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0)) { + if (argc > 1 && (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0)) { print_usage(); return 0; } - if (argc > 1 && - (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-v") == 0)) { + if (argc > 1 && (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-v") == 0)) { printf("proclens %s\n", PROCLENS_VERSION); return 0; } diff --git a/src/proclens_module.c b/src/proclens_module.c index 1ba9513..2e70fb6 100644 --- a/src/proclens_module.c +++ b/src/proclens_module.c @@ -31,8 +31,7 @@ MODULE_LICENSE("Dual MIT/GPL"); // module license MODULE_VERSION(PROCLENS_VERSION); -static char buff[20] = - "1"; // the common(global) buffer between kernel and user space +static char buff[20] = "1"; // the common(global) buffer between kernel and user space static int user_pid; // the desired pid that we get from user static int number_opens; // number of opens(writes) to the pid file @@ -42,8 +41,7 @@ static struct proc_dir_entry *proclens_module_dir, *proclens_module_det_entry, static int procfile_open(struct inode *inode, struct file *file); static ssize_t procfile_read(struct file *, char __user *, size_t, loff_t *); -static ssize_t -procfile_write(struct file *, const char __user *, size_t, loff_t *); +static ssize_t procfile_write(struct file *, const char __user *, size_t, loff_t *); static void print_memory_layout(struct seq_file *m, struct task_struct *task, @@ -56,19 +54,15 @@ static void print_memory_layout(struct seq_file *m, unsigned long elf_base) { seq_puts(m, "\nMemory Layout:\n"); - seq_puts(m, - "----------------------------------------------------------"); + seq_puts(m, "----------------------------------------------------------"); seq_puts(m, "----------------------\n"); - seq_printf(m, " Code Section: 0x%016lx - 0x%016lx\n", - task->mm->start_code, task->mm->end_code); - seq_printf(m, " Data Section: 0x%016lx - 0x%016lx\n", - task->mm->start_data, task->mm->end_data); - seq_printf(m, " BSS Section: 0x%016lx - 0x%016lx\n", bss_start, - bss_end); - seq_printf(m, " Heap: 0x%016lx - 0x%016lx\n", heap_start, - heap_end); - seq_printf(m, " Stack: 0x%016lx - 0x%016lx\n", stack_start, - stack_end); + seq_printf(m, " Code Section: 0x%016lx - 0x%016lx\n", task->mm->start_code, + task->mm->end_code); + seq_printf(m, " Data Section: 0x%016lx - 0x%016lx\n", task->mm->start_data, + task->mm->end_data); + seq_printf(m, " BSS Section: 0x%016lx - 0x%016lx\n", bss_start, bss_end); + seq_printf(m, " Heap: 0x%016lx - 0x%016lx\n", heap_start, heap_end); + seq_printf(m, " Stack: 0x%016lx - 0x%016lx\n", stack_start, stack_end); seq_printf(m, " ELF Base: 0x%016lx\n", elf_base); } @@ -122,10 +116,8 @@ static void print_memory_layout_visualization(struct seq_file *m, return; /* Calculate proportional widths */ - for (i = 0; i < 5; i++) { - widths[i] = calculate_bar_width(regions[i].size, total_size, - BAR_WIDTH); - } + for (i = 0; i < 5; i++) + widths[i] = calculate_bar_width(regions[i].size, total_size, BAR_WIDTH); seq_puts(m, "\n"); seq_puts(m, "Memory Layout Visualization:\n"); @@ -136,8 +128,7 @@ static void print_memory_layout_visualization(struct seq_file *m, /* Generate visualization for each region */ for (i = 0; i < 5; i++) { - if (generate_region_visualization(®ions[i], widths[i], - BAR_WIDTH, viz_buf, + if (generate_region_visualization(®ions[i], widths[i], BAR_WIDTH, viz_buf, sizeof(viz_buf)) > 0) { seq_puts(m, viz_buf); } @@ -149,8 +140,7 @@ static void print_memory_layout_visualization(struct seq_file *m, seq_puts(m, "----------------------\n"); } -static void print_thread_info_line(struct seq_file *m, - struct task_struct *thread) +static void print_thread_info_line(struct seq_file *m, struct task_struct *thread) { char state_char; char cpu_affinity[32]; @@ -169,13 +159,10 @@ static void print_thread_info_line(struct seq_file *m, /* Build CPU affinity mask array (show first 8 CPUs) */ for (i = 0; i < 8 && i < nr_cpu_ids; i++) cpu_mask[i] = cpumask_test_cpu(i, &thread->cpus_mask) ? 1 : 0; - build_cpu_affinity_string(cpu_mask, 8, cpu_affinity, - sizeof(cpu_affinity)); + build_cpu_affinity_string(cpu_mask, 8, cpu_affinity, sizeof(cpu_affinity)); - seq_printf(m, - "%-5d %-15.15s %4llu.%02llu %c %4d %4d %s\n", - thread->pid, thread->comm, (usage_permyriad / 100), - (usage_permyriad % 100), state_char, + seq_printf(m, "%-5d %-15.15s %4llu.%02llu %c %4d %4d %s\n", thread->pid, + thread->comm, (usage_permyriad / 100), (usage_permyriad % 100), state_char, thread->prio - 120, /* Convert to nice value */ task_nice(thread), cpu_affinity); } @@ -186,8 +173,7 @@ static void print_thread_info_line(struct seq_file *m, * Iterates through VMAs to find the one containing start_stack * Returns the vm_start (lower bound) of the stack VMA, or 0 if not found */ -static unsigned long find_stack_vma_end(struct mm_struct *mm, - unsigned long start_stack) +static unsigned long find_stack_vma_end(struct mm_struct *mm, unsigned long start_stack) { struct vm_area_struct *vma; struct ma_state mas; @@ -197,8 +183,7 @@ static unsigned long find_stack_vma_end(struct mm_struct *mm, mas_for_each(&mas, vma, ULONG_MAX) { /* Use helper to check if start_stack is within this VMA */ - if (is_address_in_range(start_stack, vma->vm_start, - vma->vm_end)) { + if (is_address_in_range(start_stack, vma->vm_start, vma->vm_end)) { /* Found the stack VMA */ stack_end = vma->vm_start; /* Stack grows down */ break; @@ -251,22 +236,17 @@ static void print_memory_pressure(struct seq_file *m, struct task_struct *task) oom_score = task->signal->oom_score_adj; seq_puts(m, "\nMemory Pressure Statistics:\n"); - seq_puts(m, - "----------------------------------------------------------"); + seq_puts(m, "----------------------------------------------------------"); seq_puts(m, "----------------------\n"); /* Display RSS breakdown */ seq_printf(m, " RSS (Resident): %lu KB\n", rss_kb); - seq_printf(m, " - Anonymous: %lu KB\n", - anon_pages << (PAGE_SHIFT - 10)); - seq_printf(m, " - File-backed: %lu KB\n", - file_pages << (PAGE_SHIFT - 10)); - seq_printf(m, " - Shared Mem: %lu KB\n", - shmem_pages << (PAGE_SHIFT - 10)); + seq_printf(m, " - Anonymous: %lu KB\n", anon_pages << (PAGE_SHIFT - 10)); + seq_printf(m, " - File-backed: %lu KB\n", file_pages << (PAGE_SHIFT - 10)); + seq_printf(m, " - Shared Mem: %lu KB\n", shmem_pages << (PAGE_SHIFT - 10)); /* Virtual memory size */ - seq_printf(m, " VSZ (Virtual): %lu KB\n", - mm->total_vm << (PAGE_SHIFT - 10)); + seq_printf(m, " VSZ (Virtual): %lu KB\n", mm->total_vm << (PAGE_SHIFT - 10)); /* Swap usage */ seq_printf(m, " Swap Usage: %lu KB\n", swap_kb); @@ -284,8 +264,7 @@ static void print_memory_pressure(struct seq_file *m, struct task_struct *task) */ seq_printf(m, " OOM Score Adj: %ld\n", oom_score); - seq_puts(m, - "----------------------------------------------------------"); + seq_puts(m, "----------------------------------------------------------"); seq_puts(m, "----------------------\n"); } @@ -312,8 +291,7 @@ static void print_io_stats(struct seq_file *m, struct task_struct *task) syscw = (u64)READ_ONCE(task->ioac.syscw); read_bytes = (u64)READ_ONCE(task->ioac.read_bytes); write_bytes = (u64)READ_ONCE(task->ioac.write_bytes); - cancelled_write_bytes = - (u64)READ_ONCE(task->ioac.cancelled_write_bytes); + cancelled_write_bytes = (u64)READ_ONCE(task->ioac.cancelled_write_bytes); avg_read_bytes = calculate_avg_bytes_per_syscall(rchar, syscr); avg_write_bytes = calculate_avg_bytes_per_syscall(wchar, syscw); @@ -410,10 +388,9 @@ static void print_network_stats(struct seq_file *m, struct task_struct *task) if (sk->sk_family == AF_UNIX) unix_count++; - try_insert_top_talker(top_talkers, &top_talker_len, - PROCLENS_MODULE_TOP_TALKERS_MAX, fd, - sk->sk_family, sk->sk_protocol, - socket_rx_bytes, socket_tx_bytes); + try_insert_top_talker(top_talkers, &top_talker_len, PROCLENS_MODULE_TOP_TALKERS_MAX, + fd, sk->sk_family, sk->sk_protocol, socket_rx_bytes, + socket_tx_bytes); ifindex = READ_ONCE(sk->sk_bound_dev_if); if (!ifindex) @@ -422,16 +399,15 @@ static void print_network_stats(struct seq_file *m, struct task_struct *task) if (ifindex > 0) { dev = dev_get_by_index_rcu(sock_net(sk), ifindex); dev_name = dev ? dev->name : "unknown"; - add_netdev_count(netdevs, &netdev_len, - PROCLENS_MODULE_NETDEV_MAX, ifindex, + add_netdev_count(netdevs, &netdev_len, PROCLENS_MODULE_NETDEV_MAX, ifindex, dev_name); } } rcu_read_unlock(); - seq_printf(m, "sockets_total: %d (tcp: %d, udp: %d, unix: %d)\n", - socket_total, tcp_count, udp_count, unix_count); + seq_printf(m, "sockets_total: %d (tcp: %d, udp: %d, unix: %d)\n", socket_total, tcp_count, + udp_count, unix_count); seq_printf(m, "rx_packets: %llu\n", rx_packets); seq_printf(m, "tx_packets: %llu\n", tx_packets); seq_printf(m, "rx_bytes: %llu\n", rx_bytes); @@ -444,8 +420,7 @@ static void print_network_stats(struct seq_file *m, struct task_struct *task) } else { seq_puts(m, "net_devices: "); for (i = 0; i < netdev_len; i++) { - seq_printf(m, "%s=%d", netdevs[i].name, - netdevs[i].count); + seq_printf(m, "%s=%d", netdevs[i].name, netdevs[i].count); if (i + 1 < netdev_len) seq_puts(m, " "); } @@ -459,8 +434,7 @@ static void print_network_stats(struct seq_file *m, struct task_struct *task) } for (i = 0; i < top_talker_len; i++) { - seq_printf(m, " #%d FD %u Proto: %-5s Family: %-10s ", i + 1, - top_talkers[i].fd, + seq_printf(m, " #%d FD %u Proto: %-5s Family: %-10s ", i + 1, top_talkers[i].fd, socket_protocol_to_string(top_talkers[i].protocol), socket_family_to_string(top_talkers[i].family)); seq_printf(m, "RX bytes=%llu TX bytes=%llu Total bytes=%llu\n", @@ -499,8 +473,7 @@ static void print_sockets(struct seq_file *m, struct task_struct *task) return; seq_puts(m, "\nOpen Sockets:\n"); - seq_puts(m, - "----------------------------------------------------------"); + seq_puts(m, "----------------------------------------------------------"); seq_puts(m, "----------------------\n"); rcu_read_lock(); @@ -527,11 +500,8 @@ static void print_sockets(struct seq_file *m, struct task_struct *task) state = sk->sk_state; protocol = socket_protocol_to_string(sk->sk_protocol); - seq_printf(m, - " [FD %u] Family: %-10s Type: %-8s State: %-12s " - "Proto: %-5s\n", - fd, socket_family_to_string(family), - socket_type_to_string(type), + seq_printf(m, " [FD %u] Family: %-10s Type: %-8s State: %-12s Proto: %-5s\n", fd, + socket_family_to_string(family), socket_type_to_string(type), socket_state_to_string(state), protocol); if (sk->sk_protocol == IPPROTO_TCP) { @@ -540,9 +510,9 @@ static void print_sockets(struct seq_file *m, struct task_struct *task) tx_packets = (u64)READ_ONCE(tp->segs_out); rx_bytes = (u64)READ_ONCE(tp->bytes_received); tx_bytes = (u64)READ_ONCE(tp->bytes_sent); - traffic_len = format_tcp_traffic_line( - rx_packets, rx_bytes, tx_packets, tx_bytes, - traffic_line, sizeof(traffic_line)); + traffic_len = + format_tcp_traffic_line(rx_packets, rx_bytes, tx_packets, tx_bytes, + traffic_line, sizeof(traffic_line)); if (traffic_len > 0) seq_puts(m, traffic_line); } else if (sk->sk_protocol == IPPROTO_UDP) { @@ -550,9 +520,9 @@ static void print_sockets(struct seq_file *m, struct task_struct *task) udp_tx_packets = skb_queue_len(&sk->sk_write_queue); rx_bytes = (u64)sk_rmem_alloc_get(sk); tx_bytes = (u64)READ_ONCE(sk->sk_wmem_queued); - traffic_len = format_udp_traffic_line( - udp_rx_packets, rx_bytes, udp_tx_packets, - tx_bytes, traffic_line, sizeof(traffic_line)); + traffic_len = format_udp_traffic_line(udp_rx_packets, rx_bytes, + udp_tx_packets, tx_bytes, + traffic_line, sizeof(traffic_line)); if (traffic_len > 0) seq_puts(m, traffic_line); } @@ -572,16 +542,11 @@ static void print_sockets(struct seq_file *m, struct task_struct *task) saddr_h = ntohl(saddr); daddr_h = ntohl(daddr); - seq_printf(m, - " Local: %u.%u.%u.%u:%u", - (saddr_h >> 24) & 0xFF, - (saddr_h >> 16) & 0xFF, - (saddr_h >> 8) & 0xFF, - saddr_h & 0xFF, ntohs(sport)); - seq_printf(m, " Remote: %u.%u.%u.%u:%u\n", - (daddr_h >> 24) & 0xFF, - (daddr_h >> 16) & 0xFF, - (daddr_h >> 8) & 0xFF, + seq_printf(m, " Local: %u.%u.%u.%u:%u", + (saddr_h >> 24) & 0xFF, (saddr_h >> 16) & 0xFF, + (saddr_h >> 8) & 0xFF, saddr_h & 0xFF, ntohs(sport)); + seq_printf(m, " Remote: %u.%u.%u.%u:%u\n", (daddr_h >> 24) & 0xFF, + (daddr_h >> 16) & 0xFF, (daddr_h >> 8) & 0xFF, daddr_h & 0xFF, ntohs(dport)); } } else if (family == AF_INET6 && sk->sk_prot) { @@ -598,8 +563,7 @@ static void print_sockets(struct seq_file *m, struct task_struct *task) for (i = 0; i < 8; i++) { if (i > 0) seq_puts(m, ":"); - seq_printf(m, "%04x", - ntohs(saddr6->s6_addr16[i])); + seq_printf(m, "%04x", ntohs(saddr6->s6_addr16[i])); } seq_printf(m, ":%u", ntohs(sport)); @@ -607,8 +571,7 @@ static void print_sockets(struct seq_file *m, struct task_struct *task) for (i = 0; i < 8; i++) { if (i > 0) seq_puts(m, ":"); - seq_printf(m, "%04x", - ntohs(daddr6->s6_addr16[i])); + seq_printf(m, "%04x", ntohs(daddr6->s6_addr16[i])); } seq_printf(m, ":%u\n", ntohs(dport)); } @@ -620,8 +583,7 @@ static void print_sockets(struct seq_file *m, struct task_struct *task) if (socket_count == 0) seq_puts(m, " No open sockets\n"); - seq_puts(m, - "----------------------------------------------------------"); + seq_puts(m, "----------------------------------------------------------"); seq_puts(m, "----------------------\n"); } @@ -684,28 +646,25 @@ static int proclens_module_show(struct seq_file *m, void *v) /* BSS: uninitialized data between end_data and start_brk * May be zero-length in modern binaries */ - compute_bss_range(task->mm->end_data, task->mm->start_brk, &bss_start, - &bss_end); + compute_bss_range(task->mm->end_data, task->mm->start_brk, &bss_start, &bss_end); /* Heap: brk-based heap from start_brk to current brk * Note: Does not include mmap-based allocations (arena heap) */ - compute_heap_range(task->mm->start_brk, task->mm->brk, &heap_start, - &heap_end); + compute_heap_range(task->mm->start_brk, task->mm->brk, &heap_start, &heap_end); mmap_read_unlock(task->mm); // now print the information we want to the det file seq_printf(m, "Process ID: %d\n", task->pid); seq_printf(m, "Name: %s\n", task->comm); - seq_printf(m, "CPU Usage: %llu.%02llu%%\n", - (usage_permyriad / 100), (usage_permyriad % 100)); + seq_printf(m, "CPU Usage: %llu.%02llu%%\n", (usage_permyriad / 100), + (usage_permyriad % 100)); print_memory_pressure(m, task); - print_memory_layout(m, task, bss_start, bss_end, heap_start, heap_end, - stack_start, stack_end, elf_base); - print_memory_layout_visualization(m, task, bss_start, bss_end, - heap_start, heap_end, stack_start, - stack_end); + print_memory_layout(m, task, bss_start, bss_end, heap_start, heap_end, stack_start, + stack_end, elf_base); + print_memory_layout_visualization(m, task, bss_start, bss_end, heap_start, heap_end, + stack_start, stack_end); print_network_stats(m, task); print_sockets(m, task); print_io_stats(m, task); @@ -748,8 +707,7 @@ static int proclens_module_threads_show(struct seq_file *m, void *v) // clang-format on rcu_read_unlock(); - seq_puts(m, - "----------------------------------------------------------"); + seq_puts(m, "----------------------------------------------------------"); seq_puts(m, "----------------------\n"); seq_printf(m, "Total threads: %d\n", thread_count); @@ -799,10 +757,7 @@ static int procfile_open(struct inode *inode, struct file *file) // when we cat elf file this function will be run (this is useless here) // because our info is in det file not here! -static ssize_t procfile_read(struct file *file, - char __user *buffer, - size_t length, - loff_t *offset) +static ssize_t procfile_read(struct file *file, char __user *buffer, size_t length, loff_t *offset) { static int finished; char tmp[64]; @@ -828,10 +783,8 @@ static ssize_t procfile_read(struct file *file, } // most important function of elf! called when we write some characters into it -static ssize_t procfile_write(struct file *file, - const char __user *buffer, - size_t length, - loff_t *offset) +static ssize_t +procfile_write(struct file *file, const char __user *buffer, size_t length, loff_t *offset) { char input_buf[sizeof(buff)]; size_t to_copy; @@ -861,18 +814,16 @@ static int proclens_module_init(void) return -ENOMEM; // 0644 means owner read/write, others read-only - proclens_module_det_entry = proc_create( - "det", 0644, proclens_module_dir, &proclens_module_det_ops); + proclens_module_det_entry = + proc_create("det", 0644, proclens_module_dir, &proclens_module_det_ops); // create proc file det with proclens_module_det_ops pr_info("det initiated; /proc/proclens_module/det created\n"); - proclens_module_pid_entry = - proc_create("pid", 0644, proclens_module_dir, &write_pops); + proclens_module_pid_entry = proc_create("pid", 0644, proclens_module_dir, &write_pops); // create proc file pid with write_pops pr_info("pid initiated; /proc/proclens_module/pid created\n"); proclens_module_threads_entry = - proc_create("threads", 0644, proclens_module_dir, - &proclens_module_threads_ops); + proc_create("threads", 0644, proclens_module_dir, &proclens_module_threads_ops); // create proc file threads with proclens_module_threads_ops pr_info("threads initiated; /proc/proclens_module/threads created\n"); @@ -890,8 +841,7 @@ static void proclens_module_exit(void) proc_remove(proclens_module_pid_entry); pr_info("proclens_module exited; /proc/proclens_module/pid deleted\n"); proc_remove(proclens_module_threads_entry); - pr_info("proclens_module exited; /proc/proclens_module/threads " - "deleted\n"); + pr_info("proclens_module exited; /proc/proclens_module/threads deleted\n"); proc_remove(proclens_module_dir); } diff --git a/src/proclens_module.h b/src/proclens_module.h index b8c724b..31d4531 100644 --- a/src/proclens_module.h +++ b/src/proclens_module.h @@ -21,10 +21,8 @@ typedef u64 eh_u64; * Clears destination to avoid stale bytes from previous writes. * Returns bytes copied from src. */ -static inline size_t update_pid_write_buffer(char *dst, - size_t dst_size, - const char *src, - size_t src_len) +static inline size_t +update_pid_write_buffer(char *dst, size_t dst_size, const char *src, size_t src_len) { size_t copy_len; @@ -57,8 +55,7 @@ static inline int procfile_read_should_finish(int *finished) } /* Format procfile read output into caller-provided buffer. */ -static inline int -format_procfile_output(const char *src, char *out, int out_size) +static inline int format_procfile_output(const char *src, char *out, int out_size) { if (!src || !out || out_size <= 0) return 0; @@ -121,9 +118,8 @@ static inline int compute_heap_range(unsigned long start_brk, * Used for finding VMAs that contain specific addresses like stack * Returns 1 if addr is within [range_start, range_end), 0 otherwise */ -static inline int is_address_in_range(unsigned long addr, - unsigned long range_start, - unsigned long range_end) +static inline int +is_address_in_range(unsigned long addr, unsigned long range_start, unsigned long range_end) { if (range_start > range_end) return 0; @@ -176,10 +172,8 @@ static inline char get_thread_state_char(unsigned long state) * out_buf: output buffer for the affinity string * buf_size: size of output buffer */ -static inline int build_cpu_affinity_string(const int *cpu_mask, - int max_cpus, - char *out_buf, - int buf_size) +static inline int +build_cpu_affinity_string(const int *cpu_mask, int max_cpus, char *out_buf, int buf_size) { int i, len = 0; int has_cpu = 0; @@ -190,8 +184,7 @@ static inline int build_cpu_affinity_string(const int *cpu_mask, for (i = 0; i < max_cpus && len < buf_size - 2; i++) { if (cpu_mask[i]) { has_cpu = 1; - len += snprintf(out_buf + len, buf_size - len, "%d,", - i); + len += snprintf(out_buf + len, buf_size - len, "%d,", i); } } @@ -293,15 +286,13 @@ static inline void try_insert_top_talker(struct top_talker_entry *list, /* Format size with appropriate unit (B, KB, MB) * Returns number of characters written (excluding null terminator) */ -static inline int -format_size_with_unit(unsigned long size, char *out_buf, int buf_size) +static inline int format_size_with_unit(unsigned long size, char *out_buf, int buf_size) { if (!out_buf || buf_size < 10) return 0; if (size >= 1024 * 1024) - return snprintf(out_buf, buf_size, "%lu MB", - size / (1024 * 1024)); + return snprintf(out_buf, buf_size, "%lu MB", size / (1024 * 1024)); else if (size >= 1024) return snprintf(out_buf, buf_size, "%lu KB", size / 1024); else @@ -312,9 +303,8 @@ format_size_with_unit(unsigned long size, char *out_buf, int buf_size) * Ensures at least 1 character width for non-zero sizes * Returns the proportional width */ -static inline int calculate_bar_width(unsigned long region_size, - unsigned long total_size, - int bar_width) +static inline int +calculate_bar_width(unsigned long region_size, unsigned long total_size, int bar_width) { int width; @@ -341,12 +331,11 @@ static inline int calculate_bar_width(unsigned long region_size, * out_buf: output buffer * buf_size: size of output buffer */ -static inline int -generate_region_visualization(const struct memory_region *region, - int width, - int bar_width, - char *out_buf, - int buf_size) +static inline int generate_region_visualization(const struct memory_region *region, + int width, + int bar_width, + char *out_buf, + int buf_size) { char size_str[32]; int len = 0; @@ -362,8 +351,7 @@ generate_region_visualization(const struct memory_region *region, format_size_with_unit(region->size, size_str, sizeof(size_str)); /* Write region header */ - len += snprintf(out_buf + len, buf_size - len, "%-5s (%s)\n", - region->name, size_str); + len += snprintf(out_buf + len, buf_size - len, "%-5s (%s)\n", region->name, size_str); /* Write bar */ len += snprintf(out_buf + len, buf_size - len, " ["); @@ -405,8 +393,7 @@ static inline void add_netdev_count(struct netdev_count *list, list[*list_len].ifindex = ifindex; list[*list_len].count = 1; - snprintf(list[*list_len].name, sizeof(list[*list_len].name), "%s", - name); + snprintf(list[*list_len].name, sizeof(list[*list_len].name), "%s", name); (*list_len)++; } @@ -416,9 +403,8 @@ static inline void add_netdev_count(struct netdev_count *list, * RSS = Anonymous pages + File-backed pages + Shared memory pages * Returns total RSS in pages */ -static inline unsigned long calculate_rss_pages(unsigned long anon_pages, - unsigned long file_pages, - unsigned long shmem_pages) +static inline unsigned long +calculate_rss_pages(unsigned long anon_pages, unsigned long file_pages, unsigned long shmem_pages) { return anon_pages + file_pages + shmem_pages; } @@ -460,8 +446,8 @@ static inline int is_valid_oom_score_adj(long oom_score_adj) * Returns (used_kb * 100) / total_kb * Returns 0 if total_kb is 0 to avoid division by zero */ -static inline unsigned long -calculate_memory_usage_percent(unsigned long used_kb, unsigned long total_kb) +static inline unsigned long calculate_memory_usage_percent(unsigned long used_kb, + unsigned long total_kb) { if (total_kb == 0) return 0; @@ -479,17 +465,15 @@ static inline int format_page_fault_stats(unsigned long major_faults, if (!out_buf || buf_size < 50) return 0; - return snprintf(out_buf, buf_size, "Major: %lu, Minor: %lu, Total: %lu", - major_faults, minor_faults, - major_faults + minor_faults); + return snprintf(out_buf, buf_size, "Major: %lu, Minor: %lu, Total: %lu", major_faults, + minor_faults, major_faults + minor_faults); } /* Check if memory pressure is high based on swap usage * Returns 1 if swap usage indicates high memory pressure, 0 otherwise * Threshold: swap > 10% of RSS indicates pressure */ -static inline int is_high_memory_pressure(unsigned long rss_kb, - unsigned long swap_kb) +static inline int is_high_memory_pressure(unsigned long rss_kb, unsigned long swap_kb) { if (rss_kb == 0) return (swap_kb > 0); @@ -501,8 +485,7 @@ static inline int is_high_memory_pressure(unsigned long rss_kb, /* Calculate average bytes per syscall * Returns 0 when syscall_count is 0 to avoid division by zero */ -static inline eh_u64 calculate_avg_bytes_per_syscall(eh_u64 total_bytes, - eh_u64 syscall_count) +static inline eh_u64 calculate_avg_bytes_per_syscall(eh_u64 total_bytes, eh_u64 syscall_count) { if (syscall_count == 0) return 0; @@ -511,8 +494,7 @@ static inline eh_u64 calculate_avg_bytes_per_syscall(eh_u64 total_bytes, } /* Calculate storage I/O intensity as read + write bytes */ -static inline eh_u64 calculate_io_intensity(eh_u64 read_bytes, - eh_u64 write_bytes) +static inline eh_u64 calculate_io_intensity(eh_u64 read_bytes, eh_u64 write_bytes) { return read_bytes + write_bytes; } @@ -588,8 +570,7 @@ static inline int format_tcp_traffic_line(eh_u64 rx_packets, return 0; return snprintf(out_buf, buf_size, - " Traffic: RX pkts=%llu bytes=%llu TX " - "pkts=%llu bytes=%llu\n", + " Traffic: RX pkts=%llu bytes=%llu TX pkts=%llu bytes=%llu\n", rx_packets, rx_bytes, tx_packets, tx_bytes); } @@ -606,10 +587,11 @@ static inline int format_udp_traffic_line(eh_u64 rx_packets, if (!out_buf || buf_size < 90) return 0; + /* clang-format off */ return snprintf(out_buf, buf_size, - " Traffic: RX pkts=%llu bytes=%llu TX " - "pkts=%llu bytes=%llu (queued)\n", + " Traffic: RX pkts=%llu bytes=%llu TX pkts=%llu bytes=%llu (queued)\n", rx_packets, rx_bytes, tx_packets, tx_bytes); + /* clang-format on */ } /* Convert TCP socket state value to string representation diff --git a/src/proclens_module_tests.c b/src/proclens_module_tests.c index c7e4af2..3145f47 100644 --- a/src/proclens_module_tests.c +++ b/src/proclens_module_tests.c @@ -120,8 +120,7 @@ int main(void) assert(strcmp(size_buf, "1 MB") == 0); assert(len > 0); - len = format_size_with_unit(5 * 1024 * 1024, size_buf, - sizeof(size_buf)); + len = format_size_with_unit(5 * 1024 * 1024, size_buf, sizeof(size_buf)); assert(strcmp(size_buf, "5 MB") == 0); /* Test edge cases */ @@ -164,8 +163,7 @@ int main(void) region.name = "CODE"; region.size = 1024 * 1024; /* 1 MB */ region.exists = 1; - len = generate_region_visualization(®ion, 25, 50, viz_buf, - sizeof(viz_buf)); + len = generate_region_visualization(®ion, 25, 50, viz_buf, sizeof(viz_buf)); assert(len > 0); assert(strstr(viz_buf, "CODE")); assert(strstr(viz_buf, "1 MB")); @@ -175,8 +173,7 @@ int main(void) region.name = "DATA"; region.size = 512; /* 512 B */ region.exists = 1; - len = generate_region_visualization(®ion, 5, 50, viz_buf, - sizeof(viz_buf)); + len = generate_region_visualization(®ion, 5, 50, viz_buf, sizeof(viz_buf)); assert(len > 0); assert(strstr(viz_buf, "DATA")); assert(strstr(viz_buf, "512 B")); @@ -185,16 +182,14 @@ int main(void) region.name = "BSS"; region.size = 0; region.exists = 0; - len = generate_region_visualization(®ion, 0, 50, viz_buf, - sizeof(viz_buf)); + len = generate_region_visualization(®ion, 0, 50, viz_buf, sizeof(viz_buf)); assert(len == 0); /* Should return 0 for non-existent regions */ /* Test zero size but exists flag set */ region.name = "HEAP"; region.size = 0; region.exists = 1; - len = generate_region_visualization(®ion, 0, 50, viz_buf, - sizeof(viz_buf)); + len = generate_region_visualization(®ion, 0, 50, viz_buf, sizeof(viz_buf)); assert(len == 0); /* Should return 0 for zero size */ /* Test single CPU at start */ @@ -356,8 +351,7 @@ int main(void) assert(strstr(fault_buf, "Total: 0")); /* Large values */ - len = format_page_fault_stats(500000, 2500000, fault_buf, - sizeof(fault_buf)); + len = format_page_fault_stats(500000, 2500000, fault_buf, sizeof(fault_buf)); assert(len > 0); assert(strstr(fault_buf, "Total: 3000000")); @@ -400,8 +394,7 @@ int main(void) assert(high_pressure == 0); /* Large values */ - high_pressure = - is_high_memory_pressure(1024 * 1024, 200 * 1024); /* ~19% */ + high_pressure = is_high_memory_pressure(1024 * 1024, 200 * 1024); /* ~19% */ assert(high_pressure == 1); /* I/O Statistics helper tests */ @@ -484,14 +477,12 @@ int main(void) /* traffic line formatting tests */ char traffic_buf[200]; - len = format_tcp_traffic_line(10, 2048, 11, 3072, traffic_buf, - sizeof(traffic_buf)); + len = format_tcp_traffic_line(10, 2048, 11, 3072, traffic_buf, sizeof(traffic_buf)); assert(len > 0); assert(strcmp(traffic_buf, " Traffic: RX pkts=10 bytes=2048 " "TX pkts=11 bytes=3072\n") == 0); - len = format_udp_traffic_line(3, 512, 4, 1024, traffic_buf, - sizeof(traffic_buf)); + len = format_udp_traffic_line(3, 512, 4, 1024, traffic_buf, sizeof(traffic_buf)); assert(len > 0); assert(strcmp(traffic_buf, " Traffic: RX pkts=3 bytes=512 TX " "pkts=4 bytes=1024 (queued)\n") == 0); @@ -508,18 +499,15 @@ int main(void) memset(talkers, 0, sizeof(talkers)); - try_insert_top_talker(talkers, &talker_len, - PROCLENS_MODULE_TOP_TALKERS_MAX, 10, 2, 6, 500, + try_insert_top_talker(talkers, &talker_len, PROCLENS_MODULE_TOP_TALKERS_MAX, 10, 2, 6, 500, 500); assert(talker_len == 1); assert(talkers[0].fd == 10); assert(talkers[0].total_bytes == 1000); - try_insert_top_talker(talkers, &talker_len, - PROCLENS_MODULE_TOP_TALKERS_MAX, 20, 2, 6, 100, + try_insert_top_talker(talkers, &talker_len, PROCLENS_MODULE_TOP_TALKERS_MAX, 20, 2, 6, 100, 100); - try_insert_top_talker(talkers, &talker_len, - PROCLENS_MODULE_TOP_TALKERS_MAX, 30, 1, 17, 700, + try_insert_top_talker(talkers, &talker_len, PROCLENS_MODULE_TOP_TALKERS_MAX, 30, 1, 17, 700, 500); assert(talker_len == 3); assert(talkers[0].fd == 30); @@ -528,16 +516,15 @@ int main(void) assert(talkers[2].fd == 20); /* Replaces the current lowest-ranked entry */ - try_insert_top_talker(talkers, &talker_len, - PROCLENS_MODULE_TOP_TALKERS_MAX, 40, 10, 6, 300, + try_insert_top_talker(talkers, &talker_len, PROCLENS_MODULE_TOP_TALKERS_MAX, 40, 10, 6, 300, 300); assert(talker_len == 3); assert(talkers[2].fd == 40); assert(talkers[2].total_bytes == 600); /* Zero-traffic entries are ignored */ - try_insert_top_talker(talkers, &talker_len, - PROCLENS_MODULE_TOP_TALKERS_MAX, 50, 2, 6, 0, 0); + try_insert_top_talker(talkers, &talker_len, PROCLENS_MODULE_TOP_TALKERS_MAX, 50, 2, 6, 0, + 0); assert(talker_len == 3); assert(talkers[0].fd == 30); @@ -620,13 +607,11 @@ int main(void) char name_buf[16]; snprintf(name_buf, sizeof(name_buf), "d%d", i); - add_netdev_count(devs, &dev_len, PROCLENS_MODULE_NETDEV_MAX, - 100 + i, name_buf); + add_netdev_count(devs, &dev_len, PROCLENS_MODULE_NETDEV_MAX, 100 + i, name_buf); } assert(dev_len == PROCLENS_MODULE_NETDEV_MAX); - add_netdev_count(devs, &dev_len, PROCLENS_MODULE_NETDEV_MAX, 999, - "extra"); + add_netdev_count(devs, &dev_len, PROCLENS_MODULE_NETDEV_MAX, 999, "extra"); assert(dev_len == PROCLENS_MODULE_NETDEV_MAX); /* procfile write/read logic tests */ @@ -636,20 +621,17 @@ int main(void) char out_buf[64]; int finished_state; - copied = update_pid_write_buffer(pid_buf, sizeof(pid_buf), - "12345", 5); + copied = update_pid_write_buffer(pid_buf, sizeof(pid_buf), "12345", 5); assert(copied == 5); assert(strcmp(pid_buf, "12345") == 0); - copied = update_pid_write_buffer(pid_buf, sizeof(pid_buf), "1", - 1); + copied = update_pid_write_buffer(pid_buf, sizeof(pid_buf), "1", 1); assert(copied == 1); assert(strcmp(pid_buf, "1") == 0); assert(pid_buf[1] == '\0'); copied = update_pid_write_buffer(pid_buf, sizeof(pid_buf), - "1234567890123456789012345", - 25); + "1234567890123456789012345", 25); assert(copied == sizeof(pid_buf) - 1); assert(pid_buf[sizeof(pid_buf) - 1] == '\0'); @@ -659,8 +641,7 @@ int main(void) assert(procfile_read_should_finish(&finished_state) == 1); assert(finished_state == 0); - assert(format_procfile_output("42", out_buf, sizeof(out_buf)) > - 0); + assert(format_procfile_output("42", out_buf, sizeof(out_buf)) > 0); assert(strcmp(out_buf, "buff variable : 42\n") == 0); } diff --git a/src/proclens_tests.c b/src/proclens_tests.c index f3dc33f..79f9ce8 100644 --- a/src/proclens_tests.c +++ b/src/proclens_tests.c @@ -38,8 +38,7 @@ static void append_output(const char *fmt, ...) return; va_start(args, fmt); - n = vsnprintf(output_buf + output_len, sizeof(output_buf) - output_len, - fmt, args); + n = vsnprintf(output_buf + output_len, sizeof(output_buf) - output_len, fmt, args); va_end(args); if (n <= 0) @@ -102,8 +101,7 @@ static FILE *mock_fopen(const char *path, const char *mode) if (!strcmp(path, "/fake_proc/threads") && strchr(mode, 'r')) { if (fail_threads_open) return NULL; - return fmemopen((void *)threads_content, - strlen(threads_content), "r"); + return fmemopen((void *)threads_content, strlen(threads_content), "r"); } if (strstr(path, "/proc/") == path && strstr(path, "/cmdline")) { @@ -114,10 +112,8 @@ static FILE *mock_fopen(const char *path, const char *mode) if (!strcmp(path, "/proc/modules") && strchr(mode, 'r')) { if (module_loaded) - return fmemopen( - (void *)"proclens_module 0 0 - Live 0x0\n", - strlen("proclens_module 0 0 - Live 0x0\n"), - "r"); + return fmemopen((void *)"proclens_module 0 0 - Live 0x0\n", + strlen("proclens_module 0 0 - Live 0x0\n"), "r"); return fmemopen((void *)"", 0, "r"); } @@ -428,8 +424,7 @@ static void test_live_header_and_footer_show_timestamps(void) reset_mocks(); memset(&snap, 0, sizeof(snap)); snprintf(snap.pid, sizeof(snap.pid), "%s", "123"); - snprintf(snap.captured_at, sizeof(snap.captured_at), "%s", - "26/03/26 12:34:56"); + snprintf(snap.captured_at, sizeof(snap.captured_at), "%s", "26/03/26 12:34:56"); snap.view = VIEW_MEMORY; print_live_header(&snap, 0, 1); print_live_footer(snap.captured_at); @@ -450,23 +445,19 @@ static void test_snapshot_history_offset_navigation(void) memset(&snap, 0, sizeof(snap)); snprintf(snap.pid, sizeof(snap.pid), "%s", "111"); - snprintf(snap.captured_at, sizeof(snap.captured_at), "%s", - "26/03/26 12:00:01"); + snprintf(snap.captured_at, sizeof(snap.captured_at), "%s", "26/03/26 12:00:01"); snap.view = VIEW_MEMORY; append_snapshot(history, &history_count, &history_next, &snap); snprintf(snap.pid, sizeof(snap.pid), "%s", "222"); - snprintf(snap.captured_at, sizeof(snap.captured_at), "%s", - "26/03/26 12:00:02"); + snprintf(snap.captured_at, sizeof(snap.captured_at), "%s", "26/03/26 12:00:02"); append_snapshot(history, &history_count, &history_next, &snap); - picked = - get_snapshot_by_offset(history, history_count, history_next, 0); + picked = get_snapshot_by_offset(history, history_count, history_next, 0); assert(picked); assert(strcmp(picked->pid, "222") == 0); - picked = - get_snapshot_by_offset(history, history_count, history_next, 1); + picked = get_snapshot_by_offset(history, history_count, history_next, 1); assert(picked); assert(strcmp(picked->pid, "111") == 0); diff --git a/src/test_multithread.c b/src/test_multithread.c index b92aee3..2b2d03a 100644 --- a/src/test_multithread.c +++ b/src/test_multithread.c @@ -56,11 +56,9 @@ int main(void) /* Allow reuse to avoid "Address already in use" errors */ int opt = 1; - setsockopt(tcp_sock, SOL_SOCKET, SO_REUSEADDR, &opt, - sizeof(opt)); + setsockopt(tcp_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); - if (bind(tcp_sock, (struct sockaddr *)&tcp_addr, - sizeof(tcp_addr)) == 0) { + if (bind(tcp_sock, (struct sockaddr *)&tcp_addr, sizeof(tcp_addr)) == 0) { listen(tcp_sock, 5); printf("TCP socket listening on 127.0.0.1:12345 " "(fd=%d)\n", @@ -80,10 +78,8 @@ int main(void) udp_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); udp_addr.sin_port = htons(12346); - if (bind(udp_sock, (struct sockaddr *)&udp_addr, - sizeof(udp_addr)) == 0) { - printf("UDP socket bound to 127.0.0.1:12346 (fd=%d)\n", - udp_sock); + if (bind(udp_sock, (struct sockaddr *)&udp_addr, sizeof(udp_addr)) == 0) { + printf("UDP socket bound to 127.0.0.1:12346 (fd=%d)\n", udp_sock); } else { perror("UDP bind failed"); close(udp_sock); @@ -102,11 +98,10 @@ int main(void) /* Remove any existing socket file */ unlink(unix_addr.sun_path); - if (bind(unix_sock, (struct sockaddr *)&unix_addr, - sizeof(unix_addr)) == 0) { + if (bind(unix_sock, (struct sockaddr *)&unix_addr, sizeof(unix_addr)) == 0) { listen(unix_sock, 5); - printf("Unix socket listening at %s (fd=%d)\n", - unix_addr.sun_path, unix_sock); + printf("Unix socket listening at %s (fd=%d)\n", unix_addr.sun_path, + unix_sock); } else { perror("Unix socket bind failed"); close(unix_sock); @@ -120,11 +115,9 @@ int main(void) for (i = 0; i < NUM_THREADS; i++) { int rc; - rc = pthread_create(&threads[i], NULL, worker_thread, - (void *)i); + rc = pthread_create(&threads[i], NULL, worker_thread, (void *)i); if (rc) { - fprintf(stderr, "Error creating thread %ld: %d\n", i, - rc); + fprintf(stderr, "Error creating thread %ld: %d\n", i, rc); exit(1); } }