diff --git a/Cargo.lock b/Cargo.lock index 420ae4de5..310387218 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,6 +31,15 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "aho-corasick" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +dependencies = [ + "memchr", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -52,6 +61,15 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "atomic-shim" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d20fdac7156779a1a30d970e838195558b4810dd06aa69e7c7461bdc518edf9b" +dependencies = [ + "crossbeam", +] + [[package]] name = "autocfg" version = "1.0.1" @@ -94,6 +112,12 @@ dependencies = [ "crunchy", ] +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + [[package]] name = "bitvec" version = "0.18.5" @@ -172,6 +196,12 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "bytes" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" + [[package]] name = "cfg-if" version = "0.1.10" @@ -227,7 +257,7 @@ dependencies = [ "cfg-if 0.1.10", "crossbeam-channel 0.4.4", "crossbeam-deque", - "crossbeam-epoch", + "crossbeam-epoch 0.8.2", "crossbeam-queue", "crossbeam-utils 0.7.2", ] @@ -258,7 +288,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" dependencies = [ - "crossbeam-epoch", + "crossbeam-epoch 0.8.2", "crossbeam-utils 0.7.2", "maybe-uninit", ] @@ -274,7 +304,20 @@ dependencies = [ "crossbeam-utils 0.7.2", "lazy_static", "maybe-uninit", - "memoffset", + "memoffset 0.5.6", + "scopeguard", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils 0.8.3", + "lazy_static", + "memoffset 0.6.3", "scopeguard", ] @@ -332,6 +375,16 @@ dependencies = [ "crypto_api", ] +[[package]] +name = "ctor" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "curve25519-dalek" version = "3.0.2" @@ -345,6 +398,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "dashmap" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" +dependencies = [ + "cfg-if 1.0.0", + "num_cpus", +] + [[package]] name = "digest" version = "0.9.0" @@ -409,6 +472,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "fpe" version = "0.4.0" @@ -434,6 +503,21 @@ version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" +[[package]] +name = "futures-channel" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c2dd2df839b57db9ab69c2c9d8f3e8c81984781937fe2807dc6dcf3b2ad2939" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15496a72fabf0e62bdc3df11a59a3787429221dd0710ba8ef163d6f7a9112c94" + [[package]] name = "futures-cpupool" version = "0.1.8" @@ -444,6 +528,24 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "futures-task" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa189ef211c15ee602667a6fcfe1c1fd9e07d42250d2156382820fba33c9df80" + +[[package]] +name = "futures-util" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1812c7ab8aedf8d6f2701a43e1243acdbcc2b36ab26e2ad421eb99ac963d96d1" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + [[package]] name = "generic-array" version = "0.14.4" @@ -477,6 +579,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" + [[package]] name = "hermit-abi" version = "0.1.18" @@ -492,6 +600,94 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "http" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7245cd7449cc792608c3c8a9eaf69bd4eabbabf802713748fd739c98b82f0747" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfb77c123b4e2f72a2069aeae0b4b4949cc7e966df277813fc16347e7549737" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "615caabe2c3160b313d52ccc905335f4ed5f10881dd63dc5699d47e90be85691" + +[[package]] +name = "httpdate" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" + +[[package]] +name = "hyper" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12219dc884514cb4a6a03737f4413c0e01c23a1b059b0156004b23f1e19dccbe" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "indexmap" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "ipnet" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" + +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + [[package]] name = "jubjub" version = "0.5.1" @@ -529,10 +725,16 @@ dependencies = [ "ed25519-zebra", "funty", "group", + "hyper", + "ipnet", "jubjub", "libc", + "metrics", + "metrics-exporter-prometheus", "rand_core", "subtle", + "thiserror", + "tokio", "tracing", "tracing-appender", "tracing-core", @@ -542,6 +744,15 @@ dependencies = [ "zcash_proofs", ] +[[package]] +name = "lock_api" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.14" @@ -551,6 +762,15 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + [[package]] name = "matchers" version = "0.0.1" @@ -566,6 +786,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" +[[package]] +name = "memchr" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" + [[package]] name = "memoffset" version = "0.5.6" @@ -575,6 +801,104 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memoffset" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metrics" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58e285601dcfb9f3a8f37a7093b9956a0df730b50848c8ac0117406aff06c851" +dependencies = [ + "metrics-macros", + "proc-macro-hack", +] + +[[package]] +name = "metrics-exporter-prometheus" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d23bb354bd7dd5d244f2e9a389a0c3249bb6b994aca71bd6c2f7cd6fa2657fc" +dependencies = [ + "hyper", + "metrics", + "metrics-util", + "parking_lot", + "quanta", + "thiserror", + "tokio", +] + +[[package]] +name = "metrics-macros" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ac60cd4d3a869fd39d57baf0ed7f79fb677580d8d6655c4330d4f1126bc27b" +dependencies = [ + "lazy_static", + "proc-macro-hack", + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "metrics-util" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ace44e2c64f785c3c37605ecf7504e5fc4efbb2936a49cc56c1084d79657a4d" +dependencies = [ + "aho-corasick", + "atomic-shim", + "crossbeam-epoch 0.9.3", + "crossbeam-utils 0.8.3", + "dashmap", + "indexmap", + "metrics", + "ordered-float", + "parking_lot", + "quanta", + "sketches-ddsketch", +] + +[[package]] +name = "mio" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956" +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", +] + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi", +] + [[package]] name = "num-bigint" version = "0.3.2" @@ -627,6 +951,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "ordered-float" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "766f840da25490628d8e63e529cd21c014f6600c6b8517add12a6fa6167a6218" +dependencies = [ + "num-traits", +] + [[package]] name = "pairing" version = "0.18.0" @@ -637,18 +970,75 @@ dependencies = [ "group", ] +[[package]] +name = "parking_lot" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall 0.2.5", + "smallvec", + "winapi", +] + +[[package]] +name = "pin-project" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc174859768806e91ae575187ada95c91a29e96a98dc5d2cd9a1fed039501ba6" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a490329918e856ed1b083f244e3bfe2d8c4f336407e4ea9e1a9f479ff09049e5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "ppv-lite86" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +[[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.24" @@ -658,6 +1048,21 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "quanta" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e76a3afdefd0ce2c0363bf3146271e947782240ea617885dd64e56c4de9fb3c9" +dependencies = [ + "atomic-shim", + "ctor", + "libc", + "mach", + "once_cell", + "raw-cpuid", + "winapi", +] + [[package]] name = "quote" version = "1.0.9" @@ -714,12 +1119,30 @@ dependencies = [ "rand_core", ] +[[package]] +name = "raw-cpuid" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c27cb5785b85bd05d4eb171556c9a1a514552e26123aeae6bb7d811353148026" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_syscall" version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +[[package]] +name = "redox_syscall" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_users" version = "0.3.5" @@ -727,7 +1150,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" dependencies = [ "getrandom", - "redox_syscall", + "redox_syscall 0.1.57", "rust-argon2", ] @@ -737,6 +1160,8 @@ version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" dependencies = [ + "aho-corasick", + "memchr", "regex-syntax", ] @@ -816,6 +1241,29 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "sketches-ddsketch" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a77a8fd93886010f05e7ea0720e569d6d16c65329dbe3ec033bbbccccb017b" + +[[package]] +name = "smallvec" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" + +[[package]] +name = "socket2" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "winapi", +] + [[package]] name = "subtle" version = "2.4.0" @@ -873,6 +1321,36 @@ dependencies = [ "winapi", ] +[[package]] +name = "tokio" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134af885d758d645f0f0505c9a8b3f9bf8a348fd822e112ab5248138348f1722" +dependencies = [ + "autocfg", + "libc", + "mio", + "pin-project-lite", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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.25" @@ -933,6 +1411,12 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + [[package]] name = "typenum" version = "1.13.0" @@ -951,6 +1435,16 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +[[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 = "wasi" version = "0.9.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 7286fdb4d..bf7362894 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,14 @@ zcash_primitives = "0.5" zcash_proofs = "0.5" ed25519-zebra = "2.0.0" +# Metrics +hyper = { version = "=0.14.2", default-features = false, features = ["server", "tcp", "http1"] } +ipnet = "2" +metrics = "0.14.2" +metrics-exporter-prometheus = "0.3" +thiserror = "1" +tokio = { version = "1.0", features = ["rt", "net", "time", "macros"] } + # Temporary workaround for https://github.com/myrrlyn/funty/issues/3 funty = "=1.1.0" diff --git a/contrib/debian/copyright b/contrib/debian/copyright index 4fd3b17f9..8b048579e 100644 --- a/contrib/debian/copyright +++ b/contrib/debian/copyright @@ -124,14 +124,24 @@ Files: src/crypto/ctaes/* Copyright: Copyright (c) 2016 Pieter Wuille License: Expat +Files: src/rust/include/rust/map.h +Copyright: Copyright (c) 2012 William Swanson +License: Expat-with-advertising-clause + +Files: src/rust/include/rust/VA_OPT.hpp +Copyright: Copyright (c) 2019 Will Wray +License: Boost-Software-License-1.0 + Files: src/rust/include/tracing.h src/rust/src/tracing_ffi.rs Copyright: Copyright (c) 2020 Jack Grigg License: Expat -Files: src/rust/include/tracing/map.h -Copyright: Copyright (c) 2012 William Swanson -License: Expat-with-advertising-clause +Files: src/rust/src/metrics_ffi/prometheus.rs +Copyright: + 2020-2021 The contributors to the metrics project + 2021 Jack Grigg +License: Expat Files: src/secp256k1/* Copyright: Copyright (c) 2013 Pieter Wuille diff --git a/contrib/metrics/prometheus.yaml b/contrib/metrics/prometheus.yaml new file mode 100644 index 000000000..77d08e5b4 --- /dev/null +++ b/contrib/metrics/prometheus.yaml @@ -0,0 +1,6 @@ +scrape_configs: + - job_name: 'zcashd' + scrape_interval: 500ms + metrics_path: '/' + static_configs: + - targets: ['127.0.0.1:9969'] diff --git a/doc/book/src/SUMMARY.md b/doc/book/src/SUMMARY.md index 0bca7e63c..65b038915 100644 --- a/doc/book/src/SUMMARY.md +++ b/doc/book/src/SUMMARY.md @@ -1,6 +1,8 @@ # The zcashd Book [zcashd](README.md) +- [User Documentation](user.md) + - [Metrics](user/metrics.md) - [Design](design.md) - [Chain state](design/chain-state.md) - ["Coins" view](design/coins-view.md) diff --git a/doc/book/src/user.md b/doc/book/src/user.md new file mode 100644 index 000000000..e23cdcf42 --- /dev/null +++ b/doc/book/src/user.md @@ -0,0 +1,6 @@ +# User Documentation + +This section contains user documentation specific to `zcashd`. + +See [here](https://zcash.readthedocs.io/) for more general Zcash documentation, as well as +installation instructions for `zcashd`. diff --git a/doc/book/src/user/metrics.md b/doc/book/src/user/metrics.md new file mode 100644 index 000000000..dd11d6d95 --- /dev/null +++ b/doc/book/src/user/metrics.md @@ -0,0 +1,84 @@ +# zcashd metrics + +## Metrics UI + +This is the user interface that `zcashd` displays by default when run. It +displays a small selection of interesting metrics, but is not intended for +programmatic consumption. + +## RPC methods + +`zcashd` provides the following JSON-RPC methods that expose node metrics: + +- Chain: + - `getblockchaininfo`: Various state info regarding block chain processing. + - `gettxoutsetinfo`: Statistics about the unspent transparent transaction output set. + - `getmempoolinfo`: Details on the active state of the TX memory pool. +- P2P network: + - `getnetworkinfo`: Various state info regarding P2P networking. + - `getpeerinfo`: Data about each connected network node. + - `getdeprecationinfo`: The current node version and deprecation block height. +- Miscellaneous + - `getmemoryinfo`: Information about memory usage. + - `getmininginfo`: Mining-related information. + - `getinfo` (deprecated): A small subset of the above metrics. + +You can see what each method provides with `zcash-cli help METHOD_NAME`. + +## Prometheus support + +`zcashd` can optionally expose an HTTP server that acts as a Prometheus scrape +endpoint. The server will respond to `GET` requests on any request path. + +To enable the endpoint, add `-prometheusport=` to your `zcashd` +configuration (either in `zcash.conf` or on the command line). After +restarting `zcashd` you can then test the endpoint by querying it: + +``` +$ curl http://127.0.0.1: +# TYPE zcash_net_out_messages counter +zcash_net_out_messages 181 + +# TYPE zcash_net_in_bytes_total counter +zcash_net_in_bytes_total 3701998 + +# TYPE zcash_net_in_messages counter +zcash_net_in_messages 184 + +# TYPE zcashd_build_info counter +zcashd_build_info{version="v4.2.0"} 1 + +# TYPE zcash_chain_verified_block_total counter +zcash_chain_verified_block_total 162 +... +``` + +By default, access is restricted to localhost. This can be expanded with +`-metricsallowip=`, which can specify IPs or subnets. Note that HTTPS is not +supported, and therefore connections to the endpoint are not encrypted or +authenticated. Access to the endpoint should be assumed to compromise the +privacy of node operations, by the provided metrics and/or by timing side +channels. Non-localhost access is **strongly discouraged** if the node has a +wallet holding live funds. + +### Example metrics collection with Docker + +The example instructions below were tested on Windows 10 using Docker Desktop +with the WSL 2 backend: + +``` +# Create a storage volume for Grafana (once) +docker volume create grafana-storage + +# Create a storage volume for Prometheus (once) +docker volume create prometheus-storage + +# Run Prometheus +# You will need to modify ~/contrib/metrics/prometheus.yaml to match the +# endpoint configured with -prometheusmetrics (and possibly also for your Docker +# network setup). +docker run --detach -p 9090:9090 --volume prometheus-storage:/prometheus --volume ~/contrib/metrics/prometheus.yaml:/etc/prometheus/prometheus.yml prom/prometheus + +# Run Grafana +docker run --detach -p 3030:3030 --env GF_SERVER_HTTP_PORT=3030 --volume grafana-storage:/var/lib/grafana grafana/grafana +``` diff --git a/doc/release-notes.md b/doc/release-notes.md index a29094b51..8cce56b9c 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -4,3 +4,25 @@ release-notes at release time) Notable changes =============== +Prometheus metrics +------------------ + +`zcashd` can now be configured to optionally expose an HTTP server that acts as +a Prometheus scrape endpoint. The server will respond to `GET` requests on any +request path. + +To enable the endpoint, add `-prometheusport=` to your `zcashd` +configuration (either in `zcash.conf` or on the command line). After +restarting `zcashd` you can then test the endpoint by querying it with e.g. +`curl http://127.0.0.1:`. + +By default, access is restricted to localhost. This can be expanded with +`-metricsallowip=`, which can specify IPs or subnets. Note that HTTPS is not +supported, and therefore connections to the endpoint are not encrypted or +authenticated. Access to the endpoint should be assumed to compromise the +privacy of node operations, by the provided metrics and/or by timing side +channels. Non-localhost access is **strongly discouraged** if the node has a +wallet holding live funds. + +The specific metrics names may change in subsequent releases, in particular to +improve interoperability with `zebrad`. diff --git a/src/init.cpp b/src/init.cpp index eef322d55..60876765b 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -64,6 +64,8 @@ #include "zmq/zmqnotificationinterface.h" #endif +#include + #include "librustzcash.h" using namespace std; @@ -410,6 +412,15 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-zmqpubrawtx=
", _("Enable publish raw transaction in
")); #endif + strUsage += HelpMessageGroup(_("Monitoring options:")); + strUsage += HelpMessageOpt("-metricsallowip=", _("Allow metrics connections from specified source. " + "Valid for are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24). " + "This option can be specified multiple times. (default: only localhost)")); + strUsage += HelpMessageOpt("-metricsbind=", _("Bind to given address to listen for metrics connections. (default: bind to all interfaces)")); + strUsage += HelpMessageOpt("-prometheusport=", _("Expose node metrics in the Prometheus exposition format. " + "An HTTP listener will be started on , which responds to GET requests on any request path. " + "Use -metricsallowip and -metricsbind to control access.")); + strUsage += HelpMessageGroup(_("Debugging/Testing options:")); if (showDebug) { @@ -1221,6 +1232,34 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) // Count uptime MarkStartTime(); + int prometheusPort = GetArg("-prometheusport", -1); + if (prometheusPort > 0) { + const std::vector& vAllow = mapMultiArgs["-metricsallowip"]; + std::vector vAllowCstr; + for (const std::string& strAllow : vAllow) { + vAllowCstr.push_back(strAllow.c_str()); + } + + std::string metricsBind = GetArg("-metricsbind", ""); + const char* metricsBindCstr = nullptr; + if (!metricsBind.empty()) { + metricsBindCstr = metricsBind.c_str(); + } + + // Start up the metrics runtime. This spins off a Rust thread that runs + // the Prometheus exporter. We just let this thread die at process end. + LogPrintf("metrics thread start"); + if (!metrics_run(metricsBindCstr, vAllowCstr.data(), vAllowCstr.size(), prometheusPort)) { + return InitError(strprintf(_("Failed to start Prometheus metrics exporter"))); + } + } + + // Expose binary metadata to metrics, using a single time series with value 1. + // https://www.robustperception.io/exposing-the-software-version-to-prometheus + MetricsIncrementCounter( + "zcashd.build.info", + "version", CLIENT_BUILD.c_str()); + if ((chainparams.NetworkIDString() != "regtest") && GetBoolArg("-showmetrics", isatty(STDOUT_FILENO)) && !fPrintToConsole && !GetBoolArg("-daemon", false)) { diff --git a/src/main.cpp b/src/main.cpp index cfa7dbb57..ed774e5fd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -46,6 +46,7 @@ #include #include +#include using namespace std; @@ -1692,6 +1693,10 @@ bool AcceptToMemoryPool( } pool.EnsureSizeLimit(); + + MetricsGauge("zcash.mempool.size.transactions", mempool.size()); + MetricsGauge("zcash.mempool.size.bytes", mempool.GetTotalTxSize()); + MetricsGauge("zcash.mempool.usage.bytes", mempool.DynamicMemoryUsage()); } } @@ -3211,6 +3216,85 @@ void PruneAndFlush() { FlushStateToDisk(Params(), state, FLUSH_STATE_NONE); } +struct PoolMetrics { + std::optional created; + std::optional spent; + std::optional unspent; + std::optional value; + + static PoolMetrics Sprout(CBlockIndex *pindex, CCoinsViewCache *view) { + PoolMetrics stats; + stats.value = pindex->nChainSproutValue; + + // RewindBlockIndex calls DisconnectTip in a way that can potentially cause a + // Sprout tree to not exist (the rewind_index RPC test reliably triggers this). + // We only need to access the tree during disconnection for metrics purposes, and + // we will never encounter this rewind situation on either mainnet or testnet, so + // if we can't access the Sprout tree we default to zero. + SproutMerkleTree sproutTree; + if (view->GetSproutAnchorAt(pindex->hashFinalSproutRoot, sproutTree)) { + stats.created = sproutTree.size(); + } else { + stats.created = 0; + } + + return stats; + } + + static PoolMetrics Sapling(CBlockIndex *pindex, CCoinsViewCache *view) { + PoolMetrics stats; + stats.value = pindex->nChainSaplingValue; + + // Before Sapling activation, the Sapling commitment set is empty. + SaplingMerkleTree saplingTree; + if (view->GetSaplingAnchorAt(pindex->hashFinalSaplingRoot, saplingTree)) { + stats.created = saplingTree.size(); + } else { + stats.created = 0; + } + + return stats; + } + + static PoolMetrics Transparent(CBlockIndex *pindex, CCoinsViewCache *view) { + PoolMetrics stats; + // TODO: Collect transparent pool value. + + // TODO: Figure out a way to efficiently collect UTXO set metrics + // (view->GetStats() is too slow to call during block verification). + + return stats; + } +}; + +#define RenderPoolMetrics(poolName, poolMetrics) \ + do { \ + if (poolMetrics.created) { \ + MetricsStaticGauge( \ + "zcash.pool.notes.created", \ + poolMetrics.created.value(), \ + "name", poolName); \ + } \ + if (poolMetrics.spent) { \ + MetricsStaticGauge( \ + "zcash.pool.notes.spent", \ + poolMetrics.spent.value(), \ + "name", poolName); \ + } \ + if (poolMetrics.unspent) { \ + MetricsStaticGauge( \ + "zcash.pool.notes.unspent", \ + poolMetrics.unspent.value(), \ + "name", poolName); \ + } \ + if (poolMetrics.value) { \ + MetricsStaticGauge( \ + "zcash.pool.value.zatoshis", \ + poolMetrics.value.value(), \ + "name", poolName); \ + } \ + } while (0) + /** Update chainActive and related internal data structures. */ void static UpdateTip(CBlockIndex *pindexNew, const CChainParams& chainParams) { chainActive.SetTip(pindexNew); @@ -3238,6 +3322,15 @@ void static UpdateTip(CBlockIndex *pindexNew, const CChainParams& chainParams) { "progress", progress.c_str(), "cache", cache.c_str()); + auto sproutPool = PoolMetrics::Sprout(pindexNew, pcoinsTip); + auto saplingPool = PoolMetrics::Sapling(pindexNew, pcoinsTip); + auto transparentPool = PoolMetrics::Transparent(pindexNew, pcoinsTip); + + MetricsGauge("zcash.chain.verified.block.height", pindexNew->nHeight); + RenderPoolMetrics("sprout", sproutPool); + RenderPoolMetrics("sapling", saplingPool); + RenderPoolMetrics("transparent", transparentPool); + cvBlockChange.notify_all(); } @@ -3369,6 +3462,8 @@ bool static ConnectTip(CValidationState& state, const CChainParams& chainparams, int64_t nTime6 = GetTimeMicros(); nTimePostConnect += nTime6 - nTime5; nTimeTotal += nTime6 - nTime1; LogPrint("bench", " - Connect postprocess: %.2fms [%.2fs]\n", (nTime6 - nTime5) * 0.001, nTimePostConnect * 0.000001); LogPrint("bench", "- Connect block: %.2fms [%.2fs]\n", (nTime6 - nTime1) * 0.001, nTimeTotal * 0.000001); + MetricsIncrementCounter("zcash.chain.verified.block.total"); + MetricsHistogram("zcash.chain.verified.block.seconds", (nTime6 - nTime1) * 0.000001); return true; } diff --git a/src/net.cpp b/src/net.cpp index 322219f54..9026140ca 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -31,6 +31,8 @@ #include +#include + // Dump addresses to peers.dat and banlist.dat every 15 minutes (900s) #define DUMP_ADDRESSES_INTERVAL 900 @@ -702,6 +704,11 @@ bool CNode::ReceiveMsgBytes(const char *pch, unsigned int nBytes) if (msg.complete()) { msg.nTime = GetTimeMicros(); + std::string strCommand = SanitizeString(msg.hdr.GetCommand()); + MetricsIncrementCounter("zcash.net.in.messages", "command", strCommand.c_str()); + MetricsCounter( + "zcash.net.in.bytes", msg.hdr.nMessageSize, + "command", strCommand.c_str()); messageHandlerCondition.notify_one(); } } @@ -1104,6 +1111,7 @@ void ThreadSocketHandler() } if (vNodesSize != nPrevNodeCount) { nPrevNodeCount = vNodesSize; + MetricsGauge("zcash.net.peers", nPrevNodeCount); uiInterface.NotifyNumConnectionsChanged(nPrevNodeCount); } @@ -2001,12 +2009,14 @@ void CNode::RecordBytesRecv(uint64_t bytes) { LOCK(cs_totalBytesRecv); nTotalBytesRecv += bytes; + MetricsCounter("zcash.net.in.bytes.total", bytes); } void CNode::RecordBytesSent(uint64_t bytes) { LOCK(cs_totalBytesSent); nTotalBytesSent += bytes; + MetricsCounter("zcash.net.out.bytes.total", bytes); uint64_t now = GetTime(); if (nMaxOutboundCycleStartTime + nMaxOutboundTimeframe < now) @@ -2218,11 +2228,11 @@ CNode::~CNode() void CNode::ReloadTracingSpan() { if (fLogIPs) { - span = TracingSpanFields("info", "net", "Peer", + span = TracingSpan("info", "net", "Peer", "id", idStr.c_str(), "addr", addrName.c_str()); } else { - span = TracingSpanFields("info", "net", "Peer", + span = TracingSpan("info", "net", "Peer", "id", idStr.c_str()); } } @@ -2265,13 +2275,16 @@ void CNode::BeginMessage(const char* pszCommand) EXCLUSIVE_LOCK_FUNCTION(cs_vSen { ENTER_CRITICAL_SECTION(cs_vSend); assert(ssSend.size() == 0); + assert(strSendCommand.empty()); ssSend << CMessageHeader(Params().MessageStart(), pszCommand, 0); - LogPrint("net", "sending: %s ", SanitizeString(pszCommand)); + strSendCommand = SanitizeString(pszCommand); + LogPrint("net", "sending: %s ", strSendCommand); } void CNode::AbortMessage() UNLOCK_FUNCTION(cs_vSend) { ssSend.clear(); + strSendCommand.clear(); LEAVE_CRITICAL_SECTION(cs_vSend); @@ -2280,6 +2293,7 @@ void CNode::AbortMessage() UNLOCK_FUNCTION(cs_vSend) void CNode::EndMessage() UNLOCK_FUNCTION(cs_vSend) { + MetricsIncrementCounter("zcash.net.out.messages", "command", strSendCommand.c_str()); // The -*messagestest options are intentionally not documented in the help message, // since they are only used during development to debug the networking code and are // not intended for end-users. @@ -2313,6 +2327,10 @@ void CNode::EndMessage() UNLOCK_FUNCTION(cs_vSend) std::deque::iterator it = vSendMsg.insert(vSendMsg.end(), CSerializeData()); ssSend.GetAndClear(*it); nSendSize += (*it).size(); + MetricsCounter( + "zcash.net.out.bytes", (*it).size(), + "command", strSendCommand.c_str()); + strSendCommand.clear(); // If write queue empty, attempt "optimistic write" if (it == vSendMsg.begin()) diff --git a/src/net.h b/src/net.h index f1949a1ee..f5a238d56 100644 --- a/src/net.h +++ b/src/net.h @@ -256,6 +256,7 @@ public: uint64_t nServices; SOCKET hSocket; CDataStream ssSend; + std::string strSendCommand; // Current command being assembled in ssSend size_t nSendSize; // total size of all vSendMsg entries size_t nSendOffset; // offset inside the first vSendMsg already sent uint64_t nSendBytes; diff --git a/src/rust/include/rust/VA_OPT.hpp b/src/rust/include/rust/VA_OPT.hpp new file mode 100644 index 000000000..553b637ac --- /dev/null +++ b/src/rust/include/rust/VA_OPT.hpp @@ -0,0 +1,156 @@ +// Copyright (c) 2019 Will Wray https://keybase.io/willwray +// Copyright (c) 2020 The Zcash developers +// +// Distributed under the Boost Software License, Version 1.0. +// http://www.boost.org/LICENSE_1_0.txt +// +// Repo: https://github.com/willwray/VA_OPT + +#ifndef ZCASH_RUST_INCLUDE_RUST_VA_OPT_H +#define ZCASH_RUST_INCLUDE_RUST_VA_OPT_H + +/* + VA_OPT.hpp + ========== + + Preprocessor utilities for testing emptiness of macro arguments + and for conditional expansion based on the emptiness of ARGS. + + VA_OPT_SUPPORT(?) + 1 if __VA_OPT__ support is detected else 0 (see platform note). + + C++20's __VA_OPT__ finally provides a reliable test for empty ARGS. + The following macros use __VA_OPT__, if detected, otherwise they + provide 'polyfill' fallbacks (though with some failing edge cases). + + IS_EMPTY(...) + 1 if the ... ARGS is empty else 0. + + IFN(...) + If ... ARGS are not empty then a trailing 'paren expression' (X) + is deparenthesized to X else the trailing (X) term is consumed. + E.g. IFN()(N) vanishes, while IFN(NotEmpty)(N) -> N + + In other words, IFN(ARGS) is like __VA_OPT__, but with explicit + (ARGS) in place of an implicit __VA_ARGS__ check. + + IFE(...) + If ... ARGS is empty expand trailing 'paren expression' (X) to X + else if ARGS are not empty consume the trailing paren expression. + E.g. IFE(NotEmpty)(E) vanishes, while IFE()(E) -> E + + IFNE(...)(N,E...) + If ... ARGS are not empty expands to N else expands to E... + E.g. IFNE(ARG)(X,()) is equivalent to IFN(ARG)(X)IFE(ARG)(()) + both put back a terminating () removed by the outer macro call. + + Without VA_OPT_SUPPORT these 'emptiness' macros are not perfect; + IS_EMPTY, IFN, IFE, IFNE may cause a compile error ('too few args') + if the argument is a function-like macro name that expects ARG(s). + + IBP(...) + IS_BEGIN_PARENS macro to test if an argument is parenthesised: + 1 if ... ARGS begins with a 'paren expression' else 0. + + Platform note: Current Sept 2019 __VA_OPT__ support: + ------------- + Clang -std=c++2a enables it. GCC has it enabled without -std=c++2a + but warns "__VA_OPT__ is not available until C++2a" if another -std + flag is supplied along with -pedantic (dont know how to supress it). + MSVC TBD + + Credits + ------- + Props to pre-pro pioneers, particularly Paul Mensonides. + The 'emptiness' methods are adapted from BOOST_VMD_IS_EMPTY which, +. in turn, depends on BOOST Preprocessor's BOOST_PP_IS_BEGIN_PARENS + (adapted and exposed here as IBP 'Is Begin Parens'): + www.boost.org/doc/libs/1_71_0/libs/vmd + www.boost.org/doc/libs/1_71_0/libs/preprocessor +*/ + +#define VA_ARG1(A0,A1,...) A1 +// VA_EMPTY works only if __VA_OPT__ is supported, else always -> 1 +#define VA_EMPTY(...) VA_ARG1(__VA_OPT__(,)0,1,) + +// VA_OPT_SUPPORT helper macro for __VA_OPT__ feature detection. +// Adapted from https://stackoverflow.com/a/48045656/7443483 +// Use as #if VA_OPT_SUPPORT(?) +#define VA_OPT_SUPPORT ! VA_EMPTY + +#if VA_OPT_SUPPORT(?) + +# define IS_EMPTY(...) VA_EMPTY(__VA_ARGS__) +# define IFN(...) VA_EAT __VA_OPT__(()VA_IDENT) +# define IFE(...) VA_IDENT __VA_OPT__(()VA_EAT) +# define IFNE(...) VA_ARGTAIL __VA_OPT__((,)VA_ARG0) + +#else + +# define IS_EMPTY(...) IFP(IBP(__VA_ARGS__))(IE_GEN_0,IE_IBP)(__VA_ARGS__) +# define IFN(...) IFP(IBP(__VA_ARGS__))(GEN_IDENT,EAT_OR_IDENT)(__VA_ARGS__) +# define IFE(...) IFP(IBP(__VA_ARGS__))(GEN_EAT,IDENT_OR_EAT)(__VA_ARGS__) +# define IFNE(...) IFP(IBP(__VA_ARGS__))(GEN_ARGTAIL,ARG0_OR_TAIL)(__VA_ARGS__) + +#endif + +#define VA_EAT(...) +#define VA_IDENT(...) __VA_ARGS__ +#define VA_ARG0_(A0,...) A0 +#define VA_ARG0(...) VA_ARG0_(__VA_ARGS__) +#define VA_ARGTAIL_(A0,...) __VA_ARGS__ +#define VA_ARGTAIL(...) VA_ARGTAIL_(__VA_ARGS__) + +// IFP helper macros to test IBP for IFN and IS_EMPTY +#define IFP_0(T,...) __VA_ARGS__ +#define IFP_1(T,...) T + +#define IFP_CAT(A,...) A##__VA_ARGS__ +#define IFP(BP) IFP_CAT(IFP_,BP) + +// IS_BEGIN_PAREN helper macros adapted from BOOST VMD +#define IBP_CAT_(A,...) A##__VA_ARGS__ +#define IBP_CAT(A,...) IBP_CAT_(A,__VA_ARGS__) + +#define IBP_ARG0_(A,...) A +#define IBP_ARG0(...) IBP_ARG0_(__VA_ARGS__) + +#define IBP_IS_ARGS(...) 1 + +#define IBP_1 1, +#define IBP_IBP_IS_ARGS 0, + +// IBP IS_BEGIN_PAREN returns 1 or 0 if ... ARGS is parenthesised +#define IBP(...) IBP_ARG0(IBP_CAT(IBP_, IBP_IS_ARGS __VA_ARGS__)) + +// IFN, IFE, IFNE and IF_EMPTY helpers without __VA_OPT__ support +#if ! VA_OPT_SUPPORT(?) + +# define IBP_(T,...) IBP_ARG0(IBP_CAT(IF##T##_, IBP_IS_ARGS __VA_ARGS__)) + + // IS_EMPTY helper macros, depend on IBP +# define IE_REDUCE_IBP(...) () +# define IE_GEN_0(...) 0 +# define IE_IBP(...) IBP(IE_REDUCE_IBP __VA_ARGS__ ()) + +# define GEN_IDENT(...) VA_IDENT +# define GEN_EAT(...) VA_EAT +# define GEN_ARGTAIL(...) VA_ARGTAIL +# define GEN_ARG0(...) VA_ARG0 + + // IFN, IFE, IFNE helper macros +# define EAT_OR_IDENT(...) IBP_(N,IE_REDUCE_IBP __VA_ARGS__ ()) +# define IFN_1 VA_EAT, +# define IFN_IBP_IS_ARGS VA_IDENT, + +# define IDENT_OR_EAT(...) IBP_(E,IE_REDUCE_IBP __VA_ARGS__ ()) +# define IFE_1 VA_IDENT, +# define IFE_IBP_IS_ARGS VA_EAT, + +# define ARG0_OR_TAIL(...) IBP_(NE,IE_REDUCE_IBP __VA_ARGS__ ()) +# define IFNE_1 VA_ARGTAIL, +# define IFNE_IBP_IS_ARGS VA_ARG0, + +#endif // IFN and IF_EMPTY defs + +#endif // ZCASH_RUST_INCLUDE_RUST_VA_OPT_H diff --git a/src/rust/include/rust/helpers.h b/src/rust/include/rust/helpers.h new file mode 100644 index 000000000..0bdaa113a --- /dev/null +++ b/src/rust/include/rust/helpers.h @@ -0,0 +1,34 @@ +// Copyright (c) 2020 Jack Grigg +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php . + +#ifndef ZCASH_RUST_INCLUDE_RUST_HELPERS_H +#define ZCASH_RUST_INCLUDE_RUST_HELPERS_H + +#include "rust/map.h" +#include "rust/VA_OPT.hpp" + +// +// Helper macros +// + +#define MAP_PAIR_LIST0(f, x, y, peek, ...) f(x, y) MAP_LIST_NEXT(peek, MAP_PAIR_LIST1)(f, peek, __VA_ARGS__) +#define MAP_PAIR_LIST1(f, x, y, peek, ...) f(x, y) MAP_LIST_NEXT(peek, MAP_PAIR_LIST0)(f, peek, __VA_ARGS__) + +/// Applies the function macro `f` to each pair of the remaining parameters and +/// inserts commas between the results. +#define MAP_PAIR_LIST(f, ...) EVAL(MAP_PAIR_LIST1(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define T_FIELD_NAME(x, y) x +#define T_FIELD_VALUE(x, y) y + +#define T_FIELD_NAMES(...) IFN(__VA_ARGS__)(MAP_PAIR_LIST(T_FIELD_NAME, __VA_ARGS__)) +#define T_FIELD_VALUES(...) IFN(__VA_ARGS__)(MAP_PAIR_LIST(T_FIELD_VALUE, __VA_ARGS__)) + +#define T_DOUBLEESCAPE(a) #a +#define T_ESCAPEQUOTE(a) T_DOUBLEESCAPE(a) + +// Computes the length of the given array. This is COUNT_OF from Chromium. +#define T_ARRLEN(x) ((sizeof(x) / sizeof(0 [x])) / ((size_t)(!(sizeof(x) % sizeof(0 [x]))))) + +#endif // ZCASH_RUST_INCLUDE_RUST_HELPERS_H diff --git a/src/rust/include/tracing/map.h b/src/rust/include/rust/map.h similarity index 94% rename from src/rust/include/tracing/map.h rename to src/rust/include/rust/map.h index 45f572ed2..efdbbb7f2 100644 --- a/src/rust/include/tracing/map.h +++ b/src/rust/include/rust/map.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 William Swanson + * Copyright (C) 2020 The Zcash developers * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -26,8 +27,8 @@ * prior written authorization from the authors. */ -#ifndef ZCASH_RUST_INCLUDE_TRACING_MAP_H -#define ZCASH_RUST_INCLUDE_TRACING_MAP_H +#ifndef ZCASH_RUST_INCLUDE_RUST_MAP_H +#define ZCASH_RUST_INCLUDE_RUST_MAP_H #define EVAL0(...) __VA_ARGS__ #define EVAL1(...) EVAL0(EVAL0(EVAL0(__VA_ARGS__))) @@ -67,4 +68,4 @@ */ #define MAP_LIST(f, ...) EVAL(MAP_LIST1(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) -#endif // ZCASH_RUST_INCLUDE_TRACING_MAP_H +#endif // ZCASH_RUST_INCLUDE_RUST_MAP_H diff --git a/src/rust/include/rust/metrics.h b/src/rust/include/rust/metrics.h new file mode 100644 index 000000000..c3133981e --- /dev/null +++ b/src/rust/include/rust/metrics.h @@ -0,0 +1,314 @@ +// Copyright (c) 2020 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php . + +#ifndef ZCASH_RUST_INCLUDE_RUST_METRICS_H +#define ZCASH_RUST_INCLUDE_RUST_METRICS_H + +#include "rust/helpers.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/// Initializes the metrics runtime and runs the Prometheus exporter in a new +/// thread. +/// +/// bind_address is an IP address to bind to, or empty to use the default. +/// +/// Returns false on any error. +bool metrics_run( + const char* bind_address, + const char* const* allow_ips, + size_t allow_ips_len, + uint16_t prometheus_port); + +struct MetricsCallsite; +typedef struct MetricsCallsite MetricsCallsite; + +struct MetricsKey; +typedef struct MetricsKey MetricsKey; + +/// Creates a metrics callsite. +/// +/// This API supports labels that MUST have static values. For non-static label +/// values, use `metrics_key`. +/// +/// You should usually call one of the helper macros such as `MetricsCounter` +/// instead of calling this directly. +/// +/// This MUST ONLY be called to assign a `static MetricsCallsite*`, and all +/// string arguments MUST be static `const char*` constants, and MUST be valid +/// UTF-8. +MetricsCallsite* metrics_callsite( + const char* name, + const char* const* label_names, + const char* const* label_values, + size_t labels_len); + +/// Creates a metrics key. +/// +/// This API supports labels that may not have static values, and is intended +/// to be called for each metrics callsite invocation. As such, it returns null +/// if a metrics recorder is not installed, to save on construction costs. +/// +/// You should usually call one of the helper macros such as `MetricsCounter` +/// instead of calling this directly. +/// +/// API requirements: +/// - label_names and label_values, if not null, MUST be the same length. +/// - All string arguments MUST be valid UTF-8. +MetricsKey* metrics_key( + const char* name, + const char* const* label_names, + const char* const* label_values, + size_t labels_len); + +/// Increments a counter. +/// +/// Counters represent a single monotonic value, which means the value can only +/// be incremented, not decremented, and always starts out with an initial value +/// of zero. +void metrics_static_increment_counter(const MetricsCallsite* callsite, uint64_t value); + +/// Increments a counter. +/// +/// Counters represent a single monotonic value, which means the value can only +/// be incremented, not decremented, and always starts out with an initial value +/// of zero. +void metrics_increment_counter(MetricsKey* key, uint64_t value); + +/// Updates a gauge. +/// +/// Gauges represent a single value that can go up or down over time, and always +/// starts out with an initial value of zero. +void metrics_static_update_gauge(const MetricsCallsite* callsite, double value); + +/// Updates a gauge. +/// +/// Gauges represent a single value that can go up or down over time, and always +/// starts out with an initial value of zero. +void metrics_update_gauge(MetricsKey* callsite, double value); + +/// Increments a gauge. +/// +/// Gauges represent a single value that can go up or down over time, and always +/// starts out with an initial value of zero. +void metrics_static_increment_gauge(const MetricsCallsite* callsite, double value); + +/// Increments a gauge. +/// +/// Gauges represent a single value that can go up or down over time, and always +/// starts out with an initial value of zero. +void metrics_increment_gauge(MetricsKey* callsite, double value); + +/// Decrements a gauge. +/// +/// Gauges represent a single value that can go up or down over time, and always +/// starts out with an initial value of zero. +void metrics_static_decrement_gauge(const MetricsCallsite* callsite, double value); + +/// Decrements a gauge. +/// +/// Gauges represent a single value that can go up or down over time, and always +/// starts out with an initial value of zero. +void metrics_decrement_gauge(MetricsKey* callsite, double value); + +/// Records a histogram. +/// +/// Histograms measure the distribution of values for a given set of +/// measurements, and start with no initial values. +void metrics_static_record_histogram(const MetricsCallsite* callsite, double value); + +/// Records a histogram. +/// +/// Histograms measure the distribution of values for a given set of +/// measurements, and start with no initial values. +void metrics_record_histogram(MetricsKey* callsite, double value); + +#ifdef __cplusplus +} +#endif + +// +// Helper macros +// + +#ifdef __cplusplus +// Constructs a metrics callsite. +// +// The 'static constexpr' hack ensures that all arguments are compile-time +// constants with static storage duration. The output of this macro MUST be +// stored as a static MetricsCallsite*. +#define M_CALLSITE(name, label_names, label_values) ([&] { \ + static constexpr const char* _m_name = name; \ + static constexpr const char* const* _m_label_names = \ + label_names; \ + static constexpr const char* const* _m_label_values = \ + label_values; \ + return metrics_callsite( \ + _m_name, _m_label_names, _m_label_values, \ + T_ARRLEN(label_names)); \ +}()) +#else +// Constructs a metrics callsite. +// +// All arguments MUST be static constants, and the output of this macro MUST be +// stored as a static MetricsCallsite*. +#define M_CALLSITE(name, label_names, label_values) \ + metrics_callsite(name, label_names, label_values, T_ARRLEN(label_names)) +#endif + +// Constructs a metrics key. +#define M_KEY(name, labels, values) \ + metrics_key( \ + name, \ + labels, \ + values, \ + T_ARRLEN(labels)) + +// +// Metrics +// + +/// Increments a counter. +/// +/// Counters represent a single monotonic value, which means the value can only +/// be incremented, not decremented, and always starts out with an initial value +/// of zero. +/// +/// name MUST be a static constant, and all strings MUST be valid UTF-8. +#define MetricsCounter(name, value, ...) \ + do { \ + IFE(__VA_ARGS__) \ + (static constexpr const char* const EMPTY[] = {}; \ + static MetricsCallsite* CALLSITE = \ + M_CALLSITE(name, EMPTY, EMPTY); \ + metrics_static_increment_counter(CALLSITE, value);) \ + IFN(__VA_ARGS__)(const char* M_LABELS[] = \ + {T_FIELD_NAMES(__VA_ARGS__)}; \ + const char* M_VALUES[] = \ + {T_FIELD_VALUES(__VA_ARGS__)}; \ + MetricsKey* KEY = \ + M_KEY(name, M_LABELS, M_VALUES); \ + metrics_increment_counter(KEY, value);) \ + } while (0) + +/// Increments a counter by one. +/// +/// Counters represent a single monotonic value, which means the value can only +/// be incremented, not decremented, and always starts out with an initial value +/// of zero. +/// +/// name MUST be a static constant, and all strings MUST be valid UTF-8. +#define MetricsIncrementCounter(name, ...) MetricsCounter(name, 1, __VA_ARGS__) + +/// Updates a gauge. +/// +/// Gauges represent a single value that can go up or down over time, and always +/// starts out with an initial value of zero. +/// +/// name MUST be a static constant, and all strings MUST be valid UTF-8. +#define MetricsGauge(name, value, ...) \ + do { \ + IFE(__VA_ARGS__) \ + (static constexpr const char* const EMPTY[] = {}; \ + static MetricsCallsite* CALLSITE = \ + M_CALLSITE(name, EMPTY, EMPTY); \ + metrics_static_update_gauge(CALLSITE, value);) \ + IFN(__VA_ARGS__)(const char* M_LABELS[] = \ + {T_FIELD_NAMES(__VA_ARGS__)}; \ + const char* M_VALUES[] = \ + {T_FIELD_VALUES(__VA_ARGS__)}; \ + MetricsKey* KEY = \ + M_KEY(name, M_LABELS, M_VALUES); \ + metrics_update_gauge(KEY, value);) \ + } while (0) + +/// Updates a gauge with optional static labels. +/// +/// Gauges represent a single value that can go up or down over time, and always +/// starts out with an initial value of zero. +/// +/// name MUST be a static constant, and all strings MUST be valid UTF-8. +#define MetricsStaticGauge(name, value, ...) \ + do { \ + static constexpr const char* M_LABELS[] = \ + {T_FIELD_NAMES(__VA_ARGS__)}; \ + static constexpr const char* M_VALUES[] = \ + {T_FIELD_VALUES(__VA_ARGS__)}; \ + static MetricsCallsite* CALLSITE = \ + M_CALLSITE(name, M_LABELS, M_VALUES); \ + metrics_static_update_gauge(CALLSITE, value); \ + } while (0) + +/// Increments a gauge. +/// +/// Gauges represent a single value that can go up or down over time, and always +/// starts out with an initial value of zero. +/// +/// name MUST be a static constant, and all strings MUST be valid UTF-8. +#define MetricsIncrementGauge(name, value, ...) \ + do { \ + IFE(__VA_ARGS__) \ + (static constexpr const char* const EMPTY[] = {}; \ + static MetricsCallsite* CALLSITE = \ + M_CALLSITE(name, EMPTY, EMPTY); \ + metrics_static_increment_gauge(CALLSITE, value);) \ + IFN(__VA_ARGS__)(const char* M_LABELS[] = \ + {T_FIELD_NAMES(__VA_ARGS__)}; \ + const char* M_VALUES[] = \ + {T_FIELD_VALUES(__VA_ARGS__)}; \ + MetricsKey* KEY = \ + M_KEY(name, M_LABELS, M_VALUES); \ + metrics_increment_gauge(KEY, value);) \ + } while (0) + +/// Decrements a gauge. +/// +/// Gauges represent a single value that can go up or down over time, and always +/// starts out with an initial value of zero. +/// +/// name MUST be a static constant, and all strings MUST be valid UTF-8. +#define MetricsDecrementGauge(name, value, ...) \ + do { \ + IFE(__VA_ARGS__) \ + (static constexpr const char* const EMPTY[] = {}; \ + static MetricsCallsite* CALLSITE = \ + M_CALLSITE(name, EMPTY, EMPTY); \ + metrics_static_decrement_gauge(CALLSITE, value);) \ + IFN(__VA_ARGS__)(const char* M_LABELS[] = \ + {T_FIELD_NAMES(__VA_ARGS__)}; \ + const char* M_VALUES[] = \ + {T_FIELD_VALUES(__VA_ARGS__)}; \ + MetricsKey* KEY = \ + M_KEY(name, M_LABELS, M_VALUES); \ + metrics_decrement_gauge(KEY, value);) \ + } while (0) + +/// Records a histogram. +/// +/// Histograms measure the distribution of values for a given set of +/// measurements, and start with no initial values. +/// +/// name MUST be a static constant, and all strings MUST be valid UTF-8. +#define MetricsHistogram(name, value, ...) \ + do { \ + IFE(__VA_ARGS__) \ + (static constexpr const char* const EMPTY[] = {}; \ + static MetricsCallsite* CALLSITE = \ + M_CALLSITE(name, EMPTY, EMPTY); \ + metrics_static_record_histogram(CALLSITE, value);) \ + IFN(__VA_ARGS__)(const char* M_LABELS[] = \ + {T_FIELD_NAMES(__VA_ARGS__)}; \ + const char* M_VALUES[] = \ + {T_FIELD_VALUES(__VA_ARGS__)}; \ + MetricsKey* KEY = \ + M_KEY(name, M_LABELS, M_VALUES); \ + metrics_record_histogram(KEY, value);) \ + } while (0) + +#endif // ZCASH_RUST_INCLUDE_RUST_METRICS_H diff --git a/src/rust/include/tracing.h b/src/rust/include/tracing.h index ce72976dd..178204595 100644 --- a/src/rust/include/tracing.h +++ b/src/rust/include/tracing.h @@ -5,8 +5,8 @@ #ifndef ZCASH_RUST_INCLUDE_TRACING_H #define ZCASH_RUST_INCLUDE_TRACING_H +#include "rust/helpers.h" #include "rust/types.h" -#include "tracing/map.h" #include #include @@ -107,25 +107,6 @@ void tracing_log( // Helper macros // -#define MAP_PAIR_LIST0(f, x, y, peek, ...) f(x, y) MAP_LIST_NEXT(peek, MAP_PAIR_LIST1)(f, peek, __VA_ARGS__) -#define MAP_PAIR_LIST1(f, x, y, peek, ...) f(x, y) MAP_LIST_NEXT(peek, MAP_PAIR_LIST0)(f, peek, __VA_ARGS__) - -/// Applies the function macro `f` to each pair of the remaining parameters and -/// inserts commas between the results. -#define MAP_PAIR_LIST(f, ...) EVAL(MAP_PAIR_LIST1(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) - -#define T_FIELD_NAME(x, y) x -#define T_FIELD_VALUE(x, y) y - -#define T_FIELD_NAMES(...) MAP_PAIR_LIST(T_FIELD_NAME, __VA_ARGS__) -#define T_FIELD_VALUES(...) MAP_PAIR_LIST(T_FIELD_VALUE, __VA_ARGS__) - -#define T_DOUBLEESCAPE(a) #a -#define T_ESCAPEQUOTE(a) T_DOUBLEESCAPE(a) - -// Computes the length of the given array. This is COUNT_OF from Chromium. -#define T_ARRLEN(x) ((sizeof(x) / sizeof(0 [x])) / ((size_t)(!(sizeof(x) % sizeof(0 [x]))))) - #ifdef __cplusplus // Constructs a tracing callsite. // @@ -251,22 +232,6 @@ public: }; } // namespace tracing -/// Expands to a `tracing::Span` object which is used to record a span. -/// The `Span::Enter` method on that object records that the span has been -/// entered, and returns a RAII guard object, which will exit the span when -/// dropped. -/// -/// level, target, and name MUST be static constants, and MUST be valid UTF-8 -/// strings. -#define TracingSpan(level, target, name) ([&] { \ - static constexpr const char* const FIELDS[] = {}; \ - const char* T_VALUES[] = {}; \ - static TracingCallsite* CALLSITE = \ - T_CALLSITE(name, target, level, FIELDS, true); \ - return tracing::Span( \ - CALLSITE, T_VALUES, T_ARRLEN(T_VALUES)); \ -}()) - /// Expands to a `tracing::Span` object which is used to record a span. /// The `Span::Enter` method on that object records that the span has been /// entered, and returns a RAII guard object, which will exit the span when @@ -276,15 +241,15 @@ public: /// /// level, target, name, and all keys MUST be static constants, and MUST be /// valid UTF-8 strings. -#define TracingSpanFields(level, target, name, ...) ([&] { \ - static constexpr const char* const FIELDS[] = \ - {T_FIELD_NAMES(__VA_ARGS__)}; \ - const char* T_VALUES[] = \ - {T_FIELD_VALUES(__VA_ARGS__)}; \ - static TracingCallsite* CALLSITE = \ - T_CALLSITE(name, target, level, FIELDS, true); \ - return tracing::Span( \ - CALLSITE, T_VALUES, T_ARRLEN(T_VALUES)); \ +#define TracingSpan(level, target, name, ...) ([&] { \ + static constexpr const char* const FIELDS[] = \ + {T_FIELD_NAMES(__VA_ARGS__)}; \ + const char* T_VALUES[] = \ + {T_FIELD_VALUES(__VA_ARGS__)}; \ + static TracingCallsite* CALLSITE = \ + T_CALLSITE(name, target, level, FIELDS, true); \ + return tracing::Span( \ + CALLSITE, T_VALUES, T_ARRLEN(T_VALUES)); \ }()) #endif diff --git a/src/rust/src/metrics_ffi.rs b/src/rust/src/metrics_ffi.rs new file mode 100644 index 000000000..6acb51347 --- /dev/null +++ b/src/rust/src/metrics_ffi.rs @@ -0,0 +1,244 @@ +use libc::{c_char, c_double}; +use metrics::{try_recorder, GaugeValue, Key, KeyData, Label}; +use metrics_exporter_prometheus::PrometheusBuilder; +use std::ffi::CStr; +use std::net::{IpAddr, SocketAddr}; +use std::ptr; +use std::slice; +use tracing::error; + +mod prometheus; + +#[no_mangle] +pub extern "C" fn metrics_run( + bind_address: *const c_char, + allow_ips: *const *const c_char, + allow_ips_len: usize, + prometheus_port: u16, +) -> bool { + // Parse any allowed IPs. + let allow_ips = unsafe { slice::from_raw_parts(allow_ips, allow_ips_len) }; + let mut allow_ips: Vec = match allow_ips + .iter() + .map(|&p| unsafe { CStr::from_ptr(p) }) + .map(|s| { + s.to_str().ok().and_then(|s| { + s.parse() + .map_err(|e| { + error!("Invalid -metricsallowip argument '{}': {}", s, e); + }) + .ok() + }) + }) + .collect() + { + Some(ips) => ips, + None => { + return false; + } + }; + // We always allow localhost. + allow_ips.extend(&["127.0.0.0/8".parse().unwrap(), "::1/128".parse().unwrap()]); + + // Parse the address to bind to. + let bind_address = SocketAddr::new( + if allow_ips.is_empty() { + // Default to loopback if not allowing external IPs. + "127.0.0.1".parse::().unwrap() + } else if bind_address.is_null() { + // No specific bind address specified, bind to any. + "0.0.0.0".parse::().unwrap() + } else { + match unsafe { CStr::from_ptr(bind_address) } + .to_str() + .ok() + .and_then(|s| s.parse::().ok()) + { + Some(addr) => addr, + None => { + error!("Invalid -metricsbind argument"); + return false; + } + } + }, + prometheus_port, + ); + + prometheus::install(bind_address, PrometheusBuilder::new(), allow_ips).is_ok() +} + +pub struct FfiCallsite { + key_data: KeyData, +} + +#[no_mangle] +pub extern "C" fn metrics_callsite( + name: *const c_char, + label_names: *const *const c_char, + label_values: *const *const c_char, + labels_len: usize, +) -> *mut FfiCallsite { + let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap(); + let labels = unsafe { slice::from_raw_parts(label_names, labels_len) }; + let values = unsafe { slice::from_raw_parts(label_values, labels_len) }; + + let stringify = |s: &[_]| { + s.iter() + .map(|&p| unsafe { CStr::from_ptr(p) }) + .map(|cs| cs.to_string_lossy().into_owned()) + .collect::>() + }; + let labels = stringify(labels); + let values = stringify(values); + + let labels: Vec<_> = labels + .into_iter() + .zip(values.into_iter()) + .map(|(name, value)| Label::new(name, value)) + .collect(); + + Box::into_raw(Box::new(FfiCallsite { + key_data: KeyData::from_parts(name, labels), + })) +} + +pub struct FfiKey { + inner: Key, +} + +#[no_mangle] +pub extern "C" fn metrics_key( + name: *const c_char, + label_names: *const *const c_char, + label_values: *const *const c_char, + labels_len: usize, +) -> *mut FfiKey { + if try_recorder().is_none() { + // No recorder is currently installed, so don't genenerate a key. We check for + // null inside each API that consumes an FfiKey, just in case a recorder was + // installed in a racy way. + ptr::null_mut() + } else { + let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap(); + let labels = unsafe { slice::from_raw_parts(label_names, labels_len) }; + let values = unsafe { slice::from_raw_parts(label_values, labels_len) }; + + let stringify = |s: &[_]| { + s.iter() + .map(|&p| unsafe { CStr::from_ptr(p) }) + .map(|cs| cs.to_string_lossy().into_owned()) + .collect::>() + }; + let labels = stringify(labels); + let values = stringify(values); + + let labels: Vec<_> = labels + .into_iter() + .zip(values.into_iter()) + .map(|(name, value)| Label::new(name, value)) + .collect(); + + Box::into_raw(Box::new(FfiKey { + inner: Key::Owned(KeyData::from_parts(name, labels)), + })) + } +} + +#[no_mangle] +pub extern "C" fn metrics_static_increment_counter(callsite: *const FfiCallsite, value: u64) { + if let Some(recorder) = try_recorder() { + let callsite = unsafe { callsite.as_ref().unwrap() }; + recorder.increment_counter(Key::Borrowed(&callsite.key_data), value); + } +} + +#[no_mangle] +pub extern "C" fn metrics_increment_counter(key: *mut FfiKey, value: u64) { + if let Some(recorder) = try_recorder() { + if !key.is_null() { + let key = unsafe { Box::from_raw(key) }; + recorder.increment_counter(key.inner, value); + } + } +} + +#[no_mangle] +pub extern "C" fn metrics_static_update_gauge(callsite: *const FfiCallsite, value: c_double) { + if let Some(recorder) = try_recorder() { + let callsite = unsafe { callsite.as_ref().unwrap() }; + recorder.update_gauge( + Key::Borrowed(&callsite.key_data), + GaugeValue::Absolute(value), + ); + } +} + +#[no_mangle] +pub extern "C" fn metrics_update_gauge(key: *mut FfiKey, value: c_double) { + if let Some(recorder) = try_recorder() { + if !key.is_null() { + let key = unsafe { Box::from_raw(key) }; + recorder.update_gauge(key.inner, GaugeValue::Absolute(value)); + } + } +} + +#[no_mangle] +pub extern "C" fn metrics_static_increment_gauge(callsite: *const FfiCallsite, value: c_double) { + if let Some(recorder) = try_recorder() { + let callsite = unsafe { callsite.as_ref().unwrap() }; + recorder.update_gauge( + Key::Borrowed(&callsite.key_data), + GaugeValue::Increment(value), + ); + } +} + +#[no_mangle] +pub extern "C" fn metrics_increment_gauge(key: *mut FfiKey, value: c_double) { + if let Some(recorder) = try_recorder() { + if !key.is_null() { + let key = unsafe { Box::from_raw(key) }; + recorder.update_gauge(key.inner, GaugeValue::Increment(value)); + } + } +} + +#[no_mangle] +pub extern "C" fn metrics_static_decrement_gauge(callsite: *const FfiCallsite, value: c_double) { + if let Some(recorder) = try_recorder() { + let callsite = unsafe { callsite.as_ref().unwrap() }; + recorder.update_gauge( + Key::Borrowed(&callsite.key_data), + GaugeValue::Decrement(value), + ); + } +} + +#[no_mangle] +pub extern "C" fn metrics_decrement_gauge(key: *mut FfiKey, value: c_double) { + if let Some(recorder) = try_recorder() { + if !key.is_null() { + let key = unsafe { Box::from_raw(key) }; + recorder.update_gauge(key.inner, GaugeValue::Decrement(value)); + } + } +} + +#[no_mangle] +pub extern "C" fn metrics_static_record_histogram(callsite: *const FfiCallsite, value: c_double) { + if let Some(recorder) = try_recorder() { + let callsite = unsafe { callsite.as_ref().unwrap() }; + recorder.record_histogram(Key::Borrowed(&callsite.key_data), value); + } +} + +#[no_mangle] +pub extern "C" fn metrics_record_histogram(key: *mut FfiKey, value: c_double) { + if let Some(recorder) = try_recorder() { + if !key.is_null() { + let key = unsafe { Box::from_raw(key) }; + recorder.record_histogram(key.inner, value); + } + } +} diff --git a/src/rust/src/metrics_ffi/prometheus.rs b/src/rust/src/metrics_ffi/prometheus.rs new file mode 100644 index 000000000..da11f5f6b --- /dev/null +++ b/src/rust/src/metrics_ffi/prometheus.rs @@ -0,0 +1,124 @@ +// This is mostly code copied from metrics_exporter_prometheus. The copied portions are +// licensed under the same terms as the zcash codebase (reproduced below from +// https://github.com/metrics-rs/metrics/blob/main/metrics-exporter-prometheus/LICENSE): +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +use hyper::{ + server::{conn::AddrStream, Server}, + service::{make_service_fn, service_fn}, + {Body, Error as HyperError, Response, StatusCode}, +}; +use metrics::SetRecorderError; +use metrics_exporter_prometheus::{PrometheusBuilder, PrometheusRecorder}; +use std::future::Future; +use std::io; +use std::net::SocketAddr; +use std::thread; +use thiserror::Error as ThisError; +use tokio::{pin, runtime, select}; + +/// Errors that could occur while installing a Prometheus recorder/exporter. +#[derive(Debug, ThisError)] +pub enum InstallError { + /// Creating the networking event loop did not succeed. + #[error("failed to spawn Tokio runtime for endpoint: {0}")] + Io(#[from] io::Error), + + /// Binding/listening to the given address did not succeed. + #[error("failed to bind to given listen address: {0}")] + Hyper(#[from] HyperError), + + /// Installing the recorder did not succeed. + #[error("failed to install exporter as global recorder: {0}")] + Recorder(#[from] SetRecorderError), +} + +/// A copy of `PrometheusBuilder::build_with_exporter` that adds support for an IP address +/// or subnet allowlist. +pub(super) fn build( + bind_address: SocketAddr, + builder: PrometheusBuilder, + allow_ips: Vec, +) -> Result< + ( + PrometheusRecorder, + impl Future> + Send + 'static, + ), + InstallError, +> { + let recorder = builder.build(); + let handle = recorder.handle(); + + let server = Server::try_bind(&bind_address)?; + + let exporter = async move { + let make_svc = make_service_fn(move |socket: &AddrStream| { + let remote_addr = socket.remote_addr().ip(); + let allowed = allow_ips.iter().any(|subnet| subnet.contains(&remote_addr)); + let handle = handle.clone(); + + async move { + Ok::<_, HyperError>(service_fn(move |_| { + let handle = handle.clone(); + + async move { + if allowed { + let output = handle.render(); + Ok(Response::new(Body::from(output))) + } else { + Response::builder() + .status(StatusCode::FORBIDDEN) + .body(Body::empty()) + } + } + })) + } + }); + + server.serve(make_svc).await + }; + + Ok((recorder, exporter)) +} + +/// A copy of `PrometheusBuilder::install` that adds support for an IP address or subnet +/// allowlist. +pub(super) fn install( + bind_address: SocketAddr, + builder: PrometheusBuilder, + allow_ips: Vec, +) -> Result<(), InstallError> { + let runtime = runtime::Builder::new_current_thread() + .enable_all() + .build()?; + + let (recorder, exporter) = { + let _guard = runtime.enter(); + build(bind_address, builder, allow_ips)? + }; + metrics::set_boxed_recorder(Box::new(recorder))?; + + thread::Builder::new() + .name("zcash-prometheus".to_string()) + .spawn(move || { + runtime.block_on(async move { + pin!(exporter); + loop { + select! { + _ = &mut exporter => {} + } + } + }); + })?; + + Ok(()) +} diff --git a/src/rust/src/rustzcash.rs b/src/rust/src/rustzcash.rs index 677011a6b..9994995d2 100644 --- a/src/rust/src/rustzcash.rs +++ b/src/rust/src/rustzcash.rs @@ -63,6 +63,7 @@ use zcash_history::{Entry as MMREntry, NodeData as MMRNodeData, Tree as MMRTree} mod blake2b; mod ed25519; +mod metrics_ffi; mod tracing_ffi; #[cfg(test)] diff --git a/src/util.cpp b/src/util.cpp index 1342c00f5..f7c6233c6 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -389,6 +389,7 @@ void ReadConfigFile(const std::string& confPath, "externalip", "fundingstream", "loadblock", + "metricsallowip", "nuparams", "onlynet", "rpcallowip",