Auto merge of #4947 - str4d:rust-metrics, r=str4d

Add metrics collection and a Prometheus exporter

This adds an FFI wrapper around the `metrics` crate, and exposes CPP macros for collecting metrics in the C++ codebase. With this, we can collect metrics from both C++ and Rust.

The following metrics are included in this PR:
- Chain metrics:
  - (counter) `zcash.chain.verified.block.total`
  - (gauge) `zcash.chain.verified.block.height`
  - (histogram) `zcash.chain.verified.block.seconds`
- Value pool metrics:
  - (gauge) `zcash.pool.notes.created[name]`
  - (gauge) `zcash.pool.notes.spent[name]` (currently not measured)
  - (gauge) `zcash.pool.notes.unspent[name]` (currently not measured)
  - (gauge) `zcash.pool.value.zatoshis[name]`
- P2P network metrics:
  - (gauge) `zcash.net.peers`
  - (counter) `zcash.net.in.bytes.total`
  - (counter) `zcash.net.in.bytes[command]`
  - (counter) `zcash.net.in.messages[command]`
  - (counter) `zcash.net.out.bytes.total`
  - (counter) `zcash.net.out.bytes[command]`
  - (counter) `zcash.net.out.messages[command]`
- Node metrics:
  - (gauge) `zcash.mempool.size.transactions`
  - (gauge) `zcash.mempool.size.bytes`
  - (gauge) `zcash.mempool.usage.bytes`
  - (informational) `zcashd.build.info`

Collection of metrics is conditional on enabling an exporter. This PR adds a Prometheus exporter that can be enabled with `-prometheusport=<port>`. Metrics names have `.` replaced by `_` for compatibility with Prometheus.

By default, metrics are only exposed to localhost. This can be expanded with `-metricsallowip=<ip>`.
This commit is contained in:
Homu 2021-03-31 02:49:42 +00:00
commit 49d76cf3f6
21 changed files with 1683 additions and 58 deletions

502
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -0,0 +1,6 @@
scrape_configs:
- job_name: 'zcashd'
scrape_interval: 500ms
metrics_path: '/'
static_configs:
- targets: ['127.0.0.1:9969']

View File

@ -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)

6
doc/book/src/user.md Normal file
View File

@ -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`.

View File

@ -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=<port>` 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:<port>
# 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=<ip>`, 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
```

View File

@ -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=<port>` 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:<port>`.
By default, access is restricted to localhost. This can be expanded with
`-metricsallowip=<ip>`, 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`.

View File

@ -64,6 +64,8 @@
#include "zmq/zmqnotificationinterface.h"
#endif
#include <rust/metrics.h>
#include "librustzcash.h"
using namespace std;
@ -410,6 +412,15 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-zmqpubrawtx=<address>", _("Enable publish raw transaction in <address>"));
#endif
strUsage += HelpMessageGroup(_("Monitoring options:"));
strUsage += HelpMessageOpt("-metricsallowip=<ip>", _("Allow metrics connections from specified source. "
"Valid for <ip> 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=<addr>", _("Bind to given address to listen for metrics connections. (default: bind to all interfaces)"));
strUsage += HelpMessageOpt("-prometheusport=<port>", _("Expose node metrics in the Prometheus exposition format. "
"An HTTP listener will be started on <port>, 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<std::string>& vAllow = mapMultiArgs["-metricsallowip"];
std::vector<const char*> 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)) {

View File

@ -46,6 +46,7 @@
#include <boost/thread.hpp>
#include <rust/ed25519.h>
#include <rust/metrics.h>
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<size_t> created;
std::optional<size_t> spent;
std::optional<size_t> unspent;
std::optional<CAmount> 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;
}

View File

@ -31,6 +31,8 @@
#include <boost/thread.hpp>
#include <rust/metrics.h>
// 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<CSerializeData>::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())

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 <stddef.h>
#include <stdint.h>
#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

View File

@ -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 <stddef.h>
#include <stdint.h>
@ -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

244
src/rust/src/metrics_ffi.rs Normal file
View File

@ -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<ipnet::IpNet> = 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::<IpAddr>().unwrap()
} else if bind_address.is_null() {
// No specific bind address specified, bind to any.
"0.0.0.0".parse::<IpAddr>().unwrap()
} else {
match unsafe { CStr::from_ptr(bind_address) }
.to_str()
.ok()
.and_then(|s| s.parse::<IpAddr>().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::<Vec<_>>()
};
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::<Vec<_>>()
};
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);
}
}
}

View File

@ -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<ipnet::IpNet>,
) -> Result<
(
PrometheusRecorder,
impl Future<Output = Result<(), HyperError>> + 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<ipnet::IpNet>,
) -> 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(())
}

View File

@ -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)]

View File

@ -389,6 +389,7 @@ void ReadConfigFile(const std::string& confPath,
"externalip",
"fundingstream",
"loadblock",
"metricsallowip",
"nuparams",
"onlynet",
"rpcallowip",