mirror of
https://github.com/cirocosta/monero-exporter.git
synced 2024-10-01 08:25:41 -04:00
Merge branch 'collector-round-2'
This commit is contained in:
commit
5dff9c1fba
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
@ -24,13 +24,6 @@ builds:
|
||||
- 6
|
||||
- 7
|
||||
|
||||
archives:
|
||||
- replacements:
|
||||
darwin: Darwin
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
amd64: x86_64
|
||||
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
|
||||
|
30
Dockerfile
Normal file
30
Dockerfile
Normal file
@ -0,0 +1,30 @@
|
||||
ARG BUILDER_IMAGE=golang@sha256:4544ae57fc735d7e415603d194d9fb09589b8ad7acd4d66e928eabfb1ed85ff1
|
||||
ARG RUNTIME_IMAGE=gcr.io/distroless/static@sha256:c9f9b040044cc23e1088772814532d90adadfa1b86dcba17d07cb567db18dc4e
|
||||
|
||||
|
||||
FROM $BUILDER_IMAGE as builder
|
||||
|
||||
WORKDIR /workspace
|
||||
|
||||
COPY .git .git
|
||||
COPY go.mod go.mod
|
||||
COPY go.sum go.sum
|
||||
COPY pkg/ pkg/
|
||||
COPY cmd/ cmd/
|
||||
|
||||
RUN set -x && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on \
|
||||
go build -a -v \
|
||||
-ldflags "-s -w -X main.version=$(git tag) -X main.commit=$(git rev-parse HEAD)" \
|
||||
-tags osusergo,netgo,static_build \
|
||||
-trimpath \
|
||||
-o monero-exporter \
|
||||
./cmd/monero-exporter
|
||||
|
||||
|
||||
FROM $RUNTIME_IMAGE
|
||||
|
||||
WORKDIR /
|
||||
COPY --from=builder /workspace/monero-exporter .
|
||||
USER 65532:65532
|
||||
|
||||
ENTRYPOINT ["/monero-exporter"]
|
118
INSTALL.md
Normal file
118
INSTALL.md
Normal file
@ -0,0 +1,118 @@
|
||||
# Install
|
||||
|
||||
## Using Go
|
||||
|
||||
If you already have [Go] installed, you can use to Go toolchain to build from
|
||||
source and install it for you (under `$GOPATH/bin`):
|
||||
|
||||
```console
|
||||
$ GO111MODULE=on go get github.com/cirocosta/monero-exporter/cmd/monero-exporter
|
||||
go: downloading github.com/cirocosta/monero-exporter v0.0.3
|
||||
|
||||
$ monero-exporter --help
|
||||
Prometheus exporter for monero metrics
|
||||
|
||||
Usage:
|
||||
monero-exporter [flags]
|
||||
...
|
||||
```
|
||||
|
||||
Note that this will install the latest tagged release (not necessarily the
|
||||
latest code).
|
||||
|
||||
|
||||
## From releases
|
||||
|
||||
In the [releases page] you'll find the pre-compiled releases for each platform.
|
||||
|
||||
|
||||
### "yeah yeah, I trust the interwebs" mode
|
||||
|
||||
```bash
|
||||
export VERSION=0.0.3
|
||||
|
||||
curl -SL -o- https://github.com/cirocosta/monero-exporter/releases/download/v$VERSION/monero-exporter_$VERSION_Linux_x86_64.tar.gz | \
|
||||
tar xvzf monero
|
||||
mv ./monero-exporter /usr/local/bin
|
||||
```
|
||||
|
||||
|
||||
### "trust, but verify" mode
|
||||
|
||||
|
||||
1. fetch my public key and make sure it matches the expected fingerprint
|
||||
|
||||
```console
|
||||
$ curl -SOL https://utxo.com.br/pgp/public-key.txt
|
||||
```
|
||||
|
||||
now, using `gpg`, derive the fingerprint. it should match the one advertised by
|
||||
me: `9CD1 1313 8578 59CC 0FAD E93B 6B93 177A 62D0 1DB8` (should be the same as
|
||||
you can find under my personal account on Twitter: http://twitter.com/cirowrc).
|
||||
|
||||
|
||||
```console
|
||||
$ gpg --keyid-format long --with-fingerprint ./public-key.txt
|
||||
pub rsa3072/6B93177A62D01DB8 2021-07-19 [SC] [expires: 2023-07-19]
|
||||
Key fingerprint = 9CD1 1313 8578 59CC 0FAD E93B 6B93 177A 62D0 1DB8
|
||||
```
|
||||
|
||||
then, import into the key to the keyring so it can be used to validate that I
|
||||
indeed signed the content advertised.
|
||||
|
||||
```console
|
||||
$ gpg --import ./public-key.txt
|
||||
gpg: key 6B93177A62D01DB8: public key "..." imported
|
||||
gpg: Total number processed: 1
|
||||
gpg: imported: 1
|
||||
```
|
||||
|
||||
|
||||
2. download the archive for your platform as well as the checksums
|
||||
|
||||
```console
|
||||
$ curl -SOL https://github.com/cirocosta/monero-exporter/releases/download/v0.0.3/monero-exporter_0.0.3_Linux_x86_64.tar.gz
|
||||
$ curl -SOL https://github.com/cirocosta/monero-exporter/releases/download/v0.0.3/checksums.txt.asc
|
||||
```
|
||||
|
||||
|
||||
3. verify that you can trust the checksums (that it has been generated and
|
||||
not tampered with), and then verify that the assets you downloaded are what
|
||||
they supposed to be
|
||||
|
||||
```console
|
||||
$ gpg --verify ./checksums.txt.asc
|
||||
gpg: Signature made Mon 19 Jul 2021 02:10:42 PM EDT
|
||||
gpg: using RSA key 9CD11313857859CC0FADE93B6B93177A62D01DB8
|
||||
gpg: Good signature from "Ciro ...
|
||||
```
|
||||
|
||||
|
||||
4. verify that the tarball is what it should be
|
||||
|
||||
compute the digest of the tarball
|
||||
|
||||
```console
|
||||
$ sha256sum ./monero-exporter_0.0.3_Linux_x86_64.tar.gz
|
||||
e2b2214c9371fe3c0333cca7feff3554c56d8d0f377180e39ff50d332639c22d ./monero-exporter_0.0.3_Linux_x86_64.tar.gz
|
||||
|
||||
|
||||
see that it matches what you found in the signed checksums file
|
||||
|
||||
$ cat ./checksums.txt.asc | grep e2b2214c9371fe3c0333cca7feff3554c56d8d0f377180e39ff50d332639c22d
|
||||
e2b2214c9371fe3c0333cca7feff3554c56d8d0f377180e39ff50d332639c22d monero-exporter_0.0.3_Linux_x86_64.tar.gz
|
||||
```
|
||||
|
||||
5. install
|
||||
|
||||
```console
|
||||
$ tar xvzf ./monero-exporter_0.0.3_Linux_x86_64.tar.gz monero
|
||||
$ mv monero /usr/local/bin
|
||||
|
||||
$ monero-exporter version
|
||||
0.0.3 ...
|
||||
```
|
||||
|
||||
|
||||
[Go]: https://golang.org/dl/
|
||||
[releases page]: https://github.com/cirocosta/monero-exporter/releases
|
13
Makefile
13
Makefile
@ -1,8 +1,19 @@
|
||||
install:
|
||||
go install -v ./cmd/monero-exporter
|
||||
|
||||
run:
|
||||
monero-exporter \
|
||||
--monero-addr=http://localhost:18081 \
|
||||
--bind-addr=:9000 \
|
||||
--geoip-filepath=./hack/geoip.mmdb
|
||||
|
||||
test:
|
||||
go test ./...
|
||||
|
||||
lint:
|
||||
go run github.com/golangci/golangci-lint/cmd/golangci-lint run --config=.golangci.yaml
|
||||
go run github.com/golangci/golangci-lint/cmd/golangci-lint run \
|
||||
--config=.golangci.yaml
|
||||
|
||||
|
||||
table-of-contents:
|
||||
doctoc --notitle ./README.md
|
||||
|
341
README.md
341
README.md
@ -1,22 +1,33 @@
|
||||
# monero-exporter
|
||||
|
||||
[Prometheus](https://prometheus.io) exporter for [monero](https://getmonero.org).
|
||||
[prometheus] exporter for [monero] nodes.
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
Using Go:
|
||||
|
||||
```
|
||||
go get github.com/cirocosta/monero-exporter/cmd/monero-exporter
|
||||
```
|
||||
|
||||
From releases:
|
||||
|
||||
**TODO**
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
|
||||
|
||||
## Usage
|
||||
- [About](#about)
|
||||
- [Installation](#installation)
|
||||
- [Metrics](#metrics)
|
||||
- [Last block](#last-block)
|
||||
- [Transaction pool](#transaction-pool)
|
||||
- [RPC](#rpc)
|
||||
- [P2P Connections](#p2p-connections)
|
||||
- [Peers](#peers)
|
||||
- [Net Stats](#net-stats)
|
||||
- [Info](#info)
|
||||
- [License](#license)
|
||||
- [Thanks](#thanks)
|
||||
- [Donate](#donate)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
## About
|
||||
|
||||
`monero-exporter` is a single multiplatform binary that extracts metrics out of
|
||||
a running [monero] node, allowing one to better observe the state of its own
|
||||
daemon and the network as seen by it.
|
||||
|
||||
```console
|
||||
$ monero-exporter --help
|
||||
@ -25,7 +36,6 @@ Prometheus exporter for monero metrics
|
||||
|
||||
Usage:
|
||||
monero-exporter [flags]
|
||||
monero-exporter [command]
|
||||
|
||||
Available Commands:
|
||||
completion generate the autocompletion script for the specified shell
|
||||
@ -33,90 +43,251 @@ Available Commands:
|
||||
version print the version of this CLI
|
||||
|
||||
Flags:
|
||||
--address string address of the monero node to collect metrics from
|
||||
--geoip-filepath string filepath of a geoip database file for ip to country resolution
|
||||
--bind-addr string address to bind the prometheus server to
|
||||
(default ":9090")
|
||||
--geoip-filepath string filepath of a geoip database file for ip to
|
||||
country resolution
|
||||
-h, --help help for monero-exporter
|
||||
--monero-addr string address of the monero instance to collect info
|
||||
from (default "http://localhost:18081")
|
||||
--telemetry-path string endpoint at which prometheus metrics are served
|
||||
(default "/metrics")
|
||||
|
||||
Use "monero-exporter [command] --help" for more information about a command.
|
||||
```
|
||||
|
||||
It works by issuing remote procedure calls (RPC) to a monero node and based on
|
||||
the responses it gets, exposing metrics to a Prometheus server querying
|
||||
`monero-exporter`.
|
||||
|
||||
|
||||
```
|
||||
|
||||
PROMETHEUS ---- get /metrics -->---.
|
||||
^ |
|
||||
| |
|
||||
| MONERO-EXPORTER --- get /jsonrpc --> MONEROD
|
||||
| |
|
||||
| |
|
||||
'-------------<-- samples ------'
|
||||
|
||||
```
|
||||
|
||||
Typically, `monero-exporter` is just one piece of the stack though. A complete
|
||||
setup usually looks like:
|
||||
|
||||
- `monerod`, the monero daemon, is running exposing its daemon RPC for those
|
||||
interested in it (see [monero-project/monero])
|
||||
- `monero-exporter` targets that `monerod` RPC port
|
||||
- [prometheus], configured to scrape `monero-exporter`, asks it for metric
|
||||
samples, then stores those in its internal timeseries database
|
||||
- [grafana] then queries that timeseries data using the [promql], displaying
|
||||
that in dashboards, alerting on thresholds, etc
|
||||
|
||||
|
||||
```
|
||||
|
||||
(ui) GRAFANA
|
||||
.
|
||||
.
|
||||
queries ..every dashboard refresh interval..
|
||||
.
|
||||
.
|
||||
.
|
||||
(ts db) PROMETHEUS --- scrape ->-. ..every scrape interval..
|
||||
^ |
|
||||
| |
|
||||
| |
|
||||
(exporter) '--------------MONERO-EXPORTER
|
||||
.
|
||||
...... rpc ......... ..on scrape..
|
||||
.
|
||||
.
|
||||
(daemon) MONEROD
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
You can either install it by using [Go], building the latest tagged release
|
||||
from scratch
|
||||
|
||||
```bash
|
||||
GO111MODULE=on \
|
||||
go get github.com/cirocosta/monero-exporter/cmd/monero-exporter
|
||||
```
|
||||
|
||||
or fetching the binary for your corresponding distribution from the [releases
|
||||
page].
|
||||
|
||||
See [INSTALL.md] for details and examples.
|
||||
|
||||
|
||||
|
||||
## Metrics
|
||||
|
||||
Below you'll find the description of the metrics exposed by `monero-exporter`
|
||||
that are retrieved by a prometheus server, stored in its timeseries database,
|
||||
and made available for querying by something like Grafana.
|
||||
|
||||
Keep in mind that Prometheus gather metrics in a pull-based fashion (see ["Why
|
||||
do you pull rather than push?"]), and as such, metric samples are based on
|
||||
information seen at the time that prometheus reached out to the exporter.
|
||||
|
||||
While the pull-based approach is very useful for looking at long timeframes and
|
||||
large number of hosts, it's not very suitable for realtime data (for that,
|
||||
consider other implementations for push-based systems like [InfluxDB]).
|
||||
|
||||
|
||||
### Last block
|
||||
|
||||
This set of metrics is extracted from information gathered regarding the last
|
||||
block seen by the node at the moment that Prometheus scraped samples from
|
||||
`monero-exporter`.
|
||||
|
||||
Think of it as _"at time X, when I looked at the last block, these were the
|
||||
pieces of information I could gather from it"_, which when looked at over time
|
||||
(say, 30d), gives you a pretty good picture of what "all" (more like, _most_)
|
||||
blocks looked like as long as the interval between those scrapes where short
|
||||
enough (i.e., in between the mean time for a block to be mined - 2min as of
|
||||
today).
|
||||
|
||||
|
||||
| name | description |
|
||||
| ---- | ----------- |
|
||||
| monero_bans | number of nodes banned |
|
||||
| monero_connections | connections info |
|
||||
| monero_connections_livetime | peers livetime distribution |
|
||||
| monero_fee_estimate | fee estimate for 1 grace block |
|
||||
| monero_height_divergence | how much our peers diverge from us in block height |
|
||||
| monero_info_alt_blocks_count | info for alt_blocks_count |
|
||||
| monero_info_block_size_limit | info for block_size_limit |
|
||||
| monero_info_block_size_median | info for block_size_median |
|
||||
| monero_info_busy_syncing | info for busy_syncing |
|
||||
| monero_info_cumulative_difficulty | info for cumulative_difficulty |
|
||||
| monero_info_difficulty | info for difficulty |
|
||||
| monero_info_free_space | info for free_space |
|
||||
| monero_info_grey_peerlist_size | info for grey_peerlist_size |
|
||||
| monero_info_height | info for height |
|
||||
| monero_info_height_without_bootstrap | info for height_without_bootstrap |
|
||||
| monero_info_incoming_connections_count | info for incoming_connections_count |
|
||||
| monero_info_mainnet | info for mainnet |
|
||||
| monero_info_offline | info for offline |
|
||||
| monero_info_outgoing_connections_count | info for outgoing_connections_count |
|
||||
| monero_info_rpc_connections_count | info for rpc_connections_count |
|
||||
| monero_info_stagenet | info for stagenet |
|
||||
| monero_info_start_time | info for start_time |
|
||||
| monero_info_synchronized | info for synchronized |
|
||||
| monero_info_target | info for target |
|
||||
| monero_info_target_height | info for target_height |
|
||||
| monero_info_testnet | info for testnet |
|
||||
| monero_info_tx_count | info for tx_count |
|
||||
| monero_info_tx_pool_size | info for tx_pool_size |
|
||||
| monero_info_untrusted | info for untrusted |
|
||||
| monero_info_was_bootstrap_ever_used | info for was_bootstrap_ever_used |
|
||||
| monero_info_white_peerlist_size | info for white_peerlist_size |
|
||||
| monero_last_block_header_block_size | info for block_size |
|
||||
| monero_last_block_header_block_weight | info for block_weight |
|
||||
| monero_last_block_header_cumulative_difficulty | info for cumulative_difficulty |
|
||||
| monero_last_block_header_cumulative_difficulty_top64 | info for cumulative_difficulty_top64 |
|
||||
| monero_last_block_header_depth | info for depth |
|
||||
| monero_last_block_header_difficulty | info for difficulty |
|
||||
| monero_last_block_header_difficulty_top64 | info for difficulty_top64 |
|
||||
| monero_last_block_header_height | info for height |
|
||||
| monero_last_block_header_long_term_weight | info for long_term_weight |
|
||||
| monero_last_block_header_major_version | info for major_version |
|
||||
| monero_last_block_header_minor_version | info for minor_version |
|
||||
| monero_last_block_header_nonce | info for nonce |
|
||||
| monero_last_block_header_num_txes | info for num_txes |
|
||||
| monero_last_block_header_orphan_status | info for orphan_status |
|
||||
| monero_last_block_header_reward | info for reward |
|
||||
| monero_last_block_header_timestamp | info for timestamp |
|
||||
| monero_last_block_txn_fee | distribution of outputs in last block |
|
||||
| monero_last_block_txn_size | distribution of tx sizes |
|
||||
| monero_last_block_vin | distribution of inputs in last block |
|
||||
| monero_last_block_vout | distribution of outputs in last block |
|
||||
| monero_mempool_bytes_max | info for bytes_max |
|
||||
| monero_mempool_bytes_med | info for bytes_med |
|
||||
| monero_mempool_bytes_min | info for bytes_min |
|
||||
| monero_mempool_bytes_total | info for bytes_total |
|
||||
| monero_mempool_fee_total | info for fee_total |
|
||||
| monero_mempool_histo_98pc | info for histo_98pc |
|
||||
| monero_mempool_num_10m | info for num_10m |
|
||||
| monero_mempool_num_double_spends | info for num_double_spends |
|
||||
| monero_mempool_num_failing | info for num_failing |
|
||||
| monero_mempool_num_not_relayed | info for num_not_relayed |
|
||||
| monero_mempool_oldest | info for oldest |
|
||||
| monero_mempool_txs_total | info for txs_total |
|
||||
| monero_net_total_in_bytes | network statistics |
|
||||
| monero_net_total_out_bytes | network statistics |
|
||||
| monero_peers_new | peers info |
|
||||
| monero_rpc_count | todo |
|
||||
| monero_rpc_time | todo |
|
||||
| monero_lastblock_difficulty | difficulty used for the last block |
|
||||
| monero_lastblock_fees_micronero_per_kb | distribution of the feeperkb utilized for txns |
|
||||
| monero_lastblock_fees_monero | total amount of fees included in this block |
|
||||
| monero_lastblock_height | height of the last block |
|
||||
| monero_lastblock_reward_monero | total amount of rewards granted in the last block (subsidy + fees) |
|
||||
| monero_lastblock_size_bytes | total size of the last block |
|
||||
| monero_lastblock_subsidy_monero | newly minted monero for this block |
|
||||
| monero_lastblock_transactions | number of transactions seen in the last block |
|
||||
| monero_lastblock_transactions_inputs | distribution of inputs in the last block |
|
||||
| monero_lastblock_transactions_outputs | distribution of outputs in the last block |
|
||||
| monero_lastblock_transactions_size_bytes | distribution of the size of the transactions included |
|
||||
|
||||
|
||||
|
||||
### Transaction pool
|
||||
|
||||
These metrics give you a view of how the transaction pool of this particular
|
||||
node looked like when Prometheus asked the exporter information about it.
|
||||
|
||||
Given the pull-based nature of Prometheus, this will be only as granular as the
|
||||
frequency of scraping configured for it.
|
||||
|
||||
|
||||
| name | description |
|
||||
| ---- | ----------- |
|
||||
| monero_transaction_pool_double_spends | transactions doubly spending outputs |
|
||||
| monero_transaction_pool_failing_transactions | number of transactions that are marked as failing |
|
||||
| monero_transaction_pool_fees_micronero_per_kb | distribution of the feeperkb utilized for txns in the pool |
|
||||
| monero_transaction_pool_fees_monero | total amount of fee being spent in the transaction pool |
|
||||
| monero_transaction_pool_not_relayed | number of transactions that have not been relayed |
|
||||
| monero_transaction_pool_older_than_10m | number of transactions that are older than 10m |
|
||||
| monero_transaction_pool_size_bytes | total size of the transaction pool |
|
||||
| monero_transaction_pool_spent_key_images | total number of key images spent across all transactions in the pool |
|
||||
| monero_transaction_pool_transactions | number of transactions in the pool at the moment of the scrape |
|
||||
| monero_transaction_pool_transactions_age | distribution of for how long transactions have been in the pool |
|
||||
| monero_transaction_pool_transactions_inputs | distribution of inputs in the pool |
|
||||
| monero_transaction_pool_transactions_outputs | distribution of outputs in the pool |
|
||||
| monero_transaction_pool_transactions_size_bytes | distribution of the size of the transactions in the transaction pool |
|
||||
|
||||
|
||||
### RPC
|
||||
|
||||
RPC metrics provide an overview of the usage of the RPC endpoints.
|
||||
|
||||
Note that `monerod` does not distinguish between `monero-exporter` and other
|
||||
users (like, a wallet), which means that these metrics will include the
|
||||
constant querying that `monero-exporter` performs to fetch statistics.
|
||||
|
||||
|
||||
| name | description |
|
||||
| ---- | ----------- |
|
||||
| monero_rpc_hits_total | number of hits that a particular rpc method had since startup |
|
||||
| monero_rpc_seconds_total | amount of time spent service the method since startup |
|
||||
|
||||
|
||||
### P2P Connections
|
||||
|
||||
Connection metrics aim at providing information about peers that are fully
|
||||
connected to the node (thus, transmitting and receiving data to/from our node).
|
||||
|
||||
|
||||
| name | description |
|
||||
| ---- | ----------- |
|
||||
| monero_p2p_connections | number of connections to/from this node |
|
||||
| monero_p2p_connections_age | distribution of age of the connections we have |
|
||||
| monero_p2p_connections_height | distribution the height of the peers connected to/from us |
|
||||
| monero_p2p_connections_rx_rate_bps | distribution of data receive rate in bytes/s |
|
||||
| monero_p2p_connections_tx_rate_bps | distribution of data transmit rate in bytes/s |
|
||||
|
||||
|
||||
### Peerlist
|
||||
|
||||
The monero daemon internally keeps track of potential peers to connect to
|
||||
called peerlists, divided in anchor, white, and gray.
|
||||
|
||||
|
||||
| name | description |
|
||||
| ---- | ----------- |
|
||||
| monero_peerlist | number of node entries in the peerlist |
|
||||
| monero_peerlist_lastseen | distribution of when our peers have been seen |
|
||||
|
||||
|
||||
### Net Stats
|
||||
|
||||
Aggregated network statistics, not specific to P2P or RPC.
|
||||
|
||||
| name | description |
|
||||
| ---- | ----------- |
|
||||
| monero_net_rx_bytes | number of bytes received by this node |
|
||||
| monero_net_tx_bytes | number of bytes received by this node |
|
||||
|
||||
|
||||
### Info
|
||||
|
||||
General information about this node.
|
||||
|
||||
| name | description |
|
||||
| ---- | ----------- |
|
||||
| monero_info_alternative_blocks | number of blocks alternative to the longest |
|
||||
| monero_info_block_size_limit | maximum hard limit of a block |
|
||||
| monero_info_block_size_median | current median size for computing dynamic fees |
|
||||
| monero_info_mainnet | whether the node is connected to mainnet |
|
||||
| monero_info_offline | whether the node is offline |
|
||||
| monero_info_synchronized | |
|
||||
| monero_info_uptime_seconds_total | for how long this node has been up |
|
||||
|
||||
|
||||
## License
|
||||
|
||||
See [LICENSE](./LICENSE).
|
||||
|
||||
|
||||
## Thanks
|
||||
|
||||
- maxmind geoip database: https://www.maxmind.com/en/geoip2-services-and-databases
|
||||
|
||||
|
||||
## Donate
|
||||
|
||||
![xmr address](./assets/donate.png)
|
||||
![donation qrcode]
|
||||
|
||||
891B5keCnwXN14hA9FoAzGFtaWmcuLjTDT5aRTp65juBLkbNpEhLNfgcBn6aWdGuBqBnSThqMPsGRjWVQadCrhoAT6CnSL3
|
||||
|
||||
["Why do you pull rather than push?"]: https://prometheus.io/docs/introduction/faq/#why-do-you-pull-rather-than-push
|
||||
[Go]: https://golang.org/
|
||||
[INSTALL.md]: ./INSTALL.md
|
||||
[InfluxDB]: https://github.com/influxdata/influxdb
|
||||
[donation qrcode]: ./.github/assets/donate.png
|
||||
[grafana]: https://github.com/grafana/grafana
|
||||
[monero-project/monero]: https://github.com/monero-project/monero
|
||||
[monero]: https://github.com/monero-project/monero
|
||||
[prometheus]: https://prometheus.io
|
||||
[promql]: https://prometheus.io/docs/prometheus/latest/querying/basics/
|
||||
[releases page]: https://github.com/cirocosta/monero-exporter/releases
|
||||
|
@ -1,24 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: node-0
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- command:
|
||||
- monerod
|
||||
- --non-interactive
|
||||
- --out-peers=128
|
||||
- --in-peers=128
|
||||
- --prune-blockchain
|
||||
- --limit-rate=128000
|
||||
image: index.docker.io/utxobr/monerod@sha256:19ba5793c00375e7115469de9c14fcad928df5867c76ab5de099e83f646e175d
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: monerod
|
||||
ports:
|
||||
- containerPort: 18080
|
||||
name: p2p
|
||||
protocol: TCP
|
||||
- containerPort: 18089
|
||||
name: restricted
|
||||
protocol: TCP
|
@ -15,8 +15,10 @@ import (
|
||||
)
|
||||
|
||||
type command struct {
|
||||
address string
|
||||
telemetryPath string
|
||||
bindAddr string
|
||||
geoIPFilepath string
|
||||
moneroAddr string
|
||||
}
|
||||
|
||||
func (c *command) Cmd() *cobra.Command {
|
||||
@ -26,13 +28,20 @@ func (c *command) Cmd() *cobra.Command {
|
||||
RunE: c.RunE,
|
||||
}
|
||||
|
||||
cmd.Flags().StringVar(&c.address, "address",
|
||||
"", "address of the monero node to collect metrics from")
|
||||
cmd.MarkFlagRequired("address")
|
||||
cmd.Flags().StringVar(&c.bindAddr, "bind-addr",
|
||||
":9000", "address to bind the prometheus server to")
|
||||
|
||||
cmd.Flags().StringVar(&c.telemetryPath, "telemetry-path",
|
||||
"/metrics", "endpoint at which prometheus metrics are served")
|
||||
|
||||
cmd.Flags().StringVar(&c.moneroAddr, "monero-addr",
|
||||
"http://localhost:18081", "address of the monero instance to "+
|
||||
"collect info from")
|
||||
|
||||
cmd.Flags().StringVar(&c.geoIPFilepath, "geoip-filepath",
|
||||
"", "filepath of a geoip database file for ip to country resolution")
|
||||
cmd.MarkFlagFilename("geoip-filepath")
|
||||
"", "filepath of a geoip database file for ip to country "+
|
||||
"resolution")
|
||||
_ = cmd.MarkFlagFilename("geoip-filepath")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@ -41,15 +50,9 @@ func (c *command) RunE(_ *cobra.Command, _ []string) error {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
prometheusExporter, err := exporter.New()
|
||||
rpcClient, err := rpc.NewClient(c.moneroAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("new exporter: %w", err)
|
||||
}
|
||||
defer prometheusExporter.Close()
|
||||
|
||||
rpcClient, err := rpc.NewClient(c.address)
|
||||
if err != nil {
|
||||
return fmt.Errorf("new client '%s': %w", c.address, err)
|
||||
return fmt.Errorf("new client '%s': %w", c.moneroAddr, err)
|
||||
}
|
||||
|
||||
daemonClient := daemon.NewClient(rpcClient)
|
||||
@ -66,20 +69,35 @@ func (c *command) RunE(_ *cobra.Command, _ []string) error {
|
||||
countryMapper := func(ip net.IP) (string, error) {
|
||||
res, err := db.Country(ip)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("country '%s': %w", ip, err)
|
||||
return "", fmt.Errorf(
|
||||
"country '%s': %w", ip, err,
|
||||
)
|
||||
}
|
||||
|
||||
return res.RegisteredCountry.IsoCode, nil
|
||||
}
|
||||
|
||||
collectorOpts = append(collectorOpts, collector.WithCountryMapper(countryMapper))
|
||||
collectorOpts = append(collectorOpts,
|
||||
collector.WithCountryMapper(countryMapper),
|
||||
)
|
||||
}
|
||||
|
||||
if err := collector.Register(daemonClient, collectorOpts...); err != nil {
|
||||
err = collector.Register(daemonClient, collectorOpts...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("collector register: %w", err)
|
||||
}
|
||||
|
||||
if err := prometheusExporter.Run(ctx); err != nil {
|
||||
prometheusExporter, err := exporter.New(
|
||||
exporter.WithBindAddress(c.bindAddr),
|
||||
exporter.WithTelemetryPath(c.telemetryPath),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("new exporter: %w", err)
|
||||
}
|
||||
defer prometheusExporter.Close()
|
||||
|
||||
err = prometheusExporter.Run(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("prometheus exporter run: %w", err)
|
||||
}
|
||||
|
||||
|
44
docker-compose.yaml
Normal file
44
docker-compose.yaml
Normal file
@ -0,0 +1,44 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
prometheus:
|
||||
image: prom/prometheus
|
||||
container_name: prometheus
|
||||
network_mode: host
|
||||
volumes:
|
||||
- prometheus:/prometheus
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
echo "
|
||||
global:
|
||||
scrape_interval: '15s'
|
||||
evaluation_interval: '15s'
|
||||
|
||||
scrape_configs:
|
||||
- job_name: 'monerod'
|
||||
static_configs:
|
||||
- targets:
|
||||
- '127.0.0.1:9000'
|
||||
" > config.yml
|
||||
|
||||
exec prometheus \
|
||||
--config.file=config.yml \
|
||||
--storage.tsdb.retention.time=30d \
|
||||
--storage.tsdb.path=/prometheus
|
||||
|
||||
|
||||
grafana:
|
||||
container_name: grafana
|
||||
build: ./grafana
|
||||
ports:
|
||||
- 3000:3000
|
||||
environment:
|
||||
- GF_INSTALL_PLUGINS=grafana-worldmap-panel
|
||||
volumes:
|
||||
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro
|
||||
|
||||
|
||||
volumes:
|
||||
prometheus:
|
4
go.mod
4
go.mod
@ -3,8 +3,9 @@ module github.com/cirocosta/monero-exporter
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b
|
||||
github.com/cirocosta/go-monero v0.0.2-0.20210711112505-425893dd54c5
|
||||
github.com/cirocosta/go-monero v0.0.5-0.20210731140604-e84b32b615af
|
||||
github.com/go-logr/logr v0.4.0
|
||||
github.com/go-logr/zapr v0.4.0
|
||||
github.com/golangci/golangci-lint v1.41.1
|
||||
@ -12,5 +13,6 @@ require (
|
||||
github.com/prometheus/client_golang v1.11.0
|
||||
github.com/spf13/cobra v1.2.1
|
||||
go.uber.org/zap v1.17.0
|
||||
golang.org/x/net v0.0.0-20210716203947-853a461950ff // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
)
|
||||
|
9
go.sum
9
go.sum
@ -106,8 +106,8 @@ github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af/go.mod h1:Qjyv4H3/
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cirocosta/go-monero v0.0.2-0.20210711112505-425893dd54c5 h1:9FxmhvhnnNG8esoFBC6kjplzYaWQcTFv9HHjqtQHyb8=
|
||||
github.com/cirocosta/go-monero v0.0.2-0.20210711112505-425893dd54c5/go.mod h1:hDrYMh+4izQypIwez8i3O1hkgJ5wDAPofkaVAtr3mbM=
|
||||
github.com/cirocosta/go-monero v0.0.5-0.20210731140604-e84b32b615af h1:3KIUbswishjkyOM0rYfprGjMTWh+64VcXMOIQTnRcxE=
|
||||
github.com/cirocosta/go-monero v0.0.5-0.20210731140604-e84b32b615af/go.mod h1:B62WVZVEggXr+Kg5wKPNgfDw/BGplrgLjk7hJYIR3FU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
@ -209,6 +209,8 @@ github.com/go-toolsmith/typep v1.0.2 h1:8xdsa1+FSIH/RhEkgnD1j2CJOy5mNllW1Q9tRiYw
|
||||
github.com/go-toolsmith/typep v1.0.2/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU=
|
||||
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b h1:khEcpUM4yFcxg4/FHQWkvVRmgijNXRfzkIDHh23ggEo=
|
||||
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
|
||||
github.com/go-zeromq/goczmq/v4 v4.2.2/go.mod h1:Sm/lxrfxP/Oxqs0tnHD6WAhwkWrx+S+1MRrKzcxoaYE=
|
||||
github.com/go-zeromq/zmq4 v0.13.0/go.mod h1:TrFwdPHMSLG7Rhp8OVhQBkb4bSajfucWv8rwoEFIgSY=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
@ -850,8 +852,9 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6 h1:0PC75Fz/kyMGhL0e1QnypqK2kQMqKt9csD1GnMJR+Zk=
|
||||
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210716203947-853a461950ff h1:j2EK/QoxYNBsXI4R7fQkkRUk8y6wnOBI+6hgPdP/6Ds=
|
||||
golang.org/x/net v0.0.0-20210716203947-853a461950ff/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
4
grafana/Dockerfile
Normal file
4
grafana/Dockerfile
Normal file
@ -0,0 +1,4 @@
|
||||
FROM grafana/grafana
|
||||
|
||||
ADD ./config.ini /etc/grafana/grafana.ini
|
||||
ADD ./provisioning /etc/grafana/provisioning
|
29
grafana/config.ini
Normal file
29
grafana/config.ini
Normal file
@ -0,0 +1,29 @@
|
||||
[analytics]
|
||||
reporting_enabled = false
|
||||
check_for_updates = false
|
||||
|
||||
|
||||
[auth]
|
||||
disable_login_form = true
|
||||
|
||||
|
||||
[auth.anonymous]
|
||||
enabled = true
|
||||
org_role = Admin
|
||||
|
||||
|
||||
[dashboards]
|
||||
min_refresh_interval = 1m
|
||||
|
||||
|
||||
[paths]
|
||||
provisioning = /etc/grafana/provisioning
|
||||
|
||||
|
||||
[server]
|
||||
enable_gzip = true
|
||||
read_timeout = 2m
|
||||
|
||||
|
||||
[snapshots]
|
||||
external_enabled = false
|
3043
grafana/dashboards/monero.json
Normal file
3043
grafana/dashboards/monero.json
Normal file
File diff suppressed because it is too large
Load Diff
12
grafana/provisioning/dashboards/all.yaml
Normal file
12
grafana/provisioning/dashboards/all.yaml
Normal file
@ -0,0 +1,12 @@
|
||||
apiVersion: 1
|
||||
|
||||
providers:
|
||||
- name: 'fs'
|
||||
orgId: 1
|
||||
folder: ''
|
||||
type: 'file'
|
||||
updateIntervalSeconds: 30
|
||||
allowUiUpdates: true
|
||||
options:
|
||||
path: '/var/lib/grafana/dashboards'
|
||||
foldersFromFilesStructure: true
|
12
grafana/provisioning/datasources/all.yaml
Normal file
12
grafana/provisioning/datasources/all.yaml
Normal file
@ -0,0 +1,12 @@
|
||||
apiVersion: 1
|
||||
|
||||
datasources:
|
||||
- name: prometheus
|
||||
type: prometheus
|
||||
access: proxy
|
||||
orgId: 1
|
||||
url: http://xps.utxo.com.br:9090
|
||||
isDefault: true
|
||||
version: 1
|
||||
editable: false
|
||||
timeInterval: 30s
|
BIN
hack/geoip.mmdb
Normal file
BIN
hack/geoip.mmdb
Normal file
Binary file not shown.
@ -3,13 +3,9 @@ package collector
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/bmizerany/perks/quantile"
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/go-logr/zapr"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
@ -63,6 +59,10 @@ func WithCountryMapper(v CountryMapper) func(c *Collector) {
|
||||
}
|
||||
}
|
||||
|
||||
func defaultCountryMapper(_ net.IP) (string, error) {
|
||||
return "unknown", nil
|
||||
}
|
||||
|
||||
// Register registers this collector with the global prometheus collectors
|
||||
// registry making it available for an exporter to collect our metrics.
|
||||
//
|
||||
@ -74,8 +74,8 @@ func Register(client *daemon.Client, opts ...Option) error {
|
||||
|
||||
c := &Collector{
|
||||
client: client,
|
||||
log: zapr.NewLogger(defaultLogger.Named("collector")),
|
||||
countryMapper: func(_ net.IP) (string, error) { return "unknown", nil },
|
||||
countryMapper: defaultCountryMapper,
|
||||
log: zapr.NewLogger(defaultLogger),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
@ -97,8 +97,13 @@ type CollectFunc func(ctx context.Context, ch chan<- prometheus.Metric) error
|
||||
// Describe implements the Describe function of the Collector interface.
|
||||
//
|
||||
func (c *Collector) Describe(ch chan<- *prometheus.Desc) {
|
||||
// Because we can present the description of the metrics at collection time, we
|
||||
// don't need to write anything to the channel.
|
||||
// Because we can present the description of the metrics at collection
|
||||
// time, we don't need to write anything to the channel.
|
||||
}
|
||||
|
||||
type CustomCollector interface {
|
||||
Name() string
|
||||
Collect(ctx context.Context) error
|
||||
}
|
||||
|
||||
// Collect implements the Collect function of the Collector interface.
|
||||
@ -114,28 +119,21 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) {
|
||||
|
||||
g, ctx = errgroup.WithContext(ctx)
|
||||
|
||||
for _, collector := range []struct {
|
||||
name string
|
||||
fn CollectFunc
|
||||
}{
|
||||
{"info_stats", c.CollectInfoStats},
|
||||
{"mempool_stats", c.CollectMempoolStats},
|
||||
{"last_block_header", c.CollectLastBlockHeader},
|
||||
{"bans", c.CollectBans},
|
||||
{"peer_height_divergence", c.CollectPeerHeightDivergence},
|
||||
{"fee_estimate", c.CollectFeeEstimate},
|
||||
{"peers", c.CollectPeers},
|
||||
{"connections", c.CollectConnections},
|
||||
{"last_block_stats", c.CollectLastBlockStats},
|
||||
{"peers_live_time", c.CollectPeersLiveTime},
|
||||
{"net_stats", c.CollectNetStats},
|
||||
{"collect_rpc", c.CollectRPC},
|
||||
for _, collector := range []CustomCollector{
|
||||
NewLastBlockStatsCollector(c.client, ch),
|
||||
NewTransactionPoolCollector(c.client, ch),
|
||||
NewRPCCollector(c.client, ch),
|
||||
NewConnectionsCollector(c.client, ch),
|
||||
NewPeersCollector(c.client, ch),
|
||||
NewNetStatsCollector(c.client, ch),
|
||||
NewOverallCollector(c.client, ch),
|
||||
} {
|
||||
collector := collector
|
||||
|
||||
g.Go(func() error {
|
||||
if err := collector.fn(ctx, ch); err != nil {
|
||||
return fmt.Errorf("collector fn '%s': %w", collector.name, err)
|
||||
if err := collector.Collect(ctx); err != nil {
|
||||
return fmt.Errorf("%s collect: %w",
|
||||
collector.Name(), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -146,495 +144,3 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) {
|
||||
c.log.Error(err, "wait")
|
||||
}
|
||||
}
|
||||
|
||||
// CollectConnections.
|
||||
//
|
||||
func (c *Collector) CollectConnections(ctx context.Context, ch chan<- prometheus.Metric) error {
|
||||
res, err := c.client.GetConnections(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get connections: %w", err)
|
||||
}
|
||||
|
||||
perCountryCounter := map[string]uint64{}
|
||||
for _, conn := range res.Connections {
|
||||
country, err := c.countryMapper(net.ParseIP(conn.Host))
|
||||
if err != nil {
|
||||
return fmt.Errorf("to country '%s': %w", conn.Host, err)
|
||||
}
|
||||
|
||||
perCountryCounter[country]++
|
||||
}
|
||||
|
||||
desc := prometheus.NewDesc(
|
||||
"monero_connections",
|
||||
"connections info",
|
||||
[]string{"country"}, nil,
|
||||
)
|
||||
|
||||
for country, count := range perCountryCounter {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
desc,
|
||||
prometheus.GaugeValue,
|
||||
float64(count),
|
||||
country,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CollectPeers.
|
||||
//
|
||||
func (c *Collector) CollectPeers(ctx context.Context, ch chan<- prometheus.Metric) error {
|
||||
res, err := c.client.GetPeerList(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get peer list: %w", err)
|
||||
}
|
||||
|
||||
perCountryCounter := map[string]uint64{}
|
||||
for _, peer := range res.WhiteList {
|
||||
country, err := c.countryMapper(net.ParseIP(peer.Host))
|
||||
if err != nil {
|
||||
return fmt.Errorf("to country '%s': %w", peer.Host, err)
|
||||
}
|
||||
|
||||
perCountryCounter[country]++
|
||||
}
|
||||
|
||||
desc := prometheus.NewDesc(
|
||||
"monero_peers_new",
|
||||
"peers info",
|
||||
[]string{"country"}, nil,
|
||||
)
|
||||
|
||||
for country, count := range perCountryCounter {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
desc,
|
||||
prometheus.GaugeValue,
|
||||
float64(count),
|
||||
country,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CollectLastBlockHeader.
|
||||
//
|
||||
func (c *Collector) CollectLastBlockHeader(ctx context.Context, ch chan<- prometheus.Metric) error {
|
||||
res, err := c.client.GetLastBlockHeader(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get last block header: %w", err)
|
||||
}
|
||||
|
||||
metrics, err := c.toMetrics("last_block_header", &res.BlockHeader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("to metrics: %w", err)
|
||||
}
|
||||
|
||||
for _, metric := range metrics {
|
||||
ch <- metric
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CollectInfoStats.
|
||||
//
|
||||
func (c *Collector) CollectInfoStats(ctx context.Context, ch chan<- prometheus.Metric) error {
|
||||
res, err := c.client.GetInfo(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get transaction pool: %w", err)
|
||||
}
|
||||
|
||||
metrics, err := c.toMetrics("info", res)
|
||||
if err != nil {
|
||||
return fmt.Errorf("to metrics: %w", err)
|
||||
}
|
||||
|
||||
for _, metric := range metrics {
|
||||
ch <- metric
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) CollectLastBlockStats(ctx context.Context, ch chan<- prometheus.Metric) error {
|
||||
lastBlockHeaderResp, err := c.client.GetLastBlockHeader(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get last block header: %w", err)
|
||||
}
|
||||
|
||||
currentHeight := lastBlockHeaderResp.BlockHeader.Height
|
||||
block, err := c.client.GetBlock(ctx, daemon.GetBlockRequestParameters{
|
||||
Height: currentHeight,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get block '%d': %w", currentHeight, err)
|
||||
}
|
||||
|
||||
blockJSON, err := block.InnerJSON()
|
||||
if err != nil {
|
||||
return fmt.Errorf("block inner json: %w", err)
|
||||
}
|
||||
|
||||
txnsResp, err := c.client.GetTransactions(ctx, blockJSON.TxHashes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get txns: %w", err)
|
||||
}
|
||||
|
||||
txns, err := txnsResp.GetTransactions()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get transactions: %w", err)
|
||||
}
|
||||
|
||||
phis := []float64{0.25, 0.50, 0.75, 0.90, 0.95, 0.99, 1}
|
||||
|
||||
var (
|
||||
streamTxnSize = quantile.NewTargeted(phis...)
|
||||
sumTxnSize = float64(0)
|
||||
quantilesTxnSize = make(map[float64]float64, len(phis))
|
||||
|
||||
streamTxnFee = quantile.NewTargeted(phis...)
|
||||
sumTxnFee = float64(0)
|
||||
quantilesTxnFee = make(map[float64]float64, len(phis))
|
||||
|
||||
streamVin = quantile.NewTargeted(phis...)
|
||||
sumVin = float64(0)
|
||||
quantilesVin = make(map[float64]float64, len(phis))
|
||||
|
||||
streamVout = quantile.NewTargeted(phis...)
|
||||
sumVout = float64(0)
|
||||
quantilesVout = make(map[float64]float64, len(phis))
|
||||
)
|
||||
|
||||
for _, txn := range txnsResp.TxsAsHex {
|
||||
streamTxnSize.Insert(float64(len(txn)))
|
||||
sumTxnSize += float64(len(txn))
|
||||
}
|
||||
|
||||
for _, txn := range txns {
|
||||
streamTxnFee.Insert(float64(txn.RctSignatures.Txnfee))
|
||||
sumTxnFee += float64(txn.RctSignatures.Txnfee)
|
||||
|
||||
streamVin.Insert(float64(len(txn.Vin)))
|
||||
sumVin += float64(len(txn.Vin))
|
||||
|
||||
streamVout.Insert(float64(len(txn.Vout)))
|
||||
sumVout += float64(len(txn.Vout))
|
||||
}
|
||||
|
||||
for _, phi := range phis {
|
||||
quantilesTxnSize[phi] = streamTxnSize.Query(phi)
|
||||
quantilesTxnFee[phi] = streamTxnFee.Query(phi)
|
||||
quantilesVin[phi] = streamVin.Query(phi)
|
||||
quantilesVout[phi] = streamVout.Query(phi)
|
||||
}
|
||||
|
||||
ch <- prometheus.MustNewConstSummary(
|
||||
prometheus.NewDesc(
|
||||
"monero_last_block_txn_size",
|
||||
"distribution of tx sizes",
|
||||
nil, nil,
|
||||
),
|
||||
uint64(streamTxnSize.Count()),
|
||||
sumTxnSize,
|
||||
quantilesTxnSize,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstSummary(
|
||||
prometheus.NewDesc(
|
||||
"monero_last_block_txn_fee",
|
||||
"distribution of outputs in last block",
|
||||
nil, nil,
|
||||
),
|
||||
uint64(streamTxnFee.Count()),
|
||||
sumTxnFee,
|
||||
quantilesTxnFee,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstSummary(
|
||||
prometheus.NewDesc(
|
||||
"monero_last_block_vin",
|
||||
"distribution of inputs in last block",
|
||||
nil, nil,
|
||||
),
|
||||
uint64(streamVin.Count()),
|
||||
sumVin,
|
||||
quantilesVin,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstSummary(
|
||||
prometheus.NewDesc(
|
||||
"monero_last_block_vout",
|
||||
"distribution of outputs in last block",
|
||||
nil, nil,
|
||||
),
|
||||
uint64(streamVout.Count()),
|
||||
sumVout,
|
||||
quantilesVout,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) CollectPeerHeightDivergence(ctx context.Context, ch chan<- prometheus.Metric) error {
|
||||
blockCountRes, err := c.client.GetBlockCount(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get block count: %w", err)
|
||||
}
|
||||
|
||||
res, err := c.client.GetConnections(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get connections: %w", err)
|
||||
}
|
||||
|
||||
phis := []float64{0.25, 0.50, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95, 0.99}
|
||||
stream := quantile.NewTargeted(phis...)
|
||||
|
||||
sum := float64(0)
|
||||
ourHeight := blockCountRes.Count
|
||||
for _, conn := range res.Connections {
|
||||
diff := math.Abs(float64(ourHeight - uint64(conn.Height)))
|
||||
|
||||
stream.Insert(diff)
|
||||
sum += diff
|
||||
}
|
||||
|
||||
quantiles := make(map[float64]float64, len(phis))
|
||||
for _, phi := range phis {
|
||||
quantiles[phi] = stream.Query(phi)
|
||||
}
|
||||
|
||||
desc := prometheus.NewDesc(
|
||||
"monero_height_divergence",
|
||||
"how much our peers diverge from us in block height",
|
||||
nil, nil,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstSummary(
|
||||
desc,
|
||||
uint64(stream.Count()),
|
||||
sum,
|
||||
quantiles,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) CollectPeersLiveTime(ctx context.Context, ch chan<- prometheus.Metric) error {
|
||||
res, err := c.client.GetConnections(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get connections: %w", err)
|
||||
}
|
||||
|
||||
var (
|
||||
phis = []float64{0.25, 0.50, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95, 0.99}
|
||||
sum = float64(0)
|
||||
stream = quantile.NewTargeted(phis...)
|
||||
quantiles = make(map[float64]float64, len(phis))
|
||||
)
|
||||
|
||||
for _, conn := range res.Connections {
|
||||
stream.Insert(float64(conn.LiveTime))
|
||||
sum += float64(conn.LiveTime)
|
||||
}
|
||||
|
||||
for _, phi := range phis {
|
||||
quantiles[phi] = stream.Query(phi)
|
||||
}
|
||||
|
||||
desc := prometheus.NewDesc(
|
||||
"monero_connections_livetime",
|
||||
"peers livetime distribution",
|
||||
nil, nil,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstSummary(
|
||||
desc,
|
||||
uint64(stream.Count()),
|
||||
sum,
|
||||
quantiles,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) CollectNetStats(ctx context.Context, ch chan<- prometheus.Metric) error {
|
||||
res, err := c.client.GetNetStats(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get fee estimate: %w", err)
|
||||
}
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
prometheus.NewDesc(
|
||||
"monero_net_total_in_bytes",
|
||||
"network statistics",
|
||||
nil, nil,
|
||||
),
|
||||
prometheus.CounterValue,
|
||||
float64(res.TotalBytesIn),
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
prometheus.NewDesc(
|
||||
"monero_net_total_out_bytes",
|
||||
"network statistics",
|
||||
nil, nil,
|
||||
),
|
||||
prometheus.CounterValue,
|
||||
float64(res.TotalBytesOut),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) CollectFeeEstimate(ctx context.Context, ch chan<- prometheus.Metric) error {
|
||||
res, err := c.client.GetFeeEstimate(ctx, 1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get fee estimate: %w", err)
|
||||
}
|
||||
|
||||
desc := prometheus.NewDesc(
|
||||
"monero_fee_estimate",
|
||||
"fee estimate for 1 grace block",
|
||||
nil, nil,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
desc,
|
||||
prometheus.GaugeValue,
|
||||
float64(res.Fee),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) CollectRPC(ctx context.Context, ch chan<- prometheus.Metric) error {
|
||||
res, err := c.client.RPCAccessTracking(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("rpc access tracking: %w", err)
|
||||
}
|
||||
|
||||
descCount := prometheus.NewDesc(
|
||||
"monero_rpc_count",
|
||||
"todo",
|
||||
[]string{"method"}, nil,
|
||||
)
|
||||
|
||||
descTime := prometheus.NewDesc(
|
||||
"monero_rpc_time",
|
||||
"todo",
|
||||
[]string{"method"}, nil,
|
||||
)
|
||||
|
||||
for _, d := range res.Data {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
descCount,
|
||||
prometheus.CounterValue,
|
||||
float64(d.Count),
|
||||
d.RPC,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
descTime,
|
||||
prometheus.CounterValue,
|
||||
float64(d.Time),
|
||||
d.RPC,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) CollectBans(ctx context.Context, ch chan<- prometheus.Metric) error {
|
||||
res, err := c.client.GetBans(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get bans: %w", err)
|
||||
}
|
||||
|
||||
desc := prometheus.NewDesc(
|
||||
"monero_bans",
|
||||
"number of nodes banned",
|
||||
nil, nil,
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
desc,
|
||||
prometheus.GaugeValue,
|
||||
float64(len(res.Bans)),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) CollectMempoolStats(ctx context.Context, ch chan<- prometheus.Metric) error {
|
||||
res, err := c.client.GetTransactionPoolStats(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get transaction pool: %w", err)
|
||||
}
|
||||
|
||||
metrics, err := c.toMetrics("mempool", &res.PoolStats)
|
||||
if err != nil {
|
||||
return fmt.Errorf("to metrics: %w", err)
|
||||
}
|
||||
|
||||
for _, metric := range metrics {
|
||||
ch <- metric
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Collector) toMetrics(ns string, res interface{}) ([]prometheus.Metric, error) {
|
||||
var (
|
||||
metrics = []prometheus.Metric{}
|
||||
v = reflect.ValueOf(res).Elem()
|
||||
err error
|
||||
)
|
||||
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
observation := float64(0)
|
||||
field := v.Field(i)
|
||||
|
||||
switch field.Type().Kind() {
|
||||
case reflect.Bool:
|
||||
if field.Bool() {
|
||||
observation = float64(1)
|
||||
}
|
||||
|
||||
case
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||
reflect.Float32, reflect.Float64,
|
||||
reflect.Uintptr:
|
||||
|
||||
observation, err = strconv.ParseFloat(fmt.Sprintf("%v", field.Interface()), 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse float: %w", err)
|
||||
}
|
||||
|
||||
default:
|
||||
c.log.Info("ignoring",
|
||||
"field", v.Type().Field(i).Name,
|
||||
"type", field.Type().Kind().String(),
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
tag := v.Type().Field(i).Tag.Get("json")
|
||||
|
||||
metrics = append(metrics, prometheus.MustNewConstMetric(
|
||||
prometheus.NewDesc(
|
||||
"monero_"+ns+"_"+tag,
|
||||
"info for "+tag,
|
||||
nil, nil,
|
||||
),
|
||||
prometheus.GaugeValue,
|
||||
observation,
|
||||
))
|
||||
}
|
||||
|
||||
return metrics, nil
|
||||
}
|
||||
|
151
pkg/collector/collector_connections.go
Normal file
151
pkg/collector/collector_connections.go
Normal file
@ -0,0 +1,151 @@
|
||||
package collector
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/cirocosta/go-monero/pkg/rpc/daemon"
|
||||
)
|
||||
|
||||
type ConnectionsCollector struct {
|
||||
client *daemon.Client
|
||||
metricsC chan<- prometheus.Metric
|
||||
|
||||
connections *daemon.GetConnectionsResult
|
||||
}
|
||||
|
||||
var _ CustomCollector = (*ConnectionsCollector)(nil)
|
||||
|
||||
func NewConnectionsCollector(
|
||||
client *daemon.Client, metricsC chan<- prometheus.Metric,
|
||||
) *ConnectionsCollector {
|
||||
return &ConnectionsCollector{
|
||||
client: client,
|
||||
metricsC: metricsC,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ConnectionsCollector) Name() string {
|
||||
return "connections"
|
||||
}
|
||||
|
||||
func (c *ConnectionsCollector) Collect(ctx context.Context) error {
|
||||
err := c.fetchData(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetch data: %w", err)
|
||||
}
|
||||
|
||||
c.collectConnectionsCount()
|
||||
c.collectHeightDistribution()
|
||||
c.collectDataRates()
|
||||
c.collectConnectionAges()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ConnectionsCollector) collectConnectionAges() {
|
||||
summary := NewSummary()
|
||||
|
||||
for _, conn := range c.connections.Connections {
|
||||
summary.Insert(float64(conn.LiveTime))
|
||||
}
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstSummary(
|
||||
prometheus.NewDesc(
|
||||
"monero_p2p_connections_age",
|
||||
"distribution of age of the connections we have",
|
||||
nil, nil,
|
||||
),
|
||||
summary.Count(), summary.Sum(), summary.Quantiles(),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *ConnectionsCollector) collectDataRates() {
|
||||
summaryRx := NewSummary()
|
||||
summaryTx := NewSummary()
|
||||
|
||||
for _, conn := range c.connections.Connections {
|
||||
summaryRx.Insert(float64(conn.RecvCount) / float64(conn.LiveTime))
|
||||
summaryTx.Insert(float64(conn.SendCount) / float64(conn.LiveTime))
|
||||
}
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstSummary(
|
||||
prometheus.NewDesc(
|
||||
"monero_p2p_connections_rx_rate_bps",
|
||||
"distribution of data receive rate in bytes/s",
|
||||
nil, nil,
|
||||
),
|
||||
summaryRx.Count(), summaryRx.Sum(), summaryRx.Quantiles(),
|
||||
)
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstSummary(
|
||||
prometheus.NewDesc(
|
||||
"monero_p2p_connections_tx_rate_bps",
|
||||
"distribution of data transmit rate in bytes/s",
|
||||
nil, nil,
|
||||
),
|
||||
summaryTx.Count(), summaryTx.Sum(), summaryTx.Quantiles(),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *ConnectionsCollector) collectHeightDistribution() {
|
||||
summary := NewSummary()
|
||||
for _, conn := range c.connections.Connections {
|
||||
summary.Insert(float64(conn.Height))
|
||||
}
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstSummary(
|
||||
prometheus.NewDesc(
|
||||
"monero_p2p_connections_height",
|
||||
"distribution the height of the peers "+
|
||||
"connected to/from us",
|
||||
nil, nil,
|
||||
),
|
||||
summary.Count(), summary.Sum(), summary.Quantiles(),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *ConnectionsCollector) collectConnectionsCount() {
|
||||
desc := prometheus.NewDesc(
|
||||
"monero_p2p_connections",
|
||||
"number of connections to/from this node",
|
||||
[]string{"type", "state"}, nil,
|
||||
)
|
||||
|
||||
type key struct {
|
||||
ttype string
|
||||
state string
|
||||
}
|
||||
|
||||
counters := map[key]float64{}
|
||||
|
||||
for _, conn := range c.connections.Connections {
|
||||
ttype := "in"
|
||||
if !conn.Incoming {
|
||||
ttype = "out"
|
||||
}
|
||||
|
||||
counters[key{ttype, conn.State}]++
|
||||
}
|
||||
|
||||
for k, v := range counters {
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
desc,
|
||||
prometheus.GaugeValue,
|
||||
v,
|
||||
k.ttype, k.state,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ConnectionsCollector) fetchData(ctx context.Context) error {
|
||||
res, err := c.client.GetConnections(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get connections: %w", err)
|
||||
}
|
||||
|
||||
c.connections = res
|
||||
return nil
|
||||
}
|
300
pkg/collector/collector_lastblock.go
Normal file
300
pkg/collector/collector_lastblock.go
Normal file
@ -0,0 +1,300 @@
|
||||
package collector
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/cirocosta/go-monero/pkg/constant"
|
||||
"github.com/cirocosta/go-monero/pkg/rpc/daemon"
|
||||
)
|
||||
|
||||
type LastBlockStatsCollector struct {
|
||||
client *daemon.Client
|
||||
metricsC chan<- prometheus.Metric
|
||||
|
||||
txns []*daemon.TransactionJSON
|
||||
txnSizes []int
|
||||
header daemon.BlockHeader
|
||||
}
|
||||
|
||||
var _ CustomCollector = (*LastBlockStatsCollector)(nil)
|
||||
|
||||
func NewLastBlockStatsCollector(
|
||||
client *daemon.Client, metricsC chan<- prometheus.Metric,
|
||||
) *LastBlockStatsCollector {
|
||||
return &LastBlockStatsCollector{
|
||||
client: client,
|
||||
metricsC: metricsC,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *LastBlockStatsCollector) Name() string {
|
||||
return "lastblock"
|
||||
}
|
||||
|
||||
func (c *LastBlockStatsCollector) Collect(ctx context.Context) error {
|
||||
err := c.fetchData(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetch last block data: %w", err)
|
||||
}
|
||||
|
||||
c.collectBlockSize()
|
||||
c.collectDifficulty()
|
||||
c.collectFees()
|
||||
c.collectHeight()
|
||||
c.collectReward()
|
||||
c.collectSubsidy()
|
||||
c.collectTransactionsCount()
|
||||
c.collectTransactionsFeePerKb()
|
||||
c.collectTransactionsInputs()
|
||||
c.collectTransactionsOutputs()
|
||||
c.collectTransactionsSize()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *LastBlockStatsCollector) fetchData(ctx context.Context) error {
|
||||
lastBlockHeaderResp, err := c.client.GetLastBlockHeader(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get last block header: %w", err)
|
||||
}
|
||||
|
||||
lastBlockHash := lastBlockHeaderResp.BlockHeader.Hash
|
||||
|
||||
params := daemon.GetBlockRequestParameters{
|
||||
Hash: lastBlockHash,
|
||||
}
|
||||
blockResp, err := c.client.GetBlock(ctx, params)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get block '%s': %w", lastBlockHash, err)
|
||||
}
|
||||
|
||||
blockJSON, err := blockResp.InnerJSON()
|
||||
if err != nil {
|
||||
return fmt.Errorf("block inner json: %w", err)
|
||||
}
|
||||
|
||||
txnsResp, err := c.client.GetTransactions(ctx, blockJSON.TxHashes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get txns: %w", err)
|
||||
}
|
||||
|
||||
txnSizes := make([]int, len(txnsResp.Txs))
|
||||
for idx, t := range txnsResp.Txs {
|
||||
txnSizes[idx] = len(t.AsHex) / 2
|
||||
}
|
||||
|
||||
txns, err := txnsResp.GetTransactions()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get transactions: %w", err)
|
||||
}
|
||||
|
||||
c.txns = txns
|
||||
c.txnSizes = txnSizes
|
||||
c.header = blockResp.BlockHeader
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *LastBlockStatsCollector) collectBlockSize() {
|
||||
desc := prometheus.NewDesc(
|
||||
"monero_lastblock_size_bytes",
|
||||
"total size of the last block",
|
||||
nil, nil,
|
||||
)
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
desc,
|
||||
prometheus.GaugeValue,
|
||||
float64(c.header.BlockSize),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *LastBlockStatsCollector) collectDifficulty() {
|
||||
desc := prometheus.NewDesc(
|
||||
"monero_lastblock_difficulty",
|
||||
"difficulty used for the last block",
|
||||
nil, nil,
|
||||
)
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
desc,
|
||||
prometheus.GaugeValue,
|
||||
float64(c.header.Difficulty),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *LastBlockStatsCollector) collectFees() {
|
||||
desc := prometheus.NewDesc(
|
||||
"monero_lastblock_fees_monero",
|
||||
"total amount of fees included in this block",
|
||||
nil, nil,
|
||||
)
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
desc,
|
||||
prometheus.GaugeValue,
|
||||
float64(c.gatherFees(c.txns))/constant.XMR,
|
||||
)
|
||||
}
|
||||
|
||||
func (c *LastBlockStatsCollector) collectHeight() {
|
||||
desc := prometheus.NewDesc(
|
||||
"monero_lastblock_height",
|
||||
"height of the last block",
|
||||
nil, nil,
|
||||
)
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
desc,
|
||||
prometheus.GaugeValue,
|
||||
float64(c.header.Height),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *LastBlockStatsCollector) collectReward() {
|
||||
desc := prometheus.NewDesc(
|
||||
"monero_lastblock_reward_monero",
|
||||
"total amount of rewards granted in the last block "+
|
||||
"(subsidy + fees)",
|
||||
nil, nil,
|
||||
)
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
desc,
|
||||
prometheus.GaugeValue,
|
||||
float64(c.header.Reward)/constant.XMR,
|
||||
)
|
||||
}
|
||||
|
||||
func (c *LastBlockStatsCollector) collectSubsidy() {
|
||||
totalReward := float64(c.header.Reward)
|
||||
fees := float64(c.gatherFees(c.txns))
|
||||
subsidy := (totalReward - fees) / constant.XMR
|
||||
|
||||
desc := prometheus.NewDesc(
|
||||
"monero_lastblock_subsidy_monero",
|
||||
"newly minted monero for this block",
|
||||
nil, nil,
|
||||
)
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
desc,
|
||||
prometheus.GaugeValue,
|
||||
subsidy,
|
||||
)
|
||||
}
|
||||
|
||||
func (c *LastBlockStatsCollector) collectTransactionsCount() {
|
||||
desc := prometheus.NewDesc(
|
||||
"monero_lastblock_transactions",
|
||||
"number of transactions seen in the last block",
|
||||
nil, nil,
|
||||
)
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
desc,
|
||||
prometheus.GaugeValue,
|
||||
float64(c.header.NumTxes),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *LastBlockStatsCollector) collectTransactionsFeePerKb() {
|
||||
summary := NewSummary()
|
||||
for idx, txn := range c.txns {
|
||||
fee := float64(txn.RctSignatures.Txnfee) / constant.MicroXMR
|
||||
size := float64(c.txnSizes[idx]) / 1024
|
||||
|
||||
summary.Insert(fee / size)
|
||||
}
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstSummary(
|
||||
prometheus.NewDesc(
|
||||
"monero_lastblock_fees_micronero_per_kb",
|
||||
"distribution of the feeperkb utilized for txns",
|
||||
nil, nil,
|
||||
),
|
||||
summary.Count(), summary.Sum(), summary.Quantiles(),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *LastBlockStatsCollector) collectTransactionsSize() {
|
||||
summary := NewSummary()
|
||||
for _, size := range c.txnSizes {
|
||||
summary.Insert(float64(size))
|
||||
}
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstSummary(
|
||||
prometheus.NewDesc(
|
||||
"monero_lastblock_transactions_size_bytes",
|
||||
"distribution of the size of the transactions included",
|
||||
nil, nil,
|
||||
),
|
||||
summary.Count(), summary.Sum(), summary.Quantiles(),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *LastBlockStatsCollector) collectTransactionsInputs() {
|
||||
summary := NewSummary()
|
||||
for _, txn := range c.txns {
|
||||
summary.Insert(float64(len(txn.Vin)))
|
||||
}
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstSummary(
|
||||
prometheus.NewDesc(
|
||||
"monero_lastblock_transactions_inputs",
|
||||
"distribution of inputs in the last block",
|
||||
nil, nil,
|
||||
),
|
||||
summary.Count(), summary.Sum(), summary.Quantiles(),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *LastBlockStatsCollector) collectTransactionsOutputs() {
|
||||
summary := NewSummary()
|
||||
for _, txn := range c.txns {
|
||||
summary.Insert(float64(len(txn.Vout)))
|
||||
}
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstSummary(
|
||||
prometheus.NewDesc(
|
||||
"monero_lastblock_transactions_outputs",
|
||||
"distribution of outputs in the last block",
|
||||
nil, nil,
|
||||
),
|
||||
summary.Count(), summary.Sum(), summary.Quantiles(),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *LastBlockStatsCollector) collectVersions() {
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
prometheus.NewDesc(
|
||||
"monero_lastblock_version_major",
|
||||
"major version of the block format",
|
||||
nil, nil,
|
||||
),
|
||||
prometheus.GaugeValue,
|
||||
float64(c.header.MajorVersion),
|
||||
)
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
prometheus.NewDesc(
|
||||
"monero_lastblock_version_minor",
|
||||
"minor version of the block format",
|
||||
nil, nil,
|
||||
),
|
||||
prometheus.GaugeValue,
|
||||
float64(c.header.MinorVersion),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *LastBlockStatsCollector) gatherFees(txns []*daemon.TransactionJSON) uint64 {
|
||||
fees := uint64(0)
|
||||
for _, txn := range txns {
|
||||
fees += txn.RctSignatures.Txnfee
|
||||
}
|
||||
|
||||
return fees
|
||||
}
|
76
pkg/collector/collector_netstats.go
Normal file
76
pkg/collector/collector_netstats.go
Normal file
@ -0,0 +1,76 @@
|
||||
package collector
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/cirocosta/go-monero/pkg/rpc/daemon"
|
||||
)
|
||||
|
||||
type NetStatsCollector struct {
|
||||
client *daemon.Client
|
||||
metricsC chan<- prometheus.Metric
|
||||
|
||||
stats *daemon.GetNetStatsResult
|
||||
}
|
||||
|
||||
var _ CustomCollector = (*NetStatsCollector)(nil)
|
||||
|
||||
func NewNetStatsCollector(
|
||||
client *daemon.Client, metricsC chan<- prometheus.Metric,
|
||||
) *NetStatsCollector {
|
||||
return &NetStatsCollector{
|
||||
client: client,
|
||||
metricsC: metricsC,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *NetStatsCollector) Name() string {
|
||||
return "net"
|
||||
}
|
||||
|
||||
func (c *NetStatsCollector) Collect(ctx context.Context) error {
|
||||
err := c.fetchData(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetch data: %w", err)
|
||||
}
|
||||
|
||||
c.collectRxTx()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *NetStatsCollector) fetchData(ctx context.Context) error {
|
||||
res, err := c.client.GetNetStats(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get netstats: %w", err)
|
||||
}
|
||||
|
||||
c.stats = res
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *NetStatsCollector) collectRxTx() {
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
prometheus.NewDesc(
|
||||
"monero_net_rx_bytes",
|
||||
"number of bytes received by this node",
|
||||
nil, nil,
|
||||
),
|
||||
prometheus.GaugeValue,
|
||||
float64(c.stats.TotalBytesIn),
|
||||
)
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
prometheus.NewDesc(
|
||||
"monero_net_tx_bytes",
|
||||
"number of bytes received by this node",
|
||||
nil, nil,
|
||||
),
|
||||
prometheus.GaugeValue,
|
||||
float64(c.stats.TotalBytesOut),
|
||||
)
|
||||
}
|
139
pkg/collector/collector_overall.go
Normal file
139
pkg/collector/collector_overall.go
Normal file
@ -0,0 +1,139 @@
|
||||
package collector
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/cirocosta/go-monero/pkg/rpc/daemon"
|
||||
)
|
||||
|
||||
type OverallCollector struct {
|
||||
client *daemon.Client
|
||||
metricsC chan<- prometheus.Metric
|
||||
|
||||
info *daemon.GetInfoResult
|
||||
}
|
||||
|
||||
var _ CustomCollector = (*OverallCollector)(nil)
|
||||
|
||||
func NewOverallCollector(
|
||||
client *daemon.Client, metricsC chan<- prometheus.Metric,
|
||||
) *OverallCollector {
|
||||
return &OverallCollector{
|
||||
client: client,
|
||||
metricsC: metricsC,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *OverallCollector) Name() string {
|
||||
return "overall"
|
||||
}
|
||||
|
||||
func (c *OverallCollector) Collect(ctx context.Context) error {
|
||||
err := c.fetchData(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetch data: %w", err)
|
||||
}
|
||||
|
||||
c.collect()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *OverallCollector) fetchData(ctx context.Context) error {
|
||||
res, err := c.client.GetInfo(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get netstats: %w", err)
|
||||
}
|
||||
|
||||
c.info = res
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *OverallCollector) collect() {
|
||||
now := time.Now()
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
prometheus.NewDesc(
|
||||
"monero_info_uptime_seconds_total",
|
||||
"for how long this node has been up",
|
||||
nil, nil,
|
||||
),
|
||||
prometheus.GaugeValue,
|
||||
float64(now.
|
||||
Sub(time.Unix(int64(c.info.StartTime), 0)).
|
||||
Seconds()),
|
||||
)
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
prometheus.NewDesc(
|
||||
"monero_info_alternative_blocks",
|
||||
"number of blocks alternative to the longest",
|
||||
nil, nil,
|
||||
),
|
||||
prometheus.GaugeValue,
|
||||
float64(c.info.AltBlocksCount),
|
||||
)
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
prometheus.NewDesc(
|
||||
"monero_info_offline",
|
||||
"whether the node is offline",
|
||||
nil, nil,
|
||||
),
|
||||
prometheus.GaugeValue,
|
||||
boolToFloat64(c.info.Offline),
|
||||
)
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
prometheus.NewDesc(
|
||||
"monero_info_mainnet",
|
||||
"whether the node is connected to mainnet",
|
||||
nil, nil,
|
||||
),
|
||||
prometheus.GaugeValue,
|
||||
boolToFloat64(c.info.Mainnet),
|
||||
)
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
prometheus.NewDesc(
|
||||
"monero_info_block_size_limit",
|
||||
"maximum hard limit of a block",
|
||||
nil, nil,
|
||||
),
|
||||
prometheus.GaugeValue,
|
||||
float64(c.info.BlockSizeLimit),
|
||||
)
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
prometheus.NewDesc(
|
||||
"monero_info_block_size_median",
|
||||
"current median size for computing dynamic fees",
|
||||
nil, nil,
|
||||
),
|
||||
prometheus.GaugeValue,
|
||||
float64(c.info.BlockSizeMedian),
|
||||
)
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
prometheus.NewDesc(
|
||||
"monero_info_synchronized",
|
||||
"",
|
||||
nil, nil,
|
||||
),
|
||||
prometheus.GaugeValue,
|
||||
boolToFloat64(c.info.Synchronized),
|
||||
)
|
||||
}
|
||||
|
||||
func boolToFloat64(b bool) float64 {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
100
pkg/collector/collector_peers.go
Normal file
100
pkg/collector/collector_peers.go
Normal file
@ -0,0 +1,100 @@
|
||||
package collector
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/cirocosta/go-monero/pkg/rpc/daemon"
|
||||
)
|
||||
|
||||
type PeersCollector struct {
|
||||
client *daemon.Client
|
||||
metricsC chan<- prometheus.Metric
|
||||
|
||||
graylist []daemon.Peer
|
||||
whitelist []daemon.Peer
|
||||
}
|
||||
|
||||
var _ CustomCollector = (*PeersCollector)(nil)
|
||||
|
||||
func NewPeersCollector(
|
||||
client *daemon.Client, metricsC chan<- prometheus.Metric,
|
||||
) *PeersCollector {
|
||||
return &PeersCollector{
|
||||
client: client,
|
||||
metricsC: metricsC,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *PeersCollector) Name() string {
|
||||
return "peerlist"
|
||||
}
|
||||
|
||||
func (c *PeersCollector) Collect(ctx context.Context) error {
|
||||
err := c.fetchData(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetch data: %w", err)
|
||||
}
|
||||
|
||||
c.collectPeersCount()
|
||||
c.collectPeersLastSeen()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *PeersCollector) fetchData(ctx context.Context) error {
|
||||
resp, err := c.client.GetPeerList(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get peerlist: %w", err)
|
||||
}
|
||||
|
||||
c.graylist = resp.GrayList
|
||||
c.whitelist = resp.WhiteList
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *PeersCollector) collectPeersCount() {
|
||||
desc := prometheus.NewDesc(
|
||||
"monero_peerlist",
|
||||
"number of node entries in the peerlist",
|
||||
[]string{"type"}, nil,
|
||||
)
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
desc,
|
||||
prometheus.GaugeValue,
|
||||
float64(len(c.whitelist)),
|
||||
"white",
|
||||
)
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
desc,
|
||||
prometheus.GaugeValue,
|
||||
float64(len(c.graylist)),
|
||||
"gray",
|
||||
)
|
||||
}
|
||||
|
||||
func (c *PeersCollector) collectPeersLastSeen() {
|
||||
now := time.Now()
|
||||
summary := NewSummary()
|
||||
|
||||
for _, peer := range c.whitelist {
|
||||
summary.Insert(now.
|
||||
Sub(time.Unix(peer.LastSeen, 0)).
|
||||
Seconds())
|
||||
}
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstSummary(
|
||||
prometheus.NewDesc(
|
||||
"monero_peerlist_lastseen",
|
||||
"distribution of when our peers have been seen",
|
||||
nil, nil,
|
||||
),
|
||||
summary.Count(), summary.Sum(), summary.Quantiles(),
|
||||
)
|
||||
}
|
87
pkg/collector/collector_rpc.go
Normal file
87
pkg/collector/collector_rpc.go
Normal file
@ -0,0 +1,87 @@
|
||||
package collector
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/cirocosta/go-monero/pkg/rpc/daemon"
|
||||
)
|
||||
|
||||
type RPCCollector struct {
|
||||
client *daemon.Client
|
||||
metricsC chan<- prometheus.Metric
|
||||
|
||||
accessTracking *daemon.RPCAccessTrackingResult
|
||||
}
|
||||
|
||||
var _ CustomCollector = (*RPCCollector)(nil)
|
||||
|
||||
func NewRPCCollector(
|
||||
client *daemon.Client, metricsC chan<- prometheus.Metric,
|
||||
) *RPCCollector {
|
||||
return &RPCCollector{
|
||||
client: client,
|
||||
metricsC: metricsC,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RPCCollector) Name() string {
|
||||
return "rpc"
|
||||
}
|
||||
|
||||
func (c *RPCCollector) Collect(ctx context.Context) error {
|
||||
err := c.fetchData(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetch data: %w", err)
|
||||
}
|
||||
|
||||
c.collectRPC()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RPCCollector) collectRPC() {
|
||||
countDesc := prometheus.NewDesc(
|
||||
"monero_rpc_hits_total",
|
||||
"number of hits that a particular rpc "+
|
||||
"method had since startup",
|
||||
[]string{"method"}, nil,
|
||||
)
|
||||
|
||||
timeDesc := prometheus.NewDesc(
|
||||
"monero_rpc_seconds_total",
|
||||
"amount of time spent service the method "+
|
||||
"since startup",
|
||||
[]string{"method"}, nil,
|
||||
)
|
||||
|
||||
for _, d := range c.accessTracking.Data {
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
countDesc,
|
||||
prometheus.GaugeValue,
|
||||
float64(d.Count),
|
||||
d.RPC,
|
||||
)
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
timeDesc,
|
||||
prometheus.GaugeValue,
|
||||
time.Duration(int64(d.Time)).Seconds(),
|
||||
d.RPC,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RPCCollector) fetchData(ctx context.Context) error {
|
||||
res, err := c.client.RPCAccessTracking(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("rpc access tracking: %w", err)
|
||||
}
|
||||
|
||||
c.accessTracking = res
|
||||
|
||||
return nil
|
||||
}
|
277
pkg/collector/collector_transactionpool.go
Normal file
277
pkg/collector/collector_transactionpool.go
Normal file
@ -0,0 +1,277 @@
|
||||
package collector
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/cirocosta/go-monero/pkg/constant"
|
||||
"github.com/cirocosta/go-monero/pkg/rpc/daemon"
|
||||
)
|
||||
|
||||
type TransactionPoolCollector struct {
|
||||
client *daemon.Client
|
||||
metricsC chan<- prometheus.Metric
|
||||
|
||||
txns []*daemon.TransactionJSON
|
||||
pool *daemon.GetTransactionPoolResult
|
||||
stats *daemon.GetTransactionPoolStatsResult
|
||||
}
|
||||
|
||||
var _ CustomCollector = (*TransactionPoolCollector)(nil)
|
||||
|
||||
func NewTransactionPoolCollector(
|
||||
client *daemon.Client, metricsC chan<- prometheus.Metric,
|
||||
) *TransactionPoolCollector {
|
||||
return &TransactionPoolCollector{
|
||||
client: client,
|
||||
metricsC: metricsC,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *TransactionPoolCollector) Name() string {
|
||||
return "transaction_pool"
|
||||
}
|
||||
|
||||
func (c *TransactionPoolCollector) Collect(ctx context.Context) error {
|
||||
err := c.fetchData(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetch data: %w", err)
|
||||
}
|
||||
|
||||
c.collectSpentKeyImages()
|
||||
c.collectSize()
|
||||
c.collectTransactionsSize()
|
||||
c.collectTransactionsCount()
|
||||
c.collectTransactionsFee()
|
||||
c.collectTransactionsFeePerKb()
|
||||
c.collectTransactionsInputs()
|
||||
c.collectTransactionsOutputs()
|
||||
c.collectTransactionsAgeDistribution()
|
||||
c.collectWeirdCases()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *TransactionPoolCollector) fetchData(ctx context.Context) error {
|
||||
stats, err := c.client.GetTransactionPoolStats(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get transactionpool stats: %w", err)
|
||||
}
|
||||
|
||||
pool, err := c.client.GetTransactionPool(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get transaction pool: %w", err)
|
||||
}
|
||||
|
||||
c.stats = stats
|
||||
c.pool = pool
|
||||
|
||||
c.txns = make([]*daemon.TransactionJSON, len(pool.Transactions))
|
||||
|
||||
for idx, txn := range c.pool.Transactions {
|
||||
c.txns[idx] = new(daemon.TransactionJSON)
|
||||
|
||||
err := json.Unmarshal([]byte(txn.TxJSON), c.txns[idx])
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarhsal tx json: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *TransactionPoolCollector) collectSpentKeyImages() {
|
||||
desc := prometheus.NewDesc(
|
||||
"monero_transaction_pool_spent_key_images",
|
||||
"total number of key images spent across all transactions"+
|
||||
" in the pool",
|
||||
nil, nil,
|
||||
)
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
desc,
|
||||
prometheus.GaugeValue,
|
||||
float64(len(c.pool.SpentKeyImages)),
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
func (c *TransactionPoolCollector) collectTransactionsCount() {
|
||||
desc := prometheus.NewDesc(
|
||||
"monero_transaction_pool_transactions",
|
||||
"number of transactions in the pool at the moment of "+
|
||||
"the scrape",
|
||||
nil, nil,
|
||||
)
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
desc,
|
||||
prometheus.GaugeValue,
|
||||
float64(len(c.pool.Transactions)),
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
func (c *TransactionPoolCollector) collectSize() {
|
||||
desc := prometheus.NewDesc(
|
||||
"monero_transaction_pool_size_bytes",
|
||||
"total size of the transaction pool",
|
||||
nil, nil,
|
||||
)
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
desc,
|
||||
prometheus.GaugeValue,
|
||||
float64(c.stats.PoolStats.BytesTotal),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *TransactionPoolCollector) collectTransactionsSize() {
|
||||
summary := NewSummary()
|
||||
for _, txn := range c.pool.Transactions {
|
||||
summary.Insert(float64(txn.BlobSize))
|
||||
}
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstSummary(
|
||||
prometheus.NewDesc(
|
||||
"monero_transaction_pool_transactions_size_bytes",
|
||||
"distribution of the size of the transactions "+
|
||||
"in the transaction pool",
|
||||
nil, nil,
|
||||
),
|
||||
summary.Count(), summary.Sum(), summary.Quantiles(),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *TransactionPoolCollector) collectTransactionsFeePerKb() {
|
||||
summary := NewSummary()
|
||||
for _, txn := range c.pool.Transactions {
|
||||
fee := float64(txn.Fee) / constant.MicroXMR
|
||||
size := float64(txn.BlobSize) / 1024
|
||||
|
||||
summary.Insert(fee / size)
|
||||
}
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstSummary(
|
||||
prometheus.NewDesc(
|
||||
"monero_transaction_pool_fees_micronero_per_kb",
|
||||
"distribution of the feeperkb utilized for txns"+
|
||||
" in the pool",
|
||||
nil, nil,
|
||||
),
|
||||
summary.Count(), summary.Sum(), summary.Quantiles(),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *TransactionPoolCollector) collectTransactionsInputs() {
|
||||
summary := NewSummary()
|
||||
for _, txn := range c.txns {
|
||||
summary.Insert(float64(len(txn.Vin)))
|
||||
}
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstSummary(
|
||||
prometheus.NewDesc(
|
||||
"monero_transaction_pool_transactions_inputs",
|
||||
"distribution of inputs in the pool",
|
||||
nil, nil,
|
||||
),
|
||||
summary.Count(), summary.Sum(), summary.Quantiles(),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *TransactionPoolCollector) collectTransactionsOutputs() {
|
||||
summary := NewSummary()
|
||||
for _, txn := range c.txns {
|
||||
summary.Insert(float64(len(txn.Vout)))
|
||||
}
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstSummary(
|
||||
prometheus.NewDesc(
|
||||
"monero_transaction_pool_transactions_outputs",
|
||||
"distribution of outputs in the pool",
|
||||
nil, nil,
|
||||
),
|
||||
summary.Count(), summary.Sum(), summary.Quantiles(),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *TransactionPoolCollector) collectTransactionsAgeDistribution() {
|
||||
now := time.Now()
|
||||
|
||||
summary := NewSummary()
|
||||
for _, txn := range c.pool.Transactions {
|
||||
summary.Insert(
|
||||
now.Sub(time.Unix(txn.ReceiveTime, 0)).Seconds(),
|
||||
)
|
||||
}
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstSummary(
|
||||
prometheus.NewDesc(
|
||||
"monero_transaction_pool_transactions_age",
|
||||
"distribution of for how long transactions have "+
|
||||
"been in the pool",
|
||||
nil, nil,
|
||||
),
|
||||
summary.Count(), summary.Sum(), summary.Quantiles(),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *TransactionPoolCollector) collectTransactionsFee() {
|
||||
desc := prometheus.NewDesc(
|
||||
"monero_transaction_pool_fees_monero",
|
||||
"total amount of fee being spent in the transaction pool",
|
||||
nil, nil,
|
||||
)
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
desc,
|
||||
prometheus.GaugeValue,
|
||||
float64(c.stats.PoolStats.FeeTotal)/constant.XMR,
|
||||
)
|
||||
}
|
||||
|
||||
func (c *TransactionPoolCollector) collectWeirdCases() {
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
prometheus.NewDesc(
|
||||
"monero_transaction_pool_failing_transactions",
|
||||
"number of transactions that are marked as failing",
|
||||
nil, nil,
|
||||
),
|
||||
prometheus.GaugeValue,
|
||||
float64(c.stats.PoolStats.NumFailing),
|
||||
)
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
prometheus.NewDesc(
|
||||
"monero_transaction_pool_double_spends",
|
||||
"transactions doubly spending outputs",
|
||||
nil, nil,
|
||||
),
|
||||
prometheus.GaugeValue,
|
||||
float64(c.stats.PoolStats.NumDoubleSpends),
|
||||
)
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
prometheus.NewDesc(
|
||||
"monero_transaction_pool_not_relayed",
|
||||
"number of transactions that have not been relayed",
|
||||
nil, nil,
|
||||
),
|
||||
prometheus.GaugeValue,
|
||||
float64(c.stats.PoolStats.NumNotRelayed),
|
||||
)
|
||||
|
||||
c.metricsC <- prometheus.MustNewConstMetric(
|
||||
prometheus.NewDesc(
|
||||
"monero_transaction_pool_older_than_10m",
|
||||
"number of transactions that are older than 10m",
|
||||
nil, nil,
|
||||
),
|
||||
prometheus.GaugeValue,
|
||||
float64(c.stats.PoolStats.Num10M),
|
||||
)
|
||||
}
|
94
pkg/collector/summary.go
Normal file
94
pkg/collector/summary.go
Normal file
@ -0,0 +1,94 @@
|
||||
package collector
|
||||
|
||||
import "github.com/beorn7/perks/quantile"
|
||||
|
||||
// defaultQuantiles is the default quantiles to compute for a given data stream
|
||||
// that we want to summarize.
|
||||
//
|
||||
// these (quantile -> epsilon) will be used by default by any Summary unless
|
||||
// initialized with the `WithQuantiles` option to override it.
|
||||
//
|
||||
var defaultQuantiles = map[float64]float64{
|
||||
0.05: 0.01,
|
||||
0.10: 0.01,
|
||||
0.25: 0.01,
|
||||
0.50: 0.01,
|
||||
0.75: 0.01,
|
||||
0.90: 0.01,
|
||||
0.95: 0.01,
|
||||
0.99: 0.01,
|
||||
1.00: 0.01,
|
||||
}
|
||||
|
||||
type Summary struct {
|
||||
count uint64
|
||||
sum float64
|
||||
quantiles map[float64]float64
|
||||
|
||||
stream *quantile.Stream
|
||||
computed bool
|
||||
}
|
||||
|
||||
type SummaryOption func(s *Summary)
|
||||
|
||||
func WithQuantiles(v map[float64]float64) SummaryOption {
|
||||
return func(s *Summary) {
|
||||
s.quantiles = v
|
||||
}
|
||||
}
|
||||
|
||||
func NewSummary(opts ...SummaryOption) *Summary {
|
||||
summary := &Summary{
|
||||
count: uint64(0),
|
||||
sum: float64(0),
|
||||
quantiles: cloneMap(defaultQuantiles),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(summary)
|
||||
}
|
||||
|
||||
summary.stream = quantile.NewTargeted(summary.quantiles)
|
||||
|
||||
return summary
|
||||
}
|
||||
|
||||
func (s *Summary) Insert(v float64) {
|
||||
s.sum += v
|
||||
s.stream.Insert(v)
|
||||
s.count++
|
||||
}
|
||||
|
||||
func (s *Summary) Count() uint64 {
|
||||
s.compute()
|
||||
return s.count
|
||||
}
|
||||
|
||||
func (s *Summary) Quantiles() map[float64]float64 {
|
||||
s.compute()
|
||||
return s.quantiles
|
||||
}
|
||||
|
||||
func (s *Summary) Sum() float64 {
|
||||
s.compute()
|
||||
return s.sum
|
||||
}
|
||||
|
||||
func (s *Summary) compute() {
|
||||
if s.computed {
|
||||
return
|
||||
}
|
||||
|
||||
for phi := range s.quantiles {
|
||||
s.quantiles[phi] = s.stream.Query(phi)
|
||||
}
|
||||
}
|
||||
|
||||
func cloneMap(o map[float64]float64) map[float64]float64 {
|
||||
m := make(map[float64]float64, len(o))
|
||||
for k, v := range o {
|
||||
m[k] = v
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
@ -12,42 +12,55 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBindAddress = ":9000"
|
||||
defaultTelemetryPath = "/metrics"
|
||||
)
|
||||
|
||||
// Exporter is responsible for bringing up a web server that collects metrics
|
||||
// that have been globally registered via prometheus collectors (e.g., see
|
||||
// `pkg/collector`).
|
||||
//
|
||||
type Exporter struct {
|
||||
// ListenAddress is the full address used by prometheus
|
||||
// to listen for scraping requests.
|
||||
//
|
||||
// Examples:
|
||||
// - :8080
|
||||
// - 127.0.0.2:1313
|
||||
//
|
||||
listenAddress string
|
||||
|
||||
// TelemetryPath configures the path under which
|
||||
// the prometheus metrics are reported.
|
||||
//
|
||||
// For instance:
|
||||
// - /metrics
|
||||
// - /telemetry
|
||||
//
|
||||
bindAddress string
|
||||
telemetryPath string
|
||||
|
||||
// listener is the TCP listener used by the webserver. `nil` if no
|
||||
// server is running.
|
||||
//
|
||||
listener net.Listener
|
||||
|
||||
log logr.Logger
|
||||
log logr.Logger
|
||||
}
|
||||
|
||||
// Option.
|
||||
// WithTelemetryPath overrides the default path under which the prometheus
|
||||
// metrics are reported.
|
||||
//
|
||||
// For instance:
|
||||
// - /
|
||||
// - /metrics
|
||||
// - /telemetry
|
||||
//
|
||||
func WithTelemetryPath(v string) Option {
|
||||
return func(e *Exporter) {
|
||||
e.telemetryPath = v
|
||||
}
|
||||
}
|
||||
|
||||
// WithBindAddress overrides the default address at which the prometheus
|
||||
// metrics HTTP server would bind to.
|
||||
//
|
||||
// Examples:
|
||||
// - :8080
|
||||
// - 127.0.0.2:1313
|
||||
//
|
||||
func WithBindAddress(v string) Option {
|
||||
return func(e *Exporter) {
|
||||
e.bindAddress = v
|
||||
}
|
||||
}
|
||||
|
||||
// Option allows overriding the exporter's defaults
|
||||
//
|
||||
type Option func(e *Exporter)
|
||||
|
||||
// New.
|
||||
// New instantiates a new exporter with defaults, unless options are passed.
|
||||
//
|
||||
func New(opts ...Option) (*Exporter, error) {
|
||||
defaultLogger, err := zap.NewDevelopment()
|
||||
@ -56,8 +69,8 @@ func New(opts ...Option) (*Exporter, error) {
|
||||
}
|
||||
|
||||
e := &Exporter{
|
||||
listenAddress: ":9000",
|
||||
telemetryPath: "/metrics",
|
||||
bindAddress: defaultBindAddress,
|
||||
telemetryPath: defaultTelemetryPath,
|
||||
log: zapr.NewLogger(defaultLogger.Named("exporter")),
|
||||
}
|
||||
|
||||
@ -76,9 +89,9 @@ func New(opts ...Option) (*Exporter, error) {
|
||||
func (e *Exporter) Run(ctx context.Context) error {
|
||||
var err error
|
||||
|
||||
e.listener, err = net.Listen("tcp", e.listenAddress)
|
||||
e.listener, err = net.Listen("tcp", e.bindAddress)
|
||||
if err != nil {
|
||||
return fmt.Errorf("listen on '%s': %w", e.listenAddress, err)
|
||||
return fmt.Errorf("listen on '%s': %w", e.bindAddress, err)
|
||||
}
|
||||
|
||||
doneChan := make(chan error, 1)
|
||||
@ -87,7 +100,7 @@ func (e *Exporter) Run(ctx context.Context) error {
|
||||
defer close(doneChan)
|
||||
|
||||
e.log.WithValues(
|
||||
"addr", e.listenAddress,
|
||||
"addr", e.bindAddress,
|
||||
"path", e.telemetryPath,
|
||||
).Info("listening")
|
||||
|
||||
@ -95,7 +108,7 @@ func (e *Exporter) Run(ctx context.Context) error {
|
||||
if err := http.Serve(e.listener, nil); err != nil {
|
||||
doneChan <- fmt.Errorf(
|
||||
"failed listening on address %s: %w",
|
||||
e.listenAddress, err,
|
||||
e.bindAddress, err,
|
||||
)
|
||||
}
|
||||
}()
|
||||
|
Loading…
Reference in New Issue
Block a user