From d7239ed8d6b2d7bafbd4843133b6b28d22275451 Mon Sep 17 00:00:00 2001 From: WEI Xikai Date: Wed, 17 Jan 2024 17:18:34 +0800 Subject: [PATCH] feat: add system_stats lib to collect system stats (#1442) ## Rationale Part of #1438. The preparations for reporting system statistics. ## Detailed Changes - Add a new component called `system_stats` to helps collect system statistics. - Introduce a dependency [`sysinfo`](https://crates.io/crates/sysinfo). ## Test Plan Add a new unit test. --- Cargo.lock | 119 ++++++++++++++++++++-- Cargo.toml | 4 +- components/skiplist/Cargo.toml | 2 - components/system_stats/Cargo.toml | 33 ++++++ components/system_stats/src/lib.rs | 158 +++++++++++++++++++++++++++++ 5 files changed, 306 insertions(+), 10 deletions(-) create mode 100644 components/system_stats/Cargo.toml create mode 100644 components/system_stats/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 9e29407d43..5c08c3e5e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3233,7 +3233,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows 0.47.0", ] [[package]] @@ -4292,6 +4292,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -5704,9 +5713,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" dependencies = [ "either", "rayon-core", @@ -5714,14 +5723,12 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" dependencies = [ - "crossbeam-channel", "crossbeam-deque 0.8.3", "crossbeam-utils 0.8.15", - "num_cpus", ] [[package]] @@ -6871,6 +6878,20 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sysinfo" +version = "0.30.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb4f3438c8f6389c864e61221cbc97e9bca98b4daf39a5beb7bea660f528bb2" +dependencies = [ + "cfg-if 1.0.0", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "windows 0.52.0", +] + [[package]] name = "system_catalog" version = "1.2.6-alpha" @@ -6893,6 +6914,14 @@ dependencies = [ "trace_metric", ] +[[package]] +name = "system_stats" +version = "1.2.6-alpha" +dependencies = [ + "sysinfo", + "tokio", +] + [[package]] name = "table_engine" version = "1.2.6-alpha" @@ -8028,6 +8057,25 @@ dependencies = [ "windows-targets 0.47.0", ] +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -8091,6 +8139,21 @@ dependencies = [ "windows_x86_64_msvc 0.48.0", ] +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -8109,6 +8172,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -8127,6 +8196,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -8145,6 +8220,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -8163,6 +8244,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -8181,6 +8268,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -8199,6 +8292,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -8217,6 +8316,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winnow" version = "0.4.1" diff --git a/Cargo.toml b/Cargo.toml index 24a9b13a10..9813f1c5f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ members = [ "components/sampling_cache", "components/size_ext", "components/skiplist", + "components/system_stats", "components/table_kv", "components/test_util", "components/time_ext", @@ -164,7 +165,7 @@ router = { path = "router" } runtime = { path = "components/runtime" } sampling_cache = { path = "components/sampling_cache" } snafu = { version = "0.6.10", features = ["backtraces"] } -serde = "1.0" +serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.60" server = { path = "server" } size_ext = { path = "components/size_ext" } @@ -173,6 +174,7 @@ slog = "2.7" spin = "0.9.6" sqlparser = { version = "0.35", features = ["serde"] } system_catalog = { path = "system_catalog" } +system_statis = { path = "component/system_stats" } table_engine = { path = "table_engine" } table_kv = { path = "components/table_kv" } tempfile = "3.1.0" diff --git a/components/skiplist/Cargo.toml b/components/skiplist/Cargo.toml index 544915aba8..84b0a5ceb4 100644 --- a/components/skiplist/Cargo.toml +++ b/components/skiplist/Cargo.toml @@ -36,8 +36,6 @@ rand = { workspace = true } [dev-dependencies] criterion = { workspace = true } yatp = { git = "https://github.com/tikv/yatp.git", rev = "4b71f8abd86890f0d1e95778c2b6bf5a9ee4c502" } -# [target.'cfg(not(target_env = "msvc"))'.dev-dependencies] -# tikv-jemallocator = "0.4.0" [[bench]] name = "bench" diff --git a/components/system_stats/Cargo.toml b/components/system_stats/Cargo.toml new file mode 100644 index 0000000000..b4ab611210 --- /dev/null +++ b/components/system_stats/Cargo.toml @@ -0,0 +1,33 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +[package] +name = "system_stats" +authors = ["HoraeDB Authors"] + +[package.license] +workspace = true + +[package.version] +workspace = true + +[package.edition] +workspace = true + +[dependencies] +sysinfo = { version = "0.30", default-features = false } +tokio = { workspace = true } diff --git a/components/system_stats/src/lib.rs b/components/system_stats/src/lib.rs new file mode 100644 index 0000000000..a5680bfc0a --- /dev/null +++ b/components/system_stats/src/lib.rs @@ -0,0 +1,158 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Helps to collect and report statistics about the system. + +use std::{sync::Mutex, time::Duration}; + +pub use sysinfo::LoadAvg; +use sysinfo::{Cpu, CpuRefreshKind, MemoryRefreshKind, RefreshKind, System}; + +/// The stats about the system. +#[derive(Debug)] +pub struct SystemStats { + /// The usage's range is [0, 1.0] + pub cpu_usage: f32, + /// The memory is counted in byte. + pub used_memory: u64, + /// The memory is counted in byte. + pub total_memory: u64, + pub load_avg: LoadAvg, +} + +/// Collect the stats of the system for reporting. +/// +/// One background thread will be spawned to run stats collection. +pub struct SystemStatsCollector { + total_memory: u64, + system: Mutex, +} + +pub type ErrorMessage = String; + +impl SystemStatsCollector { + /// Create an new collector for the system stats. + pub fn try_new() -> Result { + if !sysinfo::IS_SUPPORTED_SYSTEM { + return Err("Unsupported system to collect system metrics".to_string()); + } + + let system = System::new_with_specifics(Self::make_mem_refresh_kind()); + Ok(Self { + total_memory: system.total_memory(), + system: Mutex::new(system), + }) + } + + /// Collect the system stats for `observe_dur`. + /// + /// The [`sysinfo::MINIMUM_CPU_UPDATE_INTERVAL`] will be used if + /// `observe_dur` is smaller. + pub async fn collect_and_report(&self, observe_dur: Duration) -> SystemStats { + { + let mut system = self.system.lock().unwrap(); + system.refresh_specifics(Self::make_cpu_refresh_kind()); + } + + let wait_dur = sysinfo::MINIMUM_CPU_UPDATE_INTERVAL.max(observe_dur); + tokio::time::sleep(wait_dur).await; + + let mut system = self.system.lock().unwrap(); + system.refresh_specifics(Self::make_cpu_and_mem_refresh_kind()); + + SystemStats { + cpu_usage: self.compute_cpu_usage(system.cpus()), + used_memory: system.used_memory(), + total_memory: self.total_memory, + load_avg: System::load_average(), + } + } + + // Refresh and compute the latest cpu usage. + fn compute_cpu_usage(&self, cpus: &[Cpu]) -> f32 { + let mut num_cpus = 0; + let mut total_cpu_usage = 0.0; + let valid_cpus = cpus.iter().filter(|v| !v.cpu_usage().is_nan()); + for cpu in valid_cpus { + total_cpu_usage += cpu.cpu_usage(); + num_cpus += 1; + } + + if num_cpus != 0 { + total_cpu_usage / (num_cpus as f32) / 100.0 + } else { + 0f32 + } + } + + #[inline] + fn make_mem_refresh_kind() -> RefreshKind { + let mem_refresh_kind = MemoryRefreshKind::new().with_ram(); + RefreshKind::new().with_memory(mem_refresh_kind) + } + + #[inline] + fn make_cpu_refresh_kind() -> RefreshKind { + let cpu_refresh_kind = CpuRefreshKind::new().with_cpu_usage(); + RefreshKind::new().with_cpu(cpu_refresh_kind) + } + + #[inline] + fn make_cpu_and_mem_refresh_kind() -> RefreshKind { + let cpu_refresh_kind = CpuRefreshKind::new().with_cpu_usage(); + let mem_refresh_kind = MemoryRefreshKind::new().with_ram(); + RefreshKind::new() + .with_cpu(cpu_refresh_kind) + .with_memory(mem_refresh_kind) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn check_system_stats(stats: &SystemStats) { + assert!(stats.total_memory > 0); + assert!(stats.used_memory > 0); + assert!(stats.used_memory < stats.total_memory); + assert!(stats.cpu_usage > 0.0); + assert!(stats.load_avg.one > 0.0); + assert!(stats.load_avg.five > 0.0); + assert!(stats.load_avg.fifteen > 0.0); + } + + #[tokio::test] + async fn test_normal_case() { + let collector = SystemStatsCollector::try_new().unwrap(); + let stats = collector + .collect_and_report(Duration::from_millis(500)) + .await; + check_system_stats(&stats); + + let mut all_cpu_usages = Vec::with_capacity(5); + for _ in 0..5 { + let new_stats = collector + .collect_and_report(Duration::from_millis(200)) + .await; + check_system_stats(&new_stats); + all_cpu_usages.push(new_stats.cpu_usage); + } + + // Ensure the stats will be updated for every collection. + assert!(all_cpu_usages.into_iter().any(|v| v != stats.cpu_usage)); + } +}