From e7553b01302b974bbff8706958eeab1dbe93aa72 Mon Sep 17 00:00:00 2001 From: Sebastian Werner Date: Fri, 19 Aug 2022 13:53:27 +0200 Subject: [PATCH 001/303] Addressed issue #133 dealing with mismatching socket/core topolgoies in VM settings --- src/sensors/mod.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index f24e1763..d1487be2 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -256,13 +256,22 @@ impl Topology { .unwrap() .parse::() .unwrap(); - let socket = self + let socket_match = self .sockets .iter_mut() - .find(|x| &x.id == socket_id) - .expect("Trick: if you are running on a vm, do not forget to use --vm parameter invoking scaphandre at the command line"); + .find(|x| &x.id == socket_id); + + //In VMs there might be a missmatch betwen Sockets and Cores - see Issue#133 as a first fix we just map all cores that can't be mapped to the first + let socket = match socket_match { + Some(x) => x, + None =>self.sockets.first_mut().expect("Trick: if you are running on a vm, do not forget to use --vm parameter invoking scaphandre at the command line") + }; + if socket_id == &socket.id { socket.add_cpu_core(c); + } else { + socket.add_cpu_core(c); + warn!("coud't not match core to socket - mapping to first socket instead - if you are not using --vm there is something wrong") } } } From ae9cef3d368fc330ab3d15c90f48d810aea14730 Mon Sep 17 00:00:00 2001 From: Uggla Date: Sun, 17 Jul 2022 21:15:03 +0200 Subject: [PATCH 002/303] feat: Bump dependencies release - Bump clap to 4.0.18 - Bump regex to 1.7.0. - Bump protobuf to 2.28.0. - Dependency on time must be hold because it is shared by chrono and warp10. - Dependency on procfs not bumped yet but it seems there are broken stuffs. --- Cargo.lock | 993 +++++++++++++++++++++--------------- Cargo.toml | 11 +- src/exporters/json.rs | 12 +- src/exporters/mod.rs | 2 +- src/exporters/prometheus.rs | 10 +- src/exporters/qemu.rs | 2 +- src/exporters/riemann.rs | 10 +- src/exporters/stdout.rs | 15 +- src/exporters/warpten.rs | 14 +- src/lib.rs | 2 +- src/main.rs | 12 +- 11 files changed, 641 insertions(+), 442 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index af55d2a3..67496d16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,20 +10,20 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "0.7.15" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" dependencies = [ "memchr", ] [[package]] -name = "ansi_term" -version = "0.11.0" +name = "android_system_properties" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ - "winapi", + "libc", ] [[package]] @@ -37,9 +37,9 @@ dependencies = [ [[package]] name = "async-channel" -version = "1.6.1" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" +checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" dependencies = [ "concurrent-queue", "event-listener", @@ -59,33 +59,33 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base-x" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "bitflags" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bumpalo" -version = "3.6.1" +version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "byteorder" @@ -95,21 +95,27 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.0.1" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] name = "cache-padded" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" +checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" + +[[package]] +name = "castaway" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" [[package]] name = "cc" -version = "1.0.67" +version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" +checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" [[package]] name = "cfg-if" @@ -119,31 +125,51 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" dependencies = [ - "libc", + "iana-time-zone", + "js-sys", "num-integer", "num-traits", "serde", "time 0.1.44", + "wasm-bindgen", "winapi", ] [[package]] name = "clap" -version = "2.33.3" +version = "4.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +checksum = "8e67816e006b17427c9b4386915109b494fec2d929c63e3bd3561234cbf1bf1e" dependencies = [ - "ansi_term 0.11.0", "atty", "bitflags", - "strsim 0.8.0", - "textwrap", + "clap_lex", + "once_cell", + "strsim", + "termcolor", +] + +[[package]] +name = "clap_lex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", "unicode-width", - "vec_map", ] [[package]] @@ -159,18 +185,18 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "1.2.2" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" dependencies = [ "cache-padded", ] [[package]] name = "const_fn" -version = "0.4.6" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076a6803b0dacd6a88cfe64deba628b01533ff5ef265687e6938280c1afd0a28" +checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" [[package]] name = "core-foundation-sys" @@ -180,18 +206,18 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "crc32fast" -version = "1.3.0" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" -version = "0.5.1" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" dependencies = [ "cfg-if", "crossbeam-utils", @@ -199,9 +225,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if", "crossbeam-epoch", @@ -210,32 +236,31 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.5" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" dependencies = [ + "autocfg", "cfg-if", "crossbeam-utils", - "lazy_static", "memoffset", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.5" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" dependencies = [ "cfg-if", - "lazy_static", ] [[package]] name = "curl" -version = "0.4.38" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "003cb79c1c6d1c93344c7e1201bb51c2148f24ec2bd9c253709d6b2efb796515" +checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" dependencies = [ "curl-sys", "libc", @@ -248,9 +273,9 @@ dependencies = [ [[package]] name = "curl-sys" -version = "0.4.47+curl-7.79.0" +version = "0.4.59+curl-7.86.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ab94a47d9b61f2d905beb7a3d46aba7704c9f1dfcf84e7d178998d9e95f7989" +checksum = "6cfce34829f448b08f55b7db6d0009e23e2e86a34e8c2b366269bf5799b4a407" dependencies = [ "cc", "libc", @@ -262,6 +287,50 @@ dependencies = [ "winapi", ] +[[package]] +name = "cxx" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7d4e43b25d3c994662706a1d4fcfc32aaa6afd287502c111b237093bb23f3a" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84f8829ddc213e2c1368e51a2564c552b65a8cb6a28f31e576270ac81d5e5827" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e72537424b474af1460806647c41d4b6d35d09ef7fe031c5c2fa5766047cc56a" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "309e4fb93eed90e1e14bea0da16b209f81813ba9fc7830c20ed151dd7bc0a4d7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dirs" version = "3.0.2" @@ -273,9 +342,9 @@ dependencies = [ [[package]] name = "dirs-sys" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ "libc", "redox_users", @@ -310,54 +379,46 @@ dependencies = [ "lazy_static", "regex", "serde", - "strsim 0.10.0", + "strsim", ] -[[package]] -name = "dtoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" - [[package]] name = "either" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "encoding_rs" -version = "0.8.28" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" dependencies = [ "cfg-if", ] [[package]] name = "event-listener" -version = "2.5.1" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "fastrand" -version = "1.4.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca5faf057445ce5c9d4329e382b2ce7ca38550ef3b73a5348362d5f24e0c7fe3" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" dependencies = [ "instant", ] [[package]] name = "flate2" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" dependencies = [ - "cfg-if", "crc32fast", - "libc", "miniz_oxide", ] @@ -384,40 +445,39 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ - "matches", "percent-encoding", ] [[package]] name = "futures-channel" -version = "0.3.13" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2dd2df839b57db9ab69c2c9d8f3e8c81984781937fe2807dc6dcf3b2ad2939" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.13" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15496a72fabf0e62bdc3df11a59a3787429221dd0710ba8ef163d6f7a9112c94" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" [[package]] name = "futures-io" -version = "0.3.13" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71c2c65c57704c32f5241c1223167c2c3294fd34ac020c807ddbe6db287ba59" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" [[package]] name = "futures-lite" -version = "1.11.3" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4481d0cd0de1d204a4fa55e7d45f07b1d958abcb06714b3446438e2eff695fb" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" dependencies = [ "fastrand", "futures-core", @@ -430,21 +490,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.13" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85754d98985841b7d4f5e8e6fbfa4a4ac847916893ec511a2917ccd8525b8bb3" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" [[package]] name = "futures-task" -version = "0.3.13" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa189ef211c15ee602667a6fcfe1c1fd9e07d42250d2156382820fba33c9df80" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" [[package]] name = "futures-util" -version = "0.3.13" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1812c7ab8aedf8d6f2701a43e1243acdbcc2b36ab26e2ad421eb99ac963d96d1" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" dependencies = [ "futures-core", "futures-task", @@ -465,20 +525,20 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.3" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", - "wasi 0.10.0+wasi-snapshot-preview1", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] name = "h2" -version = "0.3.4" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f3675cfef6a30c8031cf9e6493ebdc3bb3272a3fea3923c4210d1830e6a472" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" dependencies = [ "bytes", "fnv", @@ -495,15 +555,15 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.9.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hermit-abi" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] @@ -527,9 +587,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.4" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", @@ -538,9 +598,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "399c583b2979440c60be0821a6199eca73bc3c8dcd9d070d75ac726e2c6186e5" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", "http", @@ -549,21 +609,21 @@ dependencies = [ [[package]] name = "httparse" -version = "1.5.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.13" +version = "0.14.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d1cfb9e4f68655fa04c01f59edb405b6074a0f7118ea881e5026e4a1cd8593" +checksum = "abfba89e19b959ca163c7752ba59d737c1ceea53a5d31a149c805446fc958064" dependencies = [ "bytes", "futures-channel", @@ -583,22 +643,45 @@ dependencies = [ "want", ] +[[package]] +name = "iana-time-zone" +version = "0.1.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + [[package]] name = "idna" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89829a5d69c23d348314a7ac337fe39173b61149a9864deabd260983aed48c21" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" dependencies = [ - "matches", "unicode-bidi", "unicode-normalization", ] [[package]] name = "indexmap" -version = "1.6.2" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", "hashbrown", @@ -606,20 +689,21 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.9" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", ] [[package]] name = "isahc" -version = "1.5.0" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "431445cb4ba85a80cb1438a9ae8042dadb78ae4046ecee89ad027b614aa0ddb7" +checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" dependencies = [ "async-channel", + "castaway", "crossbeam-utils", "curl", "curl-sys", @@ -643,24 +727,24 @@ dependencies = [ [[package]] name = "itoa" -version = "0.4.7" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" [[package]] name = "js-sys" -version = "0.3.49" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc15e39392125075f60c95ba416f5381ff6c3a948ff02ab12464715adf56c821" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" dependencies = [ "wasm-bindgen", ] [[package]] name = "k8s-openapi" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "748acc444200aa3528dc131a8048e131a9e75a611a52d152e276e99199313d1a" +checksum = "4f8de9873b904e74b3533f77493731ee26742418077503683db44e1b3c54aa5c" dependencies = [ "base64", "bytes", @@ -700,15 +784,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.112" +version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" [[package]] name = "libnghttp2-sys" -version = "0.1.6+1.43.0" +version = "0.1.7+1.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0af55541a8827e138d59ec9e5877fb6095ece63fb6f4da45e7491b4fbd262855" +checksum = "57ed28aba195b38d5ff02b9170cbff627e336a20925e43b4945390401c5dc93f" dependencies = [ "cc", "libc", @@ -716,9 +800,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.2" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "602113192b08db8f38796c4e85c39e960c145965140e918018bcde1952429655" +checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" dependencies = [ "cc", "libc", @@ -726,26 +810,36 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "link-cplusplus" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +dependencies = [ + "cc", +] + [[package]] name = "linked-hash-map" -version = "0.5.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "lock_api" -version = "0.4.2" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ + "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.14" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] @@ -756,7 +850,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60d8de15ae71e760bce7f05447f85f73624fe0d3b1e4c5a63ba5d4cb0748d374" dependencies = [ - "ansi_term 0.12.1", + "ansi_term", "atty", "log", ] @@ -767,17 +861,11 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" -[[package]] -name = "matches" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" - [[package]] name = "memchr" -version = "2.3.4" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" @@ -796,50 +884,39 @@ checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" [[package]] name = "miniz_oxide" -version = "0.4.4" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" dependencies = [ "adler", - "autocfg", ] [[package]] name = "mio" -version = "0.7.13" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log", - "miow", - "ntapi", - "winapi", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.42.0", ] [[package]] name = "ntapi" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" dependencies = [ "winapi", ] [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", @@ -847,18 +924,18 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" -version = "1.13.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" dependencies = [ "hermit-abi", "libc", @@ -866,35 +943,47 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.7.2" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "openssl" -version = "0.10.36" +version = "0.10.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9facdb76fec0b73c406f125d44d86fdad818d66fef0531eec9233ca425ff4a" +checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13" dependencies = [ "bitflags", "cfg-if", "foreign-types", "libc", "once_cell", + "openssl-macros", "openssl-sys", ] +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "openssl-probe" -version = "0.1.2" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.66" +version = "0.9.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1996d2d305e561b70d1ee0c53f1542833f4e1ac6ce9a6708b6ff2738ca67dc82" +checksum = "b03b84c3b2d099b81f0953422b4d4ad58761589d0229b5506356afca05a3670a" dependencies = [ "autocfg", "cc", @@ -905,13 +994,19 @@ dependencies = [ [[package]] name = "ordered-float" -version = "2.8.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97c9d06878b3a851e8026ef94bf7fef9ba93062cd412601da4d9cf369b1cc62d" +checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" dependencies = [ "num-traits", ] +[[package]] +name = "os_str_bytes" +version = "6.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3baf96e39c5359d2eb0dd6ccb42c62b91d9678aa68160d261b9e0ccbf9e9dea9" + [[package]] name = "parking" version = "2.0.0" @@ -920,49 +1015,47 @@ checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" [[package]] name = "parking_lot" -version = "0.11.1" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ - "instant", "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" -version = "0.8.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" dependencies = [ "cfg-if", - "instant", "libc", "redox_syscall", "smallvec", - "winapi", + "windows-sys 0.42.0", ] [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pin-project" -version = "1.0.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc174859768806e91ae575187ada95c91a29e96a98dc5d2cd9a1fed039501ba6" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.6" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a490329918e856ed1b083f244e3bfe2d8c4f336407e4ea9e1a9f479ff09049e5" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", @@ -971,9 +1064,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.6" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -983,28 +1076,29 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.19" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "polling" -version = "2.0.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fc12d774e799ee9ebae13f4076ca003b40d18a11ac0f3641e6f899618580b7b" +checksum = "ab4609a838d88b73d8238967b60dd115cc08d38e2bbaf51ee1e4b695f89122e2" dependencies = [ + "autocfg", "cfg-if", "libc", "log", - "wepoll-sys", + "wepoll-ffi", "winapi", ] [[package]] name = "ppv-lite86" -version = "0.2.10" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro-hack" @@ -1014,11 +1108,11 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.24" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] @@ -1038,15 +1132,15 @@ dependencies = [ [[package]] name = "protobuf" -version = "2.22.1" +version = "2.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b7f4a129bb3754c25a4e04032a90173c68f85168f77118ac4cb4936e7f06f92" +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" [[package]] name = "quote" -version = "1.0.9" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] @@ -1059,21 +1153,9 @@ checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "getrandom 0.1.16", "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc 0.2.0", -] - -[[package]] -name = "rand" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.3", - "rand_hc 0.3.1", + "rand_chacha", + "rand_core", + "rand_hc", ] [[package]] @@ -1083,17 +1165,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ "ppv-lite86", - "rand_core 0.5.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.3", + "rand_core", ] [[package]] @@ -1105,38 +1177,20 @@ dependencies = [ "getrandom 0.1.16", ] -[[package]] -name = "rand_core" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" -dependencies = [ - "getrandom 0.2.3", -] - [[package]] name = "rand_hc" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_hc" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" -dependencies = [ - "rand_core 0.6.3", + "rand_core", ] [[package]] name = "rayon" -version = "1.5.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" dependencies = [ "autocfg", "crossbeam-deque", @@ -1146,41 +1200,41 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.9.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "lazy_static", "num_cpus", ] [[package]] name = "redox_syscall" -version = "0.2.5" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] [[package]] name = "redox_users" -version = "0.4.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.3", + "getrandom 0.2.8", "redox_syscall", + "thiserror", ] [[package]] name = "regex" -version = "1.4.5" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" dependencies = [ "aho-corasick", "memchr", @@ -1189,9 +1243,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.23" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "remove_dir_all" @@ -1244,9 +1298,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "064fd21ff87c6e87ed4506e68beb42459caa4a0e2eb144932e6776768556980b" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ "base64", "log", @@ -1257,18 +1311,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09700171bbcc799d113f2c675314d6005c3dc035f3e7307cf3e7fd459ccbe246" +checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" dependencies = [ "base64", ] [[package]] name = "ryu" -version = "1.0.5" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "scaphandre" @@ -1286,13 +1340,13 @@ dependencies = [ "ordered-float", "procfs", "protobuf", - "rand 0.7.3", + "rand", "regex", "riemann_client", "serde", "serde_json", "sysinfo", - "time 0.2.26", + "time 0.2.27", "tokio", "warp10", "windows", @@ -1300,12 +1354,12 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" dependencies = [ "lazy_static", - "winapi", + "windows-sys 0.36.1", ] [[package]] @@ -1314,11 +1368,17 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scratch" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" + [[package]] name = "sct" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" dependencies = [ "ring", "untrusted", @@ -1341,9 +1401,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.130" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" dependencies = [ "serde_derive", ] @@ -1360,9 +1420,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.130" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" dependencies = [ "proc-macro2", "quote", @@ -1371,9 +1431,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.64" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" dependencies = [ "itoa", "ryu", @@ -1382,59 +1442,71 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.8.21" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8c608a35705a5d3cdc9fbe403147647ff34b921f8e833e49306df898f9b20af" +checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" dependencies = [ - "dtoa", "indexmap", + "ryu", "serde", "yaml-rust", ] [[package]] name = "sha1" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" +checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" +dependencies = [ + "sha1_smol", +] + +[[package]] +name = "sha1_smol" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" [[package]] name = "signal-hook-registry" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" dependencies = [ "libc", ] [[package]] name = "slab" -version = "0.4.2" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] [[package]] name = "sluice" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fa0333a60ff2e3474a6775cc611840c2a55610c831dd366503474c02f1a28f5" +checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" dependencies = [ - "futures-channel", + "async-channel", "futures-core", "futures-io", ] [[package]] name = "smallvec" -version = "1.6.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" -version = "0.4.2" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" dependencies = [ "libc", "winapi", @@ -1504,12 +1576,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" -[[package]] -name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - [[package]] name = "strsim" version = "0.10.0" @@ -1518,20 +1584,20 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.65" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a1d708c221c5a612956ef9f75b37e454e88d1f7b899fbd3a18d4252012d663" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] name = "sysinfo" -version = "0.22.4" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccb37aa4af23791c584202d286ed9420e023e9d27e49d5a76215623f4bcc2502" +checksum = "7f1bfab07306a27332451a662ca9c8156e3a9986f82660ba9c8e744fe8455d43" dependencies = [ "cfg-if", "core-foundation-sys", @@ -1544,25 +1610,45 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ "cfg-if", + "fastrand", "libc", - "rand 0.8.4", "redox_syscall", "remove_dir_all", "winapi", ] [[package]] -name = "textwrap" -version = "0.11.0" +name = "termcolor" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" dependencies = [ - "unicode-width", + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1578,9 +1664,9 @@ dependencies = [ [[package]] name = "time" -version = "0.2.26" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a8cbfbf47955132d0202d1662f49b2423ae35862aee471f3ba4b133358f372" +checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" dependencies = [ "const_fn", "libc", @@ -1603,9 +1689,9 @@ dependencies = [ [[package]] name = "time-macros-impl" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa" +checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" dependencies = [ "proc-macro-hack", "proc-macro2", @@ -1616,9 +1702,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.1.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] @@ -1631,9 +1717,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.11.0" +version = "1.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4efe6fc2395938c8155973d7be49fe8d03a843726e285e100a8a383cc0154ce" +checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" dependencies = [ "autocfg", "bytes", @@ -1641,19 +1727,19 @@ dependencies = [ "memchr", "mio", "num_cpus", - "once_cell", "parking_lot", "pin-project-lite", "signal-hook-registry", + "socket2", "tokio-macros", "winapi", ] [[package]] name = "tokio-macros" -version = "1.3.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54473be61f4ebe4efd09cec9bd5d16fa51d70ea0192213d754d2d500457db110" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" dependencies = [ "proc-macro2", "quote", @@ -1662,29 +1748,29 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.6.8" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d3725d3efa29485e87311c5b699de63cde14b00ed4d256b8318aa30ca452cd" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" dependencies = [ "bytes", "futures-core", "futures-sink", - "log", "pin-project-lite", "tokio", + "tracing", ] [[package]] name = "tower-service" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.25" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ebdc2bb4498ab1ab5f5b73c5803825e60199229ccba0698170e3be0e7f959f" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "log", @@ -1695,9 +1781,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.15" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", @@ -1706,11 +1792,11 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.17" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ - "lazy_static", + "once_cell", ] [[package]] @@ -1731,33 +1817,30 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "unicode-bidi" -version = "0.3.4" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -dependencies = [ - "matches", -] +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" [[package]] name = "unicode-normalization" -version = "0.1.17" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] name = "unicode-width" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" - -[[package]] -name = "unicode-xid" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "untrusted" @@ -1767,33 +1850,26 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.2.2" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", "idna", - "matches", "percent-encoding", ] [[package]] name = "vcpkg" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb" - -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "waker-fn" @@ -1813,13 +1889,15 @@ dependencies = [ [[package]] name = "warp10" -version = "1.0.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7bd564482b2d4ff8d3dc9334e2602923873e71c8406a3052dda6c3b7b6fef7c" +checksum = "140e989c5e92da4e09581133f4de7df32d1ce7de6b7a077652bdfa3f9aef97bf" dependencies = [ "isahc", "percent-encoding", - "time 0.2.26", + "serde", + "serde_json", + "time 0.2.27", "url", ] @@ -1835,11 +1913,17 @@ version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +[[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.72" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fe8f61dba8e5d645a4d8132dc7a0a66861ed5e1045d2c0ed940fab33bac0fbe" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1847,13 +1931,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.72" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046ceba58ff062da072c7cb4ba5b22a37f00a302483f7e2a6cdc18fedbdc1fd3" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", "syn", @@ -1862,9 +1946,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.72" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef9aa01d36cda046f797c57959ff5f3c615c9cc63997a8d545831ec7976819b" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1872,9 +1956,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.72" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96eb45c1b2ee33545a813a92dbb53856418bf7eb54ab34f7f7ff1448a5b3735d" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", @@ -1885,15 +1969,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.72" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7148f4696fb4960a346eaa60bbfb42a1ac4ebba21f750f75fc1375b098d5ffa" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "web-sys" -version = "0.3.49" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fe19d70f5dacc03f6e46777213facae5ac3801575d56ca6cbd4c93dcd12310" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" dependencies = [ "js-sys", "wasm-bindgen", @@ -1911,18 +1995,18 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.21.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82015b7e0b8bad8185994674a13a93306bea76cf5a16c5a181382fd3a5ec2376" +checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" dependencies = [ "webpki", ] [[package]] -name = "wepoll-sys" -version = "3.0.1" +name = "wepoll-ffi" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcb14dea929042224824779fbc82d9fab8d2e6d3cbc0ac404de8edf489e77ff" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" dependencies = [ "cc", ] @@ -1943,6 +2027,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -1955,7 +2048,7 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebbc80318ebf919219a113c41deae34aa90198e4a15e93c810a9ea1aaa4c1a78" dependencies = [ - "windows-sys", + "windows-sys 0.27.0", ] [[package]] @@ -1964,43 +2057,143 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cae116ee11e4bce7c0a0425f2b0c866a91d86d209624b7707a7deea52da786" dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", + "windows_aarch64_msvc 0.27.0", + "windows_i686_gnu 0.27.0", + "windows_i686_msvc 0.27.0", + "windows_x86_64_gnu 0.27.0", + "windows_x86_64_msvc 0.27.0", +] + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.0", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + [[package]] name = "windows_aarch64_msvc" version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7d1649bbab232cde71148c6ef7bbe647f214d2154dd66347fada60de40cda7" +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + [[package]] name = "windows_i686_gnu" version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4eb20b59b93fc302839f3b0df3e61de7e9606b44cb54cbeb68d71cf137309fa" +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + [[package]] name = "windows_i686_msvc" version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40331d8ef3e4dcdc8982eb7de16e1f09b86f5384626a56b3a99c2a51b88ff98e" +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + [[package]] name = "windows_x86_64_gnu" version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5937d290e39c3308147d9b877c5fa741c50f4121ea78d2d20c4a138ad365464a" +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + [[package]] name = "windows_x86_64_msvc" version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dee1b76aec4e2bead4758a181b663c37af0de7ec56fe6837c10215b8d6a1635f" +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" + [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/Cargo.toml b/Cargo.toml index 307ad517..a916fb4e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,16 +14,20 @@ homepage = "https://hubblo-org.github.io/scaphandre-documentation" [dependencies] loggerv = "0.7.2" log = "0.4" -clap = "2.33.3" -regex = "1" +# clap string feature is required to allow dynamic values otherwise it ended up with a lifetime pb. +# https://docs.rs/clap/4.0.19/clap/builder/struct.Str.html +clap = { version = "4.0.18", features = ["cargo", "string"] } +regex = "1.7.0" riemann_client = { version = "0.9.0", optional = true } hostname = "0.3.1" -protobuf = "2.20.0" +protobuf = "2.28.0" serde = { version = "1.0", features = ["derive"], optional = true } serde_json = { version = "1.0", optional = true } ordered-float = "2.0" warp10 = { version = "1.0.0", optional = true } rand = { version = "0.7.3" } +#time = { version = "0.3.11", features = ["std"] } +#time blocked because used by chrono and warp10 time = "0.2.25" colored = "2.0.0" chrono = "0.4.19" @@ -33,6 +37,7 @@ hyper = { version = "0.14", features = ["full"], optional = true } tokio = { version = "1", features = ["full"], optional = true} [target.'cfg(target_os="linux")'.dependencies] +#procfs = "0.13.2" procfs = { version = "0.12.0" } [target.'cfg(target_os="windows")'.dependencies] diff --git a/src/exporters/json.rs b/src/exporters/json.rs index 7cdabad3..a4d7aef1 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -23,12 +23,12 @@ impl Exporter for JSONExporter { /// Returns options needed for that exporter, as a HashMap - fn get_options() -> Vec> { + fn get_options() -> Vec> { let mut options = Vec::new(); let arg = Arg::with_name("timeout") .help("Maximum time spent measuring, in seconds.") .long("timeout") - .short("t") + .short('t') .required(false) .takes_value(true); options.push(arg); @@ -37,7 +37,7 @@ impl Exporter for JSONExporter { .default_value("2") .help("Set measurement step duration in second.") .long("step") - .short("s") + .short('s') .required(false) .takes_value(true); options.push(arg); @@ -46,7 +46,7 @@ impl Exporter for JSONExporter { .default_value("0") .help("Set measurement step duration in nano second.") .long("step_nano") - .short("n") + .short('n') .required(false) .takes_value(true); options.push(arg); @@ -55,7 +55,7 @@ impl Exporter for JSONExporter { .default_value("") .help("Destination file for the report.") .long("file") - .short("f") + .short('f') .required(false) .takes_value(true); options.push(arg); @@ -64,7 +64,7 @@ impl Exporter for JSONExporter { .default_value("10") .help("Maximum number of processes to watch.") .long("max-top-consumers") - .short("m") + .short('m') .required(false) .takes_value(true); options.push(arg); diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index 594a0d91..97e3eac3 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -104,7 +104,7 @@ pub trait Exporter { /// Entry point for all Exporters fn run(&mut self, parameters: ArgMatches); /// Get the options passed via the command line - fn get_options() -> Vec>; + fn get_options() -> Vec>; } /// MetricGenerator is an exporter helper structure to collect Scaphandre metrics. diff --git a/src/exporters/prometheus.rs b/src/exporters/prometheus.rs index c7163e22..36265585 100644 --- a/src/exporters/prometheus.rs +++ b/src/exporters/prometheus.rs @@ -59,13 +59,13 @@ impl Exporter for PrometheusExporter { ); } /// Returns options understood by the exporter. - fn get_options() -> Vec> { + fn get_options() -> Vec> { let mut options = Vec::new(); let arg = Arg::with_name("address") .default_value(DEFAULT_IP_ADDRESS) .help("ipv6 or ipv4 address to expose the service to") .long("address") - .short("a") + .short('a') .required(false) .takes_value(true); options.push(arg); @@ -74,7 +74,7 @@ impl Exporter for PrometheusExporter { .default_value("8080") .help("TCP port number to expose the service") .long("port") - .short("p") + .short('p') .required(false) .takes_value(true); options.push(arg); @@ -83,7 +83,7 @@ impl Exporter for PrometheusExporter { .default_value("metrics") .help("url suffix to access metrics") .long("suffix") - .short("s") + .short('s') .required(false) .takes_value(true); options.push(arg); @@ -91,7 +91,7 @@ impl Exporter for PrometheusExporter { let arg = Arg::with_name("qemu") .help("Apply labels to metrics of processes looking like a Qemu/KVM virtual machine") .long("qemu") - .short("q") + .short('q') .required(false) .takes_value(false); options.push(arg); diff --git a/src/exporters/qemu.rs b/src/exporters/qemu.rs index dd75f483..b92a8a71 100644 --- a/src/exporters/qemu.rs +++ b/src/exporters/qemu.rs @@ -34,7 +34,7 @@ impl Exporter for QemuExporter { } } - fn get_options() -> Vec> { + fn get_options() -> Vec> { Vec::new() } } diff --git a/src/exporters/riemann.rs b/src/exporters/riemann.rs index d8cdb76b..ef2211fe 100644 --- a/src/exporters/riemann.rs +++ b/src/exporters/riemann.rs @@ -230,13 +230,13 @@ impl Exporter for RiemannExporter { } /// Returns options understood by the exporter. - fn get_options() -> Vec> { + fn get_options() -> Vec> { let mut options = Vec::new(); let arg = Arg::with_name("address") .default_value(DEFAULT_IP_ADDRESS) .help("Riemann ipv6 or ipv4 address. If mTLS is used then server fqdn must be provided") .long("address") - .short("a") + .short('a') .required(false) .takes_value(true); options.push(arg); @@ -245,7 +245,7 @@ impl Exporter for RiemannExporter { .default_value(DEFAULT_PORT) .help("Riemann TCP port number") .long("port") - .short("p") + .short('p') .required(false) .takes_value(true); options.push(arg); @@ -254,7 +254,7 @@ impl Exporter for RiemannExporter { .default_value("5") .help("Duration between metrics dispatch") .long("dispatch") - .short("d") + .short('d') .required(false) .takes_value(true); options.push(arg); @@ -262,7 +262,7 @@ impl Exporter for RiemannExporter { let arg = Arg::with_name("qemu") .help("Instruct that scaphandre is running on an hypervisor") .long("qemu") - .short("q") + .short('q') .required(false) .takes_value(false); options.push(arg); diff --git a/src/exporters/stdout.rs b/src/exporters/stdout.rs index 55a4af80..6cb1a0dc 100644 --- a/src/exporters/stdout.rs +++ b/src/exporters/stdout.rs @@ -21,13 +21,13 @@ impl Exporter for StdoutExporter { } /// Returns options needed for that exporter, as a HashMap - fn get_options() -> Vec> { + fn get_options() -> Vec> { let mut options = Vec::new(); let arg = Arg::with_name("timeout") .default_value("10") .help("Maximum time spent measuring, in seconds. 0 means continuous measurement.") .long("timeout") - .short("t") + .short('t') .required(false) .takes_value(true); options.push(arg); @@ -36,7 +36,7 @@ impl Exporter for StdoutExporter { .default_value("2") .help("Set measurement step duration in second.") .long("step") - .short("s") + .short('s') .required(false) .takes_value(true); options.push(arg); @@ -45,7 +45,7 @@ impl Exporter for StdoutExporter { .default_value("5") .help("Number of processes to display.") .long("process") - .short("p") + .short('p') .required(false) .takes_value(true); options.push(arg); @@ -53,7 +53,7 @@ impl Exporter for StdoutExporter { let arg = Arg::with_name("regex_filter") .help("Filter processes based on regular expressions (e.g: 'scaph\\w\\wd.e'). This option disable '-p' or '--process' one.") .long("regex") - .short("r") + .short('r') .required(false) .takes_value(true); options.push(arg); @@ -61,7 +61,7 @@ impl Exporter for StdoutExporter { let arg = Arg::with_name("qemu") .help("Apply labels to metrics of processes looking like a Qemu/KVM virtual machine") .long("qemu") - .short("q") + .short('q') .required(false) .takes_value(false); options.push(arg); @@ -124,7 +124,8 @@ impl StdoutExporter { topology, utils::get_hostname(), parameters.is_present("qemu"), - parameters.is_present("containers"), + parameters.is_present("containers"), // This broke clap as the parameter is not + // defined ); println!("Measurement step is: {}s", step_duration); diff --git a/src/exporters/warpten.rs b/src/exporters/warpten.rs index 70343ce0..57cf83ce 100644 --- a/src/exporters/warpten.rs +++ b/src/exporters/warpten.rs @@ -50,13 +50,13 @@ impl Exporter for Warp10Exporter { } /// Options for configuring the exporter. - fn get_options() -> Vec> { + fn get_options() -> Vec> { let mut options = Vec::new(); let arg = Arg::with_name("host") .default_value("localhost") .help("Warp10 host's FQDN or IP address to send data to") .long("host") - .short("H") + .short('H') .required(false) .takes_value(true); options.push(arg); @@ -65,7 +65,7 @@ impl Exporter for Warp10Exporter { .default_value("http") .help("Either 'http' or 'https'") .long("scheme") - .short("s") + .short('s') .required(false) .takes_value(true); options.push(arg); @@ -74,7 +74,7 @@ impl Exporter for Warp10Exporter { .default_value("8080") .help("TCP port to join Warp10 on the host") .long("port") - .short("p") + .short('p') .required(false) .takes_value(true); options.push(arg); @@ -82,7 +82,7 @@ impl Exporter for Warp10Exporter { let arg = Arg::with_name("write-token") .help("Auth. token to write on Warp10") .long("write-token") - .short("t") + .short('t') .required(false) .takes_value(true); options.push(arg); @@ -91,7 +91,7 @@ impl Exporter for Warp10Exporter { .default_value("30") .help("Time step between measurements, in seconds.") .long("step") - .short("S") + .short('S') .required(false) .takes_value(true); options.push(arg); @@ -99,7 +99,7 @@ impl Exporter for Warp10Exporter { let arg = Arg::with_name("qemu") .help("Tells scaphandre it is running on a Qemu hypervisor.") .long("qemu") - .short("q") + .short('q') .required(false) .takes_value(false); options.push(arg); diff --git a/src/lib.rs b/src/lib.rs index f75ed58d..8d7fb59e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -141,7 +141,7 @@ pub fn run(matches: ArgMatches) { /// Returns options needed for each exporter as a HashMap. /// This function has to be updated to enable a new exporter. -pub fn get_exporters_options() -> HashMap>> { +pub fn get_exporters_options() -> HashMap>> { let mut options = HashMap::new(); options.insert( String::from("stdout"), diff --git a/src/main.rs b/src/main.rs index bd2ad3c4..092d97a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ //! Generic sensor and transmission agent for energy consumption related metrics. -use clap::{crate_authors, crate_version, App, AppSettings, Arg, SubCommand}; +use clap::{crate_authors, crate_version, App, AppSettings, Arg, ArgAction, SubCommand}; use scaphandre::{get_exporters_options, run}; fn main() { #[cfg(target_os = "linux")] @@ -22,8 +22,8 @@ fn main() { .setting(AppSettings::SubcommandRequiredElseHelp) .arg( Arg::with_name("v") - .short("v") - .multiple(true) + .short('v') + .action(ArgAction::Count) .help("Sets the level of verbosity.") ) .arg( @@ -40,9 +40,9 @@ fn main() { .help("Sensor module to apply on the host to get energy consumption metrics.") .required(false) .takes_value(true) - .default_value(&sensor_default_value) - .possible_values(&sensors) - .short("s") + .default_value("powercap_rapl") + .possible_values(sensors) + .short('s') .long("sensor") ).arg( Arg::with_name("sensor-buffer-per-domain-max-kB") From 31ca80beb2563b6f4626e9a43e0c6dde508b5f4a Mon Sep 17 00:00:00 2001 From: Uggla Date: Sat, 5 Nov 2022 19:46:42 +0100 Subject: [PATCH 003/303] feat: Upgrade stdout and json exporters to clap 4.0 - There are breaking changes introduced by clap 4.0. So as a strategy, I upgrade exporters one by one. This commit contains the upgrade of stdout and json exporters. --- src/exporters/json.rs | 82 ++++++++++++++++++++--------------------- src/exporters/mod.rs | 2 +- src/exporters/stdout.rs | 75 ++++++++++++++++++------------------- src/lib.rs | 36 +++++------------- src/main.rs | 49 +++++++++++++----------- 5 files changed, 113 insertions(+), 131 deletions(-) diff --git a/src/exporters/json.rs b/src/exporters/json.rs index a4d7aef1..5169d85d 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -1,6 +1,6 @@ use crate::exporters::*; use crate::sensors::Sensor; -use clap::Arg; +use clap::{value_parser, Arg}; use serde::{Deserialize, Serialize}; use std::fs; use std::fs::File; @@ -23,68 +23,70 @@ impl Exporter for JSONExporter { /// Returns options needed for that exporter, as a HashMap - fn get_options() -> Vec> { + fn get_options() -> Vec { let mut options = Vec::new(); - let arg = Arg::with_name("timeout") + let arg = Arg::new("timeout") .help("Maximum time spent measuring, in seconds.") .long("timeout") .short('t') .required(false) - .takes_value(true); + .value_parser(value_parser!(u64)) + .action(clap::ArgAction::Set); options.push(arg); - let arg = Arg::with_name("step_duration") + let arg = Arg::new("step_duration") .default_value("2") .help("Set measurement step duration in second.") .long("step") .short('s') .required(false) - .takes_value(true); + .value_parser(value_parser!(u64)) + .action(clap::ArgAction::Set); options.push(arg); - let arg = Arg::with_name("step_duration_nano") + let arg = Arg::new("step_duration_nano") .default_value("0") .help("Set measurement step duration in nano second.") .long("step_nano") .short('n') .required(false) - .takes_value(true); + .value_parser(value_parser!(u32)) + .action(clap::ArgAction::Set); options.push(arg); - let arg = Arg::with_name("file_path") + let arg = Arg::new("file_path") .default_value("") .help("Destination file for the report.") .long("file") .short('f') .required(false) - .takes_value(true); + .action(clap::ArgAction::Set); options.push(arg); - let arg = Arg::with_name("max_top_consumers") + let arg = Arg::new("max_top_consumers") .default_value("10") .help("Maximum number of processes to watch.") .long("max-top-consumers") .short('m') .required(false) - .takes_value(true); + .value_parser(value_parser!(u16)) + .action(clap::ArgAction::Set); options.push(arg); - let arg = Arg::with_name("containers") + let arg = Arg::new("qemu") + .help("Apply labels to metrics of processes looking like a Qemu/KVM virtual machine") + .long("qemu") + .short('q') + .required(false) + .action(clap::ArgAction::SetTrue); + options.push(arg); + + let arg = Arg::new("containers") .help("Monitor and apply labels for processes running as containers") .long("containers") .required(false) - .takes_value(false); + .action(clap::ArgAction::SetTrue); options.push(arg); - - // the resulting labels of this option are not yet used by this exporter, activate this option once we display something interesting about it - //let arg = Arg::with_name("qemu") - // .help("Apply labels to metrics of processes looking like a Qemu/KVM virtual machine") - // .long("qemu") - // .short("q") - // .required(false) - // .takes_value(false); - //options.push(arg); - options } } @@ -144,28 +146,24 @@ impl JSONExporter { let mut metric_generator = MetricGenerator::new( topology, utils::get_hostname(), - parameters.is_present("qemu"), - parameters.is_present("containers"), + parameters.get_flag("qemu"), + parameters.get_flag("containers"), ); // We have a default value of 2s so it is safe to unwrap the option // Panic if a non numerical value is passed - let step_duration: u64 = parameters - .value_of("step_duration") - .unwrap() - .parse() + let step_duration: u64 = *parameters + .get_one("step_duration") .expect("Wrong step_duration value, should be a number of seconds"); - let step_duration_nano: u32 = parameters - .value_of("step_duration_nano") - .unwrap() - .parse() + let step_duration_nano: u32 = *parameters + .get_one("step_duration_nano") .expect("Wrong step_duration_nano value, should be a number of nano seconds"); info!("Measurement step is: {}s", step_duration); - if let Some(timeout) = parameters.value_of("timeout") { + if let Some(timeout) = parameters.get_one::("timeout") { let now = Instant::now(); - let timeout_secs: u64 = timeout.parse().unwrap(); + let timeout_secs: u64 = *timeout; while now.elapsed().as_secs() <= timeout_secs { self.iterate(¶meters, &mut metric_generator); thread::sleep(Duration::new(step_duration, step_duration_nano)); @@ -207,11 +205,9 @@ impl JSONExporter { }; let consumers = metric_generator.topology.proc_tracker.get_top_consumers( - parameters - .value_of("max_top_consumers") - .unwrap_or("10") - .parse::() - .unwrap(), + *parameters + .get_one::("max_top_consumers") + .unwrap_or(&10u16), ); let top_consumers = consumers .iter() @@ -228,7 +224,7 @@ impl JSONExporter { pid: process.pid, consumption: format!("{}", metric.metric_value).parse::().unwrap(), timestamp: metric.timestamp.as_secs_f64(), - container: match parameters.is_present("containers") { + container: match parameters.get_flag("containers") { true => metric.attributes.get("container_id").map(|container_id| { Container { id: String::from(container_id), @@ -310,7 +306,7 @@ impl JSONExporter { sockets: all_sockets, }; - let file_path = parameters.value_of("file_path").unwrap(); + let file_path = parameters.get_one::("file_path").unwrap(); // Print json if file_path.is_empty() { let json: String = diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index 97e3eac3..2fcff356 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -104,7 +104,7 @@ pub trait Exporter { /// Entry point for all Exporters fn run(&mut self, parameters: ArgMatches); /// Get the options passed via the command line - fn get_options() -> Vec>; + fn get_options() -> Vec; } /// MetricGenerator is an exporter helper structure to collect Scaphandre metrics. diff --git a/src/exporters/stdout.rs b/src/exporters/stdout.rs index 6cb1a0dc..6f1aeab5 100644 --- a/src/exporters/stdout.rs +++ b/src/exporters/stdout.rs @@ -1,4 +1,5 @@ -use clap::Arg; +use clap::parser::ValueSource; +use clap::{value_parser, Arg}; use crate::exporters::*; use crate::sensors::{utils::IProcess, Sensor}; @@ -21,49 +22,59 @@ impl Exporter for StdoutExporter { } /// Returns options needed for that exporter, as a HashMap - fn get_options() -> Vec> { + fn get_options() -> Vec { let mut options = Vec::new(); - let arg = Arg::with_name("timeout") + let arg = Arg::new("timeout") .default_value("10") .help("Maximum time spent measuring, in seconds. 0 means continuous measurement.") .long("timeout") .short('t') .required(false) - .takes_value(true); + .value_parser(value_parser!(u64)) + .action(clap::ArgAction::Set); options.push(arg); - let arg = Arg::with_name("step_duration") + let arg = Arg::new("step_duration") .default_value("2") .help("Set measurement step duration in second.") .long("step") .short('s') .required(false) - .takes_value(true); + .value_parser(value_parser!(u64)) + .action(clap::ArgAction::Set); options.push(arg); - let arg = Arg::with_name("process_number") + let arg = Arg::new("process_number") .default_value("5") .help("Number of processes to display.") .long("process") .short('p') .required(false) - .takes_value(true); + .value_parser(value_parser!(u16)) + .action(clap::ArgAction::Set); options.push(arg); - let arg = Arg::with_name("regex_filter") + let arg = Arg::new("regex_filter") .help("Filter processes based on regular expressions (e.g: 'scaph\\w\\wd.e'). This option disable '-p' or '--process' one.") .long("regex") .short('r') .required(false) - .takes_value(true); + .action(clap::ArgAction::Set); options.push(arg); - let arg = Arg::with_name("qemu") + let arg = Arg::new("qemu") .help("Apply labels to metrics of processes looking like a Qemu/KVM virtual machine") .long("qemu") .short('q') .required(false) - .takes_value(false); + .action(clap::ArgAction::SetTrue); + options.push(arg); + + let arg = Arg::new("containers") + .help("Monitor and apply labels for processes running as containers") + .long("containers") + .required(false) + .action(clap::ArgAction::SetTrue); options.push(arg); options @@ -82,37 +93,24 @@ impl StdoutExporter { // All parameters have a default values so it is safe to unwrap them. // Panic if a non numerical value is passed except for regex_filter. - let timeout_secs: u64 = parameters - .value_of("timeout") - .unwrap() - .parse() + let timeout_secs: u64 = *parameters + .get_one("timeout") .expect("Wrong timeout value, should be a number of seconds"); - let step_duration: u64 = parameters - .value_of("step_duration") - .unwrap() - .parse() + let step_duration: u64 = *parameters + .get_one("step_duration") .expect("Wrong step_duration value, should be a number of seconds"); - let process_number: u16 = parameters - .value_of("process_number") - .unwrap() - .parse() + let process_number: u16 = *parameters + .get_one("process_number") .expect("Wrong process_number value, should be a number"); - let regex_filter: Option = if !parameters.is_present("regex_filter") - || parameters.value_of("regex_filter").unwrap().is_empty() - { - None - } else { - Some( - Regex::new(parameters.value_of("regex_filter").unwrap()) - .expect("Wrong regex_filter, regexp is invalid"), - ) - }; + let regex_filter: Option = parameters + .get_one::("regex_filter") + .map(|regex| Regex::new(regex).expect("Wrong regex_filter, regexp is invalid")); - if parameters.occurrences_of("regex_filter") == 1 - && parameters.occurrences_of("process_number") == 1 + if parameters.value_source("regex_filter") == Some(ValueSource::CommandLine) + && parameters.value_source("process_number") == Some(ValueSource::CommandLine) { let warning = String::from("Warning: (-p / --process) and (-r / --regex) used at the same time. (-p / --process) disabled"); @@ -123,9 +121,8 @@ impl StdoutExporter { let mut metric_generator = MetricGenerator::new( topology, utils::get_hostname(), - parameters.is_present("qemu"), - parameters.is_present("containers"), // This broke clap as the parameter is not - // defined + parameters.get_flag("qemu"), + parameters.get_flag("containers"), ); println!("Measurement step is: {}s", step_duration); diff --git a/src/lib.rs b/src/lib.rs index 8d7fb59e..db96bdb1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,36 +28,20 @@ use sensors::Sensor; use std::collections::HashMap; use std::time::{Duration, SystemTime}; -/// Helper function to get an argument from ArgMatches -fn get_argument(matches: &ArgMatches, arg: &'static str) -> String { - if let Some(value) = matches.value_of(arg) { - return String::from(value); - } - panic!("Couldn't get argument {}", arg); -} - /// Helper function to get a Sensor instance from ArgMatches fn get_sensor(matches: &ArgMatches) -> Box { - let sensor = match &get_argument(matches, "sensor")[..] { + let sensor = match matches.get_one::("sensor").unwrap().as_str() { #[cfg(target_os = "linux")] "powercap_rapl" => PowercapRAPLSensor::new( - get_argument(matches, "sensor-buffer-per-socket-max-kB") - .parse() - .unwrap(), - get_argument(matches, "sensor-buffer-per-domain-max-kB") - .parse() - .unwrap(), - matches.is_present("vm"), + *matches.get_one("sensor-buffer-per-socket-max-kB").unwrap(), + *matches.get_one("sensor-buffer-per-domain-max-kB").unwrap(), + matches.get_flag("vm"), ), #[cfg(target_os = "linux")] _ => PowercapRAPLSensor::new( - get_argument(matches, "sensor-buffer-per-socket-max-kB") - .parse() - .unwrap(), - get_argument(matches, "sensor-buffer-per-domain-max-kB") - .parse() - .unwrap(), - matches.is_present("vm"), + *matches.get_one("sensor-buffer-per-socket-max-kB").unwrap(), + *matches.get_one("sensor-buffer-per-domain-max-kB").unwrap(), + matches.get_flag("vm"), ), #[cfg(not(target_os = "linux"))] _ => MsrRAPLSensor::new(), @@ -70,13 +54,13 @@ fn get_sensor(matches: &ArgMatches) -> Box { /// the choosen exporter: run() /// This function should be updated to take new exporters into account. pub fn run(matches: ArgMatches) { - loggerv::init_with_verbosity(matches.occurrences_of("v")).unwrap(); + loggerv::init_with_verbosity(u64::from(matches.get_count("v"))).unwrap(); let sensor_boxed = get_sensor(&matches); let exporter_parameters; let mut header = true; - if matches.is_present("no-header") { + if matches.get_flag("no-header") { header = false; } @@ -141,7 +125,7 @@ pub fn run(matches: ArgMatches) { /// Returns options needed for each exporter as a HashMap. /// This function has to be updated to enable a new exporter. -pub fn get_exporters_options() -> HashMap>> { +pub fn get_exporters_options() -> HashMap> { let mut options = HashMap::new(); options.insert( String::from("stdout"), diff --git a/src/main.rs b/src/main.rs index 092d97a0..d61fe784 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ //! Generic sensor and transmission agent for energy consumption related metrics. -use clap::{crate_authors, crate_version, App, AppSettings, Arg, ArgAction, SubCommand}; + +use clap::{crate_authors, crate_version, value_parser, Arg, ArgAction, Command}; use scaphandre::{get_exporters_options, run}; fn main() { #[cfg(target_os = "linux")] @@ -7,69 +8,73 @@ fn main() { #[cfg(target_os = "windows")] let sensors = ["msr_rapl"]; let exporters_options = get_exporters_options(); - let exporters = exporters_options.keys(); - let exporters: Vec<&str> = exporters.into_iter().map(|x| x.as_str()).collect(); + let exporters: Vec = exporters_options + .keys() + .into_iter() + .map(|x| x.to_string()) + .collect(); #[cfg(target_os = "linux")] let sensor_default_value = String::from("powercap_rapl"); #[cfg(not(target_os = "linux"))] let sensor_default_value = String::from("msr_rapl"); - let mut matches = App::new("scaphandre") + let mut matches = Command::new("scaphandre") .author(crate_authors!()) .version(crate_version!()) .about("Extensible metrology agent for energy/electricity consumption related metrics") - .setting(AppSettings::SubcommandRequiredElseHelp) .arg( - Arg::with_name("v") + Arg::new("v") .short('v') - .action(ArgAction::Count) .help("Sets the level of verbosity.") + .action(ArgAction::Count) ) .arg( - Arg::with_name("no-header") + Arg::new("no-header") .value_name("no-header") .help("Prevents the header to be displayed in the terminal output.") .required(false) - .takes_value(false) .long("no-header") + .action(clap::ArgAction::SetTrue), ) .arg( - Arg::with_name("sensor") + Arg::new("sensor") .value_name("sensor") .help("Sensor module to apply on the host to get energy consumption metrics.") .required(false) - .takes_value(true) - .default_value("powercap_rapl") - .possible_values(sensors) + .default_value(&sensor_default_value) .short('s') .long("sensor") + .value_parser(sensors) + .action(clap::ArgAction::Set) ).arg( - Arg::with_name("sensor-buffer-per-domain-max-kB") + Arg::new("sensor-buffer-per-domain-max-kB") .value_name("sensor-buffer-per-domain-max-kB") .help("Maximum memory size allowed, in KiloBytes, for storing energy consumption of each domain.") .required(false) - .takes_value(true) .default_value("1") + .value_parser(value_parser!(u16)) + .action(clap::ArgAction::Set) ).arg( - Arg::with_name("sensor-buffer-per-socket-max-kB") + Arg::new("sensor-buffer-per-socket-max-kB") .value_name("sensor-buffer-per-socket-max-kB") .help("Maximum memory size allowed, in KiloBytes, for storing energy consumption of each socket.") .required(false) - .takes_value(true) .default_value("1") + .value_parser(value_parser!(u16)) + .action(clap::ArgAction::Set) ).arg( - Arg::with_name("vm") + Arg::new("vm") .value_name("vm") .help("Tell scaphandre if he is running in a virtual machine.") .long("vm") .required(false) - .takes_value(false) + .action(clap::ArgAction::SetTrue), ); for exporter in exporters { - let mut subcmd = SubCommand::with_name(exporter).about( - match exporter { + let mut subcmd = Command::new(&exporter).about( + match exporter.as_str() { "stdout" => "Stdout exporter allows you to output the power consumption data in the terminal", "json" => "JSON exporter allows you to output the power consumption data in a json file", "prometheus" => "Prometheus exporter exposes power consumption metrics on an http endpoint (/metrics is default) in prometheus accepted format", @@ -80,7 +85,7 @@ fn main() { } ); - let myopts = exporters_options.get(exporter).unwrap(); + let myopts = exporters_options.get(&exporter).unwrap(); for opt in myopts { subcmd = subcmd.arg(opt); } From d9ce3caf6607ea847964f5f5e939f5059362cf06 Mon Sep 17 00:00:00 2001 From: Uggla Date: Sun, 6 Nov 2022 22:24:02 +0100 Subject: [PATCH 004/303] feat: Upgrade prometheus exporter to clap 4.0 - There are breaking changes introduced by clap 4.0. So as a strategy, I upgrade exporters one by one. This commit contains the upgrade of prometheus exporter. --- src/exporters/prometheus.rs | 44 ++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/exporters/prometheus.rs b/src/exporters/prometheus.rs index 36265585..b3995916 100644 --- a/src/exporters/prometheus.rs +++ b/src/exporters/prometheus.rs @@ -50,80 +50,80 @@ impl Exporter for PrometheusExporter { runner( (*self.sensor.get_topology()).unwrap(), - parameters.value_of("address").unwrap().to_string(), - parameters.value_of("port").unwrap().to_string(), - parameters.value_of("suffix").unwrap().to_string(), - parameters.is_present("qemu"), - parameters.is_present("containers"), + parameters.get_one::("address").unwrap().to_string(), + parameters.get_one::("port").unwrap().to_string(), + parameters.get_one::("suffix").unwrap().to_string(), + parameters.get_flag("qemu"), + parameters.get_flag("containers"), get_hostname(), ); } /// Returns options understood by the exporter. - fn get_options() -> Vec> { + fn get_options() -> Vec { let mut options = Vec::new(); - let arg = Arg::with_name("address") + let arg = Arg::new("address") .default_value(DEFAULT_IP_ADDRESS) .help("ipv6 or ipv4 address to expose the service to") .long("address") .short('a') .required(false) - .takes_value(true); + .action(clap::ArgAction::Set); options.push(arg); - let arg = Arg::with_name("port") + let arg = Arg::new("port") .default_value("8080") .help("TCP port number to expose the service") .long("port") .short('p') .required(false) - .takes_value(true); + .action(clap::ArgAction::Set); options.push(arg); - let arg = Arg::with_name("suffix") + let arg = Arg::new("suffix") .default_value("metrics") .help("url suffix to access metrics") .long("suffix") .short('s') .required(false) - .takes_value(true); + .action(clap::ArgAction::Set); options.push(arg); - let arg = Arg::with_name("qemu") + let arg = Arg::new("qemu") .help("Apply labels to metrics of processes looking like a Qemu/KVM virtual machine") .long("qemu") .short('q') .required(false) - .takes_value(false); + .action(clap::ArgAction::SetTrue); options.push(arg); - let arg = Arg::with_name("containers") + let arg = Arg::new("containers") .help("Monitor and apply labels for processes running as containers") .long("containers") .required(false) - .takes_value(false); + .action(clap::ArgAction::SetTrue); options.push(arg); - let arg = Arg::with_name("kubernetes_host") + let arg = Arg::new("kubernetes_host") .help("FQDN of the kubernetes API server") .long("kubernetes-host") .required(false) - .takes_value(true); + .action(clap::ArgAction::Set); options.push(arg); - let arg = Arg::with_name("kubernetes_scheme") + let arg = Arg::new("kubernetes_scheme") .help("Protocol used to access kubernetes API server") .long("kubernetes-scheme") .default_value("http") .required(false) - .takes_value(true); + .action(clap::ArgAction::Set); options.push(arg); - let arg = Arg::with_name("kubernetes_port") + let arg = Arg::new("kubernetes_port") .help("Kubernetes API server port number") .long("kubernetes-port") .default_value("6443") .required(false) - .takes_value(true); + .action(clap::ArgAction::Set); options.push(arg); options From e5d6a5df1c6f2f3f1fbf6fec19e9eeeb50707bee Mon Sep 17 00:00:00 2001 From: Uggla Date: Sun, 6 Nov 2022 22:37:51 +0100 Subject: [PATCH 005/303] feat: Upgrade qemu and warp10 exporters to clap 4.0 - There are breaking changes introduced by clap 4.0. So as a strategy, I upgrade exporters one by one. This commit contains the upgrade of qemu and warp10 exporters. --- src/exporters/qemu.rs | 2 +- src/exporters/warpten.rs | 43 ++++++++++++++++++++-------------------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/exporters/qemu.rs b/src/exporters/qemu.rs index b92a8a71..37645d36 100644 --- a/src/exporters/qemu.rs +++ b/src/exporters/qemu.rs @@ -34,7 +34,7 @@ impl Exporter for QemuExporter { } } - fn get_options() -> Vec> { + fn get_options() -> Vec { Vec::new() } } diff --git a/src/exporters/warpten.rs b/src/exporters/warpten.rs index 57cf83ce..ce98065c 100644 --- a/src/exporters/warpten.rs +++ b/src/exporters/warpten.rs @@ -1,6 +1,6 @@ use crate::exporters::*; use crate::sensors::{RecordGenerator, Sensor, Topology}; -use clap::Arg; +use clap::{value_parser, Arg}; use std::time::Duration; use std::{env, thread}; use utils::get_scaphandre_version; @@ -16,10 +16,10 @@ pub struct Warp10Exporter { impl Exporter for Warp10Exporter { /// Control loop for self.iteration() fn run(&mut self, parameters: clap::ArgMatches) { - let host = parameters.value_of("host").unwrap(); - let scheme = parameters.value_of("scheme").unwrap(); - let port = parameters.value_of("port").unwrap(); - let write_token = if let Some(token) = parameters.value_of("write-token") { + let host = parameters.get_one::("host").unwrap(); + let scheme = parameters.get_one::("scheme").unwrap(); + let port = parameters.get_one::("port").unwrap(); + let write_token = if let Some(token) = parameters.get_one::("write-token") { token.to_owned() } else { match env::var("SCAPH_WARP10_WRITE_TOKEN") { @@ -30,8 +30,8 @@ impl Exporter for Warp10Exporter { } }; //let read_token = parameters.value_of("read-token"); - let step = parameters.value_of("step").unwrap(); - let qemu = parameters.is_present("qemu"); + let step: u64 = *parameters.get_one("step").unwrap(); + let qemu = parameters.get_flag("qemu"); loop { match self.iteration( @@ -45,63 +45,64 @@ impl Exporter for Warp10Exporter { Ok(res) => debug!("Result: {:?}", res), Err(err) => error!("Failed ! {:?}", err), } - thread::sleep(Duration::new(step.parse::().unwrap(), 0)); + thread::sleep(Duration::new(step, 0)); } } /// Options for configuring the exporter. - fn get_options() -> Vec> { + fn get_options() -> Vec { let mut options = Vec::new(); - let arg = Arg::with_name("host") + let arg = Arg::new("host") .default_value("localhost") .help("Warp10 host's FQDN or IP address to send data to") .long("host") .short('H') .required(false) - .takes_value(true); + .action(clap::ArgAction::Set); options.push(arg); - let arg = Arg::with_name("scheme") + let arg = Arg::new("scheme") .default_value("http") .help("Either 'http' or 'https'") .long("scheme") .short('s') .required(false) - .takes_value(true); + .action(clap::ArgAction::Set); options.push(arg); - let arg = Arg::with_name("port") + let arg = Arg::new("port") .default_value("8080") .help("TCP port to join Warp10 on the host") .long("port") .short('p') .required(false) - .takes_value(true); + .action(clap::ArgAction::Set); options.push(arg); - let arg = Arg::with_name("write-token") + let arg = Arg::new("write-token") .help("Auth. token to write on Warp10") .long("write-token") .short('t') .required(false) - .takes_value(true); + .action(clap::ArgAction::Set); options.push(arg); - let arg = Arg::with_name("step") + let arg = Arg::new("step") .default_value("30") .help("Time step between measurements, in seconds.") .long("step") .short('S') .required(false) - .takes_value(true); + .value_parser(value_parser!(u64)) + .action(clap::ArgAction::Set); options.push(arg); - let arg = Arg::with_name("qemu") + let arg = Arg::new("qemu") .help("Tells scaphandre it is running on a Qemu hypervisor.") .long("qemu") .short('q') .required(false) - .takes_value(false); + .action(clap::ArgAction::SetTrue); options.push(arg); options From 48ada7ddcb72e5ab102c15e6f1389189cd7720ed Mon Sep 17 00:00:00 2001 From: Uggla Date: Sun, 6 Nov 2022 22:57:16 +0100 Subject: [PATCH 006/303] feat: Upgrade riemann exporter to clap 4.0 - There are breaking changes introduced by clap 4.0. So as a strategy, I upgrade exporters one by one. This commit contains the upgrade of riemann exporter. --- src/exporters/riemann.rs | 74 ++++++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/src/exporters/riemann.rs b/src/exporters/riemann.rs index ef2211fe..4f7ef2d1 100644 --- a/src/exporters/riemann.rs +++ b/src/exporters/riemann.rs @@ -6,6 +6,7 @@ use crate::exporters::utils::get_hostname; use crate::exporters::*; use crate::sensors::Sensor; use chrono::Utc; +use clap::value_parser; use clap::Arg; use riemann_client::proto::Attribute; use riemann_client::proto::Event; @@ -32,16 +33,14 @@ struct RiemannClient { impl RiemannClient { /// Instanciate the Riemann client either with mTLS or using raw TCP. fn new(parameters: &ArgMatches) -> RiemannClient { - let address = String::from(parameters.value_of("address").unwrap()); - let port = parameters - .value_of("port") - .unwrap() - .parse::() + let address = parameters.get_one::("address").unwrap().to_string(); + let port: u16 = *parameters + .get_one("port") .expect("Fail parsing port number"); - let client: Client = if parameters.is_present("mtls") { - let cafile = parameters.value_of("cafile").unwrap(); - let certfile = parameters.value_of("certfile").unwrap(); - let keyfile = parameters.value_of("keyfile").unwrap(); + let client: Client = if parameters.get_flag("mtls") { + let cafile = parameters.get_one::("cafile").unwrap(); + let certfile = parameters.get_one::("certfile").unwrap(); + let keyfile = parameters.get_one::("keyfile").unwrap(); Client::connect_tls(&address, port, cafile, certfile, keyfile) .expect("Fail to connect to Riemann server using mTLS") } else { @@ -121,10 +120,8 @@ impl RiemannExporter { impl Exporter for RiemannExporter { /// Entry point of the RiemannExporter. fn run(&mut self, parameters: ArgMatches) { - let dispatch_duration: u64 = parameters - .value_of("dispatch_duration") - .unwrap() - .parse() + let dispatch_duration: u64 = *parameters + .get_one("dispatch_duration") .expect("Wrong dispatch_duration value, should be a number of seconds"); let hostname = get_hostname(); @@ -142,8 +139,8 @@ impl Exporter for RiemannExporter { let mut metric_generator = MetricGenerator::new( topology, hostname, - parameters.is_present("qemu"), - parameters.is_present("containers"), + parameters.get_flag("qemu"), + parameters.get_flag("containers"), ); loop { @@ -185,7 +182,7 @@ impl Exporter for RiemannExporter { if let Some(cmdline_str) = cmdline { attributes.insert("cmdline".to_string(), cmdline_str.replace('\"', "\\\"")); - if parameters.is_present("qemu") { + if parameters.get_flag("qemu") { if let Some(vmname) = utils::filter_qemu_cmdline(&cmdline_str) { attributes.insert("vmname".to_string(), vmname); } @@ -230,74 +227,83 @@ impl Exporter for RiemannExporter { } /// Returns options understood by the exporter. - fn get_options() -> Vec> { + fn get_options() -> Vec { let mut options = Vec::new(); - let arg = Arg::with_name("address") + let arg = Arg::new("address") .default_value(DEFAULT_IP_ADDRESS) .help("Riemann ipv6 or ipv4 address. If mTLS is used then server fqdn must be provided") .long("address") .short('a') .required(false) - .takes_value(true); + .action(clap::ArgAction::Set); options.push(arg); - let arg = Arg::with_name("port") + let arg = Arg::new("port") .default_value(DEFAULT_PORT) .help("Riemann TCP port number") .long("port") .short('p') .required(false) - .takes_value(true); + .value_parser(value_parser!(u16)) + .action(clap::ArgAction::Set); options.push(arg); - let arg = Arg::with_name("dispatch_duration") + let arg = Arg::new("dispatch_duration") .default_value("5") .help("Duration between metrics dispatch") .long("dispatch") .short('d') .required(false) - .takes_value(true); + .value_parser(value_parser!(u64)) + .action(clap::ArgAction::Set); options.push(arg); - let arg = Arg::with_name("qemu") + let arg = Arg::new("qemu") .help("Instruct that scaphandre is running on an hypervisor") .long("qemu") .short('q') .required(false) - .takes_value(false); + .action(clap::ArgAction::SetTrue); options.push(arg); - let arg = Arg::with_name("mtls") + let arg = Arg::new("containers") + .help("Monitor and apply labels for processes running as containers") + .long("containers") + .required(false) + .action(clap::ArgAction::SetTrue); + options.push(arg); + + let arg = Arg::new("mtls") .help("Connect to a Riemann server using mTLS. Parameters address, ca, cert and key must be defined.") .long("mtls") .required(false) - .takes_value(false) - .requires_all(&["address","cafile", "certfile", "keyfile"]); + .requires_all(["address","cafile", "certfile", "keyfile"]) + .action(clap::ArgAction::SetTrue); options.push(arg); - let arg = Arg::with_name("cafile") + let arg = Arg::new("cafile") .help("CA certificate file (.pem format)") .long("ca") .required(false) - .takes_value(true) + .action(clap::ArgAction::Set) .display_order(1000) .requires("mtls"); options.push(arg); - let arg = Arg::with_name("certfile") + let arg = Arg::new("certfile") .help("Client certificate file (.pem format)") .long("cert") .required(false) - .takes_value(true) + .action(clap::ArgAction::Set) .display_order(1001) .requires("mtls"); options.push(arg); - let arg = Arg::with_name("keyfile") + let arg = Arg::new("keyfile") .help("Client RSA key") .long("key") .required(false) - .takes_value(true) + .action(clap::ArgAction::Set) .display_order(1001) .requires("mtls"); options.push(arg); From 181ac1709c780b5fd6312a4191c537e9c622abb0 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 30 Jan 2023 13:46:54 +0100 Subject: [PATCH 007/303] adding funding file --- FUNDING.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 FUNDING.yml diff --git a/FUNDING.yml b/FUNDING.yml new file mode 100644 index 00000000..5c743caf --- /dev/null +++ b/FUNDING.yml @@ -0,0 +1 @@ +github: hubblo-org From 947b3b39d5d7155f4526a9881e7524b10ab9f97e Mon Sep 17 00:00:00 2001 From: bpetit Date: Wed, 1 Feb 2023 11:51:01 +0100 Subject: [PATCH 008/303] doc: fix typo in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 784d3e9e..c27f6bff 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Scaphandre *[skafɑ̃dʁ]* is a metrology agent dedicated to electrical [power]( **Scaphandre** means *heavy* **diving suit** in [:fr:](https://fr.wikipedia.org/wiki/Scaphandre_%C3%A0_casque). It comes from the idea that tech related services often don't track their power consumption and thus don't expose it to their clients. Most of the time the reason is a presumed bad [ROI](https://en.wikipedia.org/wiki/Return_on_investment). Scaphandre makes, for tech providers and tech users, easier and cheaper to go under the surface to bring back the desired power consumption metrics, take better sustainability focused decisions, and then show the metrics to their clients to allow them to do the same. -This project was born from a deep sense of duty from tech workers. Please refer to the [why](https://hubblo-org.github.io/scaphandre-documentation/why.html) section for know more about its goals. +This project was born from a deep sense of duty from tech workers. Please refer to the [why](https://hubblo-org.github.io/scaphandre-documentation/why.html) section to know more about its goals. **Warning**: this is still a very early stage project. Any feedback or contribution will be highly appreciated. Please refer to the [contribution](https://hubblo-org.github.io/scaphandre-documentation/contributing.html) section. From b36a5a98ab95e28b3bc07a599ce3ac081e9751ea Mon Sep 17 00:00:00 2001 From: fvaleye Date: Sun, 3 Jul 2022 20:52:11 +0200 Subject: [PATCH 009/303] Add Scaphandre Python binding with the first version of the PowercapRAPL sensor --- .github/workflows/python_build.yml | 69 + python/.gitignore | 18 + python/Cargo.lock | 1938 ++++++++++++++++++++++++ python/Cargo.toml | 25 + python/LICENSE.txt | 201 +++ python/Makefile | 78 + python/README.md | 0 python/docs/Makefile | 20 + python/docs/make.bat | 35 + python/docs/source/_static/.gitignore | 4 + python/docs/source/api_reference.rst | 11 + python/docs/source/conf.py | 80 + python/docs/source/index.rst | 19 + python/pyproject.toml | 60 + python/scaphandre/__init__.py | 1 + python/scaphandre/sensors.py | 59 + python/src/lib.rs | 91 ++ python/stubs/scaphandre/__init__.pyi | 3 + python/stubs/scaphandre/scaphandre.pyi | 3 + python/tests/test_scaphandre.py | 5 + src/lib.rs | 5 + 21 files changed, 2725 insertions(+) create mode 100644 .github/workflows/python_build.yml create mode 100644 python/.gitignore create mode 100644 python/Cargo.lock create mode 100644 python/Cargo.toml create mode 100644 python/LICENSE.txt create mode 100644 python/Makefile create mode 100644 python/README.md create mode 100644 python/docs/Makefile create mode 100644 python/docs/make.bat create mode 100644 python/docs/source/_static/.gitignore create mode 100644 python/docs/source/api_reference.rst create mode 100644 python/docs/source/conf.py create mode 100644 python/docs/source/index.rst create mode 100644 python/pyproject.toml create mode 100644 python/scaphandre/__init__.py create mode 100644 python/scaphandre/sensors.py create mode 100644 python/src/lib.rs create mode 100644 python/stubs/scaphandre/__init__.pyi create mode 100644 python/stubs/scaphandre/scaphandre.pyi create mode 100644 python/tests/test_scaphandre.py diff --git a/.github/workflows/python_build.yml b/.github/workflows/python_build.yml new file mode 100644 index 00000000..1a93f310 --- /dev/null +++ b/.github/workflows/python_build.yml @@ -0,0 +1,69 @@ +name: python_build + +on: + push: + branches: [main] + pull_request: + branches: [main] + +defaults: + run: + working-directory: ./python + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: 3.7 + - name: Check Python + run: | + pip install black isort mypy + make check-python + - name: Install minimal stable with clippy and rustfmt + uses: actions-rs/toolchain@v1 + with: + profile: default + toolchain: stable + override: true + - name: Check Rust + run: make check-rust + + test: + name: Python Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Install latest nightly + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + components: rustfmt, clippy + + - uses: Swatinem/rust-cache@v1 + + - uses: actions/setup-python@v3 + with: + python-version: "3.7" + + - name: Build and install scaphandre + run: | + pip install virtualenv + virtualenv venv + source venv/bin/activate + make develop + + - name: Run tests + run: | + source venv/bin/activate + make unit-test + + - name: Build Sphinx documentation + run: | + source venv/bin/activate + make build-documentation \ No newline at end of file diff --git a/python/.gitignore b/python/.gitignore new file mode 100644 index 00000000..f2914074 --- /dev/null +++ b/python/.gitignore @@ -0,0 +1,18 @@ +# venv +venv + +# Byte-compiled / optimized / DLL files +__pycache__/ +/target + +# Unit test / coverage reports +.coverage +.pytest_cache/ + +# mypy +.mypy_cache/ + +# sphinx build directory +docs/build + +*.so diff --git a/python/Cargo.lock b/python/Cargo.lock new file mode 100644 index 00000000..8a5247a0 --- /dev/null +++ b/python/Cargo.lock @@ -0,0 +1,1938 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "async-channel" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base-x" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc19a4937b4fbd3fe3379793130e42060d10627a360f2127802b10b87e7baf74" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cache-padded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" + +[[package]] +name = "castaway" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[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.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "serde", + "time 0.1.44", + "winapi", +] + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim 0.8.0", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "colored" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +dependencies = [ + "atty", + "lazy_static", + "winapi", +] + +[[package]] +name = "concurrent-queue" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +dependencies = [ + "cache-padded", +] + +[[package]] +name = "const_fn" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +dependencies = [ + "cfg-if", + "lazy_static", +] + +[[package]] +name = "curl" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d855aeef205b43f65a5001e0997d81f8efca7badad4fad7d897aa7f0d0651f" +dependencies = [ + "curl-sys", + "libc", + "openssl-probe", + "openssl-sys", + "schannel", + "socket2", + "winapi", +] + +[[package]] +name = "curl-sys" +version = "0.4.55+curl-7.83.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23734ec77368ec583c2e61dd3f0b0e5c98b93abe6d2a004ca06b91dd7e3e2762" +dependencies = [ + "cc", + "libc", + "libnghttp2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", + "winapi", +] + +[[package]] +name = "dirs" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "discard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" + +[[package]] +name = "docker-sync" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c989c4ad66535edd02443e7d7699d3ab530df4523f12f6aeb7888bdc5ab7c32" +dependencies = [ + "http", + "isahc", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "docopt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f3f119846c823f9eafcf953a8f6ffb6ed69bf6240883261a7f13b634579a51f" +dependencies = [ + "lazy_static", + "regex", + "serde", + "strsim 0.10.0", +] + +[[package]] +name = "encoding_rs" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "event-listener" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" + +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + +[[package]] +name = "flate2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "getrandom" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", +] + +[[package]] +name = "h2" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + +[[package]] +name = "http" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "indoc" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05a0bd019339e5d968b37855180087b7b9d512c5046fbd244cf8c95687927d6e" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "isahc" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" +dependencies = [ + "async-channel", + "castaway", + "crossbeam-utils", + "curl", + "curl-sys", + "encoding_rs", + "event-listener", + "futures-lite", + "http", + "log", + "mime", + "once_cell", + "polling", + "serde", + "serde_json", + "slab", + "sluice", + "tracing", + "tracing-futures", + "url", + "waker-fn", +] + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "js-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "k8s-openapi" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f8de9873b904e74b3533f77493731ee26742418077503683db44e1b3c54aa5c" +dependencies = [ + "base64", + "bytes", + "chrono", + "http", + "percent-encoding", + "serde", + "serde-value", + "serde_json", + "url", +] + +[[package]] +name = "k8s-sync" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3c2f8f5cb2611742f8ceb73f23451690ff0d930149eac45fcb63ca86fbd443" +dependencies = [ + "base64", + "chrono", + "dirs", + "http", + "isahc", + "k8s-openapi", + "openssl", + "serde", + "serde_yaml", + "tempfile", + "url", +] + +[[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.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "libnghttp2-sys" +version = "0.1.7+1.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ed28aba195b38d5ff02b9170cbff627e336a20925e43b4945390401c5dc93f" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "libz-sys" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e7e15d7610cce1d9752e137625f14e61a28cd45929b6e12e47b50fe154ee2e" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "loggerv" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60d8de15ae71e760bce7f05447f85f73624fe0d3b1e4c5a63ba5d4cb0748d374" +dependencies = [ + "ansi_term", + "atty", + "log", +] + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "miniz_oxide" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b10983b38c53aebdf33f542c6275b0f58a238129d00c4ae0e6fb59738d783ca" + +[[package]] +name = "openssl" +version = "0.10.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb81a6430ac911acb25fe5ac8f1d2af1b4ea8a4fdfda0f1ee4292af2e2d8eb0e" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5fd19fb3e0a8191c1e34935718976a3e70c112ab9a24af6d7cadccd9d90bc0" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ordered-float" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" +dependencies = [ + "num-traits", +] + +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + +[[package]] +name = "parking_lot" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + +[[package]] +name = "polling" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" +dependencies = [ + "cfg-if", + "libc", + "log", + "wepoll-ffi", + "winapi", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro2" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "procfs" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0941606b9934e2d98a3677759a971756eb821f75764d0e0d26946d08e74d9104" +dependencies = [ + "bitflags", + "byteorder", + "chrono", + "flate2", + "hex", + "lazy_static", + "libc", +] + +[[package]] +name = "protobuf" +version = "2.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf7e6d18738ecd0902d30d1ad232c9125985a3422929b16c65517b38adc14f96" + +[[package]] +name = "pyo3" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6302e85060011447471887705bb7838f14aba43fcb06957d823739a496b3dc" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "parking_lot", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b65b546c35d8a3b1b2f0ddbac7c6a569d759f357f2b9df884f5d6b719152c8" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c275a07127c1aca33031a563e384ffdd485aee34ef131116fcd58e3430d1742b" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284fc4485bfbcc9850a6d661d627783f18d19c2ab55880b021671c4ba83e90f7" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53bda0f58f73f5c5429693c96ed57f7abdb38fdfc28ae06da4101a257adb7faf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "riemann_client" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1005d55a9a8cb53f6ab2380792031394fff549b020cde16cecbc0978df9e7242" +dependencies = [ + "docopt", + "libc", + "protobuf", + "rustls", + "rustls-pemfile", + "serde", + "webpki", + "webpki-roots", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "rustls" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +dependencies = [ + "base64", + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-pemfile" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +dependencies = [ + "base64", +] + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "scaphandre" +version = "0.4.1" +dependencies = [ + "chrono", + "clap", + "colored", + "docker-sync", + "hostname", + "hyper", + "k8s-sync", + "log", + "loggerv", + "procfs", + "protobuf", + "regex", + "riemann_client", + "serde", + "serde_json", + "time 0.2.27", + "tokio", + "warp10", +] + +[[package]] +name = "scaphandre-python" +version = "0.1.0" +dependencies = [ + "env_logger", + "pyo3", + "scaphandre", +] + +[[package]] +name = "schannel" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +dependencies = [ + "lazy_static", + "windows-sys", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707d15895415db6628332b737c838b88c598522e4dc70647e59b72312924aebc" +dependencies = [ + "indexmap", + "ryu", + "serde", + "yaml-rust", +] + +[[package]] +name = "sha1" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" +dependencies = [ + "sha1_smol", +] + +[[package]] +name = "sha1_smol" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" + +[[package]] +name = "sluice" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" +dependencies = [ + "async-channel", + "futures-core", + "futures-io", +] + +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "standback" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" +dependencies = [ + "version_check", +] + +[[package]] +name = "stdweb" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" +dependencies = [ + "discard", + "rustc_version", + "stdweb-derive", + "stdweb-internal-macros", + "stdweb-internal-runtime", + "wasm-bindgen", +] + +[[package]] +name = "stdweb-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_derive", + "syn", +] + +[[package]] +name = "stdweb-internal-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +dependencies = [ + "base-x", + "proc-macro2", + "quote", + "serde", + "serde_derive", + "serde_json", + "sha1", + "syn", +] + +[[package]] +name = "stdweb-internal-runtime" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-lexicon" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1" + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[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.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "time" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" +dependencies = [ + "const_fn", + "libc", + "standback", + "stdweb", + "time-macros", + "version_check", + "winapi", +] + +[[package]] +name = "time-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" +dependencies = [ + "proc-macro-hack", + "time-macros-impl", +] + +[[package]] +name = "time-macros-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "standback", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395" +dependencies = [ + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-util" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f988a1a1adc2fb21f9c12aa96441da33a1728193ae0b95d2be22dbd17fcb4e5c" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + +[[package]] +name = "tracing" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unindent" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52fee519a3e570f7df377a06a1a7775cdbfb7aa460be7e08de2b1f0e69973a44" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[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 = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "warp10" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140e989c5e92da4e09581133f4de7df32d1ce7de6b7a077652bdfa3f9aef97bf" +dependencies = [ + "isahc", + "percent-encoding", + "serde", + "serde_json", + "time 0.2.27", + "url", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[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.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" + +[[package]] +name = "web-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" +dependencies = [ + "webpki", +] + +[[package]] +name = "wepoll-ffi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +dependencies = [ + "cc", +] + +[[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-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[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-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] diff --git a/python/Cargo.toml b/python/Cargo.toml new file mode 100644 index 00000000..2fb3fd55 --- /dev/null +++ b/python/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "scaphandre-python" +version = "0.1.0" +authors = ["fvaleye@github.com"] +homepage = "https://hubblo-org.github.io/scaphandre-documentation" +license = "Apache-2.0" +description = "Electrical power consumption measurement agent." +readme = "README.md" +edition = "2021" +keywords = ["energy", "sustainability", "measure", "virtual-machine", "energy-monitor", "electricity", "virtual-machines", "energy-consumption", "electricity-consumption", "energy-efficiency", "carbon-footprint"] + +[lib] +name = "scaphandre" +crate-type = ["cdylib"] + +[dependencies] +env_logger = "0" + +[dependencies.pyo3] +version = "0.16" +features = ["extension-module", "abi3", "abi3-py37"] + +[dependencies.scaphandre] +path = "../" +version = "0" \ No newline at end of file diff --git a/python/LICENSE.txt b/python/LICENSE.txt new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/python/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/python/Makefile b/python/Makefile new file mode 100644 index 00000000..3b805cb3 --- /dev/null +++ b/python/Makefile @@ -0,0 +1,78 @@ +.DEFAULT_GOAL := help + +VENV := venv +MATURIN_VERSION := $(shell awk -F '[ ="]+' '$$1 == "requires" { print $$4 }' pyproject.toml) +PACKAGE_VERSION := $(shell cargo pkgid | cut -d\# -f2 | cut -d: -f2) + +.PHONY: setup-venv +setup-venv: ## Setup the virtualenv + $(info --- Setup virtualenv ---) + python -m venv $(VENV) + +.PHONY: setup +setup: ## Setup the requirements + $(info --- Setup dependencies ---) + pip install maturin==$(MATURIN_VERSION) + +.PHONY: build +build: setup ## Build Python binding of scaphandre + $(info --- Build Python binding ---) + maturin build $(MATURIN_EXTRA_ARGS) + +.PHONY: develop +develop: setup ## Install Python binding of scaphandre + $(info --- Develop with Python binding ---) + maturin develop --extras=devel $(MATURIN_EXTRA_ARGS) + +.PHONY: install +install: build ## Install Python binding of scaphandre + $(info --- Uninstall Python binding ---) + pip uninstall -y scaphandre + $(info --- Install Python binding ---) + $(eval TARGET_WHEEL := $(shell ls ../target/wheels/scaphandre-${PACKAGE_VERSION}-*.whl)) + pip install $(TARGET_WHEEL)[devel] + +.PHONY: format +format: ## Format the code + $(info --- Rust format ---) + cargo fmt + $(info --- Python format ---) + black . + isort . + +.PHONY: check-rust +check-rust: ## Run check on Rust + $(info --- Check Rust clippy ---) + cargo clippy + $(info --- Check Rust format ---) + cargo fmt -- --check + +.PHONY: check-python +check-python: ## Run check on Python + $(info Check Python isort) + isort --diff --check-only . + $(info Check Python black) + black --check . + $(info Check Python mypy) + mypy + +.PHONY: unit-test +unit-test: ## Run unit test + $(info --- Run Python unit-test ---) + python -m pytest + +.PHONY: build-documentation +build-documentation: ## Build documentation with Sphinx + $(info --- Run build of the Sphinx documentation ---) + sphinx-build -Wn -b html -d ./docs/build/doctrees ./docs/source ./docs/build/html + +.PHONY: clean +clean: ## Run clean + $(warning --- Clean virtualenv and target directory ---) + cargo clean + rm -rf $(VENV) + find . -type f -name '*.pyc' -delete + +.PHONY: help +help: + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' diff --git a/python/README.md b/python/README.md new file mode 100644 index 00000000..e69de29b diff --git a/python/docs/Makefile b/python/docs/Makefile new file mode 100644 index 00000000..d0c3cbf1 --- /dev/null +++ b/python/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/python/docs/make.bat b/python/docs/make.bat new file mode 100644 index 00000000..747ffb7b --- /dev/null +++ b/python/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/python/docs/source/_static/.gitignore b/python/docs/source/_static/.gitignore new file mode 100644 index 00000000..86d0cb27 --- /dev/null +++ b/python/docs/source/_static/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore \ No newline at end of file diff --git a/python/docs/source/api_reference.rst b/python/docs/source/api_reference.rst new file mode 100644 index 00000000..0b9b37a0 --- /dev/null +++ b/python/docs/source/api_reference.rst @@ -0,0 +1,11 @@ +API Reference +==================================== + +Scaphandre +---------- + +.. automodule:: scaphandre.Scaphandre + :members: + +.. automodule:: scaphandre.EnergyRecord + :members: \ No newline at end of file diff --git a/python/docs/source/conf.py b/python/docs/source/conf.py new file mode 100644 index 00000000..c3d528a3 --- /dev/null +++ b/python/docs/source/conf.py @@ -0,0 +1,80 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +import os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) +import sys + +import toml + +sys.path.insert(0, os.path.abspath("../scaphandre/")) +sys.path.insert(0, os.path.abspath("./_ext")) + + +def get_release_version() -> str: + """ + Get the release version from the Cargo.toml file + + :return: + """ + cargo_content = toml.load("../../Cargo.toml") + return cargo_content["package"]["version"] + + +# -- Project information ----------------------------------------------------- + +project = "scaphandre" +copyright = "2022, hubblo" +author = "hubblo" + +# The full version, including alpha/beta/rc tags +release = get_release_version() + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx_rtd_theme", + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", +] +autodoc_typehints = "description" +nitpicky = True + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "sphinx_rtd_theme" + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] + +page_source_prefix = "python/docs/source" diff --git a/python/docs/source/index.rst b/python/docs/source/index.rst new file mode 100644 index 00000000..620f3fd9 --- /dev/null +++ b/python/docs/source/index.rst @@ -0,0 +1,19 @@ +.. scaphandre documentation master file, created by + sphinx-quickstart on Sun Jul 3 20:27:34 2022. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to scaphandre's documentation! +====================================== + +.. toctree:: + :maxdepth: 2 + + api_reference + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/python/pyproject.toml b/python/pyproject.toml new file mode 100644 index 00000000..6274c98c --- /dev/null +++ b/python/pyproject.toml @@ -0,0 +1,60 @@ +[build-system] +requires = ["maturin==0.12.20"] +build-backend = "maturin" + +[project] +name = "scaphandre" +description = "Electrical power consumption measurement agent." +readme = "README.md" +license = {file = "LICENSE.txt"} +requires-python = ">=3.7" +keywords = ["energy", "sustainability", "measure", "virtual-machine", "energy-monitor", "electricity", "virtual-machines", "energy-consumption", "electricity-consumption", "energy-efficiency", "carbon-footprint"] +classifiers = [ + "Development Status :: 3 - Alpha", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python :: 3 :: Only" +] +dependencies = [] + +[project.optional-dependencies] +devel = [ + "mypy", + "black", + "isort", + "pytest", + "pytest-mock", + "pytest-cov", + "sphinx", + "sphinx-rtd-theme", + "toml", +] + +[project.urls] +documentation = "https://hubblo-org.github.io/scaphandre-documentation" +repository = "https://github.com/hubblo-org/scaphandre" + +[tool.mypy] +files = "scaphandre/*.py" +exclude = "^tests" +mypy_path = "./stubs" +disallow_any_generics = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_defs = true +disallow_incomplete_defs = true +check_untyped_defs = true +disallow_untyped_decorators = true +no_implicit_optional = true +warn_redundant_casts = true +warn_unused_ignores = true +warn_return_any = false +implicit_reexport = false +strict_equality = true + +[tool.isort] +profile = "black" +src_paths = ["scaphandre", "tests"] + +[tool.black] +include = '\.pyi?$' +exclude = "venv" \ No newline at end of file diff --git a/python/scaphandre/__init__.py b/python/scaphandre/__init__.py new file mode 100644 index 00000000..5e36334c --- /dev/null +++ b/python/scaphandre/__init__.py @@ -0,0 +1 @@ +from .sensors import * diff --git a/python/scaphandre/sensors.py b/python/scaphandre/sensors.py new file mode 100644 index 00000000..7bfa4400 --- /dev/null +++ b/python/scaphandre/sensors.py @@ -0,0 +1,59 @@ +from dataclasses import dataclass + +from .scaphandre import RawScaphandre + + +@dataclass +class EnergyRecord: + """ + Energy record measured by Scaphandre + """ + + timestamp: str + value: str + unit: str + + +@dataclass(init=False) +class Scaphandre: + """ + Scaphandre, a metrology agent dedicated to electrical power consumption metrics. + """ + + sensor_name: str + + def __init__( + self, + is_virtual_machine: bool = False, + buffer_per_socket_max_kbytes: int = 8, + buffer_per_domain_max_kbytes: int = 8, + ): + """ + Init Scaphandre + + :param is_virtual_machine: running on a virtual machine for powercap_rapl sensor + :param buffer_per_socket_max_kbytes: max buffer per socket in kbytes for powercap_rapl sensor + :param buffer_per_domain_max_kbytes: max buffer per domain in kbytes for powercap_rapl sensor + """ + self._scaphandre = RawScaphandre( + buffer_per_socket_max_kbytes, + buffer_per_domain_max_kbytes, + is_virtual_machine, + ) + self.name = self._scaphandre.sensor_name + + def is_compatible(self) -> bool: + """ + Check if Scaphandre has a sensor available and valid depending on the hardware context. + + :return: a sensor is available and valid + """ + return self._scaphandre.is_compatible() + + def get_energy_consumption_measures(self) -> EnergyRecord: + """ + Get the energy records from Scaphandre. + + :return: the energy record measured + """ + return self._scaphandre.get_energy_consumption_measures() diff --git a/python/src/lib.rs b/python/src/lib.rs new file mode 100644 index 00000000..795f3499 --- /dev/null +++ b/python/src/lib.rs @@ -0,0 +1,91 @@ +#![deny(warnings)] + +extern crate pyo3; + +use pyo3::create_exception; +use pyo3::exceptions::PyException; +use pyo3::prelude::*; +use scaphandre::sensors; +use scaphandre::sensors::powercap_rapl; +use scaphandre::sensors::units; +use sensors::{powercap_rapl::PowercapRAPLSensor, Sensor}; +use std::error::Error; +use std::time::Duration; + +create_exception!(scaphandre, PyScaphandreError, PyException); + +impl PyScaphandreError { + fn from_error(err: Box) -> pyo3::PyErr { + PyScaphandreError::new_err(err.to_string()) + } +} + +#[pyclass] +struct RawScaphandre { + _scaphandre: powercap_rapl::PowercapRAPLSensor, + #[pyo3(get)] + sensor_name: String, +} + +#[pymethods] +impl RawScaphandre { + #[new] + fn new( + buffer_per_socket_max_kbytes: u16, + buffer_per_domain_max_kbytes: u16, + is_virtual_machine: bool, + ) -> PyResult { + let sensor = PowercapRAPLSensor::new( + buffer_per_socket_max_kbytes, + buffer_per_domain_max_kbytes, + is_virtual_machine, + ); + Ok(RawScaphandre { + _scaphandre: sensor, + sensor_name: "PowercapRAPL".to_string(), + }) + } + + fn is_compatible(&self) -> bool { + matches!(PowercapRAPLSensor::check_module(), Ok(_)) + } + + fn get_energy_consumption_measures(&self) -> PyResult> { + Ok(self + ._scaphandre + .generate_topology() + .map_err(PyScaphandreError::from_error)? + .record_buffer + .iter() + .map(|record| RawEnergyRecord { + _timestamp: record.timestamp, + _value: record.value.clone(), + _unit: record.unit, + }) + .collect()) + } +} + +#[pyclass] +struct RawEnergyRecord { + _timestamp: Duration, + _value: String, + _unit: units::Unit, +} + +#[pyfunction] +fn rust_core_version() -> &'static str { + scaphandre::crate_version() +} + +#[pymodule] +// module name need to match project name +fn scaphandre(py: Python, m: &PyModule) -> PyResult<()> { + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init(); + + m.add_function(pyo3::wrap_pyfunction!(rust_core_version, m)?)?; + m.add_class::()?; + m.add_class::()?; + m.add("PyScaphandreError", py.get_type::())?; + Ok(()) +} diff --git a/python/stubs/scaphandre/__init__.pyi b/python/stubs/scaphandre/__init__.pyi new file mode 100644 index 00000000..c35db584 --- /dev/null +++ b/python/stubs/scaphandre/__init__.pyi @@ -0,0 +1,3 @@ +from typing import Any + +RawScaphandre: Any diff --git a/python/stubs/scaphandre/scaphandre.pyi b/python/stubs/scaphandre/scaphandre.pyi new file mode 100644 index 00000000..c35db584 --- /dev/null +++ b/python/stubs/scaphandre/scaphandre.pyi @@ -0,0 +1,3 @@ +from typing import Any + +RawScaphandre: Any diff --git a/python/tests/test_scaphandre.py b/python/tests/test_scaphandre.py new file mode 100644 index 00000000..16fcfc96 --- /dev/null +++ b/python/tests/test_scaphandre.py @@ -0,0 +1,5 @@ +from scaphandre import RawScaphandre, Scaphandre + + +def test_scaphandre_should_init_with_the_good_name(): + assert Scaphandre().name == "PowercapRAPL" diff --git a/src/lib.rs b/src/lib.rs index 7eaea039..b9631b19 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -187,6 +187,11 @@ pub fn scaphandre_header(exporter_name: &str) { println!("Sending ⚡ metrics"); } +/// Returns rust crate version, can be use used in language bindings to expose Rust core version +pub fn crate_version() -> &'static str { + env!("CARGO_PKG_VERSION") +} + // Copyright 2020 The scaphandre authors. // // Licensed under the Apache License, Version 2.0 (the "License"); From 50f17d8584183ddba87565ff1fdddbf5f2c87900 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 13 Mar 2023 11:11:39 +0100 Subject: [PATCH 010/303] chore: made sysinfo not optional and bumped version --- Cargo.lock | 17 +++++++++++++---- Cargo.toml | 2 +- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8a359b91..f9939664 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -813,7 +813,7 @@ dependencies = [ "libc", "log", "miow", - "ntapi", + "ntapi 0.3.6", "winapi", ] @@ -835,6 +835,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "ntapi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc51db7b362b205941f71232e56c625156eb9a929f8cf74a428fd5bc094a4afc" +dependencies = [ + "winapi", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -1529,14 +1538,14 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.22.4" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccb37aa4af23791c584202d286ed9420e023e9d27e49d5a76215623f4bcc2502" +checksum = "d3e847e2de7a137c8c2cede5095872dbb00f4f9bf34d061347e36b43322acd56" dependencies = [ "cfg-if", "core-foundation-sys", "libc", - "ntapi", + "ntapi 0.4.0", "once_cell", "rayon", "winapi", diff --git a/Cargo.toml b/Cargo.toml index a06ace0a..8d5a4ded 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,13 +31,13 @@ docker-sync = { version = "0.1.2", optional = true } k8s-sync = { version = "0.2.3", optional = true } hyper = { version = "0.14", features = ["full"], optional = true } tokio = { version = "1", features = ["full"], optional = true} +sysinfo = { version = "0.28.2"} [target.'cfg(target_os="linux")'.dependencies] procfs = { version = "0.12.0" } [target.'cfg(target_os="windows")'.dependencies] windows = { version = "0.27.0", features = ["alloc","Win32_Storage_FileSystem","Win32_Foundation","Win32_Security","Win32_System_IO","Win32_System_Ioctl"]} -sysinfo = { version = "0.22.4"} [features] From 046b143ccb559cbbacf78ceada554282fba22a47 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 13 Mar 2023 12:05:15 +0100 Subject: [PATCH 011/303] fix: MetricGenerator was broken when containers feat is off --- src/exporters/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index 8241a645..23ee8eb7 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -219,7 +219,7 @@ impl MetricGenerator { topology, hostname, #[cfg(target_os = "linux")] - qemu, + qemu: _qemu, } } From 2159fa2648d006fae70dad3c2812d7d97f3667a4 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 13 Mar 2023 12:13:17 +0100 Subject: [PATCH 012/303] ci: enabling unit tests for windows --- .github/workflows/build-and-test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 8f14260f..5941c39f 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -157,6 +157,9 @@ jobs: - name: Install Rust toolchain run: | rustup toolchain install stable-x86_64-pc-windows-msvc + - name: Build (debug mode) + run: | + cargo test --no-default-features --features "prometheus json riemann" - name: Build (debug mode) run: | cargo build --no-default-features --features "prometheus json riemann" From c42dc7c516d24dcccccb06804912f124361a3801 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 13 Mar 2023 12:13:57 +0100 Subject: [PATCH 013/303] chore: checking sysinfo returns process cmdline as procfs did --- src/sensors/utils.rs | 111 ++++++++++--------------------------------- 1 file changed, 24 insertions(+), 87 deletions(-) diff --git a/src/sensors/utils.rs b/src/sensors/utils.rs index 6748c3ba..9d149b16 100644 --- a/src/sensors/utils.rs +++ b/src/sensors/utils.rs @@ -3,9 +3,7 @@ use procfs::{self, process::Process}; use regex::Regex; #[cfg(feature = "containers")] use std::collections::HashMap; -#[cfg(target_os = "windows")] -use sysinfo::{get_current_pid, Process, ProcessExt, ProcessorExt, System, SystemExt}; -//use std::error::Error; +use sysinfo::{get_current_pid, Process as SysinfoProcess, ProcessExt, System, SystemExt}; use ordered_float::*; use std::path::PathBuf; use std::time::{Duration, SystemTime}; @@ -33,48 +31,24 @@ pub struct IStat { pub tty_nr: i32, pub tpgid: i32, pub flags: u32, - //pub minflt: u64, - //pub cminflt: u64, - //pub majflt: u64, - //pub cmajflt: u64, pub utime: u64, pub stime: u64, pub cutime: i64, pub cstime: i64, - //pub priority: i64, pub nice: i64, pub num_threads: i64, pub itrealvalue: i64, pub starttime: u64, pub vsize: u64, - //pub rss: i64, - //pub rsslim: u64, - //pub startcode: u64, - //pub endcode: u64, - //pub startstack: u64, - //pub kstkesp: u64, - //pub kstkeip: u64, pub signal: u64, pub blocked: u64, - //pub sigignore: u64, - //pub sigcatch: u64, - //pub wchan: u64, - //pub nswap: u64, - //pub cnswap: u64, pub exit_signal: Option, pub processor: Option, - //pub rt_priority: Option, - //pub policy: Option, pub delayacct_blkio_ticks: Option, pub guest_time: Option, pub cguest_time: Option, pub start_data: Option, pub end_data: Option, - //pub start_brk: Option, - //pub arg_start: Option, - //pub arg_end: Option, - //pub env_start: Option, - //pub env_end: Option, pub exit_code: Option, } @@ -153,64 +127,8 @@ pub struct IStatus { pub name: String, pub umask: Option, pub state: String, - //pub tgid: i32, - //pub ngid: Option, pub pid: i32, pub ppid: i32, - //pub tracerpid: i32, - //pub ruid: u32, - //pub euid: u32, - //pub suid: u32, - //pub fuid: u32, - //pub rgid: u32, - //pub egid: u32, - //pub sgid: u32, - //pub fgid: u32, - //pub fdsize: u32, - //pub groups: Vec, - //pub nstgid: Option>, - //pub nspid: Option>, - //pub nspgid: Option>, - //pub nssid: Option>, - //pub vmpeak: Option, - //pub vmsize: Option, - //pub vmlck: Option, - //pub vmpin: Option, - //pub vmhwm: Option, - //pub vmrss: Option, - //pub rssanon: Option, - //pub rssfile: Option, - //pub rssshmem: Option, - //pub vmdata: Option, - //pub vmstk: Option, - //pub vmexe: Option, - //pub vmlib: Option, - //pub vmpte: Option, - //pub vmswap: Option, - //pub hugetlbpages: Option, - //pub threads: u64, - //pub sigq: (u64, u64), - //pub sigpnd: u64, - //pub shdpnd: u64, - //pub sigblk: u64, - //pub sigign: u64, - //pub sigcgt: u64, - //pub capinh: u64, - //pub capprm: u64, - //pub capeff: u64, - //pub capbnd: Option, - //pub capamb: Option, - //pub nonewprivs: Option, - //pub seccomp: Option, - //pub speculation_store_bypass: Option, - //pub cpus_allowed: Option>, - //pub cpus_allowed_list: Option>, - //pub mems_allowed: Option>, - //pub mems_allowed_list: Option>, - //pub voluntary_ctxt_switches: Option, - //pub nonvoluntary_ctxt_switches: Option, - //pub core_dumping: Option, - //pub thp_enabled: Option, } #[derive(Debug, Clone)] @@ -377,7 +295,7 @@ pub struct ProcessTracker { /// Maximum number of ProcessRecord instances that scaphandre is allowed to /// store, per PID (thus, for each subvector). pub max_records_per_process: u16, - #[cfg(target_os = "windows")] + /// Sysinfo system for resources monitoring pub sysinfo: System, #[cfg(feature = "containers")] pub regex_cgroup_docker: Regex, @@ -392,7 +310,6 @@ impl Clone for ProcessTracker { ProcessTracker { procs: self.procs.clone(), max_records_per_process: self.max_records_per_process, - #[cfg(target_os = "windows")] sysinfo: System::new_all(), #[cfg(feature = "containers")] regex_cgroup_docker: self.regex_cgroup_docker.clone(), @@ -426,7 +343,6 @@ impl ProcessTracker { ProcessTracker { procs: vec![], max_records_per_process, - #[cfg(target_os = "windows")] sysinfo: System::new_all(), #[cfg(feature = "containers")] regex_cgroup_docker, @@ -1067,9 +983,29 @@ pub fn current_system_time_since_epoch() -> Duration { .unwrap() } -#[cfg(all(test, target_os = "linux"))] mod tests { + use crate::sensors::Topology; + use super::*; + + #[test] + fn process_cmdline() { + // find the cmdline of current proc thanks to sysinfo + // do the same with processtracker + // assert + let mut system = System::new(); + system.refresh_all(); + let self_pid_by_sysinfo = get_current_pid(); + let self_process_by_sysinfo = system.process(self_pid_by_sysinfo.unwrap()).unwrap(); + + let mut topo = Topology::new(); + topo.refresh(); + let self_process_by_scaph = IProcess::myself().unwrap(); + + assert_eq!(self_process_by_sysinfo.cmd().concat(), topo.proc_tracker.get_process_cmdline(self_process_by_scaph.pid).unwrap()); + } + + #[cfg(all(test, target_os = "linux"))] #[test] fn process_records_added() { let proc = Process::myself().unwrap(); @@ -1086,6 +1022,7 @@ mod tests { assert_eq!(tracker.procs[0].len(), 3); } + #[cfg(all(test, target_os = "linux"))] #[test] fn process_records_cleaned() { let proc = Process::myself().unwrap(); From b5c9449e76ebf5855acc4e1fea30a35b09b5a62e Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 13 Mar 2023 12:15:03 +0100 Subject: [PATCH 014/303] style: fmt --- src/sensors/utils.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/sensors/utils.rs b/src/sensors/utils.rs index 9d149b16..ca6e402a 100644 --- a/src/sensors/utils.rs +++ b/src/sensors/utils.rs @@ -1,12 +1,12 @@ +use ordered_float::*; #[cfg(target_os = "linux")] use procfs::{self, process::Process}; use regex::Regex; #[cfg(feature = "containers")] use std::collections::HashMap; -use sysinfo::{get_current_pid, Process as SysinfoProcess, ProcessExt, System, SystemExt}; -use ordered_float::*; use std::path::PathBuf; use std::time::{Duration, SystemTime}; +use sysinfo::{get_current_pid, Process as SysinfoProcess, ProcessExt, System, SystemExt}; #[cfg(all(target_os = "linux", feature = "containers"))] use {docker_sync::container::Container, k8s_sync::Pod}; @@ -1002,7 +1002,12 @@ mod tests { topo.refresh(); let self_process_by_scaph = IProcess::myself().unwrap(); - assert_eq!(self_process_by_sysinfo.cmd().concat(), topo.proc_tracker.get_process_cmdline(self_process_by_scaph.pid).unwrap()); + assert_eq!( + self_process_by_sysinfo.cmd().concat(), + topo.proc_tracker + .get_process_cmdline(self_process_by_scaph.pid) + .unwrap() + ); } #[cfg(all(test, target_os = "linux"))] From a39b3d11f23aa09fb5d9e241b9aa9552ff1ce39d Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 13 Mar 2023 12:18:27 +0100 Subject: [PATCH 015/303] ci: enforce unit tests --- .github/workflows/build-and-test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 5941c39f..0126a17e 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -65,7 +65,7 @@ jobs: fmt_and_clippy_windows: name: Cargo Fmt and Clippy - Windows runs-on: windows-latest - needs: fmt_and_clippy_linux + needs: check_pr_is_on_the_right_branch steps: - name: Checkout uses: actions/checkout@v2 @@ -90,7 +90,6 @@ jobs: test_linux_x86_64: name: Test on GNU/Linux x86_64 (Bare metal worker) runs-on: ubuntu-latest - needs: fmt_and_clippy_linux steps: - name: Install dependencies (awxkit) uses: actions/setup-python@v2 @@ -120,7 +119,9 @@ jobs: build_linux_x86_64: name: Build on GNU/Linux x86_64 (Bare metal worker) runs-on: ubuntu-latest - needs: test_linux_x86_64 + needs: + - fmt_and_clippy_linux + - test_linux_x86_64 steps: - name: Install dependencies (awxkit) uses: actions/setup-python@v2 @@ -146,7 +147,6 @@ jobs: test_windows_x86_64: name: Test on Windows x86_64 (Virtual machine worker) runs-on: "windows-2019" - needs: fmt_and_clippy_windows steps: - name: Checkout uses: actions/checkout@v2 From b18aaee67f4e5124128e84ed63823a62edba5f11 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 13 Mar 2023 18:30:37 +0100 Subject: [PATCH 016/303] chore: replaced procfs with sysinfo as a default --- Cargo.lock | 158 +++++++---- Cargo.toml | 5 +- src/exporters/json.rs | 5 +- src/exporters/mod.rs | 46 +--- src/exporters/qemu.rs | 24 +- src/exporters/stdout.rs | 2 +- src/exporters/warpten.rs | 16 +- src/sensors/mod.rs | 153 ++++------- src/sensors/powercap_rapl.rs | 2 +- src/sensors/units.rs | 10 +- src/sensors/utils.rs | 513 ++++++++++++----------------------- 11 files changed, 369 insertions(+), 565 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f9939664..549b73a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,9 +59,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base-x" @@ -700,9 +700,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.112" +version = "0.2.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" [[package]] name = "libnghttp2-sys" @@ -734,10 +734,11 @@ checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" [[package]] name = "lock_api" -version = "0.4.2" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ + "autocfg", "scopeguard", ] @@ -806,33 +807,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.7.13" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", - "miow", - "ntapi 0.3.6", - "winapi", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", -] - -[[package]] -name = "ntapi" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" -dependencies = [ - "winapi", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.45.0", ] [[package]] @@ -929,27 +911,25 @@ checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" [[package]] name = "parking_lot" -version = "0.11.1" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ - "instant", "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" -version = "0.8.3" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", - "instant", "libc", "redox_syscall", "smallvec", - "winapi", + "windows-sys 0.45.0", ] [[package]] @@ -1168,9 +1148,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.5" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] @@ -1441,9 +1421,9 @@ checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" [[package]] name = "socket2" -version = "0.4.2" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", @@ -1545,7 +1525,7 @@ dependencies = [ "cfg-if", "core-foundation-sys", "libc", - "ntapi 0.4.0", + "ntapi", "once_cell", "rayon", "winapi", @@ -1640,9 +1620,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.11.0" +version = "1.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4efe6fc2395938c8155973d7be49fe8d03a843726e285e100a8a383cc0154ce" +checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" dependencies = [ "autocfg", "bytes", @@ -1650,19 +1630,19 @@ dependencies = [ "memchr", "mio", "num_cpus", - "once_cell", "parking_lot", "pin-project-lite", "signal-hook-registry", + "socket2", "tokio-macros", - "winapi", + "windows-sys 0.45.0", ] [[package]] name = "tokio-macros" -version = "1.3.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54473be61f4ebe4efd09cec9bd5d16fa51d70ea0192213d754d2d500457db110" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", @@ -1844,6 +1824,12 @@ version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +[[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.72" @@ -1964,7 +1950,7 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebbc80318ebf919219a113c41deae34aa90198e4a15e93c810a9ea1aaa4c1a78" dependencies = [ - "windows-sys", + "windows-sys 0.27.0", ] [[package]] @@ -1973,43 +1959,109 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cae116ee11e4bce7c0a0425f2b0c866a91d86d209624b7707a7deea52da786" dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", + "windows_aarch64_msvc 0.27.0", + "windows_i686_gnu 0.27.0", + "windows_i686_msvc 0.27.0", + "windows_x86_64_gnu 0.27.0", + "windows_x86_64_msvc 0.27.0", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_msvc" version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7d1649bbab232cde71148c6ef7bbe647f214d2154dd66347fada60de40cda7" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_i686_gnu" version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4eb20b59b93fc302839f3b0df3e61de7e9606b44cb54cbeb68d71cf137309fa" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_msvc" version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40331d8ef3e4dcdc8982eb7de16e1f09b86f5384626a56b3a99c2a51b88ff98e" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_x86_64_gnu" version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5937d290e39c3308147d9b877c5fa741c50f4121ea78d2d20c4a138ad365464a" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_msvc" version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dee1b76aec4e2bead4758a181b663c37af0de7ec56fe6837c10215b8d6a1635f" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/Cargo.toml b/Cargo.toml index 8d5a4ded..1cf6fc80 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ chrono = "0.4.19" docker-sync = { version = "0.1.2", optional = true } k8s-sync = { version = "0.2.3", optional = true } hyper = { version = "0.14", features = ["full"], optional = true } -tokio = { version = "1", features = ["full"], optional = true} +tokio = { version = "1.26.0", features = ["full"], optional = true} sysinfo = { version = "0.28.2"} [target.'cfg(target_os="linux")'.dependencies] @@ -39,11 +39,10 @@ procfs = { version = "0.12.0" } [target.'cfg(target_os="windows")'.dependencies] windows = { version = "0.27.0", features = ["alloc","Win32_Storage_FileSystem","Win32_Foundation","Win32_Security","Win32_System_IO","Win32_System_Ioctl"]} - [features] default = ["prometheus", "riemann", "warpten", "json", "containers"] prometheus = ["hyper", "tokio"] riemann = ["riemann_client"] json = ["serde", "serde_json"] containers = ["docker-sync", "k8s-sync"] -warpten = ["warp10"] +warpten = ["warp10"] \ No newline at end of file diff --git a/src/exporters/json.rs b/src/exporters/json.rs index 3fbeef1f..6da0d452 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -220,12 +220,11 @@ impl JSONExporter { .iter() .find(|x| { x.name == "scaph_process_power_consumption_microwatts" - && process.pid - == x.attributes.get("pid").unwrap().parse::().unwrap() + && &process.pid.to_string() == x.attributes.get("pid").unwrap() }) .map(|metric| Consumer { exe: PathBuf::from(metric.attributes.get("exe").unwrap()), - pid: process.pid, + pid: process.pid.to_string().parse::().unwrap(), consumption: format!("{}", metric.metric_value).parse::().unwrap(), timestamp: metric.timestamp.as_secs_f64(), container: match parameters.is_present("containers") { diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index 23ee8eb7..bf1a2c98 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -14,7 +14,7 @@ pub mod utils; #[cfg(feature = "warpten")] pub mod warpten; use crate::sensors::{ - utils::{current_system_time_since_epoch, page_size, IProcess}, + utils::{current_system_time_since_epoch, IProcess}, RecordGenerator, Topology, }; use chrono::Utc; @@ -225,9 +225,6 @@ impl MetricGenerator { /// Generate all scaphandre internal metrics. fn gen_self_metrics(&mut self) { - #[cfg(target_os = "linux")] - let myself = IProcess::myself().unwrap(); - #[cfg(target_os = "windows")] let myself = IProcess::myself(self.topology.get_proc_tracker()).unwrap(); let default_timestamp = current_system_time_since_epoch(); @@ -244,10 +241,7 @@ impl MetricGenerator { metric_value: MetricValueType::Text(get_scaphandre_version()), }); - if let Some(metric_value) = self - .topology - .get_process_cpu_consumption_percentage(myself.pid) - { + if let Some(metric_value) = self.topology.get_process_cpu_usage_percentage(myself.pid) { self.data.push(Metric { name: String::from("scaph_self_cpu_usage_percent"), metric_type: String::from("gauge"), @@ -257,17 +251,16 @@ impl MetricGenerator { state: String::from("ok"), tags: vec!["scaphandre".to_string()], attributes: HashMap::new(), - description: String::from("CPU % consumed by scaphandre."), + description: format!("CPU time consumed by scaphandre, as {}", metric_value.unit), metric_value: MetricValueType::FloatDouble( metric_value.value.parse::().unwrap(), ), }); } - if let Ok(metric_value) = myself.statm() { - let value = metric_value.size * page_size().unwrap() as u64; + if let Some(metric_value) = self.topology.get_process_virtual_memory_bytes(myself.pid) { self.data.push(Metric { - name: String::from("scaph_self_mem_total_program_size"), + name: String::from("scaph_self_memory_virtual_bytes"), metric_type: String::from("gauge"), ttl: 60.0, timestamp: default_timestamp, @@ -275,13 +268,16 @@ impl MetricGenerator { state: String::from("ok"), tags: vec!["scaphandre".to_string()], attributes: HashMap::new(), - description: String::from("Total program size, measured in bytes."), - metric_value: MetricValueType::IntUnsigned(value), + description: format!("Total program size, measured in {}.", metric_value.unit), + metric_value: MetricValueType::IntUnsigned( + metric_value.value.parse::().unwrap(), + ), }); + } - let value = metric_value.resident * page_size().unwrap() as u64; + if let Some(metric_value) = self.topology.get_process_memory_bytes(myself.pid) { self.data.push(Metric { - name: String::from("scaph_self_mem_resident_set_size"), + name: String::from("scaph_self_memory_bytes"), metric_type: String::from("gauge"), ttl: 60.0, hostname: self.hostname.clone(), @@ -290,23 +286,9 @@ impl MetricGenerator { tags: vec!["scaphandre".to_string()], attributes: HashMap::new(), description: String::from("Resident set size, measured in bytes."), - metric_value: MetricValueType::IntUnsigned(value), - }); - - let value = metric_value.shared * page_size().unwrap() as u64; - self.data.push(Metric { - name: String::from("scaph_self_mem_shared_resident_size"), - metric_type: String::from("gauge"), - ttl: 60.0, - timestamp: default_timestamp, - hostname: self.hostname.clone(), - state: String::from("ok"), - tags: vec!["scaphandre".to_string()], - attributes: HashMap::new(), - description: String::from( - "Number of resident shared bytes (i.e., backed by a file).", + metric_value: MetricValueType::IntUnsigned( + metric_value.value.parse::().unwrap(), ), - metric_value: MetricValueType::IntUnsigned(value), }); } diff --git a/src/exporters/qemu.rs b/src/exporters/qemu.rs index f8e5e8a0..e442c685 100644 --- a/src/exporters/qemu.rs +++ b/src/exporters/qemu.rs @@ -69,9 +69,10 @@ impl QemuExporter { let last = qp.first().unwrap(); let previous = qp.get(1).unwrap(); let vm_name = QemuExporter::get_vm_name_from_cmdline( - &last.process.original.cmdline().unwrap(), + &last.process.cmdline(proc_tracker).unwrap(), ); - let time_pdiff = last.total_time_jiffies() - previous.total_time_jiffies(); + let time_pdiff = last.process.total_time_jiffies(proc_tracker) + - previous.process.total_time_jiffies(proc_tracker); if let Some(time_tdiff) = &topo_stat_diff { let first_domain_path = format!("{path}/{vm_name}/intel-rapl:0:0"); if fs::read_dir(&first_domain_path).is_err() { @@ -137,15 +138,18 @@ impl QemuExporter { for vecp in processes.iter() { if !vecp.is_empty() { if let Some(pr) = vecp.get(0) { - if let Ok(cmdline) = pr.process.original.cmdline() { - if let Some(res) = cmdline.iter().find(|x| x.contains("qemu-system")) { - debug!("Found a process with {}", res); - let mut tmp: Vec = vec![]; - for p in vecp.iter() { - tmp.push(p.clone()); - } - qemu_processes.push(tmp); + if let Some(res) = pr + .process + .cmdline + .iter() + .find(|x| x.contains("qemu-system")) + { + debug!("Found a process with {}", res); + let mut tmp: Vec = vec![]; + for p in vecp.iter() { + tmp.push(p.clone()); } + qemu_processes.push(tmp); } } } diff --git a/src/exporters/stdout.rs b/src/exporters/stdout.rs index c466d6a3..46c9e83b 100644 --- a/src/exporters/stdout.rs +++ b/src/exporters/stdout.rs @@ -260,7 +260,7 @@ impl StdoutExporter { if let Some(process) = metrics.iter().find(|x| { if x.name == "scaph_process_power_consumption_microwatts" { let pid = x.attributes.get("pid").unwrap(); - pid.parse::().unwrap() == c.0.pid + pid == &c.0.pid.to_string() } else { false } diff --git a/src/exporters/warpten.rs b/src/exporters/warpten.rs index 4ebf4ce2..717c652b 100644 --- a/src/exporters/warpten.rs +++ b/src/exporters/warpten.rs @@ -1,5 +1,5 @@ use crate::exporters::*; -use crate::sensors::{RecordGenerator, Sensor, Topology}; +use crate::sensors::{utils::IProcess, RecordGenerator, Sensor, Topology}; use clap::Arg; use std::time::Duration; use std::{env, thread}; @@ -153,10 +153,9 @@ impl Warp10Exporter { warp10::Value::Double(scaphandre_version.parse::().unwrap()), )]; - if let Some(metric_value) = self - .topology - .get_process_cpu_consumption_percentage(procfs::process::Process::myself().unwrap().pid) - { + if let Some(metric_value) = self.topology.get_process_cpu_usage_percentage( + IProcess::myself(&self.topology.proc_tracker).unwrap().pid, + ) { data.push(warp10::Data::new( time::OffsetDateTime::now_utc(), None, @@ -166,10 +165,9 @@ impl Warp10Exporter { )); } - if let Some(metric_value) = self - .topology - .get_process_cpu_consumption_percentage(procfs::process::Process::myself().unwrap().pid) - { + if let Some(metric_value) = self.topology.get_process_cpu_usage_percentage( + IProcess::myself(&self.topology.proc_tracker).unwrap().pid, + ) { data.push(warp10::Data::new( time::OffsetDateTime::now_utc(), None, diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 00e370e7..077e0546 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -10,14 +10,13 @@ pub mod powercap_rapl; pub mod units; pub mod utils; #[cfg(target_os = "linux")] -use procfs::{process, CpuInfo, CpuTime, KernelStats}; +use procfs::{CpuInfo, CpuTime, KernelStats}; use std::collections::HashMap; use std::error::Error; use std::fmt; use std::mem::size_of_val; use std::time::Duration; -#[cfg(not(target_os = "linux"))] -use sysinfo::{ProcessorExt, System, SystemExt}; +use sysinfo::{Pid, SystemExt}; use utils::{current_system_time_since_epoch, IProcess, ProcessTracker}; // !!!!!!!!!!!!!!!!! Sensor !!!!!!!!!!!!!!!!!!!!!!! @@ -58,10 +57,8 @@ pub struct Topology { pub buffer_max_kbytes: u16, /// Sorted list of all domains names pub domains_names: Option>, - /// - #[cfg(target_os = "windows")] - #[allow(dead_code)] - sensor_data: HashMap, + /// Sensor-specific data needed in the topology + _sensor_data: HashMap, } impl RecordGenerator for Topology { @@ -141,19 +138,14 @@ impl RecordGenerator for Topology { impl Default for Topology { fn default() -> Self { - #[cfg(target_os = "windows")] { Self::new(HashMap::new()) } - - #[cfg(target_os = "linux")] - Self::new() } } impl Topology { /// Instanciates Topology and returns the instance - #[cfg(target_os = "windows")] pub fn new(sensor_data: HashMap) -> Topology { Topology { sockets: vec![], @@ -162,19 +154,7 @@ impl Topology { record_buffer: vec![], buffer_max_kbytes: 1, domains_names: None, - sensor_data, - } - } - /// Instanciates Topology and returns the instance - #[cfg(target_os = "linux")] - pub fn new() -> Topology { - Topology { - sockets: vec![], - proc_tracker: ProcessTracker::new(5), - stat_buffer: vec![], - record_buffer: vec![], - buffer_max_kbytes: 1, - domains_names: None, + _sensor_data: sensor_data, } } @@ -353,27 +333,6 @@ impl Topology { /// Gets currently running processes (as procfs::Process instances) and stores /// them in self.proc_tracker fn refresh_procs(&mut self) { - #[cfg(target_os = "linux")] - { - //current_procs is the up to date list of processus running on the host - if let Ok(procs) = process::all_processes() { - info!("Before refresh procs init."); - procs - .iter() - .map(IProcess::from_linux_process) - .for_each(|p| { - let pid = p.pid; - let res = self.proc_tracker.add_process_record(p); - match res { - Ok(_) => {} - Err(msg) => { - panic!("Failed to track process with pid {} !\nGot: {}", pid, msg) - } - } - }); - } - } - #[cfg(target_os = "windows")] { let pt = &mut self.proc_tracker; pt.sysinfo.refresh_processes(); @@ -382,7 +341,7 @@ impl Topology { .sysinfo .processes() .values() - .map(IProcess::from_windows_process) + .map(IProcess::new) .collect::>(); for p in current_procs { match pt.add_process_record(p) { @@ -594,50 +553,22 @@ impl Topology { } /// Returns the power consumed between last and previous measurement for a given process ID, in microwatts - pub fn get_process_power_consumption_microwatts(&self, pid: i32) -> Option { + pub fn get_process_power_consumption_microwatts(&self, pid: Pid) -> Option { let tracker = self.get_proc_tracker(); if let Some(recs) = tracker.find_records(pid) { if recs.len() > 1 { - #[cfg(target_os = "linux")] - { - let last = recs.first().unwrap(); - let previous = recs.get(1).unwrap(); - if let Some(topo_stats_diff) = self.get_stats_diff() { - //trace!("Topology stats measured diff: {:?}", topo_stats_diff); - let process_total_time = - last.total_time_jiffies() - previous.total_time_jiffies(); - let topo_total_time = topo_stats_diff.total_time_jiffies(); - let usage_percent = process_total_time as f64 / topo_total_time as f64; - let topo_conso = self.get_records_diff_power_microwatts(); - if let Some(val) = &topo_conso { - //trace!("topo conso: {}", val); - let val_f64 = val.value.parse::().unwrap(); - //trace!("val f64: {}", val_f64); - let result = (val_f64 * usage_percent) as u64; - //trace!("result: {}", result); - return Some(Record::new( - last.timestamp, - result.to_string(), - units::Unit::MicroWatt, - )); - } - } - } - #[cfg(target_os = "windows")] - { - let last = recs.first().unwrap(); - let process_cpu_percentage = - tracker.get_cpu_usage_percentage(pid as usize, tracker.nb_cores); - let topo_conso = self.get_records_diff_power_microwatts(); - if let Some(conso) = &topo_conso { - let conso_f64 = conso.value.parse::().unwrap(); - let result = (conso_f64 * process_cpu_percentage as f64) / 100.0_f64; - return Some(Record::new( - last.timestamp, - result.to_string(), - units::Unit::MicroWatt, - )); - } + let last = recs.first().unwrap(); + let process_cpu_percentage = + tracker.get_cpu_usage_percentage(pid, tracker.nb_cores); + let topo_conso = self.get_records_diff_power_microwatts(); + if let Some(conso) = &topo_conso { + let conso_f64 = conso.value.parse::().unwrap(); + let result = (conso_f64 * process_cpu_percentage as f64) / 100.0_f64; + return Some(Record::new( + last.timestamp, + result.to_string(), + units::Unit::MicroWatt, + )); } } } else { @@ -646,27 +577,37 @@ impl Topology { None } - pub fn get_process_cpu_consumption_percentage(&self, pid: i32) -> Option { - let tracker = self.get_proc_tracker(); - if let Some(recs) = tracker.find_records(pid) { - if recs.len() > 1 { - let last = recs.first().unwrap(); - let previous = recs.get(1).unwrap(); - if let Some(topo_stats_diff) = self.get_stats_diff() { - let process_total_time = - last.total_time_jiffies() - previous.total_time_jiffies(); + // Per process metrics, from ProcessRecord during last refresh, returned in Record structs - let topo_total_time = topo_stats_diff.total_time_jiffies(); + pub fn get_process_cpu_usage_percentage(&self, pid: Pid) -> Option { + if let Some(record) = self.get_proc_tracker().get_process_last_record(pid) { + return Some(Record::new( + record.timestamp, + record.process.cpu_usage_percentage.to_string(), + units::Unit::Percentage, + )); + } + None + } - let usage = process_total_time as f64 / topo_total_time as f64; + pub fn get_process_virtual_memory_bytes(&self, pid: Pid) -> Option { + if let Some(record) = self.get_proc_tracker().get_process_last_record(pid) { + return Some(Record::new( + record.timestamp, + record.process.virtual_memory.to_string(), + units::Unit::Bytes, + )); + } + None + } - return Some(Record::new( - current_system_time_since_epoch(), - usage.to_string(), - units::Unit::Percentage, - )); - } - } + pub fn get_process_memory_bytes(&self, pid: Pid) -> Option { + if let Some(record) = self.get_proc_tracker().get_process_last_record(pid) { + return Some(Record::new( + record.timestamp, + record.process.memory.to_string(), + units::Unit::Bytes, + )); } None } diff --git a/src/sensors/powercap_rapl.rs b/src/sensors/powercap_rapl.rs index 4cab2836..d1a1ed44 100644 --- a/src/sensors/powercap_rapl.rs +++ b/src/sensors/powercap_rapl.rs @@ -108,7 +108,7 @@ impl Sensor for PowercapRAPLSensor { if modules_state.is_err() && !self.virtual_machine { warn!("Couldn't find intel_rapl modules."); } - let mut topo = Topology::new(); + let mut topo = Topology::new(HashMap::new()); let re_socket = Regex::new(r"^.*/intel-rapl:\d+$").unwrap(); let re_domain = Regex::new(r"^.*/intel-rapl:\d+:\d+$").unwrap(); let mut re_domain_matched = false; diff --git a/src/sensors/units.rs b/src/sensors/units.rs index e0274f35..e208aac6 100644 --- a/src/sensors/units.rs +++ b/src/sensors/units.rs @@ -12,6 +12,10 @@ pub enum Unit { MilliWatt, MicroWatt, Percentage, + Bytes, + KiloBytes, + MegaBytes, + GigaBytes, } impl Unit { @@ -35,7 +39,7 @@ impl Unit { } else if let (Some(pos_source), Some(pos_dest)) = (pos_source_power, pos_dest_power) { Ok(measure * Unit::get_mult(pos_source, pos_dest)) } else { - panic!("Impossible conversion asked from energy value to power value (without time dimension)."); + panic!("Unimplemented or impossible conversion (if asked from energy value to power value without time dimension)."); } } @@ -63,6 +67,10 @@ impl fmt::Display for Unit { Unit::KiloWatt => write!(f, "KiloWatts"), Unit::MegaWatt => write!(f, "MegaWatts"), Unit::Percentage => write!(f, "Percentage"), + Unit::Bytes => write!(f, "Bytes"), + Unit::KiloBytes => write!(f, "KiloBytes"), + Unit::MegaBytes => write!(f, "MegaBytes"), + Unit::GigaBytes => write!(f, "GigaBytes"), } } } diff --git a/src/sensors/utils.rs b/src/sensors/utils.rs index ca6e402a..8b800f90 100644 --- a/src/sensors/utils.rs +++ b/src/sensors/utils.rs @@ -1,12 +1,12 @@ use ordered_float::*; #[cfg(target_os = "linux")] -use procfs::{self, process::Process}; +use procfs; use regex::Regex; -#[cfg(feature = "containers")] use std::collections::HashMap; +use std::io::{Error, ErrorKind}; use std::path::PathBuf; use std::time::{Duration, SystemTime}; -use sysinfo::{get_current_pid, Process as SysinfoProcess, ProcessExt, System, SystemExt}; +use sysinfo::{get_current_pid, CpuExt, Pid, Process, ProcessExt, System, SystemExt}; #[cfg(all(target_os = "linux", feature = "containers"))] use {docker_sync::container::Container, k8s_sync::Pod}; @@ -52,76 +52,6 @@ pub struct IStat { pub exit_code: Option, } -impl IStat { - #[cfg(target_os = "linux")] - fn from_procfs_stat(stat: &procfs::process::Stat) -> IStat { - IStat { - blocked: stat.blocked, - cguest_time: stat.cguest_time, - comm: stat.comm.clone(), - cstime: stat.cstime, - cutime: stat.cutime, - delayacct_blkio_ticks: stat.delayacct_blkio_ticks, - end_data: stat.end_data, - exit_code: stat.exit_code, - exit_signal: stat.exit_signal, - flags: stat.flags, - guest_time: stat.guest_time, - itrealvalue: stat.itrealvalue, - nice: stat.nice, - num_threads: stat.num_threads, - pgrp: stat.pgrp, - pid: stat.pid, - ppid: stat.ppid, - processor: stat.processor, - session: stat.session, - signal: stat.signal, - start_data: stat.start_data, - starttime: stat.starttime, - state: stat.state, - stime: stat.stime, - tpgid: stat.tpgid, - tty_nr: stat.tty_nr, - utime: stat.utime, - vsize: stat.vsize, - } - } - - #[cfg(target_os = "windows")] - fn from_windows_process_stat(_process: &Process) -> IStat { - IStat { - blocked: 0, - cguest_time: Some(0), - comm: String::from("Not implemented yet !"), - cstime: 0, - cutime: 0, - delayacct_blkio_ticks: Some(0), - end_data: Some(0), - exit_code: Some(0), - exit_signal: Some(0), - flags: 0, - guest_time: Some(0), - itrealvalue: 0, - nice: 0, - num_threads: 0, - pgrp: 0, - pid: 0, - ppid: 0, - processor: Some(0), - session: 0, - signal: 0, - start_data: Some(0), - starttime: 0, - state: 'X', - stime: 0, - tpgid: 0, - tty_nr: 0, - utime: 0, - vsize: 0, - } - } -} - #[derive(Clone)] pub struct IStatus { pub name: String, @@ -133,96 +63,68 @@ pub struct IStatus { #[derive(Debug, Clone)] pub struct IProcess { - pub pid: i32, + pub pid: Pid, pub owner: u32, pub comm: String, pub cmdline: Vec, - pub stat: Option, - //pub root: Option, + //CPU (all of them) time usage, as a percentage + pub cpu_usage_percentage: f32, + // Virtual memory used by the process (at the time the struct is created), in bytes + pub virtual_memory: u64, + // Memory consumed by the process (at the time the struct is created), in bytes + pub memory: u64, #[cfg(target_os = "linux")] - pub original: Process, + pub stime: u64, + #[cfg(target_os = "linux")] + pub utime: u64, } impl IProcess { - #[cfg(target_os = "linux")] - pub fn from_linux_process(process: &Process) -> IProcess { - //let root = process.root(); - let mut cmdline = vec![String::from("")]; - if let Ok(raw_cmdline) = process.cmdline() { - cmdline = raw_cmdline; - } - IProcess { - pid: process.pid, - owner: process.owner, - original: process.clone(), - comm: process.stat.comm.clone(), - cmdline, - stat: Some(IStat::from_procfs_stat(&process.stat)), + pub fn new(process: &Process) -> IProcess { + let mut stime = 0; + let mut utime = 0; + #[cfg(target_os = "linux")] + { + if let Ok(procfs_process) = + procfs::process::Process::new(process.pid().to_string().parse::().unwrap()) + { + if let Ok(stat) = procfs_process.stat() { + stime += stat.stime; + utime += stat.utime; + } + } } - } - #[cfg(target_os = "windows")] - pub fn from_windows_process(process: &Process) -> IProcess { IProcess { - pid: process.pid() as i32, + pid: process.pid(), owner: 0, comm: String::from(process.exe().to_str().unwrap()), cmdline: process.cmd().to_vec(), - stat: Some(IStat::from_windows_process_stat(process)), + cpu_usage_percentage: process.cpu_usage(), + memory: process.memory(), + virtual_memory: process.virtual_memory(), + #[cfg(target_os = "linux")] + stime, + #[cfg(target_os = "linux")] + utime, } } - #[cfg(target_os = "linux")] - pub fn cmdline(&self) -> Result, String> { - if let Ok(cmdline) = self.original.cmdline() { - Ok(cmdline) - } else { - Err(String::from("cmdline() was none")) - } - } - #[cfg(target_os = "windows")] - pub fn cmdline(&self, proc_tracker: &ProcessTracker) -> Result, String> { - if let Some(p) = proc_tracker.sysinfo.process(self.pid as usize) { + /// Returns the command line of related to the process, as found by sysinfo. + pub fn cmdline(&self, proc_tracker: &ProcessTracker) -> Result, Error> { + if let Some(p) = proc_tracker.sysinfo.process(self.pid) { Ok(p.cmd().to_vec()) } else { - Err(String::from("Failed to get original process.")) + Err(Error::new( + ErrorKind::Other, + "Failed to get original process.", + )) } } - pub fn statm(&self) -> Result { - #[cfg(target_os = "linux")] - { - let mystatm = self.original.statm().unwrap(); - Ok(IStatM { - size: mystatm.size, - data: mystatm.data, - dt: mystatm.dt, - lib: mystatm.lib, - resident: mystatm.resident, - shared: mystatm.shared, - text: mystatm.text, - }) - } - #[cfg(target_os = "windows")] - Ok(IStatM { - size: 42, - data: 42, - dt: 42, - lib: 42, - resident: 42, - shared: 42, - text: 42, - }) - } - - #[cfg(target_os = "linux")] - pub fn exe(&self) -> Result { - let original_exe = self.original.exe().unwrap(); - Ok(original_exe) - } - #[cfg(target_os = "windows")] + /// Returns the executable string related to the process pub fn exe(&self, proc_tracker: &ProcessTracker) -> Result { - if let Some(p) = proc_tracker.sysinfo.process(self.pid as usize) { + if let Some(p) = proc_tracker.sysinfo.process(self.pid) { Ok(PathBuf::from(p.exe().to_str().unwrap())) } else { Err(String::from("Couldn't get process.")) @@ -230,22 +132,21 @@ impl IProcess { } pub fn status(&self) -> Result { - #[cfg(target_os = "linux")] - { - if let Ok(original_status) = self.original.status() { - let status = IStatus { - name: original_status.name, - pid: original_status.pid, - ppid: original_status.ppid, - state: original_status.state, - umask: original_status.umask, - }; - Ok(status) - } else { - Err(format!("Couldn't get status for {}", self.pid)) - } - } - #[cfg(target_os = "windows")] + //#[cfg(target_os = "linux")] + //{ + // if let Ok(original_status) = self.original.status() { + // let status = IStatus { + // name: original_status.name, + // pid: original_status.pid, + // ppid: original_status.ppid, + // state: original_status.state, + // umask: original_status.umask, + // }; + // Ok(status) + // } else { + // Err(format!("Couldn't get status for {}", self.pid)) + // } + //} { Ok(IStatus { name: String::from("Not implemented yet !"), @@ -258,18 +159,24 @@ impl IProcess { } #[cfg(target_os = "linux")] - pub fn myself() -> Result { - Ok(IProcess::from_linux_process(&Process::myself().unwrap())) + pub fn total_time_jiffies(&self, proc_tracker: &ProcessTracker) -> u64 { + if let Some(rec) = proc_tracker.get_process_last_record(self.pid) { + return rec.process.stime + rec.process.utime; + } + 0 } - #[cfg(target_os = "windows")] + pub fn myself(proc_tracker: &ProcessTracker) -> Result { - Ok(IProcess::from_windows_process( + Ok(IProcess::new( proc_tracker .sysinfo - .process(get_current_pid().unwrap() as usize) + .process(get_current_pid().unwrap()) .unwrap(), )) } + + #[cfg(target_os = "linux")] + pub fn cgroups() {} } pub fn page_size() -> Result { @@ -340,20 +247,21 @@ impl ProcessTracker { #[cfg(feature = "containers")] let regex_cgroup_containerd = Regex::new("/system.slice/containerd.service/.*$").unwrap(); + let mut system = System::new_all(); + system.refresh_cpu(); + let nb_cores = system.cpus().len(); + ProcessTracker { procs: vec![], max_records_per_process, - sysinfo: System::new_all(), + sysinfo: system, #[cfg(feature = "containers")] regex_cgroup_docker, #[cfg(feature = "containers")] regex_cgroup_kubernetes, #[cfg(feature = "containers")] regex_cgroup_containerd, - #[cfg(target_os = "windows")] - nb_cores: System::new_all().processors().len(), - #[cfg(target_os = "linux")] - nb_cores: 0, // TODO implement + nb_cores, } } @@ -399,6 +307,15 @@ impl ProcessTracker { Ok(String::from("Successfully added record to process.")) } + pub fn get_process_last_record(&self, pid: Pid) -> Option<&ProcessRecord> { + if let Some(records) = self.find_records(pid) { + if let Some(last) = records.first() { + return Some(last); + } + } + None + } + /// Removes as many ProcessRecords as needed from the vector (passed as a mutable ref in parameters) /// in order for the vector length to match self.max_records_per_process. fn clean_old_process_records(records: &mut Vec, max_records_per_process: u16) { @@ -418,7 +335,7 @@ impl ProcessTracker { /// Returns a Some(ref to vector of ProcessRecords) if the pid is found /// in self.procs. Returns None otherwise. - pub fn find_records(&self, pid: i32) -> Option<&Vec> { + pub fn find_records(&self, pid: Pid) -> Option<&Vec> { let mut refer = None; for v in &self.procs { if !v.is_empty() && v[0].process.pid == pid { @@ -433,30 +350,30 @@ impl ProcessTracker { /// Returns the result of the substraction of utime between last and /// previous ProcessRecord for a given pid. - pub fn get_diff_utime(&self, pid: i32) -> Option { - let records = self.find_records(pid).unwrap(); - if records.len() > 1 { - if let Some(previous) = &records[0].process.stat { - if let Some(current) = &records[1].process.stat { - return Some(previous.utime - current.utime); - } - } - } - None - } + //pub fn get_diff_utime(&self, pid: Pid) -> Option { + // let records = self.find_records(pid).unwrap(); + // if records.len() > 1 { + // if let Some(previous) = &records[0].process.stat { + // if let Some(current) = &records[1].process.stat { + // return Some(previous.utime - current.utime); + // } + // } + // } + // None + //} /// Returns the result of the substraction of stime between last and /// previous ProcessRecord for a given pid. - pub fn get_diff_stime(&self, pid: i32) -> Option { - let records = self.find_records(pid).unwrap(); - if records.len() > 1 { - if let Some(previous) = &records[0].process.stat { - if let Some(current) = &records[1].process.stat { - return Some(previous.stime - current.stime); - } - } - } - None - } + //pub fn get_diff_stime(&self, pid: Pid) -> Option { + // let records = self.find_records(pid).unwrap(); + // if records.len() > 1 { + // if let Some(previous) = &records[0].process.stat { + // if let Some(current) = &records[1].process.stat { + // return Some(previous.stime - current.stime); + // } + // } + // } + // None + //} /// Returns all vectors of process records linked to a running, sleeping, waiting or zombie process. /// (Not terminated) @@ -464,21 +381,20 @@ impl ProcessTracker { debug!("In get alive processes."); let mut res = vec![]; for p in self.procs.iter() { - #[cfg(target_os = "linux")] - if !p.is_empty() { - let status = p[0].process.status(); - if let Ok(status_val) = status { - if !&status_val.state.contains('T') { - // !&status_val.state.contains("Z") && - res.push(p); - } - } - } - #[cfg(target_os = "windows")] + //#[cfg(target_os = "linux")] + //if !p.is_empty() { + // let status = p[0].process.status(); + // if let Ok(status_val) = status { + // if !&status_val.state.contains('T') { + // // !&status_val.state.contains("Z") && + // res.push(p); + // } + // } + //} if !p.is_empty() { //TODO implement // clippy will ask you to remove mut from res, but you just need to implement to fix that - if let Some(_sysinfo_p) = self.sysinfo.process(p[0].process.pid as usize) { + if let Some(_sysinfo_p) = self.sysinfo.process(p[0].process.pid) { //let status = sysinfo_p.status(); //if status != ProcessStatus::Dead {//&& status != ProcessStatus::Stop { res.push(p); @@ -515,7 +431,7 @@ impl ProcessTracker { #[cfg(feature = "containers")] pub fn get_process_container_description( &self, - pid: i32, // the PID of the process to look for + pid: Pid, // the PID of the process to look for containers: &[Container], docker_version: String, pods: &[Pod], @@ -528,9 +444,11 @@ impl ProcessTracker { let process = result.next().unwrap(); let mut description = HashMap::new(); let regex_clean_container_id = Regex::new("[[:alnum:]]{12,}").unwrap(); - if let Some(p) = process.get(0) { + if let Some(_p) = process.get(0) { // if we have the cgroups data from the original process struct - if let Ok(cgroups) = p.process.original.cgroups() { + let procfs_process = + procfs::process::Process::new(pid.to_string().parse::().unwrap()).unwrap(); + if let Ok(cgroups) = procfs_process.cgroups() { let mut found = false; for cg in &cgroups { if found { @@ -663,7 +581,7 @@ impl ProcessTracker { } /// Returns a vector containing pids of all running, sleeping or waiting current processes. - pub fn get_alive_pids(&self) -> Vec { + pub fn get_alive_pids(&self) -> Vec { self.get_alive_processes() .iter() .filter(|x| !x.is_empty()) @@ -672,7 +590,7 @@ impl ProcessTracker { } /// Returns a vector containing pids of all processes being tracked. - pub fn get_all_pids(&self) -> Vec { + pub fn get_all_pids(&self) -> Vec { self.procs .iter() .filter(|x| !x.is_empty()) @@ -681,7 +599,7 @@ impl ProcessTracker { } /// Returns the process name associated to a PID - pub fn get_process_name(&self, pid: i32) -> String { + pub fn get_process_name(&self, pid: Pid) -> String { let mut result = self .procs .iter() @@ -695,17 +613,14 @@ impl ProcessTracker { } /// Returns the cmdline string associated to a PID - pub fn get_process_cmdline(&self, pid: i32) -> Option { + pub fn get_process_cmdline(&self, pid: Pid) -> Option { let mut result = self .procs .iter() .filter(|x| !x.is_empty() && x.get(0).unwrap().process.pid == pid); let process = result.next().unwrap(); if let Some(p) = process.get(0) { - #[cfg(target_os = "windows")] let cmdline_request = p.process.cmdline(self); - #[cfg(target_os = "linux")] - let cmdline_request = p.process.cmdline(); if let Ok(mut cmdline_vec) = cmdline_request { let mut cmdline = String::from(""); while !cmdline_vec.is_empty() { @@ -721,24 +636,8 @@ impl ProcessTracker { None } - #[cfg(target_os = "linux")] - /// Returns the CPU time consumed between two measure iteration - fn get_cpu_time_consumed(&self, p: &[ProcessRecord]) -> u64 { - let last_time = p.first().unwrap().total_time_jiffies(); - let previous_time = p.get(1).unwrap().total_time_jiffies(); - let mut diff = 0; - if previous_time <= last_time { - diff = last_time - previous_time; - } - diff - } - - #[cfg(target_os = "windows")] - pub fn get_cpu_usage_percentage(&self, pid: usize, nb_cores: usize) -> f32 { - let mut cpu_current_usage = 0.0; - for c in self.sysinfo.processors() { - cpu_current_usage += c.cpu_usage(); - } + pub fn get_cpu_usage_percentage(&self, pid: Pid, nb_cores: usize) -> f32 { + let cpu_current_usage = self.sysinfo.global_cpu_info().cpu_usage(); if let Some(p) = self.sysinfo.process(pid) { (p.cpu_usage() + (100.0 - cpu_current_usage / nb_cores as f32) * p.cpu_usage() / 100.0) / nb_cores as f32 @@ -752,51 +651,29 @@ impl ProcessTracker { let mut consumers: Vec<(IProcess, OrderedFloat)> = vec![]; for p in &self.procs { if p.len() > 1 { - #[cfg(target_os = "linux")] + let diff = self + .get_cpu_usage_percentage(p.first().unwrap().process.pid as _, self.nb_cores); + if consumers + .iter() + .filter(|x| { + if let Some(p) = self.sysinfo.process(x.0.pid as _) { + return p.cpu_usage() > diff; + } + false + }) + .count() + < top as usize { - let diff = self.get_cpu_time_consumed(p); - if consumers - .iter() - .filter(|x| ProcessRecord::new(x.0.to_owned()).total_time_jiffies() > diff) - .count() - < top as usize - { - consumers - .push((p.last().unwrap().process.clone(), OrderedFloat(diff as f64))); + let pid = p.first().unwrap().process.pid; + if let Some(sysinfo_process) = self.sysinfo.process(pid as _) { + let new_consumer = IProcess::new(sysinfo_process); + consumers.push((new_consumer, OrderedFloat(diff as f64))); consumers.sort_by(|x, y| y.1.cmp(&x.1)); if consumers.len() > top as usize { consumers.pop(); } - } - } - #[cfg(target_os = "windows")] - { - let diff = self.get_cpu_usage_percentage( - p.first().unwrap().process.pid as _, - self.nb_cores, - ); - if consumers - .iter() - .filter(|x| { - if let Some(p) = self.sysinfo.process(x.0.pid as _) { - return p.cpu_usage() > diff; - } - false - }) - .count() - < top as usize - { - let pid = p.first().unwrap().process.pid; - if let Some(sysinfo_process) = self.sysinfo.process(pid as _) { - let new_consumer = IProcess::from_windows_process(sysinfo_process); - consumers.push((new_consumer, OrderedFloat(diff as f64))); - consumers.sort_by(|x, y| y.1.cmp(&x.1)); - if consumers.len() > top as usize { - consumers.pop(); - } - } else { - warn!("Couldn't get process info for {}", pid); - } + } else { + warn!("Couldn't get process info for {}", pid); } } } @@ -813,28 +690,12 @@ impl ProcessTracker { let mut consumers: Vec<(IProcess, OrderedFloat)> = vec![]; for p in &self.procs { if p.len() > 1 { - #[cfg(target_os = "linux")] - { - let diff = self.get_cpu_time_consumed(p); - let process_exe = p.last().unwrap().process.exe().unwrap_or_default(); - if regex_filter.is_match(process_exe.to_str().unwrap_or_default()) { - consumers - .push((p.last().unwrap().process.clone(), OrderedFloat(diff as f64))); - consumers.sort_by(|x, y| y.1.cmp(&x.1)); - } - } - #[cfg(target_os = "windows")] - { - let diff = self.get_cpu_usage_percentage( - p.first().unwrap().process.pid as _, - self.nb_cores, - ); - let process_exe = p.last().unwrap().process.exe(self).unwrap_or_default(); - if regex_filter.is_match(process_exe.to_str().unwrap_or_default()) { - consumers - .push((p.last().unwrap().process.clone(), OrderedFloat(diff as f64))); - consumers.sort_by(|x, y| y.1.cmp(&x.1)); - } + let diff = self + .get_cpu_usage_percentage(p.first().unwrap().process.pid as _, self.nb_cores); + let process_exe = p.last().unwrap().process.exe(self).unwrap_or_default(); + if regex_filter.is_match(process_exe.to_str().unwrap_or_default()) { + consumers.push((p.last().unwrap().process.clone(), OrderedFloat(diff as f64))); + consumers.sort_by(|x, y| y.1.cmp(&x.1)); } } } @@ -945,35 +806,6 @@ impl ProcessRecord { timestamp: current_system_time_since_epoch(), } } - - // Returns the total CPU time consumed by this process since its creation - pub fn total_time_jiffies(&self) -> u64 { - #[cfg(target_os = "linux")] - if let Some(stat) = &self.process.stat { - trace!( - "ProcessRecord: stime {} utime {}", //cutime {} cstime {} guest_time {} cguest_time {} delayacct_blkio_ticks {} itrealvalue {}", - stat.stime, - stat.utime //, cutime, cstime, guest_time, cguest_time, delayacct_blkio_ticks, itrealvalue - ); - return stat.stime + stat.utime; - } else { - warn!("No IStat !"); - } - - //#[cfg(target_os="windows")] - //let usage = &self.sysinfo. - //let cutime = self.process.stat.cutime as u64; - //let cstime = self.process.stat.cstime as u64; - //let guest_time = self.process.stat.guest_time.unwrap_or_default(); - //let cguest_time = self.process.stat.cguest_time.unwrap_or_default() as u64; - //let delayacct_blkio_ticks = self.process.stat.delayacct_blkio_ticks.unwrap_or_default(); - //let itrealvalue = self.process.stat.itrealvalue as u64; - - // not including cstime and cutime in total as they are reported only when child dies - // child metrics as already reported as the child processes are in the global process - // list, found as /proc/PID/stat - 0 //+ guest_time + cguest_time + delayacct_blkio_ticks + itrealvalue - } } /// Returns a Duration instance with the current timestamp @@ -984,12 +816,11 @@ pub fn current_system_time_since_epoch() -> Duration { } mod tests { - use crate::sensors::Topology; - - use super::*; #[test] fn process_cmdline() { + use super::*; + use crate::sensors::Topology; // find the cmdline of current proc thanks to sysinfo // do the same with processtracker // assert @@ -998,9 +829,9 @@ mod tests { let self_pid_by_sysinfo = get_current_pid(); let self_process_by_sysinfo = system.process(self_pid_by_sysinfo.unwrap()).unwrap(); - let mut topo = Topology::new(); + let mut topo = Topology::new(HashMap::new()); topo.refresh(); - let self_process_by_scaph = IProcess::myself().unwrap(); + let self_process_by_scaph = IProcess::myself(&topo.proc_tracker).unwrap(); assert_eq!( self_process_by_sysinfo.cmd().concat(), @@ -1013,15 +844,14 @@ mod tests { #[cfg(all(test, target_os = "linux"))] #[test] fn process_records_added() { - let proc = Process::myself().unwrap(); + use super::*; + use crate::sensors::Topology; + let mut topo = Topology::new(HashMap::new()); + topo.refresh(); + let proc = IProcess::myself(&topo.proc_tracker).unwrap(); let mut tracker = ProcessTracker::new(3); for _ in 0..3 { - assert_eq!( - tracker - .add_process_record(IProcess::from_linux_process(&proc)) - .is_ok(), - true - ); + assert_eq!(tracker.add_process_record(proc.clone()).is_ok(), true); } assert_eq!(tracker.procs.len(), 1); assert_eq!(tracker.procs[0].len(), 3); @@ -1030,25 +860,16 @@ mod tests { #[cfg(all(test, target_os = "linux"))] #[test] fn process_records_cleaned() { - let proc = Process::myself().unwrap(); + use super::*; let mut tracker = ProcessTracker::new(3); + let proc = IProcess::myself(&tracker).unwrap(); for _ in 0..5 { - assert_eq!( - tracker - .add_process_record(IProcess::from_linux_process(&proc)) - .is_ok(), - true - ); + assert_eq!(tracker.add_process_record(proc.clone()).is_ok(), true); } assert_eq!(tracker.procs.len(), 1); assert_eq!(tracker.procs[0].len(), 3); for _ in 0..15 { - assert_eq!( - tracker - .add_process_record(IProcess::from_linux_process(&proc)) - .is_ok(), - true - ); + assert_eq!(tracker.add_process_record(proc.clone()).is_ok(), true); } assert_eq!(tracker.procs.len(), 1); assert_eq!(tracker.procs[0].len(), 3); From a2b5d32ced0f1f3b66583174fb0e45e1b5d8d316 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 13 Mar 2023 18:54:22 +0100 Subject: [PATCH 017/303] fix: doctest --- src/sensors/utils.rs | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/sensors/utils.rs b/src/sensors/utils.rs index 8b800f90..0d1c6f88 100644 --- a/src/sensors/utils.rs +++ b/src/sensors/utils.rs @@ -85,7 +85,7 @@ impl IProcess { let mut utime = 0; #[cfg(target_os = "linux")] { - if let Ok(procfs_process) = + if let Ok(procfs_process) = procfs::process::Process::new(process.pid().to_string().parse::().unwrap()) { if let Ok(stat) = procfs_process.stat() { @@ -270,14 +270,26 @@ impl ProcessTracker { /// states during all the lifecycle of the exporter. /// # Linux Example: /// ``` - /// use procfs::process::Process; /// use scaphandre::sensors::utils::{ProcessTracker, IProcess}; - /// let mut tracker = ProcessTracker::new(5); - /// let pid = 1; - /// if let Ok(result) = tracker.add_process_record( - /// IProcess::from_linux_process(&Process::new(pid).unwrap()) - /// ){ - /// println!("ProcessRecord stored successfully: {}", result); + /// use scaphandre::sensors::Topology; + /// use std::collections::HashMap; + /// use sysinfo::SystemExt; + /// let mut pt = ProcessTracker::new(5); + /// pt.sysinfo.refresh_processes(); + /// pt.sysinfo.refresh_cpu(); + /// let current_procs = pt + /// .sysinfo + /// .processes() + /// .values() + /// .map(IProcess::new) + /// .collect::>(); + /// for p in current_procs { + /// match pt.add_process_record(p) { + /// Ok(result) => { println!("ProcessRecord stored successfully: {}", result); } + /// Err(msg) => { + /// panic!("Failed to track process !\nGot: {}", msg) + /// } + /// } /// } /// ``` pub fn add_process_record(&mut self, process: IProcess) -> Result { From e5f63935e861d34d7302848c0660ec47128963cc Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 13 Mar 2023 19:13:16 +0100 Subject: [PATCH 018/303] docs: edited reference for memory metrics --- docs_src/references/exporter-prometheus.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs_src/references/exporter-prometheus.md b/docs_src/references/exporter-prometheus.md index 5ca8e77a..ceca1d59 100644 --- a/docs_src/references/exporter-prometheus.md +++ b/docs_src/references/exporter-prometheus.md @@ -52,11 +52,9 @@ And some more deep metrics that you may want if you need to make more complex ca If you hack scaph or just want to investigate its behavior, you may be interested in some internal metrics: -- `scaph_self_mem_total_program_size`: Total program size, measured in pages +- `scaph_self_memory_bytes`: Scaphandre memory usage, in bytes -- `scaph_self_mem_resident_set_size`: Resident set size, measured in pages - -- `scaph_self_mem_shared_resident_size`: Number of resident shared pages (i.e., backed by a file) +- `scaph_self_memory_virtual_bytes`: Scaphandre virtual memory usage, in bytes - `scaph_self_topo_stats_nb`: Number of CPUStat traces stored for the host From 71fba22ee22dc1b4bc63e59af029b1b9717f8e0e Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 13 Mar 2023 19:21:45 +0100 Subject: [PATCH 019/303] style: fmt --- src/sensors/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sensors/utils.rs b/src/sensors/utils.rs index 0d1c6f88..4b5b7d55 100644 --- a/src/sensors/utils.rs +++ b/src/sensors/utils.rs @@ -85,7 +85,7 @@ impl IProcess { let mut utime = 0; #[cfg(target_os = "linux")] { - if let Ok(procfs_process) = + if let Ok(procfs_process) = procfs::process::Process::new(process.pid().to_string().parse::().unwrap()) { if let Ok(stat) = procfs_process.stat() { From f778d953326cab9d9da1075aad1f62f51a30eae6 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 13 Mar 2023 19:24:55 +0100 Subject: [PATCH 020/303] fixed imports fr windows --- src/sensors/mod.rs | 3 ++- src/sensors/utils.rs | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 077e0546..4bc62f73 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -16,7 +16,8 @@ use std::error::Error; use std::fmt; use std::mem::size_of_val; use std::time::Duration; -use sysinfo::{Pid, SystemExt}; +#[allow(unused_imports)] +use sysinfo::{System, Pid, SystemExt}; use utils::{current_system_time_since_epoch, IProcess, ProcessTracker}; // !!!!!!!!!!!!!!!!! Sensor !!!!!!!!!!!!!!!!!!!!!!! diff --git a/src/sensors/utils.rs b/src/sensors/utils.rs index 4b5b7d55..1360cc87 100644 --- a/src/sensors/utils.rs +++ b/src/sensors/utils.rs @@ -2,6 +2,7 @@ use ordered_float::*; #[cfg(target_os = "linux")] use procfs; use regex::Regex; +#[allow(unused_imports)] use std::collections::HashMap; use std::io::{Error, ErrorKind}; use std::path::PathBuf; From 70ab56de24fb3bac910be71468040cb9bb126347 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 13 Mar 2023 19:26:23 +0100 Subject: [PATCH 021/303] style: fmt --- src/sensors/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 4bc62f73..85b9b6f2 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -17,7 +17,7 @@ use std::fmt; use std::mem::size_of_val; use std::time::Duration; #[allow(unused_imports)] -use sysinfo::{System, Pid, SystemExt}; +use sysinfo::{Pid, System, SystemExt}; use utils::{current_system_time_since_epoch, IProcess, ProcessTracker}; // !!!!!!!!!!!!!!!!! Sensor !!!!!!!!!!!!!!!!!!!!!!! From 6e5f0746a32668b21b15de7ed3f484b702824816 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 13 Mar 2023 19:27:29 +0100 Subject: [PATCH 022/303] ci: disabled bloat test --- .github/{workflows => }/bloat-test.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{workflows => }/bloat-test.yml (100%) diff --git a/.github/workflows/bloat-test.yml b/.github/bloat-test.yml similarity index 100% rename from .github/workflows/bloat-test.yml rename to .github/bloat-test.yml From efa208edc07b73d0e6d39c9eeb4352e7ba546ac6 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 13 Mar 2023 19:37:12 +0100 Subject: [PATCH 023/303] fix: updated sysinfo last version changes --- src/sensors/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 85b9b6f2..332f9605 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -191,7 +191,7 @@ impl Topology { { warn!("generate_cpu_info is not implemented yet on this OS."); let sysinfo_system = System::new_all(); - let sysinfo_cores = sysinfo_system.processors(); + let sysinfo_cores = sysinfo_system.cpus(); for (id, c) in (0_u16..).zip(sysinfo_cores.iter()) { let mut info = HashMap::new(); info.insert(String::from("frequency"), c.frequency().to_string()); From 782fc4f0d784d651df0de0360afc1d3f4ab65350 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 13 Mar 2023 19:55:38 +0100 Subject: [PATCH 024/303] fix: fixing imports for generate_cpu_cores windows --- src/sensors/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 332f9605..6b88cab9 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -17,7 +17,7 @@ use std::fmt; use std::mem::size_of_val; use std::time::Duration; #[allow(unused_imports)] -use sysinfo::{Pid, System, SystemExt}; +use sysinfo::{Pid, System, SystemExt, CpuExt}; use utils::{current_system_time_since_epoch, IProcess, ProcessTracker}; // !!!!!!!!!!!!!!!!! Sensor !!!!!!!!!!!!!!!!!!!!!!! @@ -186,6 +186,7 @@ impl Topology { } cores.push(CPUCore::new(id as u16, info)); } + } #[cfg(target_os = "windows")] { @@ -193,7 +194,7 @@ impl Topology { let sysinfo_system = System::new_all(); let sysinfo_cores = sysinfo_system.cpus(); for (id, c) in (0_u16..).zip(sysinfo_cores.iter()) { - let mut info = HashMap::new(); + let mut info = HashMap::::new(); info.insert(String::from("frequency"), c.frequency().to_string()); info.insert(String::from("name"), c.name().to_string()); info.insert(String::from("vendor_id"), c.vendor_id().to_string()); From 17dc41feacb0f00b64bdebeaba6645aa7c99aaa9 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 13 Mar 2023 19:56:00 +0100 Subject: [PATCH 025/303] style: fmt --- src/sensors/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 6b88cab9..34327284 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -17,7 +17,7 @@ use std::fmt; use std::mem::size_of_val; use std::time::Duration; #[allow(unused_imports)] -use sysinfo::{Pid, System, SystemExt, CpuExt}; +use sysinfo::{CpuExt, Pid, System, SystemExt}; use utils::{current_system_time_since_epoch, IProcess, ProcessTracker}; // !!!!!!!!!!!!!!!!! Sensor !!!!!!!!!!!!!!!!!!!!!!! @@ -186,7 +186,6 @@ impl Topology { } cores.push(CPUCore::new(id as u16, info)); } - } #[cfg(target_os = "windows")] { From 5b7bddae8d2e10ffafafd48ecb8779db33272d91 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 13 Mar 2023 20:08:15 +0100 Subject: [PATCH 026/303] fix: integration test not working on windows --- tests/integration.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/integration.rs b/tests/integration.rs index a3dd4c2e..3f23f51a 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1,8 +1,11 @@ +#[cfg(target_os = "linux")] use scaphandre::exporters::qemu::QemuExporter; +#[cfg(target_os = "linux")] use scaphandre::sensors::powercap_rapl::PowercapRAPLSensor; use std::env::current_dir; use std::fs::{create_dir, read_dir}; +#[cfg(target_os = "linux")] #[test] fn exporter_qemu() { let sensor = PowercapRAPLSensor::new(1, 1, false); From 6c73d3c8e9dc79c23ea15be8a6a78f9e6e603901 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 13 Mar 2023 20:30:11 +0100 Subject: [PATCH 027/303] fix: unit tests for cpu cores on windows --- src/sensors/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 34327284..e036125c 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -1197,7 +1197,7 @@ mod tests { cores[0].attributes.len() ); for c in &cores { - println!("{:?}", c.attributes.get("processor")); + println!("{:?}", c.attributes); } assert_eq!(!cores.is_empty(), true); for c in &cores { From 61545fa10a6f19eb6a497f3676e2fc4dfcbd1a39 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 13 Mar 2023 20:41:00 +0100 Subject: [PATCH 028/303] fix: test condition --- src/sensors/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index e036125c..115a6c1d 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -1201,7 +1201,7 @@ mod tests { } assert_eq!(!cores.is_empty(), true); for c in &cores { - assert_eq!(c.attributes.len() > 5, true); + assert_eq!(c.attributes.len() > 3, true); } } From dcbc536c135ef2b5dbf9b947eddd5d5f6decef82 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 14 Mar 2023 11:35:03 +0100 Subject: [PATCH 029/303] fix: troubleshooting prometheus exporter --- docker-compose/docker-compose-dev.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose/docker-compose-dev.yaml b/docker-compose/docker-compose-dev.yaml index 3e70a730..a5ada54f 100644 --- a/docker-compose/docker-compose-dev.yaml +++ b/docker-compose/docker-compose-dev.yaml @@ -16,7 +16,7 @@ services: - type: bind source: /var/run/docker.sock target: /var/run/docker.sock - command: ["-v", "prometheus", "--containers"] + command: ["-vv", "prometheus", "--containers"] networks: - scaphandre-network From bd4b63d3060debb758712a4f80918f8405c6849b Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 14 Mar 2023 11:35:16 +0100 Subject: [PATCH 030/303] fix: troubleshooting prometheus exporter --- src/exporters/prometheus.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/exporters/prometheus.rs b/src/exporters/prometheus.rs index 1cd2db12..863b563a 100644 --- a/src/exporters/prometheus.rs +++ b/src/exporters/prometheus.rs @@ -230,10 +230,12 @@ async fn show_metrics( trace!("{}", req.uri()); let mut body = String::new(); if req.uri().path() == format!("/{}", &suffix) { - trace!("in metrics !"); + debug!("in metrics !"); let now = current_system_time_since_epoch(); let mut last_request = context.last_request.lock().unwrap(); + debug!("stored last request !"); let mut metric_generator = context.metric_generator.lock().unwrap(); + debug!("stored metric generator !"); if now - (*last_request) > Duration::from_secs(2) { { info!( @@ -247,6 +249,7 @@ async fn show_metrics( metric_generator.topology.refresh(); } } + debug!("refreshed topology !"); *last_request = now; info!("{}: Refresh data", Utc::now().format("%Y-%m-%dT%H:%M:%S")); @@ -255,6 +258,7 @@ async fn show_metrics( let mut metrics_pushed: Vec = vec![]; + debug!("popping metrics !"); // Send all data for msg in metric_generator.pop_metrics() { let mut attributes: Option<&HashMap> = None; From d43116e6cdbcff1813b68eb14254963ed7e5b53b Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 14 Mar 2023 11:38:12 +0100 Subject: [PATCH 031/303] ci: removed useless steps in codesee workflow --- .github/workflows/codesee-arch-diagram.yml | 46 +++++++++++----------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/codesee-arch-diagram.yml b/.github/workflows/codesee-arch-diagram.yml index 1ec93fd3..5b345f05 100644 --- a/.github/workflows/codesee-arch-diagram.yml +++ b/.github/workflows/codesee-arch-diagram.yml @@ -26,33 +26,33 @@ jobs: id: detect-languages uses: Codesee-io/codesee-detect-languages-action@latest - - name: Configure JDK 16 - uses: actions/setup-java@v2 - if: ${{ fromJSON(steps.detect-languages.outputs.languages).java }} - with: - java-version: '16' - distribution: 'zulu' + #- name: Configure JDK 16 + # uses: actions/setup-java@v2 + # if: ${{ fromJSON(steps.detect-languages.outputs.languages).java }} + # with: + # java-version: '16' + # distribution: 'zulu' - # CodeSee Maps Go support uses a static binary so there's no setup step required. + ## CodeSee Maps Go support uses a static binary so there's no setup step required. - - name: Configure Node.js 14 - uses: actions/setup-node@v2 - if: ${{ fromJSON(steps.detect-languages.outputs.languages).javascript }} - with: - node-version: '14' + #- name: Configure Node.js 14 + # uses: actions/setup-node@v2 + # if: ${{ fromJSON(steps.detect-languages.outputs.languages).javascript }} + # with: + # node-version: '14' - - name: Configure Python 3.x - uses: actions/setup-python@v2 - if: ${{ fromJSON(steps.detect-languages.outputs.languages).python }} - with: - python-version: '3.10' - architecture: 'x64' + #- name: Configure Python 3.x + # uses: actions/setup-python@v2 + # if: ${{ fromJSON(steps.detect-languages.outputs.languages).python }} + # with: + # python-version: '3.10' + # architecture: 'x64' - - name: Configure Ruby '3.x' - uses: ruby/setup-ruby@v1 - if: ${{ fromJSON(steps.detect-languages.outputs.languages).ruby }} - with: - ruby-version: '3.0' + #- name: Configure Ruby '3.x' + # uses: ruby/setup-ruby@v1 + # if: ${{ fromJSON(steps.detect-languages.outputs.languages).ruby }} + # with: + # ruby-version: '3.0' # We need the rust toolchain because it uses rustc and cargo to inspect the package - name: Configure Rust 1.x stable From e96bd696276a171e050ade91837d74a945e74857 Mon Sep 17 00:00:00 2001 From: bpetit Date: Tue, 14 Mar 2023 12:13:49 +0100 Subject: [PATCH 032/303] fix: changed doctest to it works for both windows and linux --- src/sensors/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 115a6c1d..4b76d0e3 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -169,7 +169,7 @@ impl Topology { /// if let Some(cores) = Topology::generate_cpu_cores() { /// println!("There are {} cores on this host.", cores.len()); /// for c in &cores { - /// println!("Here is CPU Core number {}", c.attributes.get("processor").unwrap()); + /// println!("CPU info {:?}", c.attributes); /// } /// } /// ``` From e3478b327320033d6e0442daa6686f3b85618417 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 14 Mar 2023 13:25:17 +0100 Subject: [PATCH 033/303] fix: troubleshooting prometheus exporter --- docker-compose/docker-compose-dev.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose/docker-compose-dev.yaml b/docker-compose/docker-compose-dev.yaml index a5ada54f..f3c39592 100644 --- a/docker-compose/docker-compose-dev.yaml +++ b/docker-compose/docker-compose-dev.yaml @@ -16,7 +16,7 @@ services: - type: bind source: /var/run/docker.sock target: /var/run/docker.sock - command: ["-vv", "prometheus", "--containers"] + command: ["-vvv", "prometheus", "--containers"] networks: - scaphandre-network From ae415b789058fab11aeeb401d40b895cd61bf9a4 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 14 Mar 2023 14:18:28 +0100 Subject: [PATCH 034/303] feat: adding process_cpu_usage_percentage --- src/exporters/mod.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index bf1a2c98..3e2b947e 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -737,11 +737,27 @@ impl MetricGenerator { hostname: self.hostname.clone(), state: String::from("ok"), tags: vec!["scaphandre".to_string()], - attributes, + attributes: attributes.clone(), description: String::from("Power consumption due to the process, measured on at the topology level, in microwatts"), metric_value: MetricValueType::Text(power.value), }); } + + let metric_name = String::from("scaph_process_cpu_usage_percentage"); + if let Some(metric_value) = self.topology.get_process_cpu_usage_percentage(pid) { + self.data.push(Metric { + name: metric_name, + metric_type: String::from("gauge"), + ttl: 60.0, + timestamp: metric_value.timestamp, + hostname: self.hostname.clone(), + state: String::from("ok"), + tags: vec!["scaphandre".to_string()], + attributes, + description: String::from("CPU time consumed by the process, as a percentage of the capacity of all the CPU Cores"), + metric_value: MetricValueType::Text(metric_value.value) + }); + } } } From 1dcb9c7a63adbcc2ff2a43d64b98c73fe72d4ca3 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 14 Mar 2023 15:08:18 +0100 Subject: [PATCH 035/303] fix: synchronized cpu usage percentage calculation --- src/sensors/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 4b76d0e3..e9d61085 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -560,11 +560,11 @@ impl Topology { if recs.len() > 1 { let last = recs.first().unwrap(); let process_cpu_percentage = - tracker.get_cpu_usage_percentage(pid, tracker.nb_cores); + self.get_process_cpu_usage_percentage(pid).unwrap(); let topo_conso = self.get_records_diff_power_microwatts(); if let Some(conso) = &topo_conso { let conso_f64 = conso.value.parse::().unwrap(); - let result = (conso_f64 * process_cpu_percentage as f64) / 100.0_f64; + let result = (conso_f64 * process_cpu_percentage.value.parse::().unwrap()) / 100.0_f64; return Some(Record::new( last.timestamp, result.to_string(), From a526a2b4c63e2d06aba964c71b8e78c4703743be Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 14 Mar 2023 15:08:44 +0100 Subject: [PATCH 036/303] fix: troubleshooting prometheus exporter --- src/exporters/prometheus.rs | 102 +++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 48 deletions(-) diff --git a/src/exporters/prometheus.rs b/src/exporters/prometheus.rs index 863b563a..46eefd3c 100644 --- a/src/exporters/prometheus.rs +++ b/src/exporters/prometheus.rs @@ -234,62 +234,68 @@ async fn show_metrics( let now = current_system_time_since_epoch(); let mut last_request = context.last_request.lock().unwrap(); debug!("stored last request !"); - let mut metric_generator = context.metric_generator.lock().unwrap(); - debug!("stored metric generator !"); - if now - (*last_request) > Duration::from_secs(2) { - { - info!( - "{}: Refresh topology", - Utc::now().format("%Y-%m-%dT%H:%M:%S") - ); - metric_generator - .topology - .proc_tracker - .clean_terminated_process_records_vectors(); - metric_generator.topology.refresh(); - } - } - debug!("refreshed topology !"); - *last_request = now; + match context.metric_generator.lock() { + Ok(mut metric_generator) => { + debug!("stored metric generator !"); + if now - (*last_request) > Duration::from_secs(2) { + { + info!( + "{}: Refresh topology", + Utc::now().format("%Y-%m-%dT%H:%M:%S") + ); + metric_generator + .topology + .proc_tracker + .clean_terminated_process_records_vectors(); + metric_generator.topology.refresh(); + } + } + debug!("refreshed topology !"); + *last_request = now; - info!("{}: Refresh data", Utc::now().format("%Y-%m-%dT%H:%M:%S")); + info!("{}: Refresh data", Utc::now().format("%Y-%m-%dT%H:%M:%S")); - metric_generator.gen_all_metrics(); + metric_generator.gen_all_metrics(); - let mut metrics_pushed: Vec = vec![]; + let mut metrics_pushed: Vec = vec![]; - debug!("popping metrics !"); - // Send all data - for msg in metric_generator.pop_metrics() { - let mut attributes: Option<&HashMap> = None; - if !msg.attributes.is_empty() { - attributes = Some(&msg.attributes); - } + debug!("popping metrics !"); + // Send all data + for msg in metric_generator.pop_metrics() { + let mut attributes: Option<&HashMap> = None; + if !msg.attributes.is_empty() { + attributes = Some(&msg.attributes); + } - let value = match msg.metric_value { - // MetricValueType::IntSigned(value) => event.set_metric_sint64(value), - // MetricValueType::Float(value) => event.set_metric_f(value), - MetricValueType::FloatDouble(value) => value.to_string(), - MetricValueType::IntUnsigned(value) => value.to_string(), - MetricValueType::Text(ref value) => value.to_string(), - }; + let value = match msg.metric_value { + // MetricValueType::IntSigned(value) => event.set_metric_sint64(value), + // MetricValueType::Float(value) => event.set_metric_f(value), + MetricValueType::FloatDouble(value) => value.to_string(), + MetricValueType::IntUnsigned(value) => value.to_string(), + MetricValueType::Text(ref value) => value.to_string(), + }; - let mut should_i_add_help = true; + let mut should_i_add_help = true; - if metrics_pushed.contains(&msg.name) { - should_i_add_help = false; - } else { - metrics_pushed.insert(0, msg.name.clone()); - } + if metrics_pushed.contains(&msg.name) { + should_i_add_help = false; + } else { + metrics_pushed.insert(0, msg.name.clone()); + } - body = push_metric( - body, - msg.description.clone(), - msg.metric_type.clone(), - msg.name.clone(), - format_metric(&msg.name, &value, attributes), - should_i_add_help, - ); + body = push_metric( + body, + msg.description.clone(), + msg.metric_type.clone(), + msg.name.clone(), + format_metric(&msg.name, &value, attributes), + should_i_add_help, + ); + } + }, + Err(e) => { + panic!("Error from lock(): {}", e); + } } } else { let _ = write!(body, "Scaphandre's prometheus exporter here. Metrics available on /{suffix}"); From a68777848e233092a080ae2d4cc4d275bfd2dee4 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 14 Mar 2023 15:11:29 +0100 Subject: [PATCH 037/303] style: fmt --- src/exporters/prometheus.rs | 2 +- src/sensors/mod.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/exporters/prometheus.rs b/src/exporters/prometheus.rs index 46eefd3c..00ec1a34 100644 --- a/src/exporters/prometheus.rs +++ b/src/exporters/prometheus.rs @@ -292,7 +292,7 @@ async fn show_metrics( should_i_add_help, ); } - }, + } Err(e) => { panic!("Error from lock(): {}", e); } diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index e9d61085..e29f6533 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -559,12 +559,12 @@ impl Topology { if let Some(recs) = tracker.find_records(pid) { if recs.len() > 1 { let last = recs.first().unwrap(); - let process_cpu_percentage = - self.get_process_cpu_usage_percentage(pid).unwrap(); + let process_cpu_percentage = self.get_process_cpu_usage_percentage(pid).unwrap(); let topo_conso = self.get_records_diff_power_microwatts(); if let Some(conso) = &topo_conso { let conso_f64 = conso.value.parse::().unwrap(); - let result = (conso_f64 * process_cpu_percentage.value.parse::().unwrap()) / 100.0_f64; + let result = (conso_f64 * process_cpu_percentage.value.parse::().unwrap()) + / 100.0_f64; return Some(Record::new( last.timestamp, result.to_string(), From 3b264e62379185e8d6d9077ad9a49dbdf90660b7 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 14 Mar 2023 18:09:11 +0100 Subject: [PATCH 038/303] chore: better dashboard for sample stack --- .../dashboards/sample-dashboard.json | 283 +++++++++++++++--- docker-compose/sample.jsonnet | 61 +++- 2 files changed, 297 insertions(+), 47 deletions(-) diff --git a/docker-compose/dashboards/sample-dashboard.json b/docker-compose/dashboards/sample-dashboard.json index 5b62fba5..1b5a78f0 100644 --- a/docker-compose/dashboards/sample-dashboard.json +++ b/docker-compose/dashboards/sample-dashboard.json @@ -227,7 +227,7 @@ "repeat": null, "seriesOverrides": [ ], "spaceLength": 10, - "span": 6, + "span": 4, "stack": false, "steppedLine": false, "targets": [ @@ -274,6 +274,175 @@ "show": true } ] + }, + { + "aliasColors": { }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${PROMETHEUS_DS}", + "fill": 1, + "fillGradient": 0, + "gridPos": { }, + "id": 5, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "sideWidth": null, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [ ], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": null, + "seriesOverrides": [ ], + "spaceLength": 10, + "span": 4, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "scaph_self_cpu_usage_percent", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{__name__}}", + "refId": "A" + } + ], + "thresholds": [ ], + "timeFrom": null, + "timeShift": null, + "title": "scaph_self_cpu", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [ ] + }, + "yaxes": [ + { + "format": "%", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "%", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + } + ] + }, + { + "aliasColors": { }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${PROMETHEUS_DS}", + "fill": 1, + "fillGradient": 0, + "gridPos": { }, + "id": 6, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "sideWidth": null, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [ ], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": null, + "seriesOverrides": [ ], + "spaceLength": 10, + "span": 4, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "scaph_self_memory_bytes", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{__name__}}", + "refId": "A" + }, + { + "expr": "scaph_self_memory_virtual_bytes", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{__name__}}", + "refId": "B" + } + ], + "thresholds": [ ], + "timeFrom": null, + "timeShift": null, + "title": "Socket power consumption", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [ ] + }, + "yaxes": [ + { + "format": "Bytes", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "Bytes", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + } + ] } ], "repeat": null, @@ -289,47 +458,85 @@ "collapsed": false, "panels": [ { + "aliasColors": { }, + "bars": false, + "dashLength": 10, + "dashes": false, "datasource": "${PROMETHEUS_DS}", - "fieldConfig": { - "defaults": { - "links": [ ], - "mappings": [ ], - "thresholds": { - "mode": "absolute", - "steps": [ ] - }, - "unit": "none" - } - }, + "fill": 1, + "fillGradient": 0, "gridPos": { }, - "id": 5, - "links": [ ], - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "mean" - ], - "fields": "", - "values": false - } + "id": 7, + "legend": { + "alignAsTable": true, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "sideWidth": "30%", + "total": false, + "values": false }, - "pluginVersion": "7", + "lines": true, + "linewidth": 1, + "links": [ ], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": null, + "seriesOverrides": [ ], + "spaceLength": 10, + "span": 6, + "stack": true, + "steppedLine": false, "targets": [ { - "expr": "sort_desc(topk(3, sum by (exe) (scaph_process_power_consumption_microwatts/1000000)))", + "expr": "scaph_process_power_consumption_microwatts{exe=~\".*${process_filter}.*\"}/1000000", "format": "time_series", "intervalFactor": 2, - "legendFormat": "{{exe}}", + "legendFormat": "{{ cmdline }}", "refId": "A" } ], - "title": "Top process consumers", - "transparent": false, - "type": "stat" + "thresholds": [ ], + "timeFrom": null, + "timeShift": null, + "title": "Filtered process (process_filter) power, by exe", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [ ] + }, + "yaxes": [ + { + "format": "W", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "W", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + } + ] }, { "aliasColors": { }, @@ -340,7 +547,7 @@ "fill": 1, "fillGradient": 0, "gridPos": { }, - "id": 6, + "id": 8, "legend": { "alignAsTable": true, "avg": false, @@ -364,12 +571,12 @@ "repeat": null, "seriesOverrides": [ ], "spaceLength": 10, - "span": 8, + "span": 6, "stack": true, "steppedLine": false, "targets": [ { - "expr": "scaph_process_power_consumption_microwatts{exe=~\".*${process_filter}.*\"}/1000000", + "expr": "scaph_process_cpu_usage_percentage{exe=~\".*${process_filter}.*\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "{{ cmdline }}", @@ -379,7 +586,7 @@ "thresholds": [ ], "timeFrom": null, "timeShift": null, - "title": "Filtered process (process_filter) power, by exe", + "title": "scaph_process_cpu", "tooltip": { "shared": true, "sort": 0, @@ -395,7 +602,7 @@ }, "yaxes": [ { - "format": "W", + "format": "%", "label": null, "logBase": 1, "max": null, @@ -403,7 +610,7 @@ "show": true }, { - "format": "W", + "format": "%", "label": null, "logBase": 1, "max": null, diff --git a/docker-compose/sample.jsonnet b/docker-compose/sample.jsonnet index 975a235b..f73322fa 100644 --- a/docker-compose/sample.jsonnet +++ b/docker-compose/sample.jsonnet @@ -70,7 +70,7 @@ dashboard.new( title='Socket power consumption', datasource='${PROMETHEUS_DS}', format='W', - span=6, + span=4, min=0 ) .addTarget( @@ -80,29 +80,72 @@ dashboard.new( ) ) ) + .addPanel( + grafana.graphPanel.new( + title='scaph_self_cpu', + datasource='${PROMETHEUS_DS}', + format='%', + span=4, + min=0 + ) + .addTarget( + grafana.prometheus.target( + 'scaph_self_cpu_usage_percent', + legendFormat='{{__name__}}', + ) + ) + ) + .addPanel( + grafana.graphPanel.new( + title='Socket power consumption', + datasource='${PROMETHEUS_DS}', + format='Bytes', + span=4, + min=0 + ) + .addTarget( + grafana.prometheus.target( + 'scaph_self_memory_bytes', + legendFormat='{{__name__}}', + ) + ) + .addTarget( + grafana.prometheus.target( + 'scaph_self_memory_virtual_bytes', + legendFormat='{{__name__}}', + ) + ) + ) ) .addRow( row.new( title='Per process', ) .addPanel( - grafana.statPanel.new( - title='Top process consumers', + grafana.graphPanel.new( + title='Filtered process (process_filter) power, by exe', datasource='${PROMETHEUS_DS}', + span=6, + format='W', + legend_rightSide=false, + legend_alignAsTable=true, + legend_sideWidth='30%', + stack=true, + min=0 ) .addTarget( grafana.prometheus.target( - 'sort_desc(topk(3, sum by (exe) (scaph_process_power_consumption_microwatts/1000000)))', - legendFormat='{{exe}}', + 'scaph_process_power_consumption_microwatts{exe=~".*${process_filter}.*"}/1000000', + legendFormat='{{ cmdline }}', ) ) ) .addPanel( grafana.graphPanel.new( - title='Filtered process (process_filter) power, by exe', + title='scaph_process_cpu', datasource='${PROMETHEUS_DS}', - span=8, - format='W', + span=6, + format='%', legend_rightSide=false, legend_alignAsTable=true, legend_sideWidth='30%', @@ -111,7 +154,7 @@ dashboard.new( ) .addTarget( grafana.prometheus.target( - 'scaph_process_power_consumption_microwatts{exe=~".*${process_filter}.*"}/1000000', + 'scaph_process_cpu_usage_percentage{exe=~".*${process_filter}.*"}', legendFormat='{{ cmdline }}', ) ) From c330b3aaa57c7ade3a8347a5038c1159f1020ea3 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 14 Mar 2023 18:09:37 +0100 Subject: [PATCH 039/303] fix: per process cpu usage percentage calculation --- src/sensors/mod.rs | 8 ++++++-- src/sensors/utils.rs | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index e29f6533..d2d24c7b 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -326,6 +326,11 @@ impl Topology { // //} } + let pt = &mut self.proc_tracker; + info!("REFRESH CPU"); + pt.sysinfo.refresh_cpu(); + pt.sysinfo.refresh_components(); + pt.sysinfo.refresh_memory(); self.refresh_procs(); self.refresh_record(); self.refresh_stats(); @@ -337,7 +342,6 @@ impl Topology { { let pt = &mut self.proc_tracker; pt.sysinfo.refresh_processes(); - pt.sysinfo.refresh_cpu(); let current_procs = pt .sysinfo .processes() @@ -584,7 +588,7 @@ impl Topology { if let Some(record) = self.get_proc_tracker().get_process_last_record(pid) { return Some(Record::new( record.timestamp, - record.process.cpu_usage_percentage.to_string(), + (record.process.cpu_usage_percentage / self.proc_tracker.nb_cores as f32).to_string(), units::Unit::Percentage, )); } diff --git a/src/sensors/utils.rs b/src/sensors/utils.rs index 1360cc87..2be0148b 100644 --- a/src/sensors/utils.rs +++ b/src/sensors/utils.rs @@ -650,10 +650,10 @@ impl ProcessTracker { } pub fn get_cpu_usage_percentage(&self, pid: Pid, nb_cores: usize) -> f32 { + info!("CALL FOR CPU USAGE"); let cpu_current_usage = self.sysinfo.global_cpu_info().cpu_usage(); if let Some(p) = self.sysinfo.process(pid) { - (p.cpu_usage() + (100.0 - cpu_current_usage / nb_cores as f32) * p.cpu_usage() / 100.0) - / nb_cores as f32 + (cpu_current_usage * p.cpu_usage() / 100.0) / nb_cores as f32 } else { 0.0 } From fea97eba56f905e235bb64dadfeb814b9e06038a Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 14 Mar 2023 18:14:01 +0100 Subject: [PATCH 040/303] style: fmt --- src/sensors/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index d2d24c7b..f65fd89f 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -588,7 +588,8 @@ impl Topology { if let Some(record) = self.get_proc_tracker().get_process_last_record(pid) { return Some(Record::new( record.timestamp, - (record.process.cpu_usage_percentage / self.proc_tracker.nb_cores as f32).to_string(), + (record.process.cpu_usage_percentage / self.proc_tracker.nb_cores as f32) + .to_string(), units::Unit::Percentage, )); } From c76eadac74caea4f3267d32f1dfcae635b27ae73 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 15 Mar 2023 16:14:31 +0100 Subject: [PATCH 041/303] feat: added memory metrics --- src/exporters/mod.rs | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index 3e2b947e..a95cf517 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -258,7 +258,7 @@ impl MetricGenerator { }); } - if let Some(metric_value) = self.topology.get_process_virtual_memory_bytes(myself.pid) { + if let Some(metric_value) = self.topology.get_process_memory_virtual_bytes(myself.pid) { self.data.push(Metric { name: String::from("scaph_self_memory_virtual_bytes"), metric_type: String::from("gauge"), @@ -753,11 +753,42 @@ impl MetricGenerator { hostname: self.hostname.clone(), state: String::from("ok"), tags: vec!["scaphandre".to_string()], - attributes, + attributes: attributes.clone(), description: String::from("CPU time consumed by the process, as a percentage of the capacity of all the CPU Cores"), metric_value: MetricValueType::Text(metric_value.value) }); } + + let metric_name = String::from("scaph_process_memory_bytes"); + if let Some(metric_value) = self.topology.get_process_memory_bytes(pid) { + self.data.push(Metric { + name: metric_name, + metric_type: String::from("gauge"), + ttl: 60.0, + timestamp: metric_value.timestamp, + hostname: self.hostname.clone(), + state: String::from("ok"), + tags: vec!["scaphandre".to_string()], + attributes: attributes.clone(), + description: String::from("Physical RAM usage by the process, in bytes"), + metric_value: MetricValueType::Text(metric_value.value) + }); + } + let metric_name = String::from("scaph_process_memory_virtual_bytes"); + if let Some(metric_value) = self.topology.get_process_memory_virtual_bytes(pid) { + self.data.push(Metric { + name: metric_name, + metric_type: String::from("gauge"), + ttl: 60.0, + timestamp: metric_value.timestamp, + hostname: self.hostname.clone(), + state: String::from("ok"), + tags: vec!["scaphandre".to_string()], + attributes, + description: String::from("Physical RAM usage by the process, in bytes"), + metric_value: MetricValueType::Text(metric_value.value) + }); + } } } From d6d598b574a227995e2aeb011fc4bd05ae5aa56e Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 15 Mar 2023 16:14:45 +0100 Subject: [PATCH 042/303] fix: fn name --- src/sensors/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index f65fd89f..2f1a9017 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -596,7 +596,7 @@ impl Topology { None } - pub fn get_process_virtual_memory_bytes(&self, pid: Pid) -> Option { + pub fn get_process_memory_virtual_bytes(&self, pid: Pid) -> Option { if let Some(record) = self.get_proc_tracker().get_process_last_record(pid) { return Some(Record::new( record.timestamp, From cd1485b232230067d4731cd4a648584d7232d4a6 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 15 Mar 2023 18:03:33 +0100 Subject: [PATCH 043/303] chore: added load average to sample dashboard --- .../dashboards/sample-dashboard.json | 273 +++++++++++++++++- docker-compose/sample.jsonnet | 71 ++++- 2 files changed, 333 insertions(+), 11 deletions(-) diff --git a/docker-compose/dashboards/sample-dashboard.json b/docker-compose/dashboards/sample-dashboard.json index 1b5a78f0..8de31c05 100644 --- a/docker-compose/dashboards/sample-dashboard.json +++ b/docker-compose/dashboards/sample-dashboard.json @@ -49,7 +49,7 @@ "repeat": null, "seriesOverrides": [ ], "spaceLength": 10, - "span": 6, + "span": 4, "stack": false, "steppedLine": false, "targets": [ @@ -180,6 +180,101 @@ "show": true } ] + }, + { + "aliasColors": { }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${PROMETHEUS_DS}", + "fill": 1, + "fillGradient": 0, + "gridPos": { }, + "id": 4, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "sideWidth": null, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [ ], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": null, + "seriesOverrides": [ ], + "spaceLength": 10, + "span": 4, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "scaph_host_load_avg_one", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "load_avg_1", + "refId": "A" + }, + { + "expr": "scaph_host_load_avg_five", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "load_avg_5", + "refId": "B" + }, + { + "expr": "scaph_host_load_avg_fifteen", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "load_avg_15", + "refId": "C" + } + ], + "thresholds": [ ], + "timeFrom": null, + "timeShift": null, + "title": "Host load average", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [ ] + }, + "yaxes": [ + { + "format": "", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + } + ] } ], "repeat": null, @@ -203,7 +298,7 @@ "fill": 1, "fillGradient": 0, "gridPos": { }, - "id": 4, + "id": 5, "legend": { "alignAsTable": false, "avg": false, @@ -284,7 +379,7 @@ "fill": 1, "fillGradient": 0, "gridPos": { }, - "id": 5, + "id": 6, "legend": { "alignAsTable": false, "avg": false, @@ -365,7 +460,7 @@ "fill": 1, "fillGradient": 0, "gridPos": { }, - "id": 6, + "id": 7, "legend": { "alignAsTable": false, "avg": false, @@ -466,7 +561,7 @@ "fill": 1, "fillGradient": 0, "gridPos": { }, - "id": 7, + "id": 8, "legend": { "alignAsTable": true, "avg": false, @@ -490,7 +585,7 @@ "repeat": null, "seriesOverrides": [ ], "spaceLength": 10, - "span": 6, + "span": 3, "stack": true, "steppedLine": false, "targets": [ @@ -547,7 +642,7 @@ "fill": 1, "fillGradient": 0, "gridPos": { }, - "id": 8, + "id": 9, "legend": { "alignAsTable": true, "avg": false, @@ -571,7 +666,7 @@ "repeat": null, "seriesOverrides": [ ], "spaceLength": 10, - "span": 6, + "span": 3, "stack": true, "steppedLine": false, "targets": [ @@ -618,6 +713,168 @@ "show": true } ] + }, + { + "aliasColors": { }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${PROMETHEUS_DS}", + "fill": 1, + "fillGradient": 0, + "gridPos": { }, + "id": 10, + "legend": { + "alignAsTable": true, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "sideWidth": "30%", + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [ ], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": null, + "seriesOverrides": [ ], + "spaceLength": 10, + "span": 3, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "scaph_process_memory_bytes{exe=~\".*${process_filter}.*\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{ cmdline }}", + "refId": "A" + } + ], + "thresholds": [ ], + "timeFrom": null, + "timeShift": null, + "title": "scaph_process_mem", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [ ] + }, + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + } + ] + }, + { + "aliasColors": { }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${PROMETHEUS_DS}", + "fill": 1, + "fillGradient": 0, + "gridPos": { }, + "id": 11, + "legend": { + "alignAsTable": true, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "sideWidth": "30%", + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [ ], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": null, + "seriesOverrides": [ ], + "spaceLength": 10, + "span": 3, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "scaph_process_memory_virtual_bytes{exe=~\".*${process_filter}.*\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{ cmdline }}", + "refId": "A" + } + ], + "thresholds": [ ], + "timeFrom": null, + "timeShift": null, + "title": "scaph_process_mem_virtual", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [ ] + }, + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + } + ] } ], "repeat": null, diff --git a/docker-compose/sample.jsonnet b/docker-compose/sample.jsonnet index f73322fa..c4eae0da 100644 --- a/docker-compose/sample.jsonnet +++ b/docker-compose/sample.jsonnet @@ -32,7 +32,7 @@ dashboard.new( title='Hosts power consumption', datasource='${PROMETHEUS_DS}', format='W', - span=6, + span=4, min=0 ) .addTarget( @@ -60,6 +60,33 @@ dashboard.new( ) ) ) + .addPanel( + grafana.graphPanel.new( + title='Host load average', + datasource='${PROMETHEUS_DS}', + span=4, + format='', + min=0 + ) + .addTarget( + grafana.prometheus.target( + 'scaph_host_load_avg_one', + legendFormat='load_avg_1', + ) + ) + .addTarget( + grafana.prometheus.target( + 'scaph_host_load_avg_five', + legendFormat='load_avg_5', + ) + ) + .addTarget( + grafana.prometheus.target( + 'scaph_host_load_avg_fifteen', + legendFormat='load_avg_15', + ) + ) + ) ) .addRow( row.new( @@ -125,7 +152,7 @@ dashboard.new( grafana.graphPanel.new( title='Filtered process (process_filter) power, by exe', datasource='${PROMETHEUS_DS}', - span=6, + span=3, format='W', legend_rightSide=false, legend_alignAsTable=true, @@ -144,7 +171,7 @@ dashboard.new( grafana.graphPanel.new( title='scaph_process_cpu', datasource='${PROMETHEUS_DS}', - span=6, + span=3, format='%', legend_rightSide=false, legend_alignAsTable=true, @@ -159,4 +186,42 @@ dashboard.new( ) ) ) + .addPanel( + grafana.graphPanel.new( + title='scaph_process_mem', + datasource='${PROMETHEUS_DS}', + span=3, + format='bytes', + legend_rightSide=false, + legend_alignAsTable=true, + legend_sideWidth='30%', + stack=true, + min=0 + ) + .addTarget( + grafana.prometheus.target( + 'scaph_process_memory_bytes{exe=~".*${process_filter}.*"}', + legendFormat='{{ cmdline }}', + ) + ) + ) + .addPanel( + grafana.graphPanel.new( + title='scaph_process_mem_virtual', + datasource='${PROMETHEUS_DS}', + span=3, + format='bytes', + legend_rightSide=false, + legend_alignAsTable=true, + legend_sideWidth='30%', + stack=true, + min=0 + ) + .addTarget( + grafana.prometheus.target( + 'scaph_process_memory_virtual_bytes{exe=~".*${process_filter}.*"}', + legendFormat='{{ cmdline }}', + ) + ) + ) ) From a8e108c96ea120678a9e699f0349269c0176386a Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 15 Mar 2023 18:04:25 +0100 Subject: [PATCH 044/303] chore: added neutral unit --- src/sensors/units.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sensors/units.rs b/src/sensors/units.rs index e208aac6..016e8322 100644 --- a/src/sensors/units.rs +++ b/src/sensors/units.rs @@ -3,6 +3,7 @@ use std::{cmp::Ordering, fmt}; // !!!!!!!!!!!!!!!!! Unit !!!!!!!!!!!!!!!!!!!!!!! #[derive(Debug)] pub enum Unit { + Numeric, Joule, MilliJoule, MicroJoule, @@ -71,6 +72,7 @@ impl fmt::Display for Unit { Unit::KiloBytes => write!(f, "KiloBytes"), Unit::MegaBytes => write!(f, "MegaBytes"), Unit::GigaBytes => write!(f, "GigaBytes"), + Unit::Numeric => write!(f, ""), } } } From cdca174553355ec8fab3c656c65c878902fe81dd Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 15 Mar 2023 18:04:48 +0100 Subject: [PATCH 045/303] feat: added load average to metrics --- src/sensors/mod.rs | 58 +++++++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 2f1a9017..762508dc 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -176,30 +176,23 @@ impl Topology { pub fn generate_cpu_cores() -> Option> { let mut cores = vec![]; - #[cfg(target_os = "linux")] - { - let cpuinfo = CpuInfo::new().unwrap(); - for id in 0..(cpuinfo.num_cores() - 1) { - let mut info = HashMap::new(); - for (k, v) in cpuinfo.get_info(id).unwrap().iter() { + let sysinfo_system = System::new_all(); + let sysinfo_cores = sysinfo_system.cpus(); + #[cfg(target_os="linux")] + let cpuinfo = CpuInfo::new().unwrap(); + for (id, c) in (0_u16..).zip(sysinfo_cores.iter()) { + let mut info = HashMap::::new(); + #[cfg(target_os="linux")] + { + for (k, v) in cpuinfo.get_info(id as usize).unwrap().iter() { info.insert(String::from(*k), String::from(*v)); } - cores.push(CPUCore::new(id as u16, info)); - } - } - #[cfg(target_os = "windows")] - { - warn!("generate_cpu_info is not implemented yet on this OS."); - let sysinfo_system = System::new_all(); - let sysinfo_cores = sysinfo_system.cpus(); - for (id, c) in (0_u16..).zip(sysinfo_cores.iter()) { - let mut info = HashMap::::new(); - info.insert(String::from("frequency"), c.frequency().to_string()); - info.insert(String::from("name"), c.name().to_string()); - info.insert(String::from("vendor_id"), c.vendor_id().to_string()); - info.insert(String::from("brand"), c.brand().to_string()); - cores.push(CPUCore::new(id, info)); } + info.insert(String::from("frequency"), c.frequency().to_string()); + info.insert(String::from("name"), c.name().to_string()); + info.insert(String::from("vendor_id"), c.vendor_id().to_string()); + info.insert(String::from("brand"), c.brand().to_string()); + cores.push(CPUCore::new(id, info)); } Some(cores) } @@ -557,6 +550,29 @@ impl Topology { None } + pub fn get_load_avg(&self) -> Option> { + let load = self.get_proc_tracker().sysinfo.load_average(); + let timestamp = current_system_time_since_epoch(); + Some( + vec![ + Record::new( + timestamp, + load.one.to_string(), + units::Unit::Numeric + ), + Record::new( + timestamp, + load.five.to_string(), + units::Unit::Numeric + ), + Record::new( + timestamp, + load.five.to_string(), + units::Unit::Numeric + ) + ]) + } + /// Returns the power consumed between last and previous measurement for a given process ID, in microwatts pub fn get_process_power_consumption_microwatts(&self, pid: Pid) -> Option { let tracker = self.get_proc_tracker(); From e8ff216951012a4d6921f5d6530b31a434e6746a Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 15 Mar 2023 18:05:12 +0100 Subject: [PATCH 046/303] feat: added load average to metrics --- src/exporters/mod.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index a95cf517..f3408e42 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -427,6 +427,44 @@ impl MetricGenerator { }); } } + if let Some(metric_value) = self.topology.get_load_avg() { + self.data.push(Metric { + name: String::from("scaph_host_load_avg_one"), + metric_type: String::from("gauge"), + ttl: 60.0, + timestamp: metric_value[0].timestamp, + hostname: self.hostname.clone(), + state: String::from("ok"), + tags: vec!["scaphandre".to_string()], + attributes: HashMap::new(), + description: String::from("Load average on 1 minute."), + metric_value: MetricValueType::Text(metric_value[0].value.clone()) + }); + self.data.push(Metric { + name: String::from("scaph_host_load_avg_five"), + metric_type: String::from("gauge"), + ttl: 60.0, + timestamp: metric_value[1].timestamp, + hostname: self.hostname.clone(), + state: String::from("ok"), + tags: vec!["scaphandre".to_string()], + attributes: HashMap::new(), + description: String::from("Load average on 5 minutes."), + metric_value: MetricValueType::Text(metric_value[1].value.clone()) + }); + self.data.push(Metric { + name: String::from("scaph_host_load_avg_fifteen"), + metric_type: String::from("gauge"), + ttl: 60.0, + timestamp: metric_value[2].timestamp, + hostname: self.hostname.clone(), + state: String::from("ok"), + tags: vec!["scaphandre".to_string()], + attributes: HashMap::new(), + description: String::from("Load average on 15 minutes."), + metric_value: MetricValueType::Text(metric_value[2].value.clone()) + }); + } } /// Generate socket metrics. @@ -585,6 +623,7 @@ impl MetricGenerator { metric_value: MetricValueType::IntUnsigned(metric_value), }); } + } /// If *self.watch_docker* is true and *self.docker_client* is Some From 795953beec93417ec90742aa200b660be4b3cdae Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 15 Mar 2023 18:13:51 +0100 Subject: [PATCH 047/303] style: fmt --- src/exporters/mod.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index f3408e42..bc34725d 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -438,7 +438,7 @@ impl MetricGenerator { tags: vec!["scaphandre".to_string()], attributes: HashMap::new(), description: String::from("Load average on 1 minute."), - metric_value: MetricValueType::Text(metric_value[0].value.clone()) + metric_value: MetricValueType::Text(metric_value[0].value.clone()), }); self.data.push(Metric { name: String::from("scaph_host_load_avg_five"), @@ -450,7 +450,7 @@ impl MetricGenerator { tags: vec!["scaphandre".to_string()], attributes: HashMap::new(), description: String::from("Load average on 5 minutes."), - metric_value: MetricValueType::Text(metric_value[1].value.clone()) + metric_value: MetricValueType::Text(metric_value[1].value.clone()), }); self.data.push(Metric { name: String::from("scaph_host_load_avg_fifteen"), @@ -462,7 +462,7 @@ impl MetricGenerator { tags: vec!["scaphandre".to_string()], attributes: HashMap::new(), description: String::from("Load average on 15 minutes."), - metric_value: MetricValueType::Text(metric_value[2].value.clone()) + metric_value: MetricValueType::Text(metric_value[2].value.clone()), }); } } @@ -623,7 +623,6 @@ impl MetricGenerator { metric_value: MetricValueType::IntUnsigned(metric_value), }); } - } /// If *self.watch_docker* is true and *self.docker_client* is Some @@ -810,7 +809,7 @@ impl MetricGenerator { tags: vec!["scaphandre".to_string()], attributes: attributes.clone(), description: String::from("Physical RAM usage by the process, in bytes"), - metric_value: MetricValueType::Text(metric_value.value) + metric_value: MetricValueType::Text(metric_value.value), }); } let metric_name = String::from("scaph_process_memory_virtual_bytes"); @@ -825,7 +824,7 @@ impl MetricGenerator { tags: vec!["scaphandre".to_string()], attributes, description: String::from("Physical RAM usage by the process, in bytes"), - metric_value: MetricValueType::Text(metric_value.value) + metric_value: MetricValueType::Text(metric_value.value), }); } } From 8d83c994995c22d3dd142ac471ce0ccd625c59b6 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 15 Mar 2023 18:14:42 +0100 Subject: [PATCH 048/303] fix: updated clean records for sysinfo and cargo fmt --- src/sensors/mod.rs | 25 +++--------- src/sensors/utils.rs | 96 ++++++++++---------------------------------- 2 files changed, 27 insertions(+), 94 deletions(-) diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 762508dc..1f676e91 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -178,11 +178,11 @@ impl Topology { let sysinfo_system = System::new_all(); let sysinfo_cores = sysinfo_system.cpus(); - #[cfg(target_os="linux")] + #[cfg(target_os = "linux")] let cpuinfo = CpuInfo::new().unwrap(); for (id, c) in (0_u16..).zip(sysinfo_cores.iter()) { let mut info = HashMap::::new(); - #[cfg(target_os="linux")] + #[cfg(target_os = "linux")] { for (k, v) in cpuinfo.get_info(id as usize).unwrap().iter() { info.insert(String::from(*k), String::from(*v)); @@ -553,23 +553,10 @@ impl Topology { pub fn get_load_avg(&self) -> Option> { let load = self.get_proc_tracker().sysinfo.load_average(); let timestamp = current_system_time_since_epoch(); - Some( - vec![ - Record::new( - timestamp, - load.one.to_string(), - units::Unit::Numeric - ), - Record::new( - timestamp, - load.five.to_string(), - units::Unit::Numeric - ), - Record::new( - timestamp, - load.five.to_string(), - units::Unit::Numeric - ) + Some(vec![ + Record::new(timestamp, load.one.to_string(), units::Unit::Numeric), + Record::new(timestamp, load.five.to_string(), units::Unit::Numeric), + Record::new(timestamp, load.five.to_string(), units::Unit::Numeric), ]) } diff --git a/src/sensors/utils.rs b/src/sensors/utils.rs index 2be0148b..0be16814 100644 --- a/src/sensors/utils.rs +++ b/src/sensors/utils.rs @@ -7,7 +7,9 @@ use std::collections::HashMap; use std::io::{Error, ErrorKind}; use std::path::PathBuf; use std::time::{Duration, SystemTime}; -use sysinfo::{get_current_pid, CpuExt, Pid, Process, ProcessExt, System, SystemExt}; +use sysinfo::{ + get_current_pid, CpuExt, Pid, Process, ProcessExt, ProcessStatus, System, SystemExt, +}; #[cfg(all(target_os = "linux", feature = "containers"))] use {docker_sync::container::Container, k8s_sync::Pod}; @@ -132,33 +134,6 @@ impl IProcess { } } - pub fn status(&self) -> Result { - //#[cfg(target_os = "linux")] - //{ - // if let Ok(original_status) = self.original.status() { - // let status = IStatus { - // name: original_status.name, - // pid: original_status.pid, - // ppid: original_status.ppid, - // state: original_status.state, - // umask: original_status.umask, - // }; - // Ok(status) - // } else { - // Err(format!("Couldn't get status for {}", self.pid)) - // } - //} - { - Ok(IStatus { - name: String::from("Not implemented yet !"), - pid: 42, - ppid: 42, - state: String::from("X"), - umask: None, - }) - } - } - #[cfg(target_os = "linux")] pub fn total_time_jiffies(&self, proc_tracker: &ProcessTracker) -> u64 { if let Some(rec) = proc_tracker.get_process_last_record(self.pid) { @@ -724,44 +699,28 @@ impl ProcessTracker { /// (if the process is not running anymore) pub fn clean_terminated_process_records_vectors(&mut self) { //TODO get stats from processes to know what is hapening ! - let mut d_unint_sleep = 0; - let mut r_running = 0; - let mut s_int_sleep = 0; - let mut t_stopped = 0; - let mut z_defunct_zombie = 0; - let mut w_no_resident_high_prio = 0; - let mut n_low_prio = 0; - let mut l_pages_locked = 0; - let mut i_idle = 0; - let mut unknown = 0; for v in &mut self.procs { if !v.is_empty() { if let Some(first) = v.first() { - if let Ok(status) = first.process.status() { - if status.state.contains('T') { - while !v.is_empty() { - v.pop(); + if let Some(p) = self.sysinfo.process(first.process.pid) { + match p.status() { + ProcessStatus::Idle => {} + ProcessStatus::Dead => {} + ProcessStatus::Stop => { + while !v.is_empty() { + v.pop(); + } } - t_stopped += 1; - } else if status.state.contains('D') { - d_unint_sleep += 1; - } else if status.state.contains('R') { - r_running += 1; - } else if status.state.contains('S') { - s_int_sleep += 1; - } else if status.state.contains('Z') { - z_defunct_zombie += 1; - } else if status.state.contains('W') { - w_no_resident_high_prio += 1; - } else if status.state.contains('N') { - n_low_prio += 1; - } else if status.state.contains('L') { - l_pages_locked += 1; - } else if status.state.contains('I') { - i_idle += 1; - } else { - unknown += 1; - debug!("unkown state: {} name: {}", status.state, status.name); + ProcessStatus::Run => {} + ProcessStatus::LockBlocked => {} + ProcessStatus::Waking => {} + ProcessStatus::Wakekill => {} + ProcessStatus::Tracing => {} + ProcessStatus::Zombie => {} + ProcessStatus::Sleep => {} + ProcessStatus::Parked => {} + ProcessStatus::UninterruptibleDiskSleep => {} + ProcessStatus::Unknown(_code) => {} } } else { while !v.is_empty() { @@ -771,19 +730,6 @@ impl ProcessTracker { } } } - debug!( - "d:{} r:{} s:{} t:{} z:{} w:{} n:{} l:{} i:{} u:{}", - d_unint_sleep, - r_running, - s_int_sleep, - t_stopped, - z_defunct_zombie, - w_no_resident_high_prio, - n_low_prio, - l_pages_locked, - i_idle, - unknown - ); self.drop_empty_process_records_vectors(); } From c5d7ebd61d05ca6868462494b40c8ef476362c52 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Thu, 16 Mar 2023 15:25:27 +0100 Subject: [PATCH 049/303] feat: added cpu frequency and load avg --- src/exporters/mod.rs | 17 ++++++++++++ src/sensors/mod.rs | 37 +++++++++++++++++++------- src/sensors/units.rs | 2 ++ src/sensors/utils.rs | 63 ++++++++++++++++++++++++++++++++------------ 4 files changed, 93 insertions(+), 26 deletions(-) diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index bc34725d..c32ef0a4 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -465,6 +465,23 @@ impl MetricGenerator { metric_value: MetricValueType::Text(metric_value[2].value.clone()), }); } + let freq = self.topology.get_cpu_frequency(); + self.data.push(Metric { + name: String::from("scaph_host_cpu_frequency"), + metric_type: String::from("gauge"), + ttl: 60.0, + timestamp: freq.timestamp, + hostname: self.hostname.clone(), + state: String::from("ok"), + tags: vec!["scaphandre".to_string()], + attributes: HashMap::new(), + description: format!("Global frequency of all the cpus. In {}", freq.unit), + metric_value: MetricValueType::Text(freq.value), + }); + //for c in self.topology.proc_tracker.components() { + // println!("component: {}", c) + //} + } /// Generate socket metrics. diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 1f676e91..4ad0f840 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -178,11 +178,11 @@ impl Topology { let sysinfo_system = System::new_all(); let sysinfo_cores = sysinfo_system.cpus(); - #[cfg(target_os = "linux")] + #[cfg(target_os="linux")] let cpuinfo = CpuInfo::new().unwrap(); for (id, c) in (0_u16..).zip(sysinfo_cores.iter()) { let mut info = HashMap::::new(); - #[cfg(target_os = "linux")] + #[cfg(target_os="linux")] { for (k, v) in cpuinfo.get_info(id as usize).unwrap().iter() { info.insert(String::from(*k), String::from(*v)); @@ -321,9 +321,7 @@ impl Topology { } let pt = &mut self.proc_tracker; info!("REFRESH CPU"); - pt.sysinfo.refresh_cpu(); - pt.sysinfo.refresh_components(); - pt.sysinfo.refresh_memory(); + self.proc_tracker.refresh(); self.refresh_procs(); self.refresh_record(); self.refresh_stats(); @@ -550,13 +548,34 @@ impl Topology { None } + pub fn get_cpu_frequency(&self) -> Record { + Record::new( + current_system_time_since_epoch(), + self.proc_tracker.get_cpu_frequency().to_string(), + units::Unit::MegaHertz + ) + } + pub fn get_load_avg(&self) -> Option> { let load = self.get_proc_tracker().sysinfo.load_average(); let timestamp = current_system_time_since_epoch(); - Some(vec![ - Record::new(timestamp, load.one.to_string(), units::Unit::Numeric), - Record::new(timestamp, load.five.to_string(), units::Unit::Numeric), - Record::new(timestamp, load.five.to_string(), units::Unit::Numeric), + Some( + vec![ + Record::new( + timestamp, + load.one.to_string(), + units::Unit::Numeric + ), + Record::new( + timestamp, + load.five.to_string(), + units::Unit::Numeric + ), + Record::new( + timestamp, + load.five.to_string(), + units::Unit::Numeric + ) ]) } diff --git a/src/sensors/units.rs b/src/sensors/units.rs index 016e8322..e4f8f14f 100644 --- a/src/sensors/units.rs +++ b/src/sensors/units.rs @@ -17,6 +17,7 @@ pub enum Unit { KiloBytes, MegaBytes, GigaBytes, + MegaHertz } impl Unit { @@ -72,6 +73,7 @@ impl fmt::Display for Unit { Unit::KiloBytes => write!(f, "KiloBytes"), Unit::MegaBytes => write!(f, "MegaBytes"), Unit::GigaBytes => write!(f, "GigaBytes"), + Unit::MegaHertz => write!(f, "MegaHertz"), Unit::Numeric => write!(f, ""), } } diff --git a/src/sensors/utils.rs b/src/sensors/utils.rs index 0be16814..15bfb33e 100644 --- a/src/sensors/utils.rs +++ b/src/sensors/utils.rs @@ -8,7 +8,7 @@ use std::io::{Error, ErrorKind}; use std::path::PathBuf; use std::time::{Duration, SystemTime}; use sysinfo::{ - get_current_pid, CpuExt, Pid, Process, ProcessExt, ProcessStatus, System, SystemExt, + get_current_pid, CpuExt, Pid, Process, ProcessExt, ProcessStatus, System, SystemExt, CpuRefreshKind }; #[cfg(all(target_os = "linux", feature = "containers"))] use {docker_sync::container::Container, k8s_sync::Pod}; @@ -84,33 +84,44 @@ pub struct IProcess { impl IProcess { pub fn new(process: &Process) -> IProcess { - let mut stime = 0; - let mut utime = 0; #[cfg(target_os = "linux")] { + let mut stime = 0; + let mut utime = 0; if let Ok(procfs_process) = procfs::process::Process::new(process.pid().to_string().parse::().unwrap()) { if let Ok(stat) = procfs_process.stat() { stime += stat.stime; utime += stat.utime; + } } + IProcess { + pid: process.pid(), + owner: 0, + comm: String::from(process.exe().to_str().unwrap()), + cmdline: process.cmd().to_vec(), + cpu_usage_percentage: process.cpu_usage(), + memory: process.memory(), + virtual_memory: process.virtual_memory(), + stime, + utime, + } } - - IProcess { - pid: process.pid(), - owner: 0, - comm: String::from(process.exe().to_str().unwrap()), - cmdline: process.cmd().to_vec(), - cpu_usage_percentage: process.cpu_usage(), - memory: process.memory(), - virtual_memory: process.virtual_memory(), - #[cfg(target_os = "linux")] - stime, - #[cfg(target_os = "linux")] - utime, + #[cfg(not(target_os="linux"))] + { + IProcess { + pid: process.pid(), + owner: 0, + comm: String::from(process.exe().to_str().unwrap()), + cmdline: process.cmd().to_vec(), + cpu_usage_percentage: process.cpu_usage(), + memory: process.memory(), + virtual_memory: process.virtual_memory(), + } } + } /// Returns the command line of related to the process, as found by sysinfo. @@ -224,7 +235,7 @@ impl ProcessTracker { let regex_cgroup_containerd = Regex::new("/system.slice/containerd.service/.*$").unwrap(); let mut system = System::new_all(); - system.refresh_cpu(); + system.refresh_cpu_specifics(CpuRefreshKind::everything()); let nb_cores = system.cpus().len(); ProcessTracker { @@ -241,6 +252,20 @@ impl ProcessTracker { } } + pub fn refresh(&mut self) { + self.sysinfo.refresh_components(); + self.sysinfo.refresh_memory(); + self.sysinfo.refresh_cpu_specifics(CpuRefreshKind::everything()); + } + + pub fn components(&mut self) -> Vec { + let mut res = vec![]; + for c in self.sysinfo.components() { + res.push(format!("{:?}", c)); + } + res + } + /// Properly creates and adds a ProcessRecord to 'procs', the vector of vectors or ProcessRecords /// owned by the ProcessTracker instance. This method should be used to keep track of processes /// states during all the lifecycle of the exporter. @@ -363,6 +388,10 @@ impl ProcessTracker { // None //} + pub fn get_cpu_frequency(&self) -> u64 { + self.sysinfo.global_cpu_info().frequency() + } + /// Returns all vectors of process records linked to a running, sleeping, waiting or zombie process. /// (Not terminated) pub fn get_alive_processes(&self) -> Vec<&Vec> { From 593b3b05ba6a0eb132a74538b31ed61dfc0440d3 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Thu, 16 Mar 2023 15:34:18 +0100 Subject: [PATCH 050/303] style: clippy and fmt --- src/exporters/mod.rs | 1 - src/sensors/mod.rs | 29 +++++++---------------------- src/sensors/units.rs | 2 +- src/sensors/utils.rs | 14 +++++++------- 4 files changed, 15 insertions(+), 31 deletions(-) diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index c32ef0a4..7fb8652d 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -481,7 +481,6 @@ impl MetricGenerator { //for c in self.topology.proc_tracker.components() { // println!("component: {}", c) //} - } /// Generate socket metrics. diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 4ad0f840..a05de87c 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -178,11 +178,11 @@ impl Topology { let sysinfo_system = System::new_all(); let sysinfo_cores = sysinfo_system.cpus(); - #[cfg(target_os="linux")] + #[cfg(target_os = "linux")] let cpuinfo = CpuInfo::new().unwrap(); for (id, c) in (0_u16..).zip(sysinfo_cores.iter()) { let mut info = HashMap::::new(); - #[cfg(target_os="linux")] + #[cfg(target_os = "linux")] { for (k, v) in cpuinfo.get_info(id as usize).unwrap().iter() { info.insert(String::from(*k), String::from(*v)); @@ -319,8 +319,6 @@ impl Topology { // //} } - let pt = &mut self.proc_tracker; - info!("REFRESH CPU"); self.proc_tracker.refresh(); self.refresh_procs(); self.refresh_record(); @@ -552,30 +550,17 @@ impl Topology { Record::new( current_system_time_since_epoch(), self.proc_tracker.get_cpu_frequency().to_string(), - units::Unit::MegaHertz + units::Unit::MegaHertz, ) } pub fn get_load_avg(&self) -> Option> { let load = self.get_proc_tracker().sysinfo.load_average(); let timestamp = current_system_time_since_epoch(); - Some( - vec![ - Record::new( - timestamp, - load.one.to_string(), - units::Unit::Numeric - ), - Record::new( - timestamp, - load.five.to_string(), - units::Unit::Numeric - ), - Record::new( - timestamp, - load.five.to_string(), - units::Unit::Numeric - ) + Some(vec![ + Record::new(timestamp, load.one.to_string(), units::Unit::Numeric), + Record::new(timestamp, load.five.to_string(), units::Unit::Numeric), + Record::new(timestamp, load.five.to_string(), units::Unit::Numeric), ]) } diff --git a/src/sensors/units.rs b/src/sensors/units.rs index e4f8f14f..a2eff801 100644 --- a/src/sensors/units.rs +++ b/src/sensors/units.rs @@ -17,7 +17,7 @@ pub enum Unit { KiloBytes, MegaBytes, GigaBytes, - MegaHertz + MegaHertz, } impl Unit { diff --git a/src/sensors/utils.rs b/src/sensors/utils.rs index 15bfb33e..26c31acb 100644 --- a/src/sensors/utils.rs +++ b/src/sensors/utils.rs @@ -8,7 +8,8 @@ use std::io::{Error, ErrorKind}; use std::path::PathBuf; use std::time::{Duration, SystemTime}; use sysinfo::{ - get_current_pid, CpuExt, Pid, Process, ProcessExt, ProcessStatus, System, SystemExt, CpuRefreshKind + get_current_pid, CpuExt, CpuRefreshKind, Pid, Process, ProcessExt, ProcessStatus, System, + SystemExt, }; #[cfg(all(target_os = "linux", feature = "containers"))] use {docker_sync::container::Container, k8s_sync::Pod}; @@ -94,7 +95,6 @@ impl IProcess { if let Ok(stat) = procfs_process.stat() { stime += stat.stime; utime += stat.utime; - } } IProcess { @@ -109,7 +109,7 @@ impl IProcess { utime, } } - #[cfg(not(target_os="linux"))] + #[cfg(not(target_os = "linux"))] { IProcess { pid: process.pid(), @@ -121,7 +121,6 @@ impl IProcess { virtual_memory: process.virtual_memory(), } } - } /// Returns the command line of related to the process, as found by sysinfo. @@ -255,13 +254,14 @@ impl ProcessTracker { pub fn refresh(&mut self) { self.sysinfo.refresh_components(); self.sysinfo.refresh_memory(); - self.sysinfo.refresh_cpu_specifics(CpuRefreshKind::everything()); + self.sysinfo + .refresh_cpu_specifics(CpuRefreshKind::everything()); } pub fn components(&mut self) -> Vec { let mut res = vec![]; for c in self.sysinfo.components() { - res.push(format!("{:?}", c)); + res.push(format!("{c:?}")); } res } @@ -389,7 +389,7 @@ impl ProcessTracker { //} pub fn get_cpu_frequency(&self) -> u64 { - self.sysinfo.global_cpu_info().frequency() + self.sysinfo.global_cpu_info().frequency() } /// Returns all vectors of process records linked to a running, sleeping, waiting or zombie process. From f5cae13b100d9be2978e7f347e8925c578e9b6de Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Thu, 16 Mar 2023 17:18:06 +0100 Subject: [PATCH 051/303] feat: added per-process disk usage metrics --- src/exporters/mod.rs | 64 +++++++++++++++++++++++++++++++++++++++++++- src/sensors/mod.rs | 43 +++++++++++++++++++++++++++++ src/sensors/utils.rs | 19 +++++++++++++ 3 files changed, 125 insertions(+), 1 deletion(-) diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index 7fb8652d..91b6e8f9 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -838,11 +838,73 @@ impl MetricGenerator { hostname: self.hostname.clone(), state: String::from("ok"), tags: vec!["scaphandre".to_string()], - attributes, + attributes: attributes.clone(), description: String::from("Physical RAM usage by the process, in bytes"), metric_value: MetricValueType::Text(metric_value.value), }); } + let metric_name = String::from("scaph_process_disk_write_bytes"); + if let Some(metric_value) = self.topology.get_process_disk_written_bytes(pid) { + self.data.push(Metric { + name: metric_name, + metric_type: String::from("gauge"), + ttl: 60.0, + timestamp: metric_value.timestamp, + hostname: self.hostname.clone(), + state: String::from("ok"), + tags: vec!["scaphandre".to_string()], + attributes: attributes.clone(), + description: String::from("Data written on disk by the process, in bytes"), + metric_value: MetricValueType::Text(metric_value.value), + }); + } + let metric_name = String::from("scaph_process_disk_read_bytes"); + if let Some(metric_value) = self.topology.get_process_disk_read_bytes(pid) { + self.data.push(Metric { + name: metric_name, + metric_type: String::from("gauge"), + ttl: 60.0, + timestamp: metric_value.timestamp, + hostname: self.hostname.clone(), + state: String::from("ok"), + tags: vec!["scaphandre".to_string()], + attributes: attributes.clone(), + description: String::from("Data read on disk by the process, in bytes"), + metric_value: MetricValueType::Text(metric_value.value), + }); + } + let metric_name = String::from("scaph_process_disk_total_written_bytes"); + if let Some(metric_value) = self.topology.get_process_disk_total_write_bytes(pid) { + self.data.push(Metric { + name: metric_name, + metric_type: String::from("gauge"), + ttl: 60.0, + timestamp: metric_value.timestamp, + hostname: self.hostname.clone(), + state: String::from("ok"), + tags: vec!["scaphandre".to_string()], + attributes: attributes.clone(), + description: String::from( + "Total data written on disk by the process, in bytes", + ), + metric_value: MetricValueType::Text(metric_value.value), + }); + } + let metric_name = String::from("scaph_process_disk_total_read_bytes"); + if let Some(metric_value) = self.topology.get_process_disk_total_read_bytes(pid) { + self.data.push(Metric { + name: metric_name, + metric_type: String::from("gauge"), + ttl: 60.0, + timestamp: metric_value.timestamp, + hostname: self.hostname.clone(), + state: String::from("ok"), + tags: vec!["scaphandre".to_string()], + attributes: attributes.clone(), + description: String::from("Total data read on disk by the process, in bytes"), + metric_value: MetricValueType::Text(metric_value.value), + }); + } } } diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index a05de87c..09c476e4 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -624,6 +624,49 @@ impl Topology { } None } + + pub fn get_process_disk_written_bytes(&self, pid: Pid) -> Option { + if let Some(record) = self.get_proc_tracker().get_process_last_record(pid) { + return Some(Record::new( + record.timestamp, + record.process.disk_written.to_string(), + units::Unit::Bytes, + )); + } + None + } + + pub fn get_process_disk_read_bytes(&self, pid: Pid) -> Option { + if let Some(record) = self.get_proc_tracker().get_process_last_record(pid) { + return Some(Record::new( + record.timestamp, + record.process.disk_read.to_string(), + units::Unit::Bytes, + )); + } + None + } + pub fn get_process_disk_total_read_bytes(&self, pid: Pid) -> Option { + if let Some(record) = self.get_proc_tracker().get_process_last_record(pid) { + return Some(Record::new( + record.timestamp, + record.process.total_disk_read.to_string(), + units::Unit::Bytes, + )); + } + None + } + + pub fn get_process_disk_total_write_bytes(&self, pid: Pid) -> Option { + if let Some(record) = self.get_proc_tracker().get_process_last_record(pid) { + return Some(Record::new( + record.timestamp, + record.process.total_disk_written.to_string(), + units::Unit::Bytes, + )); + } + None + } } // !!!!!!!!!!!!!!!!! CPUSocket !!!!!!!!!!!!!!!!!!!!!!! diff --git a/src/sensors/utils.rs b/src/sensors/utils.rs index 26c31acb..3e3609b4 100644 --- a/src/sensors/utils.rs +++ b/src/sensors/utils.rs @@ -77,6 +77,14 @@ pub struct IProcess { pub virtual_memory: u64, // Memory consumed by the process (at the time the struct is created), in bytes pub memory: u64, + // Disk bytes read by the process + pub disk_read: u64, + // Disk bytes written by the process + pub disk_written: u64, + // Total disk bytes read by the process + pub total_disk_read: u64, + // Total disk bytes written by the process + pub total_disk_written: u64, #[cfg(target_os = "linux")] pub stime: u64, #[cfg(target_os = "linux")] @@ -85,6 +93,7 @@ pub struct IProcess { impl IProcess { pub fn new(process: &Process) -> IProcess { + let disk_usage = process.disk_usage(); #[cfg(target_os = "linux")] { let mut stime = 0; @@ -105,6 +114,10 @@ impl IProcess { cpu_usage_percentage: process.cpu_usage(), memory: process.memory(), virtual_memory: process.virtual_memory(), + disk_read: disk_usage.read_bytes, + disk_written: disk_usage.written_bytes, + total_disk_read: disk_usage.total_read_bytes, + total_disk_written: disk_usage.total_written_bytes, stime, utime, } @@ -119,6 +132,10 @@ impl IProcess { cpu_usage_percentage: process.cpu_usage(), memory: process.memory(), virtual_memory: process.virtual_memory(), + disk_read: disk_usage.read_bytes, + disk_written: disk_usage.written_bytes, + total_disk_read: disk_usage.total_read_bytes, + total_disk_written: disk_usage.total_written_bytes, } } } @@ -254,6 +271,8 @@ impl ProcessTracker { pub fn refresh(&mut self) { self.sysinfo.refresh_components(); self.sysinfo.refresh_memory(); + self.sysinfo.refresh_disks(); + self.sysinfo.refresh_disks_list(); self.sysinfo .refresh_cpu_specifics(CpuRefreshKind::everything()); } From ba5b55b3a494cbc48e6e4d608f0067dd9b4daf27 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Fri, 17 Mar 2023 18:26:13 +0100 Subject: [PATCH 052/303] chore: factorized per process metrics gathering --- src/exporters/mod.rs | 144 ++++++------------------------------------- src/sensors/mod.rs | 110 ++++++++++++++++++++++++++++----- 2 files changed, 112 insertions(+), 142 deletions(-) diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index 91b6e8f9..f3d54d24 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -252,9 +252,7 @@ impl MetricGenerator { tags: vec!["scaphandre".to_string()], attributes: HashMap::new(), description: format!("CPU time consumed by scaphandre, as {}", metric_value.unit), - metric_value: MetricValueType::FloatDouble( - metric_value.value.parse::().unwrap(), - ), + metric_value: MetricValueType::Text(metric_value.value), }); } @@ -781,129 +779,23 @@ impl MetricGenerator { } } - let metric_name = String::from("scaph_process_power_consumption_microwatts"); - if let Some(power) = self.topology.get_process_power_consumption_microwatts(pid) { - self.data.push(Metric { - name: metric_name, - metric_type: String::from("gauge"), - ttl: 60.0, - timestamp: power.timestamp, - hostname: self.hostname.clone(), - state: String::from("ok"), - tags: vec!["scaphandre".to_string()], - attributes: attributes.clone(), - description: String::from("Power consumption due to the process, measured on at the topology level, in microwatts"), - metric_value: MetricValueType::Text(power.value), - }); - } - - let metric_name = String::from("scaph_process_cpu_usage_percentage"); - if let Some(metric_value) = self.topology.get_process_cpu_usage_percentage(pid) { - self.data.push(Metric { - name: metric_name, - metric_type: String::from("gauge"), - ttl: 60.0, - timestamp: metric_value.timestamp, - hostname: self.hostname.clone(), - state: String::from("ok"), - tags: vec!["scaphandre".to_string()], - attributes: attributes.clone(), - description: String::from("CPU time consumed by the process, as a percentage of the capacity of all the CPU Cores"), - metric_value: MetricValueType::Text(metric_value.value) - }); - } - - let metric_name = String::from("scaph_process_memory_bytes"); - if let Some(metric_value) = self.topology.get_process_memory_bytes(pid) { - self.data.push(Metric { - name: metric_name, - metric_type: String::from("gauge"), - ttl: 60.0, - timestamp: metric_value.timestamp, - hostname: self.hostname.clone(), - state: String::from("ok"), - tags: vec!["scaphandre".to_string()], - attributes: attributes.clone(), - description: String::from("Physical RAM usage by the process, in bytes"), - metric_value: MetricValueType::Text(metric_value.value), - }); - } - let metric_name = String::from("scaph_process_memory_virtual_bytes"); - if let Some(metric_value) = self.topology.get_process_memory_virtual_bytes(pid) { - self.data.push(Metric { - name: metric_name, - metric_type: String::from("gauge"), - ttl: 60.0, - timestamp: metric_value.timestamp, - hostname: self.hostname.clone(), - state: String::from("ok"), - tags: vec!["scaphandre".to_string()], - attributes: attributes.clone(), - description: String::from("Physical RAM usage by the process, in bytes"), - metric_value: MetricValueType::Text(metric_value.value), - }); - } - let metric_name = String::from("scaph_process_disk_write_bytes"); - if let Some(metric_value) = self.topology.get_process_disk_written_bytes(pid) { - self.data.push(Metric { - name: metric_name, - metric_type: String::from("gauge"), - ttl: 60.0, - timestamp: metric_value.timestamp, - hostname: self.hostname.clone(), - state: String::from("ok"), - tags: vec!["scaphandre".to_string()], - attributes: attributes.clone(), - description: String::from("Data written on disk by the process, in bytes"), - metric_value: MetricValueType::Text(metric_value.value), - }); - } - let metric_name = String::from("scaph_process_disk_read_bytes"); - if let Some(metric_value) = self.topology.get_process_disk_read_bytes(pid) { - self.data.push(Metric { - name: metric_name, - metric_type: String::from("gauge"), - ttl: 60.0, - timestamp: metric_value.timestamp, - hostname: self.hostname.clone(), - state: String::from("ok"), - tags: vec!["scaphandre".to_string()], - attributes: attributes.clone(), - description: String::from("Data read on disk by the process, in bytes"), - metric_value: MetricValueType::Text(metric_value.value), - }); - } - let metric_name = String::from("scaph_process_disk_total_written_bytes"); - if let Some(metric_value) = self.topology.get_process_disk_total_write_bytes(pid) { - self.data.push(Metric { - name: metric_name, - metric_type: String::from("gauge"), - ttl: 60.0, - timestamp: metric_value.timestamp, - hostname: self.hostname.clone(), - state: String::from("ok"), - tags: vec!["scaphandre".to_string()], - attributes: attributes.clone(), - description: String::from( - "Total data written on disk by the process, in bytes", - ), - metric_value: MetricValueType::Text(metric_value.value), - }); - } - let metric_name = String::from("scaph_process_disk_total_read_bytes"); - if let Some(metric_value) = self.topology.get_process_disk_total_read_bytes(pid) { - self.data.push(Metric { - name: metric_name, - metric_type: String::from("gauge"), - ttl: 60.0, - timestamp: metric_value.timestamp, - hostname: self.hostname.clone(), - state: String::from("ok"), - tags: vec!["scaphandre".to_string()], - attributes: attributes.clone(), - description: String::from("Total data read on disk by the process, in bytes"), - metric_value: MetricValueType::Text(metric_value.value), - }); + if let Some(metrics) = self.topology.get_all_per_process(pid) { + for (k,v) in metrics { + self.data.push( + Metric { + name: k, + metric_type: String::from("gauge"), + ttl: 60.0, + timestamp: v.1.timestamp, + hostname: self.hostname.clone(), + state: String::from("ok"), + tags: vec!["scaphandre".to_string()], + attributes: attributes.clone(), + description: v.0, + metric_value: MetricValueType::Text(v.1.value) + } + ) + } } } } diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 09c476e4..938a321d 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -566,22 +566,18 @@ impl Topology { /// Returns the power consumed between last and previous measurement for a given process ID, in microwatts pub fn get_process_power_consumption_microwatts(&self, pid: Pid) -> Option { - let tracker = self.get_proc_tracker(); - if let Some(recs) = tracker.find_records(pid) { - if recs.len() > 1 { - let last = recs.first().unwrap(); - let process_cpu_percentage = self.get_process_cpu_usage_percentage(pid).unwrap(); - let topo_conso = self.get_records_diff_power_microwatts(); - if let Some(conso) = &topo_conso { - let conso_f64 = conso.value.parse::().unwrap(); - let result = (conso_f64 * process_cpu_percentage.value.parse::().unwrap()) - / 100.0_f64; - return Some(Record::new( - last.timestamp, - result.to_string(), - units::Unit::MicroWatt, - )); - } + if let Some(record) = self.get_proc_tracker().get_process_last_record(pid) { + let process_cpu_percentage = self.get_process_cpu_usage_percentage(pid).unwrap(); + let topo_conso = self.get_records_diff_power_microwatts(); + if let Some(conso) = &topo_conso { + let conso_f64 = conso.value.parse::().unwrap(); + let result = (conso_f64 * process_cpu_percentage.value.parse::().unwrap()) + / 100.0_f64; + return Some(Record::new( + record.timestamp, + result.to_string(), + units::Unit::MicroWatt, + )); } } else { trace!("Couldn't find records for PID: {}", pid); @@ -589,6 +585,88 @@ impl Topology { None } + pub fn get_all_per_process(&self, pid: Pid) -> Option> { + let mut res = HashMap::new(); + if let Some(record) = self.get_proc_tracker().get_process_last_record(pid) { + let process_cpu_percentage = record.process.cpu_usage_percentage / self.proc_tracker.nb_cores as f32; + res.insert( + String::from("scaph_process_cpu_usage_percentage"), + (String::from("CPU time consumed by the process, as a percentage of the capacity of all the CPU Cores"), + Record::new( + record.timestamp, + process_cpu_percentage.to_string(), + units::Unit::Percentage, + ) + ) + ); + res.insert( + String::from("scaph_process_memory_virtual_bytes"), + (String::from("Virtual RAM usage by the process, in bytes"), + Record::new( + record.timestamp, + record.process.virtual_memory.to_string(), + units::Unit::Percentage, + )) + ); + res.insert( + String::from("scaph_process_memory_bytes"), + (String::from("Physical RAM usage by the process, in bytes"), Record::new( + record.timestamp, + record.process.memory.to_string(), + units::Unit::Bytes, + )) + ); + res.insert( + String::from("scaph_process_disk_write_bytes"), + (String::from("Data written on disk by the process, in bytes"), + Record::new( + record.timestamp, + record.process.disk_written.to_string(), + units::Unit::Bytes, + ) + ) + ); + res.insert( + String::from("scaph_process_disk_read_bytes"), + (String::from("Data read on disk by the process, in bytes"), Record::new( + record.timestamp, + record.process.disk_read.to_string(), + units::Unit::Bytes, + )) + ); + res.insert( + String::from("scaph_process_disk_total_write_bytes"), + (String::from("Total data written on disk by the process, in bytes"), Record::new( + record.timestamp, + record.process.total_disk_written.to_string(), + units::Unit::Bytes, + )) + ); + res.insert( + String::from("scaph_process_disk_total_read_bytes"), + (String::from("Total data read on disk by the process, in bytes"), + Record::new( + record.timestamp, + record.process.total_disk_read.to_string(), + units::Unit::Bytes, + )) + ); + let topo_conso = self.get_records_diff_power_microwatts(); + if let Some(conso) = &topo_conso { + let conso_f64 = conso.value.parse::().unwrap(); + let result = (conso_f64 * process_cpu_percentage as f64) + / 100.0_f64; + res.insert(String::from("scaph_process_power_consumption_microwatts"), + (String::from("Total data read on disk by the process, in bytes"), Record::new( + record.timestamp, + result.to_string(), + units::Unit::MicroWatt, + ))); + } + } + Some(res) + } + // Per process metrics, from ProcessRecord during last refresh, returned in Record structs pub fn get_process_cpu_usage_percentage(&self, pid: Pid) -> Option { From c87daabd6988b65f7a3e6112c12e45b8e8bcde64 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Fri, 17 Mar 2023 18:33:38 +0100 Subject: [PATCH 053/303] style: fmt + clippy --- src/exporters/mod.rs | 35 ++++++------ src/exporters/prometheus.rs | 2 +- src/exporters/riemann.rs | 2 +- src/exporters/stdout.rs | 5 +- src/sensors/mod.rs | 107 ++++++++++++++++++++---------------- 5 files changed, 82 insertions(+), 69 deletions(-) diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index f3d54d24..cf6bbe54 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -59,12 +59,11 @@ struct Metric { timestamp: Duration, } -#[derive(Clone)] enum MetricValueType { // IntSigned(i64), // Float(f32), Text(String), - FloatDouble(f64), + //FloatDouble(f64), IntUnsigned(u64), } @@ -74,7 +73,7 @@ impl fmt::Display for MetricValueType { // MetricValueType::IntSigned(value) => write!(f, "{}", value), // MetricValueType::Float(value) => write!(f, "{}", value), MetricValueType::Text(text) => write!(f, "{text}"), - MetricValueType::FloatDouble(value) => write!(f, "{value}"), + //MetricValueType::FloatDouble(value) => write!(f, "{value}"), MetricValueType::IntUnsigned(value) => write!(f, "{value}"), } } @@ -86,7 +85,7 @@ impl fmt::Debug for MetricValueType { // MetricValueType::IntSigned(value) => write!(f, "{}", value), // MetricValueType::Float(value) => write!(f, "{}", value), MetricValueType::Text(text) => write!(f, "{text}"), - MetricValueType::FloatDouble(value) => write!(f, "{value}"), + //MetricValueType::FloatDouble(value) => write!(f, "{value}"), MetricValueType::IntUnsigned(value) => write!(f, "{value}"), } } @@ -780,21 +779,19 @@ impl MetricGenerator { } if let Some(metrics) = self.topology.get_all_per_process(pid) { - for (k,v) in metrics { - self.data.push( - Metric { - name: k, - metric_type: String::from("gauge"), - ttl: 60.0, - timestamp: v.1.timestamp, - hostname: self.hostname.clone(), - state: String::from("ok"), - tags: vec!["scaphandre".to_string()], - attributes: attributes.clone(), - description: v.0, - metric_value: MetricValueType::Text(v.1.value) - } - ) + for (k, v) in metrics { + self.data.push(Metric { + name: k, + metric_type: String::from("gauge"), + ttl: 60.0, + timestamp: v.1.timestamp, + hostname: self.hostname.clone(), + state: String::from("ok"), + tags: vec!["scaphandre".to_string()], + attributes: attributes.clone(), + description: v.0, + metric_value: MetricValueType::Text(v.1.value), + }) } } } diff --git a/src/exporters/prometheus.rs b/src/exporters/prometheus.rs index 00ec1a34..92f474db 100644 --- a/src/exporters/prometheus.rs +++ b/src/exporters/prometheus.rs @@ -270,7 +270,7 @@ async fn show_metrics( let value = match msg.metric_value { // MetricValueType::IntSigned(value) => event.set_metric_sint64(value), // MetricValueType::Float(value) => event.set_metric_f(value), - MetricValueType::FloatDouble(value) => value.to_string(), + //MetricValueType::FloatDouble(value) => value.to_string(), MetricValueType::IntUnsigned(value) => value.to_string(), MetricValueType::Text(ref value) => value.to_string(), }; diff --git a/src/exporters/riemann.rs b/src/exporters/riemann.rs index 5533ef14..c624f074 100644 --- a/src/exporters/riemann.rs +++ b/src/exporters/riemann.rs @@ -82,7 +82,7 @@ impl RiemannClient { match metric.metric_value { // MetricValueType::IntSigned(value) => event.set_metric_sint64(value), // MetricValueType::Float(value) => event.set_metric_f(value), - MetricValueType::FloatDouble(value) => event.set_metric_d(value), + //MetricValueType::FloatDouble(value) => event.set_metric_d(value), MetricValueType::IntUnsigned(value) => event.set_metric_sint64( i64::try_from(value).expect("Metric cannot be converted to signed integer."), ), diff --git a/src/exporters/stdout.rs b/src/exporters/stdout.rs index 46c9e83b..f60fcd97 100644 --- a/src/exporters/stdout.rs +++ b/src/exporters/stdout.rs @@ -167,9 +167,10 @@ impl StdoutExporter { let metrics = metric_generator.pop_metrics(); let mut metrics_iter = metrics.iter(); + let none_value = MetricValueType::Text("0".to_string()); let host_power = match metrics_iter.find(|x| x.name == "scaph_host_power_microwatts") { - Some(m) => m.metric_value.clone(), - None => MetricValueType::Text("0".to_string()), + Some(m) => &m.metric_value, + None => &none_value, }; let domain_names = metric_generator.topology.domains_names.as_ref(); diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 938a321d..7f578d7b 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -571,8 +571,8 @@ impl Topology { let topo_conso = self.get_records_diff_power_microwatts(); if let Some(conso) = &topo_conso { let conso_f64 = conso.value.parse::().unwrap(); - let result = (conso_f64 * process_cpu_percentage.value.parse::().unwrap()) - / 100.0_f64; + let result = + (conso_f64 * process_cpu_percentage.value.parse::().unwrap()) / 100.0_f64; return Some(Record::new( record.timestamp, result.to_string(), @@ -588,80 +588,95 @@ impl Topology { pub fn get_all_per_process(&self, pid: Pid) -> Option> { let mut res = HashMap::new(); if let Some(record) = self.get_proc_tracker().get_process_last_record(pid) { - let process_cpu_percentage = record.process.cpu_usage_percentage / self.proc_tracker.nb_cores as f32; + let process_cpu_percentage = + record.process.cpu_usage_percentage / self.proc_tracker.nb_cores as f32; res.insert( String::from("scaph_process_cpu_usage_percentage"), (String::from("CPU time consumed by the process, as a percentage of the capacity of all the CPU Cores"), Record::new( record.timestamp, - process_cpu_percentage.to_string(), + process_cpu_percentage.to_string(), units::Unit::Percentage, ) ) ); res.insert( String::from("scaph_process_memory_virtual_bytes"), - (String::from("Virtual RAM usage by the process, in bytes"), - Record::new( - record.timestamp, - record.process.virtual_memory.to_string(), - units::Unit::Percentage, - )) + ( + String::from("Virtual RAM usage by the process, in bytes"), + Record::new( + record.timestamp, + record.process.virtual_memory.to_string(), + units::Unit::Percentage, + ), + ), ); res.insert( String::from("scaph_process_memory_bytes"), - (String::from("Physical RAM usage by the process, in bytes"), Record::new( - record.timestamp, - record.process.memory.to_string(), - units::Unit::Bytes, - )) + ( + String::from("Physical RAM usage by the process, in bytes"), + Record::new( + record.timestamp, + record.process.memory.to_string(), + units::Unit::Bytes, + ), + ), ); res.insert( String::from("scaph_process_disk_write_bytes"), - (String::from("Data written on disk by the process, in bytes"), - Record::new( - record.timestamp, - record.process.disk_written.to_string(), - units::Unit::Bytes, - ) - ) + ( + String::from("Data written on disk by the process, in bytes"), + Record::new( + record.timestamp, + record.process.disk_written.to_string(), + units::Unit::Bytes, + ), + ), ); res.insert( String::from("scaph_process_disk_read_bytes"), - (String::from("Data read on disk by the process, in bytes"), Record::new( - record.timestamp, - record.process.disk_read.to_string(), - units::Unit::Bytes, - )) + ( + String::from("Data read on disk by the process, in bytes"), + Record::new( + record.timestamp, + record.process.disk_read.to_string(), + units::Unit::Bytes, + ), + ), ); res.insert( String::from("scaph_process_disk_total_write_bytes"), - (String::from("Total data written on disk by the process, in bytes"), Record::new( - record.timestamp, - record.process.total_disk_written.to_string(), - units::Unit::Bytes, - )) + ( + String::from("Total data written on disk by the process, in bytes"), + Record::new( + record.timestamp, + record.process.total_disk_written.to_string(), + units::Unit::Bytes, + ), + ), ); res.insert( String::from("scaph_process_disk_total_read_bytes"), - (String::from("Total data read on disk by the process, in bytes"), - Record::new( - record.timestamp, - record.process.total_disk_read.to_string(), - units::Unit::Bytes, - )) + ( + String::from("Total data read on disk by the process, in bytes"), + Record::new( + record.timestamp, + record.process.total_disk_read.to_string(), + units::Unit::Bytes, + ), + ), ); let topo_conso = self.get_records_diff_power_microwatts(); if let Some(conso) = &topo_conso { let conso_f64 = conso.value.parse::().unwrap(); - let result = (conso_f64 * process_cpu_percentage as f64) - / 100.0_f64; - res.insert(String::from("scaph_process_power_consumption_microwatts"), - (String::from("Total data read on disk by the process, in bytes"), Record::new( - record.timestamp, - result.to_string(), - units::Unit::MicroWatt, - ))); + let result = (conso_f64 * process_cpu_percentage as f64) / 100.0_f64; + res.insert( + String::from("scaph_process_power_consumption_microwatts"), + ( + String::from("Total data read on disk by the process, in bytes"), + Record::new(record.timestamp, result.to_string(), units::Unit::MicroWatt), + ), + ); } } Some(res) From 5c908558c3489fa69af7868e6fadd831e2f6ab04 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 20 Mar 2023 09:50:59 +0100 Subject: [PATCH 054/303] fix: troubleshooting prometheus exporter --- src/exporters/prometheus.rs | 112 +++++++++++++++++++----------------- 1 file changed, 58 insertions(+), 54 deletions(-) diff --git a/src/exporters/prometheus.rs b/src/exporters/prometheus.rs index 92f474db..a220d3d8 100644 --- a/src/exporters/prometheus.rs +++ b/src/exporters/prometheus.rs @@ -232,70 +232,74 @@ async fn show_metrics( if req.uri().path() == format!("/{}", &suffix) { debug!("in metrics !"); let now = current_system_time_since_epoch(); - let mut last_request = context.last_request.lock().unwrap(); - debug!("stored last request !"); - match context.metric_generator.lock() { - Ok(mut metric_generator) => { - debug!("stored metric generator !"); - if now - (*last_request) > Duration::from_secs(2) { - { - info!( - "{}: Refresh topology", - Utc::now().format("%Y-%m-%dT%H:%M:%S") - ); - metric_generator - .topology - .proc_tracker - .clean_terminated_process_records_vectors(); - metric_generator.topology.refresh(); - } - } - debug!("refreshed topology !"); - *last_request = now; + match context.last_request.lock() { + Ok(mut last_request) => { + debug!("stored last request !"); + match context.metric_generator.lock() { + Ok(mut metric_generator) => { + debug!("stored metric generator !"); + if now - (*last_request) > Duration::from_secs(2) { + { + info!( + "{}: Refresh topology", + Utc::now().format("%Y-%m-%dT%H:%M:%S") + ); + metric_generator + .topology + .proc_tracker + .clean_terminated_process_records_vectors(); + metric_generator.topology.refresh(); + } + } + debug!("refreshed topology !"); + *last_request = now; - info!("{}: Refresh data", Utc::now().format("%Y-%m-%dT%H:%M:%S")); + info!("{}: Refresh data", Utc::now().format("%Y-%m-%dT%H:%M:%S")); - metric_generator.gen_all_metrics(); + metric_generator.gen_all_metrics(); - let mut metrics_pushed: Vec = vec![]; + let mut metrics_pushed: Vec = vec![]; - debug!("popping metrics !"); - // Send all data - for msg in metric_generator.pop_metrics() { - let mut attributes: Option<&HashMap> = None; - if !msg.attributes.is_empty() { - attributes = Some(&msg.attributes); - } + debug!("popping metrics !"); + // Send all data + for msg in metric_generator.pop_metrics() { + let mut attributes: Option<&HashMap> = None; + if !msg.attributes.is_empty() { + attributes = Some(&msg.attributes); + } - let value = match msg.metric_value { - // MetricValueType::IntSigned(value) => event.set_metric_sint64(value), - // MetricValueType::Float(value) => event.set_metric_f(value), - //MetricValueType::FloatDouble(value) => value.to_string(), - MetricValueType::IntUnsigned(value) => value.to_string(), - MetricValueType::Text(ref value) => value.to_string(), - }; + let value = match msg.metric_value { + // MetricValueType::IntSigned(value) => event.set_metric_sint64(value), + // MetricValueType::Float(value) => event.set_metric_f(value), + //MetricValueType::FloatDouble(value) => value.to_string(), + MetricValueType::IntUnsigned(value) => value.to_string(), + MetricValueType::Text(ref value) => value.to_string(), + }; - let mut should_i_add_help = true; + let mut should_i_add_help = true; - if metrics_pushed.contains(&msg.name) { - should_i_add_help = false; - } else { - metrics_pushed.insert(0, msg.name.clone()); - } + if metrics_pushed.contains(&msg.name) { + should_i_add_help = false; + } else { + metrics_pushed.insert(0, msg.name.clone()); + } - body = push_metric( - body, - msg.description.clone(), - msg.metric_type.clone(), - msg.name.clone(), - format_metric(&msg.name, &value, attributes), - should_i_add_help, - ); + body = push_metric( + body, + msg.description.clone(), + msg.metric_type.clone(), + msg.name.clone(), + format_metric(&msg.name, &value, attributes), + should_i_add_help, + ); + } + } + Err(e) => { + panic!("Error from lock(): {}", e); + } } } - Err(e) => { - panic!("Error from lock(): {}", e); - } + Err(e) => {warn!("Error in show_metrics : {e:?}");} } } else { let _ = write!(body, "Scaphandre's prometheus exporter here. Metrics available on /{suffix}"); From 07c7bdcf6c36f2981a2e9b6211795fe44c64f35d Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 20 Mar 2023 09:55:12 +0100 Subject: [PATCH 055/303] style: fmt --- src/exporters/prometheus.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/exporters/prometheus.rs b/src/exporters/prometheus.rs index a220d3d8..cd37ea09 100644 --- a/src/exporters/prometheus.rs +++ b/src/exporters/prometheus.rs @@ -299,7 +299,9 @@ async fn show_metrics( } } } - Err(e) => {warn!("Error in show_metrics : {e:?}");} + Err(e) => { + warn!("Error in show_metrics : {e:?}"); + } } } else { let _ = write!(body, "Scaphandre's prometheus exporter here. Metrics available on /{suffix}"); From 185e3f777b37f1599154a3f70d69454fd7f23d86 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 20 Mar 2023 10:09:56 +0100 Subject: [PATCH 056/303] style: fmt --- src/exporters/prometheus.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/exporters/prometheus.rs b/src/exporters/prometheus.rs index cd37ea09..eaab4d6c 100644 --- a/src/exporters/prometheus.rs +++ b/src/exporters/prometheus.rs @@ -301,6 +301,7 @@ async fn show_metrics( } Err(e) => { warn!("Error in show_metrics : {e:?}"); + warn!("Error details : {}", e.to_string()); } } } else { From 7fc5a3b920b70b4483aa3d043dc04908ee8afa43 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 20 Mar 2023 10:27:55 +0100 Subject: [PATCH 057/303] style: fmt --- src/exporters/prometheus.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/exporters/prometheus.rs b/src/exporters/prometheus.rs index eaab4d6c..5658f13c 100644 --- a/src/exporters/prometheus.rs +++ b/src/exporters/prometheus.rs @@ -295,13 +295,14 @@ async fn show_metrics( } } Err(e) => { - panic!("Error from lock(): {}", e); + error!("Error while locking metric_generator: {e:?}"); + error!("Error while locking metric_generator: {}", e.to_string()); } } } Err(e) => { - warn!("Error in show_metrics : {e:?}"); - warn!("Error details : {}", e.to_string()); + error!("Error in show_metrics : {e:?}"); + error!("Error details : {}", e.to_string()); } } } else { From 7c16c4769c7d332fcd6d6f0636ea6d2561257511 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 20 Mar 2023 10:54:44 +0100 Subject: [PATCH 058/303] fix: troubleshooting prometheus exporter --- docker-compose/docker-compose-dev.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose/docker-compose-dev.yaml b/docker-compose/docker-compose-dev.yaml index f3c39592..d754df89 100644 --- a/docker-compose/docker-compose-dev.yaml +++ b/docker-compose/docker-compose-dev.yaml @@ -16,7 +16,7 @@ services: - type: bind source: /var/run/docker.sock target: /var/run/docker.sock - command: ["-vvv", "prometheus", "--containers"] + command: ["-vvvvt ", "prometheus", "--containers"] networks: - scaphandre-network From 8c2531620b844969c3eca6c484a69fda7d5a5ec6 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 20 Mar 2023 10:59:50 +0100 Subject: [PATCH 059/303] fix: troubleshooting prometheus exporter --- docker-compose/docker-compose-dev.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose/docker-compose-dev.yaml b/docker-compose/docker-compose-dev.yaml index d754df89..45c6b802 100644 --- a/docker-compose/docker-compose-dev.yaml +++ b/docker-compose/docker-compose-dev.yaml @@ -16,7 +16,7 @@ services: - type: bind source: /var/run/docker.sock target: /var/run/docker.sock - command: ["-vvvvt ", "prometheus", "--containers"] + command: ["-vvvv", "prometheus", "--containers"] networks: - scaphandre-network From 2d018b0dccba555851c459f918950e8c5e794637 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 20 Mar 2023 11:05:54 +0100 Subject: [PATCH 060/303] fix: troubleshooting prometheus exporter --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e622898e..43edb2f1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,4 +32,4 @@ RUN apt-get update \ && rm -rf /var/lib/apt/lists/* COPY --from=builder /app/target/release/scaphandre /usr/local/bin -ENTRYPOINT ["/usr/local/bin/scaphandre"] +ENTRYPOINT ["RUST_BACKTRACE=1", "/usr/local/bin/scaphandre"] From bc7e619df157fa0e18ae4baadd3798f722a2a5d2 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 20 Mar 2023 11:13:50 +0100 Subject: [PATCH 061/303] fix: troubleshooting prometheus exporter --- Dockerfile | 2 +- docker-compose/docker-compose-dev.yaml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 43edb2f1..e622898e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,4 +32,4 @@ RUN apt-get update \ && rm -rf /var/lib/apt/lists/* COPY --from=builder /app/target/release/scaphandre /usr/local/bin -ENTRYPOINT ["RUST_BACKTRACE=1", "/usr/local/bin/scaphandre"] +ENTRYPOINT ["/usr/local/bin/scaphandre"] diff --git a/docker-compose/docker-compose-dev.yaml b/docker-compose/docker-compose-dev.yaml index 45c6b802..230c6707 100644 --- a/docker-compose/docker-compose-dev.yaml +++ b/docker-compose/docker-compose-dev.yaml @@ -17,6 +17,8 @@ services: source: /var/run/docker.sock target: /var/run/docker.sock command: ["-vvvv", "prometheus", "--containers"] + environment: + RUST_BACKTRACE: 1 networks: - scaphandre-network From 0ff427a20b98a91651896aed89b17101b36ff130 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 20 Mar 2023 11:27:05 +0100 Subject: [PATCH 062/303] fix: troubleshooting prometheus exporter --- docker-compose/docker-compose-dev.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose/docker-compose-dev.yaml b/docker-compose/docker-compose-dev.yaml index 230c6707..8c77befe 100644 --- a/docker-compose/docker-compose-dev.yaml +++ b/docker-compose/docker-compose-dev.yaml @@ -18,7 +18,7 @@ services: target: /var/run/docker.sock command: ["-vvvv", "prometheus", "--containers"] environment: - RUST_BACKTRACE: 1 + RUST_BACKTRACE: "full" networks: - scaphandre-network From 6bc03c82d5abcac5c58bdd18614f5db5e799bcb4 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 20 Mar 2023 11:38:05 +0100 Subject: [PATCH 063/303] fix: troubleshooting prometheus exporter --- src/sensors/utils.rs | 236 +++++++++++++++++++++++-------------------- 1 file changed, 125 insertions(+), 111 deletions(-) diff --git a/src/sensors/utils.rs b/src/sensors/utils.rs index 3e3609b4..d35bef84 100644 --- a/src/sensors/utils.rs +++ b/src/sensors/utils.rs @@ -482,135 +482,149 @@ impl ProcessTracker { let regex_clean_container_id = Regex::new("[[:alnum:]]{12,}").unwrap(); if let Some(_p) = process.get(0) { // if we have the cgroups data from the original process struct - let procfs_process = - procfs::process::Process::new(pid.to_string().parse::().unwrap()).unwrap(); - if let Ok(cgroups) = procfs_process.cgroups() { - let mut found = false; - for cg in &cgroups { - if found { - break; - } - // docker - if self.regex_cgroup_docker.is_match(&cg.pathname) { - debug!("regex docker matched : {}", &cg.pathname); //coucou - description - .insert(String::from("container_scheduler"), String::from("docker")); - // extract container_id - //let container_id = cg.pathname.split('/').last().unwrap(); - if let Some(container_id_capture) = - regex_clean_container_id.captures(&cg.pathname) - { - let container_id = &container_id_capture[0]; - debug!("container_id = {}", container_id); - description - .insert(String::from("container_id"), String::from(container_id)); - if let Some(container) = - containers.iter().find(|x| x.Id == container_id) + if let Ok(procfs_process) = + procfs::process::Process::new(pid.to_string().parse::().unwrap()) + { + if let Ok(cgroups) = procfs_process.cgroups() { + let mut found = false; + for cg in &cgroups { + if found { + break; + } + // docker + if self.regex_cgroup_docker.is_match(&cg.pathname) { + debug!("regex docker matched : {}", &cg.pathname); //coucou + description.insert( + String::from("container_scheduler"), + String::from("docker"), + ); + // extract container_id + //let container_id = cg.pathname.split('/').last().unwrap(); + if let Some(container_id_capture) = + regex_clean_container_id.captures(&cg.pathname) { - debug!("found container with id: {}", &container_id); - let mut names = String::from(""); - for n in &container.Names { - debug!("adding container name: {}", &n.trim().replace('/', "")); - names.push_str(&n.trim().replace('/', "")); - } - description.insert(String::from("container_names"), names); + let container_id = &container_id_capture[0]; + debug!("container_id = {}", container_id); description.insert( - String::from("container_docker_version"), - docker_version.clone(), + String::from("container_id"), + String::from(container_id), ); - if let Some(labels) = &container.Labels { - for (k, v) in labels { - let escape_list = ["-", ".", ":", " "]; - let mut key = k.clone(); - for e in escape_list.iter() { - key = key.replace(e, "_"); - } - description.insert( - format!("container_label_{key}"), - v.to_string(), + if let Some(container) = + containers.iter().find(|x| x.Id == container_id) + { + debug!("found container with id: {}", &container_id); + let mut names = String::from(""); + for n in &container.Names { + debug!( + "adding container name: {}", + &n.trim().replace('/', "") ); + names.push_str(&n.trim().replace('/', "")); + } + description.insert(String::from("container_names"), names); + description.insert( + String::from("container_docker_version"), + docker_version.clone(), + ); + if let Some(labels) = &container.Labels { + for (k, v) in labels { + let escape_list = ["-", ".", ":", " "]; + let mut key = k.clone(); + for e in escape_list.iter() { + key = key.replace(e, "_"); + } + description.insert( + format!("container_label_{key}"), + v.to_string(), + ); + } } } + found = true; } - found = true; - } - } else { - // containerd - if self.regex_cgroup_containerd.is_match(&cg.pathname) { - debug!("regex containerd matched : {}", &cg.pathname); - description.insert( - String::from("container_runtime"), - String::from("containerd"), - ); - } else if self.regex_cgroup_kubernetes.is_match(&cg.pathname) { - debug!("regex kubernetes matched : {}", &cg.pathname); - // kubernetes not using containerd but we can get the container id } else { - // cgroup not related to a container technology - continue; - } + // containerd + if self.regex_cgroup_containerd.is_match(&cg.pathname) { + debug!("regex containerd matched : {}", &cg.pathname); + description.insert( + String::from("container_runtime"), + String::from("containerd"), + ); + } else if self.regex_cgroup_kubernetes.is_match(&cg.pathname) { + debug!("regex kubernetes matched : {}", &cg.pathname); + // kubernetes not using containerd but we can get the container id + } else { + // cgroup not related to a container technology + continue; + } - let container_id = - match self.extract_pod_id_from_cgroup_path(cg.pathname.clone()) { - Ok(id) => id, - Err(err) => { - info!("Couldn't get container id : {}", err); - "ERROR Couldn't get container id".to_string() - } - }; - description.insert(String::from("container_id"), container_id.clone()); - // find pod in pods that has pod_status > container_status.container - if let Some(pod) = pods.iter().find(|x| match &x.status { - Some(status) => { - if let Some(container_statuses) = &status.container_statuses { - container_statuses.iter().any(|y| match &y.container_id { - Some(id) => { - if let Some(final_id) = id.strip_prefix("docker://") { - final_id == container_id - } else if let Some(final_id) = - id.strip_prefix("containerd://") - { - final_id == container_id - } else { - false + let container_id = + match self.extract_pod_id_from_cgroup_path(cg.pathname.clone()) { + Ok(id) => id, + Err(err) => { + info!("Couldn't get container id : {}", err); + "ERROR Couldn't get container id".to_string() + } + }; + description.insert(String::from("container_id"), container_id.clone()); + // find pod in pods that has pod_status > container_status.container + if let Some(pod) = pods.iter().find(|x| match &x.status { + Some(status) => { + if let Some(container_statuses) = &status.container_statuses { + container_statuses.iter().any(|y| match &y.container_id { + Some(id) => { + if let Some(final_id) = id.strip_prefix("docker://") + { + final_id == container_id + } else if let Some(final_id) = + id.strip_prefix("containerd://") + { + final_id == container_id + } else { + false + } } - } - None => false, - }) - } else { - false + None => false, + }) + } else { + false + } } - } - None => false, - }) { - description.insert( - String::from("container_scheduler"), - String::from("kubernetes"), - ); - if let Some(pod_name) = &pod.metadata.name { - description - .insert(String::from("kubernetes_pod_name"), pod_name.clone()); - } - if let Some(pod_namespace) = &pod.metadata.namespace { + None => false, + }) { description.insert( - String::from("kubernetes_pod_namespace"), - pod_namespace.clone(), + String::from("container_scheduler"), + String::from("kubernetes"), ); - } - if let Some(pod_spec) = &pod.spec { - if let Some(node_name) = &pod_spec.node_name { + if let Some(pod_name) = &pod.metadata.name { + description.insert( + String::from("kubernetes_pod_name"), + pod_name.clone(), + ); + } + if let Some(pod_namespace) = &pod.metadata.namespace { description.insert( - String::from("kubernetes_node_name"), - node_name.clone(), + String::from("kubernetes_pod_namespace"), + pod_namespace.clone(), ); } + if let Some(pod_spec) = &pod.spec { + if let Some(node_name) = &pod_spec.node_name { + description.insert( + String::from("kubernetes_node_name"), + node_name.clone(), + ); + } + } } - } - found = true; - } //else { - // debug!("Cgroup not identified as related to a container technology : {}", &cg.pathname); - //} + found = true; + } //else { + // debug!("Cgroup not identified as related to a container technology : {}", &cg.pathname); + //} + } } + } else { + debug!("Could'nt find {} in procfs.", pid.to_string()); } } description From baf1bcae7e74f7156aeece0c0c1a487e2615ab6e Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 20 Mar 2023 13:43:43 +0100 Subject: [PATCH 064/303] style: cleaning comments and debug messages --- src/exporters/prometheus.rs | 5 ----- src/sensors/utils.rs | 27 --------------------------- 2 files changed, 32 deletions(-) diff --git a/src/exporters/prometheus.rs b/src/exporters/prometheus.rs index 5658f13c..b1839641 100644 --- a/src/exporters/prometheus.rs +++ b/src/exporters/prometheus.rs @@ -230,14 +230,11 @@ async fn show_metrics( trace!("{}", req.uri()); let mut body = String::new(); if req.uri().path() == format!("/{}", &suffix) { - debug!("in metrics !"); let now = current_system_time_since_epoch(); match context.last_request.lock() { Ok(mut last_request) => { - debug!("stored last request !"); match context.metric_generator.lock() { Ok(mut metric_generator) => { - debug!("stored metric generator !"); if now - (*last_request) > Duration::from_secs(2) { { info!( @@ -251,7 +248,6 @@ async fn show_metrics( metric_generator.topology.refresh(); } } - debug!("refreshed topology !"); *last_request = now; info!("{}: Refresh data", Utc::now().format("%Y-%m-%dT%H:%M:%S")); @@ -260,7 +256,6 @@ async fn show_metrics( let mut metrics_pushed: Vec = vec![]; - debug!("popping metrics !"); // Send all data for msg in metric_generator.pop_metrics() { let mut attributes: Option<&HashMap> = None; diff --git a/src/sensors/utils.rs b/src/sensors/utils.rs index d35bef84..9431427b 100644 --- a/src/sensors/utils.rs +++ b/src/sensors/utils.rs @@ -380,33 +380,6 @@ impl ProcessTracker { refer } - /// Returns the result of the substraction of utime between last and - /// previous ProcessRecord for a given pid. - //pub fn get_diff_utime(&self, pid: Pid) -> Option { - // let records = self.find_records(pid).unwrap(); - // if records.len() > 1 { - // if let Some(previous) = &records[0].process.stat { - // if let Some(current) = &records[1].process.stat { - // return Some(previous.utime - current.utime); - // } - // } - // } - // None - //} - /// Returns the result of the substraction of stime between last and - /// previous ProcessRecord for a given pid. - //pub fn get_diff_stime(&self, pid: Pid) -> Option { - // let records = self.find_records(pid).unwrap(); - // if records.len() > 1 { - // if let Some(previous) = &records[0].process.stat { - // if let Some(current) = &records[1].process.stat { - // return Some(previous.stime - current.stime); - // } - // } - // } - // None - //} - pub fn get_cpu_frequency(&self) -> u64 { self.sysinfo.global_cpu_info().frequency() } From 1170985a8e80b56e0a1bdb40b3dff1d18f2da574 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 20 Mar 2023 13:46:24 +0100 Subject: [PATCH 065/303] chore: update sample dahsboard --- .../dashboards/sample-dashboard.json | 123 ++++++++++++++++-- docker-compose/sample.jsonnet | 45 +++++-- 2 files changed, 145 insertions(+), 23 deletions(-) diff --git a/docker-compose/dashboards/sample-dashboard.json b/docker-compose/dashboards/sample-dashboard.json index 8de31c05..4ed31395 100644 --- a/docker-compose/dashboards/sample-dashboard.json +++ b/docker-compose/dashboards/sample-dashboard.json @@ -59,6 +59,20 @@ "intervalFactor": 2, "legendFormat": "{{instance}}", "refId": "A" + }, + { + "expr": "sum(scaph_process_power_consumption_microwatts) / 1000000", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "sum of processes power", + "refId": "B" + }, + { + "expr": "sum(scaph_domain_power_microwatts) / 1000000", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "sum of rapl domains power", + "refId": "C" } ], "thresholds": [ ], @@ -322,7 +336,7 @@ "repeat": null, "seriesOverrides": [ ], "spaceLength": 10, - "span": 4, + "span": 3, "stack": false, "steppedLine": false, "targets": [ @@ -403,7 +417,88 @@ "repeat": null, "seriesOverrides": [ ], "spaceLength": 10, - "span": 4, + "span": 3, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "scaph_domain_power_microwatts / 1000000", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{domain_name}}", + "refId": "A" + } + ], + "thresholds": [ ], + "timeFrom": null, + "timeShift": null, + "title": "scaph_domain_power", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [ ] + }, + "yaxes": [ + { + "format": "W", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "W", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + } + ] + }, + { + "aliasColors": { }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${PROMETHEUS_DS}", + "fill": 1, + "fillGradient": 0, + "gridPos": { }, + "id": 7, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "sideWidth": null, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [ ], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": null, + "seriesOverrides": [ ], + "spaceLength": 10, + "span": 3, "stack": false, "steppedLine": false, "targets": [ @@ -460,7 +555,7 @@ "fill": 1, "fillGradient": 0, "gridPos": { }, - "id": 7, + "id": 8, "legend": { "alignAsTable": false, "avg": false, @@ -484,7 +579,7 @@ "repeat": null, "seriesOverrides": [ ], "spaceLength": 10, - "span": 4, + "span": 3, "stack": false, "steppedLine": false, "targets": [ @@ -506,7 +601,7 @@ "thresholds": [ ], "timeFrom": null, "timeShift": null, - "title": "Socket power consumption", + "title": "scaph_self_mem", "tooltip": { "shared": true, "sort": 0, @@ -561,7 +656,7 @@ "fill": 1, "fillGradient": 0, "gridPos": { }, - "id": 8, + "id": 9, "legend": { "alignAsTable": true, "avg": false, @@ -590,7 +685,7 @@ "steppedLine": false, "targets": [ { - "expr": "scaph_process_power_consumption_microwatts{exe=~\".*${process_filter}.*\"}/1000000", + "expr": "scaph_process_power_consumption_microwatts{cmdline=~\".*${process_filter}.*\"}/1000000", "format": "time_series", "intervalFactor": 2, "legendFormat": "{{ cmdline }}", @@ -600,7 +695,7 @@ "thresholds": [ ], "timeFrom": null, "timeShift": null, - "title": "Filtered process (process_filter) power, by exe", + "title": "Filtered process (process_filter) power, by cmdline", "tooltip": { "shared": true, "sort": 0, @@ -642,7 +737,7 @@ "fill": 1, "fillGradient": 0, "gridPos": { }, - "id": 9, + "id": 10, "legend": { "alignAsTable": true, "avg": false, @@ -671,7 +766,7 @@ "steppedLine": false, "targets": [ { - "expr": "scaph_process_cpu_usage_percentage{exe=~\".*${process_filter}.*\"}", + "expr": "scaph_process_cpu_usage_percentage{cmdline=~\".*${process_filter}.*\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "{{ cmdline }}", @@ -723,7 +818,7 @@ "fill": 1, "fillGradient": 0, "gridPos": { }, - "id": 10, + "id": 11, "legend": { "alignAsTable": true, "avg": false, @@ -752,7 +847,7 @@ "steppedLine": false, "targets": [ { - "expr": "scaph_process_memory_bytes{exe=~\".*${process_filter}.*\"}", + "expr": "scaph_process_memory_bytes{cmdline=~\".*${process_filter}.*\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "{{ cmdline }}", @@ -804,7 +899,7 @@ "fill": 1, "fillGradient": 0, "gridPos": { }, - "id": 11, + "id": 12, "legend": { "alignAsTable": true, "avg": false, @@ -833,7 +928,7 @@ "steppedLine": false, "targets": [ { - "expr": "scaph_process_memory_virtual_bytes{exe=~\".*${process_filter}.*\"}", + "expr": "scaph_process_memory_virtual_bytes{cmdline=~\".*${process_filter}.*\"}", "format": "time_series", "intervalFactor": 2, "legendFormat": "{{ cmdline }}", diff --git a/docker-compose/sample.jsonnet b/docker-compose/sample.jsonnet index c4eae0da..cd50f50f 100644 --- a/docker-compose/sample.jsonnet +++ b/docker-compose/sample.jsonnet @@ -41,6 +41,18 @@ dashboard.new( legendFormat='{{instance}}', ) ) + .addTarget( + grafana.prometheus.target( + 'sum(scaph_process_power_consumption_microwatts) / 1000000', + legendFormat='sum of processes power', + ) + ) + .addTarget( + grafana.prometheus.target( + 'sum(scaph_domain_power_microwatts) / 1000000', + legendFormat='sum of rapl domains power', + ) + ) ) .addPanel( grafana.graphPanel.new( @@ -97,7 +109,7 @@ dashboard.new( title='Socket power consumption', datasource='${PROMETHEUS_DS}', format='W', - span=4, + span=3, min=0 ) .addTarget( @@ -107,12 +119,27 @@ dashboard.new( ) ) ) + .addPanel( + grafana.graphPanel.new( + title='scaph_domain_power', + datasource='${PROMETHEUS_DS}', + format='W', + span=3, + min=0 + ) + .addTarget( + grafana.prometheus.target( + 'scaph_domain_power_microwatts / 1000000', + legendFormat='{{domain_name}}', + ) + ) + ) .addPanel( grafana.graphPanel.new( title='scaph_self_cpu', datasource='${PROMETHEUS_DS}', format='%', - span=4, + span=3, min=0 ) .addTarget( @@ -124,10 +151,10 @@ dashboard.new( ) .addPanel( grafana.graphPanel.new( - title='Socket power consumption', + title='scaph_self_mem', datasource='${PROMETHEUS_DS}', format='Bytes', - span=4, + span=3, min=0 ) .addTarget( @@ -150,7 +177,7 @@ dashboard.new( ) .addPanel( grafana.graphPanel.new( - title='Filtered process (process_filter) power, by exe', + title='Filtered process (process_filter) power, by cmdline', datasource='${PROMETHEUS_DS}', span=3, format='W', @@ -162,7 +189,7 @@ dashboard.new( ) .addTarget( grafana.prometheus.target( - 'scaph_process_power_consumption_microwatts{exe=~".*${process_filter}.*"}/1000000', + 'scaph_process_power_consumption_microwatts{cmdline=~".*${process_filter}.*"}/1000000', legendFormat='{{ cmdline }}', ) ) @@ -181,7 +208,7 @@ dashboard.new( ) .addTarget( grafana.prometheus.target( - 'scaph_process_cpu_usage_percentage{exe=~".*${process_filter}.*"}', + 'scaph_process_cpu_usage_percentage{cmdline=~".*${process_filter}.*"}', legendFormat='{{ cmdline }}', ) ) @@ -200,7 +227,7 @@ dashboard.new( ) .addTarget( grafana.prometheus.target( - 'scaph_process_memory_bytes{exe=~".*${process_filter}.*"}', + 'scaph_process_memory_bytes{cmdline=~".*${process_filter}.*"}', legendFormat='{{ cmdline }}', ) ) @@ -219,7 +246,7 @@ dashboard.new( ) .addTarget( grafana.prometheus.target( - 'scaph_process_memory_virtual_bytes{exe=~".*${process_filter}.*"}', + 'scaph_process_memory_virtual_bytes{cmdline=~".*${process_filter}.*"}', legendFormat='{{ cmdline }}', ) ) From a5f1606b933d1170da07ea0615325df1f7b23c23 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 20 Mar 2023 19:35:49 +0100 Subject: [PATCH 066/303] chore: bumped sysinfo version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1cf6fc80..0bf768bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ docker-sync = { version = "0.1.2", optional = true } k8s-sync = { version = "0.2.3", optional = true } hyper = { version = "0.14", features = ["full"], optional = true } tokio = { version = "1.26.0", features = ["full"], optional = true} -sysinfo = { version = "0.28.2"} +sysinfo = { version = "0.28.3"} [target.'cfg(target_os="linux")'.dependencies] procfs = { version = "0.12.0" } From 6e125d427a79be4eaacb79d4771e4bbb9b01badd Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 21 Mar 2023 14:25:38 +0100 Subject: [PATCH 067/303] feat: adding regex flag/feature to json exporter and fixed socket issue in json exporter --- src/exporters/json.rs | 86 ++++++++++++++++++++++++++++++----------- src/exporters/mod.rs | 8 ++-- src/exporters/stdout.rs | 2 +- src/sensors/mod.rs | 10 ++--- src/sensors/utils.rs | 20 ++++++---- 5 files changed, 85 insertions(+), 41 deletions(-) diff --git a/src/exporters/json.rs b/src/exporters/json.rs index 6da0d452..703477cb 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -7,12 +7,15 @@ use std::fs::File; use std::path::PathBuf; use std::thread; use std::time::{Duration, Instant}; +use colored::*; +use regex::Regex; /// An Exporter that displays power consumption data of the host /// and its processes on the standard output of the terminal. pub struct JSONExporter { sensor: Box, reports: Vec, + regex: Option } impl Exporter for JSONExporter { @@ -76,6 +79,14 @@ impl Exporter for JSONExporter { .takes_value(false); options.push(arg); + let arg = Arg::with_name("regex_filter") + .help("Filter processes based on regular expressions (e.g: 'scaph\\w\\wd.e').") + .long("regex") + .short("r") + .required(false) + .takes_value(true); + options.push(arg); + // the resulting labels of this option are not yet used by this exporter, activate this option once we display something interesting about it //let arg = Arg::with_name("qemu") // .help("Apply labels to metrics of processes looking like a Qemu/KVM virtual machine") @@ -89,6 +100,7 @@ impl Exporter for JSONExporter { } } + #[derive(Serialize, Deserialize)] struct Domain { name: String, @@ -135,6 +147,7 @@ impl JSONExporter { JSONExporter { sensor, reports: Vec::new(), + regex: None } } @@ -161,6 +174,25 @@ impl JSONExporter { .parse() .expect("Wrong step_duration_nano value, should be a number of nano seconds"); + self.regex = if !parameters.is_present("regex_filter") + || parameters.value_of("regex_filter").unwrap().is_empty() + { + None + } else { + Some( + Regex::new(parameters.value_of("regex_filter").unwrap()) + .expect("Wrong regex_filter, regexp is invalid"), + ) + }; + + if parameters.occurrences_of("regex_filter") == 1 + && parameters.occurrences_of("max_top_consumers") == 1 + { + let warning = + String::from("Warning: (--max-top-consumers) and (-r / --regex) used at the same time. (--max-top-consumers) disabled"); + eprintln!("{}", warning.bright_yellow()); + } + info!("Measurement step is: {}s", step_duration); if let Some(timeout) = parameters.value_of("timeout") { let now = Instant::now(); @@ -192,6 +224,8 @@ impl JSONExporter { let metrics = metric_generator.pop_metrics(); let mut metrics_iter = metrics.iter(); + let socket_metrics_res = metrics_iter.find(|x| x.name == "scaph_socket_power_microwatts"); + //info!("socket metrics res : {:?}", socket_metrics_res); let mut host_report: Option = None; if let Some(host_metric) = metrics_iter.find(|x| x.name == "scaph_host_power_microwatts") { let host_power_string = format!("{}", host_metric.metric_value); @@ -206,14 +240,24 @@ impl JSONExporter { info!("didn't find host metric"); }; - let consumers = metric_generator.topology.proc_tracker.get_top_consumers( - parameters - .value_of("max_top_consumers") - .unwrap_or("10") - .parse::() - .unwrap(), - ); - let top_consumers = consumers + let consumers: Vec<(IProcess, f64)>; + let top_consumers; + if let Some(regex_filter) = &self.regex { + debug!("Processes filtered by '{}':", regex_filter.as_str()); + consumers = metric_generator + .topology + .proc_tracker + .get_filtered_processes(®ex_filter); + } else { + consumers = metric_generator.topology.proc_tracker.get_top_consumers( + parameters + .value_of("max_top_consumers") + .unwrap_or("10") + .parse::() + .unwrap(), + ); + } + top_consumers = consumers .iter() .filter_map(|(process, _value)| { metrics @@ -251,24 +295,20 @@ impl JSONExporter { }) .collect::>(); - let all_sockets = metric_generator + let all_sockets_vec = metric_generator .topology - .get_sockets_passive() + .get_sockets_passive(); + let all_sockets = all_sockets_vec .iter() .filter_map(|socket| { - if let Some(metric) = metrics_iter.find(|x| { - if x.name == "scaph_socket_power_microwatts" { - socket.id - == x.attributes - .get("socket_id") - .unwrap() - .parse::() - .unwrap() - } else { - info!("socket not found ! "); - false - } - }) { + if let Some(metric) = socket_metrics_res.iter().find(|x| + socket.id + == x.attributes + .get("socket_id") + .unwrap() + .parse::() + .unwrap() + ) { let socket_power = format!("{}", metric.metric_value).parse::().unwrap(); let domains = metrics diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index cf6bbe54..7fc86a8b 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -653,7 +653,7 @@ impl MetricGenerator { current_system_time_since_epoch().as_secs().to_string(); } } else { - info!("Docker socket is None."); + debug!("Docker socket is None."); } } } @@ -670,7 +670,7 @@ impl MetricGenerator { self.pods = pods_result; debug!("Found {} pods", &self.pods.len()); } else { - info!("Failed getting pods list, despite client seems ok."); + debug!("Failed getting pods list, despite client seems ok."); } } else { debug!("Kubernetes socket is not some."); @@ -720,11 +720,11 @@ impl MetricGenerator { if self.watch_kubernetes && self.kubernetes_client.is_some() { if self.pods_last_check.is_empty() { self.gen_kubernetes_pods_basic_metadata(); - info!("First check done on pods."); + debug!("First check done on pods."); } let last_check = self.pods_last_check.clone(); if (now.parse::().unwrap() - last_check.parse::().unwrap()) > 20 { - info!( + debug!( "Just refreshed pod list ! last: {} now: {}, diff: {}", last_check, now, diff --git a/src/exporters/stdout.rs b/src/exporters/stdout.rs index f60fcd97..37e87291 100644 --- a/src/exporters/stdout.rs +++ b/src/exporters/stdout.rs @@ -239,7 +239,7 @@ impl StdoutExporter { let consumers: Vec<(IProcess, f64)>; if let Some(regex_filter) = regex_filter { - println!("Processes filtered by '{}':", regex_filter.as_str()); + debug!("Processes filtered by '{}':", regex_filter.as_str()); consumers = metric_generator .topology .proc_tracker diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 7f578d7b..7ea2b482 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -1042,13 +1042,13 @@ impl CPUSocket { .get(self.record_buffer.len() - 2) .unwrap(); debug!( - "last_record value: {} previous_record value: {}", + "socket : last_record value: {} previous_record value: {}", &last_record.value, &previous_record.value ); let last_rec_val = last_record.value.trim(); - debug!("l851 : trying to parse {} as u64", last_rec_val); + debug!("socket : l1049 : trying to parse {} as u64", last_rec_val); let prev_rec_val = previous_record.value.trim(); - debug!("l853 : trying to parse {} as u64", prev_rec_val); + debug!("socket : l1051 : trying to parse {} as u64", prev_rec_val); if let (Ok(last_microjoules), Ok(previous_microjoules)) = (last_rec_val.parse::(), prev_rec_val.parse::()) { @@ -1057,14 +1057,14 @@ impl CPUSocket { microjoules = last_microjoules - previous_microjoules; } else { debug!( - "previous_microjoules ({}) > last_microjoules ({})", + "socket: previous_microjoules ({}) > last_microjoules ({})", previous_microjoules, last_microjoules ); } let time_diff = last_record.timestamp.as_secs_f64() - previous_record.timestamp.as_secs_f64(); let microwatts = microjoules as f64 / time_diff; - debug!("l866: microwatts: {}", microwatts); + debug!("socket : l1067: microwatts: {}", microwatts); return Some(Record::new( last_record.timestamp, (microwatts as u64).to_string(), diff --git a/src/sensors/utils.rs b/src/sensors/utils.rs index 9431427b..dd3beabc 100644 --- a/src/sensors/utils.rs +++ b/src/sensors/utils.rs @@ -403,11 +403,11 @@ impl ProcessTracker { if !p.is_empty() { //TODO implement // clippy will ask you to remove mut from res, but you just need to implement to fix that - if let Some(_sysinfo_p) = self.sysinfo.process(p[0].process.pid) { - //let status = sysinfo_p.status(); - //if status != ProcessStatus::Dead {//&& status != ProcessStatus::Stop { - res.push(p); - //} + if let Some(sysinfo_p) = self.sysinfo.process(p[0].process.pid) { + let status = sysinfo_p.status(); + if status != ProcessStatus::Dead {//&& status != ProcessStatus::Stop { + res.push(p); + } } } } @@ -660,7 +660,6 @@ impl ProcessTracker { } pub fn get_cpu_usage_percentage(&self, pid: Pid, nb_cores: usize) -> f32 { - info!("CALL FOR CPU USAGE"); let cpu_current_usage = self.sysinfo.global_cpu_info().cpu_usage(); if let Some(p) = self.sysinfo.process(pid) { (cpu_current_usage * p.cpu_usage() / 100.0) / nb_cores as f32 @@ -715,9 +714,14 @@ impl ProcessTracker { if p.len() > 1 { let diff = self .get_cpu_usage_percentage(p.first().unwrap().process.pid as _, self.nb_cores); - let process_exe = p.last().unwrap().process.exe(self).unwrap_or_default(); + let p_record = p.last().unwrap(); + let process_exe = p_record.process.exe(self).unwrap_or_default(); + let process_cmdline = p_record.process.cmdline(self).unwrap_or_default(); if regex_filter.is_match(process_exe.to_str().unwrap_or_default()) { - consumers.push((p.last().unwrap().process.clone(), OrderedFloat(diff as f64))); + consumers.push((p_record.process.clone(), OrderedFloat(diff as f64))); + consumers.sort_by(|x, y| y.1.cmp(&x.1)); + } else if regex_filter.is_match(&process_cmdline.concat()) { + consumers.push((p_record.process.clone(), OrderedFloat(diff as f64))); consumers.sort_by(|x, y| y.1.cmp(&x.1)); } } From 9e841f9bc6ab7563b11f345ae96adcf49d3a871e Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 21 Mar 2023 14:26:03 +0100 Subject: [PATCH 068/303] fix: update of sysinfo in cargo.lock --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 549b73a7..816d6736 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1518,9 +1518,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.28.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e847e2de7a137c8c2cede5095872dbb00f4f9bf34d061347e36b43322acd56" +checksum = "f69e0d827cce279e61c2f3399eb789271a8f136d8245edef70f06e3c9601a670" dependencies = [ "cfg-if", "core-foundation-sys", From 617153ae9a83c37450de1efd1a9f3ef12e7a3ef3 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 21 Mar 2023 14:26:55 +0100 Subject: [PATCH 069/303] style: fmt and clippy --- src/exporters/json.rs | 22 +++++++++------------- src/sensors/utils.rs | 3 ++- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/exporters/json.rs b/src/exporters/json.rs index 703477cb..7ea0f5ad 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -1,21 +1,21 @@ use crate::exporters::*; use crate::sensors::Sensor; use clap::Arg; +use colored::*; +use regex::Regex; use serde::{Deserialize, Serialize}; use std::fs; use std::fs::File; use std::path::PathBuf; use std::thread; use std::time::{Duration, Instant}; -use colored::*; -use regex::Regex; /// An Exporter that displays power consumption data of the host /// and its processes on the standard output of the terminal. pub struct JSONExporter { sensor: Box, reports: Vec, - regex: Option + regex: Option, } impl Exporter for JSONExporter { @@ -100,7 +100,6 @@ impl Exporter for JSONExporter { } } - #[derive(Serialize, Deserialize)] struct Domain { name: String, @@ -147,7 +146,7 @@ impl JSONExporter { JSONExporter { sensor, reports: Vec::new(), - regex: None + regex: None, } } @@ -241,13 +240,12 @@ impl JSONExporter { }; let consumers: Vec<(IProcess, f64)>; - let top_consumers; if let Some(regex_filter) = &self.regex { debug!("Processes filtered by '{}':", regex_filter.as_str()); consumers = metric_generator .topology .proc_tracker - .get_filtered_processes(®ex_filter); + .get_filtered_processes(regex_filter); } else { consumers = metric_generator.topology.proc_tracker.get_top_consumers( parameters @@ -257,7 +255,7 @@ impl JSONExporter { .unwrap(), ); } - top_consumers = consumers + let top_consumers = consumers .iter() .filter_map(|(process, _value)| { metrics @@ -295,20 +293,18 @@ impl JSONExporter { }) .collect::>(); - let all_sockets_vec = metric_generator - .topology - .get_sockets_passive(); + let all_sockets_vec = metric_generator.topology.get_sockets_passive(); let all_sockets = all_sockets_vec .iter() .filter_map(|socket| { - if let Some(metric) = socket_metrics_res.iter().find(|x| + if let Some(metric) = socket_metrics_res.iter().find(|x| { socket.id == x.attributes .get("socket_id") .unwrap() .parse::() .unwrap() - ) { + }) { let socket_power = format!("{}", metric.metric_value).parse::().unwrap(); let domains = metrics diff --git a/src/sensors/utils.rs b/src/sensors/utils.rs index dd3beabc..45989b0e 100644 --- a/src/sensors/utils.rs +++ b/src/sensors/utils.rs @@ -405,7 +405,8 @@ impl ProcessTracker { // clippy will ask you to remove mut from res, but you just need to implement to fix that if let Some(sysinfo_p) = self.sysinfo.process(p[0].process.pid) { let status = sysinfo_p.status(); - if status != ProcessStatus::Dead {//&& status != ProcessStatus::Stop { + if status != ProcessStatus::Dead { + //&& status != ProcessStatus::Stop { res.push(p); } } From bd4ccdbe8a81cdf524470106f2ab0d8cb271081b Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 21 Mar 2023 14:31:52 +0100 Subject: [PATCH 070/303] fix: updated rapl domain name in json to domain name instead of useless metric name --- src/exporters/json.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/exporters/json.rs b/src/exporters/json.rs index 7ea0f5ad..8dad537f 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -319,7 +319,7 @@ impl JSONExporter { == socket.id }) .map(|d| Domain { - name: d.name.clone(), + name: d.attributes.get("domain_name").unwrap().clone(), consumption: format!("{}", d.metric_value).parse::().unwrap(), timestamp: d.timestamp.as_secs_f64(), }) From 273a5ea443ed5a66e41b41a7bd13402860366b66 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 21 Mar 2023 16:04:01 +0100 Subject: [PATCH 071/303] feat: resources flag on json exporter to get resources usage per process --- src/exporters/json.rs | 73 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/src/exporters/json.rs b/src/exporters/json.rs index 8dad537f..cd9f7da9 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -87,6 +87,12 @@ impl Exporter for JSONExporter { .takes_value(true); options.push(arg); + let arg = Arg::with_name("resources") + .help("Monitor and include CPU/RAM/Disk usage per process.") + .long("resources") + .required(false); + options.push(arg); + // the resulting labels of this option are not yet used by this exporter, activate this option once we display something interesting about it //let arg = Arg::with_name("qemu") // .help("Apply labels to metrics of processes looking like a Qemu/KVM virtual machine") @@ -118,10 +124,26 @@ struct Socket { struct Consumer { exe: PathBuf, pid: i32, + resources_usage: Option, consumption: f32, timestamp: f64, container: Option, } + +#[derive(Serialize, Deserialize)] +struct ResourcesUsage { + cpu_usage: String, + cpu_usage_unit: String, + memory_usage: String, + memory_usage_unit: String, + memory_virtual_usage: String, + memory_virtual_usage_unit: String, + disk_usage_write: String, + disk_usage_write_unit: String, + disk_usage_read: String, + disk_usage_read_unit: String, +} + #[derive(Serialize, Deserialize)] struct Container { id: String, @@ -255,7 +277,7 @@ impl JSONExporter { .unwrap(), ); } - let top_consumers = consumers + let mut top_consumers = consumers .iter() .filter_map(|(process, _value)| { metrics @@ -268,6 +290,7 @@ impl JSONExporter { exe: PathBuf::from(metric.attributes.get("exe").unwrap()), pid: process.pid.to_string().parse::().unwrap(), consumption: format!("{}", metric.metric_value).parse::().unwrap(), + resources_usage: None, timestamp: metric.timestamp.as_secs_f64(), container: match parameters.is_present("containers") { true => metric.attributes.get("container_id").map(|container_id| { @@ -293,6 +316,54 @@ impl JSONExporter { }) .collect::>(); + if parameters.is_present("resources") { + info!("ADDING RESOURCES"); + for c in top_consumers.iter_mut() { + let mut res = ResourcesUsage { + cpu_usage: String::from("0"), + cpu_usage_unit: String::from("%"), + disk_usage_read: String::from("0"), + disk_usage_read_unit: String::from("Bytes"), + disk_usage_write: String::from("0"), + disk_usage_write_unit: String::from("Bytes"), + memory_usage: String::from("0"), + memory_usage_unit: String::from("Bytes"), + memory_virtual_usage: String::from("0"), + memory_virtual_usage_unit: String::from("Bytes"), + }; + let mut metrics = metrics.iter().filter(|x| { + x.name.starts_with("scaph_process_") + && x.attributes.get("pid").unwrap() == &c.pid.to_string() + }); + if let Some(cpu_usage_metric) = + metrics.find(|y| y.name == "scaph_process_cpu_usage_percentage") + { + res.cpu_usage = cpu_usage_metric.metric_value.to_string(); + } + if let Some(mem_usage_metric) = + metrics.find(|y| y.name == "scaph_process_memory_bytes") + { + res.memory_usage = mem_usage_metric.metric_value.to_string(); + } + if let Some(mem_virtual_usage_metric) = + metrics.find(|y| y.name == "scaph_process_memory_virtual_bytes") + { + res.memory_virtual_usage = mem_virtual_usage_metric.metric_value.to_string(); + } + if let Some(disk_write_metric) = + metrics.find(|y| y.name == "scaph_process_disk_write_bytes") + { + res.disk_usage_write = disk_write_metric.metric_value.to_string(); + } + if let Some(disk_read_metric) = + metrics.find(|y| y.name == "scaph_process_disk_read_bytes") + { + res.disk_usage_read = disk_read_metric.metric_value.to_string(); + } + c.resources_usage = Some(res); + } + } + let all_sockets_vec = metric_generator.topology.get_sockets_passive(); let all_sockets = all_sockets_vec .iter() From 9e1248157b879f168119178880d2cf23ee78f108 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 21 Mar 2023 18:41:14 +0100 Subject: [PATCH 072/303] feat: added disk_metrics to json exporter --- src/exporters/json.rs | 112 +++++++++++++++++++++++++++++++++++++++--- src/exporters/mod.rs | 18 +++++-- src/sensors/mod.rs | 49 ++++++++++++++++-- 3 files changed, 164 insertions(+), 15 deletions(-) diff --git a/src/exporters/json.rs b/src/exporters/json.rs index cd9f7da9..6f9a74b0 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -4,11 +4,13 @@ use clap::Arg; use colored::*; use regex::Regex; use serde::{Deserialize, Serialize}; -use std::fs; -use std::fs::File; -use std::path::PathBuf; -use std::thread; -use std::time::{Duration, Instant}; +use std::{ + fs, + fs::File, + path::PathBuf, + thread, + time::{Duration, Instant}, +}; /// An Exporter that displays power consumption data of the host /// and its processes on the standard output of the terminal. @@ -151,9 +153,24 @@ struct Container { scheduler: String, } #[derive(Serialize, Deserialize)] +struct Disk { + disk_type: String, + disk_mount_point: String, + disk_is_removable: bool, + disk_file_system: String, + disk_total_bytes: String, + disk_available_bytes: String, + disk_name: String, +} +#[derive(Serialize, Deserialize)] +struct Components { + disks: Option>, +} +#[derive(Serialize, Deserialize)] struct Host { consumption: f32, timestamp: f64, + components: Components, } #[derive(Serialize, Deserialize)] struct Report { @@ -236,6 +253,74 @@ impl JSONExporter { self.retrieve_metrics(parameters, metric_generator); } + fn gen_disks_report(&self, metrics: &Vec<&Metric>) -> Vec { + let mut res: Vec = vec![]; + for m in metrics { + let metric_disk_name = m.attributes.get("disk_name").unwrap(); + if let Some(mut disk) = res.iter_mut().find(|x| metric_disk_name == &x.disk_name) { + info!("editing disk"); + disk.disk_name = metric_disk_name.clone(); + if m.name == "scaph_host_disk_available_bytes" { + disk.disk_available_bytes = m.metric_value.to_string(); + } else if m.name == "scaph_host_disk_total_bytes" { + disk.disk_total_bytes = m.metric_value.to_string(); + } + } else { + info!("adding disk"); + res.push(Disk { + disk_name: metric_disk_name.clone(), + disk_available_bytes: { + if m.name == "scaph_host_disk_available_bytes" { + m.metric_value.to_string() + } else { + String::from("") + } + }, + disk_file_system: { + if let Some(metric_disk_file_system) = m.attributes.get("disk_file_system") + { + metric_disk_file_system.clone() + } else { + String::from("") + } + }, + disk_is_removable: { + if let Some(metric_disk_is_removable) = + m.attributes.get("disk_is_removable") + { + metric_disk_is_removable.parse::().unwrap() + } else { + false + } + }, + disk_mount_point: { + if let Some(metric_disk_mount_point) = m.attributes.get("disk_mount_point") + { + metric_disk_mount_point.clone() + } else { + String::from("") + } + }, + disk_total_bytes: { + if m.name == "scaph_host_disk_total_bytes" { + m.metric_value.to_string() + } else { + String::from("") + } + }, + disk_type: { + if let Some(metric_disk_type) = m.attributes.get("disk_type") { + metric_disk_type.clone() + } else { + String::from("") + } + }, + }) + } + } + res + } + fn retrieve_metrics( &mut self, parameters: &ArgMatches, @@ -246,21 +331,34 @@ impl JSONExporter { let metrics = metric_generator.pop_metrics(); let mut metrics_iter = metrics.iter(); let socket_metrics_res = metrics_iter.find(|x| x.name == "scaph_socket_power_microwatts"); - //info!("socket metrics res : {:?}", socket_metrics_res); + //TODO: fix for multiple sockets let mut host_report: Option = None; - if let Some(host_metric) = metrics_iter.find(|x| x.name == "scaph_host_power_microwatts") { + let disks = self.gen_disks_report( + &metrics_iter + .filter(|x| x.name.starts_with("scaph_host_disk_")) + .collect(), + ); + if let Some(host_metric) = &metrics + .iter() + .find(|x| x.name == "scaph_host_power_microwatts") + { let host_power_string = format!("{}", host_metric.metric_value); let host_power_f32 = host_power_string.parse::().unwrap(); if host_power_f32 > 0.0 { host_report = Some(Host { consumption: host_power_f32, timestamp: host_metric.timestamp.as_secs_f64(), + components: Components { disks: None }, }); } } else { info!("didn't find host metric"); }; + if let Some(host) = &mut host_report { + host.components.disks = Some(disks); + } + let consumers: Vec<(IProcess, f64)>; if let Some(regex_filter) = &self.regex { debug!("Processes filtered by '{}':", regex_filter.as_str()); diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index 7fc86a8b..df38e0d1 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -475,9 +475,21 @@ impl MetricGenerator { description: format!("Global frequency of all the cpus. In {}", freq.unit), metric_value: MetricValueType::Text(freq.value), }); - //for c in self.topology.proc_tracker.components() { - // println!("component: {}", c) - //} + for (metric_name, metric) in self.topology.get_disks() { + info!("pushing disk metric to data : {}", metric_name); + self.data.push(Metric { + name: metric_name, + metric_type: String::from("gauge"), + ttl: 60.0, + timestamp: metric.2.timestamp, + hostname: self.hostname.clone(), + state: String::from("ok"), + tags: vec!["scaphandre".to_string()], + attributes: metric.1, + description: metric.0, + metric_value: MetricValueType::Text(metric.2.value), + }); + } } /// Generate socket metrics. diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 7ea2b482..753d5657 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -11,11 +11,8 @@ pub mod units; pub mod utils; #[cfg(target_os = "linux")] use procfs::{CpuInfo, CpuTime, KernelStats}; -use std::collections::HashMap; -use std::error::Error; -use std::fmt; -use std::mem::size_of_val; -use std::time::Duration; +use std::{collections::HashMap, error::Error, fmt, mem::size_of_val, str, time::Duration}; +use sysinfo::DiskExt; #[allow(unused_imports)] use sysinfo::{CpuExt, Pid, System, SystemExt}; use utils::{current_system_time_since_epoch, IProcess, ProcessTracker}; @@ -564,6 +561,48 @@ impl Topology { ]) } + pub fn get_disks(&self) -> HashMap, Record)> { + let timestamp = current_system_time_since_epoch(); + let mut res = HashMap::new(); + for d in self.proc_tracker.sysinfo.disks() { + let mut attributes = HashMap::new(); + if let Ok(file_system) = str::from_utf8(d.file_system()) { + attributes.insert(String::from("disk_file_system"), String::from(file_system)); + } + if let Some(mount_point) = d.mount_point().to_str() { + attributes.insert(String::from("disk_mount_point"), String::from(mount_point)); + } + attributes.insert( + String::from("disk_is_removable"), + d.is_removable().to_string(), + ); + if let Some(disk_name) = d.name().to_str() { + attributes.insert(String::from("disk_name"), String::from(disk_name)); + } + res.insert( + String::from("scaph_host_disk_total_bytes"), + ( + String::from("Total disk size, in bytes."), + attributes.clone(), + Record::new(timestamp, d.total_space().to_string(), units::Unit::Bytes), + ), + ); + res.insert( + String::from("scaph_host_disk_available_bytes"), + ( + String::from("Available disk space, in bytes."), + attributes.clone(), + Record::new( + timestamp, + d.available_space().to_string(), + units::Unit::Bytes, + ), + ), + ); + } + res + } + /// Returns the power consumed between last and previous measurement for a given process ID, in microwatts pub fn get_process_power_consumption_microwatts(&self, pid: Pid) -> Option { if let Some(record) = self.get_proc_tracker().get_process_last_record(pid) { From d3df646ee73b5aa8c18a09a7d48c58ef70fe1ce4 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 22 Mar 2023 06:21:40 +0100 Subject: [PATCH 073/303] feat: adding cmdline to processes in json exporter --- src/exporters/json.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/exporters/json.rs b/src/exporters/json.rs index 6f9a74b0..ee37dcd5 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -125,6 +125,7 @@ struct Socket { #[derive(Serialize, Deserialize)] struct Consumer { exe: PathBuf, + cmdline: String, pid: i32, resources_usage: Option, consumption: f32, @@ -386,6 +387,7 @@ impl JSONExporter { }) .map(|metric| Consumer { exe: PathBuf::from(metric.attributes.get("exe").unwrap()), + cmdline: metric.attributes.get("cmdline").unwrap().clone(), pid: process.pid.to_string().parse::().unwrap(), consumption: format!("{}", metric.metric_value).parse::().unwrap(), resources_usage: None, From 5ea276163f2f3a60eba1be73cdbeab08ac7a8358 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 22 Mar 2023 06:43:12 +0100 Subject: [PATCH 074/303] chore: added graph for disks in sample dashboard --- .../dashboards/sample-dashboard.json | 114 ++++++++++++++++-- docker-compose/sample.jsonnet | 28 ++++- 2 files changed, 125 insertions(+), 17 deletions(-) diff --git a/docker-compose/dashboards/sample-dashboard.json b/docker-compose/dashboards/sample-dashboard.json index 4ed31395..109847c4 100644 --- a/docker-compose/dashboards/sample-dashboard.json +++ b/docker-compose/dashboards/sample-dashboard.json @@ -49,7 +49,7 @@ "repeat": null, "seriesOverrides": [ ], "spaceLength": 10, - "span": 4, + "span": 3, "stack": false, "steppedLine": false, "targets": [ @@ -144,7 +144,7 @@ "repeat": null, "seriesOverrides": [ ], "spaceLength": 10, - "span": 4, + "span": 3, "stack": false, "steppedLine": false, "targets": [ @@ -228,7 +228,95 @@ "repeat": null, "seriesOverrides": [ ], "spaceLength": 10, - "span": 4, + "span": 3, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "scaph_host_disk_total_bytes", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{ disk_name }} {{ disk_type }} total", + "refId": "A" + }, + { + "expr": "scaph_host_disk_available_bytes", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{ disk_name }} {{ disk_type }} available", + "refId": "B" + } + ], + "thresholds": [ ], + "timeFrom": null, + "timeShift": null, + "title": "Disks capacity and usage", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [ ] + }, + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ] + }, + { + "aliasColors": { }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${PROMETHEUS_DS}", + "fill": 1, + "fillGradient": 0, + "gridPos": { }, + "id": 5, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "sideWidth": null, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [ ], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": null, + "seriesOverrides": [ ], + "spaceLength": 10, + "span": 3, "stack": false, "steppedLine": false, "targets": [ @@ -312,7 +400,7 @@ "fill": 1, "fillGradient": 0, "gridPos": { }, - "id": 5, + "id": 6, "legend": { "alignAsTable": false, "avg": false, @@ -393,7 +481,7 @@ "fill": 1, "fillGradient": 0, "gridPos": { }, - "id": 6, + "id": 7, "legend": { "alignAsTable": false, "avg": false, @@ -474,7 +562,7 @@ "fill": 1, "fillGradient": 0, "gridPos": { }, - "id": 7, + "id": 8, "legend": { "alignAsTable": false, "avg": false, @@ -555,7 +643,7 @@ "fill": 1, "fillGradient": 0, "gridPos": { }, - "id": 8, + "id": 9, "legend": { "alignAsTable": false, "avg": false, @@ -617,7 +705,7 @@ }, "yaxes": [ { - "format": "Bytes", + "format": "bytes", "label": null, "logBase": 1, "max": null, @@ -625,7 +713,7 @@ "show": true }, { - "format": "Bytes", + "format": "bytes", "label": null, "logBase": 1, "max": null, @@ -656,7 +744,7 @@ "fill": 1, "fillGradient": 0, "gridPos": { }, - "id": 9, + "id": 10, "legend": { "alignAsTable": true, "avg": false, @@ -737,7 +825,7 @@ "fill": 1, "fillGradient": 0, "gridPos": { }, - "id": 10, + "id": 11, "legend": { "alignAsTable": true, "avg": false, @@ -818,7 +906,7 @@ "fill": 1, "fillGradient": 0, "gridPos": { }, - "id": 11, + "id": 12, "legend": { "alignAsTable": true, "avg": false, @@ -899,7 +987,7 @@ "fill": 1, "fillGradient": 0, "gridPos": { }, - "id": 12, + "id": 13, "legend": { "alignAsTable": true, "avg": false, diff --git a/docker-compose/sample.jsonnet b/docker-compose/sample.jsonnet index cd50f50f..96dc3309 100644 --- a/docker-compose/sample.jsonnet +++ b/docker-compose/sample.jsonnet @@ -32,7 +32,7 @@ dashboard.new( title='Hosts power consumption', datasource='${PROMETHEUS_DS}', format='W', - span=4, + span=3, min=0 ) .addTarget( @@ -58,7 +58,7 @@ dashboard.new( grafana.graphPanel.new( title='Hosts power consumption total (dynamic time range)', datasource='${PROMETHEUS_DS}', - span=4, + span=3, bars=true, format='Wh', x_axis_mode='series', @@ -72,11 +72,31 @@ dashboard.new( ) ) ) + .addPanel( + grafana.graphPanel.new( + title='Disks capacity and usage', + datasource='${PROMETHEUS_DS}', + span=3, + format='bytes', + ) + .addTarget( + grafana.prometheus.target( + 'scaph_host_disk_total_bytes', + legendFormat='{{ disk_name }} {{ disk_type }} total', + ) + ) + .addTarget( + grafana.prometheus.target( + 'scaph_host_disk_available_bytes', + legendFormat='{{ disk_name }} {{ disk_type }} available', + ) + ) + ) .addPanel( grafana.graphPanel.new( title='Host load average', datasource='${PROMETHEUS_DS}', - span=4, + span=3, format='', min=0 ) @@ -153,7 +173,7 @@ dashboard.new( grafana.graphPanel.new( title='scaph_self_mem', datasource='${PROMETHEUS_DS}', - format='Bytes', + format='bytes', span=3, min=0 ) From a2f5a5ddcd7e554a674c3f362381e330ec4f6754 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 22 Mar 2023 06:57:56 +0100 Subject: [PATCH 075/303] fix: added disk_type --- src/sensors/mod.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 753d5657..2867a201 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -12,9 +12,9 @@ pub mod utils; #[cfg(target_os = "linux")] use procfs::{CpuInfo, CpuTime, KernelStats}; use std::{collections::HashMap, error::Error, fmt, mem::size_of_val, str, time::Duration}; -use sysinfo::DiskExt; #[allow(unused_imports)] use sysinfo::{CpuExt, Pid, System, SystemExt}; +use sysinfo::{DiskExt, DiskType}; use utils::{current_system_time_since_epoch, IProcess, ProcessTracker}; // !!!!!!!!!!!!!!!!! Sensor !!!!!!!!!!!!!!!!!!!!!!! @@ -572,6 +572,17 @@ impl Topology { if let Some(mount_point) = d.mount_point().to_str() { attributes.insert(String::from("disk_mount_point"), String::from(mount_point)); } + match d.type_() { + DiskType::SSD => { + attributes.insert(String::from("disk_type"), String::from("SSD")); + } + DiskType::HDD => { + attributes.insert(String::from("disk_type"), String::from("HDD")); + } + DiskType::Unknown(_) => { + attributes.insert(String::from("disk_type"), String::from("Unknown")); + } + } attributes.insert( String::from("disk_is_removable"), d.is_removable().to_string(), From 99481925de32b7704501cd292980f0e6ac121681 Mon Sep 17 00:00:00 2001 From: Nicolas Trangosi Date: Wed, 26 Oct 2022 13:59:22 +0200 Subject: [PATCH 076/303] feat: Add service monitor on helm chart Allow configuration of service monitor in order to have metrics collected by an external prometheus Signed-off-by: Nicolas Trangosi --- docs_src/tutorials/kubernetes.md | 8 ++++++ helm/scaphandre/templates/servicemonitor.yaml | 28 +++++++++++++++++++ helm/scaphandre/values.yaml | 11 ++++++-- 3 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 helm/scaphandre/templates/servicemonitor.yaml diff --git a/docs_src/tutorials/kubernetes.md b/docs_src/tutorials/kubernetes.md index 50dc76af..e82524ee 100644 --- a/docs_src/tutorials/kubernetes.md +++ b/docs_src/tutorials/kubernetes.md @@ -12,6 +12,14 @@ to be installed from the source code. git clone https://github.com/hubblo-org/scaphandre cd scaphandre helm install scaphandre helm/scaphandre +### Parameters +#### Service monitor parameters + +| Name | Description | Value | +| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------- | ------------------------- | +| `serviceMonitor.enabled` | Create ServiceMonitor Resource for scraping metrics using PrometheusOperator | `false` | +| `serviceMonitor.namespace` | The namespace in which the ServiceMonitor will be created (if not set, default to namespace on which this chart is installed) | `""` | +| `serviceMonitor.interval` | The interval at which metrics should be scraped | `1m` | ## Install Prometheus diff --git a/helm/scaphandre/templates/servicemonitor.yaml b/helm/scaphandre/templates/servicemonitor.yaml new file mode 100644 index 00000000..3d343f71 --- /dev/null +++ b/helm/scaphandre/templates/servicemonitor.yaml @@ -0,0 +1,28 @@ +{{- if and ( .Capabilities.APIVersions.Has "monitoring.coreos.com/v1" ) .Values.serviceMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "scaphandre.name" . }}-service-monitoring + {{- if .Values.serviceMonitor.namespace }} + namespace: {{ .Values.serviceMonitor.namespace }} + {{- else }} + namespace: {{ .Release.Namespace }} + {{- end }} + labels: + app.kubernetes.io/name: {{ template "scaphandre.name" . }} +spec: + endpoints: + - path: /metrics + port: metrics + scheme: http + {{- if .Values.serviceMonitor.interval }} + interval: {{ .Values.serviceMonitor.interval }} + {{- end }} + scrapeTimeout: 30s + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + app.kubernetes.io/name: {{ include "scaphandre.name" . }} +{{- end }} diff --git a/helm/scaphandre/values.yaml b/helm/scaphandre/values.yaml index dec4c7ce..1e930729 100644 --- a/helm/scaphandre/values.yaml +++ b/helm/scaphandre/values.yaml @@ -6,10 +6,10 @@ port: 8080 resources: limits: - memory: 75Mi + memory: 200Mi requests: cpu: 75m - memory: 50Mi + memory: 100Mi scaphandre: command: prometheus @@ -21,3 +21,10 @@ scaphandre: # Run as root user to get proper permissions userID: 0 groupID: 0 + +serviceMonitor: + # Specifies whether ServiceMonitor for Prometheus operator should be created + enabled: false + interval: 1m + # Specifies namespace, where ServiceMonitor should be installed + # namespace: monitoring From 5f56c0b32bc4d1d9681e7453cec985005a0141fa Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 22 Mar 2023 23:58:41 +0100 Subject: [PATCH 077/303] feat: added container_regex flag to search for process by container name in json exporter --- src/exporters/json.rs | 42 ++++++++++++++++++++++++++++++++------- src/exporters/mod.rs | 46 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 7 deletions(-) diff --git a/src/exporters/json.rs b/src/exporters/json.rs index ee37dcd5..aca84949 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -76,6 +76,7 @@ impl Exporter for JSONExporter { let arg = Arg::with_name("containers") .help("Monitor and apply labels for processes running as containers") + .short("c") .long("containers") .required(false) .takes_value(false); @@ -95,6 +96,13 @@ impl Exporter for JSONExporter { .required(false); options.push(arg); + let arg = Arg::with_name("container_regex") + .help("Filter process by container name based on regular expressions (e.g: 'scaph\\w\\wd.e'). Works only with --containers enabled.") + .long("container-regex") + .required(false) + .takes_value(true); + options.push(arg); + // the resulting labels of this option are not yet used by this exporter, activate this option once we display something interesting about it //let arg = Arg::with_name("qemu") // .help("Apply labels to metrics of processes looking like a Qemu/KVM virtual machine") @@ -149,6 +157,7 @@ struct ResourcesUsage { #[derive(Serialize, Deserialize)] struct Container { + name: String, id: String, runtime: String, scheduler: String, @@ -232,6 +241,12 @@ impl JSONExporter { eprintln!("{}", warning.bright_yellow()); } + if !parameters.is_present("containers") && parameters.is_present("container_regex") { + let warning = + String::from("Warning: --container-regex is used but --containers is not enabled. Regex search won't work."); + eprintln!("{}", warning.bright_yellow()); + } + info!("Measurement step is: {}s", step_duration); if let Some(timeout) = parameters.value_of("timeout") { let now = Instant::now(); @@ -361,20 +376,27 @@ impl JSONExporter { } let consumers: Vec<(IProcess, f64)>; + let max_top = parameters + .value_of("max_top_consumers") + .unwrap_or("10") + .parse::() + .unwrap(); if let Some(regex_filter) = &self.regex { debug!("Processes filtered by '{}':", regex_filter.as_str()); consumers = metric_generator .topology .proc_tracker .get_filtered_processes(regex_filter); - } else { - consumers = metric_generator.topology.proc_tracker.get_top_consumers( - parameters - .value_of("max_top_consumers") - .unwrap_or("10") - .parse::() - .unwrap(), + } else if parameters.is_present("container_regex") { + consumers = metric_generator.get_processes_filtered_by_container_name( + &Regex::new(parameters.value_of("container_regex").unwrap()) + .expect("Wrong container_regex expression. Regexp is invalid."), ); + } else { + consumers = metric_generator + .topology + .proc_tracker + .get_top_consumers(max_top); } let mut top_consumers = consumers .iter() @@ -396,6 +418,12 @@ impl JSONExporter { true => metric.attributes.get("container_id").map(|container_id| { Container { id: String::from(container_id), + name: String::from( + metric + .attributes + .get("container_names") + .unwrap_or(&String::from("unknown")), + ), runtime: String::from( metric .attributes diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index df38e0d1..14db32a8 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -28,6 +28,8 @@ use { docker_sync::{container::Container, Docker}, k8s_sync::kubernetes::Kubernetes, k8s_sync::Pod, + ordered_float::*, + regex::Regex, utils::{get_docker_client, get_kubernetes_client}, }; @@ -222,6 +224,50 @@ impl MetricGenerator { } } + #[cfg(feature = "containers")] + pub fn get_processes_filtered_by_container_name( + &self, + container_regex: &Regex, + ) -> Vec<(IProcess, f64)> { + let mut consumers: Vec<(IProcess, OrderedFloat)> = vec![]; + for p in &self.topology.proc_tracker.procs { + if p.len() > 1 { + let diff = self.topology.proc_tracker.get_cpu_usage_percentage( + p.first().unwrap().process.pid as _, + self.topology.proc_tracker.nb_cores, + ); + let p_record = p.last().unwrap(); + let container_description = self + .topology + .proc_tracker + .get_process_container_description( + p_record.process.pid, + &self.containers, + self.docker_version.clone(), + &self.pods, + ); + if let Some(name) = container_description.get("container_names") { + if container_regex.is_match(name) { + consumers.push((p_record.process.clone(), OrderedFloat(diff as f64))); + consumers.sort_by(|x, y| y.1.cmp(&x.1)); + } + } + //if container_regex.is_match(process_exe.to_str().unwrap_or_default()) { + // consumers.push((p_record.process.clone(), OrderedFloat(diff as f64))); + // consumers.sort_by(|x, y| y.1.cmp(&x.1)); + //} else if container_regex.is_match(&process_cmdline.concat()) { + // consumers.push((p_record.process.clone(), OrderedFloat(diff as f64))); + // consumers.sort_by(|x, y| y.1.cmp(&x.1)); + //} + } + } + let mut result: Vec<(IProcess, f64)> = vec![]; + for (p, f) in consumers { + result.push((p, f.into_inner())); + } + result + } + /// Generate all scaphandre internal metrics. fn gen_self_metrics(&mut self) { let myself = IProcess::myself(self.topology.get_proc_tracker()).unwrap(); From cbbf185cec450a400a318c34b2aedfde940839a7 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Thu, 23 Mar 2023 00:32:24 +0100 Subject: [PATCH 078/303] fix: exclude containers code for windows --- src/exporters/json.rs | 44 ++++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/src/exporters/json.rs b/src/exporters/json.rs index aca84949..10769cbe 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -74,13 +74,16 @@ impl Exporter for JSONExporter { .takes_value(true); options.push(arg); - let arg = Arg::with_name("containers") - .help("Monitor and apply labels for processes running as containers") - .short("c") - .long("containers") - .required(false) - .takes_value(false); - options.push(arg); + #[cfg(feature = "containers")] + { + let arg = Arg::with_name("containers") + .help("Monitor and apply labels for processes running as containers") + .short("c") + .long("containers") + .required(false) + .takes_value(false); + options.push(arg); + } let arg = Arg::with_name("regex_filter") .help("Filter processes based on regular expressions (e.g: 'scaph\\w\\wd.e').") @@ -96,12 +99,15 @@ impl Exporter for JSONExporter { .required(false); options.push(arg); - let arg = Arg::with_name("container_regex") - .help("Filter process by container name based on regular expressions (e.g: 'scaph\\w\\wd.e'). Works only with --containers enabled.") - .long("container-regex") - .required(false) - .takes_value(true); - options.push(arg); + #[cfg(feature = "containers")] + { + let arg = Arg::with_name("container_regex") + .help("Filter process by container name based on regular expressions (e.g: 'scaph\\w\\wd.e'). Works only with --containers enabled.") + .long("container-regex") + .required(false) + .takes_value(true); + options.push(arg); + } // the resulting labels of this option are not yet used by this exporter, activate this option once we display something interesting about it //let arg = Arg::with_name("qemu") @@ -241,6 +247,7 @@ impl JSONExporter { eprintln!("{}", warning.bright_yellow()); } + #[cfg(feature = "containers")] if !parameters.is_present("containers") && parameters.is_present("container_regex") { let warning = String::from("Warning: --container-regex is used but --containers is not enabled. Regex search won't work."); @@ -388,10 +395,13 @@ impl JSONExporter { .proc_tracker .get_filtered_processes(regex_filter); } else if parameters.is_present("container_regex") { - consumers = metric_generator.get_processes_filtered_by_container_name( - &Regex::new(parameters.value_of("container_regex").unwrap()) - .expect("Wrong container_regex expression. Regexp is invalid."), - ); + #[cfg(feature = "containers")] + { + consumers = metric_generator.get_processes_filtered_by_container_name( + &Regex::new(parameters.value_of("container_regex").unwrap()) + .expect("Wrong container_regex expression. Regexp is invalid."), + ); + } } else { consumers = metric_generator .topology From 9b508fbe2feb9520bcf68219fc98109b7ff3096b Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Thu, 23 Mar 2023 00:44:18 +0100 Subject: [PATCH 079/303] fix: exclude containers code for windows --- src/exporters/json.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/exporters/json.rs b/src/exporters/json.rs index 10769cbe..3a8538db 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -402,6 +402,13 @@ impl JSONExporter { .expect("Wrong container_regex expression. Regexp is invalid."), ); } + #[cfg(not(feature = "containers"))] + { + consumers = metric_generator + .topology + .proc_tracker + .get_top_consumers(max_top); + } } else { consumers = metric_generator .topology From f2de7333ab02789e810605998ccee211da7ebc30 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Thu, 23 Mar 2023 09:03:57 +0100 Subject: [PATCH 080/303] fix: troubleshooting json expoerter --container-regex indocker environment --- src/exporters/json.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/exporters/json.rs b/src/exporters/json.rs index 3a8538db..c86229cb 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -401,6 +401,7 @@ impl JSONExporter { &Regex::new(parameters.value_of("container_regex").unwrap()) .expect("Wrong container_regex expression. Regexp is invalid."), ); + debug!("consumers : {:?}", consumers); } #[cfg(not(feature = "containers"))] { @@ -455,7 +456,13 @@ impl JSONExporter { ), } }), - false => None, + false => { + debug!( + "No container_id for {}", + metric.attributes.get("cmdline").unwrap().clone() + ); + None + } }, }) }) From db9ca78372560a267247b9ab12133b95b37341c1 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Thu, 23 Mar 2023 09:18:54 +0100 Subject: [PATCH 081/303] fix: troubleshooting json expoerter --container-regex indocker environment --- src/exporters/json.rs | 4 ++-- src/exporters/mod.rs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/exporters/json.rs b/src/exporters/json.rs index c86229cb..249df918 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -401,7 +401,7 @@ impl JSONExporter { &Regex::new(parameters.value_of("container_regex").unwrap()) .expect("Wrong container_regex expression. Regexp is invalid."), ); - debug!("consumers : {:?}", consumers); + warn!("consumers : {:?}", consumers); } #[cfg(not(feature = "containers"))] { @@ -457,7 +457,7 @@ impl JSONExporter { } }), false => { - debug!( + info!( "No container_id for {}", metric.attributes.get("cmdline").unwrap().clone() ); diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index 14db32a8..471e8421 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -706,6 +706,7 @@ impl MetricGenerator { if self.watch_docker && self.docker_client.is_some() { if let Some(docker) = self.docker_client.as_mut() { if let Ok(containers_result) = docker.get_containers(false) { + warn!("Got containers: {:?}", containers_result); self.containers = containers_result; self.containers_last_check = current_system_time_since_epoch().as_secs().to_string(); From 0b9b2e5fd06909253f9952e0500e75cf3971073e Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 27 Mar 2023 09:41:31 +0200 Subject: [PATCH 082/303] chore: removed unused log messages --- src/exporters/json.rs | 5 ----- src/exporters/mod.rs | 1 - 2 files changed, 6 deletions(-) diff --git a/src/exporters/json.rs b/src/exporters/json.rs index 249df918..ce5d5309 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -401,7 +401,6 @@ impl JSONExporter { &Regex::new(parameters.value_of("container_regex").unwrap()) .expect("Wrong container_regex expression. Regexp is invalid."), ); - warn!("consumers : {:?}", consumers); } #[cfg(not(feature = "containers"))] { @@ -457,10 +456,6 @@ impl JSONExporter { } }), false => { - info!( - "No container_id for {}", - metric.attributes.get("cmdline").unwrap().clone() - ); None } }, diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index 471e8421..14db32a8 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -706,7 +706,6 @@ impl MetricGenerator { if self.watch_docker && self.docker_client.is_some() { if let Some(docker) = self.docker_client.as_mut() { if let Ok(containers_result) = docker.get_containers(false) { - warn!("Got containers: {:?}", containers_result); self.containers = containers_result; self.containers_last_check = current_system_time_since_epoch().as_secs().to_string(); From 023e20133af0d6873ccdc5bb51518e43568ee4d4 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 27 Mar 2023 09:41:52 +0200 Subject: [PATCH 083/303] style: fmt --- src/exporters/json.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/exporters/json.rs b/src/exporters/json.rs index ce5d5309..3a8538db 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -455,9 +455,7 @@ impl JSONExporter { ), } }), - false => { - None - } + false => None, }, }) }) From ba5b2a8f070ed2f24831048346b0782ffc37fae0 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 27 Mar 2023 10:55:11 +0200 Subject: [PATCH 084/303] feat: memory and swap metrics --- src/exporters/mod.rs | 71 ++++++++++++++++++++++++++++++++++++++++++++ src/sensors/mod.rs | 40 +++++++++++++++++++++++++ 2 files changed, 111 insertions(+) diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index 14db32a8..28a2c677 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -536,6 +536,77 @@ impl MetricGenerator { metric_value: MetricValueType::Text(metric.2.value), }); } + + let ram_attributes = HashMap::new(); + let metric_value = self.topology.get_total_memory_bytes(); + self.data.push(Metric { + name: String::from("scaph_host_memory_total_bytes"), + metric_type: String::from("gauge"), + ttl: 60.0, + timestamp: metric_value.timestamp, + hostname: self.hostname.clone(), + state: String::from("ok"), + tags: vec!["scaphandre".to_string()], + attributes: ram_attributes.clone(), + description: String::from("Random Access Memory installed on the host, in bytes."), + metric_value: MetricValueType::Text(metric_value.value), + }); + let metric_value = self.topology.get_available_memory_bytes(); + self.data.push(Metric { + name: String::from("scaph_host_memory_available_bytes"), + metric_type: String::from("gauge"), + ttl: 60.0, + timestamp: metric_value.timestamp, + hostname: self.hostname.clone(), + state: String::from("ok"), + tags: vec!["scaphandre".to_string()], + attributes: ram_attributes.clone(), + description: String::from( + "Random Access Memory available to be re-used on the host, in bytes.", + ), + metric_value: MetricValueType::Text(metric_value.value), + }); + let metric_value = self.topology.get_free_memory_bytes(); + self.data.push(Metric { + name: String::from("scaph_host_memory_free_bytes"), + metric_type: String::from("gauge"), + ttl: 60.0, + timestamp: metric_value.timestamp, + hostname: self.hostname.clone(), + state: String::from("ok"), + tags: vec!["scaphandre".to_string()], + attributes: ram_attributes.clone(), + description: String::from( + "Random Access Memory free to be used (not reused) on the host, in bytes.", + ), + metric_value: MetricValueType::Text(metric_value.value), + }); + let metric_value = self.topology.get_free_swap_bytes(); + self.data.push(Metric { + name: String::from("scaph_host_swap_free_bytes"), + metric_type: String::from("gauge"), + ttl: 60.0, + timestamp: metric_value.timestamp, + hostname: self.hostname.clone(), + state: String::from("ok"), + tags: vec!["scaphandre".to_string()], + attributes: ram_attributes.clone(), + description: String::from("Swap space free to be used on the host, in bytes."), + metric_value: MetricValueType::Text(metric_value.value), + }); + let metric_value = self.topology.get_total_swap_bytes(); + self.data.push(Metric { + name: String::from("scaph_host_swap_total_bytes"), + metric_type: String::from("gauge"), + ttl: 60.0, + timestamp: metric_value.timestamp, + hostname: self.hostname.clone(), + state: String::from("ok"), + tags: vec!["scaphandre".to_string()], + attributes: ram_attributes, + description: String::from("Total swap space on the host, in bytes."), + metric_value: MetricValueType::Text(metric_value.value), + }); } /// Generate socket metrics. diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 2867a201..21c1cdfc 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -614,6 +614,46 @@ impl Topology { res } + pub fn get_total_memory_bytes(&self) -> Record { + Record { + timestamp: current_system_time_since_epoch(), + value: self.proc_tracker.sysinfo.total_memory().to_string(), + unit: units::Unit::Bytes, + } + } + + pub fn get_available_memory_bytes(&self) -> Record { + Record { + timestamp: current_system_time_since_epoch(), + value: self.proc_tracker.sysinfo.available_memory().to_string(), + unit: units::Unit::Bytes, + } + } + + pub fn get_free_memory_bytes(&self) -> Record { + Record { + timestamp: current_system_time_since_epoch(), + value: self.proc_tracker.sysinfo.free_memory().to_string(), + unit: units::Unit::Bytes, + } + } + + pub fn get_total_swap_bytes(&self) -> Record { + Record { + timestamp: current_system_time_since_epoch(), + value: self.proc_tracker.sysinfo.total_swap().to_string(), + unit: units::Unit::Bytes, + } + } + + pub fn get_free_swap_bytes(&self) -> Record { + Record { + timestamp: current_system_time_since_epoch(), + value: self.proc_tracker.sysinfo.free_swap().to_string(), + unit: units::Unit::Bytes, + } + } + /// Returns the power consumed between last and previous measurement for a given process ID, in microwatts pub fn get_process_power_consumption_microwatts(&self, pid: Pid) -> Option { if let Some(record) = self.get_proc_tracker().get_process_last_record(pid) { From 13861584e15ddd35135c154b49aab8182da36535 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 27 Mar 2023 12:32:25 +0200 Subject: [PATCH 085/303] fixing missing changes for clap --- src/exporters/json.rs | 46 +++++++++++++++++++++------------------- src/exporters/warpten.rs | 2 +- src/main.rs | 6 +----- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/exporters/json.rs b/src/exporters/json.rs index 17e62f7b..2832ee8b 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -1,6 +1,6 @@ use crate::exporters::*; use crate::sensors::Sensor; -use clap::{value_parser, Arg}; +use clap::{parser::ValueSource, value_parser, Arg}; use colored::*; use regex::Regex; use serde::{Deserialize, Serialize}; @@ -80,24 +80,24 @@ impl Exporter for JSONExporter { #[cfg(feature = "containers")] { - let arg = Arg::with_name("containers") + let arg = Arg::new("containers") .help("Monitor and apply labels for processes running as containers") - .short("c") + .short('c') .long("containers") .required(false) - .takes_value(false); + .action(clap::ArgAction::Set); options.push(arg); } - let arg = Arg::with_name("regex_filter") + let arg = Arg::new("regex_filter") .help("Filter processes based on regular expressions (e.g: 'scaph\\w\\wd.e').") .long("regex") - .short("r") + .short('r') .required(false) - .takes_value(true); + .action(clap::ArgAction::Set); options.push(arg); - let arg = Arg::with_name("resources") + let arg = Arg::new("resources") .help("Monitor and include CPU/RAM/Disk usage per process.") .long("resources") .required(false); @@ -105,11 +105,11 @@ impl Exporter for JSONExporter { #[cfg(feature = "containers")] { - let arg = Arg::with_name("container_regex") + let arg = Arg::new("container_regex") .help("Filter process by container name based on regular expressions (e.g: 'scaph\\w\\wd.e'). Works only with --containers enabled.") .long("container-regex") .required(false) - .takes_value(true); + .action(clap::ArgAction::Set); options.push(arg); } @@ -227,19 +227,22 @@ impl JSONExporter { .get_one("step_duration_nano") .expect("Wrong step_duration_nano value, should be a number of nano seconds"); - self.regex = if !parameters.is_present("regex_filter") - || parameters.value_of("regex_filter").unwrap().is_empty() + self.regex = if !parameters.get_flag("regex_filter") + || parameters + .get_one::("regex_filter") + .unwrap() + .is_empty() { None } else { Some( - Regex::new(parameters.value_of("regex_filter").unwrap()) + Regex::new(parameters.get_one::("regex_filter").unwrap()) .expect("Wrong regex_filter, regexp is invalid"), ) }; - if parameters.occurrences_of("regex_filter") == 1 - && parameters.occurrences_of("max_top_consumers") == 1 + if parameters.value_source("regex_filter") == Some(ValueSource::CommandLine) + && parameters.value_source("max_top_consumers") == Some(ValueSource::CommandLine) { let warning = String::from("Warning: (--max-top-consumers) and (-r / --regex) used at the same time. (--max-top-consumers) disabled"); @@ -247,7 +250,7 @@ impl JSONExporter { } #[cfg(feature = "containers")] - if !parameters.is_present("containers") && parameters.is_present("container_regex") { + if !parameters.get_flag("containers") && parameters.get_flag("container_regex") { let warning = String::from("Warning: --container-regex is used but --containers is not enabled. Regex search won't work."); eprintln!("{}", warning.bright_yellow()); @@ -383,8 +386,8 @@ impl JSONExporter { let consumers: Vec<(IProcess, f64)>; let max_top = parameters - .value_of("max_top_consumers") - .unwrap_or("10") + .get_one::("max_top_consumers") + .unwrap_or(&String::from("10")) .parse::() .unwrap(); if let Some(regex_filter) = &self.regex { @@ -393,11 +396,11 @@ impl JSONExporter { .topology .proc_tracker .get_filtered_processes(regex_filter); - } else if parameters.is_present("container_regex") { + } else if parameters.get_flag("container_regex") { #[cfg(feature = "containers")] { consumers = metric_generator.get_processes_filtered_by_container_name( - &Regex::new(parameters.value_of("container_regex").unwrap()) + &Regex::new(parameters.get_one::("container_regex").unwrap()) .expect("Wrong container_regex expression. Regexp is invalid."), ); } @@ -460,8 +463,7 @@ impl JSONExporter { }) .collect::>(); - if parameters.is_present("resources") { - info!("ADDING RESOURCES"); + if parameters.get_flag("resources") { for c in top_consumers.iter_mut() { let mut res = ResourcesUsage { cpu_usage: String::from("0"), diff --git a/src/exporters/warpten.rs b/src/exporters/warpten.rs index 375cc13f..dfbe7796 100644 --- a/src/exporters/warpten.rs +++ b/src/exporters/warpten.rs @@ -1,6 +1,6 @@ use crate::exporters::*; -use clap::{value_parser, Arg}; use crate::sensors::{utils::IProcess, RecordGenerator, Sensor, Topology}; +use clap::{value_parser, Arg}; use std::time::Duration; use std::{env, thread}; use utils::get_scaphandre_version; diff --git a/src/main.rs b/src/main.rs index d61fe784..39b4d016 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,11 +8,7 @@ fn main() { #[cfg(target_os = "windows")] let sensors = ["msr_rapl"]; let exporters_options = get_exporters_options(); - let exporters: Vec = exporters_options - .keys() - .into_iter() - .map(|x| x.to_string()) - .collect(); + let exporters: Vec = exporters_options.keys().map(|x| x.to_string()).collect(); #[cfg(target_os = "linux")] let sensor_default_value = String::from("powercap_rapl"); From ae437a379f4a61c61a92a3a29c7e32c606a65463 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 27 Mar 2023 13:01:59 +0200 Subject: [PATCH 086/303] fix: add cargo.lock --- Cargo.lock | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 7b8812c9..d259f971 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2064,6 +2064,19 @@ dependencies = [ "windows_x86_64_msvc 0.27.0", ] +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -2100,6 +2113,12 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7d1649bbab232cde71148c6ef7bbe647f214d2154dd66347fada60de40cda7" +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -2112,6 +2131,12 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4eb20b59b93fc302839f3b0df3e61de7e9606b44cb54cbeb68d71cf137309fa" +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -2124,6 +2149,12 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40331d8ef3e4dcdc8982eb7de16e1f09b86f5384626a56b3a99c2a51b88ff98e" +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -2136,6 +2167,12 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5937d290e39c3308147d9b877c5fa741c50f4121ea78d2d20c4a138ad365464a" +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -2154,6 +2191,12 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dee1b76aec4e2bead4758a181b663c37af0de7ec56fe6837c10215b8d6a1635f" +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" From 444b3c64c0a9e437812ffacb161b1ac761ab006d Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 27 Mar 2023 13:15:42 +0200 Subject: [PATCH 087/303] fix: upgrade rust version in dockerfile --- Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index e622898e..c751b263 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.59 as planner +FROM rust:1.68 as planner WORKDIR app RUN cargo install cargo-chef @@ -7,7 +7,7 @@ COPY . . # Analyze dependencies RUN cargo chef prepare --recipe-path recipe.json -FROM rust:1.59 as cacher +FROM rust:1.68 as cacher WORKDIR app RUN cargo install cargo-chef COPY --from=planner /app/recipe.json recipe.json @@ -15,7 +15,7 @@ COPY --from=planner /app/recipe.json recipe.json # Cache dependencies RUN cargo chef cook --release --recipe-path recipe.json -FROM rust:1.59 as builder +FROM rust:1.68 as builder WORKDIR app COPY . . From 8e96b2c1ec8c332cf59319a561fe12a2254c6c83 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 28 Mar 2023 18:24:35 +0200 Subject: [PATCH 088/303] chore: first metrics working, still a parse error on domain metrics --- src/exporters/warpten.rs | 271 ++++++++------------------------------- 1 file changed, 51 insertions(+), 220 deletions(-) diff --git a/src/exporters/warpten.rs b/src/exporters/warpten.rs index dfbe7796..79d03bc2 100644 --- a/src/exporters/warpten.rs +++ b/src/exporters/warpten.rs @@ -1,16 +1,17 @@ +use super::utils::get_hostname; use crate::exporters::*; -use crate::sensors::{utils::IProcess, RecordGenerator, Sensor, Topology}; +use crate::sensors::{Sensor, Topology}; use clap::{value_parser, Arg}; use std::time::Duration; use std::{env, thread}; -use utils::get_scaphandre_version; + //use warp10::data::Format; /// An exporter that sends power consumption data of the host and its processes to /// a [Warp10](https://warp10.io) instance through **HTTP(s)** /// (contributions welcome to support websockets). pub struct Warp10Exporter { - topology: Topology, + sensor: Box, } impl Exporter for Warp10Exporter { @@ -32,6 +33,7 @@ impl Exporter for Warp10Exporter { //let read_token = parameters.value_of("read-token"); let step: u64 = *parameters.get_one("step").unwrap(); let qemu = parameters.get_flag("qemu"); + let watch_containers = parameters.get_flag("containers"); loop { match self.iteration( @@ -39,8 +41,8 @@ impl Exporter for Warp10Exporter { scheme, port.parse::().unwrap(), &write_token, - //read_token, qemu, + watch_containers, ) { Ok(res) => debug!("Result: {:?}", res), Err(err) => error!("Failed ! {:?}", err), @@ -105,19 +107,21 @@ impl Exporter for Warp10Exporter { .action(clap::ArgAction::SetTrue); options.push(arg); + let arg = Arg::new("containers") + .help("Monitor and apply labels for processes running as containers") + .long("containers") + .required(false) + .action(clap::ArgAction::SetTrue); + options.push(arg); + options } } impl Warp10Exporter { /// Instantiates and returns a new Warp10Exporter - pub fn new(mut sensor: Box) -> Warp10Exporter { - if let Some(topo) = *sensor.get_topology() { - Warp10Exporter { topology: topo } - } else { - error!("Could'nt generate the Topology."); - panic!("Could'nt generate the Topology."); - } + pub fn new(sensor: Box) -> Warp10Exporter { + Warp10Exporter { sensor } } /// Collects data from the Topology, creates warp10::Data objects containing the @@ -131,234 +135,63 @@ impl Warp10Exporter { write_token: &str, //read_token: Option<&str>, qemu: bool, + watch_containers: bool, ) -> Result, warp10::Error> { let client = warp10::Client::new(&format!("{scheme}://{host}:{port}"))?; let writer = client.get_writer(write_token.to_string()); - self.topology - .proc_tracker - .clean_terminated_process_records_vectors(); - - debug!("Refreshing topology."); - self.topology.refresh(); - - let records = self.topology.get_records_passive(); - let scaphandre_version = get_scaphandre_version(); - - let labels = vec![]; - - let mut data = vec![warp10::Data::new( - time::OffsetDateTime::now_utc(), - None, - String::from("scaph_self_version"), - labels.clone(), - warp10::Value::Double(scaphandre_version.parse::().unwrap()), - )]; - if let Some(metric_value) = self.topology.get_process_cpu_usage_percentage( - IProcess::myself(&self.topology.proc_tracker).unwrap().pid, - ) { - data.push(warp10::Data::new( - time::OffsetDateTime::now_utc(), - None, - String::from("scaph_self_cpu_usage_percent"), - labels.clone(), - warp10::Value::Int(metric_value.value.parse::().unwrap()), - )); - } - - if let Some(metric_value) = self.topology.get_process_cpu_usage_percentage( - IProcess::myself(&self.topology.proc_tracker).unwrap().pid, - ) { - data.push(warp10::Data::new( - time::OffsetDateTime::now_utc(), - None, - String::from("scaph_self_cpu_usage_percent"), - labels.clone(), - warp10::Value::Int(metric_value.value.parse::().unwrap()), - )); - } + let topology: Topology; - if let Ok(metric_value) = procfs::process::Process::myself().unwrap().statm() { - let value = metric_value.size * procfs::page_size().unwrap() as u64; - data.push(warp10::Data::new( - time::OffsetDateTime::now_utc(), - None, - String::from("scaph_self_mem_total_program_size"), - labels.clone(), - warp10::Value::Int(value as i32), - )); - let value = metric_value.resident * procfs::page_size().unwrap() as u64; - data.push(warp10::Data::new( - time::OffsetDateTime::now_utc(), - None, - String::from("scaph_self_mem_resident_set_size"), - labels.clone(), - warp10::Value::Int(value as i32), - )); - let value = metric_value.shared * procfs::page_size().unwrap() as u64; - data.push(warp10::Data::new( - time::OffsetDateTime::now_utc(), - None, - String::from("scaph_self_mem_shared_resident_size"), - labels.clone(), - warp10::Value::Int(value as i32), - )); + match *self.sensor.get_topology() { + Some(topo) => { + topology = topo; + } + None => { + panic!("Couldn't generate the Topology"); + } } - let metric_value = self.topology.stat_buffer.len(); - data.push(warp10::Data::new( - time::OffsetDateTime::now_utc(), - None, - String::from("scaph_self_topo_stats_nb"), - labels.clone(), - warp10::Value::Int(metric_value as i32), - )); + let mut metric_generator = + MetricGenerator::new(topology, get_hostname(), qemu, watch_containers); + metric_generator + .topology + .proc_tracker + .clean_terminated_process_records_vectors(); - let metric_value = self.topology.record_buffer.len(); - data.push(warp10::Data::new( - time::OffsetDateTime::now_utc(), - None, - String::from("scaph_self_topo_records_nb"), - labels.clone(), - warp10::Value::Int(metric_value as i32), - )); + debug!("Refreshing topology."); + metric_generator.topology.refresh(); - let metric_value = self.topology.proc_tracker.procs.len(); - data.push(warp10::Data::new( - time::OffsetDateTime::now_utc(), - None, - String::from("scaph_self_topo_procs_nb"), - labels.clone(), - warp10::Value::Int(metric_value as i32), - )); + metric_generator.gen_all_metrics(); - for socket in &self.topology.sockets { - let mut metric_labels = labels.clone(); - metric_labels.push(warp10::Label::new("socket_id", &socket.id.to_string())); - let metric_value = socket.stat_buffer.len(); - data.push(warp10::Data::new( - time::OffsetDateTime::now_utc(), - None, - String::from("scaph_self_socket_stats_nb"), - metric_labels.clone(), - warp10::Value::Int(metric_value as i32), - )); - let metric_value = socket.record_buffer.len(); - data.push(warp10::Data::new( - time::OffsetDateTime::now_utc(), - None, - String::from("scaph_self_socket_records_nb"), - metric_labels.clone(), - warp10::Value::Int(metric_value as i32), - )); - - let socket_records = socket.get_records_passive(); - if !socket_records.is_empty() { - let socket_energy_microjoules = &socket_records.last().unwrap().value; - if let Ok(metric_value) = socket_energy_microjoules.parse::() { - data.push(warp10::Data::new( - time::OffsetDateTime::now_utc(), - None, - String::from("scaph_socket_energy_microjoules"), - metric_labels.clone(), - warp10::Value::Long(metric_value), - )); - } + let mut process_data: Vec = vec![]; - if let Some(metric_value) = socket.get_records_diff_power_microwatts() { - data.push(warp10::Data::new( - time::OffsetDateTime::now_utc(), - None, - String::from("scaph_socket_power_microwatts"), - metric_labels.clone(), - warp10::Value::Long(metric_value.value.parse::().unwrap()), - )); - } - } + for metric in metric_generator.pop_metrics() { + let mut labels = vec![]; - for domain in &socket.domains { - let mut metric_labels = labels.clone(); - metric_labels.push(warp10::Label::new("rapl_domain_name", &domain.name)); - let metric_value = domain.record_buffer.len(); - data.push(warp10::Data::new( - time::OffsetDateTime::now_utc(), - None, - String::from("scaph_self_domain_records_nb"), - metric_labels.clone(), - warp10::Value::Int(metric_value as i32), - )); + for (k, v) in metric.attributes { + labels.push(warp10::Label::new(&k, &v)); } - } - if !records.is_empty() { - let record = records.last().unwrap(); - let metric_value = record.value.clone(); - - data.push(warp10::Data::new( + process_data.push(warp10::Data::new( time::OffsetDateTime::now_utc(), None, - String::from("scaph_host_energy_microjoules"), - labels.clone(), - warp10::Value::Long(metric_value.parse::().unwrap()), + metric.name, + labels, + warp10::Value::String(metric.metric_value.to_string()), )); - - if let Some(metric_value) = self.topology.get_records_diff_power_microwatts() { - data.push(warp10::Data::new( - time::OffsetDateTime::now_utc(), - None, - String::from("scaph_host_power_microwatts"), - labels.clone(), - warp10::Value::Long(metric_value.value.parse::().unwrap()), - )); - } } - let res = writer.post_sync(data)?; + let res = writer.post_sync(process_data)?; - let mut results = vec![res]; + let results = vec![res]; - let mut process_data = vec![warp10::Data::new( - time::OffsetDateTime::now_utc(), - None, - String::from("scaph_self_version"), - labels.clone(), - warp10::Value::Double(scaphandre_version.parse::().unwrap()), - )]; - - let processes_tracker = &self.topology.proc_tracker; - for pid in processes_tracker.get_alive_pids() { - let exe = processes_tracker.get_process_name(pid); - let cmdline = processes_tracker.get_process_cmdline(pid); - - let mut plabels = labels.clone(); - plabels.push(warp10::Label::new("pid", &pid.to_string())); - plabels.push(warp10::Label::new("exe", &exe)); - if let Some(cmdline_str) = cmdline { - if qemu { - if let Some(vmname) = utils::filter_qemu_cmdline(&cmdline_str) { - plabels.push(warp10::Label::new("vmname", &vmname)); - } - } - plabels.push(warp10::Label::new( - "cmdline", - &cmdline_str.replace('\"', "\\\""), - )); - } - let metric_name = format!( - "{}_{}_{}", - "scaph_process_power_consumption_microwats", pid, exe - ); - if let Some(power) = self.topology.get_process_power_consumption_microwatts(pid) { - process_data.push(warp10::Data::new( - time::OffsetDateTime::now_utc(), - None, - metric_name, - plabels, - warp10::Value::Long(power.value.parse::().unwrap()), - )); - } - } - let process_res = writer.post_sync(process_data)?; + //let mut process_data = vec![warp10::Data::new( + // time::OffsetDateTime::now_utc(), + // None, + // String::from("scaph_self_version"), + // labels.clone(), + // warp10::Value::Double(scaphandre_version.parse::().unwrap()), + //)]; //if let Some(token) = read_token { //let reader = client.get_reader(token.to_owned()); @@ -376,8 +209,6 @@ impl Warp10Exporter { //} //} - results.push(process_res); - Ok(results) } } From 715d1314435ad1aab86b904a8ed04115a90c3092 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 28 Mar 2023 18:43:11 +0200 Subject: [PATCH 089/303] style: fmt and clippy --- src/exporters/warpten.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/exporters/warpten.rs b/src/exporters/warpten.rs index 79d03bc2..3e488fa6 100644 --- a/src/exporters/warpten.rs +++ b/src/exporters/warpten.rs @@ -1,6 +1,6 @@ use super::utils::get_hostname; use crate::exporters::*; -use crate::sensors::{Sensor, Topology}; +use crate::sensors::Sensor; use clap::{value_parser, Arg}; use std::time::Duration; use std::{env, thread}; @@ -140,16 +140,12 @@ impl Warp10Exporter { let client = warp10::Client::new(&format!("{scheme}://{host}:{port}"))?; let writer = client.get_writer(write_token.to_string()); - let topology: Topology; - - match *self.sensor.get_topology() { - Some(topo) => { - topology = topo; - } + let topology = match *self.sensor.get_topology() { + Some(topo) => topo, None => { panic!("Couldn't generate the Topology"); } - } + }; let mut metric_generator = MetricGenerator::new(topology, get_hostname(), qemu, watch_containers); From 018d51425f8943502711467165dd734ecb0852f3 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 28 Mar 2023 19:14:54 +0200 Subject: [PATCH 090/303] chore: updating warp10.rs and time --- Cargo.lock | 171 ++++------------------------------------------------- Cargo.toml | 6 +- 2 files changed, 13 insertions(+), 164 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d259f971..43722bc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,12 +63,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "base-x" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" - [[package]] name = "base64" version = "0.13.1" @@ -192,12 +186,6 @@ dependencies = [ "cache-padded", ] -[[package]] -name = "const_fn" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" - [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -351,12 +339,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "discard" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" - [[package]] name = "docker-sync" version = "0.1.2" @@ -1100,12 +1082,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - [[package]] name = "proc-macro2" version = "1.0.47" @@ -1287,15 +1263,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver", -] - [[package]] name = "rustls" version = "0.19.1" @@ -1346,7 +1313,7 @@ dependencies = [ "serde", "serde_json", "sysinfo", - "time 0.2.27", + "time 0.3.20", "tokio", "warp10", "windows", @@ -1384,21 +1351,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - [[package]] name = "serde" version = "1.0.147" @@ -1452,21 +1404,6 @@ dependencies = [ "yaml-rust", ] -[[package]] -name = "sha1" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" -dependencies = [ - "sha1_smol", -] - -[[package]] -name = "sha1_smol" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" - [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -1518,64 +1455,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -[[package]] -name = "standback" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" -dependencies = [ - "version_check", -] - -[[package]] -name = "stdweb" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" -dependencies = [ - "discard", - "rustc_version", - "stdweb-derive", - "stdweb-internal-macros", - "stdweb-internal-runtime", - "wasm-bindgen", -] - -[[package]] -name = "stdweb-derive" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "serde_derive", - "syn", -] - -[[package]] -name = "stdweb-internal-macros" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" -dependencies = [ - "base-x", - "proc-macro2", - "quote", - "serde", - "serde_derive", - "serde_json", - "sha1", - "syn", -] - -[[package]] -name = "stdweb-internal-runtime" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" - [[package]] name = "strsim" version = "0.10.0" @@ -1664,41 +1543,19 @@ dependencies = [ [[package]] name = "time" -version = "0.2.27" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" dependencies = [ - "const_fn", - "libc", - "standback", - "stdweb", - "time-macros", - "version_check", - "winapi", -] - -[[package]] -name = "time-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" -dependencies = [ - "proc-macro-hack", - "time-macros-impl", + "serde", + "time-core", ] [[package]] -name = "time-macros-impl" -version = "0.1.2" +name = "time-core" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" -dependencies = [ - "proc-macro-hack", - "proc-macro2", - "quote", - "standback", - "syn", -] +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" [[package]] name = "tinyvec" @@ -1865,12 +1722,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - [[package]] name = "waker-fn" version = "1.1.0" @@ -1889,15 +1740,15 @@ dependencies = [ [[package]] name = "warp10" -version = "1.2.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140e989c5e92da4e09581133f4de7df32d1ce7de6b7a077652bdfa3f9aef97bf" +checksum = "e45b50e49a8a42f57459d1f2875c77c0825dc10dc69720a1cd146d571de4d621" dependencies = [ "isahc", "percent-encoding", "serde", "serde_json", - "time 0.2.27", + "time 0.3.20", "url", ] diff --git a/Cargo.toml b/Cargo.toml index 4410bb41..9202d98f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,11 +24,9 @@ protobuf = "2.28.0" serde = { version = "1.0", features = ["derive"], optional = true } serde_json = { version = "1.0", optional = true } ordered-float = "2.0" -warp10 = { version = "1.0.0", optional = true } +warp10 = { version = "2.0.0", optional = true } rand = { version = "0.7.3" } -#time = { version = "0.3.11", features = ["std"] } -#time blocked because used by chrono and warp10 -time = "0.2.25" +time = "0.3.20" colored = "2.0.0" chrono = "0.4.19" docker-sync = { version = "0.1.2", optional = true } From 9c4f641342acd5fd4cedf0355a0e97034b0f32ab Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 11 Apr 2023 10:45:18 +0200 Subject: [PATCH 091/303] fix: process metrics --- src/exporters/warpten.rs | 54 ++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/exporters/warpten.rs b/src/exporters/warpten.rs index 3e488fa6..98e0bfdd 100644 --- a/src/exporters/warpten.rs +++ b/src/exporters/warpten.rs @@ -12,6 +12,7 @@ use std::{env, thread}; /// (contributions welcome to support websockets). pub struct Warp10Exporter { sensor: Box, + metric_generator: MetricGenerator } impl Exporter for Warp10Exporter { @@ -34,6 +35,8 @@ impl Exporter for Warp10Exporter { let step: u64 = *parameters.get_one("step").unwrap(); let qemu = parameters.get_flag("qemu"); let watch_containers = parameters.get_flag("containers"); + self.metric_generator.watch_containers = watch_containers; + self.metric_generator.qemu = qemu; loop { match self.iteration( @@ -41,8 +44,6 @@ impl Exporter for Warp10Exporter { scheme, port.parse::().unwrap(), &write_token, - qemu, - watch_containers, ) { Ok(res) => debug!("Result: {:?}", res), Err(err) => error!("Failed ! {:?}", err), @@ -120,8 +121,16 @@ impl Exporter for Warp10Exporter { impl Warp10Exporter { /// Instantiates and returns a new Warp10Exporter - pub fn new(sensor: Box) -> Warp10Exporter { - Warp10Exporter { sensor } + pub fn new(mut sensor: Box) -> Warp10Exporter { + let topology = match *sensor.get_topology() { + Some(topo) => topo, + None => { + panic!("Couldn't generate the Topology"); + } + }; + let metric_generator = + MetricGenerator::new(topology, get_hostname(), false, false); + Warp10Exporter { sensor, metric_generator } } /// Collects data from the Topology, creates warp10::Data objects containing the @@ -134,47 +143,38 @@ impl Warp10Exporter { port: u16, write_token: &str, //read_token: Option<&str>, - qemu: bool, - watch_containers: bool, ) -> Result, warp10::Error> { let client = warp10::Client::new(&format!("{scheme}://{host}:{port}"))?; let writer = client.get_writer(write_token.to_string()); - let topology = match *self.sensor.get_topology() { - Some(topo) => topo, - None => { - panic!("Couldn't generate the Topology"); - } - }; - - let mut metric_generator = - MetricGenerator::new(topology, get_hostname(), qemu, watch_containers); - metric_generator + self.metric_generator .topology .proc_tracker .clean_terminated_process_records_vectors(); debug!("Refreshing topology."); - metric_generator.topology.refresh(); + self.metric_generator.topology.refresh(); - metric_generator.gen_all_metrics(); + self.metric_generator.gen_all_metrics(); - let mut process_data: Vec = vec![]; + let process_data: Vec = vec![]; - for metric in metric_generator.pop_metrics() { + for metric in self.metric_generator.pop_metrics() { let mut labels = vec![]; for (k, v) in metric.attributes { labels.push(warp10::Label::new(&k, &v)); } - process_data.push(warp10::Data::new( - time::OffsetDateTime::now_utc(), - None, - metric.name, - labels, - warp10::Value::String(metric.metric_value.to_string()), - )); + //if !metric.name.starts_with("scaph_domain") && !metric.name.starts_with("scaph_socket") { + // process_data.push(warp10::Data::new( + // time::OffsetDateTime::now_utc(), + // None, + // metric.name, + // labels, + // warp10::Value::String(metric.metric_value.to_string()), + // )); + //} } let res = writer.post_sync(process_data)?; From 9e126fe841c1df53113d188f62240f2065c57688 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 11 Apr 2023 10:55:06 +0200 Subject: [PATCH 092/303] style: fmt and clippy --- src/exporters/warpten.rs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/exporters/warpten.rs b/src/exporters/warpten.rs index 98e0bfdd..e65d913d 100644 --- a/src/exporters/warpten.rs +++ b/src/exporters/warpten.rs @@ -11,8 +11,7 @@ use std::{env, thread}; /// a [Warp10](https://warp10.io) instance through **HTTP(s)** /// (contributions welcome to support websockets). pub struct Warp10Exporter { - sensor: Box, - metric_generator: MetricGenerator + metric_generator: MetricGenerator, } impl Exporter for Warp10Exporter { @@ -39,12 +38,7 @@ impl Exporter for Warp10Exporter { self.metric_generator.qemu = qemu; loop { - match self.iteration( - host, - scheme, - port.parse::().unwrap(), - &write_token, - ) { + match self.iteration(host, scheme, port.parse::().unwrap(), &write_token) { Ok(res) => debug!("Result: {:?}", res), Err(err) => error!("Failed ! {:?}", err), } @@ -128,9 +122,8 @@ impl Warp10Exporter { panic!("Couldn't generate the Topology"); } }; - let metric_generator = - MetricGenerator::new(topology, get_hostname(), false, false); - Warp10Exporter { sensor, metric_generator } + let metric_generator = MetricGenerator::new(topology, get_hostname(), false, false); + Warp10Exporter { metric_generator } } /// Collects data from the Topology, creates warp10::Data objects containing the From e7cc144205d57f2ec0683643c032228f265f7e06 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 11 Apr 2023 13:34:21 +0200 Subject: [PATCH 093/303] fix: reenabling qemu even if warp10 --- src/lib.rs | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index fd44d01d..a1933ed9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,7 @@ use colored::*; use exporters::json::JSONExporter; #[cfg(feature = "prometheus")] use exporters::prometheus::PrometheusExporter; -#[cfg(all(target_os = "linux", not(feature = "warpten")))] +#[cfg(target_os = "linux")] use exporters::qemu::QemuExporter; #[cfg(feature = "riemann")] use exporters::riemann::RiemannExporter; @@ -92,32 +92,25 @@ pub fn run(matches: ArgMatches) { exporter_parameters = prometheus_exporter_parameters.clone(); let mut exporter = PrometheusExporter::new(sensor_boxed); exporter.run(exporter_parameters); + } else if let Some(warp10_exporter_parameters) = matches.subcommand_matches("warp10") { + if header { + scaphandre_header("warp10"); + } + exporter_parameters = warp10_exporter_parameters.clone(); + let mut exporter = Warp10Exporter::new(sensor_boxed); + exporter.run(exporter_parameters); } else { #[cfg(target_os = "linux")] { - #[cfg(feature = "warpten")] - { - if let Some(warp10_exporter_parameters) = matches.subcommand_matches("warp10") { - if header { - scaphandre_header("warp10"); - } - exporter_parameters = warp10_exporter_parameters.clone(); - let mut exporter = Warp10Exporter::new(sensor_boxed); - exporter.run(exporter_parameters); - } - } - #[cfg(not(feature = "warpten"))] - { - if let Some(qemu_exporter_parameters) = matches.subcommand_matches("qemu") { - if header { - scaphandre_header("qemu"); - } - exporter_parameters = qemu_exporter_parameters.clone(); - let mut exporter = QemuExporter::new(sensor_boxed); - exporter.run(exporter_parameters); + if let Some(qemu_exporter_parameters) = matches.subcommand_matches("qemu") { + if header { + scaphandre_header("qemu"); } - error!("Warp10 exporter feature was not included in this build."); + exporter_parameters = qemu_exporter_parameters.clone(); + let mut exporter = QemuExporter::new(sensor_boxed); + exporter.run(exporter_parameters); } + error!("Warp10 exporter feature was not included in this build."); } error!("Couldn't determine which exporter to run."); } From 42209e0389e934325b8ed43b1cc36a912b85be4f Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 12 Apr 2023 17:33:27 +0200 Subject: [PATCH 094/303] fix: compilation conditions for windows --- src/lib.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a1933ed9..be5f33b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,12 +93,15 @@ pub fn run(matches: ArgMatches) { let mut exporter = PrometheusExporter::new(sensor_boxed); exporter.run(exporter_parameters); } else if let Some(warp10_exporter_parameters) = matches.subcommand_matches("warp10") { - if header { - scaphandre_header("warp10"); + #[cfg(feature = "warpten")] + { + if header { + scaphandre_header("warp10"); + } + exporter_parameters = warp10_exporter_parameters.clone(); + let mut exporter = Warp10Exporter::new(sensor_boxed); + exporter.run(exporter_parameters); } - exporter_parameters = warp10_exporter_parameters.clone(); - let mut exporter = Warp10Exporter::new(sensor_boxed); - exporter.run(exporter_parameters); } else { #[cfg(target_os = "linux")] { From 1aa30994039ee73c18d51e6a77f69f51293e5312 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 17 Apr 2023 10:18:50 +0200 Subject: [PATCH 095/303] refactor: first working version of qemu exporter (poorly) using metric_generator --- src/exporters/mod.rs | 6 ++-- src/exporters/qemu.rs | 79 ++++++++++++++++++------------------------- tests/integration.rs | 52 +++++++++++++++------------- 3 files changed, 65 insertions(+), 72 deletions(-) diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index 79a3757d..36dbc301 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -35,7 +35,7 @@ use { /// General metric definition. #[derive(Debug)] -struct Metric { +pub struct Metric { /// `name` is the metric name, it will be used as service field for Riemann. name: String, // Will be used as service for Riemann /// `metric_type` mostly used by Prometheus, define is it is a gauge, counter... @@ -111,7 +111,7 @@ pub trait Exporter { /// MetricGenerator is an exporter helper structure to collect Scaphandre metrics. /// The goal is to provide a standard Vec\ that can be used by exporters /// to avoid code duplication. -struct MetricGenerator { +pub struct MetricGenerator { /// `data` will be used to store the metrics retrieved. data: Vec, /// `topology` is the system physical layout retrieve via the sensors crate with @@ -160,7 +160,7 @@ struct MetricGenerator { impl MetricGenerator { /// Returns a MetricGenerator instance that will host metrics. - fn new( + pub fn new( topology: Topology, hostname: String, _qemu: bool, diff --git a/src/exporters/qemu.rs b/src/exporters/qemu.rs index 77b70842..a82b9999 100644 --- a/src/exporters/qemu.rs +++ b/src/exporters/qemu.rs @@ -1,4 +1,5 @@ -use crate::exporters::Exporter; +use crate::exporters::utils::get_hostname; +use crate::exporters::{Exporter, MetricGenerator}; use crate::sensors::{utils::ProcessRecord, Sensor, Topology}; use std::{fs, io, thread, time}; @@ -9,7 +10,7 @@ use std::{fs, io, thread, time}; /// to collect and deal with their power consumption metrics, the same way /// they would do it if they managed bare metal machines. pub struct QemuExporter { - topology: Topology, + sensor: Box, } impl Exporter for QemuExporter { @@ -19,17 +20,21 @@ impl Exporter for QemuExporter { let path = "/var/lib/libvirt/scaphandre"; let cleaner_step = 120; let mut timer = time::Duration::from_secs(cleaner_step); - loop { - self.iteration(String::from(path)); - let step = time::Duration::from_secs(5); - thread::sleep(step); - if timer - step > time::Duration::from_millis(0) { - timer -= step; - } else { - self.topology - .proc_tracker - .clean_terminated_process_records_vectors(); - timer = time::Duration::from_secs(cleaner_step); + if let Ok(topology) = self.sensor.generate_topology() { + let mut metric_generator = MetricGenerator::new(topology, get_hostname(), true, false); + loop { + metric_generator.topology.refresh(); + self.iteration(String::from(path), &mut metric_generator); + let step = time::Duration::from_secs(5); + thread::sleep(step); + if timer - step > time::Duration::from_millis(0) { + timer -= step; + } else { + metric_generator.topology + .proc_tracker + .clean_terminated_process_records_vectors(); + timer = time::Duration::from_secs(cleaner_step); + } } } } @@ -42,53 +47,35 @@ impl Exporter for QemuExporter { impl QemuExporter { /// Instantiates and returns a new QemuExporter pub fn new(mut sensor: Box) -> QemuExporter { - let some_topology = *sensor.get_topology(); QemuExporter { - topology: some_topology.unwrap(), + sensor, } } /// Performs processing of metrics, using self.topology - pub fn iteration(&mut self, path: String) { + pub fn iteration(&mut self, path: String, metric_generator: &mut MetricGenerator) { trace!("path: {}", path); - self.topology.refresh(); - let topo_uj_diff = self.topology.get_records_diff(); - let topo_stat_diff = self.topology.get_stats_diff(); - if let Some(topo_rec_uj) = topo_uj_diff { - debug!("Got topo uj diff: {:?}", topo_rec_uj); - let proc_tracker = self.topology.get_proc_tracker(); - let processes = proc_tracker.get_alive_processes(); + + if let Some(topo_energy) = metric_generator.topology.get_records_diff_power_microwatts() { + let processes = metric_generator.topology.proc_tracker.get_alive_processes(); let qemu_processes = QemuExporter::filter_qemu_vm_processes(&processes); - debug!( - "Number of filtered qemu processes: {}", - qemu_processes.len() - ); for qp in qemu_processes { - info!("Working on {:?}", qp); if qp.len() > 2 { let last = qp.first().unwrap(); - let previous = qp.get(1).unwrap(); let vm_name = QemuExporter::get_vm_name_from_cmdline( - &last.process.cmdline(proc_tracker).unwrap(), + &last.process.cmdline(&metric_generator.topology.proc_tracker).unwrap(), ); - let time_pdiff = last.process.total_time_jiffies(proc_tracker) - - previous.process.total_time_jiffies(proc_tracker); - if let Some(time_tdiff) = &topo_stat_diff { - let first_domain_path = format!("{path}/{vm_name}/intel-rapl:0:0"); - if fs::read_dir(&first_domain_path).is_err() { - match fs::create_dir_all(&first_domain_path) { - Ok(_) => info!("Created {} folder.", &path), - Err(error) => panic!("Couldn't create {}. Got: {}", &path, error), - } + let first_domain_path = format!("{path}/{vm_name}/intel-rapl:0:0"); + if fs::read_dir(&first_domain_path).is_err() { + match fs::create_dir_all(&first_domain_path) { + Ok(_) => info!("Created {} folder.", &path), + Err(error) => panic!("Couldn't create {}. Got: {}", &path, error), } - let tdiff = time_tdiff.total_time_jiffies(); - trace!("Time_pdiff={} time_tdiff={}", time_pdiff.to_string(), tdiff); - let ratio = time_pdiff / tdiff; - trace!("Ratio is {}", ratio.to_string()); - let uj_to_add = ratio * topo_rec_uj.value.parse::().unwrap(); - trace!("Adding {} uJ", uj_to_add); + } + if let Some(ratio) = metric_generator.topology.get_process_cpu_usage_percentage(last.process.pid) { + let uj_to_add = ratio.value.parse::().unwrap() * topo_energy.value.parse::().unwrap() / 100.0; let complete_path = format!("{path}/{vm_name}/intel-rapl:0"); - if let Ok(result) = QemuExporter::add_or_create(&complete_path, uj_to_add) { + if let Ok(result) = QemuExporter::add_or_create(&complete_path, uj_to_add as u64) { trace!("{:?}", result); debug!("Updated {}", complete_path); } diff --git a/tests/integration.rs b/tests/integration.rs index 3f23f51a..2ac78389 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1,24 +1,30 @@ -#[cfg(target_os = "linux")] -use scaphandre::exporters::qemu::QemuExporter; -#[cfg(target_os = "linux")] -use scaphandre::sensors::powercap_rapl::PowercapRAPLSensor; -use std::env::current_dir; -use std::fs::{create_dir, read_dir}; +//#[cfg(target_os = "linux")] +//use scaphandre::exporters::qemu::QemuExporter; +//#[cfg(target_os = "linux")] +//use scaphandre::sensors::powercap_rapl::PowercapRAPLSensor; +//use std::env::current_dir; +//use std::fs::{create_dir, read_dir}; -#[cfg(target_os = "linux")] -#[test] -fn exporter_qemu() { - let sensor = PowercapRAPLSensor::new(1, 1, false); - let mut exporter = QemuExporter::new(Box::new(sensor)); - // Create integration_tests directory if it does not exist - let curdir = current_dir().unwrap(); - let path = curdir.join("integration_tests"); - if !path.is_dir() { - create_dir(&path).expect("Fail to create integration_tests directory"); - } - // Convert to std::string::String - let path = path.into_os_string().to_str().unwrap().to_string(); - exporter.iteration(path.clone()); - let content = read_dir(path); - assert_eq!(content.is_ok(), true); -} +//#[cfg(target_os = "linux")] +//#[test] +//fn exporter_qemu() { +// use scaphandre::{exporters::{MetricGenerator, utils::get_hostname}, sensors::Sensor}; +// +// let sensor = PowercapRAPLSensor::new(1, 1, false); +// let mut exporter = QemuExporter::new(Box::new(sensor)); +// // Create integration_tests directory if it does not exist +// let curdir = current_dir().unwrap(); +// let path = curdir.join("integration_tests"); +// if !path.is_dir() { +// create_dir(&path).expect("Fail to create integration_tests directory"); +// } +// // Convert to std::string::String +// let path = path.into_os_string().to_str().unwrap().to_string(); +// if let Ok(mut topology) = &sensor.generate_topology() { +// let mut metric_generator = MetricGenerator::new(topology, get_hostname(), true, false); +// exporter.iteration(path.clone(), &mut metric_generator); +// let content = read_dir(path); +// assert_eq!(content.is_ok(), true); +// } +//} +// \ No newline at end of file From 3634aca2eb81ef51fbd2ef6d32eeeb2ade9dce47 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 17 Apr 2023 10:19:32 +0200 Subject: [PATCH 096/303] style: clippy and fmt --- src/exporters/qemu.rs | 34 +++++++++++++++++++++++----------- tests/integration.rs | 2 +- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/exporters/qemu.rs b/src/exporters/qemu.rs index a82b9999..0b664444 100644 --- a/src/exporters/qemu.rs +++ b/src/exporters/qemu.rs @@ -1,6 +1,6 @@ use crate::exporters::utils::get_hostname; use crate::exporters::{Exporter, MetricGenerator}; -use crate::sensors::{utils::ProcessRecord, Sensor, Topology}; +use crate::sensors::{utils::ProcessRecord, Sensor}; use std::{fs, io, thread, time}; /// An Exporter that extracts power consumption data of running @@ -30,7 +30,8 @@ impl Exporter for QemuExporter { if timer - step > time::Duration::from_millis(0) { timer -= step; } else { - metric_generator.topology + metric_generator + .topology .proc_tracker .clean_terminated_process_records_vectors(); timer = time::Duration::from_secs(cleaner_step); @@ -46,24 +47,28 @@ impl Exporter for QemuExporter { impl QemuExporter { /// Instantiates and returns a new QemuExporter - pub fn new(mut sensor: Box) -> QemuExporter { - QemuExporter { - sensor, - } + pub fn new(sensor: Box) -> QemuExporter { + QemuExporter { sensor } } /// Performs processing of metrics, using self.topology pub fn iteration(&mut self, path: String, metric_generator: &mut MetricGenerator) { trace!("path: {}", path); - if let Some(topo_energy) = metric_generator.topology.get_records_diff_power_microwatts() { + if let Some(topo_energy) = metric_generator + .topology + .get_records_diff_power_microwatts() + { let processes = metric_generator.topology.proc_tracker.get_alive_processes(); let qemu_processes = QemuExporter::filter_qemu_vm_processes(&processes); for qp in qemu_processes { if qp.len() > 2 { let last = qp.first().unwrap(); let vm_name = QemuExporter::get_vm_name_from_cmdline( - &last.process.cmdline(&metric_generator.topology.proc_tracker).unwrap(), + &last + .process + .cmdline(&metric_generator.topology.proc_tracker) + .unwrap(), ); let first_domain_path = format!("{path}/{vm_name}/intel-rapl:0:0"); if fs::read_dir(&first_domain_path).is_err() { @@ -72,10 +77,17 @@ impl QemuExporter { Err(error) => panic!("Couldn't create {}. Got: {}", &path, error), } } - if let Some(ratio) = metric_generator.topology.get_process_cpu_usage_percentage(last.process.pid) { - let uj_to_add = ratio.value.parse::().unwrap() * topo_energy.value.parse::().unwrap() / 100.0; + if let Some(ratio) = metric_generator + .topology + .get_process_cpu_usage_percentage(last.process.pid) + { + let uj_to_add = ratio.value.parse::().unwrap() + * topo_energy.value.parse::().unwrap() + / 100.0; let complete_path = format!("{path}/{vm_name}/intel-rapl:0"); - if let Ok(result) = QemuExporter::add_or_create(&complete_path, uj_to_add as u64) { + if let Ok(result) = + QemuExporter::add_or_create(&complete_path, uj_to_add as u64) + { trace!("{:?}", result); debug!("Updated {}", complete_path); } diff --git a/tests/integration.rs b/tests/integration.rs index 2ac78389..fe4d60dd 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -27,4 +27,4 @@ // assert_eq!(content.is_ok(), true); // } //} -// \ No newline at end of file +// From 8638de0b1081ae47b70ce3ff1d8e5d8616b63f93 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 17 Apr 2023 11:16:16 +0200 Subject: [PATCH 097/303] doc: more instructions for qemu setup --- .../propagate-metrics-hypervisor-to-vm_qemu-kvm.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs_src/how-to_guides/propagate-metrics-hypervisor-to-vm_qemu-kvm.md b/docs_src/how-to_guides/propagate-metrics-hypervisor-to-vm_qemu-kvm.md index c3660a08..62aa94d1 100644 --- a/docs_src/how-to_guides/propagate-metrics-hypervisor-to-vm_qemu-kvm.md +++ b/docs_src/how-to_guides/propagate-metrics-hypervisor-to-vm_qemu-kvm.md @@ -29,7 +29,7 @@ In the definition of the virtual machine (here we are using libvirt), ensure you virsh edit DOMAIN_NAME -Then add: +Then add this filesystem configuration block inside the `` block: @@ -40,6 +40,13 @@ Then add: Save and (re)start the virtual machine. +If you get this error: "error: unsupported configuration: 'virtiofs' requires shared memory", you might add this configuration section to the `` section. + + + + + + Then connect to the virtual machine and mount the filesystem: mount -t 9p -o trans=virtio scaphandre /var/scaphandre From f1e80ba99c9ebbd6fe44f00e01ea59e2974faae7 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 17 Apr 2023 12:22:00 +0200 Subject: [PATCH 098/303] fix: merged #207 from @tawalaya to handle multiple logical core virtual machines --- src/sensors/mod.rs | 53 ++++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 887e5f37..231a32d8 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -275,32 +275,35 @@ impl Topology { /// Generates CPUCore instances for the host and adds them /// to appropriate CPUSocket instance from self.sockets pub fn add_cpu_cores(&mut self) { - let mut cores = Topology::generate_cpu_cores().unwrap(); - while !cores.is_empty() { - let c = cores.pop().unwrap(); - let socket_id = &c - .attributes - .get("physical id") - .unwrap() - .parse::() - .unwrap(); - let socket_match = self - .sockets - .iter_mut() - .find(|x| &x.id == socket_id); - - //In VMs there might be a missmatch betwen Sockets and Cores - see Issue#133 as a first fix we just map all cores that can't be mapped to the first - let socket = match socket_match { - Some(x) => x, - None =>self.sockets.first_mut().expect("Trick: if you are running on a vm, do not forget to use --vm parameter invoking scaphandre at the command line") - }; - - if socket_id == &socket.id { - socket.add_cpu_core(c); - } else { - socket.add_cpu_core(c); - warn!("coud't not match core to socket - mapping to first socket instead - if you are not using --vm there is something wrong") + if let Some(mut cores) = Topology::generate_cpu_cores() { + while !cores.is_empty() { + let c = cores.pop().unwrap(); + let socket_id = &c + .attributes + .get("physical id") + .unwrap() + .parse::() + .unwrap(); + let socket_match = self + .sockets + .iter_mut() + .find(|x| &x.id == socket_id); + + //In VMs there might be a missmatch betwen Sockets and Cores - see Issue#133 as a first fix we just map all cores that can't be mapped to the first + let socket = match socket_match { + Some(x) => x, + None =>self.sockets.first_mut().expect("Trick: if you are running on a vm, do not forget to use --vm parameter invoking scaphandre at the command line") + }; + + if socket_id == &socket.id { + socket.add_cpu_core(c); + } else { + socket.add_cpu_core(c); + warn!("coud't not match core to socket - mapping to first socket instead - if you are not using --vm there is something wrong") + } } + } else { + warn!("Couldn't retrieve any CPU Core from the topology. (generate_cpu_cores)"); } } From 6e3a7b8b7cc84b4a0223e59f3140c17f4f884d50 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 17 Apr 2023 12:22:18 +0200 Subject: [PATCH 099/303] style: fmt --- src/sensors/mod.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 231a32d8..3cb4c940 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -284,10 +284,7 @@ impl Topology { .unwrap() .parse::() .unwrap(); - let socket_match = self - .sockets - .iter_mut() - .find(|x| &x.id == socket_id); + let socket_match = self.sockets.iter_mut().find(|x| &x.id == socket_id); //In VMs there might be a missmatch betwen Sockets and Cores - see Issue#133 as a first fix we just map all cores that can't be mapped to the first let socket = match socket_match { From 8559d070311102f19a62f2056278a9b91a07aa0d Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 18 Apr 2023 13:41:36 +0200 Subject: [PATCH 100/303] fix: clap4 options config --- src/exporters/json.rs | 39 ++++++++++++++++----------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/src/exporters/json.rs b/src/exporters/json.rs index 2832ee8b..79da444e 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -85,7 +85,7 @@ impl Exporter for JSONExporter { .short('c') .long("containers") .required(false) - .action(clap::ArgAction::Set); + .action(clap::ArgAction::SetTrue); options.push(arg); } @@ -100,7 +100,8 @@ impl Exporter for JSONExporter { let arg = Arg::new("resources") .help("Monitor and include CPU/RAM/Disk usage per process.") .long("resources") - .required(false); + .required(false) + .action(clap::ArgAction::SetTrue); options.push(arg); #[cfg(feature = "containers")] @@ -113,14 +114,13 @@ impl Exporter for JSONExporter { options.push(arg); } - // the resulting labels of this option are not yet used by this exporter, activate this option once we display something interesting about it - //let arg = Arg::with_name("qemu") - // .help("Apply labels to metrics of processes looking like a Qemu/KVM virtual machine") - // .long("qemu") - // .short("q") - // .required(false) - // .takes_value(false); - //options.push(arg); + let arg = Arg::new("qemu") + .help("Apply labels to metrics of processes looking like a Qemu/KVM virtual machine") + .long("qemu") + .short('q') + .required(false) + .action(clap::ArgAction::SetTrue); + options.push(arg); options } } @@ -227,12 +227,7 @@ impl JSONExporter { .get_one("step_duration_nano") .expect("Wrong step_duration_nano value, should be a number of nano seconds"); - self.regex = if !parameters.get_flag("regex_filter") - || parameters - .get_one::("regex_filter") - .unwrap() - .is_empty() - { + self.regex = if parameters.get_one::("regex_filter").is_none() { None } else { Some( @@ -386,21 +381,19 @@ impl JSONExporter { let consumers: Vec<(IProcess, f64)>; let max_top = parameters - .get_one::("max_top_consumers") - .unwrap_or(&String::from("10")) - .parse::() - .unwrap(); + .get_one::("max_top_consumers") + .unwrap_or(&10); if let Some(regex_filter) = &self.regex { debug!("Processes filtered by '{}':", regex_filter.as_str()); consumers = metric_generator .topology .proc_tracker .get_filtered_processes(regex_filter); - } else if parameters.get_flag("container_regex") { + } else if let Some(param) = parameters.get_one::("container_regex") { #[cfg(feature = "containers")] { consumers = metric_generator.get_processes_filtered_by_container_name( - &Regex::new(parameters.get_one::("container_regex").unwrap()) + &Regex::new(param) .expect("Wrong container_regex expression. Regexp is invalid."), ); } @@ -415,7 +408,7 @@ impl JSONExporter { consumers = metric_generator .topology .proc_tracker - .get_top_consumers(max_top); + .get_top_consumers(*max_top); } let mut top_consumers = consumers .iter() From 222f282900e499592ef5081056d1a5727aa41161 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 19 Apr 2023 14:57:57 +0200 Subject: [PATCH 101/303] fix: clap4 parameters for windows --- src/exporters/json.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/exporters/json.rs b/src/exporters/json.rs index 79da444e..d2300295 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -402,7 +402,7 @@ impl JSONExporter { consumers = metric_generator .topology .proc_tracker - .get_top_consumers(max_top); + .get_top_consumers(*max_top); } } else { consumers = metric_generator From a1a06ea280b8e66067b2c3b73ac08a377604eb61 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 19 Apr 2023 15:51:08 +0200 Subject: [PATCH 102/303] fix: updating docker compose sample stack and docs according to #135 --- docker-compose/docker-compose.yaml | 1 + docs_src/tutorials/docker-compose.md | 2 ++ 2 files changed, 3 insertions(+) diff --git a/docker-compose/docker-compose.yaml b/docker-compose/docker-compose.yaml index 173df237..24233a62 100644 --- a/docker-compose/docker-compose.yaml +++ b/docker-compose/docker-compose.yaml @@ -20,6 +20,7 @@ services: target: "/var/lib/grafana/dashboards/sample/sample-dashboard.json" scaphandre: image: hubblo/scaphandre + privileged: true ports: - "8080:8080" volumes: diff --git a/docs_src/tutorials/docker-compose.md b/docs_src/tutorials/docker-compose.md index 2b6456a2..a559c63c 100644 --- a/docs_src/tutorials/docker-compose.md +++ b/docs_src/tutorials/docker-compose.md @@ -7,6 +7,8 @@ Once you have cloned the repository, just move to the docker-compose folder and cd docker-compose docker-compose up -d +Be warned: the sample stack runs scaphandre as a privileged container. Otherwise apparmor or equivalents might complain about ptrace calls on the host. See [#135](https://github.com/hubblo-org/scaphandre/issues/135). + Grafana will be available at `http://localhost:3000`, the default username is `admin` and the password is `secret`. Refresh the dashboard after 30s or enable auto-refresh and you should see the data filling the graphs. From 933e29b97eba2bd55e227539df795e8e1c395186 Mon Sep 17 00:00:00 2001 From: Ross Fairbanks Date: Tue, 3 Jan 2023 12:21:30 +0000 Subject: [PATCH 103/303] fix: Don't create PSP if k8s >= 1.25 --- helm/scaphandre/templates/psp.yaml | 2 ++ helm/scaphandre/templates/rbac.yaml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/helm/scaphandre/templates/psp.yaml b/helm/scaphandre/templates/psp.yaml index f7d702d5..269112ee 100644 --- a/helm/scaphandre/templates/psp.yaml +++ b/helm/scaphandre/templates/psp.yaml @@ -1,3 +1,4 @@ +{{- if .Capabilities.APIVersions.Has "policy/v1beta1" }} apiVersion: policy/v1beta1 kind: PodSecurityPolicy metadata: @@ -26,3 +27,4 @@ spec: - projected hostPID: true hostIPC: true +{{- end }} diff --git a/helm/scaphandre/templates/rbac.yaml b/helm/scaphandre/templates/rbac.yaml index 73d5003c..b13df11b 100644 --- a/helm/scaphandre/templates/rbac.yaml +++ b/helm/scaphandre/templates/rbac.yaml @@ -20,6 +20,7 @@ metadata: labels: {{- include "labels.common" . | nindent 4 }} rules: +{{- if .Capabilities.APIVersions.Has "policy/v1beta1" }} - apiGroups: - extensions resources: @@ -28,6 +29,7 @@ rules: - {{ .Chart.Name }} verbs: - "use" +{{- end }} - apiGroups: - "" resources: From 588e405492edbfd2e8a0dcaaabb8cd1a837a4b0c Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 15 May 2023 16:23:17 +0200 Subject: [PATCH 104/303] chore: first test with pushgateway --- Cargo.lock | 1 + Cargo.toml | 6 ++- src/exporters/mod.rs | 2 + src/exporters/prometheus.rs | 5 +- src/exporters/prometheuspush.rs | 96 +++++++++++++++++++++++++++++++++ src/exporters/utils.rs | 3 ++ src/lib.rs | 14 +++++ 7 files changed, 121 insertions(+), 6 deletions(-) create mode 100644 src/exporters/prometheuspush.rs diff --git a/Cargo.lock b/Cargo.lock index 43722bc0..4b92111a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1301,6 +1301,7 @@ dependencies = [ "docker-sync", "hostname", "hyper", + "isahc", "k8s-sync", "log", "loggerv", diff --git a/Cargo.toml b/Cargo.toml index 9202d98f..b5267f29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ k8s-sync = { version = "0.2.3", optional = true } hyper = { version = "0.14", features = ["full"], optional = true } tokio = { version = "1.26.0", features = ["full"], optional = true} sysinfo = { version = "0.28.3"} +isahc = { version = "1.7.2", optional = true } [target.'cfg(target_os="linux")'.dependencies] #procfs = "0.13.2" @@ -43,9 +44,10 @@ procfs = { version = "0.12.0" } windows = { version = "0.27.0", features = ["alloc","Win32_Storage_FileSystem","Win32_Foundation","Win32_Security","Win32_System_IO","Win32_System_Ioctl"]} [features] -default = ["prometheus", "riemann", "warpten", "json", "containers"] +default = ["prometheus", "riemann", "warpten", "json", "containers", "prometheuspush"] prometheus = ["hyper", "tokio"] riemann = ["riemann_client"] json = ["serde", "serde_json"] containers = ["docker-sync", "k8s-sync"] -warpten = ["warp10"] \ No newline at end of file +warpten = ["warp10"] +prometheuspush = ["isahc"] \ No newline at end of file diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index 36dbc301..08922cdd 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -3,6 +3,8 @@ //! `Exporter` is the root for all exporters. It defines the [Exporter] trait //! needed to implement an exporter. pub mod json; +#[cfg(feature = "prometheuspush")] +pub mod prometheuspush; #[cfg(feature = "prometheus")] pub mod prometheus; #[cfg(target_os = "linux")] diff --git a/src/exporters/prometheus.rs b/src/exporters/prometheus.rs index 033825ac..874376fb 100644 --- a/src/exporters/prometheus.rs +++ b/src/exporters/prometheus.rs @@ -2,7 +2,7 @@ //! //! `PrometheusExporter` implementation, expose metrics to //! a [Prometheus](https://prometheus.io/) server. -use super::utils::get_hostname; +use super::utils::{get_hostname, DEFAULT_IP_ADDRESS}; use crate::current_system_time_since_epoch; use crate::exporters::{Exporter, MetricGenerator, MetricValueType}; use crate::sensors::{Sensor, Topology}; @@ -19,9 +19,6 @@ use std::{ time::Duration, }; -/// Default ipv4/ipv6 address to expose the service is any -const DEFAULT_IP_ADDRESS: &str = "::"; - /// Exporter that exposes metrics to an HTTP endpoint /// matching the Prometheus.io metrics format. pub struct PrometheusExporter { diff --git a/src/exporters/prometheuspush.rs b/src/exporters/prometheuspush.rs new file mode 100644 index 00000000..06bd3ec2 --- /dev/null +++ b/src/exporters/prometheuspush.rs @@ -0,0 +1,96 @@ +//! # PrometheusPushExporter +//! +//! `PrometheusPushExporter` implementation, push/send metrics to +//! a [Prometheus](https://prometheus.io/) pushgateway. +//! +use clap::builder::TypedValueParser; +use isahc::{prelude::*, Request}; +use std::time::Duration; +use crate::exporters::{Exporter}; +use crate::sensors::{Sensor}; +use clap::{ArgMatches, Arg}; +use chrono::Utc; +use std::thread; + +pub struct PrometheusPushExporter { + sensor: Box, +} + +impl PrometheusPushExporter { + pub fn new(sensor: Box) -> PrometheusPushExporter { + PrometheusPushExporter { sensor } + } +} + +impl Exporter for PrometheusPushExporter { + fn run(&mut self, parameters: ArgMatches) { + info!( + "{}: Starting Prometheus Push exporter", + Utc::now().format("%Y-%m-%dT%H:%M:%S") + ); + + let step: String = *parameters.get_one("step").unwrap(); + let host: String = *parameters.get_one("host").unwrap(); + let scheme: String = *parameters.get_one("scheme").unwrap(); + let port: String = *parameters.get_one("port").unwrap(); + let route: String = *parameters.get_one("route").unwrap(); + let uri = format!("{scheme}://{host}:{port}/{route}"); + + loop { + let body = "# HELP mymetric this is my metric\n# TYPE mymetric gauge\nmymetric 50"; + if let Ok(request) = Request::post(uri.clone()) + .header("Content-Type", "text/plain") + .timeout(Duration::from_secs(5)) + .body(body) { + match request.send() { + Ok(response) => { + warn!("Got {:?}", response); + } + Err(err) => { + warn!("Got error : {:?}", err) + } + } + } + + thread::sleep(Duration::new(step.parse::().unwrap(), 0)); + } + } + /// Returns options understood by the exporter. + fn get_options() -> Vec { + let mut options = Vec::new(); + let arg = Arg::new("host") + .default_value("localhost") + .help("PushGateway's host FQDN or IP address.") + .long("host") + .short('H') + .required(false) // send to localhost if none + .action(clap::ArgAction::Set); + options.push(arg); + let arg = Arg::new("port") + .default_value("9091") + .help("PushGateway's TCP port number.") + .long("port") + .short('p') + .required(false) // send to localhost if none + .action(clap::ArgAction::Set); + options.push(arg); + let arg = Arg::new("scheme") + .default_value("https") + .help("http or https.") + .long("scheme") + .short('s') + .required(false) // send to localhost if none + .action(clap::ArgAction::Set); + options.push(arg); + let arg = Arg::new("step") + .default_value("20") + .help("Time between two push, in seconds.") + .long("step") + .short('S') + .required(false) // send to localhost if none + .action(clap::ArgAction::Set); + options.push(arg); + + options + } +} \ No newline at end of file diff --git a/src/exporters/utils.rs b/src/exporters/utils.rs index a0be7ba0..26cce7bc 100644 --- a/src/exporters/utils.rs +++ b/src/exporters/utils.rs @@ -8,6 +8,9 @@ use { k8s_sync::{errors::KubernetesError, kubernetes::Kubernetes}, }; +/// Default ipv4/ipv6 address to expose the service is any +pub const DEFAULT_IP_ADDRESS: &str = "::"; + /// Returns a cmdline String filtered from potential characters that /// could break exporters output. /// diff --git a/src/lib.rs b/src/lib.rs index be5f33b0..19daf2d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,8 @@ use colored::*; use exporters::json::JSONExporter; #[cfg(feature = "prometheus")] use exporters::prometheus::PrometheusExporter; +#[cfg(feature = "prometheuspush")] +use exporters::prometheuspush::PrometheusPushExporter; #[cfg(target_os = "linux")] use exporters::qemu::QemuExporter; #[cfg(feature = "riemann")] @@ -92,6 +94,13 @@ pub fn run(matches: ArgMatches) { exporter_parameters = prometheus_exporter_parameters.clone(); let mut exporter = PrometheusExporter::new(sensor_boxed); exporter.run(exporter_parameters); + } else if let Some(prometheuspush_exporter_parameters) = matches.subcommand_matches("prometheuspush") { + if header { + scaphandre_header("prometheuspush"); + } + exporter_parameters = prometheuspush_exporter_parameters.clone(); + let mut exporter = PrometheusPushExporter::new(sensor_boxed); + exporter.run(exporter_parameters); } else if let Some(warp10_exporter_parameters) = matches.subcommand_matches("warp10") { #[cfg(feature = "warpten")] { @@ -152,6 +161,11 @@ pub fn get_exporters_options() -> HashMap> { String::from("warp10"), exporters::warpten::Warp10Exporter::get_options(), ); + #[cfg(feature = "prometheuspush")] + options.insert( + String::from("prometheuspush"), + exporters::prometheuspush::PrometheusPushExporter::get_options(), + ); options } From b5a7d5d70e90a58e0a851c01f56fec726b5c415f Mon Sep 17 00:00:00 2001 From: "Raffin, Guillaume" Date: Tue, 28 Mar 2023 18:56:57 +0200 Subject: [PATCH 105/303] Refactor the creation of exporters with clap v4 derivation --- Cargo.lock | 725 ++++++++++++++++++++++------------- Cargo.toml | 9 +- src/exporters/json.rs | 396 ++++++++----------- src/exporters/mod.rs | 11 +- src/exporters/prometheus.rs | 224 ++++------- src/exporters/qemu.rs | 70 ++-- src/exporters/riemann.rs | 248 +++++------- src/exporters/stdout.rs | 238 +++++------- src/exporters/warpten.rs | 180 ++++----- src/lib.rs | 161 +------- src/main.rs | 262 +++++++++---- src/sensors/mod.rs | 14 +- src/sensors/msr_rapl.rs | 2 +- src/sensors/powercap_rapl.rs | 7 +- tests/integration.rs | 52 ++- 15 files changed, 1251 insertions(+), 1348 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 43722bc0..8274f23f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,9 +10,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "0.7.19" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] @@ -35,11 +35,60 @@ dependencies = [ "winapi", ] +[[package]] +name = "anstream" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" + +[[package]] +name = "anstyle-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + [[package]] name = "async-channel" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" +checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" dependencies = [ "concurrent-queue", "event-listener", @@ -52,7 +101,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -77,9 +126,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] name = "byteorder" @@ -89,15 +138,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" - -[[package]] -name = "cache-padded" -version = "1.2.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "castaway" @@ -107,9 +150,9 @@ checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" [[package]] name = "cc" -version = "1.0.74" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" @@ -119,43 +162,63 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.22" +version = "0.4.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" dependencies = [ "iana-time-zone", "js-sys", "num-integer", "num-traits", "serde", - "time 0.1.44", + "time 0.1.45", "wasm-bindgen", "winapi", ] [[package]] name = "clap" -version = "4.0.19" +version = "4.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e67816e006b17427c9b4386915109b494fec2d929c63e3bd3561234cbf1bf1e" +checksum = "49f9152d70e42172fdb87de2efd7327160beee37886027cf86f30a233d5b30b4" dependencies = [ - "atty", + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e067b220911598876eb55d52725ddcc201ffe3f0904018195973bc5b012ea2ca" +dependencies = [ + "anstream", + "anstyle", "bitflags", "clap_lex", "once_cell", "strsim", - "termcolor", ] [[package]] -name = "clap_lex" -version = "0.3.0" +name = "clap_derive" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" dependencies = [ - "os_str_bytes", + "heck", + "proc-macro2", + "quote", + "syn 2.0.15", ] +[[package]] +name = "clap_lex" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -166,6 +229,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "colored" version = "2.0.0" @@ -179,18 +248,18 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "1.2.4" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" dependencies = [ - "cache-padded", + "crossbeam-utils", ] [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "crc32fast" @@ -203,9 +272,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if", "crossbeam-utils", @@ -213,9 +282,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" dependencies = [ "cfg-if", "crossbeam-epoch", @@ -224,9 +293,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.11" +version = "0.9.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" +checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" dependencies = [ "autocfg", "cfg-if", @@ -237,9 +306,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.12" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" dependencies = [ "cfg-if", ] @@ -261,9 +330,9 @@ dependencies = [ [[package]] name = "curl-sys" -version = "0.4.59+curl-7.86.0" +version = "0.4.61+curl-8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cfce34829f448b08f55b7db6d0009e23e2e86a34e8c2b366269bf5799b4a407" +checksum = "14d05c10f541ae6f3bc5b3d923c20001f47db7d5f0b2bc6ad16490133842db79" dependencies = [ "cc", "libc", @@ -277,9 +346,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.80" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7d4e43b25d3c994662706a1d4fcfc32aaa6afd287502c111b237093bb23f3a" +checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" dependencies = [ "cc", "cxxbridge-flags", @@ -289,9 +358,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.80" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84f8829ddc213e2c1368e51a2564c552b65a8cb6a28f31e576270ac81d5e5827" +checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" dependencies = [ "cc", "codespan-reporting", @@ -299,24 +368,24 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn", + "syn 2.0.15", ] [[package]] name = "cxxbridge-flags" -version = "1.0.80" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e72537424b474af1460806647c41d4b6d35d09ef7fe031c5c2fa5766047cc56a" +checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" [[package]] name = "cxxbridge-macro" -version = "1.0.80" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "309e4fb93eed90e1e14bea0da16b209f81813ba9fc7830c20ed151dd7bc0a4d7" +checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] @@ -366,19 +435,40 @@ dependencies = [ [[package]] name = "either" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "encoding_rs" -version = "0.8.31" +version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" dependencies = [ "cfg-if", ] +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "event-listener" version = "2.5.3" @@ -387,18 +477,18 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "fastrand" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] [[package]] name = "flate2" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" dependencies = [ "crc32fast", "miniz_oxide", @@ -436,30 +526,30 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-lite" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ "fastrand", "futures-core", @@ -472,21 +562,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-core", "futures-task", @@ -507,9 +597,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "libc", @@ -518,9 +608,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.15" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" dependencies = [ "bytes", "fnv", @@ -541,6 +631,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -550,6 +646,21 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "hex" version = "0.4.3" @@ -569,9 +680,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", @@ -603,9 +714,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.22" +version = "0.14.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abfba89e19b959ca163c7752ba59d737c1ceea53a5d31a149c805446fc958064" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" dependencies = [ "bytes", "futures-channel", @@ -627,16 +738,16 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.53" +version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "winapi", + "windows 0.48.0", ] [[package]] @@ -661,9 +772,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", @@ -678,6 +789,29 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "io-lifetimes" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "isahc" version = "1.7.2" @@ -709,15 +843,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" dependencies = [ "wasm-bindgen", ] @@ -766,9 +900,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.140" +version = "0.2.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" +checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" [[package]] name = "libnghttp2-sys" @@ -794,9 +928,9 @@ dependencies = [ [[package]] name = "link-cplusplus" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" dependencies = [ "cc", ] @@ -807,6 +941,12 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "linux-raw-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f508063cc7bb32987c71511216bd5a32be15bccb6a80b52df8b9d7f01fc3aa2" + [[package]] name = "lock_api" version = "0.4.9" @@ -851,24 +991,24 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" -version = "0.6.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" dependencies = [ "autocfg", ] [[package]] name = "mime" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.5.4" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ "adler", ] @@ -915,25 +1055,25 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] [[package]] name = "once_cell" -version = "1.16.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "openssl" -version = "0.10.42" +version = "0.10.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13" +checksum = "7e30d8bc91859781f0a943411186324d580f2bbeb71b452fe91ae344806af3f1" dependencies = [ "bitflags", "cfg-if", @@ -946,13 +1086,13 @@ dependencies = [ [[package]] name = "openssl-macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] @@ -963,11 +1103,10 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.77" +version = "0.9.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03b84c3b2d099b81f0953422b4d4ad58761589d0229b5506356afca05a3670a" +checksum = "0d3d193fb1488ad46ffe3aaabc912cc931d02ee8518fe2959aea8ef52718b0c0" dependencies = [ - "autocfg", "cc", "libc", "pkg-config", @@ -983,17 +1122,11 @@ dependencies = [ "num-traits", ] -[[package]] -name = "os_str_bytes" -version = "6.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3baf96e39c5359d2eb0dd6ccb42c62b91d9678aa68160d261b9e0ccbf9e9dea9" - [[package]] name = "parking" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" [[package]] name = "parking_lot" @@ -1013,7 +1146,7 @@ checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "windows-sys 0.45.0", ] @@ -1041,7 +1174,7 @@ checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1064,16 +1197,18 @@ checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "polling" -version = "2.4.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab4609a838d88b73d8238967b60dd115cc08d38e2bbaf51ee1e4b695f89122e2" +checksum = "4be1c66a6add46bff50935c313dae30a5030cf8385c5206e8a95e9e9def974aa" dependencies = [ "autocfg", + "bitflags", "cfg-if", + "concurrent-queue", "libc", "log", - "wepoll-ffi", - "winapi", + "pin-project-lite", + "windows-sys 0.48.0", ] [[package]] @@ -1084,9 +1219,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" dependencies = [ "unicode-ident", ] @@ -1114,9 +1249,9 @@ checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" [[package]] name = "quote" -version = "1.0.21" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] @@ -1164,21 +1299,19 @@ dependencies = [ [[package]] name = "rayon" -version = "1.5.3" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" dependencies = [ - "autocfg", - "crossbeam-deque", "either", "rayon-core", ] [[package]] name = "rayon-core" -version = "1.9.3" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" dependencies = [ "crossbeam-channel", "crossbeam-deque", @@ -1195,22 +1328,31 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_users" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.8", - "redox_syscall", + "getrandom 0.2.9", + "redox_syscall 0.2.16", "thiserror", ] [[package]] name = "regex" -version = "1.7.0" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" dependencies = [ "aho-corasick", "memchr", @@ -1219,18 +1361,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" - -[[package]] -name = "remove_dir_all" -version = "0.5.3" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "riemann_client" @@ -1263,6 +1396,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "rustix" +version = "0.37.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "722529a737f5a942fdbac3a46cee213053196737c5eaa3386d52e85b786f2659" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + [[package]] name = "rustls" version = "0.19.1" @@ -1287,9 +1434,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "scaphandre" @@ -1316,17 +1463,16 @@ dependencies = [ "time 0.3.20", "tokio", "warp10", - "windows", + "windows 0.27.0", ] [[package]] name = "schannel" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ - "lazy_static", - "windows-sys 0.36.1", + "windows-sys 0.42.0", ] [[package]] @@ -1337,9 +1483,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "scratch" -version = "1.0.2" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" [[package]] name = "sct" @@ -1353,9 +1499,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.147" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" dependencies = [ "serde_derive", ] @@ -1372,20 +1518,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.147" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] name = "serde_json" -version = "1.0.87" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ "itoa", "ryu", @@ -1406,18 +1552,18 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] [[package]] name = "slab" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] @@ -1463,9 +1609,20 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.103" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" dependencies = [ "proc-macro2", "quote", @@ -1474,9 +1631,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.28.3" +version = "0.28.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f69e0d827cce279e61c2f3399eb789271a8f136d8245edef70f06e3c9601a670" +checksum = "b4c2f3ca6693feb29a89724516f016488e9aafc7f37264f898593ee4b942f31b" dependencies = [ "cfg-if", "core-foundation-sys", @@ -1489,52 +1646,51 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.3.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" dependencies = [ "cfg-if", "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.45.0", ] [[package]] name = "termcolor" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] name = "time" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" dependencies = [ "libc", "wasi 0.10.0+wasi-snapshot-preview1", @@ -1568,20 +1724,19 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.26.0" +version = "1.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" +checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" dependencies = [ "autocfg", "bytes", "libc", - "memchr", "mio", "num_cpus", "parking_lot", @@ -1594,20 +1749,20 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.8.2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] name = "tokio-util" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" dependencies = [ "bytes", "futures-core", @@ -1644,7 +1799,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1668,21 +1823,21 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unicode-normalization" @@ -1716,6 +1871,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "vcpkg" version = "0.2.15" @@ -1772,9 +1933,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1782,24 +1943,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1807,28 +1968,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" dependencies = [ "js-sys", "wasm-bindgen", @@ -1853,15 +2014,6 @@ dependencies = [ "webpki", ] -[[package]] -name = "wepoll-ffi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" -dependencies = [ - "cc", -] - [[package]] name = "winapi" version = "0.3.9" @@ -1902,6 +2054,15 @@ dependencies = [ "windows-sys 0.27.0", ] +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.0", +] + [[package]] name = "windows-sys" version = "0.27.0" @@ -1917,15 +2078,17 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -1934,7 +2097,16 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets", + "windows-targets 0.42.2", +] + +[[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.0", ] [[package]] @@ -1943,21 +2115,42 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", "windows_i686_gnu 0.42.2", "windows_i686_msvc 0.42.2", "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.42.2", "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + [[package]] name = "windows_aarch64_msvc" version = "0.27.0" @@ -1966,15 +2159,15 @@ checksum = "ec7d1649bbab232cde71148c6ef7bbe647f214d2154dd66347fada60de40cda7" [[package]] name = "windows_aarch64_msvc" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" -version = "0.42.2" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" @@ -1984,15 +2177,15 @@ checksum = "b4eb20b59b93fc302839f3b0df3e61de7e9606b44cb54cbeb68d71cf137309fa" [[package]] name = "windows_i686_gnu" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.42.2" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" @@ -2002,15 +2195,15 @@ checksum = "40331d8ef3e4dcdc8982eb7de16e1f09b86f5384626a56b3a99c2a51b88ff98e" [[package]] name = "windows_i686_msvc" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" -version = "0.42.2" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" @@ -2020,15 +2213,15 @@ checksum = "5937d290e39c3308147d9b877c5fa741c50f4121ea78d2d20c4a138ad365464a" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" -version = "0.42.2" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" @@ -2036,6 +2229,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + [[package]] name = "windows_x86_64_msvc" version = "0.27.0" @@ -2044,15 +2243,15 @@ checksum = "dee1b76aec4e2bead4758a181b663c37af0de7ec56fe6837c10215b8d6a1635f" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" -version = "0.42.2" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "yaml-rust" diff --git a/Cargo.toml b/Cargo.toml index 9202d98f..6ee81a9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,9 +14,7 @@ homepage = "https://hubblo-org.github.io/scaphandre-documentation" [dependencies] loggerv = "0.7.2" log = "0.4" -# clap string feature is required to allow dynamic values otherwise it ended up with a lifetime pb. -# https://docs.rs/clap/4.0.19/clap/builder/struct.Str.html -clap = { version = "4.0.18", features = ["cargo", "string"] } +clap = { version = "4.2", features = ["cargo", "derive"] } regex = "1.7.0" riemann_client = { version = "0.9.0", optional = true } hostname = "0.3.1" @@ -43,9 +41,10 @@ procfs = { version = "0.12.0" } windows = { version = "0.27.0", features = ["alloc","Win32_Storage_FileSystem","Win32_Foundation","Win32_Security","Win32_System_IO","Win32_System_Ioctl"]} [features] -default = ["prometheus", "riemann", "warpten", "json", "containers"] +default = ["prometheus", "riemann", "warpten", "json", "containers", "qemu"] prometheus = ["hyper", "tokio"] riemann = ["riemann_client"] json = ["serde", "serde_json"] containers = ["docker-sync", "k8s-sync"] -warpten = ["warp10"] \ No newline at end of file +warpten = ["warp10"] +qemu = [] diff --git a/src/exporters/json.rs b/src/exporters/json.rs index d2300295..8d514efd 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -1,130 +1,86 @@ use crate::exporters::*; use crate::sensors::Sensor; -use clap::{parser::ValueSource, value_parser, Arg}; -use colored::*; use regex::Regex; use serde::{Deserialize, Serialize}; use std::{ - fs, fs::File, - path::PathBuf, + io::{BufWriter, Write}, + path::{Path, PathBuf}, thread, time::{Duration, Instant}, }; -/// An Exporter that displays power consumption data of the host -/// and its processes on the standard output of the terminal. -pub struct JSONExporter { - sensor: Box, - reports: Vec, - regex: Option, +/// An Exporter that writes power consumption data of the host +/// and its processes in the JSON format, either in a file or +/// to the standard output. +pub struct JsonExporter { + metric_generator: MetricGenerator, + time_step: Duration, + time_limit: Option, + max_top_consumers: u16, + out_writer: BufWriter>, + process_regex: Option, + container_regex: Option, + monitor_resources: bool, + watch_containers: bool, } -impl Exporter for JSONExporter { - /// Lanches runner() - fn run(&mut self, parameters: ArgMatches) { - self.runner(parameters); - } - - /// Returns options needed for that exporter, as a HashMap - - fn get_options() -> Vec { - let mut options = Vec::new(); - let arg = Arg::new("timeout") - .help("Maximum time spent measuring, in seconds.") - .long("timeout") - .short('t') - .required(false) - .value_parser(value_parser!(u64)) - .action(clap::ArgAction::Set); - options.push(arg); - - let arg = Arg::new("step_duration") - .default_value("2") - .help("Set measurement step duration in second.") - .long("step") - .short('s') - .required(false) - .value_parser(value_parser!(u64)) - .action(clap::ArgAction::Set); - options.push(arg); - - let arg = Arg::new("step_duration_nano") - .default_value("0") - .help("Set measurement step duration in nano second.") - .long("step_nano") - .short('n') - .required(false) - .value_parser(value_parser!(u32)) - .action(clap::ArgAction::Set); - options.push(arg); - - let arg = Arg::new("file_path") - .default_value("") - .help("Destination file for the report.") - .long("file") - .short('f') - .required(false) - .action(clap::ArgAction::Set); - options.push(arg); - - let arg = Arg::new("max_top_consumers") - .default_value("10") - .help("Maximum number of processes to watch.") - .long("max-top-consumers") - .short('m') - .required(false) - .value_parser(value_parser!(u16)) - .action(clap::ArgAction::Set); - options.push(arg); - - #[cfg(feature = "containers")] - { - let arg = Arg::new("containers") - .help("Monitor and apply labels for processes running as containers") - .short('c') - .long("containers") - .required(false) - .action(clap::ArgAction::SetTrue); - options.push(arg); - } - - let arg = Arg::new("regex_filter") - .help("Filter processes based on regular expressions (e.g: 'scaph\\w\\wd.e').") - .long("regex") - .short('r') - .required(false) - .action(clap::ArgAction::Set); - options.push(arg); - - let arg = Arg::new("resources") - .help("Monitor and include CPU/RAM/Disk usage per process.") - .long("resources") - .required(false) - .action(clap::ArgAction::SetTrue); - options.push(arg); - - #[cfg(feature = "containers")] - { - let arg = Arg::new("container_regex") - .help("Filter process by container name based on regular expressions (e.g: 'scaph\\w\\wd.e'). Works only with --containers enabled.") - .long("container-regex") - .required(false) - .action(clap::ArgAction::Set); - options.push(arg); - } - - let arg = Arg::new("qemu") - .help("Apply labels to metrics of processes looking like a Qemu/KVM virtual machine") - .long("qemu") - .short('q') - .required(false) - .action(clap::ArgAction::SetTrue); - options.push(arg); - options - } +// Note: clap::Args automatically generate Args for the fields of this struct, +// using the field's name as the argument's name, and the doc comment +// above the field as the argument's description. + +/// Holds the arguments for a JsonExporter. +/// +/// When using Scaphandre as a command-line application, such a struct will be +/// automatically populated by the clap library. If you're using Scaphandre as +/// a library, you should populate the arguments yourself. +#[derive(clap::Args, Debug)] +pub struct ExporterArgs { + /// Maximum time spent measuring, in seconds. + /// If negative, runs forever. + #[arg(short, long, default_value_t = 10)] + pub timeout: i64, + + /// Interval between two measurements, in seconds + #[arg(short, long, value_name = "SECONDS", default_value_t = 2)] + pub step: u64, + + /// Additional step duration in _nano_ seconds. + /// This is added to `step` to get the final duration. + #[arg(long, value_name = "NANOSECS", default_value_t = 0)] + pub step_nano: u32, + + /// Maximum number of processes to watch + #[arg(long, default_value_t = 10)] + pub max_top_consumers: u16, + + /// Destination file for the report (if absent, print the report to stdout) + #[arg(short, long)] + pub file: Option, + + /// Monitor and apply labels for processes running as containers + #[arg(long)] + pub containers: bool, + + /// Filter processes based on regular expressions (example: 'scaph\\w\\w.e') + #[arg(long)] + pub process_regex: Option, + + /// Filter containers based on regular expressions + #[arg(long)] + pub container_regex: Option, + + /// Monitor and incude CPU, RAM and Disk usage per process + #[arg(long)] + pub resources: bool, + // TODO uncomment this option once we display something interesting about it + // /// Apply labels to metrics of processes looking like a Qemu/KVM virtual machine + // #[arg(short, long)] + // pub qemu: bool } +// Below are the structures that will store the reports. + #[derive(Serialize, Deserialize)] struct Domain { name: String, @@ -198,81 +154,75 @@ struct Report { sockets: Vec, } -impl JSONExporter { - /// Instantiates and returns a new JSONExporter - pub fn new(sensor: Box) -> JSONExporter { - JSONExporter { - sensor, - reports: Vec::new(), - regex: None, +impl Exporter for JsonExporter { + /// Runs [iterate()] every `step` until `timeout` + fn run(&mut self) { + let step = self.time_step; + info!("Measurement step is: {step:?}"); + + if let Some(timeout) = self.time_limit { + let t0 = Instant::now(); + while t0.elapsed() <= timeout { + self.iterate(); + thread::sleep(self.time_step); + } + } else { + loop { + self.iterate(); + thread::sleep(self.time_step); + } } } - /// Runs iteration() every 'step', until 'timeout' - pub fn runner(&mut self, parameters: ArgMatches) { - let topology = self.sensor.get_topology().unwrap(); - let mut metric_generator = MetricGenerator::new( - topology, - utils::get_hostname(), - parameters.get_flag("qemu"), - parameters.get_flag("containers"), - ); - - // We have a default value of 2s so it is safe to unwrap the option - // Panic if a non numerical value is passed - let step_duration: u64 = *parameters - .get_one("step_duration") - .expect("Wrong step_duration value, should be a number of seconds"); - let step_duration_nano: u32 = *parameters - .get_one("step_duration_nano") - .expect("Wrong step_duration_nano value, should be a number of nano seconds"); + fn kind(&self) -> &str { + "json" + } +} - self.regex = if parameters.get_one::("regex_filter").is_none() { +impl JsonExporter { + /// Instantiates and returns a new JsonExporter. + pub fn new(sensor: &dyn Sensor, args: ExporterArgs) -> JsonExporter { + // Prepare the retrieval of the measurements + let topo = sensor + .get_topology() + .expect("sensor topology should be available"); + let metric_generator = + MetricGenerator::new(topo, utils::get_hostname(), false, args.containers); + + // Extract the parameters we need to run the exporter + let time_step = Duration::new(args.step, args.step_nano); + let time_limit = if args.timeout < 0 { None } else { - Some( - Regex::new(parameters.get_one::("regex_filter").unwrap()) - .expect("Wrong regex_filter, regexp is invalid"), - ) + Some(Duration::from_secs(args.timeout.unsigned_abs())) }; - - if parameters.value_source("regex_filter") == Some(ValueSource::CommandLine) - && parameters.value_source("max_top_consumers") == Some(ValueSource::CommandLine) - { - let warning = - String::from("Warning: (--max-top-consumers) and (-r / --regex) used at the same time. (--max-top-consumers) disabled"); - eprintln!("{}", warning.bright_yellow()); - } - - #[cfg(feature = "containers")] - if !parameters.get_flag("containers") && parameters.get_flag("container_regex") { - let warning = - String::from("Warning: --container-regex is used but --containers is not enabled. Regex search won't work."); - eprintln!("{}", warning.bright_yellow()); - } - - info!("Measurement step is: {}s", step_duration); - if let Some(timeout) = parameters.get_one::("timeout") { - let now = Instant::now(); - - let timeout_secs: u64 = *timeout; - while now.elapsed().as_secs() <= timeout_secs { - self.iterate(¶meters, &mut metric_generator); - thread::sleep(Duration::new(step_duration, step_duration_nano)); - } - } else { - loop { - self.iterate(¶meters, &mut metric_generator); - thread::sleep(Duration::new(step_duration, step_duration_nano)); + let max_top_consumers = args.max_top_consumers; + let process_regex = args.process_regex; + let container_regex = args.container_regex; + let monitor_resources = args.resources; + + // Prepare the output (either stdout or a file) + let output: Box = match args.file { + Some(f) => { + let path = Path::new(&f); + Box::new(File::create(path).unwrap_or_else(|_| panic!("failed to open file {f}"))) } + None => Box::new(std::io::stdout()), + }; + let out_writer = BufWriter::new(output); + JsonExporter { + metric_generator, + time_step, + time_limit, + max_top_consumers, + out_writer, + process_regex, + container_regex, + monitor_resources, + watch_containers: args.containers, } } - fn iterate(&mut self, parameters: &ArgMatches, metric_generator: &mut MetricGenerator) { - metric_generator.topology.refresh(); - self.retrieve_metrics(parameters, metric_generator); - } - fn gen_disks_report(&self, metrics: &Vec<&Metric>) -> Vec { let mut res: Vec = vec![]; for m in metrics { @@ -341,14 +291,15 @@ impl JSONExporter { res } - fn retrieve_metrics( - &mut self, - parameters: &ArgMatches, - metric_generator: &mut MetricGenerator, - ) { - metric_generator.gen_all_metrics(); + fn iterate(&mut self) { + self.metric_generator.topology.refresh(); + self.retrieve_metrics(); + } + + fn retrieve_metrics(&mut self) { + self.metric_generator.gen_all_metrics(); - let metrics = metric_generator.pop_metrics(); + let metrics = self.metric_generator.pop_metrics(); let mut metrics_iter = metrics.iter(); let socket_metrics_res = metrics_iter.find(|x| x.name == "scaph_socket_power_microwatts"); //TODO: fix for multiple sockets @@ -373,43 +324,39 @@ impl JSONExporter { } } else { info!("didn't find host metric"); + // TODO in that case, no report is written, thus I think we should return here (?) }; if let Some(host) = &mut host_report { host.components.disks = Some(disks); } - let consumers: Vec<(IProcess, f64)>; - let max_top = parameters - .get_one::("max_top_consumers") - .unwrap_or(&10); - if let Some(regex_filter) = &self.regex { + let max_top = self.max_top_consumers; + let consumers: Vec<(IProcess, f64)> = if let Some(regex_filter) = &self.process_regex { debug!("Processes filtered by '{}':", regex_filter.as_str()); - consumers = metric_generator + self.metric_generator .topology .proc_tracker - .get_filtered_processes(regex_filter); - } else if let Some(param) = parameters.get_one::("container_regex") { + .get_filtered_processes(regex_filter) + } else if let Some(regex_filter) = &self.container_regex { #[cfg(feature = "containers")] { - consumers = metric_generator.get_processes_filtered_by_container_name( - &Regex::new(param) - .expect("Wrong container_regex expression. Regexp is invalid."), - ); + self.metric_generator + .get_processes_filtered_by_container_name(regex_filter) } + #[cfg(not(feature = "containers"))] - { - consumers = metric_generator - .topology - .proc_tracker - .get_top_consumers(*max_top); - } + self.metric_generator + .topology + .proc_tracker + .get_top_consumers(max_top) } else { - consumers = metric_generator + self.metric_generator .topology .proc_tracker - .get_top_consumers(*max_top); - } + .get_top_consumers(max_top) + }; + let mut top_consumers = consumers .iter() .filter_map(|(process, _value)| { @@ -426,9 +373,11 @@ impl JSONExporter { consumption: format!("{}", metric.metric_value).parse::().unwrap(), resources_usage: None, timestamp: metric.timestamp.as_secs_f64(), - container: match parameters.get_flag("containers") { - true => metric.attributes.get("container_id").map(|container_id| { - Container { + container: if self.watch_containers { + metric + .attributes + .get("container_id") + .map(|container_id| Container { id: String::from(container_id), name: String::from( metric @@ -448,15 +397,15 @@ impl JSONExporter { .get("container_scheduler") .unwrap_or(&String::from("unknown")), ), - } - }), - false => None, + }) + } else { + None }, }) }) .collect::>(); - if parameters.get_flag("resources") { + if self.monitor_resources { for c in top_consumers.iter_mut() { let mut res = ResourcesUsage { cpu_usage: String::from("0"), @@ -503,8 +452,10 @@ impl JSONExporter { } } - let all_sockets_vec = metric_generator.topology.get_sockets_passive(); - let all_sockets = all_sockets_vec + let all_sockets = self + .metric_generator + .topology + .get_sockets_passive() .iter() .filter_map(|socket| { if let Some(metric) = socket_metrics_res.iter().find(|x| { @@ -555,20 +506,9 @@ impl JSONExporter { sockets: all_sockets, }; - let file_path = parameters.get_one::("file_path").unwrap(); - // Print json - if file_path.is_empty() { - let json: String = - serde_json::to_string(&report).expect("Unable to parse report"); - println!("{}", &json); - } else { - self.reports.push(report); - // Serialize it to a JSON string. - let json: String = - serde_json::to_string(&self.reports).expect("Unable to parse report"); - let _ = File::create(file_path); - fs::write(file_path, json).expect("Unable to write file"); - } + // Serialize the report to json + serde_json::to_writer(&mut self.out_writer, &report) + .expect("report should be serializable to JSON"); } None => { info!("No data yet, didn't write report."); diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index 36dbc301..28545d59 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -2,6 +2,7 @@ //! //! `Exporter` is the root for all exporters. It defines the [Exporter] trait //! needed to implement an exporter. +#[cfg(feature = "json")] pub mod json; #[cfg(feature = "prometheus")] pub mod prometheus; @@ -18,7 +19,6 @@ use crate::sensors::{ RecordGenerator, Topology, }; use chrono::Utc; -use clap::ArgMatches; use std::collections::HashMap; use std::fmt; use std::time::Duration; @@ -102,10 +102,11 @@ impl fmt::Debug for MetricValueType { /// the metrics are generated/refreshed by calling the refresh* methods available /// with the structs provided by the sensor. pub trait Exporter { - /// Entry point for all Exporters - fn run(&mut self, parameters: ArgMatches); - /// Get the options passed via the command line - fn get_options() -> Vec; + /// Runs the exporter. + fn run(&mut self); + + /// The name of the kind of the exporter, for example "json". + fn kind(&self) -> &str; } /// MetricGenerator is an exporter helper structure to collect Scaphandre metrics. diff --git a/src/exporters/prometheus.rs b/src/exporters/prometheus.rs index 033825ac..6d110cf4 100644 --- a/src/exporters/prometheus.rs +++ b/src/exporters/prometheus.rs @@ -1,136 +1,99 @@ //! # PrometheusExporter //! -//! `PrometheusExporter` implementation, expose metrics to -//! a [Prometheus](https://prometheus.io/) server. -use super::utils::get_hostname; +//! The Prometheus Exporter expose metrics to a [Prometheus](https://prometheus.io/) server. +//! This is achieved by exposing an HTTP endpoint, which the Prometheus will +//! [scrape](https://prometheus.io/docs/prometheus/latest/getting_started). + +use super::utils; use crate::current_system_time_since_epoch; use crate::exporters::{Exporter, MetricGenerator, MetricValueType}; use crate::sensors::{Sensor, Topology}; use chrono::Utc; -use clap::{Arg, ArgMatches}; use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Request, Response, Server}; use std::convert::Infallible; -use std::fmt::Write as _; use std::{ collections::HashMap, - net::{IpAddr, SocketAddr}, + fmt::Write, + net::{IpAddr, Ipv4Addr, SocketAddr}, sync::{Arc, Mutex}, time::Duration, }; /// Default ipv4/ipv6 address to expose the service is any -const DEFAULT_IP_ADDRESS: &str = "::"; +const DEFAULT_IP_ADDRESS: IpAddr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)); /// Exporter that exposes metrics to an HTTP endpoint /// matching the Prometheus.io metrics format. pub struct PrometheusExporter { - /// Sensor instance that is used to generate the Topology and - /// thus get power consumption metrics. - sensor: Box, + topo: Topology, + hostname: String, + args: ExporterArgs, +} + +/// Hold the arguments for a PrometheusExporter. +#[derive(clap::Args, Debug)] +pub struct ExporterArgs { + /// IP address (v4 or v6) of the metrics endpoint for Prometheus + #[arg(short, long, default_value_t = DEFAULT_IP_ADDRESS)] + pub address: IpAddr, + + /// TCP port of the metrics endpoint for Prometheus + #[arg(short, long)] + pub port: u16, + + #[arg(short, long)] + pub suffix: String, + + /// Apply labels to metrics of processes that look like a Qemu/KVM virtual machine + #[arg(long)] + pub qmeu: bool, + + /// Apply labels to metrics of processes running as containers + #[arg(long)] + pub containers: bool, } impl PrometheusExporter { /// Instantiates PrometheusExporter and returns the instance. - pub fn new(sensor: Box) -> PrometheusExporter { - PrometheusExporter { sensor } + pub fn new(sensor: &dyn Sensor, args: ExporterArgs) -> PrometheusExporter { + // Prepare the retrieval of the measurements, catch most of the errors early + let topo = sensor + .get_topology() + .expect("sensor topology should be available"); + let hostname = utils::get_hostname(); + PrometheusExporter { + topo, + hostname, + args, + } } } impl Exporter for PrometheusExporter { - /// Entry point ot the PrometheusExporter. - /// - /// Runs HTTP server and metrics exposure through the runner function. - fn run(&mut self, parameters: ArgMatches) { + /// Starts an HTTP server to expose the metrics in Prometheus format. + fn run(&mut self) { info!( "{}: Starting Prometheus exporter", Utc::now().format("%Y-%m-%dT%H:%M:%S") ); println!("Press CTRL-C to stop scaphandre"); - - runner( - (*self.sensor.get_topology()).unwrap(), - parameters.get_one::("address").unwrap().to_string(), - parameters.get_one::("port").unwrap().to_string(), - parameters.get_one::("suffix").unwrap().to_string(), - parameters.get_flag("qemu"), - parameters.get_flag("containers"), - get_hostname(), + let socket_addr = SocketAddr::new(self.args.address, self.args.port); + let metric_generator = MetricGenerator::new( + self.topo.clone(), // improvement possible here: avoid cloning by adding a lifetime param to MetricGenerator + self.hostname.clone(), + self.args.qmeu, + self.args.containers, ); + run_server(socket_addr, metric_generator, &self.args.suffix); } - /// Returns options understood by the exporter. - fn get_options() -> Vec { - let mut options = Vec::new(); - let arg = Arg::new("address") - .default_value(DEFAULT_IP_ADDRESS) - .help("ipv6 or ipv4 address to expose the service to") - .long("address") - .short('a') - .required(false) - .action(clap::ArgAction::Set); - options.push(arg); - - let arg = Arg::new("port") - .default_value("8080") - .help("TCP port number to expose the service") - .long("port") - .short('p') - .required(false) - .action(clap::ArgAction::Set); - options.push(arg); - - let arg = Arg::new("suffix") - .default_value("metrics") - .help("url suffix to access metrics") - .long("suffix") - .short('s') - .required(false) - .action(clap::ArgAction::Set); - options.push(arg); - - let arg = Arg::new("qemu") - .help("Apply labels to metrics of processes looking like a Qemu/KVM virtual machine") - .long("qemu") - .short('q') - .required(false) - .action(clap::ArgAction::SetTrue); - options.push(arg); - let arg = Arg::new("containers") - .help("Monitor and apply labels for processes running as containers") - .long("containers") - .required(false) - .action(clap::ArgAction::SetTrue); - options.push(arg); - - let arg = Arg::new("kubernetes_host") - .help("FQDN of the kubernetes API server") - .long("kubernetes-host") - .required(false) - .action(clap::ArgAction::Set); - options.push(arg); - - let arg = Arg::new("kubernetes_scheme") - .help("Protocol used to access kubernetes API server") - .long("kubernetes-scheme") - .default_value("http") - .required(false) - .action(clap::ArgAction::Set); - options.push(arg); - - let arg = Arg::new("kubernetes_port") - .help("Kubernetes API server port number") - .long("kubernetes-port") - .default_value("6443") - .required(false) - .action(clap::ArgAction::Set); - options.push(arg); - - options + fn kind(&self) -> &str { + "prometheus" } } -/// Contains a mutex holding a Topology object. +/// Contains a mutex holding a MetricGenerator. /// Used to pass the topology data from one http worker to another. struct PowerMetrics { last_request: Mutex, @@ -138,55 +101,36 @@ struct PowerMetrics { } #[tokio::main] -async fn runner( - topology: Topology, - address: String, - port: String, - suffix: String, - qemu: bool, - watch_containers: bool, - hostname: String, +async fn run_server( + socket_addr: SocketAddr, + metric_generator: MetricGenerator, + endpoint_suffix: &str, ) { - if let Ok(addr) = address.parse::() { - if let Ok(port) = port.parse::() { - let socket_addr = SocketAddr::new(addr, port); - - let power_metrics = PowerMetrics { - last_request: Mutex::new(Duration::new(0, 0)), - metric_generator: Mutex::new(MetricGenerator::new( - topology, - hostname.clone(), - qemu, - watch_containers, - )), - }; - let context = Arc::new(power_metrics); - let make_svc = make_service_fn(move |_| { - let ctx = context.clone(); - let sfx = suffix.clone(); - async { - Ok::<_, Infallible>(service_fn(move |req| { - show_metrics(req, ctx.clone(), sfx.clone()) - })) - } - }); - let server = Server::bind(&socket_addr); - let res = server.serve(make_svc); - let (tx, rx) = tokio::sync::oneshot::channel::<()>(); - let graceful = res.with_graceful_shutdown(async { - rx.await.ok(); - }); - - if let Err(e) = graceful.await { - error!("server error: {}", e); - } - let _ = tx.send(()); - } else { - panic!("{} is not a valid TCP port number", port); + let power_metrics = PowerMetrics { + last_request: Mutex::new(Duration::new(0, 0)), + metric_generator: Mutex::new(metric_generator), + }; + let context = Arc::new(power_metrics); + let make_svc = make_service_fn(move |_| { + let ctx = context.clone(); + let sfx = endpoint_suffix.to_string(); + async { + Ok::<_, Infallible>(service_fn(move |req| { + show_metrics(req, ctx.clone(), sfx.clone()) + })) } - } else { - panic!("{} is not a valid ip address", address); + }); + let server = Server::bind(&socket_addr); + let res = server.serve(make_svc); + let (tx, rx) = tokio::sync::oneshot::channel::<()>(); + let graceful = res.with_graceful_shutdown(async { + rx.await.ok(); + }); + + if let Err(e) = graceful.await { + error!("server error: {}", e); } + let _ = tx.send(()); } /// Returns a well formatted Prometheus metric string. diff --git a/src/exporters/qemu.rs b/src/exporters/qemu.rs index 0b664444..f397c189 100644 --- a/src/exporters/qemu.rs +++ b/src/exporters/qemu.rs @@ -1,5 +1,5 @@ -use crate::exporters::utils::get_hostname; -use crate::exporters::{Exporter, MetricGenerator}; +use crate::exporters::Exporter; +use crate::sensors::Topology; use crate::sensors::{utils::ProcessRecord, Sensor}; use std::{fs, io, thread, time}; @@ -10,65 +10,59 @@ use std::{fs, io, thread, time}; /// to collect and deal with their power consumption metrics, the same way /// they would do it if they managed bare metal machines. pub struct QemuExporter { - sensor: Box, + // We don't need a MetricGenerator for this exporter, because it "justs" + // puts the metrics in files in the same way as the powercap kernel module. + topology: Topology, } impl Exporter for QemuExporter { - /// Runs iteration() in a loop. - fn run(&mut self, _parameters: clap::ArgMatches) { + /// Runs [iterate()] in a loop. + fn run(&mut self) { info!("Starting qemu exporter"); let path = "/var/lib/libvirt/scaphandre"; let cleaner_step = 120; let mut timer = time::Duration::from_secs(cleaner_step); - if let Ok(topology) = self.sensor.generate_topology() { - let mut metric_generator = MetricGenerator::new(topology, get_hostname(), true, false); - loop { - metric_generator.topology.refresh(); - self.iteration(String::from(path), &mut metric_generator); - let step = time::Duration::from_secs(5); - thread::sleep(step); - if timer - step > time::Duration::from_millis(0) { - timer -= step; - } else { - metric_generator - .topology - .proc_tracker - .clean_terminated_process_records_vectors(); - timer = time::Duration::from_secs(cleaner_step); - } + loop { + self.iterate(String::from(path)); + let step = time::Duration::from_secs(5); + thread::sleep(step); + if timer - step > time::Duration::from_millis(0) { + timer -= step; + } else { + self.topology + .proc_tracker + .clean_terminated_process_records_vectors(); + timer = time::Duration::from_secs(cleaner_step); } } } - fn get_options() -> Vec { - Vec::new() + fn kind(&self) -> &str { + "qemu" } } impl QemuExporter { /// Instantiates and returns a new QemuExporter - pub fn new(sensor: Box) -> QemuExporter { - QemuExporter { sensor } + pub fn new(sensor: &dyn Sensor) -> QemuExporter { + let topology = sensor + .get_topology() + .expect("sensor topology should be available"); + QemuExporter { topology } } - /// Performs processing of metrics, using self.topology - pub fn iteration(&mut self, path: String, metric_generator: &mut MetricGenerator) { + /// Processes the metrics of `self.topology` and exposes them at the given `path`. + pub fn iterate(&mut self, path: String) { trace!("path: {}", path); - if let Some(topo_energy) = metric_generator - .topology - .get_records_diff_power_microwatts() - { - let processes = metric_generator.topology.proc_tracker.get_alive_processes(); + if let Some(topo_energy) = self.topology.get_records_diff_power_microwatts() { + let processes = self.topology.proc_tracker.get_alive_processes(); let qemu_processes = QemuExporter::filter_qemu_vm_processes(&processes); for qp in qemu_processes { if qp.len() > 2 { let last = qp.first().unwrap(); let vm_name = QemuExporter::get_vm_name_from_cmdline( - &last - .process - .cmdline(&metric_generator.topology.proc_tracker) - .unwrap(), + &last.process.cmdline(&self.topology.proc_tracker).unwrap(), ); let first_domain_path = format!("{path}/{vm_name}/intel-rapl:0:0"); if fs::read_dir(&first_domain_path).is_err() { @@ -77,7 +71,7 @@ impl QemuExporter { Err(error) => panic!("Couldn't create {}. Got: {}", &path, error), } } - if let Some(ratio) = metric_generator + if let Some(ratio) = self .topology .get_process_cpu_usage_percentage(last.process.pid) { @@ -107,7 +101,7 @@ impl QemuExporter { return String::from(splitted.next().unwrap().split(',').next().unwrap()); } } - String::from("") + String::from("") // TODO return Option None instead, and stop at line 76 (it won't work with {path}//intel-rapl) } /// Either creates an energy_uj file (as the ones managed by powercap kernel module) diff --git a/src/exporters/riemann.rs b/src/exporters/riemann.rs index abc9993f..7635db04 100644 --- a/src/exporters/riemann.rs +++ b/src/exporters/riemann.rs @@ -1,26 +1,22 @@ //! # RiemannExporter //! -//! `RiemannExporter` implementation, sends metrics to a [Riemann](https://riemann.io/) -//! server. +//! The Riemann exporter sends metrics to a [Riemann](https://riemann.io/) server. + use crate::exporters::utils::get_hostname; use crate::exporters::*; use crate::sensors::Sensor; use chrono::Utc; -use clap::value_parser; -use clap::Arg; -use riemann_client::proto::Attribute; -use riemann_client::proto::Event; +use riemann_client::proto::{Attribute, Event}; use riemann_client::Client; use std::collections::HashMap; use std::convert::TryFrom; -use std::thread; use std::time::{Duration, SystemTime, UNIX_EPOCH}; /// Riemann server default ipv4/ipv6 address const DEFAULT_IP_ADDRESS: &str = "localhost"; /// Riemann server default port -const DEFAULT_PORT: &str = "5555"; +const DEFAULT_PORT: u16 = 5555; /// RiemannClient is a simple client implementation on top of the /// [rust-riemann_client](https://github.com/borntyping/rust-riemann_client) library. @@ -31,25 +27,6 @@ struct RiemannClient { } impl RiemannClient { - /// Instanciate the Riemann client either with mTLS or using raw TCP. - fn new(parameters: &ArgMatches) -> RiemannClient { - let address = parameters.get_one::("address").unwrap().to_string(); - let port: u16 = *parameters - .get_one("port") - .expect("Fail parsing port number"); - let client: Client = if parameters.get_flag("mtls") { - let cafile = parameters.get_one::("cafile").unwrap(); - let certfile = parameters.get_one::("certfile").unwrap(); - let keyfile = parameters.get_one::("keyfile").unwrap(); - Client::connect_tls(&address, port, cafile, certfile, keyfile) - .expect("Fail to connect to Riemann server using mTLS") - } else { - Client::connect(&(address, port)) - .expect("Fail to connect to Riemann server using raw TCP") - }; - RiemannClient { client } - } - /// Send metrics to the server. fn send_metric(&mut self, metric: &Metric) { let mut event = Event::new(); @@ -103,45 +80,103 @@ impl RiemannClient { } } -/// Exporter sends metrics to a Riemann server. +/// An exporter that sends metrics to a Riemann server. pub struct RiemannExporter { - /// Sensor instance that is used to generate the Topology and - /// thus get power consumption metrics. - sensor: Box, + metric_generator: MetricGenerator, + riemann_client: RiemannClient, + args: ExporterArgs, +} + +/// Contains the options of the Riemann exporter. +#[derive(clap::Args, Debug)] +pub struct ExporterArgs { + /// Address of the Riemann server. If mTLS is used this must be the server's FQDN. + #[arg(short, long, default_value = DEFAULT_IP_ADDRESS)] + pub address: String, + + /// TCP port number of the Riemann server + #[arg(short, long, default_value_t = DEFAULT_PORT)] + pub port: u16, + + /// Duration between each metric dispatch, in seconds + #[arg(short, long, default_value_t = 5)] + pub dispatch_interval: u64, + + /// Apply labels to metrics of processes looking like a Qemu/KVM virtual machine + #[arg(short, long)] + pub qemu: bool, + + /// Monitor and apply labels for processes running as containers + #[arg(long)] + pub containers: bool, + + /// Connect to Riemann using mTLS instead of plain TCP. + #[arg( + long, + requires = "address", + requires = "ca_file", + requires = "cert_file", + requires = "key_file" + )] + pub mtls: bool, + + /// CA certificate file (.pem format) + #[arg(long = "ca", requires = "mtls")] + pub ca_file: Option, + + /// Client certificate file (.pem format) + #[arg(long = "cert", requires = "mtls")] + pub cert_file: Option, + + /// Client RSA key file + #[arg(long = "key", requires = "mtls")] + pub key_file: Option, } impl RiemannExporter { /// Returns a RiemannExporter instance. - pub fn new(sensor: Box) -> RiemannExporter { - RiemannExporter { sensor } + pub fn new(sensor: &dyn Sensor, args: ExporterArgs) -> RiemannExporter { + // Prepare the retrieval of the measurements + let topo = sensor + .get_topology() + .expect("sensor topology should be available"); + let metric_generator = + MetricGenerator::new(topo, utils::get_hostname(), args.qemu, args.containers); + + // Initialize the connection to the Riemann server + let client = if args.mtls { + Client::connect_tls( + &args.address, + args.port, + &args.ca_file.clone().unwrap(), + &args.cert_file.clone().unwrap(), + &args.key_file.clone().unwrap(), + ) + .expect("failed to connect to Riemann using mTLS") + } else { + Client::connect(&(args.address.clone(), args.port)) + .expect("failed to connect to Riemann using raw TCP") + }; + let riemann_client = RiemannClient { client }; + RiemannExporter { + metric_generator, + riemann_client, + args, + } } } impl Exporter for RiemannExporter { /// Entry point of the RiemannExporter. - fn run(&mut self, parameters: ArgMatches) { - let dispatch_duration: u64 = *parameters - .get_one("dispatch_duration") - .expect("Wrong dispatch_duration value, should be a number of seconds"); - - let hostname = get_hostname(); - - let mut rclient = RiemannClient::new(¶meters); - + fn run(&mut self) { info!( "{}: Starting Riemann exporter", Utc::now().format("%Y-%m-%dT%H:%M:%S") ); println!("Press CTRL-C to stop scaphandre"); - println!("Measurement step is: {dispatch_duration}s"); - - let topology = self.sensor.get_topology().unwrap(); - let mut metric_generator = MetricGenerator::new( - topology, - hostname, - parameters.get_flag("qemu"), - parameters.get_flag("containers"), - ); + + let dispatch_interval = Duration::from_secs(self.args.dispatch_interval); + println!("Dispatch interval is {dispatch_interval:?}"); loop { info!( @@ -149,7 +184,7 @@ impl Exporter for RiemannExporter { Utc::now().format("%Y-%m-%dT%H:%M:%S") ); - metric_generator + self.metric_generator .topology .proc_tracker .clean_terminated_process_records_vectors(); @@ -158,17 +193,17 @@ impl Exporter for RiemannExporter { "{}: Refresh topology", Utc::now().format("%Y-%m-%dT%H:%M:%S") ); - metric_generator.topology.refresh(); + self.metric_generator.topology.refresh(); info!("{}: Refresh data", Utc::now().format("%Y-%m-%dT%H:%M:%S")); // Here we need a specific behavior for process metrics, so we call each gen function // and then implement that specific behavior (we don't use gen_all_metrics). - metric_generator.gen_self_metrics(); - metric_generator.gen_host_metrics(); - metric_generator.gen_socket_metrics(); + self.metric_generator.gen_self_metrics(); + self.metric_generator.gen_host_metrics(); + self.metric_generator.gen_socket_metrics(); let mut data = vec![]; - let processes_tracker = &metric_generator.topology.proc_tracker; + let processes_tracker = &self.metric_generator.topology.proc_tracker; for pid in processes_tracker.get_alive_pids() { let exe = processes_tracker.get_process_name(pid); @@ -182,7 +217,7 @@ impl Exporter for RiemannExporter { if let Some(cmdline_str) = cmdline { attributes.insert("cmdline".to_string(), cmdline_str.replace('\"', "\\\"")); - if parameters.get_flag("qemu") { + if self.args.qemu { if let Some(vmname) = utils::filter_qemu_cmdline(&cmdline_str) { attributes.insert("vmname".to_string(), vmname); } @@ -195,7 +230,8 @@ impl Exporter for RiemannExporter { "{}_{}_{}", "scaph_process_power_consumption_microwatts", pid, exe ); - if let Some(power) = metric_generator + if let Some(power) = self + .metric_generator .topology .get_process_power_consumption_microwatts(pid) { @@ -215,100 +251,20 @@ impl Exporter for RiemannExporter { } // Send all data info!("{}: Send data", Utc::now().format("%Y-%m-%dT%H:%M:%S")); - for metric in metric_generator.pop_metrics() { - rclient.send_metric(&metric); + for metric in self.metric_generator.pop_metrics() { + self.riemann_client.send_metric(&metric); } for metric in data { - rclient.send_metric(&metric); + self.riemann_client.send_metric(&metric); } - thread::sleep(Duration::new(dispatch_duration, 0)); + // Pause for some time + std::thread::sleep(dispatch_interval); } } - /// Returns options understood by the exporter. - fn get_options() -> Vec { - let mut options = Vec::new(); - let arg = Arg::new("address") - .default_value(DEFAULT_IP_ADDRESS) - .help("Riemann ipv6 or ipv4 address. If mTLS is used then server fqdn must be provided") - .long("address") - .short('a') - .required(false) - .action(clap::ArgAction::Set); - options.push(arg); - - let arg = Arg::new("port") - .default_value(DEFAULT_PORT) - .help("Riemann TCP port number") - .long("port") - .short('p') - .required(false) - .value_parser(value_parser!(u16)) - .action(clap::ArgAction::Set); - options.push(arg); - - let arg = Arg::new("dispatch_duration") - .default_value("5") - .help("Duration between metrics dispatch") - .long("dispatch") - .short('d') - .required(false) - .value_parser(value_parser!(u64)) - .action(clap::ArgAction::Set); - options.push(arg); - - let arg = Arg::new("qemu") - .help("Instruct that scaphandre is running on an hypervisor") - .long("qemu") - .short('q') - .required(false) - .action(clap::ArgAction::SetTrue); - options.push(arg); - - let arg = Arg::new("containers") - .help("Monitor and apply labels for processes running as containers") - .long("containers") - .required(false) - .action(clap::ArgAction::SetTrue); - options.push(arg); - - let arg = Arg::new("mtls") - .help("Connect to a Riemann server using mTLS. Parameters address, ca, cert and key must be defined.") - .long("mtls") - .required(false) - .requires_all(["address","cafile", "certfile", "keyfile"]) - .action(clap::ArgAction::SetTrue); - options.push(arg); - - let arg = Arg::new("cafile") - .help("CA certificate file (.pem format)") - .long("ca") - .required(false) - .action(clap::ArgAction::Set) - .display_order(1000) - .requires("mtls"); - options.push(arg); - - let arg = Arg::new("certfile") - .help("Client certificate file (.pem format)") - .long("cert") - .required(false) - .action(clap::ArgAction::Set) - .display_order(1001) - .requires("mtls"); - options.push(arg); - - let arg = Arg::new("keyfile") - .help("Client RSA key") - .long("key") - .required(false) - .action(clap::ArgAction::Set) - .display_order(1001) - .requires("mtls"); - options.push(arg); - - options + fn kind(&self) -> &str { + "riemann" } } diff --git a/src/exporters/stdout.rs b/src/exporters/stdout.rs index a3cc9120..07a1257d 100644 --- a/src/exporters/stdout.rs +++ b/src/exporters/stdout.rs @@ -1,169 +1,112 @@ -use clap::parser::ValueSource; -use clap::{value_parser, Arg}; - use crate::exporters::*; use crate::sensors::{utils::IProcess, Sensor}; -use colored::*; use regex::Regex; -use std::fmt::Write as _; use std::thread; use std::time::{Duration, Instant}; /// An Exporter that displays power consumption data of the host /// and its processes on the standard output of the terminal. pub struct StdoutExporter { - sensor: Box, + metric_generator: MetricGenerator, + args: ExporterArgs, } -impl Exporter for StdoutExporter { - /// Lanches runner() - fn run(&mut self, parameters: ArgMatches) { - self.runner(parameters); - } - - /// Returns options needed for that exporter, as a HashMap - fn get_options() -> Vec { - let mut options = Vec::new(); - let arg = Arg::new("timeout") - .default_value("10") - .help("Maximum time spent measuring, in seconds. 0 means continuous measurement.") - .long("timeout") - .short('t') - .required(false) - .value_parser(value_parser!(u64)) - .action(clap::ArgAction::Set); - options.push(arg); - - let arg = Arg::new("step_duration") - .default_value("2") - .help("Set measurement step duration in second.") - .long("step") - .short('s') - .required(false) - .value_parser(value_parser!(u64)) - .action(clap::ArgAction::Set); - options.push(arg); - - let arg = Arg::new("process_number") - .default_value("5") - .help("Number of processes to display.") - .long("process") - .short('p') - .required(false) - .value_parser(value_parser!(u16)) - .action(clap::ArgAction::Set); - options.push(arg); - - let arg = Arg::new("regex_filter") - .help("Filter processes based on regular expressions (e.g: 'scaph\\w\\wd.e'). This option disable '-p' or '--process' one.") - .long("regex") - .short('r') - .required(false) - .action(clap::ArgAction::Set); - options.push(arg); +/// Holds the arguments for a StdoutExporter. +/// +/// When using Scaphandre as a command-line application, such a struct will be +/// automatically populated by the clap library. If you're using Scaphandre as +/// a library, you should populate the arguments yourself. +#[derive(clap::Args, Debug)] +// The command group makes `processes` and `regex_filter` exclusive. +#[command(group(clap::ArgGroup::new("disp").args(["processes", "regex_filter"])))] +pub struct ExporterArgs { + /// Maximum time spent measuring, in seconds. + /// If negative, runs forever. + #[arg(short, long, default_value_t = 10)] + pub timeout: i64, + + /// Interval between two measurements, in seconds + #[arg(short, long, value_name = "SECONDS", default_value_t = 2)] + pub step: u64, + + /// Maximum number of processes to display + #[arg(short, long, default_value_t = 5)] + pub processes: u16, + + /// Filter processes based on regular expressions (example: 'scaph\\w\\w.e') + #[arg(short, long)] + pub regex_filter: Option, + + /// Monitor and apply labels for processes running as containers + #[arg(long)] + pub containers: bool, + + /// Apply labels to metrics of processes looking like a Qemu/KVM virtual machine + #[arg(short, long)] + pub qemu: bool, +} - let arg = Arg::new("qemu") - .help("Apply labels to metrics of processes looking like a Qemu/KVM virtual machine") - .long("qemu") - .short('q') - .required(false) - .action(clap::ArgAction::SetTrue); - options.push(arg); +impl Exporter for StdoutExporter { + /// Runs [iterate()] every `step` until `timeout` + fn run(&mut self) { + let time_step = Duration::from_secs(self.args.step); + let time_limit = if self.args.timeout < 0 { + None + } else { + Some(Duration::from_secs(self.args.timeout.unsigned_abs())) + }; - let arg = Arg::new("containers") - .help("Monitor and apply labels for processes running as containers") - .long("containers") - .required(false) - .action(clap::ArgAction::SetTrue); - options.push(arg); + println!("Measurement step is: {time_step:?}"); + if let Some(timeout) = time_limit { + let t0 = Instant::now(); + while t0.elapsed() <= timeout { + self.iterate(); + thread::sleep(time_step); + } + } else { + loop { + self.iterate(); + thread::sleep(time_step); + } + } + } - options + fn kind(&self) -> &str { + "stdout" } } impl StdoutExporter { /// Instantiates and returns a new StdoutExporter - pub fn new(sensor: Box) -> StdoutExporter { - StdoutExporter { sensor } - } - - /// Runs iteration() every 'step', during until 'timeout' - pub fn runner(&mut self, parameters: ArgMatches) { - // Parse parameters - // All parameters have a default values so it is safe to unwrap them. - // Panic if a non numerical value is passed except for regex_filter. - - let timeout_secs: u64 = *parameters - .get_one("timeout") - .expect("Wrong timeout value, should be a number of seconds"); - - let step_duration: u64 = *parameters - .get_one("step_duration") - .expect("Wrong step_duration value, should be a number of seconds"); - - let process_number: u16 = *parameters - .get_one("process_number") - .expect("Wrong process_number value, should be a number"); - - let regex_filter: Option = parameters - .get_one::("regex_filter") - .map(|regex| Regex::new(regex).expect("Wrong regex_filter, regexp is invalid")); - - if parameters.value_source("regex_filter") == Some(ValueSource::CommandLine) - && parameters.value_source("process_number") == Some(ValueSource::CommandLine) - { - let warning = - String::from("Warning: (-p / --process) and (-r / --regex) used at the same time. (-p / --process) disabled"); - eprintln!("{}", warning.bright_yellow()); - } - - let topology = self.sensor.get_topology().unwrap(); - let mut metric_generator = MetricGenerator::new( - topology, - utils::get_hostname(), - parameters.get_flag("qemu"), - parameters.get_flag("containers"), - ); - - println!("Measurement step is: {step_duration}s"); - if timeout_secs == 0 { - loop { - self.iterate(®ex_filter, process_number, &mut metric_generator); - thread::sleep(Duration::new(step_duration, 0)); - } - } else { - let now = Instant::now(); - - while now.elapsed().as_secs() <= timeout_secs { - self.iterate(®ex_filter, process_number, &mut metric_generator); - thread::sleep(Duration::new(step_duration, 0)); - } + pub fn new(sensor: &dyn Sensor, args: ExporterArgs) -> StdoutExporter { + // Prepare the retrieval of the measurements + let topo = sensor + .get_topology() + .expect("sensor topology should be available"); + + let metric_generator = + MetricGenerator::new(topo, utils::get_hostname(), args.qemu, args.containers); + + StdoutExporter { + metric_generator, + args, } } - fn iterate( - &mut self, - regex_filter: &Option, - process_number: u16, - metric_generator: &mut MetricGenerator, - ) { - metric_generator + fn iterate(&mut self) { + self.metric_generator .topology .proc_tracker .clean_terminated_process_records_vectors(); - metric_generator.topology.refresh(); - self.show_metrics(regex_filter, process_number, metric_generator); + self.metric_generator.topology.refresh(); + self.show_metrics(); } - fn show_metrics( - &self, - regex_filter: &Option, - process_number: u16, - metric_generator: &mut MetricGenerator, - ) { - metric_generator.gen_all_metrics(); + fn show_metrics(&mut self) { + use std::fmt::Write; + self.metric_generator.gen_all_metrics(); - let metrics = metric_generator.pop_metrics(); + let metrics = self.metric_generator.pop_metrics(); let mut metrics_iter = metrics.iter(); let none_value = MetricValueType::Text("0".to_string()); let host_power = match metrics_iter.find(|x| x.name == "scaph_host_power_microwatts") { @@ -171,7 +114,7 @@ impl StdoutExporter { None => &none_value, }; - let domain_names = metric_generator.topology.domains_names.as_ref(); + let domain_names = self.metric_generator.topology.domains_names.as_ref(); if domain_names.is_some() { info!("domain_names: {:?}", domain_names.unwrap()); } @@ -236,18 +179,21 @@ impl StdoutExporter { } let consumers: Vec<(IProcess, f64)>; - if let Some(regex_filter) = regex_filter { - debug!("Processes filtered by '{}':", regex_filter.as_str()); - consumers = metric_generator + if let Some(regex) = &self.args.regex_filter { + println!("Processes filtered by '{regex}':"); + consumers = self + .metric_generator .topology .proc_tracker - .get_filtered_processes(regex_filter); + .get_filtered_processes(regex); } else { - println!("Top {process_number} consumers:"); - consumers = metric_generator + let n = self.args.processes; + println!("Top {n} consumers:"); + consumers = self + .metric_generator .topology .proc_tracker - .get_top_consumers(process_number); + .get_top_consumers(n); } info!("consumers : {:?}", consumers); diff --git a/src/exporters/warpten.rs b/src/exporters/warpten.rs index e65d913d..71d0ea29 100644 --- a/src/exporters/warpten.rs +++ b/src/exporters/warpten.rs @@ -1,145 +1,101 @@ use super::utils::get_hostname; use crate::exporters::*; use crate::sensors::Sensor; -use clap::{value_parser, Arg}; use std::time::Duration; -use std::{env, thread}; - -//use warp10::data::Format; /// An exporter that sends power consumption data of the host and its processes to /// a [Warp10](https://warp10.io) instance through **HTTP(s)** /// (contributions welcome to support websockets). pub struct Warp10Exporter { metric_generator: MetricGenerator, + /// Warp10 client + client: warp10::Client, + /// Warp10 auth token + write_token: String, + + step: Duration, } -impl Exporter for Warp10Exporter { - /// Control loop for self.iteration() - fn run(&mut self, parameters: clap::ArgMatches) { - let host = parameters.get_one::("host").unwrap(); - let scheme = parameters.get_one::("scheme").unwrap(); - let port = parameters.get_one::("port").unwrap(); - let write_token = if let Some(token) = parameters.get_one::("write-token") { - token.to_owned() - } else { - match env::var("SCAPH_WARP10_WRITE_TOKEN") { - Ok(val) => val, - Err(_e) => panic!( - "SCAPH_WARP10_WRITE_TOKEN not found in env, nor write-token flag was used." - ), - } - }; - //let read_token = parameters.value_of("read-token"); - let step: u64 = *parameters.get_one("step").unwrap(); - let qemu = parameters.get_flag("qemu"); - let watch_containers = parameters.get_flag("containers"); - self.metric_generator.watch_containers = watch_containers; - self.metric_generator.qemu = qemu; +/// Holds the arguments for a Warp10Exporter. +#[derive(clap::Args, Debug)] +pub struct ExporterArgs { + /// FQDN or IP address of the Warp10 instance + #[arg(short = 'H', long, default_value = "localhost")] + pub host: String, + + /// TCP port of the Warp10 instance + #[arg(short, long, default_value_t = 8080)] + pub port: u16, + + /// "http" or "https" + #[arg(short = 'S', long, default_value = "http")] + pub scheme: String, + + /// Auth token to write data to Warp10. + /// If not specified, you must set the env variable SCAPH_WARP10_WRITE_TOKEN + #[arg(short = 't', long)] + pub write_token: Option, + + /// Interval between two measurements, in seconds + #[arg(short, long, value_name = "SECONDS", default_value_t = 2)] + pub step: u64, + + /// Apply labels to metrics of processes looking like a Qemu/KVM virtual machine + #[arg(short, long)] + pub qemu: bool, +} +const TOKEN_ENV_VAR: &str = "SCAPH_WARP10_WRITE_TOKEN"; + +impl Exporter for Warp10Exporter { + /// Control loop for self.iterate() + fn run(&mut self) { loop { - match self.iteration(host, scheme, port.parse::().unwrap(), &write_token) { + match self.iterate() { Ok(res) => debug!("Result: {:?}", res), Err(err) => error!("Failed ! {:?}", err), } - thread::sleep(Duration::new(step, 0)); + std::thread::sleep(self.step); } } - /// Options for configuring the exporter. - fn get_options() -> Vec { - let mut options = Vec::new(); - let arg = Arg::new("host") - .default_value("localhost") - .help("Warp10 host's FQDN or IP address to send data to") - .long("host") - .short('H') - .required(false) - .action(clap::ArgAction::Set); - options.push(arg); - - let arg = Arg::new("scheme") - .default_value("http") - .help("Either 'http' or 'https'") - .long("scheme") - .short('s') - .required(false) - .action(clap::ArgAction::Set); - options.push(arg); - - let arg = Arg::new("port") - .default_value("8080") - .help("TCP port to join Warp10 on the host") - .long("port") - .short('p') - .required(false) - .action(clap::ArgAction::Set); - options.push(arg); - - let arg = Arg::new("write-token") - .help("Auth. token to write on Warp10") - .long("write-token") - .short('t') - .required(false) - .action(clap::ArgAction::Set); - options.push(arg); - - let arg = Arg::new("step") - .default_value("30") - .help("Time step between measurements, in seconds.") - .long("step") - .short('S') - .required(false) - .value_parser(value_parser!(u64)) - .action(clap::ArgAction::Set); - options.push(arg); - - let arg = Arg::new("qemu") - .help("Tells scaphandre it is running on a Qemu hypervisor.") - .long("qemu") - .short('q') - .required(false) - .action(clap::ArgAction::SetTrue); - options.push(arg); - - let arg = Arg::new("containers") - .help("Monitor and apply labels for processes running as containers") - .long("containers") - .required(false) - .action(clap::ArgAction::SetTrue); - options.push(arg); - - options + fn kind(&self) -> &str { + "warp10" } } impl Warp10Exporter { /// Instantiates and returns a new Warp10Exporter - pub fn new(mut sensor: Box) -> Warp10Exporter { - let topology = match *sensor.get_topology() { - Some(topo) => topo, - None => { - panic!("Couldn't generate the Topology"); - } - }; - let metric_generator = MetricGenerator::new(topology, get_hostname(), false, false); - Warp10Exporter { metric_generator } + pub fn new(sensor: &dyn Sensor, args: ExporterArgs) -> Warp10Exporter { + // Prepare for measurement + let topology = sensor + .get_topology() + .expect("sensor topology should be available"); + let metric_generator = MetricGenerator::new(topology, get_hostname(), args.qemu, false); + + // Prepare for sending data to Warp10 + let scheme = args.scheme; + let host = args.host; + let port = args.port; + let client = warp10::Client::new(&format!("{scheme}://{host}:{port}")) + .expect("warp10 Client could not be created"); + let write_token = args.write_token.unwrap_or_else(|| { + std::env::var(TOKEN_ENV_VAR).unwrap_or_else(|_| panic!("No token found, you must provide either --write-token or the env var {TOKEN_ENV_VAR}")) + }); + + Warp10Exporter { + metric_generator, + client, + write_token, + step: Duration::from_secs(args.step), + } } /// Collects data from the Topology, creates warp10::Data objects containing the /// metric itself and some labels attaches, stores them in a vector and sends it /// to Warp10 - pub fn iteration( - &mut self, - host: &str, - scheme: &str, - port: u16, - write_token: &str, - //read_token: Option<&str>, - ) -> Result, warp10::Error> { - let client = warp10::Client::new(&format!("{scheme}://{host}:{port}"))?; - let writer = client.get_writer(write_token.to_string()); - + pub fn iterate(&mut self) -> Result, warp10::Error> { + let writer = self.client.get_writer(self.write_token.clone()); self.metric_generator .topology .proc_tracker diff --git a/src/lib.rs b/src/lib.rs index be5f33b0..b19e66bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,152 +7,27 @@ extern crate log; pub mod exporters; pub mod sensors; -use clap::ArgMatches; -use colored::*; -#[cfg(feature = "json")] -use exporters::json::JSONExporter; -#[cfg(feature = "prometheus")] -use exporters::prometheus::PrometheusExporter; -#[cfg(target_os = "linux")] -use exporters::qemu::QemuExporter; -#[cfg(feature = "riemann")] -use exporters::riemann::RiemannExporter; -#[cfg(feature = "warpten")] -use exporters::warpten::Warp10Exporter; -use exporters::{stdout::StdoutExporter, Exporter}; -#[cfg(target_os = "windows")] -use sensors::msr_rapl::MsrRAPLSensor; -#[cfg(target_os = "linux")] -use sensors::powercap_rapl::PowercapRAPLSensor; -use sensors::Sensor; -use std::collections::HashMap; -use std::time::{Duration, SystemTime}; - -/// Helper function to get a Sensor instance from ArgMatches -fn get_sensor(matches: &ArgMatches) -> Box { - let sensor = match matches.get_one::("sensor").unwrap().as_str() { - #[cfg(target_os = "linux")] - "powercap_rapl" => PowercapRAPLSensor::new( - *matches.get_one("sensor-buffer-per-socket-max-kB").unwrap(), - *matches.get_one("sensor-buffer-per-domain-max-kB").unwrap(), - matches.get_flag("vm"), - ), - #[cfg(target_os = "linux")] - _ => PowercapRAPLSensor::new( - *matches.get_one("sensor-buffer-per-socket-max-kB").unwrap(), - *matches.get_one("sensor-buffer-per-domain-max-kB").unwrap(), - matches.get_flag("vm"), - ), - #[cfg(not(target_os = "linux"))] - _ => MsrRAPLSensor::new(), - }; - Box::new(sensor) -} - -/// Matches the sensor and exporter name and options requested from the command line and -/// creates the appropriate instances. Launchs the standardized entrypoint of -/// the choosen exporter: run() -/// This function should be updated to take new exporters into account. -pub fn run(matches: ArgMatches) { - loggerv::init_with_verbosity(u64::from(matches.get_count("v"))).unwrap(); - let sensor_boxed = get_sensor(&matches); - let exporter_parameters; +#[cfg(target_os = "windows")] +use sensors::msr_rapl; - let mut header = true; - if matches.get_flag("no-header") { - header = false; - } +#[cfg(target_os = "linux")] +use sensors::powercap_rapl; - if let Some(stdout_exporter_parameters) = matches.subcommand_matches("stdout") { - if header { - scaphandre_header("stdout"); - } - exporter_parameters = stdout_exporter_parameters.clone(); - let mut exporter = StdoutExporter::new(sensor_boxed); - exporter.run(exporter_parameters); - } else if let Some(json_exporter_parameters) = matches.subcommand_matches("json") { - if header { - scaphandre_header("json"); - } - exporter_parameters = json_exporter_parameters.clone(); - let mut exporter = JSONExporter::new(sensor_boxed); - exporter.run(exporter_parameters); - } else if let Some(riemann_exporter_parameters) = matches.subcommand_matches("riemann") { - if header { - scaphandre_header("riemann"); - } - exporter_parameters = riemann_exporter_parameters.clone(); - let mut exporter = RiemannExporter::new(sensor_boxed); - exporter.run(exporter_parameters); - } else if let Some(prometheus_exporter_parameters) = matches.subcommand_matches("prometheus") { - if header { - scaphandre_header("prometheus"); - } - exporter_parameters = prometheus_exporter_parameters.clone(); - let mut exporter = PrometheusExporter::new(sensor_boxed); - exporter.run(exporter_parameters); - } else if let Some(warp10_exporter_parameters) = matches.subcommand_matches("warp10") { - #[cfg(feature = "warpten")] - { - if header { - scaphandre_header("warp10"); - } - exporter_parameters = warp10_exporter_parameters.clone(); - let mut exporter = Warp10Exporter::new(sensor_boxed); - exporter.run(exporter_parameters); - } - } else { - #[cfg(target_os = "linux")] - { - if let Some(qemu_exporter_parameters) = matches.subcommand_matches("qemu") { - if header { - scaphandre_header("qemu"); - } - exporter_parameters = qemu_exporter_parameters.clone(); - let mut exporter = QemuExporter::new(sensor_boxed); - exporter.run(exporter_parameters); - } - error!("Warp10 exporter feature was not included in this build."); - } - error!("Couldn't determine which exporter to run."); - } -} +use std::time::{Duration, SystemTime}; -/// Returns options needed for each exporter as a HashMap. -/// This function has to be updated to enable a new exporter. -pub fn get_exporters_options() -> HashMap> { - let mut options = HashMap::new(); - options.insert( - String::from("stdout"), - exporters::stdout::StdoutExporter::get_options(), - ); - #[cfg(feature = "json")] - options.insert( - String::from("json"), - exporters::json::JSONExporter::get_options(), - ); - #[cfg(feature = "prometheus")] - options.insert( - String::from("prometheus"), - exporters::prometheus::PrometheusExporter::get_options(), - ); - #[cfg(feature = "riemann")] - options.insert( - String::from("riemann"), - exporters::riemann::RiemannExporter::get_options(), - ); +/// Create a new [`Sensor`] instance with the default sensor available, +/// with its default options. +pub fn get_default_sensor() -> impl sensors::Sensor { #[cfg(target_os = "linux")] - options.insert( - String::from("qemu"), - exporters::qemu::QemuExporter::get_options(), + return powercap_rapl::PowercapRAPLSensor::new( + powercap_rapl::DEFAULT_BUFFER_PER_SOCKET_MAX_KBYTES, + powercap_rapl::DEFAULT_BUFFER_PER_DOMAIN_MAX_KBYTES, + false, ); - #[cfg(feature = "warp10")] - options.insert( - String::from("warp10"), - exporters::warpten::Warp10Exporter::get_options(), - ); - options + + #[cfg(target_os = "windows")] + return msr_rapl::MsrRAPLSensor::new(); } fn current_system_time_since_epoch() -> Duration { @@ -161,12 +36,6 @@ fn current_system_time_since_epoch() -> Duration { .unwrap() } -pub fn scaphandre_header(exporter_name: &str) { - let title = format!("Scaphandre {exporter_name} exporter"); - println!("{}", title.red().bold()); - println!("Sending ⚡ metrics"); -} - /// Returns rust crate version, can be use used in language bindings to expose Rust core version pub fn crate_version() -> &'static str { env!("CARGO_PKG_VERSION") diff --git a/src/main.rs b/src/main.rs index 39b4d016..50a33a56 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,93 +1,195 @@ //! Generic sensor and transmission agent for energy consumption related metrics. -use clap::{crate_authors, crate_version, value_parser, Arg, ArgAction, Command}; -use scaphandre::{get_exporters_options, run}; -fn main() { +use clap::{command, ArgAction, Parser, Subcommand}; +use colored::Colorize; +use scaphandre::{exporters, sensors::Sensor}; + +#[cfg(target_os = "linux")] +use scaphandre::sensors::powercap_rapl; + +// the struct below defines the main Scaphandre command-line interface +/// Extensible metrology agent for electricity consumption related metrics. +#[derive(Parser)] +#[command(author, version)] +struct Cli { + /// The exporter module to use to output the energy consumption metrics + #[command(subcommand)] + exporter: ExporterChoice, + + /// Increase the verbosity level + #[arg(short, action = ArgAction::Count, default_value_t = 0)] + verbose: u8, + + /// Don't print the header to the standard output + #[arg(long, default_value_t = false)] + no_header: bool, + + /// Tell Scaphandre that it's running in a virtual machine. + /// You should have another instance of Scaphandre running on the hypervisor (see docs). + #[arg(long, default_value_t = false)] + vm: bool, + + /// The sensor module to use to gather the energy consumption metrics + #[arg(short, long)] + sensor: Option, + + /// Maximum memory size allowed, in KiloBytes, for storing energy consumption of each **domain**. + /// Only available for the RAPL sensor (on Linux). #[cfg(target_os = "linux")] - let sensors = ["powercap_rapl"]; - #[cfg(target_os = "windows")] - let sensors = ["msr_rapl"]; - let exporters_options = get_exporters_options(); - let exporters: Vec = exporters_options.keys().map(|x| x.to_string()).collect(); + #[arg(long, default_value_t = powercap_rapl::DEFAULT_BUFFER_PER_DOMAIN_MAX_KBYTES)] + sensor_buffer_per_domain_max_kb: u16, + /// Maximum memory size allowed, in KiloBytes, for storing energy consumption of each **socket**. + /// Only available for the RAPL sensor (on Linux). #[cfg(target_os = "linux")] - let sensor_default_value = String::from("powercap_rapl"); - #[cfg(not(target_os = "linux"))] - let sensor_default_value = String::from("msr_rapl"); - - let mut matches = Command::new("scaphandre") - .author(crate_authors!()) - .version(crate_version!()) - .about("Extensible metrology agent for energy/electricity consumption related metrics") - .arg( - Arg::new("v") - .short('v') - .help("Sets the level of verbosity.") - .action(ArgAction::Count) - ) - .arg( - Arg::new("no-header") - .value_name("no-header") - .help("Prevents the header to be displayed in the terminal output.") - .required(false) - .long("no-header") - .action(clap::ArgAction::SetTrue), + #[arg(long, default_value_t = powercap_rapl::DEFAULT_BUFFER_PER_SOCKET_MAX_KBYTES)] + sensor_buffer_per_socket_max_kb: u16, +} + +/// Defines the possible subcommands, one per exporter. +/// +/// ### Description style +/// Per the clap documentation, the description of commands and arguments should be written in the style applied here, +/// *not* in the third-person. That is, use "Do xyz" instead of "Does xyz". +#[derive(Subcommand)] +enum ExporterChoice { + /// Write the metrics in the JSON format to a file or to stdout + #[cfg(feature = "json")] + Json(exporters::json::ExporterArgs), + + /// Expose the metrics to a Prometheus HTTP endpoint + #[cfg(feature = "prometheus")] + Prometheus(exporters::prometheus::ExporterArgs), + + /// Watch all Qemu-KVM virtual machines running on the host and expose the metrics + /// of each of them in a dedicated folder + #[cfg(feature = "qemu")] + Qemu, + + /// Expose the metrics to a Riemann server + #[cfg(feature = "riemann")] + Riemann(exporters::riemann::ExporterArgs), + + /// Write the metrics to the terminal + Stdout(exporters::stdout::ExporterArgs), + + /// Expose the metrics to a Warp10 host, through HTTP + #[cfg(feature = "warpten")] + Warpten(exporters::warpten::ExporterArgs), +} + +fn main() { + let cli = Cli::parse(); + loggerv::init_with_verbosity(cli.verbose.into()).expect("unable to initialize the logger"); + + let sensor = build_sensor(&cli); + let mut exporter = build_exporter(cli.exporter, &sensor); + if !cli.no_header { + print_scaphandre_header(exporter.kind()); + } + + exporter.run(); +} + +fn build_exporter(choice: ExporterChoice, sensor: &dyn Sensor) -> Box { + match choice { + ExporterChoice::Json(args) => { + Box::new(exporters::json::JsonExporter::new(sensor, args)) // keep this in braces + } + ExporterChoice::Prometheus(args) => { + Box::new(exporters::prometheus::PrometheusExporter::new(sensor, args)) + } + ExporterChoice::Qemu => { + Box::new(exporters::qemu::QemuExporter::new(sensor)) // keep this in braces + } + ExporterChoice::Riemann(args) => { + Box::new(exporters::riemann::RiemannExporter::new(sensor, args)) + } + ExporterChoice::Stdout(args) => { + Box::new(exporters::stdout::StdoutExporter::new(sensor, args)) + } + ExporterChoice::Warpten(args) => { + Box::new(exporters::warpten::Warp10Exporter::new(sensor, args)) + } + } + // Note that invalid choices are automatically turned into errors by `parse()` before the Cli is populated, + // that's why they don't appear in this function. +} + +/// Returns the sensor to use, given the command-line arguments. +/// Unless sensor-specific options are provided, this should return +/// the same thing as [`scaphandre::get_default_sensor`]. +fn build_sensor(cli: &Cli) -> impl Sensor { + #[cfg(target_os = "linux")] + let rapl_sensor = || { + powercap_rapl::PowercapRAPLSensor::new( + cli.sensor_buffer_per_socket_max_kb, + cli.sensor_buffer_per_domain_max_kb, + cli.vm, ) - .arg( - Arg::new("sensor") - .value_name("sensor") - .help("Sensor module to apply on the host to get energy consumption metrics.") - .required(false) - .default_value(&sensor_default_value) - .short('s') - .long("sensor") - .value_parser(sensors) - .action(clap::ArgAction::Set) - ).arg( - Arg::new("sensor-buffer-per-domain-max-kB") - .value_name("sensor-buffer-per-domain-max-kB") - .help("Maximum memory size allowed, in KiloBytes, for storing energy consumption of each domain.") - .required(false) - .default_value("1") - .value_parser(value_parser!(u16)) - .action(clap::ArgAction::Set) - ).arg( - Arg::new("sensor-buffer-per-socket-max-kB") - .value_name("sensor-buffer-per-socket-max-kB") - .help("Maximum memory size allowed, in KiloBytes, for storing energy consumption of each socket.") - .required(false) - .default_value("1") - .value_parser(value_parser!(u16)) - .action(clap::ArgAction::Set) - ).arg( - Arg::new("vm") - .value_name("vm") - .help("Tell scaphandre if he is running in a virtual machine.") - .long("vm") - .required(false) - .action(clap::ArgAction::SetTrue), - ); - - for exporter in exporters { - let mut subcmd = Command::new(&exporter).about( - match exporter.as_str() { - "stdout" => "Stdout exporter allows you to output the power consumption data in the terminal", - "json" => "JSON exporter allows you to output the power consumption data in a json file", - "prometheus" => "Prometheus exporter exposes power consumption metrics on an http endpoint (/metrics is default) in prometheus accepted format", - "riemann" => "Riemann exporter sends power consumption metrics to a Riemann server", - "qemu" => "Qemu exporter watches all Qemu/KVM virtual machines running on the host and exposes metrics of each of them in a dedicated folder", - "warp10" => "Warp10 exporter sends data to a Warp10 host, through HTTP", - _ => "Unknown exporter", + }; + + #[cfg(target_os = "windows")] + let msr_sensor_win = || sensors::msr_rapl::MsrRAPLSensor::new(); + + match cli.sensor.as_deref() { + Some("powercap_rapl") => { + #[cfg(target_os = "linux")] + { + rapl_sensor() + } + #[cfg(not(target_os = "linux"))] + panic!("Invalid sensor: Scaphandre's powercap_rapl only works on Linux") + } + Some("msr") => { + #[cfg(target_os = "windows")] + { + msr_sensor() } - ); + #[cfg(not(target_os = "windows"))] + panic!("Invalid sensor: Scaphandre's msr only works on Windows") + } + Some(s) => panic!("Unknown sensor type {}", s), + None => { + #[cfg(target_os = "linux")] + return rapl_sensor(); + + #[cfg(target_os = "windows")] + return msr_sensor_win(); + + #[cfg(not(any(target_os = "linux", target_os = "windows")))] + compile_error!("Unsupported target OS") + } + } +} - let myopts = exporters_options.get(&exporter).unwrap(); - for opt in myopts { - subcmd = subcmd.arg(opt); +fn print_scaphandre_header(exporter_name: &str) { + let title = format!("Scaphandre {exporter_name} exporter"); + println!("{}", title.red().bold()); + println!("Sending ⚡ metrics"); +} + +#[cfg(test)] +mod test { + use super::*; + const SUBCOMMANDS: [&str; 6] = ["json", "prometheus", "qemu", "riemann", "stdout", "warpten"]; + + /// Test that `--help` works for Scaphandre _and_ for each subcommand. + /// This also ensures that all the subcommands are properly defined, as Clap will check some constraints + /// when trying to parse a subcommand (for instance, it will check that no two short options have the same name). + #[test] + fn test_help() { + fn assert_shows_help(args: &[&str]) { + match Cli::try_parse_from(args) { + Ok(_) => panic!("The CLI didn't generate a help message for {:?}, are the inputs correct?", args), + Err(e) => assert_eq!(e.kind(), clap::error::ErrorKind::DisplayHelp, "The CLI emitted an error for {args:?}:\n{e}") + }; + } + assert_shows_help(&["scaphandre", "--help"]); + for cmd in SUBCOMMANDS { + assert_shows_help(&["scaphandre", cmd, "--help"]); } - matches = matches.subcommand(subcmd); } - run(matches.get_matches()); } // Copyright 2020 The scaphandre authors. diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 3cb4c940..1d9fd3e8 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -20,7 +20,7 @@ use utils::{current_system_time_since_epoch, IProcess, ProcessTracker}; // !!!!!!!!!!!!!!!!! Sensor !!!!!!!!!!!!!!!!!!!!!!! /// Sensor trait, the Sensor API. pub trait Sensor { - fn get_topology(&mut self) -> Box>; + fn get_topology(&self) -> Box>; fn generate_topology(&self) -> Result>; } @@ -1452,9 +1452,9 @@ mod tests { #[test] fn read_topology_stats() { #[cfg(target_os = "linux")] - let mut sensor = powercap_rapl::PowercapRAPLSensor::new(8, 8, false); + let sensor = powercap_rapl::PowercapRAPLSensor::new(8, 8, false); #[cfg(not(target_os = "linux"))] - let mut sensor = msr_rapl::MsrRAPLSensor::new(); + let sensor = msr_rapl::MsrRAPLSensor::new(); let topo = (*sensor.get_topology()).unwrap(); println!("{:?}", topo.read_stats()); } @@ -1462,9 +1462,9 @@ mod tests { #[test] fn read_core_stats() { #[cfg(target_os = "linux")] - let mut sensor = powercap_rapl::PowercapRAPLSensor::new(8, 8, false); + let sensor = powercap_rapl::PowercapRAPLSensor::new(8, 8, false); #[cfg(not(target_os = "linux"))] - let mut sensor = msr_rapl::MsrRAPLSensor::new(); + let sensor = msr_rapl::MsrRAPLSensor::new(); let mut topo = (*sensor.get_topology()).unwrap(); for s in topo.get_sockets() { for c in s.get_cores() { @@ -1476,9 +1476,9 @@ mod tests { #[test] fn read_socket_stats() { #[cfg(target_os = "linux")] - let mut sensor = powercap_rapl::PowercapRAPLSensor::new(8, 8, false); + let sensor = powercap_rapl::PowercapRAPLSensor::new(8, 8, false); #[cfg(not(target_os = "linux"))] - let mut sensor = msr_rapl::MsrRAPLSensor::new(); + let sensor = msr_rapl::MsrRAPLSensor::new(); let mut topo = (*sensor.get_topology()).unwrap(); for s in topo.get_sockets() { println!("{:?}", s.read_stats()); diff --git a/src/sensors/msr_rapl.rs b/src/sensors/msr_rapl.rs index 0ccf1d50..b06095f7 100644 --- a/src/sensors/msr_rapl.rs +++ b/src/sensors/msr_rapl.rs @@ -300,7 +300,7 @@ impl Sensor for MsrRAPLSensor { Ok(topology) } - fn get_topology(&mut self) -> Box> { + fn get_topology(&self) -> Box> { let topology = self.generate_topology().ok(); if topology.is_none() { panic!("Couldn't generate the topology !"); diff --git a/src/sensors/powercap_rapl.rs b/src/sensors/powercap_rapl.rs index d1a1ed44..81722a45 100644 --- a/src/sensors/powercap_rapl.rs +++ b/src/sensors/powercap_rapl.rs @@ -7,6 +7,9 @@ use std::collections::HashMap; use std::error::Error; use std::{env, fs}; +pub const DEFAULT_BUFFER_PER_SOCKET_MAX_KBYTES: u16 = 1; +pub const DEFAULT_BUFFER_PER_DOMAIN_MAX_KBYTES: u16 = 1; + /// This is a Sensor type that relies on powercap and rapl linux modules /// to collect energy consumption from CPU sockets and RAPL domains pub struct PowercapRAPLSensor { @@ -188,7 +191,7 @@ impl Sensor for PowercapRAPLSensor { } /// Instanciates Topology object if not existing and returns it - fn get_topology(&mut self) -> Box> { + fn get_topology(&self) -> Box> { let topology = self.generate_topology().ok(); if topology.is_none() { panic!("Couldn't generate the topology !"); @@ -207,7 +210,7 @@ mod tests { } #[test] fn get_topology_returns_topology_type() { - let mut sensor = PowercapRAPLSensor::new(1, 1, false); + let sensor = PowercapRAPLSensor::new(1, 1, false); let topology = sensor.get_topology(); assert_eq!( "alloc::boxed::Box>", diff --git a/tests/integration.rs b/tests/integration.rs index fe4d60dd..74c729f3 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1,30 +1,24 @@ -//#[cfg(target_os = "linux")] -//use scaphandre::exporters::qemu::QemuExporter; -//#[cfg(target_os = "linux")] -//use scaphandre::sensors::powercap_rapl::PowercapRAPLSensor; -//use std::env::current_dir; -//use std::fs::{create_dir, read_dir}; +#[cfg(target_os = "linux")] +use scaphandre::exporters::qemu::QemuExporter; +#[cfg(target_os = "linux")] +use scaphandre::sensors::powercap_rapl::PowercapRAPLSensor; +use std::env::current_dir; +use std::fs::{create_dir, read_dir}; -//#[cfg(target_os = "linux")] -//#[test] -//fn exporter_qemu() { -// use scaphandre::{exporters::{MetricGenerator, utils::get_hostname}, sensors::Sensor}; -// -// let sensor = PowercapRAPLSensor::new(1, 1, false); -// let mut exporter = QemuExporter::new(Box::new(sensor)); -// // Create integration_tests directory if it does not exist -// let curdir = current_dir().unwrap(); -// let path = curdir.join("integration_tests"); -// if !path.is_dir() { -// create_dir(&path).expect("Fail to create integration_tests directory"); -// } -// // Convert to std::string::String -// let path = path.into_os_string().to_str().unwrap().to_string(); -// if let Ok(mut topology) = &sensor.generate_topology() { -// let mut metric_generator = MetricGenerator::new(topology, get_hostname(), true, false); -// exporter.iteration(path.clone(), &mut metric_generator); -// let content = read_dir(path); -// assert_eq!(content.is_ok(), true); -// } -//} -// +#[cfg(target_os = "linux")] +#[test] +fn exporter_qemu() { + let sensor = PowercapRAPLSensor::new(1, 1, false); + let mut exporter = QemuExporter::new(&sensor); + // Create integration_tests directory if it does not exist + let curdir = current_dir().unwrap(); + let path = curdir.join("integration_tests"); + if !path.is_dir() { + create_dir(&path).expect("Fail to create integration_tests directory"); + } + // Convert to std::string::String + let path = path.into_os_string().to_str().unwrap().to_string(); + exporter.iterate(path.clone()); + let content = read_dir(path); + assert_eq!(content.is_ok(), true); +} From 0fa37a1acfaf70391190f53af34acdec5689ca68 Mon Sep 17 00:00:00 2001 From: "Raffin, Guillaume" Date: Thu, 6 Apr 2023 18:48:11 +0200 Subject: [PATCH 106/303] Upgrade to Rust edition 2021 --- Cargo.toml | 2 +- src/main.rs | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6ee81a9a..0f99fa00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "scaphandre" version = "0.5.0" authors = ["Benoit Petit "] -edition = "2018" +edition = "2021" license = "Apache-2.0" description = "Electrical power consumption measurement agent." repository = "https://github.com/hubblo-org/scaphandre" diff --git a/src/main.rs b/src/main.rs index 50a33a56..3f938407 100644 --- a/src/main.rs +++ b/src/main.rs @@ -47,7 +47,7 @@ struct Cli { } /// Defines the possible subcommands, one per exporter. -/// +/// /// ### Description style /// Per the clap documentation, the description of commands and arguments should be written in the style applied here, /// *not* in the third-person. That is, use "Do xyz" instead of "Does xyz". @@ -173,7 +173,7 @@ fn print_scaphandre_header(exporter_name: &str) { mod test { use super::*; const SUBCOMMANDS: [&str; 6] = ["json", "prometheus", "qemu", "riemann", "stdout", "warpten"]; - + /// Test that `--help` works for Scaphandre _and_ for each subcommand. /// This also ensures that all the subcommands are properly defined, as Clap will check some constraints /// when trying to parse a subcommand (for instance, it will check that no two short options have the same name). @@ -181,8 +181,14 @@ mod test { fn test_help() { fn assert_shows_help(args: &[&str]) { match Cli::try_parse_from(args) { - Ok(_) => panic!("The CLI didn't generate a help message for {:?}, are the inputs correct?", args), - Err(e) => assert_eq!(e.kind(), clap::error::ErrorKind::DisplayHelp, "The CLI emitted an error for {args:?}:\n{e}") + Ok(_) => panic!( + "The CLI didn't generate a help message for {args:?}, are the inputs correct?" + ), + Err(e) => assert_eq!( + e.kind(), + clap::error::ErrorKind::DisplayHelp, + "The CLI emitted an error for {args:?}:\n{e}" + ), }; } assert_shows_help(&["scaphandre", "--help"]); From c8d8d751449d295ae09246fdcd45b1d78b7b7115 Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Fri, 21 Apr 2023 10:46:46 +0200 Subject: [PATCH 107/303] Upgrade some dependencies --- Cargo.lock | 80 +++++++++++++++++++++++++++----------------- Cargo.toml | 13 ++++--- src/sensors/utils.rs | 6 ++-- 3 files changed, 59 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8274f23f..378169a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,9 +10,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" dependencies = [ "memchr", ] @@ -178,9 +178,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.2.3" +version = "4.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f9152d70e42172fdb87de2efd7327160beee37886027cf86f30a233d5b30b4" +checksum = "956ac1f6381d8d82ab4684768f89c0ea3afe66925ceadb4eeb3fc452ffc55d62" dependencies = [ "clap_builder", "clap_derive", @@ -189,9 +189,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.2.3" +version = "4.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e067b220911598876eb55d52725ddcc201ffe3f0904018195973bc5b012ea2ca" +checksum = "84080e799e54cff944f4b4a4b0e71630b0e0443b25b985175c7dddc1a859b749" dependencies = [ "anstream", "anstyle", @@ -808,7 +808,7 @@ checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ "hermit-abi 0.3.1", "io-lifetimes", - "rustix", + "rustix 0.37.13", "windows-sys 0.48.0", ] @@ -900,9 +900,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.141" +version = "0.2.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" +checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" [[package]] name = "libnghttp2-sys" @@ -943,9 +943,15 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.3.2" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + +[[package]] +name = "linux-raw-sys" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f508063cc7bb32987c71511216bd5a32be15bccb6a80b52df8b9d7f01fc3aa2" +checksum = "9b085a4f2cde5781fc4b1717f2e86c62f5cda49de7ba99a7c2eae02b61c9064c" [[package]] name = "lock_api" @@ -1027,9 +1033,9 @@ dependencies = [ [[package]] name = "ntapi" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc51db7b362b205941f71232e56c625156eb9a929f8cf74a428fd5bc094a4afc" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" dependencies = [ "winapi", ] @@ -1071,9 +1077,9 @@ checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "openssl" -version = "0.10.50" +version = "0.10.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e30d8bc91859781f0a943411186324d580f2bbeb71b452fe91ae344806af3f1" +checksum = "97ea2d98598bf9ada7ea6ee8a30fb74f9156b63bbe495d64ec2b87c269d2dda3" dependencies = [ "bitflags", "cfg-if", @@ -1103,9 +1109,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.85" +version = "0.9.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d3d193fb1488ad46ffe3aaabc912cc931d02ee8518fe2959aea8ef52718b0c0" +checksum = "992bac49bdbab4423199c654a5515bd2a6c6a23bf03f2dd3bdb7e5ae6259bc69" dependencies = [ "cc", "libc", @@ -1197,9 +1203,9 @@ checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "polling" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be1c66a6add46bff50935c313dae30a5030cf8385c5206e8a95e9e9def974aa" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ "autocfg", "bitflags", @@ -1228,9 +1234,9 @@ dependencies = [ [[package]] name = "procfs" -version = "0.12.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0941606b9934e2d98a3677759a971756eb821f75764d0e0d26946d08e74d9104" +checksum = "943ca7f9f29bab5844ecd8fdb3992c5969b6622bb9609b9502fef9b4310e3f1f" dependencies = [ "bitflags", "byteorder", @@ -1238,7 +1244,7 @@ dependencies = [ "flate2", "hex", "lazy_static", - "libc", + "rustix 0.36.12", ] [[package]] @@ -1350,9 +1356,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +checksum = "ac6cf59af1067a3fb53fbe5c88c053764e930f932be1d71d3ffe032cbe147f59" dependencies = [ "aho-corasick", "memchr", @@ -1361,9 +1367,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.29" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "b6868896879ba532248f33598de5181522d8b3d9d724dfd230911e1a7d4822f5" [[package]] name = "riemann_client" @@ -1398,15 +1404,29 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.12" +version = "0.36.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0af200a3324fa5bcd922e84e9b55a298ea9f431a489f01961acdebc6e908f25" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.1.4", + "windows-sys 0.45.0", +] + +[[package]] +name = "rustix" +version = "0.37.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "722529a737f5a942fdbac3a46cee213053196737c5eaa3386d52e85b786f2659" +checksum = "f79bef90eb6d984c72722595b5b1348ab39275a5e5123faca6863bf07d75a4e0" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", - "linux-raw-sys", + "linux-raw-sys 0.3.3", "windows-sys 0.48.0", ] @@ -1653,7 +1673,7 @@ dependencies = [ "cfg-if", "fastrand", "redox_syscall 0.3.5", - "rustix", + "rustix 0.37.13", "windows-sys 0.45.0", ] diff --git a/Cargo.toml b/Cargo.toml index 0f99fa00..28ffe1e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,10 +12,10 @@ homepage = "https://hubblo-org.github.io/scaphandre-documentation" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -loggerv = "0.7.2" +loggerv = "0.7" log = "0.4" clap = { version = "4.2", features = ["cargo", "derive"] } -regex = "1.7.0" +regex = "1.7" riemann_client = { version = "0.9.0", optional = true } hostname = "0.3.1" protobuf = "2.28.0" @@ -24,9 +24,9 @@ serde_json = { version = "1.0", optional = true } ordered-float = "2.0" warp10 = { version = "2.0.0", optional = true } rand = { version = "0.7.3" } -time = "0.3.20" -colored = "2.0.0" -chrono = "0.4.19" +time = "0.3" +colored = "2.0" +chrono = "0.4" docker-sync = { version = "0.1.2", optional = true } k8s-sync = { version = "0.2.3", optional = true } hyper = { version = "0.14", features = ["full"], optional = true } @@ -34,8 +34,7 @@ tokio = { version = "1.26.0", features = ["full"], optional = true} sysinfo = { version = "0.28.3"} [target.'cfg(target_os="linux")'.dependencies] -#procfs = "0.13.2" -procfs = { version = "0.12.0" } +procfs = { version = "0.15.0" } [target.'cfg(target_os="windows")'.dependencies] windows = { version = "0.27.0", features = ["alloc","Win32_Storage_FileSystem","Win32_Foundation","Win32_Security","Win32_System_IO","Win32_System_Ioctl"]} diff --git a/src/sensors/utils.rs b/src/sensors/utils.rs index 45989b0e..4ea37aae 100644 --- a/src/sensors/utils.rs +++ b/src/sensors/utils.rs @@ -182,15 +182,15 @@ impl IProcess { pub fn cgroups() {} } -pub fn page_size() -> Result { +pub fn page_size() -> Result { let res; #[cfg(target_os = "linux")] { - res = Ok(procfs::page_size().unwrap()) + res = Ok(procfs::page_size()) } #[cfg(target_os = "windows")] { - res = Ok(4096) + res = Ok(4096u64) } res } From a4a4d7999af99f8bd6a4bdc6f51debde3811318d Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Wed, 10 May 2023 16:40:20 +0200 Subject: [PATCH 108/303] Fix conditional features --- Cargo.toml | 2 +- src/main.rs | 39 ++++++++++++++++++++++++++++++--------- tests/integration.rs | 14 ++++++-------- 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 28ffe1e5..b722a67b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ procfs = { version = "0.15.0" } windows = { version = "0.27.0", features = ["alloc","Win32_Storage_FileSystem","Win32_Foundation","Win32_Security","Win32_System_IO","Win32_System_Ioctl"]} [features] -default = ["prometheus", "riemann", "warpten", "json", "containers", "qemu"] +default = ["prometheus", "riemann", "warpten", "json", "containers"] prometheus = ["hyper", "tokio"] riemann = ["riemann_client"] json = ["serde", "serde_json"] diff --git a/src/main.rs b/src/main.rs index 3f938407..690f0398 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,9 @@ use scaphandre::{exporters, sensors::Sensor}; #[cfg(target_os = "linux")] use scaphandre::sensors::powercap_rapl; +#[cfg(target_os = "windows")] +use scaphandre::sensors::msr_rapl; + // the struct below defines the main Scaphandre command-line interface /// Extensible metrology agent for electricity consumption related metrics. #[derive(Parser)] @@ -53,6 +56,9 @@ struct Cli { /// *not* in the third-person. That is, use "Do xyz" instead of "Does xyz". #[derive(Subcommand)] enum ExporterChoice { + /// Write the metrics to the terminal + Stdout(exporters::stdout::ExporterArgs), + /// Write the metrics in the JSON format to a file or to stdout #[cfg(feature = "json")] Json(exporters::json::ExporterArgs), @@ -70,9 +76,6 @@ enum ExporterChoice { #[cfg(feature = "riemann")] Riemann(exporters::riemann::ExporterArgs), - /// Write the metrics to the terminal - Stdout(exporters::stdout::ExporterArgs), - /// Expose the metrics to a Warp10 host, through HTTP #[cfg(feature = "warpten")] Warpten(exporters::warpten::ExporterArgs), @@ -93,21 +96,26 @@ fn main() { fn build_exporter(choice: ExporterChoice, sensor: &dyn Sensor) -> Box { match choice { + ExporterChoice::Stdout(args) => { + Box::new(exporters::stdout::StdoutExporter::new(sensor, args)) + } + #[cfg(feature = "json")] ExporterChoice::Json(args) => { Box::new(exporters::json::JsonExporter::new(sensor, args)) // keep this in braces } + #[cfg(feature = "prometheus")] ExporterChoice::Prometheus(args) => { Box::new(exporters::prometheus::PrometheusExporter::new(sensor, args)) } + #[cfg(feature = "qemu")] ExporterChoice::Qemu => { Box::new(exporters::qemu::QemuExporter::new(sensor)) // keep this in braces } + #[cfg(feature = "riemann")] ExporterChoice::Riemann(args) => { Box::new(exporters::riemann::RiemannExporter::new(sensor, args)) } - ExporterChoice::Stdout(args) => { - Box::new(exporters::stdout::StdoutExporter::new(sensor, args)) - } + #[cfg(feature = "warpten")] ExporterChoice::Warpten(args) => { Box::new(exporters::warpten::Warp10Exporter::new(sensor, args)) } @@ -130,7 +138,7 @@ fn build_sensor(cli: &Cli) -> impl Sensor { }; #[cfg(target_os = "windows")] - let msr_sensor_win = || sensors::msr_rapl::MsrRAPLSensor::new(); + let msr_sensor_win = || msr_rapl::MsrRAPLSensor::new(); match cli.sensor.as_deref() { Some("powercap_rapl") => { @@ -144,7 +152,7 @@ fn build_sensor(cli: &Cli) -> impl Sensor { Some("msr") => { #[cfg(target_os = "windows")] { - msr_sensor() + msr_sensor_win() } #[cfg(not(target_os = "windows"))] panic!("Invalid sensor: Scaphandre's msr only works on Windows") @@ -172,7 +180,20 @@ fn print_scaphandre_header(exporter_name: &str) { #[cfg(test)] mod test { use super::*; - const SUBCOMMANDS: [&str; 6] = ["json", "prometheus", "qemu", "riemann", "stdout", "warpten"]; + + const SUBCOMMANDS: &[&str] = &[ + "stdout", + #[cfg(feature = "prometheus")] + "prometheus", + #[cfg(feature = "riemann")] + "riemann", + #[cfg(feature = "json")] + "json", + #[cfg(feature = "warpten")] + "warpten", + #[cfg(feature = "qemu")] + "qemu", + ]; /// Test that `--help` works for Scaphandre _and_ for each subcommand. /// This also ensures that all the subcommands are properly defined, as Clap will check some constraints diff --git a/tests/integration.rs b/tests/integration.rs index 74c729f3..81a93c6e 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1,13 +1,11 @@ -#[cfg(target_os = "linux")] -use scaphandre::exporters::qemu::QemuExporter; -#[cfg(target_os = "linux")] -use scaphandre::sensors::powercap_rapl::PowercapRAPLSensor; -use std::env::current_dir; -use std::fs::{create_dir, read_dir}; - -#[cfg(target_os = "linux")] +#[cfg(all(feature = "qemu", target_os = "linux"))] #[test] fn exporter_qemu() { + use scaphandre::exporters::qemu::QemuExporter; + use scaphandre::sensors::powercap_rapl::PowercapRAPLSensor; + use std::env::current_dir; + use std::fs::{create_dir, read_dir}; + let sensor = PowercapRAPLSensor::new(1, 1, false); let mut exporter = QemuExporter::new(&sensor); // Create integration_tests directory if it does not exist From 85f9b91d0918e6c6d91e5c0a2024d3c120281388 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 16 May 2023 10:32:27 +0200 Subject: [PATCH 109/303] chore: adding test for json exporter --- .github/workflows/build-and-test.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 0126a17e..aa564147 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -136,6 +136,14 @@ jobs: run: | export AWX_TOKEN=$(awx --conf.host "${{env.AWX_HOST}}" --conf.username "${{env.AWX_PUBLIC_USER}}" --conf.password "${{env.AWX_PUBLIC_ACCESS}}" login | jq .token | tr -d '"') echo "::set-output name=awx_token::${AWX_TOKEN}" + - name: Build debug version + id: builddebug + run: | + awx --conf.token ${{ steps.login.outputs.awx_token }} --conf.host ${{ env.AWX_HOST }} job_templates launch --extra_vars="{\"github_repository\":\"${GITHUB_REPOSITORY}\",\"github_actor\":\"${GITHUB_ACTOR}\",\"github_workflow\":\"${GITHUB_WORKFLOW}\",\"github_workspace\":\"${GITHUB_WORKSPACE}\",\"github_event_name\":\"${GITHUB_EVENT_NAME}\",\"github_event_path\":\"${GITHUB_EVENT_PATH}\",\"github_sha\":\"${GITHUB_SHA}\",\"github_ref\":\"${GITHUB_REF}\",\"github_head_ref\":\"${GITHUB_HEAD_REF}\",\"github_base_ref\":\"${GITHUB_BASE_REF}\",\"github_server_url\":\"${GITHUB_SERVER_URL}\"}" 17 --monitor + - name: Test JSON exporter + id: jsonexporter + run: | + awx --conf.token ${{ steps.login.outputs.awx_token }} --conf.host ${{ env.AWX_HOST }} job_templates launch --extra_vars="{\"github_repository\":\"${GITHUB_REPOSITORY}\",\"github_actor\":\"${GITHUB_ACTOR}\",\"github_workflow\":\"${GITHUB_WORKFLOW}\",\"github_workspace\":\"${GITHUB_WORKSPACE}\",\"github_event_name\":\"${GITHUB_EVENT_NAME}\",\"github_event_path\":\"${GITHUB_EVENT_PATH}\",\"github_sha\":\"${GITHUB_SHA}\",\"github_ref\":\"${GITHUB_REF}\",\"github_head_ref\":\"${GITHUB_HEAD_REF}\",\"github_base_ref\":\"${GITHUB_BASE_REF}\",\"github_server_url\":\"${GITHUB_SERVER_URL}\"}" 18 --monitor - name: Build Docker image id: dockerbuild run: | From dfdcfae599e9d2b3bd9742caba07a8ea47eb5873 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 16 May 2023 11:37:24 +0200 Subject: [PATCH 110/303] fix: prometheusexporter required options --- src/exporters/prometheus.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/exporters/prometheus.rs b/src/exporters/prometheus.rs index 6d110cf4..33403a80 100644 --- a/src/exporters/prometheus.rs +++ b/src/exporters/prometheus.rs @@ -39,15 +39,15 @@ pub struct ExporterArgs { pub address: IpAddr, /// TCP port of the metrics endpoint for Prometheus - #[arg(short, long)] + #[arg(short, long, default_value_t = 8080)] pub port: u16, - #[arg(short, long)] + #[arg(short, long, default_value_t = String::from("metrics"))] pub suffix: String, /// Apply labels to metrics of processes that look like a Qemu/KVM virtual machine #[arg(long)] - pub qmeu: bool, + pub qemu: bool, /// Apply labels to metrics of processes running as containers #[arg(long)] @@ -82,7 +82,7 @@ impl Exporter for PrometheusExporter { let metric_generator = MetricGenerator::new( self.topo.clone(), // improvement possible here: avoid cloning by adding a lifetime param to MetricGenerator self.hostname.clone(), - self.args.qmeu, + self.args.qemu, self.args.containers, ); run_server(socket_addr, metric_generator, &self.args.suffix); From b4c4505970d44a6d2aeb0a1c3c6d61e2faa62204 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 16 May 2023 12:01:10 +0200 Subject: [PATCH 111/303] ci: fix deprecated set-ouput command --- .github/workflows/build-and-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index aa564147..5b48e0a0 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -103,7 +103,7 @@ jobs: id: login run: | export AWX_TOKEN=$(awx --conf.host "${{env.AWX_HOST}}" --conf.username "${{env.AWX_PUBLIC_USER}}" --conf.password "${{env.AWX_PUBLIC_ACCESS}}" login | jq .token | tr -d '"') - echo "::set-output name=awx_token::${AWX_TOKEN}" + echo "awx_token=${AWX_TOKEN}" >> $GITHUB_OUTPUT - name: Prepare Rust environment on bare metal worker id: rust run: | @@ -135,7 +135,7 @@ jobs: id: login run: | export AWX_TOKEN=$(awx --conf.host "${{env.AWX_HOST}}" --conf.username "${{env.AWX_PUBLIC_USER}}" --conf.password "${{env.AWX_PUBLIC_ACCESS}}" login | jq .token | tr -d '"') - echo "::set-output name=awx_token::${AWX_TOKEN}" + echo "awx_token=${AWX_TOKEN}" >> $GITHUB_OUTPUT - name: Build debug version id: builddebug run: | From f7173f63449eb15f4f1efb446149d1fceb375a7b Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 16 May 2023 12:08:17 +0200 Subject: [PATCH 112/303] ci: fix deprecated node12 based actions --- .github/workflows/build-and-test.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 5b48e0a0..c52af3d5 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -42,9 +42,9 @@ jobs: needs: check_pr_is_on_the_right_branch steps: - name: Cancel Previous Runs - uses: ambimax/action-cancel-previous-runs@v1 + uses: ambimax/action-cancel-previous-runs@v2 - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Rust uses: actions-rs/toolchain@v1 with: @@ -68,7 +68,7 @@ jobs: needs: check_pr_is_on_the_right_branch steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Rustup uses: crazy-max/ghaction-chocolatey@v2 with: @@ -92,7 +92,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Install dependencies (awxkit) - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: '3.x' - name: Install python requirements (awxkit) @@ -124,7 +124,7 @@ jobs: - test_linux_x86_64 steps: - name: Install dependencies (awxkit) - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: '3.x' - name: Install python requirements (awxkit) @@ -157,7 +157,7 @@ jobs: runs-on: "windows-2019" steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Rustup uses: crazy-max/ghaction-chocolatey@v2 with: From 1dcbeac990740cca9cca637d539f02aadb46b677 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 16 May 2023 12:11:11 +0200 Subject: [PATCH 113/303] ci: replacing deprecated action by concurrency config --- .github/workflows/build-and-test.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index c52af3d5..e4210dbf 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -25,6 +25,10 @@ env: AWX_PUBLIC_USER: "scaphandre-public" AWX_HOST: "https://cd.hubblo.org" +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: check_pr_is_on_the_right_branch: name: Check PR is open on the right branch @@ -41,8 +45,6 @@ jobs: runs-on: ubuntu-latest needs: check_pr_is_on_the_right_branch steps: - - name: Cancel Previous Runs - uses: ambimax/action-cancel-previous-runs@v2 - name: Checkout repository uses: actions/checkout@v3 - name: Install Rust From 7ffc79db7bba83bd8fa2db07ce9081c847a3f9ee Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 16 May 2023 12:15:11 +0200 Subject: [PATCH 114/303] ci: fix deprecated cargo/rust actions --- .github/workflows/build-and-test.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index e4210dbf..de67c92b 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -48,19 +48,19 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - name: Install Rust - uses: actions-rs/toolchain@v1 + uses: actions-rs/toolchain@v2 with: toolchain: stable profile: minimal override: true components: rustfmt - name: Check formatting - uses: actions-rs/cargo@v1 + uses: actions-rs/cargo@v2 with: command: fmt args: --all -- --check - name: Clippy Check - uses: actions-rs/cargo@v1 + uses: actions-rs/cargo@v2 with: command: clippy args: -- -A clippy::upper_case_acronyms -D warnings @@ -79,12 +79,12 @@ jobs: run: | rustup toolchain install stable-x86_64-pc-windows-msvc - name: Check formatting - uses: actions-rs/cargo@v1 + uses: actions-rs/cargo@v2 with: command: fmt args: --all -- --check - name: Clippy Check - uses: actions-rs/cargo@v1 + uses: actions-rs/cargo@v2 with: command : clippy args: --no-default-features --features "prometheus json riemann" From b86e466ad223d9ae822c6a8703d88fb036675176 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 16 May 2023 12:25:09 +0200 Subject: [PATCH 115/303] ci: fix deprecated cargo/rust actions --- .github/workflows/build-and-test.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index de67c92b..06c03855 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -48,19 +48,19 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - name: Install Rust - uses: actions-rs/toolchain@v2 + uses: actions-rs/toolchain@v1 with: toolchain: stable profile: minimal override: true components: rustfmt - name: Check formatting - uses: actions-rs/cargo@v2 + uses: bpetit/action-cargo@v2 with: command: fmt args: --all -- --check - name: Clippy Check - uses: actions-rs/cargo@v2 + uses: bpetit/action-cargo@v2 with: command: clippy args: -- -A clippy::upper_case_acronyms -D warnings @@ -79,12 +79,12 @@ jobs: run: | rustup toolchain install stable-x86_64-pc-windows-msvc - name: Check formatting - uses: actions-rs/cargo@v2 + uses: bpetit/action-cargo@v2 with: command: fmt args: --all -- --check - name: Clippy Check - uses: actions-rs/cargo@v2 + uses: bpetit/action-cargo@v2 with: command : clippy args: --no-default-features --features "prometheus json riemann" From ec86241a123dafc073bf56b4fa17971cf72a0005 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 16 May 2023 14:53:37 +0200 Subject: [PATCH 116/303] ci: fix of rust cargo deprecated version --- .github/workflows/build-and-test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 06c03855..2069ee01 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -55,12 +55,12 @@ jobs: override: true components: rustfmt - name: Check formatting - uses: bpetit/action-cargo@v2 + uses: rust-cargo-action@v2 with: command: fmt args: --all -- --check - name: Clippy Check - uses: bpetit/action-cargo@v2 + uses: rust-cargo-action@v2 with: command: clippy args: -- -A clippy::upper_case_acronyms -D warnings @@ -79,12 +79,12 @@ jobs: run: | rustup toolchain install stable-x86_64-pc-windows-msvc - name: Check formatting - uses: bpetit/action-cargo@v2 + uses: rust-cargo-action@v2 with: command: fmt args: --all -- --check - name: Clippy Check - uses: bpetit/action-cargo@v2 + uses: rust-cargo-action@v2 with: command : clippy args: --no-default-features --features "prometheus json riemann" From 45c200291a0717e4b5d6756bd8b0f5cde80df435 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 16 May 2023 14:54:46 +0200 Subject: [PATCH 117/303] ci: fix of rust cargo deprecated version --- .github/workflows/build-and-test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 2069ee01..3cb58ba1 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -55,12 +55,12 @@ jobs: override: true components: rustfmt - name: Check formatting - uses: rust-cargo-action@v2 + uses: bpetit/rust-cargo-action@v2 with: command: fmt args: --all -- --check - name: Clippy Check - uses: rust-cargo-action@v2 + uses: bpetit/rust-cargo-action@v2 with: command: clippy args: -- -A clippy::upper_case_acronyms -D warnings @@ -79,12 +79,12 @@ jobs: run: | rustup toolchain install stable-x86_64-pc-windows-msvc - name: Check formatting - uses: rust-cargo-action@v2 + uses: bpetit/rust-cargo-action@v2 with: command: fmt args: --all -- --check - name: Clippy Check - uses: rust-cargo-action@v2 + uses: bpetit/rust-cargo-action@v2 with: command : clippy args: --no-default-features --features "prometheus json riemann" From 575ded67199d36c3abf1922436e5894d1194f9eb Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 16 May 2023 14:56:32 +0200 Subject: [PATCH 118/303] ci: fix of rust cargo deprecated version --- .github/workflows/build-and-test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 3cb58ba1..ef275892 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -55,12 +55,12 @@ jobs: override: true components: rustfmt - name: Check formatting - uses: bpetit/rust-cargo-action@v2 + uses: bpetit/cargo-action@v2.0.1 with: command: fmt args: --all -- --check - name: Clippy Check - uses: bpetit/rust-cargo-action@v2 + uses: bpetit/cargo-action@v2.0.1 with: command: clippy args: -- -A clippy::upper_case_acronyms -D warnings @@ -79,12 +79,12 @@ jobs: run: | rustup toolchain install stable-x86_64-pc-windows-msvc - name: Check formatting - uses: bpetit/rust-cargo-action@v2 + uses: bpetit/cargo-action@v2.0.1 with: command: fmt args: --all -- --check - name: Clippy Check - uses: bpetit/rust-cargo-action@v2 + uses: bpetit/cargo-action@v2.0.1 with: command : clippy args: --no-default-features --features "prometheus json riemann" From c8b48f7bdbcb17bb25859e49641585f1093b2f11 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 16 May 2023 14:58:30 +0200 Subject: [PATCH 119/303] ci: fix of rust cargo deprecated version --- .github/workflows/build-and-test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index ef275892..4d5fa699 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -55,12 +55,12 @@ jobs: override: true components: rustfmt - name: Check formatting - uses: bpetit/cargo-action@v2.0.1 + uses: bpetit/action-cargo@v2.0.1 with: command: fmt args: --all -- --check - name: Clippy Check - uses: bpetit/cargo-action@v2.0.1 + uses: bpetit/action-cargo@v2.0.1 with: command: clippy args: -- -A clippy::upper_case_acronyms -D warnings @@ -79,12 +79,12 @@ jobs: run: | rustup toolchain install stable-x86_64-pc-windows-msvc - name: Check formatting - uses: bpetit/cargo-action@v2.0.1 + uses: bpetit/action-cargo@v2.0.1 with: command: fmt args: --all -- --check - name: Clippy Check - uses: bpetit/cargo-action@v2.0.1 + uses: bpetit/action-cargo@v2.0.1 with: command : clippy args: --no-default-features --features "prometheus json riemann" From dc0f0f085ab6eb9157336b9431ff63afbcefa963 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 16 May 2023 15:08:28 +0200 Subject: [PATCH 120/303] ci: fix of rust cargo deprecated version --- .github/workflows/build-and-test.yml | 2 +- .github/workflows/codesee-arch-diagram.yml | 2 +- .github/workflows/python_build.yml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 4d5fa699..ff6a1ba8 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -48,7 +48,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - name: Install Rust - uses: actions-rs/toolchain@v1 + uses: bpetit/action-toolchain@v2.0.0 with: toolchain: stable profile: minimal diff --git a/.github/workflows/codesee-arch-diagram.yml b/.github/workflows/codesee-arch-diagram.yml index 5b345f05..904763c8 100644 --- a/.github/workflows/codesee-arch-diagram.yml +++ b/.github/workflows/codesee-arch-diagram.yml @@ -56,7 +56,7 @@ jobs: # We need the rust toolchain because it uses rustc and cargo to inspect the package - name: Configure Rust 1.x stable - uses: actions-rs/toolchain@v1 + uses: bpetit/action-toolchain@v2.0.0 if: ${{ fromJSON(steps.detect-languages.outputs.languages).rust }} with: toolchain: stable diff --git a/.github/workflows/python_build.yml b/.github/workflows/python_build.yml index 1a93f310..cdd520f4 100644 --- a/.github/workflows/python_build.yml +++ b/.github/workflows/python_build.yml @@ -24,7 +24,7 @@ jobs: pip install black isort mypy make check-python - name: Install minimal stable with clippy and rustfmt - uses: actions-rs/toolchain@v1 + uses: bpetit/action-toolchain@v2.0.0 with: profile: default toolchain: stable @@ -39,7 +39,7 @@ jobs: - uses: actions/checkout@v2 - name: Install latest nightly - uses: actions-rs/toolchain@v1 + uses: bpetit/action-toolchain@v2.0.0 with: toolchain: stable override: true From fddf1b4f6e6919f9894fdc5f075345f7d25e3de4 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 16 May 2023 15:40:36 +0200 Subject: [PATCH 121/303] chore: extra info message if containers feature is not built in json exporter --- src/exporters/json.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/exporters/json.rs b/src/exporters/json.rs index 8d514efd..d6548e90 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -345,11 +345,13 @@ impl JsonExporter { .get_processes_filtered_by_container_name(regex_filter) } - #[cfg(not(feature = "containers"))] - self.metric_generator - .topology - .proc_tracker - .get_top_consumers(max_top) + #[cfg(not(feature = "containers"))]{ + info!("Container regex filter is used but containers feature is not present. Returning top consumers based on max-top-consumers."); + self.metric_generator + .topology + .proc_tracker + .get_top_consumers(max_top) + } } else { self.metric_generator .topology From 399252d01d41d6c57bb456d7f6e97812dc72989a Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 16 May 2023 15:42:10 +0200 Subject: [PATCH 122/303] style: cargo fmt --- src/exporters/json.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/exporters/json.rs b/src/exporters/json.rs index d6548e90..da6f4e74 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -345,7 +345,8 @@ impl JsonExporter { .get_processes_filtered_by_container_name(regex_filter) } - #[cfg(not(feature = "containers"))]{ + #[cfg(not(feature = "containers"))] + { info!("Container regex filter is used but containers feature is not present. Returning top consumers based on max-top-consumers."); self.metric_generator .topology From 20ddacf8fc11f8ad30e60332841d1f1a3aaedfb5 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 16 May 2023 17:35:54 +0200 Subject: [PATCH 123/303] fix: warpten code was disabled after refacto --- src/exporters/warpten.rs | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/src/exporters/warpten.rs b/src/exporters/warpten.rs index 71d0ea29..d042ce0f 100644 --- a/src/exporters/warpten.rs +++ b/src/exporters/warpten.rs @@ -106,38 +106,28 @@ impl Warp10Exporter { self.metric_generator.gen_all_metrics(); - let process_data: Vec = vec![]; + let mut process_data: Vec = vec![]; for metric in self.metric_generator.pop_metrics() { let mut labels = vec![]; - for (k, v) in metric.attributes { + for (k, v) in &metric.attributes { labels.push(warp10::Label::new(&k, &v)); } - //if !metric.name.starts_with("scaph_domain") && !metric.name.starts_with("scaph_socket") { - // process_data.push(warp10::Data::new( - // time::OffsetDateTime::now_utc(), - // None, - // metric.name, - // labels, - // warp10::Value::String(metric.metric_value.to_string()), - // )); - //} + process_data.push(warp10::Data::new( + time::OffsetDateTime::now_utc(), + None, + metric.name, + labels, + warp10::Value::String(metric.metric_value.to_string().replace("`", "")), + )); } let res = writer.post_sync(process_data)?; let results = vec![res]; - //let mut process_data = vec![warp10::Data::new( - // time::OffsetDateTime::now_utc(), - // None, - // String::from("scaph_self_version"), - // labels.clone(), - // warp10::Value::Double(scaphandre_version.parse::().unwrap()), - //)]; - //if let Some(token) = read_token { //let reader = client.get_reader(token.to_owned()); //let parameters = warp10::data::ParameterSet::new( From 4ca9cd02753753f6e1c7a1dc21d1e554b05d905e Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 16 May 2023 17:39:21 +0200 Subject: [PATCH 124/303] style: clippy and fmt --- src/exporters/warpten.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/exporters/warpten.rs b/src/exporters/warpten.rs index d042ce0f..5de93371 100644 --- a/src/exporters/warpten.rs +++ b/src/exporters/warpten.rs @@ -112,7 +112,7 @@ impl Warp10Exporter { let mut labels = vec![]; for (k, v) in &metric.attributes { - labels.push(warp10::Label::new(&k, &v)); + labels.push(warp10::Label::new(k, v)); } process_data.push(warp10::Data::new( @@ -120,7 +120,7 @@ impl Warp10Exporter { None, metric.name, labels, - warp10::Value::String(metric.metric_value.to_string().replace("`", "")), + warp10::Value::String(metric.metric_value.to_string().replace('`', "")), )); } From 011cdfd4b336b6aa43135922b59fa649c0917168 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 17 May 2023 10:17:01 +0200 Subject: [PATCH 125/303] fix: info/warn messages break out_writer in jsonexporter --- src/exporters/json.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/exporters/json.rs b/src/exporters/json.rs index da6f4e74..b8dee3ad 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -347,7 +347,6 @@ impl JsonExporter { #[cfg(not(feature = "containers"))] { - info!("Container regex filter is used but containers feature is not present. Returning top consumers based on max-top-consumers."); self.metric_generator .topology .proc_tracker From 38d4aaabaf37a1c25dd7d6728640761ce7b1ffe0 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 17 May 2023 10:22:00 +0200 Subject: [PATCH 126/303] ci: enabling windows tests for warp10 --- .github/workflows/build-and-test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index ff6a1ba8..b8b2e1bc 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -87,7 +87,7 @@ jobs: uses: bpetit/action-cargo@v2.0.1 with: command : clippy - args: --no-default-features --features "prometheus json riemann" + args: --no-default-features --features "prometheus json riemann warpten" test_linux_x86_64: name: Test on GNU/Linux x86_64 (Bare metal worker) @@ -169,7 +169,7 @@ jobs: rustup toolchain install stable-x86_64-pc-windows-msvc - name: Build (debug mode) run: | - cargo test --no-default-features --features "prometheus json riemann" + cargo test --no-default-features --features "prometheus json riemann warpten" - name: Build (debug mode) run: | - cargo build --no-default-features --features "prometheus json riemann" + cargo build --no-default-features --features "prometheus json riemann warpten" From c55556c943feeda331fb27999cb00bc89dbbf71a Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 17 May 2023 19:51:56 +0200 Subject: [PATCH 127/303] chore: first working push with dummy metric --- src/exporters/prometheuspush.rs | 101 ++++++++++++++++---------------- src/main.rs | 9 +++ 2 files changed, 58 insertions(+), 52 deletions(-) diff --git a/src/exporters/prometheuspush.rs b/src/exporters/prometheuspush.rs index 06bd3ec2..654d37a7 100644 --- a/src/exporters/prometheuspush.rs +++ b/src/exporters/prometheuspush.rs @@ -3,48 +3,79 @@ //! `PrometheusPushExporter` implementation, push/send metrics to //! a [Prometheus](https://prometheus.io/) pushgateway. //! -use clap::builder::TypedValueParser; use isahc::{prelude::*, Request}; use std::time::Duration; use crate::exporters::{Exporter}; -use crate::sensors::{Sensor}; -use clap::{ArgMatches, Arg}; +use crate::sensors::{Sensor, Topology}; use chrono::Utc; use std::thread; +use super::utils::get_hostname; pub struct PrometheusPushExporter { - sensor: Box, + topo: Topology, + hostname: String, + args: ExporterArgs +} + +/// Hold the arguments for a PrometheusExporter. +#[derive(clap::Args, Debug)] +pub struct ExporterArgs { + /// IP address (v4 or v6) of the metrics endpoint for Prometheus + #[arg(short = 'H', long = "host", default_value_t = String::from("localhost"))] + pub host: String, + + /// TCP port of the metrics endpoint for Prometheus + #[arg(short, long, default_value_t = 9091)] + pub port: u16, + + #[arg(long, default_value_t = String::from("metrics"))] + pub suffix: String, + + #[arg(short = 'S', long, default_value_t = String::from("http"))] + pub scheme: String, + + #[arg(short, long, default_value_t = 5)] + pub step: u64, + + /// Apply labels to metrics of processes that look like a Qemu/KVM virtual machine + #[arg(long)] + pub qemu: bool, + + /// Apply labels to metrics of processes running as containers + #[arg(long)] + pub containers: bool, } impl PrometheusPushExporter { - pub fn new(sensor: Box) -> PrometheusPushExporter { - PrometheusPushExporter { sensor } + pub fn new(sensor: &dyn Sensor, args: ExporterArgs) -> PrometheusPushExporter { + let topo = sensor + .get_topology() + .expect("sensor topology should be available"); + let hostname = get_hostname(); + PrometheusPushExporter { topo, hostname, args } } } impl Exporter for PrometheusPushExporter { - fn run(&mut self, parameters: ArgMatches) { + fn run(&mut self) { info!( "{}: Starting Prometheus Push exporter", Utc::now().format("%Y-%m-%dT%H:%M:%S") ); - let step: String = *parameters.get_one("step").unwrap(); - let host: String = *parameters.get_one("host").unwrap(); - let scheme: String = *parameters.get_one("scheme").unwrap(); - let port: String = *parameters.get_one("port").unwrap(); - let route: String = *parameters.get_one("route").unwrap(); - let uri = format!("{scheme}://{host}:{port}/{route}"); + let uri = format!("{}://{}:{}/{}/job/test", self.args.scheme, self.args.host, self.args.port, self.args.suffix); + // add job and per metric suffix ? loop { - let body = "# HELP mymetric this is my metric\n# TYPE mymetric gauge\nmymetric 50"; + let body = "# HELP mymetric this is my metric\n# TYPE mymetric gauge\nmymetric 50\n"; if let Ok(request) = Request::post(uri.clone()) .header("Content-Type", "text/plain") .timeout(Duration::from_secs(5)) .body(body) { match request.send() { - Ok(response) => { + Ok(mut response) => { warn!("Got {:?}", response); + warn!("Response Text {:?}", response.text()); } Err(err) => { warn!("Got error : {:?}", err) @@ -52,45 +83,11 @@ impl Exporter for PrometheusPushExporter { } } - thread::sleep(Duration::new(step.parse::().unwrap(), 0)); + thread::sleep(Duration::new(self.args.step, 0)); } } - /// Returns options understood by the exporter. - fn get_options() -> Vec { - let mut options = Vec::new(); - let arg = Arg::new("host") - .default_value("localhost") - .help("PushGateway's host FQDN or IP address.") - .long("host") - .short('H') - .required(false) // send to localhost if none - .action(clap::ArgAction::Set); - options.push(arg); - let arg = Arg::new("port") - .default_value("9091") - .help("PushGateway's TCP port number.") - .long("port") - .short('p') - .required(false) // send to localhost if none - .action(clap::ArgAction::Set); - options.push(arg); - let arg = Arg::new("scheme") - .default_value("https") - .help("http or https.") - .long("scheme") - .short('s') - .required(false) // send to localhost if none - .action(clap::ArgAction::Set); - options.push(arg); - let arg = Arg::new("step") - .default_value("20") - .help("Time between two push, in seconds.") - .long("step") - .short('S') - .required(false) // send to localhost if none - .action(clap::ArgAction::Set); - options.push(arg); - options + fn kind(&self) -> &str { + "prometheuspush" } } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 690f0398..3b14e825 100644 --- a/src/main.rs +++ b/src/main.rs @@ -79,6 +79,11 @@ enum ExporterChoice { /// Expose the metrics to a Warp10 host, through HTTP #[cfg(feature = "warpten")] Warpten(exporters::warpten::ExporterArgs), + + /// Push metrics to Prometheus Push Gateway + #[cfg(feature = "prometheuspush")] + PrometheusPush(exporters::prometheuspush::ExporterArgs) + } fn main() { @@ -119,6 +124,10 @@ fn build_exporter(choice: ExporterChoice, sensor: &dyn Sensor) -> Box { Box::new(exporters::warpten::Warp10Exporter::new(sensor, args)) } + #[cfg(feature = "prometheuspush")] + ExporterChoice::PrometheusPush(args) => { + Box::new(exporters::prometheuspush::PrometheusPushExporter::new(sensor, args)) + } } // Note that invalid choices are automatically turned into errors by `parse()` before the Cli is populated, // that's why they don't appear in this function. From 5b05252a0aaa45ecb5d52b559cce898baf193eed Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 17 May 2023 19:52:18 +0200 Subject: [PATCH 128/303] style: fmt --- src/exporters/mod.rs | 4 +-- src/exporters/prometheuspush.rs | 46 +++++++++++++++++++-------------- src/main.rs | 9 +++---- 3 files changed, 33 insertions(+), 26 deletions(-) diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index 6f2ef2ac..2d03b6d0 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -4,10 +4,10 @@ //! needed to implement an exporter. #[cfg(feature = "json")] pub mod json; -#[cfg(feature = "prometheuspush")] -pub mod prometheuspush; #[cfg(feature = "prometheus")] pub mod prometheus; +#[cfg(feature = "prometheuspush")] +pub mod prometheuspush; #[cfg(target_os = "linux")] pub mod qemu; #[cfg(feature = "riemann")] diff --git a/src/exporters/prometheuspush.rs b/src/exporters/prometheuspush.rs index 654d37a7..13b845ec 100644 --- a/src/exporters/prometheuspush.rs +++ b/src/exporters/prometheuspush.rs @@ -2,19 +2,19 @@ //! //! `PrometheusPushExporter` implementation, push/send metrics to //! a [Prometheus](https://prometheus.io/) pushgateway. -//! -use isahc::{prelude::*, Request}; -use std::time::Duration; -use crate::exporters::{Exporter}; +//! +use super::utils::get_hostname; +use crate::exporters::Exporter; use crate::sensors::{Sensor, Topology}; use chrono::Utc; +use isahc::{prelude::*, Request}; use std::thread; -use super::utils::get_hostname; +use std::time::Duration; pub struct PrometheusPushExporter { topo: Topology, hostname: String, - args: ExporterArgs + args: ExporterArgs, } /// Hold the arguments for a PrometheusExporter. @@ -52,7 +52,11 @@ impl PrometheusPushExporter { .get_topology() .expect("sensor topology should be available"); let hostname = get_hostname(); - PrometheusPushExporter { topo, hostname, args } + PrometheusPushExporter { + topo, + hostname, + args, + } } } @@ -63,25 +67,29 @@ impl Exporter for PrometheusPushExporter { Utc::now().format("%Y-%m-%dT%H:%M:%S") ); - let uri = format!("{}://{}:{}/{}/job/test", self.args.scheme, self.args.host, self.args.port, self.args.suffix); - // add job and per metric suffix ? + let uri = format!( + "{}://{}:{}/{}/job/test", + self.args.scheme, self.args.host, self.args.port, self.args.suffix + ); + // add job and per metric suffix ? loop { let body = "# HELP mymetric this is my metric\n# TYPE mymetric gauge\nmymetric 50\n"; if let Ok(request) = Request::post(uri.clone()) .header("Content-Type", "text/plain") .timeout(Duration::from_secs(5)) - .body(body) { - match request.send() { - Ok(mut response) => { - warn!("Got {:?}", response); - warn!("Response Text {:?}", response.text()); - } - Err(err) => { - warn!("Got error : {:?}", err) - } + .body(body) + { + match request.send() { + Ok(mut response) => { + warn!("Got {:?}", response); + warn!("Response Text {:?}", response.text()); + } + Err(err) => { + warn!("Got error : {:?}", err) } } + } thread::sleep(Duration::new(self.args.step, 0)); } @@ -90,4 +98,4 @@ impl Exporter for PrometheusPushExporter { fn kind(&self) -> &str { "prometheuspush" } -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index 3b14e825..d01ce7cb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -82,8 +82,7 @@ enum ExporterChoice { /// Push metrics to Prometheus Push Gateway #[cfg(feature = "prometheuspush")] - PrometheusPush(exporters::prometheuspush::ExporterArgs) - + PrometheusPush(exporters::prometheuspush::ExporterArgs), } fn main() { @@ -125,9 +124,9 @@ fn build_exporter(choice: ExporterChoice, sensor: &dyn Sensor) -> Box { - Box::new(exporters::prometheuspush::PrometheusPushExporter::new(sensor, args)) - } + ExporterChoice::PrometheusPush(args) => Box::new( + exporters::prometheuspush::PrometheusPushExporter::new(sensor, args), + ), } // Note that invalid choices are automatically turned into errors by `parse()` before the Cli is populated, // that's why they don't appear in this function. From a75c3026970475237fe5d957162375d3a89fadcc Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Thu, 18 May 2023 12:07:36 +0200 Subject: [PATCH 129/303] fix: backslashes in cmdline labels made pushgateway refuse metrics --- src/exporters/prometheus.rs | 17 +---------- src/exporters/prometheuspush.rs | 53 +++++++++++++++++++++++++++++---- src/exporters/utils.rs | 18 +++++++++++ 3 files changed, 67 insertions(+), 21 deletions(-) diff --git a/src/exporters/prometheus.rs b/src/exporters/prometheus.rs index 33403a80..05065cc2 100644 --- a/src/exporters/prometheus.rs +++ b/src/exporters/prometheus.rs @@ -133,21 +133,6 @@ async fn run_server( let _ = tx.send(()); } -/// Returns a well formatted Prometheus metric string. -fn format_metric(key: &str, value: &str, labels: Option<&HashMap>) -> String { - let mut result = key.to_string(); - if let Some(labels) = labels { - result.push('{'); - for (k, v) in labels.iter() { - let _ = write!(result, "{}=\"{}\",", k, v.replace('\"', "_")); - } - result.remove(result.len() - 1); - result.push('}'); - } - let _ = writeln!(result, " {value}"); - result -} - /// Adds lines related to a metric in the body (String) of response. fn push_metric( mut body: String, @@ -228,7 +213,7 @@ async fn show_metrics( msg.description.clone(), msg.metric_type.clone(), msg.name.clone(), - format_metric(&msg.name, &value, attributes), + utils::format_prometheus_metric(&msg.name, &value, attributes), should_i_add_help, ); } diff --git a/src/exporters/prometheuspush.rs b/src/exporters/prometheuspush.rs index 13b845ec..dbc2f82e 100644 --- a/src/exporters/prometheuspush.rs +++ b/src/exporters/prometheuspush.rs @@ -3,13 +3,14 @@ //! `PrometheusPushExporter` implementation, push/send metrics to //! a [Prometheus](https://prometheus.io/) pushgateway. //! -use super::utils::get_hostname; -use crate::exporters::Exporter; +use super::utils::{get_hostname, format_prometheus_metric}; +use crate::exporters::{Exporter, MetricGenerator}; use crate::sensors::{Sensor, Topology}; use chrono::Utc; use isahc::{prelude::*, Request}; use std::thread; use std::time::Duration; +use std::fmt::Write; pub struct PrometheusPushExporter { topo: Topology, @@ -44,6 +45,10 @@ pub struct ExporterArgs { /// Apply labels to metrics of processes running as containers #[arg(long)] pub containers: bool, + + /// Job name to apply as a label for pushed metrics + #[arg(short, long, default_value_t = String::from("scaphandre"))] + pub job: String } impl PrometheusPushExporter { @@ -68,13 +73,51 @@ impl Exporter for PrometheusPushExporter { ); let uri = format!( - "{}://{}:{}/{}/job/test", - self.args.scheme, self.args.host, self.args.port, self.args.suffix + "{}://{}:{}/{}/job/{}", + self.args.scheme, self.args.host, self.args.port, self.args.suffix, self.args.job + ); + + let mut metric_generator = MetricGenerator::new( + self.topo.clone(), + self.hostname.clone(), + self.args.qemu, + self.args.containers ); // add job and per metric suffix ? loop { - let body = "# HELP mymetric this is my metric\n# TYPE mymetric gauge\nmymetric 50\n"; + metric_generator.topology.refresh(); + metric_generator.gen_all_metrics(); + let mut body = String::from("# HELP mymetric this is my metric\n# TYPE mymetric gauge\nmymetric 50\n"); + let mut metrics_pushed: Vec = vec![]; + let mut counter = 0; + for m in metric_generator.pop_metrics() { + let mut should_i_add_help = true; + + if metrics_pushed.contains(&m.name) { + should_i_add_help = false; + } else { + metrics_pushed.insert(0, m.name.clone()); + } + + if should_i_add_help { + let _ = write!(body, "# HELP {} {}", m.name, m.description); + warn!("line {} : {}", counter, format!("# HELP {} {}", m.name, m.description)); + counter = counter + 1; + let _ = write!(body, "\n# TYPE {} {}\n", m.name, m.metric_type); + warn!("line {} : {}", counter, format!("\n# TYPE {} {}\n", m.name, m.metric_type)); + counter = counter + 1; + } + let mut attributes = None; + if !m.attributes.is_empty() { + attributes = Some(&m.attributes); + } + warn!("line {} : {}", counter, format_prometheus_metric(&m.name, &m.metric_value.to_string(), attributes)); + counter = counter + 1; + let _ = write!(body, "{}", format_prometheus_metric(&m.name, &m.metric_value.to_string(), attributes)); + } + //warn!("body: {}", body); + // TODO: fix tcp broken pipe on push gateway side if let Ok(request) = Request::post(uri.clone()) .header("Content-Type", "text/plain") .timeout(Duration::from_secs(5)) diff --git a/src/exporters/utils.rs b/src/exporters/utils.rs index 26cce7bc..af971996 100644 --- a/src/exporters/utils.rs +++ b/src/exporters/utils.rs @@ -7,6 +7,8 @@ use { docker_sync::Docker, k8s_sync::{errors::KubernetesError, kubernetes::Kubernetes}, }; +use std::fmt::Write; +use std::collections::HashMap; /// Default ipv4/ipv6 address to expose the service is any pub const DEFAULT_IP_ADDRESS: &str = "::"; @@ -21,6 +23,22 @@ pub fn filter_cmdline(cmdline: &str) -> String { cmdline.replace('\"', "\\\"").replace('\n', "") } +/// Returns a well formatted Prometheus metric string. +pub fn format_prometheus_metric(key: &str, value: &str, labels: Option<&HashMap>) -> String { + let mut result = key.to_string(); + if let Some(labels) = labels { + result.push('{'); + for (k, v) in labels.iter() { + let _ = write!(result, "{}=\"{}\",", k, v.replace('\"', "_").replace("\\", "")); + } + result.remove(result.len() - 1); + result.push('}'); + } + let _ = writeln!(result, " {value}"); + result +} + + /// Returns an Option containing the VM name of a qemu process. /// /// Then VM name is extracted from the command line. From ac8c6669c0244537017e5e56a468b27b0e82fdcc Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Thu, 18 May 2023 12:15:12 +0200 Subject: [PATCH 130/303] style: clippy --- src/exporters/prometheuspush.rs | 44 +++++++++++++++++++++++---------- src/exporters/utils.rs | 18 ++++++++++---- 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/src/exporters/prometheuspush.rs b/src/exporters/prometheuspush.rs index dbc2f82e..c8fd2b7f 100644 --- a/src/exporters/prometheuspush.rs +++ b/src/exporters/prometheuspush.rs @@ -3,14 +3,14 @@ //! `PrometheusPushExporter` implementation, push/send metrics to //! a [Prometheus](https://prometheus.io/) pushgateway. //! -use super::utils::{get_hostname, format_prometheus_metric}; +use super::utils::{format_prometheus_metric, get_hostname}; use crate::exporters::{Exporter, MetricGenerator}; use crate::sensors::{Sensor, Topology}; use chrono::Utc; use isahc::{prelude::*, Request}; +use std::fmt::Write; use std::thread; use std::time::Duration; -use std::fmt::Write; pub struct PrometheusPushExporter { topo: Topology, @@ -48,7 +48,7 @@ pub struct ExporterArgs { /// Job name to apply as a label for pushed metrics #[arg(short, long, default_value_t = String::from("scaphandre"))] - pub job: String + pub job: String, } impl PrometheusPushExporter { @@ -81,16 +81,18 @@ impl Exporter for PrometheusPushExporter { self.topo.clone(), self.hostname.clone(), self.args.qemu, - self.args.containers + self.args.containers, ); // add job and per metric suffix ? loop { metric_generator.topology.refresh(); metric_generator.gen_all_metrics(); - let mut body = String::from("# HELP mymetric this is my metric\n# TYPE mymetric gauge\nmymetric 50\n"); + let mut body = String::from( + "# HELP mymetric this is my metric\n# TYPE mymetric gauge\nmymetric 50\n", + ); let mut metrics_pushed: Vec = vec![]; - let mut counter = 0; + //let mut counter = 0; for m in metric_generator.pop_metrics() { let mut should_i_add_help = true; @@ -102,19 +104,35 @@ impl Exporter for PrometheusPushExporter { if should_i_add_help { let _ = write!(body, "# HELP {} {}", m.name, m.description); - warn!("line {} : {}", counter, format!("# HELP {} {}", m.name, m.description)); - counter = counter + 1; + //warn!( + // "line {} : {}", + // counter, + // format!("# HELP {} {}", m.name, m.description) + //); + //counter = counter + 1; let _ = write!(body, "\n# TYPE {} {}\n", m.name, m.metric_type); - warn!("line {} : {}", counter, format!("\n# TYPE {} {}\n", m.name, m.metric_type)); - counter = counter + 1; + //warn!( + // "line {} : {}", + // counter, + // format!("\n# TYPE {} {}\n", m.name, m.metric_type) + //); + //counter = counter + 1; } let mut attributes = None; if !m.attributes.is_empty() { attributes = Some(&m.attributes); } - warn!("line {} : {}", counter, format_prometheus_metric(&m.name, &m.metric_value.to_string(), attributes)); - counter = counter + 1; - let _ = write!(body, "{}", format_prometheus_metric(&m.name, &m.metric_value.to_string(), attributes)); + //warn!( + // "line {} : {}", + // counter, + // format_prometheus_metric(&m.name, &m.metric_value.to_string(), attributes) + //); + //counter = counter + 1; + let _ = write!( + body, + "{}", + format_prometheus_metric(&m.name, &m.metric_value.to_string(), attributes) + ); } //warn!("body: {}", body); // TODO: fix tcp broken pipe on push gateway side diff --git a/src/exporters/utils.rs b/src/exporters/utils.rs index af971996..2c0e4c8a 100644 --- a/src/exporters/utils.rs +++ b/src/exporters/utils.rs @@ -2,13 +2,13 @@ //! //! The utils module provides common functions used by the exporters. use clap::crate_version; +use std::collections::HashMap; +use std::fmt::Write; #[cfg(feature = "containers")] use { docker_sync::Docker, k8s_sync::{errors::KubernetesError, kubernetes::Kubernetes}, }; -use std::fmt::Write; -use std::collections::HashMap; /// Default ipv4/ipv6 address to expose the service is any pub const DEFAULT_IP_ADDRESS: &str = "::"; @@ -24,12 +24,21 @@ pub fn filter_cmdline(cmdline: &str) -> String { } /// Returns a well formatted Prometheus metric string. -pub fn format_prometheus_metric(key: &str, value: &str, labels: Option<&HashMap>) -> String { +pub fn format_prometheus_metric( + key: &str, + value: &str, + labels: Option<&HashMap>, +) -> String { let mut result = key.to_string(); if let Some(labels) = labels { result.push('{'); for (k, v) in labels.iter() { - let _ = write!(result, "{}=\"{}\",", k, v.replace('\"', "_").replace("\\", "")); + let _ = write!( + result, + "{}=\"{}\",", + k, + v.replace('\"', "_").replace('\\', "") + ); } result.remove(result.len() - 1); result.push('}'); @@ -38,7 +47,6 @@ pub fn format_prometheus_metric(key: &str, value: &str, labels: Option<&HashMap< result } - /// Returns an Option containing the VM name of a qemu process. /// /// Then VM name is extracted from the command line. From 1e7c6160d800012fa43815c89e2e2b7e3eaea739 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Thu, 18 May 2023 17:00:05 +0200 Subject: [PATCH 131/303] clean: removing comments --- src/exporters/prometheuspush.rs | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/src/exporters/prometheuspush.rs b/src/exporters/prometheuspush.rs index c8fd2b7f..26147f8d 100644 --- a/src/exporters/prometheuspush.rs +++ b/src/exporters/prometheuspush.rs @@ -83,7 +83,6 @@ impl Exporter for PrometheusPushExporter { self.args.qemu, self.args.containers, ); - // add job and per metric suffix ? loop { metric_generator.topology.refresh(); @@ -104,30 +103,13 @@ impl Exporter for PrometheusPushExporter { if should_i_add_help { let _ = write!(body, "# HELP {} {}", m.name, m.description); - //warn!( - // "line {} : {}", - // counter, - // format!("# HELP {} {}", m.name, m.description) - //); - //counter = counter + 1; let _ = write!(body, "\n# TYPE {} {}\n", m.name, m.metric_type); - //warn!( - // "line {} : {}", - // counter, - // format!("\n# TYPE {} {}\n", m.name, m.metric_type) - //); - //counter = counter + 1; } let mut attributes = None; if !m.attributes.is_empty() { attributes = Some(&m.attributes); } - //warn!( - // "line {} : {}", - // counter, - // format_prometheus_metric(&m.name, &m.metric_value.to_string(), attributes) - //); - //counter = counter + 1; + let _ = write!( body, "{}", @@ -135,7 +117,6 @@ impl Exporter for PrometheusPushExporter { ); } //warn!("body: {}", body); - // TODO: fix tcp broken pipe on push gateway side if let Ok(request) = Request::post(uri.clone()) .header("Content-Type", "text/plain") .timeout(Duration::from_secs(5)) From 4a9870bbe5aa921341fb34a2026f14217d348580 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 22 May 2023 12:43:47 +0200 Subject: [PATCH 132/303] ci: trying to build and test on windows without deactivating features --- .github/workflows/build-and-test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index b8b2e1bc..818f4f59 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -167,9 +167,9 @@ jobs: - name: Install Rust toolchain run: | rustup toolchain install stable-x86_64-pc-windows-msvc - - name: Build (debug mode) + - name: Tests run: | - cargo test --no-default-features --features "prometheus json riemann warpten" + cargo test - name: Build (debug mode) run: | - cargo build --no-default-features --features "prometheus json riemann warpten" + cargo build From e32a21dd2336998d7c03a124270d314220f7fe18 Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Mon, 22 May 2023 17:03:25 +0200 Subject: [PATCH 133/303] Remove broken python bindings --- .github/workflows/python_build.yml | 69 - python/.gitignore | 18 - python/Cargo.lock | 1938 ------------------------ python/Cargo.toml | 25 - python/LICENSE.txt | 201 --- python/Makefile | 78 - python/README.md | 0 python/docs/Makefile | 20 - python/docs/make.bat | 35 - python/docs/source/_static/.gitignore | 4 - python/docs/source/api_reference.rst | 11 - python/docs/source/conf.py | 80 - python/docs/source/index.rst | 19 - python/pyproject.toml | 60 - python/scaphandre/__init__.py | 1 - python/scaphandre/sensors.py | 59 - python/src/lib.rs | 91 -- python/stubs/scaphandre/__init__.pyi | 3 - python/stubs/scaphandre/scaphandre.pyi | 3 - python/tests/test_scaphandre.py | 5 - src/lib.rs | 5 - 21 files changed, 2725 deletions(-) delete mode 100644 .github/workflows/python_build.yml delete mode 100644 python/.gitignore delete mode 100644 python/Cargo.lock delete mode 100644 python/Cargo.toml delete mode 100644 python/LICENSE.txt delete mode 100644 python/Makefile delete mode 100644 python/README.md delete mode 100644 python/docs/Makefile delete mode 100644 python/docs/make.bat delete mode 100644 python/docs/source/_static/.gitignore delete mode 100644 python/docs/source/api_reference.rst delete mode 100644 python/docs/source/conf.py delete mode 100644 python/docs/source/index.rst delete mode 100644 python/pyproject.toml delete mode 100644 python/scaphandre/__init__.py delete mode 100644 python/scaphandre/sensors.py delete mode 100644 python/src/lib.rs delete mode 100644 python/stubs/scaphandre/__init__.pyi delete mode 100644 python/stubs/scaphandre/scaphandre.pyi delete mode 100644 python/tests/test_scaphandre.py diff --git a/.github/workflows/python_build.yml b/.github/workflows/python_build.yml deleted file mode 100644 index cdd520f4..00000000 --- a/.github/workflows/python_build.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: python_build - -on: - push: - branches: [main] - pull_request: - branches: [main] - -defaults: - run: - working-directory: ./python - -jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Setup Python - uses: actions/setup-python@v2 - with: - python-version: 3.7 - - name: Check Python - run: | - pip install black isort mypy - make check-python - - name: Install minimal stable with clippy and rustfmt - uses: bpetit/action-toolchain@v2.0.0 - with: - profile: default - toolchain: stable - override: true - - name: Check Rust - run: make check-rust - - test: - name: Python Build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Install latest nightly - uses: bpetit/action-toolchain@v2.0.0 - with: - toolchain: stable - override: true - components: rustfmt, clippy - - - uses: Swatinem/rust-cache@v1 - - - uses: actions/setup-python@v3 - with: - python-version: "3.7" - - - name: Build and install scaphandre - run: | - pip install virtualenv - virtualenv venv - source venv/bin/activate - make develop - - - name: Run tests - run: | - source venv/bin/activate - make unit-test - - - name: Build Sphinx documentation - run: | - source venv/bin/activate - make build-documentation \ No newline at end of file diff --git a/python/.gitignore b/python/.gitignore deleted file mode 100644 index f2914074..00000000 --- a/python/.gitignore +++ /dev/null @@ -1,18 +0,0 @@ -# venv -venv - -# Byte-compiled / optimized / DLL files -__pycache__/ -/target - -# Unit test / coverage reports -.coverage -.pytest_cache/ - -# mypy -.mypy_cache/ - -# sphinx build directory -docs/build - -*.so diff --git a/python/Cargo.lock b/python/Cargo.lock deleted file mode 100644 index 8a5247a0..00000000 --- a/python/Cargo.lock +++ /dev/null @@ -1,1938 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "aho-corasick" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" -dependencies = [ - "memchr", -] - -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - -[[package]] -name = "async-channel" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" -dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "base-x" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc19a4937b4fbd3fe3379793130e42060d10627a360f2127802b10b87e7baf74" - -[[package]] -name = "base64" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bumpalo" -version = "3.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "bytes" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" - -[[package]] -name = "cache-padded" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" - -[[package]] -name = "castaway" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" - -[[package]] -name = "cc" -version = "1.0.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" - -[[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.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" -dependencies = [ - "libc", - "num-integer", - "num-traits", - "serde", - "time 0.1.44", - "winapi", -] - -[[package]] -name = "clap" -version = "2.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" -dependencies = [ - "ansi_term", - "atty", - "bitflags", - "strsim 0.8.0", - "textwrap", - "unicode-width", - "vec_map", -] - -[[package]] -name = "colored" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" -dependencies = [ - "atty", - "lazy_static", - "winapi", -] - -[[package]] -name = "concurrent-queue" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" -dependencies = [ - "cache-padded", -] - -[[package]] -name = "const_fn" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" - -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" -dependencies = [ - "cfg-if", - "lazy_static", -] - -[[package]] -name = "curl" -version = "0.4.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d855aeef205b43f65a5001e0997d81f8efca7badad4fad7d897aa7f0d0651f" -dependencies = [ - "curl-sys", - "libc", - "openssl-probe", - "openssl-sys", - "schannel", - "socket2", - "winapi", -] - -[[package]] -name = "curl-sys" -version = "0.4.55+curl-7.83.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23734ec77368ec583c2e61dd3f0b0e5c98b93abe6d2a004ca06b91dd7e3e2762" -dependencies = [ - "cc", - "libc", - "libnghttp2-sys", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", - "winapi", -] - -[[package]] -name = "dirs" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "discard" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" - -[[package]] -name = "docker-sync" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c989c4ad66535edd02443e7d7699d3ab530df4523f12f6aeb7888bdc5ab7c32" -dependencies = [ - "http", - "isahc", - "serde", - "serde_derive", - "serde_json", -] - -[[package]] -name = "docopt" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f3f119846c823f9eafcf953a8f6ffb6ed69bf6240883261a7f13b634579a51f" -dependencies = [ - "lazy_static", - "regex", - "serde", - "strsim 0.10.0", -] - -[[package]] -name = "encoding_rs" -version = "0.8.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "env_logger" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" -dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", -] - -[[package]] -name = "event-listener" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" - -[[package]] -name = "fastrand" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" -dependencies = [ - "instant", -] - -[[package]] -name = "flate2" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" -dependencies = [ - "matches", - "percent-encoding", -] - -[[package]] -name = "futures-channel" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" - -[[package]] -name = "futures-io" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" - -[[package]] -name = "futures-lite" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - -[[package]] -name = "futures-sink" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" - -[[package]] -name = "futures-task" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" - -[[package]] -name = "futures-util" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" -dependencies = [ - "futures-core", - "futures-task", - "pin-project-lite", - "pin-utils", -] - -[[package]] -name = "getrandom" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", -] - -[[package]] -name = "h2" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi", -] - -[[package]] -name = "http" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" - -[[package]] -name = "httpdate" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" - -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - -[[package]] -name = "hyper" -version = "0.14.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "idna" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "indexmap" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" -dependencies = [ - "autocfg", - "hashbrown", -] - -[[package]] -name = "indoc" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05a0bd019339e5d968b37855180087b7b9d512c5046fbd244cf8c95687927d6e" - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "isahc" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" -dependencies = [ - "async-channel", - "castaway", - "crossbeam-utils", - "curl", - "curl-sys", - "encoding_rs", - "event-listener", - "futures-lite", - "http", - "log", - "mime", - "once_cell", - "polling", - "serde", - "serde_json", - "slab", - "sluice", - "tracing", - "tracing-futures", - "url", - "waker-fn", -] - -[[package]] -name = "itoa" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" - -[[package]] -name = "js-sys" -version = "0.3.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "k8s-openapi" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f8de9873b904e74b3533f77493731ee26742418077503683db44e1b3c54aa5c" -dependencies = [ - "base64", - "bytes", - "chrono", - "http", - "percent-encoding", - "serde", - "serde-value", - "serde_json", - "url", -] - -[[package]] -name = "k8s-sync" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3c2f8f5cb2611742f8ceb73f23451690ff0d930149eac45fcb63ca86fbd443" -dependencies = [ - "base64", - "chrono", - "dirs", - "http", - "isahc", - "k8s-openapi", - "openssl", - "serde", - "serde_yaml", - "tempfile", - "url", -] - -[[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.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" - -[[package]] -name = "libnghttp2-sys" -version = "0.1.7+1.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ed28aba195b38d5ff02b9170cbff627e336a20925e43b4945390401c5dc93f" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "libz-sys" -version = "1.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e7e15d7610cce1d9752e137625f14e61a28cd45929b6e12e47b50fe154ee2e" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "linked-hash-map" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" - -[[package]] -name = "lock_api" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "loggerv" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60d8de15ae71e760bce7f05447f85f73624fe0d3b1e4c5a63ba5d4cb0748d374" -dependencies = [ - "ansi_term", - "atty", - "log", -] - -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "mime" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" - -[[package]] -name = "miniz_oxide" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" -dependencies = [ - "adler", -] - -[[package]] -name = "mio" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" -dependencies = [ - "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", -] - -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "once_cell" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b10983b38c53aebdf33f542c6275b0f58a238129d00c4ae0e6fb59738d783ca" - -[[package]] -name = "openssl" -version = "0.10.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb81a6430ac911acb25fe5ac8f1d2af1b4ea8a4fdfda0f1ee4292af2e2d8eb0e" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5fd19fb3e0a8191c1e34935718976a3e70c112ab9a24af6d7cadccd9d90bc0" -dependencies = [ - "autocfg", - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "ordered-float" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" -dependencies = [ - "num-traits", -] - -[[package]] -name = "parking" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" - -[[package]] -name = "parking_lot" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-sys", -] - -[[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - -[[package]] -name = "pin-project" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkg-config" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" - -[[package]] -name = "polling" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" -dependencies = [ - "cfg-if", - "libc", - "log", - "wepoll-ffi", - "winapi", -] - -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - -[[package]] -name = "proc-macro2" -version = "1.0.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "procfs" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0941606b9934e2d98a3677759a971756eb821f75764d0e0d26946d08e74d9104" -dependencies = [ - "bitflags", - "byteorder", - "chrono", - "flate2", - "hex", - "lazy_static", - "libc", -] - -[[package]] -name = "protobuf" -version = "2.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf7e6d18738ecd0902d30d1ad232c9125985a3422929b16c65517b38adc14f96" - -[[package]] -name = "pyo3" -version = "0.16.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6302e85060011447471887705bb7838f14aba43fcb06957d823739a496b3dc" -dependencies = [ - "cfg-if", - "indoc", - "libc", - "parking_lot", - "pyo3-build-config", - "pyo3-ffi", - "pyo3-macros", - "unindent", -] - -[[package]] -name = "pyo3-build-config" -version = "0.16.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b65b546c35d8a3b1b2f0ddbac7c6a569d759f357f2b9df884f5d6b719152c8" -dependencies = [ - "once_cell", - "target-lexicon", -] - -[[package]] -name = "pyo3-ffi" -version = "0.16.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c275a07127c1aca33031a563e384ffdd485aee34ef131116fcd58e3430d1742b" -dependencies = [ - "libc", - "pyo3-build-config", -] - -[[package]] -name = "pyo3-macros" -version = "0.16.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284fc4485bfbcc9850a6d661d627783f18d19c2ab55880b021671c4ba83e90f7" -dependencies = [ - "proc-macro2", - "pyo3-macros-backend", - "quote", - "syn", -] - -[[package]] -name = "pyo3-macros-backend" -version = "0.16.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53bda0f58f73f5c5429693c96ed57f7abdb38fdfc28ae06da4101a257adb7faf" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "quote" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "redox_syscall" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" -dependencies = [ - "bitflags", -] - -[[package]] -name = "redox_users" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" -dependencies = [ - "getrandom", - "redox_syscall", - "thiserror", -] - -[[package]] -name = "regex" -version = "1.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" - -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] - -[[package]] -name = "riemann_client" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1005d55a9a8cb53f6ab2380792031394fff549b020cde16cecbc0978df9e7242" -dependencies = [ - "docopt", - "libc", - "protobuf", - "rustls", - "rustls-pemfile", - "serde", - "webpki", - "webpki-roots", -] - -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted", - "web-sys", - "winapi", -] - -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver", -] - -[[package]] -name = "rustls" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" -dependencies = [ - "base64", - "log", - "ring", - "sct", - "webpki", -] - -[[package]] -name = "rustls-pemfile" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" -dependencies = [ - "base64", -] - -[[package]] -name = "ryu" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" - -[[package]] -name = "scaphandre" -version = "0.4.1" -dependencies = [ - "chrono", - "clap", - "colored", - "docker-sync", - "hostname", - "hyper", - "k8s-sync", - "log", - "loggerv", - "procfs", - "protobuf", - "regex", - "riemann_client", - "serde", - "serde_json", - "time 0.2.27", - "tokio", - "warp10", -] - -[[package]] -name = "scaphandre-python" -version = "0.1.0" -dependencies = [ - "env_logger", - "pyo3", - "scaphandre", -] - -[[package]] -name = "schannel" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" -dependencies = [ - "lazy_static", - "windows-sys", -] - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "sct" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - -[[package]] -name = "serde" -version = "1.0.137" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde-value" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" -dependencies = [ - "ordered-float", - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.137" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_yaml" -version = "0.8.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707d15895415db6628332b737c838b88c598522e4dc70647e59b72312924aebc" -dependencies = [ - "indexmap", - "ryu", - "serde", - "yaml-rust", -] - -[[package]] -name = "sha1" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" -dependencies = [ - "sha1_smol", -] - -[[package]] -name = "sha1_smol" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" - -[[package]] -name = "signal-hook-registry" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" -dependencies = [ - "libc", -] - -[[package]] -name = "slab" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" - -[[package]] -name = "sluice" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" -dependencies = [ - "async-channel", - "futures-core", - "futures-io", -] - -[[package]] -name = "smallvec" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" - -[[package]] -name = "socket2" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "standback" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" -dependencies = [ - "version_check", -] - -[[package]] -name = "stdweb" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" -dependencies = [ - "discard", - "rustc_version", - "stdweb-derive", - "stdweb-internal-macros", - "stdweb-internal-runtime", - "wasm-bindgen", -] - -[[package]] -name = "stdweb-derive" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "serde_derive", - "syn", -] - -[[package]] -name = "stdweb-internal-macros" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" -dependencies = [ - "base-x", - "proc-macro2", - "quote", - "serde", - "serde_derive", - "serde_json", - "sha1", - "syn", -] - -[[package]] -name = "stdweb-internal-runtime" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" - -[[package]] -name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "syn" -version = "1.0.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "target-lexicon" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1" - -[[package]] -name = "tempfile" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" -dependencies = [ - "cfg-if", - "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", -] - -[[package]] -name = "termcolor" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" -dependencies = [ - "winapi-util", -] - -[[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.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "time" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "time" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" -dependencies = [ - "const_fn", - "libc", - "standback", - "stdweb", - "time-macros", - "version_check", - "winapi", -] - -[[package]] -name = "time-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" -dependencies = [ - "proc-macro-hack", - "time-macros-impl", -] - -[[package]] -name = "time-macros-impl" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" -dependencies = [ - "proc-macro-hack", - "proc-macro2", - "quote", - "standback", - "syn", -] - -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - -[[package]] -name = "tokio" -version = "1.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395" -dependencies = [ - "bytes", - "libc", - "memchr", - "mio", - "num_cpus", - "once_cell", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "winapi", -] - -[[package]] -name = "tokio-macros" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-util" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f988a1a1adc2fb21f9c12aa96441da33a1728193ae0b95d2be22dbd17fcb4e5c" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", - "tracing", -] - -[[package]] -name = "tower-service" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" - -[[package]] -name = "tracing" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" -dependencies = [ - "cfg-if", - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - -[[package]] -name = "try-lock" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" - -[[package]] -name = "unicode-bidi" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" - -[[package]] -name = "unicode-ident" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" - -[[package]] -name = "unicode-normalization" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-width" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" - -[[package]] -name = "unindent" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52fee519a3e570f7df377a06a1a7775cdbfb7aa460be7e08de2b1f0e69973a44" - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "url" -version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" -dependencies = [ - "form_urlencoded", - "idna", - "matches", - "percent-encoding", -] - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[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 = "waker-fn" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" - -[[package]] -name = "want" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -dependencies = [ - "log", - "try-lock", -] - -[[package]] -name = "warp10" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140e989c5e92da4e09581133f4de7df32d1ce7de6b7a077652bdfa3f9aef97bf" -dependencies = [ - "isahc", - "percent-encoding", - "serde", - "serde_json", - "time 0.2.27", - "url", -] - -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - -[[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.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" -dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" - -[[package]] -name = "web-sys" -version = "0.3.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki" -version = "0.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "webpki-roots" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" -dependencies = [ - "webpki", -] - -[[package]] -name = "wepoll-ffi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" -dependencies = [ - "cc", -] - -[[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-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[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-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] diff --git a/python/Cargo.toml b/python/Cargo.toml deleted file mode 100644 index 2fb3fd55..00000000 --- a/python/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "scaphandre-python" -version = "0.1.0" -authors = ["fvaleye@github.com"] -homepage = "https://hubblo-org.github.io/scaphandre-documentation" -license = "Apache-2.0" -description = "Electrical power consumption measurement agent." -readme = "README.md" -edition = "2021" -keywords = ["energy", "sustainability", "measure", "virtual-machine", "energy-monitor", "electricity", "virtual-machines", "energy-consumption", "electricity-consumption", "energy-efficiency", "carbon-footprint"] - -[lib] -name = "scaphandre" -crate-type = ["cdylib"] - -[dependencies] -env_logger = "0" - -[dependencies.pyo3] -version = "0.16" -features = ["extension-module", "abi3", "abi3-py37"] - -[dependencies.scaphandre] -path = "../" -version = "0" \ No newline at end of file diff --git a/python/LICENSE.txt b/python/LICENSE.txt deleted file mode 100644 index 261eeb9e..00000000 --- a/python/LICENSE.txt +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/python/Makefile b/python/Makefile deleted file mode 100644 index 3b805cb3..00000000 --- a/python/Makefile +++ /dev/null @@ -1,78 +0,0 @@ -.DEFAULT_GOAL := help - -VENV := venv -MATURIN_VERSION := $(shell awk -F '[ ="]+' '$$1 == "requires" { print $$4 }' pyproject.toml) -PACKAGE_VERSION := $(shell cargo pkgid | cut -d\# -f2 | cut -d: -f2) - -.PHONY: setup-venv -setup-venv: ## Setup the virtualenv - $(info --- Setup virtualenv ---) - python -m venv $(VENV) - -.PHONY: setup -setup: ## Setup the requirements - $(info --- Setup dependencies ---) - pip install maturin==$(MATURIN_VERSION) - -.PHONY: build -build: setup ## Build Python binding of scaphandre - $(info --- Build Python binding ---) - maturin build $(MATURIN_EXTRA_ARGS) - -.PHONY: develop -develop: setup ## Install Python binding of scaphandre - $(info --- Develop with Python binding ---) - maturin develop --extras=devel $(MATURIN_EXTRA_ARGS) - -.PHONY: install -install: build ## Install Python binding of scaphandre - $(info --- Uninstall Python binding ---) - pip uninstall -y scaphandre - $(info --- Install Python binding ---) - $(eval TARGET_WHEEL := $(shell ls ../target/wheels/scaphandre-${PACKAGE_VERSION}-*.whl)) - pip install $(TARGET_WHEEL)[devel] - -.PHONY: format -format: ## Format the code - $(info --- Rust format ---) - cargo fmt - $(info --- Python format ---) - black . - isort . - -.PHONY: check-rust -check-rust: ## Run check on Rust - $(info --- Check Rust clippy ---) - cargo clippy - $(info --- Check Rust format ---) - cargo fmt -- --check - -.PHONY: check-python -check-python: ## Run check on Python - $(info Check Python isort) - isort --diff --check-only . - $(info Check Python black) - black --check . - $(info Check Python mypy) - mypy - -.PHONY: unit-test -unit-test: ## Run unit test - $(info --- Run Python unit-test ---) - python -m pytest - -.PHONY: build-documentation -build-documentation: ## Build documentation with Sphinx - $(info --- Run build of the Sphinx documentation ---) - sphinx-build -Wn -b html -d ./docs/build/doctrees ./docs/source ./docs/build/html - -.PHONY: clean -clean: ## Run clean - $(warning --- Clean virtualenv and target directory ---) - cargo clean - rm -rf $(VENV) - find . -type f -name '*.pyc' -delete - -.PHONY: help -help: - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' diff --git a/python/README.md b/python/README.md deleted file mode 100644 index e69de29b..00000000 diff --git a/python/docs/Makefile b/python/docs/Makefile deleted file mode 100644 index d0c3cbf1..00000000 --- a/python/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = source -BUILDDIR = build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/python/docs/make.bat b/python/docs/make.bat deleted file mode 100644 index 747ffb7b..00000000 --- a/python/docs/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=source -set BUILDDIR=build - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.https://www.sphinx-doc.org/ - exit /b 1 -) - -if "%1" == "" goto help - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/python/docs/source/_static/.gitignore b/python/docs/source/_static/.gitignore deleted file mode 100644 index 86d0cb27..00000000 --- a/python/docs/source/_static/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# Ignore everything in this directory -* -# Except this file -!.gitignore \ No newline at end of file diff --git a/python/docs/source/api_reference.rst b/python/docs/source/api_reference.rst deleted file mode 100644 index 0b9b37a0..00000000 --- a/python/docs/source/api_reference.rst +++ /dev/null @@ -1,11 +0,0 @@ -API Reference -==================================== - -Scaphandre ----------- - -.. automodule:: scaphandre.Scaphandre - :members: - -.. automodule:: scaphandre.EnergyRecord - :members: \ No newline at end of file diff --git a/python/docs/source/conf.py b/python/docs/source/conf.py deleted file mode 100644 index c3d528a3..00000000 --- a/python/docs/source/conf.py +++ /dev/null @@ -1,80 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -import os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) -import sys - -import toml - -sys.path.insert(0, os.path.abspath("../scaphandre/")) -sys.path.insert(0, os.path.abspath("./_ext")) - - -def get_release_version() -> str: - """ - Get the release version from the Cargo.toml file - - :return: - """ - cargo_content = toml.load("../../Cargo.toml") - return cargo_content["package"]["version"] - - -# -- Project information ----------------------------------------------------- - -project = "scaphandre" -copyright = "2022, hubblo" -author = "hubblo" - -# The full version, including alpha/beta/rc tags -release = get_release_version() - - -# -- General configuration --------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - "sphinx_rtd_theme", - "sphinx.ext.autodoc", - "sphinx.ext.intersphinx", -] -autodoc_typehints = "description" -nitpicky = True - -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = [] - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = "sphinx_rtd_theme" - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] - -page_source_prefix = "python/docs/source" diff --git a/python/docs/source/index.rst b/python/docs/source/index.rst deleted file mode 100644 index 620f3fd9..00000000 --- a/python/docs/source/index.rst +++ /dev/null @@ -1,19 +0,0 @@ -.. scaphandre documentation master file, created by - sphinx-quickstart on Sun Jul 3 20:27:34 2022. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to scaphandre's documentation! -====================================== - -.. toctree:: - :maxdepth: 2 - - api_reference - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/python/pyproject.toml b/python/pyproject.toml deleted file mode 100644 index 6274c98c..00000000 --- a/python/pyproject.toml +++ /dev/null @@ -1,60 +0,0 @@ -[build-system] -requires = ["maturin==0.12.20"] -build-backend = "maturin" - -[project] -name = "scaphandre" -description = "Electrical power consumption measurement agent." -readme = "README.md" -license = {file = "LICENSE.txt"} -requires-python = ">=3.7" -keywords = ["energy", "sustainability", "measure", "virtual-machine", "energy-monitor", "electricity", "virtual-machines", "energy-consumption", "electricity-consumption", "energy-efficiency", "carbon-footprint"] -classifiers = [ - "Development Status :: 3 - Alpha", - "License :: OSI Approved :: Apache Software License", - "Programming Language :: Python :: 3 :: Only" -] -dependencies = [] - -[project.optional-dependencies] -devel = [ - "mypy", - "black", - "isort", - "pytest", - "pytest-mock", - "pytest-cov", - "sphinx", - "sphinx-rtd-theme", - "toml", -] - -[project.urls] -documentation = "https://hubblo-org.github.io/scaphandre-documentation" -repository = "https://github.com/hubblo-org/scaphandre" - -[tool.mypy] -files = "scaphandre/*.py" -exclude = "^tests" -mypy_path = "./stubs" -disallow_any_generics = true -disallow_subclassing_any = true -disallow_untyped_calls = true -disallow_untyped_defs = true -disallow_incomplete_defs = true -check_untyped_defs = true -disallow_untyped_decorators = true -no_implicit_optional = true -warn_redundant_casts = true -warn_unused_ignores = true -warn_return_any = false -implicit_reexport = false -strict_equality = true - -[tool.isort] -profile = "black" -src_paths = ["scaphandre", "tests"] - -[tool.black] -include = '\.pyi?$' -exclude = "venv" \ No newline at end of file diff --git a/python/scaphandre/__init__.py b/python/scaphandre/__init__.py deleted file mode 100644 index 5e36334c..00000000 --- a/python/scaphandre/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .sensors import * diff --git a/python/scaphandre/sensors.py b/python/scaphandre/sensors.py deleted file mode 100644 index 7bfa4400..00000000 --- a/python/scaphandre/sensors.py +++ /dev/null @@ -1,59 +0,0 @@ -from dataclasses import dataclass - -from .scaphandre import RawScaphandre - - -@dataclass -class EnergyRecord: - """ - Energy record measured by Scaphandre - """ - - timestamp: str - value: str - unit: str - - -@dataclass(init=False) -class Scaphandre: - """ - Scaphandre, a metrology agent dedicated to electrical power consumption metrics. - """ - - sensor_name: str - - def __init__( - self, - is_virtual_machine: bool = False, - buffer_per_socket_max_kbytes: int = 8, - buffer_per_domain_max_kbytes: int = 8, - ): - """ - Init Scaphandre - - :param is_virtual_machine: running on a virtual machine for powercap_rapl sensor - :param buffer_per_socket_max_kbytes: max buffer per socket in kbytes for powercap_rapl sensor - :param buffer_per_domain_max_kbytes: max buffer per domain in kbytes for powercap_rapl sensor - """ - self._scaphandre = RawScaphandre( - buffer_per_socket_max_kbytes, - buffer_per_domain_max_kbytes, - is_virtual_machine, - ) - self.name = self._scaphandre.sensor_name - - def is_compatible(self) -> bool: - """ - Check if Scaphandre has a sensor available and valid depending on the hardware context. - - :return: a sensor is available and valid - """ - return self._scaphandre.is_compatible() - - def get_energy_consumption_measures(self) -> EnergyRecord: - """ - Get the energy records from Scaphandre. - - :return: the energy record measured - """ - return self._scaphandre.get_energy_consumption_measures() diff --git a/python/src/lib.rs b/python/src/lib.rs deleted file mode 100644 index 795f3499..00000000 --- a/python/src/lib.rs +++ /dev/null @@ -1,91 +0,0 @@ -#![deny(warnings)] - -extern crate pyo3; - -use pyo3::create_exception; -use pyo3::exceptions::PyException; -use pyo3::prelude::*; -use scaphandre::sensors; -use scaphandre::sensors::powercap_rapl; -use scaphandre::sensors::units; -use sensors::{powercap_rapl::PowercapRAPLSensor, Sensor}; -use std::error::Error; -use std::time::Duration; - -create_exception!(scaphandre, PyScaphandreError, PyException); - -impl PyScaphandreError { - fn from_error(err: Box) -> pyo3::PyErr { - PyScaphandreError::new_err(err.to_string()) - } -} - -#[pyclass] -struct RawScaphandre { - _scaphandre: powercap_rapl::PowercapRAPLSensor, - #[pyo3(get)] - sensor_name: String, -} - -#[pymethods] -impl RawScaphandre { - #[new] - fn new( - buffer_per_socket_max_kbytes: u16, - buffer_per_domain_max_kbytes: u16, - is_virtual_machine: bool, - ) -> PyResult { - let sensor = PowercapRAPLSensor::new( - buffer_per_socket_max_kbytes, - buffer_per_domain_max_kbytes, - is_virtual_machine, - ); - Ok(RawScaphandre { - _scaphandre: sensor, - sensor_name: "PowercapRAPL".to_string(), - }) - } - - fn is_compatible(&self) -> bool { - matches!(PowercapRAPLSensor::check_module(), Ok(_)) - } - - fn get_energy_consumption_measures(&self) -> PyResult> { - Ok(self - ._scaphandre - .generate_topology() - .map_err(PyScaphandreError::from_error)? - .record_buffer - .iter() - .map(|record| RawEnergyRecord { - _timestamp: record.timestamp, - _value: record.value.clone(), - _unit: record.unit, - }) - .collect()) - } -} - -#[pyclass] -struct RawEnergyRecord { - _timestamp: Duration, - _value: String, - _unit: units::Unit, -} - -#[pyfunction] -fn rust_core_version() -> &'static str { - scaphandre::crate_version() -} - -#[pymodule] -// module name need to match project name -fn scaphandre(py: Python, m: &PyModule) -> PyResult<()> { - env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init(); - - m.add_function(pyo3::wrap_pyfunction!(rust_core_version, m)?)?; - m.add_class::()?; - m.add_class::()?; - m.add("PyScaphandreError", py.get_type::())?; - Ok(()) -} diff --git a/python/stubs/scaphandre/__init__.pyi b/python/stubs/scaphandre/__init__.pyi deleted file mode 100644 index c35db584..00000000 --- a/python/stubs/scaphandre/__init__.pyi +++ /dev/null @@ -1,3 +0,0 @@ -from typing import Any - -RawScaphandre: Any diff --git a/python/stubs/scaphandre/scaphandre.pyi b/python/stubs/scaphandre/scaphandre.pyi deleted file mode 100644 index c35db584..00000000 --- a/python/stubs/scaphandre/scaphandre.pyi +++ /dev/null @@ -1,3 +0,0 @@ -from typing import Any - -RawScaphandre: Any diff --git a/python/tests/test_scaphandre.py b/python/tests/test_scaphandre.py deleted file mode 100644 index 16fcfc96..00000000 --- a/python/tests/test_scaphandre.py +++ /dev/null @@ -1,5 +0,0 @@ -from scaphandre import RawScaphandre, Scaphandre - - -def test_scaphandre_should_init_with_the_good_name(): - assert Scaphandre().name == "PowercapRAPL" diff --git a/src/lib.rs b/src/lib.rs index b19e66bf..60b65c46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,11 +36,6 @@ fn current_system_time_since_epoch() -> Duration { .unwrap() } -/// Returns rust crate version, can be use used in language bindings to expose Rust core version -pub fn crate_version() -> &'static str { - env!("CARGO_PKG_VERSION") -} - // Copyright 2020 The scaphandre authors. // // Licensed under the Apache License, Version 2.0 (the "License"); From 3625d2d97b56e78729ca096eaac052698116ad95 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 22 May 2023 17:41:52 +0200 Subject: [PATCH 134/303] doc: warpten exporter now compiles on windows --- docs_src/tutorials/compilation-windows.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs_src/tutorials/compilation-windows.md b/docs_src/tutorials/compilation-windows.md index a2465b4e..47906a25 100644 --- a/docs_src/tutorials/compilation-windows.md +++ b/docs_src/tutorials/compilation-windows.md @@ -6,10 +6,6 @@ Scaphandre, on Windows, needs a kernel driver to get the same metrics as it does Once you have a working driver, you can compile Scaphandre, with the Rust for Windows usual toolkit. -For now, all Scaphandre features are not supported on windows. Use the following command line to build the binary : - -``` -cargo build --no-default-features --features "prometheus json riemann" -``` +For now, all Scaphandre features are not supported on windows. Simply run `cargo build` on a windows machine with cargo installed to get a binary. Don't forget to add the `--release` flag to build a binary suited for more than test and debug usecases. From 86edbfded2751fa65767bb62eefbf9db2390851b Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 22 May 2023 18:24:22 +0200 Subject: [PATCH 135/303] Revert "doc: warpten exporter now compiles on windows" This reverts commit 3625d2d97b56e78729ca096eaac052698116ad95. --- docs_src/tutorials/compilation-windows.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs_src/tutorials/compilation-windows.md b/docs_src/tutorials/compilation-windows.md index 47906a25..a2465b4e 100644 --- a/docs_src/tutorials/compilation-windows.md +++ b/docs_src/tutorials/compilation-windows.md @@ -6,6 +6,10 @@ Scaphandre, on Windows, needs a kernel driver to get the same metrics as it does Once you have a working driver, you can compile Scaphandre, with the Rust for Windows usual toolkit. -For now, all Scaphandre features are not supported on windows. Simply run `cargo build` on a windows machine with cargo installed to get a binary. +For now, all Scaphandre features are not supported on windows. Use the following command line to build the binary : + +``` +cargo build --no-default-features --features "prometheus json riemann" +``` Don't forget to add the `--release` flag to build a binary suited for more than test and debug usecases. From 7de4a1ec77aa052fc46ed84b6e5876b131484f3d Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 22 May 2023 18:27:42 +0200 Subject: [PATCH 136/303] ci: reverting warpten test for windows --- .github/workflows/build-and-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 818f4f59..81837045 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -169,7 +169,7 @@ jobs: rustup toolchain install stable-x86_64-pc-windows-msvc - name: Tests run: | - cargo test + cargo test --no-default-features --features "prometheus json riemann" - name: Build (debug mode) run: | - cargo build + cargo build --no-default-features --features "prometheus json riemann" From bfbae216ddbe3ae568b35238cb44ea0a844ebf80 Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Mon, 22 May 2023 17:39:07 +0200 Subject: [PATCH 137/303] Fix openssl issues in windows builds --- .github/workflows/build-and-test.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 818f4f59..13fb715d 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -64,6 +64,7 @@ jobs: with: command: clippy args: -- -A clippy::upper_case_acronyms -D warnings + fmt_and_clippy_windows: name: Cargo Fmt and Clippy - Windows runs-on: windows-latest @@ -118,6 +119,7 @@ jobs: id: unittests run: | awx --conf.token ${{ steps.login.outputs.awx_token }} --conf.host ${{ env.AWX_HOST }} job_templates launch --extra_vars="{\"github_repository\":\"${GITHUB_REPOSITORY}\",\"github_actor\":\"${GITHUB_ACTOR}\",\"github_workflow\":\"${GITHUB_WORKFLOW}\",\"github_workspace\":\"${GITHUB_WORKSPACE}\",\"github_event_name\":\"${GITHUB_EVENT_NAME}\",\"github_event_path\":\"${GITHUB_EVENT_PATH}\",\"github_sha\":\"${GITHUB_SHA}\",\"github_ref\":\"${GITHUB_REF}\",\"github_head_ref\":\"${GITHUB_HEAD_REF}\",\"github_base_ref\":\"${GITHUB_BASE_REF}\",\"github_server_url\":\"${GITHUB_SERVER_URL}\"}" 14 --monitor + build_linux_x86_64: name: Build on GNU/Linux x86_64 (Bare metal worker) runs-on: ubuntu-latest @@ -154,12 +156,17 @@ jobs: id: promtest run: | awx --conf.token ${{ steps.login.outputs.awx_token }} --conf.host ${{ env.AWX_HOST }} job_templates launch --extra_vars="{\"github_repository\":\"${GITHUB_REPOSITORY}\",\"github_actor\":\"${GITHUB_ACTOR}\",\"github_workflow\":\"${GITHUB_WORKFLOW}\",\"github_workspace\":\"${GITHUB_WORKSPACE}\",\"github_event_name\":\"${GITHUB_EVENT_NAME}\",\"github_event_path\":\"${GITHUB_EVENT_PATH}\",\"github_sha\":\"${GITHUB_SHA}\",\"github_ref\":\"${GITHUB_REF}\",\"github_head_ref\":\"${GITHUB_HEAD_REF}\",\"github_base_ref\":\"${GITHUB_BASE_REF}\",\"github_server_url\":\"${GITHUB_SERVER_URL}\"}" 16 --monitor + test_windows_x86_64: name: Test on Windows x86_64 (Virtual machine worker) - runs-on: "windows-2019" + runs-on: "windows-latest" steps: - name: Checkout uses: actions/checkout@v3 + - name: Install openssl for Windows with vcpkg + run: | + echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append + vcpkg install openssl:x64-windows-static-md - name: Install Rustup uses: crazy-max/ghaction-chocolatey@v2 with: From 02ff520e7950e762e707a3b95338de98da245e67 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 24 May 2023 11:42:13 +0200 Subject: [PATCH 138/303] chore: adding packaging folder with sample service files --- packaging/linux/redhat/scaphandre.service | 55 +++++++++++++++++++++++ packaging/linux/ubuntu/scaphandre.service | 55 +++++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 packaging/linux/redhat/scaphandre.service create mode 100644 packaging/linux/ubuntu/scaphandre.service diff --git a/packaging/linux/redhat/scaphandre.service b/packaging/linux/redhat/scaphandre.service new file mode 100644 index 00000000..45b98a52 --- /dev/null +++ b/packaging/linux/redhat/scaphandre.service @@ -0,0 +1,55 @@ +[Unit] +Description=Scaphandre +Wants=network.target +After=network.target + +[Service] +# systemctl edit and add these if you want to further limit access +#IPAddressAllow=localhost +#IPAddressDeny=any + +ExecStartPre=-+/usr/bin/modprobe intel_rapl_common +ExecStartPre=+/usr/bin/find /sys/devices/virtual/powercap -name energy_uj -exec chmod g+r -R {} + -exec chown root:powercap {} + +ExecStart=/usr/local/bin/scaphandre $SCAPHANDRE_ARGS +EnvironmentFile=/etc/conf.d/scaphandre + +CapabilityBoundingSet=CAP_NET_BIND_SERVICE +DevicePolicy=closed +DynamicUser=yes +Group=powercap +IPAccounting=yes +LockPersonality=yes +MemoryDenyWriteExecute=yes +MemoryMax=100M +NoNewPrivileges=yes +PrivateDevices=yes +PrivateTmp=yes +PrivateUsers=yes +ProtectClock=yes +ProtectControlGroups=yes +ProtectHome=yes +ProtectHostname=yes +ProtectKernelLogs=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +ProtectSystem=strict +RestrictAddressFamilies=AF_INET AF_INET6 +RestrictNamespaces=yes +RestrictRealtime=yes +RestrictSUIDSGID=yes +SyslogIdentifier=scaphandre +SystemCallFilter=~@cpu-emulation +SystemCallFilter=~@debug +SystemCallFilter=~@keyring +SystemCallFilter=~@module +SystemCallFilter=~@mount +SystemCallFilter=~@obsolete +SystemCallFilter=~@privileged +SystemCallFilter=~@raw-io +SystemCallFilter=~@reboot +SystemCallFilter=~@resources +SystemCallFilter=~@swap +UMask=0777 + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/packaging/linux/ubuntu/scaphandre.service b/packaging/linux/ubuntu/scaphandre.service new file mode 100644 index 00000000..5b9cbbd3 --- /dev/null +++ b/packaging/linux/ubuntu/scaphandre.service @@ -0,0 +1,55 @@ +[Unit] +Description=Scaphandre +Wants=network.target +After=network.target + +[Service] +# systemctl edit and add these if you want to further limit access +#IPAddressAllow=localhost +#IPAddressDeny=any + +ExecStartPre=-+/usr/sbin/modprobe intel_rapl_common +ExecStartPre=+/usr/bin/find /sys/devices/virtual/powercap -name energy_uj -exec chmod g+r -R {} + -exec chown root:powercap {} + +ExecStart=/usr/local/bin/scaphandre prometheus -p 8088 +EnvironmentFile=/etc/scaphandre + +CapabilityBoundingSet=CAP_NET_BIND_SERVICE +DevicePolicy=closed +DynamicUser=yes +Group=powercap +IPAccounting=yes +LockPersonality=yes +MemoryDenyWriteExecute=yes +MemoryMax=100M +NoNewPrivileges=yes +PrivateDevices=yes +PrivateTmp=yes +PrivateUsers=yes +ProtectClock=yes +ProtectControlGroups=yes +ProtectHome=yes +ProtectHostname=yes +ProtectKernelLogs=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +ProtectSystem=strict +RestrictAddressFamilies=AF_INET AF_INET6 +RestrictNamespaces=yes +RestrictRealtime=yes +RestrictSUIDSGID=yes +SyslogIdentifier=scaphandre +SystemCallFilter=~@cpu-emulation +SystemCallFilter=~@debug +SystemCallFilter=~@keyring +SystemCallFilter=~@module +SystemCallFilter=~@mount +SystemCallFilter=~@obsolete +SystemCallFilter=~@privileged +SystemCallFilter=~@raw-io +SystemCallFilter=~@reboot +SystemCallFilter=~@resources +SystemCallFilter=~@swap +UMask=0777 + +[Install] +WantedBy=multi-user.target \ No newline at end of file From a46351be59d89702e54364e07dda7614183fd31b Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Thu, 25 May 2023 15:28:49 +0200 Subject: [PATCH 139/303] chore: fix service files --- packaging/linux/redhat/scaphandre.service | 6 +++--- packaging/linux/ubuntu/scaphandre.service | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packaging/linux/redhat/scaphandre.service b/packaging/linux/redhat/scaphandre.service index 45b98a52..17f7e887 100644 --- a/packaging/linux/redhat/scaphandre.service +++ b/packaging/linux/redhat/scaphandre.service @@ -8,10 +8,10 @@ After=network.target #IPAddressAllow=localhost #IPAddressDeny=any -ExecStartPre=-+/usr/bin/modprobe intel_rapl_common +ExecStartPre=-+/usr/sbin/modprobe intel_rapl_common ExecStartPre=+/usr/bin/find /sys/devices/virtual/powercap -name energy_uj -exec chmod g+r -R {} + -exec chown root:powercap {} + -ExecStart=/usr/local/bin/scaphandre $SCAPHANDRE_ARGS -EnvironmentFile=/etc/conf.d/scaphandre +ExecStart=/usr/bin/scaphandre $SCAPHANDRE_ARGS +EnvironmentFile=/etc/scaphandre/default CapabilityBoundingSet=CAP_NET_BIND_SERVICE DevicePolicy=closed diff --git a/packaging/linux/ubuntu/scaphandre.service b/packaging/linux/ubuntu/scaphandre.service index 5b9cbbd3..bd03faeb 100644 --- a/packaging/linux/ubuntu/scaphandre.service +++ b/packaging/linux/ubuntu/scaphandre.service @@ -10,7 +10,7 @@ After=network.target ExecStartPre=-+/usr/sbin/modprobe intel_rapl_common ExecStartPre=+/usr/bin/find /sys/devices/virtual/powercap -name energy_uj -exec chmod g+r -R {} + -exec chown root:powercap {} + -ExecStart=/usr/local/bin/scaphandre prometheus -p 8088 +ExecStart=/usr/local/bin/scaphandre prometheus -p 8080 EnvironmentFile=/etc/scaphandre CapabilityBoundingSet=CAP_NET_BIND_SERVICE From 385eedaf1c0d5ce61871c96075cd64c58180ab4c Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Thu, 25 May 2023 16:03:16 +0200 Subject: [PATCH 140/303] chore: intiating rpm package --- .gitignore | 7 +++ packaging/rpmbuild/SPECS/scaphandre.spec | 59 ++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 packaging/rpmbuild/SPECS/scaphandre.spec diff --git a/.gitignore b/.gitignore index ecc91c21..3f700077 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,10 @@ # Used by integration tests /integration_tests + +# Packaging folder : avoir build artefacts +packaging/*/BUILD/* +*.rpm +*.tar.gz +*.swp +packaging/*/BUILDROOT/* diff --git a/packaging/rpmbuild/SPECS/scaphandre.spec b/packaging/rpmbuild/SPECS/scaphandre.spec new file mode 100644 index 00000000..ccc5eb6d --- /dev/null +++ b/packaging/rpmbuild/SPECS/scaphandre.spec @@ -0,0 +1,59 @@ +Name: scaphandre +Version: 0.5.0 +Release: 1%{?dist} +Summary: Power usage / Electricity / Energy monitoring agent + +License: Apache-2.0 +URL: https://github.com/hubblo-org/scaphandre +Source0: %{name}-%{version}.tar.gz +#Source0 will be github.com url for tar gz of source + +BuildRequires: rust,cargo,openssl-devel,systemd-rpm-macros +#Requires: + +%global debug_package %{nil} + +%description + +%prep +%autosetup + +%build +cargo build --release + +%pre + +%install +#rm -rf $RPM_BUILD_ROOT +mkdir -p $RPM_BUILD_ROOT/%{_bindir}/ +cp target/release/scaphandre $RPM_BUILD_ROOT/%{_bindir}/ +chmod +x $RPM_BUILD_ROOT/%{_bindir}/scaphandre +mkdir -p $RPM_BUILD_ROOT/lib/systemd/system +mkdir -p $RPM_BUILD_ROOT/etc/scaphandre +touch $RPM_BUILD_ROOT/etc/scaphandre/default +mkdir -p $RPM_BUILD_ROOT/lib/systemd/system +cp packaging/linux/redhat/scaphandre.service $RPM_BUILD_ROOT/lib/systemd/system/scaphandre.service + +%post +%systemd_post scaphandre.service + +%preun +%systemd_preun scpahandre.service + +%postun +%systemd_postun_with_restart scaphandre.service + +%clean +#rm -rf $RPM_BUILD_ROOT + +%files +#%doc README.md +%{_bindir}/scaphandre +/lib/systemd/system/scaphandre.service +/etc/scaphandre/default + +#%license LICENSE + +%changelog +* Wed May 25 2023 - bpetit + first package From 60df90576d2196a825e4383dfd1e1ba62fe27881 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Thu, 25 May 2023 16:42:06 +0200 Subject: [PATCH 141/303] fix: reenabling topp refresh in qemu exporter --- src/exporters/qemu.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/exporters/qemu.rs b/src/exporters/qemu.rs index f397c189..a41a1306 100644 --- a/src/exporters/qemu.rs +++ b/src/exporters/qemu.rs @@ -55,6 +55,7 @@ impl QemuExporter { pub fn iterate(&mut self, path: String) { trace!("path: {}", path); + self.topology.refresh(); if let Some(topo_energy) = self.topology.get_records_diff_power_microwatts() { let processes = self.topology.proc_tracker.get_alive_processes(); let qemu_processes = QemuExporter::filter_qemu_vm_processes(&processes); From 94adb454230e39885b7441dd6622fd1434c04cfd Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Thu, 25 May 2023 17:12:55 +0200 Subject: [PATCH 142/303] fix: qemu exporter missed refesh and permission errors --- src/exporters/qemu.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/exporters/qemu.rs b/src/exporters/qemu.rs index a41a1306..59100b54 100644 --- a/src/exporters/qemu.rs +++ b/src/exporters/qemu.rs @@ -80,11 +80,14 @@ impl QemuExporter { * topo_energy.value.parse::().unwrap() / 100.0; let complete_path = format!("{path}/{vm_name}/intel-rapl:0"); - if let Ok(result) = - QemuExporter::add_or_create(&complete_path, uj_to_add as u64) - { - trace!("{:?}", result); - debug!("Updated {}", complete_path); + match QemuExporter::add_or_create(&complete_path, uj_to_add as u64) { + Ok(result) => { + trace!("{:?}", result); + debug!("Updated {}", complete_path); + }, + Err(err) => { + error!("Could'nt edit {}. Please check file permissions : {}", complete_path, err); + } } } } From 7830cc146f0b123cfde31d100cd11733ff5d4db5 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Thu, 25 May 2023 17:13:30 +0200 Subject: [PATCH 143/303] style: fmt and clippy --- src/exporters/qemu.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/exporters/qemu.rs b/src/exporters/qemu.rs index 59100b54..de3355e5 100644 --- a/src/exporters/qemu.rs +++ b/src/exporters/qemu.rs @@ -84,9 +84,12 @@ impl QemuExporter { Ok(result) => { trace!("{:?}", result); debug!("Updated {}", complete_path); - }, + } Err(err) => { - error!("Could'nt edit {}. Please check file permissions : {}", complete_path, err); + error!( + "Could'nt edit {}. Please check file permissions : {}", + complete_path, err + ); } } } From 9c45c38cd160268c0d456c1ce4fe23d69ae62f8e Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 30 May 2023 12:18:32 +0200 Subject: [PATCH 144/303] fix: not specifying timeout on json exporter makes it run forever now --- src/exporters/json.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/exporters/json.rs b/src/exporters/json.rs index b8dee3ad..f0cd1b51 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -37,9 +37,9 @@ pub struct JsonExporter { #[derive(clap::Args, Debug)] pub struct ExporterArgs { /// Maximum time spent measuring, in seconds. - /// If negative, runs forever. - #[arg(short, long, default_value_t = 10)] - pub timeout: i64, + /// If unspecified, runs forever. + #[arg(short, long)] + pub timeout: Option, /// Interval between two measurements, in seconds #[arg(short, long, value_name = "SECONDS", default_value_t = 2)] @@ -191,10 +191,11 @@ impl JsonExporter { // Extract the parameters we need to run the exporter let time_step = Duration::new(args.step, args.step_nano); - let time_limit = if args.timeout < 0 { - None + let time_limit; + if let Some(t) = args.timeout { + time_limit = Some(Duration::from_secs(t.unsigned_abs())) } else { - Some(Duration::from_secs(args.timeout.unsigned_abs())) + time_limit = None }; let max_top_consumers = args.max_top_consumers; let process_regex = args.process_regex; From 88dc7826b88cdda198e24a61e571f988b613ef50 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 30 May 2023 12:28:29 +0200 Subject: [PATCH 145/303] fix: useless warn message, should be debug --- src/sensors/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sensors/utils.rs b/src/sensors/utils.rs index 4ea37aae..1827d054 100644 --- a/src/sensors/utils.rs +++ b/src/sensors/utils.rs @@ -696,7 +696,7 @@ impl ProcessTracker { consumers.pop(); } } else { - warn!("Couldn't get process info for {}", pid); + debug!("Couldn't get process info for {}", pid); } } } From c909ffa8cfc9ec90a65c0784768871e6e6d4a1e1 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Thu, 25 May 2023 16:42:06 +0200 Subject: [PATCH 146/303] fix: reenabling topp refresh in qemu exporter --- src/exporters/qemu.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/exporters/qemu.rs b/src/exporters/qemu.rs index f397c189..a41a1306 100644 --- a/src/exporters/qemu.rs +++ b/src/exporters/qemu.rs @@ -55,6 +55,7 @@ impl QemuExporter { pub fn iterate(&mut self, path: String) { trace!("path: {}", path); + self.topology.refresh(); if let Some(topo_energy) = self.topology.get_records_diff_power_microwatts() { let processes = self.topology.proc_tracker.get_alive_processes(); let qemu_processes = QemuExporter::filter_qemu_vm_processes(&processes); From 643c299e1567d2f6d978fb7ecfeb8781d6753d7e Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Thu, 25 May 2023 17:12:55 +0200 Subject: [PATCH 147/303] fix: qemu exporter missed refesh and permission errors --- src/exporters/qemu.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/exporters/qemu.rs b/src/exporters/qemu.rs index a41a1306..59100b54 100644 --- a/src/exporters/qemu.rs +++ b/src/exporters/qemu.rs @@ -80,11 +80,14 @@ impl QemuExporter { * topo_energy.value.parse::().unwrap() / 100.0; let complete_path = format!("{path}/{vm_name}/intel-rapl:0"); - if let Ok(result) = - QemuExporter::add_or_create(&complete_path, uj_to_add as u64) - { - trace!("{:?}", result); - debug!("Updated {}", complete_path); + match QemuExporter::add_or_create(&complete_path, uj_to_add as u64) { + Ok(result) => { + trace!("{:?}", result); + debug!("Updated {}", complete_path); + }, + Err(err) => { + error!("Could'nt edit {}. Please check file permissions : {}", complete_path, err); + } } } } From 7689fa388491f6421a4f91cc3ba976891791ea69 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Thu, 25 May 2023 17:13:30 +0200 Subject: [PATCH 148/303] ci: chaining package build and package test chore: adding default for rpm conf --- .github/workflows/build-and-test.yml | 21 +++---- .github/workflows/rpm-release.yml | 62 +++++++++++++++++++ .gitignore | 1 + .../redhat}/rpmbuild/SPECS/scaphandre.spec | 14 ++--- src/exporters/qemu.rs | 7 ++- 5 files changed, 83 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/rpm-release.yml rename packaging/{ => linux/redhat}/rpmbuild/SPECS/scaphandre.spec (81%) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 81837045..5da8a5d0 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -21,9 +21,6 @@ on: env: CARGO_TERM_COLOR: always - AWX_PUBLIC_ACCESS: "{Xs%g5/a/Si=;_[4LL" - AWX_PUBLIC_USER: "scaphandre-public" - AWX_HOST: "https://cd.hubblo.org" concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} @@ -104,20 +101,20 @@ jobs: - name: Log on AWX id: login run: | - export AWX_TOKEN=$(awx --conf.host "${{env.AWX_HOST}}" --conf.username "${{env.AWX_PUBLIC_USER}}" --conf.password "${{env.AWX_PUBLIC_ACCESS}}" login | jq .token | tr -d '"') + export AWX_TOKEN=$(awx --conf.host "${{ secrets.AWX_HOST }}" --conf.username "${{ secrets.AWX_PUBLIC_USER }}" --conf.password "${{ secrets.AWX_PASSWORD }}" login | jq .token | tr -d '"') echo "awx_token=${AWX_TOKEN}" >> $GITHUB_OUTPUT - name: Prepare Rust environment on bare metal worker id: rust run: | - awx --conf.token ${{ steps.login.outputs.awx_token }} --conf.host ${{ env.AWX_HOST }} job_templates launch --extra_vars="{\"github_repository\":\"$GITHUB_REPOSITORY\",\"github_actor\":\"${GITHUB_ACTOR}\",\"github_workflow\":\"${GITHUB_WORKFLOW}\",\"github_workspace\":\"${GITHUB_WORKSPACE}\",\"github_event_name\":\"${GITHUB_EVENT_NAME}\",\"github_event_path\":\"${GITHUB_EVENT_PATH}\",\"github_sha\":\"${GITHUB_SHA}\",\"github_ref\":\"${GITHUB_REF}\",\"github_head_ref\":\"${GITHUB_HEAD_REF}\",\"github_base_ref\":\"${GITHUB_BASE_REF}\",\"github_server_url\":\"${GITHUB_SERVER_URL}\"}" 12 --monitor + awx --conf.token ${{ steps.login.outputs.awx_token }} --conf.host ${{ secrets.AWX_HOST }} job_templates launch --extra_vars="{\"github_repository\":\"$GITHUB_REPOSITORY\",\"github_actor\":\"${GITHUB_ACTOR}\",\"github_workflow\":\"${GITHUB_WORKFLOW}\",\"github_workspace\":\"${GITHUB_WORKSPACE}\",\"github_event_name\":\"${GITHUB_EVENT_NAME}\",\"github_event_path\":\"${GITHUB_EVENT_PATH}\",\"github_sha\":\"${GITHUB_SHA}\",\"github_ref\":\"${GITHUB_REF}\",\"github_head_ref\":\"${GITHUB_HEAD_REF}\",\"github_base_ref\":\"${GITHUB_BASE_REF}\",\"github_server_url\":\"${GITHUB_SERVER_URL}\"}" 12 --monitor - name: Clone Scaphandre repository id: clone run: | - awx --conf.token ${{ steps.login.outputs.awx_token }} --conf.host ${{ env.AWX_HOST }} job_templates launch --extra_vars="{\"github_repository\":\"${GITHUB_REPOSITORY}\",\"github_actor\":\"${GITHUB_ACTOR}\",\"github_workflow\":\"${GITHUB_WORKFLOW}\",\"github_workspace\":\"${GITHUB_WORKSPACE}\",\"github_event_name\":\"${GITHUB_EVENT_NAME}\",\"github_event_path\":\"${GITHUB_EVENT_PATH}\",\"github_sha\":\"${GITHUB_SHA}\",\"github_ref\":\"${GITHUB_REF}\",\"github_head_ref\":\"${GITHUB_HEAD_REF}\",\"github_base_ref\":\"${GITHUB_BASE_REF}\",\"github_server_url\":\"${GITHUB_SERVER_URL}\"}" 13 --monitor + awx --conf.token ${{ steps.login.outputs.awx_token }} --conf.host ${{ secrets.AWX_HOST }} job_templates launch --extra_vars="{\"github_repository\":\"${GITHUB_REPOSITORY}\",\"github_actor\":\"${GITHUB_ACTOR}\",\"github_workflow\":\"${GITHUB_WORKFLOW}\",\"github_workspace\":\"${GITHUB_WORKSPACE}\",\"github_event_name\":\"${GITHUB_EVENT_NAME}\",\"github_event_path\":\"${GITHUB_EVENT_PATH}\",\"github_sha\":\"${GITHUB_SHA}\",\"github_ref\":\"${GITHUB_REF}\",\"github_head_ref\":\"${GITHUB_HEAD_REF}\",\"github_base_ref\":\"${GITHUB_BASE_REF}\",\"github_server_url\":\"${GITHUB_SERVER_URL}\"}" 13 --monitor - name: Run Unit Tests id: unittests run: | - awx --conf.token ${{ steps.login.outputs.awx_token }} --conf.host ${{ env.AWX_HOST }} job_templates launch --extra_vars="{\"github_repository\":\"${GITHUB_REPOSITORY}\",\"github_actor\":\"${GITHUB_ACTOR}\",\"github_workflow\":\"${GITHUB_WORKFLOW}\",\"github_workspace\":\"${GITHUB_WORKSPACE}\",\"github_event_name\":\"${GITHUB_EVENT_NAME}\",\"github_event_path\":\"${GITHUB_EVENT_PATH}\",\"github_sha\":\"${GITHUB_SHA}\",\"github_ref\":\"${GITHUB_REF}\",\"github_head_ref\":\"${GITHUB_HEAD_REF}\",\"github_base_ref\":\"${GITHUB_BASE_REF}\",\"github_server_url\":\"${GITHUB_SERVER_URL}\"}" 14 --monitor + awx --conf.token ${{ steps.login.outputs.awx_token }} --conf.host ${{ secrets.AWX_HOST }} job_templates launch --extra_vars="{\"github_repository\":\"${GITHUB_REPOSITORY}\",\"github_actor\":\"${GITHUB_ACTOR}\",\"github_workflow\":\"${GITHUB_WORKFLOW}\",\"github_workspace\":\"${GITHUB_WORKSPACE}\",\"github_event_name\":\"${GITHUB_EVENT_NAME}\",\"github_event_path\":\"${GITHUB_EVENT_PATH}\",\"github_sha\":\"${GITHUB_SHA}\",\"github_ref\":\"${GITHUB_REF}\",\"github_head_ref\":\"${GITHUB_HEAD_REF}\",\"github_base_ref\":\"${GITHUB_BASE_REF}\",\"github_server_url\":\"${GITHUB_SERVER_URL}\"}" 14 --monitor build_linux_x86_64: name: Build on GNU/Linux x86_64 (Bare metal worker) runs-on: ubuntu-latest @@ -136,24 +133,24 @@ jobs: - name: Log on AWX id: login run: | - export AWX_TOKEN=$(awx --conf.host "${{env.AWX_HOST}}" --conf.username "${{env.AWX_PUBLIC_USER}}" --conf.password "${{env.AWX_PUBLIC_ACCESS}}" login | jq .token | tr -d '"') + export AWX_TOKEN=$(awx --conf.host "${{ secrets.AWX_HOST }}" --conf.username "${{ secrets.AWX_PUBLIC_USER }}" --conf.password "${{ secrets.AWX_PASSWORD }}" login | jq .token | tr -d '"') echo "awx_token=${AWX_TOKEN}" >> $GITHUB_OUTPUT - name: Build debug version id: builddebug run: | - awx --conf.token ${{ steps.login.outputs.awx_token }} --conf.host ${{ env.AWX_HOST }} job_templates launch --extra_vars="{\"github_repository\":\"${GITHUB_REPOSITORY}\",\"github_actor\":\"${GITHUB_ACTOR}\",\"github_workflow\":\"${GITHUB_WORKFLOW}\",\"github_workspace\":\"${GITHUB_WORKSPACE}\",\"github_event_name\":\"${GITHUB_EVENT_NAME}\",\"github_event_path\":\"${GITHUB_EVENT_PATH}\",\"github_sha\":\"${GITHUB_SHA}\",\"github_ref\":\"${GITHUB_REF}\",\"github_head_ref\":\"${GITHUB_HEAD_REF}\",\"github_base_ref\":\"${GITHUB_BASE_REF}\",\"github_server_url\":\"${GITHUB_SERVER_URL}\"}" 17 --monitor + awx --conf.token ${{ steps.login.outputs.awx_token }} --conf.host ${{ secrets.AWX_HOST }} job_templates launch --extra_vars="{\"github_repository\":\"${GITHUB_REPOSITORY}\",\"github_actor\":\"${GITHUB_ACTOR}\",\"github_workflow\":\"${GITHUB_WORKFLOW}\",\"github_workspace\":\"${GITHUB_WORKSPACE}\",\"github_event_name\":\"${GITHUB_EVENT_NAME}\",\"github_event_path\":\"${GITHUB_EVENT_PATH}\",\"github_sha\":\"${GITHUB_SHA}\",\"github_ref\":\"${GITHUB_REF}\",\"github_head_ref\":\"${GITHUB_HEAD_REF}\",\"github_base_ref\":\"${GITHUB_BASE_REF}\",\"github_server_url\":\"${GITHUB_SERVER_URL}\"}" 17 --monitor - name: Test JSON exporter id: jsonexporter run: | - awx --conf.token ${{ steps.login.outputs.awx_token }} --conf.host ${{ env.AWX_HOST }} job_templates launch --extra_vars="{\"github_repository\":\"${GITHUB_REPOSITORY}\",\"github_actor\":\"${GITHUB_ACTOR}\",\"github_workflow\":\"${GITHUB_WORKFLOW}\",\"github_workspace\":\"${GITHUB_WORKSPACE}\",\"github_event_name\":\"${GITHUB_EVENT_NAME}\",\"github_event_path\":\"${GITHUB_EVENT_PATH}\",\"github_sha\":\"${GITHUB_SHA}\",\"github_ref\":\"${GITHUB_REF}\",\"github_head_ref\":\"${GITHUB_HEAD_REF}\",\"github_base_ref\":\"${GITHUB_BASE_REF}\",\"github_server_url\":\"${GITHUB_SERVER_URL}\"}" 18 --monitor + awx --conf.token ${{ steps.login.outputs.awx_token }} --conf.host ${{ secrets.AWX_HOST }} job_templates launch --extra_vars="{\"github_repository\":\"${GITHUB_REPOSITORY}\",\"github_actor\":\"${GITHUB_ACTOR}\",\"github_workflow\":\"${GITHUB_WORKFLOW}\",\"github_workspace\":\"${GITHUB_WORKSPACE}\",\"github_event_name\":\"${GITHUB_EVENT_NAME}\",\"github_event_path\":\"${GITHUB_EVENT_PATH}\",\"github_sha\":\"${GITHUB_SHA}\",\"github_ref\":\"${GITHUB_REF}\",\"github_head_ref\":\"${GITHUB_HEAD_REF}\",\"github_base_ref\":\"${GITHUB_BASE_REF}\",\"github_server_url\":\"${GITHUB_SERVER_URL}\"}" 18 --monitor - name: Build Docker image id: dockerbuild run: | - awx --conf.token ${{ steps.login.outputs.awx_token }} --conf.host ${{ env.AWX_HOST }} job_templates launch --extra_vars="{\"github_repository\":\"${GITHUB_REPOSITORY}\",\"github_actor\":\"${GITHUB_ACTOR}\",\"github_workflow\":\"${GITHUB_WORKFLOW}\",\"github_workspace\":\"${GITHUB_WORKSPACE}\",\"github_event_name\":\"${GITHUB_EVENT_NAME}\",\"github_event_path\":\"${GITHUB_EVENT_PATH}\",\"github_sha\":\"${GITHUB_SHA}\",\"github_ref\":\"${GITHUB_REF}\",\"github_head_ref\":\"${GITHUB_HEAD_REF}\",\"github_base_ref\":\"${GITHUB_BASE_REF}\",\"github_server_url\":\"${GITHUB_SERVER_URL}\"}" 15 --monitor + awx --conf.token ${{ steps.login.outputs.awx_token }} --conf.host ${{ secrets.AWX_HOST }} job_templates launch --extra_vars="{\"github_repository\":\"${GITHUB_REPOSITORY}\",\"github_actor\":\"${GITHUB_ACTOR}\",\"github_workflow\":\"${GITHUB_WORKFLOW}\",\"github_workspace\":\"${GITHUB_WORKSPACE}\",\"github_event_name\":\"${GITHUB_EVENT_NAME}\",\"github_event_path\":\"${GITHUB_EVENT_PATH}\",\"github_sha\":\"${GITHUB_SHA}\",\"github_ref\":\"${GITHUB_REF}\",\"github_head_ref\":\"${GITHUB_HEAD_REF}\",\"github_base_ref\":\"${GITHUB_BASE_REF}\",\"github_server_url\":\"${GITHUB_SERVER_URL}\"}" 15 --monitor - name: Test Scaphandre + Prometheus in docker-compose id: promtest run: | - awx --conf.token ${{ steps.login.outputs.awx_token }} --conf.host ${{ env.AWX_HOST }} job_templates launch --extra_vars="{\"github_repository\":\"${GITHUB_REPOSITORY}\",\"github_actor\":\"${GITHUB_ACTOR}\",\"github_workflow\":\"${GITHUB_WORKFLOW}\",\"github_workspace\":\"${GITHUB_WORKSPACE}\",\"github_event_name\":\"${GITHUB_EVENT_NAME}\",\"github_event_path\":\"${GITHUB_EVENT_PATH}\",\"github_sha\":\"${GITHUB_SHA}\",\"github_ref\":\"${GITHUB_REF}\",\"github_head_ref\":\"${GITHUB_HEAD_REF}\",\"github_base_ref\":\"${GITHUB_BASE_REF}\",\"github_server_url\":\"${GITHUB_SERVER_URL}\"}" 16 --monitor + awx --conf.token ${{ steps.login.outputs.awx_token }} --conf.host ${{ secrets.AWX_HOST }} job_templates launch --extra_vars="{\"github_repository\":\"${GITHUB_REPOSITORY}\",\"github_actor\":\"${GITHUB_ACTOR}\",\"github_workflow\":\"${GITHUB_WORKFLOW}\",\"github_workspace\":\"${GITHUB_WORKSPACE}\",\"github_event_name\":\"${GITHUB_EVENT_NAME}\",\"github_event_path\":\"${GITHUB_EVENT_PATH}\",\"github_sha\":\"${GITHUB_SHA}\",\"github_ref\":\"${GITHUB_REF}\",\"github_head_ref\":\"${GITHUB_HEAD_REF}\",\"github_base_ref\":\"${GITHUB_BASE_REF}\",\"github_server_url\":\"${GITHUB_SERVER_URL}\"}" 16 --monitor test_windows_x86_64: name: Test on Windows x86_64 (Virtual machine worker) runs-on: "windows-2019" diff --git a/.github/workflows/rpm-release.yml b/.github/workflows/rpm-release.yml new file mode 100644 index 00000000..991eac79 --- /dev/null +++ b/.github/workflows/rpm-release.yml @@ -0,0 +1,62 @@ +name: Build RPM package + +on: + push: + paths-ignore: + - 'docs_src/**' + - 'README.md' + - 'CITATION' + - 'book.toml' + - 'CONTRIBUTING.md' + tags: [ 'v*.*.*', 'dev*.*.*' ] + +jobs: + build_rpm: + name: Build RPM package + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install s3cmd + run: sudo apt install python3-pip -y && sudo pip3 install s3cmd awxkit + - name: Get tag + id: tag + uses: devops-actions/action-get-tag@v1.0.2 + with: + strip_v: true # Optional: Remove 'v' character from version + default: "v0.0.0" # Optional: Default version when tag not found + - name: Override version + run: "sed -i 's/Version: .*/Version: ${{steps.tag.outputs.tag}}/' packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec" + - name: Debug + run: grep Version packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec + - name: Extract release notes + id: extract-release-notes + uses: ffurrer2/extract-release-notes@v1 + #- name: Display release notes + # run: "echo ${{ steps.extract-release-notes.outputs.release_notes }}" + - name: Edit changelog #TODO commit and push to increase changelog + run: date=$(date "+%a %b %d %Y - "); sed -i "/%changelog/ a * ${date}${{steps.tag.outputs.tag}}/" packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec + - name: Edit changelog + run: echo " Packaging for version ${{steps.tag.outputs.tag}}" >> packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec + - name: Troubleshoot parameters + run: | + echo "token: ${{ secrets.GITHUB_TOKEN }} run_id: ${GITHUB_RUN_ID} repository: ${GITHUB_REPOSITORY}" + - name: build RPM package + id: rpm + uses: bpetit/rpmbuild@master + with: + spec_file: "packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec" + - name: Upload to scw s3 + run: | + s3cmd --access_key="${{ secrets.S3_ACCESS_KEY_ID }}" --secret_key="${{ secrets.S3_SECRET_ACCESS_KEY }}" --region="fr-par" --acl-public --host="s3.fr-par.scw.cloud" --host-bucket="%(bucket).s3.fr-par.scw.cloud" put --recursive ${{ steps.rpm.outputs.rpm_dir_path }} s3://scaphandre/ + - name: Log on AWX + id: login + run: | + RAW_RESULT=$(awx --conf.host "${{ secrets.AWX_HOST }}" --conf.username "${{ secrets.AWX_PUBLIC_USER }}" --conf.password "${{ secrets.AWX_PASSWORD }}" login) + echo $RAW_RESULT + export AWX_TOKEN=$(echo $RAW_RESULT | jq .token | tr -d '"') + echo "awx_token=${AWX_TOKEN}" >> $GITHUB_OUTPUT + - name: Install and test RPM package + id: rpmtest + run: | + awx --conf.token ${{ steps.login.outputs.awx_token }} --conf.host ${{ secrets.AWX_HOST }} job_templates launch --extra_vars="{\"github_repository\":\"${GITHUB_REPOSITORY}\",\"github_actor\":\"${GITHUB_ACTOR}\",\"github_workflow\":\"${GITHUB_WORKFLOW}\",\"github_workspace\":\"${GITHUB_WORKSPACE}\",\"github_event_name\":\"${GITHUB_EVENT_NAME}\",\"github_event_path\":\"${GITHUB_EVENT_PATH}\",\"github_sha\":\"${GITHUB_SHA}\",\"github_ref\":\"${GITHUB_REF}\",\"github_head_ref\":\"${GITHUB_HEAD_REF}\",\"github_base_ref\":\"${GITHUB_BASE_REF}\",\"github_server_url\":\"${GITHUB_SERVER_URL}\",\"github_rpm_url\":\"https://scaphandre.s3.fr-par.scw.cloud/x86_64/scaphandre-${{steps.tag.outputs.tag}}-1.el9.x86_64.rpm\"}" 19 --monitor \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3f700077..2c9f2c0b 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ # Packaging folder : avoir build artefacts packaging/*/BUILD/* +packaging/*/BUILDROOT/* *.rpm *.tar.gz *.swp diff --git a/packaging/rpmbuild/SPECS/scaphandre.spec b/packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec similarity index 81% rename from packaging/rpmbuild/SPECS/scaphandre.spec rename to packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec index ccc5eb6d..c8fd85e3 100644 --- a/packaging/rpmbuild/SPECS/scaphandre.spec +++ b/packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec @@ -1,5 +1,5 @@ Name: scaphandre -Version: 0.5.0 +Version: CHANGEME Release: 1%{?dist} Summary: Power usage / Electricity / Energy monitoring agent @@ -19,18 +19,18 @@ BuildRequires: rust,cargo,openssl-devel,systemd-rpm-macros %autosetup %build -cargo build --release +cargo build %pre %install #rm -rf $RPM_BUILD_ROOT mkdir -p $RPM_BUILD_ROOT/%{_bindir}/ -cp target/release/scaphandre $RPM_BUILD_ROOT/%{_bindir}/ +cp target/debug/scaphandre $RPM_BUILD_ROOT/%{_bindir}/ chmod +x $RPM_BUILD_ROOT/%{_bindir}/scaphandre mkdir -p $RPM_BUILD_ROOT/lib/systemd/system mkdir -p $RPM_BUILD_ROOT/etc/scaphandre -touch $RPM_BUILD_ROOT/etc/scaphandre/default +echo "prometheus" > $RPM_BUILD_ROOT/etc/scaphandre/default mkdir -p $RPM_BUILD_ROOT/lib/systemd/system cp packaging/linux/redhat/scaphandre.service $RPM_BUILD_ROOT/lib/systemd/system/scaphandre.service @@ -38,7 +38,7 @@ cp packaging/linux/redhat/scaphandre.service $RPM_BUILD_ROOT/lib/systemd/system/ %systemd_post scaphandre.service %preun -%systemd_preun scpahandre.service +%systemd_preun scaphandre.service %postun %systemd_postun_with_restart scaphandre.service @@ -54,6 +54,4 @@ cp packaging/linux/redhat/scaphandre.service $RPM_BUILD_ROOT/lib/systemd/system/ #%license LICENSE -%changelog -* Wed May 25 2023 - bpetit - first package +%changelog \ No newline at end of file diff --git a/src/exporters/qemu.rs b/src/exporters/qemu.rs index 59100b54..de3355e5 100644 --- a/src/exporters/qemu.rs +++ b/src/exporters/qemu.rs @@ -84,9 +84,12 @@ impl QemuExporter { Ok(result) => { trace!("{:?}", result); debug!("Updated {}", complete_path); - }, + } Err(err) => { - error!("Could'nt edit {}. Please check file permissions : {}", complete_path, err); + error!( + "Could'nt edit {}. Please check file permissions : {}", + complete_path, err + ); } } } From b4fd7c80f4da5914042c38c26687a86258d5eaa2 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 5 Jun 2023 15:45:04 +0200 Subject: [PATCH 149/303] chore: adding default for rpm conf --- .github/workflows/rpm-release.yml | 4 ---- packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/rpm-release.yml b/.github/workflows/rpm-release.yml index 991eac79..beb83cdd 100644 --- a/.github/workflows/rpm-release.yml +++ b/.github/workflows/rpm-release.yml @@ -38,9 +38,6 @@ jobs: run: date=$(date "+%a %b %d %Y - "); sed -i "/%changelog/ a * ${date}${{steps.tag.outputs.tag}}/" packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec - name: Edit changelog run: echo " Packaging for version ${{steps.tag.outputs.tag}}" >> packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec - - name: Troubleshoot parameters - run: | - echo "token: ${{ secrets.GITHUB_TOKEN }} run_id: ${GITHUB_RUN_ID} repository: ${GITHUB_REPOSITORY}" - name: build RPM package id: rpm uses: bpetit/rpmbuild@master @@ -53,7 +50,6 @@ jobs: id: login run: | RAW_RESULT=$(awx --conf.host "${{ secrets.AWX_HOST }}" --conf.username "${{ secrets.AWX_PUBLIC_USER }}" --conf.password "${{ secrets.AWX_PASSWORD }}" login) - echo $RAW_RESULT export AWX_TOKEN=$(echo $RAW_RESULT | jq .token | tr -d '"') echo "awx_token=${AWX_TOKEN}" >> $GITHUB_OUTPUT - name: Install and test RPM package diff --git a/packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec b/packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec index c8fd85e3..d0321110 100644 --- a/packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec +++ b/packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec @@ -30,7 +30,7 @@ cp target/debug/scaphandre $RPM_BUILD_ROOT/%{_bindir}/ chmod +x $RPM_BUILD_ROOT/%{_bindir}/scaphandre mkdir -p $RPM_BUILD_ROOT/lib/systemd/system mkdir -p $RPM_BUILD_ROOT/etc/scaphandre -echo "prometheus" > $RPM_BUILD_ROOT/etc/scaphandre/default +echo "SCAPHANDRE_ARGS=prometheus" > $RPM_BUILD_ROOT/etc/scaphandre/default mkdir -p $RPM_BUILD_ROOT/lib/systemd/system cp packaging/linux/redhat/scaphandre.service $RPM_BUILD_ROOT/lib/systemd/system/scaphandre.service From 8b2fe3fd129ea237997fa67833a8fbb02f073cf3 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 5 Jun 2023 15:59:12 +0200 Subject: [PATCH 150/303] build: switching to release build --- packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec b/packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec index d0321110..8f109694 100644 --- a/packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec +++ b/packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec @@ -19,14 +19,14 @@ BuildRequires: rust,cargo,openssl-devel,systemd-rpm-macros %autosetup %build -cargo build +cargo build --release %pre %install #rm -rf $RPM_BUILD_ROOT mkdir -p $RPM_BUILD_ROOT/%{_bindir}/ -cp target/debug/scaphandre $RPM_BUILD_ROOT/%{_bindir}/ +cp target/release/scaphandre $RPM_BUILD_ROOT/%{_bindir}/ chmod +x $RPM_BUILD_ROOT/%{_bindir}/scaphandre mkdir -p $RPM_BUILD_ROOT/lib/systemd/system mkdir -p $RPM_BUILD_ROOT/etc/scaphandre From 12571bacc509176fea0690250a9a6d0852018b96 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 5 Jun 2023 16:04:19 +0200 Subject: [PATCH 151/303] ci: trying cache for build --- .github/workflows/rpm-release.yml | 53 +++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/.github/workflows/rpm-release.yml b/.github/workflows/rpm-release.yml index beb83cdd..aa287605 100644 --- a/.github/workflows/rpm-release.yml +++ b/.github/workflows/rpm-release.yml @@ -17,6 +17,59 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 + - uses: Swatinem/rust-cache@v2 + with: + # The prefix cache key, this can be changed to start a new cache manually. + # default: "v0-rust" + prefix-key: "" + + # A cache key that is used instead of the automatic `job`-based key, + # and is stable over multiple jobs. + # default: empty + shared-key: "" + + # An additional cache key that is added alongside the automatic `job`-based + # cache key and can be used to further differentiate jobs. + # default: empty + key: "" + + # A whitespace separated list of env-var *prefixes* who's value contributes + # to the environment cache key. + # The env-vars are matched by *prefix*, so the default `RUST` var will + # match all of `RUSTC`, `RUSTUP_*`, `RUSTFLAGS`, `RUSTDOC_*`, etc. + # default: "CARGO CC CFLAGS CXX CMAKE RUST" + env-vars: "" + + # The cargo workspaces and target directory configuration. + # These entries are separated by newlines and have the form + # `$workspace -> $target`. The `$target` part is treated as a directory + # relative to the `$workspace` and defaults to "target" if not explicitly given. + # default: ". -> target" + workspaces: "" + + # Additional non workspace directories to be cached, separated by newlines. + cache-directories: "" + + # Determines whether workspace `target` directories are cached. + # If `false`, only the cargo registry will be cached. + # default: "true" + cache-targets: "" + + # Determines if the cache should be saved even when the workflow has failed. + # default: "false" + cache-on-failure: "" + + # Determines which crates are cached. + # If `true` all crates will be cached, otherwise only dependent crates will be cached. + # Useful if additional crates are used for CI tooling. + # default: "false" + cache-all-crates: "" + + # Determiners whether the cache should be saved. + # If `false`, the cache is only restored. + # Useful for jobs where the matrix is additive e.g. additional Cargo features. + # default: "true" + save-if: "" - name: Install s3cmd run: sudo apt install python3-pip -y && sudo pip3 install s3cmd awxkit - name: Get tag From 2df7e5e5397452e88db06952d508502493865edf Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 6 Jun 2023 15:10:24 +0200 Subject: [PATCH 152/303] fix: ensure hostname and instance labels are sent by prompush --- src/exporters/prometheuspush.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/exporters/prometheuspush.rs b/src/exporters/prometheuspush.rs index 26147f8d..6b1013ff 100644 --- a/src/exporters/prometheuspush.rs +++ b/src/exporters/prometheuspush.rs @@ -92,7 +92,7 @@ impl Exporter for PrometheusPushExporter { ); let mut metrics_pushed: Vec = vec![]; //let mut counter = 0; - for m in metric_generator.pop_metrics() { + for mut m in metric_generator.pop_metrics() { let mut should_i_add_help = true; if metrics_pushed.contains(&m.name) { @@ -105,10 +105,15 @@ impl Exporter for PrometheusPushExporter { let _ = write!(body, "# HELP {} {}", m.name, m.description); let _ = write!(body, "\n# TYPE {} {}\n", m.name, m.metric_type); } - let mut attributes = None; - if !m.attributes.is_empty() { - attributes = Some(&m.attributes); + if !&m.attributes.contains_key("instance") { + m.attributes + .insert(String::from("instance"), m.hostname.clone()); } + if !&m.attributes.contains_key("hostname") { + m.attributes + .insert(String::from("hostname"), m.hostname.clone()); + } + let attributes= Some(&m.attributes); let _ = write!( body, From 3eb04043f43e26c04a7e4bb74d031af88dfdb95c Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 6 Jun 2023 15:10:46 +0200 Subject: [PATCH 153/303] style: fmt --- src/exporters/prometheuspush.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/exporters/prometheuspush.rs b/src/exporters/prometheuspush.rs index 6b1013ff..fe21e266 100644 --- a/src/exporters/prometheuspush.rs +++ b/src/exporters/prometheuspush.rs @@ -113,7 +113,7 @@ impl Exporter for PrometheusPushExporter { m.attributes .insert(String::from("hostname"), m.hostname.clone()); } - let attributes= Some(&m.attributes); + let attributes = Some(&m.attributes); let _ = write!( body, From 61d5811f6b479d25ec6722319a1fcd9936ff970b Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 6 Jun 2023 16:09:59 +0200 Subject: [PATCH 154/303] feat: providing no tls check flag for prompush --- src/exporters/prometheuspush.rs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/exporters/prometheuspush.rs b/src/exporters/prometheuspush.rs index fe21e266..4c533805 100644 --- a/src/exporters/prometheuspush.rs +++ b/src/exporters/prometheuspush.rs @@ -7,6 +7,7 @@ use super::utils::{format_prometheus_metric, get_hostname}; use crate::exporters::{Exporter, MetricGenerator}; use crate::sensors::{Sensor, Topology}; use chrono::Utc; +use isahc::config::SslOption; use isahc::{prelude::*, Request}; use std::fmt::Write; use std::thread; @@ -49,6 +50,10 @@ pub struct ExporterArgs { /// Job name to apply as a label for pushed metrics #[arg(short, long, default_value_t = String::from("scaphandre"))] pub job: String, + + /// Don't verify remote TLS certificate (works with --scheme="https") + #[arg(long)] + pub no_tls_check: bool, } impl PrometheusPushExporter { @@ -113,7 +118,7 @@ impl Exporter for PrometheusPushExporter { m.attributes .insert(String::from("hostname"), m.hostname.clone()); } - let attributes = Some(&m.attributes); + let attributes= Some(&m.attributes); let _ = write!( body, @@ -121,11 +126,21 @@ impl Exporter for PrometheusPushExporter { format_prometheus_metric(&m.name, &m.metric_value.to_string(), attributes) ); } - //warn!("body: {}", body); - if let Ok(request) = Request::post(uri.clone()) - .header("Content-Type", "text/plain") + + let pre_request = Request::post(uri.clone()) .timeout(Duration::from_secs(5)) - .body(body) + .header("Content-Type", "text/plain"); + let final_request = match self.args.no_tls_check { + true => { + pre_request + .ssl_options(SslOption::DANGER_ACCEPT_INVALID_CERTS | SslOption::DANGER_ACCEPT_REVOKED_CERTS) + } + false => { + pre_request + } + }; + //warn!("body: {}", body); + if let Ok(request) = final_request.body(body) { match request.send() { Ok(mut response) => { From ab33bf2caf2cca78bef78390ae7af6e1bdadca52 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 6 Jun 2023 16:10:16 +0200 Subject: [PATCH 155/303] style: fmt --- src/exporters/prometheuspush.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/exporters/prometheuspush.rs b/src/exporters/prometheuspush.rs index 4c533805..9c57e7e3 100644 --- a/src/exporters/prometheuspush.rs +++ b/src/exporters/prometheuspush.rs @@ -118,7 +118,7 @@ impl Exporter for PrometheusPushExporter { m.attributes .insert(String::from("hostname"), m.hostname.clone()); } - let attributes= Some(&m.attributes); + let attributes = Some(&m.attributes); let _ = write!( body, @@ -126,22 +126,18 @@ impl Exporter for PrometheusPushExporter { format_prometheus_metric(&m.name, &m.metric_value.to_string(), attributes) ); } - + let pre_request = Request::post(uri.clone()) .timeout(Duration::from_secs(5)) .header("Content-Type", "text/plain"); let final_request = match self.args.no_tls_check { - true => { - pre_request - .ssl_options(SslOption::DANGER_ACCEPT_INVALID_CERTS | SslOption::DANGER_ACCEPT_REVOKED_CERTS) - } - false => { - pre_request - } + true => pre_request.ssl_options( + SslOption::DANGER_ACCEPT_INVALID_CERTS | SslOption::DANGER_ACCEPT_REVOKED_CERTS, + ), + false => pre_request, }; //warn!("body: {}", body); - if let Ok(request) = final_request.body(body) - { + if let Ok(request) = final_request.body(body) { match request.send() { Ok(mut response) => { warn!("Got {:?}", response); From 421f85060796106b353fbe63b968b6c1532d305c Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 6 Jun 2023 16:32:29 +0200 Subject: [PATCH 156/303] fix: adding invalid hosts to no tls check --- src/exporters/prometheuspush.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/exporters/prometheuspush.rs b/src/exporters/prometheuspush.rs index 9c57e7e3..1451b4a8 100644 --- a/src/exporters/prometheuspush.rs +++ b/src/exporters/prometheuspush.rs @@ -132,7 +132,9 @@ impl Exporter for PrometheusPushExporter { .header("Content-Type", "text/plain"); let final_request = match self.args.no_tls_check { true => pre_request.ssl_options( - SslOption::DANGER_ACCEPT_INVALID_CERTS | SslOption::DANGER_ACCEPT_REVOKED_CERTS, + SslOption::DANGER_ACCEPT_INVALID_CERTS + | SslOption::DANGER_ACCEPT_REVOKED_CERTS + | SslOption::DANGER_ACCEPT_INVALID_HOSTS, ), false => pre_request, }; From fd8c2eaa2f6339e2cd333a036b0f0f7ff4cf6e82 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 6 Jun 2023 18:32:56 +0200 Subject: [PATCH 157/303] style: removed useless comma --- src/exporters/prometheuspush.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/exporters/prometheuspush.rs b/src/exporters/prometheuspush.rs index 1451b4a8..d85c772f 100644 --- a/src/exporters/prometheuspush.rs +++ b/src/exporters/prometheuspush.rs @@ -134,7 +134,7 @@ impl Exporter for PrometheusPushExporter { true => pre_request.ssl_options( SslOption::DANGER_ACCEPT_INVALID_CERTS | SslOption::DANGER_ACCEPT_REVOKED_CERTS - | SslOption::DANGER_ACCEPT_INVALID_HOSTS, + | SslOption::DANGER_ACCEPT_INVALID_HOSTS ), false => pre_request, }; From 6c3174bc1ccfc9b7c1779e7f0a6b52b605a01f8b Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 6 Jun 2023 18:36:51 +0200 Subject: [PATCH 158/303] build: adding workflow to build prometheuspush only version as an rpm package --- .../workflows/rpm-release-prometheuspush.yml | 111 ++++++++++++++++++ .../SPECS/scaphandre-prometheuspush-only.spec | 57 +++++++++ 2 files changed, 168 insertions(+) create mode 100644 .github/workflows/rpm-release-prometheuspush.yml create mode 100644 packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec diff --git a/.github/workflows/rpm-release-prometheuspush.yml b/.github/workflows/rpm-release-prometheuspush.yml new file mode 100644 index 00000000..24c6c61a --- /dev/null +++ b/.github/workflows/rpm-release-prometheuspush.yml @@ -0,0 +1,111 @@ +name: Build RPM package + +on: + push: + paths-ignore: + - 'docs_src/**' + - 'README.md' + - 'CITATION' + - 'book.toml' + - 'CONTRIBUTING.md' + tags: [ 'v*.*.*', 'dev*.*.*' ] + +jobs: + build_rpm: + name: Build RPM package + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - uses: Swatinem/rust-cache@v2 + with: + # The prefix cache key, this can be changed to start a new cache manually. + # default: "v0-rust" + prefix-key: "" + + # A cache key that is used instead of the automatic `job`-based key, + # and is stable over multiple jobs. + # default: empty + shared-key: "" + + # An additional cache key that is added alongside the automatic `job`-based + # cache key and can be used to further differentiate jobs. + # default: empty + key: "" + + # A whitespace separated list of env-var *prefixes* who's value contributes + # to the environment cache key. + # The env-vars are matched by *prefix*, so the default `RUST` var will + # match all of `RUSTC`, `RUSTUP_*`, `RUSTFLAGS`, `RUSTDOC_*`, etc. + # default: "CARGO CC CFLAGS CXX CMAKE RUST" + env-vars: "" + + # The cargo workspaces and target directory configuration. + # These entries are separated by newlines and have the form + # `$workspace -> $target`. The `$target` part is treated as a directory + # relative to the `$workspace` and defaults to "target" if not explicitly given. + # default: ". -> target" + workspaces: "" + + # Additional non workspace directories to be cached, separated by newlines. + cache-directories: "" + + # Determines whether workspace `target` directories are cached. + # If `false`, only the cargo registry will be cached. + # default: "true" + cache-targets: "" + + # Determines if the cache should be saved even when the workflow has failed. + # default: "false" + cache-on-failure: "" + + # Determines which crates are cached. + # If `true` all crates will be cached, otherwise only dependent crates will be cached. + # Useful if additional crates are used for CI tooling. + # default: "false" + cache-all-crates: "" + + # Determiners whether the cache should be saved. + # If `false`, the cache is only restored. + # Useful for jobs where the matrix is additive e.g. additional Cargo features. + # default: "true" + save-if: "" + - name: Install s3cmd + run: sudo apt install python3-pip -y && sudo pip3 install s3cmd awxkit + - name: Get tag + id: tag + uses: devops-actions/action-get-tag@v1.0.2 + with: + strip_v: true # Optional: Remove 'v' character from version + default: "v0.0.0" # Optional: Default version when tag not found + - name: Override version + run: "sed -i 's/Version: .*/Version: ${{steps.tag.outputs.tag}}/' packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec" + - name: Debug + run: grep Version packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec + - name: Extract release notes + id: extract-release-notes + uses: ffurrer2/extract-release-notes@v1 + #- name: Display release notes + # run: "echo ${{ steps.extract-release-notes.outputs.release_notes }}" + - name: Edit changelog #TODO commit and push to increase changelog + run: date=$(date "+%a %b %d %Y - "); sed -i "/%changelog/ a * ${date}${{steps.tag.outputs.tag}}/" packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec + - name: Edit changelog + run: echo " Packaging for version ${{steps.tag.outputs.tag}}" >> packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec + - name: build RPM package + id: rpm + uses: bpetit/rpmbuild@master + with: + spec_file: "packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec" + - name: Upload to scw s3 + run: | + s3cmd --access_key="${{ secrets.S3_ACCESS_KEY_ID }}" --secret_key="${{ secrets.S3_SECRET_ACCESS_KEY }}" --region="fr-par" --acl-public --host="s3.fr-par.scw.cloud" --host-bucket="%(bucket).s3.fr-par.scw.cloud" put --recursive ${{ steps.rpm.outputs.rpm_dir_path }} s3://scaphandre/ + - name: Log on AWX + id: login + run: | + RAW_RESULT=$(awx --conf.host "${{ secrets.AWX_HOST }}" --conf.username "${{ secrets.AWX_PUBLIC_USER }}" --conf.password "${{ secrets.AWX_PASSWORD }}" login) + export AWX_TOKEN=$(echo $RAW_RESULT | jq .token | tr -d '"') + echo "awx_token=${AWX_TOKEN}" >> $GITHUB_OUTPUT + - name: Install and test RPM package + id: rpmtest + run: | + awx --conf.token ${{ steps.login.outputs.awx_token }} --conf.host ${{ secrets.AWX_HOST }} job_templates launch --extra_vars="{\"github_repository\":\"${GITHUB_REPOSITORY}\",\"github_actor\":\"${GITHUB_ACTOR}\",\"github_workflow\":\"${GITHUB_WORKFLOW}\",\"github_workspace\":\"${GITHUB_WORKSPACE}\",\"github_event_name\":\"${GITHUB_EVENT_NAME}\",\"github_event_path\":\"${GITHUB_EVENT_PATH}\",\"github_sha\":\"${GITHUB_SHA}\",\"github_ref\":\"${GITHUB_REF}\",\"github_head_ref\":\"${GITHUB_HEAD_REF}\",\"github_base_ref\":\"${GITHUB_BASE_REF}\",\"github_server_url\":\"${GITHUB_SERVER_URL}\",\"github_rpm_url\":\"https://scaphandre.s3.fr-par.scw.cloud/x86_64/scaphandre-prometheuspush-${{steps.tag.outputs.tag}}-1.el9.x86_64.rpm\"}" 19 --monitor \ No newline at end of file diff --git a/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec b/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec new file mode 100644 index 00000000..57b14473 --- /dev/null +++ b/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec @@ -0,0 +1,57 @@ +Name: scaphandre-prometheuspush +Version: CHANGEME +Release: 1%{?dist} +Summary: Power usage / Electricity / Energy monitoring agent + +License: Apache-2.0 +URL: https://github.com/hubblo-org/scaphandre +Source0: %{name}-%{version}.tar.gz +#Source0 will be github.com url for tar gz of source + +BuildRequires: rust,cargo,openssl-devel,systemd-rpm-macros +#Requires: + +%global debug_package %{nil} + +%description + +%prep +%autosetup + +%build +cargo build --release --no-default-features --features prometheuspush + +%pre + +%install +#rm -rf $RPM_BUILD_ROOT +mkdir -p $RPM_BUILD_ROOT/%{_bindir}/ +cp target/release/scaphandre $RPM_BUILD_ROOT/%{_bindir}/ +chmod +x $RPM_BUILD_ROOT/%{_bindir}/scaphandre +mkdir -p $RPM_BUILD_ROOT/lib/systemd/system +mkdir -p $RPM_BUILD_ROOT/etc/scaphandre +echo 'SCAPHANDRE_ARGS="prometheus-push -H localhost -S http"' > $RPM_BUILD_ROOT/etc/scaphandre/default +mkdir -p $RPM_BUILD_ROOT/lib/systemd/system +cp packaging/linux/redhat/scaphandre.service $RPM_BUILD_ROOT/lib/systemd/system/scaphandre.service + +%post +%systemd_post scaphandre.service + +%preun +%systemd_preun scaphandre.service + +%postun +%systemd_postun_with_restart scaphandre.service + +%clean +#rm -rf $RPM_BUILD_ROOT + +%files +#%doc README.md +%{_bindir}/scaphandre +/lib/systemd/system/scaphandre.service +/etc/scaphandre/default + +#%license LICENSE + +%changelog \ No newline at end of file From 3dab16fdfd3d71d6eefe1e01444377b3cf631145 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 6 Jun 2023 18:42:28 +0200 Subject: [PATCH 159/303] build: adding workflow to build prometheuspush only version as an rpm package --- .github/workflows/rpm-release-prometheuspush.yml | 2 +- .../redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rpm-release-prometheuspush.yml b/.github/workflows/rpm-release-prometheuspush.yml index 24c6c61a..c736ebe9 100644 --- a/.github/workflows/rpm-release-prometheuspush.yml +++ b/.github/workflows/rpm-release-prometheuspush.yml @@ -1,4 +1,4 @@ -name: Build RPM package +name: Build RPM package for prometheus-push only version on: push: diff --git a/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec b/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec index 57b14473..6482e7c8 100644 --- a/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec +++ b/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec @@ -19,7 +19,7 @@ BuildRequires: rust,cargo,openssl-devel,systemd-rpm-macros %autosetup %build -cargo build --release --no-default-features --features prometheuspush +cargo build --release --no-default-features --features json,prometheuspush %pre From 63ffa65190ba138703dedb22d70c88810c0431ad Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 6 Jun 2023 18:45:37 +0200 Subject: [PATCH 160/303] style: clippy --- src/exporters/prometheuspush.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/exporters/prometheuspush.rs b/src/exporters/prometheuspush.rs index d85c772f..1451b4a8 100644 --- a/src/exporters/prometheuspush.rs +++ b/src/exporters/prometheuspush.rs @@ -134,7 +134,7 @@ impl Exporter for PrometheusPushExporter { true => pre_request.ssl_options( SslOption::DANGER_ACCEPT_INVALID_CERTS | SslOption::DANGER_ACCEPT_REVOKED_CERTS - | SslOption::DANGER_ACCEPT_INVALID_HOSTS + | SslOption::DANGER_ACCEPT_INVALID_HOSTS, ), false => pre_request, }; From d9cdf208622a78dcd2ee8d075a111599cff4cfb0 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 6 Jun 2023 19:26:30 +0200 Subject: [PATCH 161/303] build: enabling to install both rpm packages in the same machine --- .../SPECS/scaphandre-prometheuspush-only.spec | 8 +-- .../redhat/scaphandre-prometheuspush.service | 55 +++++++++++++++++++ 2 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 packaging/linux/redhat/scaphandre-prometheuspush.service diff --git a/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec b/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec index 6482e7c8..00123d15 100644 --- a/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec +++ b/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec @@ -30,9 +30,9 @@ cp target/release/scaphandre $RPM_BUILD_ROOT/%{_bindir}/ chmod +x $RPM_BUILD_ROOT/%{_bindir}/scaphandre mkdir -p $RPM_BUILD_ROOT/lib/systemd/system mkdir -p $RPM_BUILD_ROOT/etc/scaphandre -echo 'SCAPHANDRE_ARGS="prometheus-push -H localhost -S http"' > $RPM_BUILD_ROOT/etc/scaphandre/default +echo 'SCAPHANDRE_ARGS="prometheus-push -H localhost -S http"' > $RPM_BUILD_ROOT/etc/scaphandre/prometheuspush mkdir -p $RPM_BUILD_ROOT/lib/systemd/system -cp packaging/linux/redhat/scaphandre.service $RPM_BUILD_ROOT/lib/systemd/system/scaphandre.service +cp packaging/linux/redhat/scaphandre-prometheuspush.service $RPM_BUILD_ROOT/lib/systemd/system/scaphandre-prometheuspush.service %post %systemd_post scaphandre.service @@ -49,8 +49,8 @@ cp packaging/linux/redhat/scaphandre.service $RPM_BUILD_ROOT/lib/systemd/system/ %files #%doc README.md %{_bindir}/scaphandre -/lib/systemd/system/scaphandre.service -/etc/scaphandre/default +/lib/systemd/system/scaphandre-prometheuspush.service +/etc/scaphandre/prometheuspush #%license LICENSE diff --git a/packaging/linux/redhat/scaphandre-prometheuspush.service b/packaging/linux/redhat/scaphandre-prometheuspush.service new file mode 100644 index 00000000..079e570b --- /dev/null +++ b/packaging/linux/redhat/scaphandre-prometheuspush.service @@ -0,0 +1,55 @@ +[Unit] +Description=Scaphandre (prometheus-push exporter) +Wants=network.target +After=network.target + +[Service] +# systemctl edit and add these if you want to further limit access +#IPAddressAllow=localhost +#IPAddressDeny=any + +ExecStartPre=-+/usr/sbin/modprobe intel_rapl_common +ExecStartPre=+/usr/bin/find /sys/devices/virtual/powercap -name energy_uj -exec chmod g+r -R {} + -exec chown root:powercap {} + +ExecStart=/usr/bin/scaphandre $SCAPHANDRE_ARGS +EnvironmentFile=/etc/scaphandre/prometheuspush + +CapabilityBoundingSet=CAP_NET_BIND_SERVICE +DevicePolicy=closed +DynamicUser=yes +Group=powercap +IPAccounting=yes +LockPersonality=yes +MemoryDenyWriteExecute=yes +MemoryMax=100M +NoNewPrivileges=yes +PrivateDevices=yes +PrivateTmp=yes +PrivateUsers=yes +ProtectClock=yes +ProtectControlGroups=yes +ProtectHome=yes +ProtectHostname=yes +ProtectKernelLogs=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +ProtectSystem=strict +RestrictAddressFamilies=AF_INET AF_INET6 +RestrictNamespaces=yes +RestrictRealtime=yes +RestrictSUIDSGID=yes +SyslogIdentifier=scaphandre +SystemCallFilter=~@cpu-emulation +SystemCallFilter=~@debug +SystemCallFilter=~@keyring +SystemCallFilter=~@module +SystemCallFilter=~@mount +SystemCallFilter=~@obsolete +SystemCallFilter=~@privileged +SystemCallFilter=~@raw-io +SystemCallFilter=~@reboot +SystemCallFilter=~@resources +SystemCallFilter=~@swap +UMask=0777 + +[Install] +WantedBy=multi-user.target \ No newline at end of file From 29f3a12ce5af35b16def9c490a852d7c3a898d8a Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 7 Jun 2023 11:53:22 +0200 Subject: [PATCH 162/303] fix: enabling install of full rpm and rpm with prompush only --- .../SPECS/scaphandre-prometheuspush-only.spec | 12 ++++++------ .../linux/redhat/scaphandre-prometheuspush.service | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec b/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec index 00123d15..a4edbc94 100644 --- a/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec +++ b/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec @@ -26,8 +26,8 @@ cargo build --release --no-default-features --features json,prometheuspush %install #rm -rf $RPM_BUILD_ROOT mkdir -p $RPM_BUILD_ROOT/%{_bindir}/ -cp target/release/scaphandre $RPM_BUILD_ROOT/%{_bindir}/ -chmod +x $RPM_BUILD_ROOT/%{_bindir}/scaphandre +cp target/release/scaphandre $RPM_BUILD_ROOT/%{_bindir}/scaphandre-prometheuspush +chmod +x $RPM_BUILD_ROOT/%{_bindir}/scaphandre-prometheuspush mkdir -p $RPM_BUILD_ROOT/lib/systemd/system mkdir -p $RPM_BUILD_ROOT/etc/scaphandre echo 'SCAPHANDRE_ARGS="prometheus-push -H localhost -S http"' > $RPM_BUILD_ROOT/etc/scaphandre/prometheuspush @@ -35,20 +35,20 @@ mkdir -p $RPM_BUILD_ROOT/lib/systemd/system cp packaging/linux/redhat/scaphandre-prometheuspush.service $RPM_BUILD_ROOT/lib/systemd/system/scaphandre-prometheuspush.service %post -%systemd_post scaphandre.service +%systemd_post scaphandre-prometheuspush.service %preun -%systemd_preun scaphandre.service +%systemd_preun scaphandre-prometheuspush.service %postun -%systemd_postun_with_restart scaphandre.service +%systemd_postun_with_restart scaphandre-prometheuspush.service %clean #rm -rf $RPM_BUILD_ROOT %files #%doc README.md -%{_bindir}/scaphandre +%{_bindir}/scaphandre-prometheuspush /lib/systemd/system/scaphandre-prometheuspush.service /etc/scaphandre/prometheuspush diff --git a/packaging/linux/redhat/scaphandre-prometheuspush.service b/packaging/linux/redhat/scaphandre-prometheuspush.service index 079e570b..ce2d498f 100644 --- a/packaging/linux/redhat/scaphandre-prometheuspush.service +++ b/packaging/linux/redhat/scaphandre-prometheuspush.service @@ -10,7 +10,7 @@ After=network.target ExecStartPre=-+/usr/sbin/modprobe intel_rapl_common ExecStartPre=+/usr/bin/find /sys/devices/virtual/powercap -name energy_uj -exec chmod g+r -R {} + -exec chown root:powercap {} + -ExecStart=/usr/bin/scaphandre $SCAPHANDRE_ARGS +ExecStart=/usr/bin/scaphandre-prometheuspush $SCAPHANDRE_ARGS EnvironmentFile=/etc/scaphandre/prometheuspush CapabilityBoundingSet=CAP_NET_BIND_SERVICE From 2b8094beb4023eac9ec725fbbd24ee180a123290 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 7 Jun 2023 12:04:46 +0200 Subject: [PATCH 163/303] ci: adding parameter for rpm test of prompush version --- .github/workflows/rpm-release-prometheuspush.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rpm-release-prometheuspush.yml b/.github/workflows/rpm-release-prometheuspush.yml index c736ebe9..6517dd1d 100644 --- a/.github/workflows/rpm-release-prometheuspush.yml +++ b/.github/workflows/rpm-release-prometheuspush.yml @@ -108,4 +108,4 @@ jobs: - name: Install and test RPM package id: rpmtest run: | - awx --conf.token ${{ steps.login.outputs.awx_token }} --conf.host ${{ secrets.AWX_HOST }} job_templates launch --extra_vars="{\"github_repository\":\"${GITHUB_REPOSITORY}\",\"github_actor\":\"${GITHUB_ACTOR}\",\"github_workflow\":\"${GITHUB_WORKFLOW}\",\"github_workspace\":\"${GITHUB_WORKSPACE}\",\"github_event_name\":\"${GITHUB_EVENT_NAME}\",\"github_event_path\":\"${GITHUB_EVENT_PATH}\",\"github_sha\":\"${GITHUB_SHA}\",\"github_ref\":\"${GITHUB_REF}\",\"github_head_ref\":\"${GITHUB_HEAD_REF}\",\"github_base_ref\":\"${GITHUB_BASE_REF}\",\"github_server_url\":\"${GITHUB_SERVER_URL}\",\"github_rpm_url\":\"https://scaphandre.s3.fr-par.scw.cloud/x86_64/scaphandre-prometheuspush-${{steps.tag.outputs.tag}}-1.el9.x86_64.rpm\"}" 19 --monitor \ No newline at end of file + awx --conf.token ${{ steps.login.outputs.awx_token }} --conf.host ${{ secrets.AWX_HOST }} job_templates launch --extra_vars="{\"github_repository\":\"${GITHUB_REPOSITORY}\",\"github_actor\":\"${GITHUB_ACTOR}\",\"github_workflow\":\"${GITHUB_WORKFLOW}\",\"github_workspace\":\"${GITHUB_WORKSPACE}\",\"github_event_name\":\"${GITHUB_EVENT_NAME}\",\"github_event_path\":\"${GITHUB_EVENT_PATH}\",\"github_sha\":\"${GITHUB_SHA}\",\"github_ref\":\"${GITHUB_REF}\",\"github_head_ref\":\"${GITHUB_HEAD_REF}\",\"github_base_ref\":\"${GITHUB_BASE_REF}\",\"github_server_url\":\"${GITHUB_SERVER_URL}\",\"github_rpm_url\":\"https://scaphandre.s3.fr-par.scw.cloud/x86_64/scaphandre-prometheuspush-${{steps.tag.outputs.tag}}-1.el9.x86_64.rpm\",\"github_package_name\":\"scaphandre-prometheuspush\"}" 19 --monitor \ No newline at end of file From 9b0959d06f496a1be0255a0bca66210313fa6c8b Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 7 Jun 2023 12:55:43 +0200 Subject: [PATCH 164/303] feat: adding raw metrics flag to stdout exporter --- src/exporters/stdout.rs | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/exporters/stdout.rs b/src/exporters/stdout.rs index 07a1257d..24708a40 100644 --- a/src/exporters/stdout.rs +++ b/src/exporters/stdout.rs @@ -1,8 +1,9 @@ use crate::exporters::*; -use crate::sensors::{utils::IProcess, Sensor}; +use crate::sensors::{utils::IProcess, Sensor, utils::current_system_time_since_epoch}; use regex::Regex; use std::thread; use std::time::{Duration, Instant}; +use std::fmt::Write; /// An Exporter that displays power consumption data of the host /// and its processes on the standard output of the terminal. @@ -44,6 +45,10 @@ pub struct ExporterArgs { /// Apply labels to metrics of processes looking like a Qemu/KVM virtual machine #[arg(short, long)] pub qemu: bool, + + /// Display metrics with their names + #[arg(long)] + pub raw_metrics: bool, } impl Exporter for StdoutExporter { @@ -102,11 +107,7 @@ impl StdoutExporter { self.show_metrics(); } - fn show_metrics(&mut self) { - use std::fmt::Write; - self.metric_generator.gen_all_metrics(); - - let metrics = self.metric_generator.pop_metrics(); + fn summarized_view(&mut self, metrics: Vec) { let mut metrics_iter = metrics.iter(); let none_value = MetricValueType::Text("0".to_string()); let host_power = match metrics_iter.find(|x| x.name == "scaph_host_power_microwatts") { @@ -221,6 +222,26 @@ impl StdoutExporter { } println!("------------------------------------------------------------\n"); } + + fn raw_metrics_view(&mut self, metrics: Vec) { + println!("## At {}", current_system_time_since_epoch().as_secs()); + for m in metrics { + let serialized_data = serde_json::to_string(&m.attributes).unwrap(); + println!("{} = {} {} # {}", m.name, m.metric_value, serialized_data, m.description); + } + } + + fn show_metrics(&mut self) { + self.metric_generator.gen_all_metrics(); + + let metrics = self.metric_generator.pop_metrics(); + + if self.args.raw_metrics { + self.raw_metrics_view(metrics); + } else { + self.summarized_view(metrics); + } + } } #[cfg(test)] From 88e25b06b87c6c0d746f12cc7ba9ac5fbb3f35c5 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 7 Jun 2023 12:56:05 +0200 Subject: [PATCH 165/303] style: clippy --- src/exporters/stdout.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/exporters/stdout.rs b/src/exporters/stdout.rs index 24708a40..e3d0717d 100644 --- a/src/exporters/stdout.rs +++ b/src/exporters/stdout.rs @@ -1,9 +1,9 @@ use crate::exporters::*; -use crate::sensors::{utils::IProcess, Sensor, utils::current_system_time_since_epoch}; +use crate::sensors::{utils::current_system_time_since_epoch, utils::IProcess, Sensor}; use regex::Regex; +use std::fmt::Write; use std::thread; use std::time::{Duration, Instant}; -use std::fmt::Write; /// An Exporter that displays power consumption data of the host /// and its processes on the standard output of the terminal. @@ -227,7 +227,10 @@ impl StdoutExporter { println!("## At {}", current_system_time_since_epoch().as_secs()); for m in metrics { let serialized_data = serde_json::to_string(&m.attributes).unwrap(); - println!("{} = {} {} # {}", m.name, m.metric_value, serialized_data, m.description); + println!( + "{} = {} {} # {}", + m.name, m.metric_value, serialized_data, m.description + ); } } @@ -237,7 +240,7 @@ impl StdoutExporter { let metrics = self.metric_generator.pop_metrics(); if self.args.raw_metrics { - self.raw_metrics_view(metrics); + self.raw_metrics_view(metrics); } else { self.summarized_view(metrics); } From 64909515b819ed17fc06947c36ef4f67143ee3ac Mon Sep 17 00:00:00 2001 From: Ross Fairbanks Date: Tue, 6 Jun 2023 15:04:46 +0200 Subject: [PATCH 166/303] fix: Don't use value for --containers flag in chart --- helm/scaphandre/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm/scaphandre/values.yaml b/helm/scaphandre/values.yaml index 1e930729..60f47c80 100644 --- a/helm/scaphandre/values.yaml +++ b/helm/scaphandre/values.yaml @@ -15,7 +15,7 @@ scaphandre: command: prometheus args: {} extraArgs: - containers: true + containers: # rustBacktrace: '1' # Run as root user to get proper permissions From 23d00d24cd94d69f57357ad0a311ddb4a7456e0a Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 12 Jun 2023 18:32:04 +0200 Subject: [PATCH 167/303] fixL prompush version doesn't need openssl --- .../redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec b/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec index a4edbc94..5d76e8c5 100644 --- a/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec +++ b/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec @@ -8,7 +8,7 @@ URL: https://github.com/hubblo-org/scaphandre Source0: %{name}-%{version}.tar.gz #Source0 will be github.com url for tar gz of source -BuildRequires: rust,cargo,openssl-devel,systemd-rpm-macros +BuildRequires: rust,cargo,systemd-rpm-macros #Requires: %global debug_package %{nil} From 3472ecc723a86b9ef49c6ea3edb796769af098cb Mon Sep 17 00:00:00 2001 From: bpetit Date: Tue, 11 Jul 2023 15:52:53 +0200 Subject: [PATCH 168/303] chore: new iss file to build exe package --- .gitignore | 1 + Cargo.lock | 18 ++++++++++ Cargo.toml | 1 + docs_src/scaphandre.ico | Bin 0 -> 15560 bytes packaging/windows/installer.iss | 61 ++++++++++++++++++++++++++++++++ 5 files changed, 81 insertions(+) create mode 100644 docs_src/scaphandre.ico create mode 100644 packaging/windows/installer.iss diff --git a/.gitignore b/.gitignore index 2c9f2c0b..8c53dab9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Generated by Cargo # will have compiled files and executables /target/ +/Output/ # These are backup files generated by rustfmt **/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock index f6291ed5..3821d73d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1485,6 +1485,7 @@ dependencies = [ "tokio", "warp10", "windows 0.27.0", + "windows-service", ] [[package]] @@ -2035,6 +2036,12 @@ dependencies = [ "webpki", ] +[[package]] +name = "widestring" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" + [[package]] name = "winapi" version = "0.3.9" @@ -2084,6 +2091,17 @@ dependencies = [ "windows-targets 0.48.0", ] +[[package]] +name = "windows-service" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9db37ecb5b13762d95468a2fc6009d4b2c62801243223aabd44fca13ad13c8" +dependencies = [ + "bitflags", + "widestring", + "windows-sys 0.45.0", +] + [[package]] name = "windows-sys" version = "0.27.0" diff --git a/Cargo.toml b/Cargo.toml index 871fdf93..5b66fd3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ procfs = { version = "0.15.0" } [target.'cfg(target_os="windows")'.dependencies] windows = { version = "0.27.0", features = ["alloc","Win32_Storage_FileSystem","Win32_Foundation","Win32_Security","Win32_System_IO","Win32_System_Ioctl"]} +windows-service = { version = "0.6.0" } [features] default = ["prometheus", "riemann", "warpten", "json", "containers", "prometheuspush"] diff --git a/docs_src/scaphandre.ico b/docs_src/scaphandre.ico new file mode 100644 index 0000000000000000000000000000000000000000..2431e35bc601b21fa84a99415ff5753657b3c0fb GIT binary patch literal 15560 zcmb_jg;x|`8=hU3?nVSbx*I`4x;vx=BqXJzC6+Gfl3uz)x>-^{5D*mUZlpVQKYsti zH*@C9oiq2`d*{Zy&-1+R1pv^~`QHHoXn}e$03drxM`@}n;9^l?Jtc9K6lJyld;8yo zf%bGWaVz`q-;=YOx-Mv{9E&e}j$`1Zz(EV?HRt)i-sVx?9>^3|Xjl|i zI5I|K$$d-_>O5o~aXay;LJ|i3gH@`3e4-+yW`I{UBahvk&fn$_q=3C+&S>(4q*9@)wPulF;DK7QLcJ*GTueZ(|Mx&I1Kd~piy z(+zz2T^pr%{eqzUVVngMA$;hhyS|cdqR=MoW~Xv^St+`)IqG`iHK{cN?5ktm)vQU@PZx$AAQ@uPi&3X_&1U2=<{9SZU8+%#5+u$31_G!4nVdIBsW4ATFCM!dO1r(B1sCfXt9Uve9 zakMMO#j+cgyXSsKVS{@i5u_!kXp~8vMAy~n z^r2y)&Y`5uFHm8&2_quB=*`RHP{JRPx1Lb2lavcfb0flB)y4+XwsnfD8-N5I1Sjj1 z7W}STXrUX=#`Ex%(D2l*Izp8Laje-#8A$TPmS79RY?2lU-G1!qBEs(p%mmQ--%(|e zg^tEW=e;qwbA5mt$CRSt_vV=?gdxe}6DemPk0-r770V4wxS@4H)*}2%WA}NX8Ba@sKT5vrU1x`4Bw8U?HHswSmx!sMt z?ApEs7_II@aoJ6&iI$%iCbTN}{CJKTXb^OI$&k=axiecZ^xj3JZqf=2c8B#*=S%PuGjS_}R1Z6&Tb0 zN*2l`#ZN^0IzA}`LSRqfam;CjNfMi`5|JeA05=y{8`uQ)9vaH1rbCM9%il;y4bp@} zBk^^so*%LFBxJ~}jrC!$1oHo~X3Zhr{^hWC9}zOtDyhOMpJ6jO5|~_>u5xROtGNyB zU8OZMBFKCM{rdK-@g5yla?9*3pQU2#LbNmMvyUmSI>`o6@k(xt=s{Ry2s|apocj-* zWiMG2KcqvaVqD$k&C7)a6@xR8NztX~`0OJXw+dHrp2XhIh9ruJ`%M?CS{Z+UrxxVNVY;$ z!Cy=ZA6n0K7-^E={)~Gs+2d_+>>4TISiwhM2 z`7M0;KnbgsCdrkNo&MFsPKwr(B+8#Z_1zulU|%JJF5xi40G{2=Z)jckM9}n<_&)Os zv8~b&(Q>JRv>qP8lK@@ByFVn`7mF-qCKT9$F+`!dJ(TFy4gDQWjL^{8dUaSam2}jP z>09Mn6XAhfJC=-n=20Ix!{ZtFXU*H$ro~&kbO})jvDTP}#Lny={VGZD7%jv0$ynQ51*~5>$6jPNg?m(?M)zfk9us`q- z!}m+MK$%6I++3aD$oTY+f?dt2(@iOD-VT&6N5|Lbg=h4jqWfj7q!(v+Z2iB}Mx_F} z1-F=A_elP>HBnD9b;N1&Eb!c!wcYK{=3+zy{*r!+)uAc|ats=T|qoxpc6MK0xS-adEgKOY4) zC7RC3`C#Ur<+NjsOC&8uiF-cGmujapSNIM>DEbEk@nH}i5aGY-IxCmtJqp6Oj(i64)aVU+OOBu%nPYE zLiLzqD;w998fR86GohsS1?Bn(f=9Q*o?#ZCj6maXM75}G8^P}NRv|y1W10ip!r`U2 z2$1$(70=nDjnVnz$6`bo8Eh# zLv=bMD1qI@DX7BV4}r6xt`RHdz@IAZsuabU+OB4CikxBmIv+#)EIWYJq!s_497H`0 zJ?4vKt|@{?kYtN&17|`DF(Y|)U%q)wXfo;1OeXU0V#iDyd$809XeoST8Y~695I`RD zu?1}WF>w7{Jy^!=6=oD>wq)IiQxibQycjvn9r1hv{n@`&G~skyTEh$>DNlB52n7!Q zODM1xsqXolYm+L%|89YPFapbK{Z{k+_H>Qvs1kADAY5a*P9yWy3v*<3=*=}r-cZE5 zft?z|Lc$5|T(1~C1B%%MjNx$@MM%dSfL`67DTqHZ`=hh9TDU)CEO2TgJ~&W8M9lxg z{<%i4DDNb(+6BAldghRyv9eqm(SA$W5*<{<*uq$xTj`&#-(#0Ndx%NgD841k-DzRz zSti#e14w}KqTXhle)QQFt~kWK^3!h;XVuOo+@sz4XaX;+qL|m^qm2pw3Sxc7s8ZP| zu{@Z+vS4*S_vY0R;#9Lk-SV<+!k-AGDX%=!0@cbcK~TE4ubB+>wGzN!7>;>n+?LE! zcz{X!TY)XBkcr4(K5>lnFY@L7Kva9l_pVv2b{uJmUPX?T7vleC}{~ zn+vVGwsOHzPF1=H|J@gqO;H;38?Ri`Kj{r19Q--}TwGX=xK6VQ^;^&y4ZKTrN&MMR zT&CY$cLYvI%DoZBsqud{`wAOK?Fb0*aAUm}CK~-zQ1t z4HVHT@O3J&?57r{-69oxZ+EDkHXpq#TgD5jQ0&%DdW_Y>0>#$gfYvVe7p!cCpj6wJ zx*y8JvW?531%onceeGYfSi5S8v`$XFk@KdD)vWdLZn0;GZOWk9*myfSa(5w)keI%Q z?>le8C*HLfTA%xCoh0y~GVh3JfFqbi-{HIT!;;4*@-{hpmv8iDH}8(T%LBUf*fQg1 zxs3Z`zD~(+P3RSzu@|4`*}CKpF5#gQIY)?Nw)C4~2cfj2j8TXYlDk-bW|!EytUZeS z#FidtX(l$~AKrneuw{7^rc&p?4I9w z$o%mBYC7m-Kh)eHtk!2WR&n@I7PIN5U6w=^E!}INFU%1&`(4mPmjF0gLVyUntE7-{ zgQSY2xA%)DE~KD?cEt~olP?h>5!+flcS(CI5>zu?E$E=D{kB=T!mxOAaLk2ztM#+) zR+$~);48-&r@?n0pK~2nx7In2B0@Q1^XzbTmZKf@8}d8@&Pv~LZod44998Eud7vZ1 z+bJ^_KZ2LX-^)0*lHZKp^`qd60sePlz;)q;56qe02e6hz1`ts40P=QQC-T}n51_|e zY}+7{1#0w5V_nVj3?i<7lkF*{MfH|8iy#~9V7%g6>A|yMexa+O<3=~}X46MHCz~gZ zv5=ZkVMYi_sr>*pZ^bZJqkn77s+n>Po4ZHn3n>SN^adAGtH04{&b}DcuzTbCCcW~; zrKP)3^abMVJ&ss>hp+oE?e0o#t=8QAMOe50eNcKj&ZZm9$<bUsWp!==$YsJVlqr}2;U&o1Q%ICMPH$BDYeP2L5 zC|qfW>zo-!{F7Dq7kev>{ny7UG@E?R`F~%A^qcP{uwY=v_Acx)VNxq!TV^idW9vkI z$HT)z%~;eRP>?Rdd6PqoCHS#UmF2V6>HEeIRp-aApy?!s6~7XMyfP0b=O^PD>B_DfK>)h97fnqs!_Nic* zM0@?Avw{!8@$G`V+G=s@J8up5C3FO-Zc`h}YK|JI(P1#^oP1}U{E3+NEzk&wexKW-@=OZdYblJW-7N9UJ#K{hpcsCOM>8z9>SyL zXp3ri*7j>N%7Ow;-_(E^_l?D@#2P|C?s1Bn>5PZ`Qdk=yGsotw_>vs)CgTBwkWvc> zaBoC=;j@Y9{cy_sl1+OFlYsyw@qJ>QnD{RruT|)`H*ll2I&m1guy=^3JE8$YK{o0wzjrp zq53X~gE>Yt9UW0~ca;Dc{+zJBWZu+bYGlBhdtZn8##y$9RT3IogJTvHSh(}DuFR>? z1D6CTq^^`t#RHvCBxH?*O$b*$IZ+$BKh^3|^KRfB@*LHE*!T;iPRqf|rt*Ph$OjAy z&YPthaGjsm$f9X~d49Q078Cr$+0hun(uYu`db<7dX;=fz6_a~tXoRTTMZ}9r)WgbI zd>;(+Bjv!mRQgcU9Vs;&zm6gwtmJy8lsCmoUsIAX;^;0fTEd(5^_oCe7^|8U+(zcj zIz{?w)<2BRG=tv1m4gUP?>W)w4G(}txaQaV_TYd`%gDq!zJ zurGkrh)*mYxPV3n;@Ir$z@b4P@<=u3D5nD7;=tK@Y%!6>0r8ZYF` zjIQ_gp3O@R-6^|oKoWr;H}Q+NeR~tCH9(Hm&Df~T4=tJg54a{P)XW%@zp(0*0Fl>J zxkuRg%?o%bzw%jp5gImZYb)%Qa_J16NK3Wmv*mfm8Z;|m6=0V;*O;39txe_5uVDMd z7w9x4<*T)|l>;>v4QRRcAikU`R2~IHqRIZB1zPA^W#KG2=A-pB2CnUhRQaBjg@w|Q z!}uuqbYMK7;VfA-SL_)PvAp~I(F&hT-I>qES^=5d>v0XX03zI2GuzlE;vfA)i%-uE>bz;?U>SP~lwR(XZ3 z#nRp-zopBMLl=d15w#b%+xRXu_56W3Jyga=y{N30hK!N;Ho$U#<}pCG5wf_Yqs=5l z4|Kh0n+^_at_Dy!^2Bj!j+&P=H#Zg5G@G;*dL&_{W&fv`Gg z#g76zHV+dlY?xY2DJ}I5t^YL`#s+^o)v{i28%fgOszBArqmGF@&$I;Bk^Qazy%ymq z(ePU34e#Yi;VaV}*Bxfm*WCgnT!k5`m)DQ?*I`m2otbWrglo#)M{}+g-+S!tG=dW8 zU;N`6Y-t4N`Unt?zM(;jHEz^;694d1*w_y!7+`v4o6bmiO{qozRn)d{!9N zbsR@>8q=<}`?D&V@DWm$OmInDHV?DC`^n`TMH&I4B7~o!_R_i7CIig|FQJZ?hA9+G zKsYuR@Ec&+rIP&66b1as)$R)-&!L5COm+G%pZWmyM|PE&|K?vz(;?Xi5lto_sqV-3 zaJW>NNupX%GqXahUGu#T2Q8h+@2$}l2AR}477qrK`5fBJB$LGwLR@7wO)jU|Mz+=0 z4J^No>L!`Mb<@jVL@0EGiVQ7T&?c>(W^vh*$azEv7qG!J?;?gkx4GW-y{^iX0UL<3 zliXb%U^O{7tZwxo+rZuF76}|Oo7-`zW?Krx zYCq41F@6FXiOzm}WaBt)WH4cWcz<4cth=iF&2=URX@F2OKFVtu-~&0d9vFWI{MT== zjY85P+Er~rh%ET)Wff5XGMX26SC3$l3UHs%ML~MLci<4KNtEe9piC9T)n|S>9<}W$ z6ox93;%Au!78xdMWvwir;bwmopV@XbCxrNYQc7tg1hx_9 zc5t*OwNfYR*gg-DmpI2qc+xU6l}X#6_$K1{qYs#C7305 zmI(|KSk-)L=yaJpHelKrzYj8X)r+h8!Fp5c!PrZ+|H4lNhz z2jSRxj$+-!!k1vhC+o+Fr)O2RM&tbY4UTSu_L@03t{lz0))IWP@mXv2SeyV2J;FnU zoR&@+g}Wp!eDq%w9wwCSVbm12VFCX{v7N?bZtaFgQ$3uh+l-K1-D&-Cao_Q`zu+O5 zCtJ=k?$=*JVPkPUca&u!>;p4RnWK9}051+j-@mGEfuLvv>=Fzh9;5^ycfP#@I*6Wy zL7<@VWb}epv#B2w+nkvQ?(iOO2V}Bb(e#<>YjE@PJpKH8682$APH5mCtIFe=k@w`# zHHOjUOVW%dW_n4l2&9*3eV=u4F zAeSozRg-07t!vH_7LaZUhAnkCYL&eM3k&~7=W^0sV1m9oT|b!$>?#Q^tjwYee&}Y? zxzyP|hMNnc8qhd9VHC)Mg=bIN$N~&%!hVtN;yK1OE!;1knaA*R!Vd}IQ@`0Tu+uzr zz>(mOhZw^?m#n-IlkW*!6sWRIgl7<{(2-@$4!yNH#=qAwzcugdIA*GeA$1_kRZl0( zAs0?k#T`(+?$a25-xso5>tnJ^eO8?5_zHE2dZnT_Ob{%5sV!`>wAPBZ%3<`JKDC>` zLIkm}gt6L-P7}uIEU~AXCn+-G^-xT3e-1GF$ZU54Q0nvu)yo;IO2czi9c<2ME1|cP0jU^fUC%Jz%bxkfvB;TS7~Y^pjS*%5(djz_Li%=u2tVb+ubnGC(D;0UzfMkJXGi$=5dL{p zU~XTE1=WIW@|7q8$#pR8~zlu{1wW5cJ_Na}u3HBF+oDJB% ze61Nw=1AEW^X8lESpweI`6GJ?~`T+{}X0O$Tpy4G78Z$YO*LMiYq}E)&PdSz(twK!VS8Z z^m`(cE8KJozQ$;P_(Vt;pUD&@T_nIap2={v+JmyQsj<>uQKrJIja}v+%YC+s{|&jX zS-X=it7`QT;CrI1uZPRnkILTIY|9L}?vW%IPaEBqAGnSvxCNV5Ib+b7;?HYj1H ze^iCPKJgT@B~gxdL$$@Zoz{>*kJ%AN-)X^hc)K!LCx^0?F{Rne0ao{;Lfe^@kNNps z_fh|wDkfutwBt3Xj%$l0NEP@};|2QVS6oP6Y&?lN?3w|LarUJT3M}mXBmHTzlm!@m z+IY#<$FB%&CzQC|oIV*H!_;gIX#F>}WH!`m&Cps|l4iQCwQRHUCAhDVRzD?g6Wty3 z;z^+x-Zi39!6tKsuJ64(ZjJ(mu?z^79hDOb&sN)WqUwaj3B63;rY8bjPFA5<^0B^@ z+p2jO5?1l<56i|g2>mKe={@ZCPi{eIC8TZ^cHWL1Mo!z0QU!hTJ=+?&h_GG!ZZ-m? zs*v`Xcq#|kW|Jks>*xBV3ZwtI6T!622f&mH{o;>$Nu-`~8s-pp5l!P5$E}SAaLrLA-V@hu>dFA088w zog)QF*^h~Tj<+$S7TU7?B`mn9|DAS#WpOSU7YF|lLI7vr4+rqvjoSLvE%LTqs`G{- z_dN(U?82L8&kjmR=^?fAx~u+8wAqDul1_@>oANC_SekI9QN}CuOhLhJ_1kxa{!Jy{ zsog@q1D>5lvY3tbbpt_Ca2FZtn@>*;!EwFSS4ALyB(pFue3C5;Z=@YXyI_5C87@(C+Fctq@L46XvM$5nc{U-laX2chBYQD9W7xoP-rn5=(7#EnXXre zHfbNxTNwWa zbK|>4sLvj;oL8Ps7LEI3S4IKCJ=+L^*p3kNUw&l;oSg}yz)u+bgjj)h(3eD+4bB}Z z0E5*Fv-v6ywUMi5pVze-reZpXR##rrzrU<3eDAdHyp~a#8yrCsAWaY~odIAq5?B;i zy4uV}FXe;=``UZZF)+Qs(fHzvH55mUKV7Oy()RdBBZgO;CvGd&>jn4ND!rCtzF^~j z>$3kl@|_OfuT1 zmt@915VBpHuQ5n`D;yDUhd+nk+uTAE;bjva9>$*=wwIiX@)){f-q-q#Cty+dt8f5E z5kqVkd5H$pYbQ2>lv^A%(Bz7O%2bGI-)jJy&;D;2VXf4M&gnTUomv!MiOuVnvcQ;p z){i3qhthZg)`e;32}N!yCW|^vRj=H;-!Jq4YR)$;!b6H+Pm;KlZvlm={j#swS{v1- zbq!=Ygh&aeQgRCqh}OOzqOX}5(yw+@MXcUeB_?F?Q>i;rqQ#mqQFh$CfG;hRd~5MD zwKvf2QKF~x7nV_#zTLAu|2$P4Uee5p0!yBdPsADuJu9&E#v2ApJ`0U$ZxqLXQ;~=v zrM^UFFU)(CFQ|LOr4oC!Zlu*llWh(DSx2?7s4X@qm+WjDy!%f$8U3(xTdi@|ru1TG zIlfthw+;6h)b|q_j5f>xpvXB5jqfFEk+OIiiAWGef?|Ehie zlekbHPY-iys89g>;?QK8 zh$s~C@fg1^^zE#8QXmQ6i9enJLVSS4=APAc;YqNiH`$rTa*43y@WDSdU>ivmzSIyJ^A4O7#V5nMAt??v$}Cn zpeW2cjj*xre+GSrmUFm+_C$+H*G{YgvB1q{OmMv%c(6$P*?nX&p|`IuJJyF5ytgtO^SLXK~R9D%iaF z@p6_QQ0dmdO2A5b{j|J>C0ETWuvA@kRG|&HaueO8sHv5@eH!EqkOT;q({v=6Me2d2 zYzwtlY7Z+#>pN`nKLhZ+w}hIx#Ek7EiR;O2+pJAc1Ng**hTfXs5p_U~>RegX#HTWMaq?AM7wWjKpF z;pF`GiPc}Q41z356OmZDTm*@=su$(lS&&d~l;f;6ve z;M3%%DFOPmjR~s#MF@lF`rR{NE*JT^{&=FYpQujHuoW}nlhlTH^ zpXc}&c|`XtBV;lOM^z9q(aO2F~;lYAu> z^aOK>fZ}D_bq!2n=x2=bqRv-YB@ptT z7ZLV+kJaS-|CN^o3=_3>i{jb8(SivQ{5dKY(9rYcyNoYE!}_#hI5@%2idfqVaM{k$ zESz)h&`x|Zplc9wpGHpwj+q*5j2PG+TvdMJB}Chs@3b8L-L7~xBPCb$k@bz{gEVb6 zk_AjD+Z&SBdAbFXV6-K+JBUimQc zj?TP|5OF4JA$BpjtN0H${io@{Yl}%PWoES`bHwC$d{6CeCj>PzpiF+V?T*Wf!6=k*no?ib?yv2Bwt-)K(x;@P?DRoCs=~@^aA( zU$Oy9tI`NI)93zt0W)Edewyg*f`T40@D@#|)b9%0GNdl+!|j2YQ6q^La+r@8DBRFY zt@c#r`WRQNy{h$*31C(XS`2@ZreLR^a0RM#q?YU%XFjmz+sol(BYB8^c9NnMV@JSG zeM0iRJqf6?NHN0r!K=IOfx>*__;{taz4_&>pERU04(W@akZ~7nle-L0JLNWN<6{2e+FY}KQ)nu_Kn0+C3 zTqSEhjCvP}fXLrE-_(Yh%{WiacfCn9v@fTd_Wofm9JbF9<^x)Kn_B$~CT6Ab1W5Y1 zooBnFu+Q8?xm^|p%vnqL?~B|U`%Ragv8x^ojoarZY#Ptj>R=^zcs7CU-7G?9GOayu zX7bPetXb}U!+rkN<&))>y#BI!jNCS-eH_u#4&2;z*D3Aq!)RIR1N$haQL7 z^1O;s?byao{_=X(<^CGAk?B8e?0w$zXUnL?X;+N>ApnVmX@|KH7Wbw-$q)kVzr70! zl?s%IJ}L%V&K*m+NjARd(aW(!w!;Jp9=b)0FJm$t8Mz(9lec^7fKa>FgL)H>AEw>( zDqUv-*;7iPEih^8+p@>X{mVhdt3iOub1g92wDKSQ8J{4ll z!xhY5MbM>gKL_3&WKpCH9nF*EBOvef6;7zb?^eDc>Q-KiMQq>;sD>2ZkN(ba74%l? z89KOSTt1STi<$MI84XBw40}9%VqRTpr_pDe9lh$#Yh6f*_eN5oL=^kG#cTj6*p7~1eCEgb>COa>$Q*wgG1_DM&7k>S-}ij&TXY#iUSE`xbrn<*vrbZo z3qDt*zLt8klLjLS1E5!Mqya!7=G96gj43NS2VDZlKk@KOT4INgscB5wj|4||>lQy; zK#nqPfBZ1QT7rY~1Y-sg(!Q*jjANfE6)UK0hC}@6oK3H;QSK+V4s4*&kZTp$asOg; zC5DW1c8oeM&hS{%4{xgtEKV#L&kUvce%*zN2^~W`-=;P#=`E&20-MBv!+YYZ7w$QUF(k0kHtp)ozAI}c;t z+R3M;=0RYSa+n=2&G3ye5+XM^QyL2uQVbJ-j~Lj0v7REuVWnj~Px^tWVAi9aR{&W{ z*dl8>>5AJ6+Cg)XwRz2cotW82V$|?+VT!-!n!<=Du{@#2xqG!y(a=z|q_U2EIQRhyPjvcxBmw1gMrK7%8vUxI9Yfquj}cK(x&9a{)S zY0dNW)lEQW6hJ0g52*JL;BIlV!5lpy?C#G)3l5JC5i^;c4JHj_5En{LK!QZ?YwiJf z_}4fuU{uCQF1vTWrg>>kHN&Ay&|>Qq6&zM zJVnal6!*4HH{yYFGl@}u*A6~~G3Vz^{M4^=6Mg<5v$90Jb$keS#P+hzliKkhw~nC@ zPXW#@&t7^Pzi0oom;{V5xS%b8^<2Jp^z=UqdeL|v!y#$kz#;iZs9dI4Mk&O=(3c4m zDFQ`!=VLpfW(^q5_ki#E|PR;i%-V?g$JbAA$nV>IJfc2YW?i0E|PA*@! z1B>M3hCZ0vdq?)C(CU+VG~|oX(WAfVPRUdUn@f4a;o!KE*`CO(e%qhGg*gcWZVIJ5 zmOkwdQ>N7i75hqvq?=~|4!a-wGw>5GXe)k*5am5{rbmG~ zuT}+a@1GpU`@anmSVha>V_<+C&UR2(OyB@c+1eW|`Tussz9VWNv=9*HNw9pJ* z|BJ3ENqt_)}*n(jjxgxiX=hsr7| zlrim}O`v~ntEKF!ft4W1oQG%gR9H#q^+^}*V1PA|BPgDlTI_X;q;)dDW;TVTpMU-5 z9=+||@ygumKv6;LQXCOO;2zlp49UX^7=$sMxwFP6H}u9dKkEUc=sCI1e>>;|9FaY^ zd(ecFkwOE9XGa1CNugbA1T4=j_KDcZ8k&Tmk*KipiC#FFl!Q?HooDe%;IzM;USs3t zf|8bYP%bvq7ZVg24gakA5gseY@~V9zA;T%Wbxs^8z2M>(Z!b!^9~m*Q&yDwlhV&zIbG-{mp`EHc9jX-*t7!8!p4d6 zh&P5{HC!1oZ9^A>__(W%gY#+R=cKcA0U=~L!pzbfyz4K_*Ik>Eb!d4+;69$ZD^QF+ zLb*F)CpkLRk+olVJWcF}NZXT$xxVodcs$-pH1A3ThA#QPgZeaox_j~~Yj7PGL(4i8 zK97uD4Wu1#jv?{m)LW0!{Bp?IWoy5nOu`Zv=9XFsu0B<|6pjQAt0m_}hIGql=px9b zkd%!G`J_GM&Z$-vP8T66F}k8pvaNO_*eh9ozC2dJAxVLWCz z+22^(Q+Xk=SUOs%q|0z^DR$d9^VaJ19Jy+MTa^v7^RTz*maz}8=4C3>*`E~9-BV+AN=}?;vu{Me( z@+uvLe<(rDNJ^Q^*yN>TgnGKEyp+fd&=92;PvgM2GxLqh)kB#oICFm2y?lVD7a)IN zZti9_y-DjTxqDucvPyz|!r>Oi9EKFHc*dNH&~aC?V0 znZx2=lktFyE0{|mpVzATs&9Sga@;Sj&H2!g6o0A!R}S6b3D;h~HC9{>!^N8WT6v$y zVR0|IEF_L7OE!;UHm(An_uobrRXw=5Ue!^@z4OG=x>qlkIC}U|M}t!&ELl(f8*MQP z+vd43Xo=@6PyOSMF6UQ|lOqvYz514$X5K;kR2$z(8o^&WeWJ4X*v=e^^qmK z-@G+8gq1G}FA=sFhV97zl*0k4)XvG;yKE#Ee_9kN6 zvd-XT2hWaKR&DxY+6VD8#Fdk2*%v&9Agwn;i59+dE+*bv+fdqBG@4} z|GZr2yzZ>s89=ION~m{Izt)NZb{$4nc)bWe=);IVA;#4LQIw%=U7{gVYXl16u{Dx` zCVt3;!#V;RRFUZmXSf55{9R7lYQ$6q%gpDj=MiGT*r%OSq^o>uanu&`qZcdYleq0a zbZf?AlBRrwBS-VNqt&ch)};Nui@p150^%arPbmuoB{?V9ZVYJ!%BK&Ys5XIYF)w-- zB#h!lxPGpBOQMrK%uIXN`gL)q-|@A7g?zx_n;_R+>*HvDhQG?zi+6Uvd7QYfVyCry2v1SeN=86W#{y@(K z5f<4#{9Ead;u<@G0{RLoyfkO;y~`LS1;MBz76=ncu0OV~SmW75yX2A>LOv*{^ z)80*GPAR^B^h~eB{o_+ayJ@)j!XuKQdt$ydKePBCZYBdiI733xYCwq~YF4HnFrfjg zu@L2$&|m0SmBXX9=LaQNKT7eKd>&I!0=_t*vylWFx=ke?!JG)%Ao%OS%aLv+jD%k~ zQRpDrk@O{rOG1o_0|bi0ZnQ5^>r*TmzLo%@NkqxQ_rmw_UAJ%I)P%Zw`Wnqo1IF{# zhIIo^=f*ZhtNqbSJWY|Z84i;pciY$m?2Zul8O5v?Ln>H=*r<%CS!L&vXI19 zIC;TCzu4zV>ioaKTAkI(K~V;ai6PC1KkH5NH_ysy`*?|8zj+5sU%B33pi|Gd_?TqE zwu$oQixUl%>RD16tAbKQ5_+WMdntRKaheC*@yhRV5)tA01rL#Kl*Lo;v`@_29P6RG zkQLLE>r+vR2t*fmfI`djNo#3vLfn35(XS{|$18qc3c}SI&wzqFT1d!OJ^Ey~WWOSl zoWnT3u0x?4FMFXYl2L{fcU`=C60&>_Im6icp_ybCiP2fIGo&UWIw{8OuWq8jB1n4T z^KcQ|hm&D;M)1~)#Vp@Zuxlli#W`}OOdX+`TLv76EtHVRl z1zur9jZE9{@zptS&4-knql(b5I$MNVDdv0$?CFdwZ@WQduF4R0@Q`q%>a&0n_XypgD5fRBj%aHT6dBp zGi4w^-TBlI*FAf_=mb?MB=l)tZ9_5i4=A>QD4H&1a4}HQX)dj*P1A$w4cCghz@P1n zMFL-^(cYaE$Ao#+v&`i@JN?r8?Im$Ln6B8BL{Z;q&o=hh|9N}v(_Y^~{Z%AN8m{g~ VN#pLmr%lL!lAOA1jkJ02{{T$RRMh|g literal 0 HcmV?d00001 diff --git a/packaging/windows/installer.iss b/packaging/windows/installer.iss new file mode 100644 index 00000000..844ee657 --- /dev/null +++ b/packaging/windows/installer.iss @@ -0,0 +1,61 @@ + ; Script generated by the Inno Setup Script Wizard. +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! + +#define MyAppName "scaphandre" +#define MyAppVersion "0.5.0" +#define MyAppPublisher "Hubblo" +#define MyAppURL "https://hubblo-org.github.io/scaphandre-documentation" +#define MyAppExeName "scaphandre.exe" +#define MyAppSourceFolder "C:\Users\bpeti\Documents\GitHub\scaphandre" +#define RaplDriverSourceFolder "C:\Users\bpeti\Documents\GitHub\windows-rapl-driver" + +[Setup] +; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. +; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) +AppId={{7DB7B851-1DD2-4FF5-BFC7-282FEBA3B28D} +AppName={#MyAppName} +AppVersion={#MyAppVersion} +;AppVerName={#MyAppName} {#MyAppVersion} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +AppSupportURL={#MyAppURL} +AppUpdatesURL={#MyAppURL} +DefaultDirName={autopf}\{#MyAppName} +DefaultGroupName={#MyAppName} +LicenseFile=C:\Users\bpeti\Documents\GitHub\scaphandre\LICENSE +; Uncomment the following line to run in non administrative install mode (install for current user only.) +;PrivilegesRequired=lowest +OutputBaseFilename={#MyAppName}_{#MyAppVersion}_installer +Compression=lzma +SolidCompression=yes +WizardStyle=modern +Uninstallable=yes +SetupIconFile=C:\Users\bpeti\Documents\GitHub\scaphandre\docs_src\scaphandre.ico + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Files] +Source: "{#MyAppSourceFolder}\target\release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\DriverLoader.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.inf"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.sys"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.cat"; DestDir: "{app}"; Flags: ignoreversion +Source: "C:\Program Files (x86)\Windows Kits\10\Tools\10.0.22621.0\x64\devcon.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\certmgr.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#MyAppSourceFolder}\README.md"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#MyAppSourceFolder}\CHANGELOG.md"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#RaplDriverSourceFolder}\ScaphandreDrvTest.cer"; DestDir: "{app}"; Flags: ignoreversion +; NOTE: Don't use "Flags: ignoreversion" on any shared system files + +[Icons] +Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" + +[Run] +; Filename: "{app}/DriverLoader.exe"; Parameters: "install"; WorkingDir: "{app}"; Description: "Install MSR/RAPL Driver ?"; Flags: postinstall +; Filename: "{app}/DriverLoader.exe"; Parameters: "start"; WorkingDir: "{app}"; Description: "Install MSR/RAPL Driver ?"; Flags: postinstall +Filename: "C:\windows\System32\WindowsPowershell\v1.0\powershell.exe"; Parameters: "Import-Certificate -FilePath {app}\ScaphandreDrvTest.cer -CertStoreLocation Cert:\LocalMachine\Root"; Description: "Register test certificate"; Flags: waituntilidle shellexec +Filename: "{app}\devcon.exe"; Parameters: "install {app}\ScaphandreDrv.inf root\SCAPHANDREDRV"; Description: "Install MSR/RAPL Driver ?"; Flags: postinstall waituntilidle +Filename: "{app}\devcon.exe"; Parameters: "enable {app}\ScaphandreDrv.inf root\SCAPHANDREDRV"; Description: "Enable MSR/RAPL Driver ?"; Flags: postinstall waituntilidle +; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; + From 31637202d01891a75a088befe7235a3c7af74a37 Mon Sep 17 00:00:00 2001 From: bpetit Date: Wed, 12 Jul 2023 08:44:42 +0200 Subject: [PATCH 169/303] fix: adding instance hostname label to grouping key --- src/exporters/prometheuspush.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/exporters/prometheuspush.rs b/src/exporters/prometheuspush.rs index 1451b4a8..c72b5328 100644 --- a/src/exporters/prometheuspush.rs +++ b/src/exporters/prometheuspush.rs @@ -3,6 +3,20 @@ //! `PrometheusPushExporter` implementation, push/send metrics to //! a [Prometheus](https://prometheus.io/) pushgateway. //! + +#[cfg(target_os="windows")] +extern crate windows_service; +#[cfg(target_os="windows")] +use std::{ + ffi::OsString +}; +#[cfg(target_os="windows")] +use windows_service::{ + service::ServiceControl, service::ServiceControlAccept, + service::ServiceExitCode, service::ServiceState, service::ServiceStatus, + service::ServiceType, service_control_handler::{self, ServiceControlHandlerResult} +}; + use super::utils::{format_prometheus_metric, get_hostname}; use crate::exporters::{Exporter, MetricGenerator}; use crate::sensors::{Sensor, Topology}; @@ -78,8 +92,8 @@ impl Exporter for PrometheusPushExporter { ); let uri = format!( - "{}://{}:{}/{}/job/{}", - self.args.scheme, self.args.host, self.args.port, self.args.suffix, self.args.job + "{}://{}:{}/{}/job/{}/instance/{}", + self.args.scheme, self.args.host, self.args.port, self.args.suffix, self.args.job, self.hostname.clone() ); let mut metric_generator = MetricGenerator::new( From 57fda6c8b255843009685e9fadd179b44f04470d Mon Sep 17 00:00:00 2001 From: bpetit Date: Wed, 12 Jul 2023 08:45:36 +0200 Subject: [PATCH 170/303] chore: excluding Output iss folders from git history --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8c53dab9..98738b5b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # Generated by Cargo # will have compiled files and executables /target/ -/Output/ +**/Output/ # These are backup files generated by rustfmt **/*.rs.bk From 771cf9c7c37ea119e1edb4d228b74b718da7aae1 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 14 Jun 2023 18:24:51 +0200 Subject: [PATCH 171/303] build: disabling auto dependencies --- .../redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec | 1 + 1 file changed, 1 insertion(+) diff --git a/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec b/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec index 5d76e8c5..6fbd93c8 100644 --- a/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec +++ b/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec @@ -10,6 +10,7 @@ Source0: %{name}-%{version}.tar.gz BuildRequires: rust,cargo,systemd-rpm-macros #Requires: +AutoReqProv: no %global debug_package %{nil} From c9004eb652a85cad803c93b19f287fbde3e83807 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 14 Jun 2023 20:22:11 +0200 Subject: [PATCH 172/303] ci: adding workflow for rhel8 --- .../workflows/rpm-release-prometheuspush.yml | 102 +++++++++++++++++- 1 file changed, 100 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rpm-release-prometheuspush.yml b/.github/workflows/rpm-release-prometheuspush.yml index 6517dd1d..fa899516 100644 --- a/.github/workflows/rpm-release-prometheuspush.yml +++ b/.github/workflows/rpm-release-prometheuspush.yml @@ -11,8 +11,8 @@ on: tags: [ 'v*.*.*', 'dev*.*.*' ] jobs: - build_rpm: - name: Build RPM package + build_rpm_rhel9: + name: Build RPM package for RHEL9 runs-on: ubuntu-latest steps: - name: Checkout @@ -105,6 +105,104 @@ jobs: RAW_RESULT=$(awx --conf.host "${{ secrets.AWX_HOST }}" --conf.username "${{ secrets.AWX_PUBLIC_USER }}" --conf.password "${{ secrets.AWX_PASSWORD }}" login) export AWX_TOKEN=$(echo $RAW_RESULT | jq .token | tr -d '"') echo "awx_token=${AWX_TOKEN}" >> $GITHUB_OUTPUT + - name: Install and test RPM package + id: rpmtest + run: | + awx --conf.token ${{ steps.login.outputs.awx_token }} --conf.host ${{ secrets.AWX_HOST }} job_templates launch --extra_vars="{\"github_repository\":\"${GITHUB_REPOSITORY}\",\"github_actor\":\"${GITHUB_ACTOR}\",\"github_workflow\":\"${GITHUB_WORKFLOW}\",\"github_workspace\":\"${GITHUB_WORKSPACE}\",\"github_event_name\":\"${GITHUB_EVENT_NAME}\",\"github_event_path\":\"${GITHUB_EVENT_PATH}\",\"github_sha\":\"${GITHUB_SHA}\",\"github_ref\":\"${GITHUB_REF}\",\"github_head_ref\":\"${GITHUB_HEAD_REF}\",\"github_base_ref\":\"${GITHUB_BASE_REF}\",\"github_server_url\":\"${GITHUB_SERVER_URL}\",\"github_rpm_url\":\"https://scaphandre.s3.fr-par.scw.cloud/x86_64/scaphandre-prometheuspush-${{steps.tag.outputs.tag}}-1.el9.x86_64.rpm\",\"github_package_name\":\"scaphandre-prometheuspush\"}" 19 --monitor + build_rpm_rhel8: + name: Build RPM package for RHEL8 + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - uses: Swatinem/rust-cache@v2 + with: + # The prefix cache key, this can be changed to start a new cache manually. + # default: "v0-rust" + prefix-key: "" + + # A cache key that is used instead of the automatic `job`-based key, + # and is stable over multiple jobs. + # default: empty + shared-key: "" + + # An additional cache key that is added alongside the automatic `job`-based + # cache key and can be used to further differentiate jobs. + # default: empty + key: "" + + # A whitespace separated list of env-var *prefixes* who's value contributes + # to the environment cache key. + # The env-vars are matched by *prefix*, so the default `RUST` var will + # match all of `RUSTC`, `RUSTUP_*`, `RUSTFLAGS`, `RUSTDOC_*`, etc. + # default: "CARGO CC CFLAGS CXX CMAKE RUST" + env-vars: "" + + # The cargo workspaces and target directory configuration. + # These entries are separated by newlines and have the form + # `$workspace -> $target`. The `$target` part is treated as a directory + # relative to the `$workspace` and defaults to "target" if not explicitly given. + # default: ". -> target" + workspaces: "" + + # Additional non workspace directories to be cached, separated by newlines. + cache-directories: "" + + # Determines whether workspace `target` directories are cached. + # If `false`, only the cargo registry will be cached. + # default: "true" + cache-targets: "" + + # Determines if the cache should be saved even when the workflow has failed. + # default: "false" + cache-on-failure: "" + + # Determines which crates are cached. + # If `true` all crates will be cached, otherwise only dependent crates will be cached. + # Useful if additional crates are used for CI tooling. + # default: "false" + cache-all-crates: "" + + # Determiners whether the cache should be saved. + # If `false`, the cache is only restored. + # Useful for jobs where the matrix is additive e.g. additional Cargo features. + # default: "true" + save-if: "" + - name: Install s3cmd + run: sudo apt install python3-pip -y && sudo pip3 install s3cmd awxkit + - name: Get tag + id: tag + uses: devops-actions/action-get-tag@v1.0.2 + with: + strip_v: true # Optional: Remove 'v' character from version + default: "v0.0.0" # Optional: Default version when tag not found + - name: Override version + run: "sed -i 's/Version: .*/Version: ${{steps.tag.outputs.tag}}/' packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec" + - name: Debug + run: grep Version packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec + - name: Extract release notes + id: extract-release-notes + uses: ffurrer2/extract-release-notes@v1 + #- name: Display release notes + # run: "echo ${{ steps.extract-release-notes.outputs.release_notes }}" + - name: Edit changelog #TODO commit and push to increase changelog + run: date=$(date "+%a %b %d %Y - "); sed -i "/%changelog/ a * ${date}${{steps.tag.outputs.tag}}/" packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec + - name: Edit changelog + run: echo " Packaging for version ${{steps.tag.outputs.tag}}" >> packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec + - name: build RPM package + id: rpm + uses: bpetit/rpmbuild@rhel8 + with: + spec_file: "packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec" + - name: Upload to scw s3 + run: | + s3cmd --access_key="${{ secrets.S3_ACCESS_KEY_ID }}" --secret_key="${{ secrets.S3_SECRET_ACCESS_KEY }}" --region="fr-par" --acl-public --host="s3.fr-par.scw.cloud" --host-bucket="%(bucket).s3.fr-par.scw.cloud" put --recursive ${{ steps.rpm.outputs.rpm_dir_path }} s3://scaphandre/ + - name: Log on AWX + id: login + run: | + RAW_RESULT=$(awx --conf.host "${{ secrets.AWX_HOST }}" --conf.username "${{ secrets.AWX_PUBLIC_USER }}" --conf.password "${{ secrets.AWX_PASSWORD }}" login) + export AWX_TOKEN=$(echo $RAW_RESULT | jq .token | tr -d '"') + echo "awx_token=${AWX_TOKEN}" >> $GITHUB_OUTPUT - name: Install and test RPM package id: rpmtest run: | From a5c8c0a7c0866f1909e24fd46f8258137b4f5e47 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Thu, 15 Jun 2023 11:33:07 +0200 Subject: [PATCH 173/303] build: reenabling auto req search for rhel8 --- .../redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec b/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec index 6fbd93c8..0f6b2e18 100644 --- a/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec +++ b/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec @@ -9,8 +9,7 @@ Source0: %{name}-%{version}.tar.gz #Source0 will be github.com url for tar gz of source BuildRequires: rust,cargo,systemd-rpm-macros -#Requires: -AutoReqProv: no +#Requires: %global debug_package %{nil} From 94df03077bd9d508f270a95f4d05a3c9efd256f8 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Thu, 15 Jun 2023 11:42:48 +0200 Subject: [PATCH 174/303] build: disabling service file config that seem unknown by rhel8 --- packaging/linux/redhat/scaphandre-prometheuspush.service | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packaging/linux/redhat/scaphandre-prometheuspush.service b/packaging/linux/redhat/scaphandre-prometheuspush.service index ce2d498f..533fc91f 100644 --- a/packaging/linux/redhat/scaphandre-prometheuspush.service +++ b/packaging/linux/redhat/scaphandre-prometheuspush.service @@ -25,11 +25,11 @@ NoNewPrivileges=yes PrivateDevices=yes PrivateTmp=yes PrivateUsers=yes -ProtectClock=yes +#ProtectClock=yes ProtectControlGroups=yes ProtectHome=yes -ProtectHostname=yes -ProtectKernelLogs=yes +#ProtectHostname=yes +#ProtectKernelLogs=yes ProtectKernelModules=yes ProtectKernelTunables=yes ProtectSystem=strict From 65bef6f8357597af77b1e1ac63dbec3a1ded72eb Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 12 Jul 2023 10:53:28 +0200 Subject: [PATCH 175/303] fix: disabling prompush text on bare metal for now --- .../workflows/rpm-release-prometheuspush.yml | 20 +++++++++---------- .../SPECS/scaphandre-prometheuspush-only.spec | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/rpm-release-prometheuspush.yml b/.github/workflows/rpm-release-prometheuspush.yml index fa899516..b34d7746 100644 --- a/.github/workflows/rpm-release-prometheuspush.yml +++ b/.github/workflows/rpm-release-prometheuspush.yml @@ -197,13 +197,13 @@ jobs: - name: Upload to scw s3 run: | s3cmd --access_key="${{ secrets.S3_ACCESS_KEY_ID }}" --secret_key="${{ secrets.S3_SECRET_ACCESS_KEY }}" --region="fr-par" --acl-public --host="s3.fr-par.scw.cloud" --host-bucket="%(bucket).s3.fr-par.scw.cloud" put --recursive ${{ steps.rpm.outputs.rpm_dir_path }} s3://scaphandre/ - - name: Log on AWX - id: login - run: | - RAW_RESULT=$(awx --conf.host "${{ secrets.AWX_HOST }}" --conf.username "${{ secrets.AWX_PUBLIC_USER }}" --conf.password "${{ secrets.AWX_PASSWORD }}" login) - export AWX_TOKEN=$(echo $RAW_RESULT | jq .token | tr -d '"') - echo "awx_token=${AWX_TOKEN}" >> $GITHUB_OUTPUT - - name: Install and test RPM package - id: rpmtest - run: | - awx --conf.token ${{ steps.login.outputs.awx_token }} --conf.host ${{ secrets.AWX_HOST }} job_templates launch --extra_vars="{\"github_repository\":\"${GITHUB_REPOSITORY}\",\"github_actor\":\"${GITHUB_ACTOR}\",\"github_workflow\":\"${GITHUB_WORKFLOW}\",\"github_workspace\":\"${GITHUB_WORKSPACE}\",\"github_event_name\":\"${GITHUB_EVENT_NAME}\",\"github_event_path\":\"${GITHUB_EVENT_PATH}\",\"github_sha\":\"${GITHUB_SHA}\",\"github_ref\":\"${GITHUB_REF}\",\"github_head_ref\":\"${GITHUB_HEAD_REF}\",\"github_base_ref\":\"${GITHUB_BASE_REF}\",\"github_server_url\":\"${GITHUB_SERVER_URL}\",\"github_rpm_url\":\"https://scaphandre.s3.fr-par.scw.cloud/x86_64/scaphandre-prometheuspush-${{steps.tag.outputs.tag}}-1.el9.x86_64.rpm\",\"github_package_name\":\"scaphandre-prometheuspush\"}" 19 --monitor \ No newline at end of file + #- name: Log on AWX + # id: login + # run: | + # RAW_RESULT=$(awx --conf.host "${{ secrets.AWX_HOST }}" --conf.username "${{ secrets.AWX_PUBLIC_USER }}" --conf.password "${{ secrets.AWX_PASSWORD }}" login) + # export AWX_TOKEN=$(echo $RAW_RESULT | jq .token | tr -d '"') + # echo "awx_token=${AWX_TOKEN}" >> $GITHUB_OUTPUT + #- name: Install and test RPM package + # id: rpmtest + # run: | + # awx --conf.token ${{ steps.login.outputs.awx_token }} --conf.host ${{ secrets.AWX_HOST }} job_templates launch --extra_vars="{\"github_repository\":\"${GITHUB_REPOSITORY}\",\"github_actor\":\"${GITHUB_ACTOR}\",\"github_workflow\":\"${GITHUB_WORKFLOW}\",\"github_workspace\":\"${GITHUB_WORKSPACE}\",\"github_event_name\":\"${GITHUB_EVENT_NAME}\",\"github_event_path\":\"${GITHUB_EVENT_PATH}\",\"github_sha\":\"${GITHUB_SHA}\",\"github_ref\":\"${GITHUB_REF}\",\"github_head_ref\":\"${GITHUB_HEAD_REF}\",\"github_base_ref\":\"${GITHUB_BASE_REF}\",\"github_server_url\":\"${GITHUB_SERVER_URL}\",\"github_rpm_url\":\"https://scaphandre.s3.fr-par.scw.cloud/x86_64/scaphandre-prometheuspush-${{steps.tag.outputs.tag}}-1.el8.x86_64.rpm\",\"github_package_name\":\"scaphandre-prometheuspush\"}" 19 --monitor diff --git a/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec b/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec index 0f6b2e18..f6bc21ee 100644 --- a/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec +++ b/packaging/linux/redhat/rpmbuild/SPECS/scaphandre-prometheuspush-only.spec @@ -8,7 +8,7 @@ URL: https://github.com/hubblo-org/scaphandre Source0: %{name}-%{version}.tar.gz #Source0 will be github.com url for tar gz of source -BuildRequires: rust,cargo,systemd-rpm-macros +BuildRequires: rust,cargo,systemd-rpm-macros #Requires: %global debug_package %{nil} @@ -54,4 +54,4 @@ cp packaging/linux/redhat/scaphandre-prometheuspush.service $RPM_BUILD_ROOT/lib/ #%license LICENSE -%changelog \ No newline at end of file +%changelog From 4e34871a165ca7ce9c7d88e837a73d43b4263d57 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 12 Jul 2023 10:59:44 +0200 Subject: [PATCH 176/303] fix: adding instance hostname to pushgateway grouping key --- src/exporters/prometheuspush.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/exporters/prometheuspush.rs b/src/exporters/prometheuspush.rs index 1451b4a8..a4c8e523 100644 --- a/src/exporters/prometheuspush.rs +++ b/src/exporters/prometheuspush.rs @@ -78,8 +78,8 @@ impl Exporter for PrometheusPushExporter { ); let uri = format!( - "{}://{}:{}/{}/job/{}", - self.args.scheme, self.args.host, self.args.port, self.args.suffix, self.args.job + "{}://{}:{}/{}/job/{}/instance/{}", + self.args.scheme, self.args.host, self.args.port, self.args.suffix, self.args.job, self.hostname.clone() ); let mut metric_generator = MetricGenerator::new( From b24c4ca0cc37988cbdc2b0cedd27706a0aca17e3 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 12 Jul 2023 11:03:04 +0200 Subject: [PATCH 177/303] style: fmt --- src/exporters/prometheuspush.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/exporters/prometheuspush.rs b/src/exporters/prometheuspush.rs index a4c8e523..3932c6c6 100644 --- a/src/exporters/prometheuspush.rs +++ b/src/exporters/prometheuspush.rs @@ -79,7 +79,12 @@ impl Exporter for PrometheusPushExporter { let uri = format!( "{}://{}:{}/{}/job/{}/instance/{}", - self.args.scheme, self.args.host, self.args.port, self.args.suffix, self.args.job, self.hostname.clone() + self.args.scheme, + self.args.host, + self.args.port, + self.args.suffix, + self.args.job, + self.hostname.clone() ); let mut metric_generator = MetricGenerator::new( From cbe2c4974d7f2b8d6e176ba0b61caf993e478752 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 12 Jul 2023 11:22:28 +0200 Subject: [PATCH 178/303] docs: adding prometheus-push exporter reference doc --- .../references/exporter-prometheuspush.md | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 docs_src/references/exporter-prometheuspush.md diff --git a/docs_src/references/exporter-prometheuspush.md b/docs_src/references/exporter-prometheuspush.md new file mode 100644 index 00000000..5b86b6c2 --- /dev/null +++ b/docs_src/references/exporter-prometheuspush.md @@ -0,0 +1,34 @@ +# PrometheusPush Exporter for Prometheus Push Gateway + +## Usage + +You can launch the prometheus exporter this way (running the default powercap_rapl sensor): + + scaphandre prometheus-push + +As always exporter's options can be displayed with `-h`: +``` + scaphandre prometheus-push -h + Push metrics to Prometheus Push Gateway + + Usage: scaphandre prometheus-push [OPTIONS] + + Options: + -H, --host IP address (v4 or v6) of the metrics endpoint for Prometheus [default: localhost] + -p, --port TCP port of the metrics endpoint for Prometheus [default: 9091] + --suffix [default: metrics] + -S, --scheme [default: http] + -s, --step [default: 5] + --qemu Apply labels to metrics of processes that look like a Qemu/KVM virtual machine + --containers Apply labels to metrics of processes running as containers + -j, --job Job name to apply as a label for pushed metrics [default: scaphandre] + --no-tls-check Don't verify remote TLS certificate (works with --scheme="https") + -h, --help Print help +``` +With default options values, the metrics are sent to http://localhost:9091/metrics + +## Metrics exposed + +Metrics exposed are the same as the Prometheus (pull mode) exporter. + +Push gateway's grouping key for each host is in the form `job/scaphandre/instance/${HOSTNAME}` with HOSTNAME being the hostname of the host sending metrics. \ No newline at end of file From 45dd30ffe141246a6b5766373c05f5b1d2452571 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 12 Jul 2023 13:44:23 +0200 Subject: [PATCH 179/303] docs: adding promepush on rhel use case doc --- docs_src/SUMMARY.md | 1 + .../install-prometheuspush-only-rhel.md | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 docs_src/how-to_guides/install-prometheuspush-only-rhel.md diff --git a/docs_src/SUMMARY.md b/docs_src/SUMMARY.md index 9fb99bfd..34dbd8dc 100644 --- a/docs_src/SUMMARY.md +++ b/docs_src/SUMMARY.md @@ -14,6 +14,7 @@ - [Propagate power consumption metrics from hypervisor to virtual machines (Qemu/KVM)](how-to_guides/propagate-metrics-hypervisor-to-vm_qemu-kvm.md) - [Get process-level power consumption in my grafana dashboard](how-to_guides/get-process-level-power-in-grafana.md) +- [Install Scaphandre with only Prometheus-push exporter compiled, for Prometheus Push Gateway, on RHEL 8 and 9](how-to_guides/install-prometheuspush-only-rhel.md) # Explanations diff --git a/docs_src/how-to_guides/install-prometheuspush-only-rhel.md b/docs_src/how-to_guides/install-prometheuspush-only-rhel.md new file mode 100644 index 00000000..06c3a607 --- /dev/null +++ b/docs_src/how-to_guides/install-prometheuspush-only-rhel.md @@ -0,0 +1,37 @@ +# Install Scaphandre with only Prometheus-push exporter compiled, for Prometheus Push Gateway, on RHEL 8 and 9 + +## Manual installation + +Scaphandre can be compiled with a limited set of features. You have the choice to only install Scaphandre with prometheus-push exporter (alongside with stdout and json exporters, which might be useful locally). + +RPM packages containing only those features are provided for RHEL 8 and 9 : +- [RPM package for RHEL8](https://scaphandre.s3.fr-par.scw.cloud/x86_64/scaphandre-prometheuspush-dev0.5.10-1.el8.x86_64.rpm) +- [RPM package for RHEL9](https://scaphandre.s3.fr-par.scw.cloud/x86_64/scaphandre-prometheuspush-dev0.5.10-1.el9.x86_64.rpm) + +You can download it and install it just providing the right URL to dnf : + + dnf install -y URL + +Then you'll probably need to change its configuration to target the appropriate Push Gateway server. Edit the configuration file : + + vi /etc/scaphandre/prometheuspush + +Default options look like : + + SCAPHANDRE_ARGS="prometheus-push -H localhost -S http" + +Those are prometheus-push exporter CLI options. Run the executable to get the reference of the options : + + /usr/bin/scaphandre-prometheuspush --help + +A simple configuration to target Push Gateway reachable on https://myserver.mydomain:PORT would look like : + + SCAPHANDRE_ARGS="prometheus-push -H myserver.mydomain -S https -p PORT" + +Once the configuration is changed, you can restart the service and ensure it is enabled as well for next reboot : + + systemctl restart scaphandre-prometheuspush && systemctl enable scaphandre-prometheuspush + +Configuration issues or issues to reach the push gateway should be visible in the logs : + + systemctl status scaphandre-prometheuspush \ No newline at end of file From fdbc65efdb0ddca2e2f34374efe2ddec6d36890e Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 12 Jul 2023 13:44:46 +0200 Subject: [PATCH 180/303] docs: adding folder for automation samples --- .../install-configure-prometheuspush-rhel.yml | 32 +++++++++++++++++++ automation/ansible/inventory | 2 ++ 2 files changed, 34 insertions(+) create mode 100644 automation/ansible/install-configure-prometheuspush-rhel.yml create mode 100644 automation/ansible/inventory diff --git a/automation/ansible/install-configure-prometheuspush-rhel.yml b/automation/ansible/install-configure-prometheuspush-rhel.yml new file mode 100644 index 00000000..35f77c4d --- /dev/null +++ b/automation/ansible/install-configure-prometheuspush-rhel.yml @@ -0,0 +1,32 @@ +- hosts: targets + vars: + rhel_version: 9 + scaphandre_version: "dev0.5.10" + pushgateway_host: localhost + pushgateway_scheme: http + pushgateway_port: 9091 + scaphandre_config_path: /etc/scaphandre/prometheuspush + service_name: scaphandre-prometheuspush + tasks: + #- name: Ensure scaphandre package is purged + # shell: "dnf remove -y {{ }}" + - name: Install RPM package + shell: "dnf install -y https://scaphandre.s3.fr-par.scw.cloud/x86_64/scaphandre-prometheuspush-{{ scaphandre_version }}-1.el{{ rhel_version }}.x86_64.rpm" + - name: Refresh systemd config + shell: systemctl daemon-reload + - name: Configure prometheus-push exporter to target push gateway + lineinfile: + path: "{{ scaphandre_config_path }}" + regexp: '^SCAPHANDRE_ARGS=.*' + backrefs: true + line: "SCAPHANDRE_ARGS=\"prometheus-push -H {{ pushgateway_host }} -S {{ pushgateway_scheme }} -p {{ pushgateway_port }}\"" + state: present + - name: Start & enable service + shell: "systemctl start {{ service_name }} && systemctl enable {{ service_name }}" + - name: Check service state + shell: "systemctl status {{ service_name }}" + register: result + - name: Display error if failed + fail: + msg: "STDOUT: {{ result.stdout }} STDERR: {{ result.stderr }}" + when: result.rc != 0 diff --git a/automation/ansible/inventory b/automation/ansible/inventory new file mode 100644 index 00000000..848fffbd --- /dev/null +++ b/automation/ansible/inventory @@ -0,0 +1,2 @@ +[targets] +myhost # CHANGEME ! From e930f7592132e12b34e5a4aa9837229dcb8e8634 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 12 Jul 2023 14:57:32 +0200 Subject: [PATCH 181/303] docs: adding ansible sample for prompush on rhel --- .../ansible/install-configure-prometheuspush-rhel.yml | 4 ++-- automation/ansible/inventory | 2 +- .../how-to_guides/install-prometheuspush-only-rhel.md | 10 +++++++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/automation/ansible/install-configure-prometheuspush-rhel.yml b/automation/ansible/install-configure-prometheuspush-rhel.yml index 35f77c4d..29b97480 100644 --- a/automation/ansible/install-configure-prometheuspush-rhel.yml +++ b/automation/ansible/install-configure-prometheuspush-rhel.yml @@ -4,7 +4,7 @@ scaphandre_version: "dev0.5.10" pushgateway_host: localhost pushgateway_scheme: http - pushgateway_port: 9091 + pushgateway_port: 9092 scaphandre_config_path: /etc/scaphandre/prometheuspush service_name: scaphandre-prometheuspush tasks: @@ -22,7 +22,7 @@ line: "SCAPHANDRE_ARGS=\"prometheus-push -H {{ pushgateway_host }} -S {{ pushgateway_scheme }} -p {{ pushgateway_port }}\"" state: present - name: Start & enable service - shell: "systemctl start {{ service_name }} && systemctl enable {{ service_name }}" + shell: "systemctl restart {{ service_name }} && systemctl enable {{ service_name }}" - name: Check service state shell: "systemctl status {{ service_name }}" register: result diff --git a/automation/ansible/inventory b/automation/ansible/inventory index 848fffbd..8151d196 100644 --- a/automation/ansible/inventory +++ b/automation/ansible/inventory @@ -1,2 +1,2 @@ [targets] -myhost # CHANGEME ! +nce.hopto.org ansible_ssh_port=22029 diff --git a/docs_src/how-to_guides/install-prometheuspush-only-rhel.md b/docs_src/how-to_guides/install-prometheuspush-only-rhel.md index 06c3a607..1f1ac059 100644 --- a/docs_src/how-to_guides/install-prometheuspush-only-rhel.md +++ b/docs_src/how-to_guides/install-prometheuspush-only-rhel.md @@ -34,4 +34,12 @@ Once the configuration is changed, you can restart the service and ensure it is Configuration issues or issues to reach the push gateway should be visible in the logs : - systemctl status scaphandre-prometheuspush \ No newline at end of file + systemctl status scaphandre-prometheuspush + +## Automatic installation with ansible + +There is a [sample Ansible playbook](https://github.com/hubblo-org/scaphandre/blob/dev/automation/ansible/install-configure-prometheuspush-rhel.yml) available in the [automation/ansible](https://github.com/hubblo-org/scaphandre/tree/dev/automation/ansible) folder of the project. + +This can be used this way : + + ansible-playbook -i inventory -b -u myunprivilegeduser -K install-configure-prometheuspush-rhel.yml \ No newline at end of file From fd79004160fd3c86866dd7b3b92c6c327d3171be Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 12 Jul 2023 15:01:39 +0200 Subject: [PATCH 182/303] docs: more details on ansible for prompush on rhel --- .../install-prometheuspush-only-rhel.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs_src/how-to_guides/install-prometheuspush-only-rhel.md b/docs_src/how-to_guides/install-prometheuspush-only-rhel.md index 1f1ac059..bb26d760 100644 --- a/docs_src/how-to_guides/install-prometheuspush-only-rhel.md +++ b/docs_src/how-to_guides/install-prometheuspush-only-rhel.md @@ -42,4 +42,16 @@ There is a [sample Ansible playbook](https://github.com/hubblo-org/scaphandre/bl This can be used this way : - ansible-playbook -i inventory -b -u myunprivilegeduser -K install-configure-prometheuspush-rhel.yml \ No newline at end of file + ansible-playbook -i inventory -b -u myunprivilegeduser -K install-configure-prometheuspush-rhel.yml + +Beware of the playbook parameters : + + rhel_version: 9 + scaphandre_version: "dev0.5.10" + pushgateway_host: localhost + pushgateway_scheme: http + pushgateway_port: 9092 + scaphandre_config_path: /etc/scaphandre/prometheuspush + service_name: scaphandre-prometheuspush + +Ensure to change those to match your context, including changing rhel version if needed (8 and 9 are supported) and parameters to reach the Push Gateway on the network. \ No newline at end of file From d47b5b78d9d75af6667cad36230e0c4b41c90c69 Mon Sep 17 00:00:00 2001 From: bpetit Date: Thu, 13 Jul 2023 17:06:55 +0200 Subject: [PATCH 183/303] docs: adding windows installation instrcutions --- docs_src/tutorials/installation-windows.md | 37 ++++++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/docs_src/tutorials/installation-windows.md b/docs_src/tutorials/installation-windows.md index 6b9c15ca..55314e6f 100644 --- a/docs_src/tutorials/installation-windows.md +++ b/docs_src/tutorials/installation-windows.md @@ -1,5 +1,36 @@ -# Install Scaphandre on Windows (experimental) +# Install Scaphandre on Windows -A better procedure and packaging should come soon. +**!! Warning: This is a first testing version of the package and installation procedure.** +**!! A new version is on its way with proper driver signature and Windows service proper management.** -The release 0.5.0 of Scaphandre can be tested on windows by compiling both the kernel driver and Scaphandre. See [Compilation for Windows (experimental)](compilation-windows.md) +## Using the installer + +In this first itration of the package, you'll need to enable Test Mode on Windows prior to proceed to this installation, then reboot. (Next version will have an officially signed version of the driver, so this won't be ncessaerry anymore.) + + bcdedit.exe -set TESTSIGNING ON + +The installer will ensure that test mode is enabled and fail otherwise, but activation of test mode **and a reboot** is needed before anyway. + +Then download the [package](https://scaphandre.s3.fr-par.scw.cloud/x86_64/scaphandre_0.5.0_installer.exe) and install it **as an administrator**. + +Once installed, you should be able to run scaphandre from Powershell, by running : + + & 'C:\Program Files (x86)\scaphandre\scaphandre.exe' stdout + +## Troubleshooting + +An error such as + + scaphandre::sensors::msr_rapl: Failed to open device : HANDLE(-1) + +means that the driver is not properly setup. Check it's state by running: + + driverquery /v | findstr capha + +If there is not item returned, the installation of the driver encountered an issue. + +If the service is STOPPED, there is also something wrong. + +## Compilation + +If you look for compiling Scaphandre and its driver yourself, see [Compilation for Windows](compilation-windows.md) \ No newline at end of file From 3dde8a49492259c4eba7f3048ef3d71e86041d14 Mon Sep 17 00:00:00 2001 From: bpetit Date: Fri, 14 Jul 2023 14:08:39 +0200 Subject: [PATCH 184/303] docs: improving windows install doc --- docs_src/tutorials/installation-windows.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs_src/tutorials/installation-windows.md b/docs_src/tutorials/installation-windows.md index 55314e6f..f71a09e8 100644 --- a/docs_src/tutorials/installation-windows.md +++ b/docs_src/tutorials/installation-windows.md @@ -8,6 +8,7 @@ In this first itration of the package, you'll need to enable Test Mode on Windows prior to proceed to this installation, then reboot. (Next version will have an officially signed version of the driver, so this won't be ncessaerry anymore.) bcdedit.exe -set TESTSIGNING ON + bcdedit.exe -set nointegritychecks on The installer will ensure that test mode is enabled and fail otherwise, but activation of test mode **and a reboot** is needed before anyway. From 1410c83b9298b24695903155eaf22a9c0c434c45 Mon Sep 17 00:00:00 2001 From: bpetit Date: Fri, 14 Jul 2023 14:09:11 +0200 Subject: [PATCH 185/303] fix: changing default step value for prompush --- src/exporters/prometheuspush.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/exporters/prometheuspush.rs b/src/exporters/prometheuspush.rs index c72b5328..09a5991f 100644 --- a/src/exporters/prometheuspush.rs +++ b/src/exporters/prometheuspush.rs @@ -50,7 +50,7 @@ pub struct ExporterArgs { #[arg(short = 'S', long, default_value_t = String::from("http"))] pub scheme: String, - #[arg(short, long, default_value_t = 5)] + #[arg(short, long, default_value_t = 30)] pub step: u64, /// Apply labels to metrics of processes that look like a Qemu/KVM virtual machine From cde000fcb30b12a9d37cdd9b76975450831d2fea Mon Sep 17 00:00:00 2001 From: bpetit Date: Fri, 21 Jul 2023 15:36:23 +0200 Subject: [PATCH 186/303] build: improving exe installer to get driver properlly setup --- packaging/windows/installer.iss | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/packaging/windows/installer.iss b/packaging/windows/installer.iss index 844ee657..fb06f118 100644 --- a/packaging/windows/installer.iss +++ b/packaging/windows/installer.iss @@ -8,6 +8,8 @@ #define MyAppExeName "scaphandre.exe" #define MyAppSourceFolder "C:\Users\bpeti\Documents\GitHub\scaphandre" #define RaplDriverSourceFolder "C:\Users\bpeti\Documents\GitHub\windows-rapl-driver" +#define SystemFolder "C:\Windows\System32" +#define System64Folder "C:\Windows\SysWOW64" [Setup] ; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. @@ -37,10 +39,14 @@ Name: "english"; MessagesFile: "compiler:Default.isl" [Files] Source: "{#MyAppSourceFolder}\target\release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion -Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\DriverLoader.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#RaplDriverSourceFolder}\x64\Release\DriverLoader.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.inf"; DestDir: "{app}"; Flags: ignoreversion -Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.sys"; DestDir: "{app}"; Flags: ignoreversion -Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.cat"; DestDir: "{app}"; Flags: ignoreversion +; Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.sys"; DestDir: "{#SystemFolder}"; +; Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.sys"; DestDir: "{#System64Folder}"; +Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.sys"; DestDir: "{app}"; +Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.cat"; DestDir: "{app}"; +; Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.cat"; DestDir: "{#SystemFolder}"; +; Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.cat"; DestDir: "{#System64Folder}"; Source: "C:\Program Files (x86)\Windows Kits\10\Tools\10.0.22621.0\x64\devcon.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\certmgr.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "{#MyAppSourceFolder}\README.md"; DestDir: "{app}"; Flags: ignoreversion @@ -52,10 +58,18 @@ Source: "{#RaplDriverSourceFolder}\ScaphandreDrvTest.cer"; DestDir: "{app}"; Fla Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" [Run] -; Filename: "{app}/DriverLoader.exe"; Parameters: "install"; WorkingDir: "{app}"; Description: "Install MSR/RAPL Driver ?"; Flags: postinstall -; Filename: "{app}/DriverLoader.exe"; Parameters: "start"; WorkingDir: "{app}"; Description: "Install MSR/RAPL Driver ?"; Flags: postinstall Filename: "C:\windows\System32\WindowsPowershell\v1.0\powershell.exe"; Parameters: "Import-Certificate -FilePath {app}\ScaphandreDrvTest.cer -CertStoreLocation Cert:\LocalMachine\Root"; Description: "Register test certificate"; Flags: waituntilidle shellexec -Filename: "{app}\devcon.exe"; Parameters: "install {app}\ScaphandreDrv.inf root\SCAPHANDREDRV"; Description: "Install MSR/RAPL Driver ?"; Flags: postinstall waituntilidle -Filename: "{app}\devcon.exe"; Parameters: "enable {app}\ScaphandreDrv.inf root\SCAPHANDREDRV"; Description: "Enable MSR/RAPL Driver ?"; Flags: postinstall waituntilidle +Filename: "{app}/devcon.exe"; Parameters: "install {app}\ScaphandreDrv.inf root\SCAPHANDREDRV"; Description: "Install Driver"; Flags: waituntilidle +Filename: "{app}/devcon.exe"; Parameters: "enable {app}\ScaphandreDrv.inf root\SCAPHANDREDRV"; Description: "Enable Driver"; Flags: waituntilidle +Filename: "{app}/DriverLoader.exe"; Parameters: "install"; WorkingDir: "{app}"; Description: "Install Driver Service"; +Filename: "{app}/DriverLoader.exe"; Parameters: "start"; WorkingDir: "{app}"; Description: "Start Driver Service"; ; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; +; Filename: "schtasks.exe"; Parameters: "/Create /SC ONSTART {app}\scaphandre.exe prometheus-push " + +[UninstallRun] +Filename: "{app}/DriverLoader.exe"; Parameters: "stop"; WorkingDir: "{app}"; RunOnceId: "StopService"; +Filename: "{app}/DriverLoader.exe"; Parameters: "remove"; WorkingDir: "{app}"; RunOnceId: "RemoveService"; +Filename: "{app}/devcon.exe"; Parameters: "disable ScaphandreDrv"; RunOnceId: "DisableDrier"; +Filename: "{app}/devcon.exe"; Parameters: "remove ScaphandreDrv"; RunOnceId: "RemoveService"; + From f24e5d16040caf74771adc8980a3f854ad69984b Mon Sep 17 00:00:00 2001 From: bpetit Date: Fri, 21 Jul 2023 15:37:03 +0200 Subject: [PATCH 187/303] chore: first try on argfile --- Cargo.lock | 19 +++++++++++++++++++ Cargo.toml | 1 + src/main.rs | 41 +++++++++++++++++++++++++++++++++-------- 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3821d73d..467e5e11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,6 +84,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "argfile" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "265f5108974489a217d5098cd81666b60480c8dd67302acbbe7cbdd8aa09d638" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "async-channel" version = "1.8.0" @@ -1128,6 +1137,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "os_str_bytes" +version = "6.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" +dependencies = [ + "memchr", +] + [[package]] name = "parking" version = "2.1.0" @@ -1462,6 +1480,7 @@ checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" name = "scaphandre" version = "0.5.0" dependencies = [ + "argfile", "chrono", "clap", "colored", diff --git a/Cargo.toml b/Cargo.toml index 5b66fd3d..39fd0be3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ hyper = { version = "0.14", features = ["full"], optional = true } tokio = { version = "1.26.0", features = ["full"], optional = true} sysinfo = { version = "0.28.3"} isahc = { version = "1.7.2", optional = true } +argfile = { version = "0.1.5" } [target.'cfg(target_os="linux")'.dependencies] procfs = { version = "0.15.0" } diff --git a/src/main.rs b/src/main.rs index d01ce7cb..78af2d76 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ use clap::{command, ArgAction, Parser, Subcommand}; use colored::Colorize; use scaphandre::{exporters, sensors::Sensor}; +use argfile::expand_args; #[cfg(target_os = "linux")] use scaphandre::sensors::powercap_rapl; @@ -101,15 +102,27 @@ fn main() { fn build_exporter(choice: ExporterChoice, sensor: &dyn Sensor) -> Box { match choice { ExporterChoice::Stdout(args) => { - Box::new(exporters::stdout::StdoutExporter::new(sensor, args)) + if let Ok(args_from_file) = expand_args(argfile::parse_fromfile, argfile::PREFIX) { + Box::new(exporters::stdout::StdoutExporter::new(sensor, args_from_file)) + } else { + Box::new(exporters::stdout::StdoutExporter::new(sensor, args)) + } } #[cfg(feature = "json")] ExporterChoice::Json(args) => { - Box::new(exporters::json::JsonExporter::new(sensor, args)) // keep this in braces + if let Ok(args_from_file) = expand_args(argfile::parse_fromfile, argfile::PREFIX) { + Box::new(exporters::json::JsonExporter::new(sensor, args_from_file)) + } else { + Box::new(exporters::json::JsonExporter::new(sensor, args)) // keep this in braces + } } #[cfg(feature = "prometheus")] ExporterChoice::Prometheus(args) => { - Box::new(exporters::prometheus::PrometheusExporter::new(sensor, args)) + if let Ok(args_from_file) = expand_args(argfile::parse_fromfile, argfile::PREFIX) { + Box::new(exporters::prometheus::PrometheusExporter::new(sensor, args_from_file)) + } else { + Box::new(exporters::prometheus::PrometheusExporter::new(sensor, args)) + } } #[cfg(feature = "qemu")] ExporterChoice::Qemu => { @@ -117,16 +130,28 @@ fn build_exporter(choice: ExporterChoice, sensor: &dyn Sensor) -> Box { - Box::new(exporters::riemann::RiemannExporter::new(sensor, args)) + if let Ok(args_from_file) = expand_args(argfile::parse_fromfile, argfile::PREFIX) { + Box::new(exporters::riemann::RiemannExporter::new(sensor, args_from_file)) + } else { + Box::new(exporters::riemann::RiemannExporter::new(sensor, args)) + } } #[cfg(feature = "warpten")] ExporterChoice::Warpten(args) => { - Box::new(exporters::warpten::Warp10Exporter::new(sensor, args)) + if let Ok(args_from_file) = expand_args(argfile::parse_fromfile, argfile::PREFIX) { + Box::new(exporters::warpten::Warp10Exporter::new(sensor, args_from_file)) + } else { + Box::new(exporters::warpten::Warp10Exporter::new(sensor, args)) + } } #[cfg(feature = "prometheuspush")] - ExporterChoice::PrometheusPush(args) => Box::new( - exporters::prometheuspush::PrometheusPushExporter::new(sensor, args), - ), + ExporterChoice::PrometheusPush(args) => { + if let Ok(args_from_file) = expand_args(argfile::parse_fromfile, argfile::PREFIX) { + Box::new(exporters::prometheuspush::PrometheusPushExporter::new(sensor, args_from_file)) + } else { + Box::new(exporters::prometheuspush::PrometheusPushExporter::new(sensor, args)) + } + }, } // Note that invalid choices are automatically turned into errors by `parse()` before the Cli is populated, // that's why they don't appear in this function. From 192cbcbb532ebf98a65cda8ed6d6a420eabb4b9f Mon Sep 17 00:00:00 2001 From: bpetit Date: Fri, 21 Jul 2023 15:39:43 +0200 Subject: [PATCH 188/303] docs: encouraging default of 30 seconds step for prometheus push --- automation/ansible/install-configure-prometheuspush-rhel.yml | 2 +- docs_src/how-to_guides/install-prometheuspush-only-rhel.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/automation/ansible/install-configure-prometheuspush-rhel.yml b/automation/ansible/install-configure-prometheuspush-rhel.yml index 29b97480..f87d0a5f 100644 --- a/automation/ansible/install-configure-prometheuspush-rhel.yml +++ b/automation/ansible/install-configure-prometheuspush-rhel.yml @@ -19,7 +19,7 @@ path: "{{ scaphandre_config_path }}" regexp: '^SCAPHANDRE_ARGS=.*' backrefs: true - line: "SCAPHANDRE_ARGS=\"prometheus-push -H {{ pushgateway_host }} -S {{ pushgateway_scheme }} -p {{ pushgateway_port }}\"" + line: "SCAPHANDRE_ARGS=\"prometheus-push -H {{ pushgateway_host }} -S {{ pushgateway_scheme }} -p {{ pushgateway_port }} -s 30\"" state: present - name: Start & enable service shell: "systemctl restart {{ service_name }} && systemctl enable {{ service_name }}" diff --git a/docs_src/how-to_guides/install-prometheuspush-only-rhel.md b/docs_src/how-to_guides/install-prometheuspush-only-rhel.md index bb26d760..c02bf935 100644 --- a/docs_src/how-to_guides/install-prometheuspush-only-rhel.md +++ b/docs_src/how-to_guides/install-prometheuspush-only-rhel.md @@ -24,9 +24,9 @@ Those are prometheus-push exporter CLI options. Run the executable to get the re /usr/bin/scaphandre-prometheuspush --help -A simple configuration to target Push Gateway reachable on https://myserver.mydomain:PORT would look like : +A simple configuration to target Push Gateway reachable on https://myserver.mydomain:PORT and send data every 30 seconds would look like : - SCAPHANDRE_ARGS="prometheus-push -H myserver.mydomain -S https -p PORT" + SCAPHANDRE_ARGS="prometheus-push -H myserver.mydomain -S https -p PORT -s 30" Once the configuration is changed, you can restart the service and ensure it is enabled as well for next reboot : From 70c3c238c6e468c486db798f1fc890bc76295218 Mon Sep 17 00:00:00 2001 From: bpetit Date: Fri, 21 Jul 2023 15:54:06 +0200 Subject: [PATCH 189/303] fix: encouraging default of 30 seconds step for prometheus push --- src/exporters/prometheuspush.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/exporters/prometheuspush.rs b/src/exporters/prometheuspush.rs index 3932c6c6..406b5bf1 100644 --- a/src/exporters/prometheuspush.rs +++ b/src/exporters/prometheuspush.rs @@ -36,7 +36,7 @@ pub struct ExporterArgs { #[arg(short = 'S', long, default_value_t = String::from("http"))] pub scheme: String, - #[arg(short, long, default_value_t = 5)] + #[arg(short, long, default_value_t = 30)] pub step: u64, /// Apply labels to metrics of processes that look like a Qemu/KVM virtual machine From 193235e5f6ca33824b6522c8c8f70e3ae7f9f427 Mon Sep 17 00:00:00 2001 From: bpetit Date: Tue, 25 Jul 2023 18:20:50 +0200 Subject: [PATCH 190/303] clean: removing useless sample metric --- src/exporters/prometheuspush.rs | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/exporters/prometheuspush.rs b/src/exporters/prometheuspush.rs index 09a5991f..a5cd5de1 100644 --- a/src/exporters/prometheuspush.rs +++ b/src/exporters/prometheuspush.rs @@ -4,19 +4,6 @@ //! a [Prometheus](https://prometheus.io/) pushgateway. //! -#[cfg(target_os="windows")] -extern crate windows_service; -#[cfg(target_os="windows")] -use std::{ - ffi::OsString -}; -#[cfg(target_os="windows")] -use windows_service::{ - service::ServiceControl, service::ServiceControlAccept, - service::ServiceExitCode, service::ServiceState, service::ServiceStatus, - service::ServiceType, service_control_handler::{self, ServiceControlHandlerResult} -}; - use super::utils::{format_prometheus_metric, get_hostname}; use crate::exporters::{Exporter, MetricGenerator}; use crate::sensors::{Sensor, Topology}; @@ -106,9 +93,7 @@ impl Exporter for PrometheusPushExporter { loop { metric_generator.topology.refresh(); metric_generator.gen_all_metrics(); - let mut body = String::from( - "# HELP mymetric this is my metric\n# TYPE mymetric gauge\nmymetric 50\n", - ); + let mut body = String::from(""); let mut metrics_pushed: Vec = vec![]; //let mut counter = 0; for mut m in metric_generator.pop_metrics() { @@ -167,6 +152,7 @@ impl Exporter for PrometheusPushExporter { thread::sleep(Duration::new(self.args.step, 0)); } + } fn kind(&self) -> &str { From c9d5547bc5553bab34706c5bf2b653af3120d4a7 Mon Sep 17 00:00:00 2001 From: bpetit Date: Tue, 25 Jul 2023 18:52:08 +0200 Subject: [PATCH 191/303] feat: adding basic windows service management support --- Cargo.lock | 19 -------- Cargo.toml | 1 - src/main.rs | 122 +++++++++++++++++++++++++++++++++++++++------------- 3 files changed, 91 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 467e5e11..3821d73d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,15 +84,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "argfile" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "265f5108974489a217d5098cd81666b60480c8dd67302acbbe7cbdd8aa09d638" -dependencies = [ - "os_str_bytes", -] - [[package]] name = "async-channel" version = "1.8.0" @@ -1137,15 +1128,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "os_str_bytes" -version = "6.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" -dependencies = [ - "memchr", -] - [[package]] name = "parking" version = "2.1.0" @@ -1480,7 +1462,6 @@ checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" name = "scaphandre" version = "0.5.0" dependencies = [ - "argfile", "chrono", "clap", "colored", diff --git a/Cargo.toml b/Cargo.toml index 39fd0be3..5b66fd3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,6 @@ hyper = { version = "0.14", features = ["full"], optional = true } tokio = { version = "1.26.0", features = ["full"], optional = true} sysinfo = { version = "0.28.3"} isahc = { version = "1.7.2", optional = true } -argfile = { version = "0.1.5" } [target.'cfg(target_os="linux")'.dependencies] procfs = { version = "0.15.0" } diff --git a/src/main.rs b/src/main.rs index 78af2d76..5c11e06a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,6 @@ use clap::{command, ArgAction, Parser, Subcommand}; use colored::Colorize; use scaphandre::{exporters, sensors::Sensor}; -use argfile::expand_args; #[cfg(target_os = "linux")] use scaphandre::sensors::powercap_rapl; @@ -11,6 +10,29 @@ use scaphandre::sensors::powercap_rapl; #[cfg(target_os = "windows")] use scaphandre::sensors::msr_rapl; +#[cfg(target_os="windows")] +use windows_service::{ + Result, service_dispatcher, + service::ServiceControl, service::ServiceControlAccept, + service::ServiceExitCode, service::ServiceState, service::ServiceStatus, + service::ServiceType, service_control_handler::{self, ServiceControlHandlerResult} +}; + +#[cfg(target_os="windows")] +define_windows_service!(ffi_service_main, my_service_main); + +#[cfg(target_os="windows")] +#[macro_use] +extern crate windows_service; + +#[cfg(target_os="windows")] +use std::time::Duration; + +#[cfg(target_os="windows")] +use std::{ + ffi::OsString +}; + // the struct below defines the main Scaphandre command-line interface /// Extensible metrology agent for electricity consumption related metrics. #[derive(Parser)] @@ -86,7 +108,69 @@ enum ExporterChoice { PrometheusPush(exporters::prometheuspush::ExporterArgs), } +#[cfg(target_os="windows")] +fn my_service_main(arguments: Vec) { + if let Err(_e) = run_service(arguments) { + // Handle errors in some way. + } +} + +#[cfg(target_os="windows")] +fn run_service(arguments: Vec) -> Result<()> { + #[cfg(target_os="windows")] + let event_handler = move |control_event| -> ServiceControlHandlerResult { + match control_event { + ServiceControl::Stop => { + // Handle stop event and return control back to the system. + ServiceControlHandlerResult::NoError + } + // All services must accept Interrogate even if it's a no-op. + ServiceControl::Interrogate => ServiceControlHandlerResult::NoError, + _ => ServiceControlHandlerResult::NotImplemented, + } + }; + #[cfg(target_os="windows")] + if let Ok(system_handler) = service_control_handler::register("Scaphandre", event_handler) { + let next_status = ServiceStatus { + // Should match the one from system service registry + service_type: ServiceType::OWN_PROCESS, + // The new state + current_state: ServiceState::Running, + // Accept stop events when running + controls_accepted: ServiceControlAccept::STOP, + // Used to report an error when starting or stopping only, otherwise must be zero + exit_code: ServiceExitCode::Win32(0), + // Only used for pending states, otherwise must be zero + checkpoint: 0, + // Only used for pending states, otherwise must be zero + wait_hint: Duration::default(), + // Unused for setting status + process_id: None, + }; + + // Tell the system that the service is running now + if let Ok(status_set) = system_handler.set_service_status(next_status) { + parse_cli_and_run_exporter(); + } else { + panic!("Couldn't set Windows service status."); + } + } else { + panic!("Couldn't get Windows system events handler."); + } + Ok(()) +} + fn main() { + #[cfg(target_os="windows")] + match service_dispatcher::start("Scaphandre", ffi_service_main) { + Ok(_) => { }, + Err(e) => { println!("Couldn't start Windows service dispatcher. Got : {}", e); } + } + + parse_cli_and_run_exporter(); +} + +fn parse_cli_and_run_exporter() { let cli = Cli::parse(); loggerv::init_with_verbosity(cli.verbose.into()).expect("unable to initialize the logger"); @@ -102,27 +186,15 @@ fn main() { fn build_exporter(choice: ExporterChoice, sensor: &dyn Sensor) -> Box { match choice { ExporterChoice::Stdout(args) => { - if let Ok(args_from_file) = expand_args(argfile::parse_fromfile, argfile::PREFIX) { - Box::new(exporters::stdout::StdoutExporter::new(sensor, args_from_file)) - } else { - Box::new(exporters::stdout::StdoutExporter::new(sensor, args)) - } + Box::new(exporters::stdout::StdoutExporter::new(sensor, args)) } #[cfg(feature = "json")] ExporterChoice::Json(args) => { - if let Ok(args_from_file) = expand_args(argfile::parse_fromfile, argfile::PREFIX) { - Box::new(exporters::json::JsonExporter::new(sensor, args_from_file)) - } else { - Box::new(exporters::json::JsonExporter::new(sensor, args)) // keep this in braces - } + Box::new(exporters::json::JsonExporter::new(sensor, args)) // keep this in braces } #[cfg(feature = "prometheus")] ExporterChoice::Prometheus(args) => { - if let Ok(args_from_file) = expand_args(argfile::parse_fromfile, argfile::PREFIX) { - Box::new(exporters::prometheus::PrometheusExporter::new(sensor, args_from_file)) - } else { - Box::new(exporters::prometheus::PrometheusExporter::new(sensor, args)) - } + Box::new(exporters::prometheus::PrometheusExporter::new(sensor, args)) } #[cfg(feature = "qemu")] ExporterChoice::Qemu => { @@ -130,27 +202,15 @@ fn build_exporter(choice: ExporterChoice, sensor: &dyn Sensor) -> Box { - if let Ok(args_from_file) = expand_args(argfile::parse_fromfile, argfile::PREFIX) { - Box::new(exporters::riemann::RiemannExporter::new(sensor, args_from_file)) - } else { - Box::new(exporters::riemann::RiemannExporter::new(sensor, args)) - } + Box::new(exporters::riemann::RiemannExporter::new(sensor, args)) } #[cfg(feature = "warpten")] ExporterChoice::Warpten(args) => { - if let Ok(args_from_file) = expand_args(argfile::parse_fromfile, argfile::PREFIX) { - Box::new(exporters::warpten::Warp10Exporter::new(sensor, args_from_file)) - } else { - Box::new(exporters::warpten::Warp10Exporter::new(sensor, args)) - } + Box::new(exporters::warpten::Warp10Exporter::new(sensor, args)) } #[cfg(feature = "prometheuspush")] ExporterChoice::PrometheusPush(args) => { - if let Ok(args_from_file) = expand_args(argfile::parse_fromfile, argfile::PREFIX) { - Box::new(exporters::prometheuspush::PrometheusPushExporter::new(sensor, args_from_file)) - } else { - Box::new(exporters::prometheuspush::PrometheusPushExporter::new(sensor, args)) - } + Box::new(exporters::prometheuspush::PrometheusPushExporter::new(sensor, args)) }, } // Note that invalid choices are automatically turned into errors by `parse()` before the Cli is populated, From dbdbc774ef74a9cfaea93a1de0d7d44fcd1bca10 Mon Sep 17 00:00:00 2001 From: bpetit Date: Tue, 25 Jul 2023 19:07:46 +0200 Subject: [PATCH 192/303] style: clippy and fmt --- src/exporters/json.rs | 1 + src/exporters/mod.rs | 3 ++ src/exporters/prometheuspush.rs | 1 - src/lib.rs | 2 ++ src/main.rs | 56 ++++++++++++++++++--------------- 5 files changed, 36 insertions(+), 27 deletions(-) diff --git a/src/exporters/json.rs b/src/exporters/json.rs index f0cd1b51..1abaae88 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -340,6 +340,7 @@ impl JsonExporter { .proc_tracker .get_filtered_processes(regex_filter) } else if let Some(regex_filter) = &self.container_regex { + debug!("Processes filtered by '{}':", regex_filter.as_str()); #[cfg(feature = "containers")] { self.metric_generator diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index 2d03b6d0..4d054a4e 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -43,12 +43,15 @@ pub struct Metric { /// `metric_type` mostly used by Prometheus, define is it is a gauge, counter... metric_type: String, /// `ttl` time to live for this metric used by Riemann. + #[allow(dead_code)] ttl: f32, /// `hostname` host that provides the metric. hostname: String, /// `state` used by Riemann, define a state like Ok or Ko regarding this metric. + #[allow(dead_code)] state: String, /// `tags` used by Riemann, tags attached to the metric. + #[allow(dead_code)] tags: Vec, /// `attributes` used by exporters to better qualify the metric. In Prometheus context /// this is used as a metric tag (socket_id) : `scaph_self_socket_stats_nb{socket_id="0"} 2`. diff --git a/src/exporters/prometheuspush.rs b/src/exporters/prometheuspush.rs index 3d2755a1..b460c45c 100644 --- a/src/exporters/prometheuspush.rs +++ b/src/exporters/prometheuspush.rs @@ -157,7 +157,6 @@ impl Exporter for PrometheusPushExporter { thread::sleep(Duration::new(self.args.step, 0)); } - } fn kind(&self) -> &str { diff --git a/src/lib.rs b/src/lib.rs index b19e66bf..2a4e2b21 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ use sensors::msr_rapl; #[cfg(target_os = "linux")] use sensors::powercap_rapl; +#[cfg(target_os = "linux")] use std::time::{Duration, SystemTime}; /// Create a new [`Sensor`] instance with the default sensor available, @@ -30,6 +31,7 @@ pub fn get_default_sensor() -> impl sensors::Sensor { return msr_rapl::MsrRAPLSensor::new(); } +#[cfg(target_os = "linux")] fn current_system_time_since_epoch() -> Duration { SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) diff --git a/src/main.rs b/src/main.rs index 5c11e06a..83db7998 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,28 +10,30 @@ use scaphandre::sensors::powercap_rapl; #[cfg(target_os = "windows")] use scaphandre::sensors::msr_rapl; -#[cfg(target_os="windows")] +#[cfg(target_os = "windows")] use windows_service::{ - Result, service_dispatcher, - service::ServiceControl, service::ServiceControlAccept, - service::ServiceExitCode, service::ServiceState, service::ServiceStatus, - service::ServiceType, service_control_handler::{self, ServiceControlHandlerResult} + service::ServiceControl, + service::ServiceControlAccept, + service::ServiceExitCode, + service::ServiceState, + service::ServiceStatus, + service::ServiceType, + service_control_handler::{self, ServiceControlHandlerResult}, + service_dispatcher, Result, }; -#[cfg(target_os="windows")] +#[cfg(target_os = "windows")] define_windows_service!(ffi_service_main, my_service_main); -#[cfg(target_os="windows")] +#[cfg(target_os = "windows")] #[macro_use] extern crate windows_service; -#[cfg(target_os="windows")] +#[cfg(target_os = "windows")] use std::time::Duration; -#[cfg(target_os="windows")] -use std::{ - ffi::OsString -}; +#[cfg(target_os = "windows")] +use std::ffi::OsString; // the struct below defines the main Scaphandre command-line interface /// Extensible metrology agent for electricity consumption related metrics. @@ -108,16 +110,16 @@ enum ExporterChoice { PrometheusPush(exporters::prometheuspush::ExporterArgs), } -#[cfg(target_os="windows")] +#[cfg(target_os = "windows")] fn my_service_main(arguments: Vec) { if let Err(_e) = run_service(arguments) { // Handle errors in some way. } } -#[cfg(target_os="windows")] -fn run_service(arguments: Vec) -> Result<()> { - #[cfg(target_os="windows")] +#[cfg(target_os = "windows")] +fn run_service(_arguments: Vec) -> Result<()> { + #[cfg(target_os = "windows")] let event_handler = move |control_event| -> ServiceControlHandlerResult { match control_event { ServiceControl::Stop => { @@ -129,7 +131,7 @@ fn run_service(arguments: Vec) -> Result<()> { _ => ServiceControlHandlerResult::NotImplemented, } }; - #[cfg(target_os="windows")] + #[cfg(target_os = "windows")] if let Ok(system_handler) = service_control_handler::register("Scaphandre", event_handler) { let next_status = ServiceStatus { // Should match the one from system service registry @@ -147,9 +149,9 @@ fn run_service(arguments: Vec) -> Result<()> { // Unused for setting status process_id: None, }; - + // Tell the system that the service is running now - if let Ok(status_set) = system_handler.set_service_status(next_status) { + if let Ok(_status_set) = system_handler.set_service_status(next_status) { parse_cli_and_run_exporter(); } else { panic!("Couldn't set Windows service status."); @@ -161,10 +163,12 @@ fn run_service(arguments: Vec) -> Result<()> { } fn main() { - #[cfg(target_os="windows")] + #[cfg(target_os = "windows")] match service_dispatcher::start("Scaphandre", ffi_service_main) { - Ok(_) => { }, - Err(e) => { println!("Couldn't start Windows service dispatcher. Got : {}", e); } + Ok(_) => {} + Err(e) => { + println!("Couldn't start Windows service dispatcher. Got : {}", e); + } } parse_cli_and_run_exporter(); @@ -209,9 +213,9 @@ fn build_exporter(choice: ExporterChoice, sensor: &dyn Sensor) -> Box { - Box::new(exporters::prometheuspush::PrometheusPushExporter::new(sensor, args)) - }, + ExporterChoice::PrometheusPush(args) => Box::new( + exporters::prometheuspush::PrometheusPushExporter::new(sensor, args), + ), } // Note that invalid choices are automatically turned into errors by `parse()` before the Cli is populated, // that's why they don't appear in this function. @@ -231,7 +235,7 @@ fn build_sensor(cli: &Cli) -> impl Sensor { }; #[cfg(target_os = "windows")] - let msr_sensor_win = || msr_rapl::MsrRAPLSensor::new(); + let msr_sensor_win = msr_rapl::MsrRAPLSensor::new; match cli.sensor.as_deref() { Some("powercap_rapl") => { From b115bc34ca996c429a871508f63c348eeca161bb Mon Sep 17 00:00:00 2001 From: bpetit Date: Tue, 25 Jul 2023 19:14:05 +0200 Subject: [PATCH 193/303] fix: removed fn not needed in conditional compilation but needed otherwise --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 2a4e2b21..c5f6e634 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,6 @@ pub fn get_default_sensor() -> impl sensors::Sensor { return msr_rapl::MsrRAPLSensor::new(); } -#[cfg(target_os = "linux")] fn current_system_time_since_epoch() -> Duration { SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) From c02506379724b071551cb9b532b80896cff07416 Mon Sep 17 00:00:00 2001 From: bpetit Date: Wed, 26 Jul 2023 12:11:00 +0200 Subject: [PATCH 194/303] style: clippy rules --- src/exporters/json.rs | 2 +- src/lib.rs | 1 - src/sensors/mod.rs | 3 +-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/exporters/json.rs b/src/exporters/json.rs index 1abaae88..c448cd4f 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -228,7 +228,7 @@ impl JsonExporter { let mut res: Vec = vec![]; for m in metrics { let metric_disk_name = m.attributes.get("disk_name").unwrap(); - if let Some(mut disk) = res.iter_mut().find(|x| metric_disk_name == &x.disk_name) { + if let Some(disk) = res.iter_mut().find(|x| metric_disk_name == &x.disk_name) { info!("editing disk"); disk.disk_name = metric_disk_name.clone(); if m.name == "scaph_host_disk_available_bytes" { diff --git a/src/lib.rs b/src/lib.rs index c5f6e634..b19e66bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,6 @@ use sensors::msr_rapl; #[cfg(target_os = "linux")] use sensors::powercap_rapl; -#[cfg(target_os = "linux")] use std::time::{Duration, SystemTime}; /// Create a new [`Sensor`] instance with the default sensor available, diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 1d9fd3e8..fa89aa1e 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -276,8 +276,7 @@ impl Topology { /// to appropriate CPUSocket instance from self.sockets pub fn add_cpu_cores(&mut self) { if let Some(mut cores) = Topology::generate_cpu_cores() { - while !cores.is_empty() { - let c = cores.pop().unwrap(); + while let Some(c) = cores.pop() { let socket_id = &c .attributes .get("physical id") From 8a5ff5b0c0fd633fcb7ec78fbdf2890dab9df4bc Mon Sep 17 00:00:00 2001 From: bpetit Date: Wed, 26 Jul 2023 15:25:38 +0200 Subject: [PATCH 195/303] ci: adding sample job to build and publish exe installer --- .github/bloat-test.yml | 31 -------- .../workflows/exe-release-prometheuspush.yml | 78 +++++++++++++++++++ 2 files changed, 78 insertions(+), 31 deletions(-) delete mode 100644 .github/bloat-test.yml create mode 100644 .github/workflows/exe-release-prometheuspush.yml diff --git a/.github/bloat-test.yml b/.github/bloat-test.yml deleted file mode 100644 index e6462579..00000000 --- a/.github/bloat-test.yml +++ /dev/null @@ -1,31 +0,0 @@ -#name: Check changes in binary size -# -#on: -# push: -# branches: [ main ] -# paths-ignore: -# - 'docs_src/**' -# - 'README.md' -# - 'CHANGELOG.md' -# - 'CITATION' -# - 'book.toml' -# pull_request: -# branches: [ main ] -# paths-ignore: -# - 'docs_src/**' -# - 'README.md' -# - 'CHANGELOG.md' -# - 'CITATION' -# - 'book.toml' -# -#jobs: -# bloat_test: -# name: Cargo Fmt and Clippy - Linux -# runs-on: ubuntu-latest -# steps: -# - name: Checkout repository -# uses: actions/checkout@v2 -# - name: Run cargo bloat -# uses: orf/cargo-bloat-action@v1 -# with: -# token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/exe-release-prometheuspush.yml b/.github/workflows/exe-release-prometheuspush.yml new file mode 100644 index 00000000..8e4c8917 --- /dev/null +++ b/.github/workflows/exe-release-prometheuspush.yml @@ -0,0 +1,78 @@ +name: Build exe installer for windows for prometheus-push only version + +on: + push: + paths-ignore: + - 'docs_src/**' + - 'README.md' + - 'CITATION' + - 'book.toml' + - 'CONTRIBUTING.md' + tags: [ 'v*.*.*', 'dev*.*.*' ] + branches: [ '311-github-workflow-to-build-and-publish-a-exemsi-file-including-signed-rapl-driver-at-each-tagrelease' ] + +jobs: + build_exe_win1011: + name: Build exe installer for windows 10/11/server 2016/server 2019/server 2022 + runs-on: windows + steps: + - name: Checkout + uses: actions/checkout@v3 + - uses: Swatinem/rust-cache@v2 + with: + # The prefix cache key, this can be changed to start a new cache manually. + # default: "v0-rust" + prefix-key: "" + + # A cache key that is used instead of the automatic `job`-based key, + # and is stable over multiple jobs. + # default: empty + shared-key: "" + + # An additional cache key that is added alongside the automatic `job`-based + # cache key and can be used to further differentiate jobs. + # default: empty + key: "" + + # A whitespace separated list of env-var *prefixes* who's value contributes + # to the environment cache key. + # The env-vars are matched by *prefix*, so the default `RUST` var will + # match all of `RUSTC`, `RUSTUP_*`, `RUSTFLAGS`, `RUSTDOC_*`, etc. + # default: "CARGO CC CFLAGS CXX CMAKE RUST" + env-vars: "" + + # The cargo workspaces and target directory configuration. + # These entries are separated by newlines and have the form + # `$workspace -> $target`. The `$target` part is treated as a directory + # relative to the `$workspace` and defaults to "target" if not explicitly given. + # default: ". -> target" + workspaces: "" + + # Additional non workspace directories to be cached, separated by newlines. + cache-directories: "" + + # Determines whether workspace `target` directories are cached. + # If `false`, only the cargo registry will be cached. + # default: "true" + cache-targets: "" + + # Determines if the cache should be saved even when the workflow has failed. + # default: "false" + cache-on-failure: "" + + # Determines which crates are cached. + # If `true` all crates will be cached, otherwise only dependent crates will be cached. + # Useful if additional crates are used for CI tooling. + # default: "false" + cache-all-crates: "" + + # Determiners whether the cache should be saved. + # If `false`, the cache is only restored. + # Useful for jobs where the matrix is additive e.g. additional Cargo features. + # default: "true" + save-if: "" + - name: Install Innosoft + run: | + $url = "https://jrsoftware.org/download.php/is.exe" + $dest = "is.exe" + Invoke-WebRequest -Uri $url -OutFile $dest \ No newline at end of file From de1095cbcac653cfa9df2c9e8602a96b9a58d086 Mon Sep 17 00:00:00 2001 From: bpetit Date: Wed, 26 Jul 2023 15:48:19 +0200 Subject: [PATCH 196/303] ci: fix job label --- .github/workflows/exe-release-prometheuspush.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/exe-release-prometheuspush.yml b/.github/workflows/exe-release-prometheuspush.yml index 8e4c8917..6d4031d2 100644 --- a/.github/workflows/exe-release-prometheuspush.yml +++ b/.github/workflows/exe-release-prometheuspush.yml @@ -14,7 +14,7 @@ on: jobs: build_exe_win1011: name: Build exe installer for windows 10/11/server 2016/server 2019/server 2022 - runs-on: windows + runs-on: "windows-2019" steps: - name: Checkout uses: actions/checkout@v3 From 8d1365b537ef9996906886d42401f0e86e27dcd1 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Fri, 9 Jun 2023 15:42:46 +0200 Subject: [PATCH 197/303] doc: adding exporter stdout new flag --- docs_src/references/exporter-stdout.md | 30 ++++------ docs_src/references/metrics.md | 76 ++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 18 deletions(-) create mode 100644 docs_src/references/metrics.md diff --git a/docs_src/references/exporter-stdout.md b/docs_src/references/exporter-stdout.md index af569898..93a06bc3 100644 --- a/docs_src/references/exporter-stdout.md +++ b/docs_src/references/exporter-stdout.md @@ -30,26 +30,20 @@ Here is how to display power data for the 'scaphandre' process: scaphandre stdout -r 'scaphandre' -Note +Since 1.0.0 the flag `--raw-metrics` displays all metrics available for the host, as a parseable list. This might be useful to list metrics that you would like to fetch afterwards in your monitoring dashboard. As always exporter's options can be displayed with `-h`: - $ scaphandre stdout -h - scaphandre-stdout - Stdout exporter allows you to output the power consumption data in the terminal + Write the metrics to the terminal - USAGE: - scaphandre stdout [OPTIONS] - - FLAGS: - -h, --help Prints help information - -V, --version Prints version information - - OPTIONS: - -p, --process Number of processes to display. [default: 5] - -r, --regex Filter processes based on regular expressions (e.g: 'scaph\w\wd.e'). This option - disable '-p' or '--process' one. - -s, --step Set measurement step duration in seconds. [default: 2] - -t, --timeout Maximum time spent measuring, in seconds. 0 means continuous measurement. - [default: 10] + Usage: scaphandre stdout [OPTIONS] + Options: + -t, --timeout Maximum time spent measuring, in seconds. If negative, runs forever [default: 10] + -s, --step Interval between two measurements, in seconds [default: 2] + -p, --processes Maximum number of processes to display [default: 5] + -r, --regex-filter Filter processes based on regular expressions (example: 'scaph\\w\\w.e') + --containers Monitor and apply labels for processes running as containers + -q, --qemu Apply labels to metrics of processes looking like a Qemu/KVM virtual machine + --raw-metrics Display metrics with their names + -h, --help Print help \ No newline at end of file diff --git a/docs_src/references/metrics.md b/docs_src/references/metrics.md new file mode 100644 index 00000000..5b1178aa --- /dev/null +++ b/docs_src/references/metrics.md @@ -0,0 +1,76 @@ +# Metrics exposed by Scaphandre + +All metrics have a HELP section provided on /metrics (or whatever suffix you choosed to expose them). + +Here are some key metrics that you will most probably be interested in: + +- `scaph_host_power_microwatts`: Power measurement on the whole host, in microwatts (GAUGE) +- `scaph_process_power_consumption_microwatts{exe="$PROCESS_EXE",pid="$PROCESS_PID",cmdline="path/to/exe --and-maybe-options"}`: Power consumption due to the process, measured on at the topology level, in microwatts. PROCESS_EXE being the name of the executable and PROCESS_PID being the pid of the process. (GAUGE) + +For more details on that metric labels, see [this section](#scaph_process_power_consumption_microwatts). + +And some more deep metrics that you may want if you need to make more complex calculations and data processing: + +- `scaph_host_energy_microjoules` : Energy measurement for the whole host, as extracted from the sensor, in microjoules. (COUNTER) +- `scaph_socket_power_microwatts{socket_id="$SOCKET_ID"}`: Power measurement relative to a CPU socket, in microwatts. SOCKET_ID being the socket numerical id (GAUGE) + +If you hack scaph or just want to investigate its behavior, you may be interested in some internal metrics: + +- `scaph_self_memory_bytes`: Scaphandre memory usage, in bytes + +- `scaph_self_memory_virtual_bytes`: Scaphandre virtual memory usage, in bytes + +- `scaph_self_topo_stats_nb`: Number of CPUStat traces stored for the host + +- `scaph_self_topo_records_nb`: Number of energy consumption Records stored for the host + +- `scaph_self_topo_procs_nb`: Number of processes monitored by scaph + +- `scaph_self_socket_stats_nb{socket_id="SOCKET_ID"}`: Number of CPUStat traces stored for each socket + +- `scaph_self_socket_records_nb{socket_id="SOCKET_ID"}`: Number of energy consumption Records stored for each socket, with SOCKET_ID being the id of the socket measured + +- `scaph_self_domain_records_nb{socket_id="SOCKET_ID",rapl_domain_name="RAPL_DOMAIN_NAME +"}`: Number of energy consumption Records stored for a Domain, where SOCKET_ID identifies the socket and RAPL_DOMAIN_NAME identifies the rapl domain measured on that socket + +### Getting per process data with scaph_process_* metrics + +Here are available labels for the `scaph_process_power_consumption_microwatts` metric that you may need to extract the data you need: + +- `exe`: is the name of the executable that is the origin of that process. This is good to be used when your application is running one or only a few processes. +- `cmdline`: this contains the whole command line with the executable path and its parameters (concatenated). You can filter on this label by using prometheus `=~` operator to match a regular expression pattern. This is very practical in many situations. +- `instance`: this is a prometheus generated label to enable you to filter the metrics by the originating host. This is very useful when you monitor distributed services, so that you can not only sum the metrics for the same service on the different hosts but also see what instance of that service is consuming the most, or notice differences beteween hosts that may not have the same hardware, and so on... +- `pid`: is the process id, which is useful if you want to track a specific process and have your eyes on what's happening on the host, but not so practical to use in a more general use case + +### Get container-specific labels on scaph_process_* metrics + +The flag --containers enables Scaphandre to collect data about the running Docker containers or Kubernetes pods on the local machine. This way, it adds specific labels to make filtering processes power consumption metrics by their encapsulation in containers easier. + +Generic labels help to identify the container runtime and scheduler used (based on the content of `/proc/PID/cgroup`): + +`container_scheduler`: possible values are `docker` or `kubernetes`. If this label is not attached to the metric, it means that scaphandre didn't manage to identify the container scheduler based on cgroups data. + +Then the label `container_runtime` could be attached. The only possible value for now is `containerd`. + +`container_id` is the ID scaphandre got from /proc/PID/cgroup for that container. + +For Docker containers (if `container_scheduler` is set), available labels are : + +- `container_names`: is a string containing names attached to that container, according to the docker daemon +- `container_docker_version`: version of the docker daemon +- `container_label_maintainer`: content of the maintainer field for this container + +For containers coming from a docker-compose file, there are a bunch of labels related to data coming from the docker daemon: + +- `container_label_com_docker_compose_project_working_dir` +- `container_label_com_docker_compose_container_number` +- `container_label_com_docker_compose_project_config_files` +- `container_label_com_docker_compose_version` +- `container_label_com_docker_compose_service` +- `container_label_com_docker_compose_oneoff` + +For Kubernetes pods (if `container_scheduler` is set), available labels are : + +- `kubernetes_node_name`: identifies the name of the kubernetes node scaphandre is running on +- `kubernetes_pod_name`: the name of the pod the container belongs to +- `kubernetes_pod_namespace`: the namespace of the pod the container belongs to \ No newline at end of file From 40a5f643a8bdb94ded4f7caffc5e44717bb405aa Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Fri, 9 Jun 2023 15:50:39 +0200 Subject: [PATCH 198/303] doc: referencing page dedicated to metrics --- docs_src/references/exporter-json.md | 46 ++++++++----- docs_src/references/exporter-prometheus.md | 77 +--------------------- docs_src/references/exporter-riemann.md | 3 +- docs_src/references/exporter-stdout.md | 4 +- docs_src/references/exporter-warp10.md | 4 +- 5 files changed, 36 insertions(+), 98 deletions(-) diff --git a/docs_src/references/exporter-json.md b/docs_src/references/exporter-json.md index 65e51855..30fae878 100644 --- a/docs_src/references/exporter-json.md +++ b/docs_src/references/exporter-json.md @@ -26,22 +26,34 @@ To get informations about processes that are running in containers, add `--conta scaphandre --no-header json --containers --max-top-consumers=15 | jq -As always exporter's options can be displayed with `-h`: - - $ scaphandre json -h - JSON exporter allows you to output the power consumption data in a json file +Since 1.0.0 you can filter the processes, either by their process name with `--process-regex`, or by the name of the container they run in with `--container-regex` (needs the flag `--containers` to be active as well). - USAGE: - scaphandre json [FLAGS] [OPTIONS] - - FLAGS: - --containers Monitor and apply labels for processes running as containers - -h, --help Prints help information - -V, --version Prints version information +As always exporter's options can be displayed with `-h`: - OPTIONS: - -f, --file Destination file for the report. [default: ] - -m, --max-top-consumers Maximum number of processes to watch. [default: 10] - -s, --step Set measurement step duration in second. [default: 2] - -n, --step_nano Set measurement step duration in nano second. [default: 0] - -t, --timeout Maximum time spent measuring, in seconds. + Write the metrics in the JSON format to a file or to stdout + + Usage: scaphandre json [OPTIONS] + + Options: + -t, --timeout + Maximum time spent measuring, in seconds. If unspecified, runs forever + -s, --step + Interval between two measurements, in seconds [default: 2] + --step-nano + Additional step duration in _nano_ seconds. This is added to `step` to get the final duration [default: 0] + --max-top-consumers + Maximum number of processes to watch [default: 10] + -f, --file + Destination file for the report (if absent, print the report to stdout) + --containers + Monitor and apply labels for processes running as containers + --process-regex + Filter processes based on regular expressions (example: 'scaph\\w\\w.e') + --container-regex + Filter containers based on regular expressions + --resources + Monitor and incude CPU, RAM and Disk usage per process + -h, --help + Print help + +Metrics provided Scaphandre are documented [here](references/metrics.md). \ No newline at end of file diff --git a/docs_src/references/exporter-prometheus.md b/docs_src/references/exporter-prometheus.md index ceca1d59..ee3cd158 100644 --- a/docs_src/references/exporter-prometheus.md +++ b/docs_src/references/exporter-prometheus.md @@ -34,79 +34,4 @@ With default options values, the metrics are exposed on http://localhost:8080/me Use -q or --qemu option if you are running scaphandre on a hypervisor. In that case a label with the vm name will be added to all `qemu-system*` processes. This will allow to easily create charts consumption for each vm and defined which one is the top contributor. -## Metrics exposed - -All metrics have a HELP section provided on /metrics (or whatever suffix you choosed to expose them). - -Here are some key metrics that you will most probably be interested in: - -- `scaph_host_power_microwatts`: Power measurement on the whole host, in microwatts (GAUGE) -- `scaph_process_power_consumption_microwatts{exe="$PROCESS_EXE",pid="$PROCESS_PID",cmdline="path/to/exe --and-maybe-options"}`: Power consumption due to the process, measured on at the topology level, in microwatts. PROCESS_EXE being the name of the executable and PROCESS_PID being the pid of the process. (GAUGE) - -For more details on that metric labels, see [this section](#scaph_process_power_consumption_microwatts). - -And some more deep metrics that you may want if you need to make more complex calculations and data processing: - -- `scaph_host_energy_microjoules` : Energy measurement for the whole host, as extracted from the sensor, in microjoules. (COUNTER) -- `scaph_socket_power_microwatts{socket_id="$SOCKET_ID"}`: Power measurement relative to a CPU socket, in microwatts. SOCKET_ID being the socket numerical id (GAUGE) - -If you hack scaph or just want to investigate its behavior, you may be interested in some internal metrics: - -- `scaph_self_memory_bytes`: Scaphandre memory usage, in bytes - -- `scaph_self_memory_virtual_bytes`: Scaphandre virtual memory usage, in bytes - -- `scaph_self_topo_stats_nb`: Number of CPUStat traces stored for the host - -- `scaph_self_topo_records_nb`: Number of energy consumption Records stored for the host - -- `scaph_self_topo_procs_nb`: Number of processes monitored by scaph - -- `scaph_self_socket_stats_nb{socket_id="SOCKET_ID"}`: Number of CPUStat traces stored for each socket - -- `scaph_self_socket_records_nb{socket_id="SOCKET_ID"}`: Number of energy consumption Records stored for each socket, with SOCKET_ID being the id of the socket measured - -- `scaph_self_domain_records_nb{socket_id="SOCKET_ID",rapl_domain_name="RAPL_DOMAIN_NAME -"}`: Number of energy consumption Records stored for a Domain, where SOCKET_ID identifies the socket and RAPL_DOMAIN_NAME identifies the rapl domain measured on that socket - -### scaph_process_power_consumption_microwatts - -Here are available labels for the `scaph_process_power_consumption_microwatts` metric that you may need to extract the data you need: - -- `exe`: is the name of the executable that is the origin of that process. This is good to be used when your application is running one or only a few processes. -- `cmdline`: this contains the whole command line with the executable path and its parameters (concatenated). You can filter on this label by using prometheus `=~` operator to match a regular expression pattern. This is very practical in many situations. -- `instance`: this is a prometheus generated label to enable you to filter the metrics by the originating host. This is very useful when you monitor distributed services, so that you can not only sum the metrics for the same service on the different hosts but also see what instance of that service is consuming the most, or notice differences beteween hosts that may not have the same hardware, and so on... -- `pid`: is the process id, which is useful if you want to track a specific process and have your eyes on what's happening on the host, but not so practical to use in a more general use case - -### Get container-specific labels on scaph_process_power_consumption_microwatts metrics - -The flag --containers enables Scaphandre to collect data about the running Docker containers or Kubernetes pods on the local machine. This way, it adds specific labels to make filtering processes power consumption metrics by their encapsulation in containers easier. - -Generic labels help to identify the container runtime and scheduler used (based on the content of `/proc/PID/cgroup`): - -`container_scheduler`: possible values are `docker` or `kubernetes`. If this label is not attached to the metric, it means that scaphandre didn't manage to identify the container scheduler based on cgroups data. - -Then the label `container_runtime` could be attached. The only possible value for now is `containerd`. - -`container_id` is the ID scaphandre got from /proc/PID/cgroup for that container. - -For Docker containers (if `container_scheduler` is set), available labels are : - -- `container_names`: is a string containing names attached to that container, according to the docker daemon -- `container_docker_version`: version of the docker daemon -- `container_label_maintainer`: content of the maintainer field for this container - -For containers coming from a docker-compose file, there are a bunch of labels related to data coming from the docker daemon: - -- `container_label_com_docker_compose_project_working_dir` -- `container_label_com_docker_compose_container_number` -- `container_label_com_docker_compose_project_config_files` -- `container_label_com_docker_compose_version` -- `container_label_com_docker_compose_service` -- `container_label_com_docker_compose_oneoff` - -For Kubernetes pods (if `container_scheduler` is set), available labels are : - -- `kubernetes_node_name`: identifies the name of the kubernetes node scaphandre is running on -- `kubernetes_pod_name`: the name of the pod the container belongs to -- `kubernetes_pod_namespace`: the namespace of the pod the container belongs to +Metrics provided Scaphandre are documented [here](references/metrics.md). \ No newline at end of file diff --git a/docs_src/references/exporter-riemann.md b/docs_src/references/exporter-riemann.md index 8fdabae6..6699f224 100644 --- a/docs_src/references/exporter-riemann.md +++ b/docs_src/references/exporter-riemann.md @@ -79,7 +79,8 @@ As a reference here is a Riemann configuration: ``` ## Metrics exposed -Typically the Riemann exporter is working in the same way as the prometheus exporter regarding metrics. Please look at details in [Prometheus exporter](exporter-prometheus.md) documentations. + +Metrics provided Scaphandre are documented [here](references/metrics.md). There is only one exception about `process_power_consumption_microwatts` each process has a service name `process_power_consumption_microwatts_pid_exe`. diff --git a/docs_src/references/exporter-stdout.md b/docs_src/references/exporter-stdout.md index 93a06bc3..de37a939 100644 --- a/docs_src/references/exporter-stdout.md +++ b/docs_src/references/exporter-stdout.md @@ -30,7 +30,9 @@ Here is how to display power data for the 'scaphandre' process: scaphandre stdout -r 'scaphandre' -Since 1.0.0 the flag `--raw-metrics` displays all metrics available for the host, as a parseable list. This might be useful to list metrics that you would like to fetch afterwards in your monitoring dashboard. +Metrics provided Scaphandre are documented [here](references/metrics.md). + +Since 1.0.0 the flag `--raw-metrics` displays all metrics available for the host, as a parseable list. This might be useful to list metrics that you would like to fetch afterwards in your monitoring dashboard. Without this flag enabled, Stdout exporter has it's own format and might not show you all available metrics. As always exporter's options can be displayed with `-h`: diff --git a/docs_src/references/exporter-warp10.md b/docs_src/references/exporter-warp10.md index 6b2a76cd..0d239c03 100644 --- a/docs_src/references/exporter-warp10.md +++ b/docs_src/references/exporter-warp10.md @@ -37,6 +37,4 @@ With default options values, the metrics are sent to http://localhost:8080 every Use -q or --qemu option if you are running scaphandre on a hypervisor. In that case a label with the vm name will be added to all `qemu-system*` processes. This will allow to easily create charts consumption for each vm and defined which one is the top contributor. -## Metrics exposed - -Typically the Warp10 exporter is working the same way as the riemann and the prometheus exporters regarding metrics. Please look at details in [Prometheus exporter](exporter-prometheus.md) documentations to get the extensive list of metrics available. \ No newline at end of file +Metrics provided Scaphandre are documented [here](references/metrics.md). \ No newline at end of file From 681d3a7a4bcd6ead05f4286b3d141da86bf2d915 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Fri, 9 Jun 2023 15:57:25 +0200 Subject: [PATCH 199/303] doc: adding new system metrics --- docs_src/references/metrics.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs_src/references/metrics.md b/docs_src/references/metrics.md index 5b1178aa..5520c3b2 100644 --- a/docs_src/references/metrics.md +++ b/docs_src/references/metrics.md @@ -14,6 +14,8 @@ And some more deep metrics that you may want if you need to make more complex ca - `scaph_host_energy_microjoules` : Energy measurement for the whole host, as extracted from the sensor, in microjoules. (COUNTER) - `scaph_socket_power_microwatts{socket_id="$SOCKET_ID"}`: Power measurement relative to a CPU socket, in microwatts. SOCKET_ID being the socket numerical id (GAUGE) +Since 1.0.0 + If you hack scaph or just want to investigate its behavior, you may be interested in some internal metrics: - `scaph_self_memory_bytes`: Scaphandre memory usage, in bytes @@ -42,6 +44,23 @@ Here are available labels for the `scaph_process_power_consumption_microwatts` m - `instance`: this is a prometheus generated label to enable you to filter the metrics by the originating host. This is very useful when you monitor distributed services, so that you can not only sum the metrics for the same service on the different hosts but also see what instance of that service is consuming the most, or notice differences beteween hosts that may not have the same hardware, and so on... - `pid`: is the process id, which is useful if you want to track a specific process and have your eyes on what's happening on the host, but not so practical to use in a more general use case +Since 1.0.0 the following per-process metrics are available as well : + + scaph_process_cpu_usage_percentage + # CPU time consumed by the process, as a percentage of the capacity of all the CPU Cores + scaph_process_memory_bytes + # Physical RAM usage by the process, in bytes + scaph_process_memory_virtual_bytes + # Virtual RAM usage by the process, in bytes + scaph_process_disk_total_write_bytes + # Total data written on disk by the process, in bytes + scaph_process_disk_write_bytes + # Data written on disk by the process, in bytes + scaph_process_disk_read_bytes + # Data read on disk by the process, in bytes + scaph_process_disk_total_read_bytes + # Total data read on disk by the process, in bytes + ### Get container-specific labels on scaph_process_* metrics The flag --containers enables Scaphandre to collect data about the running Docker containers or Kubernetes pods on the local machine. This way, it adds specific labels to make filtering processes power consumption metrics by their encapsulation in containers easier. From d4d3ace5f0864a1dcd963c88200fa3549d8ada95 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Fri, 9 Jun 2023 15:59:07 +0200 Subject: [PATCH 200/303] doc: updating warp10 ref --- docs_src/references/exporter-warp10.md | 31 ++++++++++++-------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/docs_src/references/exporter-warp10.md b/docs_src/references/exporter-warp10.md index 0d239c03..92a8be1f 100644 --- a/docs_src/references/exporter-warp10.md +++ b/docs_src/references/exporter-warp10.md @@ -13,25 +13,22 @@ The `SCAPH_WARP10_WRITE_TOKEN` env var can be used to make it available to scaph Please refer to the warp10 documentation to know how to get the token in the first place. As always exporter's options can be displayed with `-h`: + ``` -scaphandre-warp10 -Warp10 exporter sends data to a Warp10 host, through HTTP - -USAGE: - scaphandre warp10 [FLAGS] [OPTIONS] - -FLAGS: - -h, --help Prints help information - -q, --qemu Tells scaphandre it is running on a Qemu hypervisor. - -V, --version Prints version information - -OPTIONS: - -H, --host Warp10 host's FQDN or IP address to send data to [default: localhost] - -p, --port TCP port to join Warp10 on the host [default: 8080] - -s, --scheme Either 'http' or 'https' [default: http] - -S, --step Time step between measurements, in seconds. [default: 30] - -t, --write-token Auth. token to write on Warp10 +Expose the metrics to a Warp10 host, through HTTP + +Usage: scaphandre warpten [OPTIONS] + +Options: + -H, --host FQDN or IP address of the Warp10 instance [default: localhost] + -p, --port TCP port of the Warp10 instance [default: 8080] + -S, --scheme "http" or "https" [default: http] + -t, --write-token Auth token to write data to Warp10. If not specified, you must set the env variable SCAPH_WARP10_WRITE_TOKEN + -s, --step Interval between two measurements, in seconds [default: 2] + -q, --qemu Apply labels to metrics of processes looking like a Qemu/KVM virtual machine + -h, --help Print help ``` + With default options values, the metrics are sent to http://localhost:8080 every 60 seconds Use -q or --qemu option if you are running scaphandre on a hypervisor. In that case a label with the vm name will be added to all `qemu-system*` processes. From c197451fc89f2366094be7e7ae0fe704b0d86993 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Fri, 9 Jun 2023 16:00:08 +0200 Subject: [PATCH 201/303] doc: updating rieman ref --- docs_src/references/exporter-riemann.md | 47 ++++++++++++++----------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/docs_src/references/exporter-riemann.md b/docs_src/references/exporter-riemann.md index 6699f224..255089d1 100644 --- a/docs_src/references/exporter-riemann.md +++ b/docs_src/references/exporter-riemann.md @@ -9,28 +9,35 @@ You can launch the Riemann exporter this way (running the default powercap_rapl scaphandre riemann As always exporter's options can be displayed with `-h`: + ``` -scaphandre-riemann -Riemann exporter sends power consumption metrics to a Riemann server - -USAGE: - scaphandre riemann [FLAGS] [OPTIONS] - -FLAGS: - -h, --help Prints help information - --mtls Connect to a Riemann server using mTLS. Parameters address, ca, cert and key must be defined. - -q, --qemu Instruct that scaphandre is running on an hypervisor - -V, --version Prints version information - -OPTIONS: - -a, --address
Riemann ipv6 or ipv4 address. If mTLS is used then server fqdn must be - provided [default: localhost] - -d, --dispatch Duration between metrics dispatch [default: 5] - -p, --port Riemann TCP port number [default: 5555] - --ca CA certificate file (.pem format) - --cert Client certificate file (.pem format) - --key Client RSA key +Expose the metrics to a Riemann server + +Usage: scaphandre riemann [OPTIONS] + +Options: + -a, --address
+ Address of the Riemann server. If mTLS is used this must be the server's FQDN [default: localhost] + -p, --port + TCP port number of the Riemann server [default: 5555] + -d, --dispatch-interval + Duration between each metric dispatch, in seconds [default: 5] + -q, --qemu + Apply labels to metrics of processes looking like a Qemu/KVM virtual machine + --containers + Monitor and apply labels for processes running as containers + --mtls + Connect to Riemann using mTLS instead of plain TCP + --ca + CA certificate file (.pem format) + --cert + Client certificate file (.pem format) + --key + Client RSA key file + -h, --help + Print help ``` + With default options values, the metrics are sent to http://localhost:5555 every 5 seconds Use `--mtls` option to connect to a Riemann server using mTLS. In such case, you must provide the following parameters: From 64dc375330fd29909f208292cc36c4e41085a150 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Fri, 9 Jun 2023 16:22:26 +0200 Subject: [PATCH 202/303] doc: improved metrics section --- docs_src/references/metrics.md | 43 +++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/docs_src/references/metrics.md b/docs_src/references/metrics.md index 5520c3b2..cb852ce6 100644 --- a/docs_src/references/metrics.md +++ b/docs_src/references/metrics.md @@ -1,6 +1,7 @@ # Metrics exposed by Scaphandre -All metrics have a HELP section provided on /metrics (or whatever suffix you choosed to expose them). +With [Stdout](references/exporter-stdout.md) exporter, you can see all metrics available on your machine with flag `--raw-metrics`. +With [prometheus](references/exporter-prometheus.md) exporter, all metrics have a HELP section provided on /metrics (or whatever suffix you choosed to expose them). Here are some key metrics that you will most probably be interested in: @@ -14,7 +15,24 @@ And some more deep metrics that you may want if you need to make more complex ca - `scaph_host_energy_microjoules` : Energy measurement for the whole host, as extracted from the sensor, in microjoules. (COUNTER) - `scaph_socket_power_microwatts{socket_id="$SOCKET_ID"}`: Power measurement relative to a CPU socket, in microwatts. SOCKET_ID being the socket numerical id (GAUGE) -Since 1.0.0 +If your machine provides RAPL PSYS domain (see [RAPL domains](explanations/rapl-domains.md)), you can get the raw energy counter for PSYS/platform with `scaph_host_rapl_psys_microjoules`. Note that `scaph_host_power_microwatts` is based on this PSYS counter if it is available. + +Since 1.0.0 the following host metrics are availalable as well ; + +- `scaph_host_swap_total_bytes`: Total swap space on the host, in bytes. +- `scaph_host_swap_free_bytes`: Swap space free to be used on the host, in bytes. +- `scaph_host_memory_free_bytes`: Random Access Memory free to be used (not reused) on the host, in bytes. +- `scaph_host_memory_available_bytes`: Random Access Memory available to be re-used on the host, in bytes. +- `scaph_host_memory_total_bytes`: Random Access Memory installed on the host, in bytes. +- `scaph_host_disk_total_bytes`: Total disk size, in bytes. +- `scaph_host_disk_available_bytes`: Available disk space, in bytes. + +Disk metrics have the following labels : disk_file_system, disk_is_removable, disk_type, disk_mount_point, disk_name + +- `scaph_host_cpu_frequency`: Global frequency of all the cpus. In MegaHertz +- `scaph_host_load_avg_fifteen`: Load average on 15 minutes. +- `scaph_host_load_avg_five`: Load average on 5 minutes. +- `scaph_host_load_avg_one`: Load average on 1 minute. If you hack scaph or just want to investigate its behavior, you may be interested in some internal metrics: @@ -46,20 +64,13 @@ Here are available labels for the `scaph_process_power_consumption_microwatts` m Since 1.0.0 the following per-process metrics are available as well : - scaph_process_cpu_usage_percentage - # CPU time consumed by the process, as a percentage of the capacity of all the CPU Cores - scaph_process_memory_bytes - # Physical RAM usage by the process, in bytes - scaph_process_memory_virtual_bytes - # Virtual RAM usage by the process, in bytes - scaph_process_disk_total_write_bytes - # Total data written on disk by the process, in bytes - scaph_process_disk_write_bytes - # Data written on disk by the process, in bytes - scaph_process_disk_read_bytes - # Data read on disk by the process, in bytes - scaph_process_disk_total_read_bytes - # Total data read on disk by the process, in bytes +- `scaph_process_cpu_usage_percentage`: CPU time consumed by the process, as a percentage of the capacity of all the CPU Cores +- `scaph_process_memory_bytes`: Physical RAM usage by the process, in bytes +- `scaph_process_memory_virtual_bytes`: Virtual RAM usage by the process, in bytes +- `scaph_process_disk_total_write_bytes`: Total data written on disk by the process, in bytes +- `scaph_process_disk_write_bytes`: Data written on disk by the process, in bytes +- `scaph_process_disk_read_bytes`: Data read on disk by the process, in bytes +- `scaph_process_disk_total_read_bytes`: Total data read on disk by the process, in bytes ### Get container-specific labels on scaph_process_* metrics From 84e9139f84a276ac544bfa5eb6ee2447e8e64693 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Fri, 9 Jun 2023 16:47:18 +0200 Subject: [PATCH 203/303] doc: improved metrics section --- docs_src/SUMMARY.md | 4 ++++ docs_src/references/metrics.md | 10 +++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/docs_src/SUMMARY.md b/docs_src/SUMMARY.md index 34dbd8dc..af8e0759 100644 --- a/docs_src/SUMMARY.md +++ b/docs_src/SUMMARY.md @@ -21,9 +21,12 @@ - [How scaphandre computes per process power consumption](explanations/how-scaph-computes-per-process-power-consumption.md) - [Internal structure](explanations/internal-structure.md) - [About containers](explanations/about-containers.md) +- [About RAPL domains](explanations/rapl-domains.md) # References +- [Metrics available](references/metrics.md) + ## Exporters - [JSON exporter](references/exporter-json.md) @@ -36,6 +39,7 @@ ## Sensors - [PowercapRAPL sensor](references/sensor-powercap_rapl.md) +- [MSRRAPL sensor](references/sensor-msr_rapl.md) [Why this project ?](why.md) [Compatibility](compatibility.md) diff --git a/docs_src/references/metrics.md b/docs_src/references/metrics.md index cb852ce6..e9db61c6 100644 --- a/docs_src/references/metrics.md +++ b/docs_src/references/metrics.md @@ -1,21 +1,21 @@ # Metrics exposed by Scaphandre -With [Stdout](references/exporter-stdout.md) exporter, you can see all metrics available on your machine with flag `--raw-metrics`. -With [prometheus](references/exporter-prometheus.md) exporter, all metrics have a HELP section provided on /metrics (or whatever suffix you choosed to expose them). +With [Stdout](exporter-stdout.md) exporter, you can see all metrics available on your machine with flag `--raw-metrics`. +With [prometheus](exporter-prometheus.md) exporter, all metrics have a HELP section provided on /metrics (or whatever suffix you choosed to expose them). Here are some key metrics that you will most probably be interested in: -- `scaph_host_power_microwatts`: Power measurement on the whole host, in microwatts (GAUGE) +- `scaph_host_power_microwatts`: Aggregation of several measurements to give a try on the power usage of the the whole host, in microwatts (GAUGE). It might be the same as RAPL PSYS (see [RAPL domains](../explanations/rapl-domains.md)) measurement if available, or a combination of RAPL PKG and DRAM domains + an estimation of other hardware componentes power usage. - `scaph_process_power_consumption_microwatts{exe="$PROCESS_EXE",pid="$PROCESS_PID",cmdline="path/to/exe --and-maybe-options"}`: Power consumption due to the process, measured on at the topology level, in microwatts. PROCESS_EXE being the name of the executable and PROCESS_PID being the pid of the process. (GAUGE) -For more details on that metric labels, see [this section](#scaph_process_power_consumption_microwatts). +For more details on that metric labels, see [this section](#getting-per-process-data-with-scaph_process_-metrics). And some more deep metrics that you may want if you need to make more complex calculations and data processing: - `scaph_host_energy_microjoules` : Energy measurement for the whole host, as extracted from the sensor, in microjoules. (COUNTER) - `scaph_socket_power_microwatts{socket_id="$SOCKET_ID"}`: Power measurement relative to a CPU socket, in microwatts. SOCKET_ID being the socket numerical id (GAUGE) -If your machine provides RAPL PSYS domain (see [RAPL domains](explanations/rapl-domains.md)), you can get the raw energy counter for PSYS/platform with `scaph_host_rapl_psys_microjoules`. Note that `scaph_host_power_microwatts` is based on this PSYS counter if it is available. +If your machine provides RAPL PSYS domain (see [RAPL domains](../explanations/rapl-domains.md)), you can get the raw energy counter for PSYS/platform with `scaph_host_rapl_psys_microjoules`. Note that `scaph_host_power_microwatts` is based on this PSYS counter if it is available. Since 1.0.0 the following host metrics are availalable as well ; From 053b3dee81c583752131ae6d7c02a1c1b85c9ee5 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Fri, 9 Jun 2023 16:52:42 +0200 Subject: [PATCH 204/303] feat!: using rapl psys domain for host_power when available doc: pushing in progress doc about rapl domains --- docs_src/explanations/rapl-domains.md | 72 ++++++++++++++++++ src/exporters/mod.rs | 17 +++++ src/sensors/mod.rs | 104 ++++++++++++++++++-------- src/sensors/powercap_rapl.rs | 93 +++++++++++++++++------ 4 files changed, 231 insertions(+), 55 deletions(-) create mode 100644 docs_src/explanations/rapl-domains.md diff --git a/docs_src/explanations/rapl-domains.md b/docs_src/explanations/rapl-domains.md new file mode 100644 index 00000000..b22df3e9 --- /dev/null +++ b/docs_src/explanations/rapl-domains.md @@ -0,0 +1,72 @@ +# Explanation on RAPL domains (what we know so far) + +## PSYS + +[Kepler documentation](https://sustainable-computing.io/design/metrics/) says PSYS "is the energy consumed by the "System on a chipt" (SOC)." +"Generally, this metric is the host energy consumption (from acpi)." but also "Generally, this metric is the **host energy consumption (from acpi) less the RAPL Package and DRAM**." + +[https://zhenkai-zhang.github.io/papers/rapl.pdf](https://zhenkai-zhang.github.io/papers/rapl.pdf) says +Microarchitecture Package CORE (PP0) UNCORE (PP1) DRAM +Haswell Y/Y Y/N Y/N Y/Y +Broadwell Y/Y Y/N Y/N Y/Y +Skylake Y/Y Y/Y Y/N Y/Y +Kaby Lake Y/Y Y/Y Y/N Y/Y + + +[https://www.arcsi.fr/doc/platypus.pdf](https://www.arcsi.fr/doc/platypus.pdf) says PSYS is "covering the entire SoC.". + +http://www.micheledellipaoli.com/documents/EnergyConsumptionAnalysis.pdf says +"PSys: (introduced with Intel Skylake) monitors and con- +trols the thermal and power specifications of the entire +SoC and it is useful especially when the source of the +power consumption is neither the CPU nor the GPU. For +multi-socket server systems, each socket reports its own +RAPL values." + +https://hal.science/hal-03809858/document says +"PSys. Domain available on some Intel architectures, to monitor and control the thermal +and power specifications of the entire system on the chip (SoC), instead of just CPU or +GPU. It includes the power consumption of the package domain, System Agent, PCH, +eDRAM, and a few more domains on a single-socket SoC" + +![RAPL domains](rapl.png) + +https://github.com/hubblo-org/scaphandre/issues/116 +https://github.com/hubblo-org/scaphandre/issues/241 +https://github.com/hubblo-org/scaphandre/issues/140 +https://github.com/hubblo-org/scaphandre/issues/289 +https://github.com/hubblo-org/scaphandre/issues/117 +https://github.com/hubblo-org/scaphandre/issues/25 +https://github.com/hubblo-org/scaphandre/issues/316 +https://github.com/hubblo-org/scaphandre/issues/318 + +PSYS MSR is "MSR_PLATFORM_ENERGY_STATUS" +https://copyprogramming.com/howto/perf-power-consumption-measure-how-does-it-work + +https://pyjoules.readthedocs.io/en/stable/devices/intel_cpu.html + +Problems of RAPL on Saphire Rapids +https://community.intel.com/t5/Software-Tuning-Performance/RAPL-quirks-on-Sapphire-Rapids/td-p/1446761 + +Misc info on RAPL +https://web.eece.maine.edu/~vweaver/projects/rapl/ + +PSYS MSR have a different layout than PKG and dram +https://patchwork.kernel.org/project/linux-pm/patch/20211207131734.2607104-1-rui.zhang@intel.com/ + +https://edc.intel.com/content/www/us/en/design/ipla/software-development-platforms/client/platforms/alder-lake-desktop/12th-generation-intel-core-processors-datasheet-volume-1-of-2/010/power-management/ ==> intel doc avout thermal and power management +https://edc.intel.com/content/www/us/en/design/ipla/software-development-platforms/client/platforms/alder-lake-desktop/12th-generation-intel-core-processors-datasheet-volume-1-of-2/002/platform-power-control/ ==> about psys + +https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html ==> intel software developer manual + +CVE-8694/8695 and mitigation by intel +https://www.intel.com/content/www/us/en/developer/articles/technical/software-security-guidance/advisory-guidance/running-average-power-limit-energy-reporting.html + +Patch in the kernel +https://groups.google.com/g/linux.kernel/c/x_7RbqcrxAs +Patch in powercap +https://lkml.iu.edu/hypermail/linux/kernel/1603.2/02415.html +https://lkml.kernel.org/lkml/1460930581-29748-1-git-send-email-srinivas.pandruvada@linux.intel.com/T/ + +Random +https://stackoverflow.com/questions/55956287/perf-power-consumption-measure-how-does-it-work \ No newline at end of file diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index 2d03b6d0..3d0b1bd6 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -610,6 +610,23 @@ impl MetricGenerator { description: String::from("Total swap space on the host, in bytes."), metric_value: MetricValueType::Text(metric_value.value), }); + + if let Some(psys) = self.topology.get_rapl_psys_energy_microjoules() { + self.data.push(Metric { + name: String::from("scaph_host_rapl_psys_microjoules"), + metric_type: String::from("counter"), + ttl: 60.0, + timestamp: psys.timestamp, + hostname: self.hostname.clone(), + state: String::from("ok"), + tags: vec!["scaphandre".to_string()], + attributes: HashMap::new(), + description: String::from( + "Raw extract of RAPL PSYS domain energy value, in microjoules", + ), + metric_value: MetricValueType::Text(psys.value), + }) + } } /// Generate socket metrics. diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 1d9fd3e8..4485fd44 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -11,7 +11,7 @@ pub mod units; pub mod utils; #[cfg(target_os = "linux")] use procfs::{CpuInfo, CpuTime, KernelStats}; -use std::{collections::HashMap, error::Error, fmt, mem::size_of_val, str, time::Duration}; +use std::{collections::HashMap, error::Error, fmt, fs, mem::size_of_val, str, time::Duration}; #[allow(unused_imports)] use sysinfo::{CpuExt, Pid, System, SystemExt}; use sysinfo::{DiskExt, DiskType}; @@ -64,26 +64,28 @@ impl RecordGenerator for Topology { /// and returns a clone of this record. /// fn refresh_record(&mut self) { - let mut value: u64 = 0; - let mut last_timestamp = current_system_time_since_epoch(); - for s in self.get_sockets() { - let records = s.get_records_passive(); - if !records.is_empty() { - let last = records.last(); - let last_record = last.unwrap(); - last_timestamp = last_record.timestamp; - let res = last_record.value.trim(); - if let Ok(val) = res.parse::() { - value += val; - } else { - trace!("couldn't parse value : {}", res); - } - } - } - debug!("Record value from topo (addition of sockets) : {}", value); - let record = Record::new(last_timestamp, value.to_string(), units::Unit::MicroJoule); + //let mut value: u64 = 0; + //let mut last_timestamp = current_system_time_since_epoch(); + //for s in self.get_sockets() { + // let records = s.get_records_passive(); + // if !records.is_empty() { + // let last = records.last(); + // let last_record = last.unwrap(); + // last_timestamp = last_record.timestamp; + // let res = last_record.value.trim(); + // if let Ok(val) = res.parse::() { + // value += val; + // } else { + // trace!("couldn't parse value : {}", res); + // } + // } + //} + //debug!("Record value from topo (addition of sockets) : {}", value); + //let record = Record::new(last_timestamp, value.to_string(), units::Unit::MicroJoule); - self.record_buffer.push(record); + if let Ok(record) = self.read_record() { + self.record_buffer.push(record); + } if !self.record_buffer.is_empty() { self.clean_old_records(); @@ -416,20 +418,30 @@ impl Topology { .record_buffer .get(self.record_buffer.len() - 2) .unwrap(); - let last_microjoules = last_record.value.parse::().unwrap(); - let previous_microjoules = previous_record.value.parse::().unwrap(); - if previous_microjoules > last_microjoules { - return None; + + if let Ok(last_microjoules) = last_record.value.trim().parse::() { + if let Ok(previous_microjoules) = previous_record.value.trim().parse::() { + if previous_microjoules > last_microjoules { + return None; + } + let microjoules = last_microjoules - previous_microjoules; + let time_diff = last_record.timestamp.as_secs_f64() + - previous_record.timestamp.as_secs_f64(); + let microwatts = microjoules as f64 / time_diff; + return Some(Record::new( + last_record.timestamp, + (microwatts as u64).to_string(), + units::Unit::MicroWatt, + )); + } else { + warn!( + "Could'nt get previous_microjoules: {}", + previous_record.value + ); + } + } else { + warn!("Could'nt get last_microjoules: {}", last_record.value); } - let microjoules = last_microjoules - previous_microjoules; - let time_diff = - last_record.timestamp.as_secs_f64() - previous_record.timestamp.as_secs_f64(); - let microwatts = microjoules as f64 / time_diff; - return Some(Record::new( - last_record.timestamp, - (microwatts as u64).to_string(), - units::Unit::MicroWatt, - )); } None } @@ -856,6 +868,32 @@ impl Topology { } None } + + pub fn get_rapl_psys_energy_microjoules(&self) -> Option { + if let Some(psys) = self._sensor_data.get("psys") { + if let Ok(val) = &fs::read_to_string(format!("{psys}/energy_uj")) { + return Some(Record::new( + current_system_time_since_epoch(), + val.to_string(), + units::Unit::MicroJoule, + )); + } + } + None + } + + //pub fn get_rapl_psys_power_microwatts(&self) -> Option { + // if let Some(psys) = self._sensor_data.get("psys") { + // if let Ok(val) = &fs::read_to_string(format!("{psys}/energy_uj")) { + // return Some(Record::new( + // current_system_time_since_epoch(), + // val.to_string(), + // units::Unit::MicroJoule + // )); + // } + // } + // None + //} } // !!!!!!!!!!!!!!!!! CPUSocket !!!!!!!!!!!!!!!!!!!!!!! diff --git a/src/sensors/powercap_rapl.rs b/src/sensors/powercap_rapl.rs index 81722a45..8b62dc56 100644 --- a/src/sensors/powercap_rapl.rs +++ b/src/sensors/powercap_rapl.rs @@ -7,6 +7,8 @@ use std::collections::HashMap; use std::error::Error; use std::{env, fs}; +use super::units::Unit; + pub const DEFAULT_BUFFER_PER_SOCKET_MAX_KBYTES: u16 = 1; pub const DEFAULT_BUFFER_PER_DOMAIN_MAX_KBYTES: u16 = 1; @@ -70,11 +72,33 @@ impl PowercapRAPLSensor { impl RecordReader for Topology { fn read_record(&self) -> Result> { - Ok(Record { - timestamp: current_system_time_since_epoch(), - value: String::from("5"), - unit: MicroJoule, - }) + // if psys is available, return psys + // else return pkg + dram + F(disks) + + if let Some(psys_record) = self.get_rapl_psys_energy_microjoules() { + Ok(psys_record) + } else { + let mut total = 0; + for s in &self.sockets { + if let Ok(r) = s.read_record() { + if let Ok(val) = r.value.parse::() { + total += val; + } else { + trace!("could'nt convert {} to i32", r.value); + } + } + //for d in &s.domains { + // if let Ok(r) = d.read_record() { + // total = total + r.value.parse::().unwrap() + // } + //} + } + Ok(Record::new( + current_system_time_since_epoch(), + total.to_string(), + Unit::MicroJoule, + )) + } } } impl RecordReader for CPUSocket { @@ -164,25 +188,50 @@ impl Sensor for PowercapRAPLSensor { if !re_domain_matched { warn!("Couldn't find domain folders from powercap. Fallback on socket folders."); warn!("Scaphandre will not be able to provide per-domain data."); + let mut found = false; for folder in fs::read_dir(&self.base_path).unwrap() { let folder_name = String::from(folder.unwrap().path().to_str().unwrap()); - if re_socket.is_match(&folder_name) { - let mut splitted = folder_name.split(':'); - let _ = splitted.next(); - let socket_id = String::from(splitted.next().unwrap()).parse().unwrap(); - let mut sensor_data_for_socket = HashMap::new(); - sensor_data_for_socket.insert( - String::from("source_file"), - format!("{}/intel-rapl:{}/energy_uj", self.base_path, socket_id), - ); - topo.safe_add_socket( - socket_id, - vec![], - vec![], - format!("{}/intel-rapl:{}/energy_uj", self.base_path, socket_id), - self.buffer_per_socket_max_kbytes, - sensor_data_for_socket, - ) + if let Ok(domain_name) = &fs::read_to_string(format!("{folder_name}/name")) { + if domain_name != "psys" && re_socket.is_match(&folder_name) { + let mut splitted = folder_name.split(':'); + let _ = splitted.next(); + let socket_id = String::from(splitted.next().unwrap()).parse().unwrap(); + let mut sensor_data_for_socket = HashMap::new(); + sensor_data_for_socket.insert( + String::from("source_file"), + format!("{}/intel-rapl:{}/energy_uj", self.base_path, socket_id), + ); + topo.safe_add_socket( + socket_id, + vec![], + vec![], + format!("{}/intel-rapl:{}/energy_uj", self.base_path, socket_id), + self.buffer_per_socket_max_kbytes, + sensor_data_for_socket, + ); + found = true; + } + } else { + warn!("Couldn't read RAPL folder name : {folder_name}"); + } + } + if !found { + warn!("Could'nt find any RAPL PKG domain (not psys)."); + } + } + for folder in fs::read_dir(&self.base_path).unwrap() { + let folder_name = String::from(folder.unwrap().path().to_str().unwrap()); + match &fs::read_to_string(format!("{folder_name}/name")) { + Ok(domain_name) => { + let domain_name_trimed = domain_name.trim(); + if domain_name_trimed == "psys" { + debug!("Found PSYS domain RAPL folder."); + topo._sensor_data + .insert(String::from("psys"), folder_name); + } + } + Err(e) => { + debug!("Got error while reading {folder_name}: {e}"); } } } From 05a18c1318ff0270cc7098d2a9c1bb47f9ab284b Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Fri, 9 Jun 2023 16:54:36 +0200 Subject: [PATCH 205/303] doc: adding missing image --- docs_src/explanations/rapl.png | Bin 0 -> 152084 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs_src/explanations/rapl.png diff --git a/docs_src/explanations/rapl.png b/docs_src/explanations/rapl.png new file mode 100644 index 0000000000000000000000000000000000000000..22208db9be172b4cb4415c3696c1577ed87a4c15 GIT binary patch literal 152084 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

`c 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Ν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 literal 0 HcmV?d00001 From c79a376b2368c8f834e910e3dd04555be6092d4b Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Fri, 9 Jun 2023 16:54:56 +0200 Subject: [PATCH 206/303] style: clippy --- src/sensors/powercap_rapl.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sensors/powercap_rapl.rs b/src/sensors/powercap_rapl.rs index 8b62dc56..830f0e9f 100644 --- a/src/sensors/powercap_rapl.rs +++ b/src/sensors/powercap_rapl.rs @@ -226,8 +226,7 @@ impl Sensor for PowercapRAPLSensor { let domain_name_trimed = domain_name.trim(); if domain_name_trimed == "psys" { debug!("Found PSYS domain RAPL folder."); - topo._sensor_data - .insert(String::from("psys"), folder_name); + topo._sensor_data.insert(String::from("psys"), folder_name); } } Err(e) => { From 5c99eafc72801c115fb50e4fd93f01b1bbe1295a Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 12 Jun 2023 12:14:52 +0200 Subject: [PATCH 207/303] feat: adding mmio metric when available --- src/exporters/mod.rs | 21 ++++++++++++++++++--- src/sensors/mod.rs | 30 +++++++++++++++++++++++++++++- src/sensors/powercap_rapl.rs | 31 +++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index 3d0b1bd6..b40f84fc 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -634,14 +634,13 @@ impl MetricGenerator { let sockets = self.topology.get_sockets_passive(); for socket in sockets { let records = socket.get_records_passive(); + let mut attributes = HashMap::new(); + attributes.insert("socket_id".to_string(), socket.id.to_string()); if !records.is_empty() { let metric = records.last().unwrap(); let metric_value = metric.value.clone(); let metric_timestamp = metric.timestamp; - let mut attributes = HashMap::new(); - attributes.insert("socket_id".to_string(), socket.id.to_string()); - self.data.push(Metric { name: String::from("scaph_socket_energy_microjoules"), metric_type: String::from("counter"), @@ -674,6 +673,22 @@ impl MetricGenerator { }); } } + if let Some(mmio) = socket.get_rapl_mmio_energy_microjoules() { + self.data.push(Metric { + name: String::from("scaph_socket_rapl_mmio_energy_microjoules"), + metric_type: String::from("counter"), + ttl: 60.0, + timestamp: mmio.timestamp, + hostname: self.hostname.clone(), + state: String::from("ok"), + tags: vec!["scaphandre".to_string()], + attributes: attributes.clone(), + description: format!( + "Energy counter from RAPL mmio interface for Package-0 of CPU socket {}", socket.id + ), + metric_value: MetricValueType::Text(mmio.value) + }); + } for domain in socket.get_domains_passive() { let records = domain.get_records_passive(); if !records.is_empty() { diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 4485fd44..b6ebcc1e 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -919,7 +919,7 @@ pub struct CPUSocket { pub stat_buffer: Vec, /// #[allow(dead_code)] - sensor_data: HashMap, + pub sensor_data: HashMap, } impl RecordGenerator for CPUSocket { @@ -1210,6 +1210,20 @@ impl CPUSocket { } None } + + pub fn get_rapl_mmio_energy_microjoules(&self) -> Option { + if let Some(mmio) = self.sensor_data.get("mmio") { + if let Ok(val) = &fs::read_to_string(format!("{mmio}/energy_uj")) { + return Some(Record::new( + current_system_time_since_epoch(), + val.to_string(), + units::Unit::MicroJoule + )); + } + } + None + } + } // !!!!!!!!!!!!!!!!! CPUCore !!!!!!!!!!!!!!!!!!!!!!! @@ -1356,6 +1370,20 @@ impl Domain { } None } + + pub fn get_rapl_mmio_energy_microjoules(&self) -> Option { + if let Some(mmio) = self.sensor_data.get("mmio") { + if let Ok(val) = &fs::read_to_string(format!("{mmio}/energy_uj")) { + return Some(Record::new( + current_system_time_since_epoch(), + val.to_string(), + units::Unit::MicroJoule + )); + } + } + None + } + } impl fmt::Display for Domain { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/src/sensors/powercap_rapl.rs b/src/sensors/powercap_rapl.rs index 830f0e9f..3a1bba79 100644 --- a/src/sensors/powercap_rapl.rs +++ b/src/sensors/powercap_rapl.rs @@ -138,9 +138,12 @@ impl Sensor for PowercapRAPLSensor { let mut topo = Topology::new(HashMap::new()); let re_socket = Regex::new(r"^.*/intel-rapl:\d+$").unwrap(); let re_domain = Regex::new(r"^.*/intel-rapl:\d+:\d+$").unwrap(); + let re_socket_mmio = Regex::new(r"^.*/intel-rapl-mmio:\d+$").unwrap(); + let re_domain_mmio = Regex::new(r"^.*/intel-rapl-mmio:\d+:\d+$").unwrap(); let mut re_domain_matched = false; for folder in fs::read_dir(&self.base_path).unwrap() { let folder_name = String::from(folder.unwrap().path().to_str().unwrap()); + warn!("working on {folder_name}"); // let's catch domain folders if re_domain.is_match(&folder_name) { re_domain_matched = true; @@ -183,6 +186,34 @@ impl Sensor for PowercapRAPLSensor { sensor_data_for_domain, ); } + } else if re_socket_mmio.is_match(&folder_name) { + warn!("matched {folder_name}"); + let mut splitted = folder_name.split(':'); + let _ = splitted.next(); + let socket_id: u16 = String::from(splitted.next().unwrap()).parse().unwrap(); + for s in topo.get_sockets() { + if socket_id == s.id { + s.sensor_data.insert( + String::from("mmio"), + format!("{}/intel-rapl-mmio:{}/energy_uj", self.base_path, socket_id), + ); + } + } + } else if re_domain_mmio.is_match(&folder_name) { + warn!("matched {folder_name}"); + let mut splitted = folder_name.split(':'); + let _ = splitted.next(); + let socket_id: u16 = String::from(splitted.next().unwrap()).parse().unwrap(); + let domain_id: u16 = String::from(splitted.next().unwrap()).parse().unwrap(); + for s in topo.get_sockets() { + if socket_id == s.id { + for d in s.get_domains() { + if d.id == domain_id && &d.name.trim() == &fs::read_to_string(format!("{folder_name}/name")).unwrap().trim() { + d.sensor_data.insert(String::from("mmio"), format!("{}/energy_uj", folder_name)); + } + } + } + } } } if !re_domain_matched { From f1608bcd05a1c9775e0bcf27ac0039928102f65b Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 8 Aug 2023 18:49:43 +0200 Subject: [PATCH 208/303] feat: adding label to host power/energy metrics to know where it comes from --- src/exporters/mod.rs | 15 +++++++++++---- src/sensors/mod.rs | 8 +++----- src/sensors/powercap_rapl.rs | 16 ++++++++++++---- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index b40f84fc..1443c6f2 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -442,6 +442,12 @@ impl MetricGenerator { if !records.is_empty() { let record = records.last().unwrap(); let host_energy_microjoules = record.value.clone(); + let mut attributes = HashMap::new(); + if self.topology._sensor_data.contains_key("psys") { + attributes.insert(String::from("value_source"), String::from("rapl_psys")); + } else if self.topology._sensor_data.contains_key("source_file") { + attributes.insert(String::from("value_source"), String::from("rapl_pkg")); + } self.data.push(Metric { name: String::from("scaph_host_energy_microjoules"), @@ -451,7 +457,7 @@ impl MetricGenerator { hostname: self.hostname.clone(), state: String::from("ok"), tags: vec!["scaphandre".to_string()], - attributes: HashMap::new(), + attributes: attributes.clone(), description: String::from( "Energy measurement for the whole host, as extracted from the sensor, in microjoules.", ), @@ -467,7 +473,7 @@ impl MetricGenerator { hostname: self.hostname.clone(), state: String::from("ok"), tags: vec!["scaphandre".to_string()], - attributes: HashMap::new(), + attributes, description: String::from("Power measurement on the whole host, in microwatts"), metric_value: MetricValueType::Text(power.value), }); @@ -684,9 +690,10 @@ impl MetricGenerator { tags: vec!["scaphandre".to_string()], attributes: attributes.clone(), description: format!( - "Energy counter from RAPL mmio interface for Package-0 of CPU socket {}", socket.id + "Energy counter from RAPL mmio interface for Package-0 of CPU socket {}", + socket.id ), - metric_value: MetricValueType::Text(mmio.value) + metric_value: MetricValueType::Text(mmio.value), }); } for domain in socket.get_domains_passive() { diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index b6ebcc1e..6aac0511 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -56,7 +56,7 @@ pub struct Topology { /// Sorted list of all domains names pub domains_names: Option>, /// Sensor-specific data needed in the topology - _sensor_data: HashMap, + pub _sensor_data: HashMap, } impl RecordGenerator for Topology { @@ -1217,13 +1217,12 @@ impl CPUSocket { return Some(Record::new( current_system_time_since_epoch(), val.to_string(), - units::Unit::MicroJoule + units::Unit::MicroJoule, )); } } None } - } // !!!!!!!!!!!!!!!!! CPUCore !!!!!!!!!!!!!!!!!!!!!!! @@ -1377,13 +1376,12 @@ impl Domain { return Some(Record::new( current_system_time_since_epoch(), val.to_string(), - units::Unit::MicroJoule + units::Unit::MicroJoule, )); } } None } - } impl fmt::Display for Domain { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/src/sensors/powercap_rapl.rs b/src/sensors/powercap_rapl.rs index 3a1bba79..82c37e1a 100644 --- a/src/sensors/powercap_rapl.rs +++ b/src/sensors/powercap_rapl.rs @@ -197,7 +197,7 @@ impl Sensor for PowercapRAPLSensor { String::from("mmio"), format!("{}/intel-rapl-mmio:{}/energy_uj", self.base_path, socket_id), ); - } + } } } else if re_domain_mmio.is_match(&folder_name) { warn!("matched {folder_name}"); @@ -208,11 +208,19 @@ impl Sensor for PowercapRAPLSensor { for s in topo.get_sockets() { if socket_id == s.id { for d in s.get_domains() { - if d.id == domain_id && &d.name.trim() == &fs::read_to_string(format!("{folder_name}/name")).unwrap().trim() { - d.sensor_data.insert(String::from("mmio"), format!("{}/energy_uj", folder_name)); + if d.id == domain_id + && d.name.trim() + == fs::read_to_string(format!("{folder_name}/name")) + .unwrap() + .trim() + { + d.sensor_data.insert( + String::from("mmio"), + format!("{}/energy_uj", folder_name), + ); } } - } + } } } } From 92ba2dfcf8280b4e68e8b29258391c1a7efcbcb4 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 8 Aug 2023 19:09:32 +0200 Subject: [PATCH 209/303] style: clippy and fmt --- src/exporters/json.rs | 2 +- src/sensors/mod.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/exporters/json.rs b/src/exporters/json.rs index f0cd1b51..14b91254 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -228,7 +228,7 @@ impl JsonExporter { let mut res: Vec = vec![]; for m in metrics { let metric_disk_name = m.attributes.get("disk_name").unwrap(); - if let Some(mut disk) = res.iter_mut().find(|x| metric_disk_name == &x.disk_name) { + if let Some(disk) = res.iter_mut().find(|x| metric_disk_name == &x.disk_name) { info!("editing disk"); disk.disk_name = metric_disk_name.clone(); if m.name == "scaph_host_disk_available_bytes" { diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 6aac0511..dc915f22 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -278,8 +278,7 @@ impl Topology { /// to appropriate CPUSocket instance from self.sockets pub fn add_cpu_cores(&mut self) { if let Some(mut cores) = Topology::generate_cpu_cores() { - while !cores.is_empty() { - let c = cores.pop().unwrap(); + while let Some(c) = cores.pop() { let socket_id = &c .attributes .get("physical id") From c64aeb4911ce05f21d95d884c4a3372897ad1315 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 8 Aug 2023 19:09:52 +0200 Subject: [PATCH 210/303] chore: adding label for windows drv as a value source --- src/exporters/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index 1443c6f2..89a28e87 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -444,9 +444,11 @@ impl MetricGenerator { let host_energy_microjoules = record.value.clone(); let mut attributes = HashMap::new(); if self.topology._sensor_data.contains_key("psys") { - attributes.insert(String::from("value_source"), String::from("rapl_psys")); + attributes.insert(String::from("value_source"), String::from("powercap_rapl_psys")); } else if self.topology._sensor_data.contains_key("source_file") { - attributes.insert(String::from("value_source"), String::from("rapl_pkg")); + attributes.insert(String::from("value_source"), String::from("powercap_rapl_pkg")); + } else if self.topology._sensor_data.contains_key("DRIVER_NAME") { + attributes.insert(String::from("value_source"), String::from("scaphandredrv_rapl_pkg")); } self.data.push(Metric { From 4501805a60ca1455a2796f7786482b1656c420d7 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 8 Aug 2023 19:10:11 +0200 Subject: [PATCH 211/303] style: fm --- src/exporters/mod.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index 89a28e87..f215d68b 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -444,11 +444,20 @@ impl MetricGenerator { let host_energy_microjoules = record.value.clone(); let mut attributes = HashMap::new(); if self.topology._sensor_data.contains_key("psys") { - attributes.insert(String::from("value_source"), String::from("powercap_rapl_psys")); + attributes.insert( + String::from("value_source"), + String::from("powercap_rapl_psys"), + ); } else if self.topology._sensor_data.contains_key("source_file") { - attributes.insert(String::from("value_source"), String::from("powercap_rapl_pkg")); + attributes.insert( + String::from("value_source"), + String::from("powercap_rapl_pkg"), + ); } else if self.topology._sensor_data.contains_key("DRIVER_NAME") { - attributes.insert(String::from("value_source"), String::from("scaphandredrv_rapl_pkg")); + attributes.insert( + String::from("value_source"), + String::from("scaphandredrv_rapl_pkg"), + ); } self.data.push(Metric { From c588741581901c25fcc503a50fb72489add2c961 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 8 Aug 2023 19:26:58 +0200 Subject: [PATCH 212/303] fix: applying fixes from windows branch for prom push --- src/exporters/prometheuspush.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/exporters/prometheuspush.rs b/src/exporters/prometheuspush.rs index 406b5bf1..ef343178 100644 --- a/src/exporters/prometheuspush.rs +++ b/src/exporters/prometheuspush.rs @@ -97,9 +97,7 @@ impl Exporter for PrometheusPushExporter { loop { metric_generator.topology.refresh(); metric_generator.gen_all_metrics(); - let mut body = String::from( - "# HELP mymetric this is my metric\n# TYPE mymetric gauge\nmymetric 50\n", - ); + let mut body = String::from(""); let mut metrics_pushed: Vec = vec![]; //let mut counter = 0; for mut m in metric_generator.pop_metrics() { From c67fcadbda3652f567da3f2bb44a0d7e283ab56e Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 8 Aug 2023 19:28:42 +0200 Subject: [PATCH 213/303] chore: lowering verbosity --- src/exporters/prometheuspush.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/exporters/prometheuspush.rs b/src/exporters/prometheuspush.rs index ef343178..eaad1591 100644 --- a/src/exporters/prometheuspush.rs +++ b/src/exporters/prometheuspush.rs @@ -141,12 +141,11 @@ impl Exporter for PrometheusPushExporter { ), false => pre_request, }; - //warn!("body: {}", body); if let Ok(request) = final_request.body(body) { match request.send() { Ok(mut response) => { - warn!("Got {:?}", response); - warn!("Response Text {:?}", response.text()); + debug!("Got {:?}", response); + debug!("Response Text {:?}", response.text()); } Err(err) => { warn!("Got error : {:?}", err) From 55cdbdf52a2c695ae908e1c36c1e51c903de1eb0 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Thu, 10 Aug 2023 15:52:27 +0200 Subject: [PATCH 214/303] chore: moveddebug message from warn to info --- src/sensors/powercap_rapl.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sensors/powercap_rapl.rs b/src/sensors/powercap_rapl.rs index 82c37e1a..da9e9b32 100644 --- a/src/sensors/powercap_rapl.rs +++ b/src/sensors/powercap_rapl.rs @@ -143,7 +143,7 @@ impl Sensor for PowercapRAPLSensor { let mut re_domain_matched = false; for folder in fs::read_dir(&self.base_path).unwrap() { let folder_name = String::from(folder.unwrap().path().to_str().unwrap()); - warn!("working on {folder_name}"); + info!("working on {folder_name}"); // let's catch domain folders if re_domain.is_match(&folder_name) { re_domain_matched = true; @@ -187,7 +187,7 @@ impl Sensor for PowercapRAPLSensor { ); } } else if re_socket_mmio.is_match(&folder_name) { - warn!("matched {folder_name}"); + info!("matched {folder_name}"); let mut splitted = folder_name.split(':'); let _ = splitted.next(); let socket_id: u16 = String::from(splitted.next().unwrap()).parse().unwrap(); From b38798d2c568351658c421f0505aec9ee1a905d0 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Fri, 11 Aug 2023 03:51:51 +0200 Subject: [PATCH 215/303] fix: mmio per socket and specific dram domain metrics working now --- src/exporters/mod.rs | 18 +++++++++++++ src/sensors/mod.rs | 51 +++++++++++++++++++++++------------- src/sensors/powercap_rapl.rs | 16 +++++------ 3 files changed, 58 insertions(+), 27 deletions(-) diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index f215d68b..d179f7e6 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -751,6 +751,24 @@ impl MetricGenerator { metric_value: MetricValueType::Text(domain_power_microwatts.clone()), }); } + let mut mmio_attributes = attributes.clone() ; + mmio_attributes.insert(String::from("value_source"), String::from("powercap_rapl_mmio")); + if let Some(mmio) = domain.get_rapl_mmio_energy_microjoules() { + self.data.push(Metric { + name: String::from("scaph_domain_rapl_mmio_energy_microjoules"), + metric_type: String::from("counter"), + ttl: 60.0, + timestamp: mmio.timestamp, + hostname: self.hostname.clone(), + state: String::from("ok"), + tags: vec!["scaphandre".to_string()], + attributes: mmio_attributes, + description: format!( + "Energy counter from RAPL mmio interface for the {} domain, socket {}.", domain.name, socket.id + ), + metric_value: MetricValueType::Text(mmio.value), + }); + } } } } diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index dc915f22..73c42d18 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -870,12 +870,17 @@ impl Topology { pub fn get_rapl_psys_energy_microjoules(&self) -> Option { if let Some(psys) = self._sensor_data.get("psys") { - if let Ok(val) = &fs::read_to_string(format!("{psys}/energy_uj")) { - return Some(Record::new( - current_system_time_since_epoch(), - val.to_string(), - units::Unit::MicroJoule, - )); + match &fs::read_to_string(format!("{psys}/energy_uj")) { + Ok(val) => { + return Some(Record::new( + current_system_time_since_epoch(), + val.to_string(), + units::Unit::MicroJoule, + )); + }, + Err(e) => { + warn!("PSYS Error: {:?}", e); + } } } None @@ -1212,12 +1217,17 @@ impl CPUSocket { pub fn get_rapl_mmio_energy_microjoules(&self) -> Option { if let Some(mmio) = self.sensor_data.get("mmio") { - if let Ok(val) = &fs::read_to_string(format!("{mmio}/energy_uj")) { - return Some(Record::new( - current_system_time_since_epoch(), - val.to_string(), - units::Unit::MicroJoule, - )); + match &fs::read_to_string(format!("{mmio}")) { + Ok(val) => { + return Some(Record::new( + current_system_time_since_epoch(), + val.to_string(), + units::Unit::MicroJoule, + )); + }, + Err(e)=> { + debug!("MMIO Error: {:?}", e) + } } } None @@ -1371,12 +1381,17 @@ impl Domain { pub fn get_rapl_mmio_energy_microjoules(&self) -> Option { if let Some(mmio) = self.sensor_data.get("mmio") { - if let Ok(val) = &fs::read_to_string(format!("{mmio}/energy_uj")) { - return Some(Record::new( - current_system_time_since_epoch(), - val.to_string(), - units::Unit::MicroJoule, - )); + match &fs::read_to_string(format!("{mmio}")) { + Ok(val) => { + return Some(Record::new( + current_system_time_since_epoch(), + val.to_string(), + units::Unit::MicroJoule, + )); + }, + Err(e) => { + debug!("MMIO Error in get microjoules: {:?}", e); + } } } None diff --git a/src/sensors/powercap_rapl.rs b/src/sensors/powercap_rapl.rs index da9e9b32..a3bb03fe 100644 --- a/src/sensors/powercap_rapl.rs +++ b/src/sensors/powercap_rapl.rs @@ -200,23 +200,21 @@ impl Sensor for PowercapRAPLSensor { } } } else if re_domain_mmio.is_match(&folder_name) { - warn!("matched {folder_name}"); + debug!("matched {folder_name}"); let mut splitted = folder_name.split(':'); let _ = splitted.next(); let socket_id: u16 = String::from(splitted.next().unwrap()).parse().unwrap(); - let domain_id: u16 = String::from(splitted.next().unwrap()).parse().unwrap(); for s in topo.get_sockets() { if socket_id == s.id { + let mmio_file = format!("{}/energy_uj", folder_name); for d in s.get_domains() { - if d.id == domain_id - && d.name.trim() - == fs::read_to_string(format!("{folder_name}/name")) - .unwrap() - .trim() + let name_in_folder = fs::read_to_string(format!("{folder_name}/name")).unwrap(); + // domain id doesn't match between regular and mmio folders, the name is coherent however (dram) + if d.name.trim() == name_in_folder.trim() { d.sensor_data.insert( String::from("mmio"), - format!("{}/energy_uj", folder_name), + mmio_file.clone() ); } } @@ -255,7 +253,7 @@ impl Sensor for PowercapRAPLSensor { } } if !found { - warn!("Could'nt find any RAPL PKG domain (not psys)."); + warn!("Could'nt find any RAPL PKG domain (nor psys)."); } } for folder in fs::read_dir(&self.base_path).unwrap() { From d297a9d85443ff10bf9c23ab3225091cd00de9d0 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Fri, 11 Aug 2023 03:53:07 +0200 Subject: [PATCH 216/303] style: fmt and clippy --- src/exporters/mod.rs | 7 +++++-- src/sensors/mod.rs | 12 ++++++------ src/sensors/powercap_rapl.rs | 12 +++++------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index d179f7e6..26611fd9 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -751,8 +751,11 @@ impl MetricGenerator { metric_value: MetricValueType::Text(domain_power_microwatts.clone()), }); } - let mut mmio_attributes = attributes.clone() ; - mmio_attributes.insert(String::from("value_source"), String::from("powercap_rapl_mmio")); + let mut mmio_attributes = attributes.clone(); + mmio_attributes.insert( + String::from("value_source"), + String::from("powercap_rapl_mmio"), + ); if let Some(mmio) = domain.get_rapl_mmio_energy_microjoules() { self.data.push(Metric { name: String::from("scaph_domain_rapl_mmio_energy_microjoules"), diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 73c42d18..c0055c2a 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -877,7 +877,7 @@ impl Topology { val.to_string(), units::Unit::MicroJoule, )); - }, + } Err(e) => { warn!("PSYS Error: {:?}", e); } @@ -1217,15 +1217,15 @@ impl CPUSocket { pub fn get_rapl_mmio_energy_microjoules(&self) -> Option { if let Some(mmio) = self.sensor_data.get("mmio") { - match &fs::read_to_string(format!("{mmio}")) { + match &fs::read_to_string(mmio) { Ok(val) => { return Some(Record::new( current_system_time_since_epoch(), val.to_string(), units::Unit::MicroJoule, )); - }, - Err(e)=> { + } + Err(e) => { debug!("MMIO Error: {:?}", e) } } @@ -1381,14 +1381,14 @@ impl Domain { pub fn get_rapl_mmio_energy_microjoules(&self) -> Option { if let Some(mmio) = self.sensor_data.get("mmio") { - match &fs::read_to_string(format!("{mmio}")) { + match &fs::read_to_string(mmio) { Ok(val) => { return Some(Record::new( current_system_time_since_epoch(), val.to_string(), units::Unit::MicroJoule, )); - }, + } Err(e) => { debug!("MMIO Error in get microjoules: {:?}", e); } diff --git a/src/sensors/powercap_rapl.rs b/src/sensors/powercap_rapl.rs index a3bb03fe..a294cdbb 100644 --- a/src/sensors/powercap_rapl.rs +++ b/src/sensors/powercap_rapl.rs @@ -208,14 +208,12 @@ impl Sensor for PowercapRAPLSensor { if socket_id == s.id { let mmio_file = format!("{}/energy_uj", folder_name); for d in s.get_domains() { - let name_in_folder = fs::read_to_string(format!("{folder_name}/name")).unwrap(); + let name_in_folder = + fs::read_to_string(format!("{folder_name}/name")).unwrap(); // domain id doesn't match between regular and mmio folders, the name is coherent however (dram) - if d.name.trim() == name_in_folder.trim() - { - d.sensor_data.insert( - String::from("mmio"), - mmio_file.clone() - ); + if d.name.trim() == name_in_folder.trim() { + d.sensor_data + .insert(String::from("mmio"), mmio_file.clone()); } } } From 024fe289a6b8238a76d3237ce80ce1eaf93648fe Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 8 Aug 2023 18:11:08 +0200 Subject: [PATCH 217/303] docs: quick fix from @rossf7 --- docs_src/explanations/about-containers.md | 6 +++--- docs_src/troubleshooting.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs_src/explanations/about-containers.md b/docs_src/explanations/about-containers.md index db4a6fcc..b6130d15 100644 --- a/docs_src/explanations/about-containers.md +++ b/docs_src/explanations/about-containers.md @@ -2,10 +2,10 @@ There are several ways scaphandre can interact with containers. -You may run scaphandre **in a container**, to not have to manage the dependencies, then measure the power consumption of the **bare metal host**. This is described in the [quickstart tutorial](../tutorials/quickstart.md). Note that you need to expose `/sys/class/powercap` and `/proc` as volumes in the container to allow scaphandre to get the relevant metrics from the bare metal host. +You may run scaphandre **in a container**, to not have to manage the dependencies, then measure the power consumption of the **bare metal host**. This is described in the [quickstart tutorial](../tutorials/getting_started.md). Note that you need to expose `/sys/class/powercap` and `/proc` as volumes in the container to allow scaphandre to get the relevant metrics from the bare metal host. Scaphandre may help you measure the **power consumption of containers** running on a given host. You can already get to that goal using the tips provided in the howto section called ["Get process level power consumption"](../how-to_guides/get-process-level-power-in-grafana.md). It may still require some tweaking and inventiveness from you in making the approriate queries to your favorite TSDB. This should be made easier by the upcoming [scaphandre features](https://github.com/hubblo-org/scaphandre/projects/1). -Another use case scenario would be to measure the power consumption of a **container orchestrator** (like [kubernetes](https://kubernetes.io/)), its nodes and the containers and applications running on it. This is a feature we are [currently working on](https://github.com/hubblo-org/scaphandre/issues/29#issuecomment-755353175) (you may try yourself the helm chart that is proposed in that thread, before it is officially supported). +Another use case scenario is measuring the power consumption of a **container orchestrator** (like [kubernetes](https://kubernetes.io/)), its nodes and the containers and applications running on it. Scaphandre can be installed on Kubernetes via the Helm chart and there is a [tutorial](../tutorials/kubernetes.md) for installing it along with Prometheus and Grafana to view the metrics. -As described [here](../compatibility.md), scaphandre provides several ways ([sensors](../explanations/sensors.md)) to collect the power consumption metrics. Depending on your use case a sensor should be more suitable than the other. Each of them comes with strengths and weaknesses. This is basically always a tradeoff between precision and simplicity. This is especially true if you run a container-based workloads on public cloud instances. We are working to provide a solution [for that as well](https://github.com/hubblo-org/scaphandre/issues/25). \ No newline at end of file +As described [here](../compatibility.md), scaphandre provides several ways ([sensors](../explanations/internal-structure.md#sensors)) to collect the power consumption metrics. Depending on your use case a sensor should be more suitable than the other. Each of them comes with strengths and weaknesses. This is basically always a tradeoff between precision and simplicity. This is especially true if you run a container-based workloads on public cloud instances. We are working to provide a solution [for that as well](https://github.com/hubblo-org/scaphandre/issues/25). diff --git a/docs_src/troubleshooting.md b/docs_src/troubleshooting.md index 61b88a16..463741ee 100644 --- a/docs_src/troubleshooting.md +++ b/docs_src/troubleshooting.md @@ -18,7 +18,7 @@ It can mean that your cpu doesn't support RAPL. Please refer to the [compatibili If you are in a situation comparable to [this one](https://github.com/hubblo-org/scaphandre/issues/59), you may need to install additional packages. -On ubuntu 20.01 and 20.10, try to install `linux-modules-extra-$(uname-r)` with apt. Then you should be able to `modprobe intel_rapl_common`. +On ubuntu 20.01 and 20.10, try to install `linux-modules-extra-$(uname -r)` with apt. Then you should be able to `modprobe intel_rapl_common`. ### On an AMD cpu machine, I get the following stracktrace From 99d54eefb0adbb4147c7c7a5afa809502a4b1395 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Fri, 11 Aug 2023 18:59:10 +0200 Subject: [PATCH 218/303] docs: updating readme --- README.md | 13 +++++++------ docs_src/scaphandre.small.cleaned.ico | Bin 0 -> 93062 bytes 2 files changed, 7 insertions(+), 6 deletions(-) create mode 100644 docs_src/scaphandre.small.cleaned.ico diff --git a/README.md b/README.md index c27f6bff..cc17d1dc 100644 --- a/README.md +++ b/README.md @@ -29,12 +29,13 @@ Join us on [Gitter](https://gitter.im/hubblo-org/scaphandre) or [Matrix](https:/ ## ✨ Features -- measuring power consumption on **bare metal hosts** -- measuring power consumption of **qemu/kvm virtual machines** from the host -- **exposing** power consumption metrics of a virtual machine, to allow **manipulating those metrics in the VM** as if it was a bare metal machine (relies on hypervisor features) -- exposing power consumption metrics as a **[prometheus](https://prometheus.io) (HTTP) exporter** -- sending power consumption metrics to **[riemann](http://riemann.io/)** -- sending power consumption metrics to **[Warp10](http://warp10.io/)** +- measuring power/energy consumed on **bare metal hosts** +- measuring power/energy consumed of **qemu/kvm virtual machines** from the host +- **exposing** power/energy metrics of a virtual machine, to allow **manipulating those metrics in the VM** as if it was a bare metal machine (relies on hypervisor features) +- exposing metrics as a **[prometheus](https://prometheus.io) (HTTP) exporter** +- sending metrics in push mode to a **[prometheus](https://prometheus.io) [Push Gateway](https://github.com/prometheus/pushgateway)** +- sending metrics to **[riemann](http://riemann.io/)** +- sending metrics to **[Warp10](http://warp10.io/)** - works on **[kubernetes](https://kubernetes.io/)** - storing power consumption metrics in a **JSON** file - showing basic power consumption metrics **in the terminal** diff --git a/docs_src/scaphandre.small.cleaned.ico b/docs_src/scaphandre.small.cleaned.ico new file mode 100644 index 0000000000000000000000000000000000000000..39b5a015d674e27b5f612cc24f941cc0287000f2 GIT binary patch literal 93062 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 PCws0dNu(hZR}KFUj%(!j literal 0 HcmV?d00001 From 185ad9cf2228a5d4d02db9cbece567f12ab8e2f9 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Fri, 11 Aug 2023 19:06:13 +0200 Subject: [PATCH 219/303] docs: fix readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cc17d1dc..6fe88dad 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ --- -Scaphandre *[skafɑ̃dʁ]* is a metrology agent dedicated to electrical [power](https://en.wikipedia.org/wiki/Electric_power) consumption metrics. The goal of the project is to permit to any company or individual to **measure** the power consumption of its tech services and get this data in a convenient form, sending it through any monitoring or data analysis toolchain. +Scaphandre *[skafɑ̃dʁ]* is a metrology agent dedicated to electric [power](https://en.wikipedia.org/wiki/Electric_power) and energy consumption metrics. The goal of the project is to permit to any company or individual to **measure** the power consumption of its tech services and get this data in a convenient form, sending it through any monitoring or data analysis toolchain. **Scaphandre** means *heavy* **diving suit** in [:fr:](https://fr.wikipedia.org/wiki/Scaphandre_%C3%A0_casque). It comes from the idea that tech related services often don't track their power consumption and thus don't expose it to their clients. Most of the time the reason is a presumed bad [ROI](https://en.wikipedia.org/wiki/Return_on_investment). Scaphandre makes, for tech providers and tech users, easier and cheaper to go under the surface to bring back the desired power consumption metrics, take better sustainability focused decisions, and then show the metrics to their clients to allow them to do the same. From c2cf73dd5d19962a57b56672501f2b8d5113ba27 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Fri, 11 Aug 2023 19:10:15 +0200 Subject: [PATCH 220/303] docs: first version of project website --- .github/workflows/oranda.yml | 84 ++++++++++++++++++++++++++++++++++++ oranda.json | 22 ++++++++++ 2 files changed, 106 insertions(+) create mode 100644 .github/workflows/oranda.yml create mode 100644 oranda.json diff --git a/.github/workflows/oranda.yml b/.github/workflows/oranda.yml new file mode 100644 index 00000000..0fa63632 --- /dev/null +++ b/.github/workflows/oranda.yml @@ -0,0 +1,84 @@ +# Workflow to build your docs with oranda (and mdbook) +# and deploy them to Github Pages +name: Oranda-based project website deploy + +# We're going to push to the gh-pages branch, so we need that permission +permissions: + contents: write + +# What situations do we want to build docs in? +# All of these work independently and can be removed / commented out +# if you don't want oranda/mdbook running in that situation +on: + # Check that a PR didn't break docs! + # + # Note that the "Deploy to Github Pages" step won't run in this mode, + # so this won't have any side-effects. But it will tell you if a PR + # completely broke oranda/mdbook. Sadly we don't provide previews (yet)! + pull_request: + + # Whenever something gets pushed to main, update the docs! + # This is great for getting docs changes live without cutting a full release. + # + # Note that if you're using cargo-dist, this will "race" the Release workflow + # that actually builds the Github Release that oranda tries to read (and + # this will almost certainly complete first). As a result you will publish + # docs for the latest commit but the oranda landing page won't know about + # the latest release. The workflow_run trigger below will properly wait for + # cargo-dist, and so this half-published state will only last for ~10 minutes. + # + # If you only want docs to update with releases, disable this one. + push: + branches: + - dev + + # Whenever a workflow called "Release" completes, update the docs! + # + # If you're using cargo-dist, this is recommended, as it will ensure that + # oranda always sees the latest release right when it's available. Note + # however that Github's UI is wonky when you use workflow_run, and won't + # show this workflow as part of any commit. You have to go to the "actions" + # tab for your repo to see this one running (the gh-pages deploy will also + # only show up there). + #workflow_run: + # workflows: ["Release"] + # types: + # - completed + +# Alright, let's do it! +jobs: + web: + name: Build and deploy site and docs + runs-on: ubuntu-latest + steps: + # Setup + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: dtolnay/rust-toolchain@stable + - uses: swatinem/rust-cache@v2 + + # Install and run oranda (and mdbook) + # This will write all output to ./public/ (including copying mdbook's output to there) + - name: Install and run oranda + run: | + curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/oranda/releases/latest/download/oranda-installer.sh | sh + oranda build + + # Deploy to our gh-pages branch (making it if it doesn't exist) + # the "public" dir that oranda made above will become the root dir + # of this branch. + # + # Note that once the gh-pages branch exists, you must + # go into repo's settings > pages and set "deploy from branch: gh-pages" + # the other defaults work fine. + - name: Deploy to Github Pages + uses: JamesIves/github-pages-deploy-action@v4.4.1 + # ONLY if we're on dev (so no PRs or feature branches allowed!) + if: ${{ github.ref == 'refs/heads/dev' }} + with: + branch: gh-pages + # Gotta tell the action where to find oranda's output + folder: public + token: ${{ secrets.GITHUB_TOKEN }} + single-commit: true diff --git a/oranda.json b/oranda.json new file mode 100644 index 00000000..7f126466 --- /dev/null +++ b/oranda.json @@ -0,0 +1,22 @@ +{ + "project" : { + "homepage": "https://scaphandre.hubblo.org", + "repository": "https://github.com/hubblo-org/scaphandre" + }, + "styles": { + "theme": "light", + "favicon": "https://raw.githubusercontent.com/hubblo-org/scaphandre/dev/docs_src/scaphandre.small.cleaned.png" + }, + "marketing": { + "social": { + "image_alt": "Hubblo's twitter/x account", + "twitter_account": "@HubbloOrg" + } + }, + "components": { + "changelog": true, + "funding": { + "preferred_funding": "github" + } + } +} From 1b386178432c6c62167b7ff238fcf59a0bf41642 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 14 Aug 2023 15:17:23 +0200 Subject: [PATCH 221/303] docs: improving compatiblity and rapl description --- docs_src/compatibility.md | 22 +++++--- docs_src/explanations/rapl-domains.md | 76 ++++----------------------- 2 files changed, 24 insertions(+), 74 deletions(-) diff --git a/docs_src/compatibility.md b/docs_src/compatibility.md index e9d62114..0797e320 100644 --- a/docs_src/compatibility.md +++ b/docs_src/compatibility.md @@ -6,14 +6,22 @@ To summarize, scaphandre should provide two ways to estimate the power consumpti In scaphandre, the code responsible to collect the power consumption data before any further processing is grouped in components called **sensors**. If you want more details about scaphandre structure, [here are the explanations](explanations/internal-structure.md). -The [PowercapRAPL sensor](references/sensor-powercap_rapl.md) enables you to measure the power consumption, it is the most precise solution, but it doesn't work in all contexts. A future sensor is to be developed to support other use cases. Here is the current state of scaphandre's compatibility: +On GNU/Linux [PowercapRAPL sensor](references/sensor-powercap_rapl.md) enables you to measure the power consumption, but it doesn't work in all contexts. + +On Windows, [the MsrRAPL sensor](references/sensor-msr_rapl.md), coupled with the [driver responsible to read RAPL MSR's](https://github.com/hubblo-org/windows-rapl-driver/) enables you to do (almost) the same. | Sensor | Intel x86 bare metal | AMD x86 bare metal | ARM bare metal | Virtual Machine | Public cloud instance | Container | | :------------- | :------------------: | :----------------: | :------------: | :-------------: | :-------------------: | :-------: | -| PowercapRAPL | [Yes](references/sensor-powercap_rapl.md) | Yes ⚠️ kernel > 5.11 required | We don't know yet | Yes, if on a qemu/KVM hypervisor that runs scaphandre and the [Qemu exporter](references/exporter-qemu.md) | No, until your cloud provider uses scaphandre on its hypervisors | [Depends on what you want](explanations/about-containers.md) | -| Future estimation based sensor | Future Yes | Future Yes | Future Yes | Future Yes | Future Yes | +| PowercapRAPL (GNU/Linux only) | [Yes](references/sensor-powercap_rapl.md) | Yes ⚠️ kernel > 5.11 required | We don't know yet | Yes, if on a qemu/KVM hypervisor that runs scaphandre and the [Qemu exporter](references/exporter-qemu.md) | No, until your cloud provider uses scaphandre on its hypervisors | [Depends on what you want](explanations/about-containers.md) | +| MsrRAPL (Windows only) | Yes | Probable yes (not tested yet, if you have windows operated AMD gear, please consider [contributing](contributing.md) | No | Not yet, depends on improvements on the MsrRAPL sensors and overall windows/hypervisors support in Scaphandre | No, until your cloud provider uses scaphandre on its hypervisors | Might work, not tested yet. If you want to join us in this journey, please consider [contributing](contributing.md) | +| Future estimation based sensor | Future Yes | Future Yes | Future Yes | Future Yes | Future Yes | Future Yes + +## Checking RAPL is available on your CPU + +Sensors including "RAPL" in their name rely on [RAPL](explanations/rapl-domains.md). + +The `pts` and `pln` feature flags ("Intel Package Thermal Status" and "Intel Power Limit Notification" respectively) seem to indicate that RAPL is supported on a CPU. On GNU/Linux, you could be sure of their presence, if this command succeds and matches : -| Sensor | GNU/Linux | Windows | MacOS | -| :-----------: | :--------------: | :------------------------------------: | :---: | -| PowercapRAPL | Yes (see above) | No | No | -| MsrRAPL | No | Yes (tested on windows 10/server 2019) | No | \ No newline at end of file +``` +egrep "(pts|pln)" /proc/cpuinfo +``` \ No newline at end of file diff --git a/docs_src/explanations/rapl-domains.md b/docs_src/explanations/rapl-domains.md index b22df3e9..f1c58e54 100644 --- a/docs_src/explanations/rapl-domains.md +++ b/docs_src/explanations/rapl-domains.md @@ -1,72 +1,14 @@ -# Explanation on RAPL domains (what we know so far) +# Explanation on RAPL / Running Average Power Limit domains: what we (think we) know so far -## PSYS - -[Kepler documentation](https://sustainable-computing.io/design/metrics/) says PSYS "is the energy consumed by the "System on a chipt" (SOC)." -"Generally, this metric is the host energy consumption (from acpi)." but also "Generally, this metric is the **host energy consumption (from acpi) less the RAPL Package and DRAM**." - -[https://zhenkai-zhang.github.io/papers/rapl.pdf](https://zhenkai-zhang.github.io/papers/rapl.pdf) says -Microarchitecture Package CORE (PP0) UNCORE (PP1) DRAM -Haswell Y/Y Y/N Y/N Y/Y -Broadwell Y/Y Y/N Y/N Y/Y -Skylake Y/Y Y/Y Y/N Y/Y -Kaby Lake Y/Y Y/Y Y/N Y/Y - - -[https://www.arcsi.fr/doc/platypus.pdf](https://www.arcsi.fr/doc/platypus.pdf) says PSYS is "covering the entire SoC.". - -http://www.micheledellipaoli.com/documents/EnergyConsumptionAnalysis.pdf says -"PSys: (introduced with Intel Skylake) monitors and con- -trols the thermal and power specifications of the entire -SoC and it is useful especially when the source of the -power consumption is neither the CPU nor the GPU. For -multi-socket server systems, each socket reports its own -RAPL values." - -https://hal.science/hal-03809858/document says -"PSys. Domain available on some Intel architectures, to monitor and control the thermal -and power specifications of the entire system on the chip (SoC), instead of just CPU or -GPU. It includes the power consumption of the package domain, System Agent, PCH, -eDRAM, and a few more domains on a single-socket SoC" +RAPL stands for "Running Average Power Limit", it is a feature on Intel/AMD x86 CPU's (manufactured after 2012) that allows to set limits on power used by the CPU and other components. This feature also allows to just get "measurements" (mind the double quotes, as at least part of the numbers RAPL gives are coming from estimations/modeling) of components power usage. ![RAPL domains](rapl.png) -https://github.com/hubblo-org/scaphandre/issues/116 -https://github.com/hubblo-org/scaphandre/issues/241 -https://github.com/hubblo-org/scaphandre/issues/140 -https://github.com/hubblo-org/scaphandre/issues/289 -https://github.com/hubblo-org/scaphandre/issues/117 -https://github.com/hubblo-org/scaphandre/issues/25 -https://github.com/hubblo-org/scaphandre/issues/316 -https://github.com/hubblo-org/scaphandre/issues/318 - -PSYS MSR is "MSR_PLATFORM_ENERGY_STATUS" -https://copyprogramming.com/howto/perf-power-consumption-measure-how-does-it-work - -https://pyjoules.readthedocs.io/en/stable/devices/intel_cpu.html - -Problems of RAPL on Saphire Rapids -https://community.intel.com/t5/Software-Tuning-Performance/RAPL-quirks-on-Sapphire-Rapids/td-p/1446761 - -Misc info on RAPL -https://web.eece.maine.edu/~vweaver/projects/rapl/ - -PSYS MSR have a different layout than PKG and dram -https://patchwork.kernel.org/project/linux-pm/patch/20211207131734.2607104-1-rui.zhang@intel.com/ - -https://edc.intel.com/content/www/us/en/design/ipla/software-development-platforms/client/platforms/alder-lake-desktop/12th-generation-intel-core-processors-datasheet-volume-1-of-2/010/power-management/ ==> intel doc avout thermal and power management -https://edc.intel.com/content/www/us/en/design/ipla/software-development-platforms/client/platforms/alder-lake-desktop/12th-generation-intel-core-processors-datasheet-volume-1-of-2/002/platform-power-control/ ==> about psys - -https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html ==> intel software developer manual - -CVE-8694/8695 and mitigation by intel -https://www.intel.com/content/www/us/en/developer/articles/technical/software-security-guidance/advisory-guidance/running-average-power-limit-energy-reporting.html - -Patch in the kernel -https://groups.google.com/g/linux.kernel/c/x_7RbqcrxAs -Patch in powercap -https://lkml.iu.edu/hypermail/linux/kernel/1603.2/02415.html -https://lkml.kernel.org/lkml/1460930581-29748-1-git-send-email-srinivas.pandruvada@linux.intel.com/T/ +It is composed of "domains", that, in 2023, may include: +- **Core/PP0**: Energy consumed by the CPU Cores themselves. +- **Uncore/PP1**: Energy consumed by components close to the CPU : most of the time it means the embedded GPU chipset. +- **Dram**: Energy consumed by the memory/RAM sticks +- **Package/PKG**: Includes "Core" and "Uncore". In some documentations and in some of our experiments it seem to include "Dram", but this doesn't seem true in every cases. +- **PSys**: We don't have a clear understanding on this one (yet). But most documentations refer to it with words similar to "PSys: (introduced with Intel Skylake) monitors and controls the thermal and power specifications of the entire SoC and it is useful especially when the source of the power consumption is neither the CPU nor the GPU. For multi-socket server systems, each socket reports its own RAPL values.". To summarize, Psys seems like an interesting metric to get energy consumed by a motherboard and connected components (which includes RAPL usual suspects but also WiFi/Bluetooth cards and probably more). If you want to know more about this metric, we gathered references/sources [here](https://github.com/bpetit/awesome-energy/tree/master#rapl-psys-domain). If you want to help us understanding and documenting better this metric, please consider constributing to the [Energizta project](https://github.com/Boavizta/Energizta/). -Random -https://stackoverflow.com/questions/55956287/perf-power-consumption-measure-how-does-it-work \ No newline at end of file +RAPL documentation from Intel doesn't necessarily give very precise informations about how RAPL behaves depending on the platform, or about what is included in the calculation. Actively looking for other experimentations/feedbacks/documentations is needed. You might find some informations gathered here: [awesome-energy](https://github.com/bpetit/awesome-energy#rapl). If you have more or more precise informations and are willing to contribute, don't hesitate to open a PR to dev branch on [scaphandre's repository](https://github.com/hubblo-org/scaphandre/tree/dev) (targeting [docs_src folder](https://github.com/hubblo-org/scaphandre/tree/dev/docs_src)) and/or the [awesome-energy](https://github.com/bpetit/awesome-energy) repository. \ No newline at end of file From 434ea581ef0e938f344a081387ab5d7e3877090d Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 14 Aug 2023 15:21:27 +0200 Subject: [PATCH 222/303] docs: improving compatiblity and rapl description --- docs_src/explanations/rapl-domains.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs_src/explanations/rapl-domains.md b/docs_src/explanations/rapl-domains.md index f1c58e54..b2a14a64 100644 --- a/docs_src/explanations/rapl-domains.md +++ b/docs_src/explanations/rapl-domains.md @@ -11,4 +11,6 @@ It is composed of "domains", that, in 2023, may include: - **Package/PKG**: Includes "Core" and "Uncore". In some documentations and in some of our experiments it seem to include "Dram", but this doesn't seem true in every cases. - **PSys**: We don't have a clear understanding on this one (yet). But most documentations refer to it with words similar to "PSys: (introduced with Intel Skylake) monitors and controls the thermal and power specifications of the entire SoC and it is useful especially when the source of the power consumption is neither the CPU nor the GPU. For multi-socket server systems, each socket reports its own RAPL values.". To summarize, Psys seems like an interesting metric to get energy consumed by a motherboard and connected components (which includes RAPL usual suspects but also WiFi/Bluetooth cards and probably more). If you want to know more about this metric, we gathered references/sources [here](https://github.com/bpetit/awesome-energy/tree/master#rapl-psys-domain). If you want to help us understanding and documenting better this metric, please consider constributing to the [Energizta project](https://github.com/Boavizta/Energizta/). -RAPL documentation from Intel doesn't necessarily give very precise informations about how RAPL behaves depending on the platform, or about what is included in the calculation. Actively looking for other experimentations/feedbacks/documentations is needed. You might find some informations gathered here: [awesome-energy](https://github.com/bpetit/awesome-energy#rapl). If you have more or more precise informations and are willing to contribute, don't hesitate to open a PR to dev branch on [scaphandre's repository](https://github.com/hubblo-org/scaphandre/tree/dev) (targeting [docs_src folder](https://github.com/hubblo-org/scaphandre/tree/dev/docs_src)) and/or the [awesome-energy](https://github.com/bpetit/awesome-energy) repository. \ No newline at end of file +RAPL documentation from Intel doesn't necessarily give very precise informations about how RAPL behaves depending on the platform, or about what is included in the calculation. Actively looking for other experimentations/feedbacks/documentations is needed. You might find some informations gathered here: [awesome-energy](https://github.com/bpetit/awesome-energy#rapl). If you have more or more precise informations and are willing to contribute, don't hesitate to open a PR to dev branch on [scaphandre's repository](https://github.com/hubblo-org/scaphandre/tree/dev) (targeting [docs_src folder](https://github.com/hubblo-org/scaphandre/tree/dev/docs_src)) and/or the [awesome-energy](https://github.com/bpetit/awesome-energy) repository. + +If you want to know if RAPL is supported by your CPU, please have a look to the end of the [Compatibility](../compatibility.md/) section. \ No newline at end of file From 85f6543207e548ce93d0104d308b49f8c3836c60 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 14 Aug 2023 16:53:59 +0200 Subject: [PATCH 223/303] fix: removing unused function --- src/lib.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 26bc185c..60b65c46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,16 +36,6 @@ fn current_system_time_since_epoch() -> Duration { .unwrap() } -/// Returns rust crate version, can be use used in language bindings to expose Rust core version -pub fn crate_version() -> &'static str { - env!("CARGO_PKG_VERSION") -} - -/// Returns rust crate version, can be use used in language bindings to expose Rust core version -pub fn crate_version() -> &'static str { - env!("CARGO_PKG_VERSION") -} - // Copyright 2020 The scaphandre authors. // // Licensed under the Apache License, Version 2.0 (the "License"); From 67d3fb6c0b37ddd1c591e613e7c414c131e84442 Mon Sep 17 00:00:00 2001 From: bpetit Date: Wed, 26 Jul 2023 17:00:21 +0200 Subject: [PATCH 224/303] ci: checkout windows rapl driver to be able to build full exe --- .../workflows/exe-release-prometheuspush.yml | 74 ++++++------------- 1 file changed, 21 insertions(+), 53 deletions(-) diff --git a/.github/workflows/exe-release-prometheuspush.yml b/.github/workflows/exe-release-prometheuspush.yml index 6d4031d2..b019c987 100644 --- a/.github/workflows/exe-release-prometheuspush.yml +++ b/.github/workflows/exe-release-prometheuspush.yml @@ -18,61 +18,29 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 - - uses: Swatinem/rust-cache@v2 with: - # The prefix cache key, this can be changed to start a new cache manually. - # default: "v0-rust" - prefix-key: "" - - # A cache key that is used instead of the automatic `job`-based key, - # and is stable over multiple jobs. - # default: empty - shared-key: "" - - # An additional cache key that is added alongside the automatic `job`-based - # cache key and can be used to further differentiate jobs. - # default: empty - key: "" - - # A whitespace separated list of env-var *prefixes* who's value contributes - # to the environment cache key. - # The env-vars are matched by *prefix*, so the default `RUST` var will - # match all of `RUSTC`, `RUSTUP_*`, `RUSTFLAGS`, `RUSTDOC_*`, etc. - # default: "CARGO CC CFLAGS CXX CMAKE RUST" - env-vars: "" - - # The cargo workspaces and target directory configuration. - # These entries are separated by newlines and have the form - # `$workspace -> $target`. The `$target` part is treated as a directory - # relative to the `$workspace` and defaults to "target" if not explicitly given. - # default: ". -> target" - workspaces: "" - - # Additional non workspace directories to be cached, separated by newlines. - cache-directories: "" - - # Determines whether workspace `target` directories are cached. - # If `false`, only the cargo registry will be cached. - # default: "true" - cache-targets: "" - - # Determines if the cache should be saved even when the workflow has failed. - # default: "false" - cache-on-failure: "" - - # Determines which crates are cached. - # If `true` all crates will be cached, otherwise only dependent crates will be cached. - # Useful if additional crates are used for CI tooling. - # default: "false" - cache-all-crates: "" - - # Determiners whether the cache should be saved. - # If `false`, the cache is only restored. - # Useful for jobs where the matrix is additive e.g. additional Cargo features. - # default: "true" - save-if: "" + path: scaphandre + - name: Checkout + uses: actions/checkout@v3 + with: + repository: hubblo-org/windows-rapl-driver.git + ref: master + path: windows-rapl-driver - name: Install Innosoft run: | $url = "https://jrsoftware.org/download.php/is.exe" $dest = "is.exe" - Invoke-WebRequest -Uri $url -OutFile $dest \ No newline at end of file + Invoke-WebRequest -Uri $url -OutFile $dest + ls + & "$dest" /verysilent /suppressmsgbox + ls "C:\Program Files (x86)\" + - name: Install Rustup + uses: crazy-max/ghaction-chocolatey@v2 + with: + args: install rustup.install --ignore-checksums + - name: Install Rust toolchain + run: | + rustup toolchain install stable-x86_64-pc-windows-msvc + - name: Build (debug mode) + run: | + cargo build --release --no-default-features --features "prometheuspush json" \ No newline at end of file From b8be3e2230be819f4b91bae9ebf23e07433770a2 Mon Sep 17 00:00:00 2001 From: bpetit Date: Wed, 26 Jul 2023 17:28:04 +0200 Subject: [PATCH 225/303] ci: fixing exe push to s3 --- .../workflows/exe-release-prometheuspush.yml | 53 ++++++++++++++----- packaging/windows/installer.iss | 35 ++++++------ 2 files changed, 57 insertions(+), 31 deletions(-) diff --git a/.github/workflows/exe-release-prometheuspush.yml b/.github/workflows/exe-release-prometheuspush.yml index b019c987..706b2a7c 100644 --- a/.github/workflows/exe-release-prometheuspush.yml +++ b/.github/workflows/exe-release-prometheuspush.yml @@ -11,6 +11,10 @@ on: tags: [ 'v*.*.*', 'dev*.*.*' ] branches: [ '311-github-workflow-to-build-and-publish-a-exemsi-file-including-signed-rapl-driver-at-each-tagrelease' ] +env: + WRD_VERSION: v0.0.2 + WRD_BASE_URL: https://github.com/hubblo-org/windows-rapl-driver/releases/download + jobs: build_exe_win1011: name: Build exe installer for windows 10/11/server 2016/server 2019/server 2022 @@ -18,22 +22,38 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 - with: - path: scaphandre - - name: Checkout - uses: actions/checkout@v3 - with: - repository: hubblo-org/windows-rapl-driver.git - ref: master - path: windows-rapl-driver - name: Install Innosoft run: | $url = "https://jrsoftware.org/download.php/is.exe" $dest = "is.exe" Invoke-WebRequest -Uri $url -OutFile $dest ls - & "$dest" /verysilent /suppressmsgbox - ls "C:\Program Files (x86)\" + & "D:\a\scaphandre\scaphandre\$dest" /verysilent /suppressmsgbox + ls "C:\Program Files (x86)\Inno Setup 6\" + - name: Get windows-rapl-driver + run: | + $dest = "DriverLoader.exe" + $url = "${{ env.WRD_BASE_URL }}/${{ env.WRD_VERSION }}/DriverLoader.exe" + Invoke-WebRequest -Uri ($url -replace '"', "") -OutFile $dest + $dest = "ScaphandreDrv.cat" + $url = "${{ env.WRD_BASE_URL }}/${{ env.WRD_VERSION }}/ScaphandreDrv.cat" + Invoke-WebRequest -Uri ($url -replace '"', "") -OutFile $dest + $dest = "ScaphandreDrv.sys" + $url = "${{ env.WRD_BASE_URL }}/${{ env.WRD_VERSION }}/ScaphandreDrv.sys" + Invoke-WebRequest -Uri ($url -replace '"', "") -OutFile $dest + $dest = "ScaphandreDrv.inf" + $url = "${{ env.WRD_BASE_URL }}/${{ env.WRD_VERSION }}/ScaphandreDrv.inf" + Invoke-WebRequest -Uri ($url -replace '"', "") -OutFile $dest + $dest = "ScaphandreDrvTest.cer" + $url = "${{ env.WRD_BASE_URL }}/${{ env.WRD_VERSION }}/ScaphandreDrvTest.cer" + Invoke-WebRequest -Uri ($url -replace '"', "") -OutFile $dest + $dest = "devcon.exe" + $url = "${{ env.WRD_BASE_URL }}/${{ env.WRD_VERSION }}/devcon.exe" + Invoke-WebRequest -Uri ($url -replace '"', "") -OutFile $dest + $dest = "certmgr.exe" + $url = "${{ env.WRD_BASE_URL }}/${{ env.WRD_VERSION }}/certmgr.exe" + Invoke-WebRequest -Uri ($url -replace '"', "") -OutFile $dest + ls - name: Install Rustup uses: crazy-max/ghaction-chocolatey@v2 with: @@ -41,6 +61,15 @@ jobs: - name: Install Rust toolchain run: | rustup toolchain install stable-x86_64-pc-windows-msvc - - name: Build (debug mode) + - name: Build Scaphandre + run: | + cargo build --release --no-default-features --features "prometheuspush json" + - name: Build package + run: | + & "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" packaging/windows/installer.iss + - name: Upload artifact run: | - cargo build --release --no-default-features --features "prometheuspush json" \ No newline at end of file + Install-Module -Name AWS.Tools.Installer + Install-AWSToolsModule AWS.Tools.EC2,AWS.Tools.S3 -CleanUp + Set-AWSCredential -AccessKey ${{ secrets.S3_ACCESS_KEY_ID }} -SecretKey ${{ secrets.S3_SECRET_ACCESS_KEY }} -StoreAs default + Write-S3Object -BucketName scaphandre -File scaphandre_0.5.0_installer.exe \ No newline at end of file diff --git a/packaging/windows/installer.iss b/packaging/windows/installer.iss index fb06f118..2389673e 100644 --- a/packaging/windows/installer.iss +++ b/packaging/windows/installer.iss @@ -6,8 +6,6 @@ #define MyAppPublisher "Hubblo" #define MyAppURL "https://hubblo-org.github.io/scaphandre-documentation" #define MyAppExeName "scaphandre.exe" -#define MyAppSourceFolder "C:\Users\bpeti\Documents\GitHub\scaphandre" -#define RaplDriverSourceFolder "C:\Users\bpeti\Documents\GitHub\windows-rapl-driver" #define SystemFolder "C:\Windows\System32" #define System64Folder "C:\Windows\SysWOW64" @@ -24,7 +22,7 @@ AppSupportURL={#MyAppURL} AppUpdatesURL={#MyAppURL} DefaultDirName={autopf}\{#MyAppName} DefaultGroupName={#MyAppName} -LicenseFile=C:\Users\bpeti\Documents\GitHub\scaphandre\LICENSE +LicenseFile=../../LICENSE ; Uncomment the following line to run in non administrative install mode (install for current user only.) ;PrivilegesRequired=lowest OutputBaseFilename={#MyAppName}_{#MyAppVersion}_installer @@ -32,26 +30,26 @@ Compression=lzma SolidCompression=yes WizardStyle=modern Uninstallable=yes -SetupIconFile=C:\Users\bpeti\Documents\GitHub\scaphandre\docs_src\scaphandre.ico +SetupIconFile=../../docs_src/scaphandre.ico [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" [Files] -Source: "{#MyAppSourceFolder}\target\release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion -Source: "{#RaplDriverSourceFolder}\x64\Release\DriverLoader.exe"; DestDir: "{app}"; Flags: ignoreversion -Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.inf"; DestDir: "{app}"; Flags: ignoreversion -; Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.sys"; DestDir: "{#SystemFolder}"; -; Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.sys"; DestDir: "{#System64Folder}"; -Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.sys"; DestDir: "{app}"; -Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.cat"; DestDir: "{app}"; -; Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.cat"; DestDir: "{#SystemFolder}"; -; Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.cat"; DestDir: "{#System64Folder}"; -Source: "C:\Program Files (x86)\Windows Kits\10\Tools\10.0.22621.0\x64\devcon.exe"; DestDir: "{app}"; Flags: ignoreversion -Source: "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\certmgr.exe"; DestDir: "{app}"; Flags: ignoreversion -Source: "{#MyAppSourceFolder}\README.md"; DestDir: "{app}"; Flags: ignoreversion -Source: "{#MyAppSourceFolder}\CHANGELOG.md"; DestDir: "{app}"; Flags: ignoreversion -Source: "{#RaplDriverSourceFolder}\ScaphandreDrvTest.cer"; DestDir: "{app}"; Flags: ignoreversion +Source: "../../target/release/{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion +Source: "../../DriverLoader.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "../../ScaphandreDrv.inf"; DestDir: "{app}"; Flags: ignoreversion +; Source: "../../ScaphandreDrv.sys"; DestDir: "{#SystemFolder}"; +; Source: "../../ScaphandreDrv.sys"; DestDir: "{#System64Folder}"; +Source: "../../ScaphandreDrv.sys"; DestDir: "{app}"; +Source: "../../ScaphandreDrv.cat"; DestDir: "{app}"; +; Source: "../../ScaphandreDrv.cat"; DestDir: "{#SystemFolder}"; +; Source: "../../ScaphandreDrv.cat"; DestDir: "{#System64Folder}"; +Source: "../../devcon.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "../../certmgr.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "../../README.md"; DestDir: "{app}"; Flags: ignoreversion +Source: "../../CHANGELOG.md"; DestDir: "{app}"; Flags: ignoreversion +Source: "../../ScaphandreDrvTest.cer"; DestDir: "{app}"; Flags: ignoreversion ; NOTE: Don't use "Flags: ignoreversion" on any shared system files [Icons] @@ -64,7 +62,6 @@ Filename: "{app}/devcon.exe"; Parameters: "enable {app}\ScaphandreDrv.inf root\S Filename: "{app}/DriverLoader.exe"; Parameters: "install"; WorkingDir: "{app}"; Description: "Install Driver Service"; Filename: "{app}/DriverLoader.exe"; Parameters: "start"; WorkingDir: "{app}"; Description: "Start Driver Service"; ; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; -; Filename: "schtasks.exe"; Parameters: "/Create /SC ONSTART {app}\scaphandre.exe prometheus-push " [UninstallRun] Filename: "{app}/DriverLoader.exe"; Parameters: "stop"; WorkingDir: "{app}"; RunOnceId: "StopService"; From d1d797584d7977b8e4337c8e2ba14f0e1d166fa9 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 14 Aug 2023 17:36:49 +0200 Subject: [PATCH 226/303] fix: trying latest windows version to be abble to install aws tools as powershell cmdlet ci: fixing artifact push on scw s3 --- .../workflows/exe-release-prometheuspush.yml | 19 +++-- packaging/windows/dev_installer.iss | 75 +++++++++++++++++++ packaging/windows/installer.iss | 2 +- 3 files changed, 90 insertions(+), 6 deletions(-) create mode 100644 packaging/windows/dev_installer.iss diff --git a/.github/workflows/exe-release-prometheuspush.yml b/.github/workflows/exe-release-prometheuspush.yml index 706b2a7c..c7c99db0 100644 --- a/.github/workflows/exe-release-prometheuspush.yml +++ b/.github/workflows/exe-release-prometheuspush.yml @@ -18,7 +18,7 @@ env: jobs: build_exe_win1011: name: Build exe installer for windows 10/11/server 2016/server 2019/server 2022 - runs-on: "windows-2019" + runs-on: "windows-latest" steps: - name: Checkout uses: actions/checkout@v3 @@ -67,9 +67,18 @@ jobs: - name: Build package run: | & "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" packaging/windows/installer.iss - - name: Upload artifact + - name: Upload artifact #Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force run: | - Install-Module -Name AWS.Tools.Installer - Install-AWSToolsModule AWS.Tools.EC2,AWS.Tools.S3 -CleanUp + Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted + Install-Module -Confirm:$False -Name AWS.Tools.Installer + Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine + Import-Module AWS.Tools.Installer + Install-AWSToolsModule AWS.Tools.EC2,AWS.Tools.S3 -CleanUp -Confirm:$False Set-AWSCredential -AccessKey ${{ secrets.S3_ACCESS_KEY_ID }} -SecretKey ${{ secrets.S3_SECRET_ACCESS_KEY }} -StoreAs default - Write-S3Object -BucketName scaphandre -File scaphandre_0.5.0_installer.exe \ No newline at end of file + mv packaging/windows/Output/scaphandre_installer.exe scaphandre_${GITHUB_REF_NAME}_installer.exe + $clientconfig=@{ + SignatureVersion="s3v4" + ServiceUrl="https://s3.fr-par.scw.cloud" + S3Region="fr-par" + } + Write-S3Object -EndpointUrl "https://s3.fr-par.scw.cloud" -Region "fr-par" -BucketName "scaphandre" -File scaphandre_${GITHUB_REF_NAME}_installer.exe -key "x86_64/scaphandre_${GITHUB_REF_NAME}_installer.exe" -PublicReadOnly -ClientConfig $clientconfig \ No newline at end of file diff --git a/packaging/windows/dev_installer.iss b/packaging/windows/dev_installer.iss new file mode 100644 index 00000000..fb06f118 --- /dev/null +++ b/packaging/windows/dev_installer.iss @@ -0,0 +1,75 @@ + ; Script generated by the Inno Setup Script Wizard. +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! + +#define MyAppName "scaphandre" +#define MyAppVersion "0.5.0" +#define MyAppPublisher "Hubblo" +#define MyAppURL "https://hubblo-org.github.io/scaphandre-documentation" +#define MyAppExeName "scaphandre.exe" +#define MyAppSourceFolder "C:\Users\bpeti\Documents\GitHub\scaphandre" +#define RaplDriverSourceFolder "C:\Users\bpeti\Documents\GitHub\windows-rapl-driver" +#define SystemFolder "C:\Windows\System32" +#define System64Folder "C:\Windows\SysWOW64" + +[Setup] +; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. +; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) +AppId={{7DB7B851-1DD2-4FF5-BFC7-282FEBA3B28D} +AppName={#MyAppName} +AppVersion={#MyAppVersion} +;AppVerName={#MyAppName} {#MyAppVersion} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +AppSupportURL={#MyAppURL} +AppUpdatesURL={#MyAppURL} +DefaultDirName={autopf}\{#MyAppName} +DefaultGroupName={#MyAppName} +LicenseFile=C:\Users\bpeti\Documents\GitHub\scaphandre\LICENSE +; Uncomment the following line to run in non administrative install mode (install for current user only.) +;PrivilegesRequired=lowest +OutputBaseFilename={#MyAppName}_{#MyAppVersion}_installer +Compression=lzma +SolidCompression=yes +WizardStyle=modern +Uninstallable=yes +SetupIconFile=C:\Users\bpeti\Documents\GitHub\scaphandre\docs_src\scaphandre.ico + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Files] +Source: "{#MyAppSourceFolder}\target\release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#RaplDriverSourceFolder}\x64\Release\DriverLoader.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.inf"; DestDir: "{app}"; Flags: ignoreversion +; Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.sys"; DestDir: "{#SystemFolder}"; +; Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.sys"; DestDir: "{#System64Folder}"; +Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.sys"; DestDir: "{app}"; +Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.cat"; DestDir: "{app}"; +; Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.cat"; DestDir: "{#SystemFolder}"; +; Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.cat"; DestDir: "{#System64Folder}"; +Source: "C:\Program Files (x86)\Windows Kits\10\Tools\10.0.22621.0\x64\devcon.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\certmgr.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#MyAppSourceFolder}\README.md"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#MyAppSourceFolder}\CHANGELOG.md"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#RaplDriverSourceFolder}\ScaphandreDrvTest.cer"; DestDir: "{app}"; Flags: ignoreversion +; NOTE: Don't use "Flags: ignoreversion" on any shared system files + +[Icons] +Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" + +[Run] +Filename: "C:\windows\System32\WindowsPowershell\v1.0\powershell.exe"; Parameters: "Import-Certificate -FilePath {app}\ScaphandreDrvTest.cer -CertStoreLocation Cert:\LocalMachine\Root"; Description: "Register test certificate"; Flags: waituntilidle shellexec +Filename: "{app}/devcon.exe"; Parameters: "install {app}\ScaphandreDrv.inf root\SCAPHANDREDRV"; Description: "Install Driver"; Flags: waituntilidle +Filename: "{app}/devcon.exe"; Parameters: "enable {app}\ScaphandreDrv.inf root\SCAPHANDREDRV"; Description: "Enable Driver"; Flags: waituntilidle +Filename: "{app}/DriverLoader.exe"; Parameters: "install"; WorkingDir: "{app}"; Description: "Install Driver Service"; +Filename: "{app}/DriverLoader.exe"; Parameters: "start"; WorkingDir: "{app}"; Description: "Start Driver Service"; +; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; +; Filename: "schtasks.exe"; Parameters: "/Create /SC ONSTART {app}\scaphandre.exe prometheus-push " + +[UninstallRun] +Filename: "{app}/DriverLoader.exe"; Parameters: "stop"; WorkingDir: "{app}"; RunOnceId: "StopService"; +Filename: "{app}/DriverLoader.exe"; Parameters: "remove"; WorkingDir: "{app}"; RunOnceId: "RemoveService"; +Filename: "{app}/devcon.exe"; Parameters: "disable ScaphandreDrv"; RunOnceId: "DisableDrier"; +Filename: "{app}/devcon.exe"; Parameters: "remove ScaphandreDrv"; RunOnceId: "RemoveService"; + + diff --git a/packaging/windows/installer.iss b/packaging/windows/installer.iss index 2389673e..bc5287ac 100644 --- a/packaging/windows/installer.iss +++ b/packaging/windows/installer.iss @@ -25,7 +25,7 @@ DefaultGroupName={#MyAppName} LicenseFile=../../LICENSE ; Uncomment the following line to run in non administrative install mode (install for current user only.) ;PrivilegesRequired=lowest -OutputBaseFilename={#MyAppName}_{#MyAppVersion}_installer +OutputBaseFilename={#MyAppName}_installer Compression=lzma SolidCompression=yes WizardStyle=modern From 9267e0a240595c7d8b2039d972233462f30ad4ae Mon Sep 17 00:00:00 2001 From: bpetit Date: Tue, 15 Aug 2023 11:59:46 +0200 Subject: [PATCH 227/303] ci: fixing artifact push on scw s3 --- .github/workflows/exe-release-prometheuspush.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/exe-release-prometheuspush.yml b/.github/workflows/exe-release-prometheuspush.yml index c7c99db0..2e8963ca 100644 --- a/.github/workflows/exe-release-prometheuspush.yml +++ b/.github/workflows/exe-release-prometheuspush.yml @@ -79,6 +79,5 @@ jobs: $clientconfig=@{ SignatureVersion="s3v4" ServiceUrl="https://s3.fr-par.scw.cloud" - S3Region="fr-par" } Write-S3Object -EndpointUrl "https://s3.fr-par.scw.cloud" -Region "fr-par" -BucketName "scaphandre" -File scaphandre_${GITHUB_REF_NAME}_installer.exe -key "x86_64/scaphandre_${GITHUB_REF_NAME}_installer.exe" -PublicReadOnly -ClientConfig $clientconfig \ No newline at end of file From 23af28668e90967cf5e1bb72983d4124cd33b22a Mon Sep 17 00:00:00 2001 From: bpetit Date: Tue, 15 Aug 2023 12:44:52 +0200 Subject: [PATCH 228/303] ci: removed old codesee workflow --- .github/workflows/codesee-arch-diagram.yml | 87 ---------------------- 1 file changed, 87 deletions(-) delete mode 100644 .github/workflows/codesee-arch-diagram.yml diff --git a/.github/workflows/codesee-arch-diagram.yml b/.github/workflows/codesee-arch-diagram.yml deleted file mode 100644 index 904763c8..00000000 --- a/.github/workflows/codesee-arch-diagram.yml +++ /dev/null @@ -1,87 +0,0 @@ -on: - push: - branches: - - main - pull_request_target: - types: [opened, synchronize, reopened] - -name: CodeSee Map - -jobs: - test_map_action: - runs-on: ubuntu-latest - continue-on-error: true - name: Run CodeSee Map Analysis - steps: - - name: checkout - id: checkout - uses: actions/checkout@v2 - with: - repository: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.event.pull_request.head.ref }} - fetch-depth: 0 - - # codesee-detect-languages has an output with id languages. - - name: Detect Languages - id: detect-languages - uses: Codesee-io/codesee-detect-languages-action@latest - - #- name: Configure JDK 16 - # uses: actions/setup-java@v2 - # if: ${{ fromJSON(steps.detect-languages.outputs.languages).java }} - # with: - # java-version: '16' - # distribution: 'zulu' - - ## CodeSee Maps Go support uses a static binary so there's no setup step required. - - #- name: Configure Node.js 14 - # uses: actions/setup-node@v2 - # if: ${{ fromJSON(steps.detect-languages.outputs.languages).javascript }} - # with: - # node-version: '14' - - #- name: Configure Python 3.x - # uses: actions/setup-python@v2 - # if: ${{ fromJSON(steps.detect-languages.outputs.languages).python }} - # with: - # python-version: '3.10' - # architecture: 'x64' - - #- name: Configure Ruby '3.x' - # uses: ruby/setup-ruby@v1 - # if: ${{ fromJSON(steps.detect-languages.outputs.languages).ruby }} - # with: - # ruby-version: '3.0' - - # We need the rust toolchain because it uses rustc and cargo to inspect the package - - name: Configure Rust 1.x stable - uses: bpetit/action-toolchain@v2.0.0 - if: ${{ fromJSON(steps.detect-languages.outputs.languages).rust }} - with: - toolchain: stable - - - name: Generate Map - id: generate-map - uses: Codesee-io/codesee-map-action@latest - with: - step: map - api_token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }} - github_ref: ${{ github.ref }} - languages: ${{ steps.detect-languages.outputs.languages }} - - - name: Upload Map - id: upload-map - uses: Codesee-io/codesee-map-action@latest - with: - step: mapUpload - api_token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }} - github_ref: ${{ github.ref }} - - - name: Insights - id: insights - uses: Codesee-io/codesee-map-action@latest - with: - step: insights - api_token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }} - github_ref: ${{ github.ref }} From b48d0e7a2a8a5dbf1504d8a1a26a46151ce8dcbb Mon Sep 17 00:00:00 2001 From: bpetit Date: Tue, 15 Aug 2023 15:01:07 +0200 Subject: [PATCH 229/303] ci: fixing output name of exe file from workflow --- .github/workflows/exe-release-prometheuspush.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/exe-release-prometheuspush.yml b/.github/workflows/exe-release-prometheuspush.yml index 2e8963ca..78bbaa46 100644 --- a/.github/workflows/exe-release-prometheuspush.yml +++ b/.github/workflows/exe-release-prometheuspush.yml @@ -9,7 +9,7 @@ on: - 'book.toml' - 'CONTRIBUTING.md' tags: [ 'v*.*.*', 'dev*.*.*' ] - branches: [ '311-github-workflow-to-build-and-publish-a-exemsi-file-including-signed-rapl-driver-at-each-tagrelease' ] + branches: [ '336-proper-handling-of-windows-service-management' ] env: WRD_VERSION: v0.0.2 @@ -75,9 +75,9 @@ jobs: Import-Module AWS.Tools.Installer Install-AWSToolsModule AWS.Tools.EC2,AWS.Tools.S3 -CleanUp -Confirm:$False Set-AWSCredential -AccessKey ${{ secrets.S3_ACCESS_KEY_ID }} -SecretKey ${{ secrets.S3_SECRET_ACCESS_KEY }} -StoreAs default - mv packaging/windows/Output/scaphandre_installer.exe scaphandre_${GITHUB_REF_NAME}_installer.exe + mv packaging/windows/Output/scaphandre_installer.exe scaphandre_${{ env.GITHUB_REF_NAME }}_installer.exe $clientconfig=@{ SignatureVersion="s3v4" ServiceUrl="https://s3.fr-par.scw.cloud" } - Write-S3Object -EndpointUrl "https://s3.fr-par.scw.cloud" -Region "fr-par" -BucketName "scaphandre" -File scaphandre_${GITHUB_REF_NAME}_installer.exe -key "x86_64/scaphandre_${GITHUB_REF_NAME}_installer.exe" -PublicReadOnly -ClientConfig $clientconfig \ No newline at end of file + Write-S3Object -EndpointUrl "https://s3.fr-par.scw.cloud" -Region "fr-par" -BucketName "scaphandre" -File scaphandre_${{ env.GITHUB_REF_NAME }}_installer.exe -key "x86_64/scaphandre_${GITHUB_REF_NAME}_installer.exe" -PublicReadOnly -ClientConfig $clientconfig \ No newline at end of file From 60eabe4b19c09a77e49faf64d4de3bb2ef0282c4 Mon Sep 17 00:00:00 2001 From: bpetit Date: Tue, 15 Aug 2023 15:16:13 +0200 Subject: [PATCH 230/303] ci: fixing output name of exe file from workflow --- .github/workflows/exe-release-prometheuspush.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/exe-release-prometheuspush.yml b/.github/workflows/exe-release-prometheuspush.yml index 78bbaa46..3976a05a 100644 --- a/.github/workflows/exe-release-prometheuspush.yml +++ b/.github/workflows/exe-release-prometheuspush.yml @@ -75,9 +75,9 @@ jobs: Import-Module AWS.Tools.Installer Install-AWSToolsModule AWS.Tools.EC2,AWS.Tools.S3 -CleanUp -Confirm:$False Set-AWSCredential -AccessKey ${{ secrets.S3_ACCESS_KEY_ID }} -SecretKey ${{ secrets.S3_SECRET_ACCESS_KEY }} -StoreAs default - mv packaging/windows/Output/scaphandre_installer.exe scaphandre_${{ env.GITHUB_REF_NAME }}_installer.exe + mv packaging/windows/Output/scaphandre_installer.exe scaphandre_${{ github.ref.name }}_installer.exe $clientconfig=@{ SignatureVersion="s3v4" ServiceUrl="https://s3.fr-par.scw.cloud" } - Write-S3Object -EndpointUrl "https://s3.fr-par.scw.cloud" -Region "fr-par" -BucketName "scaphandre" -File scaphandre_${{ env.GITHUB_REF_NAME }}_installer.exe -key "x86_64/scaphandre_${GITHUB_REF_NAME}_installer.exe" -PublicReadOnly -ClientConfig $clientconfig \ No newline at end of file + Write-S3Object -EndpointUrl "https://s3.fr-par.scw.cloud" -Region "fr-par" -BucketName "scaphandre" -File scaphandre_${{ env.GITHUB_REF_NAME }}_installer.exe -key "x86_64/scaphandre_${{ github.ref.name }}_installer.exe" -PublicReadOnly -ClientConfig $clientconfig \ No newline at end of file From d3829ebb63fd3c1399146caed42b65f48149dea5 Mon Sep 17 00:00:00 2001 From: bpetit Date: Tue, 15 Aug 2023 15:36:21 +0200 Subject: [PATCH 231/303] ci: fixing output name of exe file from workflow --- .github/workflows/exe-release-prometheuspush.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/exe-release-prometheuspush.yml b/.github/workflows/exe-release-prometheuspush.yml index 3976a05a..3056f635 100644 --- a/.github/workflows/exe-release-prometheuspush.yml +++ b/.github/workflows/exe-release-prometheuspush.yml @@ -75,9 +75,9 @@ jobs: Import-Module AWS.Tools.Installer Install-AWSToolsModule AWS.Tools.EC2,AWS.Tools.S3 -CleanUp -Confirm:$False Set-AWSCredential -AccessKey ${{ secrets.S3_ACCESS_KEY_ID }} -SecretKey ${{ secrets.S3_SECRET_ACCESS_KEY }} -StoreAs default - mv packaging/windows/Output/scaphandre_installer.exe scaphandre_${{ github.ref.name }}_installer.exe + mv packaging/windows/Output/scaphandre_installer.exe scaphandre_${{ github.ref_name }}_installer.exe $clientconfig=@{ SignatureVersion="s3v4" ServiceUrl="https://s3.fr-par.scw.cloud" } - Write-S3Object -EndpointUrl "https://s3.fr-par.scw.cloud" -Region "fr-par" -BucketName "scaphandre" -File scaphandre_${{ env.GITHUB_REF_NAME }}_installer.exe -key "x86_64/scaphandre_${{ github.ref.name }}_installer.exe" -PublicReadOnly -ClientConfig $clientconfig \ No newline at end of file + Write-S3Object -EndpointUrl "https://s3.fr-par.scw.cloud" -Region "fr-par" -BucketName "scaphandre" -File scaphandre_${{ env.GITHUB_REF_NAME }}_installer.exe -key "x86_64/scaphandre_${{ github.ref_name }}_installer.exe" -PublicReadOnly -ClientConfig $clientconfig \ No newline at end of file From 8b3ea5a598bb5f8adc8c91e1fab360b2de09597f Mon Sep 17 00:00:00 2001 From: bpetit Date: Tue, 15 Aug 2023 16:05:59 +0200 Subject: [PATCH 232/303] ci: fixing output name of exe file from workflow --- .github/workflows/exe-release-prometheuspush.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/exe-release-prometheuspush.yml b/.github/workflows/exe-release-prometheuspush.yml index 3056f635..650bfacb 100644 --- a/.github/workflows/exe-release-prometheuspush.yml +++ b/.github/workflows/exe-release-prometheuspush.yml @@ -80,4 +80,4 @@ jobs: SignatureVersion="s3v4" ServiceUrl="https://s3.fr-par.scw.cloud" } - Write-S3Object -EndpointUrl "https://s3.fr-par.scw.cloud" -Region "fr-par" -BucketName "scaphandre" -File scaphandre_${{ env.GITHUB_REF_NAME }}_installer.exe -key "x86_64/scaphandre_${{ github.ref_name }}_installer.exe" -PublicReadOnly -ClientConfig $clientconfig \ No newline at end of file + Write-S3Object -EndpointUrl "https://s3.fr-par.scw.cloud" -Region "fr-par" -BucketName "scaphandre" -File scaphandre_${{ github.ref_name }}_installer.exe -key "x86_64/scaphandre_${{ github.ref_name }}_installer.exe" -PublicReadOnly -ClientConfig $clientconfig \ No newline at end of file From 4da908db48bcc6a635ea3fb0f970d1b41876a2f8 Mon Sep 17 00:00:00 2001 From: bpetit Date: Wed, 16 Aug 2023 15:32:49 +0200 Subject: [PATCH 233/303] feat: enabling to stop the windows service --- src/main.rs | 55 +++++++++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/main.rs b/src/main.rs index 83db7998..50db79e0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -114,16 +114,18 @@ enum ExporterChoice { fn my_service_main(arguments: Vec) { if let Err(_e) = run_service(arguments) { // Handle errors in some way. + } } #[cfg(target_os = "windows")] fn run_service(_arguments: Vec) -> Result<()> { - #[cfg(target_os = "windows")] + let mut stop = false; let event_handler = move |control_event| -> ServiceControlHandlerResult { match control_event { ServiceControl::Stop => { // Handle stop event and return control back to the system. + stop = true; ServiceControlHandlerResult::NoError } // All services must accept Interrogate even if it's a no-op. @@ -131,33 +133,36 @@ fn run_service(_arguments: Vec) -> Result<()> { _ => ServiceControlHandlerResult::NotImplemented, } }; - #[cfg(target_os = "windows")] - if let Ok(system_handler) = service_control_handler::register("Scaphandre", event_handler) { - let next_status = ServiceStatus { - // Should match the one from system service registry - service_type: ServiceType::OWN_PROCESS, - // The new state - current_state: ServiceState::Running, - // Accept stop events when running - controls_accepted: ServiceControlAccept::STOP, - // Used to report an error when starting or stopping only, otherwise must be zero - exit_code: ServiceExitCode::Win32(0), - // Only used for pending states, otherwise must be zero - checkpoint: 0, - // Only used for pending states, otherwise must be zero - wait_hint: Duration::default(), - // Unused for setting status - process_id: None, - }; - - // Tell the system that the service is running now - if let Ok(_status_set) = system_handler.set_service_status(next_status) { - parse_cli_and_run_exporter(); + if ! stop { + if let Ok(system_handler) = service_control_handler::register("Scaphandre", event_handler) { + let next_status = ServiceStatus { + // Should match the one from system service registry + service_type: ServiceType::OWN_PROCESS, + // The new state + current_state: ServiceState::Running, + // Accept stop events when running + controls_accepted: ServiceControlAccept::STOP, + // Used to report an error when starting or stopping only, otherwise must be zero + exit_code: ServiceExitCode::Win32(0), + // Only used for pending states, otherwise must be zero + checkpoint: 0, + // Only used for pending states, otherwise must be zero + wait_hint: Duration::default(), + // Unused for setting status + process_id: None, + }; + + // Tell the system that the service is running now + if let Ok(_status_set) = system_handler.set_service_status(next_status) { + parse_cli_and_run_exporter(); + } else { + panic!("Couldn't set Windows service status."); + } } else { - panic!("Couldn't set Windows service status."); + panic!("Couldn't get Windows system events handler."); } } else { - panic!("Couldn't get Windows system events handler."); + panic!("Service has been stopped !"); } Ok(()) } From cb1b5f2ff6e436847d29f183b0a0e11186876f3c Mon Sep 17 00:00:00 2001 From: bpetit Date: Wed, 16 Aug 2023 16:05:43 +0200 Subject: [PATCH 234/303] feat: enabling to stop the windows service --- src/main.rs | 54 ++++++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/main.rs b/src/main.rs index 50db79e0..b71dba89 100644 --- a/src/main.rs +++ b/src/main.rs @@ -133,36 +133,36 @@ fn run_service(_arguments: Vec) -> Result<()> { _ => ServiceControlHandlerResult::NotImplemented, } }; - if ! stop { - if let Ok(system_handler) = service_control_handler::register("Scaphandre", event_handler) { - let next_status = ServiceStatus { - // Should match the one from system service registry - service_type: ServiceType::OWN_PROCESS, - // The new state - current_state: ServiceState::Running, - // Accept stop events when running - controls_accepted: ServiceControlAccept::STOP, - // Used to report an error when starting or stopping only, otherwise must be zero - exit_code: ServiceExitCode::Win32(0), - // Only used for pending states, otherwise must be zero - checkpoint: 0, - // Only used for pending states, otherwise must be zero - wait_hint: Duration::default(), - // Unused for setting status - process_id: None, - }; - - // Tell the system that the service is running now - if let Ok(_status_set) = system_handler.set_service_status(next_status) { - parse_cli_and_run_exporter(); - } else { - panic!("Couldn't set Windows service status."); - } + if let Ok(system_handler) = service_control_handler::register("Scaphandre", event_handler) { + let mut next_status = ServiceStatus { + // Should match the one from system service registry + service_type: ServiceType::OWN_PROCESS, + // The new state + current_state: ServiceState::Running, + // Accept stop events when running + controls_accepted: ServiceControlAccept::STOP, + // Used to report an error when starting or stopping only, otherwise must be zero + exit_code: ServiceExitCode::Win32(0), + // Only used for pending states, otherwise must be zero + checkpoint: 0, + // Only used for pending states, otherwise must be zero + wait_hint: Duration::default(), + // Unused for setting status + process_id: None, + }; + if stop { + next_status.current_state = ServiceState::StopPending; + next_status.exit_code = ServiceExitCode::Win32(0); + next_status.wait_hint = Duration::from_secs(1); + } + // Tell the system that the service is running now + if let Ok(_status_set) = system_handler.set_service_status(next_status) { + parse_cli_and_run_exporter(); } else { - panic!("Couldn't get Windows system events handler."); + panic!("Couldn't set Windows service status."); } } else { - panic!("Service has been stopped !"); + panic!("Couldn't get Windows system events handler."); } Ok(()) } From 3f96f3f05862f1e108ac57d2615d270262820227 Mon Sep 17 00:00:00 2001 From: bpetit Date: Wed, 16 Aug 2023 18:31:26 +0200 Subject: [PATCH 235/303] feat: enabling to stop the windows service --- src/exporters/json.rs | 3 ++- src/exporters/mod.rs | 15 ++++++++++- src/exporters/prometheus.rs | 2 +- src/exporters/prometheuspush.rs | 11 +++++++- src/exporters/qemu.rs | 3 ++- src/exporters/riemann.rs | 3 ++- src/exporters/stdout.rs | 3 ++- src/main.rs | 46 ++++++++++++++++++++++----------- 8 files changed, 64 insertions(+), 22 deletions(-) diff --git a/src/exporters/json.rs b/src/exporters/json.rs index c448cd4f..6733e46f 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -8,6 +8,7 @@ use std::{ path::{Path, PathBuf}, thread, time::{Duration, Instant}, + sync::mpsc::Receiver }; /// An Exporter that writes power consumption data of the host @@ -156,7 +157,7 @@ struct Report { impl Exporter for JsonExporter { /// Runs [iterate()] every `step` until `timeout` - fn run(&mut self) { + fn run(&mut self, channel: &Receiver) { let step = self.time_step; info!("Measurement step is: {step:?}"); diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index 32be23d9..3ece8fcb 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -25,6 +25,7 @@ use std::collections::HashMap; use std::fmt; use std::time::Duration; use utils::get_scaphandre_version; +use std::sync::mpsc::Receiver; #[cfg(feature = "containers")] use { docker_sync::{container::Container, Docker}, @@ -108,10 +109,22 @@ impl fmt::Debug for MetricValueType { /// with the structs provided by the sensor. pub trait Exporter { /// Runs the exporter. - fn run(&mut self); + fn run(&mut self, channel: &Receiver); /// The name of the kind of the exporter, for example "json". fn kind(&self) -> &str; + + fn watch_signal(&mut self, channel: &Receiver) -> Option { + match channel.try_recv() { + Ok(received) => { + info!("Received signal: {}", received); + Some(1) + }, + Err(_) => { + None + } + } + } } /// MetricGenerator is an exporter helper structure to collect Scaphandre metrics. diff --git a/src/exporters/prometheus.rs b/src/exporters/prometheus.rs index 05065cc2..3fe8a238 100644 --- a/src/exporters/prometheus.rs +++ b/src/exporters/prometheus.rs @@ -72,7 +72,7 @@ impl PrometheusExporter { impl Exporter for PrometheusExporter { /// Starts an HTTP server to expose the metrics in Prometheus format. - fn run(&mut self) { + fn run(&mut self, channel: Receiver) { info!( "{}: Starting Prometheus exporter", Utc::now().format("%Y-%m-%dT%H:%M:%S") diff --git a/src/exporters/prometheuspush.rs b/src/exporters/prometheuspush.rs index 73981e4f..0ff558c2 100644 --- a/src/exporters/prometheuspush.rs +++ b/src/exporters/prometheuspush.rs @@ -13,6 +13,7 @@ use isahc::{prelude::*, Request}; use std::fmt::Write; use std::thread; use std::time::Duration; +use std::sync::mpsc::Receiver; pub struct PrometheusPushExporter { topo: Topology, @@ -72,7 +73,7 @@ impl PrometheusPushExporter { } impl Exporter for PrometheusPushExporter { - fn run(&mut self) { + fn run(&mut self, channel: &Receiver) { info!( "{}: Starting Prometheus Push exporter", Utc::now().format("%Y-%m-%dT%H:%M:%S") @@ -96,6 +97,10 @@ impl Exporter for PrometheusPushExporter { ); loop { + if self.watch_signal(channel).is_some() { + info!("Daemon/Service has received a stop signal."); + break; + } metric_generator.topology.refresh(); metric_generator.gen_all_metrics(); let mut body = String::from(""); @@ -154,6 +159,10 @@ impl Exporter for PrometheusPushExporter { } } + if self.watch_signal(channel).is_some() { + info!("Daemon/Service has received a stop signal."); + break; + } thread::sleep(Duration::new(self.args.step, 0)); } } diff --git a/src/exporters/qemu.rs b/src/exporters/qemu.rs index de3355e5..239293de 100644 --- a/src/exporters/qemu.rs +++ b/src/exporters/qemu.rs @@ -2,6 +2,7 @@ use crate::exporters::Exporter; use crate::sensors::Topology; use crate::sensors::{utils::ProcessRecord, Sensor}; use std::{fs, io, thread, time}; +use std::sync::mpsc::Receiver; /// An Exporter that extracts power consumption data of running /// Qemu/KVM virtual machines on the host and store those data @@ -17,7 +18,7 @@ pub struct QemuExporter { impl Exporter for QemuExporter { /// Runs [iterate()] in a loop. - fn run(&mut self) { + fn run(&mut self, channel: Receiver) { info!("Starting qemu exporter"); let path = "/var/lib/libvirt/scaphandre"; let cleaner_step = 120; diff --git a/src/exporters/riemann.rs b/src/exporters/riemann.rs index 7635db04..5c36b2ca 100644 --- a/src/exporters/riemann.rs +++ b/src/exporters/riemann.rs @@ -11,6 +11,7 @@ use riemann_client::Client; use std::collections::HashMap; use std::convert::TryFrom; use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use std::sync::mpsc::Receiver; /// Riemann server default ipv4/ipv6 address const DEFAULT_IP_ADDRESS: &str = "localhost"; @@ -168,7 +169,7 @@ impl RiemannExporter { impl Exporter for RiemannExporter { /// Entry point of the RiemannExporter. - fn run(&mut self) { + fn run(&mut self, channel: Receiver) { info!( "{}: Starting Riemann exporter", Utc::now().format("%Y-%m-%dT%H:%M:%S") diff --git a/src/exporters/stdout.rs b/src/exporters/stdout.rs index e3d0717d..1132c99f 100644 --- a/src/exporters/stdout.rs +++ b/src/exporters/stdout.rs @@ -4,6 +4,7 @@ use regex::Regex; use std::fmt::Write; use std::thread; use std::time::{Duration, Instant}; +use std::sync::mpsc::Receiver; /// An Exporter that displays power consumption data of the host /// and its processes on the standard output of the terminal. @@ -53,7 +54,7 @@ pub struct ExporterArgs { impl Exporter for StdoutExporter { /// Runs [iterate()] every `step` until `timeout` - fn run(&mut self) { + fn run(&mut self, channel: &Receiver) { let time_step = Duration::from_secs(self.args.step); let time_limit = if self.args.timeout < 0 { None diff --git a/src/main.rs b/src/main.rs index b71dba89..6c43193e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ use clap::{command, ArgAction, Parser, Subcommand}; use colored::Colorize; use scaphandre::{exporters, sensors::Sensor}; +use std::sync::mpsc::{self, Receiver}; #[cfg(target_os = "linux")] use scaphandre::sensors::powercap_rapl; @@ -112,20 +113,19 @@ enum ExporterChoice { #[cfg(target_os = "windows")] fn my_service_main(arguments: Vec) { - if let Err(_e) = run_service(arguments) { - // Handle errors in some way. - + if let Err(e) = run_service(arguments) { + panic!("{:?}", e); } } #[cfg(target_os = "windows")] fn run_service(_arguments: Vec) -> Result<()> { - let mut stop = false; + let (tx, rx) = mpsc::channel(); let event_handler = move |control_event| -> ServiceControlHandlerResult { match control_event { ServiceControl::Stop => { // Handle stop event and return control back to the system. - stop = true; + let _ = tx.send(1); ServiceControlHandlerResult::NoError } // All services must accept Interrogate even if it's a no-op. @@ -134,7 +134,7 @@ fn run_service(_arguments: Vec) -> Result<()> { } }; if let Ok(system_handler) = service_control_handler::register("Scaphandre", event_handler) { - let mut next_status = ServiceStatus { + let next_status = ServiceStatus { // Should match the one from system service registry service_type: ServiceType::OWN_PROCESS, // The new state @@ -150,17 +150,31 @@ fn run_service(_arguments: Vec) -> Result<()> { // Unused for setting status process_id: None, }; - if stop { - next_status.current_state = ServiceState::StopPending; - next_status.exit_code = ServiceExitCode::Win32(0); - next_status.wait_hint = Duration::from_secs(1); - } + // next_status.current_state = ServiceState::StopPending; + // next_status.exit_code = ServiceExitCode::Win32(0); + // next_status.wait_hint = Duration::from_secs(1); // Tell the system that the service is running now if let Ok(_status_set) = system_handler.set_service_status(next_status) { - parse_cli_and_run_exporter(); + parse_cli_and_run_exporter(&rx); } else { panic!("Couldn't set Windows service status."); } + + let stop_status = ServiceStatus { + service_type: ServiceType::OWN_PROCESS, + current_state: ServiceState::Stopped, + controls_accepted: ServiceControlAccept::STOP, + exit_code: ServiceExitCode::Win32(0), + checkpoint: 0, + wait_hint: Duration::default(), + process_id: None + }; + + if let Ok(_status_set) = system_handler.set_service_status(stop_status) { + } else { + panic!("Couldn't set Windows service STOP status."); + } + } else { panic!("Couldn't get Windows system events handler."); } @@ -176,10 +190,12 @@ fn main() { } } - parse_cli_and_run_exporter(); + let (_, rx) = mpsc::channel(); + + parse_cli_and_run_exporter(&rx); } -fn parse_cli_and_run_exporter() { +fn parse_cli_and_run_exporter(channel: &Receiver) { let cli = Cli::parse(); loggerv::init_with_verbosity(cli.verbose.into()).expect("unable to initialize the logger"); @@ -189,7 +205,7 @@ fn parse_cli_and_run_exporter() { print_scaphandre_header(exporter.kind()); } - exporter.run(); + exporter.run(channel); } fn build_exporter(choice: ExporterChoice, sensor: &dyn Sensor) -> Box { From f1156046d2d32e97ac68db1c8d34a17e9a9e4453 Mon Sep 17 00:00:00 2001 From: bpetit Date: Wed, 16 Aug 2023 18:50:57 +0200 Subject: [PATCH 236/303] feat: enabling to stop the windows service --- src/exporters/prometheus.rs | 3 ++- src/exporters/riemann.rs | 2 +- src/main.rs | 5 ++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/exporters/prometheus.rs b/src/exporters/prometheus.rs index 3fe8a238..ad0e6150 100644 --- a/src/exporters/prometheus.rs +++ b/src/exporters/prometheus.rs @@ -18,6 +18,7 @@ use std::{ net::{IpAddr, Ipv4Addr, SocketAddr}, sync::{Arc, Mutex}, time::Duration, + sync::mpsc::Receiver }; /// Default ipv4/ipv6 address to expose the service is any @@ -72,7 +73,7 @@ impl PrometheusExporter { impl Exporter for PrometheusExporter { /// Starts an HTTP server to expose the metrics in Prometheus format. - fn run(&mut self, channel: Receiver) { + fn run(&mut self, channel: &Receiver) { info!( "{}: Starting Prometheus exporter", Utc::now().format("%Y-%m-%dT%H:%M:%S") diff --git a/src/exporters/riemann.rs b/src/exporters/riemann.rs index 5c36b2ca..94843e2e 100644 --- a/src/exporters/riemann.rs +++ b/src/exporters/riemann.rs @@ -169,7 +169,7 @@ impl RiemannExporter { impl Exporter for RiemannExporter { /// Entry point of the RiemannExporter. - fn run(&mut self, channel: Receiver) { + fn run(&mut self, channel: &Receiver) { info!( "{}: Starting Riemann exporter", Utc::now().format("%Y-%m-%dT%H:%M:%S") diff --git a/src/main.rs b/src/main.rs index 6c43193e..91fa1b87 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ use clap::{command, ArgAction, Parser, Subcommand}; use colored::Colorize; use scaphandre::{exporters, sensors::Sensor}; use std::sync::mpsc::{self, Receiver}; +use std::thread; #[cfg(target_os = "linux")] use scaphandre::sensors::powercap_rapl; @@ -155,7 +156,9 @@ fn run_service(_arguments: Vec) -> Result<()> { // next_status.wait_hint = Duration::from_secs(1); // Tell the system that the service is running now if let Ok(_status_set) = system_handler.set_service_status(next_status) { - parse_cli_and_run_exporter(&rx); + let handle = thread::spawn(move || { + parse_cli_and_run_exporter(&rx); + }); } else { panic!("Couldn't set Windows service status."); } From d3ab6ebfa0079569f5c05f3b22a6824686779de7 Mon Sep 17 00:00:00 2001 From: bpetit Date: Thu, 17 Aug 2023 16:13:51 +0200 Subject: [PATCH 237/303] feat: enabling to stop the windows service --- src/main.rs | 125 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 73 insertions(+), 52 deletions(-) diff --git a/src/main.rs b/src/main.rs index 91fa1b87..98539238 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ use clap::{command, ArgAction, Parser, Subcommand}; use colored::Colorize; use scaphandre::{exporters, sensors::Sensor}; -use std::sync::mpsc::{self, Receiver}; +use std::sync::mpsc::{self, Receiver, Sender}; use std::thread; #[cfg(target_os = "linux")] @@ -114,19 +114,47 @@ enum ExporterChoice { #[cfg(target_os = "windows")] fn my_service_main(arguments: Vec) { - if let Err(e) = run_service(arguments) { - panic!("{:?}", e); - } -} + use std::thread::JoinHandle; + let graceful_period = 3; -#[cfg(target_os = "windows")] -fn run_service(_arguments: Vec) -> Result<()> { let (tx, rx) = mpsc::channel(); + let start_status = ServiceStatus { + service_type: ServiceType::OWN_PROCESS, // Should match the one from system service registry + current_state: ServiceState::Running, // The new state + controls_accepted: ServiceControlAccept::STOP, // Accept stop events when running + exit_code: ServiceExitCode::Win32(0), // Used to report an error when starting or stopping only, otherwise must be zero + checkpoint: 0, // Only used for pending states, otherwise must be zero + wait_hint: Duration::default(), // Only used for pending states, otherwise must be zero + process_id: None, // Unused for setting status + }; + let stop_status = ServiceStatus { + service_type: ServiceType::OWN_PROCESS, + current_state: ServiceState::Stopped, + controls_accepted: ServiceControlAccept::STOP, + exit_code: ServiceExitCode::Win32(0), + checkpoint: 0, + wait_hint: Duration::default(), + process_id: None + }; + let stoppending_status = ServiceStatus { + service_type: ServiceType::OWN_PROCESS, + current_state: ServiceState::StopPending, + controls_accepted: ServiceControlAccept::STOP, + exit_code: ServiceExitCode::Win32(0), + checkpoint: 0, + wait_hint: Duration::from_secs(graceful_period), + process_id: None + }; + + let mut thread_handle: Option> = None; + let mut stop = false; let event_handler = move |control_event| -> ServiceControlHandlerResult { + println!("Got service control event: {:?}", control_event); match control_event { ServiceControl::Stop => { // Handle stop event and return control back to the system. - let _ = tx.send(1); + stop = true; + let _ = &tx.send(1); ServiceControlHandlerResult::NoError } // All services must accept Interrogate even if it's a no-op. @@ -134,54 +162,47 @@ fn run_service(_arguments: Vec) -> Result<()> { _ => ServiceControlHandlerResult::NotImplemented, } }; - if let Ok(system_handler) = service_control_handler::register("Scaphandre", event_handler) { - let next_status = ServiceStatus { - // Should match the one from system service registry - service_type: ServiceType::OWN_PROCESS, - // The new state - current_state: ServiceState::Running, - // Accept stop events when running - controls_accepted: ServiceControlAccept::STOP, - // Used to report an error when starting or stopping only, otherwise must be zero - exit_code: ServiceExitCode::Win32(0), - // Only used for pending states, otherwise must be zero - checkpoint: 0, - // Only used for pending states, otherwise must be zero - wait_hint: Duration::default(), - // Unused for setting status - process_id: None, - }; - // next_status.current_state = ServiceState::StopPending; - // next_status.exit_code = ServiceExitCode::Win32(0); - // next_status.wait_hint = Duration::from_secs(1); - // Tell the system that the service is running now - if let Ok(_status_set) = system_handler.set_service_status(next_status) { - let handle = thread::spawn(move || { - parse_cli_and_run_exporter(&rx); - }); - } else { - panic!("Couldn't set Windows service status."); - } - let stop_status = ServiceStatus { - service_type: ServiceType::OWN_PROCESS, - current_state: ServiceState::Stopped, - controls_accepted: ServiceControlAccept::STOP, - exit_code: ServiceExitCode::Win32(0), - checkpoint: 0, - wait_hint: Duration::default(), - process_id: None - }; - - if let Ok(_status_set) = system_handler.set_service_status(stop_status) { - } else { - panic!("Couldn't set Windows service STOP status."); + if let Ok(system_handler) = service_control_handler::register("scaphandre", event_handler) { + // Tell the system that the service is running now and run it + match system_handler.set_service_status(start_status.clone()) { + Ok(status_set) => { + println!("Starting main thread, service status has been set: {:?}", status_set); + thread_handle = Some(thread::spawn(move || { parse_cli_and_run_exporter(&rx); })); + }, + Err(e) => { + panic!("Couldn't set Windows service status. Error: {:?}", e); + } + } + loop { + if stop { + // Wait for the thread to finnish, then end the current function + match system_handler.set_service_status(stoppending_status.clone()) { + Ok(status_set) => { + println!("Stop status has been set for service: {:?}", status_set); + if let Some(thr) = thread_handle { + if let Ok(_) = thr.join() { + match system_handler.set_service_status(stop_status.clone()) { + Ok(laststatus_set) => {println!("Scaphandre gracefully stopped: {:?}", laststatus_set);}, + Err(e) => {panic!("Could'nt set Stop status on scaphandre service: {:?}", e);} + } + } else { + panic!("Joining the thread failed."); + } + break; + } else { + panic!("Thread handle was not initialized."); + } + }, + Err(e) => { + panic!("Couldn't set Windows service status. Error: {:?}", e); + } + } + } } - } else { - panic!("Couldn't get Windows system events handler."); + panic!("Failed getting system_handle."); } - Ok(()) } fn main() { From 55b15a4235997acb6c97c241e235b61c0052db15 Mon Sep 17 00:00:00 2001 From: bpetit Date: Thu, 31 Aug 2023 12:02:06 +0200 Subject: [PATCH 238/303] test: testing package with new driver version --- .github/workflows/exe-release-prometheuspush.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/exe-release-prometheuspush.yml b/.github/workflows/exe-release-prometheuspush.yml index 650bfacb..17750501 100644 --- a/.github/workflows/exe-release-prometheuspush.yml +++ b/.github/workflows/exe-release-prometheuspush.yml @@ -12,7 +12,7 @@ on: branches: [ '336-proper-handling-of-windows-service-management' ] env: - WRD_VERSION: v0.0.2 + WRD_VERSION: experimental-multi-socket-1 WRD_BASE_URL: https://github.com/hubblo-org/windows-rapl-driver/releases/download jobs: From 84d4c36874e3e634b42c227c3ac09c41830a7d0e Mon Sep 17 00:00:00 2001 From: bpetit Date: Thu, 31 Aug 2023 12:04:36 +0200 Subject: [PATCH 239/303] test: testing package with new driver version --- .github/workflows/exe-release-prometheuspush.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/exe-release-prometheuspush.yml b/.github/workflows/exe-release-prometheuspush.yml index 17750501..655ede23 100644 --- a/.github/workflows/exe-release-prometheuspush.yml +++ b/.github/workflows/exe-release-prometheuspush.yml @@ -12,7 +12,7 @@ on: branches: [ '336-proper-handling-of-windows-service-management' ] env: - WRD_VERSION: experimental-multi-socket-1 + WRD_VERSION: "experimental-multi-socket-1" WRD_BASE_URL: https://github.com/hubblo-org/windows-rapl-driver/releases/download jobs: From a3ca569639c23d84cb44c3a595b6c469b2915043 Mon Sep 17 00:00:00 2001 From: bpetit Date: Thu, 31 Aug 2023 12:14:08 +0200 Subject: [PATCH 240/303] test: testing package with new driver version --- .github/workflows/exe-release-prometheuspush.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/exe-release-prometheuspush.yml b/.github/workflows/exe-release-prometheuspush.yml index 655ede23..07772b47 100644 --- a/.github/workflows/exe-release-prometheuspush.yml +++ b/.github/workflows/exe-release-prometheuspush.yml @@ -12,7 +12,7 @@ on: branches: [ '336-proper-handling-of-windows-service-management' ] env: - WRD_VERSION: "experimental-multi-socket-1" + WRD_VERSION: experimental\-multi\-socket\-1 WRD_BASE_URL: https://github.com/hubblo-org/windows-rapl-driver/releases/download jobs: From e6acf6c65d16caee2c1d62a8bec5f319abeb5025 Mon Sep 17 00:00:00 2001 From: bpetit Date: Thu, 31 Aug 2023 12:19:26 +0200 Subject: [PATCH 241/303] test: testing package with new driver version --- .github/workflows/exe-release-prometheuspush.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/exe-release-prometheuspush.yml b/.github/workflows/exe-release-prometheuspush.yml index 07772b47..27bc3be0 100644 --- a/.github/workflows/exe-release-prometheuspush.yml +++ b/.github/workflows/exe-release-prometheuspush.yml @@ -32,8 +32,10 @@ jobs: ls "C:\Program Files (x86)\Inno Setup 6\" - name: Get windows-rapl-driver run: | + $dest = "DriverLoader.exe" $url = "${{ env.WRD_BASE_URL }}/${{ env.WRD_VERSION }}/DriverLoader.exe" + echo ($url -replace '"', "") Invoke-WebRequest -Uri ($url -replace '"', "") -OutFile $dest $dest = "ScaphandreDrv.cat" $url = "${{ env.WRD_BASE_URL }}/${{ env.WRD_VERSION }}/ScaphandreDrv.cat" From 9d2651f9d4a2c72805c847bba6fc87a0ec348c45 Mon Sep 17 00:00:00 2001 From: bpetit Date: Thu, 31 Aug 2023 12:21:05 +0200 Subject: [PATCH 242/303] test: testing package with new driver version --- .github/workflows/exe-release-prometheuspush.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/exe-release-prometheuspush.yml b/.github/workflows/exe-release-prometheuspush.yml index 27bc3be0..6afea088 100644 --- a/.github/workflows/exe-release-prometheuspush.yml +++ b/.github/workflows/exe-release-prometheuspush.yml @@ -12,7 +12,7 @@ on: branches: [ '336-proper-handling-of-windows-service-management' ] env: - WRD_VERSION: experimental\-multi\-socket\-1 + WRD_VERSION: experimental-multi-socket-1 WRD_BASE_URL: https://github.com/hubblo-org/windows-rapl-driver/releases/download jobs: From b2fd1b034a985a72360744bc64e96e5146fad925 Mon Sep 17 00:00:00 2001 From: bpetit Date: Thu, 31 Aug 2023 12:27:12 +0200 Subject: [PATCH 243/303] test: testing package with new driver version --- .github/workflows/exe-release-prometheuspush.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/exe-release-prometheuspush.yml b/.github/workflows/exe-release-prometheuspush.yml index 6afea088..a4ead442 100644 --- a/.github/workflows/exe-release-prometheuspush.yml +++ b/.github/workflows/exe-release-prometheuspush.yml @@ -31,8 +31,8 @@ jobs: & "D:\a\scaphandre\scaphandre\$dest" /verysilent /suppressmsgbox ls "C:\Program Files (x86)\Inno Setup 6\" - name: Get windows-rapl-driver + shell: pwsh run: | - $dest = "DriverLoader.exe" $url = "${{ env.WRD_BASE_URL }}/${{ env.WRD_VERSION }}/DriverLoader.exe" echo ($url -replace '"', "") From 53dcb339e2009cee97be524c1e20dc56e5b71ff8 Mon Sep 17 00:00:00 2001 From: bpetit Date: Thu, 31 Aug 2023 12:47:17 +0200 Subject: [PATCH 244/303] test: testing package with new driver version --- .github/workflows/exe-release-prometheuspush.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/exe-release-prometheuspush.yml b/.github/workflows/exe-release-prometheuspush.yml index a4ead442..aded447c 100644 --- a/.github/workflows/exe-release-prometheuspush.yml +++ b/.github/workflows/exe-release-prometheuspush.yml @@ -12,7 +12,7 @@ on: branches: [ '336-proper-handling-of-windows-service-management' ] env: - WRD_VERSION: experimental-multi-socket-1 + WRD_VERSION: v0.0.3 WRD_BASE_URL: https://github.com/hubblo-org/windows-rapl-driver/releases/download jobs: From b19e43efdb47dd9771fce411cee96f79967fe8ec Mon Sep 17 00:00:00 2001 From: bpetit Date: Thu, 31 Aug 2023 18:06:17 +0200 Subject: [PATCH 245/303] build: fixed is script for local dev --- packaging/windows/dev_installer.iss | 14 ++++---- packaging/windows/register_log_source.ps1 | 40 +++++++++++++++++++++++ 2 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 packaging/windows/register_log_source.ps1 diff --git a/packaging/windows/dev_installer.iss b/packaging/windows/dev_installer.iss index fb06f118..710174d8 100644 --- a/packaging/windows/dev_installer.iss +++ b/packaging/windows/dev_installer.iss @@ -40,13 +40,13 @@ Name: "english"; MessagesFile: "compiler:Default.isl" [Files] Source: "{#MyAppSourceFolder}\target\release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion Source: "{#RaplDriverSourceFolder}\x64\Release\DriverLoader.exe"; DestDir: "{app}"; Flags: ignoreversion -Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.inf"; DestDir: "{app}"; Flags: ignoreversion -; Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.sys"; DestDir: "{#SystemFolder}"; -; Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.sys"; DestDir: "{#System64Folder}"; -Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.sys"; DestDir: "{app}"; -Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.cat"; DestDir: "{app}"; -; Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.cat"; DestDir: "{#SystemFolder}"; -; Source: "{#RaplDriverSourceFolder}\ScaphandreDrv\ScaphandreDrv.cat"; DestDir: "{#System64Folder}"; +Source: "{#RaplDriverSourceFolder}\x64\Release\ScaphandreDrv\ScaphandreDrv.inf"; DestDir: "{app}"; Flags: ignoreversion +; Source: "{#RaplDriverSourceFolder}\x64\Release\ScaphandreDrv\ScaphandreDrv.sys"; DestDir: "{#SystemFolder}"; +; Source: "{#RaplDriverSourceFolder}\x64\Release\ScaphandreDrv\ScaphandreDrv.sys"; DestDir: "{#System64Folder}"; +Source: "{#RaplDriverSourceFolder}\x64\Release\ScaphandreDrv\ScaphandreDrv.sys"; DestDir: "{app}"; +Source: "{#RaplDriverSourceFolder}\x64\Release\ScaphandreDrv\ScaphandreDrv.cat"; DestDir: "{app}"; +; Source: "{#RaplDriverSourceFolder}\x64\Release\ScaphandreDrv\ScaphandreDrv.cat"; DestDir: "{#SystemFolder}"; +; Source: "{#RaplDriverSourceFolder}\x64\Release\ScaphandreDrv\ScaphandreDrv.cat"; DestDir: "{#System64Folder}"; Source: "C:\Program Files (x86)\Windows Kits\10\Tools\10.0.22621.0\x64\devcon.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\certmgr.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "{#MyAppSourceFolder}\README.md"; DestDir: "{app}"; Flags: ignoreversion diff --git a/packaging/windows/register_log_source.ps1 b/packaging/windows/register_log_source.ps1 new file mode 100644 index 00000000..e6d7283b --- /dev/null +++ b/packaging/windows/register_log_source.ps1 @@ -0,0 +1,40 @@ +# https://github.com/dansmith +# +$source = "scaphandre" + + +$wid=[System.Security.Principal.WindowsIdentity]::GetCurrent() +$prp=new-object System.Security.Principal.WindowsPrincipal($wid) +$adm=[System.Security.Principal.WindowsBuiltInRole]::Administrator +$IsAdmin=$prp.IsInRole($adm) + +if($IsAdmin -eq $false) +{ + [System.Reflection.Assembly]::LoadWithPartialName(“System.Windows.Forms”) + [Windows.Forms.MessageBox]::Show(“Please run this as an Administrator”, + “Not Administrator”, + [Windows.Forms.MessageBoxButtons]::OK, + [Windows.Forms.MessageBoxIcon]::Information) + exit +} + + +if ([System.Diagnostics.EventLog]::SourceExists($source) -eq $false) +{ + [System.Diagnostics.EventLog]::CreateEventSource($source, "Application") + + [System.Reflection.Assembly]::LoadWithPartialName(“System.Windows.Forms”) + [Windows.Forms.MessageBox]::Show(“Event log created successfully”, + “Complete”, + [Windows.Forms.MessageBoxButtons]::OK, + [Windows.Forms.MessageBoxIcon]::Information) +} +else +{ + [System.Reflection.Assembly]::LoadWithPartialName(“System.Windows.Forms”) + [Windows.Forms.MessageBox]::Show(“Event log already exists”, + “Complete”, + [Windows.Forms.MessageBoxButtons]::OK, + [Windows.Forms.MessageBoxIcon]::Information) + +} \ No newline at end of file From e7b91a7a62e08baee81aaa8a78669264b623dab6 Mon Sep 17 00:00:00 2001 From: bpetit Date: Thu, 31 Aug 2023 18:06:50 +0200 Subject: [PATCH 246/303] feat: enabled manually setting number of cpu sockets for windows/msrrapl sensor --- src/exporters/mod.rs | 4 +- src/exporters/stdout.rs | 1 + src/lib.rs | 4 +- src/main.rs | 11 ++- src/sensors/mod.rs | 93 ++++++++++++-------- src/sensors/msr_rapl.rs | 185 ++++++++++++++++++++++++---------------- src/sensors/utils.rs | 7 +- 7 files changed, 185 insertions(+), 120 deletions(-) diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index 3ece8fcb..d458c475 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -900,7 +900,7 @@ impl MetricGenerator { /// Generate process metrics. fn gen_process_metrics(&mut self) { - debug!("In gen_process_metrics."); + trace!("In gen_process_metrics."); #[cfg(feature = "containers")] if self.watch_containers { let now = current_system_time_since_epoch().as_secs().to_string(); @@ -1043,7 +1043,7 @@ impl MetricGenerator { Utc::now().format("%Y-%m-%dT%H:%M:%S") ); self.gen_process_metrics(); - debug!("self_metrics: {:#?}", self.data); + trace!("self_metrics: {:#?}", self.data); } pub fn pop_metrics(&mut self) -> Vec { diff --git a/src/exporters/stdout.rs b/src/exporters/stdout.rs index 1132c99f..35f880c5 100644 --- a/src/exporters/stdout.rs +++ b/src/exporters/stdout.rs @@ -134,6 +134,7 @@ impl StdoutExporter { .iter() .filter(|x| x.name == "scaph_socket_power_microwatts") { + warn!("✅ Found socket power metric !"); let power = format!("{}", s.metric_value).parse::().unwrap() / 1000000.0; let mut power_str = String::from("----"); if power > 0.0 { diff --git a/src/lib.rs b/src/lib.rs index 60b65c46..352cbdc8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,7 +27,9 @@ pub fn get_default_sensor() -> impl sensors::Sensor { ); #[cfg(target_os = "windows")] - return msr_rapl::MsrRAPLSensor::new(); + return msr_rapl::MsrRAPLSensor::new( + 1 + ); } fn current_system_time_since_epoch() -> Duration { diff --git a/src/main.rs b/src/main.rs index 98539238..899f0463 100644 --- a/src/main.rs +++ b/src/main.rs @@ -74,6 +74,11 @@ struct Cli { #[cfg(target_os = "linux")] #[arg(long, default_value_t = powercap_rapl::DEFAULT_BUFFER_PER_SOCKET_MAX_KBYTES)] sensor_buffer_per_socket_max_kb: u16, + + /// Number of physical CPU packages/sockets enabled on the host + #[cfg(target_os = "windows")] + #[arg(long, default_value_t = 1)] + sensor_nb_cpu_sockets: u16, } /// Defines the possible subcommands, one per exporter. @@ -280,7 +285,11 @@ fn build_sensor(cli: &Cli) -> impl Sensor { }; #[cfg(target_os = "windows")] - let msr_sensor_win = msr_rapl::MsrRAPLSensor::new; + let msr_sensor_win = || { + msr_rapl::MsrRAPLSensor::new( + cli.sensor_nb_cpu_sockets + ) + }; match cli.sensor.as_deref() { Some("powercap_rapl") => { diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index c0055c2a..47691eea 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -278,30 +278,48 @@ impl Topology { /// to appropriate CPUSocket instance from self.sockets pub fn add_cpu_cores(&mut self) { if let Some(mut cores) = Topology::generate_cpu_cores() { - while let Some(c) = cores.pop() { - let socket_id = &c - .attributes - .get("physical id") - .unwrap() - .parse::() - .unwrap(); - let socket_match = self.sockets.iter_mut().find(|x| &x.id == socket_id); - - //In VMs there might be a missmatch betwen Sockets and Cores - see Issue#133 as a first fix we just map all cores that can't be mapped to the first - let socket = match socket_match { - Some(x) => x, - None =>self.sockets.first_mut().expect("Trick: if you are running on a vm, do not forget to use --vm parameter invoking scaphandre at the command line") - }; - - if socket_id == &socket.id { - socket.add_cpu_core(c); - } else { - socket.add_cpu_core(c); - warn!("coud't not match core to socket - mapping to first socket instead - if you are not using --vm there is something wrong") + #[cfg(target_os = "linux")] { + while let Some(c) = cores.pop() { + let socket_id = &c + .attributes + .get("physical id") + .unwrap() + .parse::() + .unwrap(); + let socket_match = self.sockets.iter_mut().find(|x| &x.id == socket_id); + + //In VMs there might be a missmatch betwen Sockets and Cores - see Issue#133 as a first fix we just map all cores that can't be mapped to the first + let socket = match socket_match { + Some(x) => x, + None =>self.sockets.first_mut().expect("Trick: if you are running on a vm, do not forget to use --vm parameter invoking scaphandre at the command line") + }; + + if socket_id == &socket.id { + socket.add_cpu_core(c); + } else { + socket.add_cpu_core(c); + warn!("coud't not match core to socket - mapping to first socket instead - if you are not using --vm there is something wrong") + } + } + } + #[cfg(target_os = "windows")] + { + let nb_cores_per_socket = &cores.len() / &self.sockets.len(); + for s in self.sockets.iter_mut() { + for c in 1..nb_cores_per_socket { + match cores.pop() { + Some(core) => { + s.add_cpu_core(core); + }, + None => { + error!("Uneven number of CPU cores !"); + } + } + } } } } else { - warn!("Couldn't retrieve any CPU Core from the topology. (generate_cpu_cores)"); + panic!("Couldn't retrieve any CPU Core from the topology. (generate_cpu_cores)"); } } @@ -1109,16 +1127,17 @@ impl CPUSocket { steal: Some(0), }; for c in &self.cpu_cores { - let c_stats = c.read_stats().unwrap(); - stats.user += c_stats.user; - stats.nice += c_stats.nice; - stats.system += c_stats.system; - stats.idle += c_stats.idle; - stats.iowait = - Some(stats.iowait.unwrap_or_default() + c_stats.iowait.unwrap_or_default()); - stats.irq = Some(stats.irq.unwrap_or_default() + c_stats.irq.unwrap_or_default()); - stats.softirq = - Some(stats.softirq.unwrap_or_default() + c_stats.softirq.unwrap_or_default()); + if let Some(c_stats) = c.read_stats() { + stats.user += c_stats.user; + stats.nice += c_stats.nice; + stats.system += c_stats.system; + stats.idle += c_stats.idle; + stats.iowait = + Some(stats.iowait.unwrap_or_default() + c_stats.iowait.unwrap_or_default()); + stats.irq = Some(stats.irq.unwrap_or_default() + c_stats.irq.unwrap_or_default()); + stats.softirq = + Some(stats.softirq.unwrap_or_default() + c_stats.softirq.unwrap_or_default()); + } } Some(stats) } @@ -1184,9 +1203,9 @@ impl CPUSocket { &last_record.value, &previous_record.value ); let last_rec_val = last_record.value.trim(); - debug!("socket : l1049 : trying to parse {} as u64", last_rec_val); + debug!("socket : l1187 : trying to parse {} as u64", last_rec_val); let prev_rec_val = previous_record.value.trim(); - debug!("socket : l1051 : trying to parse {} as u64", prev_rec_val); + debug!("socket : l1189 : trying to parse {} as u64", prev_rec_val); if let (Ok(last_microjoules), Ok(previous_microjoules)) = (last_rec_val.parse::(), prev_rec_val.parse::()) { @@ -1210,7 +1229,7 @@ impl CPUSocket { )); } } else { - debug!("Not enough records for socket"); + warn!("Not enough records for socket"); } None } @@ -1532,7 +1551,7 @@ mod tests { #[cfg(target_os = "linux")] let sensor = powercap_rapl::PowercapRAPLSensor::new(8, 8, false); #[cfg(not(target_os = "linux"))] - let sensor = msr_rapl::MsrRAPLSensor::new(); + let sensor = msr_rapl::MsrRAPLSensor::new(1); let topo = (*sensor.get_topology()).unwrap(); println!("{:?}", topo.read_stats()); } @@ -1542,7 +1561,7 @@ mod tests { #[cfg(target_os = "linux")] let sensor = powercap_rapl::PowercapRAPLSensor::new(8, 8, false); #[cfg(not(target_os = "linux"))] - let sensor = msr_rapl::MsrRAPLSensor::new(); + let sensor = msr_rapl::MsrRAPLSensor::new(1); let mut topo = (*sensor.get_topology()).unwrap(); for s in topo.get_sockets() { for c in s.get_cores() { @@ -1556,7 +1575,7 @@ mod tests { #[cfg(target_os = "linux")] let sensor = powercap_rapl::PowercapRAPLSensor::new(8, 8, false); #[cfg(not(target_os = "linux"))] - let sensor = msr_rapl::MsrRAPLSensor::new(); + let sensor = msr_rapl::MsrRAPLSensor::new(1); let mut topo = (*sensor.get_topology()).unwrap(); for s in topo.get_sockets() { println!("{:?}", s.read_stats()); diff --git a/src/sensors/msr_rapl.rs b/src/sensors/msr_rapl.rs index b06095f7..0c186f16 100644 --- a/src/sensors/msr_rapl.rs +++ b/src/sensors/msr_rapl.rs @@ -12,18 +12,27 @@ use windows::Win32::Storage::FileSystem::{ use windows::Win32::System::Ioctl::{FILE_DEVICE_UNKNOWN, METHOD_BUFFERED}; use windows::Win32::System::IO::DeviceIoControl; -const MSR_RAPL_POWER_UNIT: u16 = 0x606; // - //const MSR_PKG_POWER_LIMIT: u16 = 0x610; // PKG RAPL Power Limit Control (R/W) See Section 14.7.3, Package RAPL Domain. -const MSR_PKG_ENERGY_STATUS: u16 = 0x611; -//const MSR_PKG_POWER_INFO: u16 = 0x614; -//const MSR_DRAM_ENERGY_STATUS: u16 = 0x619; -//const MSR_PP0_ENERGY_STATUS: u16 = 0x639; //PP0 Energy Status (R/O) See Section 14.7.4, PP0/PP1 RAPL Domains. -//const MSR_PP0_PERF_STATUS: u16 = 0x63b; // PP0 Performance Throttling Status (R/O) See Section 14.7.4, PP0/PP1 RAPL Domains. -//const MSR_PP0_POLICY: u16 = 0x63a; //PP0 Balance Policy (R/W) See Section 14.7.4, PP0/PP1 RAPL Domains. -//const MSR_PP0_POWER_LIMIT: u16 = 0x638; // PP0 RAPL Power Limit Control (R/W) See Section 14.7.4, PP0/PP1 RAPL Domains. -//const MSR_PP1_ENERGY_STATUS: u16 = 0x641; // PP1 Energy Status (R/O) See Section 14.7.4, PP0/PP1 RAPL Domains. -//const MSR_PP1_POLICY: u16 = 0x642; // PP1 Balance Policy (R/W) See Section 14.7.4, PP0/PP1 RAPL Domains. -//const MSR_PP1_POWER_LIMIT: u16 = 0x640; // PP1 RAPL Power Limit Control (R/W) See Section 14.7.4, PP0/PP1 RAPL Domains. +// Intel RAPL MSRs +const MSR_RAPL_POWER_UNIT: u32 = 0x606; // +const MSR_PKG_POWER_LIMIT: u32 = 0x610; // PKG RAPL Power Limit Control (R/W) See Section 14.7.3, Package RAPL Domain. +const MSR_PKG_ENERGY_STATUS: u32 = 0x611; +const MSR_PKG_POWER_INFO: u32 = 0x614; +const MSR_DRAM_ENERGY_STATUS: u32 = 0x619; +const MSR_PP0_ENERGY_STATUS: u32 = 0x639; //PP0 Energy Status (R/O) See Section 14.7.4, PP0/PP1 RAPL Domains. +const MSR_PP0_PERF_STATUS: u32 = 0x63b; // PP0 Performance Throttling Status (R/O) See Section 14.7.4, PP0/PP1 RAPL Domains. +const MSR_PP0_POLICY: u32 = 0x63a; //PP0 Balance Policy (R/W) See Section 14.7.4, PP0/PP1 RAPL Domains. +const MSR_PP0_POWER_LIMIT: u32 = 0x638; // PP0 RAPL Power Limit Control (R/W) See Section 14.7.4, PP0/PP1 RAPL Domains. +const MSR_PP1_ENERGY_STATUS: u32 = 0x641; // PP1 Energy Status (R/O) See Section 14.7.4, PP0/PP1 RAPL Domains. +const MSR_PP1_POLICY: u32 = 0x642; // PP1 Balance Policy (R/W) See Section 14.7.4, PP0/PP1 RAPL Domains. +const MSR_PP1_POWER_LIMIT: u32 = 0x640; // PP1 RAPL Power Limit Control (R/W) See Section 14.7.4, PP0/PP1 RAPL Domains. +const MSR_PLATFORM_ENERGY_STATUS: u32 = 0x0000064d; +const MSR_PLATFORM_POWER_LIMIT: u32 = 0x0000065c ; + +// AMD RAPL MSRs +const MSR_AMD_RAPL_POWER_UNIT: u32 = 0xc0010299; +const MSR_AMD_CORE_ENERGY_STATUS: u32 = 0xc001029a; +const MSR_AMD_PKG_ENERGY_STATUS: u32 = 0xc001029b; + unsafe fn ctl_code(device_type: u32, request_code: u32, method: u32, access: u32) -> u32 { ((device_type) << 16) | ((access) << 14) | ((request_code) << 2) | (method) @@ -67,16 +76,17 @@ pub struct MsrRAPLSensor { power_unit: f64, energy_unit: f64, time_unit: f64, + nb_cpu_sockets: u16 } impl Default for MsrRAPLSensor { fn default() -> Self { - Self::new() + Self::new(1) } } impl MsrRAPLSensor { - pub fn new() -> MsrRAPLSensor { + pub fn new(nb_cpu_sockets: u16) -> MsrRAPLSensor { let driver_name = "\\\\.\\ScaphandreDriver"; let mut power_unit: f64 = 1.0; @@ -114,6 +124,7 @@ impl MsrRAPLSensor { energy_unit, power_unit, time_unit, + nb_cpu_sockets } } @@ -159,18 +170,30 @@ impl MsrRAPLSensor { impl RecordReader for Topology { fn read_record(&self) -> Result> { - let randval: i32 = rand::random(); + let mut res: u64 = 0; + warn!("Topology: I have {} sockets", self.sockets.len()); + for s in &self.sockets { + match s.read_record() { + Ok(rec) => { + warn!("rec: {:?}", rec); + res = res + rec.value.parse::()?; + }, + Err(e) => { + error!("Failed to get socket record : {:?}", e); + } + } + } Ok(Record { timestamp: current_system_time_since_epoch(), unit: super::units::Unit::MicroJoule, - value: format!("{}", randval), + value: res.to_string(), }) } } unsafe fn send_request( device: HANDLE, - request_code: u16, + request_code: u32, request: *const u64, request_length: usize, reply: *mut u64, @@ -180,7 +203,7 @@ unsafe fn send_request( let len_ptr: *mut u32 = &mut len; if DeviceIoControl( - device, // envoi 8 octet et je recoi 8 octet + device, // send 8 bytes, receive 8 bytes crate::sensors::msr_rapl::ctl_code( FILE_DEVICE_UNKNOWN, request_code as _, @@ -213,61 +236,65 @@ impl RecordReader for CPUSocket { fn read_record(&self) -> Result> { unsafe { let driver_name = self.sensor_data.get("DRIVER_NAME").unwrap(); - if let Ok(device) = get_handle(driver_name) { - let mut msr_result: u64 = 0; - let ptr_result = &mut msr_result as *mut u64; - let mut src = MSR_RAPL_POWER_UNIT as u64; - let ptr = &src as *const u64; - - src = MSR_PKG_ENERGY_STATUS as u64; - trace!("src: {:x}", src); - trace!("src: {:b}", src); - - trace!("*ptr: {}", *ptr); - trace!("&request: {:?} ptr (as *const u8): {:?}", &src, ptr); - - if let Ok(res) = send_request( - device, - MSR_PKG_ENERGY_STATUS, - // nouvelle version à integrer : request_code est ignoré et request doit contenir - // request_code sous forme d'un char * - ptr, - 8, - ptr_result, - size_of::(), - ) { - debug!("{}", res); - - close_handle(device); - - let energy_unit = self - .sensor_data - .get("ENERGY_UNIT") - .unwrap() - .parse::() - .unwrap(); - - Ok(Record { - timestamp: current_system_time_since_epoch(), - unit: super::units::Unit::MicroJoule, - value: MsrRAPLSensor::extract_rapl_current_power(msr_result, energy_unit), - }) - } else { - error!("Failed to get data from send_request."); - close_handle(device); - Ok(Record { - timestamp: current_system_time_since_epoch(), - unit: super::units::Unit::MicroJoule, - value: String::from("0"), - }) + match get_handle(driver_name) { + Ok(device) => { + let mut msr_result: u64 = 0; + let ptr_result = &mut msr_result as *mut u64; + // get core numbers tied to the socket + let src = MSR_PKG_ENERGY_STATUS as u64; + let ptr = &src as *const u64; + + trace!("src: {:x}", src); + trace!("src: {:b}", src); + + trace!("*ptr: {}", *ptr); + trace!("&request: {:?} ptr (as *const u8): {:?}", &src, ptr); + + match send_request( + device, + MSR_PKG_ENERGY_STATUS, + // nouvelle version à integrer : request_code est ignoré et request doit contenir + // request_code sous forme d'un char * + ptr, + 8, + ptr_result, + size_of::(), + ) { + Ok(res) => { + debug!("{}", res); + + close_handle(device); + + let energy_unit = self + .sensor_data + .get("ENERGY_UNIT") + .unwrap() + .parse::() + .unwrap(); + + let current_power = MsrRAPLSensor::extract_rapl_current_power(msr_result, energy_unit); + warn!("current_power: {}", current_power); + + Ok(Record { + timestamp: current_system_time_since_epoch(), + unit: super::units::Unit::MicroJoule, + value: current_power, + }) + }, + Err(e) => { + error!("Failed to get data from send_request: {:?}", e); + close_handle(device); + Ok(Record { + timestamp: current_system_time_since_epoch(), + unit: super::units::Unit::MicroJoule, + value: String::from("0"), + }) + } + } + }, + Err(e) => { + panic!("Couldn't get driver handle : {:?}", e); } - } else { - error!("Couldn't get handle."); - Ok(Record { - timestamp: current_system_time_since_epoch(), - unit: super::units::Unit::MicroJoule, - value: String::from("0"), - }) } } } @@ -293,9 +320,19 @@ impl Sensor for MsrRAPLSensor { let mut topology = Topology::new(sensor_data.clone()); let mut sys = System::new_all(); sys.refresh_all(); - let i = 0; + + warn!("Got {} sockets CPU", self.nb_cpu_sockets); + //TODO fix that to actually count the number of sockets - topology.safe_add_socket(i, vec![], vec![], String::from(""), 4, sensor_data.clone()); + let mut i = 0; + let logical_cpus = sys.cpus() ; + + while i < self.nb_cpu_sockets { + topology.safe_add_socket(i, vec![], vec![], String::from(""), 4, sensor_data.clone()); + i = i + 1; + } + + topology.add_cpu_cores(); Ok(topology) } diff --git a/src/sensors/utils.rs b/src/sensors/utils.rs index 1827d054..ceac1cf2 100644 --- a/src/sensors/utils.rs +++ b/src/sensors/utils.rs @@ -387,7 +387,7 @@ impl ProcessTracker { /// Returns all vectors of process records linked to a running, sleeping, waiting or zombie process. /// (Not terminated) pub fn get_alive_processes(&self) -> Vec<&Vec> { - debug!("In get alive processes."); + trace!("In get alive processes."); let mut res = vec![]; for p in self.procs.iter() { //#[cfg(target_os = "linux")] @@ -412,7 +412,7 @@ impl ProcessTracker { } } } - debug!("End of get alive processes."); + trace!("End of get alive processes."); res } @@ -632,7 +632,6 @@ impl ProcessTracker { if result.next().is_some() { panic!("Found two vectors of processes with the same id, maintainers should fix this."); } - debug!("End of get process name."); process.get(0).unwrap().process.comm.clone() } @@ -652,11 +651,9 @@ impl ProcessTracker { cmdline.push_str(&cmdline_vec.remove(0)); } } - debug!("End of get process cmdline."); return Some(cmdline); } } - debug!("End of get process cmdline."); None } From 33d4dd63742719070b1876f7c576d1b1c83f74f1 Mon Sep 17 00:00:00 2001 From: bpetit Date: Mon, 4 Sep 2023 15:52:15 +0200 Subject: [PATCH 247/303] feat: early verboe version with multi-socket fixed --- src/exporters/stdout.rs | 2 ++ src/sensors/msr_rapl.rs | 18 ++++++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/exporters/stdout.rs b/src/exporters/stdout.rs index 35f880c5..9044a46c 100644 --- a/src/exporters/stdout.rs +++ b/src/exporters/stdout.rs @@ -178,6 +178,8 @@ impl StdoutExporter { } } println!("{to_print}\n"); + } else { + println!("{to_print} Could'nt get per-domain metrics.\n"); } } diff --git a/src/sensors/msr_rapl.rs b/src/sensors/msr_rapl.rs index 0c186f16..fa92cfe3 100644 --- a/src/sensors/msr_rapl.rs +++ b/src/sensors/msr_rapl.rs @@ -240,14 +240,24 @@ impl RecordReader for CPUSocket { Ok(device) => { let mut msr_result: u64 = 0; let ptr_result = &mut msr_result as *mut u64; + let mut core_id: u32 = 0; // get core numbers tied to the socket - let src = MSR_PKG_ENERGY_STATUS as u64; + if let Some(core) = self.cpu_cores.first() { + core_id = core.id as u32; + } else { + panic!("Couldn't get a CPUCore in socket {}", self.id); + } + warn!("msr: {:x}", (MSR_PKG_ENERGY_STATUS as u64)); + warn!("msr: {:b}", (MSR_PKG_ENERGY_STATUS as u64)); + warn!("core_id: {:x} {:b}", (core_id as u64), (core_id as u64)); + warn!("core_id: {:b}", ((core_id as u64) << 54)); + let src = ((core_id as u64) << 32) | (MSR_PKG_ENERGY_STATUS as u64); let ptr = &src as *const u64; - trace!("src: {:x}", src); - trace!("src: {:b}", src); + warn!("src: {:x}", src); + warn!("src: {:b}", src); - trace!("*ptr: {}", *ptr); + warn!("*ptr: {}", *ptr); trace!("&request: {:?} ptr (as *const u8): {:?}", &src, ptr); match send_request( From 8a2149e17aa90fe57fe677e6694ecc24e6ab9c36 Mon Sep 17 00:00:00 2001 From: bpetit Date: Thu, 7 Sep 2023 18:50:58 +0200 Subject: [PATCH 248/303] feat: added rapl core+uncore+domains to topology --- Cargo.lock | 22 ++++ Cargo.toml | 2 + src/sensors/mod.rs | 6 +- src/sensors/msr_rapl.rs | 255 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 277 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3821d73d..a971b21e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -261,6 +261,17 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "core_affinity" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622892f5635ce1fc38c8f16dfc938553ed64af482edb5e150bf4caedbfcb2304" +dependencies = [ + "libc", + "num_cpus", + "winapi", +] + [[package]] name = "crc32fast" version = "1.3.2" @@ -1303,6 +1314,15 @@ dependencies = [ "rand_core", ] +[[package]] +name = "raw-cpuid" +version = "10.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +dependencies = [ + "bitflags", +] + [[package]] name = "rayon" version = "1.7.0" @@ -1465,6 +1485,7 @@ dependencies = [ "chrono", "clap", "colored", + "core_affinity", "docker-sync", "hostname", "hyper", @@ -1476,6 +1497,7 @@ dependencies = [ "procfs", "protobuf", "rand", + "raw-cpuid", "regex", "riemann_client", "serde", diff --git a/Cargo.toml b/Cargo.toml index cf0319e9..e7478467 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,8 @@ procfs = { version = "0.15.0" } [target.'cfg(target_os="windows")'.dependencies] windows = { version = "0.27.0", features = ["alloc","Win32_Storage_FileSystem","Win32_Foundation","Win32_Security","Win32_System_IO","Win32_System_Ioctl"]} windows-service = { version = "0.6.0" } +raw-cpuid = { version = "10.5.0" } +core_affinity = { version = "0.8.1"} [features] default = ["prometheus", "riemann", "warpten", "json", "containers", "prometheuspush"] diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 47691eea..a1f08211 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -305,10 +305,12 @@ impl Topology { #[cfg(target_os = "windows")] { let nb_cores_per_socket = &cores.len() / &self.sockets.len(); - for s in self.sockets.iter_mut() { - for c in 1..nb_cores_per_socket { + warn!("nb_cores_per_socket: {} cores_len: {} sockets_len: {}", nb_cores_per_socket, &cores.len(), &self.sockets.len()); + for s in self.sockets.iter_mut().rev() { + for c in 0..nb_cores_per_socket { match cores.pop() { Some(core) => { + warn!("adding core {} to socket {}", core.id, s.id); s.add_cpu_core(core); }, None => { diff --git a/src/sensors/msr_rapl.rs b/src/sensors/msr_rapl.rs index fa92cfe3..c70c864e 100644 --- a/src/sensors/msr_rapl.rs +++ b/src/sensors/msr_rapl.rs @@ -4,6 +4,7 @@ use std::collections::HashMap; use std::error::Error; use std::mem::size_of; use sysinfo::{System, SystemExt}; +use raw_cpuid::{CpuId, TopologyType}; use windows::Win32::Foundation::{CloseHandle, GetLastError, HANDLE, INVALID_HANDLE_VALUE}; use windows::Win32::Storage::FileSystem::{ CreateFileW, FILE_FLAG_OVERLAPPED, FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_READ_DATA, @@ -12,6 +13,8 @@ use windows::Win32::Storage::FileSystem::{ use windows::Win32::System::Ioctl::{FILE_DEVICE_UNKNOWN, METHOD_BUFFERED}; use windows::Win32::System::IO::DeviceIoControl; +use core_affinity; + // Intel RAPL MSRs const MSR_RAPL_POWER_UNIT: u32 = 0x606; // const MSR_PKG_POWER_LIMIT: u32 = 0x610; // PKG RAPL Power Limit Control (R/W) See Section 14.7.3, Package RAPL Domain. @@ -244,6 +247,20 @@ impl RecordReader for CPUSocket { // get core numbers tied to the socket if let Some(core) = self.cpu_cores.first() { core_id = core.id as u32; + match core_affinity::get_core_ids() { + Some(core_ids) => { + for c in core_ids { + if c.id == core.id as usize { + core_affinity::set_for_current(c); + warn!("Set core_affinity to {}", c.id); + break; + } + } + }, + None => { + warn!("Could'nt get core ids from core_affinity."); + } + } } else { panic!("Couldn't get a CPUCore in socket {}", self.id); } @@ -258,6 +275,7 @@ impl RecordReader for CPUSocket { warn!("src: {:b}", src); warn!("*ptr: {}", *ptr); + warn!("*ptr: {:b}", *ptr); trace!("&request: {:?} ptr (as *const u8): {:?}", &src, ptr); match send_request( @@ -311,14 +329,42 @@ impl RecordReader for CPUSocket { } impl RecordReader for Domain { fn read_record(&self) -> Result> { - Ok(Record { - timestamp: current_system_time_since_epoch(), - unit: super::units::Unit::MicroJoule, - value: String::from("10"), - }) + if let core_id = self.sensor_data.get("CORE_ID").unwrap().parse::().unwrap() { + if let msr_addr = self.sensor_data.get("MSR_ADDR").unwrap().parse::().unwrap() { + unsafe { + match get_msr_value(core_id, msr_addr, &self.sensor_data) { + Ok(rec) => { + return Ok(Record { + timestamp: current_system_time_since_epoch(), + unit: super::units::Unit::MicroJoule, + value: rec.value, + }) + }, + Err(e) => { + error!("Could'nt get MSR value for {}: {}", msr_addr, e); + Ok(Record { + timestamp: current_system_time_since_epoch(), + value: String::from("0"), + unit: super::units::Unit::MicroJoule + }) + } + } + } + } else { + panic!("Couldn't get msr_addr to target for domain {}", self.name); + } + } else { + panic!("Couldn't get core_id to target for domain {}", self.name); + } } } +//fn get_cpu_info() -> Option { +// let cpuid = CpuId::new(); +// +// +//} + impl Sensor for MsrRAPLSensor { fn generate_topology(&self) -> Result> { let mut sensor_data = HashMap::new(); @@ -331,18 +377,143 @@ impl Sensor for MsrRAPLSensor { let mut sys = System::new_all(); sys.refresh_all(); - warn!("Got {} sockets CPU", self.nb_cpu_sockets); //TODO fix that to actually count the number of sockets let mut i = 0; let logical_cpus = sys.cpus() ; + + warn!("Got {} sockets CPU from command line", self.nb_cpu_sockets); + + let mut nb_cpu_sockets = 0; + let mut logical_cpus_from_cpuid = 0; + let cpuid = CpuId::new(); + match cpuid.get_vendor_info() { + Some(info) => { + warn!("Got CPU {:?}", info); + }, + None => { + warn!("Couldn't get cpuinfo"); + } + } + for i in 0..5 { + match cpuid.get_extended_topology_info() { + Some(info) => { + warn!("Got CPU topo info {:?}", info); + for t in info { + if t.level_type() == TopologyType::Core { + logical_cpus_from_cpuid = t.processors() + } + } + }, + None => { + warn!("Couldn't get cpu topo info"); + } + } + } + warn!("Logical cpus from sysinfo: {} logical cpus from cpuid: {}", logical_cpus.len(), logical_cpus_from_cpuid); + match cpuid.get_advanced_power_mgmt_info() { + Some(info) => { + warn!("Got CPU power mgmt info {:?}", info); + }, + None => { + warn!("Couldn't get cpu power info"); + } + } + match cpuid.get_extended_feature_info() { + Some(info) => { + warn!("Got CPU feature info {:?}", info); + }, + None => { + warn!("Couldn't get cpu feature info"); + } + } + match cpuid.get_performance_monitoring_info() { + Some(info) => { + warn!("Got CPU perfmonitoring info {:?}", info); + }, + None => { + warn!("Couldn't get cpu perfmonitoring info"); + } + } + match cpuid.get_thermal_power_info() { + Some(info) => { + warn!("Got CPU thermal info {:?}", info); + }, + None => { + warn!("Couldn't get cpu thermal info"); + } + } + match cpuid.get_extended_state_info() { + Some(info) => { + warn!("Got CPU state info {:?}", info); + }, + None => { + warn!("Couldn't get cpu state info"); + } + } + match cpuid.get_processor_capacity_feature_info() { + Some(info) => { + warn!("Got CPU capacity info {:?}", info); + }, + None => { + warn!("Couldn't get cpu capacity info"); + } + } + if self.nb_cpu_sockets > 2 && logical_cpus.len() < 12 { + warn!("Scaphandre has been told to expect {} CPU sockets but there is less than 12 logical cores in total ({}).", self.nb_cpu_sockets, logical_cpus.len()); + warn!("This is unlikely, be careful to configure Scaphandre for the right number of active CPU sockets on your machine"); + } while i < self.nb_cpu_sockets { topology.safe_add_socket(i, vec![], vec![], String::from(""), 4, sensor_data.clone()); + + //topology.safe_add_domain_to_socket(i, , name, uj_counter, buffer_max_kbytes, sensor_data) i = i + 1; } topology.add_cpu_cores(); + + for s in topology.get_sockets() { + unsafe { + let core_id = s.get_cores_passive().first().unwrap().id; + match get_msr_value(core_id as usize, MSR_DRAM_ENERGY_STATUS as u64, &sensor_data) { + Ok(rec) => { + warn!("Added domain Dram !"); + let mut domain_sensor_data = sensor_data.clone(); + domain_sensor_data.insert(String::from("MSR_ADDR"), MSR_DRAM_ENERGY_STATUS.to_string()); + domain_sensor_data.insert(String::from("CORE_ID"), core_id.to_string()); + s.safe_add_domain(Domain::new(2, String::from("dram"), String::from(""), 5, domain_sensor_data)) + }, + Err(e) => { + error!("Could'nt add Dram domain."); + } + } + match get_msr_value(core_id as usize, MSR_PP0_ENERGY_STATUS as u64, &sensor_data) { + Ok(rec) => { + warn!("Added domain Core !"); + let mut domain_sensor_data = sensor_data.clone(); + domain_sensor_data.insert(String::from("MSR_ADDR"), MSR_PP0_ENERGY_STATUS.to_string()); + domain_sensor_data.insert(String::from("CORE_ID"), core_id.to_string()); + s.safe_add_domain(Domain::new(2, String::from("core"), String::from(""), 5, domain_sensor_data)) + }, + Err(e) => { + error!("Could'nt add Core domain."); + } + } + match get_msr_value(core_id as usize, MSR_PP1_ENERGY_STATUS as u64, &sensor_data) { + Ok(rec) => { + warn!("Added domain Uncore !"); + let mut domain_sensor_data = sensor_data.clone(); + domain_sensor_data.insert(String::from("MSR_ADDR"), MSR_PP1_ENERGY_STATUS.to_string()); + domain_sensor_data.insert(String::from("CORE_ID"), core_id.to_string()); + s.safe_add_domain(Domain::new(2, String::from("uncore"), String::from(""), 5, domain_sensor_data)) + }, + Err(e) => { + error!("Could'nt add Uncore domain."); + } + } + } + } Ok(topology) } @@ -355,3 +526,75 @@ impl Sensor for MsrRAPLSensor { Box::new(topology) } } + +unsafe fn get_msr_value(core_id: usize, msr_addr: u64, sensor_data: &HashMap) -> Result { + match get_handle(sensor_data.get("DRIVER_NAME").unwrap()) { + Ok(device) => { + let mut msr_result: u64 = 0; + let ptr_result = &mut msr_result as *mut u64; + let mut core_id: u32 = 0; + // get core numbers tied to the socket + match core_affinity::get_core_ids() { + Some(core_ids) => { + for c in core_ids { + if c.id == core_id as usize { + core_affinity::set_for_current(c); + warn!("Set core_affinity to {}", c.id); + break; + } + } + }, + None => { + warn!("Could'nt get core ids from core_affinity."); + } + } + //warn!("msr: {:x}", (MSR_PKG_ENERGY_STATUS as u64)); + //warn!("msr: {:b}", (MSR_PKG_ENERGY_STATUS as u64)); + //warn!("core_id: {:x} {:b}", (core_id as u64), (core_id as u64)); + //warn!("core_id: {:b}", ((core_id as u64) << 54)); + let src = ((core_id as u64) << 32) | msr_addr; + let ptr = &src as *const u64; + + //warn!("src: {:x}", src); + //warn!("src: {:b}", src); + //warn!("*ptr: {}", *ptr); + //warn!("*ptr: {:b}", *ptr); + + match send_request( + device, + MSR_PKG_ENERGY_STATUS, + ptr, + 8, + ptr_result, + size_of::(), + ) { + Ok(res) => { + close_handle(device); + + let energy_unit = sensor_data + .get("ENERGY_UNIT") + .unwrap() + .parse::() + .unwrap(); + let current_value = MsrRAPLSensor::extract_rapl_current_power(msr_result, energy_unit); + warn!("current_value: {}", current_value); + + Ok(Record { + timestamp: current_system_time_since_epoch(), + unit: super::units::Unit::MicroJoule, + value: current_value, + }) + }, + Err(e) => { + error!("Failed to get data from send_request: {:?}", e); + close_handle(device); + Err(format!("Failed to get data from send_request: {:?}", e)) + } + } + }, + Err(e) => { + error!("Couldn't get driver handle : {:?}", e); + Err(format!("Couldn't get driver handle : {:?}", e)) + } + } +} \ No newline at end of file From 197c84fb920ca8bd5177d6027b86c7a4ae037727 Mon Sep 17 00:00:00 2001 From: bpetit Date: Fri, 6 Oct 2023 17:02:20 +0200 Subject: [PATCH 249/303] chore: loop to get informations about cores and sockets --- src/sensors/msr_rapl.rs | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/sensors/msr_rapl.rs b/src/sensors/msr_rapl.rs index c70c864e..6d2872a4 100644 --- a/src/sensors/msr_rapl.rs +++ b/src/sensors/msr_rapl.rs @@ -395,18 +395,32 @@ impl Sensor for MsrRAPLSensor { warn!("Couldn't get cpuinfo"); } } - for i in 0..5 { - match cpuid.get_extended_topology_info() { - Some(info) => { - warn!("Got CPU topo info {:?}", info); - for t in info { - if t.level_type() == TopologyType::Core { - logical_cpus_from_cpuid = t.processors() + for i in 0..logical_cpus.len() { + match core_affinity::get_core_ids() { + Some(core_ids) => { + for c in core_ids { + if c.id == i as usize { + core_affinity::set_for_current(c); + warn!("Set core_affinity to {}", c.id); + match cpuid.get_extended_topology_info() { + Some(info) => { + warn!("Got CPU topo info {:?}", info); + for t in info { + if t.level_type() == TopologyType::Core { + logical_cpus_from_cpuid = t.processors() + } + } + }, + None => { + warn!("Couldn't get cpu topo info"); + } + } + break; } - } + } }, None => { - warn!("Couldn't get cpu topo info"); + warn!("Could'nt get core ids from core_affinity."); } } } From 9f8381ef5eaecdc99904709a3b0f6ef79b946693 Mon Sep 17 00:00:00 2001 From: bpetit Date: Tue, 14 Nov 2023 10:56:58 +0100 Subject: [PATCH 250/303] fix: getting core - socket mapping from apic_id --- Cargo.lock | 18 +++ Cargo.toml | 1 + src/lib.rs | 4 +- src/main.rs | 9 +- src/sensors/mod.rs | 62 +++++---- src/sensors/msr_rapl.rs | 270 +++++++++++++++++++++++----------------- 6 files changed, 218 insertions(+), 146 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a971b21e..94da456c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -118,6 +118,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + [[package]] name = "bitflags" version = "1.3.2" @@ -1508,6 +1514,7 @@ dependencies = [ "warp10", "windows 0.27.0", "windows-service", + "x86", ] [[package]] @@ -2314,6 +2321,17 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "x86" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2781db97787217ad2a2845c396a5efe286f87467a5810836db6d74926e94a385" +dependencies = [ + "bit_field", + "bitflags", + "raw-cpuid", +] + [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/Cargo.toml b/Cargo.toml index e7478467..cab7a7ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ windows = { version = "0.27.0", features = ["alloc","Win32_Storage_FileSystem"," windows-service = { version = "0.6.0" } raw-cpuid = { version = "10.5.0" } core_affinity = { version = "0.8.1"} +x86 = { version = "0.52.0" } [features] default = ["prometheus", "riemann", "warpten", "json", "containers", "prometheuspush"] diff --git a/src/lib.rs b/src/lib.rs index 352cbdc8..60b65c46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,9 +27,7 @@ pub fn get_default_sensor() -> impl sensors::Sensor { ); #[cfg(target_os = "windows")] - return msr_rapl::MsrRAPLSensor::new( - 1 - ); + return msr_rapl::MsrRAPLSensor::new(); } fn current_system_time_since_epoch() -> Duration { diff --git a/src/main.rs b/src/main.rs index 899f0463..187fd3fc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -74,11 +74,6 @@ struct Cli { #[cfg(target_os = "linux")] #[arg(long, default_value_t = powercap_rapl::DEFAULT_BUFFER_PER_SOCKET_MAX_KBYTES)] sensor_buffer_per_socket_max_kb: u16, - - /// Number of physical CPU packages/sockets enabled on the host - #[cfg(target_os = "windows")] - #[arg(long, default_value_t = 1)] - sensor_nb_cpu_sockets: u16, } /// Defines the possible subcommands, one per exporter. @@ -286,9 +281,7 @@ fn build_sensor(cli: &Cli) -> impl Sensor { #[cfg(target_os = "windows")] let msr_sensor_win = || { - msr_rapl::MsrRAPLSensor::new( - cli.sensor_nb_cpu_sockets - ) + msr_rapl::MsrRAPLSensor::new() }; match cli.sensor.as_deref() { diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index a1f08211..d64c33c6 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -177,6 +177,7 @@ impl Topology { let sysinfo_system = System::new_all(); let sysinfo_cores = sysinfo_system.cpus(); + warn!("Sysinfo sees {}", sysinfo_cores.len()); #[cfg(target_os = "linux")] let cpuinfo = CpuInfo::new().unwrap(); for (id, c) in (0_u16..).zip(sysinfo_cores.iter()) { @@ -206,7 +207,7 @@ impl Topology { counter_uj_path: String, buffer_max_kbytes: u16, sensor_data: HashMap, - ) { + ) -> Option { if !self.sockets.iter().any(|s| s.id == socket_id) { let socket = CPUSocket::new( socket_id, @@ -216,10 +217,23 @@ impl Topology { buffer_max_kbytes, sensor_data, ); + let res = socket.clone(); self.sockets.push(socket); + Some(res) + } else { + None } } + pub fn safe_insert_socket( + &mut self, + socket: CPUSocket + ) { + if !self.sockets.iter().any(|s| s.id == socket.id) { + self.sockets.push(socket); + } + } + /// Returns a immutable reference to self.proc_tracker pub fn get_proc_tracker(&self) -> &ProcessTracker { &self.proc_tracker @@ -302,24 +316,28 @@ impl Topology { } } } - #[cfg(target_os = "windows")] - { - let nb_cores_per_socket = &cores.len() / &self.sockets.len(); - warn!("nb_cores_per_socket: {} cores_len: {} sockets_len: {}", nb_cores_per_socket, &cores.len(), &self.sockets.len()); - for s in self.sockets.iter_mut().rev() { - for c in 0..nb_cores_per_socket { - match cores.pop() { - Some(core) => { - warn!("adding core {} to socket {}", core.id, s.id); - s.add_cpu_core(core); - }, - None => { - error!("Uneven number of CPU cores !"); - } - } - } - } - } + //#[cfg(target_os = "windows")] + //{ + //TODO: fix + //let nb_sockets = &self.sockets.len(); + //let mut socket_counter = 0; + //let nb_cores_per_socket = &cores.len() / nb_sockets; + //warn!("nb_cores_per_socket: {} cores_len: {} sockets_len: {}", nb_cores_per_socket, &cores.len(), &self.sockets.len()); + //for s in self.sockets.iter_mut() { + // for c in (socket_counter * nb_cores_per_socket)..((socket_counter+1) * nb_cores_per_socket) { + // match cores.pop() { + // Some(core) => { + // warn!("adding core {} to socket {}", core.id, s.id); + // s.add_cpu_core(core); + // }, + // None => { + // error!("Uneven number of CPU cores !"); + // } + // } + // } + // socket_counter = socket_counter + 1; + //} + //} } else { panic!("Couldn't retrieve any CPU Core from the topology. (generate_cpu_cores)"); } @@ -1553,7 +1571,7 @@ mod tests { #[cfg(target_os = "linux")] let sensor = powercap_rapl::PowercapRAPLSensor::new(8, 8, false); #[cfg(not(target_os = "linux"))] - let sensor = msr_rapl::MsrRAPLSensor::new(1); + let sensor = msr_rapl::MsrRAPLSensor::new(); let topo = (*sensor.get_topology()).unwrap(); println!("{:?}", topo.read_stats()); } @@ -1563,7 +1581,7 @@ mod tests { #[cfg(target_os = "linux")] let sensor = powercap_rapl::PowercapRAPLSensor::new(8, 8, false); #[cfg(not(target_os = "linux"))] - let sensor = msr_rapl::MsrRAPLSensor::new(1); + let sensor = msr_rapl::MsrRAPLSensor::new(); let mut topo = (*sensor.get_topology()).unwrap(); for s in topo.get_sockets() { for c in s.get_cores() { @@ -1577,7 +1595,7 @@ mod tests { #[cfg(target_os = "linux")] let sensor = powercap_rapl::PowercapRAPLSensor::new(8, 8, false); #[cfg(not(target_os = "linux"))] - let sensor = msr_rapl::MsrRAPLSensor::new(1); + let sensor = msr_rapl::MsrRAPLSensor::new(); let mut topo = (*sensor.get_topology()).unwrap(); for s in topo.get_sockets() { println!("{:?}", s.read_stats()); diff --git a/src/sensors/msr_rapl.rs b/src/sensors/msr_rapl.rs index 6d2872a4..3517adef 100644 --- a/src/sensors/msr_rapl.rs +++ b/src/sensors/msr_rapl.rs @@ -1,9 +1,9 @@ use crate::sensors::utils::current_system_time_since_epoch; -use crate::sensors::{CPUSocket, Domain, Record, RecordReader, Sensor, Topology}; +use crate::sensors::{CPUSocket, Domain, Record, RecordReader, Sensor, Topology, CPUCore}; use std::collections::HashMap; use std::error::Error; use std::mem::size_of; -use sysinfo::{System, SystemExt}; +use sysinfo::{System, SystemExt, CpuExt, Cpu}; use raw_cpuid::{CpuId, TopologyType}; use windows::Win32::Foundation::{CloseHandle, GetLastError, HANDLE, INVALID_HANDLE_VALUE}; use windows::Win32::Storage::FileSystem::{ @@ -12,22 +12,23 @@ use windows::Win32::Storage::FileSystem::{ }; use windows::Win32::System::Ioctl::{FILE_DEVICE_UNKNOWN, METHOD_BUFFERED}; use windows::Win32::System::IO::DeviceIoControl; +use windows::Win32::System::Threading::SetThreadGroupAffinity; -use core_affinity; +use core_affinity::{self, CoreId}; +use x86::cpuid; // Intel RAPL MSRs -const MSR_RAPL_POWER_UNIT: u32 = 0x606; // -const MSR_PKG_POWER_LIMIT: u32 = 0x610; // PKG RAPL Power Limit Control (R/W) See Section 14.7.3, Package RAPL Domain. -const MSR_PKG_ENERGY_STATUS: u32 = 0x611; -const MSR_PKG_POWER_INFO: u32 = 0x614; -const MSR_DRAM_ENERGY_STATUS: u32 = 0x619; -const MSR_PP0_ENERGY_STATUS: u32 = 0x639; //PP0 Energy Status (R/O) See Section 14.7.4, PP0/PP1 RAPL Domains. -const MSR_PP0_PERF_STATUS: u32 = 0x63b; // PP0 Performance Throttling Status (R/O) See Section 14.7.4, PP0/PP1 RAPL Domains. -const MSR_PP0_POLICY: u32 = 0x63a; //PP0 Balance Policy (R/W) See Section 14.7.4, PP0/PP1 RAPL Domains. -const MSR_PP0_POWER_LIMIT: u32 = 0x638; // PP0 RAPL Power Limit Control (R/W) See Section 14.7.4, PP0/PP1 RAPL Domains. -const MSR_PP1_ENERGY_STATUS: u32 = 0x641; // PP1 Energy Status (R/O) See Section 14.7.4, PP0/PP1 RAPL Domains. -const MSR_PP1_POLICY: u32 = 0x642; // PP1 Balance Policy (R/W) See Section 14.7.4, PP0/PP1 RAPL Domains. -const MSR_PP1_POWER_LIMIT: u32 = 0x640; // PP1 RAPL Power Limit Control (R/W) See Section 14.7.4, PP0/PP1 RAPL Domains. +use x86::msr::{ + MSR_RAPL_POWER_UNIT, + MSR_PKG_POWER_LIMIT, + MSR_PKG_POWER_INFO, + MSR_PKG_ENERGY_STATUS, + MSR_DRAM_ENERGY_STATUS, + MSR_DRAM_PERF_STATUS, + MSR_PP0_ENERGY_STATUS, + MSR_PP0_PERF_STATUS, + MSR_PP1_ENERGY_STATUS, +}; const MSR_PLATFORM_ENERGY_STATUS: u32 = 0x0000064d; const MSR_PLATFORM_POWER_LIMIT: u32 = 0x0000065c ; @@ -79,17 +80,16 @@ pub struct MsrRAPLSensor { power_unit: f64, energy_unit: f64, time_unit: f64, - nb_cpu_sockets: u16 } impl Default for MsrRAPLSensor { fn default() -> Self { - Self::new(1) + Self::new() } } impl MsrRAPLSensor { - pub fn new(nb_cpu_sockets: u16) -> MsrRAPLSensor { + pub fn new() -> MsrRAPLSensor { let driver_name = "\\\\.\\ScaphandreDriver"; let mut power_unit: f64 = 1.0; @@ -127,7 +127,6 @@ impl MsrRAPLSensor { energy_unit, power_unit, time_unit, - nb_cpu_sockets } } @@ -243,7 +242,7 @@ impl RecordReader for CPUSocket { Ok(device) => { let mut msr_result: u64 = 0; let ptr_result = &mut msr_result as *mut u64; - let mut core_id: u32 = 0; + let mut core_id: u32 = 2; // get core numbers tied to the socket if let Some(core) = self.cpu_cores.first() { core_id = core.id as u32; @@ -251,8 +250,11 @@ impl RecordReader for CPUSocket { Some(core_ids) => { for c in core_ids { if c.id == core.id as usize { - core_affinity::set_for_current(c); - warn!("Set core_affinity to {}", c.id); + if core_affinity::set_for_current(c) { + warn!("Set core_affinity to {}", c.id); + } else { + warn!("Failed to set core_affinity to {}", c.id); + } break; } } @@ -329,10 +331,12 @@ impl RecordReader for CPUSocket { } impl RecordReader for Domain { fn read_record(&self) -> Result> { - if let core_id = self.sensor_data.get("CORE_ID").unwrap().parse::().unwrap() { - if let msr_addr = self.sensor_data.get("MSR_ADDR").unwrap().parse::().unwrap() { + if let Some(core_id) = self.sensor_data.get("CORE_ID") { + let usize_coreid = core_id.parse::().unwrap(); + warn!("Reading Domain {} on Core {}", self.name, usize_coreid); + if let Some(msr_addr) = self.sensor_data.get("MSR_ADDR") { unsafe { - match get_msr_value(core_id, msr_addr, &self.sensor_data) { + match get_msr_value(usize_coreid, msr_addr.parse::().unwrap(), &self.sensor_data) { Ok(rec) => { return Ok(Record { timestamp: current_system_time_since_epoch(), @@ -359,12 +363,6 @@ impl RecordReader for Domain { } } -//fn get_cpu_info() -> Option { -// let cpuid = CpuId::new(); -// -// -//} - impl Sensor for MsrRAPLSensor { fn generate_topology(&self) -> Result> { let mut sensor_data = HashMap::new(); @@ -376,38 +374,70 @@ impl Sensor for MsrRAPLSensor { let mut topology = Topology::new(sensor_data.clone()); let mut sys = System::new_all(); sys.refresh_all(); - //TODO fix that to actually count the number of sockets - let mut i = 0; + let mut i: u16 = 0; let logical_cpus = sys.cpus() ; - - warn!("Got {} sockets CPU from command line", self.nb_cpu_sockets); - - let mut nb_cpu_sockets = 0; - let mut logical_cpus_from_cpuid = 0; + let mut nb_cpu_sockets: u16 = 0; let cpuid = CpuId::new(); - match cpuid.get_vendor_info() { + let mut logical_cpus_from_cpuid = 1; + match cpuid.get_extended_topology_info() { Some(info) => { - warn!("Got CPU {:?}", info); + for t in info { + if t.level_type() == TopologyType::Core { + logical_cpus_from_cpuid = t.processors(); + } + } }, None => { - warn!("Couldn't get cpuinfo"); + panic!("Could'nt get cpuid data."); } } - for i in 0..logical_cpus.len() { - match core_affinity::get_core_ids() { - Some(core_ids) => { - for c in core_ids { - if c.id == i as usize { - core_affinity::set_for_current(c); - warn!("Set core_affinity to {}", c.id); + if logical_cpus_from_cpuid <= 1 { + panic!("CpuID data is likely to be wrong."); + } + let mut no_more_sockets = false; + + match core_affinity::get_core_ids() { + Some(core_ids) => { + warn!("CPU SETUP - Cores from core_affinity, len={} : {:?}", core_ids.len(), core_ids); + warn!("CPU SETUP - Logical CPUs from sysinfo: {}", logical_cpus.len()); + while !no_more_sockets { + let start = i * logical_cpus_from_cpuid; + let stop = (i+1)*logical_cpus_from_cpuid; + warn!("Looping over {} .. {}", start, stop); + let mut current_socket = CPUSocket::new(i, vec![], vec![], String::from(""),1, sensor_data.clone()); + for c in start..stop {//core_ids { + if core_affinity::set_for_current(CoreId { id: c.into() }) { + match cpuid.get_vendor_info() { + Some(info) => { + warn!("Got CPU {:?}", info); + }, + None => { + warn!("Couldn't get cpuinfo"); + } + } + warn!("Set core_affinity to {}", c); match cpuid.get_extended_topology_info() { Some(info) => { warn!("Got CPU topo info {:?}", info); for t in info { if t.level_type() == TopologyType::Core { - logical_cpus_from_cpuid = t.processors() + //nb_cpu_sockets = logical_cpus.len() as u16 / t.processors(); + //logical_cpus_from_cpuid = t.processors() + let x2apic_id = t.x2apic_id(); + let socket_id = (x2apic_id & 240) >> 4; // upper bits of x2apic_id are socket_id, mask them, then bit shift to get socket_id + let core_id = x2apic_id & 15; // 4 last bits of x2apic_id are the core_id (per-socket) + warn!("Found socketid={} and coreid={}", socket_id, core_id); + let mut attributes = HashMap::::new(); + let ref_core = logical_cpus.first().unwrap(); + attributes.insert(String::from("frequency"), ref_core.frequency().to_string()); + attributes.insert(String::from("name"), ref_core.name().to_string()); + attributes.insert(String::from("vendor_id"), ref_core.vendor_id().to_string()); + attributes.insert(String::from("brand"), ref_core.brand().to_string()); + warn!("Adding core id {} to socket_id {}", ((i * (logical_cpus_from_cpuid - 1)) + core_id as u16), current_socket.id); + current_socket.add_cpu_core(CPUCore::new((i * (logical_cpus_from_cpuid - 1)) + core_id as u16, attributes)); + warn!("Reviewing sockets : {:?}", topology.get_sockets_passive()); } } }, @@ -415,81 +445,88 @@ impl Sensor for MsrRAPLSensor { warn!("Couldn't get cpu topo info"); } } + } else { + no_more_sockets = true; + warn!("There's likely to be no more socket to explore."); break; } } - }, - None => { - warn!("Could'nt get core ids from core_affinity."); + if !no_more_sockets { + warn!("inserting socket {:?}", current_socket); + topology.safe_insert_socket(current_socket); + i = i + 1; + } } - } - } - warn!("Logical cpus from sysinfo: {} logical cpus from cpuid: {}", logical_cpus.len(), logical_cpus_from_cpuid); - match cpuid.get_advanced_power_mgmt_info() { - Some(info) => { - warn!("Got CPU power mgmt info {:?}", info); - }, - None => { - warn!("Couldn't get cpu power info"); - } - } - match cpuid.get_extended_feature_info() { - Some(info) => { - warn!("Got CPU feature info {:?}", info); - }, - None => { - warn!("Couldn't get cpu feature info"); - } - } - match cpuid.get_performance_monitoring_info() { - Some(info) => { - warn!("Got CPU perfmonitoring info {:?}", info); - }, - None => { - warn!("Couldn't get cpu perfmonitoring info"); - } - } - match cpuid.get_thermal_power_info() { - Some(info) => { - warn!("Got CPU thermal info {:?}", info); - }, - None => { - warn!("Couldn't get cpu thermal info"); - } - } - match cpuid.get_extended_state_info() { - Some(info) => { - warn!("Got CPU state info {:?}", info); - }, - None => { - warn!("Couldn't get cpu state info"); - } - } - match cpuid.get_processor_capacity_feature_info() { - Some(info) => { - warn!("Got CPU capacity info {:?}", info); + nb_cpu_sockets = i; }, None => { - warn!("Couldn't get cpu capacity info"); + panic!("Could'nt get core ids from core_affinity."); } } - - if self.nb_cpu_sockets > 2 && logical_cpus.len() < 12 { - warn!("Scaphandre has been told to expect {} CPU sockets but there is less than 12 logical cores in total ({}).", self.nb_cpu_sockets, logical_cpus.len()); - warn!("This is unlikely, be careful to configure Scaphandre for the right number of active CPU sockets on your machine"); - } - while i < self.nb_cpu_sockets { - topology.safe_add_socket(i, vec![], vec![], String::from(""), 4, sensor_data.clone()); - - //topology.safe_add_domain_to_socket(i, , name, uj_counter, buffer_max_kbytes, sensor_data) - i = i + 1; - } - - topology.add_cpu_cores(); + //nb_cpu_sockets = logical_cpus.len() as u16 / logical_cpus_from_cpuid; + //let mut core_id_counter = logical_cpus.len(); + + //match cpuid.get_advanced_power_mgmt_info() { + // Some(info) => { + // warn!("Got CPU power mgmt info {:?}", info); + // }, + // None => { + // warn!("Couldn't get cpu power info"); + // } + //} + //match cpuid.get_extended_feature_info() { + // Some(info) => { + // warn!("Got CPU feature info {:?}", info); + // }, + // None => { + // warn!("Couldn't get cpu feature info"); + // } + //} + //match cpuid.get_performance_monitoring_info() { + // Some(info) => { + // warn!("Got CPU perfmonitoring info {:?}", info); + // }, + // None => { + // warn!("Couldn't get cpu perfmonitoring info"); + // } + //} + //match cpuid.get_thermal_power_info() { + // Some(info) => { + // warn!("Got CPU thermal info {:?}", info); + // }, + // None => { + // warn!("Couldn't get cpu thermal info"); + // } + //} + //match cpuid.get_extended_state_info() { + // Some(info) => { + // warn!("Got CPU state info {:?}", info); + // }, + // None => { + // warn!("Couldn't get cpu state info"); + // } + //} + //match cpuid.get_processor_capacity_feature_info() { + // Some(info) => { + // warn!("Got CPU capacity info {:?}", info); + // }, + // None => { + // warn!("Couldn't get cpu capacity info"); + // } + //} + //TODO: fix + //i=0; + //while i < nb_cpu_sockets { + // //topology.safe_add_domain_to_socket(i, , name, uj_counter, buffer_max_kbytes, sensor_data) + // i = i + 1; + //} + + //topology.add_cpu_cores(); for s in topology.get_sockets() { + warn!("Inspecting CPUSocket: {:?}", s); unsafe { - let core_id = s.get_cores_passive().first().unwrap().id; + let core_id = s.get_cores_passive().get(0).unwrap().id; match get_msr_value(core_id as usize, MSR_DRAM_ENERGY_STATUS as u64, &sensor_data) { Ok(rec) => { warn!("Added domain Dram !"); @@ -526,6 +563,13 @@ impl Sensor for MsrRAPLSensor { error!("Could'nt add Uncore domain."); } } + //match get_msr_value(core_id as usize, MSR_PLATFORM_ENERGY_STATUS as u64, &sensor_data) { + // Ok(rec) => { + // }, + // Err(e) => { + // error!("Could'nt find Platform/PSYS domain."); + // } + //} } } From 746fa16a503d2ea4a57d2a40dd5facd75aca3184 Mon Sep 17 00:00:00 2001 From: bpetit Date: Wed, 22 Nov 2023 10:21:58 +0100 Subject: [PATCH 251/303] style: cleaning code --- Cargo.toml | 2 +- src/sensors/mod.rs | 4 + src/sensors/msr_rapl.rs | 356 +++++++++++++++++++--------------------- 3 files changed, 177 insertions(+), 185 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cab7a7ec..2074d408 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ isahc = { version = "1.7.2", optional = true } procfs = { version = "0.15.0" } [target.'cfg(target_os="windows")'.dependencies] -windows = { version = "0.27.0", features = ["alloc","Win32_Storage_FileSystem","Win32_Foundation","Win32_Security","Win32_System_IO","Win32_System_Ioctl"]} +windows = { version = "0.27.0", features = ["alloc","Win32_Storage_FileSystem","Win32_Foundation","Win32_Security","Win32_System_IO","Win32_System_Ioctl","Win32_System_Threading", "Win32_System_SystemInformation"]} windows-service = { version = "0.6.0" } raw-cpuid = { version = "10.5.0" } core_affinity = { version = "0.8.1"} diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index d64c33c6..3fa8f302 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -1048,6 +1048,10 @@ impl CPUSocket { } } + pub fn set_id(&mut self, id: u16) { + self.id = id + } + /// Adds a new Domain instance to the domains vector if and only if it doesn't exist in the vector already. fn safe_add_domain(&mut self, domain: Domain) { if !self.domains.iter().any(|d| d.id == domain.id) { diff --git a/src/sensors/msr_rapl.rs b/src/sensors/msr_rapl.rs index 3517adef..aef55c66 100644 --- a/src/sensors/msr_rapl.rs +++ b/src/sensors/msr_rapl.rs @@ -12,7 +12,11 @@ use windows::Win32::Storage::FileSystem::{ }; use windows::Win32::System::Ioctl::{FILE_DEVICE_UNKNOWN, METHOD_BUFFERED}; use windows::Win32::System::IO::DeviceIoControl; -use windows::Win32::System::Threading::SetThreadGroupAffinity; +use windows::Win32::System::Threading::{ + GetThreadGroupAffinity, GetProcessGroupAffinity, GetCurrentProcess, GetProcessInformation, + GetCurrentThread, GetActiveProcessorGroupCount, SetThreadGroupAffinity +}; +use windows::Win32::System::SystemInformation::GROUP_AFFINITY; use core_affinity::{self, CoreId}; @@ -237,94 +241,35 @@ unsafe fn send_request( impl RecordReader for CPUSocket { fn read_record(&self) -> Result> { unsafe { - let driver_name = self.sensor_data.get("DRIVER_NAME").unwrap(); - match get_handle(driver_name) { - Ok(device) => { - let mut msr_result: u64 = 0; - let ptr_result = &mut msr_result as *mut u64; - let mut core_id: u32 = 2; - // get core numbers tied to the socket - if let Some(core) = self.cpu_cores.first() { - core_id = core.id as u32; - match core_affinity::get_core_ids() { - Some(core_ids) => { - for c in core_ids { - if c.id == core.id as usize { - if core_affinity::set_for_current(c) { - warn!("Set core_affinity to {}", c.id); - } else { - warn!("Failed to set core_affinity to {}", c.id); - } - break; - } - } - }, - None => { - warn!("Could'nt get core ids from core_affinity."); - } - } - } else { - panic!("Couldn't get a CPUCore in socket {}", self.id); - } - warn!("msr: {:x}", (MSR_PKG_ENERGY_STATUS as u64)); - warn!("msr: {:b}", (MSR_PKG_ENERGY_STATUS as u64)); - warn!("core_id: {:x} {:b}", (core_id as u64), (core_id as u64)); - warn!("core_id: {:b}", ((core_id as u64) << 54)); - let src = ((core_id as u64) << 32) | (MSR_PKG_ENERGY_STATUS as u64); - let ptr = &src as *const u64; - - warn!("src: {:x}", src); - warn!("src: {:b}", src); - - warn!("*ptr: {}", *ptr); - warn!("*ptr: {:b}", *ptr); - trace!("&request: {:?} ptr (as *const u8): {:?}", &src, ptr); - - match send_request( - device, - MSR_PKG_ENERGY_STATUS, - // nouvelle version à integrer : request_code est ignoré et request doit contenir - // request_code sous forme d'un char * - ptr, - 8, - ptr_result, - size_of::(), - ) { - Ok(res) => { - debug!("{}", res); - - close_handle(device); - - let energy_unit = self - .sensor_data - .get("ENERGY_UNIT") - .unwrap() - .parse::() - .unwrap(); - - let current_power = MsrRAPLSensor::extract_rapl_current_power(msr_result, energy_unit); - warn!("current_power: {}", current_power); - - Ok(Record { - timestamp: current_system_time_since_epoch(), - unit: super::units::Unit::MicroJoule, - value: current_power, - }) + let current_thread = GetCurrentThread(); + let processorgroup_id = self.sensor_data.get("PROCESSORGROUP_ID").unwrap().parse::().unwrap(); + let mut thread_group_affinity: GROUP_AFFINITY = GROUP_AFFINITY { Mask: 255, Group: processorgroup_id, Reserved: [0,0,0] }; + let thread_affinity = GetThreadGroupAffinity(current_thread, &mut thread_group_affinity); + if thread_affinity.as_bool() { + warn!("got thead_affinity : {:?}", thread_group_affinity); + let core_id = self.cpu_cores.last().unwrap().id; //(self.cpu_cores.last().unwrap().id + self.id * self.cpu_cores.len() as u16) as usize + let newaffinity = GROUP_AFFINITY { Mask: (self.cpu_cores.len() + self.id as usize * self.cpu_cores.len() - 1) as usize, Group: processorgroup_id, Reserved: [0, 0, 0]}; + let res = SetThreadGroupAffinity(current_thread, &newaffinity, &mut thread_group_affinity); + if res.as_bool() { + warn!("Asking get_msr_value, from socket, with core_id={}", core_id); + match get_msr_value(core_id as usize, MSR_PKG_ENERGY_STATUS as u64, &self.sensor_data) { + Ok(rec) => { + return Ok(Record { timestamp: current_system_time_since_epoch(), value: rec.value, unit: super::units::Unit::MicroJoule }) }, Err(e) => { - error!("Failed to get data from send_request: {:?}", e); - close_handle(device); - Ok(Record { + error!("Could'nt get MSR value for {}: {}", MSR_PKG_ENERGY_STATUS, e); + return Ok(Record { timestamp: current_system_time_since_epoch(), - unit: super::units::Unit::MicroJoule, value: String::from("0"), + unit: super::units::Unit::MicroJoule }) } } - }, - Err(e) => { - panic!("Couldn't get driver handle : {:?}", e); + } else { + panic!("Couldn't set Thread affinity !"); } + } else { + panic!("Coudld'nt get Thread affinity !"); } } } @@ -336,6 +281,7 @@ impl RecordReader for Domain { warn!("Reading Domain {} on Core {}", self.name, usize_coreid); if let Some(msr_addr) = self.sensor_data.get("MSR_ADDR") { unsafe { + warn!("Asking, from Domain, get_msr_value with core_id={}", usize_coreid); match get_msr_value(usize_coreid, msr_addr.parse::().unwrap(), &self.sensor_data) { Ok(rec) => { return Ok(Record { @@ -374,94 +320,132 @@ impl Sensor for MsrRAPLSensor { let mut topology = Topology::new(sensor_data.clone()); let mut sys = System::new_all(); sys.refresh_all(); - - //TODO fix that to actually count the number of sockets - let mut i: u16 = 0; - let logical_cpus = sys.cpus() ; - let mut nb_cpu_sockets: u16 = 0; - let cpuid = CpuId::new(); - let mut logical_cpus_from_cpuid = 1; - match cpuid.get_extended_topology_info() { - Some(info) => { - for t in info { - if t.level_type() == TopologyType::Core { - logical_cpus_from_cpuid = t.processors(); + + unsafe { + let current_thread = GetCurrentThread(); + + let group_count = GetActiveProcessorGroupCount(); + warn!("GROUP COUNT : {}", group_count); + + for group_id in 0..group_count { + //TODO fix that to actually count the number of sockets + let logical_cpus = sys.cpus() ; + let mut nb_cpu_sockets: u16 = 0; + let cpuid = CpuId::new(); + let mut logical_cpus_from_cpuid = 1; + match cpuid.get_extended_topology_info() { + Some(info) => { + for t in info { + if t.level_type() == TopologyType::Core { + logical_cpus_from_cpuid = t.processors(); + } + } + }, + None => { + panic!("Could'nt get cpuid data."); } } - }, - None => { - panic!("Could'nt get cpuid data."); - } - } - if logical_cpus_from_cpuid <= 1 { - panic!("CpuID data is likely to be wrong."); - } - let mut no_more_sockets = false; - - match core_affinity::get_core_ids() { - Some(core_ids) => { - warn!("CPU SETUP - Cores from core_affinity, len={} : {:?}", core_ids.len(), core_ids); - warn!("CPU SETUP - Logical CPUs from sysinfo: {}", logical_cpus.len()); - while !no_more_sockets { - let start = i * logical_cpus_from_cpuid; - let stop = (i+1)*logical_cpus_from_cpuid; - warn!("Looping over {} .. {}", start, stop); - let mut current_socket = CPUSocket::new(i, vec![], vec![], String::from(""),1, sensor_data.clone()); - for c in start..stop {//core_ids { - if core_affinity::set_for_current(CoreId { id: c.into() }) { - match cpuid.get_vendor_info() { - Some(info) => { - warn!("Got CPU {:?}", info); - }, - None => { - warn!("Couldn't get cpuinfo"); - } - } - warn!("Set core_affinity to {}", c); - match cpuid.get_extended_topology_info() { - Some(info) => { - warn!("Got CPU topo info {:?}", info); - for t in info { - if t.level_type() == TopologyType::Core { - //nb_cpu_sockets = logical_cpus.len() as u16 / t.processors(); - //logical_cpus_from_cpuid = t.processors() - let x2apic_id = t.x2apic_id(); - let socket_id = (x2apic_id & 240) >> 4; // upper bits of x2apic_id are socket_id, mask them, then bit shift to get socket_id - let core_id = x2apic_id & 15; // 4 last bits of x2apic_id are the core_id (per-socket) - warn!("Found socketid={} and coreid={}", socket_id, core_id); - let mut attributes = HashMap::::new(); - let ref_core = logical_cpus.first().unwrap(); - attributes.insert(String::from("frequency"), ref_core.frequency().to_string()); - attributes.insert(String::from("name"), ref_core.name().to_string()); - attributes.insert(String::from("vendor_id"), ref_core.vendor_id().to_string()); - attributes.insert(String::from("brand"), ref_core.brand().to_string()); - warn!("Adding core id {} to socket_id {}", ((i * (logical_cpus_from_cpuid - 1)) + core_id as u16), current_socket.id); - current_socket.add_cpu_core(CPUCore::new((i * (logical_cpus_from_cpuid - 1)) + core_id as u16, attributes)); - warn!("Reviewing sockets : {:?}", topology.get_sockets_passive()); + if logical_cpus_from_cpuid <= 1 { + panic!("CpuID data is likely to be wrong."); + } + let mut i: u16 = 0; + let mut no_more_sockets = false; + warn!("Entering ProcessorGroup {}", group_id); + let newaffinity = GROUP_AFFINITY { Mask: 255, Group: group_id, Reserved: [0, 0, 0]}; + let mut thread_group_affinity: GROUP_AFFINITY = GROUP_AFFINITY { Mask: 255, Group: 0, Reserved: [0,0,0] }; + let thread_affinity = GetThreadGroupAffinity(current_thread, &mut thread_group_affinity); + warn!("Thread group affinity result : {:?}", thread_affinity); + if thread_affinity.as_bool() { + warn!("got thead_affinity : {:?}", thread_group_affinity); + let res = SetThreadGroupAffinity(current_thread, &newaffinity, &mut thread_group_affinity); + if res.as_bool() { + warn!("Have set thread affinity: {:?}", newaffinity); + match core_affinity::get_core_ids() { + Some(core_ids) => { + warn!("CPU SETUP - Cores from core_affinity, len={} : {:?}", core_ids.len(), core_ids); + warn!("CPU SETUP - Logical CPUs from sysinfo: {}", logical_cpus.len()); + while !no_more_sockets { + let start = i * logical_cpus_from_cpuid; + let stop = (i+1)*logical_cpus_from_cpuid; + warn!("Looping over {} .. {}", start, stop); + sensor_data.insert(String::from("PROCESSORGROUP_ID"), group_id.to_string()); + let mut current_socket = CPUSocket::new(i, vec![], vec![], String::from(""),1, sensor_data.clone()); + for c in start..stop {//core_ids { + if core_affinity::set_for_current(CoreId { id: c.into() }) { + match cpuid.get_vendor_info() { + Some(info) => { + warn!("Got CPU {:?}", info); + }, + None => { + warn!("Couldn't get cpuinfo"); + } + } + warn!("Set core_affinity to {}", c); + match cpuid.get_extended_topology_info() { + Some(info) => { + warn!("Got CPU topo info {:?}", info); + for t in info { + if t.level_type() == TopologyType::Core { + //nb_cpu_sockets = logical_cpus.len() as u16 / t.processors(); + //logical_cpus_from_cpuid = t.processors() + let x2apic_id = t.x2apic_id(); + let socket_id = (x2apic_id & 240) >> 4; // upper bits of x2apic_id are socket_id, mask them, then bit shift to get socket_id + current_socket.set_id(socket_id as u16); + let core_id = x2apic_id & 15; // 4 last bits of x2apic_id are the core_id (per-socket) + warn!("Found socketid={} and coreid={}", socket_id, core_id); + let mut attributes = HashMap::::new(); + let ref_core = logical_cpus.first().unwrap(); + attributes.insert(String::from("frequency"), ref_core.frequency().to_string()); + attributes.insert(String::from("name"), ref_core.name().to_string()); + attributes.insert(String::from("vendor_id"), ref_core.vendor_id().to_string()); + attributes.insert(String::from("brand"), ref_core.brand().to_string()); + warn!("Adding core id {} to socket_id {}", ((i * (logical_cpus_from_cpuid - 1)) + core_id as u16), current_socket.id); + current_socket.add_cpu_core(CPUCore::new((i * (logical_cpus_from_cpuid - 1)) + core_id as u16, attributes)); + warn!("Reviewing sockets : {:?}", topology.get_sockets_passive()); + } + } + }, + None => { + warn!("Couldn't get cpu topo info"); + } + } + } else { + no_more_sockets = true; + warn!("There's likely to be no more socket to explore."); + break; } + } + if !no_more_sockets { + warn!("inserting socket {:?}", current_socket); + topology.safe_insert_socket(current_socket); + i = i + 1; } - }, - None => { - warn!("Couldn't get cpu topo info"); + } + nb_cpu_sockets = i; + }, + None => { + panic!("Could'nt get core ids from core_affinity."); + } + } + if let Some(info) = CpuId::new().get_extended_topology_info() { + for c in info { + if c.level_type() == TopologyType::Core { + warn!("CPUID : {:?}", c); } } - } else { - no_more_sockets = true; - warn!("There's likely to be no more socket to explore."); - break; } - } - if !no_more_sockets { - warn!("inserting socket {:?}", current_socket); - topology.safe_insert_socket(current_socket); - i = i + 1; + } else { + error!("Could'nt set thread affinity !"); + let last_error = GetLastError(); + panic!("Error was : {:?}", last_error); } + } else { + warn!("Getting thread group affinity failed !"); + let last_error = GetLastError(); + panic!("Error was: {:?}", last_error); // win32 error 122 is insufficient buffer } - nb_cpu_sockets = i; - }, - None => { - panic!("Could'nt get core ids from core_affinity."); } + //let process_information = GetProcessInformation(current_process, , , ); } //nb_cpu_sockets = logical_cpus.len() as u16 / logical_cpus_from_cpuid; //let mut core_id_counter = logical_cpus.len(); @@ -526,13 +510,14 @@ impl Sensor for MsrRAPLSensor { for s in topology.get_sockets() { warn!("Inspecting CPUSocket: {:?}", s); unsafe { - let core_id = s.get_cores_passive().get(0).unwrap().id; + let core_id = s.get_cores_passive().last().unwrap().id + s.id * s.cpu_cores.len() as u16; + warn!("Asking get_msr_value, from generate_tpopo, with core_id={}", core_id); match get_msr_value(core_id as usize, MSR_DRAM_ENERGY_STATUS as u64, &sensor_data) { Ok(rec) => { warn!("Added domain Dram !"); let mut domain_sensor_data = sensor_data.clone(); domain_sensor_data.insert(String::from("MSR_ADDR"), MSR_DRAM_ENERGY_STATUS.to_string()); - domain_sensor_data.insert(String::from("CORE_ID"), core_id.to_string()); + domain_sensor_data.insert(String::from("CORE_ID"), core_id.to_string()); // nb of cores in a socket * socket_id + local_core_id s.safe_add_domain(Domain::new(2, String::from("dram"), String::from(""), 5, domain_sensor_data)) }, Err(e) => { @@ -586,35 +571,38 @@ impl Sensor for MsrRAPLSensor { } unsafe fn get_msr_value(core_id: usize, msr_addr: u64, sensor_data: &HashMap) -> Result { + let current_process = GetCurrentProcess(); + let current_thread = GetCurrentThread(); + let mut thread_group_affinity = GROUP_AFFINITY { Mask: 255, Group: 9, Reserved: [0,0,0] }; + let thread_affinity_res = GetThreadGroupAffinity(current_thread, &mut thread_group_affinity); + if thread_affinity_res.as_bool() { + warn!("Thread affinity found : {:?}", thread_group_affinity); + } else { + error!("Could'nt get thread group affinity"); + } + let mut process_group_array: [u16; 8] = [0,0,0,0,0,0,0,0]; + let mut process_group_array_len = 8; + let process_affinity_res = GetProcessGroupAffinity(current_process, &mut process_group_array_len, process_group_array.as_mut_ptr()); + if process_affinity_res.as_bool() { + warn!("Process affinity found: {:?}", process_group_array); + } else { + error!("Could'nt get process group affinity"); + error!("Error was : {:?}", GetLastError()); + } + warn!("Core ID requested to the driver : {}", core_id); match get_handle(sensor_data.get("DRIVER_NAME").unwrap()) { Ok(device) => { let mut msr_result: u64 = 0; let ptr_result = &mut msr_result as *mut u64; - let mut core_id: u32 = 0; - // get core numbers tied to the socket - match core_affinity::get_core_ids() { - Some(core_ids) => { - for c in core_ids { - if c.id == core_id as usize { - core_affinity::set_for_current(c); - warn!("Set core_affinity to {}", c.id); - break; - } - } - }, - None => { - warn!("Could'nt get core ids from core_affinity."); - } - } - //warn!("msr: {:x}", (MSR_PKG_ENERGY_STATUS as u64)); - //warn!("msr: {:b}", (MSR_PKG_ENERGY_STATUS as u64)); - //warn!("core_id: {:x} {:b}", (core_id as u64), (core_id as u64)); - //warn!("core_id: {:b}", ((core_id as u64) << 54)); - let src = ((core_id as u64) << 32) | msr_addr; + warn!("msr_addr: {:b}", msr_addr); + warn!("core_id: {:x} {:b}", (core_id as u64), (core_id as u64)); + warn!("core_id: {:b}", ((core_id as u64) << 32)); + let src = ((core_id as u64) << 32) | msr_addr; //let src = ((core_id as u64) << 32) | msr_addr; let ptr = &src as *const u64; - //warn!("src: {:x}", src); - //warn!("src: {:b}", src); + warn!("src: {:x}", src); + warn!("src: {:b}", src); + warn!("*ptr: {:b}", *ptr); //warn!("*ptr: {}", *ptr); //warn!("*ptr: {:b}", *ptr); From 02de9b4dee48daddca51a2a703d4d57ef1fe1080 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Fri, 24 Nov 2023 15:01:12 +0100 Subject: [PATCH 252/303] style: fmt and clippy --- src/sensors/mod.rs | 66 +++++++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index c0055c2a..0ff34ed1 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -64,27 +64,19 @@ impl RecordGenerator for Topology { /// and returns a clone of this record. /// fn refresh_record(&mut self) { - //let mut value: u64 = 0; - //let mut last_timestamp = current_system_time_since_epoch(); - //for s in self.get_sockets() { - // let records = s.get_records_passive(); - // if !records.is_empty() { - // let last = records.last(); - // let last_record = last.unwrap(); - // last_timestamp = last_record.timestamp; - // let res = last_record.value.trim(); - // if let Ok(val) = res.parse::() { - // value += val; - // } else { - // trace!("couldn't parse value : {}", res); - // } - // } - //} - //debug!("Record value from topo (addition of sockets) : {}", value); - //let record = Record::new(last_timestamp, value.to_string(), units::Unit::MicroJoule); - - if let Ok(record) = self.read_record() { - self.record_buffer.push(record); + match self.read_record() { + Ok(record) => { + self.record_buffer.push(record); + } + Err(e) => { + warn!( + "Could'nt read record from {}, error was : {:?}", + self._sensor_data + .get("source_file") + .unwrap_or(&String::from("SRCFILENOTKNOWN")), + e + ); + } } if !self.record_buffer.is_empty() { @@ -930,9 +922,19 @@ impl RecordGenerator for CPUSocket { /// Generates a new record of the socket energy consumption and stores it in the record_buffer. /// Returns a clone of this Record instance. fn refresh_record(&mut self) { - //if let Ok(record) = self.read_record_uj() { - if let Ok(record) = self.read_record() { - self.record_buffer.push(record); + match self.read_record() { + Ok(record) => { + self.record_buffer.push(record); + } + Err(e) => { + warn!( + "Could'nt read record from {}, error was: {:?}", + self.sensor_data + .get("source_file") + .unwrap_or(&String::from("SRCFILENOTKNOWN")), + e + ); + } } if !self.record_buffer.is_empty() { @@ -1288,9 +1290,19 @@ impl RecordGenerator for Domain { /// Computes a measurement of energy comsumption for this CPU domain, /// stores a copy in self.record_buffer and returns it. fn refresh_record(&mut self) { - //if let Ok(record) = self.read_record_uj() { - if let Ok(record) = self.read_record() { - self.record_buffer.push(record); + match self.read_record() { + Ok(record) => { + self.record_buffer.push(record); + } + Err(e) => { + warn!( + "Could'nt read record from {}. Error was : {:?}.", + self.sensor_data + .get("source_file") + .unwrap_or(&String::from("SRCFILENOTKNOWN")), + e + ); + } } if !self.record_buffer.is_empty() { From ced76a62060a7d8c3bccc2cc38582938dd7c38b5 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Fri, 24 Nov 2023 15:08:44 +0100 Subject: [PATCH 253/303] chore: upgrade rust version in dockerfile --- Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index c751b263..33502a9f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.68 as planner +FROM rust:1.74 as planner WORKDIR app RUN cargo install cargo-chef @@ -7,7 +7,7 @@ COPY . . # Analyze dependencies RUN cargo chef prepare --recipe-path recipe.json -FROM rust:1.68 as cacher +FROM rust:1.74 as cacher WORKDIR app RUN cargo install cargo-chef COPY --from=planner /app/recipe.json recipe.json @@ -15,7 +15,7 @@ COPY --from=planner /app/recipe.json recipe.json # Cache dependencies RUN cargo chef cook --release --recipe-path recipe.json -FROM rust:1.68 as builder +FROM rust:1.74 as builder WORKDIR app COPY . . From 4f8875bde1bd9fbac53f6092a4ccac931d41d4fe Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Fri, 24 Nov 2023 15:17:33 +0100 Subject: [PATCH 254/303] fix: adding missing requirements in linux test action --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index f5a31d14..a235d651 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -98,7 +98,7 @@ jobs: - name: Install python requirements (awxkit) run: | python -m pip install --upgrade pip - pip install awxkit + pip install awxkit setuptools - name: Log on AWX id: login run: | From 8b7fdf6590c952fd529a85000eeda41d3a139dc3 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Fri, 24 Nov 2023 15:18:23 +0100 Subject: [PATCH 255/303] style: clippy --- src/exporters/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index 32be23d9..036165d2 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -914,7 +914,6 @@ impl MetricGenerator { Ok(events) => { if !events.is_empty() { self.gen_docker_containers_basic_metadata(); - } else { } } Err(err) => debug!("couldn't get docker events - {:?} - {}", err, err), From de70b238ae07914db226c13199dd61861933e441 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Fri, 24 Nov 2023 15:35:43 +0100 Subject: [PATCH 256/303] fix: temporarily limit python version to 3.11 to avoid distutils unfixed mess --- .github/workflows/build-and-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index a235d651..c75ebcf7 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -94,7 +94,7 @@ jobs: - name: Install dependencies (awxkit) uses: actions/setup-python@v3 with: - python-version: '3.x' + python-version: '3.11' - name: Install python requirements (awxkit) run: | python -m pip install --upgrade pip @@ -126,7 +126,7 @@ jobs: - name: Install dependencies (awxkit) uses: actions/setup-python@v3 with: - python-version: '3.x' + python-version: '3.11' - name: Install python requirements (awxkit) run: | python -m pip install --upgrade pip From 6c769cf31ed34c2b252bbe7d7c7f3c772fb212cc Mon Sep 17 00:00:00 2001 From: bpetit Date: Fri, 24 Nov 2023 15:50:14 +0100 Subject: [PATCH 257/303] doc: update CITATION --- CITATION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CITATION b/CITATION index b61b20ed..f150cd67 100644 --- a/CITATION +++ b/CITATION @@ -1,7 +1,7 @@ @software{scaphandre, author = {Benoit Petit}, title = {scaphandre}, - year = 2021, - version = {v0.3}, + year = 2023, + version = {v1.0}, url = {https://github.com/hubblo-org/scaphandre} } From 33ea5232db33986024c30c637c6e17adda09a32c Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Fri, 24 Nov 2023 16:26:12 +0100 Subject: [PATCH 258/303] chore: upgrade docker image ubuntu version --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 33502a9f..38a3d497 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,11 +24,11 @@ COPY --from=cacher /app/target target COPY --from=cacher $CARGO_HOME $CARGO_HOME RUN cargo build --release -FROM ubuntu:20.04 as runtime +FROM ubuntu:22.04 as runtime WORKDIR app RUN apt-get update \ - && DEBIAN_FRONTEND="noninteractive" apt-get install -y ca-certificates tzdata \ + && DEBIAN_FRONTEND="noninteractive" apt-get install -y ca-certificates tzdata libssl3 \ && rm -rf /var/lib/apt/lists/* COPY --from=builder /app/target/release/scaphandre /usr/local/bin From 7f5e721fafc11d196409ba8b7b09aa75f47b89e1 Mon Sep 17 00:00:00 2001 From: bpetit Date: Wed, 6 Dec 2023 17:17:18 +0100 Subject: [PATCH 259/303] chore: improved domains support and lowered verbosity of debug messages --- src/exporters/stdout.rs | 2 +- src/sensors/mod.rs | 4 ++ src/sensors/msr_rapl.rs | 90 +++++++++++++++++++++-------------------- 3 files changed, 52 insertions(+), 44 deletions(-) diff --git a/src/exporters/stdout.rs b/src/exporters/stdout.rs index 9044a46c..6ce78e28 100644 --- a/src/exporters/stdout.rs +++ b/src/exporters/stdout.rs @@ -134,7 +134,7 @@ impl StdoutExporter { .iter() .filter(|x| x.name == "scaph_socket_power_microwatts") { - warn!("✅ Found socket power metric !"); + debug!("✅ Found socket power metric !"); let power = format!("{}", s.metric_value).parse::().unwrap() / 1000000.0; let mut power_str = String::from("----"); if power > 0.0 { diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 3fa8f302..0e2a0e3e 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -262,6 +262,10 @@ impl Topology { self.domains_names = Some(domain_names); } + pub fn set_domains_names(&mut self, names: Vec) { + self.domains_names = Some(names); + } + /// Adds a Domain instance to a given socket, if and only if the domain /// id doesn't exist already for the socket. pub fn safe_add_domain_to_socket( diff --git a/src/sensors/msr_rapl.rs b/src/sensors/msr_rapl.rs index aef55c66..131a7914 100644 --- a/src/sensors/msr_rapl.rs +++ b/src/sensors/msr_rapl.rs @@ -177,11 +177,11 @@ impl MsrRAPLSensor { impl RecordReader for Topology { fn read_record(&self) -> Result> { let mut res: u64 = 0; - warn!("Topology: I have {} sockets", self.sockets.len()); + debug!("Topology: I have {} sockets", self.sockets.len()); for s in &self.sockets { match s.read_record() { Ok(rec) => { - warn!("rec: {:?}", rec); + debug!("rec: {:?}", rec); res = res + rec.value.parse::()?; }, Err(e) => { @@ -246,12 +246,12 @@ impl RecordReader for CPUSocket { let mut thread_group_affinity: GROUP_AFFINITY = GROUP_AFFINITY { Mask: 255, Group: processorgroup_id, Reserved: [0,0,0] }; let thread_affinity = GetThreadGroupAffinity(current_thread, &mut thread_group_affinity); if thread_affinity.as_bool() { - warn!("got thead_affinity : {:?}", thread_group_affinity); + debug!("got thead_affinity : {:?}", thread_group_affinity); let core_id = self.cpu_cores.last().unwrap().id; //(self.cpu_cores.last().unwrap().id + self.id * self.cpu_cores.len() as u16) as usize let newaffinity = GROUP_AFFINITY { Mask: (self.cpu_cores.len() + self.id as usize * self.cpu_cores.len() - 1) as usize, Group: processorgroup_id, Reserved: [0, 0, 0]}; let res = SetThreadGroupAffinity(current_thread, &newaffinity, &mut thread_group_affinity); if res.as_bool() { - warn!("Asking get_msr_value, from socket, with core_id={}", core_id); + debug!("Asking get_msr_value, from socket, with core_id={}", core_id); match get_msr_value(core_id as usize, MSR_PKG_ENERGY_STATUS as u64, &self.sensor_data) { Ok(rec) => { return Ok(Record { timestamp: current_system_time_since_epoch(), value: rec.value, unit: super::units::Unit::MicroJoule }) @@ -278,10 +278,10 @@ impl RecordReader for Domain { fn read_record(&self) -> Result> { if let Some(core_id) = self.sensor_data.get("CORE_ID") { let usize_coreid = core_id.parse::().unwrap(); - warn!("Reading Domain {} on Core {}", self.name, usize_coreid); + debug!("Reading Domain {} on Core {}", self.name, usize_coreid); if let Some(msr_addr) = self.sensor_data.get("MSR_ADDR") { unsafe { - warn!("Asking, from Domain, get_msr_value with core_id={}", usize_coreid); + debug!("Asking, from Domain, get_msr_value with core_id={}", usize_coreid); match get_msr_value(usize_coreid, msr_addr.parse::().unwrap(), &self.sensor_data) { Ok(rec) => { return Ok(Record { @@ -325,7 +325,7 @@ impl Sensor for MsrRAPLSensor { let current_thread = GetCurrentThread(); let group_count = GetActiveProcessorGroupCount(); - warn!("GROUP COUNT : {}", group_count); + debug!("GROUP COUNT : {}", group_count); for group_id in 0..group_count { //TODO fix that to actually count the number of sockets @@ -350,40 +350,40 @@ impl Sensor for MsrRAPLSensor { } let mut i: u16 = 0; let mut no_more_sockets = false; - warn!("Entering ProcessorGroup {}", group_id); + debug!("Entering ProcessorGroup {}", group_id); let newaffinity = GROUP_AFFINITY { Mask: 255, Group: group_id, Reserved: [0, 0, 0]}; let mut thread_group_affinity: GROUP_AFFINITY = GROUP_AFFINITY { Mask: 255, Group: 0, Reserved: [0,0,0] }; let thread_affinity = GetThreadGroupAffinity(current_thread, &mut thread_group_affinity); - warn!("Thread group affinity result : {:?}", thread_affinity); + debug!("Thread group affinity result : {:?}", thread_affinity); if thread_affinity.as_bool() { - warn!("got thead_affinity : {:?}", thread_group_affinity); + debug!("got thead_affinity : {:?}", thread_group_affinity); let res = SetThreadGroupAffinity(current_thread, &newaffinity, &mut thread_group_affinity); if res.as_bool() { - warn!("Have set thread affinity: {:?}", newaffinity); + debug!("Have set thread affinity: {:?}", newaffinity); match core_affinity::get_core_ids() { Some(core_ids) => { - warn!("CPU SETUP - Cores from core_affinity, len={} : {:?}", core_ids.len(), core_ids); - warn!("CPU SETUP - Logical CPUs from sysinfo: {}", logical_cpus.len()); + debug!("CPU SETUP - Cores from core_affinity, len={} : {:?}", core_ids.len(), core_ids); + debug!("CPU SETUP - Logical CPUs from sysinfo: {}", logical_cpus.len()); while !no_more_sockets { let start = i * logical_cpus_from_cpuid; let stop = (i+1)*logical_cpus_from_cpuid; - warn!("Looping over {} .. {}", start, stop); + debug!("Looping over {} .. {}", start, stop); sensor_data.insert(String::from("PROCESSORGROUP_ID"), group_id.to_string()); let mut current_socket = CPUSocket::new(i, vec![], vec![], String::from(""),1, sensor_data.clone()); for c in start..stop {//core_ids { if core_affinity::set_for_current(CoreId { id: c.into() }) { match cpuid.get_vendor_info() { Some(info) => { - warn!("Got CPU {:?}", info); + debug!("Got CPU {:?}", info); }, None => { warn!("Couldn't get cpuinfo"); } } - warn!("Set core_affinity to {}", c); + debug!("Set core_affinity to {}", c); match cpuid.get_extended_topology_info() { Some(info) => { - warn!("Got CPU topo info {:?}", info); + debug!("Got CPU topo info {:?}", info); for t in info { if t.level_type() == TopologyType::Core { //nb_cpu_sockets = logical_cpus.len() as u16 / t.processors(); @@ -392,16 +392,16 @@ impl Sensor for MsrRAPLSensor { let socket_id = (x2apic_id & 240) >> 4; // upper bits of x2apic_id are socket_id, mask them, then bit shift to get socket_id current_socket.set_id(socket_id as u16); let core_id = x2apic_id & 15; // 4 last bits of x2apic_id are the core_id (per-socket) - warn!("Found socketid={} and coreid={}", socket_id, core_id); + debug!("Found socketid={} and coreid={}", socket_id, core_id); let mut attributes = HashMap::::new(); let ref_core = logical_cpus.first().unwrap(); attributes.insert(String::from("frequency"), ref_core.frequency().to_string()); attributes.insert(String::from("name"), ref_core.name().to_string()); attributes.insert(String::from("vendor_id"), ref_core.vendor_id().to_string()); attributes.insert(String::from("brand"), ref_core.brand().to_string()); - warn!("Adding core id {} to socket_id {}", ((i * (logical_cpus_from_cpuid - 1)) + core_id as u16), current_socket.id); + debug!("Adding core id {} to socket_id {}", ((i * (logical_cpus_from_cpuid - 1)) + core_id as u16), current_socket.id); current_socket.add_cpu_core(CPUCore::new((i * (logical_cpus_from_cpuid - 1)) + core_id as u16, attributes)); - warn!("Reviewing sockets : {:?}", topology.get_sockets_passive()); + debug!("Reviewing sockets : {:?}", topology.get_sockets_passive()); } } }, @@ -411,12 +411,12 @@ impl Sensor for MsrRAPLSensor { } } else { no_more_sockets = true; - warn!("There's likely to be no more socket to explore."); + debug!("There's likely to be no more socket to explore."); break; } } if !no_more_sockets { - warn!("inserting socket {:?}", current_socket); + debug!("inserting socket {:?}", current_socket); topology.safe_insert_socket(current_socket); i = i + 1; } @@ -430,7 +430,7 @@ impl Sensor for MsrRAPLSensor { if let Some(info) = CpuId::new().get_extended_topology_info() { for c in info { if c.level_type() == TopologyType::Core { - warn!("CPUID : {:?}", c); + debug!("CPUID : {:?}", c); } } } @@ -440,7 +440,7 @@ impl Sensor for MsrRAPLSensor { panic!("Error was : {:?}", last_error); } } else { - warn!("Getting thread group affinity failed !"); + panic!("Getting thread group affinity failed !"); let last_error = GetLastError(); panic!("Error was: {:?}", last_error); // win32 error 122 is insufficient buffer } @@ -506,46 +506,49 @@ impl Sensor for MsrRAPLSensor { //} //topology.add_cpu_cores(); - + let mut domains = vec![]; for s in topology.get_sockets() { - warn!("Inspecting CPUSocket: {:?}", s); + debug!("Inspecting CPUSocket: {:?}", s); unsafe { let core_id = s.get_cores_passive().last().unwrap().id + s.id * s.cpu_cores.len() as u16; - warn!("Asking get_msr_value, from generate_tpopo, with core_id={}", core_id); + debug!("Asking get_msr_value, from generate_tpopo, with core_id={}", core_id); match get_msr_value(core_id as usize, MSR_DRAM_ENERGY_STATUS as u64, &sensor_data) { Ok(rec) => { - warn!("Added domain Dram !"); + debug!("Added domain Dram !"); let mut domain_sensor_data = sensor_data.clone(); domain_sensor_data.insert(String::from("MSR_ADDR"), MSR_DRAM_ENERGY_STATUS.to_string()); domain_sensor_data.insert(String::from("CORE_ID"), core_id.to_string()); // nb of cores in a socket * socket_id + local_core_id + domains.push(String::from("dram")); s.safe_add_domain(Domain::new(2, String::from("dram"), String::from(""), 5, domain_sensor_data)) }, Err(e) => { - error!("Could'nt add Dram domain."); + warn!("Could'nt add Dram domain."); } } match get_msr_value(core_id as usize, MSR_PP0_ENERGY_STATUS as u64, &sensor_data) { Ok(rec) => { - warn!("Added domain Core !"); + debug!("Added domain Core !"); let mut domain_sensor_data = sensor_data.clone(); domain_sensor_data.insert(String::from("MSR_ADDR"), MSR_PP0_ENERGY_STATUS.to_string()); domain_sensor_data.insert(String::from("CORE_ID"), core_id.to_string()); + domains.push(String::from("core")); s.safe_add_domain(Domain::new(2, String::from("core"), String::from(""), 5, domain_sensor_data)) }, Err(e) => { - error!("Could'nt add Core domain."); + warn!("Could'nt add Core domain."); } } match get_msr_value(core_id as usize, MSR_PP1_ENERGY_STATUS as u64, &sensor_data) { Ok(rec) => { - warn!("Added domain Uncore !"); + debug!("Added domain Uncore !"); let mut domain_sensor_data = sensor_data.clone(); domain_sensor_data.insert(String::from("MSR_ADDR"), MSR_PP1_ENERGY_STATUS.to_string()); domain_sensor_data.insert(String::from("CORE_ID"), core_id.to_string()); + domains.push(String::from("uncore")); s.safe_add_domain(Domain::new(2, String::from("uncore"), String::from(""), 5, domain_sensor_data)) }, Err(e) => { - error!("Could'nt add Uncore domain."); + warn!("Could'nt add Uncore domain."); } } //match get_msr_value(core_id as usize, MSR_PLATFORM_ENERGY_STATUS as u64, &sensor_data) { @@ -558,6 +561,7 @@ impl Sensor for MsrRAPLSensor { } } + topology.set_domains_names(domains); Ok(topology) } @@ -576,7 +580,7 @@ unsafe fn get_msr_value(core_id: usize, msr_addr: u64, sensor_data: &HashMap { let mut msr_result: u64 = 0; let ptr_result = &mut msr_result as *mut u64; - warn!("msr_addr: {:b}", msr_addr); - warn!("core_id: {:x} {:b}", (core_id as u64), (core_id as u64)); - warn!("core_id: {:b}", ((core_id as u64) << 32)); + debug!("msr_addr: {:b}", msr_addr); + debug!("core_id: {:x} {:b}", (core_id as u64), (core_id as u64)); + debug!("core_id: {:b}", ((core_id as u64) << 32)); let src = ((core_id as u64) << 32) | msr_addr; //let src = ((core_id as u64) << 32) | msr_addr; let ptr = &src as *const u64; - warn!("src: {:x}", src); - warn!("src: {:b}", src); - warn!("*ptr: {:b}", *ptr); + debug!("src: {:x}", src); + debug!("src: {:b}", src); + debug!("*ptr: {:b}", *ptr); //warn!("*ptr: {}", *ptr); //warn!("*ptr: {:b}", *ptr); @@ -623,7 +627,7 @@ unsafe fn get_msr_value(core_id: usize, msr_addr: u64, sensor_data: &HashMap() .unwrap(); let current_value = MsrRAPLSensor::extract_rapl_current_power(msr_result, energy_unit); - warn!("current_value: {}", current_value); + debug!("current_value: {}", current_value); Ok(Record { timestamp: current_system_time_since_epoch(), From 0100e455f181d77a235adb422688ec97ced822f2 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 13 Dec 2023 16:18:30 +0100 Subject: [PATCH 260/303] docs: improving windows installation documentation --- docs_src/tutorials/installation-windows.md | 25 ++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/docs_src/tutorials/installation-windows.md b/docs_src/tutorials/installation-windows.md index f71a09e8..ae8a3618 100644 --- a/docs_src/tutorials/installation-windows.md +++ b/docs_src/tutorials/installation-windows.md @@ -1,18 +1,31 @@ # Install Scaphandre on Windows -**!! Warning: This is a first testing version of the package and installation procedure.** -**!! A new version is on its way with proper driver signature and Windows service proper management.** +**!! Warning: Windows version of Scaphandre is still in early stage. !!** ## Using the installer -In this first itration of the package, you'll need to enable Test Mode on Windows prior to proceed to this installation, then reboot. (Next version will have an officially signed version of the driver, so this won't be ncessaerry anymore.) +Download the [package](https://scaphandre.s3.fr-par.scw.cloud/x86_64/scaphandre_0.5.0_installer.exe) and install it **as an administrator**. + +### Configuring a Windows service to run Scaphandre in the background + +For example, to run the prometheus-push exporter in the background and target the Prometheus Push Gateway server with ip address `198.51.100.5` using HTTPS on port 443 and a step to send metrics of 45s, without checking the certificate of the push gateway (remove that option if you have a properly signed TLS certificate): + + sc.exe create Scaphandre binPath="C:\Program Files (x86)\scaphandre\scaphandre.exe prometheus-push -H 198.51.100.5 -s 45 -S https -p 443 --no-tls-check" DisplayName=Scaphandre start=auto + +Ensure the service is started in Services.msc, start it by right clicking on it, then Start, otherwise. + +To delete the service, you can do it in Services.msc, or: + + sc.exe delete Scaphandre + +### Using an installer including a development version of the driver + +If you are running a development version of the installer (which probably means a development version of the [driver](https://github.com/hubblo-org/windows-rapl-driver/)), you'll need to enable Test Mode on Windows prior to proceed to this installation, then reboot. bcdedit.exe -set TESTSIGNING ON bcdedit.exe -set nointegritychecks on -The installer will ensure that test mode is enabled and fail otherwise, but activation of test mode **and a reboot** is needed before anyway. - -Then download the [package](https://scaphandre.s3.fr-par.scw.cloud/x86_64/scaphandre_0.5.0_installer.exe) and install it **as an administrator**. +Beware: in this case, activation of test mode **and a reboot** is needed before anyway. Once installed, you should be able to run scaphandre from Powershell, by running : From dcb0680640112b995b4c63897c4dbf89a18a8b99 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 13 Dec 2023 16:29:49 +0100 Subject: [PATCH 261/303] chore: adding trace to troubleshoot host power metrics --- src/sensors/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 0ff34ed1..42ffcd63 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -864,6 +864,7 @@ impl Topology { if let Some(psys) = self._sensor_data.get("psys") { match &fs::read_to_string(format!("{psys}/energy_uj")) { Ok(val) => { + debug!("Read PSYS from {psys}/energy_uj: {}", val.to_string()); return Some(Record::new( current_system_time_since_epoch(), val.to_string(), From 22dd1557afaeff9da4493174c754895c2698cdc1 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 13 Dec 2023 16:44:44 +0100 Subject: [PATCH 262/303] chore: troubleshooting host metrics on redhat8 --- src/sensors/powercap_rapl.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sensors/powercap_rapl.rs b/src/sensors/powercap_rapl.rs index a294cdbb..ced3ef9a 100644 --- a/src/sensors/powercap_rapl.rs +++ b/src/sensors/powercap_rapl.rs @@ -79,6 +79,7 @@ impl RecordReader for Topology { Ok(psys_record) } else { let mut total = 0; + debug!("Suming socket metrics to get host metric"); for s in &self.sockets { if let Ok(r) = s.read_record() { if let Ok(val) = r.value.parse::() { From 453bf5c2c955ca1d8eccef4b0e78426751d4eead Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 13 Dec 2023 16:47:31 +0100 Subject: [PATCH 263/303] chore: troubleshooting host metrics on redhat8 --- src/sensors/powercap_rapl.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/sensors/powercap_rapl.rs b/src/sensors/powercap_rapl.rs index ced3ef9a..12f7dcfe 100644 --- a/src/sensors/powercap_rapl.rs +++ b/src/sensors/powercap_rapl.rs @@ -82,10 +82,13 @@ impl RecordReader for Topology { debug!("Suming socket metrics to get host metric"); for s in &self.sockets { if let Ok(r) = s.read_record() { - if let Ok(val) = r.value.parse::() { - total += val; - } else { - trace!("could'nt convert {} to i32", r.value); + match r.value.trim().parse::() { + Ok(val) => { + total += val; + }, + Err(e) => { + debug!("could'nt convert {} to i32: {}", r.value, e); + } } } //for d in &s.domains { From 96fc36840dfcfbf5be11163e10ac1da546c3532c Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 13 Dec 2023 16:58:29 +0100 Subject: [PATCH 264/303] chore: troubleshooting host metrics on redhat8 --- src/sensors/powercap_rapl.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sensors/powercap_rapl.rs b/src/sensors/powercap_rapl.rs index 12f7dcfe..cfb2bb58 100644 --- a/src/sensors/powercap_rapl.rs +++ b/src/sensors/powercap_rapl.rs @@ -78,16 +78,16 @@ impl RecordReader for Topology { if let Some(psys_record) = self.get_rapl_psys_energy_microjoules() { Ok(psys_record) } else { - let mut total = 0; + let mut total: i64 = 0; debug!("Suming socket metrics to get host metric"); for s in &self.sockets { if let Ok(r) = s.read_record() { - match r.value.trim().parse::() { + match r.value.trim().parse::() { Ok(val) => { total += val; }, Err(e) => { - debug!("could'nt convert {} to i32: {}", r.value, e); + debug!("could'nt convert {} to i64: {}", r.value, e); } } } From f85c4ebd4c095a7c9bc65d39fbf32507ede0315a Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 13 Dec 2023 17:10:32 +0100 Subject: [PATCH 265/303] chore: troubleshooting host metrics on redhat8 --- src/sensors/powercap_rapl.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/sensors/powercap_rapl.rs b/src/sensors/powercap_rapl.rs index cfb2bb58..72bf9014 100644 --- a/src/sensors/powercap_rapl.rs +++ b/src/sensors/powercap_rapl.rs @@ -78,24 +78,26 @@ impl RecordReader for Topology { if let Some(psys_record) = self.get_rapl_psys_energy_microjoules() { Ok(psys_record) } else { - let mut total: i64 = 0; + let mut total: i128 = 0; debug!("Suming socket metrics to get host metric"); for s in &self.sockets { if let Ok(r) = s.read_record() { - match r.value.trim().parse::() { + match r.value.trim().parse::() { Ok(val) => { total += val; }, Err(e) => { - debug!("could'nt convert {} to i64: {}", r.value, e); + debug!("could'nt convert {} to i128: {}", r.value, e); + } + } + } + for d in &s.domains { + if d.name == "dram" { + if let Ok(dr) = d.read_record() { + total = total + dr.value.parse::().unwrap() } } } - //for d in &s.domains { - // if let Ok(r) = d.read_record() { - // total = total + r.value.parse::().unwrap() - // } - //} } Ok(Record::new( current_system_time_since_epoch(), From 50bc04d9ddc6fa432f9413281457e5c7fe8b8d82 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 13 Dec 2023 17:19:39 +0100 Subject: [PATCH 266/303] fix: troubleshooting host metrics on redhat8 --- src/sensors/powercap_rapl.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/sensors/powercap_rapl.rs b/src/sensors/powercap_rapl.rs index 72bf9014..6a35c6e1 100644 --- a/src/sensors/powercap_rapl.rs +++ b/src/sensors/powercap_rapl.rs @@ -76,10 +76,11 @@ impl RecordReader for Topology { // else return pkg + dram + F(disks) if let Some(psys_record) = self.get_rapl_psys_energy_microjoules() { + debug!("Using PSYS metric"); Ok(psys_record) } else { let mut total: i128 = 0; - debug!("Suming socket metrics to get host metric"); + debug!("Suming socket PKG and DRAM metrics to get host metric"); for s in &self.sockets { if let Ok(r) = s.read_record() { match r.value.trim().parse::() { @@ -87,14 +88,21 @@ impl RecordReader for Topology { total += val; }, Err(e) => { - debug!("could'nt convert {} to i128: {}", r.value, e); + warn!("could'nt convert {} to i128: {}", r.value.trim(), e); } } } for d in &s.domains { if d.name == "dram" { if let Ok(dr) = d.read_record() { - total = total + dr.value.parse::().unwrap() + match dr.value.trim().parse::() { + Ok(val) => { + total += val; + }, + Err(e) =>{ + warn!("could'nt convert {} to i128: {}", dr.value.trim(), e); + } + } } } } From 1103155edd34a7dc9d47e123ce50d392ebd5f6cf Mon Sep 17 00:00:00 2001 From: bpetit Date: Thu, 4 Jan 2024 19:56:47 +0100 Subject: [PATCH 267/303] feat: enabled psys for windows --- src/exporters/mod.rs | 18 +-------- src/exporters/stdout.rs | 15 ++++++-- src/sensors/mod.rs | 83 ++++++++++++++++++++++++---------------- src/sensors/msr_rapl.rs | 84 +++++++++++++++++++++++++---------------- 4 files changed, 115 insertions(+), 85 deletions(-) diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index d458c475..c5e8f427 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -18,7 +18,7 @@ pub mod utils; pub mod warpten; use crate::sensors::{ utils::{current_system_time_since_epoch, IProcess}, - RecordGenerator, Topology, + RecordGenerator, Topology, Record }; use chrono::Utc; use std::collections::HashMap; @@ -644,22 +644,6 @@ impl MetricGenerator { metric_value: MetricValueType::Text(metric_value.value), }); - if let Some(psys) = self.topology.get_rapl_psys_energy_microjoules() { - self.data.push(Metric { - name: String::from("scaph_host_rapl_psys_microjoules"), - metric_type: String::from("counter"), - ttl: 60.0, - timestamp: psys.timestamp, - hostname: self.hostname.clone(), - state: String::from("ok"), - tags: vec!["scaphandre".to_string()], - attributes: HashMap::new(), - description: String::from( - "Raw extract of RAPL PSYS domain energy value, in microjoules", - ), - metric_value: MetricValueType::Text(psys.value), - }) - } } /// Generate socket metrics. diff --git a/src/exporters/stdout.rs b/src/exporters/stdout.rs index 6ce78e28..ad5fe321 100644 --- a/src/exporters/stdout.rs +++ b/src/exporters/stdout.rs @@ -111,8 +111,14 @@ impl StdoutExporter { fn summarized_view(&mut self, metrics: Vec) { let mut metrics_iter = metrics.iter(); let none_value = MetricValueType::Text("0".to_string()); + let mut host_power_source = String::from(""); let host_power = match metrics_iter.find(|x| x.name == "scaph_host_power_microwatts") { - Some(m) => &m.metric_value, + Some(m) => { + if let Some(src) = &m.attributes.get("value_source") { + host_power_source = src.to_string() + } + &m.metric_value + }, None => &none_value, }; @@ -122,8 +128,9 @@ impl StdoutExporter { } println!( - "Host:\t{} W", - (format!("{host_power}").parse::().unwrap() / 1000000.0) + "Host:\t{} W from {}", + (format!("{host_power}").parse::().unwrap() / 1000000.0), + host_power_source ); if domain_names.is_some() { @@ -144,6 +151,8 @@ impl StdoutExporter { let mut to_print = format!("Socket{socket_id}\t{power_str} W |\t"); + + let domains = metrics.iter().filter(|x| { x.name == "scaph_domain_power_microwatts" && x.attributes.get("socket_id").unwrap() == &socket_id diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 0e2a0e3e..fd9f5518 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -3,8 +3,10 @@ //! `Sensor` is the root for all sensors. It defines the [Sensor] trait //! needed to implement a sensor. -#[cfg(not(target_os = "linux"))] +#[cfg(target_os = "windows")] pub mod msr_rapl; +#[cfg(target_os="windows")] +use msr_rapl::get_msr_value; #[cfg(target_os = "linux")] pub mod powercap_rapl; pub mod units; @@ -459,29 +461,35 @@ impl Topology { .record_buffer .get(self.record_buffer.len() - 2) .unwrap(); + match previous_record.value.trim().parse::() { + Ok(previous_microjoules) => { + match last_record.value.trim().parse::() { + Ok(last_microjoules) => { + if previous_microjoules > last_microjoules { + return None; + } + let microjoules = last_microjoules - previous_microjoules; + let time_diff = last_record.timestamp.as_secs_f64() + - previous_record.timestamp.as_secs_f64(); + let microwatts = microjoules as f64 / time_diff; + return Some(Record::new( + last_record.timestamp, + (microwatts as u64).to_string(), + units::Unit::MicroWatt, + )); + }, + Err(e) => { + warn!( + "Could'nt get previous_microjoules - value : '{}' - error : {:?}", + previous_record.value, e + ); + } - if let Ok(last_microjoules) = last_record.value.trim().parse::() { - if let Ok(previous_microjoules) = previous_record.value.trim().parse::() { - if previous_microjoules > last_microjoules { - return None; } - let microjoules = last_microjoules - previous_microjoules; - let time_diff = last_record.timestamp.as_secs_f64() - - previous_record.timestamp.as_secs_f64(); - let microwatts = microjoules as f64 / time_diff; - return Some(Record::new( - last_record.timestamp, - (microwatts as u64).to_string(), - units::Unit::MicroWatt, - )); - } else { - warn!( - "Could'nt get previous_microjoules: {}", - previous_record.value - ); + }, + Err(e) => { + warn!("Couldn't parse previous_microjoules - value : '{}' - error : {:?}", previous_record.value.trim(), e); } - } else { - warn!("Could'nt get last_microjoules: {}", last_record.value); } } None @@ -910,6 +918,7 @@ impl Topology { None } + #[cfg(target_os="linux")] pub fn get_rapl_psys_energy_microjoules(&self) -> Option { if let Some(psys) = self._sensor_data.get("psys") { match &fs::read_to_string(format!("{psys}/energy_uj")) { @@ -924,22 +933,30 @@ impl Topology { warn!("PSYS Error: {:?}", e); } } + } else { + debug!("Asked for PSYS but there is no psys entry in sensor_data."); + } + None + } + + #[cfg(target_os="windows")] + pub unsafe fn get_rapl_psys_energy_microjoules(&self) -> Option { + let msr_addr = msr_rapl::MSR_PLATFORM_ENERGY_STATUS; + match msr_rapl::get_msr_value(0, msr_addr.into(), &self._sensor_data) { + Ok(res) => { + return Some(Record::new( + current_system_time_since_epoch(), + res.value.to_string(), + units::Unit::MicroJoule + )) + }, + Err(e) => { + debug!("get_msr_value returned error : {}", e); + } } None } - //pub fn get_rapl_psys_power_microwatts(&self) -> Option { - // if let Some(psys) = self._sensor_data.get("psys") { - // if let Ok(val) = &fs::read_to_string(format!("{psys}/energy_uj")) { - // return Some(Record::new( - // current_system_time_since_epoch(), - // val.to_string(), - // units::Unit::MicroJoule - // )); - // } - // } - // None - //} } // !!!!!!!!!!!!!!!!! CPUSocket !!!!!!!!!!!!!!!!!!!!!!! diff --git a/src/sensors/msr_rapl.rs b/src/sensors/msr_rapl.rs index 131a7914..3f7097bd 100644 --- a/src/sensors/msr_rapl.rs +++ b/src/sensors/msr_rapl.rs @@ -20,9 +20,9 @@ use windows::Win32::System::SystemInformation::GROUP_AFFINITY; use core_affinity::{self, CoreId}; -use x86::cpuid; +pub use x86::cpuid; // Intel RAPL MSRs -use x86::msr::{ +pub use x86::msr::{ MSR_RAPL_POWER_UNIT, MSR_PKG_POWER_LIMIT, MSR_PKG_POWER_INFO, @@ -33,13 +33,13 @@ use x86::msr::{ MSR_PP0_PERF_STATUS, MSR_PP1_ENERGY_STATUS, }; -const MSR_PLATFORM_ENERGY_STATUS: u32 = 0x0000064d; -const MSR_PLATFORM_POWER_LIMIT: u32 = 0x0000065c ; +pub const MSR_PLATFORM_ENERGY_STATUS: u32 = 0x0000064d; +pub const MSR_PLATFORM_POWER_LIMIT: u32 = 0x0000065c ; // AMD RAPL MSRs -const MSR_AMD_RAPL_POWER_UNIT: u32 = 0xc0010299; -const MSR_AMD_CORE_ENERGY_STATUS: u32 = 0xc001029a; -const MSR_AMD_PKG_ENERGY_STATUS: u32 = 0xc001029b; +pub const MSR_AMD_RAPL_POWER_UNIT: u32 = 0xc0010299; +pub const MSR_AMD_CORE_ENERGY_STATUS: u32 = 0xc001029a; +pub const MSR_AMD_PKG_ENERGY_STATUS: u32 = 0xc001029b; unsafe fn ctl_code(device_type: u32, request_code: u32, method: u32, access: u32) -> u32 { @@ -176,24 +176,32 @@ impl MsrRAPLSensor { impl RecordReader for Topology { fn read_record(&self) -> Result> { - let mut res: u64 = 0; - debug!("Topology: I have {} sockets", self.sockets.len()); - for s in &self.sockets { - match s.read_record() { - Ok(rec) => { - debug!("rec: {:?}", rec); - res = res + rec.value.parse::()?; - }, - Err(e) => { - error!("Failed to get socket record : {:?}", e); + let mut record: Option = None; + unsafe { + record = self.get_rapl_psys_energy_microjoules(); + } + if let Some(psys_record) = record { + Ok(psys_record) + } else { + let mut res: u64 = 0; + debug!("Topology: I have {} sockets", self.sockets.len()); + for s in &self.sockets { + match s.read_record() { + Ok(rec) => { + debug!("rec: {:?}", rec); + res = res + rec.value.parse::()?; + }, + Err(e) => { + error!("Failed to get socket record : {:?}", e); + } } } + Ok(Record { + timestamp: current_system_time_since_epoch(), + unit: super::units::Unit::MicroJoule, + value: res.to_string(), + }) } - Ok(Record { - timestamp: current_system_time_since_epoch(), - unit: super::units::Unit::MicroJoule, - value: res.to_string(), - }) } } @@ -513,8 +521,8 @@ impl Sensor for MsrRAPLSensor { let core_id = s.get_cores_passive().last().unwrap().id + s.id * s.cpu_cores.len() as u16; debug!("Asking get_msr_value, from generate_tpopo, with core_id={}", core_id); match get_msr_value(core_id as usize, MSR_DRAM_ENERGY_STATUS as u64, &sensor_data) { - Ok(rec) => { - debug!("Added domain Dram !"); + Ok(_rec) => { + debug!("Adding domain Dram !"); let mut domain_sensor_data = sensor_data.clone(); domain_sensor_data.insert(String::from("MSR_ADDR"), MSR_DRAM_ENERGY_STATUS.to_string()); domain_sensor_data.insert(String::from("CORE_ID"), core_id.to_string()); // nb of cores in a socket * socket_id + local_core_id @@ -522,12 +530,12 @@ impl Sensor for MsrRAPLSensor { s.safe_add_domain(Domain::new(2, String::from("dram"), String::from(""), 5, domain_sensor_data)) }, Err(e) => { - warn!("Could'nt add Dram domain."); + warn!("Could'nt add Dram domain: {}", e); } } match get_msr_value(core_id as usize, MSR_PP0_ENERGY_STATUS as u64, &sensor_data) { - Ok(rec) => { - debug!("Added domain Core !"); + Ok(_rec) => { + debug!("Adding domain Core !"); let mut domain_sensor_data = sensor_data.clone(); domain_sensor_data.insert(String::from("MSR_ADDR"), MSR_PP0_ENERGY_STATUS.to_string()); domain_sensor_data.insert(String::from("CORE_ID"), core_id.to_string()); @@ -535,12 +543,12 @@ impl Sensor for MsrRAPLSensor { s.safe_add_domain(Domain::new(2, String::from("core"), String::from(""), 5, domain_sensor_data)) }, Err(e) => { - warn!("Could'nt add Core domain."); + warn!("Could'nt add Core domain: {}", e); } } match get_msr_value(core_id as usize, MSR_PP1_ENERGY_STATUS as u64, &sensor_data) { - Ok(rec) => { - debug!("Added domain Uncore !"); + Ok(_rec) => { + debug!("Adding domain Uncore !"); let mut domain_sensor_data = sensor_data.clone(); domain_sensor_data.insert(String::from("MSR_ADDR"), MSR_PP1_ENERGY_STATUS.to_string()); domain_sensor_data.insert(String::from("CORE_ID"), core_id.to_string()); @@ -548,7 +556,7 @@ impl Sensor for MsrRAPLSensor { s.safe_add_domain(Domain::new(2, String::from("uncore"), String::from(""), 5, domain_sensor_data)) }, Err(e) => { - warn!("Could'nt add Uncore domain."); + warn!("Could'nt add Uncore domain: {}", e); } } //match get_msr_value(core_id as usize, MSR_PLATFORM_ENERGY_STATUS as u64, &sensor_data) { @@ -561,6 +569,18 @@ impl Sensor for MsrRAPLSensor { } } + unsafe { + match get_msr_value(0, MSR_PLATFORM_ENERGY_STATUS as u64, &sensor_data) { + Ok(_rec) => { + debug!("Adding domain Platform / PSYS !"); + topology._sensor_data.insert(String::from("psys"), String::from("")); + }, + Err(e) => { + warn!("Could'nt add Uncore domain: {}", e); + } + } + } + topology.set_domains_names(domains); Ok(topology) } @@ -574,7 +594,7 @@ impl Sensor for MsrRAPLSensor { } } -unsafe fn get_msr_value(core_id: usize, msr_addr: u64, sensor_data: &HashMap) -> Result { +pub unsafe fn get_msr_value(core_id: usize, msr_addr: u64, sensor_data: &HashMap) -> Result { let current_process = GetCurrentProcess(); let current_thread = GetCurrentThread(); let mut thread_group_affinity = GROUP_AFFINITY { Mask: 255, Group: 9, Reserved: [0,0,0] }; From 4fec8333f4957846ca11ce39832de7603b975feb Mon Sep 17 00:00:00 2001 From: bpetit Date: Fri, 5 Jan 2024 13:54:26 +0100 Subject: [PATCH 268/303] style: clippy and fmt --- src/exporters/json.rs | 3 +- src/exporters/mod.rs | 18 +- src/exporters/prometheus.rs | 6 +- src/exporters/prometheuspush.rs | 11 +- src/exporters/qemu.rs | 2 +- src/exporters/riemann.rs | 2 +- src/exporters/stdout.rs | 7 +- src/lib.rs | 8 - src/main.rs | 64 +++--- src/sensors/mod.rs | 156 +++++++------- src/sensors/msr_rapl.rs | 346 ++++++++++++++++++++++---------- 11 files changed, 370 insertions(+), 253 deletions(-) diff --git a/src/exporters/json.rs b/src/exporters/json.rs index 6733e46f..c448cd4f 100644 --- a/src/exporters/json.rs +++ b/src/exporters/json.rs @@ -8,7 +8,6 @@ use std::{ path::{Path, PathBuf}, thread, time::{Duration, Instant}, - sync::mpsc::Receiver }; /// An Exporter that writes power consumption data of the host @@ -157,7 +156,7 @@ struct Report { impl Exporter for JsonExporter { /// Runs [iterate()] every `step` until `timeout` - fn run(&mut self, channel: &Receiver) { + fn run(&mut self) { let step = self.time_step; info!("Measurement step is: {step:?}"); diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index c5e8f427..60c6d857 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -18,14 +18,13 @@ pub mod utils; pub mod warpten; use crate::sensors::{ utils::{current_system_time_since_epoch, IProcess}, - RecordGenerator, Topology, Record + RecordGenerator, Topology, }; use chrono::Utc; use std::collections::HashMap; use std::fmt; use std::time::Duration; use utils::get_scaphandre_version; -use std::sync::mpsc::Receiver; #[cfg(feature = "containers")] use { docker_sync::{container::Container, Docker}, @@ -109,22 +108,10 @@ impl fmt::Debug for MetricValueType { /// with the structs provided by the sensor. pub trait Exporter { /// Runs the exporter. - fn run(&mut self, channel: &Receiver); + fn run(&mut self); /// The name of the kind of the exporter, for example "json". fn kind(&self) -> &str; - - fn watch_signal(&mut self, channel: &Receiver) -> Option { - match channel.try_recv() { - Ok(received) => { - info!("Received signal: {}", received); - Some(1) - }, - Err(_) => { - None - } - } - } } /// MetricGenerator is an exporter helper structure to collect Scaphandre metrics. @@ -643,7 +630,6 @@ impl MetricGenerator { description: String::from("Total swap space on the host, in bytes."), metric_value: MetricValueType::Text(metric_value.value), }); - } /// Generate socket metrics. diff --git a/src/exporters/prometheus.rs b/src/exporters/prometheus.rs index ad0e6150..9159c3a9 100644 --- a/src/exporters/prometheus.rs +++ b/src/exporters/prometheus.rs @@ -5,7 +5,7 @@ //! [scrape](https://prometheus.io/docs/prometheus/latest/getting_started). use super::utils; -use crate::current_system_time_since_epoch; +use crate::sensors::utils::current_system_time_since_epoch; use crate::exporters::{Exporter, MetricGenerator, MetricValueType}; use crate::sensors::{Sensor, Topology}; use chrono::Utc; @@ -16,9 +16,9 @@ use std::{ collections::HashMap, fmt::Write, net::{IpAddr, Ipv4Addr, SocketAddr}, + sync::mpsc::Receiver, sync::{Arc, Mutex}, time::Duration, - sync::mpsc::Receiver }; /// Default ipv4/ipv6 address to expose the service is any @@ -73,7 +73,7 @@ impl PrometheusExporter { impl Exporter for PrometheusExporter { /// Starts an HTTP server to expose the metrics in Prometheus format. - fn run(&mut self, channel: &Receiver) { + fn run(&mut self) { info!( "{}: Starting Prometheus exporter", Utc::now().format("%Y-%m-%dT%H:%M:%S") diff --git a/src/exporters/prometheuspush.rs b/src/exporters/prometheuspush.rs index 0ff558c2..73981e4f 100644 --- a/src/exporters/prometheuspush.rs +++ b/src/exporters/prometheuspush.rs @@ -13,7 +13,6 @@ use isahc::{prelude::*, Request}; use std::fmt::Write; use std::thread; use std::time::Duration; -use std::sync::mpsc::Receiver; pub struct PrometheusPushExporter { topo: Topology, @@ -73,7 +72,7 @@ impl PrometheusPushExporter { } impl Exporter for PrometheusPushExporter { - fn run(&mut self, channel: &Receiver) { + fn run(&mut self) { info!( "{}: Starting Prometheus Push exporter", Utc::now().format("%Y-%m-%dT%H:%M:%S") @@ -97,10 +96,6 @@ impl Exporter for PrometheusPushExporter { ); loop { - if self.watch_signal(channel).is_some() { - info!("Daemon/Service has received a stop signal."); - break; - } metric_generator.topology.refresh(); metric_generator.gen_all_metrics(); let mut body = String::from(""); @@ -159,10 +154,6 @@ impl Exporter for PrometheusPushExporter { } } - if self.watch_signal(channel).is_some() { - info!("Daemon/Service has received a stop signal."); - break; - } thread::sleep(Duration::new(self.args.step, 0)); } } diff --git a/src/exporters/qemu.rs b/src/exporters/qemu.rs index 239293de..829645e6 100644 --- a/src/exporters/qemu.rs +++ b/src/exporters/qemu.rs @@ -1,8 +1,8 @@ use crate::exporters::Exporter; use crate::sensors::Topology; use crate::sensors::{utils::ProcessRecord, Sensor}; -use std::{fs, io, thread, time}; use std::sync::mpsc::Receiver; +use std::{fs, io, thread, time}; /// An Exporter that extracts power consumption data of running /// Qemu/KVM virtual machines on the host and store those data diff --git a/src/exporters/riemann.rs b/src/exporters/riemann.rs index 94843e2e..e2abadac 100644 --- a/src/exporters/riemann.rs +++ b/src/exporters/riemann.rs @@ -10,8 +10,8 @@ use riemann_client::proto::{Attribute, Event}; use riemann_client::Client; use std::collections::HashMap; use std::convert::TryFrom; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::sync::mpsc::Receiver; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; /// Riemann server default ipv4/ipv6 address const DEFAULT_IP_ADDRESS: &str = "localhost"; diff --git a/src/exporters/stdout.rs b/src/exporters/stdout.rs index ad5fe321..ce50c727 100644 --- a/src/exporters/stdout.rs +++ b/src/exporters/stdout.rs @@ -4,7 +4,6 @@ use regex::Regex; use std::fmt::Write; use std::thread; use std::time::{Duration, Instant}; -use std::sync::mpsc::Receiver; /// An Exporter that displays power consumption data of the host /// and its processes on the standard output of the terminal. @@ -54,7 +53,7 @@ pub struct ExporterArgs { impl Exporter for StdoutExporter { /// Runs [iterate()] every `step` until `timeout` - fn run(&mut self, channel: &Receiver) { + fn run(&mut self) { let time_step = Duration::from_secs(self.args.step); let time_limit = if self.args.timeout < 0 { None @@ -118,7 +117,7 @@ impl StdoutExporter { host_power_source = src.to_string() } &m.metric_value - }, + } None => &none_value, }; @@ -151,8 +150,6 @@ impl StdoutExporter { let mut to_print = format!("Socket{socket_id}\t{power_str} W |\t"); - - let domains = metrics.iter().filter(|x| { x.name == "scaph_domain_power_microwatts" && x.attributes.get("socket_id").unwrap() == &socket_id diff --git a/src/lib.rs b/src/lib.rs index 60b65c46..af59dc34 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,8 +14,6 @@ use sensors::msr_rapl; #[cfg(target_os = "linux")] use sensors::powercap_rapl; -use std::time::{Duration, SystemTime}; - /// Create a new [`Sensor`] instance with the default sensor available, /// with its default options. pub fn get_default_sensor() -> impl sensors::Sensor { @@ -30,12 +28,6 @@ pub fn get_default_sensor() -> impl sensors::Sensor { return msr_rapl::MsrRAPLSensor::new(); } -fn current_system_time_since_epoch() -> Duration { - SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() -} - // Copyright 2020 The scaphandre authors. // // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/main.rs b/src/main.rs index 187fd3fc..320c0cb9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,6 @@ use clap::{command, ArgAction, Parser, Subcommand}; use colored::Colorize; use scaphandre::{exporters, sensors::Sensor}; -use std::sync::mpsc::{self, Receiver, Sender}; use std::thread; #[cfg(target_os = "linux")] @@ -21,7 +20,7 @@ use windows_service::{ service::ServiceStatus, service::ServiceType, service_control_handler::{self, ServiceControlHandlerResult}, - service_dispatcher, Result, + service_dispatcher }; #[cfg(target_os = "windows")] @@ -113,14 +112,13 @@ enum ExporterChoice { } #[cfg(target_os = "windows")] -fn my_service_main(arguments: Vec) { +fn my_service_main(_arguments: Vec) { use std::thread::JoinHandle; let graceful_period = 3; - let (tx, rx) = mpsc::channel(); let start_status = ServiceStatus { service_type: ServiceType::OWN_PROCESS, // Should match the one from system service registry - current_state: ServiceState::Running, // The new state + current_state: ServiceState::Running, // The new state controls_accepted: ServiceControlAccept::STOP, // Accept stop events when running exit_code: ServiceExitCode::Win32(0), // Used to report an error when starting or stopping only, otherwise must be zero checkpoint: 0, // Only used for pending states, otherwise must be zero @@ -134,7 +132,7 @@ fn my_service_main(arguments: Vec) { exit_code: ServiceExitCode::Win32(0), checkpoint: 0, wait_hint: Duration::default(), - process_id: None + process_id: None, }; let stoppending_status = ServiceStatus { service_type: ServiceType::OWN_PROCESS, @@ -143,18 +141,17 @@ fn my_service_main(arguments: Vec) { exit_code: ServiceExitCode::Win32(0), checkpoint: 0, wait_hint: Duration::from_secs(graceful_period), - process_id: None + process_id: None, }; - let mut thread_handle: Option> = None; - let mut stop = false; + let thread_handle: Option>; + let mut _stop = false; let event_handler = move |control_event| -> ServiceControlHandlerResult { println!("Got service control event: {:?}", control_event); match control_event { ServiceControl::Stop => { // Handle stop event and return control back to the system. - stop = true; - let _ = &tx.send(1); + _stop = true; ServiceControlHandlerResult::NoError } // All services must accept Interrogate even if it's a no-op. @@ -164,27 +161,42 @@ fn my_service_main(arguments: Vec) { }; if let Ok(system_handler) = service_control_handler::register("scaphandre", event_handler) { - // Tell the system that the service is running now and run it + // Tell the system that the service is running now and run it match system_handler.set_service_status(start_status.clone()) { Ok(status_set) => { - println!("Starting main thread, service status has been set: {:?}", status_set); - thread_handle = Some(thread::spawn(move || { parse_cli_and_run_exporter(&rx); })); - }, + println!( + "Starting main thread, service status has been set: {:?}", + status_set + ); + thread_handle = Some(thread::spawn(move || { + parse_cli_and_run_exporter(); + })); + } Err(e) => { panic!("Couldn't set Windows service status. Error: {:?}", e); } } loop { - if stop { + if _stop { // Wait for the thread to finnish, then end the current function match system_handler.set_service_status(stoppending_status.clone()) { Ok(status_set) => { println!("Stop status has been set for service: {:?}", status_set); if let Some(thr) = thread_handle { - if let Ok(_) = thr.join() { + if thr.join().is_ok() { match system_handler.set_service_status(stop_status.clone()) { - Ok(laststatus_set) => {println!("Scaphandre gracefully stopped: {:?}", laststatus_set);}, - Err(e) => {panic!("Could'nt set Stop status on scaphandre service: {:?}", e);} + Ok(laststatus_set) => { + println!( + "Scaphandre gracefully stopped: {:?}", + laststatus_set + ); + } + Err(e) => { + panic!( + "Could'nt set Stop status on scaphandre service: {:?}", + e + ); + } } } else { panic!("Joining the thread failed."); @@ -193,7 +205,7 @@ fn my_service_main(arguments: Vec) { } else { panic!("Thread handle was not initialized."); } - }, + } Err(e) => { panic!("Couldn't set Windows service status. Error: {:?}", e); } @@ -214,12 +226,10 @@ fn main() { } } - let (_, rx) = mpsc::channel(); - - parse_cli_and_run_exporter(&rx); + parse_cli_and_run_exporter(); } -fn parse_cli_and_run_exporter(channel: &Receiver) { +fn parse_cli_and_run_exporter() { let cli = Cli::parse(); loggerv::init_with_verbosity(cli.verbose.into()).expect("unable to initialize the logger"); @@ -229,7 +239,7 @@ fn parse_cli_and_run_exporter(channel: &Receiver) { print_scaphandre_header(exporter.kind()); } - exporter.run(channel); + exporter.run(); } fn build_exporter(choice: ExporterChoice, sensor: &dyn Sensor) -> Box { @@ -280,9 +290,7 @@ fn build_sensor(cli: &Cli) -> impl Sensor { }; #[cfg(target_os = "windows")] - let msr_sensor_win = || { - msr_rapl::MsrRAPLSensor::new() - }; + let msr_sensor_win = msr_rapl::MsrRAPLSensor::new; match cli.sensor.as_deref() { Some("powercap_rapl") => { diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index fd9f5518..40dd4547 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -5,7 +5,7 @@ #[cfg(target_os = "windows")] pub mod msr_rapl; -#[cfg(target_os="windows")] +#[cfg(target_os = "windows")] use msr_rapl::get_msr_value; #[cfg(target_os = "linux")] pub mod powercap_rapl; @@ -227,13 +227,10 @@ impl Topology { } } - pub fn safe_insert_socket( - &mut self, - socket: CPUSocket - ) { + pub fn safe_insert_socket(&mut self, socket: CPUSocket) { if !self.sockets.iter().any(|s| s.id == socket.id) { self.sockets.push(socket); - } + } } /// Returns a immutable reference to self.proc_tracker @@ -296,53 +293,53 @@ impl Topology { /// Generates CPUCore instances for the host and adds them /// to appropriate CPUSocket instance from self.sockets + #[cfg(target_os = "linux")] pub fn add_cpu_cores(&mut self) { if let Some(mut cores) = Topology::generate_cpu_cores() { - #[cfg(target_os = "linux")] { - while let Some(c) = cores.pop() { - let socket_id = &c - .attributes - .get("physical id") - .unwrap() - .parse::() - .unwrap(); - let socket_match = self.sockets.iter_mut().find(|x| &x.id == socket_id); - - //In VMs there might be a missmatch betwen Sockets and Cores - see Issue#133 as a first fix we just map all cores that can't be mapped to the first - let socket = match socket_match { - Some(x) => x, - None =>self.sockets.first_mut().expect("Trick: if you are running on a vm, do not forget to use --vm parameter invoking scaphandre at the command line") - }; - - if socket_id == &socket.id { - socket.add_cpu_core(c); - } else { - socket.add_cpu_core(c); - warn!("coud't not match core to socket - mapping to first socket instead - if you are not using --vm there is something wrong") - } + while let Some(c) = cores.pop() { + let socket_id = &c + .attributes + .get("physical id") + .unwrap() + .parse::() + .unwrap(); + let socket_match = self.sockets.iter_mut().find(|x| &x.id == socket_id); + + //In VMs there might be a missmatch betwen Sockets and Cores - see Issue#133 as a first fix we just map all cores that can't be mapped to the first + let socket = match socket_match { + Some(x) => x, + None =>self.sockets.first_mut().expect("Trick: if you are running on a vm, do not forget to use --vm parameter invoking scaphandre at the command line") + }; + + if socket_id == &socket.id { + socket.add_cpu_core(c); + } else { + socket.add_cpu_core(c); + warn!("coud't not match core to socket - mapping to first socket instead - if you are not using --vm there is something wrong") } } + //#[cfg(target_os = "windows")] //{ - //TODO: fix - //let nb_sockets = &self.sockets.len(); - //let mut socket_counter = 0; - //let nb_cores_per_socket = &cores.len() / nb_sockets; - //warn!("nb_cores_per_socket: {} cores_len: {} sockets_len: {}", nb_cores_per_socket, &cores.len(), &self.sockets.len()); - //for s in self.sockets.iter_mut() { - // for c in (socket_counter * nb_cores_per_socket)..((socket_counter+1) * nb_cores_per_socket) { - // match cores.pop() { - // Some(core) => { - // warn!("adding core {} to socket {}", core.id, s.id); - // s.add_cpu_core(core); - // }, - // None => { - // error!("Uneven number of CPU cores !"); - // } - // } - // } - // socket_counter = socket_counter + 1; - //} + //TODO: fix + //let nb_sockets = &self.sockets.len(); + //let mut socket_counter = 0; + //let nb_cores_per_socket = &cores.len() / nb_sockets; + //warn!("nb_cores_per_socket: {} cores_len: {} sockets_len: {}", nb_cores_per_socket, &cores.len(), &self.sockets.len()); + //for s in self.sockets.iter_mut() { + // for c in (socket_counter * nb_cores_per_socket)..((socket_counter+1) * nb_cores_per_socket) { + // match cores.pop() { + // Some(core) => { + // warn!("adding core {} to socket {}", core.id, s.id); + // s.add_cpu_core(core); + // }, + // None => { + // error!("Uneven number of CPU cores !"); + // } + // } + // } + // socket_counter = socket_counter + 1; + //} //} } else { panic!("Couldn't retrieve any CPU Core from the topology. (generate_cpu_cores)"); @@ -462,33 +459,34 @@ impl Topology { .get(self.record_buffer.len() - 2) .unwrap(); match previous_record.value.trim().parse::() { - Ok(previous_microjoules) => { - match last_record.value.trim().parse::() { - Ok(last_microjoules) => { - if previous_microjoules > last_microjoules { - return None; - } - let microjoules = last_microjoules - previous_microjoules; - let time_diff = last_record.timestamp.as_secs_f64() - - previous_record.timestamp.as_secs_f64(); - let microwatts = microjoules as f64 / time_diff; - return Some(Record::new( - last_record.timestamp, - (microwatts as u64).to_string(), - units::Unit::MicroWatt, - )); - }, - Err(e) => { - warn!( - "Could'nt get previous_microjoules - value : '{}' - error : {:?}", - previous_record.value, e - ); + Ok(previous_microjoules) => match last_record.value.trim().parse::() { + Ok(last_microjoules) => { + if previous_microjoules > last_microjoules { + return None; } - + let microjoules = last_microjoules - previous_microjoules; + let time_diff = last_record.timestamp.as_secs_f64() + - previous_record.timestamp.as_secs_f64(); + let microwatts = microjoules as f64 / time_diff; + return Some(Record::new( + last_record.timestamp, + (microwatts as u64).to_string(), + units::Unit::MicroWatt, + )); + } + Err(e) => { + warn!( + "Could'nt get previous_microjoules - value : '{}' - error : {:?}", + previous_record.value, e + ); } }, Err(e) => { - warn!("Couldn't parse previous_microjoules - value : '{}' - error : {:?}", previous_record.value.trim(), e); + warn!( + "Couldn't parse previous_microjoules - value : '{}' - error : {:?}", + previous_record.value.trim(), + e + ); } } } @@ -918,7 +916,7 @@ impl Topology { None } - #[cfg(target_os="linux")] + #[cfg(target_os = "linux")] pub fn get_rapl_psys_energy_microjoules(&self) -> Option { if let Some(psys) = self._sensor_data.get("psys") { match &fs::read_to_string(format!("{psys}/energy_uj")) { @@ -939,24 +937,30 @@ impl Topology { None } - #[cfg(target_os="windows")] + /// # Safety + /// + /// This function is unsafe rust as it calls get_msr_value function from msr_rapl sensor module. + /// It calls the msr_RAPL::MSR_PLATFORM_ENERGY_STATUS MSR address, which has been tested on several Intel x86 processors + /// but might fail on AMD (needs testing). That being said, it returns None if the msr query fails (which means if the Windows + /// driver fails.) and should not prevent from using a value coming from elsewhere, which means from another get_msr_value calls + /// targeting another msr address. + #[cfg(target_os = "windows")] pub unsafe fn get_rapl_psys_energy_microjoules(&self) -> Option { let msr_addr = msr_rapl::MSR_PLATFORM_ENERGY_STATUS; - match msr_rapl::get_msr_value(0, msr_addr.into(), &self._sensor_data) { + match get_msr_value(0, msr_addr.into(), &self._sensor_data) { Ok(res) => { return Some(Record::new( current_system_time_since_epoch(), res.value.to_string(), - units::Unit::MicroJoule + units::Unit::MicroJoule, )) - }, + } Err(e) => { debug!("get_msr_value returned error : {}", e); } } None } - } // !!!!!!!!!!!!!!!!! CPUSocket !!!!!!!!!!!!!!!!!!!!!!! diff --git a/src/sensors/msr_rapl.rs b/src/sensors/msr_rapl.rs index 3f7097bd..1a0ae715 100644 --- a/src/sensors/msr_rapl.rs +++ b/src/sensors/msr_rapl.rs @@ -1,47 +1,40 @@ use crate::sensors::utils::current_system_time_since_epoch; -use crate::sensors::{CPUSocket, Domain, Record, RecordReader, Sensor, Topology, CPUCore}; +use crate::sensors::{CPUCore, CPUSocket, Domain, Record, RecordReader, Sensor, Topology}; +use raw_cpuid::{CpuId, TopologyType}; use std::collections::HashMap; use std::error::Error; use std::mem::size_of; -use sysinfo::{System, SystemExt, CpuExt, Cpu}; -use raw_cpuid::{CpuId, TopologyType}; +use sysinfo::{CpuExt, System, SystemExt}; use windows::Win32::Foundation::{CloseHandle, GetLastError, HANDLE, INVALID_HANDLE_VALUE}; use windows::Win32::Storage::FileSystem::{ CreateFileW, FILE_FLAG_OVERLAPPED, FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_READ_DATA, FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_WRITE_DATA, OPEN_EXISTING, }; use windows::Win32::System::Ioctl::{FILE_DEVICE_UNKNOWN, METHOD_BUFFERED}; -use windows::Win32::System::IO::DeviceIoControl; +use windows::Win32::System::SystemInformation::GROUP_AFFINITY; use windows::Win32::System::Threading::{ - GetThreadGroupAffinity, GetProcessGroupAffinity, GetCurrentProcess, GetProcessInformation, - GetCurrentThread, GetActiveProcessorGroupCount, SetThreadGroupAffinity + GetActiveProcessorGroupCount, GetCurrentProcess, GetCurrentThread, GetProcessGroupAffinity, + GetThreadGroupAffinity, SetThreadGroupAffinity, }; -use windows::Win32::System::SystemInformation::GROUP_AFFINITY; +use windows::Win32::System::IO::DeviceIoControl; use core_affinity::{self, CoreId}; pub use x86::cpuid; // Intel RAPL MSRs pub use x86::msr::{ + MSR_DRAM_ENERGY_STATUS, MSR_DRAM_PERF_STATUS, MSR_PKG_ENERGY_STATUS, MSR_PKG_POWER_INFO, + MSR_PKG_POWER_LIMIT, MSR_PP0_ENERGY_STATUS, MSR_PP0_PERF_STATUS, MSR_PP1_ENERGY_STATUS, MSR_RAPL_POWER_UNIT, - MSR_PKG_POWER_LIMIT, - MSR_PKG_POWER_INFO, - MSR_PKG_ENERGY_STATUS, - MSR_DRAM_ENERGY_STATUS, - MSR_DRAM_PERF_STATUS, - MSR_PP0_ENERGY_STATUS, - MSR_PP0_PERF_STATUS, - MSR_PP1_ENERGY_STATUS, }; pub const MSR_PLATFORM_ENERGY_STATUS: u32 = 0x0000064d; -pub const MSR_PLATFORM_POWER_LIMIT: u32 = 0x0000065c ; +pub const MSR_PLATFORM_POWER_LIMIT: u32 = 0x0000065c; // AMD RAPL MSRs pub const MSR_AMD_RAPL_POWER_UNIT: u32 = 0xc0010299; pub const MSR_AMD_CORE_ENERGY_STATUS: u32 = 0xc001029a; pub const MSR_AMD_PKG_ENERGY_STATUS: u32 = 0xc001029b; - unsafe fn ctl_code(device_type: u32, request_code: u32, method: u32, access: u32) -> u32 { ((device_type) << 16) | ((access) << 14) | ((request_code) << 2) | (method) } @@ -176,7 +169,7 @@ impl MsrRAPLSensor { impl RecordReader for Topology { fn read_record(&self) -> Result> { - let mut record: Option = None; + let record: Option; unsafe { record = self.get_rapl_psys_energy_microjoules(); } @@ -189,8 +182,8 @@ impl RecordReader for Topology { match s.read_record() { Ok(rec) => { debug!("rec: {:?}", rec); - res = res + rec.value.parse::()?; - }, + res += rec.value.parse::()?; + } Err(e) => { error!("Failed to get socket record : {:?}", e); } @@ -250,32 +243,65 @@ impl RecordReader for CPUSocket { fn read_record(&self) -> Result> { unsafe { let current_thread = GetCurrentThread(); - let processorgroup_id = self.sensor_data.get("PROCESSORGROUP_ID").unwrap().parse::().unwrap(); - let mut thread_group_affinity: GROUP_AFFINITY = GROUP_AFFINITY { Mask: 255, Group: processorgroup_id, Reserved: [0,0,0] }; - let thread_affinity = GetThreadGroupAffinity(current_thread, &mut thread_group_affinity); + let processorgroup_id = self + .sensor_data + .get("PROCESSORGROUP_ID") + .unwrap() + .parse::() + .unwrap(); + let mut thread_group_affinity: GROUP_AFFINITY = GROUP_AFFINITY { + Mask: 255, + Group: processorgroup_id, + Reserved: [0, 0, 0], + }; + let thread_affinity = + GetThreadGroupAffinity(current_thread, &mut thread_group_affinity); if thread_affinity.as_bool() { debug!("got thead_affinity : {:?}", thread_group_affinity); let core_id = self.cpu_cores.last().unwrap().id; //(self.cpu_cores.last().unwrap().id + self.id * self.cpu_cores.len() as u16) as usize - let newaffinity = GROUP_AFFINITY { Mask: (self.cpu_cores.len() + self.id as usize * self.cpu_cores.len() - 1) as usize, Group: processorgroup_id, Reserved: [0, 0, 0]}; - let res = SetThreadGroupAffinity(current_thread, &newaffinity, &mut thread_group_affinity); + let newaffinity = GROUP_AFFINITY { + Mask: self.cpu_cores.len() + self.id as usize * self.cpu_cores.len() - 1, + Group: processorgroup_id, + Reserved: [0, 0, 0], + }; + let res = SetThreadGroupAffinity( + current_thread, + &newaffinity, + &mut thread_group_affinity, + ); if res.as_bool() { - debug!("Asking get_msr_value, from socket, with core_id={}", core_id); - match get_msr_value(core_id as usize, MSR_PKG_ENERGY_STATUS as u64, &self.sensor_data) { + debug!( + "Asking get_msr_value, from socket, with core_id={}", + core_id + ); + match get_msr_value( + core_id as usize, + MSR_PKG_ENERGY_STATUS as u64, + &self.sensor_data, + ) { Ok(rec) => { - return Ok(Record { timestamp: current_system_time_since_epoch(), value: rec.value, unit: super::units::Unit::MicroJoule }) - }, + Ok(Record { + timestamp: current_system_time_since_epoch(), + value: rec.value, + unit: super::units::Unit::MicroJoule, + }) + } Err(e) => { - error!("Could'nt get MSR value for {}: {}", MSR_PKG_ENERGY_STATUS, e); - return Ok(Record { + error!( + "Could'nt get MSR value for {}: {}", + MSR_PKG_ENERGY_STATUS, e + ); + Ok(Record { timestamp: current_system_time_since_epoch(), value: String::from("0"), - unit: super::units::Unit::MicroJoule + unit: super::units::Unit::MicroJoule, }) } } } else { panic!("Couldn't set Thread affinity !"); } + //TODO add DRAM domain to result when available } else { panic!("Coudld'nt get Thread affinity !"); } @@ -289,21 +315,28 @@ impl RecordReader for Domain { debug!("Reading Domain {} on Core {}", self.name, usize_coreid); if let Some(msr_addr) = self.sensor_data.get("MSR_ADDR") { unsafe { - debug!("Asking, from Domain, get_msr_value with core_id={}", usize_coreid); - match get_msr_value(usize_coreid, msr_addr.parse::().unwrap(), &self.sensor_data) { + debug!( + "Asking, from Domain, get_msr_value with core_id={}", + usize_coreid + ); + match get_msr_value( + usize_coreid, + msr_addr.parse::().unwrap(), + &self.sensor_data, + ) { Ok(rec) => { - return Ok(Record { + Ok(Record { timestamp: current_system_time_since_epoch(), unit: super::units::Unit::MicroJoule, value: rec.value, }) - }, + } Err(e) => { error!("Could'nt get MSR value for {}: {}", msr_addr, e); - Ok(Record { + Ok(Record { timestamp: current_system_time_since_epoch(), value: String::from("0"), - unit: super::units::Unit::MicroJoule + unit: super::units::Unit::MicroJoule, }) } } @@ -337,8 +370,7 @@ impl Sensor for MsrRAPLSensor { for group_id in 0..group_count { //TODO fix that to actually count the number of sockets - let logical_cpus = sys.cpus() ; - let mut nb_cpu_sockets: u16 = 0; + let logical_cpus = sys.cpus(); let cpuid = CpuId::new(); let mut logical_cpus_from_cpuid = 1; match cpuid.get_extended_topology_info() { @@ -348,7 +380,7 @@ impl Sensor for MsrRAPLSensor { logical_cpus_from_cpuid = t.processors(); } } - }, + } None => { panic!("Could'nt get cpuid data."); } @@ -359,31 +391,62 @@ impl Sensor for MsrRAPLSensor { let mut i: u16 = 0; let mut no_more_sockets = false; debug!("Entering ProcessorGroup {}", group_id); - let newaffinity = GROUP_AFFINITY { Mask: 255, Group: group_id, Reserved: [0, 0, 0]}; - let mut thread_group_affinity: GROUP_AFFINITY = GROUP_AFFINITY { Mask: 255, Group: 0, Reserved: [0,0,0] }; - let thread_affinity = GetThreadGroupAffinity(current_thread, &mut thread_group_affinity); + let newaffinity = GROUP_AFFINITY { + Mask: 255, + Group: group_id, + Reserved: [0, 0, 0], + }; + let mut thread_group_affinity: GROUP_AFFINITY = GROUP_AFFINITY { + Mask: 255, + Group: 0, + Reserved: [0, 0, 0], + }; + let thread_affinity = + GetThreadGroupAffinity(current_thread, &mut thread_group_affinity); debug!("Thread group affinity result : {:?}", thread_affinity); if thread_affinity.as_bool() { debug!("got thead_affinity : {:?}", thread_group_affinity); - let res = SetThreadGroupAffinity(current_thread, &newaffinity, &mut thread_group_affinity); + let res = SetThreadGroupAffinity( + current_thread, + &newaffinity, + &mut thread_group_affinity, + ); if res.as_bool() { debug!("Have set thread affinity: {:?}", newaffinity); match core_affinity::get_core_ids() { Some(core_ids) => { - debug!("CPU SETUP - Cores from core_affinity, len={} : {:?}", core_ids.len(), core_ids); - debug!("CPU SETUP - Logical CPUs from sysinfo: {}", logical_cpus.len()); + debug!( + "CPU SETUP - Cores from core_affinity, len={} : {:?}", + core_ids.len(), + core_ids + ); + debug!( + "CPU SETUP - Logical CPUs from sysinfo: {}", + logical_cpus.len() + ); while !no_more_sockets { let start = i * logical_cpus_from_cpuid; - let stop = (i+1)*logical_cpus_from_cpuid; + let stop = (i + 1) * logical_cpus_from_cpuid; debug!("Looping over {} .. {}", start, stop); - sensor_data.insert(String::from("PROCESSORGROUP_ID"), group_id.to_string()); - let mut current_socket = CPUSocket::new(i, vec![], vec![], String::from(""),1, sensor_data.clone()); - for c in start..stop {//core_ids { + sensor_data.insert( + String::from("PROCESSORGROUP_ID"), + group_id.to_string(), + ); + let mut current_socket = CPUSocket::new( + i, + vec![], + vec![], + String::from(""), + 1, + sensor_data.clone(), + ); + for c in start..stop { + //core_ids { if core_affinity::set_for_current(CoreId { id: c.into() }) { match cpuid.get_vendor_info() { Some(info) => { debug!("Got CPU {:?}", info); - }, + } None => { warn!("Couldn't get cpuinfo"); } @@ -394,43 +457,76 @@ impl Sensor for MsrRAPLSensor { debug!("Got CPU topo info {:?}", info); for t in info { if t.level_type() == TopologyType::Core { - //nb_cpu_sockets = logical_cpus.len() as u16 / t.processors(); //logical_cpus_from_cpuid = t.processors() let x2apic_id = t.x2apic_id(); let socket_id = (x2apic_id & 240) >> 4; // upper bits of x2apic_id are socket_id, mask them, then bit shift to get socket_id current_socket.set_id(socket_id as u16); let core_id = x2apic_id & 15; // 4 last bits of x2apic_id are the core_id (per-socket) - debug!("Found socketid={} and coreid={}", socket_id, core_id); - let mut attributes = HashMap::::new(); - let ref_core = logical_cpus.first().unwrap(); - attributes.insert(String::from("frequency"), ref_core.frequency().to_string()); - attributes.insert(String::from("name"), ref_core.name().to_string()); - attributes.insert(String::from("vendor_id"), ref_core.vendor_id().to_string()); - attributes.insert(String::from("brand"), ref_core.brand().to_string()); - debug!("Adding core id {} to socket_id {}", ((i * (logical_cpus_from_cpuid - 1)) + core_id as u16), current_socket.id); - current_socket.add_cpu_core(CPUCore::new((i * (logical_cpus_from_cpuid - 1)) + core_id as u16, attributes)); - debug!("Reviewing sockets : {:?}", topology.get_sockets_passive()); + debug!( + "Found socketid={} and coreid={}", + socket_id, core_id + ); + let mut attributes = + HashMap::::new(); + let ref_core = + logical_cpus.first().unwrap(); + attributes.insert( + String::from("frequency"), + ref_core.frequency().to_string(), + ); + attributes.insert( + String::from("name"), + ref_core.name().to_string(), + ); + attributes.insert( + String::from("vendor_id"), + ref_core.vendor_id().to_string(), + ); + attributes.insert( + String::from("brand"), + ref_core.brand().to_string(), + ); + debug!( + "Adding core id {} to socket_id {}", + ((i * (logical_cpus_from_cpuid + - 1)) + + core_id as u16), + current_socket.id + ); + current_socket.add_cpu_core( + CPUCore::new( + (i * (logical_cpus_from_cpuid + - 1)) + + core_id as u16, + attributes, + ), + ); + debug!( + "Reviewing sockets : {:?}", + topology.get_sockets_passive() + ); } } - }, + } None => { warn!("Couldn't get cpu topo info"); } } } else { no_more_sockets = true; - debug!("There's likely to be no more socket to explore."); + debug!( + "There's likely to be no more socket to explore." + ); break; } - } + } if !no_more_sockets { debug!("inserting socket {:?}", current_socket); topology.safe_insert_socket(current_socket); - i = i + 1; + i += 1; } } - nb_cpu_sockets = i; - }, + } None => { panic!("Could'nt get core ids from core_affinity."); } @@ -448,14 +544,13 @@ impl Sensor for MsrRAPLSensor { panic!("Error was : {:?}", last_error); } } else { - panic!("Getting thread group affinity failed !"); + error!("Getting thread group affinity failed !"); let last_error = GetLastError(); panic!("Error was: {:?}", last_error); // win32 error 122 is insufficient buffer } } //let process_information = GetProcessInformation(current_process, , , ); } - //nb_cpu_sockets = logical_cpus.len() as u16 / logical_cpus_from_cpuid; //let mut core_id_counter = logical_cpus.len(); //match cpuid.get_advanced_power_mgmt_info() { @@ -506,29 +601,38 @@ impl Sensor for MsrRAPLSensor { // warn!("Couldn't get cpu capacity info"); // } //} - //TODO: fix - //i=0; - //while i < nb_cpu_sockets { - // //topology.safe_add_domain_to_socket(i, , name, uj_counter, buffer_max_kbytes, sensor_data) - // i = i + 1; - //} //topology.add_cpu_cores(); - let mut domains = vec![]; + let mut domains = vec![]; for s in topology.get_sockets() { debug!("Inspecting CPUSocket: {:?}", s); unsafe { - let core_id = s.get_cores_passive().last().unwrap().id + s.id * s.cpu_cores.len() as u16; - debug!("Asking get_msr_value, from generate_tpopo, with core_id={}", core_id); - match get_msr_value(core_id as usize, MSR_DRAM_ENERGY_STATUS as u64, &sensor_data) { + let core_id = + s.get_cores_passive().last().unwrap().id + s.id * s.cpu_cores.len() as u16; + debug!( + "Asking get_msr_value, from generate_tpopo, with core_id={}", + core_id + ); + match get_msr_value( + core_id as usize, + MSR_DRAM_ENERGY_STATUS as u64, + &sensor_data, + ) { Ok(_rec) => { debug!("Adding domain Dram !"); let mut domain_sensor_data = sensor_data.clone(); - domain_sensor_data.insert(String::from("MSR_ADDR"), MSR_DRAM_ENERGY_STATUS.to_string()); + domain_sensor_data + .insert(String::from("MSR_ADDR"), MSR_DRAM_ENERGY_STATUS.to_string()); domain_sensor_data.insert(String::from("CORE_ID"), core_id.to_string()); // nb of cores in a socket * socket_id + local_core_id domains.push(String::from("dram")); - s.safe_add_domain(Domain::new(2, String::from("dram"), String::from(""), 5, domain_sensor_data)) - }, + s.safe_add_domain(Domain::new( + 2, + String::from("dram"), + String::from(""), + 5, + domain_sensor_data, + )) + } Err(e) => { warn!("Could'nt add Dram domain: {}", e); } @@ -537,11 +641,18 @@ impl Sensor for MsrRAPLSensor { Ok(_rec) => { debug!("Adding domain Core !"); let mut domain_sensor_data = sensor_data.clone(); - domain_sensor_data.insert(String::from("MSR_ADDR"), MSR_PP0_ENERGY_STATUS.to_string()); + domain_sensor_data + .insert(String::from("MSR_ADDR"), MSR_PP0_ENERGY_STATUS.to_string()); domain_sensor_data.insert(String::from("CORE_ID"), core_id.to_string()); domains.push(String::from("core")); - s.safe_add_domain(Domain::new(2, String::from("core"), String::from(""), 5, domain_sensor_data)) - }, + s.safe_add_domain(Domain::new( + 2, + String::from("core"), + String::from(""), + 5, + domain_sensor_data, + )) + } Err(e) => { warn!("Could'nt add Core domain: {}", e); } @@ -550,11 +661,18 @@ impl Sensor for MsrRAPLSensor { Ok(_rec) => { debug!("Adding domain Uncore !"); let mut domain_sensor_data = sensor_data.clone(); - domain_sensor_data.insert(String::from("MSR_ADDR"), MSR_PP1_ENERGY_STATUS.to_string()); + domain_sensor_data + .insert(String::from("MSR_ADDR"), MSR_PP1_ENERGY_STATUS.to_string()); domain_sensor_data.insert(String::from("CORE_ID"), core_id.to_string()); domains.push(String::from("uncore")); - s.safe_add_domain(Domain::new(2, String::from("uncore"), String::from(""), 5, domain_sensor_data)) - }, + s.safe_add_domain(Domain::new( + 2, + String::from("uncore"), + String::from(""), + 5, + domain_sensor_data, + )) + } Err(e) => { warn!("Could'nt add Uncore domain: {}", e); } @@ -573,8 +691,10 @@ impl Sensor for MsrRAPLSensor { match get_msr_value(0, MSR_PLATFORM_ENERGY_STATUS as u64, &sensor_data) { Ok(_rec) => { debug!("Adding domain Platform / PSYS !"); - topology._sensor_data.insert(String::from("psys"), String::from("")); - }, + topology + ._sensor_data + .insert(String::from("psys"), String::from("")); + } Err(e) => { warn!("Could'nt add Uncore domain: {}", e); } @@ -594,19 +714,38 @@ impl Sensor for MsrRAPLSensor { } } -pub unsafe fn get_msr_value(core_id: usize, msr_addr: u64, sensor_data: &HashMap) -> Result { +/// # Safety +/// +/// This function should is unsafe rust as it uses send_request, hence calls a DeviceIO Windows driver. +/// The safety burden actuallr resides in the DeviceIO driver that is called. Please refer to the documentation to +/// get the relationship between Scaphandre and its driver for Windows. The driver should exit smoothly if a wrong +/// MSR address is called, then this function should throw an Error. Any improper issue with the operating system would mean +/// there is an issue in the driver used behind the scene, or the way it is configured. +pub unsafe fn get_msr_value( + core_id: usize, + msr_addr: u64, + sensor_data: &HashMap, +) -> Result { let current_process = GetCurrentProcess(); let current_thread = GetCurrentThread(); - let mut thread_group_affinity = GROUP_AFFINITY { Mask: 255, Group: 9, Reserved: [0,0,0] }; - let thread_affinity_res = GetThreadGroupAffinity(current_thread, &mut thread_group_affinity); + let mut thread_group_affinity = GROUP_AFFINITY { + Mask: 255, + Group: 9, + Reserved: [0, 0, 0], + }; + let thread_affinity_res = GetThreadGroupAffinity(current_thread, &mut thread_group_affinity); if thread_affinity_res.as_bool() { debug!("Thread affinity found : {:?}", thread_group_affinity); } else { error!("Could'nt get thread group affinity"); } - let mut process_group_array: [u16; 8] = [0,0,0,0,0,0,0,0]; + let mut process_group_array: [u16; 8] = [0, 0, 0, 0, 0, 0, 0, 0]; let mut process_group_array_len = 8; - let process_affinity_res = GetProcessGroupAffinity(current_process, &mut process_group_array_len, process_group_array.as_mut_ptr()); + let process_affinity_res = GetProcessGroupAffinity( + current_process, + &mut process_group_array_len, + process_group_array.as_mut_ptr(), + ); if process_affinity_res.as_bool() { debug!("Process affinity found: {:?}", process_group_array); } else { @@ -623,7 +762,7 @@ pub unsafe fn get_msr_value(core_id: usize, msr_addr: u64, sensor_data: &HashMap debug!("core_id: {:b}", ((core_id as u64) << 32)); let src = ((core_id as u64) << 32) | msr_addr; //let src = ((core_id as u64) << 32) | msr_addr; let ptr = &src as *const u64; - + debug!("src: {:x}", src); debug!("src: {:b}", src); debug!("*ptr: {:b}", *ptr); @@ -638,7 +777,7 @@ pub unsafe fn get_msr_value(core_id: usize, msr_addr: u64, sensor_data: &HashMap ptr_result, size_of::(), ) { - Ok(res) => { + Ok(_res) => { close_handle(device); let energy_unit = sensor_data @@ -646,7 +785,8 @@ pub unsafe fn get_msr_value(core_id: usize, msr_addr: u64, sensor_data: &HashMap .unwrap() .parse::() .unwrap(); - let current_value = MsrRAPLSensor::extract_rapl_current_power(msr_result, energy_unit); + let current_value = + MsrRAPLSensor::extract_rapl_current_power(msr_result, energy_unit); debug!("current_value: {}", current_value); Ok(Record { @@ -654,17 +794,17 @@ pub unsafe fn get_msr_value(core_id: usize, msr_addr: u64, sensor_data: &HashMap unit: super::units::Unit::MicroJoule, value: current_value, }) - }, + } Err(e) => { error!("Failed to get data from send_request: {:?}", e); close_handle(device); Err(format!("Failed to get data from send_request: {:?}", e)) } } - }, + } Err(e) => { error!("Couldn't get driver handle : {:?}", e); Err(format!("Couldn't get driver handle : {:?}", e)) } } -} \ No newline at end of file +} From 872987e589bd0429daf6cabf734c18b0b3605ce5 Mon Sep 17 00:00:00 2001 From: bpetit Date: Fri, 5 Jan 2024 14:09:11 +0100 Subject: [PATCH 269/303] style: clippy --- src/sensors/powercap_rapl.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sensors/powercap_rapl.rs b/src/sensors/powercap_rapl.rs index 6a35c6e1..c82a3d58 100644 --- a/src/sensors/powercap_rapl.rs +++ b/src/sensors/powercap_rapl.rs @@ -86,7 +86,7 @@ impl RecordReader for Topology { match r.value.trim().parse::() { Ok(val) => { total += val; - }, + } Err(e) => { warn!("could'nt convert {} to i128: {}", r.value.trim(), e); } @@ -98,7 +98,7 @@ impl RecordReader for Topology { match dr.value.trim().parse::() { Ok(val) => { total += val; - }, + } Err(e) =>{ warn!("could'nt convert {} to i128: {}", dr.value.trim(), e); } From 759ab4c0529eb9099de4702a469556cf4bb2da91 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Fri, 5 Jan 2024 15:02:14 +0100 Subject: [PATCH 270/303] style: clippy and fmt --- src/exporters/mod.rs | 1 - src/exporters/prometheus.rs | 3 +-- src/exporters/qemu.rs | 3 +-- src/exporters/riemann.rs | 3 +-- src/main.rs | 3 +-- src/sensors/mod.rs | 4 ++-- src/sensors/msr_rapl.rs | 24 ++++++++++-------------- 7 files changed, 16 insertions(+), 25 deletions(-) diff --git a/src/exporters/mod.rs b/src/exporters/mod.rs index 60c6d857..5b3d750c 100644 --- a/src/exporters/mod.rs +++ b/src/exporters/mod.rs @@ -897,7 +897,6 @@ impl MetricGenerator { Ok(events) => { if !events.is_empty() { self.gen_docker_containers_basic_metadata(); - } else { } } Err(err) => debug!("couldn't get docker events - {:?} - {}", err, err), diff --git a/src/exporters/prometheus.rs b/src/exporters/prometheus.rs index 9159c3a9..29d7cd01 100644 --- a/src/exporters/prometheus.rs +++ b/src/exporters/prometheus.rs @@ -5,8 +5,8 @@ //! [scrape](https://prometheus.io/docs/prometheus/latest/getting_started). use super::utils; -use crate::sensors::utils::current_system_time_since_epoch; use crate::exporters::{Exporter, MetricGenerator, MetricValueType}; +use crate::sensors::utils::current_system_time_since_epoch; use crate::sensors::{Sensor, Topology}; use chrono::Utc; use hyper::service::{make_service_fn, service_fn}; @@ -16,7 +16,6 @@ use std::{ collections::HashMap, fmt::Write, net::{IpAddr, Ipv4Addr, SocketAddr}, - sync::mpsc::Receiver, sync::{Arc, Mutex}, time::Duration, }; diff --git a/src/exporters/qemu.rs b/src/exporters/qemu.rs index 829645e6..de3355e5 100644 --- a/src/exporters/qemu.rs +++ b/src/exporters/qemu.rs @@ -1,7 +1,6 @@ use crate::exporters::Exporter; use crate::sensors::Topology; use crate::sensors::{utils::ProcessRecord, Sensor}; -use std::sync::mpsc::Receiver; use std::{fs, io, thread, time}; /// An Exporter that extracts power consumption data of running @@ -18,7 +17,7 @@ pub struct QemuExporter { impl Exporter for QemuExporter { /// Runs [iterate()] in a loop. - fn run(&mut self, channel: Receiver) { + fn run(&mut self) { info!("Starting qemu exporter"); let path = "/var/lib/libvirt/scaphandre"; let cleaner_step = 120; diff --git a/src/exporters/riemann.rs b/src/exporters/riemann.rs index e2abadac..7635db04 100644 --- a/src/exporters/riemann.rs +++ b/src/exporters/riemann.rs @@ -10,7 +10,6 @@ use riemann_client::proto::{Attribute, Event}; use riemann_client::Client; use std::collections::HashMap; use std::convert::TryFrom; -use std::sync::mpsc::Receiver; use std::time::{Duration, SystemTime, UNIX_EPOCH}; /// Riemann server default ipv4/ipv6 address @@ -169,7 +168,7 @@ impl RiemannExporter { impl Exporter for RiemannExporter { /// Entry point of the RiemannExporter. - fn run(&mut self, channel: &Receiver) { + fn run(&mut self) { info!( "{}: Starting Riemann exporter", Utc::now().format("%Y-%m-%dT%H:%M:%S") diff --git a/src/main.rs b/src/main.rs index 320c0cb9..531e862d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,6 @@ use clap::{command, ArgAction, Parser, Subcommand}; use colored::Colorize; use scaphandre::{exporters, sensors::Sensor}; -use std::thread; #[cfg(target_os = "linux")] use scaphandre::sensors::powercap_rapl; @@ -20,7 +19,7 @@ use windows_service::{ service::ServiceStatus, service::ServiceType, service_control_handler::{self, ServiceControlHandlerResult}, - service_dispatcher + service_dispatcher, }; #[cfg(target_os = "windows")] diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 40dd4547..18054b8a 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -318,7 +318,7 @@ impl Topology { warn!("coud't not match core to socket - mapping to first socket instead - if you are not using --vm there is something wrong") } } - + //#[cfg(target_os = "windows")] //{ //TODO: fix @@ -941,7 +941,7 @@ impl Topology { /// /// This function is unsafe rust as it calls get_msr_value function from msr_rapl sensor module. /// It calls the msr_RAPL::MSR_PLATFORM_ENERGY_STATUS MSR address, which has been tested on several Intel x86 processors - /// but might fail on AMD (needs testing). That being said, it returns None if the msr query fails (which means if the Windows + /// but might fail on AMD (needs testing). That being said, it returns None if the msr query fails (which means if the Windows /// driver fails.) and should not prevent from using a value coming from elsewhere, which means from another get_msr_value calls /// targeting another msr address. #[cfg(target_os = "windows")] diff --git a/src/sensors/msr_rapl.rs b/src/sensors/msr_rapl.rs index 1a0ae715..e41426f0 100644 --- a/src/sensors/msr_rapl.rs +++ b/src/sensors/msr_rapl.rs @@ -279,13 +279,11 @@ impl RecordReader for CPUSocket { MSR_PKG_ENERGY_STATUS as u64, &self.sensor_data, ) { - Ok(rec) => { - Ok(Record { - timestamp: current_system_time_since_epoch(), - value: rec.value, - unit: super::units::Unit::MicroJoule, - }) - } + Ok(rec) => Ok(Record { + timestamp: current_system_time_since_epoch(), + value: rec.value, + unit: super::units::Unit::MicroJoule, + }), Err(e) => { error!( "Could'nt get MSR value for {}: {}", @@ -324,13 +322,11 @@ impl RecordReader for Domain { msr_addr.parse::().unwrap(), &self.sensor_data, ) { - Ok(rec) => { - Ok(Record { - timestamp: current_system_time_since_epoch(), - unit: super::units::Unit::MicroJoule, - value: rec.value, - }) - } + Ok(rec) => Ok(Record { + timestamp: current_system_time_since_epoch(), + unit: super::units::Unit::MicroJoule, + value: rec.value, + }), Err(e) => { error!("Could'nt get MSR value for {}: {}", msr_addr, e); Ok(Record { From 839d660ad3558a9c643676d23d7386424548bbcd Mon Sep 17 00:00:00 2001 From: bpetit Date: Fri, 5 Jan 2024 15:33:56 +0100 Subject: [PATCH 271/303] style: clippy --- src/main.rs | 2 +- src/sensors/utils.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 531e862d..f1d68881 100644 --- a/src/main.rs +++ b/src/main.rs @@ -167,7 +167,7 @@ fn my_service_main(_arguments: Vec) { "Starting main thread, service status has been set: {:?}", status_set ); - thread_handle = Some(thread::spawn(move || { + thread_handle = Some(std::thread::spawn(move || { parse_cli_and_run_exporter(); })); } diff --git a/src/sensors/utils.rs b/src/sensors/utils.rs index ceac1cf2..2081b1b4 100644 --- a/src/sensors/utils.rs +++ b/src/sensors/utils.rs @@ -324,7 +324,7 @@ impl ProcessTracker { // check if the previous records in the vector are from the same process // (if the process with that pid is not a new one) and if so, drop it for a new one if !vector.is_empty() - && process_record.process.comm != vector.get(0).unwrap().process.comm + && process_record.process.comm != vector.first().unwrap().process.comm { *vector = vec![]; } @@ -627,12 +627,12 @@ impl ProcessTracker { let mut result = self .procs .iter() - .filter(|x| !x.is_empty() && x.get(0).unwrap().process.pid == pid); + .filter(|x| !x.is_empty() && x.first().unwrap().process.pid == pid); let process = result.next().unwrap(); if result.next().is_some() { panic!("Found two vectors of processes with the same id, maintainers should fix this."); } - process.get(0).unwrap().process.comm.clone() + process.first().unwrap().process.comm.clone() } /// Returns the cmdline string associated to a PID @@ -640,9 +640,9 @@ impl ProcessTracker { let mut result = self .procs .iter() - .filter(|x| !x.is_empty() && x.get(0).unwrap().process.pid == pid); + .filter(|x| !x.is_empty() && x.first().unwrap().process.pid == pid); let process = result.next().unwrap(); - if let Some(p) = process.get(0) { + if let Some(p) = process.first() { let cmdline_request = p.process.cmdline(self); if let Ok(mut cmdline_vec) = cmdline_request { let mut cmdline = String::from(""); From 402fdacd1b48752296458d866c2904b6f5adbdf1 Mon Sep 17 00:00:00 2001 From: bpetit Date: Fri, 5 Jan 2024 17:10:20 +0100 Subject: [PATCH 272/303] feat: adding dram to sum of sockets PKG in host total power, when psys is unavailable, on windows --- src/sensors/msr_rapl.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/sensors/msr_rapl.rs b/src/sensors/msr_rapl.rs index e41426f0..8b212f1b 100644 --- a/src/sensors/msr_rapl.rs +++ b/src/sensors/msr_rapl.rs @@ -176,18 +176,28 @@ impl RecordReader for Topology { if let Some(psys_record) = record { Ok(psys_record) } else { - let mut res: u64 = 0; + let mut res: u128 = 0; debug!("Topology: I have {} sockets", self.sockets.len()); for s in &self.sockets { match s.read_record() { Ok(rec) => { debug!("rec: {:?}", rec); - res += rec.value.parse::()?; + res += rec.value.trim().parse::()?; } Err(e) => { error!("Failed to get socket record : {:?}", e); } } + let dram_filter: Vec<&Domain> = s + .get_domains_passive() + .iter() + .filter(|d| d.name == "dram") + .collect(); + if let Some(dram) = dram_filter.first() { + if let Ok(val) = dram.read_record() { + res += val.value.trim().parse::()?; + } + } } Ok(Record { timestamp: current_system_time_since_epoch(), From 837082e6af7080abfd3d241be71d2e327635999e Mon Sep 17 00:00:00 2001 From: bpetit Date: Fri, 5 Jan 2024 17:14:24 +0100 Subject: [PATCH 273/303] style: clkippy --- src/sensors/powercap_rapl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sensors/powercap_rapl.rs b/src/sensors/powercap_rapl.rs index c82a3d58..2d56700a 100644 --- a/src/sensors/powercap_rapl.rs +++ b/src/sensors/powercap_rapl.rs @@ -99,7 +99,7 @@ impl RecordReader for Topology { Ok(val) => { total += val; } - Err(e) =>{ + Err(e) => { warn!("could'nt convert {} to i128: {}", dr.value.trim(), e); } } From 94115c6211478a832599c952927d06c12fd6d92e Mon Sep 17 00:00:00 2001 From: bpetit Date: Fri, 12 Jan 2024 17:03:27 +0100 Subject: [PATCH 274/303] chore: lowering log level for fail cases, as it might happen on first metric collection --- src/sensors/msr_rapl.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sensors/msr_rapl.rs b/src/sensors/msr_rapl.rs index 8b212f1b..75596a50 100644 --- a/src/sensors/msr_rapl.rs +++ b/src/sensors/msr_rapl.rs @@ -185,7 +185,7 @@ impl RecordReader for Topology { res += rec.value.trim().parse::()?; } Err(e) => { - error!("Failed to get socket record : {:?}", e); + warn!("Failed to get socket record : {:?}", e); } } let dram_filter: Vec<&Domain> = s @@ -245,7 +245,7 @@ unsafe fn send_request( info!("Device answered"); Ok(String::from("Device answered !")) } else { - error!("DeviceIoControl failed"); + info!("DeviceIoControl failed"); Err(String::from("DeviceIoControl failed")) } } @@ -802,7 +802,7 @@ pub unsafe fn get_msr_value( }) } Err(e) => { - error!("Failed to get data from send_request: {:?}", e); + info!("Failed to get data from send_request: {:?}", e); close_handle(device); Err(format!("Failed to get data from send_request: {:?}", e)) } From 14cf9e9c636d76fa55569179db12d89109d038f2 Mon Sep 17 00:00:00 2001 From: repair Date: Tue, 16 Jan 2024 15:13:30 +0100 Subject: [PATCH 275/303] feat: initial debian release pkg with github actions --- .github/workflows/debian-release.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/workflows/debian-release.yml diff --git a/.github/workflows/debian-release.yml b/.github/workflows/debian-release.yml new file mode 100644 index 00000000..9dfa5209 --- /dev/null +++ b/.github/workflows/debian-release.yml @@ -0,0 +1,11 @@ +name: debian-release + +on: + push: + paths-ignore: + - 'docs_src/**' + - 'README.md' + - 'CITATION' + - 'book.toml' + - 'CONTRIBUTING.md' + tags: [ 'v*.*.*', 'dev*.*.*' ] From 148827e9b4ff5c19c4ff68a31b68b6e951d8981d Mon Sep 17 00:00:00 2001 From: repair Date: Tue, 16 Jan 2024 18:49:35 +0100 Subject: [PATCH 276/303] feat: checkout, deb11 and deb12 pkgs steps --- .github/workflows/debian-release.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/debian-release.yml b/.github/workflows/debian-release.yml index 9dfa5209..a66acb91 100644 --- a/.github/workflows/debian-release.yml +++ b/.github/workflows/debian-release.yml @@ -9,3 +9,23 @@ on: - 'book.toml' - 'CONTRIBUTING.md' tags: [ 'v*.*.*', 'dev*.*.*' ] + +env: + VERSION: ${{ vars.GITHUB_REF_NAME }} + + +jobs: + create_debian_pkg_with_tag: + name: Create Debian package associated to version tag + runs-on: ubuntu-latest + steps: + - name: Checkout scaphandre-debian repository + uses: actions/checkout@v4 + with: + repository: 'barnumbirr/scaphandre-debian' + - name: Build package with version tag and Debian 11 Bullseye + run : | + ./build.sh -v {{ $VERSION }} + - name: Build package with version tag and Debian 12 Bookworm + run: | + $IMAGE="debian:bookworm-slim" ./build.sh -v {{ $VERSION }} From 942a99e2c2af9bc4112118e1baa7043951034155 Mon Sep 17 00:00:00 2001 From: repair Date: Mon, 22 Jan 2024 10:42:39 +0100 Subject: [PATCH 277/303] feat: trigger on release, syntax fix for build script with docker image --- .github/workflows/debian-release.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/debian-release.yml b/.github/workflows/debian-release.yml index a66acb91..157aeeb0 100644 --- a/.github/workflows/debian-release.yml +++ b/.github/workflows/debian-release.yml @@ -1,7 +1,7 @@ name: debian-release on: - push: + release: paths-ignore: - 'docs_src/**' - 'README.md' @@ -9,6 +9,7 @@ on: - 'book.toml' - 'CONTRIBUTING.md' tags: [ 'v*.*.*', 'dev*.*.*' ] + types: [published] env: VERSION: ${{ vars.GITHUB_REF_NAME }} @@ -28,4 +29,4 @@ jobs: ./build.sh -v {{ $VERSION }} - name: Build package with version tag and Debian 12 Bookworm run: | - $IMAGE="debian:bookworm-slim" ./build.sh -v {{ $VERSION }} + ./build.sh -i debian:bookworm-slim -v {{ $VERSION }} From 5e9867835b282f2ffb4378f74ec3b3b43f6da295 Mon Sep 17 00:00:00 2001 From: repair Date: Mon, 22 Jan 2024 17:03:57 +0100 Subject: [PATCH 278/303] ci: added downlad and install scaphandre jobs in containers --- .github/workflows/debian-release.yml | 91 +++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 9 deletions(-) diff --git a/.github/workflows/debian-release.yml b/.github/workflows/debian-release.yml index 157aeeb0..3bbf759d 100644 --- a/.github/workflows/debian-release.yml +++ b/.github/workflows/debian-release.yml @@ -1,7 +1,7 @@ -name: debian-release +name: Build Debian package on release on: - release: + push: paths-ignore: - 'docs_src/**' - 'README.md' @@ -9,24 +9,97 @@ on: - 'book.toml' - 'CONTRIBUTING.md' tags: [ 'v*.*.*', 'dev*.*.*' ] - types: [published] - -env: - VERSION: ${{ vars.GITHUB_REF_NAME }} jobs: create_debian_pkg_with_tag: name: Create Debian package associated to version tag runs-on: ubuntu-latest + outputs: + deb11output: ${{ steps.deb11pkgname.outputs.deb11pkg }} + deb12output: ${{ steps.deb12pkgname.outputs.deb12pkg }} steps: + - name: Install s3cmd + run: sudo apt install python3-pip -y && sudo pip3 install s3cmd awxkit + - name: Checkout scaphandre repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Get latest tag of scaphandre repository + id: latest-scaphandre-tag + uses: "WyriHaximus/github-action-get-previous-tag@v1.3.0" + with: + fallback: dev0.5.18 - name: Checkout scaphandre-debian repository uses: actions/checkout@v4 with: - repository: 'barnumbirr/scaphandre-debian' + repository: hubblo-org/scaphandre-debian - name: Build package with version tag and Debian 11 Bullseye run : | - ./build.sh -v {{ $VERSION }} + ./build.sh -v ${{ steps.latest-scaphandre-tag.outputs.tag }} + - name: Modify name of package to include tag version for Debian 11 + id: deb11pkgname + run: | + cd target + PKG_NAME=$(ls | sed "s/\([0-9]\+\.\)\{2\}[0-9]\+\-[0-9]\+\?/${{ steps.latest-scaphandre-tag.outputs.tag }}-deb11/") + mv *.deb $PKG_NAME + echo "deb11pkg=$PKG_NAME" >> "$GITHUB_OUTPUT" + - name: Upload to scw s3 and remove package + run: | + cd target + s3cmd --access_key="${{ secrets.S3_ACCESS_KEY_ID }}" --secret_key="${{ secrets.S3_SECRET_ACCESS_KEY }}" --region="fr-par" --acl-public --host="s3.fr-par.scw.cloud" --host-bucket="%(bucket).s3.fr-par.scw.cloud" put ${{ steps.deb11pkgname.outputs.deb11pkg }} s3://scaphandre/x86_64/ + rm *.deb - name: Build package with version tag and Debian 12 Bookworm run: | - ./build.sh -i debian:bookworm-slim -v {{ $VERSION }} + ./build.sh -i debian:bookworm-slim -v ${{ steps.latest-scaphandre-tag.outputs.tag }} + - name: Modify name of package to include tag version for Debian 12 + id: deb12pkgname + run: | + cd target + PKG_NAME=$(ls | sed "s/\([0-9]\+\.\)\{2\}[0-9]\+\-[0-9]\+\?/${{ steps.latest-scaphandre-tag.outputs.tag }}-deb12/") + mv *.deb $PKG_NAME + echo "deb12pkg=$PKG_NAME" >> "$GITHUB_OUTPUT" + - name: Upload to scw s3 + run: | + cd target + s3cmd --access_key="${{ secrets.S3_ACCESS_KEY_ID }}" --secret_key="${{ secrets.S3_SECRET_ACCESS_KEY }}" --region="fr-par" --acl-public --host="s3.fr-par.scw.cloud" --host-bucket="%(bucket).s3.fr-par.scw.cloud" put ${{ steps.deb12pkgname.outputs.deb12pkg }} s3://scaphandre/x86_64/ + deb11-container-install-scaphandre: + name: Create Debian 11 container and install scaphandre with URL + needs: create_debian_pkg_with_tag + env: + DEB11PKG: ${{ needs.create_debian_pkg_with_tag.outputs.deb11output }} + runs-on: ubuntu-latest + container: + image: debian:buster-slim + steps: + - name: Install dependencies + run: | + apt update + apt install -y curl + - name: Download Debian 11 scaphandre package + run: | + curl -O https://s3.fr-par.scw.cloud/scaphandre/x86_64/${{ env.DEB11PKG }} + - name: Install and show scaphandre version + run: | + apt install -y ./${{ env.DEB11PKG }} + scaphandre -V + deb12-container-install-scaphandre: + name: Create Debian 12 container and install scaphandre with URL + needs: create_debian_pkg_with_tag + runs-on: ubuntu-latest + env: + DEB12PKG: ${{ needs.create_debian_pkg_with_tag.outputs.deb12output }} + container: + image: debian:bookworm-slim + steps: + - name: Install dependencies + run: | + apt update + apt install -y curl + - name: Download Debian 12 scaphandre package + run: | + curl -O https://s3.fr-par.scw.cloud/scaphandre/x86_64/${{ env.DEB12PKG }} + - name: Install and show scaphandre version + run: | + apt install -y ./${{ env.DEB12PKG }} + scaphandre -V From 02779ae96875672afab940174245b6226db8806e Mon Sep 17 00:00:00 2001 From: repair Date: Thu, 25 Jan 2024 16:21:13 +0100 Subject: [PATCH 279/303] ci: build website with scaphandre path_prefix --- .github/workflows/oranda.yml | 4 ++-- oranda.json | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/oranda.yml b/.github/workflows/oranda.yml index 865c3792..0fa63632 100644 --- a/.github/workflows/oranda.yml +++ b/.github/workflows/oranda.yml @@ -30,7 +30,7 @@ on: # If you only want docs to update with releases, disable this one. push: branches: - - main + - dev # Whenever a workflow called "Release" completes, update the docs! # @@ -75,7 +75,7 @@ jobs: - name: Deploy to Github Pages uses: JamesIves/github-pages-deploy-action@v4.4.1 # ONLY if we're on dev (so no PRs or feature branches allowed!) - if: ${{ github.ref == 'refs/heads/main' }} + if: ${{ github.ref == 'refs/heads/dev' }} with: branch: gh-pages # Gotta tell the action where to find oranda's output diff --git a/oranda.json b/oranda.json index 86c70695..17e10eea 100644 --- a/oranda.json +++ b/oranda.json @@ -1,4 +1,7 @@ { + "build": { + "path_prefix": "scaphandre" + }, "styles": { "theme": "light", "favicon": "https://raw.githubusercontent.com/hubblo-org/scaphandre/dev/docs_src/scaphandre.small.cleaned.png" @@ -15,4 +18,4 @@ "preferred_funding": "github" } } -} \ No newline at end of file +} From 5152eb3fa2e46d545417ddbbeec82d11d6f25d75 Mon Sep 17 00:00:00 2001 From: repair Date: Thu, 25 Jan 2024 16:42:17 +0100 Subject: [PATCH 280/303] ci: trying artifacts auto to detect assets --- oranda.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/oranda.json b/oranda.json index 17e10eea..0ce9bb15 100644 --- a/oranda.json +++ b/oranda.json @@ -14,6 +14,9 @@ }, "components": { "changelog": true, + "artifacts": { + "auto": true + } "funding": { "preferred_funding": "github" } From a2aa30de9736e307e278de7e90c6489bedd343e2 Mon Sep 17 00:00:00 2001 From: repair Date: Thu, 25 Jan 2024 16:49:36 +0100 Subject: [PATCH 281/303] fix: forgot comma in json --- oranda.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oranda.json b/oranda.json index 0ce9bb15..fb77abd1 100644 --- a/oranda.json +++ b/oranda.json @@ -16,7 +16,7 @@ "changelog": true, "artifacts": { "auto": true - } + }, "funding": { "preferred_funding": "github" } From fa679f242239be3d7755f5fad1ce39836008bc1a Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Wed, 13 Dec 2023 17:29:00 +0100 Subject: [PATCH 282/303] docs: updated doc for redhat --- docs_src/how-to_guides/install-prometheuspush-only-rhel.md | 4 ++-- docs_src/tutorials/installation-linux.md | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs_src/how-to_guides/install-prometheuspush-only-rhel.md b/docs_src/how-to_guides/install-prometheuspush-only-rhel.md index c02bf935..b1eea7a5 100644 --- a/docs_src/how-to_guides/install-prometheuspush-only-rhel.md +++ b/docs_src/how-to_guides/install-prometheuspush-only-rhel.md @@ -5,8 +5,8 @@ Scaphandre can be compiled with a limited set of features. You have the choice to only install Scaphandre with prometheus-push exporter (alongside with stdout and json exporters, which might be useful locally). RPM packages containing only those features are provided for RHEL 8 and 9 : -- [RPM package for RHEL8](https://scaphandre.s3.fr-par.scw.cloud/x86_64/scaphandre-prometheuspush-dev0.5.10-1.el8.x86_64.rpm) -- [RPM package for RHEL9](https://scaphandre.s3.fr-par.scw.cloud/x86_64/scaphandre-prometheuspush-dev0.5.10-1.el9.x86_64.rpm) +- [RPM package for RHEL8](https://scaphandre.s3.fr-par.scw.cloud/x86_64/scaphandre-prometheuspush-dev0.5.18-1.el8.x86_64.rpm) +- [RPM package for RHEL9](https://scaphandre.s3.fr-par.scw.cloud/x86_64/scaphandre-prometheuspush-dev0.5.18-1.el9.x86_64.rpm) You can download it and install it just providing the right URL to dnf : diff --git a/docs_src/tutorials/installation-linux.md b/docs_src/tutorials/installation-linux.md index 074d1f59..850313aa 100644 --- a/docs_src/tutorials/installation-linux.md +++ b/docs_src/tutorials/installation-linux.md @@ -16,8 +16,9 @@ Here are some other ways to install scaphandre depending on your context: - [quickly try the project with docker-compose/docker stack](docker-compose.md) - [run scaphandre on kubernetes](kubernetes.md) +- [run scaphandre on RHEL, with prometheus-push mode](../how-to_guides/install-prometheuspush-only-rhel.md) -Brave contributors work on system packages, please have a try and/or contribute to: +Kudos to contributors who work on system packages, please have a try and/or contribute to: - [Debian package](https://github.com/barnumbirr/scaphandre-debian), maintainer: @barnumbirr - [NixOS package](https://github.com/mmai/scaphandre-flake), maintainer: @mmai From 03e05c89e4a63441a658c42bf3c81303a8996554 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Fri, 26 Jan 2024 19:33:13 +0100 Subject: [PATCH 283/303] style: clippy --- src/exporters/qemu.rs | 2 +- src/sensors/utils.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/exporters/qemu.rs b/src/exporters/qemu.rs index de3355e5..5ed27073 100644 --- a/src/exporters/qemu.rs +++ b/src/exporters/qemu.rs @@ -137,7 +137,7 @@ impl QemuExporter { trace!("Got {} processes to filter.", processes.len()); for vecp in processes.iter() { if !vecp.is_empty() { - if let Some(pr) = vecp.get(0) { + if let Some(pr) = vecp.first() { if let Some(res) = pr .process .cmdline diff --git a/src/sensors/utils.rs b/src/sensors/utils.rs index 1827d054..f3b5de7f 100644 --- a/src/sensors/utils.rs +++ b/src/sensors/utils.rs @@ -449,12 +449,12 @@ impl ProcessTracker { ) -> HashMap { let mut result = self.procs.iter().filter( // get all processes that have process records - |x| !x.is_empty() && x.get(0).unwrap().process.pid == pid, + |x| !x.is_empty() && x.first().unwrap().process.pid == pid, ); let process = result.next().unwrap(); let mut description = HashMap::new(); let regex_clean_container_id = Regex::new("[[:alnum:]]{12,}").unwrap(); - if let Some(_p) = process.get(0) { + if let Some(_p) = process.first() { // if we have the cgroups data from the original process struct if let Ok(procfs_process) = procfs::process::Process::new(pid.to_string().parse::().unwrap()) @@ -643,7 +643,7 @@ impl ProcessTracker { .iter() .filter(|x| !x.is_empty() && x.get(0).unwrap().process.pid == pid); let process = result.next().unwrap(); - if let Some(p) = process.get(0) { + if let Some(p) = process.first() { let cmdline_request = p.process.cmdline(self); if let Ok(mut cmdline_vec) = cmdline_request { let mut cmdline = String::from(""); From f0fbd3e351e48a5324164258890f260e92a6f493 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Fri, 26 Jan 2024 19:36:30 +0100 Subject: [PATCH 284/303] style: clippy --- src/sensors/utils.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sensors/utils.rs b/src/sensors/utils.rs index f3b5de7f..ba80b28d 100644 --- a/src/sensors/utils.rs +++ b/src/sensors/utils.rs @@ -324,7 +324,7 @@ impl ProcessTracker { // check if the previous records in the vector are from the same process // (if the process with that pid is not a new one) and if so, drop it for a new one if !vector.is_empty() - && process_record.process.comm != vector.get(0).unwrap().process.comm + && process_record.process.comm != vector.first().unwrap().process.comm { *vector = vec![]; } @@ -627,13 +627,13 @@ impl ProcessTracker { let mut result = self .procs .iter() - .filter(|x| !x.is_empty() && x.get(0).unwrap().process.pid == pid); + .filter(|x| !x.is_empty() && x.first().unwrap().process.pid == pid); let process = result.next().unwrap(); if result.next().is_some() { panic!("Found two vectors of processes with the same id, maintainers should fix this."); } debug!("End of get process name."); - process.get(0).unwrap().process.comm.clone() + process.first().unwrap().process.comm.clone() } /// Returns the cmdline string associated to a PID @@ -641,7 +641,7 @@ impl ProcessTracker { let mut result = self .procs .iter() - .filter(|x| !x.is_empty() && x.get(0).unwrap().process.pid == pid); + .filter(|x| !x.is_empty() && x.first().unwrap().process.pid == pid); let process = result.next().unwrap(); if let Some(p) = process.first() { let cmdline_request = p.process.cmdline(self); From 7739309840b8bc251c28c886511a91de1a1f2dac Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Sat, 27 Jan 2024 11:14:04 +0100 Subject: [PATCH 285/303] fix: removed useless check fails in CI + style --- src/sensors/msr_rapl.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/sensors/msr_rapl.rs b/src/sensors/msr_rapl.rs index 8b212f1b..33271742 100644 --- a/src/sensors/msr_rapl.rs +++ b/src/sensors/msr_rapl.rs @@ -391,9 +391,6 @@ impl Sensor for MsrRAPLSensor { panic!("Could'nt get cpuid data."); } } - if logical_cpus_from_cpuid <= 1 { - panic!("CpuID data is likely to be wrong."); - } let mut i: u16 = 0; let mut no_more_sockets = false; debug!("Entering ProcessorGroup {}", group_id); From a5cfb797c35e3e325f252ded412cff470e754746 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Sat, 27 Jan 2024 11:38:08 +0100 Subject: [PATCH 286/303] docs: oranda config for artifacts --- oranda.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/oranda.json b/oranda.json index fb77abd1..879d3f35 100644 --- a/oranda.json +++ b/oranda.json @@ -15,10 +15,19 @@ "components": { "changelog": true, "artifacts": { - "auto": true + "auto": true, + "cargo_dist": false }, "funding": { "preferred_funding": "github" + }, + "package_managers": { + "preferred": { + "crates.io": "cargo install scaphandre" + }, + "additional": { + "binstall": "cargo binstall scaphandre" + } } } } From 7aa78686dbd43905514c54c721b95edce56e939a Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Sat, 27 Jan 2024 11:39:35 +0100 Subject: [PATCH 287/303] docs: oranda config for artifacts --- oranda.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/oranda.json b/oranda.json index 879d3f35..56f659a0 100644 --- a/oranda.json +++ b/oranda.json @@ -16,18 +16,18 @@ "changelog": true, "artifacts": { "auto": true, - "cargo_dist": false + "cargo_dist": false, + "package_managers": { + "preferred": { + "crates.io": "cargo install scaphandre" + }, + "additional": { + "binstall": "cargo binstall scaphandre" + } + } }, "funding": { "preferred_funding": "github" - }, - "package_managers": { - "preferred": { - "crates.io": "cargo install scaphandre" - }, - "additional": { - "binstall": "cargo binstall scaphandre" - } } } } From a3c3d28b281d6a8888d2b331969f3e29fb4d0ed8 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 29 Jan 2024 11:11:13 +0100 Subject: [PATCH 288/303] docs: started to fill changelod for 1.0 --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1a4fa95..52382b4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Please check dev branch. +### Added + +- Per-process resources consumption metrics : + +### Changed + +- `scaph_self_mem_total_program_size`, `scaph_self_mem_resident_set_size` and `scaph_self_mem_shared_resident_size` are replaced by `scaph_self_memory_bytes` and `scaph_self_memory_virtual_bytes`, see https://github.com/hubblo-org/scaphandre/pull/274/files + ## [0.5.0](https://github.com/hubblo-org/scaphandre/releases/tag/v0.5.0) ### Changed From 6d1bae92243a38628c245b93a4f8438234629c20 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 29 Jan 2024 11:17:54 +0100 Subject: [PATCH 289/303] docs: config oranda to get releases and packages --- oranda.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/oranda.json b/oranda.json index 56f659a0..4a7b38a3 100644 --- a/oranda.json +++ b/oranda.json @@ -2,6 +2,9 @@ "build": { "path_prefix": "scaphandre" }, + "project": { + "repository": "https://github.com/hubblo-org/scaphandre" + }, "styles": { "theme": "light", "favicon": "https://raw.githubusercontent.com/hubblo-org/scaphandre/dev/docs_src/scaphandre.small.cleaned.png" From 5ac0f4ce39b706acdbec3ce793856f5930b839a5 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 29 Jan 2024 11:36:47 +0100 Subject: [PATCH 290/303] docs: improved changelog for 1.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52382b4c..42804cd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ Please check dev branch. ### Added -- Per-process resources consumption metrics : +- Per-process resources consumption metrics for disks : `scaph_host_disk_total_bytes`, `scaph_host_disk_available_bytes` - see https://hubblo-org.github.io/scaphandre-documentation/references/metrics.html ### Changed From abd1ef0fcfd21bdd0d9324cb54b3f477bde50ea7 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Tue, 30 Jan 2024 15:38:48 +0100 Subject: [PATCH 291/303] docs: improved changelog for next release 1.0 --- CHANGELOG.md | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42804cd7..8fa73fb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,11 +9,37 @@ Please check dev branch. ### Added -- Per-process resources consumption metrics for disks : `scaph_host_disk_total_bytes`, `scaph_host_disk_available_bytes` - see https://hubblo-org.github.io/scaphandre-documentation/references/metrics.html +- Host resources consumption metrics : scaph_host_swap_total_bytes, scaph_host_swap_free_bytes, scaph_host_memory_free_bytes, scaph_host_memory_available_bytes, scaph_host_memory_total_bytes, scaph_host_disk_total_bytes, scaph_host_disk_available_bytes, scaph_host_cpu_frequency, scaph_host_load_avg_fifteen, scaph_host_load_avg_five, scaph_host_load_avg_one - see https://hubblo-org.github.io/scaphandre-documentation/references/metrics.html for details, https://github.com/hubblo-org/scaphandre/issues/271 and https://github.com/hubblo-org/scaphandre/pull/278 for reference +- Per-process resource consumption metrics : scaph_process_cpu_usage_percentage, scaph_process_memory_bytes, scaph_process_memory_virtual_bytes, scaph_process_disk_total_write_bytes, scaph_process_disk_write_bytes, scaph_process_disk_read_bytes, scaph_process_disk_total_read_bytes - see https://hubblo-org.github.io/scaphandre-documentation/references/metrics.html for details, https://github.com/hubblo-org/scaphandre/issues/141 and https://github.com/hubblo-org/scaphandre/pull/274 for reference +- Added service monitor to helm chart, see https://github.com/hubblo-org/scaphandre/pull/230, thanks @mmadoo +- Added packaging folder with sample systemd services files, see https://github.com/hubblo-org/scaphandre/pull/317 and https://github.com/hubblo-org/scaphandre/issues/261, thanks @jcaesar +- Added prometheus push mode exporter, see https://github.com/hubblo-org/scaphandre/issues/269 +- Added RPM build github action workflow, see https://github.com/hubblo-org/scaphandre/issues/310 +- Added RAPL mmio metric, when the domain is present, see https://github.com/hubblo-org/scaphandre/issues/318 and https://github.com/hubblo-org/scaphandre/pull/329 +- Added specific RAPL PSYS metric, when available, see https://github.com/hubblo-org/scaphandre/issues/316 and https://github.com/hubblo-org/scaphandre/pull/329 +- Filtering per process in JSON exporter, see https://github.com/hubblo-org/scaphandre/issues/216 +- Github action workflow to build Windows EXE installer on each release, see https://github.com/hubblo-org/scaphandre/pull/333 +- Github action workflow to build DEB package on each release, see https://github.com/hubblo-org/scaphandre/pull/352, thanks @bdromard +- Added warning messages when powercap files permissions won't allow Scaphandre to read RAPL data, see https://github.com/hubblo-org/scaphandre/issues/214 ### Changed - `scaph_self_mem_total_program_size`, `scaph_self_mem_resident_set_size` and `scaph_self_mem_shared_resident_size` are replaced by `scaph_self_memory_bytes` and `scaph_self_memory_virtual_bytes`, see https://github.com/hubblo-org/scaphandre/pull/274/files +- Refactored warp10 exporter, see https://github.com/hubblo-org/scaphandre/pull/291 and https://github.com/hubblo-org/scaphandre/issues/105 +- Refactored exporters creation with clap4, see https://github.com/hubblo-org/scaphandre/pull/292, thanks @TheElectronWill +- Default docker-compose sets a privileged container now, otherwise it doesn't work in an apparmor context, see https://github.com/hubblo-org/scaphandre/issues/135 and https://github.com/hubblo-org/scaphandre/commit/a1a06ea280b8e66067b2c3b73ac08a377604eb61 +- Moved from procfs, to sysinfo, see https://github.com/hubblo-org/scaphandre/issues/267 + +### Fixed + +- Fixed doc broken links, see https://github.com/hubblo-org/scaphandre/pull/259 and https://github.com/hubblo-org/scaphandre/issues/288, thanks @homersimpsons +- Now works on more than 1 vcpu Qemu/KVM virtual machines, see https://github.com/hubblo-org/scaphandre/issues/133 and https://github.com/hubblo-org/scaphandre/pull/207, thanks @tawalaya +- Fix for Kubernetes, don't create PSP if version of kubernetes is above 1.25, see https://github.com/hubblo-org/scaphandre/pull/250, thanks @rossf7 +- Fixed bug in --containers flag, see https://github.com/hubblo-org/scaphandre/pull/326, thanks rossf7 +- Fix on qemu exporter, see https://github.com/hubblo-org/scaphandre/issues/260 +- Fixed panics on regex filters, see https://github.com/hubblo-org/scaphandre/issues/295 +- Fixed invalid escape sequence in Prometheus exporter, see https://github.com/hubblo-org/scaphandre/issues/204, thanks @demeringo +- Removed broken python bindings, until it is fixed, see https://github.com/hubblo-org/scaphandre/pull/315 and https://github.com/hubblo-org/scaphandre/issues/296 ## [0.5.0](https://github.com/hubblo-org/scaphandre/releases/tag/v0.5.0) From fd4244e98af532a20f2ba7783c3ec759a201ff1d Mon Sep 17 00:00:00 2001 From: bpetit Date: Tue, 30 Jan 2024 16:30:26 +0100 Subject: [PATCH 292/303] ci: disabling tests that can't run on a GH virtual machine style: fmt ci: disabling tests that can't run on a GH virtual machine ci: fix ci: fix ci: fix ci: fix ci: fix ci: fix ci: fixing path for devcon.exe in exe build workflow ci: fix ci: fix ci: fix ci: fix --- .github/workflows/build-and-test.yml | 4 +- .../workflows/exe-release-prometheuspush.yml | 17 +-- packaging/windows/installer.iss | 5 +- src/sensors/msr_rapl.rs | 101 ++++++++++-------- 4 files changed, 61 insertions(+), 66 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index c75ebcf7..eb7a4267 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -171,7 +171,7 @@ jobs: rustup toolchain install stable-x86_64-pc-windows-msvc - name: Tests run: | - cargo test --no-default-features --features "prometheus json riemann" + cargo test --no-default-features --features "prometheus prometheuspush json riemann" exporters - name: Build (debug mode) run: | - cargo build --no-default-features --features "prometheus json riemann" + cargo build --no-default-features --features "prometheus prometheuspush json riemann" diff --git a/.github/workflows/exe-release-prometheuspush.yml b/.github/workflows/exe-release-prometheuspush.yml index aded447c..d7ffd4f7 100644 --- a/.github/workflows/exe-release-prometheuspush.yml +++ b/.github/workflows/exe-release-prometheuspush.yml @@ -12,7 +12,7 @@ on: branches: [ '336-proper-handling-of-windows-service-management' ] env: - WRD_VERSION: v0.0.3 + WRD_VERSION: v0.0.4 WRD_BASE_URL: https://github.com/hubblo-org/windows-rapl-driver/releases/download jobs: @@ -35,7 +35,6 @@ jobs: run: | $dest = "DriverLoader.exe" $url = "${{ env.WRD_BASE_URL }}/${{ env.WRD_VERSION }}/DriverLoader.exe" - echo ($url -replace '"', "") Invoke-WebRequest -Uri ($url -replace '"', "") -OutFile $dest $dest = "ScaphandreDrv.cat" $url = "${{ env.WRD_BASE_URL }}/${{ env.WRD_VERSION }}/ScaphandreDrv.cat" @@ -46,16 +45,6 @@ jobs: $dest = "ScaphandreDrv.inf" $url = "${{ env.WRD_BASE_URL }}/${{ env.WRD_VERSION }}/ScaphandreDrv.inf" Invoke-WebRequest -Uri ($url -replace '"', "") -OutFile $dest - $dest = "ScaphandreDrvTest.cer" - $url = "${{ env.WRD_BASE_URL }}/${{ env.WRD_VERSION }}/ScaphandreDrvTest.cer" - Invoke-WebRequest -Uri ($url -replace '"', "") -OutFile $dest - $dest = "devcon.exe" - $url = "${{ env.WRD_BASE_URL }}/${{ env.WRD_VERSION }}/devcon.exe" - Invoke-WebRequest -Uri ($url -replace '"', "") -OutFile $dest - $dest = "certmgr.exe" - $url = "${{ env.WRD_BASE_URL }}/${{ env.WRD_VERSION }}/certmgr.exe" - Invoke-WebRequest -Uri ($url -replace '"', "") -OutFile $dest - ls - name: Install Rustup uses: crazy-max/ghaction-chocolatey@v2 with: @@ -72,10 +61,10 @@ jobs: - name: Upload artifact #Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force run: | Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted - Install-Module -Confirm:$False -Name AWS.Tools.Installer + Install-Module -Confirm:$False -Name AWS.Tools.Installer Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine Import-Module AWS.Tools.Installer - Install-AWSToolsModule AWS.Tools.EC2,AWS.Tools.S3 -CleanUp -Confirm:$False + Install-AWSToolsModule AWS.Tools.EC2,AWS.Tools.S3 -CleanUp -Confirm:$False -AllowClobber Set-AWSCredential -AccessKey ${{ secrets.S3_ACCESS_KEY_ID }} -SecretKey ${{ secrets.S3_SECRET_ACCESS_KEY }} -StoreAs default mv packaging/windows/Output/scaphandre_installer.exe scaphandre_${{ github.ref_name }}_installer.exe $clientconfig=@{ diff --git a/packaging/windows/installer.iss b/packaging/windows/installer.iss index bc5287ac..4817f2a9 100644 --- a/packaging/windows/installer.iss +++ b/packaging/windows/installer.iss @@ -45,11 +45,10 @@ Source: "../../ScaphandreDrv.sys"; DestDir: "{app}"; Source: "../../ScaphandreDrv.cat"; DestDir: "{app}"; ; Source: "../../ScaphandreDrv.cat"; DestDir: "{#SystemFolder}"; ; Source: "../../ScaphandreDrv.cat"; DestDir: "{#System64Folder}"; -Source: "../../devcon.exe"; DestDir: "{app}"; Flags: ignoreversion -Source: "../../certmgr.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "C:\Program Files (x86)\Windows Kits\10\Tools\10.0.22621.0\x64\devcon.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\certmgr.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "../../README.md"; DestDir: "{app}"; Flags: ignoreversion Source: "../../CHANGELOG.md"; DestDir: "{app}"; Flags: ignoreversion -Source: "../../ScaphandreDrvTest.cer"; DestDir: "{app}"; Flags: ignoreversion ; NOTE: Don't use "Flags: ignoreversion" on any shared system files [Icons] diff --git a/src/sensors/msr_rapl.rs b/src/sensors/msr_rapl.rs index e4533006..36dd1395 100644 --- a/src/sensors/msr_rapl.rs +++ b/src/sensors/msr_rapl.rs @@ -756,58 +756,65 @@ pub unsafe fn get_msr_value( error!("Error was : {:?}", GetLastError()); } debug!("Core ID requested to the driver : {}", core_id); - match get_handle(sensor_data.get("DRIVER_NAME").unwrap()) { - Ok(device) => { - let mut msr_result: u64 = 0; - let ptr_result = &mut msr_result as *mut u64; - debug!("msr_addr: {:b}", msr_addr); - debug!("core_id: {:x} {:b}", (core_id as u64), (core_id as u64)); - debug!("core_id: {:b}", ((core_id as u64) << 32)); - let src = ((core_id as u64) << 32) | msr_addr; //let src = ((core_id as u64) << 32) | msr_addr; - let ptr = &src as *const u64; - - debug!("src: {:x}", src); - debug!("src: {:b}", src); - debug!("*ptr: {:b}", *ptr); - //warn!("*ptr: {}", *ptr); - //warn!("*ptr: {:b}", *ptr); - - match send_request( - device, - MSR_PKG_ENERGY_STATUS, - ptr, - 8, - ptr_result, - size_of::(), - ) { - Ok(_res) => { - close_handle(device); - - let energy_unit = sensor_data - .get("ENERGY_UNIT") - .unwrap() - .parse::() - .unwrap(); - let current_value = - MsrRAPLSensor::extract_rapl_current_power(msr_result, energy_unit); - debug!("current_value: {}", current_value); - - Ok(Record { - timestamp: current_system_time_since_epoch(), - unit: super::units::Unit::MicroJoule, - value: current_value, - }) + match sensor_data.get("DRIVER_NAME") { + Some(driver) => { + match get_handle(driver) { + Ok(device) => { + let mut msr_result: u64 = 0; + let ptr_result = &mut msr_result as *mut u64; + debug!("msr_addr: {:b}", msr_addr); + debug!("core_id: {:x} {:b}", (core_id as u64), (core_id as u64)); + debug!("core_id: {:b}", ((core_id as u64) << 32)); + let src = ((core_id as u64) << 32) | msr_addr; //let src = ((core_id as u64) << 32) | msr_addr; + let ptr = &src as *const u64; + + debug!("src: {:x}", src); + debug!("src: {:b}", src); + debug!("*ptr: {:b}", *ptr); + //warn!("*ptr: {}", *ptr); + //warn!("*ptr: {:b}", *ptr); + + match send_request( + device, + MSR_PKG_ENERGY_STATUS, + ptr, + 8, + ptr_result, + size_of::(), + ) { + Ok(_res) => { + close_handle(device); + + let energy_unit = sensor_data + .get("ENERGY_UNIT") + .unwrap() + .parse::() + .unwrap(); + let current_value = + MsrRAPLSensor::extract_rapl_current_power(msr_result, energy_unit); + debug!("current_value: {}", current_value); + + Ok(Record { + timestamp: current_system_time_since_epoch(), + unit: super::units::Unit::MicroJoule, + value: current_value, + }) + } + Err(e) => { + info!("Failed to get data from send_request: {:?}", e); + close_handle(device); + Err(format!("Failed to get data from send_request: {:?}", e)) + } + } } Err(e) => { - info!("Failed to get data from send_request: {:?}", e); - close_handle(device); - Err(format!("Failed to get data from send_request: {:?}", e)) + error!("Couldn't get driver handle : {:?}", e); + Err(format!("Couldn't get driver handle : {:?}", e)) } } } - Err(e) => { - error!("Couldn't get driver handle : {:?}", e); - Err(format!("Couldn't get driver handle : {:?}", e)) + None => { + panic!("DRIVER_NAME not set."); } } } From 75b1de64e7463b65b802ed6ea4841b1fa57bec41 Mon Sep 17 00:00:00 2001 From: bpetit Date: Fri, 9 Feb 2024 17:52:14 +0100 Subject: [PATCH 293/303] ci: fix --- .github/workflows/build-and-test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index eb7a4267..db6d6261 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -10,6 +10,7 @@ on: - 'CITATION' - 'book.toml' - 'CONTRIBUTING.md' + - '.github/workflows/exe-release-prometheuspush.yml' pull_request: branches: [ main, dev ] paths-ignore: @@ -18,6 +19,7 @@ on: - 'CHANGELOG.md' - 'CITATION' - 'book.toml' + - '.github/workflows/exe-release-prometheuspush.yml' env: CARGO_TERM_COLOR: always From b3b027e6d692a6c187be1029c0cb5bd435f6c02d Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 12 Feb 2024 12:27:10 +0100 Subject: [PATCH 294/303] docs: improving explanations on host level vs process level metriucs --- CHANGELOG.md | 1 + docs_src/SUMMARY.md | 1 + docs_src/explanations/host_metrics.md | 22 ++++++++++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 docs_src/explanations/host_metrics.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fa73fb9..705cb42b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ Please check dev branch. ### Changed +- Global power metrics have changed and could give higher numbers than previously. Please have a look at the [documentation](https://hubblo-org.github.io/scaphandre-documentation/explanations/host_metrics.html). - `scaph_self_mem_total_program_size`, `scaph_self_mem_resident_set_size` and `scaph_self_mem_shared_resident_size` are replaced by `scaph_self_memory_bytes` and `scaph_self_memory_virtual_bytes`, see https://github.com/hubblo-org/scaphandre/pull/274/files - Refactored warp10 exporter, see https://github.com/hubblo-org/scaphandre/pull/291 and https://github.com/hubblo-org/scaphandre/issues/105 - Refactored exporters creation with clap4, see https://github.com/hubblo-org/scaphandre/pull/292, thanks @TheElectronWill diff --git a/docs_src/SUMMARY.md b/docs_src/SUMMARY.md index c0167811..fe343879 100644 --- a/docs_src/SUMMARY.md +++ b/docs_src/SUMMARY.md @@ -18,6 +18,7 @@ # Explanations +- [Explanations about host level power and energy metrics](explanations/host_metrics.md) - [How scaphandre computes per process power consumption](explanations/how-scaph-computes-per-process-power-consumption.md) - [Internal structure](explanations/internal-structure.md) - [About containers](explanations/about-containers.md) diff --git a/docs_src/explanations/host_metrics.md b/docs_src/explanations/host_metrics.md new file mode 100644 index 00000000..ac3895e8 --- /dev/null +++ b/docs_src/explanations/host_metrics.md @@ -0,0 +1,22 @@ +# Explanations about host level power and energy metrics. + +This is true starting **from Scaphandre >= 1.0.** + +There are several [metrics](../references/metrics.md) available at the host level in Scaphandre: +- `scaph_host_power_microwatts` : always returned, computed from Record structs made from `scaph_host_energy_microjoules` metric +- `scaph_host_energy_microjoules` : always returned, either one value or a sum of values coming directly from RAPL counters (`energy_uj` files or direct read from an MSR) +- `scaph_host_rapl_psys_microjoules` : is available only when the PSYS [RAPL domain](explanations/rapl-domains.md) is available on the machine. + +In addition to those metrics, you might want to build, on your time series database, the sum of process_ metrics to have a view of the weight of all processes on the host power. Using Prometheus, it would look like: `sum(scaph_process_power_consumption_microwatts{hostname="$hostname"}) / 1000000`, to get it in Watts. + +Let's explain the relationship between those metrics, and what you could expect. + +`host_power` metric will return : +1. If PSYS domain is available, a computed power coming from PSYS energy records +2. If not, a computed power which is the sum of per-socket power (PKG RAPL domain) + DRAM RAPL domain power + +Briefly explained (see [RAPL domains](explanations/rapl-domains.md) for detailled explanations), PSYS covers most components on the machine ("all components connected to the SoC / motherboard" according to most documentations), so we return this wider ranged metric when available. If not we use a combination of PKG domain, that includes CPU and integrated GPU power, and DRAM domain, that includes memory power. The first options gives higher figures than the second, for now. + +Suming the power of all processes, if the machine is mostly IDLE, you'll get a tiny percentage of the host machine, most likely. The difference between host power and the sum of processes power can be accounted as "power due to IDLE activity", in other words the power your machine demands for "doing nothing". The higher this difference on a long period of time (better seen as a graph), the higher chance that there is room for improvement in moving the workloads to another machine and shut the current machine down (and make it available for another project or to another organization to prevent from buying a new machine). + +**Warning:** that being said, the way per-process power is computed is still biased and shall be improved in the following versions of Scaphandre. For now, the main key for allocation is CPU time. As host level power metrics include power usage of more and more components on the machine (work in progress) this allocation key will be more and more inaccurate. Future versions of this allocation model should include keys regarding the activity of other components than CPU. Enabling a better set of allocation keys for per-process power is part of the [roadmap](https://github.com/hubblo-org/scaphandre/projects/1). From c4136a5a17b76f35084848ea271b79dfc6d3fb00 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 12 Feb 2024 12:50:47 +0100 Subject: [PATCH 295/303] ci: disabling build and test workflow when only a markdown file is touched --- .github/workflows/build-and-test.yml | 2 + .github/workflows/windows-release.yml | 111 ++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 .github/workflows/windows-release.yml diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index db6d6261..31aaa452 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -11,6 +11,7 @@ on: - 'book.toml' - 'CONTRIBUTING.md' - '.github/workflows/exe-release-prometheuspush.yml' + - '*.md' pull_request: branches: [ main, dev ] paths-ignore: @@ -20,6 +21,7 @@ on: - 'CITATION' - 'book.toml' - '.github/workflows/exe-release-prometheuspush.yml' + - '*.md' env: CARGO_TERM_COLOR: always diff --git a/.github/workflows/windows-release.yml b/.github/workflows/windows-release.yml new file mode 100644 index 00000000..aa287605 --- /dev/null +++ b/.github/workflows/windows-release.yml @@ -0,0 +1,111 @@ +name: Build RPM package + +on: + push: + paths-ignore: + - 'docs_src/**' + - 'README.md' + - 'CITATION' + - 'book.toml' + - 'CONTRIBUTING.md' + tags: [ 'v*.*.*', 'dev*.*.*' ] + +jobs: + build_rpm: + name: Build RPM package + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - uses: Swatinem/rust-cache@v2 + with: + # The prefix cache key, this can be changed to start a new cache manually. + # default: "v0-rust" + prefix-key: "" + + # A cache key that is used instead of the automatic `job`-based key, + # and is stable over multiple jobs. + # default: empty + shared-key: "" + + # An additional cache key that is added alongside the automatic `job`-based + # cache key and can be used to further differentiate jobs. + # default: empty + key: "" + + # A whitespace separated list of env-var *prefixes* who's value contributes + # to the environment cache key. + # The env-vars are matched by *prefix*, so the default `RUST` var will + # match all of `RUSTC`, `RUSTUP_*`, `RUSTFLAGS`, `RUSTDOC_*`, etc. + # default: "CARGO CC CFLAGS CXX CMAKE RUST" + env-vars: "" + + # The cargo workspaces and target directory configuration. + # These entries are separated by newlines and have the form + # `$workspace -> $target`. The `$target` part is treated as a directory + # relative to the `$workspace` and defaults to "target" if not explicitly given. + # default: ". -> target" + workspaces: "" + + # Additional non workspace directories to be cached, separated by newlines. + cache-directories: "" + + # Determines whether workspace `target` directories are cached. + # If `false`, only the cargo registry will be cached. + # default: "true" + cache-targets: "" + + # Determines if the cache should be saved even when the workflow has failed. + # default: "false" + cache-on-failure: "" + + # Determines which crates are cached. + # If `true` all crates will be cached, otherwise only dependent crates will be cached. + # Useful if additional crates are used for CI tooling. + # default: "false" + cache-all-crates: "" + + # Determiners whether the cache should be saved. + # If `false`, the cache is only restored. + # Useful for jobs where the matrix is additive e.g. additional Cargo features. + # default: "true" + save-if: "" + - name: Install s3cmd + run: sudo apt install python3-pip -y && sudo pip3 install s3cmd awxkit + - name: Get tag + id: tag + uses: devops-actions/action-get-tag@v1.0.2 + with: + strip_v: true # Optional: Remove 'v' character from version + default: "v0.0.0" # Optional: Default version when tag not found + - name: Override version + run: "sed -i 's/Version: .*/Version: ${{steps.tag.outputs.tag}}/' packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec" + - name: Debug + run: grep Version packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec + - name: Extract release notes + id: extract-release-notes + uses: ffurrer2/extract-release-notes@v1 + #- name: Display release notes + # run: "echo ${{ steps.extract-release-notes.outputs.release_notes }}" + - name: Edit changelog #TODO commit and push to increase changelog + run: date=$(date "+%a %b %d %Y - "); sed -i "/%changelog/ a * ${date}${{steps.tag.outputs.tag}}/" packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec + - name: Edit changelog + run: echo " Packaging for version ${{steps.tag.outputs.tag}}" >> packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec + - name: build RPM package + id: rpm + uses: bpetit/rpmbuild@master + with: + spec_file: "packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec" + - name: Upload to scw s3 + run: | + s3cmd --access_key="${{ secrets.S3_ACCESS_KEY_ID }}" --secret_key="${{ secrets.S3_SECRET_ACCESS_KEY }}" --region="fr-par" --acl-public --host="s3.fr-par.scw.cloud" --host-bucket="%(bucket).s3.fr-par.scw.cloud" put --recursive ${{ steps.rpm.outputs.rpm_dir_path }} s3://scaphandre/ + - name: Log on AWX + id: login + run: | + RAW_RESULT=$(awx --conf.host "${{ secrets.AWX_HOST }}" --conf.username "${{ secrets.AWX_PUBLIC_USER }}" --conf.password "${{ secrets.AWX_PASSWORD }}" login) + export AWX_TOKEN=$(echo $RAW_RESULT | jq .token | tr -d '"') + echo "awx_token=${AWX_TOKEN}" >> $GITHUB_OUTPUT + - name: Install and test RPM package + id: rpmtest + run: | + awx --conf.token ${{ steps.login.outputs.awx_token }} --conf.host ${{ secrets.AWX_HOST }} job_templates launch --extra_vars="{\"github_repository\":\"${GITHUB_REPOSITORY}\",\"github_actor\":\"${GITHUB_ACTOR}\",\"github_workflow\":\"${GITHUB_WORKFLOW}\",\"github_workspace\":\"${GITHUB_WORKSPACE}\",\"github_event_name\":\"${GITHUB_EVENT_NAME}\",\"github_event_path\":\"${GITHUB_EVENT_PATH}\",\"github_sha\":\"${GITHUB_SHA}\",\"github_ref\":\"${GITHUB_REF}\",\"github_head_ref\":\"${GITHUB_HEAD_REF}\",\"github_base_ref\":\"${GITHUB_BASE_REF}\",\"github_server_url\":\"${GITHUB_SERVER_URL}\",\"github_rpm_url\":\"https://scaphandre.s3.fr-par.scw.cloud/x86_64/scaphandre-${{steps.tag.outputs.tag}}-1.el9.x86_64.rpm\"}" 19 --monitor \ No newline at end of file From aeaee5934b92f4f33e1ab4cc38fe7b9ada4a310d Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 12 Feb 2024 12:51:05 +0100 Subject: [PATCH 296/303] docs: updating changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 705cb42b..1ab181ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Please check dev branch. +## [1.0.0](https://github.com/hubblo-org/scaphandre/releases/tag/v1.0.0) + ### Added - Host resources consumption metrics : scaph_host_swap_total_bytes, scaph_host_swap_free_bytes, scaph_host_memory_free_bytes, scaph_host_memory_available_bytes, scaph_host_memory_total_bytes, scaph_host_disk_total_bytes, scaph_host_disk_available_bytes, scaph_host_cpu_frequency, scaph_host_load_avg_fifteen, scaph_host_load_avg_five, scaph_host_load_avg_one - see https://hubblo-org.github.io/scaphandre-documentation/references/metrics.html for details, https://github.com/hubblo-org/scaphandre/issues/271 and https://github.com/hubblo-org/scaphandre/pull/278 for reference - Per-process resource consumption metrics : scaph_process_cpu_usage_percentage, scaph_process_memory_bytes, scaph_process_memory_virtual_bytes, scaph_process_disk_total_write_bytes, scaph_process_disk_write_bytes, scaph_process_disk_read_bytes, scaph_process_disk_total_read_bytes - see https://hubblo-org.github.io/scaphandre-documentation/references/metrics.html for details, https://github.com/hubblo-org/scaphandre/issues/141 and https://github.com/hubblo-org/scaphandre/pull/274 for reference - Added service monitor to helm chart, see https://github.com/hubblo-org/scaphandre/pull/230, thanks @mmadoo -- Added packaging folder with sample systemd services files, see https://github.com/hubblo-org/scaphandre/pull/317 and https://github.com/hubblo-org/scaphandre/issues/261, thanks @jcaesar +- Added packaging folder with sample systemd services files, see https://github.com/hubblo-org/scaphandre/pull/317 and https://github.com/hubblo-org/scaphandre/issues/261, thanks @jcaesar - Added prometheus push mode exporter, see https://github.com/hubblo-org/scaphandre/issues/269 - Added RPM build github action workflow, see https://github.com/hubblo-org/scaphandre/issues/310 - Added RAPL mmio metric, when the domain is present, see https://github.com/hubblo-org/scaphandre/issues/318 and https://github.com/hubblo-org/scaphandre/pull/329 From 9f8e8fb39819c4af302cec857e5f9870acffc3f4 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 12 Feb 2024 12:58:21 +0100 Subject: [PATCH 297/303] ci: adding prometheus pull only build for windows --- .../workflows/exe-release-prometheuspush.yml | 4 +- .github/workflows/exe-release.yml | 74 ++++++++++++ .github/workflows/windows-release.yml | 111 ------------------ 3 files changed, 76 insertions(+), 113 deletions(-) create mode 100644 .github/workflows/exe-release.yml delete mode 100644 .github/workflows/windows-release.yml diff --git a/.github/workflows/exe-release-prometheuspush.yml b/.github/workflows/exe-release-prometheuspush.yml index d7ffd4f7..dc76afb9 100644 --- a/.github/workflows/exe-release-prometheuspush.yml +++ b/.github/workflows/exe-release-prometheuspush.yml @@ -66,9 +66,9 @@ jobs: Import-Module AWS.Tools.Installer Install-AWSToolsModule AWS.Tools.EC2,AWS.Tools.S3 -CleanUp -Confirm:$False -AllowClobber Set-AWSCredential -AccessKey ${{ secrets.S3_ACCESS_KEY_ID }} -SecretKey ${{ secrets.S3_SECRET_ACCESS_KEY }} -StoreAs default - mv packaging/windows/Output/scaphandre_installer.exe scaphandre_${{ github.ref_name }}_installer.exe + mv packaging/windows/Output/scaphandre_installer.exe scaphandre_${{ github.ref_name }}_prometheuspush_installer.exe $clientconfig=@{ SignatureVersion="s3v4" ServiceUrl="https://s3.fr-par.scw.cloud" } - Write-S3Object -EndpointUrl "https://s3.fr-par.scw.cloud" -Region "fr-par" -BucketName "scaphandre" -File scaphandre_${{ github.ref_name }}_installer.exe -key "x86_64/scaphandre_${{ github.ref_name }}_installer.exe" -PublicReadOnly -ClientConfig $clientconfig \ No newline at end of file + Write-S3Object -EndpointUrl "https://s3.fr-par.scw.cloud" -Region "fr-par" -BucketName "scaphandre" -File scaphandre_${{ github.ref_name }}_prometheuspush_installer.exe -key "x86_64/scaphandre_${{ github.ref_name }}_prometheuspush_installer.exe" -PublicReadOnly -ClientConfig $clientconfig \ No newline at end of file diff --git a/.github/workflows/exe-release.yml b/.github/workflows/exe-release.yml new file mode 100644 index 00000000..a83f8955 --- /dev/null +++ b/.github/workflows/exe-release.yml @@ -0,0 +1,74 @@ +name: Build exe installer for windows for prometheus-push only version + +on: + push: + paths-ignore: + - 'docs_src/**' + - 'README.md' + - 'CITATION' + - 'book.toml' + - 'CONTRIBUTING.md' + tags: [ 'v*.*.*', 'dev*.*.*' ] + branches: [ '336-proper-handling-of-windows-service-management' ] + +env: + WRD_VERSION: v0.0.4 + WRD_BASE_URL: https://github.com/hubblo-org/windows-rapl-driver/releases/download + +jobs: + build_exe_win1011: + name: Build exe installer for windows 10/11/server 2016/server 2019/server 2022 + runs-on: "windows-latest" + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install Innosoft + run: | + $url = "https://jrsoftware.org/download.php/is.exe" + $dest = "is.exe" + Invoke-WebRequest -Uri $url -OutFile $dest + ls + & "D:\a\scaphandre\scaphandre\$dest" /verysilent /suppressmsgbox + ls "C:\Program Files (x86)\Inno Setup 6\" + - name: Get windows-rapl-driver + shell: pwsh + run: | + $dest = "DriverLoader.exe" + $url = "${{ env.WRD_BASE_URL }}/${{ env.WRD_VERSION }}/DriverLoader.exe" + Invoke-WebRequest -Uri ($url -replace '"', "") -OutFile $dest + $dest = "ScaphandreDrv.cat" + $url = "${{ env.WRD_BASE_URL }}/${{ env.WRD_VERSION }}/ScaphandreDrv.cat" + Invoke-WebRequest -Uri ($url -replace '"', "") -OutFile $dest + $dest = "ScaphandreDrv.sys" + $url = "${{ env.WRD_BASE_URL }}/${{ env.WRD_VERSION }}/ScaphandreDrv.sys" + Invoke-WebRequest -Uri ($url -replace '"', "") -OutFile $dest + $dest = "ScaphandreDrv.inf" + $url = "${{ env.WRD_BASE_URL }}/${{ env.WRD_VERSION }}/ScaphandreDrv.inf" + Invoke-WebRequest -Uri ($url -replace '"', "") -OutFile $dest + - name: Install Rustup + uses: crazy-max/ghaction-chocolatey@v2 + with: + args: install rustup.install --ignore-checksums + - name: Install Rust toolchain + run: | + rustup toolchain install stable-x86_64-pc-windows-msvc + - name: Build Scaphandre + run: | + cargo build --release --no-default-features --features "prometheus json" + - name: Build package + run: | + & "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" packaging/windows/installer.iss + - name: Upload artifact #Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force + run: | + Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted + Install-Module -Confirm:$False -Name AWS.Tools.Installer + Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine + Import-Module AWS.Tools.Installer + Install-AWSToolsModule AWS.Tools.EC2,AWS.Tools.S3 -CleanUp -Confirm:$False -AllowClobber + Set-AWSCredential -AccessKey ${{ secrets.S3_ACCESS_KEY_ID }} -SecretKey ${{ secrets.S3_SECRET_ACCESS_KEY }} -StoreAs default + mv packaging/windows/Output/scaphandre_installer.exe scaphandre_${{ github.ref_name }}_installer.exe + $clientconfig=@{ + SignatureVersion="s3v4" + ServiceUrl="https://s3.fr-par.scw.cloud" + } + Write-S3Object -EndpointUrl "https://s3.fr-par.scw.cloud" -Region "fr-par" -BucketName "scaphandre" -File scaphandre_${{ github.ref_name }}_installer.exe -key "x86_64/scaphandre_${{ github.ref_name }}_installer.exe" -PublicReadOnly -ClientConfig $clientconfig \ No newline at end of file diff --git a/.github/workflows/windows-release.yml b/.github/workflows/windows-release.yml deleted file mode 100644 index aa287605..00000000 --- a/.github/workflows/windows-release.yml +++ /dev/null @@ -1,111 +0,0 @@ -name: Build RPM package - -on: - push: - paths-ignore: - - 'docs_src/**' - - 'README.md' - - 'CITATION' - - 'book.toml' - - 'CONTRIBUTING.md' - tags: [ 'v*.*.*', 'dev*.*.*' ] - -jobs: - build_rpm: - name: Build RPM package - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - uses: Swatinem/rust-cache@v2 - with: - # The prefix cache key, this can be changed to start a new cache manually. - # default: "v0-rust" - prefix-key: "" - - # A cache key that is used instead of the automatic `job`-based key, - # and is stable over multiple jobs. - # default: empty - shared-key: "" - - # An additional cache key that is added alongside the automatic `job`-based - # cache key and can be used to further differentiate jobs. - # default: empty - key: "" - - # A whitespace separated list of env-var *prefixes* who's value contributes - # to the environment cache key. - # The env-vars are matched by *prefix*, so the default `RUST` var will - # match all of `RUSTC`, `RUSTUP_*`, `RUSTFLAGS`, `RUSTDOC_*`, etc. - # default: "CARGO CC CFLAGS CXX CMAKE RUST" - env-vars: "" - - # The cargo workspaces and target directory configuration. - # These entries are separated by newlines and have the form - # `$workspace -> $target`. The `$target` part is treated as a directory - # relative to the `$workspace` and defaults to "target" if not explicitly given. - # default: ". -> target" - workspaces: "" - - # Additional non workspace directories to be cached, separated by newlines. - cache-directories: "" - - # Determines whether workspace `target` directories are cached. - # If `false`, only the cargo registry will be cached. - # default: "true" - cache-targets: "" - - # Determines if the cache should be saved even when the workflow has failed. - # default: "false" - cache-on-failure: "" - - # Determines which crates are cached. - # If `true` all crates will be cached, otherwise only dependent crates will be cached. - # Useful if additional crates are used for CI tooling. - # default: "false" - cache-all-crates: "" - - # Determiners whether the cache should be saved. - # If `false`, the cache is only restored. - # Useful for jobs where the matrix is additive e.g. additional Cargo features. - # default: "true" - save-if: "" - - name: Install s3cmd - run: sudo apt install python3-pip -y && sudo pip3 install s3cmd awxkit - - name: Get tag - id: tag - uses: devops-actions/action-get-tag@v1.0.2 - with: - strip_v: true # Optional: Remove 'v' character from version - default: "v0.0.0" # Optional: Default version when tag not found - - name: Override version - run: "sed -i 's/Version: .*/Version: ${{steps.tag.outputs.tag}}/' packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec" - - name: Debug - run: grep Version packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec - - name: Extract release notes - id: extract-release-notes - uses: ffurrer2/extract-release-notes@v1 - #- name: Display release notes - # run: "echo ${{ steps.extract-release-notes.outputs.release_notes }}" - - name: Edit changelog #TODO commit and push to increase changelog - run: date=$(date "+%a %b %d %Y - "); sed -i "/%changelog/ a * ${date}${{steps.tag.outputs.tag}}/" packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec - - name: Edit changelog - run: echo " Packaging for version ${{steps.tag.outputs.tag}}" >> packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec - - name: build RPM package - id: rpm - uses: bpetit/rpmbuild@master - with: - spec_file: "packaging/linux/redhat/rpmbuild/SPECS/scaphandre.spec" - - name: Upload to scw s3 - run: | - s3cmd --access_key="${{ secrets.S3_ACCESS_KEY_ID }}" --secret_key="${{ secrets.S3_SECRET_ACCESS_KEY }}" --region="fr-par" --acl-public --host="s3.fr-par.scw.cloud" --host-bucket="%(bucket).s3.fr-par.scw.cloud" put --recursive ${{ steps.rpm.outputs.rpm_dir_path }} s3://scaphandre/ - - name: Log on AWX - id: login - run: | - RAW_RESULT=$(awx --conf.host "${{ secrets.AWX_HOST }}" --conf.username "${{ secrets.AWX_PUBLIC_USER }}" --conf.password "${{ secrets.AWX_PASSWORD }}" login) - export AWX_TOKEN=$(echo $RAW_RESULT | jq .token | tr -d '"') - echo "awx_token=${AWX_TOKEN}" >> $GITHUB_OUTPUT - - name: Install and test RPM package - id: rpmtest - run: | - awx --conf.token ${{ steps.login.outputs.awx_token }} --conf.host ${{ secrets.AWX_HOST }} job_templates launch --extra_vars="{\"github_repository\":\"${GITHUB_REPOSITORY}\",\"github_actor\":\"${GITHUB_ACTOR}\",\"github_workflow\":\"${GITHUB_WORKFLOW}\",\"github_workspace\":\"${GITHUB_WORKSPACE}\",\"github_event_name\":\"${GITHUB_EVENT_NAME}\",\"github_event_path\":\"${GITHUB_EVENT_PATH}\",\"github_sha\":\"${GITHUB_SHA}\",\"github_ref\":\"${GITHUB_REF}\",\"github_head_ref\":\"${GITHUB_HEAD_REF}\",\"github_base_ref\":\"${GITHUB_BASE_REF}\",\"github_server_url\":\"${GITHUB_SERVER_URL}\",\"github_rpm_url\":\"https://scaphandre.s3.fr-par.scw.cloud/x86_64/scaphandre-${{steps.tag.outputs.tag}}-1.el9.x86_64.rpm\"}" 19 --monitor \ No newline at end of file From e502b55c18041c29970c64e682f2949ae6282432 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 12 Feb 2024 13:09:29 +0100 Subject: [PATCH 298/303] docs: fixing website favicon --- README.md | 2 ++ oranda.json | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6fe88dad..f4ecea10 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,8 @@ Join us on [Gitter](https://gitter.im/hubblo-org/scaphandre) or [Matrix](https:/ - works on **[kubernetes](https://kubernetes.io/)** - storing power consumption metrics in a **JSON** file - showing basic power consumption metrics **in the terminal** +- operating systems supported so far : **Gnu/Linux**, **Windows 10, 11 and Server 2016/2019/2022** +- packages available for **RHEL 8 and 9, Debian 11 and 12 and Windows**, also **NixOS** (community support) Here is an example dashboard built thanks to scaphandre: [https://metrics.hubblo.org](https://metrics.hubblo.org). diff --git a/oranda.json b/oranda.json index 4a7b38a3..397b2dc5 100644 --- a/oranda.json +++ b/oranda.json @@ -7,10 +7,11 @@ }, "styles": { "theme": "light", - "favicon": "https://raw.githubusercontent.com/hubblo-org/scaphandre/dev/docs_src/scaphandre.small.cleaned.png" + "favicon": "https://raw.githubusercontent.com/hubblo-org/scaphandre/dev/docs_src/scaphandre.small.cleaned.ico" }, "marketing": { "social": { + "image": "https://raw.githubusercontent.com/hubblo-org/scaphandre/dev/docs_src/scaphandre.cleaned.png", "image_alt": "Hubblo's twitter/x account", "twitter_account": "@HubbloOrg" } From 017672ce34984c0938a5de0a6017811ecd3449d6 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 12 Feb 2024 13:13:22 +0100 Subject: [PATCH 299/303] ci: disabling unwanted runs --- .github/workflows/build-and-test.yml | 2 ++ .github/workflows/debian-release.yml | 2 ++ .github/workflows/docker-release.yml | 2 ++ .github/workflows/exe-release-prometheuspush.yml | 2 ++ .github/workflows/exe-release.yml | 2 ++ .github/workflows/rpm-release-prometheuspush.yml | 2 ++ .github/workflows/rpm-release.yml | 2 ++ oranda.json | 2 +- 8 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 31aaa452..b15bf654 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -12,6 +12,7 @@ on: - 'CONTRIBUTING.md' - '.github/workflows/exe-release-prometheuspush.yml' - '*.md' + - 'oranda.json' pull_request: branches: [ main, dev ] paths-ignore: @@ -22,6 +23,7 @@ on: - 'book.toml' - '.github/workflows/exe-release-prometheuspush.yml' - '*.md' + - 'oranda.json' env: CARGO_TERM_COLOR: always diff --git a/.github/workflows/debian-release.yml b/.github/workflows/debian-release.yml index 3bbf759d..c0d27dae 100644 --- a/.github/workflows/debian-release.yml +++ b/.github/workflows/debian-release.yml @@ -8,6 +8,8 @@ on: - 'CITATION' - 'book.toml' - 'CONTRIBUTING.md' + - '*.md' + - 'oranda.json' tags: [ 'v*.*.*', 'dev*.*.*' ] diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index cd2e829f..d39ecab5 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -10,6 +10,8 @@ on: - 'CITATION' - 'book.toml' - 'CONTRIBUTING.md' + - '*.md' + - 'oranda.json' tags: [ 'v*.*.*' ] jobs: diff --git a/.github/workflows/exe-release-prometheuspush.yml b/.github/workflows/exe-release-prometheuspush.yml index dc76afb9..7dc86883 100644 --- a/.github/workflows/exe-release-prometheuspush.yml +++ b/.github/workflows/exe-release-prometheuspush.yml @@ -8,6 +8,8 @@ on: - 'CITATION' - 'book.toml' - 'CONTRIBUTING.md' + - '*.md' + - 'oranda.json' tags: [ 'v*.*.*', 'dev*.*.*' ] branches: [ '336-proper-handling-of-windows-service-management' ] diff --git a/.github/workflows/exe-release.yml b/.github/workflows/exe-release.yml index a83f8955..afb01c0b 100644 --- a/.github/workflows/exe-release.yml +++ b/.github/workflows/exe-release.yml @@ -8,6 +8,8 @@ on: - 'CITATION' - 'book.toml' - 'CONTRIBUTING.md' + - '*.md' + - 'oranda.json' tags: [ 'v*.*.*', 'dev*.*.*' ] branches: [ '336-proper-handling-of-windows-service-management' ] diff --git a/.github/workflows/rpm-release-prometheuspush.yml b/.github/workflows/rpm-release-prometheuspush.yml index b34d7746..ce37bf8f 100644 --- a/.github/workflows/rpm-release-prometheuspush.yml +++ b/.github/workflows/rpm-release-prometheuspush.yml @@ -8,6 +8,8 @@ on: - 'CITATION' - 'book.toml' - 'CONTRIBUTING.md' + - '*.md' + - 'oranda.json' tags: [ 'v*.*.*', 'dev*.*.*' ] jobs: diff --git a/.github/workflows/rpm-release.yml b/.github/workflows/rpm-release.yml index aa287605..0bcdc99e 100644 --- a/.github/workflows/rpm-release.yml +++ b/.github/workflows/rpm-release.yml @@ -8,6 +8,8 @@ on: - 'CITATION' - 'book.toml' - 'CONTRIBUTING.md' + - '*.md' + - 'oranda.json' tags: [ 'v*.*.*', 'dev*.*.*' ] jobs: diff --git a/oranda.json b/oranda.json index 397b2dc5..b06aa3c9 100644 --- a/oranda.json +++ b/oranda.json @@ -7,7 +7,7 @@ }, "styles": { "theme": "light", - "favicon": "https://raw.githubusercontent.com/hubblo-org/scaphandre/dev/docs_src/scaphandre.small.cleaned.ico" + "favicon": "docs_src/scaphandre.small.cleaned.ico" }, "marketing": { "social": { From eb74855ce15686cae628afe65b0922bbb3854d48 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 12 Feb 2024 13:20:29 +0100 Subject: [PATCH 300/303] docs: fixing website artifacts --- oranda.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oranda.json b/oranda.json index b06aa3c9..b39c9cc2 100644 --- a/oranda.json +++ b/oranda.json @@ -19,7 +19,7 @@ "components": { "changelog": true, "artifacts": { - "auto": true, + "auto": false, "cargo_dist": false, "package_managers": { "preferred": { From 1833a8eae85372c6c810aa5cb189c783718e3259 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 12 Feb 2024 13:27:11 +0100 Subject: [PATCH 301/303] docs: fixing website favicon --- .../{scaphandre.small.cleaned.ico => favicon.ico} | Bin oranda.json | 5 +++-- 2 files changed, 3 insertions(+), 2 deletions(-) rename docs_src/{scaphandre.small.cleaned.ico => favicon.ico} (100%) diff --git a/docs_src/scaphandre.small.cleaned.ico b/docs_src/favicon.ico similarity index 100% rename from docs_src/scaphandre.small.cleaned.ico rename to docs_src/favicon.ico diff --git a/oranda.json b/oranda.json index b39c9cc2..e0b006a7 100644 --- a/oranda.json +++ b/oranda.json @@ -7,7 +7,7 @@ }, "styles": { "theme": "light", - "favicon": "docs_src/scaphandre.small.cleaned.ico" + "favicon": "docs_src/favicon.ico" }, "marketing": { "social": { @@ -23,7 +23,8 @@ "cargo_dist": false, "package_managers": { "preferred": { - "crates.io": "cargo install scaphandre" + "crates.io": "cargo install scaphandre", + "debian package": "dpkg -i scaphandre.X.deb" }, "additional": { "binstall": "cargo binstall scaphandre" From abad85c71b3f94490b1c40bd969c55f77efe45b5 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 12 Feb 2024 14:31:08 +0100 Subject: [PATCH 302/303] docs: fix website config --- oranda.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/oranda.json b/oranda.json index e0b006a7..7579ccd9 100644 --- a/oranda.json +++ b/oranda.json @@ -24,10 +24,9 @@ "package_managers": { "preferred": { "crates.io": "cargo install scaphandre", - "debian package": "dpkg -i scaphandre.X.deb" - }, - "additional": { - "binstall": "cargo binstall scaphandre" + "DEB package": "dpkg -i scaphandre.X.deb", + "docker": "docker pull hubblo/scaphandre", + "RPM package": "rpm -ivh scaphandre.X.rpm" } } }, From c43d72b43ca5203d165db7fbff22ef2215743dd6 Mon Sep 17 00:00:00 2001 From: Benoit Petit Date: Mon, 12 Feb 2024 14:38:12 +0100 Subject: [PATCH 303/303] docs: adding sponsoring page to readme --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index f4ecea10..ace3a8fc 100644 --- a/README.md +++ b/README.md @@ -65,3 +65,8 @@ The ongoing roadmap can be seen [here](https://github.com/hubblo-org/scaphandre/ ## ⚖️ Footprint In opposition to its name, scaphandre aims to be as light and clean as possible. One of the main focus areas of the project is to come as close as possible to a 0 overhead, both about resources consumption and power consumption. + +## 🙏 Sponsoring + +If you like this project and would like to provide financial help, here's our [sponsoring page](https://github.com/sponsors/hubblo-org). +Thanks a lot for considering it ! \ No newline at end of file