diff --git a/Cargo.lock b/Cargo.lock index 2eddd276e..d565d4708 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2048,6 +2048,7 @@ dependencies = [ "indexmap", "itertools", "jsonwebtoken", + "lazy_static", "log", "manifest-dir-macros", "maplit", @@ -2061,6 +2062,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "platform-dirs", + "prometheus", "rand", "rayon", "regex", @@ -2667,6 +2669,36 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "procfs" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0941606b9934e2d98a3677759a971756eb821f75764d0e0d26946d08e74d9104" +dependencies = [ + "bitflags", + "byteorder", + "hex", + "lazy_static", + "libc", +] + +[[package]] +name = "prometheus" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cface98dfa6d645ea4c789839f176e4b072265d085bfcc48eaa8d137f58d3c39" +dependencies = [ + "cfg-if 1.0.0", + "fnv", + "lazy_static", + "libc", + "memchr", + "parking_lot", + "procfs", + "protobuf", + "thiserror", +] + [[package]] name = "proptest" version = "1.0.0" @@ -2698,6 +2730,12 @@ dependencies = [ "syn 0.15.44", ] +[[package]] +name = "protobuf" +version = "2.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf7e6d18738ecd0902d30d1ad232c9125985a3422929b16c65517b38adc14f96" + [[package]] name = "quick-error" version = "1.2.3" diff --git a/grafana-dashboards/dashboard.json b/grafana-dashboards/dashboard.json new file mode 100644 index 000000000..fe64e2966 --- /dev/null +++ b/grafana-dashboards/dashboard.json @@ -0,0 +1,1007 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 1, + "links": [], + "liveNow": false, + "panels": [ + { + "collapsed": false, + "datasource": { + "type": "prometheus" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 14, + "panels": [], + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "i51CxikVz" + }, + "refId": "A" + } + ], + "title": "Web application metrics", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "1MRsknzVz" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-YlBl" + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 0, + "y": 1 + }, + "id": 2, + "interval": "5s", + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "text": {} + }, + "pluginVersion": "9.0.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "1MRsknzVz" + }, + "editorMode": "builder", + "exemplar": true, + "expr": "meilisearch_database_size{job=\"meilisearch\", instance=\"$instance\"}", + "interval": "", + "legendFormat": "", + "range": true, + "refId": "A" + } + ], + "title": "Database Size", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "1MRsknzVz" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "purple", + "mode": "fixed" + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 4, + "y": 1 + }, + "id": 22, + "interval": "5s", + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "text": {} + }, + "pluginVersion": "9.0.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "1MRsknzVz" + }, + "editorMode": "builder", + "exemplar": true, + "expr": "meilisearch_total_index{job=\"meilisearch\", instance=\"$instance\"}", + "interval": "", + "legendFormat": "", + "range": true, + "refId": "A" + } + ], + "title": "Indexes Count", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "1MRsknzVz" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 8, + "y": 1 + }, + "id": 18, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "text": {} + }, + "pluginVersion": "9.0.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "1MRsknzVz" + }, + "editorMode": "builder", + "expr": "meilisearch_docs_count{job=\"meilisearch\", index=\"$Index\", instance=\"$instance\"}", + "hide": false, + "range": true, + "refId": "A" + } + ], + "title": "Total Documents", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "1MRsknzVz" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 12, + "y": 1 + }, + "id": 19, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "text": {} + }, + "pluginVersion": "9.0.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "1MRsknzVz" + }, + "editorMode": "builder", + "exemplar": true, + "expr": "round(increase(http_requests_total{method=\"POST\", path=\"/indexes/$Index/search\", job=\"meilisearch\"}[1h]))", + "interval": "", + "legendFormat": "", + "range": true, + "refId": "A" + } + ], + "title": "Total Searches (1h)", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "1MRsknzVz" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 16, + "y": 1 + }, + "id": 20, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "text": {} + }, + "pluginVersion": "9.0.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "1MRsknzVz" + }, + "editorMode": "builder", + "exemplar": true, + "expr": "round(increase(http_requests_total{method=\"POST\", path=\"/indexes/$Index/search\", job=\"meilisearch\"}[24h]))", + "interval": "", + "legendFormat": "", + "range": true, + "refId": "A" + } + ], + "title": "Total Searches (24h)", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "1MRsknzVz" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 20, + "y": 1 + }, + "id": 21, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "text": {} + }, + "pluginVersion": "9.0.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "1MRsknzVz" + }, + "editorMode": "builder", + "exemplar": true, + "expr": "round(increase(http_requests_total{method=\"POST\", path=\"/indexes/$Index/search\", job=\"meilisearch\"}[30d]))", + "interval": "", + "legendFormat": "", + "range": true, + "refId": "A" + } + ], + "title": "Total Searches (30d)", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "1MRsknzVz" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-YlBl" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 12, + "x": 0, + "y": 7 + }, + "id": 1, + "interval": "5s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "right" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "1MRsknzVz" + }, + "editorMode": "builder", + "exemplar": true, + "expr": "rate(http_requests_total{instance=\"$instance\", job=\"meilisearch\"}[5m])", + "interval": "", + "legendFormat": "{{method}} {{path}}", + "range": true, + "refId": "A" + } + ], + "title": "HTTP requests per second (All Indexes)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "1MRsknzVz" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-YlBl" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 12, + "x": 12, + "y": 7 + }, + "id": 3, + "interval": "5s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "right" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "8.1.4", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "1MRsknzVz" + }, + "editorMode": "builder", + "exemplar": true, + "expr": "rate(http_response_time_seconds_sum{instance=\"$instance\", job=\"meilisearch\"}[5m]) / rate(http_response_time_seconds_count[5m])", + "interval": "", + "legendFormat": "{{method}} {{path}}", + "range": true, + "refId": "A" + } + ], + "title": "Mean response time (All Indexes)", + "type": "timeseries" + }, + { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateBlues", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "tsbuckets", + "datasource": { + "type": "prometheus", + "uid": "1MRsknzVz" + }, + "gridPos": { + "h": 12, + "w": 24, + "x": 0, + "y": 18 + }, + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "id": 16, + "legend": { + "show": false + }, + "pluginVersion": "8.1.4", + "reverseYBuckets": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "1MRsknzVz" + }, + "editorMode": "builder", + "exemplar": true, + "expr": "sum by(le) (increase(http_response_time_seconds_bucket{path=\"/indexes/$Index/search\", instance=\"$instance\", job=\"meilisearch\"}[30s]))", + "format": "heatmap", + "interval": "", + "legendFormat": "{{le}}", + "range": true, + "refId": "A" + } + ], + "title": "Response time distribution over time (`POST /indexes/:index/search`)", + "tooltip": { + "show": true, + "showHistogram": false + }, + "type": "heatmap", + "xAxis": { + "show": true + }, + "xBucketNumber": 10, + "yAxis": { + "decimals": 2, + "format": "s", + "logBase": 1, + "show": true + }, + "yBucketBound": "auto", + "yBucketNumber": 10 + }, + { + "collapsed": false, + "datasource": { + "type": "prometheus", + "uid": "i51CxikVz" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 30 + }, + "id": 12, + "panels": [], + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "i51CxikVz" + }, + "refId": "A" + } + ], + "title": "System metrics", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "1MRsknzVz" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-YlBl" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 12, + "x": 0, + "y": 31 + }, + "id": 4, + "interval": "5s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "1MRsknzVz" + }, + "editorMode": "builder", + "exemplar": true, + "expr": "rate(process_cpu_seconds_total{job=\"meilisearch\", instance=\"$instance\"}[1m])", + "interval": "", + "legendFormat": "process", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "1MRsknzVz" + }, + "exemplar": true, + "expr": "sum(rate(container_cpu_usage_seconds_total{name='mongodb-redis'}[1m])) by (name)", + "interval": "", + "legendFormat": "container", + "refId": "B" + } + ], + "title": "CPU usage", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "1MRsknzVz" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-YlBl" + }, + "custom": { + "axisLabel": "MiB", + "axisPlacement": "left", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 12, + "x": 12, + "y": 31 + }, + "id": 5, + "interval": "5s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "1MRsknzVz" + }, + "editorMode": "builder", + "exemplar": true, + "expr": "process_resident_memory_bytes{job=\"meilisearch\", instance=\"$instance\"} / 1024 / 1024", + "interval": "", + "legendFormat": "process", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "1MRsknzVz" + }, + "exemplar": true, + "expr": "container_memory_usage_bytes{name=\"mongodb-redis\"} / 1024 / 1024", + "interval": "", + "legendFormat": "container", + "refId": "B" + } + ], + "title": "Memory usage", + "type": "timeseries" + } + ], + "refresh": "5s", + "schemaVersion": 36, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "localhost:7700", + "value": "localhost:7700" + }, + "datasource": { + "type": "prometheus", + "uid": "1MRsknzVz" + }, + "definition": "label_values(instance)", + "hide": 0, + "includeAll": false, + "label": "Instance", + "multi": false, + "name": "instance", + "options": [], + "query": { + "query": "label_values(instance)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": "movie-collection", + "value": "movie-collection" + }, + "datasource": { + "type": "prometheus", + "uid": "1MRsknzVz" + }, + "definition": "label_values(index)", + "hide": 0, + "includeAll": false, + "label": "index", + "multi": false, + "name": "Index", + "options": [], + "query": { + "query": "label_values(index)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m" + ] + }, + "timezone": "", + "title": "MeiliSearch", + "uid": "7wcZ94dnz", + "version": 47, + "weekStart": "" + } \ No newline at end of file diff --git a/meilisearch-http/Cargo.toml b/meilisearch-http/Cargo.toml index 81e0205da..9395a604c 100644 --- a/meilisearch-http/Cargo.toml +++ b/meilisearch-http/Cargo.toml @@ -77,6 +77,8 @@ tokio = { version = "1.17.0", features = ["full"] } tokio-stream = "0.1.8" uuid = { version = "1.1.2", features = ["serde", "v4"] } walkdir = "2.3.2" +prometheus = { version = "0.13.0", features = ["process"] } +lazy_static = "1.4.0" [dev-dependencies] actix-rt = "2.7.0" diff --git a/meilisearch-http/src/lib.rs b/meilisearch-http/src/lib.rs index fcf07587f..a4395f606 100644 --- a/meilisearch-http/src/lib.rs +++ b/meilisearch-http/src/lib.rs @@ -7,6 +7,7 @@ pub mod task; pub mod extractors; pub mod option; pub mod routes; +pub mod metrics; use std::sync::{atomic::AtomicBool, Arc}; use std::time::Duration; @@ -146,16 +147,48 @@ macro_rules! create_app { use actix_cors::Cors; use actix_web::middleware::TrailingSlash; use actix_web::App; + use actix_web::dev::Service; use actix_web::{middleware, web}; use meilisearch_http::error::MeilisearchHttpError; use meilisearch_http::routes; use meilisearch_http::{configure_data, dashboard}; use meilisearch_types::error::ResponseError; + use meilisearch_http::metrics; + + use lazy_static::lazy_static; + use prometheus::{opts, register_histogram_vec, register_int_counter_vec, register_int_gauge}; + use prometheus::{HistogramVec, IntCounterVec, IntGauge, HistogramTimer}; App::new() .configure(|s| configure_data(s, $data.clone(), $auth.clone(), &$opt, $analytics)) .configure(routes::configure) .configure(|s| dashboard(s, $enable_frontend)) + .wrap_fn(|req, srv| { + let mut histogram_timer: Option = None; + let request_path = req.path(); + let is_registered_resource = req.resource_map().has_resource(request_path); + if is_registered_resource { + let request_method = req.method().to_string(); + histogram_timer = Some( + metrics::HTTP_RESPONSE_TIME_SECONDS + .with_label_values(&[&request_method, request_path]) + .start_timer(), + ); + metrics::HTTP_REQUESTS_TOTAL + .with_label_values(&[&request_method, request_path]) + .inc(); + } + + let fut = srv.call(req); + + async { + let res = fut.await?; + if let Some(histogram_timer) = histogram_timer { + histogram_timer.observe_duration(); + }; + Ok(res) + } + }) .wrap( Cors::default() .send_wildcard() diff --git a/meilisearch-http/src/metrics.rs b/meilisearch-http/src/metrics.rs new file mode 100644 index 000000000..b93696dd3 --- /dev/null +++ b/meilisearch-http/src/metrics.rs @@ -0,0 +1,40 @@ +use lazy_static::lazy_static; +use prometheus::{opts, register_histogram_vec, register_int_counter_vec, register_int_gauge, register_int_gauge_vec}; +use prometheus::{HistogramVec, IntCounterVec, IntGauge, IntGaugeVec}; + +const HTTP_RESPONSE_TIME_CUSTOM_BUCKETS: &[f64; 14] = &[ + 0.0005, 0.0008, 0.00085, 0.0009, 0.00095, 0.001, 0.00105, 0.0011, 0.00115, 0.0012, 0.0015, + 0.002, 0.003, 1.0, +]; + +lazy_static! { + pub static ref HTTP_REQUESTS_TOTAL: IntCounterVec = register_int_counter_vec!( + opts!("http_requests_total", "HTTP requests total"), + &["method", "path"] + ) + .expect("Can't create a metric"); + + pub static ref MEILISEARCH_DB_SIZE: IntGauge = register_int_gauge!( + opts!("meilisearch_database_size", "MeiliSearch Stats DbSize") + ) + .expect("Can't create a metric"); + + pub static ref MEILISEARCH_INDEX_COUNT: IntGauge = register_int_gauge!( + opts!("meilisearch_total_index", "MeiliSearch Stats Index Count") + ) + .expect("Can't create a metric"); + + pub static ref MEILISEARCH_DOCS_COUNT: IntGaugeVec = register_int_gauge_vec!( + opts!("meilisearch_docs_count", "MeiliSearch Stats Docs Count"), + &["index"] + ) + .expect("Can't create a metric"); + + pub static ref HTTP_RESPONSE_TIME_SECONDS: HistogramVec = register_histogram_vec!( + "http_response_time_seconds", + "HTTP response times", + &["method", "path"], + HTTP_RESPONSE_TIME_CUSTOM_BUCKETS.to_vec() + ) + .expect("Can't create a metric"); +} \ No newline at end of file diff --git a/meilisearch-http/src/routes/mod.rs b/meilisearch-http/src/routes/mod.rs index 6a673f600..e588189ce 100644 --- a/meilisearch-http/src/routes/mod.rs +++ b/meilisearch-http/src/routes/mod.rs @@ -1,4 +1,5 @@ -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{web, HttpResponse}; +use actix_web::http::header::{self}; use log::debug; use serde::{Deserialize, Serialize}; @@ -12,6 +13,7 @@ use meilisearch_types::star_or::StarOr; use crate::analytics::Analytics; use crate::extractors::authentication::{policies::*, GuardedData}; +use prometheus::{Encoder, TextEncoder}; mod api_key; mod dump; @@ -21,6 +23,7 @@ mod tasks; pub fn configure(cfg: &mut web::ServiceConfig) { cfg.service(web::scope("/tasks").configure(tasks::configure)) .service(web::resource("/health").route(web::get().to(get_health))) + .service(web::resource("/metrics").route(web::get().to(get_metrics))) .service(web::scope("/keys").configure(api_key::configure)) .service(web::scope("/dumps").configure(dump::configure)) .service(web::resource("/stats").route(web::get().to(get_stats))) @@ -278,3 +281,36 @@ struct KeysResponse { pub async fn get_health() -> Result { Ok(HttpResponse::Ok().json(serde_json::json!({ "status": "available" }))) } + +pub async fn get_metrics( + meilisearch: GuardedData, MeiliSearch>, +) -> Result { + + let search_rules = &meilisearch.filters().search_rules; + let response = meilisearch.get_all_stats(search_rules).await?; + + crate::metrics::MEILISEARCH_DB_SIZE + .set(response.database_size as i64); + + crate::metrics::MEILISEARCH_INDEX_COUNT + .set(response.indexes.len() as i64); + + for (index, value) in response.indexes.iter() { + crate::metrics::MEILISEARCH_DOCS_COUNT + .with_label_values(&[&index]) + .set(value.number_of_documents as i64); + } + + let encoder = TextEncoder::new(); + let mut buffer = vec![]; + encoder + .encode(&prometheus::gather(), &mut buffer) + .expect("Failed to encode metrics"); + + let response = String::from_utf8(buffer.clone()).expect("Failed to convert bytes to string"); + buffer.clear(); + + Ok(HttpResponse::Ok() + .insert_header(header::ContentType(mime::TEXT_PLAIN)) + .body(response)) +}