From c291e07acaade68cacfc0fa69fe9f846901df644 Mon Sep 17 00:00:00 2001 From: Dima Kniazev <98769322+redima@users.noreply.github.com> Date: Thu, 17 Aug 2023 12:31:43 -0500 Subject: [PATCH 1/2] RDSC-876: builtin-rdi-monitor (#2745) * RDI built-in monitor and Prometheus exporter * updated based on comments * Update monitoring-guide.md --- content/rdi/monitoring-guide.md | 88 ++++++------------ static/images/rdi/monitoring-architecture.png | Bin 269522 -> 0 bytes static/images/rdi/monitoring-diagram.png | Bin 0 -> 155361 bytes 3 files changed, 27 insertions(+), 61 deletions(-) delete mode 100644 static/images/rdi/monitoring-architecture.png create mode 100644 static/images/rdi/monitoring-diagram.png diff --git a/content/rdi/monitoring-guide.md b/content/rdi/monitoring-guide.md index 6aaae301a03..b9801c11fc2 100644 --- a/content/rdi/monitoring-guide.md +++ b/content/rdi/monitoring-guide.md @@ -5,56 +5,43 @@ description: Monitor RDI Engine and data processing jobs weight: 70 alwaysopen: false categories: ["redis-di"] -aliases: +aliases: --- -RDI Engine accumulates operating statistics that you can: +RDI Engine accumulates operating statistics that you can: -* Observe and analyze to discover various types of problems. -* Use for optimization purposes. +- Observe and analyze to discover various types of problems. +- Use for optimization purposes. ## Console metrics -Some basic RDI operating metrics can be displayed using the [`redis-di status`]({{}}) command. The command provides information about the current RDI Engine status, target database configuration and processing statistics broken down by stream. This tool is intended to be used by an Operator to get the current snapshot of the system. +RDI can display its operating metrics in the console using the [`redis-di status`]({{}}) command. The command provides information about the current RDI Engine status, target database configuration and processing statistics broken down by stream. This tool is intended to be used by an Operator to get the current snapshot of the system as well as the ongoing data processing monitoring (when used in live mode). ## Prometheus integration -RDI allows exporting its metrics to [Prometheus](https://prometheus.io/) and visualizing them in [Grafana](https://grafana.com/). Currently, RDI relies on the external [OSS Redis Exporter](https://github.com/oliver006/redis_exporter) that connects to RDI database to source the metrics and serve them for Prometheus job scraping. The diagram below describes this flow and components involved. +RDI allows collecting and exporting its metrics to [Prometheus](https://prometheus.io/) and visualizing them in [Grafana](https://grafana.com/). Operator can start the built-in exporter using the [`redis-di monitor`]({{}}) command. The diagram describes this flow and components involved: -![Metrics architecture](/images/rdi/monitoring-architecture.png) +![Metrics architecture](/images/rdi/monitoring-diagram.png) > Note: The host names and ports above are examples only and can be changed as needed. -## Install and configure the Exporter +### Test RDI metrics exporter -OSS Metrics Exporter is available as a [pre-built docker container](https://hub.docker.com/r/oliver006/redis_exporter) so that you integrate it into container-based or Kubernetes environments. Alternatively, you can be [build and install](https://hub.docker.com/r/oliver006/redis_exporter) it as a binary to any compute node that has access to an RDI database that needs to be monitored. +Start the RDI metrics exporter using the command below: -To connect OSS Metrics Exporter to the RDI database, provide the following information by using the command-line options or environment variables: - -| Variable Name | Description | Example | -| --------------------- | ------------------------------------------------------------------------------------------------ | ------------------------ | -| REDIS_ADDR | RDI database host/port | redis://localhost:12001 | -| REDIS_USER | RDI database user (optional, if `default` is used) | RedisUser | -| REDIS_PASSWORD | RDI database password | Redis123 | -| REDIS_EXPORTER_SCRIPT | Lua script that triggers the Metrics Collector [(see below)](#lua-script-for-metrics-collection) | /scripts/rdi_metrics.lua | - -### Lua script for metrics collection - -Create the following [Lua script](https://redis.io/docs/manual/programmability/eval-intro/) and make it available for the OSS Metrics Exporter by using the `REDIS_EXPORTER_SCRIPT` environment variable: - -```lua -return (redis.call('RG.TRIGGER', 'GetMetrics', '*'))[1] +```bash +redis-di monitor ``` -### Test OSS Metrics Exporter +> Note: The default port for the exporter is `9121`. If you need to change it, use the `--exporter-port` option. The default metrics collection interval is 5 seconds. If you need to change it, use the `--collect-interval` option. -Start the OSS Metrics Exporter and navigate to `http://localhost:9121/metrics` to see the exported metrics. You should be able to see the following metric: +Then navigate to `http://localhost:9121/` to see the exported metrics. You should be able to see the following metric: ``` -redis_script_values{key="rdi_engine_status{status=RUNNING}"} 1 +rdi_engine_state{state="RUNNING",sync_mode="UNKNOWN"} 1.0 ``` -> Note: The actual value of the metric above can be 0, if you haven't started RDI Engine yet. You must have the RDI database created and configured before observing any metrics. If you are not seeing it or getting an error value instead, this indicates that either the OSS Metrics Exporter or the RDI database is not properly configured. +> Note: The actual value of the metric above can be 0, if you haven't started RDI Engine yet (in which case, the `state` label should indicate that as well). You must have the RDI database created and configured before observing any metrics. If you are not seeing it or getting an error value instead, this indicates that the RDI database is not properly configured. ## Configure Prometheus @@ -62,46 +49,25 @@ Next, configure the Prometheus scraper. Edit the `prometheus.yml` file to add th ```yaml scrape_configs: - # scrape OSS Metrics Exporter - - job_name: redis-exporter + # scrape RDI metrics exporter + - job_name: rdi-exporter static_configs: - targets: ["redis-exporter:9121"] - metric_relabel_configs: - - source_labels: [key] - regex: ".+operation=([^,}]+).+" - target_label: "operation" - replacement: "${1}" - - source_labels: [key] - regex: ".+data_source=([^,]+).+" - target_label: "data_source" - replacement: "${1}" - - source_labels: [key] - regex: ".+status=([^,]+).+" - target_label: "status" - replacement: "${1}" - - source_labels: [key] - regex: "([^{]+){.+" - target_label: "metric_name" - replacement: "${1}" - - source_labels: [__name__, metric_name] - regex: "redis_script_values;(.*)" - target_label: __name__ - replacement: "${1}" - - action: labeldrop - regex: "metric_name|key" ``` -> Note: Make sure the `targets` value above points to the host and port you configured to run the OSS Metrics Exporter. +> Note: Make sure the `targets` value above points to the host and port you configured to run the RDI metrics exporter. +> Note: The `scrape_interval` setting in Prometheus should be the same or more than the `collect_interval` setting for the exporter. For example, if the `collect_interval` is set to 5 seconds, the `scrape_interval` should also be set to 5 seconds or more. If the `scrape_interval` is set to less than the `collect_interval`, Prometheus will scrape the exporter before it has a chance to collect and refresh metrics, and you will see the same values duplicated in Prometheus. + +## Test Prometheus scraper -### Test Prometheus scraper +After the scraper config is added to the Prometheus configuration, you should now be able to navigate to `http://:9090/graph` (replace `` with a valid Prometheus hostname or IP address). -After the scraper config is added to the Prometheus configuration, you should now be able to navigate to `http://:9090/graph` (replace `` with a valid Prometheus hostname or IP address). -Explore RDI metrics using the [expression browser](https://prometheus.io/docs/visualization/browser/). +Explore RDI metrics using the [expression browser](https://prometheus.io/docs/visualization/browser/). -In the expression box, type in a metric name (for example, `rdi_engine_status`) and select `Enter` or the `Execute` button to see the following result: +In the expression box, type in a metric name (for example, `rdi_engine_state`) and select `Enter` or the `Execute` button to see the following result: ``` -rdi_engine_status{instance="redis-exporter:9121", job="redis-exporter", status="RUNNING"} 1 +rdi_engine_state{instance="redis-exporter:9121", job="rdi-exporter", status="RUNNING", sync_mode="UNKNOWN"} 1 ``` > Note: You may see more than just one RDI metric, if RDI Engine has already processed any data. If you do not see any metrics please check your scraper job configuration in Prometheus. @@ -116,7 +82,7 @@ Optionally, you may deploy the sample Grafana dashboard to monitor the status of ![New dashboard creation](/images/rdi/monitoring-grafana-new-dash.png.png) -1. On the next screen, select **Upload JSON file** and upload the file you downloaded in step 1. Make sure you select the data source that is connected to the OSS Metrics Exporter: +1. On the next screen, select **Upload JSON file** and upload the file you downloaded in step 1. Make sure you select the data source that is connected to the RDI metrics exporter: ![Data source connection](/images/rdi/monitoring-grafana-dash-configure.png) @@ -130,6 +96,6 @@ This list shows exported RDI metrics along with their descriptions: | Metric Name | Labels | Values | Description | | --------------------------- | ---------------------------------------------------------------------------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| rdi_engine_status | {status=RUNNING \| STOPPED} | 0, 1 | Status of RDI Engine. 0 - RDI Engine is stopped, 1 - RDI Engine is running. | +| rdi_engine_state | {status=RUNNING \| STOPPED, sync_mode=SNAPSHOT \| STREAMING \| UNKNOWN} | 0, 1 | Status of RDI Engine. 0 - RDI Engine is stopped, 1 - RDI Engine is running. Sync mode label indicates the last reported ingest synchronization mode. | | rdi_incoming_entries | {data_source=``, operation=pending \| inserted \| updated \| deleted \| filtered \| rejected} | `` | Counters, indicating the number of operations performed for each stream. | | rdi_stream_event_latency_ms | {data_source=``} | 0 - ∞ | Latency calculated for each stream. Indicates the time in milliseconds the first available record has spent in the stream waiting to be processed by RDI Engine. If no records pending it will always return zero. | diff --git a/static/images/rdi/monitoring-architecture.png b/static/images/rdi/monitoring-architecture.png deleted file mode 100644 index 54048d25f0f83b63a77d9539a52cd3ecdedb0382..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 269522 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