From acd52ee8ed05675ef4e6260fc3f0e71af1d455a8 Mon Sep 17 00:00:00 2001 From: Daniel Harvey Date: Wed, 15 Nov 2023 13:02:39 +0000 Subject: [PATCH 1/5] Time query execution --- .../src/configuration/version1.rs | 19 +++--- crates/ndc-sqlserver/src/connector.rs | 8 ++- crates/ndc-sqlserver/src/lib.rs | 1 - crates/ndc-sqlserver/src/metrics.rs | 58 ------------------- .../query-engine/execution/src/execution.rs | 37 ++++++++---- 5 files changed, 38 insertions(+), 85 deletions(-) delete mode 100644 crates/ndc-sqlserver/src/metrics.rs diff --git a/crates/ndc-sqlserver/src/configuration/version1.rs b/crates/ndc-sqlserver/src/configuration/version1.rs index 9ad89263..d6c2d081 100644 --- a/crates/ndc-sqlserver/src/configuration/version1.rs +++ b/crates/ndc-sqlserver/src/configuration/version1.rs @@ -1,8 +1,8 @@ //! Configuration and state for our connector. -use crate::metrics; use crate::configuration::introspection; use ndc_sdk::connector; +use query_engine_execution::metrics; use query_engine_metadata::metadata; use query_engine_metadata::metadata::{database, Nullable}; use schemars::JsonSchema; @@ -47,7 +47,7 @@ pub struct Configuration { #[derive(Debug, Clone)] pub struct State { pub mssql_pool: bb8::Pool, - pub metrics: metrics::Metrics, + pub metrics: query_engine_execution::metrics::Metrics, } /// Validate the user configuration. @@ -72,15 +72,12 @@ pub async fn validate_raw_configuration( pub async fn create_state( configuration: &Configuration, metrics_registry: &mut prometheus::Registry, -) -> Result { +) -> Result { let mssql_pool = create_mssql_pool(&configuration.config) .await - .map_err(|e| { - connector::InitializationError::Other( - InitializationError::UnableToCreateMSSQLPool(e).into(), - ) - })?; - let metrics = metrics::initialise_metrics(metrics_registry).await?; + .map_err(InitializationError::UnableToCreateMSSQLPool)?; + let metrics = query_engine_execution::metrics::Metrics::initialize(metrics_registry) + .map_err(InitializationError::MetricsError)?; Ok(State { mssql_pool, metrics, @@ -361,8 +358,8 @@ fn get_column_info( pub enum InitializationError { #[error("unable to initialize mssql connection pool: {0}")] UnableToCreateMSSQLPool(bb8_tiberius::Error), - #[error("error initializing Prometheus metrics: {0}")] - PrometheusError(prometheus::Error), + #[error("error initializing metrics: {0}")] + MetricsError(metrics::Error), } /// Collect all the types that can occur in the metadata. This is a bit circumstantial. A better diff --git a/crates/ndc-sqlserver/src/connector.rs b/crates/ndc-sqlserver/src/connector.rs index 6793123b..fd09385b 100644 --- a/crates/ndc-sqlserver/src/connector.rs +++ b/crates/ndc-sqlserver/src/connector.rs @@ -59,7 +59,9 @@ impl connector::Connector for SQLServer { configuration: &Self::Configuration, metrics: &mut prometheus::Registry, ) -> Result { - configuration::create_state(configuration, metrics).await + configuration::create_state(configuration, metrics) + .await + .map_err(|err| connector::InitializationError::Other(err.into())) } /// Update any metrics from the state @@ -174,7 +176,7 @@ impl connector::Connector for SQLServer { }?; // Execute the query. - let result = execution::mssql_execute(&state.mssql_pool, plan) + let result = execution::mssql_execute(&state.mssql_pool, &state.metrics, plan) .await .map_err(|err| match err { execution::Error::Query(err) => { @@ -184,7 +186,7 @@ impl connector::Connector for SQLServer { })?; // assuming query succeeded, increment counter - state.metrics.query_total.inc(); + state.metrics.record_successful_query(); // TODO: return raw JSON Ok(JsonResponse::Value(result)) diff --git a/crates/ndc-sqlserver/src/lib.rs b/crates/ndc-sqlserver/src/lib.rs index 1d98c5e8..9bfe246e 100644 --- a/crates/ndc-sqlserver/src/lib.rs +++ b/crates/ndc-sqlserver/src/lib.rs @@ -1,4 +1,3 @@ pub mod configuration; pub mod connector; -pub mod metrics; pub mod schema; diff --git a/crates/ndc-sqlserver/src/metrics.rs b/crates/ndc-sqlserver/src/metrics.rs deleted file mode 100644 index d5807022..00000000 --- a/crates/ndc-sqlserver/src/metrics.rs +++ /dev/null @@ -1,58 +0,0 @@ -//! Metrics setup and update for our connector. - -use super::configuration::InitializationError; -use ndc_sdk::connector; -use prometheus::core::{AtomicU64, GenericCounter}; - -#[derive(Debug, Clone)] -pub struct Metrics { - pub query_total: GenericCounter, - pub explain_total: GenericCounter, -} - -/// Create a new int counter metric and register it with the provided Prometheus Registry -fn add_int_counter_metric( - metrics_registry: &mut prometheus::Registry, - metric_name: &str, - metric_description: &str, -) -> Result, connector::InitializationError> { - let int_counter = - prometheus::IntCounter::with_opts(prometheus::Opts::new(metric_name, metric_description)) - .map_err(|prometheus_error| { - connector::InitializationError::Other( - InitializationError::PrometheusError(prometheus_error).into(), - ) - })?; - - metrics_registry - .register(Box::new(int_counter.clone())) - .map_err(|prometheus_error| { - connector::InitializationError::Other( - InitializationError::PrometheusError(prometheus_error).into(), - ) - })?; - - Ok(int_counter) -} - -/// Setup counters and gauges used to produce Prometheus metrics -pub async fn initialise_metrics( - metrics_registry: &mut prometheus::Registry, -) -> Result { - let query_total = add_int_counter_metric( - metrics_registry, - "sqlserver_ndc_query_total", - "Total successful queries.", - )?; - - let explain_total = add_int_counter_metric( - metrics_registry, - "sqlserver_ndc_explain_total", - "Total successful explains.", - )?; - - Ok(Metrics { - query_total, - explain_total, - }) -} diff --git a/crates/query-engine/execution/src/execution.rs b/crates/query-engine/execution/src/execution.rs index fa0886ca..50677c53 100644 --- a/crates/query-engine/execution/src/execution.rs +++ b/crates/query-engine/execution/src/execution.rs @@ -1,5 +1,6 @@ //! Execute an execution plan against the database. +use crate::metrics; use ndc_sdk::models; use query_engine_sql::sql; use serde_json; @@ -10,6 +11,7 @@ use tokio_stream::StreamExt; /// Execute a query against sqlserver. pub async fn mssql_execute( mssql_pool: &bb8::Pool, + metrics: &metrics::Metrics, plan: sql::execution_plan::ExecutionPlan, ) -> Result { let query = plan.query(); @@ -21,6 +23,28 @@ pub async fn mssql_execute( &plan.variables, ); + let query_timer = metrics.time_query_execution(); + let rows_result = execute_query(mssql_pool, plan).await; + let rows = query_timer.complete_with(rows_result)?; + + tracing::info!("Database rows result: {:?}", rows); + + // Hack a response from the query results. See the 'response_hack' for more details. + let response = rows_to_response(rows); + + // tracing::info!( + // "Query response: {}", + // serde_json::to_string(&response).unwrap() + // ); + + Ok(response) +} + +async fn execute_query( + mssql_pool: &bb8::Pool, + plan: sql::execution_plan::ExecutionPlan, +) -> Result, Error> { + let query = plan.query(); // run the query on each set of variables. The result is a vector of rows each // element in the vector is the result of running the query on one set of variables. let rows: Vec = match plan.variables { @@ -38,18 +62,7 @@ pub async fn mssql_execute( sets_of_rows } }; - - tracing::info!("Database rows result: {:?}", rows); - - // Hack a response from the query results. See the 'response_hack' for more details. - let response = rows_to_response(rows); - - // tracing::info!( - // "Query response: {}", - // serde_json::to_string(&response).unwrap() - // ); - - Ok(response) + Ok(rows) } /// Take the sqlserver results and return them as a QueryResponse. From ce3519ae29844cf20d8af9ea60d4cda117240f70 Mon Sep 17 00:00:00 2001 From: Daniel Harvey Date: Wed, 15 Nov 2023 14:35:07 +0000 Subject: [PATCH 2/5] Split query logic into query.rs --- crates/ndc-sqlserver/src/connector.rs | 37 +----------- crates/ndc-sqlserver/src/lib.rs | 1 + crates/ndc-sqlserver/src/query.rs | 58 +++++++++++++++++++ .../query-engine/execution/src/execution.rs | 20 ++++--- 4 files changed, 73 insertions(+), 43 deletions(-) create mode 100644 crates/ndc-sqlserver/src/query.rs diff --git a/crates/ndc-sqlserver/src/connector.rs b/crates/ndc-sqlserver/src/connector.rs index fd09385b..8dff70ca 100644 --- a/crates/ndc-sqlserver/src/connector.rs +++ b/crates/ndc-sqlserver/src/connector.rs @@ -11,10 +11,9 @@ use async_trait::async_trait; use ndc_sdk::connector; use ndc_sdk::json_response::JsonResponse; use ndc_sdk::models; -use query_engine_execution::execution; -use query_engine_translation::translation; use super::configuration; +use super::query; use super::schema; #[derive(Clone, Default)] @@ -157,39 +156,7 @@ impl connector::Connector for SQLServer { state: &Self::State, query_request: models::QueryRequest, ) -> Result, connector::QueryError> { - tracing::info!("{}", serde_json::to_string(&query_request).unwrap()); - tracing::info!("{:?}", query_request); - - // Compile the query. - let plan = - match translation::query::translate(&configuration.config.metadata, query_request) { - Ok(plan) => Ok(plan), - Err(err) => { - tracing::error!("{}", err); - match err { - translation::query::error::Error::NotSupported(_) => { - Err(connector::QueryError::UnsupportedOperation(err.to_string())) - } - _ => Err(connector::QueryError::InvalidRequest(err.to_string())), - } - } - }?; - - // Execute the query. - let result = execution::mssql_execute(&state.mssql_pool, &state.metrics, plan) - .await - .map_err(|err| match err { - execution::Error::Query(err) => { - tracing::error!("{}", err); - connector::QueryError::Other(err.into()) - } - })?; - - // assuming query succeeded, increment counter - state.metrics.record_successful_query(); - - // TODO: return raw JSON - Ok(JsonResponse::Value(result)) + query::query(configuration, state, query_request).await } } diff --git a/crates/ndc-sqlserver/src/lib.rs b/crates/ndc-sqlserver/src/lib.rs index 9bfe246e..d07b6caf 100644 --- a/crates/ndc-sqlserver/src/lib.rs +++ b/crates/ndc-sqlserver/src/lib.rs @@ -1,3 +1,4 @@ pub mod configuration; pub mod connector; +pub mod query; pub mod schema; diff --git a/crates/ndc-sqlserver/src/query.rs b/crates/ndc-sqlserver/src/query.rs new file mode 100644 index 00000000..f1b9ef1d --- /dev/null +++ b/crates/ndc-sqlserver/src/query.rs @@ -0,0 +1,58 @@ +//! Implement the `/query` endpoint to run a query against SQLServer. +//! See the Hasura +//! [Native Data Connector Specification](https://hasura.github.io/ndc-spec/specification/queries/index.html) +//! for further details. + +use super::configuration; +use ndc_sdk::connector; +use ndc_sdk::json_response::JsonResponse; +use ndc_sdk::models; +use query_engine_execution::execution; +use query_engine_translation::translation; + +/// Execute a query +/// +/// This function implements the [query endpoint](https://hasura.github.io/ndc-spec/specification/queries/index.html) +/// from the NDC specification. +pub async fn query( + configuration: &configuration::Configuration, + state: &configuration::State, + query_request: models::QueryRequest, +) -> Result, connector::QueryError> { + tracing::info!("{}", serde_json::to_string(&query_request).unwrap()); + tracing::info!("{:?}", query_request); + + // Compile the query. + let plan = match translation::query::translate(&configuration.config.metadata, query_request) { + Ok(plan) => Ok(plan), + Err(err) => { + tracing::error!("{}", err); + match err { + translation::query::error::Error::NotSupported(_) => { + Err(connector::QueryError::UnsupportedOperation(err.to_string())) + } + _ => Err(connector::QueryError::InvalidRequest(err.to_string())), + } + } + }?; + + // Execute the query. + let result = execution::mssql_execute(&state.mssql_pool, &state.metrics, plan) + .await + .map_err(|err| match err { + execution::Error::Query(err) => { + tracing::error!("{}", err); + connector::QueryError::Other(err.into()) + } + execution::Error::ConnectionPool(err) => { + tracing::error!("{}", err); + connector::QueryError::Other(err.into()) + } + })?; + + // assuming query succeeded, increment counter + state.metrics.record_successful_query(); + + // TODO: return raw JSON + Ok(JsonResponse::Value(result)) +} diff --git a/crates/query-engine/execution/src/execution.rs b/crates/query-engine/execution/src/execution.rs index 50677c53..9ba46d36 100644 --- a/crates/query-engine/execution/src/execution.rs +++ b/crates/query-engine/execution/src/execution.rs @@ -23,8 +23,12 @@ pub async fn mssql_execute( &plan.variables, ); + let acquisition_timer = metrics.time_connection_acquisition_wait(); + let connection_result = mssql_pool.get().await.map_err(Error::ConnectionPool); + let mut connection = acquisition_timer.complete_with(connection_result)?; + let query_timer = metrics.time_query_execution(); - let rows_result = execute_query(mssql_pool, plan).await; + let rows_result = execute_query(&mut connection, plan).await; let rows = query_timer.complete_with(rows_result)?; tracing::info!("Database rows result: {:?}", rows); @@ -41,22 +45,23 @@ pub async fn mssql_execute( } async fn execute_query( - mssql_pool: &bb8::Pool, + connection: &mut bb8::PooledConnection<'_, bb8_tiberius::ConnectionManager>, plan: sql::execution_plan::ExecutionPlan, ) -> Result, Error> { let query = plan.query(); + // run the query on each set of variables. The result is a vector of rows each // element in the vector is the result of running the query on one set of variables. let rows: Vec = match plan.variables { None => { let empty_map = BTreeMap::new(); - let rows = execute_mssql_query(mssql_pool, &query, &empty_map).await?; + let rows = execute_mssql_query(connection, &query, &empty_map).await?; vec![rows] } Some(variable_sets) => { let mut sets_of_rows = vec![]; for vars in &variable_sets { - let rows = execute_mssql_query(mssql_pool, &query, vars).await?; + let rows = execute_mssql_query(connection, &query, vars).await?; sets_of_rows.push(rows); } sets_of_rows @@ -77,12 +82,10 @@ fn rows_to_response(results: Vec) -> models::QueryResponse { /// Execute the query on one set of variables. async fn execute_mssql_query( - mssql_pool: &bb8::Pool, + connection: &mut bb8::PooledConnection<'_, bb8_tiberius::ConnectionManager>, query: &sql::string::SQL, variables: &BTreeMap, ) -> Result { - let mut connection = mssql_pool.get().await.unwrap(); - // let's do a query to check everything is ok let query_text = query.sql.as_str(); @@ -126,7 +129,7 @@ async fn execute_mssql_query( } // go! - let mut stream = mssql_query.query(&mut connection).await.unwrap(); + let mut stream = mssql_query.query(connection).await.unwrap(); // collect big lump of json here let mut result_str = String::new(); @@ -154,4 +157,5 @@ async fn execute_mssql_query( pub enum Error { Query(String), + ConnectionPool(bb8::RunError), } From 045f8afa0f2f7edd7d8bc97a836fb8c737611ea9 Mon Sep 17 00:00:00 2001 From: Daniel Harvey Date: Wed, 15 Nov 2023 14:55:13 +0000 Subject: [PATCH 3/5] Instrument query with spans --- Cargo.lock | 1 + crates/ndc-sqlserver/Cargo.toml | 3 +- crates/ndc-sqlserver/src/query.rs | 65 ++++++++++++++----- .../query-engine/execution/src/execution.rs | 11 +++- 4 files changed, 59 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8815d056..67f60bf2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1314,6 +1314,7 @@ dependencies = [ "prometheus", "query-engine-execution", "query-engine-metadata", + "query-engine-sql", "query-engine-translation", "schemars", "serde", diff --git a/crates/ndc-sqlserver/Cargo.toml b/crates/ndc-sqlserver/Cargo.toml index 1fe1e438..cd8d284f 100644 --- a/crates/ndc-sqlserver/Cargo.toml +++ b/crates/ndc-sqlserver/Cargo.toml @@ -17,8 +17,9 @@ path = "bin/main.rs" ndc-sdk = { git = "https://github.com/hasura/ndc-hub.git", rev = "f2a2a75", package = "ndc-sdk" } query-engine-execution = { path = "../query-engine/execution" } -query-engine-translation = { path = "../query-engine/translation" } query-engine-metadata = {path = "../query-engine/metadata"} +query-engine-sql = { path = "../query-engine/sql" } +query-engine-translation = { path = "../query-engine/translation" } tiberius = { version = "0.12.2", default-features = false, features = ["rustls"] } bb8 = "0.8.1" diff --git a/crates/ndc-sqlserver/src/query.rs b/crates/ndc-sqlserver/src/query.rs index f1b9ef1d..4c162bf5 100644 --- a/crates/ndc-sqlserver/src/query.rs +++ b/crates/ndc-sqlserver/src/query.rs @@ -2,13 +2,14 @@ //! See the Hasura //! [Native Data Connector Specification](https://hasura.github.io/ndc-spec/specification/queries/index.html) //! for further details. - use super::configuration; use ndc_sdk::connector; use ndc_sdk::json_response::JsonResponse; use ndc_sdk::models; use query_engine_execution::execution; +use query_engine_sql::sql; use query_engine_translation::translation; +use tracing::{info_span, Instrument}; /// Execute a query /// @@ -22,23 +23,57 @@ pub async fn query( tracing::info!("{}", serde_json::to_string(&query_request).unwrap()); tracing::info!("{:?}", query_request); - // Compile the query. - let plan = match translation::query::translate(&configuration.config.metadata, query_request) { - Ok(plan) => Ok(plan), - Err(err) => { + let timer = state.metrics.time_query_total(); + let result = async move { + // Plan the query + let plan = async { plan_query(configuration, state, query_request) } + .instrument(info_span!("Plan query")) + .await?; + + // Execute the query. + let result = execute_query(state, plan) + .instrument(info_span!("Execute query")) + .await?; + + // assuming query succeeded, increment counter + state.metrics.record_successful_query(); + + // TODO: return raw JSON + Ok(result) + } + .instrument(info_span!("Execute query")) + .await; + + timer.complete_with(result) +} + +// Compile the query. +fn plan_query( + configuration: &configuration::Configuration, + state: &configuration::State, + query_request: models::QueryRequest, +) -> Result { + let timer = state.metrics.time_query_plan(); + let result = translation::query::translate(&configuration.config.metadata, query_request) + .map_err(|err| { tracing::error!("{}", err); match err { translation::query::error::Error::NotSupported(_) => { - Err(connector::QueryError::UnsupportedOperation(err.to_string())) + connector::QueryError::UnsupportedOperation(err.to_string()) } - _ => Err(connector::QueryError::InvalidRequest(err.to_string())), + _ => connector::QueryError::InvalidRequest(err.to_string()), } - } - }?; + }); + timer.complete_with(result) +} - // Execute the query. - let result = execution::mssql_execute(&state.mssql_pool, &state.metrics, plan) +async fn execute_query( + state: &configuration::State, + plan: sql::execution_plan::ExecutionPlan, +) -> Result, connector::QueryError> { + execution::mssql_execute(&state.mssql_pool, &state.metrics, plan) .await + .map(JsonResponse::Value) .map_err(|err| match err { execution::Error::Query(err) => { tracing::error!("{}", err); @@ -48,11 +83,5 @@ pub async fn query( tracing::error!("{}", err); connector::QueryError::Other(err.into()) } - })?; - - // assuming query succeeded, increment counter - state.metrics.record_successful_query(); - - // TODO: return raw JSON - Ok(JsonResponse::Value(result)) + }) } diff --git a/crates/query-engine/execution/src/execution.rs b/crates/query-engine/execution/src/execution.rs index 9ba46d36..62b79e4b 100644 --- a/crates/query-engine/execution/src/execution.rs +++ b/crates/query-engine/execution/src/execution.rs @@ -7,6 +7,7 @@ use serde_json; use std::collections::BTreeMap; use tiberius::QueryItem; use tokio_stream::StreamExt; +use tracing::{info_span, Instrument}; /// Execute a query against sqlserver. pub async fn mssql_execute( @@ -24,11 +25,17 @@ pub async fn mssql_execute( ); let acquisition_timer = metrics.time_connection_acquisition_wait(); - let connection_result = mssql_pool.get().await.map_err(Error::ConnectionPool); + let connection_result = mssql_pool + .get() + .instrument(info_span!("Acquire connection")) + .await + .map_err(Error::ConnectionPool); let mut connection = acquisition_timer.complete_with(connection_result)?; let query_timer = metrics.time_query_execution(); - let rows_result = execute_query(&mut connection, plan).await; + let rows_result = execute_query(&mut connection, plan) + .instrument(info_span!("Database request")) + .await; let rows = query_timer.complete_with(rows_result)?; tracing::info!("Database rows result: {:?}", rows); From bf559034ca35cf5ecaf511b085ebcba7f62b69ea Mon Sep 17 00:00:00 2001 From: Daniel Harvey Date: Wed, 15 Nov 2023 15:33:04 +0000 Subject: [PATCH 4/5] Pass Otel details to connector and engine --- docker-compose.yaml | 19 +++++++++++++++++++ justfile | 13 +++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 3b7c1d1b..d3f93bc6 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -72,5 +72,24 @@ services: ports: - 3050:3050 + jaeger: + image: jaegertracing/all-in-one:1.37 + restart: always + ports: + - 5775:5775/udp + - 6831:6831/udp + - 6832:6832/udp + - 5778:5778 + - 4002:16686 + - 14250:14250 + - 14268:14268 + - 14269:14269 + - 4317:4317 # OTLP gRPC + - 4318:4318 # OTLP HTTP + - 9411:9411 + environment: + COLLECTOR_OTLP_ENABLED: "true" + COLLECTOR_ZIPKIN_HOST_PORT: "9411" + volumes: prom_data: diff --git a/justfile b/justfile index e9e4bae9..ec8d2ab3 100644 --- a/justfile +++ b/justfile @@ -18,11 +18,15 @@ check: format-check find-unused-dependencies build lint test # run the connector run: start-dependencies RUST_LOG=INFO \ + OTLP_ENDPOINT=http://localhost:4317 \ + OTEL_SERVICE_NAME=ndc-sqlserver \ cargo run --release -- serve --configuration {{CHINOOK_DEPLOYMENT}} # watch the code, then test and re-run on changes dev: start-dependencies RUST_LOG=INFO \ + OTLP_ENDPOINT=http://localhost:4317 \ + OTEL_SERVICE_NAME=ndc-sqlserver \ cargo watch -i "tests/snapshots/*" \ -c \ -x test \ @@ -32,6 +36,8 @@ dev: start-dependencies # watch the code, then test and re-run config server ron changes dev-config: start-dependencies RUST_LOG=DEBUG \ + OTLP_ENDPOINT=http://localhost:4317 \ + OTEL_SERVICE_NAME=ndc-sqlserver \ cargo watch -i "tests/snapshots/*" \ -c \ -x clippy \ @@ -88,7 +94,7 @@ test-integrated: # run sqlserver start-dependencies: - docker compose up --wait sqlserver + docker compose up --wait sqlserver jaeger # run prometheus + grafana start-metrics: @@ -102,11 +108,14 @@ run-engine: start-dependencies docker compose up --wait auth-hook # Run graphql-engine using static Chinook metadata # we expect the `v3-engine` repo to live next door to this one - RUST_LOG=DEBUG cargo run --release \ + RUST_LOG=DEBUG \ + OTLP_ENDPOINT=http://localhost:4317 \ + cargo run --release \ --manifest-path ../v3-engine/Cargo.toml \ --bin engine -- \ --metadata-path ./static/chinook-metadata.json \ --authn-config-path ./static/auth_config.json + # pasting multiline SQL into `sqlcmd` is a bad time, so here is a script to # smash a file in for rapid fire application development business value run-temp-sql: From c12eef1bbdc5c339259dc7fb4fa32204ba899603 Mon Sep 17 00:00:00 2001 From: Daniel Harvey Date: Wed, 15 Nov 2023 15:40:42 +0000 Subject: [PATCH 5/5] Update dashboard to match ndc-postgres --- docker-compose.yaml | 9 +- .../grafana-data/alerting/1/__default__.tmpl | 53 -- metrics/grafana-data/grafana.db | Bin 929792 -> 0 bytes metrics/grafana/dashboard.yaml | 12 + metrics/grafana/dashboards/ndc-sqlserver.json | 791 ++++++++++++++++++ metrics/grafana/datasource.yml | 20 +- metrics/prometheus/prometheus.yml | 47 +- 7 files changed, 848 insertions(+), 84 deletions(-) delete mode 100644 metrics/grafana-data/alerting/1/__default__.tmpl delete mode 100644 metrics/grafana-data/grafana.db create mode 100644 metrics/grafana/dashboard.yaml create mode 100644 metrics/grafana/dashboards/ndc-sqlserver.json diff --git a/docker-compose.yaml b/docker-compose.yaml index d3f93bc6..aaa688db 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -63,7 +63,13 @@ services: target: /etc/grafana/provisioning/datasources read_only: true - type: bind - source: ./metrics/grafana-data + source: ./metrics/grafana/dashboard.yaml + target: /etc/grafana/provisioning/dashboards/main.yaml + - type: bind + source: ./metrics/grafana/dashboards + target: /var/lib/grafana/dashboards + - type: volume + source: grafana_data target: /var/lib/grafana auth-hook: @@ -93,3 +99,4 @@ services: volumes: prom_data: + grafana_data: diff --git a/metrics/grafana-data/alerting/1/__default__.tmpl b/metrics/grafana-data/alerting/1/__default__.tmpl deleted file mode 100644 index b8633d16..00000000 --- a/metrics/grafana-data/alerting/1/__default__.tmpl +++ /dev/null @@ -1,53 +0,0 @@ - -{{ define "__subject" }}[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ if gt (.Alerts.Resolved | len) 0 }}, RESOLVED:{{ .Alerts.Resolved | len }}{{ end }}{{ end }}] {{ .GroupLabels.SortedPairs.Values | join " " }} {{ if gt (len .CommonLabels) (len .GroupLabels) }}({{ with .CommonLabels.Remove .GroupLabels.Names }}{{ .Values | join " " }}{{ end }}){{ end }}{{ end }} - -{{ define "__text_values_list" }}{{ if len .Values }}{{ $first := true }}{{ range $refID, $value := .Values -}} -{{ if $first }}{{ $first = false }}{{ else }}, {{ end }}{{ $refID }}={{ $value }}{{ end -}} -{{ else }}[no value]{{ end }}{{ end }} - -{{ define "__text_alert_list" }}{{ range . }} -Value: {{ template "__text_values_list" . }} -Labels: -{{ range .Labels.SortedPairs }} - {{ .Name }} = {{ .Value }} -{{ end }}Annotations: -{{ range .Annotations.SortedPairs }} - {{ .Name }} = {{ .Value }} -{{ end }}{{ if gt (len .GeneratorURL) 0 }}Source: {{ .GeneratorURL }} -{{ end }}{{ if gt (len .SilenceURL) 0 }}Silence: {{ .SilenceURL }} -{{ end }}{{ if gt (len .DashboardURL) 0 }}Dashboard: {{ .DashboardURL }} -{{ end }}{{ if gt (len .PanelURL) 0 }}Panel: {{ .PanelURL }} -{{ end }}{{ end }}{{ end }} - -{{ define "default.title" }}{{ template "__subject" . }}{{ end }} - -{{ define "default.message" }}{{ if gt (len .Alerts.Firing) 0 }}**Firing** -{{ template "__text_alert_list" .Alerts.Firing }}{{ if gt (len .Alerts.Resolved) 0 }} - -{{ end }}{{ end }}{{ if gt (len .Alerts.Resolved) 0 }}**Resolved** -{{ template "__text_alert_list" .Alerts.Resolved }}{{ end }}{{ end }} - - -{{ define "__teams_text_alert_list" }}{{ range . }} -Value: {{ template "__text_values_list" . }} -Labels: -{{ range .Labels.SortedPairs }} - {{ .Name }} = {{ .Value }} -{{ end }} -Annotations: -{{ range .Annotations.SortedPairs }} - {{ .Name }} = {{ .Value }} -{{ end }} -{{ if gt (len .GeneratorURL) 0 }}Source: [{{ .GeneratorURL }}]({{ .GeneratorURL }}) - -{{ end }}{{ if gt (len .SilenceURL) 0 }}Silence: [{{ .SilenceURL }}]({{ .SilenceURL }}) - -{{ end }}{{ if gt (len .DashboardURL) 0 }}Dashboard: [{{ .DashboardURL }}]({{ .DashboardURL }}) - -{{ end }}{{ if gt (len .PanelURL) 0 }}Panel: [{{ .PanelURL }}]({{ .PanelURL }}) - -{{ end }} -{{ end }}{{ end }} - - -{{ define "teams.default.message" }}{{ if gt (len .Alerts.Firing) 0 }}**Firing** -{{ template "__teams_text_alert_list" .Alerts.Firing }}{{ if gt (len .Alerts.Resolved) 0 }} - -{{ end }}{{ end }}{{ if gt (len .Alerts.Resolved) 0 }}**Resolved** -{{ template "__teams_text_alert_list" .Alerts.Resolved }}{{ end }}{{ end }} diff --git a/metrics/grafana-data/grafana.db b/metrics/grafana-data/grafana.db deleted file mode 100644 index c1266410f56805c444fc6b21fcd21d4b4e59140d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 929792 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 tj`;B!C2v01`j~NB{{S0VIF~kN^@02uNKC z_c(yu{~sGZF2FDTAOR$R1dsp{Kmter2_OL^fCP{L68KIgV80bREJ%q{gLeMqmj~l< zX>e*vnvx{m<@CujXQs8YrB!WWMxE5gCpGo;mDS0WnbPX2I<+#r3jd!uH?g9=UMfwE zpPQLHH?Ee)wdsj*b#?m8j5;xX?%c%cs5Q8{_A!$lExkW*!rC=KR#m`Khz+_5VK>hX43Gxr?#wNB{{S z0VIF~kN^@u0!RP}AOR$R1fC)S&%(PETJHZR*Z;xkAO0W#B!C2v01`j~NB{{S0VIF~ zkN^@u0#7jka{Zs~|A*icISwHI;|~%*0!RP}AOR$R1dsp{Kmter2_OL^@Dvds8~($> zZDHv521f_}$AS6&zwH09{!8&&(!Y_6-oNPmiQc0<|FUPL=Xm#zbbqev2M=T+>CS() z&BKx~`}Ll{`y%m4z<7)Xzo=tpJGo!B7 zMy;1)eLTaIaNoV(wUsJhYm@?4cqfLAI-$jBcvYVc0HOu zsy_imo-6tHo+Tda2cbh#;MmvoKujur&2fBk+WF{cA05fm3vqhxrmM}ae-t{RGK+yX zbtj@Y>Z*2;>tNXy4oX7WcR>%0DeDcQFXdfR@G|bU`4^uNg|l97I6iD@Yp#+JPcvkw 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 diff --git a/metrics/grafana/dashboard.yaml b/metrics/grafana/dashboard.yaml new file mode 100644 index 00000000..81f9295f --- /dev/null +++ b/metrics/grafana/dashboard.yaml @@ -0,0 +1,12 @@ +apiVersion: 1 + +providers: + - name: "Dashboard provider" + orgId: 1 + type: file + disableDeletion: false + updateIntervalSeconds: 10 + allowUiUpdates: true + options: + path: /var/lib/grafana/dashboards + foldersFromFilesStructure: true diff --git a/metrics/grafana/dashboards/ndc-sqlserver.json b/metrics/grafana/dashboards/ndc-sqlserver.json new file mode 100644 index 00000000..379ff3b9 --- /dev/null +++ b/metrics/grafana/dashboards/ndc-sqlserver.json @@ -0,0 +1,791 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 1, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "description": "This is only the time inside `ndc-sqlserver` rather than including any time spent in ndc-hub functions / axum router.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 7, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "10.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "(ndc_sqlserver_query_total_time_sum - ndc_sqlserver_query_execution_time_sum - ndc_sqlserver_connection_acquisition_wait_time_sum) / ndc_sqlserver_query_total_time_count", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Query time - query execution time - connection acquisition time", + "transformations": [ + { + "id": "concatenate", + "options": {} + } + ], + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "description": "This is only the time inside `ndc-sqlserver` rather than including any time spent in ndc-hub functions / axum router.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 8, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "10.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "(ndc_sqlserver_query_total_time_sum - ndc_sqlserver_query_execution_time_sum) / ndc_sqlserver_query_total_time_count", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Query time - query execution time", + "transformations": [ + { + "id": "concatenate", + "options": {} + } + ], + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 9, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": 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": 10, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(ndc_sqlserver_connection_acquisition_wait_time_sum[$__rate_interval])", + "fullMetaSearch": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Total connection acquisition wait time", + "type": "timeseries" + }, + { + "datasource": {}, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 14, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": 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": 10, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(ndc_sqlserver_query_execution_time_sum[$__rate_interval])", + "fullMetaSearch": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Total query execution time", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 18 + }, + "id": 2, + "options": { + "displayMode": "gradient", + "minVizHeight": 10, + "minVizWidth": 0, + "namePlacement": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showUnfilled": true, + "valueMode": "color" + }, + "pluginVersion": "10.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "ndc_sqlserver_connection_acquisition_wait_time_bucket", + "format": "heatmap", + "fullMetaSearch": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Connection acquisition wait time", + "type": "bargauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "{instance=\"host.docker.internal:8100\", job=\"ndc-sqlserver\", le=\"0.005\"}" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 18 + }, + "id": 6, + "options": { + "displayMode": "gradient", + "minVizHeight": 10, + "minVizWidth": 0, + "namePlacement": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["last"], + "fields": "", + "values": false + }, + "showUnfilled": true, + "valueMode": "color" + }, + "pluginVersion": "10.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "ndc_sqlserver_query_execution_time_bucket", + "format": "heatmap", + "fullMetaSearch": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Query execution time", + "transparent": true, + "type": "bargauge" + }, + { + "datasource": {}, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "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": 8, + "w": 12, + "x": 0, + "y": 28 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "rate(ndc_sqlserver_query_total[1m])", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "ndc_sqlserver_explain_total", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "B", + "useBackend": false + } + ], + "title": "Requests over time", + "type": "timeseries" + }, + { + "datasource": {}, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 14, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": 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": 8, + "w": 12, + "x": 12, + "y": 28 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "ndc_sqlserver_pool_size", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "ndc_sqlserver_pool_max_connections", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "B", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "ndc_sqlserver_pool_min_connections", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "C", + "useBackend": false + } + ], + "title": "Connection pool size", + "type": "timeseries" + } + ], + "refresh": "5s", + "schemaVersion": 38, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-30m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "SQLServer NDC", + "uid": "fa23d46b-326f-4e0a-aa3b-0495f5f984d5", + "version": 2, + "weekStart": "" +} diff --git a/metrics/grafana/datasource.yml b/metrics/grafana/datasource.yml index 4870174e..06a635d8 100644 --- a/metrics/grafana/datasource.yml +++ b/metrics/grafana/datasource.yml @@ -1,9 +1,17 @@ +# For configuration options, see +# https://grafana.com/docs/grafana/latest/administration/provisioning/#example-data-source-config-file + apiVersion: 1 datasources: -- name: Prometheus - type: prometheus - url: http://prometheus:9090 - isDefault: true - access: proxy - editable: true + - name: prometheus + type: prometheus + access: proxy + url: http://prometheus:9090 + isDefault: true + editable: false + - name: jaeger + type: jaeger + access: proxy + url: http://jaeger:16686 + editable: false diff --git a/metrics/prometheus/prometheus.yml b/metrics/prometheus/prometheus.yml index c15612ad..6007b7d2 100644 --- a/metrics/prometheus/prometheus.yml +++ b/metrics/prometheus/prometheus.yml @@ -4,28 +4,27 @@ global: evaluation_interval: 15s alerting: alertmanagers: - - static_configs: - - targets: [] - scheme: http - timeout: 10s - api_version: v1 + - static_configs: + - targets: [] + scheme: http + timeout: 10s + api_version: v1 scrape_configs: -- job_name: prometheus - honor_timestamps: true - scrape_interval: 15s - scrape_timeout: 10s - metrics_path: /metrics - scheme: http - static_configs: - - targets: - - localhost:9090 -- job_name: sqlserver-ndc - honor_timestamps: true - scrape_interval: 15s - scrape_timeout: 10s - metrics_path: /metrics - scheme: http - static_configs: - - targets: - - host.docker.internal:8100 - + - job_name: prometheus + honor_timestamps: true + scrape_interval: 15s + scrape_timeout: 10s + metrics_path: /metrics + scheme: http + static_configs: + - targets: + - localhost:9090 + - job_name: ndc-sqlserver + honor_timestamps: true + scrape_interval: 15s + scrape_timeout: 10s + metrics_path: /metrics + scheme: http + static_configs: + - targets: + - host.docker.internal:8100