From d986c32f7691ed4f53fa7ae3d8ed746e6327388e Mon Sep 17 00:00:00 2001 From: Brian Baligad Date: Sun, 13 Oct 2019 12:32:17 -0700 Subject: [PATCH 1/2] Optionally export Prometheus metrics --- README.md | 11 +- backend/stakepoold/config.go | 10 ++ backend/stakepoold/server.go | 4 + backend/stakepoold/stakepool/stakepool.go | 95 +++++++++++++++ docs/img/prometheus-email-alert.png | Bin 0 -> 10980 bytes docs/img/prometheus-first-run.png | Bin 0 -> 53985 bytes docs/prometheus-examples.md | 44 +++++++ docs/prometheus-quickstart.md | 139 ++++++++++++++++++++++ docs/samples/alerting.rules.yml | 28 +++++ docs/samples/alertmanager.service | 24 ++++ docs/samples/alertmanager.yml | 29 +++++ docs/samples/blackbox.yml | 8 ++ docs/samples/blackbox_exporter.service | 17 +++ docs/samples/prometheus.service | 21 ++++ docs/samples/prometheus.yml | 35 ++++++ go.mod | 1 + sample-stakepoold.conf | 8 ++ 17 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 docs/img/prometheus-email-alert.png create mode 100644 docs/img/prometheus-first-run.png create mode 100644 docs/prometheus-examples.md create mode 100644 docs/prometheus-quickstart.md create mode 100644 docs/samples/alerting.rules.yml create mode 100644 docs/samples/alertmanager.service create mode 100644 docs/samples/alertmanager.yml create mode 100644 docs/samples/blackbox.yml create mode 100644 docs/samples/blackbox_exporter.service create mode 100644 docs/samples/prometheus.service create mode 100644 docs/samples/prometheus.yml diff --git a/README.md b/README.md index f2f0f155..88f42ef5 100644 --- a/README.md +++ b/README.md @@ -298,21 +298,24 @@ running with `txindex=1`): dcrctl --wallet stakepooluserinfo "MultiSigAddress" | grep -Pzo '(?<="invalid": \[)[^\]]*' | tr -d , | xargs -Itickethash dcrctl --wallet getrawtransaction tickethash | xargs -Itickethex dcrctl --wallet addticket "tickethex" ``` -## Backups, monitoring, security considerations +## Backups, security considerations - MySQL should be backed up often and regularly (probably at least hourly). Backups should be transferred off-site. If using binary backups, do a test restore. For .sql files, verify visually. +- Wallets should never be used for anything else (they should always have a + balance of 0). + +## Monitoring + - A monitoring system with alerting should be pointed at dcrstakepool and tested/verified to be operating properly. There is a hidden /status page which throws 500 if the RPC client is shutdown. If your monitoring system supports it, add additional points of verification such as: checking that the /stats page loads and has expected information in it, create a test account and setup automated login testing, etc. - -- Wallets should never be used for anything else (they should always have a - balance of 0). +- [docs/prometheus-quickstart.md](docs/prometheus-quickstart.md) and [docs/prometheus-examples.md](docs/prometheus-examples.md) describe how to roll your own monitoring system that can detect and notify you about problems. ## Disaster Recovery diff --git a/backend/stakepoold/config.go b/backend/stakepoold/config.go index 2ca889c2..4275a591 100644 --- a/backend/stakepoold/config.go +++ b/backend/stakepoold/config.go @@ -13,6 +13,7 @@ import ( "runtime" "sort" "strings" + "time" "github.com/decred/dcrd/dcrutil/v2" "github.com/decred/dcrstakepool/internal/version" @@ -39,6 +40,9 @@ var ( defaultDBName = "stakepool" defaultDBPort = "3306" defaultDBUser = "stakepool" + + defaultPrometheusWait = time.Minute + defaultPrometheusListen = ":9101" ) // runServiceCommand is only set to a real function on Windows. It is used @@ -76,6 +80,9 @@ type config struct { RPCListeners []string `long:"rpclisten" description:"Add an interface/port to listen for RPC connections (default port: 9113, testnet: 19113)"` RPCCert string `long:"rpccert" description:"File containing the certificate file"` RPCKey string `long:"rpckey" description:"File containing the certificate key"` + Prometheus bool `long:"prometheus" description:"Export metrics for external monitoring. Disabled by default."` + PrometheusWait time.Duration `long:"prometheuswait" description:"How long to wait between metric updates. Valid time units are {s, m, h}."` + PrometheusListen string `long:"prometheuslisten" description:"Address/port to listen for Promethues scrapes. (default port: 9101)"` } // serviceOptions defines the configuration options for the daemon as a service @@ -261,6 +268,9 @@ func loadConfig() (*config, []string, error) { PoolFees: defaultPoolFees, RPCKey: defaultRPCKeyFile, RPCCert: defaultRPCCertFile, + + PrometheusWait: defaultPrometheusWait, + PrometheusListen: defaultPrometheusListen, } // Service options which are only added on Windows. diff --git a/backend/stakepoold/server.go b/backend/stakepoold/server.go index e5942494..9683aab9 100644 --- a/backend/stakepoold/server.go +++ b/backend/stakepoold/server.go @@ -362,6 +362,10 @@ func runMain(ctx context.Context) error { }() } + if cfg.Prometheus { + spd.ExportPrometheusMetrics(ctx, wg, cfg.PrometheusWait, cfg.PrometheusListen) + } + // Wait for CTRL+C to signal goroutines to terminate wg.Wait() saveData(spd) diff --git a/backend/stakepoold/stakepool/stakepool.go b/backend/stakepoold/stakepool/stakepool.go index 92dafa87..323d35db 100644 --- a/backend/stakepoold/stakepool/stakepool.go +++ b/backend/stakepoold/stakepool/stakepool.go @@ -10,6 +10,8 @@ import ( "encoding/hex" "errors" "fmt" + "net" + "net/http" "strings" "sync" "time" @@ -24,6 +26,10 @@ import ( "github.com/decred/dcrd/wire" "github.com/decred/dcrstakepool/backend/stakepoold/userdata" "github.com/decred/dcrwallet/wallet/v3/txrules" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/prometheus/client_golang/prometheus/promhttp" ) var ( @@ -472,6 +478,95 @@ func (spd *Stakepoold) UpdateUserDataFromMySQL() error { return nil } +// Enable early detection of outages by periodically scraping dcrd and +// dcrwallet, and exporting the results to a monitoring system. +func (spd *Stakepoold) ExportPrometheusMetrics(ctx context.Context, wg *sync.WaitGroup, wait time.Duration, addr string) { + promConnectionCount := promauto.NewGauge(prometheus.GaugeOpts{ + Name: "dcr_peer_connections", + Help: "Returns a count of connections from peers", + }) + promTicketsLive := promauto.NewGauge(prometheus.GaugeOpts{ + Name: "dcr_tickets_live", + Help: "Returns a count of live tickets", + }) + promTicketsMissed := promauto.NewGauge(prometheus.GaugeOpts{ + Name: "dcr_tickets_missed", + Help: "Returns a count of missed tickets", + }) + promBlockHeight := promauto.NewGauge(prometheus.GaugeOpts{ + Name: "dcr_block_height", + Help: "Returns the latest block height", + }) + + // Initialize metrics on startup from dcrd and dcrwallet + dcrdConnectionCount, err := spd.NodeConnection.GetConnectionCount() + if err == nil { + promConnectionCount.Set(float64(dcrdConnectionCount)) + } + stakeInfo, err := spd.GetStakeInfo() + if err == nil { + promBlockHeight.Set(float64(stakeInfo.BlockHeight)) + promTicketsLive.Set(float64(stakeInfo.Live)) + promTicketsMissed.Set(float64(stakeInfo.Missed)) + } + + // Periodically update metrics in a thread + wg.Add(1) + go func() { + defer wg.Done() + for { + select { + case <-ctx.Done(): + return + case <-time.After(wait): + dcrdConnectionCount, err := spd.NodeConnection.GetConnectionCount() + if err != nil { + log.Debugf("ExportPrometheusMetrics: unable to retreive metrics: %v", err) + continue + } + promConnectionCount.Set(float64(dcrdConnectionCount)) + stakeInfo, err := spd.GetStakeInfo() + if err != nil { + log.Debugf("ExportPrometheusMetrics: unable to retreive metrics: %v", err) + continue + } + promBlockHeight.Set(float64(stakeInfo.BlockHeight)) + promTicketsLive.Set(float64(stakeInfo.Live)) + promTicketsMissed.Set(float64(stakeInfo.Missed)) + } + } + }() + + // Set up http server to provide metrics to a scraping Prometheus server. + srv := &http.Server{} + http.Handle("/metrics", promhttp.Handler()) + listener, err := net.Listen("tcp", addr) + if err != nil { + log.Errorf("Error parsing PrometheusListen: %s", err.Error()) + return + } + + // Cleanly shutdown http server on interrupt signal. + wg.Add(1) + go func() { + defer wg.Done() + // Wait for shutdown. + <-ctx.Done() + + // We received an interrupt signal, shut down. + if err := srv.Shutdown(context.Background()); err != nil { + // Error from closing listeners, or context timeout: + log.Errorf("HTTP server Shutdown: %v", err) + } + }() + + // Start the http server. + log.Infof("Exporting metrics on %v", listener.Addr()) + if err = srv.Serve(listener); err != http.ErrServerClosed { + log.Errorf("Error exporting metrics: %s", err.Error()) + } +} + // vote Generates a vote and send it off to the network. This is a go routine! func (spd *Stakepoold) vote(wg *sync.WaitGroup, blockHash *chainhash.Hash, blockHeight int64, w *ticketMetadata) { start := time.Now() diff --git a/docs/img/prometheus-email-alert.png b/docs/img/prometheus-email-alert.png new file mode 100644 index 0000000000000000000000000000000000000000..8425a0c3719ed54ba22c783a6fdcc03a44e1302c GIT binary patch literal 10980 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 literal 0 HcmV?d00001 diff --git a/docs/img/prometheus-first-run.png b/docs/img/prometheus-first-run.png new file mode 100644 index 0000000000000000000000000000000000000000..3403f5f1593557e37cf6fdd34bae42b52138e468 GIT binary patch literal 53985 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 n4nZd|BR5iBD^if>8; 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 P=#qEfC7WTj^KYNB?ZI==?Ch#rwsp?WykS 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 F{x3D46;uEK literal 0 HcmV?d00001 diff --git a/docs/prometheus-examples.md b/docs/prometheus-examples.md new file mode 100644 index 00000000..22d315e2 --- /dev/null +++ b/docs/prometheus-examples.md @@ -0,0 +1,44 @@ +## Prometheus examples +A basic Prometheus setup is described in [prometheus-quickstart.md](prometheus-quickstart.md). This document provides examples for how a permanent Prometheus setup can be deployed. It is assumed you have some familiarity with Linux and Systemd. As these are all examples, they obviously need not be followed verbatim. Samples are provided as-is with minimum necessary descriptions. You should refer to [official documentation](https://prometheus.io/docs/prometheus/latest/configuration/configuration/) to fully understand them. Please find the following samples in the `docs/samples` directory: + - alerting.rules.yml + - alertmanager.service + - alertmanager.yml + - blackbox_exporter.service + - blackbox.yml + - prometheus.service + - prometheus.yml + +### Voting node health +The `alerting.rules.yml` example can proactively detect potentially transient failures: + - The node appears online, but `dcrd` has stalled (BlockHeightStalled) + - Block height is correct but `dcrwallet` doesn't agree with the other voting nodes about the state of the ticket pool (LiveTicketParity) + +On my testnet vsp, which uses extremely cheap virtual servers, the first alert frequently catches problems early. The second scenario happens often, but resolves itself within a few minutes. So that alert is configured to wait for a much longer duration before firing. Definitely consider tuning how long an alert waits (eg. start with `for: 5m` and increase until false positives stop) before it fires. When the duration is tuned appropriately, these alerts can inform you of a problem before `dcrstakepool` itself collapses. At a minimum, when you find your `dcrstakepool` instance offline, you can pull up the Prometheus `/alerts` page and drill down to see which specific nodes aren't healthy. + +### Systemd service units +These [service units](https://www.freedesktop.org/wiki/Software/systemd/) assume you've set up Prometheus under various `/opt` subdirectories, with binaries stored in `/opt/bin`. They use an unprivileged user account to run the daemon. For your convenience, `ExecReload` has been configured so that you can reload configuration on the fly, e.g. `systemctl reload prometheus`. Assuming you went through the quickstart already, here is how you could set up the `prometheus` daemon : +```bash + sudo mkdir -p /opt/bin + sudo mkdir -p /opt/prometheus/data + sudo cp -r prometheus-2.*.linux-amd64/{consoles,console_libraries,*.yml} /opt/prometheus/ + sudo cp prometheus-2.*.linux-amd64/{prometheus,promtool} /opt/bin/ + sudo useradd -Us /usr/bin/nologin -Md /opt/prometheus -c "Prometheus daemon" prometheus + sudo chown prometheus:prometheus /opt/prometheus/data + sudo cp prometheus.service /etc/systemd/system + sudo systemctl daemon-reload + sudo systemctl enable prometheus + sudo systemctl start prometheus +``` + +### Mobile push notifications +The provided `alertmanager.yml` example sends alerts to the api of a service called [pushover.net](https://pushover.net/). For one time fee, you can use their api to send push notifications directly to your Android or iOS mobile device. Alertmanager supports numerous notification methods, including a generic webhook api, PagerDuty (a significantly more expensive service), OpsGenie (free for up to 5 users), and more. + +### Blackbox Exporter +[Blackbox exporter](https://github.com/prometheus/blackbox_exporter/) is a Prometheus daemon you can deploy to monitor http endpoints like `dcrstakepool` itself. See the relevant job named "health-checks" in `prometheus.yml`. The service unit assumes configuration is in a directory `/opt/prometheus` owned by the `prometheus` user like in the example above. + +### Observability with Grafana +Once you've set up alerts, you might find it valuable to visualize your metrics. [Grafana](https://grafana.com/docs/) supports Prometheus as a datasource out of the box, and can be set up by simply adding a third party repository. + +### Node Exporter +To monitor basic system statistics like CPU usage, RAM, disk IO, etc, there is [Node Exporter](https://github.com/prometheus/node_exporter). It's another daemon you can run on your nodes to export system-level metrics. There's an [example dashboard](https://grafana.com/grafana/dashboards/1860) that visualizes all of the numerous collected metrics. + diff --git a/docs/prometheus-quickstart.md b/docs/prometheus-quickstart.md new file mode 100644 index 00000000..df1f249f --- /dev/null +++ b/docs/prometheus-quickstart.md @@ -0,0 +1,139 @@ +## Prometheus Quickstart +A voting service provider (VSP) facilitates user participation blockchain governance, so [reliability](https://landing.google.com/sre/sre-book/chapters/introduction/) is paramount. The purpose of this guide is to briefly demonstrate how a VSP operator can use [Prometheus](https://prometheus.io/) to monitor `stakepoold` node health. Prometheus provides a time series database with a query language, along with an `alertmanager` that aggregates and deduplicates alerts. Prometheus is multi-platform and supports various notification methods. The follow-up article [prometheus-examples.md](prometheus-examples.md) provides samples for detecting transient voting wallet errors, sending alerts by mobile push notifications, and setting up system services for a permanent deployment. Prometheus metrics are widely supported, so it is also possible to consume `stakepoold` metrics using other software such as Sensu+Influxdb. + +This guide uses Linux (Debian Stretch, not that it matters too much). If you aren't comfortable using tmux or screen, multiple concurrent ssh sessions will be necessary. This is written for VSP operators, so it is assumed the reader has basic sysadmin skills like creating and editing plain text configuration files. In the event that the instructions here fail you for any reason (sorry), you are advised to skip to the Further Reading section below and the official documentation. Prometheus is an open source project, so it changes quite rapidly and this guide may not always be up to date. + +Let’s start. + +You can export some relevant metrics from stakepoold by using the `--prometheus` flag or just by adding `prometheus=1` in your `stakepoold.conf`. You should be able to see stakepoold listening on a new port (default: 9101) e.g. `netstat -tnp | grep stakepoold`. When enabled, you should be able to connect to and see metrics for your local node: + +``` +$ curl http://localhost:9101 +# HELP dcr_block_height Returns the latest block height +# TYPE dcr_block_height gauge +dcr_block_height 268862 +# HELP dcr_peer_connections Returns a count of connections from peers +# TYPE dcr_peer_connections gauge +dcr_peer_connections 23 +# HELP dcr_tickets_live Returns a count of live tickets +# TYPE dcr_tickets_live gauge +dcr_tickets_live 42 +# HELP dcr_tickets_missed Returns a count of missed tickets +# TYPE dcr_tickets_missed gauge +dcr_tickets_missed 4 +``` +Ensure that your iptables policies don’t allow public access to your metrics. You are encouraged to set up a TLS reverse proxy with HTTP authentication to lock it down even more. Finally, there are two other related configuration parameters: + - `prometheuslisten` - The default is port 9101, but feel free to change it. + - `prometheuswait` - The default is one minute. Every metric update requires rpc calls to dcrd and dcrwallet, so avoid setting this to a low threshold, and never set it lower than your Prometheus `scrape_interval`. + +### Prometheus +Now let’s move on to your monitoring server. Ideally this new server is not in the same network or data center as your VSP nodes, but for the sake of the quickstart it’s okay if it isn’t. See the [Prometheus download page](https://prometheus.io/download/) for links to the latest official build. +```bash +$ cd ~ +$ wget https://github.com/prometheus/prometheus/releases/download/v2.13.1/prometheus-2.13.1.linux-amd64.tar.gz +$ tar xvfz prometheus-*.tar.gz +$ cd prometheus-2.13.1.linux-amd64/ +$ ./promtool check config prometheus.yml +$ ./prometheus --config.file=prometheus.yml +``` +The daemon should start up with a message “Server is ready to receive web requests.” and listen on port 9090. Note that the `promtool` command is how you confirm that the configuration is valid. You should always run this after you make configuration changes. Now try connecting to your monitoring machine on port 9090 in your web browser. The default configuration in `prometheus.yml` will simply have Prometheus monitor itself. In the text input box, try typing “up” and pressing execute. You should see a result like this: + +![Prometheus with default configuration](img/prometheus-first-run.png) + +Here you should see that Prometheus is confirming that Prometheus is up. Cool. Go ahead and stop prometheus (Ctrl-C). Let’s make our own `myprometheus.yml` and configure it to monitor our `stakepoold` nodes. Here’s an example: +```yaml +scrape_configs: + - job_name: 'stakepoold' + scrape_interval: 30s + static_configs: + - targets: ['node1.example.com:9101', 'node2.example.com:9101', 'node3.example.com:9101'] +``` +Don’t forget to update each of your stakepoold node iptables with an allow policy for the monitoring server: +```bash +iptables -I INPUT -s -m tcp -p tcp --dport 9101 -j ACCEPT +iptables -I INPUT -m tcp -p tcp --dport 9101 -j REJECT +``` +You should be able to curl any of your targets from the machine running Prometheus. Finally, rerun `./promtool check config myprometheus.yml` to confirm your configuration is valid and run the daemon again `./prometheus --config.file=prometheus.yml`. Connect to Prometheus again on port 9090 in your web browser (or just refresh). Navigate to Status > Targets. You should see your stakepoold nodes there. If Prometheus wasn’t able to connect for some reason, it will display a reason in the error column. + +If all your targets are online, congratulations! Try navigating back to the Status > Graph page. Type in the text input box “dcr_block_height” and press execute. The information there should match your nodes' block height. The autocomplete feature will also suggest the other relevant dcr metrics that you can measure. Prometheus is now observing your stakepoold nodes. + +If you navigate to the `/alerts` you should see a message that says "No alerting rules defined." Let's set up a simple alert for when it notices a problem. Add the following to the top of your `myprometheus.yml` configuration: +```yml +rule_files: + - alerting.rules.yml +``` +Now let's create a new rule file `alerting.rules.yml`: +```yml +groups: + - name: stakepoold + rules: + - alert: InstanceDown + expr: up{job="stakepoold"} == 0 + for: 10m + annotations: + summary: Node unreachable +``` +Check your configuration with `promtool` as before and then restart `prometheus`. You should now see an "InstanceDown" alert on your Prometheus `/alerts` page. You can drill down into the alert and click the expression to show the results (which should be zero if all your nodes are online). See the [official docs](https://prometheus.io/docs/prometheus/latest/configuration/configuration/) for more details on configuring `prometheus`. + +#### Simulate an outage +You can simulate an outage on one of your nodes simply by restarting `stakepoold` without the `--prometheus` flag. The next time Prometheus scrapes for metrics (every minute by default), this alert will change from green to yellow. This is the "PENDING" status, where Prometheus has identified a failing monitor. After the period configured above with `for: 10m` this alert will change to a red "FIRING" status. This grace period is useful for weeding out false positives. Next, we will configure the `alertmanager` daemon to send out pages for active alerts. + +### Alertmanager +See the [Prometheus download page](https://prometheus.io/download/) for the latest version. +```bash +$ cd ~ +$ wget https://github.com/prometheus/alertmanager/releases/download/v0.19.0/alertmanager-0.19.0.linux-amd64.tar.gz +$ tar xvfz alertmanager-*.tar.gz +$ cd alertmanager-0.19.0.linux-amd64/ +$ ./amtool check-config alertmanager.yml +$ ./alertmanager --config.file=alertmanager.yml --cluster.advertise-address=127.0.0.1:9093 +``` +Note that the cluster parameter is a [workaround](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/3764) and may not be necessary in a future release. Alertmanager will start up listening on port 0.0.0.0:9093, so you should be able to see it in your web browser running the default configuration. Hopefully you already use a default deny iptables policy, otherwise you'll need to write another rule to restrict public access. Now let's write our own `myalertmanager.yml` configuration to email us the alerts: +```yml +route: + receiver: vsp-mail +receivers: + - name: vsp-mail + email_configs: + - to: support@example.com + from: noreply@example.com + smarthost: smtp.mailgun.org:587 + auth_username: postmaster@example.com + auth_password: muh_secure_password! + send_resolved: true + tls_config: + insecure_skip_verify: true +``` + +Use `amtool` again to confirm your configuration syntax is valid, then restart `alertmanager`. Setting up a mail server is outside the scope of this guide, but in the example I've used a free service called Mailgun. The `insecure_skip_verify` setting is a bad practice that should be avoided, but included here for the sake of a quick and dirty working example. See the [official docs](https://prometheus.io/docs/alerting/configuration/) for more details on configuring `alertmanager`. + +Finally, we'll need to configure `prometheus` to send alerts to our new `alertmanager` daemon. Add this to your `myprometheus.yml`, then verify with `promtool` and restart Prometheus. +```yml +alerting: + alertmanagers: + - static_configs: + - targets: ['127.0.0.1:9093'] +``` +As `prometheus` starts up, it will attempt to scrape targets. If you're still simulating an outage, it will fail to scrape and signal an alert after the default period. After a couple minutes, you should see the simulated alert in the alertmanager web interface on port 9093. That means it will try to email the alert, assuming the smtp settings are valid. + +![Example email alert from Prometheus alertmanager](img/prometheus-email-alert.png) + +Note: Make sure your builds are using Go versions 1.13.1 and 1.12.10, or later. cve-2019-16276 is relevant here as we're using `net/http`. + +Next: [prometheus-examples.md](prometheus-examples.md) + +### Further Reading + - Prometheus overview [1] - a highly informative one-page overview + - Prometheus media [2] - several introductory presentations + - Prometheus talks on YT [3] - in depth talks + - Brian Brazil's book [4] + - Brian Brazil's blog [5] + - StackOverflow [6] + + 1. https://prometheus.io/docs/introduction/overview/ + 2. https://prometheus.io/docs/introduction/media/ + 3. https://www.youtube.com/channel/UC4pLFely0-Odea4B2NL1nWA/videos + 4. https://www.amazon.com/dp/B07FCV2VVG + 5. https://www.robustperception.io/tag/prometheus + 6. https://stackoverflow.com/questions/tagged/prometheus + diff --git a/docs/samples/alerting.rules.yml b/docs/samples/alerting.rules.yml new file mode 100644 index 00000000..f916c36f --- /dev/null +++ b/docs/samples/alerting.rules.yml @@ -0,0 +1,28 @@ +groups: + - name: observer + rules: + + - alert: ProbeFailing + expr: probe_success{job="health-checks"} == 0 + for: 15m + annotations: + summary: The dcrstakepool health check failed, my require a restart + + - alert: InstanceDown + expr: up{job="stakepoold"} == 0 + for: 10m + annotations: + summary: Node is unreachable + + - alert: BlockHeightStalled + expr: dcr_block_height{job="stakepoold"} < on() group_left max(dcr_block_height{job="stakepoold"}) + for: 2h + annotations: + summary: Node is behind on best block height, may require a restart + + - alert: LiveTicketParity + expr: dcr_tickets_live{job="stakepoold"} > on() group_left min(dcr_tickets_live{job="stakepoold"}) + for: 2h + annotations: + summary: Live ticket count differs, wallet may require a rescan + diff --git a/docs/samples/alertmanager.service b/docs/samples/alertmanager.service new file mode 100644 index 00000000..f2bf1225 --- /dev/null +++ b/docs/samples/alertmanager.service @@ -0,0 +1,24 @@ +[Unit] +Description=Prometheus alertmanager daemon +After=network.target + +[Service] +# useradd -Us /usr/bin/nologin -Md /opt/prometheus -c "Prometheus daemon" prometheus +User=prometheus +Group=prometheus + +ExecStartPre=/opt/bin/amtool check-config /opt/alertmanager/alertmanager.yml + +# alertmanager fails to start if no private address is found +# fix: set cluster.advertise-address +# https://gitlab.com/gitlab-org/omnibus-gitlab/issues/3764 +ExecStart=/opt/bin/alertmanager \ + --config.file=/opt/alertmanager/alertmanager.yml \ + --storage.path=/opt/alertmanager/data \ + --web.listen-address=0.0.0.0:9093 \ + --cluster.advertise-address=127.0.0.1:9093 + +ExecReload=/bin/kill -HUP $MAINPID + +[Install] +WantedBy=multi-user.target diff --git a/docs/samples/alertmanager.yml b/docs/samples/alertmanager.yml new file mode 100644 index 00000000..b0c4dabd --- /dev/null +++ b/docs/samples/alertmanager.yml @@ -0,0 +1,29 @@ +# https://prometheus.io/docs/alerting/configuration/ +global: + resolve_timeout: 2m + +route: + group_by: [group] + group_wait: 90s + group_interval: 1m + repeat_interval: 6h + receiver: pushover-notify + match: + group: example + receiver: pushover-escalate + +receivers: + - name: pushover-notify + pushover_configs: + - user_key: + token: + sound: none + # pushover will vibrate notify once + priority: '{{ if eq .Status "firing" }}1{{ else }}0{{ end }}' + - name: pushover-escalate + pushover_configs: + - user_key: + token: + sound: none + # pushover will notify periodically until acknowleded + priority: '{{ if eq .Status "firing" }}2{{ else }}0{{ end }}' diff --git a/docs/samples/blackbox.yml b/docs/samples/blackbox.yml new file mode 100644 index 00000000..1b740939 --- /dev/null +++ b/docs/samples/blackbox.yml @@ -0,0 +1,8 @@ +modules: + http_2xx: + prober: http + timeout: 5s + http: + valid_status_codes: [] + method: GET + preferred_ip_protocol: ip4 diff --git a/docs/samples/blackbox_exporter.service b/docs/samples/blackbox_exporter.service new file mode 100644 index 00000000..221154cf --- /dev/null +++ b/docs/samples/blackbox_exporter.service @@ -0,0 +1,17 @@ +[Unit] +Description=Prometheus blackbox exporter +After=network.target + +[Service] +User=prometheus +Group=prometheus +ExecStartPre=/opt/bin/blackbox_exporter \ + --config.file=/opt/blackbox_exporter/blackbox.yml \ + --config.check +ExecStart=/opt/bin/blackbox_exporter \ + --config.file=/opt/blackbox_exporter/blackbox.yml +ExecReload=/bin/kill -HUP $MAINPID + + +[Install] +WantedBy=multi-user.target diff --git a/docs/samples/prometheus.service b/docs/samples/prometheus.service new file mode 100644 index 00000000..551414ab --- /dev/null +++ b/docs/samples/prometheus.service @@ -0,0 +1,21 @@ +[Unit] +Description=Prometheus monitoring daemon +After=network.target + +[Service] +# useradd -Us /usr/bin/nologin -Md /opt/prometheus -c "Prometheus daemon" prometheus +User=prometheus +Group=prometheus + +ExecStartPre=/opt/bin/promtool check config /opt/prometheus/prometheus.yml + +ExecStart=/opt/bin/prometheus \ + --config.file=/opt/prometheus/prometheus.yml \ + --storage.tsdb.path=/opt/prometheus/data \ + --web.console.templates=/opt/prometheus/consoles \ + --web.console.libraries=/opt/prometheus/console_libraries + +ExecReload=/bin/kill -HUP $MAINPID + +[Install] +WantedBy=multi-user.target diff --git a/docs/samples/prometheus.yml b/docs/samples/prometheus.yml new file mode 100644 index 00000000..a4face7a --- /dev/null +++ b/docs/samples/prometheus.yml @@ -0,0 +1,35 @@ +rule_files: + - alerting.rules.yml + +alerting: + alertmanagers: + - static_configs: + - targets: ['127.0.0.1:9093'] + +scrape_configs: + + - job_name: 'stakepoold' + scrape_interval: 30s + static_configs: + - targets: ['node1.example.com:9101', 'node2.example.com:9101', 'node3.example.com:9101'] + labels: + group: 'mainnet' + + - job_name: 'health-checks' + metrics_path: /probe + scrape_interval: 30s + params: + module: [http_2xx] + static_configs: + - targets: + - https://example.com/api/v2/stats + labels: + group: 'mainnet' + relabel_configs: + - source_labels: [__address__] + target_label: __param_target + - source_labels: [__param_target] + target_label: instance + - target_label: __address__ + replacement: 127.0.0.1:9115 + diff --git a/go.mod b/go.mod index 140c21e7..2f502a4d 100644 --- a/go.mod +++ b/go.mod @@ -31,6 +31,7 @@ require ( github.com/lib/pq v1.3.0 // indirect github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1 // indirect + github.com/prometheus/client_golang v1.2.1 github.com/stretchr/testify v1.4.0 // indirect github.com/zenazn/goji v0.9.0 github.com/ziutek/mymysql v1.5.4 // indirect diff --git a/sample-stakepoold.conf b/sample-stakepoold.conf index 99c99038..ba3ad5ea 100644 --- a/sample-stakepoold.conf +++ b/sample-stakepoold.conf @@ -45,3 +45,11 @@ walletcert=../.dcrwallet/rpc.cert ; log level for individual subsystems. Use stakepoold --debuglevel=show to list ; available subsystems. ; debuglevel=info + +; Whether to export metrics for external monitoring. Other values are ignored +; unless enabled. (Default: false) +;prometheus=0 +; Valid time units are {s, m, h}. Should match your Prometheus scrape interval. +;prometheuswait=1m +; Address:Port to listen for scrapes from Prometheus. +;prometheuslisten=0.0.0.0:9101 From a331114ba79d603eec6e4bc9235191085d374d85 Mon Sep 17 00:00:00 2001 From: Brian Baligad Date: Fri, 31 Jan 2020 07:57:39 -0800 Subject: [PATCH 2/2] rebase to master --- backend/stakepoold/config.go | 54 ++++++++++++++++---------------- go.sum | 60 ++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 27 deletions(-) diff --git a/backend/stakepoold/config.go b/backend/stakepoold/config.go index 4275a591..dcb9fcb8 100644 --- a/backend/stakepoold/config.go +++ b/backend/stakepoold/config.go @@ -53,33 +53,33 @@ var runServiceCommand func(string) error // // See loadConfig for details on the configuration load process. type config struct { - HomeDir string `short:"A" long:"appdata" description:"Path to application home directory"` - ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"` - ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"` - DataDir string `short:"b" long:"datadir" description:"Directory to store data"` - LogDir string `long:"logdir" description:"Directory to log output."` - TestNet bool `long:"testnet" description:"Use the test network"` - SimNet bool `long:"simnet" description:"Use the simulation test network"` - DebugLevel string `short:"d" long:"debuglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify =,=,... to set the log level for individual subsystems -- Use show to list available subsystems"` - ColdWalletExtPub string `long:"coldwalletextpub" description:"The extended public key for addresses to which voting service user fees are sent."` - PoolFees float64 `long:"poolfees" description:"The per-ticket fees the user must send to the voting service with their tickets"` - DBHost string `long:"dbhost" description:"Hostname for database connection"` - DBUser string `long:"dbuser" description:"Username for database connection"` - DBPassword string `long:"dbpassword" description:"Password for database connection"` - DBPort string `long:"dbport" description:"Port for database connection"` - DBName string `long:"dbname" description:"Name of database"` - DcrdHost string `long:"dcrdhost" description:"Hostname/IP for dcrd server"` - DcrdUser string `long:"dcrduser" description:"Username for dcrd server"` - DcrdPassword string `long:"dcrdpassword" description:"Password for dcrd server"` - DcrdCert string `long:"dcrdcert" description:"Certificate path for dcrd server"` - WalletHost string `long:"wallethost" description:"Hostname for wallet server"` - WalletUser string `long:"walletuser" description:"Username for wallet server"` - WalletPassword string `long:"walletpassword" description:"Password for wallet server"` - WalletCert string `long:"walletcert" description:"Certificate path for wallet server"` - NoRPCListen bool `long:"norpclisten" description:"Do not start a gRPC server. User voting preferences update on a ticker"` - RPCListeners []string `long:"rpclisten" description:"Add an interface/port to listen for RPC connections (default port: 9113, testnet: 19113)"` - RPCCert string `long:"rpccert" description:"File containing the certificate file"` - RPCKey string `long:"rpckey" description:"File containing the certificate key"` + HomeDir string `short:"A" long:"appdata" description:"Path to application home directory"` + ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"` + ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"` + DataDir string `short:"b" long:"datadir" description:"Directory to store data"` + LogDir string `long:"logdir" description:"Directory to log output."` + TestNet bool `long:"testnet" description:"Use the test network"` + SimNet bool `long:"simnet" description:"Use the simulation test network"` + DebugLevel string `short:"d" long:"debuglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify =,=,... to set the log level for individual subsystems -- Use show to list available subsystems"` + ColdWalletExtPub string `long:"coldwalletextpub" description:"The extended public key for addresses to which voting service user fees are sent."` + PoolFees float64 `long:"poolfees" description:"The per-ticket fees the user must send to the voting service with their tickets"` + DBHost string `long:"dbhost" description:"Hostname for database connection"` + DBUser string `long:"dbuser" description:"Username for database connection"` + DBPassword string `long:"dbpassword" description:"Password for database connection"` + DBPort string `long:"dbport" description:"Port for database connection"` + DBName string `long:"dbname" description:"Name of database"` + DcrdHost string `long:"dcrdhost" description:"Hostname/IP for dcrd server"` + DcrdUser string `long:"dcrduser" description:"Username for dcrd server"` + DcrdPassword string `long:"dcrdpassword" description:"Password for dcrd server"` + DcrdCert string `long:"dcrdcert" description:"Certificate path for dcrd server"` + WalletHost string `long:"wallethost" description:"Hostname for wallet server"` + WalletUser string `long:"walletuser" description:"Username for wallet server"` + WalletPassword string `long:"walletpassword" description:"Password for wallet server"` + WalletCert string `long:"walletcert" description:"Certificate path for wallet server"` + NoRPCListen bool `long:"norpclisten" description:"Do not start a gRPC server. User voting preferences update on a ticker"` + RPCListeners []string `long:"rpclisten" description:"Add an interface/port to listen for RPC connections (default port: 9113, testnet: 19113)"` + RPCCert string `long:"rpccert" description:"File containing the certificate file"` + RPCKey string `long:"rpckey" description:"File containing the certificate key"` Prometheus bool `long:"prometheus" description:"Export metrics for external monitoring. Disabled by default."` PrometheusWait time.Duration `long:"prometheuswait" description:"How long to wait between metric updates. Valid time units are {s, m, h}."` PrometheusListen string `long:"prometheuslisten" description:"Address/port to listen for Promethues scrapes. (default port: 9101)"` diff --git a/go.sum b/go.sum index 0ee81b20..6129f469 100644 --- a/go.sum +++ b/go.sum @@ -5,11 +5,21 @@ github.com/DATA-DOG/go-sqlmock v1.4.0 h1:yxQ63CFIA8Sxkh0vqIofuNrsXl/LZ42TpeTLV4N github.com/DATA-DOG/go-sqlmock v1.4.0/go.mod h1:3TucWNLPFOLcHhha1CPp7Kis1UG2h/AqGROPyOeZzsM= github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI= github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/btcsuite/goleveldb v1.0.0 h1:Tvd0BfvqX9o823q1j2UZ/epQo09eJh6dTcRp79ilIN4= github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= github.com/btcsuite/snappy-go v1.0.0 h1:ZxaA6lo2EpxGddsA8JwWOcxlzRybb444sgmeJQMJGQE= github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA= +github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/dajohi/goemail v1.0.1 h1:6vIRgcmNliBfspzBb5esYADkwJyedUUKkdx8Lq2wH2w= github.com/dajohi/goemail v1.0.1/go.mod h1:ydte3Emz5JRZugNPGivvqAvPSXPCSWPElMXu0eirukA= @@ -130,18 +140,28 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-gorp/gorp v2.2.0+incompatible h1:xAUh4QgEeqPPhK3vxZN+bzrim1z5Av6q837gtjUlshc= github.com/go-gorp/gorp v2.2.0+incompatible/go.mod h1:7IfkAQnO7jfT/9IQ3R9wL1dFhukN6aQxzKTHnkxzA/E= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gorilla/csrf v1.6.2 h1:QqQ/OWwuFp4jMKgBFAzJVW3FMULdyUW7JoM4pEWuqKg= github.com/gorilla/csrf v1.6.2/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= @@ -159,6 +179,11 @@ github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/jrick/wsrpc/v2 v2.0.0/go.mod h1:naH/fojac6vQWYgAA0e7b9TX/bShsWoVL7CwrdvFmUk= github.com/jrick/wsrpc/v2 v2.2.0/go.mod h1:naH/fojac6vQWYgAA0e7b9TX/bShsWoVL7CwrdvFmUk= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -168,6 +193,13 @@ github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= @@ -178,12 +210,33 @@ github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1 h1:oL4IBbcqwhhNWh31bjOX8C/OCy0zs9906d/VUru+bqg= github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI= +github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8= +github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/zenazn/goji v0.9.0 h1:RSQQAbXGArQ0dIDEq+PI6WqN6if+5KHu6x2Cx/GXLTQ= @@ -192,6 +245,7 @@ github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -205,6 +259,7 @@ golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -215,12 +270,16 @@ golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190614084037-d442b75600c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= @@ -247,6 +306,7 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=