From 4407f6e098ca74b3497b9b02986470b84eaf332d Mon Sep 17 00:00:00 2001 From: Hui Zhu Date: Wed, 27 Nov 2024 15:28:56 +0800 Subject: [PATCH] mem-agent: Add to src mem-agent is a component designed for managing memory in Linux environments. Sub-feature memcg: Utilizes the MgLRU feature to monitor each cgroup's memory usage and periodically reclaim cold memory. Sub-feature compact: Periodically compacts memory to facilitate the kernel's free page reporting feature, enabling the release of more idle memory from guests. During memory reclamation and compaction, mem-agent monitors system pressure using Pressure Stall Information (PSI). If the system pressure becomes too high, memory reclamation or compaction will automatically stop. Fixes: #10625 Signed-off-by: Hui Zhu --- src/mem-agent/.gitignore | 5 + src/mem-agent/Cargo.lock | 943 ++++++++++ src/mem-agent/Cargo.toml | 22 + src/mem-agent/Makefile | 6 + src/mem-agent/example/Cargo.lock | 1658 +++++++++++++++++ src/mem-agent/example/Cargo.toml | 36 + src/mem-agent/example/build.rs | 29 + src/mem-agent/example/ctl.rs | 79 + src/mem-agent/example/protocols/mod.rs | 8 + .../protos/google/protobuf/empty.proto | 52 + .../protos/google/protobuf/timestamp.proto | 138 ++ .../example/protocols/protos/mem-agent.proto | 66 + src/mem-agent/example/share/misc.rs | 29 + src/mem-agent/example/share/mod.rs | 7 + src/mem-agent/example/share/option.rs | 146 ++ src/mem-agent/example/share/rpc.rs | 221 +++ src/mem-agent/example/srv.rs | 95 + src/mem-agent/src/agent.rs | 378 ++++ src/mem-agent/src/compact.rs | 446 +++++ src/mem-agent/src/lib.rs | 12 + src/mem-agent/src/memcg.rs | 843 +++++++++ src/mem-agent/src/mglru.rs | 924 +++++++++ src/mem-agent/src/misc.rs | 71 + src/mem-agent/src/proc.rs | 44 + src/mem-agent/src/psi.rs | 260 +++ src/mem-agent/src/timer.rs | 88 + tests/static-checks.sh | 1 + 27 files changed, 6607 insertions(+) create mode 100644 src/mem-agent/.gitignore create mode 100644 src/mem-agent/Cargo.lock create mode 100644 src/mem-agent/Cargo.toml create mode 100644 src/mem-agent/Makefile create mode 100644 src/mem-agent/example/Cargo.lock create mode 100644 src/mem-agent/example/Cargo.toml create mode 100644 src/mem-agent/example/build.rs create mode 100644 src/mem-agent/example/ctl.rs create mode 100644 src/mem-agent/example/protocols/mod.rs create mode 100644 src/mem-agent/example/protocols/protos/google/protobuf/empty.proto create mode 100644 src/mem-agent/example/protocols/protos/google/protobuf/timestamp.proto create mode 100644 src/mem-agent/example/protocols/protos/mem-agent.proto create mode 100644 src/mem-agent/example/share/misc.rs create mode 100644 src/mem-agent/example/share/mod.rs create mode 100644 src/mem-agent/example/share/option.rs create mode 100644 src/mem-agent/example/share/rpc.rs create mode 100644 src/mem-agent/example/srv.rs create mode 100644 src/mem-agent/src/agent.rs create mode 100644 src/mem-agent/src/compact.rs create mode 100644 src/mem-agent/src/lib.rs create mode 100644 src/mem-agent/src/memcg.rs create mode 100644 src/mem-agent/src/mglru.rs create mode 100644 src/mem-agent/src/misc.rs create mode 100644 src/mem-agent/src/proc.rs create mode 100644 src/mem-agent/src/psi.rs create mode 100644 src/mem-agent/src/timer.rs diff --git a/src/mem-agent/.gitignore b/src/mem-agent/.gitignore new file mode 100644 index 0000000000..5a4fbfff62 --- /dev/null +++ b/src/mem-agent/.gitignore @@ -0,0 +1,5 @@ +/target +/example/target +/.vscode +.vscode-ctags + diff --git a/src/mem-agent/Cargo.lock b/src/mem-agent/Cargo.lock new file mode 100644 index 0000000000..e53b68499c --- /dev/null +++ b/src/mem-agent/Cargo.lock @@ -0,0 +1,943 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[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 = "anyhow" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" + +[[package]] +name = "arc-swap" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b3d0060af21e8d11a926981cc00c6c1541aa91dd64b9f881985c3da1094425f" + +[[package]] +name = "async-trait" +version = "0.1.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bumpalo" +version = "3.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cc" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.4", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "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 = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi 0.4.0", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.167" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", +] + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "mem-agent" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "chrono", + "lazy_static", + "maplit", + "nix", + "once_cell", + "page_size", + "slog", + "slog-async", + "slog-scope", + "slog-term", + "tokio", +] + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "nix" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" +dependencies = [ + "bitflags 1.3.2", + "cc", + "cfg-if", + "libc", + "memoffset", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.9", + "libc", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "slog" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" + +[[package]] +name = "slog-async" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c8038f898a2c79507940990f05386455b3a317d8f18d4caea7cbc3d5096b84" +dependencies = [ + "crossbeam-channel", + "slog", + "take_mut", + "thread_local", +] + +[[package]] +name = "slog-scope" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f95a4b4c3274cd2869549da82b57ccc930859bdbf5bcea0424bc5f140b3c786" +dependencies = [ + "arc-swap", + "lazy_static", + "slog", +] + +[[package]] +name = "slog-term" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6e022d0b998abfe5c3782c1f03551a596269450ccd677ea51c56f8b214610e8" +dependencies = [ + "is-terminal", + "slog", + "term", + "thread_local", + "time", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "socket2" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "syn" +version = "2.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "take_mut" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tokio" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[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.4", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" diff --git a/src/mem-agent/Cargo.toml b/src/mem-agent/Cargo.toml new file mode 100644 index 0000000000..e985c8757e --- /dev/null +++ b/src/mem-agent/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "mem-agent" +version = "0.1.0" +edition = "2018" + +[dependencies] +slog = "2.5.2" +slog-scope = "4.1.2" +anyhow = "1.0" +page_size = "0.6" +chrono = "0.4" +tokio = { version = "1.33", features = ["full"] } +async-trait = "0.1" +lazy_static = "1.4" +nix = "0.23.2" + +[dev-dependencies] +maplit = "1.0" +slog-term = "2.9.0" +slog-async = "2.7" +once_cell = "1.9.0" + diff --git a/src/mem-agent/Makefile b/src/mem-agent/Makefile new file mode 100644 index 0000000000..e5af8de6fc --- /dev/null +++ b/src/mem-agent/Makefile @@ -0,0 +1,6 @@ +# Copyright (C) 2024 Ant group. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +default: + cd example; cargo build --examples --target x86_64-unknown-linux-musl diff --git a/src/mem-agent/example/Cargo.lock b/src/mem-agent/example/Cargo.lock new file mode 100644 index 0000000000..5aa818e9e5 --- /dev/null +++ b/src/mem-agent/example/Cargo.lock @@ -0,0 +1,1658 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[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 = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" + +[[package]] +name = "arc-swap" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b3d0060af21e8d11a926981cc00c6c1541aa91dd64b9f881985c3da1094425f" + +[[package]] +name = "async-trait" +version = "0.1.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[package]] +name = "bumpalo" +version = "3.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cc" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.4", +] + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags 1.3.2", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive-new" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3418329ca0ad70234b9735dc4ceed10af4df60eff9c8e7b06cb5e520d92c3535" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "fixedbitset" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "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 = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi 0.4.0", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.4.2", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "mem-agent" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "chrono", + "lazy_static", + "nix 0.23.2", + "page_size", + "slog", + "slog-scope", + "tokio", +] + +[[package]] +name = "mem-agent-bin" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "byteorder", + "chrono", + "home", + "lazy_static", + "libc", + "maplit", + "mem-agent", + "page_size", + "protobuf 3.4.0", + "slog", + "slog-async", + "slog-scope", + "slog-term", + "structopt", + "tokio", + "ttrpc", + "ttrpc-codegen", +] + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + +[[package]] +name = "nix" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" +dependencies = [ + "bitflags 1.3.2", + "cc", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + +[[package]] +name = "nix" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.7.1", + "pin-utils", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.9", + "libc", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "petgraph" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de5e2533f59d08fcf364fd374ebda0692a70bd6d7e66ef97f306f45c6c5d8020" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "355f634b43cdd80724ee7848f95770e7e70eefa6dcf14fea676216573b8fd603" +dependencies = [ + "bytes", + "heck", + "itertools", + "log", + "multimap", + "petgraph", + "prost", + "prost-types", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "600d2f334aa05acb02a755e217ef1ab6dea4d51b58b7846588b747edec04efba" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "prost-types" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "603bbd6394701d13f3f25aada59c7de9d35a6a5887cfc156181234a44002771b" +dependencies = [ + "bytes", + "prost", +] + +[[package]] +name = "protobuf" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" + +[[package]] +name = "protobuf" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58678a64de2fced2bdec6bca052a6716a0efe692d6e3f53d1bda6a1def64cfc0" +dependencies = [ + "once_cell", + "protobuf-support", + "thiserror", +] + +[[package]] +name = "protobuf-codegen" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "033460afb75cf755fcfc16dfaed20b86468082a2ea24e05ac35ab4a099a017d6" +dependencies = [ + "protobuf 2.28.0", +] + +[[package]] +name = "protobuf-codegen" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32777b0b3f6538d9d2e012b3fad85c7e4b9244b5958d04a6415f4333782b7a77" +dependencies = [ + "anyhow", + "once_cell", + "protobuf 3.4.0", + "protobuf-parse", + "regex", + "tempfile", + "thiserror", +] + +[[package]] +name = "protobuf-parse" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96cb37955261126624a25b5e6bda40ae34cf3989d52a783087ca6091b29b5642" +dependencies = [ + "anyhow", + "indexmap", + "log", + "protobuf 3.4.0", + "protobuf-support", + "tempfile", + "thiserror", + "which", +] + +[[package]] +name = "protobuf-support" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1ed294a835b0f30810e13616b1cd34943c6d1e84a8f3b0dcfe466d256c3e7e7" +dependencies = [ + "thiserror", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slog" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" + +[[package]] +name = "slog-async" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c8038f898a2c79507940990f05386455b3a317d8f18d4caea7cbc3d5096b84" +dependencies = [ + "crossbeam-channel", + "slog", + "take_mut", + "thread_local", +] + +[[package]] +name = "slog-scope" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f95a4b4c3274cd2869549da82b57ccc930859bdbf5bcea0424bc5f140b3c786" +dependencies = [ + "arc-swap", + "lazy_static", + "slog", +] + +[[package]] +name = "slog-term" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6e022d0b998abfe5c3782c1f03551a596269450ccd677ea51c56f8b214610e8" +dependencies = [ + "is-terminal", + "slog", + "term", + "thread_local", + "time", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "socket2" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "structopt" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "take_mut" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tokio" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "tokio-vsock" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a15c15b1bc91f90902347eff163b5b682643aff0c8e972912cca79bd9208dd" +dependencies = [ + "bytes", + "futures", + "libc", + "tokio", + "vsock", +] + +[[package]] +name = "ttrpc" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ea338db445bee75c596cf8a478fbfcefad5a943c9e92a7e1c805c65ed39551" +dependencies = [ + "async-trait", + "byteorder", + "crossbeam", + "futures", + "libc", + "log", + "nix 0.26.4", + "protobuf 3.4.0", + "protobuf-codegen 3.4.0", + "thiserror", + "tokio", + "tokio-vsock", + "windows-sys 0.48.0", +] + +[[package]] +name = "ttrpc-codegen" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94d7f7631d7a9ebed715a47cd4cb6072cbc7ae1d4ec01598971bbec0024340c2" +dependencies = [ + "protobuf 2.28.0", + "protobuf-codegen 3.4.0", + "protobuf-support", + "ttrpc-compiler", +] + +[[package]] +name = "ttrpc-compiler" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0672eb06e5663ad190c7b93b2973f5d730259859b62e4e3381301a12a7441107" +dependencies = [ + "derive-new", + "prost", + "prost-build", + "prost-types", + "protobuf 2.28.0", + "protobuf-codegen 2.28.0", + "tempfile", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "vsock" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8e1df0bf1e1b28095c24564d1b90acae64ca69b097ed73896e342fa6649c57" +dependencies = [ + "libc", + "nix 0.24.3", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.52", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[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.4", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" diff --git a/src/mem-agent/example/Cargo.toml b/src/mem-agent/example/Cargo.toml new file mode 100644 index 0000000000..cc28050db6 --- /dev/null +++ b/src/mem-agent/example/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "mem-agent-bin" +version = "0.1.0" +edition = "2018" + +[dependencies] +slog = "2.5.2" +slog-scope = "4.1.2" +slog-term = "2.9.0" +slog-async = "2.7" +structopt = "0.3" +anyhow = "1.0" +libc = "0.2" +page_size = "0.6" +chrono = "0.4" +maplit = "1.0" +ttrpc = { version = "0.8", features = ["async"] } +tokio = { version = "1.33", features = ["full"] } +async-trait = "0.1" +byteorder = "1.5" +protobuf = "3.1" +lazy_static = "1.4" +# Rust 1.68 doesn't support 0.5.9 +home = "=0.5.5" +mem-agent = { path = "../" } + +[[example]] +name = "mem-agent-srv" +path = "./srv.rs" + +[[example]] +name = "mem-agent-ctl" +path = "./ctl.rs" + +[build-dependencies] +ttrpc-codegen = "0.4" diff --git a/src/mem-agent/example/build.rs b/src/mem-agent/example/build.rs new file mode 100644 index 0000000000..d1e2cb891f --- /dev/null +++ b/src/mem-agent/example/build.rs @@ -0,0 +1,29 @@ +// Copyright (C) 2024 Ant group. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +use ttrpc_codegen::{Codegen, Customize, ProtobufCustomize}; + +fn main() -> Result<(), Box> { + let protos = vec![ + "protocols/protos/mem-agent.proto", + "protocols/protos/google/protobuf/empty.proto", + "protocols/protos/google/protobuf/timestamp.proto", + ]; + + let protobuf_customized = ProtobufCustomize::default().gen_mod_rs(false); + + Codegen::new() + .out_dir("protocols/") + .inputs(&protos) + .include("protocols/protos/") + .rust_protobuf() + .customize(Customize { + async_all: true, + ..Default::default() + }) + .rust_protobuf_customize(protobuf_customized.clone()) + .run()?; + + Ok(()) +} diff --git a/src/mem-agent/example/ctl.rs b/src/mem-agent/example/ctl.rs new file mode 100644 index 0000000000..45d11d4216 --- /dev/null +++ b/src/mem-agent/example/ctl.rs @@ -0,0 +1,79 @@ +// Copyright (C) 2023 Ant group. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +mod protocols; +mod share; + +use anyhow::{anyhow, Result}; +use protocols::empty; +use protocols::mem_agent_ttrpc; +use share::option::{CompactSetOption, MemcgSetOption}; +use structopt::StructOpt; +use ttrpc::r#async::Client; + +#[derive(Debug, StructOpt)] +enum Command { + #[structopt(name = "memcgstatus", about = "get memory cgroup status")] + MemcgStatus, + + #[structopt(name = "memcgset", about = "set memory cgroup")] + MemcgSet(MemcgSetOption), + + #[structopt(name = "compactset", about = "set compact")] + CompactSet(CompactSetOption), +} + +#[derive(StructOpt, Debug)] +#[structopt(name = "mem-agent-ctl", about = "Memory agent controler")] +struct Opt { + #[structopt(long, default_value = "unix:///var/run/mem-agent.sock")] + addr: String, + + #[structopt(subcommand)] + command: Command, +} + +#[tokio::main] +async fn main() -> Result<()> { + let opt = Opt::from_args(); + + // setup client + let c = Client::connect(&opt.addr).unwrap(); + let client = mem_agent_ttrpc::ControlClient::new(c.clone()); + + match opt.command { + Command::MemcgStatus => { + let mss = client + .memcg_status(ttrpc::context::with_timeout(0), &empty::Empty::new()) + .await + .map_err(|e| anyhow!("client.memcg_status fail: {}", e))?; + for mcg in mss.mem_cgroups { + println!("{:?}", mcg); + for (numa_id, n) in mcg.numa { + if let Some(t) = n.last_inc_time.into_option() { + println!("{} {:?}", numa_id, share::misc::timestamp_to_datetime(t)?); + } + } + } + } + + Command::MemcgSet(c) => { + let config = c.to_rpc_memcg_config(); + client + .memcg_set(ttrpc::context::with_timeout(0), &config) + .await + .map_err(|e| anyhow!("client.memcg_status fail: {}", e))?; + } + + Command::CompactSet(c) => { + let config = c.to_rpc_compact_config(); + client + .compact_set(ttrpc::context::with_timeout(0), &config) + .await + .map_err(|e| anyhow!("client.memcg_status fail: {}", e))?; + } + } + + Ok(()) +} diff --git a/src/mem-agent/example/protocols/mod.rs b/src/mem-agent/example/protocols/mod.rs new file mode 100644 index 0000000000..2666e077b0 --- /dev/null +++ b/src/mem-agent/example/protocols/mod.rs @@ -0,0 +1,8 @@ +// Copyright (C) 2023 Ant group. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +pub mod empty; +pub mod mem_agent; +pub mod mem_agent_ttrpc; +pub mod timestamp; diff --git a/src/mem-agent/example/protocols/protos/google/protobuf/empty.proto b/src/mem-agent/example/protocols/protos/google/protobuf/empty.proto new file mode 100644 index 0000000000..6057c8522d --- /dev/null +++ b/src/mem-agent/example/protocols/protos/google/protobuf/empty.proto @@ -0,0 +1,52 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option go_package = "types"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "EmptyProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option cc_enable_arenas = true; + +// A generic empty message that you can re-use to avoid defining duplicated +// empty messages in your APIs. A typical example is to use it as the request +// or the response type of an API method. For instance: +// +// service Foo { +// rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); +// } +// +// The JSON representation for `Empty` is empty JSON object `{}`. +message Empty {} diff --git a/src/mem-agent/example/protocols/protos/google/protobuf/timestamp.proto b/src/mem-agent/example/protocols/protos/google/protobuf/timestamp.proto new file mode 100644 index 0000000000..cd357864a9 --- /dev/null +++ b/src/mem-agent/example/protocols/protos/google/protobuf/timestamp.proto @@ -0,0 +1,138 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option cc_enable_arenas = true; +option go_package = "github.com/golang/protobuf/ptypes/timestamp"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "TimestampProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; + +// A Timestamp represents a point in time independent of any time zone or local +// calendar, encoded as a count of seconds and fractions of seconds at +// nanosecond resolution. The count is relative to an epoch at UTC midnight on +// January 1, 1970, in the proleptic Gregorian calendar which extends the +// Gregorian calendar backwards to year one. +// +// All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap +// second table is needed for interpretation, using a [24-hour linear +// smear](https://developers.google.com/time/smear). +// +// The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By +// restricting to that range, we ensure that we can convert to and from [RFC +// 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. +// +// # Examples +// +// Example 1: Compute Timestamp from POSIX `time()`. +// +// Timestamp timestamp; +// timestamp.set_seconds(time(NULL)); +// timestamp.set_nanos(0); +// +// Example 2: Compute Timestamp from POSIX `gettimeofday()`. +// +// struct timeval tv; +// gettimeofday(&tv, NULL); +// +// Timestamp timestamp; +// timestamp.set_seconds(tv.tv_sec); +// timestamp.set_nanos(tv.tv_usec * 1000); +// +// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. +// +// FILETIME ft; +// GetSystemTimeAsFileTime(&ft); +// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; +// +// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z +// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. +// Timestamp timestamp; +// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); +// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); +// +// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. +// +// long millis = System.currentTimeMillis(); +// +// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) +// .setNanos((int) ((millis % 1000) * 1000000)).build(); +// +// +// Example 5: Compute Timestamp from current time in Python. +// +// timestamp = Timestamp() +// timestamp.GetCurrentTime() +// +// # JSON Mapping +// +// In JSON format, the Timestamp type is encoded as a string in the +// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the +// format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" +// where {year} is always expressed using four digits while {month}, {day}, +// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional +// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), +// are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone +// is required. A proto3 JSON serializer should always use UTC (as indicated by +// "Z") when printing the Timestamp type and a proto3 JSON parser should be +// able to accept both UTC and other timezones (as indicated by an offset). +// +// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past +// 01:30 UTC on January 15, 2017. +// +// In JavaScript, one can convert a Date object to this format using the +// standard +// [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) +// method. In Python, a standard `datetime.datetime` object can be converted +// to this format using +// [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with +// the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use +// the Joda Time's [`ISODateTimeFormat.dateTime()`]( +// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D +// ) to obtain a formatter capable of generating timestamps in this format. +// +// +message Timestamp { + // Represents seconds of UTC time since Unix epoch + // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + // 9999-12-31T23:59:59Z inclusive. + int64 seconds = 1; + + // Non-negative fractions of a second at nanosecond resolution. Negative + // second values with fractions must still have non-negative nanos values + // that count forward in time. Must be from 0 to 999,999,999 + // inclusive. + int32 nanos = 2; +} diff --git a/src/mem-agent/example/protocols/protos/mem-agent.proto b/src/mem-agent/example/protocols/protos/mem-agent.proto new file mode 100644 index 0000000000..3903e55570 --- /dev/null +++ b/src/mem-agent/example/protocols/protos/mem-agent.proto @@ -0,0 +1,66 @@ +// Copyright (C) 2023 Ant group. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +syntax = "proto3"; + +package MemAgent; + +import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; + +service Control { + rpc MemcgStatus(google.protobuf.Empty) returns (MemcgStatusReply); + rpc MemcgSet(MemcgConfig) returns (google.protobuf.Empty); + rpc CompactSet(CompactConfig) returns (google.protobuf.Empty); +} + +message EvictionCount { + uint64 page = 1; + uint64 no_min_lru_file = 2; + uint64 min_lru_inc = 3; + uint64 other_error = 4; + uint64 error = 5; + uint64 psi_exceeds_limit = 6; +} + +message StatusNuma { + google.protobuf.Timestamp last_inc_time = 1; + uint64 max_seq = 2; + uint64 min_seq = 3; + uint64 run_aging_count = 4; + EvictionCount eviction_count = 5; +} + +message MemCgroup { + uint32 id = 1; + uint64 ino = 2; + string path = 3; + uint64 sleep_psi_exceeds_limit = 4; + map numa = 5; +} + +message MemcgStatusReply { + repeated MemCgroup mem_cgroups = 1; +} + +message MemcgConfig { + optional bool disabled = 1; + optional bool swap = 2; + optional uint32 swappiness_max = 3; + optional uint64 period_secs = 4; + optional uint32 period_psi_percent_limit = 5; + optional uint32 eviction_psi_percent_limit = 6; + optional uint64 eviction_run_aging_count_min = 7; +} + +message CompactConfig { + optional bool disabled = 1; + optional uint64 period_secs = 2; + optional uint32 period_psi_percent_limit = 3; + optional uint32 compact_psi_percent_limit = 4; + optional int64 compact_sec_max = 5; + optional uint32 compact_order = 6; + optional uint64 compact_threshold = 7; + optional uint64 compact_force_times = 8; +} diff --git a/src/mem-agent/example/share/misc.rs b/src/mem-agent/example/share/misc.rs new file mode 100644 index 0000000000..1df293b061 --- /dev/null +++ b/src/mem-agent/example/share/misc.rs @@ -0,0 +1,29 @@ +// Copyright (C) 2023 Ant group. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::{anyhow, Result}; +use chrono::{DateTime, LocalResult, TimeZone, Utc}; +use protobuf::well_known_types::timestamp::Timestamp; + +pub fn datatime_to_timestamp(dt: DateTime) -> Timestamp { + let seconds = dt.timestamp(); + let nanos = dt.timestamp_subsec_nanos(); + + Timestamp { + seconds, + nanos: nanos as i32, + ..Default::default() + } +} + +#[allow(dead_code)] +pub fn timestamp_to_datetime(timestamp: Timestamp) -> Result> { + let seconds = timestamp.seconds; + let nanos = timestamp.nanos; + + match Utc.timestamp_opt(seconds, nanos as u32) { + LocalResult::Single(t) => Ok(t), + _ => Err(anyhow!("Utc.timestamp_opt {} fail", timestamp)), + } +} diff --git a/src/mem-agent/example/share/mod.rs b/src/mem-agent/example/share/mod.rs new file mode 100644 index 0000000000..28057a6a5c --- /dev/null +++ b/src/mem-agent/example/share/mod.rs @@ -0,0 +1,7 @@ +// Copyright (C) 2023 Ant group. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +pub mod misc; +pub mod option; +pub mod rpc; diff --git a/src/mem-agent/example/share/option.rs b/src/mem-agent/example/share/option.rs new file mode 100644 index 0000000000..1a9e178b11 --- /dev/null +++ b/src/mem-agent/example/share/option.rs @@ -0,0 +1,146 @@ +// Copyright (C) 2024 Ant group. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +use crate::protocols::mem_agent as rpc; +use structopt::StructOpt; + +#[derive(Debug, StructOpt)] +pub struct MemcgSetOption { + #[structopt(long)] + memcg_disabled: Option, + #[structopt(long)] + memcg_swap: Option, + #[structopt(long)] + memcg_swappiness_max: Option, + #[structopt(long)] + memcg_period_secs: Option, + #[structopt(long)] + memcg_period_psi_percent_limit: Option, + #[structopt(long)] + memcg_eviction_psi_percent_limit: Option, + #[structopt(long)] + memcg_eviction_run_aging_count_min: Option, +} + +impl MemcgSetOption { + #[allow(dead_code)] + pub fn to_rpc_memcg_config(&self) -> rpc::MemcgConfig { + let config = rpc::MemcgConfig { + disabled: self.memcg_disabled, + swap: self.memcg_swap, + swappiness_max: self.memcg_swappiness_max.map(|v| v as u32), + period_secs: self.memcg_period_secs, + period_psi_percent_limit: self.memcg_period_psi_percent_limit.map(|v| v as u32), + eviction_psi_percent_limit: self.memcg_eviction_psi_percent_limit.map(|v| v as u32), + eviction_run_aging_count_min: self.memcg_eviction_run_aging_count_min, + ..Default::default() + }; + + config + } + + #[allow(dead_code)] + pub fn to_mem_agent_memcg_config(&self) -> mem_agent::memcg::Config { + let mut config = mem_agent::memcg::Config { + ..Default::default() + }; + + if let Some(v) = self.memcg_disabled { + config.disabled = v; + } + if let Some(v) = self.memcg_swap { + config.swap = v; + } + if let Some(v) = self.memcg_swappiness_max { + config.swappiness_max = v; + } + if let Some(v) = self.memcg_period_secs { + config.period_secs = v; + } + if let Some(v) = self.memcg_period_psi_percent_limit { + config.period_psi_percent_limit = v; + } + if let Some(v) = self.memcg_eviction_psi_percent_limit { + config.eviction_psi_percent_limit = v; + } + if let Some(v) = self.memcg_eviction_run_aging_count_min { + config.eviction_run_aging_count_min = v; + } + + config + } +} + +#[derive(Debug, StructOpt)] +pub struct CompactSetOption { + #[structopt(long)] + compact_disabled: Option, + #[structopt(long)] + compact_period_secs: Option, + #[structopt(long)] + compact_period_psi_percent_limit: Option, + #[structopt(long)] + compact_psi_percent_limit: Option, + #[structopt(long)] + compact_sec_max: Option, + #[structopt(long)] + compact_order: Option, + #[structopt(long)] + compact_threshold: Option, + #[structopt(long)] + compact_force_times: Option, +} + +impl CompactSetOption { + #[allow(dead_code)] + pub fn to_rpc_compact_config(&self) -> rpc::CompactConfig { + let config = rpc::CompactConfig { + disabled: self.compact_disabled, + period_secs: self.compact_period_secs, + period_psi_percent_limit: self.compact_period_psi_percent_limit.map(|v| v as u32), + compact_psi_percent_limit: self.compact_psi_percent_limit.map(|v| v as u32), + compact_sec_max: self.compact_sec_max, + compact_order: self.compact_order.map(|v| v as u32), + compact_threshold: self.compact_threshold, + compact_force_times: self.compact_force_times, + ..Default::default() + }; + + config + } + + #[allow(dead_code)] + pub fn to_mem_agent_compact_config(&self) -> mem_agent::compact::Config { + let mut config = mem_agent::compact::Config { + ..Default::default() + }; + + if let Some(v) = self.compact_disabled { + config.disabled = v; + } + if let Some(v) = self.compact_period_secs { + config.period_secs = v; + } + if let Some(v) = self.compact_period_psi_percent_limit { + config.period_psi_percent_limit = v; + } + if let Some(v) = self.compact_psi_percent_limit { + config.compact_psi_percent_limit = v; + } + if let Some(v) = self.compact_sec_max { + config.compact_sec_max = v; + } + if let Some(v) = self.compact_order { + config.compact_order = v; + } + if let Some(v) = self.compact_threshold { + config.compact_threshold = v; + } + if let Some(v) = self.compact_force_times { + config.compact_force_times = v; + } + + config + } +} diff --git a/src/mem-agent/example/share/rpc.rs b/src/mem-agent/example/share/rpc.rs new file mode 100644 index 0000000000..ee5dbea65b --- /dev/null +++ b/src/mem-agent/example/share/rpc.rs @@ -0,0 +1,221 @@ +// Copyright (C) 2023 Ant group. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +use crate::protocols::mem_agent as rpc_mem_agent; +use crate::protocols::{empty, mem_agent_ttrpc}; +use anyhow::{anyhow, Result}; +use async_trait::async_trait; +use mem_agent::{agent, compact, memcg}; +use slog_scope::{error, info}; +use std::fs; +use std::os::unix::fs::PermissionsExt; +use std::sync::Arc; +use tokio::signal::unix::{signal, SignalKind}; +use ttrpc::asynchronous::Server; +use ttrpc::error::Error; +use ttrpc::proto::Code; + +#[derive(Debug)] +pub struct MyControl { + agent: agent::MemAgent, +} + +impl MyControl { + #[allow(dead_code)] + pub fn new(agent: agent::MemAgent) -> Self { + Self { agent } + } +} + +fn mem_cgroup_to_mem_cgroup_rpc(mcg: &memcg::MemCgroup) -> rpc_mem_agent::MemCgroup { + rpc_mem_agent::MemCgroup { + id: mcg.id as u32, + ino: mcg.ino as u64, + path: mcg.path.clone(), + sleep_psi_exceeds_limit: mcg.sleep_psi_exceeds_limit, + numa: mcg + .numa + .iter() + .map(|(numa_id, n)| { + ( + *numa_id, + rpc_mem_agent::StatusNuma { + last_inc_time: protobuf::MessageField::some( + crate::share::misc::datatime_to_timestamp(n.last_inc_time), + ), + max_seq: n.max_seq, + min_seq: n.min_seq, + run_aging_count: n.run_aging_count, + eviction_count: protobuf::MessageField::some( + rpc_mem_agent::EvictionCount { + page: n.eviction_count.page, + no_min_lru_file: n.eviction_count.no_min_lru_file, + min_lru_inc: n.eviction_count.min_lru_inc, + other_error: n.eviction_count.other_error, + error: n.eviction_count.error, + psi_exceeds_limit: n.eviction_count.psi_exceeds_limit, + ..Default::default() + }, + ), + ..Default::default() + }, + ) + }) + .collect(), + ..Default::default() + } +} + +fn mem_cgroups_to_memcg_status_reply( + mgs: Vec, +) -> rpc_mem_agent::MemcgStatusReply { + let mem_cgroups: Vec = mgs + .iter() + .map(|x| mem_cgroup_to_mem_cgroup_rpc(&x)) + .collect(); + + rpc_mem_agent::MemcgStatusReply { + mem_cgroups, + ..Default::default() + } +} + +fn memcgconfig_to_memcg_optionconfig(mc: &rpc_mem_agent::MemcgConfig) -> memcg::OptionConfig { + let moc = memcg::OptionConfig { + disabled: mc.disabled, + swap: mc.swap, + swappiness_max: mc.swappiness_max.map(|val| val as u8), + period_secs: mc.period_secs, + period_psi_percent_limit: mc.period_psi_percent_limit.map(|val| val as u8), + eviction_psi_percent_limit: mc.eviction_psi_percent_limit.map(|val| val as u8), + eviction_run_aging_count_min: mc.eviction_run_aging_count_min, + ..Default::default() + }; + + moc +} + +fn compactconfig_to_compact_optionconfig( + cc: &rpc_mem_agent::CompactConfig, +) -> compact::OptionConfig { + let coc = compact::OptionConfig { + disabled: cc.disabled, + period_secs: cc.period_secs, + period_psi_percent_limit: cc.period_psi_percent_limit.map(|val| val as u8), + compact_psi_percent_limit: cc.compact_psi_percent_limit.map(|val| val as u8), + compact_sec_max: cc.compact_sec_max, + compact_order: cc.compact_order.map(|val| val as u8), + compact_threshold: cc.compact_threshold, + compact_force_times: cc.compact_force_times, + ..Default::default() + }; + + coc +} + +#[async_trait] +impl mem_agent_ttrpc::Control for MyControl { + async fn memcg_status( + &self, + _ctx: &::ttrpc::r#async::TtrpcContext, + _: empty::Empty, + ) -> ::ttrpc::Result { + Ok(mem_cgroups_to_memcg_status_reply( + self.agent.memcg_status_async().await.map_err(|e| { + let estr = format!("agent.memcg_status_async fail: {}", e); + error!("{}", estr); + Error::RpcStatus(ttrpc::get_status(Code::INTERNAL, estr)) + })?, + )) + } + + async fn memcg_set( + &self, + _ctx: &::ttrpc::r#async::TtrpcContext, + mc: rpc_mem_agent::MemcgConfig, + ) -> ::ttrpc::Result { + self.agent + .memcg_set_config_async(memcgconfig_to_memcg_optionconfig(&mc)) + .await + .map_err(|e| { + let estr = format!("agent.memcg_set_config_async fail: {}", e); + error!("{}", estr); + Error::RpcStatus(ttrpc::get_status(Code::INTERNAL, estr)) + })?; + Ok(empty::Empty::new()) + } + + async fn compact_set( + &self, + _ctx: &::ttrpc::r#async::TtrpcContext, + cc: rpc_mem_agent::CompactConfig, + ) -> ::ttrpc::Result { + self.agent + .compact_set_config_async(compactconfig_to_compact_optionconfig(&cc)) + .await + .map_err(|e| { + let estr = format!("agent.compact_set_config_async fail: {}", e); + error!("{}", estr); + Error::RpcStatus(ttrpc::get_status(Code::INTERNAL, estr)) + })?; + Ok(empty::Empty::new()) + } +} + +#[allow(dead_code)] +#[tokio::main] +pub async fn rpc_loop(agent: agent::MemAgent, addr: String) -> Result<()> { + let path = addr + .strip_prefix("unix://") + .ok_or(anyhow!("format of addr {} is not right", addr))?; + if std::path::Path::new(path).exists() { + return Err(anyhow!("addr {} is exist", addr)); + } + + let control = MyControl::new(agent); + let c = Box::new(control) as Box; + let c = Arc::new(c); + let service = mem_agent_ttrpc::create_control(c); + + let mut server = Server::new().bind(&addr).unwrap().register_service(service); + + let metadata = fs::metadata(path).map_err(|e| anyhow!("fs::metadata {} fail: {}", path, e))?; + let mut permissions = metadata.permissions(); + permissions.set_mode(0o600); + fs::set_permissions(path, permissions) + .map_err(|e| anyhow!("fs::set_permissions {} fail: {}", path, e))?; + + let mut interrupt = signal(SignalKind::interrupt()) + .map_err(|e| anyhow!("signal(SignalKind::interrupt()) fail: {}", e))?; + let mut quit = signal(SignalKind::quit()) + .map_err(|e| anyhow!("signal(SignalKind::quit()) fail: {}", e))?; + let mut terminate = signal(SignalKind::terminate()) + .map_err(|e| anyhow!("signal(SignalKind::terminate()) fail: {}", e))?; + server + .start() + .await + .map_err(|e| anyhow!("server.start() fail: {}", e))?; + + tokio::select! { + _ = interrupt.recv() => { + info!("mem-agent: interrupt shutdown"); + } + + _ = quit.recv() => { + info!("mem-agent: quit shutdown"); + } + + _ = terminate.recv() => { + info!("mem-agent: terminate shutdown"); + } + }; + + server + .shutdown() + .await + .map_err(|e| anyhow!("server.shutdown() fail: {}", e))?; + fs::remove_file(&path).map_err(|e| anyhow!("fs::remove_file {} fail: {}", path, e))?; + + Ok(()) +} diff --git a/src/mem-agent/example/srv.rs b/src/mem-agent/example/srv.rs new file mode 100644 index 0000000000..4a08e94789 --- /dev/null +++ b/src/mem-agent/example/srv.rs @@ -0,0 +1,95 @@ +// Copyright (C) 2023 Ant group. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::{anyhow, Result}; +use share::option::{CompactSetOption, MemcgSetOption}; +use slog::{Drain, Level, Logger}; +use slog_async; +use slog_scope::set_global_logger; +use slog_scope::{error, info}; +use slog_term; +use std::fs::OpenOptions; +use std::io::BufWriter; +use structopt::StructOpt; + +mod protocols; +mod share; + +#[derive(StructOpt, Debug)] +#[structopt(name = "mem-agent", about = "Memory agent")] +struct Opt { + #[structopt(long, default_value = "unix:///var/run/mem-agent.sock")] + addr: String, + #[structopt(long)] + log_file: Option, + #[structopt(long, default_value = "trace", parse(try_from_str = parse_slog_level))] + log_level: Level, + #[structopt(flatten)] + memcg: MemcgSetOption, + #[structopt(flatten)] + compact: CompactSetOption, +} + +fn parse_slog_level(src: &str) -> Result { + match src.to_lowercase().as_str() { + "trace" => Ok(Level::Trace), + "debug" => Ok(Level::Debug), + "info" => Ok(Level::Info), + "warning" => Ok(Level::Warning), + "warn" => Ok(Level::Warning), + "error" => Ok(Level::Error), + _ => Err(format!("Invalid log level: {}", src)), + } +} + +fn setup_logging(opt: &Opt) -> Result { + let drain = if let Some(f) = &opt.log_file { + let log_file = OpenOptions::new() + .create(true) + .write(true) + .append(true) + .open(f) + .map_err(|e| anyhow!("Open log file {} fail: {}", f, e))?; + let buffered = BufWriter::new(log_file); + let decorator = slog_term::PlainDecorator::new(buffered); + let drain = slog_term::CompactFormat::new(decorator) + .build() + .filter_level(opt.log_level) + .fuse(); + slog_async::Async::new(drain).build().fuse() + } else { + let decorator = slog_term::TermDecorator::new().stderr().build(); + let drain = slog_term::CompactFormat::new(decorator) + .build() + .filter_level(opt.log_level) + .fuse(); + slog_async::Async::new(drain).build().fuse() + }; + + let logger = Logger::root(drain, slog::o!()); + Ok(set_global_logger(logger.clone())) +} + +fn main() -> Result<()> { + // Check opt + let opt = Opt::from_args(); + + let _ = setup_logging(&opt).map_err(|e| anyhow!("setup_logging fail: {}", e))?; + + let memcg_config = opt.memcg.to_mem_agent_memcg_config(); + let compact_config = opt.compact.to_mem_agent_compact_config(); + + let (ma, _rt) = mem_agent::agent::MemAgent::new(memcg_config, compact_config) + .map_err(|e| anyhow!("MemAgent::new fail: {}", e))?; + + info!("MemAgent started"); + + share::rpc::rpc_loop(ma, opt.addr).map_err(|e| { + let estr = format!("rpc::rpc_loop fail: {}", e); + error!("{}", estr); + anyhow!("{}", estr) + })?; + + Ok(()) +} diff --git a/src/mem-agent/src/agent.rs b/src/mem-agent/src/agent.rs new file mode 100644 index 0000000000..da9a1247d5 --- /dev/null +++ b/src/mem-agent/src/agent.rs @@ -0,0 +1,378 @@ +// Copyright (C) 2023 Ant group. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +use crate::compact; +use crate::memcg::{self, MemCgroup}; +use crate::{error, info}; +use anyhow::{anyhow, Result}; +use std::thread; +use tokio::runtime::{Builder, Runtime}; +use tokio::select; +use tokio::sync::mpsc; +use tokio::sync::oneshot; +use tokio::time::{sleep, Duration, Instant}; + +const AGENT_WORK_ERROR_SLEEP_SECS: u64 = 5 * 60; + +#[derive(Debug)] +enum AgentCmd { + MemcgStatus, + MemcgSet(memcg::OptionConfig), + CompactSet(compact::OptionConfig), +} + +#[allow(dead_code)] +#[derive(Debug)] +enum AgentReturn { + Ok, + Err(anyhow::Error), + MemcgStatus(Vec), +} + +async fn handle_agent_cmd( + cmd: AgentCmd, + ret_tx: oneshot::Sender, + memcg: &mut memcg::MemCG, + comp: &mut compact::Compact, +) -> Result { + #[allow(unused_assignments)] + let mut ret_msg = AgentReturn::Ok; + + let need_reset_mas = match cmd { + AgentCmd::MemcgStatus => { + ret_msg = AgentReturn::MemcgStatus(memcg.get_status().await); + false + } + AgentCmd::MemcgSet(opt) => memcg.set_config(opt).await, + AgentCmd::CompactSet(opt) => comp.set_config(opt).await, + }; + + ret_tx + .send(ret_msg) + .map_err(|e| anyhow!("ret_tx.send failed: {:?}", e))?; + + Ok(need_reset_mas) +} + +fn get_remaining_tokio_duration(memcg: &memcg::MemCG, comp: &compact::Compact) -> Duration { + let memcg_d = memcg.get_remaining_tokio_duration(); + let comp_d = comp.get_remaining_tokio_duration(); + + if memcg_d > comp_d { + comp_d + } else { + memcg_d + } +} + +async fn async_get_remaining_tokio_duration( + memcg: &memcg::MemCG, + comp: &compact::Compact, +) -> Duration { + let memcg_f = memcg.async_get_remaining_tokio_duration(); + let comp_f = comp.async_get_remaining_tokio_duration(); + + let memcg_d = memcg_f.await; + let comp_d = comp_f.await; + + if memcg_d > comp_d { + comp_d + } else { + memcg_d + } +} + +fn agent_work(mut memcg: memcg::MemCG, mut comp: compact::Compact) -> Result { + let memcg_need_reset = if memcg.need_work() { + info!("memcg.work start"); + memcg + .work() + .map_err(|e| anyhow!("memcg.work failed: {}", e))?; + info!("memcg.work stop"); + true + } else { + false + }; + + let compact_need_reset = if comp.need_work() { + info!("compact.work start"); + comp.work() + .map_err(|e| anyhow!("comp.work failed: {}", e))?; + info!("compact.work stop"); + true + } else { + false + }; + + if memcg_need_reset { + memcg.reset_timer(); + } + if compact_need_reset { + comp.reset_timer(); + } + + Ok(get_remaining_tokio_duration(&memcg, &comp)) +} + +struct MemAgentSleep { + duration: Duration, + start_wait_time: Instant, + timeout: bool, +} + +impl MemAgentSleep { + fn new() -> Self { + Self { + duration: Duration::MAX, + start_wait_time: Instant::now(), + timeout: true, + } + } + + fn set_timeout(&mut self) { + self.duration = Duration::MAX; + self.timeout = true; + } + + fn set_sleep(&mut self, d: Duration) { + self.duration = d; + self.start_wait_time = Instant::now(); + } + + /* Return true if timeout */ + fn refresh(&mut self) -> bool { + if self.duration != Duration::MAX { + let elapsed = self.start_wait_time.elapsed(); + if self.duration > elapsed { + self.duration -= elapsed; + } else { + /* timeout */ + self.set_timeout(); + return true; + } + } + + false + } +} + +async fn mem_agent_loop( + mut cmd_rx: mpsc::Receiver<(AgentCmd, oneshot::Sender)>, + mut memcg: memcg::MemCG, + mut comp: compact::Compact, +) -> Result<()> { + let (work_ret_tx, mut work_ret_rx) = mpsc::channel(2); + // the time that wait to next. + let mut mas = MemAgentSleep::new(); + + loop { + if mas.timeout { + let thread_memcg = memcg.clone(); + let thread_comp = comp.clone(); + let thread_work_ret_tx = work_ret_tx.clone(); + thread::spawn(move || { + info!("agent work thread start"); + let d = agent_work(thread_memcg, thread_comp).unwrap_or_else(|err| { + error!("agent work thread fail {}", err); + Duration::from_secs(AGENT_WORK_ERROR_SLEEP_SECS) + }); + if let Err(e) = thread_work_ret_tx.blocking_send(d) { + error!("work_ret_tx.blocking_send failed: {}", e); + } + }); + + mas.timeout = false; + } else { + if mas.refresh() { + continue; + } + } + + info!("mem_agent_loop wait timeout {:?}", mas.duration); + select! { + Some((cmd, ret_tx)) = cmd_rx.recv() => { + if handle_agent_cmd(cmd, ret_tx, &mut memcg, &mut comp).await.map_err(|e| anyhow!("handle_agent_cmd failed: {}", e))? && !mas.timeout{ + mas.set_sleep(async_get_remaining_tokio_duration(&memcg, &comp).await); + } + } + d = work_ret_rx.recv() => { + info!("agent work thread stop"); + mas.set_sleep(d.unwrap_or(Duration::from_secs(AGENT_WORK_ERROR_SLEEP_SECS))); + } + _ = async { + sleep(mas.duration).await; + } => { + mas.set_timeout(); + } + } + } +} + +#[derive(Clone, Debug)] +pub struct MemAgent { + cmd_tx: mpsc::Sender<(AgentCmd, oneshot::Sender)>, +} + +impl MemAgent { + pub fn new( + memcg_config: memcg::Config, + compact_config: compact::Config, + ) -> Result<(Self, Runtime)> { + let mg = memcg::MemCG::new(memcg_config) + .map_err(|e| anyhow!("memcg::MemCG::new fail: {}", e))?; + + let comp = compact::Compact::new(compact_config) + .map_err(|e| anyhow!("compact::Compact::new fail: {}", e))?; + + let (cmd_tx, cmd_rx) = mpsc::channel(10); + + let runtime = Builder::new_multi_thread() + .worker_threads(1) + .enable_all() + .build() + .map_err(|e| anyhow!("Builder::new_multi_threa failed: {}", e))?; + + runtime.spawn(async move { + info!("mem-agent start"); + match mem_agent_loop(cmd_rx, mg, comp).await { + Err(e) => error!("mem-agent error {}", e), + Ok(()) => info!("mem-agent stop"), + } + }); + + Ok((Self { cmd_tx }, runtime)) + } + + async fn send_cmd_async(&self, cmd: AgentCmd) -> Result { + let (ret_tx, ret_rx) = oneshot::channel(); + + self.cmd_tx + .send((cmd, ret_tx)) + .await + .map_err(|e| anyhow!("cmd_tx.send cmd failed: {}", e))?; + + let ret = ret_rx + .await + .map_err(|e| anyhow!("ret_rx.recv failed: {}", e))?; + + Ok(ret) + } + + pub async fn memcg_set_config_async(&self, opt: memcg::OptionConfig) -> Result<()> { + let ret = self + .send_cmd_async(AgentCmd::MemcgSet(opt)) + .await + .map_err(|e| anyhow!("send_cmd failed: {}", e))?; + + match ret { + AgentReturn::Err(e) => Err(anyhow!( + "mem_agent thread memcg_set_config_async failed: {}", + e + )), + AgentReturn::Ok => Ok(()), + _ => Err(anyhow!( + "mem_agent thread memcg_set_config_async return wrong value" + )), + } + } + + pub async fn compact_set_config_async(&self, opt: compact::OptionConfig) -> Result<()> { + let ret = self + .send_cmd_async(AgentCmd::CompactSet(opt)) + .await + .map_err(|e| anyhow!("send_cmd failed: {}", e))?; + + match ret { + AgentReturn::Err(e) => Err(anyhow!( + "mem_agent thread compact_set_config_async failed: {}", + e + )), + AgentReturn::Ok => Ok(()), + _ => Err(anyhow!( + "mem_agent thread compact_set_config_async return wrong value" + )), + } + } + + pub async fn memcg_status_async(&self) -> Result> { + let ret = self + .send_cmd_async(AgentCmd::MemcgStatus) + .await + .map_err(|e| anyhow!("send_cmd failed: {}", e))?; + + let status = match ret { + AgentReturn::Err(e) => { + return Err(anyhow!("mem_agent thread memcg_status_async failed: {}", e)) + } + AgentReturn::Ok => { + return Err(anyhow!( + "mem_agent thread memcg_status_async return wrong value" + )) + } + AgentReturn::MemcgStatus(s) => s, + }; + + Ok(status) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_agent() { + let memcg_config = memcg::Config { + disabled: true, + ..Default::default() + }; + let compact_config = compact::Config { + disabled: true, + ..Default::default() + }; + + let (ma, _rt) = MemAgent::new(memcg_config, compact_config).unwrap(); + + tokio::runtime::Runtime::new() + .unwrap() + .block_on({ + let memcg_config = memcg::OptionConfig { + period_secs: Some(120), + ..Default::default() + }; + ma.memcg_set_config_async(memcg_config) + }) + .unwrap(); + + tokio::runtime::Runtime::new() + .unwrap() + .block_on({ + let compact_config = compact::OptionConfig { + period_secs: Some(280), + ..Default::default() + }; + ma.compact_set_config_async(compact_config) + }) + .unwrap(); + } + + #[test] + fn test_agent_memcg_status() { + let memcg_config = memcg::Config { + disabled: true, + ..Default::default() + }; + let compact_config = compact::Config { + disabled: true, + ..Default::default() + }; + + let (ma, _rt) = MemAgent::new(memcg_config, compact_config).unwrap(); + + tokio::runtime::Runtime::new() + .unwrap() + .block_on(ma.memcg_status_async()) + .unwrap(); + } +} diff --git a/src/mem-agent/src/compact.rs b/src/mem-agent/src/compact.rs new file mode 100644 index 0000000000..93587b5056 --- /dev/null +++ b/src/mem-agent/src/compact.rs @@ -0,0 +1,446 @@ +// Copyright (C) 2024 Ant group. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +use crate::proc; +use crate::psi; +use crate::timer::Timeout; +use crate::{debug, error, info, trace}; +use anyhow::{anyhow, Result}; +use nix::sched::sched_yield; +use std::fs::File; +use std::io::{BufRead, BufReader}; +use std::path::PathBuf; +use std::process::Command; +use std::sync::Arc; +use std::thread; +use std::time::Duration; +use tokio::sync::RwLock; +use tokio::time::Duration as TokioDuration; + +const PAGE_REPORTING_MIN_ORDER: u8 = 9; + +#[derive(Debug, Clone, PartialEq)] +pub struct Config { + pub disabled: bool, + pub psi_path: PathBuf, + pub period_secs: u64, + pub period_psi_percent_limit: u8, + pub compact_psi_percent_limit: u8, + pub compact_sec_max: i64, + + // the order that want to get from compaction + pub compact_order: u8, + + // compact_threshold is the pages number. + // When examining the /proc/pagetypeinfo, if there's an increase in the + // number of movable pages of orders smaller than the compact_order + // compared to the amount following the previous compaction, + // and this increase surpasses a certain threshold—specifically, + // more than 'compact_threshold' number of pages. + // Or the number of free pages has decreased by 'compact_threshold' + // since the previous compaction. + // then the system should initiate another round of memory compaction. + pub compact_threshold: u64, + + // After one compaction, if there has not been a compaction within + // the next compact_force_times times, a compaction will be forced + // regardless of the system's memory situation. + // If compact_force_times is set to 0, will do force compaction each time. + // If compact_force_times is set to std::u64::MAX, will never do force compaction. + pub compact_force_times: u64, +} + +impl Default for Config { + fn default() -> Self { + Self { + disabled: false, + psi_path: PathBuf::from(""), + period_secs: 10 * 60, + period_psi_percent_limit: 1, + compact_psi_percent_limit: 5, + compact_sec_max: 30 * 60, + compact_order: PAGE_REPORTING_MIN_ORDER, + compact_threshold: 2 << PAGE_REPORTING_MIN_ORDER, + compact_force_times: std::u64::MAX, + } + } +} + +#[derive(Debug, Clone, Default)] +pub struct OptionConfig { + pub disabled: Option, + pub psi_path: Option, + pub period_secs: Option, + pub period_psi_percent_limit: Option, + pub compact_psi_percent_limit: Option, + pub compact_sec_max: Option, + + pub compact_order: Option, + + pub compact_threshold: Option, + + pub compact_force_times: Option, +} + +#[derive(Debug, Clone)] +struct CompactCore { + timeout: Timeout, + config: Config, + psi: psi::Period, + force_counter: u64, + prev_free_movable_pages_after_compact: u64, + prev_memfree_kb: u64, +} + +impl CompactCore { + fn new(config: Config) -> Self { + Self { + timeout: Timeout::new(config.period_secs), + psi: psi::Period::new(&config.psi_path, true), + force_counter: 0, + prev_free_movable_pages_after_compact: 0, + prev_memfree_kb: 0, + config, + } + } + + fn psi_ok(&mut self) -> bool { + if crate::misc::is_test_environment() { + return false; + } + let percent = match self.psi.get_percent() { + Ok(v) => v, + Err(e) => { + debug!("psi.get_percent failed: {}", e); + return false; + } + }; + if percent > self.config.period_psi_percent_limit as u64 { + info!( + "compact will not work because period psi {}% exceeds limit", + percent + ); + false + } else { + true + } + } + + fn need_force_compact(&self) -> bool { + if self.config.compact_force_times == std::u64::MAX { + return false; + } + + self.force_counter >= self.config.compact_force_times + } + + fn check_compact_threshold(&self, memfree_kb: u64, free_movable_pages: u64) -> bool { + if self.prev_memfree_kb > memfree_kb + (self.config.compact_threshold << 2) { + return true; + } + + let threshold = self.config.compact_threshold + self.prev_free_movable_pages_after_compact; + if free_movable_pages > threshold { + true + } else { + info!( + "compact will not work because free movable pages {} less than threshold {} and prev_free {}kB current_free {}kB", + free_movable_pages, threshold, self.prev_memfree_kb, memfree_kb + ); + false + } + } + + fn get_special_psi(&self) -> psi::Period { + psi::Period::new(&self.config.psi_path, true) + } + + fn set_prev(&mut self, memfree_kb: u64, free_movable_pages: u64) { + self.prev_memfree_kb = memfree_kb; + self.prev_free_movable_pages_after_compact = free_movable_pages; + } + + fn set_disabled(&mut self, disabled: bool) { + if !disabled { + self.timeout.reset(); + } + + self.config.disabled = disabled; + } + + // return if MemAgentSleep need be reset + fn set_config(&mut self, new_config: OptionConfig) -> bool { + let mut need_reset_mas = false; + + if let Some(d) = new_config.disabled { + if self.config.disabled != d { + self.set_disabled(d); + need_reset_mas = true; + } + } + if let Some(p) = new_config.psi_path { + self.config.psi_path = p.clone(); + } + if let Some(p) = new_config.period_psi_percent_limit { + self.config.period_psi_percent_limit = p; + } + if let Some(p) = new_config.compact_psi_percent_limit { + self.config.compact_psi_percent_limit = p; + } + if let Some(p) = new_config.compact_sec_max { + self.config.compact_sec_max = p; + } + if let Some(p) = new_config.compact_order { + self.config.compact_order = p; + } + if let Some(p) = new_config.compact_threshold { + self.config.compact_threshold = p; + } + if let Some(p) = new_config.compact_force_times { + self.config.compact_force_times = p; + } + if let Some(p) = new_config.period_secs { + self.config.period_secs = p; + self.timeout.set_sleep_duration(p); + if !self.config.disabled { + need_reset_mas = true; + } + } + + info!("new compact config: {:#?}", self.config); + if need_reset_mas { + info!("need reset mem-agent sleep"); + } + + need_reset_mas + } + + fn need_work(&self) -> bool { + if self.config.disabled { + return false; + } + self.timeout.is_timeout() + } + + pub fn get_remaining_tokio_duration(&self) -> TokioDuration { + if self.config.disabled { + return TokioDuration::MAX; + } + + self.timeout.remaining_tokio_duration() + } +} + +#[derive(Debug, Clone)] +pub struct Compact { + core: Arc>, +} + +impl Compact { + pub fn new(mut config: Config) -> Result { + config.psi_path = + psi::check(&config.psi_path).map_err(|e| anyhow!("psi::check failed: {}", e))?; + + let c = Self { + core: Arc::new(RwLock::new(CompactCore::new(config))), + }; + + Ok(c) + } + + fn calculate_free_movable_pages(&self) -> Result { + let file = File::open("/proc/pagetypeinfo")?; + let reader = BufReader::new(file); + + let order_limit = self.core.blocking_read().config.compact_order as usize; + + let mut total_free_movable_pages = 0; + + for line in reader.lines() { + let line = line?; + if line.contains("Movable") { + let parts: Vec<&str> = line.split_whitespace().collect(); + if let Some(index) = parts.iter().position(|&element| element == "Movable") { + for (order, &count_str) in parts[(index + 1)..].iter().enumerate() { + if order < order_limit { + if let Ok(count) = count_str.parse::() { + total_free_movable_pages += count * 1 << order; + } + } + } + } + } + } + + Ok(total_free_movable_pages) + } + + fn check_compact_threshold(&self) -> bool { + let memfree_kb = match proc::get_memfree_kb() { + Ok(v) => v, + Err(e) => { + error!("get_memfree_kb failed: {}", e); + return false; + } + }; + let free_movable_pages = match self.calculate_free_movable_pages() { + Ok(v) => v, + Err(e) => { + error!("calculate_free_movable_pages failed: {}", e); + return false; + } + }; + + self.core + .blocking_read() + .check_compact_threshold(memfree_kb, free_movable_pages) + } + + fn set_prev(&mut self) -> Result<()> { + let memfree_kb = + proc::get_memfree_kb().map_err(|e| anyhow!("get_memfree_kb failed: {}", e))?; + let free_movable_pages = self + .calculate_free_movable_pages() + .map_err(|e| anyhow!("calculate_free_movable_pages failed: {}", e))?; + + self.core + .blocking_write() + .set_prev(memfree_kb, free_movable_pages); + + Ok(()) + } + + fn do_compact(&self) -> Result<()> { + let compact_psi_percent_limit = self.core.blocking_read().config.compact_psi_percent_limit; + let mut compact_psi = self.core.blocking_read().get_special_psi(); + let mut rest_sec = self.core.blocking_read().config.compact_sec_max; + + if let Err(e) = sched_yield() { + error!("sched_yield failed: {:?}", e); + } + + info!("compact start"); + + let mut child = Command::new("sh") + .arg("-c") + .arg("echo 1 > /proc/sys/vm/compact_memory") + .spawn() + .map_err(|e| anyhow!("Command::new failed: {}", e))?; + + debug!("compact pid {}", child.id()); + + let mut killed = false; + loop { + match child.try_wait() { + Ok(Some(status)) => { + debug!("compact done with status {}", status); + break; + } + Ok(None) => { + if killed { + if rest_sec <= 0 { + error!("compact killed but not quit"); + break; + } else { + debug!("compact killed and keep wait"); + } + } else { + if rest_sec <= 0 { + debug!("compact timeout"); + child + .kill() + .map_err(|e| anyhow!("child.kill failed: {}", e))?; + killed = true; + } + } + + let percent = compact_psi + .get_percent() + .map_err(|e| anyhow!("compact_psi.get_percent failed: {}", e))?; + if percent > compact_psi_percent_limit as u64 { + info!( + "compaction need stop because period psi {}% exceeds limit", + percent + ); + child + .kill() + .map_err(|e| anyhow!("child.kill failed: {}", e))?; + killed = true; + } + } + Err(e) => { + // try_wait will fail with code 10 because some task will + // wait compact task before try_wait. + debug!("compact try_wait fail: {:?}", e); + break; + } + } + + thread::sleep(Duration::from_secs(1)); + rest_sec -= 1; + } + + info!("compact stop"); + + Ok(()) + } + + pub fn need_work(&self) -> bool { + self.core.blocking_read().need_work() + } + + pub fn reset_timer(&mut self) { + self.core.blocking_write().timeout.reset(); + } + + pub fn get_remaining_tokio_duration(&self) -> TokioDuration { + self.core.blocking_read().get_remaining_tokio_duration() + } + + pub async fn async_get_remaining_tokio_duration(&self) -> TokioDuration { + self.core.read().await.get_remaining_tokio_duration() + } + + pub fn work(&mut self) -> Result<()> { + let mut can_work = self.core.blocking_write().psi_ok(); + if can_work { + if !self.core.blocking_read().need_force_compact() { + if !self.check_compact_threshold() { + trace!("not enough free movable pages"); + can_work = false; + } + } else { + trace!("force compact"); + } + } + + if can_work { + self.do_compact() + .map_err(|e| anyhow!("do_compact failed: {}", e))?; + + self.set_prev()?; + + self.core.blocking_write().force_counter = 0; + } else { + self.core.blocking_write().force_counter += 1; + } + + Ok(()) + } + + pub async fn set_config(&mut self, new_config: OptionConfig) -> bool { + self.core.write().await.set_config(new_config) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_compact() { + let mut c = Compact::new(Config::default()).unwrap(); + assert!(c.work().is_ok()); + } +} diff --git a/src/mem-agent/src/lib.rs b/src/mem-agent/src/lib.rs new file mode 100644 index 0000000000..f503f93140 --- /dev/null +++ b/src/mem-agent/src/lib.rs @@ -0,0 +1,12 @@ +// Copyright (C) 2024 Ant group. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +pub mod agent; +pub mod compact; +pub mod memcg; +mod mglru; +mod misc; +mod proc; +mod psi; +mod timer; diff --git a/src/mem-agent/src/memcg.rs b/src/mem-agent/src/memcg.rs new file mode 100644 index 0000000000..c9d7c0d896 --- /dev/null +++ b/src/mem-agent/src/memcg.rs @@ -0,0 +1,843 @@ +// Copyright (C) 2023 Ant group. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +use crate::mglru::{self, MGenLRU}; +use crate::timer::Timeout; +use crate::{debug, error, info, trace}; +use crate::{proc, psi}; +use anyhow::{anyhow, Context, Result}; +use chrono::{DateTime, Utc}; +use nix::sched::sched_yield; +use page_size; +use std::collections::HashMap; +use std::collections::HashSet; +use std::path::PathBuf; +use std::sync::Arc; +use tokio::sync::RwLock; +use tokio::time::Duration as TokioDuration; + +/* If last_inc_time to current_time small than IDLE_FRESH_IGNORE_SECS, +not do idle_fresh for this memcg. */ +const IDLE_FRESH_IGNORE_SECS: i64 = 60; + +#[derive(Debug, Clone, PartialEq)] +pub struct Config { + pub disabled: bool, + pub swap: bool, + pub swappiness_max: u8, + pub psi_path: PathBuf, + pub period_secs: u64, + pub period_psi_percent_limit: u8, + pub eviction_psi_percent_limit: u8, + pub eviction_run_aging_count_min: u64, +} + +impl Default for Config { + fn default() -> Self { + Self { + disabled: false, + swap: false, + swappiness_max: 50, + psi_path: PathBuf::from(""), + period_secs: 10 * 60, + period_psi_percent_limit: 1, + eviction_psi_percent_limit: 1, + eviction_run_aging_count_min: 3, + } + } +} + +#[derive(Debug, Clone, Default)] +pub struct OptionConfig { + pub disabled: Option, + pub swap: Option, + pub swappiness_max: Option, + pub psi_path: Option, + pub period_secs: Option, + pub period_psi_percent_limit: Option, + pub eviction_psi_percent_limit: Option, + pub eviction_run_aging_count_min: Option, +} + +#[derive(Debug, Clone)] +pub struct EvictionCount { + pub page: u64, + pub no_min_lru_file: u64, + pub min_lru_inc: u64, + pub other_error: u64, + pub error: u64, + pub psi_exceeds_limit: u64, +} + +#[derive(Debug, Clone)] +pub struct Numa { + pub max_seq: u64, + pub min_seq: u64, + pub last_inc_time: DateTime, + pub min_lru_file: u64, + pub min_lru_anon: u64, + + pub run_aging_count: u64, + pub eviction_count: EvictionCount, +} + +impl Numa { + fn new(mglru: &MGenLRU) -> Self { + Self { + max_seq: mglru.max_seq, + min_seq: mglru.min_seq, + last_inc_time: mglru.last_birth, + min_lru_file: mglru.lru[mglru.min_lru_index].file, + min_lru_anon: mglru.lru[mglru.min_lru_index].anon, + run_aging_count: 0, + eviction_count: EvictionCount { + page: 0, + no_min_lru_file: 0, + min_lru_inc: 0, + other_error: 0, + error: 0, + psi_exceeds_limit: 0, + }, + } + } + + fn update(&mut self, mglru: &MGenLRU) { + self.max_seq = mglru.max_seq; + self.min_seq = mglru.min_seq; + self.last_inc_time = mglru.last_birth; + self.min_lru_file = mglru.lru[mglru.min_lru_index].file; + self.min_lru_anon = mglru.lru[mglru.min_lru_index].anon; + } +} + +#[derive(Debug, Clone)] +pub struct MemCgroup { + /* get from Linux kernel static inline unsigned short mem_cgroup_id(struct mem_cgroup *memcg) */ + pub id: u16, + pub ino: usize, + pub path: String, + pub numa: HashMap, + psi: psi::Period, + + pub sleep_psi_exceeds_limit: u64, +} + +impl MemCgroup { + fn new( + id: &usize, + ino: &usize, + path: &String, + hmg: &HashMap, + psi_path: &PathBuf, + ) -> Self { + let s = Self { + id: *id as u16, + ino: *ino, + path: path.to_string(), + numa: hmg + .iter() + .map(|(numa_id, mglru)| (*numa_id as u32, Numa::new(mglru))) + .collect(), + psi: psi::Period::new(&psi_path.join(path.trim_start_matches('/')), false), + sleep_psi_exceeds_limit: 0, + }; + info!("MemCgroup::new {:?}", s); + s + } + + fn update_from_hostmemcg(&mut self, hmg: &HashMap) { + self.numa + .retain(|numa_id, _| hmg.contains_key(&(*numa_id as usize))); + + for (numa_id, mglru) in hmg { + self.numa + .entry(*numa_id as u32) + .and_modify(|e| e.update(mglru)) + .or_insert(Numa::new(mglru)); + } + } +} + +#[derive(Debug, Clone)] +enum EvictionStopReason { + None, + NoMinLru, + MinLruInc, + GetError, + PsiExceedsLimit, +} + +#[derive(Debug, Clone)] +struct EvictionInfo { + psi: psi::Period, + + // the min_lru_file and min_lru_anon before last time mglru::run_eviction + last_min_lru_file: u64, + last_min_lru_anon: u64, + + // the evicted page count + file_page_count: u64, + anon_page_count: u64, + + only_swap_mode: bool, + anon_eviction_max: u64, + + stop_reason: EvictionStopReason, +} + +#[derive(Debug, Clone)] +struct Info { + memcg_id: usize, + numa_id: usize, + path: String, + max_seq: u64, + min_seq: u64, + last_inc_time: DateTime, + min_lru_file: u64, + min_lru_anon: u64, + + eviction: Option, +} + +impl Info { + fn new(mcg: &MemCgroup, numa_id: usize, numa: &Numa) -> Self { + Self { + memcg_id: mcg.id as usize, + numa_id: numa_id, + path: mcg.path.clone(), + min_seq: numa.min_seq, + max_seq: numa.max_seq, + last_inc_time: numa.last_inc_time, + min_lru_file: numa.min_lru_file, + min_lru_anon: numa.min_lru_anon, + eviction: None, + } + } + + fn update(&mut self, numa: &Numa) { + self.min_seq = numa.min_seq; + self.max_seq = numa.max_seq; + self.last_inc_time = numa.last_inc_time; + self.min_lru_file = numa.min_lru_file; + self.min_lru_anon = numa.min_lru_anon; + } +} + +#[derive(Debug)] +struct MemCgroups { + timeout: Timeout, + config: Config, + id_map: HashMap, + ino2id: HashMap, + path2id: HashMap, +} + +impl MemCgroups { + fn new(config: Config) -> Self { + Self { + timeout: Timeout::new(config.period_secs), + config, + id_map: HashMap::new(), + ino2id: HashMap::new(), + path2id: HashMap::new(), + } + } + + /* Remove not exist in host or id, ino changed memcgroup */ + fn remove_changed( + &mut self, + mg_hash: &HashMap)>, + ) { + /* Remove not exist in host or id, ino changed memcgroup */ + let mut remove_target = Vec::new(); + for (_, mg) in &self.id_map { + let mut should_remove = true; + + if let Some((id, ino, _)) = mg_hash.get(&mg.path) { + if mg.id as usize == *id && mg.ino == *ino { + should_remove = false; + } + } + + if should_remove { + remove_target.push((mg.id, mg.ino, mg.path.clone())); + } + } + + for (id, ino, path) in remove_target { + self.id_map.remove(&id); + self.ino2id.remove(&ino); + self.path2id.remove(&path); + info!("Remove memcg {} {} {} because host changed.", id, ino, path) + } + } + + fn update_and_add( + &mut self, + mg_hash: &HashMap)>, + ) { + for (path, (id, ino, hmg)) in mg_hash { + if *id == 0 { + info!( + "Not add {} {} {} because it is disabled.", + *id, + *ino, + path.to_string() + ) + } + if let Some(mg) = self.id_map.get_mut(&(*id as u16)) { + mg.update_from_hostmemcg(&hmg); + } else { + self.id_map.insert( + *id as u16, + MemCgroup::new(id, ino, path, hmg, &self.config.psi_path), + ); + self.ino2id.insert(*ino, *id as u16); + self.path2id.insert(path.to_string(), *id as u16); + } + } + } + + fn check_psi_get_info(&mut self) -> Vec { + let mut info_ret = Vec::new(); + + for (_, mcg) in self.id_map.iter_mut() { + let percent = match mcg.psi.get_percent() { + Ok(p) => p, + Err(e) => { + debug!("mcg.psi.get_percent {} failed: {}", mcg.path, e); + continue; + } + }; + if percent > self.config.period_psi_percent_limit as u64 { + mcg.sleep_psi_exceeds_limit += 1; + info!("{} period psi {}% exceeds limit", mcg.path, percent); + continue; + } + + for (numa_id, numa) in &mcg.numa { + info_ret.push(Info::new(&mcg, *numa_id as usize, numa)); + } + } + + info_ret + } + + fn update_info(&self, infov: &mut Vec) { + let mut i = 0; + while i < infov.len() { + if let Some(mg) = self.id_map.get(&(infov[i].memcg_id as u16)) { + if let Some(numa) = mg.numa.get(&(infov[i].numa_id as u32)) { + infov[i].update(numa); + i += 1; + continue; + } + } + + infov.remove(i); + } + } + + fn inc_run_aging_count(&mut self, infov: &mut Vec) { + let mut i = 0; + while i < infov.len() { + if let Some(mg) = self.id_map.get_mut(&(infov[i].memcg_id as u16)) { + if let Some(numa) = mg.numa.get_mut(&(infov[i].numa_id as u32)) { + numa.run_aging_count += 1; + if numa.run_aging_count >= self.config.eviction_run_aging_count_min { + i += 1; + continue; + } + } + } + + infov.remove(i); + } + } + + fn record_eviction(&mut self, infov: &Vec) { + for info in infov { + if let Some(mg) = self.id_map.get_mut(&(info.memcg_id as u16)) { + if let Some(numa) = mg.numa.get_mut(&(info.numa_id as u32)) { + if let Some(ei) = &info.eviction { + numa.eviction_count.page += ei.file_page_count + ei.anon_page_count; + match ei.stop_reason { + EvictionStopReason::None => numa.eviction_count.other_error += 1, + EvictionStopReason::NoMinLru => { + numa.eviction_count.no_min_lru_file += 1 + } + EvictionStopReason::MinLruInc => numa.eviction_count.min_lru_inc += 1, + EvictionStopReason::GetError => numa.eviction_count.error += 1, + EvictionStopReason::PsiExceedsLimit => { + numa.eviction_count.psi_exceeds_limit += 1 + } + } + } + } + } + } + } + + fn set_disabled(&mut self, disabled: bool) { + if !disabled { + self.timeout.reset(); + } + + self.config.disabled = disabled; + } + + // return if MemAgentSleep need be reset + fn set_config(&mut self, new_config: OptionConfig) -> bool { + let mut need_reset_mas = false; + + if let Some(d) = new_config.disabled { + if self.config.disabled != d { + self.set_disabled(d); + need_reset_mas = true; + } + } + if let Some(s) = new_config.swap { + self.config.swap = s; + } + if let Some(s) = new_config.swappiness_max { + self.config.swappiness_max = s; + } + if let Some(p) = new_config.psi_path { + self.config.psi_path = p.clone(); + } + if let Some(p) = new_config.period_psi_percent_limit { + self.config.period_psi_percent_limit = p; + } + if let Some(p) = new_config.eviction_psi_percent_limit { + self.config.eviction_psi_percent_limit = p; + } + if let Some(p) = new_config.eviction_run_aging_count_min { + self.config.eviction_run_aging_count_min = p; + } + if let Some(p) = new_config.period_secs { + self.config.period_secs = p; + self.timeout.set_sleep_duration(p); + if !self.config.disabled { + need_reset_mas = true; + } + } + + info!("new memcg config: {:#?}", self.config); + if need_reset_mas { + info!("need reset mem-agent sleep"); + } + + need_reset_mas + } + + fn need_work(&self) -> bool { + if self.config.disabled { + return false; + } + self.timeout.is_timeout() + } + + pub fn get_remaining_tokio_duration(&self) -> TokioDuration { + if self.config.disabled { + return TokioDuration::MAX; + } + + self.timeout.remaining_tokio_duration() + } +} + +#[derive(Debug, Clone)] +pub struct MemCG { + memcgs: Arc>, +} + +impl MemCG { + pub fn new(mut config: Config) -> Result { + mglru::check().map_err(|e| anyhow!("mglru::check failed: {}", e))?; + config.psi_path = + psi::check(&config.psi_path).map_err(|e| anyhow!("psi::check failed: {}", e))?; + + let memcg = Self { + memcgs: Arc::new(RwLock::new(MemCgroups::new(config))), + }; + + Ok(memcg) + } + + /* + * If target_paths.len == 0, + * will remove the updated or not exist cgroup in the host from MemCgroups. + * If target_paths.len > 0, will not do that. + */ + fn refresh(&mut self, target_paths: &HashSet) -> Result<()> { + let mg_hash = mglru::host_memcgs_get(target_paths, true) + .map_err(|e| anyhow!("lru_gen_parse::file_parse failed: {}", e))?; + + let mut mgs = self.memcgs.blocking_write(); + + if target_paths.len() == 0 { + mgs.remove_changed(&mg_hash); + } + mgs.update_and_add(&mg_hash); + + Ok(()) + } + + fn run_aging(&mut self, infov: &mut Vec, swap: bool) { + infov.retain(|info| { + let now = Utc::now(); + if now.signed_duration_since(info.last_inc_time).num_seconds() < IDLE_FRESH_IGNORE_SECS + { + info!( + "{} not run aging because last_inc_time {}", + info.path, info.last_inc_time, + ); + true + } else { + let res = if let Err(e) = + mglru::run_aging(info.memcg_id, info.numa_id, info.max_seq, swap, true) + { + error!( + "mglru::run_aging {} {} {} failed: {}", + info.path, info.memcg_id, info.numa_id, e + ); + false + } else { + true + }; + + if let Err(e) = sched_yield() { + error!("sched_yield failed: {:?}", e); + } + + res + } + }); + + self.memcgs.blocking_write().inc_run_aging_count(infov); + } + + fn swap_not_available(&self) -> Result { + let freeswap_kb = proc::get_freeswap_kb().context("proc::get_freeswap_kb")?; + + if freeswap_kb > (256 * page_size::get() as u64 / 1024) { + Ok(false) + } else { + Ok(true) + } + } + + fn get_swappiness(&self, anon_count: u64, file_count: u64) -> u8 { + assert!( + anon_count != 0 && file_count != 0, + "anon and file must be non-zero" + ); + + let total = anon_count + file_count; + let c = 200 * anon_count / total; + + c as u8 + } + + fn run_eviction(&mut self, infov: &mut Vec, mut swap: bool) -> Result<()> { + if self + .swap_not_available() + .context("self.swap_not_available")? + { + swap = false; + } + + let psi_path = self.memcgs.blocking_read().config.psi_path.clone(); + for info in infov.into_iter() { + info.eviction = Some(EvictionInfo { + psi: psi::Period::new(&psi_path.join(info.path.trim_start_matches('/')), false), + last_min_lru_file: 0, + last_min_lru_anon: 0, + file_page_count: 0, + anon_page_count: 0, + only_swap_mode: false, + anon_eviction_max: 0, + stop_reason: EvictionStopReason::None, + }); + } + + let mut removed_infov = Vec::new(); + + let mut ret = Ok(()); + let eviction_psi_percent_limit = self + .memcgs + .blocking_read() + .config + .eviction_psi_percent_limit as u64; + + let swappiness_max = self.memcgs.blocking_read().config.swappiness_max; + + 'main_loop: while infov.len() != 0 { + let mut i = 0; + while i < infov.len() { + let path_set: HashSet = + infov.iter().map(|info| info.path.clone()).collect(); + match self.refresh(&path_set) { + Ok(_) => {} + Err(e) => { + ret = Err(anyhow!("refresh failed: {}", e)); + break 'main_loop; + } + }; + self.update_info(infov); + + let ci = infov[i].clone(); + + trace!("{} {} run_eviction single loop start", ci.path, ci.numa_id); + + if let Some(ref mut ei) = infov[i].eviction { + if ei.last_min_lru_file == 0 && ei.last_min_lru_anon == 0 { + // First loop + trace!("{} {} run_eviction begin", ci.path, ci.numa_id,); + if ci.min_lru_file == 0 { + if !swap || ci.min_lru_anon == 0 { + info!( + "{} {} run_eviction stop because min_lru_file is 0 or min_lru_anon is 0, release {} {} pages", + ci.path, ci.numa_id, ei.anon_page_count, ei.file_page_count, + ); + ei.stop_reason = EvictionStopReason::NoMinLru; + removed_infov.push(infov.remove(i)); + continue; + } else { + ei.only_swap_mode = true; + ei.anon_eviction_max = + ci.min_lru_anon * swappiness_max as u64 / 200; + trace!( + "{} {} run_eviction only swap mode anon_eviction_max {}", + ci.path, + ci.numa_id, + ei.anon_eviction_max + ); + } + } + + ei.last_min_lru_file = ci.min_lru_file; + ei.last_min_lru_anon = ci.min_lru_anon; + } else { + if (!ei.only_swap_mode && ci.min_lru_file >= ei.last_min_lru_file) + || (ei.only_swap_mode && ci.min_lru_file > 0) + || (swap && ci.min_lru_anon > ei.last_min_lru_anon) + { + info!( + "{} {} run_eviction stop because min_lru_file {} last_min_lru_file {} min_lru_anon {} last_min_lru_anon {}, release {} {} pages", + ci.path, ci.numa_id, ci.min_lru_file, ei.last_min_lru_file, ci.min_lru_anon, ei.last_min_lru_anon, ei.anon_page_count, ei.file_page_count, + ); + + ei.stop_reason = EvictionStopReason::MinLruInc; + removed_infov.push(infov.remove(i)); + continue; + } + + let released = ei.last_min_lru_anon - ci.min_lru_anon; + trace!( + "{} {} run_eviction anon {} pages", + ci.path, + ci.numa_id, + released + ); + ei.anon_page_count += released; + + let released = ei.last_min_lru_file - ci.min_lru_file; + trace!( + "{} {} run_eviction file {} pages", + ci.path, + ci.numa_id, + released + ); + ei.file_page_count += released; + + if !ei.only_swap_mode { + if ci.min_lru_file == 0 { + info!( + "{} {} run_eviction stop because min_lru_file is 0, release {} {} pages", + ci.path, ci.numa_id, ei.anon_page_count, ei.file_page_count, + ); + ei.stop_reason = EvictionStopReason::NoMinLru; + removed_infov.push(infov.remove(i)); + continue; + } + } else { + if ei.anon_page_count >= ei.anon_eviction_max { + info!( + "{} {} run_eviction stop because anon_page_count is bigger than anon_eviction_max {}, release {} {} pages", + ci.path, ci.numa_id, ei.anon_eviction_max, ei.anon_page_count, ei.file_page_count, + ); + ei.stop_reason = EvictionStopReason::NoMinLru; + removed_infov.push(infov.remove(i)); + continue; + } + } + + let percent = match ei.psi.get_percent() { + Ok(p) => p, + Err(e) => { + debug!( + "{} {} ei.psi.get_percent failed: {}, release {} {} pages", + ci.path, ci.numa_id, e, ei.anon_page_count, ei.file_page_count, + ); + ei.stop_reason = EvictionStopReason::GetError; + removed_infov.push(infov.remove(i)); + continue; + } + }; + if percent > eviction_psi_percent_limit { + info!( + "{} {} run_eviction stop because period psi {}% exceeds limit, release {} {} pages", + ci.path, ci.numa_id, percent, ei.anon_page_count, ei.file_page_count, + ); + ei.stop_reason = EvictionStopReason::PsiExceedsLimit; + removed_infov.push(infov.remove(i)); + continue; + } + + ei.last_min_lru_file = ci.min_lru_file; + ei.last_min_lru_anon = ci.min_lru_anon; + } + + // get swapiness + let swappiness = if ei.only_swap_mode { + 200 + } else if !swap + || ci.min_lru_anon == 0 + || match self.swap_not_available() { + Ok(b) => b, + Err(e) => { + ret = Err(anyhow!("swap_not_available failed: {:?}", e)); + break 'main_loop; + } + } + { + 0 + } else { + let s = self.get_swappiness(ci.min_lru_anon, ci.min_lru_file); + if s > swappiness_max { + swappiness_max + } else { + s + } + }; + + trace!( + "{} {} run_eviction min_seq {} swappiness {}", + ci.path, + ci.numa_id, + ci.min_seq, + swappiness + ); + + match mglru::run_eviction(ci.memcg_id, ci.numa_id, ci.min_seq, swappiness, 1) { + Ok(_) => {} + Err(e) => { + error!( + "{} {} mglru::run_eviction failed: {}, release {} {} pages", + ci.path, ci.numa_id, e, ei.anon_page_count, ei.file_page_count, + ); + ei.stop_reason = EvictionStopReason::GetError; + removed_infov.push(infov.remove(i)); + continue; + } + } + + if let Err(e) = sched_yield() { + error!("sched_yield failed: {:?}", e); + } + } else { + unreachable!(); + } + + i += 1; + } + } + + let mut mgs = self.memcgs.blocking_write(); + mgs.record_eviction(&infov); + mgs.record_eviction(&removed_infov); + + ret + } + + fn check_psi_get_info(&mut self) -> Vec { + self.memcgs.blocking_write().check_psi_get_info() + } + + fn update_info(&self, infov: &mut Vec) { + self.memcgs.blocking_read().update_info(infov); + } + + pub fn need_work(&self) -> bool { + self.memcgs.blocking_read().need_work() + } + + pub fn reset_timer(&mut self) { + self.memcgs.blocking_write().timeout.reset(); + } + + pub fn get_remaining_tokio_duration(&self) -> TokioDuration { + self.memcgs.blocking_read().get_remaining_tokio_duration() + } + + pub async fn async_get_remaining_tokio_duration(&self) -> TokioDuration { + self.memcgs.read().await.get_remaining_tokio_duration() + } + + pub fn work(&mut self) -> Result<()> { + /* Refresh memcgroups info from host and store it to infov. */ + self.refresh(&HashSet::new()) + .map_err(|e| anyhow!("first refresh failed: {}", e))?; + let mut infov = self.check_psi_get_info(); + + let swap = self.memcgs.blocking_read().config.swap; + + /* Run aging with infov. */ + self.run_aging(&mut infov, swap); + + self.run_eviction(&mut infov, swap) + .map_err(|e| anyhow!("run_eviction failed: {}", e))?; + + Ok(()) + } + + pub async fn set_config(&mut self, new_config: OptionConfig) -> bool { + self.memcgs.write().await.set_config(new_config) + } + + pub async fn get_status(&self) -> Vec { + let mut mcgs = Vec::new(); + + let mgs = self.memcgs.read().await; + for (_, m) in mgs.id_map.iter() { + mcgs.push((*m).clone()); + } + + mcgs + } +} + +mod tests { + #[allow(unused_imports)] + use super::*; + + #[test] + fn test_memcg_swap_not_available() { + let m = MemCG::new(Config::default()).unwrap(); + assert!(m.swap_not_available().is_ok()); + } + + #[test] + fn test_memcg_get_swappiness() { + let m = MemCG::new(Config::default()).unwrap(); + assert_eq!(m.get_swappiness(100, 50), 133); + } + + #[test] + fn test_memcg_need_work() { + let m = MemCG::new(Config::default()).unwrap(); + assert_eq!(m.need_work(), true); + } +} diff --git a/src/mem-agent/src/mglru.rs b/src/mem-agent/src/mglru.rs new file mode 100644 index 0000000000..dda13bfa08 --- /dev/null +++ b/src/mem-agent/src/mglru.rs @@ -0,0 +1,924 @@ +// Copyright (C) 2023 Ant group. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +use crate::debug; +use crate::warn; +use anyhow::{anyhow, Result}; +use chrono::{DateTime, Duration, Utc}; +use std::collections::HashMap; +use std::collections::HashSet; +use std::fs::{self, File, OpenOptions}; +use std::io::{BufRead, BufReader}; +use std::os::unix::fs::MetadataExt; +use std::path::PathBuf; + +const WORKINGSET_ANON: usize = 0; +const WORKINGSET_FILE: usize = 1; +const LRU_GEN_ENABLED_PATH: &str = "/sys/kernel/mm/lru_gen/enabled"; +const LRU_GEN_PATH: &str = "/sys/kernel/debug/lru_gen"; +const MEMCGS_PATH: &str = "/sys/fs/cgroup/memory"; + +fn lru_gen_head_parse(line: &str) -> Result<(usize, String)> { + let words: Vec<&str> = line.split_whitespace().map(|word| word.trim()).collect(); + if words.len() != 3 || words[0] != "memcg" { + return Err(anyhow!("line {} format is not right", line)); + } + + let id = usize::from_str_radix(words[1], 10) + .map_err(|e| anyhow!("parse line {} failed: {}", line, e))?; + + Ok((id, words[2].to_string())) +} + +#[derive(Debug, PartialEq)] +pub struct GenLRU { + pub seq: u64, + pub anon: u64, + pub file: u64, + pub birth: DateTime, +} + +impl GenLRU { + fn new() -> Self { + Self { + seq: 0, + anon: 0, + file: 0, + birth: Utc::now(), + } + } +} + +#[derive(Debug, PartialEq)] +pub struct MGenLRU { + pub min_seq: u64, + pub max_seq: u64, + pub last_birth: DateTime, + pub min_lru_index: usize, + pub lru: Vec, +} + +impl MGenLRU { + fn new() -> Self { + Self { + min_seq: 0, + max_seq: 0, + last_birth: Utc::now(), + min_lru_index: 0, + lru: Vec::new(), + } + } +} + +//result: +// last_line, HashMap +fn lru_gen_lines_parse(reader: &mut BufReader) -> Result<(String, HashMap)> { + let mut line = String::new(); + let mut ret_hash = HashMap::new(); + while line.len() > 0 + || reader + .read_line(&mut line) + .map_err(|e| anyhow!("read file {} failed: {}", LRU_GEN_PATH, e))? + > 0 + { + let words: Vec<&str> = line.split_whitespace().map(|word| word.trim()).collect(); + if words.len() == 2 && words[0] == "node" { + // Got a new node + let node_id = usize::from_str_radix(words[1], 10) + .map_err(|e| anyhow!("parse line {} failed: {}", line, e))?; + let (ret_line, node_size) = lru_gen_seq_lines_parse(reader) + .map_err(|e| anyhow!("lru_gen_seq_lines_parse failed: {}", e))?; + if let Some(size) = node_size { + ret_hash.insert(node_id, size); + } + line = ret_line; + } else { + // Cannot get node, return the line let caller handle it. + break; + } + } + + Ok((line, ret_hash)) +} + +fn str_to_u64(str: &str) -> Result { + if str.starts_with("-") { + warn!("{} format {} is not right", LRU_GEN_PATH, str); + return Ok(0); + } + Ok(u64::from_str_radix(str, 10)?) +} + +//result: +// last_line, Option +fn lru_gen_seq_lines_parse(reader: &mut BufReader) -> Result<(String, Option)> { + let mut line = String::new(); + let mut ret = MGenLRU::new(); + let mut got = false; + + while reader + .read_line(&mut line) + .map_err(|e| anyhow!("read file {} failed: {}", LRU_GEN_PATH, e))? + > 0 + { + let words: Vec<&str> = line.split_whitespace().map(|word| word.trim()).collect(); + if words.len() != 4 { + //line is not format of seq line + break; + } + + let msecs = i64::from_str_radix(words[1], 10) + .map_err(|e| anyhow!("parse line {} failed: {}", line, e))?; + // Use milliseconds because will got build error with try_milliseconds. + #[allow(deprecated)] + let birth = Utc::now() - Duration::milliseconds(msecs); + + let mut gen = GenLRU::new(); + gen.birth = birth; + + gen.seq = u64::from_str_radix(words[0], 10) + .map_err(|e| anyhow!("parse line {} failed: {}", line, e))?; + gen.anon = str_to_u64(&words[2 + WORKINGSET_ANON]) + .map_err(|e| anyhow!("parse line {} failed: {}", line, e))?; + gen.file = str_to_u64(&words[2 + WORKINGSET_FILE]) + .map_err(|e| anyhow!("parse line {} failed: {}", line, e))?; + + if !got { + ret.min_seq = gen.seq; + ret.max_seq = gen.seq; + ret.last_birth = birth; + got = true; + } else { + ret.min_seq = std::cmp::min(ret.min_seq, gen.seq); + ret.max_seq = std::cmp::max(ret.max_seq, gen.seq); + if ret.last_birth < birth { + ret.last_birth = birth; + } + } + if gen.seq == ret.min_seq { + ret.min_lru_index = ret.lru.len(); + } + ret.lru.push(gen); + + line.clear(); + } + + Ok((line, if got { Some(ret) } else { None })) +} + +// Just handle the path in the target_patchs. But if len of target_patchs is 0, will handle all paths. +// if parse_line is false +// HashMap will be empty. +//result: +// HashMap)> +fn lru_gen_file_parse( + mut reader: &mut BufReader, + target_patchs: &HashSet, + parse_line: bool, +) -> Result)>> { + let mut line = String::new(); + let mut ret_hash = HashMap::new(); + while line.len() > 0 + || reader + .read_line(&mut line) + .map_err(|e| anyhow!("read file {} failed: {}", LRU_GEN_PATH, e))? + > 0 + { + let mut clear_line = true; + // Not handle the Err of lru_gen_head_parse because all lines of file will be checked. + if let Ok((id, path)) = lru_gen_head_parse(&line) { + if target_patchs.len() == 0 || target_patchs.contains(&path) { + let seq_data = if parse_line { + let (ret_line, data) = lru_gen_lines_parse(&mut reader).map_err(|e| { + anyhow!( + "lru_gen_seq_lines_parse file {} failed: {}", + LRU_GEN_PATH, + e + ) + })?; + line = ret_line; + clear_line = false; + data + } else { + HashMap::new() + }; + + /*trace!( + "lru_gen_file_parse path {} id {} seq_data {:#?}", + path, + id, + seq_data + );*/ + + ret_hash.insert(path.clone(), (id, seq_data)); + } + } + if clear_line { + line.clear(); + } + } + Ok(ret_hash) +} + +fn file_parse( + target_patchs: &HashSet, + parse_line: bool, +) -> Result)>> { + let file = File::open(LRU_GEN_PATH) + .map_err(|e| anyhow!("open file {} failed: {}", LRU_GEN_PATH, e))?; + + let mut reader = BufReader::new(file); + + lru_gen_file_parse(&mut reader, target_patchs, parse_line) +} + +//result: +// HashMap)> +pub fn host_memcgs_get( + target_patchs: &HashSet, + parse_line: bool, +) -> Result)>> { + let mgs = file_parse(target_patchs, parse_line) + .map_err(|e| anyhow!("mglru file_parse failed: {}", e))?; + + let mut host_mgs = HashMap::new(); + for (path, (id, mglru)) in mgs { + let host_path = PathBuf::from(MEMCGS_PATH).join(path.trim_start_matches('/')); + + let metadata = match fs::metadata(host_path.clone()) { + Err(e) => { + if id != 0 { + debug!("fs::metadata {:?} fail: {}", host_path, e); + } + continue; + } + Ok(m) => m, + }; + + host_mgs.insert(path, (id, metadata.ino() as usize, mglru)); + } + + Ok(host_mgs) +} + +pub fn check() -> Result<()> { + if crate::misc::is_test_environment() { + return Ok(()); + } + + let content = fs::read_to_string(LRU_GEN_ENABLED_PATH) + .map_err(|e| anyhow!("open file {} failed: {}", LRU_GEN_ENABLED_PATH, e))?; + let content = content.trim(); + let r = if content.starts_with("0x") { + u32::from_str_radix(&content[2..], 16) + } else { + content.parse() + }; + let enabled = r.map_err(|e| anyhow!("parse file {} failed: {}", LRU_GEN_ENABLED_PATH, e))?; + if enabled != 7 { + fs::write(LRU_GEN_ENABLED_PATH, "7") + .map_err(|e| anyhow!("write file {} failed: {}", LRU_GEN_ENABLED_PATH, e))?; + } + + let _ = OpenOptions::new() + .read(true) + .write(true) + .open(LRU_GEN_PATH) + .map_err(|e| anyhow!("open file {} failed: {}", LRU_GEN_PATH, e))?; + + Ok(()) +} + +pub fn run_aging( + memcg_id: usize, + numa_id: usize, + max_seq: u64, + can_swap: bool, + force_scan: bool, +) -> Result<()> { + let cmd = format!( + "+ {} {} {} {} {}", + memcg_id, numa_id, max_seq, can_swap as i32, force_scan as i32 + ); + //trace!("send cmd {} to {}", cmd, LRU_GEN_PATH); + fs::write(LRU_GEN_PATH, &cmd) + .map_err(|e| anyhow!("write file {} cmd {} failed: {}", LRU_GEN_PATH, cmd, e))?; + Ok(()) +} + +pub fn run_eviction( + memcg_id: usize, + numa_id: usize, + min_seq: u64, + swappiness: u8, + nr_to_reclaim: usize, +) -> Result<()> { + let cmd = format!( + "- {} {} {} {} {}", + memcg_id, numa_id, min_seq, swappiness, nr_to_reclaim + ); + //trace!("send cmd {} to {}", cmd, LRU_GEN_PATH); + fs::write(LRU_GEN_PATH, &cmd) + .map_err(|e| anyhow!("write file {} cmd {} failed: {}", LRU_GEN_PATH, cmd, e))?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use maplit::hashmap; + use once_cell::sync::OnceCell; + use slog::{Drain, Level, Logger}; + use slog_async; + use slog_scope::set_global_logger; + use slog_term; + use std::collections::HashMap; + use std::fs; + use std::fs::File; + use std::io::BufReader; + use std::io::Write; + use std::sync::Mutex; + + lazy_static::lazy_static! { + static ref TEST_MUTEX: Mutex<()> = Mutex::new(()); + } + static LOGGER: OnceCell = OnceCell::new(); + + impl GenLRU { + pub fn new_from_data(seq: u64, anon: u64, file: u64, birth: DateTime) -> Self { + Self { + seq, + anon, + file, + birth, + } + } + } + + pub fn init_logger() -> &'static slog_scope::GlobalLoggerGuard { + LOGGER.get_or_init(|| { + let decorator = slog_term::TermDecorator::new().stderr().build(); + let drain = slog_term::CompactFormat::new(decorator) + .build() + .filter_level(Level::Trace) + .fuse(); + let drain = slog_async::Async::new(drain).build().fuse(); + let logger = Logger::root(drain, slog::o!()); + set_global_logger(logger.clone()) + }) + } + + #[test] + fn test_lru_gen_file_parse_single_no_parse() { + let _lock = TEST_MUTEX.lock().unwrap_or_else(|e| e.into_inner()); + let _logger = init_logger(); + + let mut reader = setup_test_file(); + let paths = ["/justto.slice/boot.mount".to_string()] + .iter() + .cloned() + .collect(); + let ret = lru_gen_file_parse(&mut reader, &paths, false).unwrap(); + assert_eq!(ret.len(), 1); + assert_eq!( + ret.get("/justto.slice/boot.mount"), + Some(&(16, HashMap::new())) + ); + remove_test_file(); + } + + #[test] + fn test_lru_gen_file_parse_multi_no_parse() { + let _lock = TEST_MUTEX.lock().unwrap_or_else(|e| e.into_inner()); + let _logger = init_logger(); + + let mut reader = setup_test_file(); + let paths = [ + "/aabbc/tea-loglogl".to_string(), + "/aabbc/staraabbc".to_string(), + "/aabbc/TEAE-iaabbc".to_string(), + "/justto.slice/cpupower.service".to_string(), + ] + .iter() + .cloned() + .collect(); + let ret = lru_gen_file_parse(&mut reader, &paths, false).unwrap(); + assert_eq!(ret.len(), 4); + assert_eq!(ret.get("/justto.slice/boot.mount"), None); + assert_eq!(ret.get("/aabbc/tea-loglogl"), Some(&(30, hashmap![]))); + assert_eq!(ret.get("/aabbc/staraabbc"), Some(&(22, hashmap![]))); + assert_eq!(ret.get("/aabbc/TEAE-iaabbc"), Some(&(21, hashmap![]))); + assert_eq!( + ret.get("/justto.slice/cpupower.service"), + Some(&(0, hashmap![])) + ); + remove_test_file(); + } + + #[test] + fn test_lru_gen_file_parse_multi_parse() { + let _lock = TEST_MUTEX.lock().unwrap_or_else(|e| e.into_inner()); + let _logger = init_logger(); + + let mut reader = setup_test_file(); + let paths = [ + "/aabbc/tea-loglogl".to_string(), + "/aabbc/staraabbc".to_string(), + "/aabbc/TEAE-iaabbc".to_string(), + "/justto.slice/cpupower.service".to_string(), + ] + .iter() + .cloned() + .collect(); + let ret = lru_gen_file_parse(&mut reader, &paths, true).unwrap(); + assert_eq!(ret.len(), 4); + assert_eq!(ret.get("/justto.slice/boot.mount"), None); + let birth_vec: Vec> = ret["/aabbc/tea-loglogl"].1[&0] + .lru + .iter() + .map(|g| g.birth) + .collect(); + assert_eq!( + ret.get("/aabbc/tea-loglogl"), + Some(&( + 30, + hashmap![0 => MGenLRU{min_seq: 0, max_seq: 3, last_birth: birth_vec[3], min_lru_index: 0, + lru: vec![GenLRU::new_from_data(0, 20, 23, birth_vec[0]), + GenLRU::new_from_data(1, 9, 23, birth_vec[1]), + GenLRU::new_from_data(2, 20, 19, birth_vec[2]), + GenLRU::new_from_data(3, 3, 8, birth_vec[3])]}] + )) + ); + let birth_vec: Vec> = ret["/aabbc/staraabbc"].1[&1] + .lru + .iter() + .map(|g| g.birth) + .collect(); + assert_eq!( + ret.get("/aabbc/staraabbc"), + Some(&( + 22, + hashmap![1 => MGenLRU{min_seq: 2, max_seq: 5, last_birth: birth_vec[3], min_lru_index: 0, + lru: vec![GenLRU::new_from_data(2, 0, 86201, birth_vec[0]), + GenLRU::new_from_data(3, 253, 0, birth_vec[1]), + GenLRU::new_from_data(4, 0, 0, birth_vec[2]), + GenLRU::new_from_data(5, 2976, 41252, birth_vec[3])]}] + )) + ); + let birth_vec: Vec> = ret["/aabbc/TEAE-iaabbc"].1[&0] + .lru + .iter() + .map(|g| g.birth) + .collect(); + let birth1_vec: Vec> = ret["/aabbc/TEAE-iaabbc"].1[&1] + .lru + .iter() + .map(|g| g.birth) + .collect(); + assert_eq!( + ret.get("/aabbc/TEAE-iaabbc"), + Some(&( + 21, + hashmap![0 => MGenLRU{min_seq: 0, max_seq: 3, last_birth: birth_vec[3], min_lru_index: 0, lru: vec![GenLRU::new_from_data(0, 0, 1, birth_vec[0]), + GenLRU::new_from_data(1, 2, 3, birth_vec[1]), + GenLRU::new_from_data(2, 6, 7, birth_vec[2]), + GenLRU::new_from_data(3, 8, 9, birth_vec[3])]}, + 1 => MGenLRU{min_seq: 3, max_seq: 6, last_birth: birth1_vec[3], min_lru_index: 0, lru: vec![GenLRU::new_from_data(3, 10, 11, birth1_vec[0]), + GenLRU::new_from_data(4, 12, 16, birth1_vec[1]), + GenLRU::new_from_data(5, 17, 18, birth1_vec[2]), + GenLRU::new_from_data(6, 19, 20, birth1_vec[3])]}] + )) + ); + let birth_vec: Vec> = ret["/justto.slice/cpupower.service"].1[&0] + .lru + .iter() + .map(|g| g.birth) + .collect(); + assert_eq!( + ret.get("/justto.slice/cpupower.service"), + Some(&( + 0, + hashmap![0 => MGenLRU{min_seq: 0, max_seq: 3, last_birth: birth_vec[3], min_lru_index: 0, lru: vec![GenLRU::new_from_data(0, 0, 33, birth_vec[0]), + GenLRU::new_from_data(1, 0, 0, birth_vec[1]), + GenLRU::new_from_data(2, 0, 0, birth_vec[2]), + GenLRU::new_from_data(3, 0, 115, birth_vec[3])]}] + )) + ); + remove_test_file(); + } + + #[test] + fn test_lru_gen_file_parse_no_target_no_parse() { + let _lock = TEST_MUTEX.lock().unwrap_or_else(|e| e.into_inner()); + let _logger = init_logger(); + + let mut reader = setup_test_file(); + let paths = [].iter().cloned().collect(); + let ret = lru_gen_file_parse(&mut reader, &paths, false).unwrap(); + assert_eq!(ret.len(), 55); + assert_eq!(ret.get("/justto.slice/boot.mount"), Some(&(16, hashmap![]))); + assert_eq!(ret.get("/aabbc/tea-loglogl"), Some(&(30, hashmap![]))); + assert_eq!(ret.get("/aabbc/staraabbc"), Some(&(22, hashmap![]))); + assert_eq!(ret.get("/aabbc/TEAE-iaabbc"), Some(&(21, hashmap![]))); + assert_eq!( + ret.get("/justto.slice/cpupower.service"), + Some(&(0, hashmap![])) + ); + remove_test_file(); + } + + #[test] + fn test_lru_gen_file_parse_no_target_parse() { + let _lock = TEST_MUTEX.lock().unwrap_or_else(|e| e.into_inner()); + let _logger = init_logger(); + + let mut reader = setup_test_file(); + let paths = [].iter().cloned().collect(); + let ret = lru_gen_file_parse(&mut reader, &paths, true).unwrap(); + assert_eq!(ret.len(), 55); + let birth_vec: Vec> = ret["/aabbc/tea-loglogl"].1[&0] + .lru + .iter() + .map(|g| g.birth) + .collect(); + assert_eq!( + ret.get("/aabbc/tea-loglogl"), + Some(&( + 30, + hashmap![0 => MGenLRU{min_seq: 0, max_seq: 3, last_birth: birth_vec[3], min_lru_index: 0, + lru: vec![GenLRU::new_from_data(0, 20, 23, birth_vec[0]), + GenLRU::new_from_data(1, 9, 23, birth_vec[1]), + GenLRU::new_from_data(2, 20, 19, birth_vec[2]), + GenLRU::new_from_data(3, 3, 8, birth_vec[3])]}] + )) + ); + let birth_vec: Vec> = ret["/aabbc/staraabbc"].1[&1] + .lru + .iter() + .map(|g| g.birth) + .collect(); + assert_eq!( + ret.get("/aabbc/staraabbc"), + Some(&( + 22, + hashmap![1 => MGenLRU{min_seq: 2, max_seq: 5, last_birth: birth_vec[3], min_lru_index: 0, + lru: vec![GenLRU::new_from_data(2, 0, 86201, birth_vec[0]), + GenLRU::new_from_data(3, 253, 0, birth_vec[1]), + GenLRU::new_from_data(4, 0, 0, birth_vec[2]), + GenLRU::new_from_data(5, 2976, 41252, birth_vec[3])]}] + )) + ); + remove_test_file(); + } + + fn setup_test_file() -> BufReader { + let data = r#" + memcg 1 / + node 0 + 0 589359037 0 -0 + 1 589359037 12 0 + 2 589359037 0 0 + 3 589359037 1265 2471 + memcg 2 /justto.slice + node 0 + 0 589334424 0 0 + 1 589334424 0 0 + 2 589334424 0 0 + 3 589334424 0 0 + memcg 3 /justto.slice/justtod-teawated.service + node 0 + 0 589334423 0 217 + 1 589334423 8 0 + 2 589334423 0 0 + 3 589334423 225 40293 + memcg 0 /justto.slice/justtod-readahead-replay.service + node 0 + 0 589334411 0 266694 + 1 589334411 0 0 + 2 589334411 0 0 + 3 589334411 1 21 + memcg 0 /justto.slice/tea0-domainname.service + node 0 + 0 589334410 0 6 + 1 589334410 0 0 + 2 589334410 0 0 + 3 589334410 0 198 + memcg 6 /justto.slice/justto-serial\x2dgetty.slice + node 0 + 0 589334408 0 1 + 1 589334408 1 0 + 2 589334408 0 0 + 3 589334408 32 0 + memcg 7 /justto.slice/justto-getty.slice + node 0 + 0 589334408 0 0 + 1 589334408 1 0 + 2 589334408 0 0 + 3 589334408 31 0 + memcg 8 /justto.slice/sys-kernel-debug.mount + node 0 + 0 589334407 0 6 + 1 589334408 0 0 + 2 589334408 0 0 + 3 589334408 0 7 + memcg 10 /justto.slice/dev-hugepages.mount + node 0 + 0 589334406 0 1 + 1 589334406 0 0 + 2 589334406 0 0 + 3 589334406 0 0 + memcg 0 /justto.slice/justtod-readahead-collect.service + node 0 + 0 589334405 0 96 + 1 589334405 0 0 + 2 589334405 0 0 + 3 589334405 0 0 + memcg 12 /justto.slice/justto-justtod\x2dfsck.slice + node 0 + 0 589334403 0 25 + 1 589334403 0 0 + 2 589334403 0 0 + 3 589334403 0 239 + memcg 13 /justto.slice/justto-selinux\x2dpolicy\x2dmigrate\x2dlocal\x2dchanges.slice + node 0 + 0 589334403 0 0 + 1 589334403 0 0 + 2 589334403 0 0 + 3 589334403 0 0 + memcg 14 /justto.slice/dev-mqueue.mount + node 0 + 0 589334402 0 0 + 1 589334402 0 0 + 2 589334402 0 0 + 3 589334402 0 0 + memcg 0 /justto.slice/tea2-monitor.service + node 0 + 0 589334401 0 9 + 1 589334401 0 0 + 2 589334401 0 0 + 3 589334401 0 582 + memcg 0 /justto.slice/kmod-static-nodes.service + node 0 + 0 589334399 0 4 + 1 589334399 1 0 + 2 589334399 0 0 + 3 589334399 0 33 + memcg 0 /justto.slice/plymouth-start.service + node 0 + 0 589334397 0 1 + 1 589334397 0 0 + 2 589334397 0 0 + 3 589334397 0 0 + memcg 18 /justto.slice/sys-kernel-config.mount + node 0 + 0 589334396 0 0 + 1 589334396 0 0 + 2 589334396 0 0 + 3 589334396 0 0 + memcg 5 /justto.slice/tea2-teaetad.service + node 0 + 0 589334383 0 4 + 1 589334383 2 0 + 2 589334383 0 0 + 3 589334383 587 14 + memcg 0 /justto.slice/justtod-remount-fs.service + node 0 + 0 589334381 0 4 + 1 589334381 0 0 + 2 589334381 0 0 + 3 589334381 0 11 + memcg 0 /justto.slice/justtod-tmpfiles-setup-dev.service + node 0 + 0 589334380 0 28 + 1 589334380 0 0 + 2 589334380 0 0 + 3 589334380 0 32 + memcg 0 /justto.slice/justtod-sysctl.service + node 0 + 0 589334378 0 42 + 1 589334378 0 0 + 2 589334378 0 0 + 3 589334378 0 13 + memcg 0 /justto.slice/justtod-teawate-flush.service + node 0 + 0 589334367 0 8 + 1 589334367 0 0 + 2 589334367 0 0 + 3 589334367 0 141 + memcg 0 /justto.slice/justtod-udev-trigger.service + node 0 + 0 589334365 0 5 + 1 589334365 0 0 + 2 589334365 0 0 + 3 589334365 0 103 + memcg 0 /justto.slice/tea0-readonly.service + node 0 + 0 589334364 0 163 + 1 589334364 0 0 + 2 589334364 0 0 + 3 589334364 0 35 + memcg 0 /justto.slice/justtod-random-seed.service + node 0 + 0 589334363 0 38 + 1 589334363 0 0 + 2 589334363 0 0 + 3 589334363 0 9 + memcg 25 /justto.slice/justtod-udevd.service + node 0 + 0 589334362 0 12553 + 1 589334362 249 0 + 2 589334362 0 0 + 3 589334362 124 1415 + memcg 15 /justto.slice/dev-disk-by\x2dlabel-SWAP.swap + node 0 + 0 589334085 0 5 + 1 589334085 0 0 + 2 589334085 0 0 + 3 589334085 0 10 + memcg 16 /justto.slice/boot.mount + node 0 + 0 589334035 0 26 + 1 589334035 0 0 + 2 589334035 0 0 + 3 589334035 0 0 + memcg 0 /justto.slice/plymouth-read-write.service + node 0 + 0 589334011 0 9 + 1 589334011 0 0 + 2 589334011 0 0 + 3 589334011 0 27 + memcg 0 /justto.slice/tea0-import-state.service + node 0 + 0 589334008 0 5 + 1 589334008 0 0 + 2 589334008 0 0 + 3 589334008 0 45 + memcg 0 /justto.slice/justtod-tmpfiles-setup.service + node 0 + 0 589333868 0 8 + 1 589333868 0 0 + 2 589333868 0 0 + 3 589333868 0 0 + memcg 0 /justto.slice/justtod-update-utmp.service + node 0 + 0 589333772 0 5 + 1 589333772 1 0 + 2 589333772 0 0 + 3 589333772 0 74 + memcg 0 /justto.slice/network.service + node 0 + 0 589333758 0 2480 + 1 589333758 0 0 + 2 589333758 0 0 + 3 589333758 0 542 + memcg 0 /justto.slice/tea0-dmesg.service + node 0 + 0 589333757 0 35 + 1 589333757 0 0 + 2 589333757 0 0 + 3 589333757 0 37 + memcg 0 /justto.slice/cpupower.service + node 0 + 0 589333755 0 33 + 1 589333755 0 0 + 2 589333755 0 0 + 3 589333755 0 115 + memcg 0 /justto.slice/justtod-user-sessions.service + node 0 + 0 589333749 0 4 + 1 589333749 0 0 + 2 589333749 0 0 + 3 589333749 0 8 + memcg 0 /justto.slice/sysstat.service + node 0 + 0 589333747 0 17 + 1 589333747 0 0 + 2 589333747 0 0 + 3 589333747 0 41 + memcg 26 /justto.slice/mcelog.service + node 0 + 0 589333745 0 41 + 1 589333745 2 0 + 2 589333745 0 0 + 3 589333745 554 37 + memcg 27 /justto.slice/dbus.service + node 0 + 0 589333743 0 82 + 1 589333743 1 0 + 2 589333743 0 0 + 3 589333743 119 216 + memcg 20 /justto.slice/syslog-ng.service + node 0 + 0 589333722 0 5889 + 1 589333722 2 0 + 2 589333722 0 0 + 3 589333722 418 6488 + memcg 0 /justto.slice/cpunoturbo.service + node 0 + 0 589333596 0 1 + 1 589333596 0 0 + 2 589333596 0 0 + 3 589333596 0 0 + memcg 4 /justto.slice/staraabbcctl.service + node 0 + 0 589327556 0 7 + 1 589327556 2 0 + 2 589327556 0 0 + 3 589327556 69 11 + memcg 19 /justto.slice/sshd.service + node 0 + 0 589327547 0 9670 + 1 589327547 11 0 + 2 589327547 0 0 + 3 589327547 2304 421 + memcg 0 /justto.slice/vmcore-collect.service + node 0 + 0 589327544 0 3 + 1 589327544 0 0 + 2 589327544 0 0 + 3 589327544 0 0 + memcg 0 /justto.slice/kdump.service + node 0 + 0 589327543 0 417259 + 1 589327543 2 0 + 2 589327543 0 0 + 3 589327543 0 0 + memcg 31 /justto.slice/proc-sys-fs-binfmt_misc.mount + node 0 + 0 589311768 0 0 + 1 589311768 0 0 + 2 589311768 0 0 + 3 589311768 0 0 + memcg 32 /justto.slice/ntpd.service + node 0 + 0 589297199 0 14 + 1 589297199 2 0 + 2 589297199 0 0 + 3 589297199 120 198 + memcg 29 /justto.slice/crond.service + node 0 + 0 589297184 0 115157 + 1 589297184 2 0 + 2 589297184 0 0 + 3 589297184 195 324 + memcg 0 /justto.slice/justtod-tmpfiles-clean.service + node 0 + 0 588459896 0 8 + 1 588459896 0 0 + 2 588459896 0 0 + 3 588459896 0 0 + memcg 9 /docker.slice + node 0 + 0 589334407 0 13919 + 1 589334407 7 0 + 2 589334407 0 0 + 3 589334407 7254 146884 + memcg 17 /aabbc + node 0 + 0 589327431 0 0 + 1 589327431 0 0 + 2 589327431 0 0 + 3 589327431 0 0 + memcg 22 /aabbc/staraabbc + node 1 + 2 589327430 0 86201 + 3 589327430 253 0 + 4 589327430 0 0 + 5 589327430 2976 41252 + memcg 21 /aabbc/TEAE-iaabbc + node 0 + 0 589324388 0 1 + 1 589324388 2 3 + 2 589324388 6 7 + 3 589324388 8 9 + node 1 + 3 589324388 10 11 + 4 589324388 12 16 + 5 589324388 17 18 + 6 589324388 19 20 + memcg 28 /aabbc/teawa_tea + node 0 + 0 589324387 0 69337 + 1 589324387 2 0 + 2 589324387 0 0 + 3 589324387 1892 6103 + memcg 30 /aabbc/tea-loglogl + node 0 + 0 589324385 20 23 + 1 589324385 9 23 + 2 589324385 20 19 + 3 589324380 3 8 + + "#; + let mut file = File::create("test_lru_gen").unwrap(); + file.write_all(data.as_bytes()).unwrap(); + + let file = File::open("test_lru_gen").unwrap(); + BufReader::new(file) + } + + fn remove_test_file() { + fs::remove_file("test_lru_gen").unwrap(); + } +} diff --git a/src/mem-agent/src/misc.rs b/src/mem-agent/src/misc.rs new file mode 100644 index 0000000000..f9d7e9c3cc --- /dev/null +++ b/src/mem-agent/src/misc.rs @@ -0,0 +1,71 @@ +// Copyright (C) 2024 Ant group. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +pub fn sl() -> slog::Logger { + slog_scope::logger().new(slog::o!("subsystem" => "mem-agent")) +} + +#[macro_export] +macro_rules! error { + ($($arg:tt)*) => { + slog::info!(crate::misc::sl(), "{}", format_args!($($arg)*)) + } +} + +#[macro_export] +macro_rules! warn { + ($($arg:tt)*) => { + slog::info!(crate::misc::sl(), "{}", format_args!($($arg)*)) + } +} + +#[macro_export] +macro_rules! info { + ($($arg:tt)*) => { + slog::info!(crate::misc::sl(), "{}", format_args!($($arg)*)) + } +} + +#[macro_export] +macro_rules! trace { + ($($arg:tt)*) => { + slog::info!(crate::misc::sl(), "{}", format_args!($($arg)*)) + } +} + +#[macro_export] +macro_rules! debug { + ($($arg:tt)*) => { + slog::info!(crate::misc::sl(), "{}", format_args!($($arg)*)) + } +} + +#[cfg(test)] +pub fn is_test_environment() -> bool { + true +} + +#[cfg(not(test))] +pub fn is_test_environment() -> bool { + false +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_environment_check() { + assert!(is_test_environment()); + } + + #[test] + fn test_log_macro() { + error!("error"); + warn!("warn"); + info!("info"); + trace!("trace"); + debug!("debug"); + } +} diff --git a/src/mem-agent/src/proc.rs b/src/mem-agent/src/proc.rs new file mode 100644 index 0000000000..87a6681b64 --- /dev/null +++ b/src/mem-agent/src/proc.rs @@ -0,0 +1,44 @@ +// Copyright (C) 2024 Ant group. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::{anyhow, Result}; +use std::fs::File; +use std::io::{BufRead, BufReader}; + +fn get_meminfo(opt: &str) -> Result { + let file = File::open("/proc/meminfo")?; + let reader = BufReader::new(file); + + for line in reader.lines() { + let line = line?; + if line.starts_with(opt) { + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() >= 2 { + let kb = parts[1].parse::()?; + return Ok(kb); + } + } + } + + Err(anyhow!("no {} found", opt)) +} + +pub fn get_memfree_kb() -> Result { + get_meminfo("MemFree:") +} + +pub fn get_freeswap_kb() -> Result { + get_meminfo("SwapFree:") +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_memfree_kb() { + let memfree_kb = get_memfree_kb().unwrap(); + assert!(memfree_kb > 0); + } +} diff --git a/src/mem-agent/src/psi.rs b/src/mem-agent/src/psi.rs new file mode 100644 index 0000000000..efa5172181 --- /dev/null +++ b/src/mem-agent/src/psi.rs @@ -0,0 +1,260 @@ +// Copyright (C) 2024 Ant group. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +use crate::info; +use anyhow::{anyhow, Result}; +use chrono::{DateTime, Utc}; +use std::fs; +use std::fs::File; +use std::fs::OpenOptions; +use std::io::{BufRead, BufReader}; +use std::path::PathBuf; + +const CGROUP_PATH: &str = "/sys/fs/cgroup/"; +const MEM_PSI: &str = "memory.pressure"; +const IO_PSI: &str = "io.pressure"; + +fn find_psi_subdirs() -> Result { + if PathBuf::from(CGROUP_PATH).is_dir() { + for entry in fs::read_dir(CGROUP_PATH)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + if path.join(MEM_PSI).is_file() && path.join(IO_PSI).is_file() { + return Ok(path.clone()); + } + } + } + + Err(anyhow!("cannot find cpuacct dir in {}", CGROUP_PATH)) + } else { + Err(anyhow!("{} is not a directory", CGROUP_PATH)) + } +} + +pub fn check(psi_path: &PathBuf) -> Result { + if crate::misc::is_test_environment() { + return Ok(psi_path.clone()); + } + + let p = if psi_path.as_os_str().is_empty() { + find_psi_subdirs().map_err(|e| anyhow!("find_psi_subdirs failed: {}", e))? + } else { + psi_path.clone() + }; + + let mem_psi_path = p.join(MEM_PSI); + let _ = OpenOptions::new() + .read(true) + .write(true) + .open(mem_psi_path.clone()) + .map_err(|e| anyhow!("open file {:?} failed: {}", mem_psi_path, e))?; + + info!("psi is available at {:?}", p); + + Ok(p) +} + +fn read_pressure_some_total(file_path: PathBuf) -> Result { + let file = File::open(file_path).map_err(|e| anyhow!("File::open failed: {}", e))?; + let mut reader = BufReader::new(file); + + let mut first_line = String::new(); + if reader + .read_line(&mut first_line) + .map_err(|e| anyhow!("reader.read_line failed: {}", e))? + <= 0 + { + return Err(anyhow!("File is empty")); + } + + let parts: Vec<&str> = first_line.split_whitespace().collect(); + let total_str = parts.get(4).ok_or_else(|| anyhow!("format is not right"))?; + let val = total_str + .split('=') + .nth(1) + .ok_or_else(|| anyhow!("format is not right"))?; + + let total_value = val + .parse::() + .map_err(|e| anyhow!("parse {} failed: {}", total_str, e))?; + + Ok(total_value) +} + +#[derive(Debug, Clone)] +pub struct Period { + path: PathBuf, + last_psi: u64, + last_update_time: DateTime, + include_child: bool, +} + +impl Period { + pub fn new(path: &PathBuf, include_child: bool) -> Self { + Self { + path: path.to_owned(), + last_psi: 0, + last_update_time: Utc::now(), + include_child, + } + } + + fn get_path_pressure_us(&self, psi_name: &str) -> Result { + let cur_path = self.path.join(psi_name); + let mut parent_val = read_pressure_some_total(cur_path.clone()) + .map_err(|e| anyhow!("read_pressure_some_total {:?} failed: {}", cur_path, e))?; + + if !self.include_child { + let mut child_val = 0; + let entries = fs::read_dir(self.path.clone()) + .map_err(|e| anyhow!("fs::read_dir failed: {}", e))?; + for entry in entries { + let entry = entry.map_err(|e| anyhow!("get path failed: {}", e))?; + let epath = entry.path(); + + if epath.is_dir() { + let full_path = self.path.join(entry.file_name()).join(psi_name); + + child_val += read_pressure_some_total(full_path.clone()).map_err(|e| { + anyhow!("read_pressure_some_total {:?} failed: {}", full_path, e) + })?; + } + } + if parent_val < child_val { + parent_val = 0; + } else { + parent_val -= child_val; + } + } + + Ok(parent_val) + } + + pub fn get_percent(&mut self) -> Result { + let now = Utc::now(); + let mut psi = self + .get_path_pressure_us(MEM_PSI) + .map_err(|e| anyhow!("get_path_pressure_us MEM_PSI {:?} failed: {}", self.path, e))?; + psi += self + .get_path_pressure_us(IO_PSI) + .map_err(|e| anyhow!("get_path_pressure_us IO_PSI {:?} failed: {}", self.path, e))?; + + let mut percent = 0; + + if self.last_psi != 0 && self.last_psi < psi && self.last_update_time < now { + let us = (now - self.last_update_time).num_milliseconds() as u64 * 1000; + + if us != 0 { + percent = (psi - self.last_psi) * 100 / us; + } + } + + self.last_psi = psi; + self.last_update_time = now; + + Ok(percent) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Write; + + #[test] + fn test_read_pressure_some_total() { + remove_fake_file(); + let val = read_pressure_some_total(PathBuf::from(setup_fake_file())).unwrap(); + assert_eq!(val, 37820); + remove_fake_file(); + } + + #[test] + fn test_period() { + remove_fake_cgroup_dir(); + + let dir = setup_fake_cgroup_dir(); + + let period = Period::new(&dir, true); + let us = period.get_path_pressure_us(MEM_PSI).unwrap(); + assert_eq!(us, 37820); + let us = period.get_path_pressure_us(IO_PSI).unwrap(); + assert_eq!(us, 82345); + + let period = Period::new(&dir, false); + let us = period.get_path_pressure_us(MEM_PSI).unwrap(); + assert_eq!(us, 26688); + let us = period.get_path_pressure_us(IO_PSI).unwrap(); + assert_eq!(us, 66879); + + remove_fake_cgroup_dir(); + } + + fn write_fake_file(path: &PathBuf, data: &str) { + let mut file = File::create(path).unwrap(); + file.write_all(data.as_bytes()).unwrap(); + } + + fn setup_fake_file() -> String { + let data = r#"some avg10=0.00 avg60=0.00 avg300=0.00 total=37820 + full avg10=0.00 avg60=0.00 avg300=0.00 total=28881 + "#; + + write_fake_file(&PathBuf::from("test_psi"), data); + + "test_psi".to_string() + } + + fn remove_fake_file() { + let _ = fs::remove_file("test_psi"); + } + + fn setup_fake_cgroup_dir() -> PathBuf { + let dir = PathBuf::from("fake_cgroup"); + fs::create_dir(&dir).unwrap(); + let mem_psi = dir.join(MEM_PSI); + let io_psi = dir.join(IO_PSI); + let data = r#"some avg10=0.00 avg60=0.00 avg300=0.00 total=37820 + full avg10=0.00 avg60=0.00 avg300=0.00 total=28881 + "#; + write_fake_file(&mem_psi, data); + let data = r#"some avg10=0.00 avg60=0.00 avg300=0.00 total=82345 + full avg10=0.00 avg60=0.00 avg300=0.00 total=67890 + "#; + write_fake_file(&io_psi, data); + + let child_dir = dir.join("c1"); + fs::create_dir(&child_dir).unwrap(); + let child_mem_psi = child_dir.join(MEM_PSI); + let child_io_psi = child_dir.join(IO_PSI); + let data = r#"some avg10=0.00 avg60=0.00 avg300=0.00 total=3344 + full avg10=0.00 avg60=0.00 avg300=0.00 total=1234 + "#; + write_fake_file(&child_mem_psi, data); + let data = r#"some avg10=0.00 avg60=0.00 avg300=0.00 total=5566 + full avg10=0.00 avg60=0.00 avg300=0.00 total=5678 + "#; + write_fake_file(&child_io_psi, data); + + let child_dir = dir.join("c2"); + fs::create_dir(&child_dir).unwrap(); + let child_mem_psi = child_dir.join(MEM_PSI); + let child_io_psi = child_dir.join(IO_PSI); + let data = r#"some avg10=0.00 avg60=0.00 avg300=0.00 total=7788 + full avg10=0.00 avg60=0.00 avg300=0.00 total=4321 + "#; + write_fake_file(&child_mem_psi, data); + let data = r#"some avg10=0.00 avg60=0.00 avg300=0.00 total=9900 + full avg10=0.00 avg60=0.00 avg300=0.00 total=8765 + "#; + write_fake_file(&child_io_psi, data); + + dir + } + + fn remove_fake_cgroup_dir() { + let _ = fs::remove_dir_all("fake_cgroup"); + } +} diff --git a/src/mem-agent/src/timer.rs b/src/mem-agent/src/timer.rs new file mode 100644 index 0000000000..0c469597b6 --- /dev/null +++ b/src/mem-agent/src/timer.rs @@ -0,0 +1,88 @@ +// Copyright (C) 2024 Ant group. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +use chrono::Duration as ChronoDuration; +use chrono::{DateTime, Utc}; +use tokio::time::Duration as TokioDuration; + +fn chrono_to_tokio_duration(chrono_duration: ChronoDuration) -> TokioDuration { + if chrono_duration.num_nanoseconds().unwrap_or(0) >= 0 { + TokioDuration::new( + chrono_duration.num_seconds() as u64, + (chrono_duration.num_nanoseconds().unwrap_or(0) % 1_000_000_000) as u32, + ) + } else { + TokioDuration::new(0, 0) + } +} + +#[derive(Debug, Clone)] +pub struct Timeout { + sleep_duration: ChronoDuration, + start_wait_time: DateTime, +} + +impl Timeout { + pub fn new(secs: u64) -> Self { + Self { + sleep_duration: ChronoDuration::microseconds(secs as i64 * 1000000), + /* Make sure the first time to timeout */ + start_wait_time: Utc::now() - ChronoDuration::microseconds(secs as i64 * 1000000) * 2, + } + } + + pub fn is_timeout(&self) -> bool { + let now = Utc::now(); + now >= self.start_wait_time + self.sleep_duration + } + + pub fn reset(&mut self) { + self.start_wait_time = Utc::now(); + } + + pub fn remaining_tokio_duration(&self) -> TokioDuration { + let now = Utc::now(); + + if now >= self.start_wait_time + self.sleep_duration { + return TokioDuration::ZERO; + } + + chrono_to_tokio_duration(self.start_wait_time + self.sleep_duration - now) + } + + pub fn set_sleep_duration(&mut self, secs: u64) { + self.sleep_duration = ChronoDuration::microseconds(secs as i64 * 1000000); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::thread; + use std::time::Duration; + + #[test] + fn test_timeout() { + let mut timeout = Timeout::new(1); + + // timeout should be timeout at once. + assert_eq!(timeout.is_timeout(), true); + + timeout.reset(); + + assert_eq!(timeout.is_timeout(), false); + thread::sleep(Duration::from_secs(2)); + assert_eq!(timeout.is_timeout(), true); + + timeout.set_sleep_duration(2); + timeout.reset(); + + assert_eq!(timeout.is_timeout(), false); + thread::sleep(Duration::from_secs(1)); + assert_eq!(timeout.is_timeout(), false); + + thread::sleep(Duration::from_secs(1)); + assert_eq!(timeout.is_timeout(), true); + } +} diff --git a/tests/static-checks.sh b/tests/static-checks.sh index 698fc03f1f..3582736de6 100755 --- a/tests/static-checks.sh +++ b/tests/static-checks.sh @@ -472,6 +472,7 @@ static_check_license_headers() --exclude="tools/packaging/qemu/default-configs/*" \ --exclude="src/libs/protocols/protos/gogo/*.proto" \ --exclude="src/libs/protocols/protos/google/*.proto" \ + --exclude="src/mem-agent/example/protocols/protos/google/protobuf/*.proto" \ --exclude="src/libs/*/test/texture/*" \ --exclude="*.dic" \ -EL $extra_args "\<${pattern}\>" \