diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml new file mode 100644 index 0000000..535504c --- /dev/null +++ b/.github/workflows/auto-release.yml @@ -0,0 +1,69 @@ +name: Auto Multi-Arch Release + +on: + push: + branches: + - main + +permissions: + contents: write + +jobs: + build: + name: Build (${{ matrix.target }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - target: x86_64-unknown-linux-musl + asset_suffix: linux-x86_64 + - target: aarch64-unknown-linux-musl + asset_suffix: linux-arm64 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install cross tool + run: cargo install cross --git https://github.com/cross-rs/cross + + - name: Build release binary + run: cross build --release --target ${{ matrix.target }} + + - name: Prepare artifact + run: | + cp target/${{ matrix.target }}/release/cpu_usage cpu_usage-${{ matrix.asset_suffix }} + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: cpu_usage-${{ matrix.asset_suffix }} + path: cpu_usage-${{ matrix.asset_suffix }} + + release: + name: Create GitHub Release + needs: build + runs-on: ubuntu-latest + + steps: + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + path: dist + + - name: Generate release metadata + id: meta + run: | + TAG="auto-${GITHUB_RUN_NUMBER}-${GITHUB_SHA::7}" + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + + - name: Create release and upload assets + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.meta.outputs.tag }} + name: Auto Release ${{ steps.meta.outputs.tag }} + generate_release_notes: true + files: | + dist/cpu_usage-linux-x86_64/cpu_usage-linux-x86_64 + dist/cpu_usage-linux-arm64/cpu_usage-linux-arm64 diff --git a/.github/workflows/manual-arm-release.yml b/.github/workflows/manual-arm-release.yml deleted file mode 100644 index bd5b417..0000000 --- a/.github/workflows/manual-arm-release.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Build ARM Release - -on: - workflow_dispatch: - inputs: - version: - description: 'Release version' - required: true - default: '0.1.0' - -permissions: - contents: write - -jobs: - build-and-release: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install -y musl-tools - - - name: Set up Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: aarch64-unknown-linux-musl - override: true - - - name: Build - uses: actions-rs/cargo@v1 - with: - use-cross: true - command: build - args: --release --target aarch64-unknown-linux-musl - - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: v${{ github.event.inputs.version }} - release_name: Release ${{ github.event.inputs.version }} - draft: false - prerelease: false - - - name: Upload Release Asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./target/aarch64-unknown-linux-musl/release/cpu_usage - asset_name: cpu_usage-${{ github.event.inputs.version }}-linux-arm64 - asset_content_type: application/octet-stream diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml deleted file mode 100644 index 09eff29..0000000 --- a/.github/workflows/manual-release.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Build Release - -on: - workflow_dispatch: - inputs: - version: - description: 'Release version' - required: true - default: '0.1.0' - -permissions: - contents: write - -jobs: - build-and-release: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install -y musl-tools - - - name: Set up Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: x86_64-unknown-linux-musl - override: true - - - name: Build - uses: actions-rs/cargo@v1 - with: - use-cross: true - command: build - args: --release --target x86_64-unknown-linux-musl - - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: v${{ github.event.inputs.version }} - release_name: Release ${{ github.event.inputs.version }} - draft: false - prerelease: false - - - name: Upload Release Asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./target/x86_64-unknown-linux-musl/release/cpu_usage - asset_name: cpu_usage-${{ github.event.inputs.version }}-linux-x86_64 - asset_content_type: application/octet-stream diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e83d272 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,467 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "cc" +version = "1.2.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpu_usage" +version = "0.1.0" +dependencies = [ + "chrono", + "ctrlc", + "num_cpus", + "serde", + "serde_yaml", +] + +[[package]] +name = "ctrlc" +version = "3.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162" +dependencies = [ + "dispatch2", + "nix", + "windows-sys", +] + +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "nix" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225e7cfe711e0ba79a68baeddb2982723e4235247aefce1482f2f16c27865b66" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "objc2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/Cargo.toml b/Cargo.toml index 807bea2..447bd9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,8 @@ edition = "2021" num_cpus = "1.16" serde = { version = "1.0", features = ["derive"] } serde_yaml = "0.9" -openssl = { version = "0.10", features = ["vendored"] } chrono = "0.4" +ctrlc = "3.4" #sysinfo = "0.30.0" [profile.release] opt-level = 3 diff --git a/README.md b/README.md index 6996f48..474924e 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ This project is a CPU load simulator that adjusts the CPU usage based on the con - Supports custom workdays and rest days. - Multi-threaded CPU load simulation, supporting multi-core CPUs. - Simulates memory usage during work and rest periods using `work_memory_usage` and `rest_memory_usage`. +- Supports graceful shutdown via `Ctrl+C` so worker threads can exit cleanly. ## Installation @@ -33,6 +34,8 @@ rest_cpu_usage: 10.0 # CPU usage during rest hours (%) Run the following command to start the program: cargo run --release +Stop with `Ctrl+C` to trigger graceful shutdown. + The program will read the configuration from the config.yml file and adjust the CPU and memory usage based on the current time. Each CPU core will have a thread to simulate the load. `work_memory_usage` specifies the desired memory usage during work hours, while `rest_memory_usage` sets the memory usage for rest periods. Configure them in `config.yml` as shown below: diff --git a/src/main.rs b/src/main.rs index f5d28bc..6bcd503 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,20 @@ -use std::fs; use chrono::Datelike; -use std::thread; -use std::time::{Duration, Instant}; -use std::sync::{Arc, Mutex}; -use serde::{Deserialize, Serialize}; -use num_cpus; use chrono::{Local, NaiveTime, Weekday}; +use num_cpus; +use serde::{Deserialize, Serialize}; +use std::fs; use std::process; +use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::{Duration, Instant}; // 常量定义 const CPU_CYCLE_DURATION_MS: u64 = 10; const MIN_SLEEP_MICROS: u64 = 100; const MEMORY_CHECK_INTERVAL_SECS: u64 = 60; const MEMORY_KEEP_ALIVE_INTERVAL_SECS: u64 = 10; +const TARGET_REFRESH_INTERVAL_SECS: u64 = 1; const SYSTEM_RESERVE_RATIO: f64 = 0.8; const MIN_SYSTEM_RESERVE_MB: usize = 1024; const RANDOM_DATA_SIZE: usize = 1024 * 1024; @@ -23,18 +25,18 @@ const BATCH_PAUSE_DURATION_MS: u64 = 100; #[derive(Debug, Serialize, Deserialize, Clone)] struct Config { - work_days: Vec, // 指定工作日列表 格式:"2025-01-01" - rest_days: Vec, // 指定休息日列表 格式:"2025-01-01" - work_start_time: String, // 工作开始时间 格式:"09:00" - work_end_time: String, // 工作结束时间 格式:"18:00" - work_cpu_usage: f64, // 工作时间 CPU 使用率(百分比) - rest_cpu_usage: f64, // 休息时间 CPU 使用率(百分比) - work_memory_usage: f64, // 工作时间内存使用率(百分比) - rest_memory_usage: f64, // 休息时间内存使用率(百分比) + work_days: Vec, // 指定工作日列表 格式:"2025-01-01" + rest_days: Vec, // 指定休息日列表 格式:"2025-01-01" + work_start_time: String, // 工作开始时间 格式:"09:00" + work_end_time: String, // 工作结束时间 格式:"18:00" + work_cpu_usage: f64, // 工作时间 CPU 使用率(百分比) + rest_cpu_usage: f64, // 休息时间 CPU 使用率(百分比) + work_memory_usage: f64, // 工作时间内存使用率(百分比) + rest_memory_usage: f64, // 休息时间内存使用率(百分比) #[serde(skip)] - work_start: NaiveTime, // 解析后的工作开始时间 + work_start: NaiveTime, // 解析后的工作开始时间 #[serde(skip)] - work_end: NaiveTime, // 解析后的工作结束时间 + work_end: NaiveTime, // 解析后的工作结束时间 } fn parse_time(time_str: &str) -> Result { @@ -65,25 +67,25 @@ fn is_work_time_at(config: &Config, now: chrono::DateTime) -> bool { } else { current_time >= work_start || current_time < work_end }; - + // 检查当前日期是否在特定工作日列表中 let current_date = now.format("%Y-%m-%d").to_string(); if config.work_days.contains(¤t_date) { return in_time_range; } - + // 检查是否在特定休息日列表中 if config.rest_days.contains(¤t_date) { return false; } - + // 如果不在特定列表中,则按周一到周五判断 let weekday = now.weekday(); - let is_weekday = match weekday { - Weekday::Mon | Weekday::Tue | Weekday::Wed | Weekday::Thu | Weekday::Fri => true, - _ => false, - }; - + let is_weekday = matches!( + weekday, + Weekday::Mon | Weekday::Tue | Weekday::Wed | Weekday::Thu | Weekday::Fri + ); + is_weekday && in_time_range } @@ -108,23 +110,63 @@ fn get_current_memory_usage(config: &Config) -> f64 { } } +fn percent_to_permyriad(percent: f64) -> u32 { + (percent.clamp(0.0, 100.0) * 100.0).round() as u32 +} + +fn permyriad_to_percent(permyriad: u32) -> f64 { + permyriad as f64 / 100.0 +} + +struct RuntimeTargets { + cpu_permyriad: AtomicU32, + memory_permyriad: AtomicU32, + is_work: AtomicBool, +} -fn cpu_load(config: Arc>) { - loop { +impl RuntimeTargets { + fn new(cpu_percent: f64, memory_percent: f64, is_work: bool) -> Self { + Self { + cpu_permyriad: AtomicU32::new(percent_to_permyriad(cpu_percent)), + memory_permyriad: AtomicU32::new(percent_to_permyriad(memory_percent)), + is_work: AtomicBool::new(is_work), + } + } + + fn cpu_percent(&self) -> f64 { + permyriad_to_percent(self.cpu_permyriad.load(Ordering::Relaxed)) + } + + fn memory_percent(&self) -> f64 { + permyriad_to_percent(self.memory_permyriad.load(Ordering::Relaxed)) + } + + fn is_work(&self) -> bool { + self.is_work.load(Ordering::Relaxed) + } + + fn update(&self, cpu_percent: f64, memory_percent: f64, is_work: bool) { + self.cpu_permyriad + .store(percent_to_permyriad(cpu_percent), Ordering::Relaxed); + self.memory_permyriad + .store(percent_to_permyriad(memory_percent), Ordering::Relaxed); + self.is_work.store(is_work, Ordering::Relaxed); + } +} + +fn cpu_load(targets: Arc, running: Arc) { + while running.load(Ordering::Relaxed) { let cycle_start = Instant::now(); let cycle_duration = Duration::from_millis(CPU_CYCLE_DURATION_MS); - - let cpu_usage = { - let cfg = config.lock().unwrap(); - get_current_cpu_usage(&cfg) - }; - + let cpu_usage = targets.cpu_percent(); + let work_ratio = cpu_usage / 100.0; - let target_work_duration = Duration::from_secs_f64(cycle_duration.as_secs_f64() * work_ratio); - + let target_work_duration = + Duration::from_secs_f64(cycle_duration.as_secs_f64() * work_ratio); + if work_ratio > 0.0 { let work_start = Instant::now(); - while work_start.elapsed() < target_work_duration { + while running.load(Ordering::Relaxed) && work_start.elapsed() < target_work_duration { let mut x = 1.0f64; for _ in 0..5000 { x = x.powi(2).sqrt().sin().cos().tan().exp().ln(); @@ -132,10 +174,10 @@ fn cpu_load(config: Arc>) { std::hint::black_box(x); } } - + let elapsed = cycle_start.elapsed(); let sleep_time = cycle_duration.saturating_sub(elapsed); - + if sleep_time > Duration::from_micros(MIN_SLEEP_MICROS) { thread::sleep(sleep_time); } else { @@ -144,21 +186,17 @@ fn cpu_load(config: Arc>) { } } - - - - - // 简洁且高效的 Linux 版本 fn get_system_total_memory() -> u64 { use std::sync::OnceLock; static MEMORY_MB: OnceLock = OnceLock::new(); - + *MEMORY_MB.get_or_init(|| { std::fs::read_to_string("/proc/meminfo") .ok() .and_then(|content| { - content.lines() + content + .lines() .find(|line| line.starts_with("MemTotal:")) .and_then(|line| line.split_whitespace().nth(1)) .and_then(|kb| kb.parse::().ok()) @@ -170,30 +208,30 @@ fn get_system_total_memory() -> u64 { }) }) } - - // 更高效的内存管理结构体 struct MemoryManager { memory_blocks: Vec>, current_percent: f64, + current_allocated_mb: usize, system_total_memory: u64, - random_data: Vec, // 预先生成的随机数据,用于填充内存 + random_data: Vec, // 预先生成的随机数据,用于填充内存 } impl MemoryManager { fn new() -> Self { let system_total_memory = get_system_total_memory(); - + // 生成随机数据 let mut random_data = Vec::with_capacity(RANDOM_DATA_SIZE); for i in 0..RANDOM_DATA_SIZE { random_data.push((i % 256) as u8); } - + MemoryManager { memory_blocks: Vec::new(), current_percent: 0.0, + current_allocated_mb: 0, system_total_memory, random_data, } @@ -211,104 +249,109 @@ impl MemoryManager { // 计算目标内存字节数(百分比 * 总内存) let target_mb = (self.system_total_memory as f64 * percent / 100.0) as usize; - + // 预留给系统和其他程序,以避免影响系统稳定性 let safe_mb = (target_mb as f64 * SYSTEM_RESERVE_RATIO) as usize; - + // 确保至少保留给系统 - let max_usable_mb = (self.system_total_memory as usize).saturating_sub(MIN_SYSTEM_RESERVE_MB); - + let max_usable_mb = + (self.system_total_memory as usize).saturating_sub(MIN_SYSTEM_RESERVE_MB); + safe_mb.min(max_usable_mb) } fn adjust_memory_usage(&mut self, target_percent: f64) { println!("调整内存使用率: {:.1}%", target_percent); - + let target_mb = self.percent_to_mb(target_percent); - let current_mb = self.get_current_memory_mb(); - + let current_mb = self.current_allocated_mb; + println!("目标内存使用量: {} MB", target_mb); println!("当前内存使用量: {} MB", current_mb); - + match target_mb.cmp(¤t_mb) { std::cmp::Ordering::Less => self.release_memory_to_target(target_mb), std::cmp::Ordering::Greater => self.allocate_memory_to_target(target_mb, current_mb), std::cmp::Ordering::Equal => {} } - - self.current_percent = target_percent; - } - fn get_current_memory_mb(&self) -> usize { - self.memory_blocks.iter().map(|b| b.len()).sum::() / (1024 * 1024) + self.current_percent = target_percent; } fn release_memory_to_target(&mut self, target_mb: usize) { - while self.get_current_memory_mb() > target_mb && !self.memory_blocks.is_empty() { - self.memory_blocks.pop(); + while self.current_allocated_mb > target_mb && !self.memory_blocks.is_empty() { + if let Some(block) = self.memory_blocks.pop() { + self.current_allocated_mb -= block.len() / (1024 * 1024); + } } - let final_mb = self.get_current_memory_mb(); - println!("释放后内存使用量: {} MB", final_mb); + println!("释放后内存使用量: {} MB", self.current_allocated_mb); } fn allocate_memory_to_target(&mut self, target_mb: usize, current_mb: usize) { let additional_mb = target_mb - current_mb; let block_size = self.calculate_block_size(additional_mb); - let blocks_needed = (additional_mb + block_size - 1) / block_size; - - println!("需要添加 {} 个内存块,每块 {} MB", blocks_needed, block_size); - + let blocks_needed = additional_mb.div_ceil(block_size); + + println!( + "需要添加 {} 个内存块,每块 {} MB", + blocks_needed, block_size + ); + for i in 0..blocks_needed { let block = self.create_memory_block(block_size); self.memory_blocks.push(block); - + self.current_allocated_mb += block_size; + if i % BATCH_PROGRESS_INTERVAL == 0 || i == blocks_needed - 1 { println!("已添加 {} 个内存块中的 {} 个", blocks_needed, i + 1); } - + if i % BATCH_PAUSE_INTERVAL == BATCH_PAUSE_INTERVAL - 1 { thread::sleep(Duration::from_millis(BATCH_PAUSE_DURATION_MS)); } } - - let final_mb = self.get_current_memory_mb(); - println!("分配后内存使用量: {} MB", final_mb); + + println!("分配后内存使用量: {} MB", self.current_allocated_mb); } fn calculate_block_size(&self, additional_mb: usize) -> usize { - if additional_mb < 256 { - MEMORY_BLOCK_SIZES[0] - } else if additional_mb < 1024 { - MEMORY_BLOCK_SIZES[1] - } else { - MEMORY_BLOCK_SIZES[2] + if additional_mb < 256 { + MEMORY_BLOCK_SIZES[0] + } else if additional_mb < 1024 { + MEMORY_BLOCK_SIZES[1] + } else { + MEMORY_BLOCK_SIZES[2] } } fn create_memory_block(&self, size_mb: usize) -> Vec { let mut block = Vec::with_capacity(size_mb * 1024 * 1024); let block_bytes = size_mb * 1024 * 1024; - + // 用真实数据填充 for chunk_start in (0..block_bytes).step_by(self.random_data.len()) { let remaining = block_bytes - chunk_start; let chunk_size = remaining.min(self.random_data.len()); block.extend_from_slice(&self.random_data[0..chunk_size]); } - + // 确保内存被实际使用 - for chunk_start in (0..block.len()).step_by(1024*1024) { + for chunk_start in (0..block.len()).step_by(1024 * 1024) { let end = (chunk_start + 1000).min(block.len()); for j in chunk_start..end { block[j] = block[j].wrapping_add(1); } } - + block } } -fn memory_load(config: Arc>, memory_manager: Arc>) { +fn memory_load( + targets: Arc, + memory_manager: Arc>, + running: Arc, +) { // 获取进程ID用于日志记录 let pid = process::id(); println!("内存管理线程启动,进程ID: {}", pid); @@ -316,9 +359,10 @@ fn memory_load(config: Arc>, memory_manager: Arc> 共享内存管理器 let mm_for_keep_alive = Arc::clone(&memory_manager); - thread::spawn(move || { + let running_for_keep_alive = Arc::clone(&running); + let keep_alive_handle = thread::spawn(move || { println!("内存保持活跃线程已启动"); - loop { + while running_for_keep_alive.load(Ordering::Relaxed) { { let mut manager = mm_for_keep_alive.lock().unwrap(); for block in manager.memory_blocks.iter_mut() { @@ -333,40 +377,61 @@ fn memory_load(config: Arc>, memory_manager: Arc>, + targets: Arc, + running: Arc, +) { + while running.load(Ordering::Relaxed) { + let (cpu_usage, memory_usage, is_work) = { + let cfg = config.lock().unwrap(); + ( + get_current_cpu_usage(&cfg), + get_current_memory_usage(&cfg), + is_work_time(&cfg), + ) + }; + + targets.update(cpu_usage, memory_usage, is_work); + thread::sleep(Duration::from_secs(TARGET_REFRESH_INTERVAL_SECS)); + } } fn main() -> Result<(), String> { // 打印开始信息 println!("===== 系统资源调节程序启动 ====="); println!("当前进程ID: {}", process::id()); - + // 尝试读取配置文件 let config_content = match fs::read_to_string("config.yml") { Ok(content) => content, @@ -382,31 +447,32 @@ work_cpu_usage: 70.0 rest_cpu_usage: 10.0 work_memory_usage: 70.0 rest_memory_usage: 20.0 - "#.to_string() + "# + .to_string() } }; - - let config: Config = match serde_yaml::from_str::(&config_content) { - Ok(mut config) => { - // 校验时间格式并存储解析结果 - config.work_start = parse_time(&config.work_start_time) - .map_err(|e| format!("work_start_time {}", e))?; - config.work_end = parse_time(&config.work_end_time) - .map_err(|e| format!("work_end_time {}", e))?; - - clamp_percent(&mut config.work_cpu_usage, "work_cpu_usage"); - clamp_percent(&mut config.rest_cpu_usage, "rest_cpu_usage"); - clamp_percent(&mut config.work_memory_usage, "work_memory_usage"); // 添加这行 - clamp_percent(&mut config.rest_memory_usage, "rest_memory_usage"); // 添加这行 - - config - } - Err(e) => { - println!("配置文件错误: {}", e); - return Err(format!("配置文件错误: {}", e)); - } -}; - + + let config: Config = match serde_yaml::from_str::(&config_content) { + Ok(mut config) => { + // 校验时间格式并存储解析结果 + config.work_start = parse_time(&config.work_start_time) + .map_err(|e| format!("work_start_time {}", e))?; + config.work_end = + parse_time(&config.work_end_time).map_err(|e| format!("work_end_time {}", e))?; + + clamp_percent(&mut config.work_cpu_usage, "work_cpu_usage"); + clamp_percent(&mut config.rest_cpu_usage, "rest_cpu_usage"); + clamp_percent(&mut config.work_memory_usage, "work_memory_usage"); + clamp_percent(&mut config.rest_memory_usage, "rest_memory_usage"); + + config + } + Err(e) => { + println!("配置文件错误: {}", e); + return Err(format!("配置文件错误: {}", e)); + } + }; + println!("\n===== 配置信息 ====="); println!("CPU 核心数: {}", num_cpus::get()); println!("工作时间 CPU 使用率: {:.1}%", config.work_cpu_usage); @@ -417,63 +483,112 @@ rest_memory_usage: 20.0 println!("工作结束时间: {}", config.work_end_time); println!("指定工作日数量: {}", config.work_days.len()); println!("指定休息日数量: {}", config.rest_days.len()); - + // 检查当前是否是工作时间 let is_work_now = is_work_time(&config); println!("\n当前时间: {}", Local::now().format("%Y-%m-%d %H:%M:%S")); - println!("当前状态: {}", if is_work_now { "工作时间" } else { "休息时间" }); - println!("当前应用 CPU 使用率: {:.1}%", get_current_cpu_usage(&config)); - println!("当前应用内存使用率: {:.1}%", get_current_memory_usage(&config)); - + println!( + "当前状态: {}", + if is_work_now { + "工作时间" + } else { + "休息时间" + } + ); + println!( + "当前应用 CPU 使用率: {:.1}%", + get_current_cpu_usage(&config) + ); + println!( + "当前应用内存使用率: {:.1}%", + get_current_memory_usage(&config) + ); + // 将配置封装在 Arc> 中以便在线程间共享 let shared_config = Arc::new(Mutex::new(config)); - + + // 创建共享目标快照,降低高频线程锁竞争 + let targets = Arc::new(RuntimeTargets::new( + { + let cfg = shared_config.lock().unwrap(); + get_current_cpu_usage(&cfg) + }, + { + let cfg = shared_config.lock().unwrap(); + get_current_memory_usage(&cfg) + }, + { + let cfg = shared_config.lock().unwrap(); + is_work_time(&cfg) + }, + )); + + let running = Arc::new(AtomicBool::new(true)); + { + let running_for_signal = Arc::clone(&running); + ctrlc::set_handler(move || { + println!("\n收到退出信号,正在优雅停止..."); + running_for_signal.store(false, Ordering::Relaxed); + }) + .map_err(|e| format!("注册信号处理器失败: {e}"))?; + } + // 创建内存管理器 let memory_manager = Arc::new(Mutex::new(MemoryManager::new())); - + println!("\n===== 启动资源管理线程 ====="); - + + // 启动目标刷新线程 + let refresh_config = Arc::clone(&shared_config); + let refresh_targets = Arc::clone(&targets); + let refresh_running = Arc::clone(&running); + let refresh_handle = thread::spawn(move || { + target_refresh_loop(refresh_config, refresh_targets, refresh_running); + }); + // 启动内存管理线程 - let memory_config = Arc::clone(&shared_config); + let memory_targets = Arc::clone(&targets); let memory_mgr = Arc::clone(&memory_manager); + let memory_running = Arc::clone(&running); let memory_handle = thread::spawn(move || { println!("启动内存负载线程"); - memory_load(memory_config, memory_mgr); + memory_load(memory_targets, memory_mgr, memory_running); }); - + // 启动 CPU 负载线程 let mut handles = vec![]; for i in 0..num_cpus::get() { - let config_clone = Arc::clone(&shared_config); + let targets_clone = Arc::clone(&targets); + let running_clone = Arc::clone(&running); let handle = thread::spawn(move || { println!("启动 CPU 负载线程,核心 #{}", i); - cpu_load(config_clone); + cpu_load(targets_clone, running_clone); }); - + handles.push(handle); } - + println!("\n===== 所有线程已启动 ====="); - println!("程序运行中..."); - + println!("程序运行中...(按 Ctrl+C 优雅退出)"); + // 等待所有线程完成 handles.push(memory_handle); + handles.push(refresh_handle); for handle in handles { - //handle.join().unwrap(); - if let Err(e) = handle.join() { + if let Err(e) = handle.join() { eprintln!("线程意外退出: {:?}", e); - } - + } } + println!("程序已退出"); Ok(()) } #[cfg(test)] mod tests { use super::*; - use chrono::{NaiveDate, Local, TimeZone}; + use chrono::{Local, NaiveDate, TimeZone}; fn make_config( work_start: &str, @@ -512,23 +627,24 @@ mod tests { fn workday_detection() { let config = make_config("09:00", "18:00", vec![], vec![]); let date = NaiveDate::from_ymd_opt(2024, 10, 21).unwrap(); // Monday - let dt_in = Local.from_local_datetime(&date.and_hms_opt(10, 0, 0).unwrap()).unwrap(); + let dt_in = Local + .from_local_datetime(&date.and_hms_opt(10, 0, 0).unwrap()) + .unwrap(); assert!(is_work_time_at(&config, dt_in)); - let dt_out = Local.from_local_datetime(&date.and_hms_opt(20, 0, 0).unwrap()).unwrap(); + let dt_out = Local + .from_local_datetime(&date.and_hms_opt(20, 0, 0).unwrap()) + .unwrap(); assert!(!is_work_time_at(&config, dt_out)); } #[test] fn rest_day_detection() { - let config = make_config( - "09:00", - "18:00", - vec![], - vec!["2024-10-21".to_string()], - ); + let config = make_config("09:00", "18:00", vec![], vec!["2024-10-21".to_string()]); let date = NaiveDate::from_ymd_opt(2024, 10, 21).unwrap(); - let dt = Local.from_local_datetime(&date.and_hms_opt(10, 0, 0).unwrap()).unwrap(); + let dt = Local + .from_local_datetime(&date.and_hms_opt(10, 0, 0).unwrap()) + .unwrap(); assert!(!is_work_time_at(&config, dt)); } @@ -538,16 +654,30 @@ mod tests { // Monday 23:00 should be work time let mon = NaiveDate::from_ymd_opt(2024, 10, 21).unwrap(); - let dt_mon = Local.from_local_datetime(&mon.and_hms_opt(23, 0, 0).unwrap()).unwrap(); + let dt_mon = Local + .from_local_datetime(&mon.and_hms_opt(23, 0, 0).unwrap()) + .unwrap(); assert!(is_work_time_at(&config, dt_mon)); // Tuesday 05:00 should still be work time let tue = NaiveDate::from_ymd_opt(2024, 10, 22).unwrap(); - let dt_tue = Local.from_local_datetime(&tue.and_hms_opt(5, 0, 0).unwrap()).unwrap(); + let dt_tue = Local + .from_local_datetime(&tue.and_hms_opt(5, 0, 0).unwrap()) + .unwrap(); assert!(is_work_time_at(&config, dt_tue)); // Tuesday 07:00 should be rest time - let dt_after = Local.from_local_datetime(&tue.and_hms_opt(7, 0, 0).unwrap()).unwrap(); + let dt_after = Local + .from_local_datetime(&tue.and_hms_opt(7, 0, 0).unwrap()) + .unwrap(); assert!(!is_work_time_at(&config, dt_after)); } + + #[test] + fn runtime_target_conversion() { + assert_eq!(percent_to_permyriad(-1.0), 0); + assert_eq!(percent_to_permyriad(50.12), 5012); + assert_eq!(percent_to_permyriad(200.0), 10000); + assert!((permyriad_to_percent(1234) - 12.34).abs() < f64::EPSILON); + } }