From 6adec897b5cdb7ad39936de13fd7734db690a489 Mon Sep 17 00:00:00 2001 From: Giorgi Imerlishvili Date: Wed, 2 Sep 2026 23:46:04 +0400 Subject: [PATCH 1/2] fix(grafana): refuse agg(A or B) instead of dropping an operand `count(node_a or node_b)` emitted `STATS node_a = COUNT(node_a)` and reported the panel as cleanly migrated with no warning, so an operator saw a confident number that counted half of what was asked for. `_ast_aggregate_fragment` defers the `or` operator (unlike every other binary operator, which it refuses) because `or` carries two legitimate reductions that need a resolver parse time does not have: the Grafana same-metric range-window fallback, and the live-absent operand drop. Nothing downstream then claimed the fragment, so the generic fragment_extract/stats_expression fallback rebuilt `agg()` from the fragment's summary fields and shipped it clean. The bare chain keeps both operands via COALESCE / unified `WHERE ... OR`, so the aggregation wrapper was inverting the verdict on the identical expression. Refuse at the stage that has the resolver instead. A new priority-0 classifier marks the fragment not_feasible when no reduction can elect a single operand, and discloses the removal when a reduction does succeed but dropped one -- the same warnings the bare path emits. Both the classifier and colocated_binary_agg_plan now share one reduction helper so they cannot drift into disagreeing about what is renderable. This also closes a second silent drop the issue did not name: same metric with differing label matchers, `count(node_a{job="x"} or node_a{job="y"})`, previously collapsed to `COUNT(node_a)` and discarded the second series set without saying so. Fixes #434 --- docs/pipeline-trace.md | 38 +- docs/sources/datadog-trace.md | 11 +- docs/sources/grafana-trace.md | 1123 ++++++++++------- docs/sources/grafana.md | 3 +- .../adapters/source/grafana/promql.py | 122 +- .../adapters/source/grafana/translate.py | 50 + .../test_issue377_agg_over_binary_operator.py | 15 +- .../test_issue434_agg_over_or_operand_drop.py | 379 ++++++ tests/test_migrate.py | 20 + 9 files changed, 1277 insertions(+), 484 deletions(-) create mode 100644 tests/test_issue434_agg_over_or_operand_drop.py diff --git a/docs/pipeline-trace.md b/docs/pipeline-trace.md index 2f8d3d58..721942cf 100644 --- a/docs/pipeline-trace.md +++ b/docs/pipeline-trace.md @@ -45,13 +45,13 @@ For the exact source-specific stage order, see `docs/architecture.md`, |--------|-----------|--------|----------|----------|--------|--------------|---------|------| | grafana | Diverse Panel Types Test | 11 | 4 | 7 | 0 | 0 | 0 | 1 | | grafana | Express Prometheus Middleware | 23 | 1 | 22 | 0 | 0 | 0 | 1 | -| grafana | Home - Migration Test Lab | 6 | 3 | 2 | 0 | 1 | 0 | 0 | +| grafana | Home - Migration Test Lab | 6 | 2 | 3 | 0 | 1 | 0 | 0 | | grafana | Kubernetes / Views / Global | 26 | 11 | 15 | 0 | 0 | 0 | 4 | | grafana | Kitchen Sink Panel Canary | 16 | 9 | 7 | 0 | 0 | 0 | 0 | | grafana | Multi Pattern Coverage | 10 | 5 | 4 | 0 | 0 | 1 | 1 | -| grafana | Node Exporter Full | 117 | 43 | 74 | 0 | 0 | 0 | 16 | -| grafana | Prometheus 2.0 (by FUSAKLA) | 45 | 29 | 11 | 5 | 0 | 0 | 0 | -| grafana | Redis Dashboard for Prometheus Redis Exporter (helm stable/redis-ha) | 12 | 9 | 3 | 0 | 0 | 0 | 0 | +| grafana | Node Exporter Full | 117 | 40 | 77 | 0 | 0 | 0 | 16 | +| grafana | Prometheus 2.0 (by FUSAKLA) | 45 | 27 | 13 | 5 | 0 | 0 | 0 | +| grafana | Redis Dashboard for Prometheus Redis Exporter (helm stable/redis-ha) | 12 | 8 | 4 | 0 | 0 | 0 | 0 | | datadog | Apache - Overview | 22 | 12 | 9 | 1 | 0 | 0 | 0 | | datadog | Celery Overview | 17 | 10 | 1 | 2 | 0 | 4 | 0 | | datadog | Consul Overview | 27 | 16 | 2 | 4 | 0 | 5 | 0 | @@ -76,8 +76,8 @@ For the exact source-specific stage order, see `docs/architecture.md`, | Verdict | Count | Meaning | |---------|-------|---------| -| **CORRECT** | 122 | Translation is semantically accurate | -| **MINOR_ISSUE** | 406 | Translated with approximations — review recommended | +| **CORRECT** | 121 | Translation is semantically accurate | +| **MINOR_ISSUE** | 407 | Translated with approximations — review recommended | | **EXPECTED_LIMITATION** | 212 | Known unsupported feature — placeholder or skip | @@ -88,19 +88,19 @@ For the exact source-specific stage order, see `docs/architecture.md`, |------:|---------| | 136 | Datadog $scope template variable cannot be represented by a single Kibana control and was omitted; recreate the scope filters manually in Kibana | | 56 | Composited multi-label grouping (instance, job) into a single XY breakdown column | -| 35 | Grafana panel description is not carried into the migrated Kibana panel automatically | -| 27 | Grafana panel has 1 field override(s); verify visual mappings manually | -| 22 | Approximated PromQL arithmetic using same-bucket ES\|QL math | +| 23 | Approximated PromQL arithmetic using same-bucket ES\|QL math | | 20 | Counter referenced without rate(); using LAST_OVER_TIME to preserve raw cumulative value | -| 14 | PromQL series labels were not retained; output is bucket-level and may collapse multiple source series | +| 15 | PromQL series labels were not retained; output is bucket-level and may collapse multiple source series | | 10 | rollup interval is approximated in ES\|QL | | 9 | Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead | | 9 | as_count interval semantics are approximated in ES\|QL | -| 7 | Grafana panel has 2 field override(s); verify visual mappings manually | | 7 | fill(zero) only applies to null values in returned rows; empty buckets may still be omitted | -| 6 | Grafana panel has 18 field override(s); verify visual mappings manually | -| 6 | Grafana panel has 19 field override(s); verify visual mappings manually | -| 5 | Approximated bargauge as bar chart | +| 5 | Applied Grafana transformation 'calculateField' as ES\|QL rewrite | +| 5 | Approximated grouped stat panel as summary table | +| 5 | Visible panel targets did not expose PromQL-compatible expressions | +| 5 | Panel queries InfluxQL/Flux (InfluxDB), not PromQL; this migration translates Prometheus queries, so it must be rebuilt against an Elasticsearch data source | +| 5 | hostmap visual approximated as a grouped table; host dimensions and metric values are preserved, but Datadog tile coloring is not | +| 5 | rate semantics approximated with delta over observed bucket span; when switching Agent→OTel collection, map counters with --metric-map-file (transform/unit_scale) so RATE() emits against the OTel counter field | --- @@ -263,8 +263,8 @@ From the latest trace run: ``` Elements: 740 total (717 panels + 23 rows) Renderable panels: 717 - Migrated: 114 (15.9%) - With warnings: 145 (20.2%) + Migrated: 107 (14.9%) + With warnings: 152 (21.2%) OK: 192 (26.8%) Warning: 167 (23.3%) Requires manual: 29 (4.0%) @@ -275,12 +275,12 @@ Renderable panels: 717 Verdict breakdown: ``` - CORRECT: 122 - MINOR_ISSUE: 406 + CORRECT: 121 + MINOR_ISSUE: 407 EXPECTED_LIMITATION: 212 ``` --- -*Last generated: 2026-08-04 18:38 UTC* +*Last generated: 2026-09-02 14:04 UTC* diff --git a/docs/sources/datadog-trace.md b/docs/sources/datadog-trace.md index 32be3c1b..410d9c9e 100644 --- a/docs/sources/datadog-trace.md +++ b/docs/sources/datadog-trace.md @@ -613,6 +613,7 @@ min:apache.performance.uptime{$host,$scope} FROM metrics-* | WHERE @timestamp >= ?_tstart AND @timestamp <= ?_tend | STATS _bucket_value = MIN(apache_performance_uptime) BY time_bucket = BUCKET(@timestamp, 75, ?_tstart, ?_tend) +| WHERE _bucket_value IS NOT NULL | STATS value = LAST(_bucket_value, time_bucket) ``` @@ -911,6 +912,7 @@ sum:celery.flower.events.count{$task,$endpoint} by {worker,task,type}.as_count() FROM metrics-* | WHERE @timestamp >= ?_tstart AND @timestamp <= ?_tend | STATS query1 = SUM(celery_flower_events_count) BY time_bucket = BUCKET(@timestamp, 75, ?_tstart, ?_tend), worker, task, type +| WHERE query1 IS NOT NULL | STATS query1 = LAST(query1, time_bucket) BY worker, task, type | KEEP worker, task, type, query1 | SORT query1 ASC @@ -1797,6 +1799,7 @@ sum:docker.containers.running{$scope} FROM metrics-* | WHERE @timestamp >= ?_tstart AND @timestamp <= ?_tend | STATS _bucket_value = SUM(docker_containers_running) BY time_bucket = BUCKET(@timestamp, 75, ?_tstart, ?_tend) +| WHERE _bucket_value IS NOT NULL | STATS value = LAST(_bucket_value, time_bucket) ``` @@ -1836,6 +1839,7 @@ sum:docker.containers.stopped{$scope} FROM metrics-* | WHERE @timestamp >= ?_tstart AND @timestamp <= ?_tend | STATS _bucket_value = SUM(docker_containers_stopped) BY time_bucket = BUCKET(@timestamp, 75, ?_tstart, ?_tend) +| WHERE _bucket_value IS NOT NULL | STATS value = LAST(_bucket_value, time_bucket) ``` @@ -3953,6 +3957,7 @@ FROM metrics-* | WHERE @timestamp >= ?_tstart AND @timestamp <= ?_tend | STATS query1 = AVG(mongodb_uptime) BY time_bucket = BUCKET(@timestamp, 75, ?_tstart, ?_tend) | EVAL value = query1 +| WHERE value IS NOT NULL | STATS value = LAST(value, time_bucket) | KEEP value ``` @@ -4292,6 +4297,7 @@ max:mongodb.replset.optime_lag{$scope,$replset_name} by {replset_name} FROM metrics-* | WHERE @timestamp >= ?_tstart AND @timestamp <= ?_tend | STATS query1 = MAX(mongodb_replset_optime_lag) BY time_bucket = BUCKET(@timestamp, 75, ?_tstart, ?_tend), replset_name +| WHERE query1 IS NOT NULL | STATS query1 = LAST(query1, time_bucket) BY replset_name | KEEP replset_name, query1 | SORT query1 DESC @@ -6822,6 +6828,7 @@ avg:redis_memory_used_bytes{*} by {instance} FROM metrics-* | WHERE @timestamp >= ?_tstart AND @timestamp <= ?_tend | STATS _bucket_value = AVG(redis_memory_used_bytes) BY time_bucket = BUCKET(@timestamp, 75, ?_tstart, ?_tend), instance +| WHERE _bucket_value IS NOT NULL | STATS value = LAST(_bucket_value, time_bucket) BY instance | SORT value DESC | LIMIT 100 @@ -7484,6 +7491,7 @@ FROM metrics-* | WHERE @timestamp >= ?_tstart AND @timestamp <= ?_tend | STATS query1 = AVG(system_cpu_user) BY time_bucket = BUCKET(@timestamp, 75, ?_tstart, ?_tend) | EVAL value = query1 +| WHERE value IS NOT NULL | STATS value = LAST(value, time_bucket) | KEEP value ``` @@ -7761,6 +7769,7 @@ FROM metrics-* | WHERE @timestamp >= ?_tstart AND @timestamp <= ?_tend AND host.name == "web01" AND deployment.environment != "staging" | STATS query1 = SUM(system_net_bytes_rcvd) BY time_bucket = BUCKET(@timestamp, 75, ?_tstart, ?_tend) | EVAL value = query1 +| WHERE value IS NOT NULL | STATS value = LAST(value, time_bucket) | KEEP value ``` @@ -7844,4 +7853,4 @@ Every panel marked `not_feasible` in the trace run (5 total): --- -*Last generated: 2026-08-12 13:42 UTC* +*Last generated: 2026-09-02 14:04 UTC* diff --git a/docs/sources/grafana-trace.md b/docs/sources/grafana-trace.md index a393ea4a..59dd1f01 100644 --- a/docs/sources/grafana-trace.md +++ b/docs/sources/grafana-trace.md @@ -63,13 +63,13 @@ queries because the Kibana control applies the filter at dashboard level. |--------|-----------|--------|----------|----------|--------|--------------|---------|------| | grafana | Diverse Panel Types Test | 11 | 4 | 7 | 0 | 0 | 0 | 1 | | grafana | Express Prometheus Middleware | 23 | 1 | 22 | 0 | 0 | 0 | 1 | -| grafana | Home - Migration Test Lab | 6 | 3 | 2 | 0 | 1 | 0 | 0 | +| grafana | Home - Migration Test Lab | 6 | 2 | 3 | 0 | 1 | 0 | 0 | | grafana | Kubernetes / Views / Global | 26 | 11 | 15 | 0 | 0 | 0 | 4 | | grafana | Kitchen Sink Panel Canary | 16 | 9 | 7 | 0 | 0 | 0 | 0 | | grafana | Multi Pattern Coverage | 10 | 5 | 4 | 0 | 0 | 1 | 1 | -| grafana | Node Exporter Full | 117 | 43 | 74 | 0 | 0 | 0 | 16 | -| grafana | Prometheus 2.0 (by FUSAKLA) | 45 | 29 | 11 | 5 | 0 | 0 | 0 | -| grafana | Redis Dashboard for Prometheus Redis Exporter (helm stable/redis-ha) | 12 | 9 | 3 | 0 | 0 | 0 | 0 | +| grafana | Node Exporter Full | 117 | 40 | 77 | 0 | 0 | 0 | 16 | +| grafana | Prometheus 2.0 (by FUSAKLA) | 45 | 27 | 13 | 5 | 0 | 0 | 0 | +| grafana | Redis Dashboard for Prometheus Redis Exporter (helm stable/redis-ha) | 12 | 8 | 4 | 0 | 0 | 0 | 0 | **9 dashboards, 266 panels** audited from `infra/grafana/dashboards/`. @@ -79,8 +79,8 @@ queries because the Kibana control applies the filter at dashboard level. | Verdict | Count | Meaning | |---------|-------|---------| -| **CORRECT** | 11 | Translation is semantically accurate | -| **MINOR_ISSUE** | 238 | Translated with approximations — review recommended | +| **CORRECT** | 10 | Translation is semantically accurate | +| **MINOR_ISSUE** | 239 | Translated with approximations — review recommended | | **EXPECTED_LIMITATION** | 40 | Known unsupported feature — placeholder or skip | @@ -90,20 +90,20 @@ queries because the Kibana control applies the filter at dashboard level. | Count | Warning | |------:|---------| | 56 | Composited multi-label grouping (instance, job) into a single XY breakdown column | -| 35 | Grafana panel description is not carried into the migrated Kibana panel automatically | -| 27 | Grafana panel has 1 field override(s); verify visual mappings manually | -| 22 | Approximated PromQL arithmetic using same-bucket ES\|QL math | +| 23 | Approximated PromQL arithmetic using same-bucket ES\|QL math | | 20 | Counter referenced without rate(); using LAST_OVER_TIME to preserve raw cumulative value | -| 14 | PromQL series labels were not retained; output is bucket-level and may collapse multiple source series | +| 15 | PromQL series labels were not retained; output is bucket-level and may collapse multiple source series | | 9 | Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead | -| 7 | Grafana panel has 2 field override(s); verify visual mappings manually | -| 6 | Grafana panel has 18 field override(s); verify visual mappings manually | -| 6 | Grafana panel has 19 field override(s); verify visual mappings manually | -| 5 | Approximated bargauge as bar chart | | 5 | Applied Grafana transformation 'calculateField' as ES\|QL rewrite | | 5 | Approximated grouped stat panel as summary table | -| 5 | Grafana panel has 20 field override(s); verify visual mappings manually | -| 5 | Grafana panel has 17 field override(s); verify visual mappings manually | +| 5 | Visible panel targets did not expose PromQL-compatible expressions | +| 5 | Panel queries InfluxQL/Flux (InfluxDB), not PromQL; this migration translates Prometheus queries, so it must be rebuilt against an Elasticsearch data source | +| 4 | Grafana panel has 2 transformation(s); manual review recommended | +| 4 | Applied Grafana transformation 'organize' as ES\|QL rewrite | +| 4 | Approximated nested count(count()) as COUNT_DISTINCT(cpu) | +| 3 | Collapsed all series of `node_memory_MemAvailable_bytes` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. | +| 3 | Collapsed all series of `node_memory_MemTotal_bytes` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. | +| 3 | histogram_quantile target field type could not be determined; assumed exponential_histogram and emitted PERCENTILE(). If the field is a classic histogram, pin the mapping or re-run with field capabilities so TO_TDIGEST() is used | --- @@ -121,7 +121,7 @@ queries because the Kibana control applies the filter at dashboard level. | Request Latency Heatmap | `heatmap` → `heatmap` | migrated | **CORRECT** | sum(rate(http_request_duration_seconds_bucket[5m])) by (le) | TS metrics-prometheus-* \| WHERE http_request_duration_seconds_bucket IS NOT NUL... | | Traffic Distribution | `piechart` → `pie` | migrated | **MINOR_ISSUE** | sum(rate(http_requests_total{instance=~"$instance"}[5m])) by (handler) | TS metrics-prometheus-* \| WHERE http_requests_total IS NOT NULL \| STATS http_r... | | Top Endpoints | `barchart` → `bar` | migrated_with_warnings | **CORRECT** | topk(10, sum(rate(http_requests_total[5m])) by (handler)) | TS metrics-prometheus-* \| WHERE http_requests_total IS NOT NULL \| STATS _bucke... | -| CPU Usage | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | 100 - (avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) | TS metrics-prometheus-* \| WHERE mode == "idle" \| WHERE node_cpu_seconds_total ... | +| CPU Usage | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | 100 - (avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) | TS metrics-prometheus-* \| WHERE mode == "idle" \| WHERE node_cpu_seconds_total ... | | Memory Usage | `gauge` → `gauge` | migrated_with_warnings | **MINOR_ISSUE** | (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 | TS metrics-prometheus-* \| WHERE node_memory_MemAvailable_bytes IS NOT NULL OR n... | | Uptime | `stat` → `metric` | migrated_with_warnings | **MINOR_ISSUE** | time() - node_boot_time_seconds | FROM metrics-prometheus-* \| WHERE node_boot_time_seconds IS NOT NULL \| STATS s... | | Disk Usage per Mount | `bargauge` → `bar` | migrated_with_warnings | **MINOR_ISSUE** | 100 - ((node_filesystem_avail_bytes{mountpoint!~".*pods.*"} / node_filesystem_si... | TS metrics-prometheus-* \| WHERE node_filesystem_avail_bytes IS NOT NULL OR node... | @@ -149,6 +149,7 @@ sum(rate(http_request_duration_seconds_bucket[5m])) by (le) - `query_preprocessors` / `grafana_macros` - `query_preprocessors` / `parse_fragment` → parsed fragment family=range_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -177,6 +178,7 @@ sum(rate(http_request_duration_seconds_bucket[5m])) by (le) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -193,7 +195,7 @@ sum(rate(http_request_duration_seconds_bucket[5m])) by (le) ``` TS metrics-prometheus-* | WHERE http_request_duration_seconds_bucket IS NOT NULL -| STATS http_request_duration_seconds_bucket = SUM(RATE(http_request_duration_seconds_bucket)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), le +| STATS http_request_duration_seconds_bucket = SUM(RATE(http_request_duration_seconds_bucket)) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend), le | SORT time_bucket ASC ``` @@ -244,6 +246,7 @@ sum(rate(http_requests_total{instance=~"$instance"}[5m])) by (handler) - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=range_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -272,6 +275,7 @@ sum(rate(http_requests_total{instance=~"$instance"}[5m])) by (handler) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -289,7 +293,7 @@ sum(rate(http_requests_total{instance=~"$instance"}[5m])) by (handler) ``` TS metrics-prometheus-* | WHERE http_requests_total IS NOT NULL -| STATS http_requests_total = SUM(RATE(http_requests_total)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), handler +| STATS http_requests_total = SUM(RATE(http_requests_total)) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend), handler | STATS http_requests_total = LAST(http_requests_total, time_bucket) BY handler | KEEP handler, http_requests_total ``` @@ -344,6 +348,7 @@ topk(10, sum(rate(http_requests_total[5m])) by (handler)) - `query_preprocessors` / `grafana_macros` - `query_preprocessors` / `parse_fragment` → parsed fragment family=topk backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family topk bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -366,6 +371,7 @@ topk(10, sum(rate(http_requests_total[5m])) by (handler)) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -379,7 +385,7 @@ topk(10, sum(rate(http_requests_total[5m])) by (handler)) ``` TS metrics-prometheus-* | WHERE http_requests_total IS NOT NULL -| STATS _bucket_value = SUM(RATE(http_requests_total)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), handler +| STATS _bucket_value = SUM(RATE(http_requests_total)) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend), handler | SORT time_bucket ASC | STATS value = LAST(_bucket_value, time_bucket) BY handler | KEEP handler, value @@ -436,6 +442,7 @@ TS metrics-prometheus-* - `query_preprocessors` / `grafana_macros` - `query_preprocessors` / `parse_fragment` → parsed fragment family=binary_expr backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family binary_expr bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -457,21 +464,22 @@ TS metrics-prometheus-* - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` - `query_validators` / `late_bound_group_control` - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` -- `panel_translators` / `xy_panel` → mapped to line panel +- `panel_translators` / `xy_panel` → mapped to area panel -**Translated (line):** +**Translated (area):** ``` TS metrics-prometheus-* | WHERE mode == "idle" | WHERE node_cpu_seconds_total IS NOT NULL -| STATS node_cpu_seconds_total_mode_idle_rate_avg = AVG(RATE(node_cpu_seconds_total)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS node_cpu_seconds_total_mode_idle_rate_avg = AVG(RATE(node_cpu_seconds_total)) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend) | EVAL node_cpu_seconds_total_mode_idle_rate_avg_calc = node_cpu_seconds_total_mode_idle_rate_avg * 100 | EVAL computed_value = (100 - node_cpu_seconds_total_mode_idle_rate_avg_calc) | KEEP time_bucket, computed_value @@ -492,10 +500,10 @@ TS metrics-prometheus-* **Visual IR:** -- Kibana type: `line` +- Kibana type: `area` - Layout: x=0, y=0, w=24, h=12 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, legend +- Config keys: type, query, dimension, metrics, mode **Operational IR:** @@ -527,6 +535,7 @@ TS metrics-prometheus-* - `query_preprocessors` / `grafana_macros` - `query_preprocessors` / `parse_fragment` → parsed fragment family=binary_expr backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family binary_expr bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -548,6 +557,7 @@ TS metrics-prometheus-* - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -562,7 +572,7 @@ TS metrics-prometheus-* ``` TS metrics-prometheus-* | WHERE node_memory_MemAvailable_bytes IS NOT NULL OR node_memory_MemTotal_bytes IS NOT NULL -| STATS node_memory_MemAvailable_bytes = AVG(LAST_OVER_TIME(node_memory_MemAvailable_bytes)), node_memory_MemTotal_bytes = AVG(LAST_OVER_TIME(node_memory_MemTotal_bytes)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS node_memory_MemAvailable_bytes = AVG(LAST_OVER_TIME(node_memory_MemAvailable_bytes)), node_memory_MemTotal_bytes = AVG(LAST_OVER_TIME(node_memory_MemTotal_bytes)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | EVAL computed_value = ((1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100) | STATS computed_value = LAST(computed_value, time_bucket) | KEEP computed_value @@ -617,6 +627,7 @@ time() - node_boot_time_seconds - `query_preprocessors` / `grafana_macros` - `query_preprocessors` / `parse_fragment` → parsed fragment family=uptime backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family uptime bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -636,6 +647,7 @@ time() - node_boot_time_seconds - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -700,6 +712,7 @@ FROM metrics-prometheus-* - `query_preprocessors` / `grafana_macros` - `query_preprocessors` / `parse_fragment` → parsed fragment family=binary_expr backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family binary_expr bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -721,6 +734,7 @@ FROM metrics-prometheus-* - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -733,7 +747,7 @@ FROM metrics-prometheus-* ``` TS metrics-prometheus-* | WHERE node_filesystem_avail_bytes IS NOT NULL OR node_filesystem_size_bytes IS NOT NULL -| STATS node_filesystem_avail_bytes_mountpoint_pods = AVG(LAST_OVER_TIME(CASE(((NOT (mountpoint RLIKE ".*pods.*") OR (mountpoint IS NULL AND NOT ("" RLIKE ".*pods.*")))), node_filesystem_avail_bytes, NULL), 5m)), node_filesystem_size_bytes = AVG(LAST_OVER_TIME(node_filesystem_size_bytes)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), mountpoint +| STATS node_filesystem_avail_bytes_mountpoint_pods = AVG(LAST_OVER_TIME(CASE(((NOT (mountpoint RLIKE ".*pods.*") OR (mountpoint IS NULL AND NOT ("" RLIKE ".*pods.*")))), node_filesystem_avail_bytes, NULL), 5m)), node_filesystem_size_bytes = AVG(LAST_OVER_TIME(node_filesystem_size_bytes)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), mountpoint | EVAL computed_value = (100 - ((node_filesystem_avail_bytes_mountpoint_pods / node_filesystem_size_bytes) * 100)) | STATS computed_value = LAST(computed_value, time_bucket) BY mountpoint | KEEP mountpoint, computed_value @@ -788,6 +802,7 @@ ALERTS{alertstate="firing"} - `query_preprocessors` / `grafana_macros` - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_metric backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -818,6 +833,7 @@ ALERTS{alertstate="firing"} - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -834,7 +850,7 @@ ALERTS{alertstate="firing"} TS metrics-prometheus-* | WHERE alertstate == "firing" | WHERE ALERTS IS NOT NULL -| STATS ALERTS = MAX(LAST_OVER_TIME(ALERTS)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS ALERTS = MAX(LAST_OVER_TIME(ALERTS)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | STATS time_bucket = MAX(time_bucket), ALERTS = MAX(ALERTS) | KEEP time_bucket, ALERTS | SORT time_bucket ASC @@ -848,6 +864,7 @@ TS metrics-prometheus-* - Source lang: `promql` - Target index: `metrics-prometheus-*` - Output metric: `ALERTS` +- Semantic losses: Collapsed all series of `ALERTS` into a single MAX line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. **Visual IR:** @@ -864,10 +881,15 @@ TS metrics-prometheus-* - targets: 1 - field_overrides: 1 +- field_override_properties: 1 +- non_color_field_override_properties: 1 +- actionable_field_override_properties: 1 -**Warnings:** Grafana panel has 1 field override(s); verify visual mappings manually; ALERTS{} is a Prometheus meta-metric exposing per-alert label sets; ES|QL aggregation collapses individual alerts into a single value +**Warnings:** Grafana panel has 1 field override(s) including 1 non-color override property (e.g. stacking, transforms); verify visual mappings manually; ALERTS{} is a Prometheus meta-metric exposing per-alert label sets; ES|QL aggregation collapses individual alerts into a single value; Collapsed all series of `ALERTS` into a single MAX line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. -**Notes:** Grafana panel has 1 field override(s); verify visual mappings manually +**Semantic losses:** Collapsed all series of `ALERTS` into a single MAX line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. + +**Notes:** Grafana panel has 1 field override(s) including 1 non-color override property (e.g. stacking, transforms); verify visual mappings manually **Verdict:** MINOR_ISSUE @@ -887,6 +909,7 @@ TS metrics-prometheus-* - `query_preprocessors` / `grafana_macros` - `query_preprocessors` / `parse_fragment` → parsed fragment family=logql_stream backend=regex - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family logql_stream bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -904,6 +927,7 @@ TS metrics-prometheus-* - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -976,28 +1000,28 @@ FROM logs-* |-------|---------------------|--------|---------|-------------|-----------------| | HTTP Requests | `row` → `section` | skipped | **EXPECTED_LIMITATION** | — | — | | Count by class | `gauge` → `datatable` | migrated_with_warnings | **MINOR_ISSUE** | sum( http_requests_total{instance="$instance",status=~".{1,2}"} or on() label_... | TS metrics-prometheus-* \| WHERE http_requests_total IS NOT NULL \| STATS series... | -| Request duration average by request | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | http_request_duration_seconds_sum{instance="$instance"} / http_request_duration_... | TS metrics-prometheus-* \| WHERE http_request_duration_seconds_sum IS NOT NULL O... | -| Request count by request | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | http_requests_total{instance="$instance"} | TS metrics-prometheus-* \| WHERE http_requests_total IS NOT NULL \| STATS http_r... | -| Request duration 95th percentile | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | histogram_quantile(0.95, sum by (job, le) (rate(http_request_duration_seconds_bu... | TS metrics-prometheus-* \| WHERE http_request_duration_seconds IS NOT NULL \| ST... | -| Request duration 99th percentile | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | histogram_quantile(0.99, sum by (job, le) (rate(http_request_duration_seconds_bu... | TS metrics-prometheus-* \| WHERE http_request_duration_seconds IS NOT NULL \| ST... | -| Request duration up to 5ms by request | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | http_request_duration_seconds_bucket{instance="$instance",le="0.005"} | TS metrics-prometheus-* \| WHERE le == "0.005" \| WHERE http_request_duration_se... | -| Request duration up to 10ms by request | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | http_request_duration_seconds_bucket{instance="$instance",le="0.01"} | TS metrics-prometheus-* \| WHERE le == "0.01" \| WHERE http_request_duration_sec... | -| Request duration up to 25ms by request | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | http_request_duration_seconds_bucket{instance="$instance",le="0.025"} | TS metrics-prometheus-* \| WHERE le == "0.025" \| WHERE http_request_duration_se... | -| Request duration up to 50ms by request | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | http_request_duration_seconds_bucket{instance="$instance",le="0.05"} | TS metrics-prometheus-* \| WHERE le == "0.05" \| WHERE http_request_duration_sec... | -| Request duration up to 100ms by request | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | http_request_duration_seconds_bucket{instance="$instance",le="0.1"} | TS metrics-prometheus-* \| WHERE le == "0.1" \| WHERE http_request_duration_seco... | -| Request duration up to 250ms by request | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | http_request_duration_seconds_bucket{instance="$instance",le="0.25"} | TS metrics-prometheus-* \| WHERE le == "0.25" \| WHERE http_request_duration_sec... | -| Request duration up to 500ms by request | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | http_request_duration_seconds_bucket{instance="$instance",le="0.5"} | TS metrics-prometheus-* \| WHERE le == "0.5" \| WHERE http_request_duration_seco... | -| Request duration up to 1s by request | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | http_request_duration_seconds_bucket{instance="$instance",le="1"} | TS metrics-prometheus-* \| WHERE (le == "1" OR le == "1.0") \| WHERE http_reques... | -| Request duration up to 2.5s by request | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | http_request_duration_seconds_bucket{instance="$instance",le="2.5"} | TS metrics-prometheus-* \| WHERE le == "2.5" \| WHERE http_request_duration_seco... | -| Request duration up to 5s by request | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | http_request_duration_seconds_bucket{instance="$instance",le="5"} | TS metrics-prometheus-* \| WHERE (le == "5" OR le == "5.0") \| WHERE http_reques... | -| Request duration up to 10s by request | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | http_request_duration_seconds_bucket{instance="$instance",le="10"} | TS metrics-prometheus-* \| WHERE (le == "10" OR le == "10.0") \| WHERE http_requ... | -| Request duration up to Infinity by request | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | http_request_duration_seconds_bucket{instance="$instance",le="+Inf"} | TS metrics-prometheus-* \| WHERE le == "+Inf" \| WHERE http_request_duration_sec... | -| Request rate | `timeseries` → `line` | migrated | **MINOR_ISSUE** | sum(rate(http_requests_total{instance="$instance"}[$__rate_interval])) | TS metrics-prometheus-* \| WHERE http_requests_total IS NOT NULL \| STATS http_r... | -| 4xx by request | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | http_requests_total{instance="$instance",status=~"4.."} | TS metrics-prometheus-* \| WHERE status RLIKE "4.." \| WHERE http_requests_total... | -| 5xx by request | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | http_requests_total{instance="$instance",status=~"5.."} | TS metrics-prometheus-* \| WHERE status RLIKE "5.." \| WHERE http_requests_total... | -| 4xx or 5xx by request | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | http_requests_total{instance="$instance",status=~"4.."} or http_requests_total{i... | TS metrics-prometheus-* \| WHERE (status RLIKE "4.." OR status RLIKE "5..") \| W... | -| Instance CPU usage | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | ((sum(process_cpu_seconds_total{instance="$instance"}) - avg(rate(node_cpu_secon... | TS metrics-prometheus-* \| WHERE process_cpu_seconds_total IS NOT NULL OR node_c... | -| Instance RAM usage | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | sum(process_resident_memory_bytes{instance="$instance"}) / sum(node_memory_MemTo... | TS metrics-prometheus-* \| WHERE process_resident_memory_bytes IS NOT NULL OR no... | +| Request duration average by request | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | http_request_duration_seconds_sum{instance="$instance"} / http_request_duration_... | TS metrics-prometheus-* \| WHERE http_request_duration_seconds_sum IS NOT NULL O... | +| Request count by request | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | http_requests_total{instance="$instance"} | TS metrics-prometheus-* \| WHERE http_requests_total IS NOT NULL \| STATS http_r... | +| Request duration 95th percentile | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | histogram_quantile(0.95, sum by (job, le) (rate(http_request_duration_seconds_bu... | TS metrics-prometheus-* \| WHERE http_request_duration_seconds IS NOT NULL \| ST... | +| Request duration 99th percentile | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | histogram_quantile(0.99, sum by (job, le) (rate(http_request_duration_seconds_bu... | TS metrics-prometheus-* \| WHERE http_request_duration_seconds IS NOT NULL \| ST... | +| Request duration up to 5ms by request | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | http_request_duration_seconds_bucket{instance="$instance",le="0.005"} | TS metrics-prometheus-* \| WHERE le == "0.005" \| WHERE http_request_duration_se... | +| Request duration up to 10ms by request | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | http_request_duration_seconds_bucket{instance="$instance",le="0.01"} | TS metrics-prometheus-* \| WHERE le == "0.01" \| WHERE http_request_duration_sec... | +| Request duration up to 25ms by request | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | http_request_duration_seconds_bucket{instance="$instance",le="0.025"} | TS metrics-prometheus-* \| WHERE le == "0.025" \| WHERE http_request_duration_se... | +| Request duration up to 50ms by request | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | http_request_duration_seconds_bucket{instance="$instance",le="0.05"} | TS metrics-prometheus-* \| WHERE le == "0.05" \| WHERE http_request_duration_sec... | +| Request duration up to 100ms by request | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | http_request_duration_seconds_bucket{instance="$instance",le="0.1"} | TS metrics-prometheus-* \| WHERE le == "0.1" \| WHERE http_request_duration_seco... | +| Request duration up to 250ms by request | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | http_request_duration_seconds_bucket{instance="$instance",le="0.25"} | TS metrics-prometheus-* \| WHERE le == "0.25" \| WHERE http_request_duration_sec... | +| Request duration up to 500ms by request | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | http_request_duration_seconds_bucket{instance="$instance",le="0.5"} | TS metrics-prometheus-* \| WHERE le == "0.5" \| WHERE http_request_duration_seco... | +| Request duration up to 1s by request | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | http_request_duration_seconds_bucket{instance="$instance",le="1"} | TS metrics-prometheus-* \| WHERE (le == "1" OR le == "1.0") \| WHERE http_reques... | +| Request duration up to 2.5s by request | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | http_request_duration_seconds_bucket{instance="$instance",le="2.5"} | TS metrics-prometheus-* \| WHERE le == "2.5" \| WHERE http_request_duration_seco... | +| Request duration up to 5s by request | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | http_request_duration_seconds_bucket{instance="$instance",le="5"} | TS metrics-prometheus-* \| WHERE (le == "5" OR le == "5.0") \| WHERE http_reques... | +| Request duration up to 10s by request | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | http_request_duration_seconds_bucket{instance="$instance",le="10"} | TS metrics-prometheus-* \| WHERE (le == "10" OR le == "10.0") \| WHERE http_requ... | +| Request duration up to Infinity by request | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | http_request_duration_seconds_bucket{instance="$instance",le="+Inf"} | TS metrics-prometheus-* \| WHERE le == "+Inf" \| WHERE http_request_duration_sec... | +| Request rate | `timeseries` → `area` | migrated | **MINOR_ISSUE** | sum(rate(http_requests_total{instance="$instance"}[$__rate_interval])) | TS metrics-prometheus-* \| WHERE http_requests_total IS NOT NULL \| STATS http_r... | +| 4xx by request | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | http_requests_total{instance="$instance",status=~"4.."} | TS metrics-prometheus-* \| WHERE status RLIKE "4.." \| WHERE http_requests_total... | +| 5xx by request | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | http_requests_total{instance="$instance",status=~"5.."} | TS metrics-prometheus-* \| WHERE status RLIKE "5.." \| WHERE http_requests_total... | +| 4xx or 5xx by request | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | http_requests_total{instance="$instance",status=~"4.."} or http_requests_total{i... | TS metrics-prometheus-* \| WHERE (status RLIKE "4.." OR status RLIKE "5..") \| W... | +| Instance CPU usage | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | ((sum(process_cpu_seconds_total{instance="$instance"}) - avg(rate(node_cpu_secon... | TS metrics-prometheus-* \| WHERE process_cpu_seconds_total IS NOT NULL OR node_c... | +| Instance RAM usage | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | sum(process_resident_memory_bytes{instance="$instance"}) / sum(node_memory_MemTo... | TS metrics-prometheus-* \| WHERE process_resident_memory_bytes IS NOT NULL OR no... |
Detailed traces (23 panels) @@ -1039,6 +1063,7 @@ sum( - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -1068,6 +1093,7 @@ sum( - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -1082,7 +1108,7 @@ sum( ``` TS metrics-prometheus-* | WHERE http_requests_total IS NOT NULL -| STATS series_0xx = SUM(LAST_OVER_TIME(CASE((status RLIKE ".{1,2}"), http_requests_total, NULL), 5m)), series_1xx = SUM(LAST_OVER_TIME(CASE((status RLIKE "1.."), http_requests_total, NULL), 5m)), series_2xx = SUM(LAST_OVER_TIME(CASE((status RLIKE "2.."), http_requests_total, NULL), 5m)), series_3xx = SUM(LAST_OVER_TIME(CASE((status RLIKE "3.."), http_requests_total, NULL), 5m)), series_4xx = SUM(LAST_OVER_TIME(CASE((status RLIKE "4.."), http_requests_total, NULL), 5m)), series_5xx = SUM(LAST_OVER_TIME(CASE((status RLIKE "5.."), http_requests_total, NULL), 5m)), Total = SUM(LAST_OVER_TIME(http_requests_total)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS series_0xx = SUM(LAST_OVER_TIME(CASE((status RLIKE ".{1,2}"), http_requests_total, NULL), 5m)), series_1xx = SUM(LAST_OVER_TIME(CASE((status RLIKE "1.."), http_requests_total, NULL), 5m)), series_2xx = SUM(LAST_OVER_TIME(CASE((status RLIKE "2.."), http_requests_total, NULL), 5m)), series_3xx = SUM(LAST_OVER_TIME(CASE((status RLIKE "3.."), http_requests_total, NULL), 5m)), series_4xx = SUM(LAST_OVER_TIME(CASE((status RLIKE "4.."), http_requests_total, NULL), 5m)), series_5xx = SUM(LAST_OVER_TIME(CASE((status RLIKE "5.."), http_requests_total, NULL), 5m)), Total = SUM(LAST_OVER_TIME(http_requests_total)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | STATS series_0xx = MAX(series_0xx), series_1xx = MAX(series_1xx), series_2xx = MAX(series_2xx), series_3xx = MAX(series_3xx), series_4xx = MAX(series_4xx), series_5xx = MAX(series_5xx), Total = MAX(Total) | KEEP series_0xx, series_1xx, series_2xx, series_3xx, series_4xx, series_5xx, Total ``` @@ -1135,6 +1161,7 @@ http_request_duration_seconds_sum{instance="$instance"} / http_request_duration_ - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=binary_expr backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family binary_expr bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -1156,20 +1183,21 @@ http_request_duration_seconds_sum{instance="$instance"} / http_request_duration_ - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` - `query_validators` / `late_bound_group_control` - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` -- `panel_translators` / `xy_panel` → mapped to line panel +- `panel_translators` / `xy_panel` → mapped to area panel -**Translated (line):** +**Translated (area):** ``` TS metrics-prometheus-* | WHERE http_request_duration_seconds_sum IS NOT NULL OR http_request_duration_seconds_count IS NOT NULL -| STATS http_request_duration_seconds_sum_instance = MAX(LAST_OVER_TIME(http_request_duration_seconds_sum)), http_request_duration_seconds_count_instance = MAX(LAST_OVER_TIME(http_request_duration_seconds_count)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), method, path, status +| STATS http_request_duration_seconds_sum_instance = MAX(LAST_OVER_TIME(http_request_duration_seconds_sum)), http_request_duration_seconds_count_instance = MAX(LAST_OVER_TIME(http_request_duration_seconds_count)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), method, path, status | EVAL computed_value = (http_request_duration_seconds_sum_instance / http_request_duration_seconds_count_instance) | EVAL legend = CONCAT(COALESCE(TO_STRING(method), ""), " ", COALESCE(TO_STRING(path), ""), " - ", COALESCE(TO_STRING(status), "")) | KEEP time_bucket, method, path, status, computed_value, legend @@ -1190,10 +1218,10 @@ TS metrics-prometheus-* **Visual IR:** -- Kibana type: `line` +- Kibana type: `area` - Layout: x=0, y=12, w=24, h=12 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, breakdown +- Config keys: type, query, dimension, metrics, mode **Operational IR:** @@ -1225,6 +1253,7 @@ http_requests_total{instance="$instance"} - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_metric backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -1255,20 +1284,21 @@ http_requests_total{instance="$instance"} - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` - `query_validators` / `late_bound_group_control` - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` -- `panel_translators` / `xy_panel` → mapped to line panel +- `panel_translators` / `xy_panel` → mapped to area panel -**Translated (line):** +**Translated (area):** ``` TS metrics-prometheus-* | WHERE http_requests_total IS NOT NULL -| STATS http_requests_total = MAX(LAST_OVER_TIME(http_requests_total)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), method, path, status +| STATS http_requests_total = MAX(LAST_OVER_TIME(http_requests_total)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), method, path, status | EVAL legend = CONCAT(COALESCE(TO_STRING(method), ""), " ", COALESCE(TO_STRING(path), ""), " - ", COALESCE(TO_STRING(status), "")) | SORT time_bucket ASC ``` @@ -1286,10 +1316,10 @@ TS metrics-prometheus-* **Visual IR:** -- Kibana type: `line` +- Kibana type: `area` - Layout: x=24, y=12, w=24, h=12 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, breakdown +- Config keys: type, query, dimension, metrics, mode **Operational IR:** @@ -1321,6 +1351,7 @@ histogram_quantile(0.95, sum by (job, le) (rate(http_request_duration_seconds_bu - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=histogram_quantile backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -1347,20 +1378,21 @@ histogram_quantile(0.95, sum by (job, le) (rate(http_request_duration_seconds_bu - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` - `query_validators` / `late_bound_group_control` - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` -- `panel_translators` / `xy_panel` → mapped to line panel +- `panel_translators` / `xy_panel` → mapped to area panel -**Translated (line):** +**Translated (area):** ``` TS metrics-prometheus-* | WHERE http_request_duration_seconds IS NOT NULL -| STATS http_request_duration_seconds = PERCENTILE(http_request_duration_seconds, 95) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), job +| STATS http_request_duration_seconds = PERCENTILE(http_request_duration_seconds, 95) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend), job | SORT time_bucket ASC ``` @@ -1379,10 +1411,10 @@ TS metrics-prometheus-* **Visual IR:** -- Kibana type: `line` +- Kibana type: `area` - Layout: x=0, y=24, w=24, h=12 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, breakdown +- Config keys: type, query, dimension, metrics, mode **Operational IR:** @@ -1414,6 +1446,7 @@ histogram_quantile(0.99, sum by (job, le) (rate(http_request_duration_seconds_bu - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=histogram_quantile backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -1440,20 +1473,21 @@ histogram_quantile(0.99, sum by (job, le) (rate(http_request_duration_seconds_bu - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` - `query_validators` / `late_bound_group_control` - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` -- `panel_translators` / `xy_panel` → mapped to line panel +- `panel_translators` / `xy_panel` → mapped to area panel -**Translated (line):** +**Translated (area):** ``` TS metrics-prometheus-* | WHERE http_request_duration_seconds IS NOT NULL -| STATS http_request_duration_seconds = PERCENTILE(http_request_duration_seconds, 99) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), job +| STATS http_request_duration_seconds = PERCENTILE(http_request_duration_seconds, 99) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend), job | SORT time_bucket ASC ``` @@ -1472,10 +1506,10 @@ TS metrics-prometheus-* **Visual IR:** -- Kibana type: `line` +- Kibana type: `area` - Layout: x=24, y=24, w=24, h=12 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, breakdown +- Config keys: type, query, dimension, metrics, mode **Operational IR:** @@ -1507,6 +1541,7 @@ http_request_duration_seconds_bucket{instance="$instance",le="0.005"} - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_metric backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -1537,21 +1572,22 @@ http_request_duration_seconds_bucket{instance="$instance",le="0.005"} - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` - `query_validators` / `late_bound_group_control` - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` -- `panel_translators` / `xy_panel` → mapped to line panel +- `panel_translators` / `xy_panel` → mapped to area panel -**Translated (line):** +**Translated (area):** ``` TS metrics-prometheus-* | WHERE le == "0.005" | WHERE http_request_duration_seconds_bucket IS NOT NULL -| STATS http_request_duration_seconds_bucket = MAX(LAST_OVER_TIME(http_request_duration_seconds_bucket)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), method, path, status +| STATS http_request_duration_seconds_bucket = MAX(LAST_OVER_TIME(http_request_duration_seconds_bucket)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), method, path, status | EVAL legend = CONCAT(COALESCE(TO_STRING(method), ""), " ", COALESCE(TO_STRING(path), ""), " - ", COALESCE(TO_STRING(status), "")) | SORT time_bucket ASC ``` @@ -1569,10 +1605,10 @@ TS metrics-prometheus-* **Visual IR:** -- Kibana type: `line` +- Kibana type: `area` - Layout: x=0, y=36, w=24, h=12 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, breakdown +- Config keys: type, query, dimension, metrics, mode **Operational IR:** @@ -1604,6 +1640,7 @@ http_request_duration_seconds_bucket{instance="$instance",le="0.01"} - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_metric backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -1634,21 +1671,22 @@ http_request_duration_seconds_bucket{instance="$instance",le="0.01"} - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` - `query_validators` / `late_bound_group_control` - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` -- `panel_translators` / `xy_panel` → mapped to line panel +- `panel_translators` / `xy_panel` → mapped to area panel -**Translated (line):** +**Translated (area):** ``` TS metrics-prometheus-* | WHERE le == "0.01" | WHERE http_request_duration_seconds_bucket IS NOT NULL -| STATS http_request_duration_seconds_bucket = MAX(LAST_OVER_TIME(http_request_duration_seconds_bucket)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), method, path, status +| STATS http_request_duration_seconds_bucket = MAX(LAST_OVER_TIME(http_request_duration_seconds_bucket)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), method, path, status | EVAL legend = CONCAT(COALESCE(TO_STRING(method), ""), " ", COALESCE(TO_STRING(path), ""), " - ", COALESCE(TO_STRING(status), "")) | SORT time_bucket ASC ``` @@ -1666,10 +1704,10 @@ TS metrics-prometheus-* **Visual IR:** -- Kibana type: `line` +- Kibana type: `area` - Layout: x=24, y=36, w=24, h=12 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, breakdown +- Config keys: type, query, dimension, metrics, mode **Operational IR:** @@ -1701,6 +1739,7 @@ http_request_duration_seconds_bucket{instance="$instance",le="0.025"} - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_metric backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -1731,21 +1770,22 @@ http_request_duration_seconds_bucket{instance="$instance",le="0.025"} - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` - `query_validators` / `late_bound_group_control` - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` -- `panel_translators` / `xy_panel` → mapped to line panel +- `panel_translators` / `xy_panel` → mapped to area panel -**Translated (line):** +**Translated (area):** ``` TS metrics-prometheus-* | WHERE le == "0.025" | WHERE http_request_duration_seconds_bucket IS NOT NULL -| STATS http_request_duration_seconds_bucket = MAX(LAST_OVER_TIME(http_request_duration_seconds_bucket)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), method, path, status +| STATS http_request_duration_seconds_bucket = MAX(LAST_OVER_TIME(http_request_duration_seconds_bucket)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), method, path, status | EVAL legend = CONCAT(COALESCE(TO_STRING(method), ""), " ", COALESCE(TO_STRING(path), ""), " - ", COALESCE(TO_STRING(status), "")) | SORT time_bucket ASC ``` @@ -1763,10 +1803,10 @@ TS metrics-prometheus-* **Visual IR:** -- Kibana type: `line` +- Kibana type: `area` - Layout: x=0, y=48, w=24, h=12 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, breakdown +- Config keys: type, query, dimension, metrics, mode **Operational IR:** @@ -1798,6 +1838,7 @@ http_request_duration_seconds_bucket{instance="$instance",le="0.05"} - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_metric backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -1828,21 +1869,22 @@ http_request_duration_seconds_bucket{instance="$instance",le="0.05"} - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` - `query_validators` / `late_bound_group_control` - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` -- `panel_translators` / `xy_panel` → mapped to line panel +- `panel_translators` / `xy_panel` → mapped to area panel -**Translated (line):** +**Translated (area):** ``` TS metrics-prometheus-* | WHERE le == "0.05" | WHERE http_request_duration_seconds_bucket IS NOT NULL -| STATS http_request_duration_seconds_bucket = MAX(LAST_OVER_TIME(http_request_duration_seconds_bucket)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), method, path, status +| STATS http_request_duration_seconds_bucket = MAX(LAST_OVER_TIME(http_request_duration_seconds_bucket)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), method, path, status | EVAL legend = CONCAT(COALESCE(TO_STRING(method), ""), " ", COALESCE(TO_STRING(path), ""), " - ", COALESCE(TO_STRING(status), "")) | SORT time_bucket ASC ``` @@ -1860,10 +1902,10 @@ TS metrics-prometheus-* **Visual IR:** -- Kibana type: `line` +- Kibana type: `area` - Layout: x=24, y=48, w=24, h=12 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, breakdown +- Config keys: type, query, dimension, metrics, mode **Operational IR:** @@ -1895,6 +1937,7 @@ http_request_duration_seconds_bucket{instance="$instance",le="0.1"} - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_metric backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -1925,21 +1968,22 @@ http_request_duration_seconds_bucket{instance="$instance",le="0.1"} - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` - `query_validators` / `late_bound_group_control` - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` -- `panel_translators` / `xy_panel` → mapped to line panel +- `panel_translators` / `xy_panel` → mapped to area panel -**Translated (line):** +**Translated (area):** ``` TS metrics-prometheus-* | WHERE le == "0.1" | WHERE http_request_duration_seconds_bucket IS NOT NULL -| STATS http_request_duration_seconds_bucket = MAX(LAST_OVER_TIME(http_request_duration_seconds_bucket)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), method, path, status +| STATS http_request_duration_seconds_bucket = MAX(LAST_OVER_TIME(http_request_duration_seconds_bucket)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), method, path, status | EVAL legend = CONCAT(COALESCE(TO_STRING(method), ""), " ", COALESCE(TO_STRING(path), ""), " - ", COALESCE(TO_STRING(status), "")) | SORT time_bucket ASC ``` @@ -1957,10 +2001,10 @@ TS metrics-prometheus-* **Visual IR:** -- Kibana type: `line` +- Kibana type: `area` - Layout: x=0, y=60, w=24, h=12 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, breakdown +- Config keys: type, query, dimension, metrics, mode **Operational IR:** @@ -1992,6 +2036,7 @@ http_request_duration_seconds_bucket{instance="$instance",le="0.25"} - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_metric backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -2022,21 +2067,22 @@ http_request_duration_seconds_bucket{instance="$instance",le="0.25"} - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` - `query_validators` / `late_bound_group_control` - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` -- `panel_translators` / `xy_panel` → mapped to line panel +- `panel_translators` / `xy_panel` → mapped to area panel -**Translated (line):** +**Translated (area):** ``` TS metrics-prometheus-* | WHERE le == "0.25" | WHERE http_request_duration_seconds_bucket IS NOT NULL -| STATS http_request_duration_seconds_bucket = MAX(LAST_OVER_TIME(http_request_duration_seconds_bucket)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), method, path, status +| STATS http_request_duration_seconds_bucket = MAX(LAST_OVER_TIME(http_request_duration_seconds_bucket)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), method, path, status | EVAL legend = CONCAT(COALESCE(TO_STRING(method), ""), " ", COALESCE(TO_STRING(path), ""), " - ", COALESCE(TO_STRING(status), "")) | SORT time_bucket ASC ``` @@ -2054,10 +2100,10 @@ TS metrics-prometheus-* **Visual IR:** -- Kibana type: `line` +- Kibana type: `area` - Layout: x=24, y=60, w=24, h=12 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, breakdown +- Config keys: type, query, dimension, metrics, mode **Operational IR:** @@ -2089,6 +2135,7 @@ http_request_duration_seconds_bucket{instance="$instance",le="0.5"} - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_metric backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -2119,21 +2166,22 @@ http_request_duration_seconds_bucket{instance="$instance",le="0.5"} - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` - `query_validators` / `late_bound_group_control` - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` -- `panel_translators` / `xy_panel` → mapped to line panel +- `panel_translators` / `xy_panel` → mapped to area panel -**Translated (line):** +**Translated (area):** ``` TS metrics-prometheus-* | WHERE le == "0.5" | WHERE http_request_duration_seconds_bucket IS NOT NULL -| STATS http_request_duration_seconds_bucket = MAX(LAST_OVER_TIME(http_request_duration_seconds_bucket)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), method, path, status +| STATS http_request_duration_seconds_bucket = MAX(LAST_OVER_TIME(http_request_duration_seconds_bucket)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), method, path, status | EVAL legend = CONCAT(COALESCE(TO_STRING(method), ""), " ", COALESCE(TO_STRING(path), ""), " - ", COALESCE(TO_STRING(status), "")) | SORT time_bucket ASC ``` @@ -2151,10 +2199,10 @@ TS metrics-prometheus-* **Visual IR:** -- Kibana type: `line` +- Kibana type: `area` - Layout: x=0, y=72, w=24, h=12 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, breakdown +- Config keys: type, query, dimension, metrics, mode **Operational IR:** @@ -2186,6 +2234,7 @@ http_request_duration_seconds_bucket{instance="$instance",le="1"} - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_metric backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -2216,21 +2265,22 @@ http_request_duration_seconds_bucket{instance="$instance",le="1"} - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` - `query_validators` / `late_bound_group_control` - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` -- `panel_translators` / `xy_panel` → mapped to line panel +- `panel_translators` / `xy_panel` → mapped to area panel -**Translated (line):** +**Translated (area):** ``` TS metrics-prometheus-* | WHERE (le == "1" OR le == "1.0") | WHERE http_request_duration_seconds_bucket IS NOT NULL -| STATS http_request_duration_seconds_bucket = MAX(LAST_OVER_TIME(http_request_duration_seconds_bucket)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), method, path, status +| STATS http_request_duration_seconds_bucket = MAX(LAST_OVER_TIME(http_request_duration_seconds_bucket)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), method, path, status | EVAL legend = CONCAT(COALESCE(TO_STRING(method), ""), " ", COALESCE(TO_STRING(path), ""), " - ", COALESCE(TO_STRING(status), "")) | SORT time_bucket ASC ``` @@ -2248,10 +2298,10 @@ TS metrics-prometheus-* **Visual IR:** -- Kibana type: `line` +- Kibana type: `area` - Layout: x=24, y=72, w=24, h=12 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, breakdown +- Config keys: type, query, dimension, metrics, mode **Operational IR:** @@ -2283,6 +2333,7 @@ http_request_duration_seconds_bucket{instance="$instance",le="2.5"} - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_metric backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -2313,21 +2364,22 @@ http_request_duration_seconds_bucket{instance="$instance",le="2.5"} - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` - `query_validators` / `late_bound_group_control` - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` -- `panel_translators` / `xy_panel` → mapped to line panel +- `panel_translators` / `xy_panel` → mapped to area panel -**Translated (line):** +**Translated (area):** ``` TS metrics-prometheus-* | WHERE le == "2.5" | WHERE http_request_duration_seconds_bucket IS NOT NULL -| STATS http_request_duration_seconds_bucket = MAX(LAST_OVER_TIME(http_request_duration_seconds_bucket)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), method, path, status +| STATS http_request_duration_seconds_bucket = MAX(LAST_OVER_TIME(http_request_duration_seconds_bucket)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), method, path, status | EVAL legend = CONCAT(COALESCE(TO_STRING(method), ""), " ", COALESCE(TO_STRING(path), ""), " - ", COALESCE(TO_STRING(status), "")) | SORT time_bucket ASC ``` @@ -2345,10 +2397,10 @@ TS metrics-prometheus-* **Visual IR:** -- Kibana type: `line` +- Kibana type: `area` - Layout: x=0, y=84, w=24, h=12 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, breakdown +- Config keys: type, query, dimension, metrics, mode **Operational IR:** @@ -2380,6 +2432,7 @@ http_request_duration_seconds_bucket{instance="$instance",le="5"} - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_metric backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -2410,21 +2463,22 @@ http_request_duration_seconds_bucket{instance="$instance",le="5"} - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` - `query_validators` / `late_bound_group_control` - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` -- `panel_translators` / `xy_panel` → mapped to line panel +- `panel_translators` / `xy_panel` → mapped to area panel -**Translated (line):** +**Translated (area):** ``` TS metrics-prometheus-* | WHERE (le == "5" OR le == "5.0") | WHERE http_request_duration_seconds_bucket IS NOT NULL -| STATS http_request_duration_seconds_bucket = MAX(LAST_OVER_TIME(http_request_duration_seconds_bucket)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), method, path, status +| STATS http_request_duration_seconds_bucket = MAX(LAST_OVER_TIME(http_request_duration_seconds_bucket)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), method, path, status | EVAL legend = CONCAT(COALESCE(TO_STRING(method), ""), " ", COALESCE(TO_STRING(path), ""), " - ", COALESCE(TO_STRING(status), "")) | SORT time_bucket ASC ``` @@ -2442,10 +2496,10 @@ TS metrics-prometheus-* **Visual IR:** -- Kibana type: `line` +- Kibana type: `area` - Layout: x=24, y=84, w=24, h=12 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, breakdown +- Config keys: type, query, dimension, metrics, mode **Operational IR:** @@ -2484,7 +2538,7 @@ TS metrics-prometheus-* | Scrape Duration by Job | `timeseries` → `line` | migrated | **CORRECT** | scrape_duration_seconds | TS metrics-prometheus-* \| WHERE scrape_duration_seconds IS NOT NULL \| STATS sc... | | Memory Usage % | `gauge` → `gauge` | migrated_with_warnings | **MINOR_ISSUE** | (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 | TS metrics-prometheus-* \| WHERE node_memory_MemAvailable_bytes IS NOT NULL OR n... | | Top Metrics by Series Count | `bargauge` → `markdown` | not_feasible | **EXPECTED_LIMITATION** | topk(10, count by (__name__)({__name__=~".+"})) | — | -| Target Health Status | `table` → `datatable` | migrated | **CORRECT** | up | TS metrics-prometheus-* \| WHERE up IS NOT NULL \| STATS up = MAX(LAST_OVER_TIME... | +| Target Health Status | `table` → `datatable` | migrated_with_warnings | **MINOR_ISSUE** | up | TS metrics-prometheus-* \| WHERE up IS NOT NULL \| STATS up = MAX(LAST_OVER_TIME... |
Detailed traces (5 panels) @@ -2505,6 +2559,7 @@ count(up == 1) - `query_preprocessors` / `grafana_macros` - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -2534,6 +2589,7 @@ count(up == 1) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -2594,6 +2650,7 @@ scrape_duration_seconds - `query_preprocessors` / `grafana_macros` - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_metric backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -2624,6 +2681,7 @@ scrape_duration_seconds - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -2637,7 +2695,7 @@ scrape_duration_seconds ``` TS metrics-prometheus-* | WHERE scrape_duration_seconds IS NOT NULL -| STATS scrape_duration_seconds = MAX(LAST_OVER_TIME(scrape_duration_seconds)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS scrape_duration_seconds = MAX(LAST_OVER_TIME(scrape_duration_seconds)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | SORT time_bucket ASC ``` @@ -2684,6 +2742,7 @@ TS metrics-prometheus-* - `query_preprocessors` / `grafana_macros` - `query_preprocessors` / `parse_fragment` → parsed fragment family=binary_expr backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family binary_expr bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -2705,6 +2764,7 @@ TS metrics-prometheus-* - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -2719,7 +2779,7 @@ TS metrics-prometheus-* ``` TS metrics-prometheus-* | WHERE node_memory_MemAvailable_bytes IS NOT NULL OR node_memory_MemTotal_bytes IS NOT NULL -| STATS node_memory_MemAvailable_bytes = AVG(LAST_OVER_TIME(node_memory_MemAvailable_bytes)), node_memory_MemTotal_bytes = AVG(LAST_OVER_TIME(node_memory_MemTotal_bytes)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS node_memory_MemAvailable_bytes = AVG(LAST_OVER_TIME(node_memory_MemAvailable_bytes)), node_memory_MemTotal_bytes = AVG(LAST_OVER_TIME(node_memory_MemTotal_bytes)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | EVAL computed_value = ((1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100) | STATS computed_value = LAST(computed_value, time_bucket) | KEEP computed_value @@ -2774,6 +2834,7 @@ topk(10, count by (__name__)({__name__=~".+"})) - `query_preprocessors` / `grafana_macros` - `query_preprocessors` / `parse_fragment` → parsed fragment family=unknown backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` → PromQL metric-name introspection via __name__ requires manual redesign **Query IR:** @@ -2822,6 +2883,7 @@ up - `query_preprocessors` / `grafana_macros` - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_metric backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -2852,6 +2914,7 @@ up - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -2867,7 +2930,7 @@ up ``` TS metrics-prometheus-* | WHERE up IS NOT NULL -| STATS up = MAX(LAST_OVER_TIME(up)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS up = MAX(LAST_OVER_TIME(up)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | STATS time_bucket = MAX(time_bucket), up = MAX(up) | KEEP time_bucket, up | SORT time_bucket ASC @@ -2881,6 +2944,7 @@ TS metrics-prometheus-* - Source lang: `promql` - Target index: `metrics-prometheus-*` - Output metric: `up` +- Semantic losses: Collapsed all series of `up` into a single MAX line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. **Visual IR:** @@ -2897,7 +2961,11 @@ TS metrics-prometheus-* - targets: 1 -**Verdict:** CORRECT +**Warnings:** Collapsed all series of `up` into a single MAX line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. + +**Semantic losses:** Collapsed all series of `up` into a single MAX line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. + +**Verdict:** MINOR_ISSUE
@@ -2913,32 +2981,32 @@ TS metrics-prometheus-* | Resources | `row` → `section` | skipped | **EXPECTED_LIMITATION** | — | — | | Kubernetes | `row` → `section` | skipped | **EXPECTED_LIMITATION** | — | — | | Network | `row` → `section` | skipped | **EXPECTED_LIMITATION** | — | — | -| Global CPU Usage | `bargauge` → `bar` | migrated_with_warnings | **MINOR_ISSUE** | avg(sum by (instance, cpu) (rate(node_cpu_seconds_total{mode!~"idle\|iowait\|ste... | TS metrics-prometheus-* \| WHERE kube_pod_container_resource_requests IS NOT NUL... | -| Global RAM Usage | `bargauge` → `bar` | migrated_with_warnings | **MINOR_ISSUE** | sum(node_memory_MemTotal_bytes{cluster="$cluster", job="$job"} - node_memory_Mem... | TS metrics-prometheus-* \| WHERE node_memory_MemTotal_bytes IS NOT NULL OR node_... | +| Global CPU Usage | `bargauge` → `metric` | migrated_with_warnings | **MINOR_ISSUE** | avg(sum by (instance, cpu) (rate(node_cpu_seconds_total{mode!~"idle\|iowait\|ste... | TS metrics-prometheus-* \| WHERE kube_pod_container_resource_requests IS NOT NUL... | +| Global RAM Usage | `bargauge` → `metric` | migrated_with_warnings | **MINOR_ISSUE** | sum(node_memory_MemTotal_bytes{cluster="$cluster", job="$job"} - node_memory_Mem... | TS metrics-prometheus-* \| WHERE node_memory_MemTotal_bytes IS NOT NULL OR node_... | | Nodes | `stat` → `metric` | migrated_with_warnings | **MINOR_ISSUE** | count(count by (node) (kube_node_info{cluster="$cluster"})) | FROM metrics-prometheus-* \| WHERE kube_node_info IS NOT NULL \| STATS kube_node... | -| Kubernetes Resource Count | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | sum(kube_namespace_labels{cluster="$cluster"}) \|\|\| sum(kube_pod_container_sta... | TS metrics-prometheus-* \| WHERE kube_namespace_labels IS NOT NULL OR kube_pod_c... | +| Kubernetes Resource Count | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | sum(kube_namespace_labels{cluster="$cluster"}) \|\|\| sum(kube_pod_container_sta... | TS metrics-prometheus-* \| WHERE kube_namespace_labels IS NOT NULL OR kube_pod_c... | | Namespaces | `stat` → `metric` | migrated | **MINOR_ISSUE** | count(kube_namespace_created{cluster="$cluster"}) | FROM metrics-prometheus-* \| WHERE kube_namespace_created IS NOT NULL \| STATS s... | | CPU Usage | `stat` → `datatable` | migrated_with_warnings | **MINOR_ISSUE** | sum(rate(node_cpu_seconds_total{mode!~"idle\|iowait\|steal", cluster="$cluster",... | TS metrics-prometheus-* \| WHERE node_cpu_seconds_total IS NOT NULL OR windows_c... | | RAM Usage | `stat` → `datatable` | migrated_with_warnings | **MINOR_ISSUE** | sum(node_memory_MemTotal_bytes{cluster="$cluster", job="$job"} - node_memory_Mem... | TS metrics-prometheus-* \| STATS node_memory_MemTotal_bytes_cluster_job_sum_Real... | | Running Pods | `stat` → `metric` | migrated | **MINOR_ISSUE** | sum(kube_pod_status_phase{phase="Running", cluster="$cluster"}) | TS metrics-prometheus-* \| WHERE phase == "Running" \| WHERE kube_pod_status_pha... | -| Cluster CPU Utilization | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | avg(sum by (instance, cpu) (rate(node_cpu_seconds_total{mode!~"idle\|iowait\|ste... | TS metrics-prometheus-* \| WHERE (NOT (mode RLIKE "idle\|iowait\|steal") OR (mod... | -| Cluster Memory Utilization | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | sum(node_memory_MemTotal_bytes{cluster="$cluster", job="$job"} - node_memory_Mem... | TS metrics-prometheus-* \| WHERE node_memory_MemTotal_bytes IS NOT NULL OR node_... | -| CPU Utilization by namespace | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | sum(rate(container_cpu_usage_seconds_total{image!="", cluster="$cluster"}[$__rat... | TS metrics-prometheus-* \| WHERE (image != "" OR (image IS NULL AND "" != "")) \... | -| Memory Utilization by namespace | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | sum(container_memory_working_set_bytes{image!="", cluster="$cluster"}) by (names... | TS metrics-prometheus-* \| WHERE (image != "" OR (image IS NULL AND "" != "")) \... | -| CPU Utilization by instance | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | avg(sum by (instance, cpu) (rate(node_cpu_seconds_total{mode!~"idle\|iowait\|ste... | TS metrics-prometheus-* \| WHERE (NOT (mode RLIKE "idle\|iowait\|steal") OR (mod... | -| Memory Utilization by instance | `timeseries` → `line` | migrated | **MINOR_ISSUE** | sum(node_memory_MemTotal_bytes{cluster="$cluster", job="$job"} - node_memory_Mem... | TS metrics-prometheus-* \| WHERE node_memory_MemTotal_bytes IS NOT NULL OR node_... | -| CPU Throttled seconds by namespace | `timeseries` → `line` | migrated | **MINOR_ISSUE** | sum(rate(container_cpu_cfs_throttled_seconds_total{image!="", cluster="$cluster"... | TS metrics-prometheus-* \| WHERE (image != "" OR (image IS NULL AND "" != "")) \... | -| CPU Core Throttled by instance | `timeseries` → `line` | migrated | **MINOR_ISSUE** | sum(rate(node_cpu_core_throttles_total{cluster="$cluster", job="$job"}[$__rate_i... | TS metrics-prometheus-* \| WHERE node_cpu_core_throttles_total IS NOT NULL \| ST... | -| Kubernetes Pods QoS classes | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | sum(kube_pod_status_qos_class{cluster="$cluster"}) by (qos_class) \|\|\| sum(kub... | TS metrics-prometheus-* \| WHERE kube_pod_status_qos_class IS NOT NULL OR kube_p... | -| Kubernetes Pods Status Reason | `timeseries` → `line` | migrated | **MINOR_ISSUE** | sum(kube_pod_status_reason{cluster="$cluster"}) by (reason) | TS metrics-prometheus-* \| WHERE kube_pod_status_reason IS NOT NULL \| STATS kub... | -| OOM Events by namespace | `timeseries` → `line` | migrated | **MINOR_ISSUE** | sum(increase(container_oom_events_total{cluster="$cluster"}[$__rate_interval])) ... | TS metrics-prometheus-* \| WHERE container_oom_events_total IS NOT NULL \| STATS... | -| Container Restarts by namespace | `timeseries` → `line` | migrated | **MINOR_ISSUE** | sum(increase(kube_pod_container_status_restarts_total{cluster="$cluster"}[$__rat... | TS metrics-prometheus-* \| WHERE kube_pod_container_status_restarts_total IS NOT... | -| Global Network Utilization by device | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | sum(rate(node_network_receive_bytes_total{device!~"(veth\|azv\|lxc).*", cluster=... | TS metrics-prometheus-* \| WHERE (NOT (device RLIKE "(veth\|azv\|lxc).*") OR (de... | -| Network Saturation - Packets dropped | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | sum(rate(node_network_receive_drop_total{cluster="$cluster", job="$job"}[$__rate... | TS metrics-prometheus-* \| WHERE node_network_receive_drop_total IS NOT NULL OR ... | -| Network Received by namespace | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | sum(rate(container_network_receive_bytes_total{cluster="$cluster"}[$__rate_inter... | TS metrics-prometheus-* \| WHERE container_network_receive_bytes_total IS NOT NU... | -| Total Network Received (with all virtual devices) by instance | `timeseries` → `line` | migrated | **MINOR_ISSUE** | sum(rate(node_network_receive_bytes_total{cluster="$cluster", job="$job"}[$__rat... | TS metrics-prometheus-* \| WHERE node_network_receive_bytes_total IS NOT NULL OR... | -| Network Received (without loopback) by instance | `timeseries` → `line` | migrated | **MINOR_ISSUE** | sum(rate(node_network_receive_bytes_total{device!~"(veth\|azv\|lxc\|lo).*", clus... | TS metrics-prometheus-* \| WHERE node_network_receive_bytes_total IS NOT NULL OR... | -| Network Received (loopback only) by instance | `timeseries` → `line` | migrated | **MINOR_ISSUE** | sum(rate(node_network_receive_bytes_total{device="lo", cluster="$cluster", job="... | TS metrics-prometheus-* \| WHERE device == "lo" \| WHERE node_network_receive_by... | +| Cluster CPU Utilization | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | avg(sum by (instance, cpu) (rate(node_cpu_seconds_total{mode!~"idle\|iowait\|ste... | TS metrics-prometheus-* \| WHERE (NOT (mode RLIKE "idle\|iowait\|steal") OR (mod... | +| Cluster Memory Utilization | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | sum(node_memory_MemTotal_bytes{cluster="$cluster", job="$job"} - node_memory_Mem... | TS metrics-prometheus-* \| WHERE node_memory_MemTotal_bytes IS NOT NULL OR node_... | +| CPU Utilization by namespace | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | sum(rate(container_cpu_usage_seconds_total{image!="", cluster="$cluster"}[$__rat... | TS metrics-prometheus-* \| WHERE (image != "" OR (image IS NULL AND "" != "")) \... | +| Memory Utilization by namespace | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | sum(container_memory_working_set_bytes{image!="", cluster="$cluster"}) by (names... | TS metrics-prometheus-* \| WHERE (image != "" OR (image IS NULL AND "" != "")) \... | +| CPU Utilization by instance | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | avg(sum by (instance, cpu) (rate(node_cpu_seconds_total{mode!~"idle\|iowait\|ste... | TS metrics-prometheus-* \| WHERE (NOT (mode RLIKE "idle\|iowait\|steal") OR (mod... | +| Memory Utilization by instance | `timeseries` → `area` | migrated | **MINOR_ISSUE** | sum(node_memory_MemTotal_bytes{cluster="$cluster", job="$job"} - node_memory_Mem... | TS metrics-prometheus-* \| WHERE node_memory_MemTotal_bytes IS NOT NULL OR node_... | +| CPU Throttled seconds by namespace | `timeseries` → `area` | migrated | **MINOR_ISSUE** | sum(rate(container_cpu_cfs_throttled_seconds_total{image!="", cluster="$cluster"... | TS metrics-prometheus-* \| WHERE (image != "" OR (image IS NULL AND "" != "")) \... | +| CPU Core Throttled by instance | `timeseries` → `area` | migrated | **MINOR_ISSUE** | sum(rate(node_cpu_core_throttles_total{cluster="$cluster", job="$job"}[$__rate_i... | TS metrics-prometheus-* \| WHERE node_cpu_core_throttles_total IS NOT NULL \| ST... | +| Kubernetes Pods QoS classes | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | sum(kube_pod_status_qos_class{cluster="$cluster"}) by (qos_class) \|\|\| sum(kub... | TS metrics-prometheus-* \| WHERE kube_pod_status_qos_class IS NOT NULL OR kube_p... | +| Kubernetes Pods Status Reason | `timeseries` → `area` | migrated | **MINOR_ISSUE** | sum(kube_pod_status_reason{cluster="$cluster"}) by (reason) | TS metrics-prometheus-* \| WHERE kube_pod_status_reason IS NOT NULL \| STATS kub... | +| OOM Events by namespace | `timeseries` → `area` | migrated | **MINOR_ISSUE** | sum(increase(container_oom_events_total{cluster="$cluster"}[$__rate_interval])) ... | TS metrics-prometheus-* \| WHERE container_oom_events_total IS NOT NULL \| STATS... | +| Container Restarts by namespace | `timeseries` → `area` | migrated | **MINOR_ISSUE** | sum(increase(kube_pod_container_status_restarts_total{cluster="$cluster"}[$__rat... | TS metrics-prometheus-* \| WHERE kube_pod_container_status_restarts_total IS NOT... | +| Global Network Utilization by device | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | sum(rate(node_network_receive_bytes_total{device!~"(veth\|azv\|lxc).*", cluster=... | TS metrics-prometheus-* \| WHERE (NOT (device RLIKE "(veth\|azv\|lxc).*") OR (de... | +| Network Saturation - Packets dropped | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | sum(rate(node_network_receive_drop_total{cluster="$cluster", job="$job"}[$__rate... | TS metrics-prometheus-* \| WHERE node_network_receive_drop_total IS NOT NULL OR ... | +| Network Received by namespace | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | sum(rate(container_network_receive_bytes_total{cluster="$cluster"}[$__rate_inter... | TS metrics-prometheus-* \| WHERE container_network_receive_bytes_total IS NOT NU... | +| Total Network Received (with all virtual devices) by instance | `timeseries` → `area` | migrated | **MINOR_ISSUE** | sum(rate(node_network_receive_bytes_total{cluster="$cluster", job="$job"}[$__rat... | TS metrics-prometheus-* \| WHERE node_network_receive_bytes_total IS NOT NULL OR... | +| Network Received (without loopback) by instance | `timeseries` → `area` | migrated | **MINOR_ISSUE** | sum(rate(node_network_receive_bytes_total{device!~"(veth\|azv\|lxc\|lo).*", clus... | TS metrics-prometheus-* \| WHERE node_network_receive_bytes_total IS NOT NULL OR... | +| Network Received (loopback only) by instance | `timeseries` → `area` | migrated | **MINOR_ISSUE** | sum(rate(node_network_receive_bytes_total{device="lo", cluster="$cluster", job="... | TS metrics-prometheus-* \| WHERE device == "lo" \| WHERE node_network_receive_by... |
Detailed traces (26 panels) @@ -2959,6 +3027,7 @@ avg(sum by (instance, cpu) (rate(node_cpu_seconds_total{mode!~"idle|iowait|steal - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=binary_expr backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family binary_expr bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -2980,6 +3049,7 @@ avg(sum by (instance, cpu) (rate(node_cpu_seconds_total{mode!~"idle|iowait|steal - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -2987,21 +3057,21 @@ avg(sum by (instance, cpu) (rate(node_cpu_seconds_total{mode!~"idle|iowait|steal - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` → approximated bargauge panel -**Translated (bar):** +**Translated (metric):** ``` TS metrics-prometheus-* | WHERE kube_pod_container_resource_requests IS NOT NULL OR machine_cpu_cores IS NOT NULL OR kube_pod_container_resource_limits IS NOT NULL -| STATS kube_pod_container_resource_requests_Requests_lhs = SUM(CASE((resource == "cpu"), kube_pod_container_resource_requests, NULL)), machine_cpu_cores_Requests_rhs = SUM(machine_cpu_cores), kube_pod_container_resource_limits_Limits_lhs = SUM(CASE((resource == "cpu"), kube_pod_container_resource_limits, NULL)), machine_cpu_cores_Limits_rhs = SUM(machine_cpu_cores) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS kube_pod_container_resource_requests_Requests_lhs = SUM(CASE((resource == "cpu"), kube_pod_container_resource_requests, NULL)), machine_cpu_cores_Requests_rhs = SUM(machine_cpu_cores), kube_pod_container_resource_limits_Limits_lhs = SUM(CASE((resource == "cpu"), kube_pod_container_resource_limits, NULL)), machine_cpu_cores_Limits_rhs = SUM(machine_cpu_cores) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend) | EVAL Requests = (kube_pod_container_resource_requests_Requests_lhs / machine_cpu_cores_Requests_rhs) | EVAL Limits = (kube_pod_container_resource_limits_Limits_lhs / machine_cpu_cores_Limits_rhs) | STATS Requests = MAX(Requests), Limits = MAX(Limits) | KEEP Requests, Limits | EVAL __labels = MV_APPEND("Requests", "Limits"), __values = MV_APPEND(COALESCE(TO_STRING(Requests), ""), COALESCE(TO_STRING(Limits), "")) -| EVAL __pairs = MV_ZIP(__labels, __values, "~") +| EVAL __pairs = MV_ZIP(__labels, __values, "\t") | MV_EXPAND __pairs -| EVAL label = MV_FIRST(SPLIT(__pairs, "~")), value = TO_DOUBLE(MV_LAST(SPLIT(__pairs, "~"))) -| KEEP label, value +| EVAL label = MV_FIRST(SPLIT(__pairs, "\t")), gauge_value = (TO_DOUBLE(MV_LAST(SPLIT(__pairs, "\t")))) * 100 +| KEEP label, gauge_value | SORT label ASC ``` @@ -3014,14 +3084,14 @@ TS metrics-prometheus-* - Source lang: `promql` - Target index: `metrics-prometheus-*` - Output metric: `Requests` -- Semantic losses: Dropped variable-driven label filters during migration, Dropped 2 incompatible target(s); showing 2 mergeable targets (1 of the dropped targets are Windows-specific), Approximated bargauge as bar chart +- Semantic losses: Dropped variable-driven label filters during migration, Dropped 2 incompatible target(s); showing 2 mergeable targets (1 of the dropped targets are Windows-specific), Approximated bargauge as metric tiles **Visual IR:** -- Kibana type: `bar` +- Kibana type: `metric` - Layout: x=0, y=0, w=12, h=16 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, legend +- Config keys: type, query, primary, breakdown, styling **Operational IR:** @@ -3032,9 +3102,9 @@ TS metrics-prometheus-* - targets: 4 - transformations: 2 -**Warnings:** Grafana panel has 2 transformation(s); manual review recommended; Dropped 2 incompatible target(s); showing 2 mergeable targets (1 of the dropped targets are Windows-specific); Approximated bargauge as bar chart +**Warnings:** Grafana panel has 2 transformation(s); manual review recommended; Dropped 2 incompatible target(s); showing 2 mergeable targets (1 of the dropped targets are Windows-specific); Approximated bargauge as metric tiles -**Semantic losses:** Dropped variable-driven label filters during migration; Dropped 2 incompatible target(s); showing 2 mergeable targets (1 of the dropped targets are Windows-specific); Approximated bargauge as bar chart +**Semantic losses:** Dropped variable-driven label filters during migration; Dropped 2 incompatible target(s); showing 2 mergeable targets (1 of the dropped targets are Windows-specific); Approximated bargauge as metric tiles **Notes:** Grafana panel has 2 transformation(s); manual review recommended @@ -3056,6 +3126,7 @@ sum(node_memory_MemTotal_bytes{cluster="$cluster", job="$job"} - node_memory_Mem - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=binary_expr backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family binary_expr bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -3077,6 +3148,7 @@ sum(node_memory_MemTotal_bytes{cluster="$cluster", job="$job"} - node_memory_Mem - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -3084,12 +3156,12 @@ sum(node_memory_MemTotal_bytes{cluster="$cluster", job="$job"} - node_memory_Mem - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` → approximated bargauge panel -**Translated (bar):** +**Translated (metric):** ``` TS metrics-prometheus-* | WHERE node_memory_MemTotal_bytes IS NOT NULL OR node_memory_MemAvailable_bytes IS NOT NULL OR windows_memory_available_bytes IS NOT NULL OR windows_memory_cache_bytes IS NOT NULL OR windows_os_visible_memory_bytes IS NOT NULL OR kube_pod_container_resource_requests IS NOT NULL OR machine_memory_bytes IS NOT NULL OR kube_pod_container_resource_limits IS NOT NULL -| STATS node_memory_MemTotal_bytes_Real_Linux_lhs_lhs = SUM(node_memory_MemTotal_bytes), node_memory_MemAvailable_bytes_Real_Linux_lhs_rhs = SUM(node_memory_MemAvailable_bytes), node_memory_MemTotal_bytes_Real_Linux_rhs = SUM(node_memory_MemTotal_bytes), windows_memory_available_bytes_Real_Windows_lhs_lhs = SUM(windows_memory_available_bytes), windows_memory_cache_bytes_Real_Windows_lhs_rhs = SUM(windows_memory_cache_bytes), windows_os_visible_memory_bytes_Real_Windows_rhs = SUM(windows_os_visible_memory_bytes), kube_pod_container_resource_requests_Requests_lhs = SUM(CASE((resource == "memory"), kube_pod_container_resource_requests, NULL)), machine_memory_bytes_Requests_rhs = SUM(machine_memory_bytes), kube_pod_container_resource_limits_Limits_lhs = SUM(CASE((resource == "memory"), kube_pod_container_resource_limits, NULL)), machine_memory_bytes_Limits_rhs = SUM(machine_memory_bytes) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS node_memory_MemTotal_bytes_Real_Linux_lhs_lhs = SUM(node_memory_MemTotal_bytes), node_memory_MemAvailable_bytes_Real_Linux_lhs_rhs = SUM(node_memory_MemAvailable_bytes), node_memory_MemTotal_bytes_Real_Linux_rhs = SUM(node_memory_MemTotal_bytes), windows_memory_available_bytes_Real_Windows_lhs_lhs = SUM(windows_memory_available_bytes), windows_memory_cache_bytes_Real_Windows_lhs_rhs = SUM(windows_memory_cache_bytes), windows_os_visible_memory_bytes_Real_Windows_rhs = SUM(windows_os_visible_memory_bytes), kube_pod_container_resource_requests_Requests_lhs = SUM(CASE((resource == "memory"), kube_pod_container_resource_requests, NULL)), machine_memory_bytes_Requests_rhs = SUM(machine_memory_bytes), kube_pod_container_resource_limits_Limits_lhs = SUM(CASE((resource == "memory"), kube_pod_container_resource_limits, NULL)), machine_memory_bytes_Limits_rhs = SUM(machine_memory_bytes) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | EVAL Real_Linux = ((node_memory_MemTotal_bytes_Real_Linux_lhs_lhs - node_memory_MemAvailable_bytes_Real_Linux_lhs_rhs) / node_memory_MemTotal_bytes_Real_Linux_rhs) | EVAL Real_Windows = ((windows_memory_available_bytes_Real_Windows_lhs_lhs + windows_memory_cache_bytes_Real_Windows_lhs_rhs) / windows_os_visible_memory_bytes_Real_Windows_rhs) | EVAL Requests = (kube_pod_container_resource_requests_Requests_lhs / machine_memory_bytes_Requests_rhs) @@ -3098,10 +3170,10 @@ TS metrics-prometheus-* | EVAL Real = CASE((CASE(Real_Linux IS NOT NULL, 1, 0) + CASE(Real_Windows IS NOT NULL, 1, 0)) > 0, (COALESCE(Real_Linux, 0) + COALESCE(Real_Windows, 0)) / (CASE(Real_Linux IS NOT NULL, 1, 0) + CASE(Real_Windows IS NOT NULL, 1, 0)), NULL) | KEEP Requests, Limits, Real | EVAL __labels = MV_APPEND(MV_APPEND("Requests", "Limits"), "Real"), __values = MV_APPEND(MV_APPEND(COALESCE(TO_STRING(Requests), ""), COALESCE(TO_STRING(Limits), "")), COALESCE(TO_STRING(Real), "")) -| EVAL __pairs = MV_ZIP(__labels, __values, "~") +| EVAL __pairs = MV_ZIP(__labels, __values, "\t") | MV_EXPAND __pairs -| EVAL label = MV_FIRST(SPLIT(__pairs, "~")), value = TO_DOUBLE(MV_LAST(SPLIT(__pairs, "~"))) -| KEEP label, value +| EVAL label = MV_FIRST(SPLIT(__pairs, "\t")), gauge_value = (TO_DOUBLE(MV_LAST(SPLIT(__pairs, "\t")))) * 100 +| KEEP label, gauge_value | SORT label ASC ``` @@ -3114,14 +3186,14 @@ TS metrics-prometheus-* - Source lang: `promql` - Target index: `metrics-prometheus-*` - Output metric: `Requests` -- Semantic losses: Dropped variable-driven label filters during migration, Approximated bargauge as bar chart +- Semantic losses: Dropped variable-driven label filters during migration, Approximated bargauge as metric tiles **Visual IR:** -- Kibana type: `bar` +- Kibana type: `metric` - Layout: x=12, y=0, w=12, h=16 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, legend +- Config keys: type, query, primary, breakdown, styling **Operational IR:** @@ -3132,9 +3204,9 @@ TS metrics-prometheus-* - targets: 4 - transformations: 2 -**Warnings:** Grafana panel has 2 transformation(s); manual review recommended; Applied Grafana transformation 'calculateField' as ES|QL rewrite; Applied Grafana transformation 'organize' as ES|QL rewrite; Approximated bargauge as bar chart +**Warnings:** Grafana panel has 2 transformation(s); manual review recommended; Applied Grafana transformation 'calculateField' as ES|QL rewrite; Applied Grafana transformation 'organize' as ES|QL rewrite; Approximated bargauge as metric tiles -**Semantic losses:** Dropped variable-driven label filters during migration; Approximated bargauge as bar chart +**Semantic losses:** Dropped variable-driven label filters during migration; Approximated bargauge as metric tiles **Notes:** Grafana panel has 2 transformation(s); manual review recommended @@ -3156,6 +3228,7 @@ count(count by (node) (kube_node_info{cluster="$cluster"})) - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=nested_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family nested_agg bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -3183,6 +3256,7 @@ count(count by (node) (kube_node_info{cluster="$cluster"})) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -3245,6 +3319,7 @@ sum(kube_namespace_labels{cluster="$cluster"}) ||| sum(kube_pod_container_status - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -3274,20 +3349,21 @@ sum(kube_namespace_labels{cluster="$cluster"}) ||| sum(kube_pod_container_status - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` - `query_validators` / `late_bound_group_control` - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` -- `panel_translators` / `xy_panel` → mapped to line panel +- `panel_translators` / `xy_panel` → mapped to area panel -**Translated (line):** +**Translated (area):** ``` TS metrics-prometheus-* | WHERE kube_namespace_labels IS NOT NULL OR kube_pod_container_status_running IS NOT NULL OR kube_pod_status_phase IS NOT NULL OR kube_service_info IS NOT NULL OR kube_endpoint_info IS NOT NULL OR kube_ingress_info IS NOT NULL OR kube_deployment_labels IS NOT NULL OR kube_statefulset_labels IS NOT NULL OR kube_daemonset_labels IS NOT NULL OR kube_persistentvolumeclaim_info IS NOT NULL OR kube_hpa_labels IS NOT NULL OR kube_configmap_info IS NOT NULL OR kube_secret_info IS NOT NULL OR kube_networkpolicy_labels IS NOT NULL OR kube_node_info IS NOT NULL -| STATS Namespaces = SUM(kube_namespace_labels), Running_Containers = SUM(kube_pod_container_status_running), Running_Pods = SUM(CASE((phase == "Running"), kube_pod_status_phase, NULL)), Services = SUM(kube_service_info), Endpoints = SUM(kube_endpoint_info), Ingresses = SUM(kube_ingress_info), Deployments = SUM(kube_deployment_labels), Statefulsets = SUM(kube_statefulset_labels), Daemonsets = SUM(kube_daemonset_labels), Persistent_Volume_Claims = SUM(kube_persistentvolumeclaim_info), Horizontal_Pod_Autoscalers = SUM(kube_hpa_labels), Configmaps = SUM(kube_configmap_info), Secrets = SUM(kube_secret_info), Network_Policies = SUM(kube_networkpolicy_labels), Nodes = COUNT_DISTINCT(node) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS Namespaces = SUM(kube_namespace_labels), Running_Containers = SUM(kube_pod_container_status_running), Running_Pods = SUM(CASE((phase == "Running"), kube_pod_status_phase, NULL)), Services = SUM(kube_service_info), Endpoints = SUM(kube_endpoint_info), Ingresses = SUM(kube_ingress_info), Deployments = SUM(kube_deployment_labels), Statefulsets = SUM(kube_statefulset_labels), Daemonsets = SUM(kube_daemonset_labels), Persistent_Volume_Claims = SUM(kube_persistentvolumeclaim_info), Horizontal_Pod_Autoscalers = SUM(kube_hpa_labels), Configmaps = SUM(kube_configmap_info), Secrets = SUM(kube_secret_info), Network_Policies = SUM(kube_networkpolicy_labels), Nodes = COUNT_DISTINCT(node) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | KEEP time_bucket, Namespaces, Running_Containers, Running_Pods, Services, Endpoints, Ingresses, Deployments, Statefulsets, Daemonsets, Persistent_Volume_Claims, Horizontal_Pod_Autoscalers, Configmaps, Secrets, Network_Policies, Nodes | SORT time_bucket ASC ``` @@ -3306,10 +3382,10 @@ TS metrics-prometheus-* **Visual IR:** -- Kibana type: `line` +- Kibana type: `area` - Layout: x=28, y=0, w=20, h=24 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, legend +- Config keys: type, query, dimension, metrics, mode **Operational IR:** @@ -3341,6 +3417,7 @@ count(kube_namespace_created{cluster="$cluster"}) - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -3370,6 +3447,7 @@ count(kube_namespace_created{cluster="$cluster"}) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -3431,6 +3509,7 @@ sum(rate(node_cpu_seconds_total{mode!~"idle|iowait|steal", cluster="$cluster", j - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=range_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -3459,6 +3538,7 @@ sum(rate(node_cpu_seconds_total{mode!~"idle|iowait|steal", cluster="$cluster", j - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -3470,7 +3550,7 @@ sum(rate(node_cpu_seconds_total{mode!~"idle|iowait|steal", cluster="$cluster", j ``` TS metrics-prometheus-* | WHERE node_cpu_seconds_total IS NOT NULL OR windows_cpu_time_total IS NOT NULL OR kube_pod_container_resource_requests IS NOT NULL OR kube_pod_container_resource_limits IS NOT NULL OR machine_cpu_cores IS NOT NULL -| STATS Real_Linux = SUM(CASE(((NOT (mode RLIKE "idle|iowait|steal") OR (mode IS NULL AND NOT ("" RLIKE "idle|iowait|steal")))), RATE(node_cpu_seconds_total), NULL)), Real_Windows = SUM(CASE(((mode != "idle" OR (mode IS NULL AND "" != "idle"))), RATE(windows_cpu_time_total), NULL)), Requests = SUM(LAST_OVER_TIME(CASE((resource == "cpu"), kube_pod_container_resource_requests, NULL), 5m)), Limits = SUM(LAST_OVER_TIME(CASE((resource == "cpu"), kube_pod_container_resource_limits, NULL), 5m)), Total = SUM(CASE(true, LAST_OVER_TIME(machine_cpu_cores, 5m), NULL)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS Real_Linux = SUM(CASE(((NOT (mode RLIKE "idle|iowait|steal") OR (mode IS NULL AND NOT ("" RLIKE "idle|iowait|steal")))), RATE(node_cpu_seconds_total), NULL)), Real_Windows = SUM(CASE(((mode != "idle" OR (mode IS NULL AND "" != "idle"))), RATE(windows_cpu_time_total), NULL)), Requests = SUM(LAST_OVER_TIME(CASE((resource == "cpu"), kube_pod_container_resource_requests, NULL), 5m)), Limits = SUM(LAST_OVER_TIME(CASE((resource == "cpu"), kube_pod_container_resource_limits, NULL), 5m)), Total = SUM(CASE(true, LAST_OVER_TIME(machine_cpu_cores, 5m), NULL)) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend) | STATS Real_Linux = MAX(Real_Linux), Real_Windows = MAX(Real_Windows), Requests = MAX(Requests), Limits = MAX(Limits), Total = MAX(Total) | EVAL Real = COALESCE(Real_Linux, 0) + COALESCE(Real_Windows, 0) | KEEP Requests, Limits, Total, Real @@ -3529,6 +3609,7 @@ sum(node_memory_MemTotal_bytes{cluster="$cluster", job="$job"} - node_memory_Mem - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=binary_expr backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family binary_expr bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -3550,6 +3631,7 @@ sum(node_memory_MemTotal_bytes{cluster="$cluster", job="$job"} - node_memory_Mem - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -3560,7 +3642,7 @@ sum(node_memory_MemTotal_bytes{cluster="$cluster", job="$job"} - node_memory_Mem ``` TS metrics-prometheus-* -| STATS node_memory_MemTotal_bytes_cluster_job_sum_Real_Linux = SUM(CASE((node_memory_MemTotal_bytes IS NOT NULL OR node_memory_MemAvailable_bytes IS NOT NULL), node_memory_MemTotal_bytes, NULL)), node_memory_MemAvailable_bytes_cluster_job_sum_Real_Linux = SUM(CASE((node_memory_MemTotal_bytes IS NOT NULL OR node_memory_MemAvailable_bytes IS NOT NULL), node_memory_MemAvailable_bytes, NULL)), computed_value_Real_Windows = SUM(CASE((windows_os_visible_memory_bytes IS NOT NULL) and (windows_memory_available_bytes IS NOT NULL) and (windows_memory_cache_bytes IS NOT NULL), ((windows_os_visible_memory_bytes - windows_memory_available_bytes) - windows_memory_cache_bytes), NULL)), kube_pod_container_resource_requests_Requests = SUM(CASE((resource == "memory") and (kube_pod_container_resource_requests IS NOT NULL), kube_pod_container_resource_requests, NULL)), kube_pod_container_resource_limits_Limits = SUM(CASE((resource == "memory") and (kube_pod_container_resource_limits IS NOT NULL), kube_pod_container_resource_limits, NULL)), machine_memory_bytes_Total = SUM(CASE((machine_memory_bytes IS NOT NULL), machine_memory_bytes, NULL)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS node_memory_MemTotal_bytes_cluster_job_sum_Real_Linux = SUM(CASE((node_memory_MemTotal_bytes IS NOT NULL OR node_memory_MemAvailable_bytes IS NOT NULL), node_memory_MemTotal_bytes, NULL)), node_memory_MemAvailable_bytes_cluster_job_sum_Real_Linux = SUM(CASE((node_memory_MemTotal_bytes IS NOT NULL OR node_memory_MemAvailable_bytes IS NOT NULL), node_memory_MemAvailable_bytes, NULL)), computed_value_Real_Windows = SUM(CASE((windows_os_visible_memory_bytes IS NOT NULL) and (windows_memory_available_bytes IS NOT NULL) and (windows_memory_cache_bytes IS NOT NULL), ((windows_os_visible_memory_bytes - windows_memory_available_bytes) - windows_memory_cache_bytes), NULL)), kube_pod_container_resource_requests_Requests = SUM(CASE((resource == "memory") and (kube_pod_container_resource_requests IS NOT NULL), kube_pod_container_resource_requests, NULL)), kube_pod_container_resource_limits_Limits = SUM(CASE((resource == "memory") and (kube_pod_container_resource_limits IS NOT NULL), kube_pod_container_resource_limits, NULL)), machine_memory_bytes_Total = SUM(CASE((machine_memory_bytes IS NOT NULL), machine_memory_bytes, NULL)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | EVAL Real_Linux = (node_memory_MemTotal_bytes_cluster_job_sum_Real_Linux - node_memory_MemAvailable_bytes_cluster_job_sum_Real_Linux) | EVAL Real_Windows = computed_value_Real_Windows | EVAL Requests = kube_pod_container_resource_requests_Requests @@ -3599,7 +3681,7 @@ TS metrics-prometheus-* - targets: 5 - transformations: 2 -**Warnings:** Grafana panel has 2 transformation(s); manual review recommended; Fused multi-target panel from independently translated ES|QL queries; Per-element arithmetic between co-located metrics evaluated per document before aggregation (exact for Prometheus layouts that store one document per label-set; PromQL's all-label matching guarantees the operands align); Applied Grafana transformation 'calculateField' as ES|QL rewrite; Applied Grafana transformation 'organize' as ES|QL rewrite +**Warnings:** Grafana panel has 2 transformation(s); manual review recommended; Per-element arithmetic between co-located metrics evaluated per document before aggregation (exact for Prometheus layouts that store one document per label-set; PromQL's all-label matching guarantees the operands align); Applied Grafana transformation 'calculateField' as ES|QL rewrite; Applied Grafana transformation 'organize' as ES|QL rewrite; Approximated grouped stat panel as summary table **Semantic losses:** Dropped variable-driven label filters during migration; Approximated grouped stat panel as summary table @@ -3623,6 +3705,7 @@ sum(kube_pod_status_phase{phase="Running", cluster="$cluster"}) - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -3652,6 +3735,7 @@ sum(kube_pod_status_phase{phase="Running", cluster="$cluster"}) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -3664,7 +3748,7 @@ sum(kube_pod_status_phase{phase="Running", cluster="$cluster"}) TS metrics-prometheus-* | WHERE phase == "Running" | WHERE kube_pod_status_phase IS NOT NULL -| STATS kube_pod_status_phase = SUM(kube_pod_status_phase) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS kube_pod_status_phase = SUM(kube_pod_status_phase) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | STATS kube_pod_status_phase = LAST(kube_pod_status_phase, time_bucket) | KEEP kube_pod_status_phase ``` @@ -3715,6 +3799,7 @@ avg(sum by (instance, cpu) (rate(node_cpu_seconds_total{mode!~"idle|iowait|steal - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=nested_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family nested_agg bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -3742,21 +3827,22 @@ avg(sum by (instance, cpu) (rate(node_cpu_seconds_total{mode!~"idle|iowait|steal - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` - `query_validators` / `late_bound_group_control` - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` -- `panel_translators` / `xy_panel` → mapped to line panel +- `panel_translators` / `xy_panel` → mapped to area panel -**Translated (line):** +**Translated (area):** ``` TS metrics-prometheus-* | WHERE (NOT (mode RLIKE "idle|iowait|steal") OR (mode IS NULL AND NOT ("" RLIKE "idle|iowait|steal"))) | WHERE node_cpu_seconds_total IS NOT NULL -| STATS inner_val = SUM(RATE(node_cpu_seconds_total)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance, cpu +| STATS inner_val = SUM(RATE(node_cpu_seconds_total)) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend), instance, cpu | STATS node_cpu_seconds_total_avg = AVG(inner_val) BY time_bucket | SORT time_bucket ASC ``` @@ -3777,10 +3863,10 @@ TS metrics-prometheus-* **Visual IR:** -- Kibana type: `line` +- Kibana type: `area` - Layout: x=0, y=0, w=24, h=12 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, legend +- Config keys: type, query, dimension, metrics, mode **Operational IR:** @@ -3815,6 +3901,7 @@ sum(node_memory_MemTotal_bytes{cluster="$cluster", job="$job"} - node_memory_Mem - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=binary_expr backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family binary_expr bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -3836,20 +3923,21 @@ sum(node_memory_MemTotal_bytes{cluster="$cluster", job="$job"} - node_memory_Mem - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` - `query_validators` / `late_bound_group_control` - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` -- `panel_translators` / `xy_panel` → mapped to line panel +- `panel_translators` / `xy_panel` → mapped to area panel -**Translated (line):** +**Translated (area):** ``` TS metrics-prometheus-* | WHERE node_memory_MemTotal_bytes IS NOT NULL OR node_memory_MemAvailable_bytes IS NOT NULL OR windows_os_visible_memory_bytes IS NOT NULL OR windows_memory_available_bytes IS NOT NULL -| STATS node_memory_MemTotal_bytes_Linux_lhs_lhs = SUM(node_memory_MemTotal_bytes), node_memory_MemAvailable_bytes_Linux_lhs_rhs = SUM(node_memory_MemAvailable_bytes), node_memory_MemTotal_bytes_Linux_rhs = SUM(node_memory_MemTotal_bytes), windows_os_visible_memory_bytes_Windows_lhs_lhs = SUM(windows_os_visible_memory_bytes), windows_memory_available_bytes_Windows_lhs_rhs = SUM(windows_memory_available_bytes), windows_os_visible_memory_bytes_Windows_rhs = SUM(windows_os_visible_memory_bytes) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS node_memory_MemTotal_bytes_Linux_lhs_lhs = SUM(node_memory_MemTotal_bytes), node_memory_MemAvailable_bytes_Linux_lhs_rhs = SUM(node_memory_MemAvailable_bytes), node_memory_MemTotal_bytes_Linux_rhs = SUM(node_memory_MemTotal_bytes), windows_os_visible_memory_bytes_Windows_lhs_lhs = SUM(windows_os_visible_memory_bytes), windows_memory_available_bytes_Windows_lhs_rhs = SUM(windows_memory_available_bytes), windows_os_visible_memory_bytes_Windows_rhs = SUM(windows_os_visible_memory_bytes) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | EVAL Linux = ((node_memory_MemTotal_bytes_Linux_lhs_lhs - node_memory_MemAvailable_bytes_Linux_lhs_rhs) / node_memory_MemTotal_bytes_Linux_rhs) | EVAL Windows = ((windows_os_visible_memory_bytes_Windows_lhs_lhs - windows_memory_available_bytes_Windows_lhs_rhs) / windows_os_visible_memory_bytes_Windows_rhs) | EVAL Memory_usage_in = CASE((CASE(Linux IS NOT NULL, 1, 0) + CASE(Windows IS NOT NULL, 1, 0)) > 0, (COALESCE(Linux, 0) + COALESCE(Windows, 0)) / (CASE(Linux IS NOT NULL, 1, 0) + CASE(Windows IS NOT NULL, 1, 0)), NULL) @@ -3871,10 +3959,10 @@ TS metrics-prometheus-* **Visual IR:** -- Kibana type: `line` +- Kibana type: `area` - Layout: x=24, y=0, w=24, h=12 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, legend +- Config keys: type, query, dimension, metrics, mode **Operational IR:** @@ -3911,6 +3999,7 @@ sum(rate(container_cpu_usage_seconds_total{image!="", cluster="$cluster"}[$__rat - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=binary_expr backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family binary_expr bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -3932,21 +4021,22 @@ sum(rate(container_cpu_usage_seconds_total{image!="", cluster="$cluster"}[$__rat - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` - `query_validators` / `late_bound_group_control` - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` -- `panel_translators` / `xy_panel` → mapped to line panel +- `panel_translators` / `xy_panel` → mapped to area panel -**Translated (line):** +**Translated (area):** ``` TS metrics-prometheus-* | WHERE (image != "" OR (image IS NULL AND "" != "")) | WHERE container_cpu_usage_seconds_total IS NOT NULL -| STATS container_cpu_usage_seconds_total_image_rate_sum = SUM(RATE(container_cpu_usage_seconds_total)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), namespace +| STATS container_cpu_usage_seconds_total_image_rate_sum = SUM(RATE(container_cpu_usage_seconds_total)) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend), namespace | EVAL computed_value = container_cpu_usage_seconds_total_image_rate_sum | KEEP time_bucket, namespace, computed_value | SORT time_bucket ASC @@ -3966,10 +4056,10 @@ TS metrics-prometheus-* **Visual IR:** -- Kibana type: `line` +- Kibana type: `area` - Layout: x=0, y=12, w=24, h=12 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, breakdown +- Config keys: type, query, dimension, metrics, mode **Operational IR:** @@ -4003,6 +4093,7 @@ sum(container_memory_working_set_bytes{image!="", cluster="$cluster"}) by (names - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=binary_expr backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family binary_expr bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -4024,21 +4115,22 @@ sum(container_memory_working_set_bytes{image!="", cluster="$cluster"}) by (names - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` - `query_validators` / `late_bound_group_control` - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` -- `panel_translators` / `xy_panel` → mapped to line panel +- `panel_translators` / `xy_panel` → mapped to area panel -**Translated (line):** +**Translated (area):** ``` TS metrics-prometheus-* | WHERE (image != "" OR (image IS NULL AND "" != "")) | WHERE container_memory_working_set_bytes IS NOT NULL -| STATS container_memory_working_set_bytes_image_sum = SUM(container_memory_working_set_bytes) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), namespace +| STATS container_memory_working_set_bytes_image_sum = SUM(container_memory_working_set_bytes) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), namespace | EVAL computed_value = container_memory_working_set_bytes_image_sum | KEEP time_bucket, namespace, computed_value | SORT time_bucket ASC @@ -4058,10 +4150,10 @@ TS metrics-prometheus-* **Visual IR:** -- Kibana type: `line` +- Kibana type: `area` - Layout: x=24, y=12, w=24, h=12 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, breakdown +- Config keys: type, query, dimension, metrics, mode **Operational IR:** @@ -4093,6 +4185,7 @@ avg(sum by (instance, cpu) (rate(node_cpu_seconds_total{mode!~"idle|iowait|steal - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=nested_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family nested_agg bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -4120,21 +4213,22 @@ avg(sum by (instance, cpu) (rate(node_cpu_seconds_total{mode!~"idle|iowait|steal - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` - `query_validators` / `late_bound_group_control` - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` -- `panel_translators` / `xy_panel` → mapped to line panel +- `panel_translators` / `xy_panel` → mapped to area panel -**Translated (line):** +**Translated (area):** ``` TS metrics-prometheus-* | WHERE (NOT (mode RLIKE "idle|iowait|steal") OR (mode IS NULL AND NOT ("" RLIKE "idle|iowait|steal"))) | WHERE node_cpu_seconds_total IS NOT NULL -| STATS inner_val = SUM(RATE(node_cpu_seconds_total)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance, cpu +| STATS inner_val = SUM(RATE(node_cpu_seconds_total)) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend), instance, cpu | STATS node_cpu_seconds_total_avg = AVG(inner_val) BY time_bucket | SORT time_bucket ASC ``` @@ -4156,10 +4250,10 @@ TS metrics-prometheus-* **Visual IR:** -- Kibana type: `line` +- Kibana type: `area` - Layout: x=0, y=24, w=24, h=12 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, legend +- Config keys: type, query, dimension, metrics, mode **Operational IR:** @@ -4191,6 +4285,7 @@ sum(node_memory_MemTotal_bytes{cluster="$cluster", job="$job"} - node_memory_Mem - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=binary_expr backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family binary_expr bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -4212,20 +4307,21 @@ sum(node_memory_MemTotal_bytes{cluster="$cluster", job="$job"} - node_memory_Mem - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` - `query_validators` / `late_bound_group_control` - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` -- `panel_translators` / `xy_panel` → mapped to line panel +- `panel_translators` / `xy_panel` → mapped to area panel -**Translated (line):** +**Translated (area):** ``` TS metrics-prometheus-* | WHERE node_memory_MemTotal_bytes IS NOT NULL OR node_memory_MemAvailable_bytes IS NOT NULL OR windows_os_visible_memory_bytes IS NOT NULL OR windows_memory_available_bytes IS NOT NULL -| STATS node_memory_MemTotal_bytes_Linux_lhs = SUM(node_memory_MemTotal_bytes), node_memory_MemAvailable_bytes_Linux_rhs = SUM(node_memory_MemAvailable_bytes), windows_os_visible_memory_bytes_Windows_lhs = SUM(windows_os_visible_memory_bytes), windows_memory_available_bytes_Windows_rhs = SUM(windows_memory_available_bytes) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance +| STATS node_memory_MemTotal_bytes_Linux_lhs = SUM(node_memory_MemTotal_bytes), node_memory_MemAvailable_bytes_Linux_rhs = SUM(node_memory_MemAvailable_bytes), windows_os_visible_memory_bytes_Windows_lhs = SUM(windows_os_visible_memory_bytes), windows_memory_available_bytes_Windows_rhs = SUM(windows_memory_available_bytes) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), instance | EVAL instance = (node_memory_MemTotal_bytes_Linux_lhs - node_memory_MemAvailable_bytes_Linux_rhs) | EVAL instance_Windows = (windows_os_visible_memory_bytes_Windows_lhs - windows_memory_available_bytes_Windows_rhs) | KEEP time_bucket, instance, instance_Windows @@ -4247,10 +4343,10 @@ TS metrics-prometheus-* **Visual IR:** -- Kibana type: `line` +- Kibana type: `area` - Layout: x=24, y=24, w=24, h=12 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, breakdown +- Config keys: type, query, dimension, metrics, mode **Operational IR:** @@ -4280,6 +4376,7 @@ sum(rate(container_cpu_cfs_throttled_seconds_total{image!="", cluster="$cluster" - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=range_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -4308,21 +4405,22 @@ sum(rate(container_cpu_cfs_throttled_seconds_total{image!="", cluster="$cluster" - `query_postprocessors` / `post_filter` → applied post-aggregation filter > 0 - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` - `query_validators` / `late_bound_group_control` - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` -- `panel_translators` / `xy_panel` → mapped to line panel +- `panel_translators` / `xy_panel` → mapped to area panel -**Translated (line):** +**Translated (area):** ``` TS metrics-prometheus-* | WHERE (image != "" OR (image IS NULL AND "" != "")) | WHERE container_cpu_cfs_throttled_seconds_total IS NOT NULL -| STATS container_cpu_cfs_throttled_seconds_total = SUM(RATE(container_cpu_cfs_throttled_seconds_total)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), namespace +| STATS container_cpu_cfs_throttled_seconds_total = SUM(RATE(container_cpu_cfs_throttled_seconds_total)) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend), namespace | WHERE container_cpu_cfs_throttled_seconds_total > 0 | SORT time_bucket ASC ``` @@ -4344,10 +4442,10 @@ TS metrics-prometheus-* **Visual IR:** -- Kibana type: `line` +- Kibana type: `area` - Layout: x=0, y=36, w=24, h=12 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, breakdown +- Config keys: type, query, dimension, metrics, mode **Operational IR:** @@ -4358,8 +4456,6 @@ TS metrics-prometheus-* - targets: 1 - has_description: True -**Warnings:** Grafana panel description is not carried into the migrated Kibana panel automatically - **Semantic losses:** Dropped variable-driven label filters during migration **Verdict:** MINOR_ISSUE @@ -4418,6 +4514,7 @@ sum(redis_memory_used_bytes{instance=~"$instance"}) by (instance) - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -4447,6 +4544,7 @@ sum(redis_memory_used_bytes{instance=~"$instance"}) by (instance) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -4460,7 +4558,7 @@ sum(redis_memory_used_bytes{instance=~"$instance"}) by (instance) ``` TS metrics-prometheus-* | WHERE redis_memory_used_bytes IS NOT NULL -| STATS redis_memory_used_bytes = SUM(redis_memory_used_bytes) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance +| STATS redis_memory_used_bytes = SUM(redis_memory_used_bytes) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), instance | SORT time_bucket ASC ``` @@ -4512,6 +4610,7 @@ sum(redis_memory_used_bytes{instance=~"$instance"}) by (instance) - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -4541,6 +4640,7 @@ sum(redis_memory_used_bytes{instance=~"$instance"}) by (instance) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -4554,7 +4654,7 @@ sum(redis_memory_used_bytes{instance=~"$instance"}) by (instance) ``` TS metrics-prometheus-* | WHERE redis_memory_used_bytes IS NOT NULL -| STATS redis_memory_used_bytes = SUM(redis_memory_used_bytes) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance +| STATS redis_memory_used_bytes = SUM(redis_memory_used_bytes) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), instance | SORT time_bucket ASC ``` @@ -4606,6 +4706,7 @@ sum(redis_connected_clients{instance=~"$instance"}) by (instance) - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -4635,6 +4736,7 @@ sum(redis_connected_clients{instance=~"$instance"}) by (instance) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -4646,7 +4748,7 @@ sum(redis_connected_clients{instance=~"$instance"}) by (instance) ``` TS metrics-prometheus-* | WHERE redis_connected_clients IS NOT NULL -| STATS redis_connected_clients = SUM(redis_connected_clients) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance +| STATS redis_connected_clients = SUM(redis_connected_clients) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), instance | STATS redis_connected_clients = LAST(redis_connected_clients, time_bucket) BY instance | KEEP instance, redis_connected_clients ``` @@ -4701,6 +4803,7 @@ sum(redis_db_keys{instance=~"$instance"}) by (instance) - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -4730,6 +4833,7 @@ sum(redis_db_keys{instance=~"$instance"}) by (instance) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -4741,7 +4845,7 @@ sum(redis_db_keys{instance=~"$instance"}) by (instance) ``` TS metrics-prometheus-* | WHERE redis_db_keys IS NOT NULL -| STATS redis_db_keys = SUM(redis_db_keys) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance +| STATS redis_db_keys = SUM(redis_db_keys) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), instance | STATS redis_db_keys = LAST(redis_db_keys, time_bucket) BY instance | KEEP instance, redis_db_keys ``` @@ -4796,6 +4900,7 @@ sum(redis_memory_used_bytes{instance=~"$instance"}) by (instance) - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -4825,6 +4930,7 @@ sum(redis_memory_used_bytes{instance=~"$instance"}) by (instance) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -4839,7 +4945,7 @@ sum(redis_memory_used_bytes{instance=~"$instance"}) by (instance) ``` TS metrics-prometheus-* | WHERE redis_memory_used_bytes IS NOT NULL -| STATS redis_memory_used_bytes = SUM(redis_memory_used_bytes) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance +| STATS redis_memory_used_bytes = SUM(redis_memory_used_bytes) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), instance | STATS redis_memory_used_bytes = LAST(redis_memory_used_bytes, time_bucket) BY instance | KEEP instance, redis_memory_used_bytes ``` @@ -4894,6 +5000,7 @@ sum(redis_db_keys{instance=~"$instance"}) by (instance) - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -4923,6 +5030,7 @@ sum(redis_db_keys{instance=~"$instance"}) by (instance) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -4935,7 +5043,7 @@ sum(redis_db_keys{instance=~"$instance"}) by (instance) ``` TS metrics-prometheus-* | WHERE redis_db_keys IS NOT NULL -| STATS redis_db_keys = SUM(redis_db_keys) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance +| STATS redis_db_keys = SUM(redis_db_keys) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), instance | STATS redis_db_keys = LAST(redis_db_keys, time_bucket) BY instance | KEEP instance, redis_db_keys ``` @@ -4990,6 +5098,7 @@ sum(redis_db_keys{instance=~"$instance"}) by (instance) - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -5019,6 +5128,7 @@ sum(redis_db_keys{instance=~"$instance"}) by (instance) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -5034,7 +5144,7 @@ sum(redis_db_keys{instance=~"$instance"}) by (instance) ``` TS metrics-prometheus-* | WHERE redis_db_keys IS NOT NULL -| STATS redis_db_keys = SUM(redis_db_keys) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance +| STATS redis_db_keys = SUM(redis_db_keys) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), instance | SORT time_bucket ASC ``` @@ -5086,6 +5196,7 @@ sum(redis_db_keys{instance=~"$instance"}) by (instance) - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -5115,6 +5226,7 @@ sum(redis_db_keys{instance=~"$instance"}) by (instance) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -5130,7 +5242,7 @@ sum(redis_db_keys{instance=~"$instance"}) by (instance) ``` TS metrics-prometheus-* | WHERE redis_db_keys IS NOT NULL -| STATS redis_db_keys = SUM(redis_db_keys) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance +| STATS redis_db_keys = SUM(redis_db_keys) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), instance | SORT time_bucket ASC ``` @@ -5182,6 +5294,7 @@ sum(redis_db_keys{instance=~"$instance"}) by (instance) - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -5211,6 +5324,7 @@ sum(redis_db_keys{instance=~"$instance"}) by (instance) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -5226,7 +5340,7 @@ sum(redis_db_keys{instance=~"$instance"}) by (instance) ``` TS metrics-prometheus-* | WHERE redis_db_keys IS NOT NULL -| STATS redis_db_keys = SUM(redis_db_keys) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance +| STATS redis_db_keys = SUM(redis_db_keys) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), instance | SORT time_bucket ASC ``` @@ -5278,6 +5392,7 @@ sum(redis_commands_processed_total{instance=~"$instance"}) by (instance) - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -5307,6 +5422,7 @@ sum(redis_commands_processed_total{instance=~"$instance"}) by (instance) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -5323,7 +5439,7 @@ sum(redis_commands_processed_total{instance=~"$instance"}) by (instance) ``` TS metrics-prometheus-* | WHERE redis_commands_processed_total IS NOT NULL -| STATS redis_commands_processed_total = SUM(LAST_OVER_TIME(redis_commands_processed_total)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance +| STATS redis_commands_processed_total = SUM(LAST_OVER_TIME(redis_commands_processed_total)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), instance | SORT time_bucket ASC ``` @@ -5377,6 +5493,7 @@ sum(redis_db_keys{instance=~"$instance"}) by (instance) - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -5406,6 +5523,7 @@ sum(redis_db_keys{instance=~"$instance"}) by (instance) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -5423,7 +5541,7 @@ sum(redis_db_keys{instance=~"$instance"}) by (instance) ``` TS metrics-prometheus-* | WHERE redis_db_keys IS NOT NULL -| STATS redis_db_keys = SUM(redis_db_keys) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance +| STATS redis_db_keys = SUM(redis_db_keys) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), instance | STATS redis_db_keys = LAST(redis_db_keys, time_bucket) BY instance | KEEP instance, redis_db_keys ``` @@ -5476,6 +5594,7 @@ sum(redis_db_keys{instance=~"$instance"}) by (instance) - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -5505,6 +5624,7 @@ sum(redis_db_keys{instance=~"$instance"}) by (instance) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -5522,7 +5642,7 @@ sum(redis_db_keys{instance=~"$instance"}) by (instance) ``` TS metrics-prometheus-* | WHERE redis_db_keys IS NOT NULL -| STATS redis_db_keys = SUM(redis_db_keys) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance +| STATS redis_db_keys = SUM(redis_db_keys) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), instance | SORT time_bucket ASC ``` @@ -5574,6 +5694,7 @@ sum(redis_db_keys{instance=~"$instance"}) by (instance) - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -5603,6 +5724,7 @@ sum(redis_db_keys{instance=~"$instance"}) by (instance) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -5616,7 +5738,7 @@ sum(redis_db_keys{instance=~"$instance"}) by (instance) ``` TS metrics-prometheus-* | WHERE redis_db_keys IS NOT NULL -| STATS redis_db_keys = SUM(redis_db_keys) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance +| STATS redis_db_keys = SUM(redis_db_keys) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), instance | SORT time_bucket ASC ``` @@ -5668,6 +5790,7 @@ sum(redis_up{instance=~"$instance"}) by (instance) - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -5697,6 +5820,7 @@ sum(redis_up{instance=~"$instance"}) by (instance) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -5710,7 +5834,7 @@ sum(redis_up{instance=~"$instance"}) by (instance) ``` TS metrics-prometheus-* | WHERE redis_up IS NOT NULL -| STATS redis_up = SUM(redis_up) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance +| STATS redis_up = SUM(redis_up) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), instance | SORT time_bucket ASC ``` @@ -5764,6 +5888,7 @@ sum(redis_up{instance=~"$instance"}) by (instance) - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -5793,6 +5918,7 @@ sum(redis_up{instance=~"$instance"}) by (instance) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -5806,7 +5932,7 @@ sum(redis_up{instance=~"$instance"}) by (instance) ``` TS metrics-prometheus-* | WHERE redis_up IS NOT NULL -| STATS redis_up = SUM(redis_up) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance +| STATS redis_up = SUM(redis_up) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), instance | SORT time_bucket ASC ``` @@ -5892,6 +6018,7 @@ sum(rate(http_requests_total{status=~"2.."}[5m])) by (service, route) - `query_preprocessors` / `grafana_macros` - `query_preprocessors` / `parse_fragment` → parsed fragment family=range_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -5920,6 +6047,7 @@ sum(rate(http_requests_total{status=~"2.."}[5m])) by (service, route) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -5934,7 +6062,7 @@ sum(rate(http_requests_total{status=~"2.."}[5m])) by (service, route) TS metrics-prometheus-* | WHERE status RLIKE "2.." | WHERE http_requests_total IS NOT NULL -| STATS http_requests_total = SUM(RATE(http_requests_total)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), service, route +| STATS http_requests_total = SUM(RATE(http_requests_total)) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend), service, route | EVAL legend = CONCAT(COALESCE(TO_STRING(service), ""), " / ", COALESCE(TO_STRING(route), "")) | SORT time_bucket ASC ``` @@ -5986,6 +6114,7 @@ avg(queue_depth) - `query_preprocessors` / `grafana_macros` - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -6015,6 +6144,7 @@ avg(queue_depth) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -6027,7 +6157,7 @@ avg(queue_depth) ``` TS metrics-prometheus-* | WHERE queue_depth IS NOT NULL -| STATS queue_depth = AVG(queue_depth) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS queue_depth = AVG(queue_depth) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | STATS queue_depth = LAST(queue_depth, time_bucket) | KEEP queue_depth | EVAL _gauge_min = 0, _gauge_max = 500, _gauge_goal = 300 @@ -6081,6 +6211,7 @@ rate(frontend_requests_total[5m]) ||| rate(worker_jobs_total[5m]) - `query_preprocessors` / `grafana_macros` - `query_preprocessors` / `parse_fragment` → parsed fragment family=range_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -6109,6 +6240,7 @@ rate(frontend_requests_total[5m]) ||| rate(worker_jobs_total[5m]) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -6122,7 +6254,7 @@ rate(frontend_requests_total[5m]) ||| rate(worker_jobs_total[5m]) ``` TS metrics-prometheus-* | WHERE frontend_requests_total IS NOT NULL OR worker_jobs_total IS NOT NULL -| STATS frontend = RATE(frontend_requests_total), worker = RATE(worker_jobs_total) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS frontend = RATE(frontend_requests_total), worker = RATE(worker_jobs_total) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend) | KEEP time_bucket, frontend, worker | SORT time_bucket ASC ``` @@ -6172,6 +6304,7 @@ rate(api_requests_total[5m]) ||| avg(node_load1) ||| histogram_quantile(0.95, ra - `query_preprocessors` / `grafana_macros` - `query_preprocessors` / `parse_fragment` → parsed fragment family=range_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -6200,6 +6333,7 @@ rate(api_requests_total[5m]) ||| avg(node_load1) ||| histogram_quantile(0.95, ra - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -6212,7 +6346,7 @@ rate(api_requests_total[5m]) ||| avg(node_load1) ||| histogram_quantile(0.95, ra ``` TS metrics-prometheus-* -| STATS api_requests_total_A = CASE((api_requests_total IS NOT NULL), RATE(api_requests_total), NULL), node_load1_B = AVG(CASE((node_load1 IS NOT NULL), node_load1, NULL)), api_request_duration_seconds_C = PERCENTILE(CASE((api_request_duration_seconds IS NOT NULL), api_request_duration_seconds, NULL), 95) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS api_requests_total_A = CASE((api_requests_total IS NOT NULL), RATE(api_requests_total), NULL), node_load1_B = AVG(CASE((node_load1 IS NOT NULL), node_load1, NULL)), api_request_duration_seconds_C = PERCENTILE(CASE((api_request_duration_seconds IS NOT NULL), api_request_duration_seconds, NULL), 95) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend) | EVAL api = api_requests_total_A | EVAL load = node_load1_B | EVAL p95 = api_request_duration_seconds_C @@ -6248,7 +6382,7 @@ TS metrics-prometheus-* - targets: 3 -**Warnings:** Fused multi-target panel from independently translated ES|QL queries; histogram_quantile target field type could not be determined; assumed exponential_histogram and emitted PERCENTILE(). If the field is a classic histogram, pin the mapping or re-run with field capabilities so TO_TDIGEST() is used; histogram_quantile translated to an ES|QL PERCENTILE() aggregation; this is approximate — PERCENTILE uses t-digest, which treats histogram buckets as point masses rather than interpolating within them as Prometheus does, so results can diverge noticeably when traffic concentrates in a few wide buckets (the common latency shape). Prefer a target on ES >= 9.5 (native histogram_quantile) for exact results. +**Warnings:** histogram_quantile target field type could not be determined; assumed exponential_histogram and emitted PERCENTILE(). If the field is a classic histogram, pin the mapping or re-run with field capabilities so TO_TDIGEST() is used; histogram_quantile translated to an ES|QL PERCENTILE() aggregation; this is approximate — PERCENTILE uses t-digest, which treats histogram buckets as point masses rather than interpolating within them as Prometheus does, so results can diverge noticeably when traffic concentrates in a few wide buckets (the common latency shape). Prefer a target on ES >= 9.5 (native histogram_quantile) for exact results. **Semantic losses:** histogram_quantile translated to an ES|QL PERCENTILE() aggregation; this is approximate — PERCENTILE uses t-digest, which treats histogram buckets as point masses rather than interpolating within them as Prometheus does, so results can diverge noticeably when traffic concentrates in a few wide buckets (the common latency shape). Prefer a target on ES >= 9.5 (native histogram_quantile) for exact results. @@ -6312,6 +6446,7 @@ sum(kube_pod_info) by (pod) - `query_preprocessors` / `grafana_macros` - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -6341,6 +6476,7 @@ sum(kube_pod_info) by (pod) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -6352,7 +6488,7 @@ sum(kube_pod_info) by (pod) ``` TS metrics-prometheus-* | WHERE kube_pod_info IS NOT NULL -| STATS kube_pod_info = SUM(kube_pod_info) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), pod +| STATS kube_pod_info = SUM(kube_pod_info) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), pod | STATS kube_pod_info = LAST(kube_pod_info, time_bucket) BY pod | KEEP pod, kube_pod_info ``` @@ -6407,6 +6543,7 @@ avg(slo_burn_rate) - `query_preprocessors` / `grafana_macros` - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -6436,6 +6573,7 @@ avg(slo_burn_rate) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -6450,7 +6588,7 @@ avg(slo_burn_rate) ``` TS metrics-prometheus-* | WHERE slo_burn_rate IS NOT NULL -| STATS slo_burn_rate = AVG(slo_burn_rate) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS slo_burn_rate = AVG(slo_burn_rate) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | STATS slo_burn_rate = LAST(slo_burn_rate, time_bucket) | KEEP slo_burn_rate | EVAL _gauge_min = 0, _gauge_max = 100, _gauge_goal = 2 @@ -6499,6 +6637,7 @@ TS metrics-prometheus-* - `query_preprocessors` / `grafana_macros` - `query_preprocessors` / `parse_fragment` → parsed fragment family=logql_stream backend=regex - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family logql_stream bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -6516,6 +6655,7 @@ TS metrics-prometheus-* - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -6603,7 +6743,7 @@ FROM logs-* | Network Sockstat | `row` → `section` | skipped | **EXPECTED_LIMITATION** | — | — | | Network Netstat | `row` → `section` | skipped | **EXPECTED_LIMITATION** | — | — | | Node Exporter | `row` → `section` | skipped | **EXPECTED_LIMITATION** | — | — | -| Pressure | `bargauge` → `bar` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_pressure_cpu_waiting_seconds_total{instance="$node",job="$job"}[$__ra... | TS metrics-prometheus-* \| WHERE node_pressure_cpu_waiting_seconds_total IS NOT ... | +| Pressure | `bargauge` → `metric` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_pressure_cpu_waiting_seconds_total{instance="$node",job="$job"}[$__ra... | TS metrics-prometheus-* \| WHERE node_pressure_cpu_waiting_seconds_total IS NOT ... | | CPU Busy | `gauge` → `gauge` | migrated_with_warnings | **MINOR_ISSUE** | 100 * (1 - avg(rate(node_cpu_seconds_total{mode="idle", instance="$node"}[$__rat... | TS metrics-prometheus-* \| WHERE mode == "idle" \| WHERE node_cpu_seconds_total ... | | Sys Load | `gauge` → `gauge` | migrated_with_warnings | **MINOR_ISSUE** | scalar(node_load1{instance="$node",job="$job"}) * 100 / count(count(node_cpu_sec... | TS metrics-prometheus-* \| WHERE node_load1 IS NOT NULL OR node_cpu_seconds_tota... | | RAM Used | `gauge` → `gauge` | migrated_with_warnings | **MINOR_ISSUE** | (1 - (node_memory_MemAvailable_bytes{instance="$node", job="$job"} / node_memory... | TS metrics-prometheus-* \| WHERE node_memory_MemAvailable_bytes IS NOT NULL OR n... | @@ -6611,114 +6751,114 @@ FROM logs-* | Root FS Used | `gauge` → `gauge` | migrated_with_warnings | **MINOR_ISSUE** | 100 - ((node_filesystem_avail_bytes{instance="$node",job="$job",mountpoint="/",f... | TS metrics-prometheus-* \| WHERE mountpoint == "/" \| WHERE (fstype != "rootfs" ... | | CPU Cores | `stat` → `metric` | migrated_with_warnings | **MINOR_ISSUE** | count(count(node_cpu_seconds_total{instance="$node",job="$job"}) by (cpu)) | FROM metrics-prometheus-* \| WHERE node_cpu_seconds_total IS NOT NULL \| STATS n... | | Uptime | `stat` → `metric` | migrated_with_warnings | **MINOR_ISSUE** | node_time_seconds{instance="$node",job="$job"} - node_boot_time_seconds{instance... | TS metrics-prometheus-* \| WHERE node_time_seconds IS NOT NULL OR node_boot_time... | -| RootFS Total | `stat` → `metric` | migrated | **MINOR_ISSUE** | node_filesystem_size_bytes{instance="$node",job="$job",mountpoint="/",fstype!="r... | TS metrics-prometheus-* \| WHERE mountpoint == "/" \| WHERE (fstype != "rootfs" ... | -| RAM Total | `stat` → `metric` | migrated | **MINOR_ISSUE** | node_memory_MemTotal_bytes{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_memory_MemTotal_bytes IS NOT NULL \| STATS... | -| SWAP Total | `stat` → `metric` | migrated | **MINOR_ISSUE** | node_memory_SwapTotal_bytes{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_memory_SwapTotal_bytes IS NOT NULL \| STAT... | +| RootFS Total | `stat` → `metric` | migrated_with_warnings | **MINOR_ISSUE** | node_filesystem_size_bytes{instance="$node",job="$job",mountpoint="/",fstype!="r... | TS metrics-prometheus-* \| WHERE mountpoint == "/" \| WHERE (fstype != "rootfs" ... | +| RAM Total | `stat` → `metric` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_MemTotal_bytes{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_memory_MemTotal_bytes IS NOT NULL \| STATS... | +| SWAP Total | `stat` → `metric` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_SwapTotal_bytes{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_memory_SwapTotal_bytes IS NOT NULL \| STAT... | | CPU Basic | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | sum(irate(node_cpu_seconds_total{instance="$node",job="$job", mode="system"}[$__... | TS metrics-prometheus-* \| WHERE node_cpu_seconds_total IS NOT NULL \| STATS nod... | | Memory Basic | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_MemTotal_bytes{instance="$node",job="$job"} \|\|\| node_memory_MemTo... | TS metrics-prometheus-* \| WHERE node_memory_MemTotal_bytes IS NOT NULL OR node_... | -| Network Traffic Basic | `timeseries` → `line` | migrated | **MINOR_ISSUE** | irate(node_network_receive_bytes_total{instance="$node",job="$job"}[$__rate_inte... | TS metrics-prometheus-* \| WHERE node_network_receive_bytes_total IS NOT NULL OR... | -| Disk Space Used Basic | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | 100 - ((node_filesystem_avail_bytes{instance="$node",job="$job",device!~'rootfs'... | TS metrics-prometheus-* \| WHERE (NOT (device RLIKE "rootfs") OR (device IS NULL... | +| Network Traffic Basic | `timeseries` → `area` | migrated | **MINOR_ISSUE** | irate(node_network_receive_bytes_total{instance="$node",job="$job"}[$__rate_inte... | TS metrics-prometheus-* \| WHERE node_network_receive_bytes_total IS NOT NULL OR... | +| Disk Space Used Basic | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | 100 - ((node_filesystem_avail_bytes{instance="$node",job="$job",device!~'rootfs'... | TS metrics-prometheus-* \| WHERE (NOT (device RLIKE "rootfs") OR (device IS NULL... | | CPU | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | sum(irate(node_cpu_seconds_total{instance="$node",job="$job", mode="system"}[$__... | TS metrics-prometheus-* \| WHERE node_cpu_seconds_total IS NOT NULL \| STATS nod... | | Memory Stack | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_MemTotal_bytes{instance="$node",job="$job"} - node_memory_MemFree_by... | TS metrics-prometheus-* \| WHERE node_memory_MemTotal_bytes IS NOT NULL OR node_... | -| Network Traffic | `timeseries` → `line` | migrated | **MINOR_ISSUE** | irate(node_network_receive_bytes_total{instance="$node",job="$job"}[$__rate_inte... | TS metrics-prometheus-* \| WHERE node_network_receive_bytes_total IS NOT NULL OR... | -| Disk Space Used | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_filesystem_size_bytes{instance="$node",job="$job",device!~'rootfs'} - node_... | TS metrics-prometheus-* \| WHERE (NOT (device RLIKE "rootfs") OR (device IS NULL... | -| Disk IOps | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_disk_reads_completed_total{instance="$node",job="$job",device=~"$disk... | TS metrics-prometheus-* \| WHERE node_disk_reads_completed_total IS NOT NULL OR ... | -| I/O Usage Read / Write | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_disk_read_bytes_total{instance="$node",job="$job",device=~"$diskdevic... | TS metrics-prometheus-* \| WHERE node_disk_read_bytes_total IS NOT NULL OR node_... | -| I/O Utilization | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_disk_io_time_seconds_total{instance="$node",job="$job",device=~"$disk... | TS metrics-prometheus-* \| WHERE node_disk_io_time_seconds_total IS NOT NULL \| ... | -| CPU spent seconds in guests (VMs) | `timeseries` → `bar` | migrated_with_warnings | **MINOR_ISSUE** | sum by(instance) (irate(node_cpu_guest_seconds_total{instance="$node",job="$job"... | TS metrics-prometheus-* \| STATS numerator_A = SUM(CASE((mode == "user"), IRATE(... | +| Network Traffic | `timeseries` → `area` | migrated | **MINOR_ISSUE** | irate(node_network_receive_bytes_total{instance="$node",job="$job"}[$__rate_inte... | TS metrics-prometheus-* \| WHERE node_network_receive_bytes_total IS NOT NULL OR... | +| Disk Space Used | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_filesystem_size_bytes{instance="$node",job="$job",device!~'rootfs'} - node_... | TS metrics-prometheus-* \| WHERE (NOT (device RLIKE "rootfs") OR (device IS NULL... | +| Disk IOps | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_disk_reads_completed_total{instance="$node",job="$job",device=~"$disk... | TS metrics-prometheus-* \| WHERE node_disk_reads_completed_total IS NOT NULL OR ... | +| I/O Usage Read / Write | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_disk_read_bytes_total{instance="$node",job="$job",device=~"$diskdevic... | TS metrics-prometheus-* \| WHERE node_disk_read_bytes_total IS NOT NULL OR node_... | +| I/O Utilization | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_disk_io_time_seconds_total{instance="$node",job="$job",device=~"$disk... | TS metrics-prometheus-* \| WHERE node_disk_io_time_seconds_total IS NOT NULL \| ... | +| CPU spent seconds in guests (VMs) | `timeseries` → `bar` | migrated | **MINOR_ISSUE** | sum by(instance) (irate(node_cpu_guest_seconds_total{instance="$node",job="$job"... | TS metrics-prometheus-* \| STATS numerator_A = SUM(CASE((mode == "user"), IRATE(... | | Memory Active / Inactive | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_Inactive_bytes{instance="$node",job="$job"} \|\|\| node_memory_Activ... | TS metrics-prometheus-* \| WHERE node_memory_Inactive_bytes IS NOT NULL OR node_... | -| Memory Committed | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_Committed_AS_bytes{instance="$node",job="$job"} \|\|\| node_memory_C... | TS metrics-prometheus-* \| WHERE node_memory_Committed_AS_bytes IS NOT NULL OR n... | +| Memory Committed | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_Committed_AS_bytes{instance="$node",job="$job"} \|\|\| node_memory_C... | TS metrics-prometheus-* \| WHERE node_memory_Committed_AS_bytes IS NOT NULL OR n... | | Memory Active / Inactive Detail | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_Inactive_file_bytes{instance="$node",job="$job"} \|\|\| node_memory_... | TS metrics-prometheus-* \| WHERE node_memory_Inactive_file_bytes IS NOT NULL OR ... | -| Memory Writeback and Dirty | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_Writeback_bytes{instance="$node",job="$job"} \|\|\| node_memory_Writ... | TS metrics-prometheus-* \| WHERE node_memory_Writeback_bytes IS NOT NULL OR node... | -| Memory Shared and Mapped | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_Mapped_bytes{instance="$node",job="$job"} \|\|\| node_memory_Shmem_b... | TS metrics-prometheus-* \| WHERE node_memory_Mapped_bytes IS NOT NULL OR node_me... | +| Memory Writeback and Dirty | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_Writeback_bytes{instance="$node",job="$job"} \|\|\| node_memory_Writ... | TS metrics-prometheus-* \| WHERE node_memory_Writeback_bytes IS NOT NULL OR node... | +| Memory Shared and Mapped | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_Mapped_bytes{instance="$node",job="$job"} \|\|\| node_memory_Shmem_b... | TS metrics-prometheus-* \| WHERE node_memory_Mapped_bytes IS NOT NULL OR node_me... | | Memory Slab | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_SUnreclaim_bytes{instance="$node",job="$job"} \|\|\| node_memory_SRe... | TS metrics-prometheus-* \| WHERE node_memory_SUnreclaim_bytes IS NOT NULL OR nod... | -| Memory Vmalloc | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_VmallocChunk_bytes{instance="$node",job="$job"} \|\|\| node_memory_V... | TS metrics-prometheus-* \| WHERE node_memory_VmallocChunk_bytes IS NOT NULL OR n... | -| Memory Bounce | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_Bounce_bytes{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_memory_Bounce_bytes IS NOT NULL \| STATS n... | -| Memory Anonymous | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_AnonHugePages_bytes{instance="$node",job="$job"} \|\|\| node_memory_... | TS metrics-prometheus-* \| WHERE node_memory_AnonHugePages_bytes IS NOT NULL OR ... | -| Memory Kernel / CPU | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_KernelStack_bytes{instance="$node",job="$job"} \|\|\| node_memory_Pe... | TS metrics-prometheus-* \| WHERE node_memory_KernelStack_bytes IS NOT NULL OR no... | -| Memory HugePages Counter | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_HugePages_Free{instance="$node",job="$job"} \|\|\| node_memory_HugeP... | TS metrics-prometheus-* \| WHERE node_memory_HugePages_Free IS NOT NULL OR node_... | -| Memory HugePages Size | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_HugePages_Total{instance="$node",job="$job"} \|\|\| node_memory_Huge... | TS metrics-prometheus-* \| WHERE node_memory_HugePages_Total IS NOT NULL OR node... | -| Memory DirectMap | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_DirectMap1G_bytes{instance="$node",job="$job"} \|\|\| node_memory_Di... | TS metrics-prometheus-* \| WHERE node_memory_DirectMap1G_bytes IS NOT NULL OR no... | -| Memory Unevictable and MLocked | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_Unevictable_bytes{instance="$node",job="$job"} \|\|\| node_memory_Ml... | TS metrics-prometheus-* \| WHERE node_memory_Unevictable_bytes IS NOT NULL OR no... | -| Memory NFS | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_NFS_Unstable_bytes{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_memory_NFS_Unstable_bytes IS NOT NULL \| S... | -| Memory Pages In / Out | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_vmstat_pgpgin{instance="$node",job="$job"}[$__rate_interval]) \|\|\| ... | TS metrics-prometheus-* \| WHERE node_vmstat_pgpgin IS NOT NULL OR node_vmstat_p... | -| Memory Pages Swap In / Out | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_vmstat_pswpin{instance="$node",job="$job"}[$__rate_interval]) \|\|\| ... | TS metrics-prometheus-* \| WHERE node_vmstat_pswpin IS NOT NULL OR node_vmstat_p... | +| Memory Vmalloc | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_VmallocChunk_bytes{instance="$node",job="$job"} \|\|\| node_memory_V... | TS metrics-prometheus-* \| WHERE node_memory_VmallocChunk_bytes IS NOT NULL OR n... | +| Memory Bounce | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_Bounce_bytes{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_memory_Bounce_bytes IS NOT NULL \| STATS n... | +| Memory Anonymous | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_AnonHugePages_bytes{instance="$node",job="$job"} \|\|\| node_memory_... | TS metrics-prometheus-* \| WHERE node_memory_AnonHugePages_bytes IS NOT NULL OR ... | +| Memory Kernel / CPU | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_KernelStack_bytes{instance="$node",job="$job"} \|\|\| node_memory_Pe... | TS metrics-prometheus-* \| WHERE node_memory_KernelStack_bytes IS NOT NULL OR no... | +| Memory HugePages Counter | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_HugePages_Free{instance="$node",job="$job"} \|\|\| node_memory_HugeP... | TS metrics-prometheus-* \| WHERE node_memory_HugePages_Free IS NOT NULL OR node_... | +| Memory HugePages Size | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_HugePages_Total{instance="$node",job="$job"} \|\|\| node_memory_Huge... | TS metrics-prometheus-* \| WHERE node_memory_HugePages_Total IS NOT NULL OR node... | +| Memory DirectMap | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_DirectMap1G_bytes{instance="$node",job="$job"} \|\|\| node_memory_Di... | TS metrics-prometheus-* \| WHERE node_memory_DirectMap1G_bytes IS NOT NULL OR no... | +| Memory Unevictable and MLocked | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_Unevictable_bytes{instance="$node",job="$job"} \|\|\| node_memory_Ml... | TS metrics-prometheus-* \| WHERE node_memory_Unevictable_bytes IS NOT NULL OR no... | +| Memory NFS | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_memory_NFS_Unstable_bytes{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_memory_NFS_Unstable_bytes IS NOT NULL \| S... | +| Memory Pages In / Out | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_vmstat_pgpgin{instance="$node",job="$job"}[$__rate_interval]) \|\|\| ... | TS metrics-prometheus-* \| WHERE node_vmstat_pgpgin IS NOT NULL OR node_vmstat_p... | +| Memory Pages Swap In / Out | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_vmstat_pswpin{instance="$node",job="$job"}[$__rate_interval]) \|\|\| ... | TS metrics-prometheus-* \| WHERE node_vmstat_pswpin IS NOT NULL OR node_vmstat_p... | | Memory Page Faults | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_vmstat_pgfault{instance="$node",job="$job"}[$__rate_interval]) \|\|\|... | TS metrics-prometheus-* \| WHERE node_vmstat_pgfault IS NOT NULL OR node_vmstat_... | -| OOM Killer | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_vmstat_oom_kill{instance="$node",job="$job"}[$__rate_interval]) | TS metrics-prometheus-* \| WHERE node_vmstat_oom_kill IS NOT NULL \| STATS node_... | -| Time Synchronized Drift | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_timex_estimated_error_seconds{instance="$node",job="$job"} \|\|\| node_time... | TS metrics-prometheus-* \| WHERE node_timex_estimated_error_seconds IS NOT NULL ... | -| Time PLL Adjust | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_timex_loop_time_constant{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_timex_loop_time_constant IS NOT NULL \| ST... | -| Time Synchronized Status | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_timex_sync_status{instance="$node",job="$job"} \|\|\| node_timex_frequency_... | TS metrics-prometheus-* \| WHERE node_timex_sync_status IS NOT NULL OR node_time... | -| Time Misc | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_timex_tick_seconds{instance="$node",job="$job"} \|\|\| node_timex_tai_offse... | TS metrics-prometheus-* \| WHERE node_timex_tick_seconds IS NOT NULL OR node_tim... | -| Processes Status | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_procs_blocked{instance="$node",job="$job"} \|\|\| node_procs_running{instan... | TS metrics-prometheus-* \| WHERE node_procs_blocked IS NOT NULL OR node_procs_ru... | +| OOM Killer | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_vmstat_oom_kill{instance="$node",job="$job"}[$__rate_interval]) | TS metrics-prometheus-* \| WHERE node_vmstat_oom_kill IS NOT NULL \| STATS node_... | +| Time Synchronized Drift | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_timex_estimated_error_seconds{instance="$node",job="$job"} \|\|\| node_time... | TS metrics-prometheus-* \| WHERE node_timex_estimated_error_seconds IS NOT NULL ... | +| Time PLL Adjust | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_timex_loop_time_constant{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_timex_loop_time_constant IS NOT NULL \| ST... | +| Time Synchronized Status | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_timex_sync_status{instance="$node",job="$job"} \|\|\| node_timex_frequency_... | TS metrics-prometheus-* \| WHERE node_timex_sync_status IS NOT NULL OR node_time... | +| Time Misc | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_timex_tick_seconds{instance="$node",job="$job"} \|\|\| node_timex_tai_offse... | TS metrics-prometheus-* \| WHERE node_timex_tick_seconds IS NOT NULL OR node_tim... | +| Processes Status | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_procs_blocked{instance="$node",job="$job"} \|\|\| node_procs_running{instan... | TS metrics-prometheus-* \| WHERE node_procs_blocked IS NOT NULL OR node_procs_ru... | | Processes State | `timeseries` → `area` | migrated | **MINOR_ISSUE** | node_processes_state{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_processes_state IS NOT NULL \| STATS node_... | -| Processes Forks | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_forks_total{instance="$node",job="$job"}[$__rate_interval]) | TS metrics-prometheus-* \| WHERE node_forks_total IS NOT NULL \| STATS node_fork... | -| Processes Memory | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | irate(process_virtual_memory_bytes{instance="$node",job="$job"}[$__rate_interval... | TS metrics-prometheus-* \| WHERE process_virtual_memory_bytes IS NOT NULL OR pro... | -| PIDs Number and Limit | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_processes_pids{instance="$node",job="$job"} \|\|\| node_processes_max_proce... | TS metrics-prometheus-* \| WHERE node_processes_pids IS NOT NULL OR node_process... | -| Process schedule stats Running / Waiting | `timeseries` → `line` | migrated | **MINOR_ISSUE** | irate(node_schedstat_running_seconds_total{instance="$node",job="$job"}[$__rate_... | TS metrics-prometheus-* \| WHERE node_schedstat_running_seconds_total IS NOT NUL... | -| Threads Number and Limit | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_processes_threads{instance="$node",job="$job"} \|\|\| node_processes_max_th... | TS metrics-prometheus-* \| WHERE node_processes_threads IS NOT NULL OR node_proc... | -| Context Switches / Interrupts | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_context_switches_total{instance="$node",job="$job"}[$__rate_interval]... | TS metrics-prometheus-* \| WHERE node_context_switches_total IS NOT NULL OR node... | -| System Load | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_load1{instance="$node",job="$job"} \|\|\| node_load5{instance="$node",job="... | TS metrics-prometheus-* \| WHERE node_load1 IS NOT NULL OR node_load5 IS NOT NUL... | +| Processes Forks | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_forks_total{instance="$node",job="$job"}[$__rate_interval]) | TS metrics-prometheus-* \| WHERE node_forks_total IS NOT NULL \| STATS node_fork... | +| Processes Memory | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | irate(process_virtual_memory_bytes{instance="$node",job="$job"}[$__rate_interval... | TS metrics-prometheus-* \| WHERE process_virtual_memory_bytes IS NOT NULL OR pro... | +| PIDs Number and Limit | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_processes_pids{instance="$node",job="$job"} \|\|\| node_processes_max_proce... | TS metrics-prometheus-* \| WHERE node_processes_pids IS NOT NULL OR node_process... | +| Process schedule stats Running / Waiting | `timeseries` → `area` | migrated | **MINOR_ISSUE** | irate(node_schedstat_running_seconds_total{instance="$node",job="$job"}[$__rate_... | TS metrics-prometheus-* \| WHERE node_schedstat_running_seconds_total IS NOT NUL... | +| Threads Number and Limit | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_processes_threads{instance="$node",job="$job"} \|\|\| node_processes_max_th... | TS metrics-prometheus-* \| WHERE node_processes_threads IS NOT NULL OR node_proc... | +| Context Switches / Interrupts | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_context_switches_total{instance="$node",job="$job"}[$__rate_interval]... | TS metrics-prometheus-* \| WHERE node_context_switches_total IS NOT NULL OR node... | +| System Load | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_load1{instance="$node",job="$job"} \|\|\| node_load5{instance="$node",job="... | TS metrics-prometheus-* \| WHERE node_load1 IS NOT NULL OR node_load5 IS NOT NUL... | | CPU Frequency Scaling | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_cpu_scaling_frequency_hertz{instance="$node",job="$job"} \|\|\| avg(node_cp... | TS metrics-prometheus-* \| WHERE node_cpu_scaling_frequency_hertz IS NOT NULL OR... | -| Pressure Stall Information | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | rate(node_pressure_cpu_waiting_seconds_total{instance="$node",job="$job"}[$__rat... | TS metrics-prometheus-* \| WHERE node_pressure_cpu_waiting_seconds_total IS NOT ... | -| Interrupts Detail | `timeseries` → `line` | migrated | **MINOR_ISSUE** | irate(node_interrupts_total{instance="$node",job="$job"}[$__rate_interval]) | TS metrics-prometheus-* \| WHERE node_interrupts_total IS NOT NULL \| STATS node... | -| Schedule timeslices executed by each cpu | `timeseries` → `line` | migrated | **MINOR_ISSUE** | irate(node_schedstat_timeslices_total{instance="$node",job="$job"}[$__rate_inter... | TS metrics-prometheus-* \| WHERE node_schedstat_timeslices_total IS NOT NULL \| ... | -| Entropy | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_entropy_available_bits{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_entropy_available_bits IS NOT NULL \| STAT... | -| CPU time spent in user and system contexts | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | irate(process_cpu_seconds_total{instance="$node",job="$job"}[$__rate_interval]) | TS metrics-prometheus-* \| WHERE process_cpu_seconds_total IS NOT NULL \| STATS ... | -| File Descriptors | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | process_max_fds{instance="$node",job="$job"} \|\|\| process_open_fds{instance="$... | TS metrics-prometheus-* \| WHERE process_max_fds IS NOT NULL OR process_open_fds... | -| Hardware temperature monitor | `timeseries` → `line` | migrated | **MINOR_ISSUE** | node_hwmon_temp_celsius{instance="$node",job="$job"} * on(chip) group_left(chip_... | TS metrics-prometheus-* \| WHERE node_hwmon_temp_celsius IS NOT NULL OR node_hwm... | -| Throttle cooling device | `timeseries` → `line` | migrated | **MINOR_ISSUE** | node_cooling_device_cur_state{instance="$node",job="$job"} \|\|\| node_cooling_d... | TS metrics-prometheus-* \| WHERE node_cooling_device_cur_state IS NOT NULL OR no... | -| Power supply | `timeseries` → `line` | migrated | **MINOR_ISSUE** | node_power_supply_online{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_power_supply_online IS NOT NULL \| STATS n... | -| Systemd Sockets | `timeseries` → `line` | migrated | **MINOR_ISSUE** | irate(node_systemd_socket_accepted_connections_total{instance="$node",job="$job"... | TS metrics-prometheus-* \| WHERE node_systemd_socket_accepted_connections_total ... | +| Pressure Stall Information | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | rate(node_pressure_cpu_waiting_seconds_total{instance="$node",job="$job"}[$__rat... | TS metrics-prometheus-* \| WHERE node_pressure_cpu_waiting_seconds_total IS NOT ... | +| Interrupts Detail | `timeseries` → `area` | migrated | **MINOR_ISSUE** | irate(node_interrupts_total{instance="$node",job="$job"}[$__rate_interval]) | TS metrics-prometheus-* \| WHERE node_interrupts_total IS NOT NULL \| STATS node... | +| Schedule timeslices executed by each cpu | `timeseries` → `area` | migrated | **MINOR_ISSUE** | irate(node_schedstat_timeslices_total{instance="$node",job="$job"}[$__rate_inter... | TS metrics-prometheus-* \| WHERE node_schedstat_timeslices_total IS NOT NULL \| ... | +| Entropy | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_entropy_available_bits{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_entropy_available_bits IS NOT NULL \| STAT... | +| CPU time spent in user and system contexts | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | irate(process_cpu_seconds_total{instance="$node",job="$job"}[$__rate_interval]) | TS metrics-prometheus-* \| WHERE process_cpu_seconds_total IS NOT NULL \| STATS ... | +| File Descriptors | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | process_max_fds{instance="$node",job="$job"} \|\|\| process_open_fds{instance="$... | TS metrics-prometheus-* \| WHERE process_max_fds IS NOT NULL OR process_open_fds... | +| Hardware temperature monitor | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_hwmon_temp_celsius{instance="$node",job="$job"} * on(chip) group_left(chip_... | TS metrics-prometheus-* \| WHERE node_hwmon_temp_celsius IS NOT NULL OR node_hwm... | +| Throttle cooling device | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_cooling_device_cur_state{instance="$node",job="$job"} \|\|\| node_cooling_d... | TS metrics-prometheus-* \| WHERE node_cooling_device_cur_state IS NOT NULL OR no... | +| Power supply | `timeseries` → `area` | migrated | **MINOR_ISSUE** | node_power_supply_online{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_power_supply_online IS NOT NULL \| STATS n... | +| Systemd Sockets | `timeseries` → `area` | migrated | **MINOR_ISSUE** | irate(node_systemd_socket_accepted_connections_total{instance="$node",job="$job"... | TS metrics-prometheus-* \| WHERE node_systemd_socket_accepted_connections_total ... | | Systemd Units State | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_systemd_units{instance="$node",job="$job",state="activating"} \|\|\| node_s... | TS metrics-prometheus-* \| WHERE node_systemd_units IS NOT NULL \| STATS Activat... | -| Disk IOps Completed | `timeseries` → `line` | migrated | **MINOR_ISSUE** | irate(node_disk_reads_completed_total{instance="$node",job="$job"}[$__rate_inter... | TS metrics-prometheus-* \| WHERE node_disk_reads_completed_total IS NOT NULL OR ... | -| Disk R/W Data | `timeseries` → `line` | migrated | **MINOR_ISSUE** | irate(node_disk_read_bytes_total{instance="$node",job="$job"}[$__rate_interval])... | TS metrics-prometheus-* \| WHERE node_disk_read_bytes_total IS NOT NULL OR node_... | -| Disk Average Wait Time | `timeseries` → `line` | migrated | **MINOR_ISSUE** | irate(node_disk_read_time_seconds_total{instance="$node",job="$job"}[$__rate_int... | TS metrics-prometheus-* \| WHERE node_disk_read_time_seconds_total IS NOT NULL O... | -| Average Queue Size | `timeseries` → `line` | migrated | **MINOR_ISSUE** | irate(node_disk_io_time_weighted_seconds_total{instance="$node",job="$job"}[$__r... | TS metrics-prometheus-* \| WHERE node_disk_io_time_weighted_seconds_total IS NOT... | -| Disk R/W Merged | `timeseries` → `line` | migrated | **MINOR_ISSUE** | irate(node_disk_reads_merged_total{instance="$node",job="$job"}[$__rate_interval... | TS metrics-prometheus-* \| WHERE node_disk_reads_merged_total IS NOT NULL OR nod... | -| Time Spent Doing I/Os | `timeseries` → `line` | migrated | **MINOR_ISSUE** | irate(node_disk_io_time_seconds_total{instance="$node",job="$job"}[$__rate_inter... | TS metrics-prometheus-* \| WHERE node_disk_io_time_seconds_total IS NOT NULL OR ... | -| Instantaneous Queue Size | `timeseries` → `line` | migrated | **MINOR_ISSUE** | node_disk_io_now{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_disk_io_now IS NOT NULL \| STATS node_disk... | -| Disk IOps Discards completed / merged | `timeseries` → `line` | migrated | **MINOR_ISSUE** | irate(node_disk_discards_completed_total{instance="$node",job="$job"}[$__rate_in... | TS metrics-prometheus-* \| WHERE node_disk_discards_completed_total IS NOT NULL ... | -| Filesystem space available | `timeseries` → `line` | migrated | **MINOR_ISSUE** | node_filesystem_avail_bytes{instance="$node",job="$job",device!~'rootfs'} | TS metrics-prometheus-* \| WHERE (NOT (device RLIKE "rootfs") OR (device IS NULL... | -| File Nodes Free | `timeseries` → `line` | migrated | **MINOR_ISSUE** | node_filesystem_files_free{instance="$node",job="$job",device!~'rootfs'} | TS metrics-prometheus-* \| WHERE (NOT (device RLIKE "rootfs") OR (device IS NULL... | -| File Descriptor | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_filefd_maximum{instance="$node",job="$job"} \|\|\| node_filefd_allocated{in... | TS metrics-prometheus-* \| WHERE node_filefd_maximum IS NOT NULL OR node_filefd_... | -| File Nodes Size | `timeseries` → `line` | migrated | **MINOR_ISSUE** | node_filesystem_files{instance="$node",job="$job",device!~'rootfs'} | TS metrics-prometheus-* \| WHERE (NOT (device RLIKE "rootfs") OR (device IS NULL... | +| Disk IOps Completed | `timeseries` → `area` | migrated | **MINOR_ISSUE** | irate(node_disk_reads_completed_total{instance="$node",job="$job"}[$__rate_inter... | TS metrics-prometheus-* \| WHERE node_disk_reads_completed_total IS NOT NULL OR ... | +| Disk R/W Data | `timeseries` → `area` | migrated | **MINOR_ISSUE** | irate(node_disk_read_bytes_total{instance="$node",job="$job"}[$__rate_interval])... | TS metrics-prometheus-* \| WHERE node_disk_read_bytes_total IS NOT NULL OR node_... | +| Disk Average Wait Time | `timeseries` → `area` | migrated | **MINOR_ISSUE** | irate(node_disk_read_time_seconds_total{instance="$node",job="$job"}[$__rate_int... | TS metrics-prometheus-* \| WHERE node_disk_read_time_seconds_total IS NOT NULL O... | +| Average Queue Size | `timeseries` → `area` | migrated | **MINOR_ISSUE** | irate(node_disk_io_time_weighted_seconds_total{instance="$node",job="$job"}[$__r... | TS metrics-prometheus-* \| WHERE node_disk_io_time_weighted_seconds_total IS NOT... | +| Disk R/W Merged | `timeseries` → `area` | migrated | **MINOR_ISSUE** | irate(node_disk_reads_merged_total{instance="$node",job="$job"}[$__rate_interval... | TS metrics-prometheus-* \| WHERE node_disk_reads_merged_total IS NOT NULL OR nod... | +| Time Spent Doing I/Os | `timeseries` → `area` | migrated | **MINOR_ISSUE** | irate(node_disk_io_time_seconds_total{instance="$node",job="$job"}[$__rate_inter... | TS metrics-prometheus-* \| WHERE node_disk_io_time_seconds_total IS NOT NULL OR ... | +| Instantaneous Queue Size | `timeseries` → `area` | migrated | **MINOR_ISSUE** | node_disk_io_now{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_disk_io_now IS NOT NULL \| STATS node_disk... | +| Disk IOps Discards completed / merged | `timeseries` → `area` | migrated | **MINOR_ISSUE** | irate(node_disk_discards_completed_total{instance="$node",job="$job"}[$__rate_in... | TS metrics-prometheus-* \| WHERE node_disk_discards_completed_total IS NOT NULL ... | +| Filesystem space available | `timeseries` → `area` | migrated | **MINOR_ISSUE** | node_filesystem_avail_bytes{instance="$node",job="$job",device!~'rootfs'} | TS metrics-prometheus-* \| WHERE (NOT (device RLIKE "rootfs") OR (device IS NULL... | +| File Nodes Free | `timeseries` → `area` | migrated | **MINOR_ISSUE** | node_filesystem_files_free{instance="$node",job="$job",device!~'rootfs'} | TS metrics-prometheus-* \| WHERE (NOT (device RLIKE "rootfs") OR (device IS NULL... | +| File Descriptor | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_filefd_maximum{instance="$node",job="$job"} \|\|\| node_filefd_allocated{in... | TS metrics-prometheus-* \| WHERE node_filefd_maximum IS NOT NULL OR node_filefd_... | +| File Nodes Size | `timeseries` → `area` | migrated | **MINOR_ISSUE** | node_filesystem_files{instance="$node",job="$job",device!~'rootfs'} | TS metrics-prometheus-* \| WHERE (NOT (device RLIKE "rootfs") OR (device IS NULL... | | Filesystem in ReadOnly / Error | `timeseries` → `area` | migrated | **MINOR_ISSUE** | node_filesystem_readonly{instance="$node",job="$job",device!~'rootfs'} \|\|\| no... | TS metrics-prometheus-* \| WHERE (NOT (device RLIKE "rootfs") OR (device IS NULL... | -| Network Traffic by Packets | `timeseries` → `line` | migrated | **MINOR_ISSUE** | irate(node_network_receive_packets_total{instance="$node",job="$job"}[$__rate_in... | TS metrics-prometheus-* \| WHERE node_network_receive_packets_total IS NOT NULL ... | -| Network Traffic Errors | `timeseries` → `line` | migrated | **MINOR_ISSUE** | irate(node_network_receive_errs_total{instance="$node",job="$job"}[$__rate_inter... | TS metrics-prometheus-* \| WHERE node_network_receive_errs_total IS NOT NULL OR ... | -| Network Traffic Drop | `timeseries` → `line` | migrated | **MINOR_ISSUE** | irate(node_network_receive_drop_total{instance="$node",job="$job"}[$__rate_inter... | TS metrics-prometheus-* \| WHERE node_network_receive_drop_total IS NOT NULL OR ... | -| Network Traffic Compressed | `timeseries` → `line` | migrated | **MINOR_ISSUE** | irate(node_network_receive_compressed_total{instance="$node",job="$job"}[$__rate... | TS metrics-prometheus-* \| WHERE node_network_receive_compressed_total IS NOT NU... | -| Network Traffic Multicast | `timeseries` → `line` | migrated | **MINOR_ISSUE** | irate(node_network_receive_multicast_total{instance="$node",job="$job"}[$__rate_... | TS metrics-prometheus-* \| WHERE node_network_receive_multicast_total IS NOT NUL... | -| Network Traffic Fifo | `timeseries` → `line` | migrated | **MINOR_ISSUE** | irate(node_network_receive_fifo_total{instance="$node",job="$job"}[$__rate_inter... | TS metrics-prometheus-* \| WHERE node_network_receive_fifo_total IS NOT NULL OR ... | -| Network Traffic Frame | `timeseries` → `line` | migrated | **MINOR_ISSUE** | irate(node_network_receive_frame_total{instance="$node",job="$job"}[$__rate_inte... | TS metrics-prometheus-* \| WHERE node_network_receive_frame_total IS NOT NULL \|... | -| Network Traffic Carrier | `timeseries` → `line` | migrated | **MINOR_ISSUE** | irate(node_network_transmit_carrier_total{instance="$node",job="$job"}[$__rate_i... | TS metrics-prometheus-* \| WHERE node_network_transmit_carrier_total IS NOT NULL... | -| Network Traffic Colls | `timeseries` → `line` | migrated | **MINOR_ISSUE** | irate(node_network_transmit_colls_total{instance="$node",job="$job"}[$__rate_int... | TS metrics-prometheus-* \| WHERE node_network_transmit_colls_total IS NOT NULL \... | -| NF Conntrack | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_nf_conntrack_entries{instance="$node",job="$job"} \|\|\| node_nf_conntrack_... | TS metrics-prometheus-* \| WHERE node_nf_conntrack_entries IS NOT NULL OR node_n... | -| ARP Entries | `timeseries` → `line` | migrated | **MINOR_ISSUE** | node_arp_entries{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_arp_entries IS NOT NULL \| STATS node_arp_... | -| MTU | `timeseries` → `line` | migrated | **MINOR_ISSUE** | node_network_mtu_bytes{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_network_mtu_bytes IS NOT NULL \| STATS nod... | -| Speed | `timeseries` → `line` | migrated | **MINOR_ISSUE** | node_network_speed_bytes{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_network_speed_bytes IS NOT NULL \| STATS n... | -| Queue Length | `timeseries` → `line` | migrated | **MINOR_ISSUE** | node_network_transmit_queue_length{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_network_transmit_queue_length IS NOT NULL ... | -| Softnet Packets | `timeseries` → `line` | migrated | **MINOR_ISSUE** | irate(node_softnet_processed_total{instance="$node",job="$job"}[$__rate_interval... | TS metrics-prometheus-* \| WHERE node_softnet_processed_total IS NOT NULL OR nod... | -| Softnet Out of Quota | `timeseries` → `line` | migrated | **MINOR_ISSUE** | irate(node_softnet_times_squeezed_total{instance="$node",job="$job"}[$__rate_int... | TS metrics-prometheus-* \| WHERE node_softnet_times_squeezed_total IS NOT NULL \... | -| Network Operational Status | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_network_up{operstate="up",instance="$node",job="$job"} \|\|\| node_network_... | TS metrics-prometheus-* \| STATS node_network_up_A = MAX(LAST_OVER_TIME(CASE((op... | -| Sockstat TCP | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_sockstat_TCP_alloc{instance="$node",job="$job"} \|\|\| node_sockstat_TCP_in... | TS metrics-prometheus-* \| WHERE node_sockstat_TCP_alloc IS NOT NULL OR node_soc... | -| Sockstat UDP | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_sockstat_UDPLITE_inuse{instance="$node",job="$job"} \|\|\| node_sockstat_UD... | TS metrics-prometheus-* \| WHERE node_sockstat_UDPLITE_inuse IS NOT NULL OR node... | -| Sockstat FRAG / RAW | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_sockstat_FRAG_inuse{instance="$node",job="$job"} \|\|\| node_sockstat_RAW_i... | TS metrics-prometheus-* \| WHERE node_sockstat_FRAG_inuse IS NOT NULL OR node_so... | -| Sockstat Memory Size | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_sockstat_TCP_mem_bytes{instance="$node",job="$job"} \|\|\| node_sockstat_UD... | TS metrics-prometheus-* \| WHERE node_sockstat_TCP_mem_bytes IS NOT NULL OR node... | -| Sockstat Used | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_sockstat_sockets_used{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_sockstat_sockets_used IS NOT NULL \| STATS... | -| Netstat IP In / Out Octets | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_netstat_IpExt_InOctets{instance="$node",job="$job"}[$__rate_interval]... | TS metrics-prometheus-* \| WHERE node_netstat_IpExt_InOctets IS NOT NULL OR node... | -| Netstat IP Forwarding | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_netstat_Ip_Forwarding{instance="$node",job="$job"}[$__rate_interval]) | TS metrics-prometheus-* \| WHERE node_netstat_Ip_Forwarding IS NOT NULL \| STATS... | -| ICMP In / Out | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_netstat_Icmp_InMsgs{instance="$node",job="$job"}[$__rate_interval]) \... | TS metrics-prometheus-* \| WHERE node_netstat_Icmp_InMsgs IS NOT NULL OR node_ne... | -| ICMP Errors | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_netstat_Icmp_InErrors{instance="$node",job="$job"}[$__rate_interval]) | TS metrics-prometheus-* \| WHERE node_netstat_Icmp_InErrors IS NOT NULL \| STATS... | -| UDP In / Out | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_netstat_Udp_InDatagrams{instance="$node",job="$job"}[$__rate_interval... | TS metrics-prometheus-* \| WHERE node_netstat_Udp_InDatagrams IS NOT NULL OR nod... | -| UDP Errors | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_netstat_Udp_InErrors{instance="$node",job="$job"}[$__rate_interval]) ... | TS metrics-prometheus-* \| WHERE node_netstat_Udp_InErrors IS NOT NULL OR node_n... | -| TCP In / Out | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_netstat_Tcp_InSegs{instance="$node",job="$job"}[$__rate_interval]) \|... | TS metrics-prometheus-* \| WHERE node_netstat_Tcp_InSegs IS NOT NULL OR node_net... | -| TCP Errors | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_netstat_TcpExt_ListenOverflows{instance="$node",job="$job"}[$__rate_i... | TS metrics-prometheus-* \| WHERE node_netstat_TcpExt_ListenOverflows IS NOT NULL... | -| TCP Connections | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_netstat_Tcp_CurrEstab{instance="$node",job="$job"} \|\|\| node_netstat_Tcp_... | TS metrics-prometheus-* \| WHERE node_netstat_Tcp_CurrEstab IS NOT NULL OR node_... | -| TCP SynCookie | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_netstat_TcpExt_SyncookiesFailed{instance="$node",job="$job"}[$__rate_... | TS metrics-prometheus-* \| WHERE node_netstat_TcpExt_SyncookiesFailed IS NOT NUL... | -| TCP Direct Transition | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_netstat_Tcp_ActiveOpens{instance="$node",job="$job"}[$__rate_interval... | TS metrics-prometheus-* \| WHERE node_netstat_Tcp_ActiveOpens IS NOT NULL OR nod... | -| TCP Stat | `timeseries` → `line` | migrated_with_warnings | **MINOR_ISSUE** | node_tcp_connection_states{state="established",instance="$node",job="$job"} \|\|... | TS metrics-prometheus-* \| WHERE node_tcp_connection_states IS NOT NULL \| STATS... | +| Network Traffic by Packets | `timeseries` → `area` | migrated | **MINOR_ISSUE** | irate(node_network_receive_packets_total{instance="$node",job="$job"}[$__rate_in... | TS metrics-prometheus-* \| WHERE node_network_receive_packets_total IS NOT NULL ... | +| Network Traffic Errors | `timeseries` → `area` | migrated | **MINOR_ISSUE** | irate(node_network_receive_errs_total{instance="$node",job="$job"}[$__rate_inter... | TS metrics-prometheus-* \| WHERE node_network_receive_errs_total IS NOT NULL OR ... | +| Network Traffic Drop | `timeseries` → `area` | migrated | **MINOR_ISSUE** | irate(node_network_receive_drop_total{instance="$node",job="$job"}[$__rate_inter... | TS metrics-prometheus-* \| WHERE node_network_receive_drop_total IS NOT NULL OR ... | +| Network Traffic Compressed | `timeseries` → `area` | migrated | **MINOR_ISSUE** | irate(node_network_receive_compressed_total{instance="$node",job="$job"}[$__rate... | TS metrics-prometheus-* \| WHERE node_network_receive_compressed_total IS NOT NU... | +| Network Traffic Multicast | `timeseries` → `area` | migrated | **MINOR_ISSUE** | irate(node_network_receive_multicast_total{instance="$node",job="$job"}[$__rate_... | TS metrics-prometheus-* \| WHERE node_network_receive_multicast_total IS NOT NUL... | +| Network Traffic Fifo | `timeseries` → `area` | migrated | **MINOR_ISSUE** | irate(node_network_receive_fifo_total{instance="$node",job="$job"}[$__rate_inter... | TS metrics-prometheus-* \| WHERE node_network_receive_fifo_total IS NOT NULL OR ... | +| Network Traffic Frame | `timeseries` → `area` | migrated | **MINOR_ISSUE** | irate(node_network_receive_frame_total{instance="$node",job="$job"}[$__rate_inte... | TS metrics-prometheus-* \| WHERE node_network_receive_frame_total IS NOT NULL \|... | +| Network Traffic Carrier | `timeseries` → `area` | migrated | **MINOR_ISSUE** | irate(node_network_transmit_carrier_total{instance="$node",job="$job"}[$__rate_i... | TS metrics-prometheus-* \| WHERE node_network_transmit_carrier_total IS NOT NULL... | +| Network Traffic Colls | `timeseries` → `area` | migrated | **MINOR_ISSUE** | irate(node_network_transmit_colls_total{instance="$node",job="$job"}[$__rate_int... | TS metrics-prometheus-* \| WHERE node_network_transmit_colls_total IS NOT NULL \... | +| NF Conntrack | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_nf_conntrack_entries{instance="$node",job="$job"} \|\|\| node_nf_conntrack_... | TS metrics-prometheus-* \| WHERE node_nf_conntrack_entries IS NOT NULL OR node_n... | +| ARP Entries | `timeseries` → `area` | migrated | **MINOR_ISSUE** | node_arp_entries{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_arp_entries IS NOT NULL \| STATS node_arp_... | +| MTU | `timeseries` → `area` | migrated | **MINOR_ISSUE** | node_network_mtu_bytes{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_network_mtu_bytes IS NOT NULL \| STATS nod... | +| Speed | `timeseries` → `area` | migrated | **MINOR_ISSUE** | node_network_speed_bytes{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_network_speed_bytes IS NOT NULL \| STATS n... | +| Queue Length | `timeseries` → `area` | migrated | **MINOR_ISSUE** | node_network_transmit_queue_length{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_network_transmit_queue_length IS NOT NULL ... | +| Softnet Packets | `timeseries` → `area` | migrated | **MINOR_ISSUE** | irate(node_softnet_processed_total{instance="$node",job="$job"}[$__rate_interval... | TS metrics-prometheus-* \| WHERE node_softnet_processed_total IS NOT NULL OR nod... | +| Softnet Out of Quota | `timeseries` → `area` | migrated | **MINOR_ISSUE** | irate(node_softnet_times_squeezed_total{instance="$node",job="$job"}[$__rate_int... | TS metrics-prometheus-* \| WHERE node_softnet_times_squeezed_total IS NOT NULL \... | +| Network Operational Status | `timeseries` → `area` | migrated | **MINOR_ISSUE** | node_network_up{operstate="up",instance="$node",job="$job"} \|\|\| node_network_... | TS metrics-prometheus-* \| STATS node_network_up_A = MAX(LAST_OVER_TIME(CASE((op... | +| Sockstat TCP | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_sockstat_TCP_alloc{instance="$node",job="$job"} \|\|\| node_sockstat_TCP_in... | TS metrics-prometheus-* \| WHERE node_sockstat_TCP_alloc IS NOT NULL OR node_soc... | +| Sockstat UDP | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_sockstat_UDPLITE_inuse{instance="$node",job="$job"} \|\|\| node_sockstat_UD... | TS metrics-prometheus-* \| WHERE node_sockstat_UDPLITE_inuse IS NOT NULL OR node... | +| Sockstat FRAG / RAW | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_sockstat_FRAG_inuse{instance="$node",job="$job"} \|\|\| node_sockstat_RAW_i... | TS metrics-prometheus-* \| WHERE node_sockstat_FRAG_inuse IS NOT NULL OR node_so... | +| Sockstat Memory Size | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_sockstat_TCP_mem_bytes{instance="$node",job="$job"} \|\|\| node_sockstat_UD... | TS metrics-prometheus-* \| WHERE node_sockstat_TCP_mem_bytes IS NOT NULL OR node... | +| Sockstat Used | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_sockstat_sockets_used{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_sockstat_sockets_used IS NOT NULL \| STATS... | +| Netstat IP In / Out Octets | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_netstat_IpExt_InOctets{instance="$node",job="$job"}[$__rate_interval]... | TS metrics-prometheus-* \| WHERE node_netstat_IpExt_InOctets IS NOT NULL OR node... | +| Netstat IP Forwarding | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_netstat_Ip_Forwarding{instance="$node",job="$job"}[$__rate_interval]) | TS metrics-prometheus-* \| WHERE node_netstat_Ip_Forwarding IS NOT NULL \| STATS... | +| ICMP In / Out | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_netstat_Icmp_InMsgs{instance="$node",job="$job"}[$__rate_interval]) \... | TS metrics-prometheus-* \| WHERE node_netstat_Icmp_InMsgs IS NOT NULL OR node_ne... | +| ICMP Errors | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_netstat_Icmp_InErrors{instance="$node",job="$job"}[$__rate_interval]) | TS metrics-prometheus-* \| WHERE node_netstat_Icmp_InErrors IS NOT NULL \| STATS... | +| UDP In / Out | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_netstat_Udp_InDatagrams{instance="$node",job="$job"}[$__rate_interval... | TS metrics-prometheus-* \| WHERE node_netstat_Udp_InDatagrams IS NOT NULL OR nod... | +| UDP Errors | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_netstat_Udp_InErrors{instance="$node",job="$job"}[$__rate_interval]) ... | TS metrics-prometheus-* \| WHERE node_netstat_Udp_InErrors IS NOT NULL OR node_n... | +| TCP In / Out | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_netstat_Tcp_InSegs{instance="$node",job="$job"}[$__rate_interval]) \|... | TS metrics-prometheus-* \| WHERE node_netstat_Tcp_InSegs IS NOT NULL OR node_net... | +| TCP Errors | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_netstat_TcpExt_ListenOverflows{instance="$node",job="$job"}[$__rate_i... | TS metrics-prometheus-* \| WHERE node_netstat_TcpExt_ListenOverflows IS NOT NULL... | +| TCP Connections | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_netstat_Tcp_CurrEstab{instance="$node",job="$job"} \|\|\| node_netstat_Tcp_... | TS metrics-prometheus-* \| WHERE node_netstat_Tcp_CurrEstab IS NOT NULL OR node_... | +| TCP SynCookie | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_netstat_TcpExt_SyncookiesFailed{instance="$node",job="$job"}[$__rate_... | TS metrics-prometheus-* \| WHERE node_netstat_TcpExt_SyncookiesFailed IS NOT NUL... | +| TCP Direct Transition | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | irate(node_netstat_Tcp_ActiveOpens{instance="$node",job="$job"}[$__rate_interval... | TS metrics-prometheus-* \| WHERE node_netstat_Tcp_ActiveOpens IS NOT NULL OR nod... | +| TCP Stat | `timeseries` → `area` | migrated_with_warnings | **MINOR_ISSUE** | node_tcp_connection_states{state="established",instance="$node",job="$job"} \|\|... | TS metrics-prometheus-* \| WHERE node_tcp_connection_states IS NOT NULL \| STATS... | | Node Exporter Scrape Time | `timeseries` → `area` | migrated | **MINOR_ISSUE** | node_scrape_collector_duration_seconds{instance="$node",job="$job"} | TS metrics-prometheus-* \| WHERE node_scrape_collector_duration_seconds IS NOT N... | -| Node Exporter Scrape | `timeseries` → `line` | migrated | **MINOR_ISSUE** | node_scrape_collector_success{instance="$node",job="$job"} \|\|\| node_textfile_... | TS metrics-prometheus-* \| WHERE node_scrape_collector_success IS NOT NULL OR no... | +| Node Exporter Scrape | `timeseries` → `area` | migrated | **MINOR_ISSUE** | node_scrape_collector_success{instance="$node",job="$job"} \|\|\| node_textfile_... | TS metrics-prometheus-* \| WHERE node_scrape_collector_success IS NOT NULL OR no... | | Dashboard Links | `dashboard_links` → `links` | migrated | **EXPECTED_LIMITATION** | — | — |
@@ -6740,6 +6880,7 @@ irate(node_pressure_cpu_waiting_seconds_total{instance="$node",job="$job"}[$__ra - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=range_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -6768,6 +6909,7 @@ irate(node_pressure_cpu_waiting_seconds_total{instance="$node",job="$job"}[$__ra - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -6775,19 +6917,19 @@ irate(node_pressure_cpu_waiting_seconds_total{instance="$node",job="$job"}[$__ra - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` → approximated bargauge panel -**Translated (bar):** +**Translated (metric):** ``` TS metrics-prometheus-* | WHERE node_pressure_cpu_waiting_seconds_total IS NOT NULL OR node_pressure_memory_waiting_seconds_total IS NOT NULL OR node_pressure_io_waiting_seconds_total IS NOT NULL -| STATS CPU = IRATE(node_pressure_cpu_waiting_seconds_total), Mem = IRATE(node_pressure_memory_waiting_seconds_total), I_O = IRATE(node_pressure_io_waiting_seconds_total) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS CPU = IRATE(node_pressure_cpu_waiting_seconds_total), Mem = IRATE(node_pressure_memory_waiting_seconds_total), I_O = IRATE(node_pressure_io_waiting_seconds_total) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend) | STATS CPU = MAX(CPU), Mem = MAX(Mem), I_O = MAX(I_O) | KEEP CPU, Mem, I_O | EVAL __labels = MV_APPEND(MV_APPEND("CPU", "Mem"), "I/O"), __values = MV_APPEND(MV_APPEND(COALESCE(TO_STRING(CPU), ""), COALESCE(TO_STRING(Mem), "")), COALESCE(TO_STRING(I_O), "")) -| EVAL __pairs = MV_ZIP(__labels, __values, "~") +| EVAL __pairs = MV_ZIP(__labels, __values, "\t") | MV_EXPAND __pairs -| EVAL label = MV_FIRST(SPLIT(__pairs, "~")), value = TO_DOUBLE(MV_LAST(SPLIT(__pairs, "~"))) -| KEEP label, value +| EVAL label = MV_FIRST(SPLIT(__pairs, "\t")), gauge_value = (TO_DOUBLE(MV_LAST(SPLIT(__pairs, "\t")))) * 100 +| KEEP label, gauge_value | SORT label ASC ``` @@ -6801,14 +6943,14 @@ TS metrics-prometheus-* - Source lang: `promql` - Target index: `metrics-prometheus-*` - Output metric: `CPU` -- Semantic losses: Dropped variable-driven label filters during migration, Approximated bargauge as bar chart +- Semantic losses: Dropped variable-driven label filters during migration, Approximated bargauge as metric tiles **Visual IR:** -- Kibana type: `bar` +- Kibana type: `metric` - Layout: x=0, y=0, w=6, h=12 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, legend +- Config keys: type, query, primary, breakdown, styling **Operational IR:** @@ -6819,11 +6961,9 @@ TS metrics-prometheus-* - targets: 3 - has_description: True -**Warnings:** Grafana panel description is not carried into the migrated Kibana panel automatically; Approximated bargauge as bar chart - -**Semantic losses:** Dropped variable-driven label filters during migration; Approximated bargauge as bar chart +**Warnings:** Approximated bargauge as metric tiles -**Notes:** Grafana panel description is not carried into the migrated Kibana panel automatically +**Semantic losses:** Dropped variable-driven label filters during migration; Approximated bargauge as metric tiles **Verdict:** MINOR_ISSUE @@ -6843,6 +6983,7 @@ TS metrics-prometheus-* - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=binary_expr backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family binary_expr bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -6864,6 +7005,7 @@ TS metrics-prometheus-* - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -6879,8 +7021,9 @@ TS metrics-prometheus-* TS metrics-prometheus-* | WHERE mode == "idle" | WHERE node_cpu_seconds_total IS NOT NULL -| STATS node_cpu_seconds_total_mode_idle_rate_avg = AVG(RATE(node_cpu_seconds_total)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS node_cpu_seconds_total_mode_idle_rate_avg = AVG(RATE(node_cpu_seconds_total)) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend) | EVAL computed_value = (100 * (1 - node_cpu_seconds_total_mode_idle_rate_avg)) +| WHERE computed_value IS NOT NULL | SORT time_bucket DESC | LIMIT 2 | SORT time_bucket ASC @@ -6917,11 +7060,11 @@ TS metrics-prometheus-* - value_mappings: 1 - has_description: True -**Warnings:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead; Grafana panel description is not carried into the migrated Kibana panel automatically; Approximated PromQL arithmetic using same-bucket ES|QL math; PromQL series labels were not retained; output is bucket-level and may collapse multiple source series +**Warnings:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead; Approximated PromQL arithmetic using same-bucket ES|QL math; PromQL series labels were not retained; output is bucket-level and may collapse multiple source series **Semantic losses:** Approximated PromQL arithmetic using same-bucket ES|QL math; Dropped variable-driven label filters during migration -**Notes:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead; Grafana panel description is not carried into the migrated Kibana panel automatically +**Notes:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead **Verdict:** MINOR_ISSUE @@ -6941,6 +7084,7 @@ scalar(node_load1{instance="$node",job="$job"}) * 100 / count(count(node_cpu_sec - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=binary_expr backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family binary_expr bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -6962,6 +7106,7 @@ scalar(node_load1{instance="$node",job="$job"}) * 100 / count(count(node_cpu_sec - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -6976,7 +7121,7 @@ scalar(node_load1{instance="$node",job="$job"}) * 100 / count(count(node_cpu_sec ``` TS metrics-prometheus-* | WHERE node_load1 IS NOT NULL OR node_cpu_seconds_total IS NOT NULL -| STATS node_load1_instance_job = AVG(LAST_OVER_TIME(node_load1)), node_cpu_seconds_total_instance_job_count = COUNT_DISTINCT(cpu) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS node_load1_instance_job = AVG(LAST_OVER_TIME(node_load1)), node_cpu_seconds_total_instance_job_count = COUNT_DISTINCT(cpu) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | EVAL computed_value = ((node_load1_instance_job * 100) / node_cpu_seconds_total_instance_job_count) | STATS computed_value = LAST(computed_value, time_bucket) | KEEP computed_value @@ -7011,11 +7156,11 @@ TS metrics-prometheus-* - value_mappings: 1 - has_description: True -**Warnings:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead; Grafana panel description is not carried into the migrated Kibana panel automatically; Approximated PromQL arithmetic using same-bucket ES|QL math; Collapsed all series of `node_load1` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity.; Approximated nested count(count()) as COUNT_DISTINCT(cpu) +**Warnings:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead; Approximated PromQL arithmetic using same-bucket ES|QL math; Collapsed all series of `node_load1` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity.; Approximated nested count(count()) as COUNT_DISTINCT(cpu); PromQL series labels were not retained; output is bucket-level and may collapse multiple source series **Semantic losses:** Approximated PromQL arithmetic using same-bucket ES|QL math; Dropped variable-driven label filters during migration; Collapsed all series of `node_load1` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity.; Approximated nested count(count()) as COUNT_DISTINCT(cpu) -**Notes:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead; Grafana panel description is not carried into the migrated Kibana panel automatically +**Notes:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead **Verdict:** MINOR_ISSUE @@ -7035,6 +7180,7 @@ TS metrics-prometheus-* - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=binary_expr backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family binary_expr bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -7056,6 +7202,7 @@ TS metrics-prometheus-* - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -7070,7 +7217,7 @@ TS metrics-prometheus-* ``` TS metrics-prometheus-* | WHERE node_memory_MemAvailable_bytes IS NOT NULL OR node_memory_MemTotal_bytes IS NOT NULL -| STATS node_memory_MemAvailable_bytes_instance_job = AVG(LAST_OVER_TIME(node_memory_MemAvailable_bytes)), node_memory_MemTotal_bytes_instance_job = AVG(LAST_OVER_TIME(node_memory_MemTotal_bytes)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS node_memory_MemAvailable_bytes_instance_job = AVG(LAST_OVER_TIME(node_memory_MemAvailable_bytes)), node_memory_MemTotal_bytes_instance_job = AVG(LAST_OVER_TIME(node_memory_MemTotal_bytes)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | EVAL computed_value = ((1 - (node_memory_MemAvailable_bytes_instance_job / node_memory_MemTotal_bytes_instance_job)) * 100) | STATS computed_value = LAST(computed_value, time_bucket) | KEEP computed_value @@ -7104,12 +7251,10 @@ TS metrics-prometheus-* - targets: 2 - has_description: True -**Warnings:** Grafana panel description is not carried into the migrated Kibana panel automatically; Approximated PromQL arithmetic using same-bucket ES|QL math; Collapsed all series of `node_memory_MemAvailable_bytes` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity.; Collapsed all series of `node_memory_MemTotal_bytes` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity.; PromQL series labels were not retained; output is bucket-level and may collapse multiple source series +**Warnings:** Approximated PromQL arithmetic using same-bucket ES|QL math; Collapsed all series of `node_memory_MemAvailable_bytes` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity.; Collapsed all series of `node_memory_MemTotal_bytes` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity.; PromQL series labels were not retained; output is bucket-level and may collapse multiple source series **Semantic losses:** Approximated PromQL arithmetic using same-bucket ES|QL math; Dropped variable-driven label filters during migration; Collapsed all series of `node_memory_MemAvailable_bytes` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity.; Collapsed all series of `node_memory_MemTotal_bytes` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. -**Notes:** Grafana panel description is not carried into the migrated Kibana panel automatically - **Verdict:** MINOR_ISSUE #### SWAP Used @@ -7128,6 +7273,7 @@ TS metrics-prometheus-* - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=binary_expr backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family binary_expr bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -7149,6 +7295,7 @@ TS metrics-prometheus-* - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -7163,7 +7310,7 @@ TS metrics-prometheus-* ``` TS metrics-prometheus-* | WHERE node_memory_SwapTotal_bytes IS NOT NULL OR node_memory_SwapFree_bytes IS NOT NULL -| STATS node_memory_SwapTotal_bytes_instance_job = AVG(LAST_OVER_TIME(node_memory_SwapTotal_bytes)), node_memory_SwapFree_bytes_instance_job = AVG(LAST_OVER_TIME(node_memory_SwapFree_bytes)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS node_memory_SwapTotal_bytes_instance_job = AVG(LAST_OVER_TIME(node_memory_SwapTotal_bytes)), node_memory_SwapFree_bytes_instance_job = AVG(LAST_OVER_TIME(node_memory_SwapFree_bytes)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | EVAL computed_value = (((node_memory_SwapTotal_bytes_instance_job - node_memory_SwapFree_bytes_instance_job) / node_memory_SwapTotal_bytes_instance_job) * 100) | STATS computed_value = LAST(computed_value, time_bucket) | KEEP computed_value @@ -7198,11 +7345,11 @@ TS metrics-prometheus-* - value_mappings: 1 - has_description: True -**Warnings:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead; Grafana panel description is not carried into the migrated Kibana panel automatically; Approximated PromQL arithmetic using same-bucket ES|QL math; Collapsed all series of `node_memory_SwapTotal_bytes` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity.; Collapsed all series of `node_memory_SwapFree_bytes` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. +**Warnings:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead; Approximated PromQL arithmetic using same-bucket ES|QL math; Collapsed all series of `node_memory_SwapTotal_bytes` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity.; Collapsed all series of `node_memory_SwapFree_bytes` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity.; PromQL series labels were not retained; output is bucket-level and may collapse multiple source series **Semantic losses:** Approximated PromQL arithmetic using same-bucket ES|QL math; Dropped variable-driven label filters during migration; Collapsed all series of `node_memory_SwapTotal_bytes` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity.; Collapsed all series of `node_memory_SwapFree_bytes` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. -**Notes:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead; Grafana panel description is not carried into the migrated Kibana panel automatically +**Notes:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead **Verdict:** MINOR_ISSUE @@ -7222,6 +7369,7 @@ TS metrics-prometheus-* - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=binary_expr backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family binary_expr bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -7243,6 +7391,7 @@ TS metrics-prometheus-* - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -7259,7 +7408,7 @@ TS metrics-prometheus-* | WHERE mountpoint == "/" | WHERE (fstype != "rootfs" OR (fstype IS NULL AND "" != "rootfs")) | WHERE node_filesystem_avail_bytes IS NOT NULL OR node_filesystem_size_bytes IS NOT NULL -| STATS node_filesystem_avail_bytes_mountpoint_fstype_rootfs = AVG(LAST_OVER_TIME(node_filesystem_avail_bytes)), node_filesystem_size_bytes_mountpoint_fstype_rootfs = AVG(LAST_OVER_TIME(node_filesystem_size_bytes)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS node_filesystem_avail_bytes_mountpoint_fstype_rootfs = AVG(LAST_OVER_TIME(node_filesystem_avail_bytes)), node_filesystem_size_bytes_mountpoint_fstype_rootfs = AVG(LAST_OVER_TIME(node_filesystem_size_bytes)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | EVAL computed_value = (100 - ((node_filesystem_avail_bytes_mountpoint_fstype_rootfs * 100) / node_filesystem_size_bytes_mountpoint_fstype_rootfs)) | STATS computed_value = LAST(computed_value, time_bucket) | KEEP computed_value @@ -7294,11 +7443,11 @@ TS metrics-prometheus-* - value_mappings: 1 - has_description: True -**Warnings:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead; Grafana panel description is not carried into the migrated Kibana panel automatically; Approximated PromQL arithmetic using same-bucket ES|QL math; Collapsed all series of `node_filesystem_avail_bytes` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity.; Collapsed all series of `node_filesystem_size_bytes` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. +**Warnings:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead; Approximated PromQL arithmetic using same-bucket ES|QL math; Collapsed all series of `node_filesystem_avail_bytes` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity.; Collapsed all series of `node_filesystem_size_bytes` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity.; PromQL series labels were not retained; output is bucket-level and may collapse multiple source series **Semantic losses:** Approximated PromQL arithmetic using same-bucket ES|QL math; Dropped variable-driven label filters during migration; Collapsed all series of `node_filesystem_avail_bytes` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity.; Collapsed all series of `node_filesystem_size_bytes` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. -**Notes:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead; Grafana panel description is not carried into the migrated Kibana panel automatically +**Notes:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead **Verdict:** MINOR_ISSUE @@ -7318,6 +7467,7 @@ count(count(node_cpu_seconds_total{instance="$node",job="$job"}) by (cpu)) - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=nested_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family nested_agg bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -7345,6 +7495,7 @@ count(count(node_cpu_seconds_total{instance="$node",job="$job"}) by (cpu)) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -7387,11 +7538,11 @@ FROM metrics-prometheus-* - value_mappings: 1 - has_description: True -**Warnings:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead; Grafana panel description is not carried into the migrated Kibana panel automatically; Approximated nested count(count()) as COUNT_DISTINCT(cpu) +**Warnings:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead; Approximated nested count(count()) as COUNT_DISTINCT(cpu) **Semantic losses:** Dropped variable-driven label filters during migration; Approximated nested count(count()) as COUNT_DISTINCT(cpu) -**Notes:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead; Grafana panel description is not carried into the migrated Kibana panel automatically +**Notes:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead **Verdict:** MINOR_ISSUE @@ -7411,6 +7562,7 @@ node_time_seconds{instance="$node",job="$job"} - node_boot_time_seconds{instance - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=binary_expr backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family binary_expr bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -7432,6 +7584,7 @@ node_time_seconds{instance="$node",job="$job"} - node_boot_time_seconds{instance - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -7443,7 +7596,7 @@ node_time_seconds{instance="$node",job="$job"} - node_boot_time_seconds{instance ``` TS metrics-prometheus-* | WHERE node_time_seconds IS NOT NULL OR node_boot_time_seconds IS NOT NULL -| STATS node_time_seconds_instance_job = AVG(LAST_OVER_TIME(node_time_seconds)), node_boot_time_seconds_instance_job = AVG(LAST_OVER_TIME(node_boot_time_seconds)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS node_time_seconds_instance_job = AVG(LAST_OVER_TIME(node_time_seconds)), node_boot_time_seconds_instance_job = AVG(LAST_OVER_TIME(node_boot_time_seconds)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | EVAL computed_value = (node_time_seconds_instance_job - node_boot_time_seconds_instance_job) | STATS computed_value = LAST(computed_value, time_bucket) | KEEP computed_value @@ -7477,11 +7630,11 @@ TS metrics-prometheus-* - value_mappings: 1 - has_description: True -**Warnings:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead; Grafana panel description is not carried into the migrated Kibana panel automatically; Approximated PromQL arithmetic using same-bucket ES|QL math; Collapsed all series of `node_time_seconds` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity.; Collapsed all series of `node_boot_time_seconds` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. +**Warnings:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead; Approximated PromQL arithmetic using same-bucket ES|QL math; Collapsed all series of `node_time_seconds` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity.; Collapsed all series of `node_boot_time_seconds` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity.; PromQL series labels were not retained; output is bucket-level and may collapse multiple source series **Semantic losses:** Approximated PromQL arithmetic using same-bucket ES|QL math; Dropped variable-driven label filters during migration; Collapsed all series of `node_time_seconds` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity.; Collapsed all series of `node_boot_time_seconds` into a single AVG line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. -**Notes:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead; Grafana panel description is not carried into the migrated Kibana panel automatically +**Notes:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead **Verdict:** MINOR_ISSUE @@ -7501,6 +7654,7 @@ node_filesystem_size_bytes{instance="$node",job="$job",mountpoint="/",fstype!="r - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_metric backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -7531,6 +7685,7 @@ node_filesystem_size_bytes{instance="$node",job="$job",mountpoint="/",fstype!="r - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -7544,7 +7699,7 @@ TS metrics-prometheus-* | WHERE mountpoint == "/" | WHERE (fstype != "rootfs" OR (fstype IS NULL AND "" != "rootfs")) | WHERE node_filesystem_size_bytes IS NOT NULL -| STATS node_filesystem_size_bytes = MAX(LAST_OVER_TIME(node_filesystem_size_bytes)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS node_filesystem_size_bytes = MAX(LAST_OVER_TIME(node_filesystem_size_bytes)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | STATS node_filesystem_size_bytes = LAST(node_filesystem_size_bytes, time_bucket) | KEEP node_filesystem_size_bytes ``` @@ -7557,7 +7712,7 @@ TS metrics-prometheus-* - Source lang: `promql` - Target index: `metrics-prometheus-*` - Output metric: `node_filesystem_size_bytes` -- Semantic losses: Dropped variable-driven label filters during migration +- Semantic losses: Dropped variable-driven label filters during migration, Collapsed all series of `node_filesystem_size_bytes` into a single MAX line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. **Visual IR:** @@ -7576,9 +7731,11 @@ TS metrics-prometheus-* - value_mappings: 1 - has_description: True -**Warnings:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead; Grafana panel description is not carried into the migrated Kibana panel automatically +**Warnings:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead; Collapsed all series of `node_filesystem_size_bytes` into a single MAX line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. -**Semantic losses:** Dropped variable-driven label filters during migration +**Semantic losses:** Dropped variable-driven label filters during migration; Collapsed all series of `node_filesystem_size_bytes` into a single MAX line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. + +**Notes:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead **Verdict:** MINOR_ISSUE @@ -7598,6 +7755,7 @@ node_memory_MemTotal_bytes{instance="$node",job="$job"} - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_metric backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -7628,6 +7786,7 @@ node_memory_MemTotal_bytes{instance="$node",job="$job"} - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -7639,7 +7798,7 @@ node_memory_MemTotal_bytes{instance="$node",job="$job"} ``` TS metrics-prometheus-* | WHERE node_memory_MemTotal_bytes IS NOT NULL -| STATS node_memory_MemTotal_bytes = MAX(LAST_OVER_TIME(node_memory_MemTotal_bytes)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS node_memory_MemTotal_bytes = MAX(LAST_OVER_TIME(node_memory_MemTotal_bytes)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | STATS node_memory_MemTotal_bytes = LAST(node_memory_MemTotal_bytes, time_bucket) | KEEP node_memory_MemTotal_bytes ``` @@ -7652,7 +7811,7 @@ TS metrics-prometheus-* - Source lang: `promql` - Target index: `metrics-prometheus-*` - Output metric: `node_memory_MemTotal_bytes` -- Semantic losses: Dropped variable-driven label filters during migration +- Semantic losses: Dropped variable-driven label filters during migration, Collapsed all series of `node_memory_MemTotal_bytes` into a single MAX line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. **Visual IR:** @@ -7671,9 +7830,11 @@ TS metrics-prometheus-* - value_mappings: 1 - has_description: True -**Warnings:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead; Grafana panel description is not carried into the migrated Kibana panel automatically +**Warnings:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead; Collapsed all series of `node_memory_MemTotal_bytes` into a single MAX line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. -**Semantic losses:** Dropped variable-driven label filters during migration +**Semantic losses:** Dropped variable-driven label filters during migration; Collapsed all series of `node_memory_MemTotal_bytes` into a single MAX line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. + +**Notes:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead **Verdict:** MINOR_ISSUE @@ -7693,6 +7854,7 @@ node_memory_SwapTotal_bytes{instance="$node",job="$job"} - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_metric backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -7723,6 +7885,7 @@ node_memory_SwapTotal_bytes{instance="$node",job="$job"} - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -7734,7 +7897,7 @@ node_memory_SwapTotal_bytes{instance="$node",job="$job"} ``` TS metrics-prometheus-* | WHERE node_memory_SwapTotal_bytes IS NOT NULL -| STATS node_memory_SwapTotal_bytes = MAX(LAST_OVER_TIME(node_memory_SwapTotal_bytes)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS node_memory_SwapTotal_bytes = MAX(LAST_OVER_TIME(node_memory_SwapTotal_bytes)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | STATS node_memory_SwapTotal_bytes = LAST(node_memory_SwapTotal_bytes, time_bucket) | KEEP node_memory_SwapTotal_bytes ``` @@ -7747,7 +7910,7 @@ TS metrics-prometheus-* - Source lang: `promql` - Target index: `metrics-prometheus-*` - Output metric: `node_memory_SwapTotal_bytes` -- Semantic losses: Dropped variable-driven label filters during migration +- Semantic losses: Dropped variable-driven label filters during migration, Collapsed all series of `node_memory_SwapTotal_bytes` into a single MAX line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. **Visual IR:** @@ -7766,9 +7929,11 @@ TS metrics-prometheus-* - value_mappings: 1 - has_description: True -**Warnings:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead; Grafana panel description is not carried into the migrated Kibana panel automatically +**Warnings:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead; Collapsed all series of `node_memory_SwapTotal_bytes` into a single MAX line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. -**Semantic losses:** Dropped variable-driven label filters during migration +**Semantic losses:** Dropped variable-driven label filters during migration; Collapsed all series of `node_memory_SwapTotal_bytes` into a single MAX line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. + +**Notes:** Grafana panel has 1 value mapping(s) (e.g. 0 -> 'Down', null -> 'N/A'); Kibana panel mappings assign colors, not display text, so the raw value is shown instead **Verdict:** MINOR_ISSUE @@ -7788,6 +7953,7 @@ sum(irate(node_cpu_seconds_total{instance="$node",job="$job", mode="system"}[$__ - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=binary_expr backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family binary_expr bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -7809,6 +7975,7 @@ sum(irate(node_cpu_seconds_total{instance="$node",job="$job", mode="system"}[$__ - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -7822,7 +7989,7 @@ sum(irate(node_cpu_seconds_total{instance="$node",job="$job", mode="system"}[$__ ``` TS metrics-prometheus-* | WHERE node_cpu_seconds_total IS NOT NULL -| STATS node_cpu_seconds_total_A_lhs = SUM(CASE((mode == "system"), IRATE(node_cpu_seconds_total), NULL)), node_cpu_seconds_total_A_rhs = COUNT_DISTINCT(cpu), node_cpu_seconds_total_B_lhs = SUM(CASE((mode == "user"), IRATE(node_cpu_seconds_total), NULL)), node_cpu_seconds_total_B_rhs = COUNT_DISTINCT(cpu), node_cpu_seconds_total_C_lhs = SUM(CASE((mode == "iowait"), IRATE(node_cpu_seconds_total), NULL)), node_cpu_seconds_total_C_rhs = COUNT_DISTINCT(cpu), node_cpu_seconds_total_D_lhs = SUM(CASE((mode RLIKE ".*irq"), IRATE(node_cpu_seconds_total), NULL)), node_cpu_seconds_total_D_rhs = COUNT_DISTINCT(cpu), node_cpu_seconds_total_E_lhs = SUM(CASE(((mode != "idle" OR (mode IS NULL AND "" != "idle"))) and ((mode != "user" OR (mode IS NULL AND "" != "user"))) and ((mode != "system" OR (mode IS NULL AND "" != "system"))) and ((mode != "iowait" OR (mode IS NULL AND "" != "iowait"))) and ((mode != "irq" OR (mode IS NULL AND "" != "irq"))) and ((mode != "softirq" OR (mode IS NULL AND "" != "softirq"))), IRATE(node_cpu_seconds_total), NULL)), node_cpu_seconds_total_E_rhs = COUNT_DISTINCT(cpu), node_cpu_seconds_total_F_lhs = SUM(CASE((mode == "idle"), IRATE(node_cpu_seconds_total), NULL)), node_cpu_seconds_total_F_rhs = COUNT_DISTINCT(cpu) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS node_cpu_seconds_total_A_lhs = SUM(CASE((mode == "system"), IRATE(node_cpu_seconds_total), NULL)), node_cpu_seconds_total_A_rhs = COUNT_DISTINCT(cpu), node_cpu_seconds_total_B_lhs = SUM(CASE((mode == "user"), IRATE(node_cpu_seconds_total), NULL)), node_cpu_seconds_total_B_rhs = COUNT_DISTINCT(cpu), node_cpu_seconds_total_C_lhs = SUM(CASE((mode == "iowait"), IRATE(node_cpu_seconds_total), NULL)), node_cpu_seconds_total_C_rhs = COUNT_DISTINCT(cpu), node_cpu_seconds_total_D_lhs = SUM(CASE((mode RLIKE ".*irq"), IRATE(node_cpu_seconds_total), NULL)), node_cpu_seconds_total_D_rhs = COUNT_DISTINCT(cpu), node_cpu_seconds_total_E_lhs = SUM(CASE(((mode != "idle" OR (mode IS NULL AND "" != "idle"))) and ((mode != "user" OR (mode IS NULL AND "" != "user"))) and ((mode != "system" OR (mode IS NULL AND "" != "system"))) and ((mode != "iowait" OR (mode IS NULL AND "" != "iowait"))) and ((mode != "irq" OR (mode IS NULL AND "" != "irq"))) and ((mode != "softirq" OR (mode IS NULL AND "" != "softirq"))), IRATE(node_cpu_seconds_total), NULL)), node_cpu_seconds_total_E_rhs = COUNT_DISTINCT(cpu), node_cpu_seconds_total_F_lhs = SUM(CASE((mode == "idle"), IRATE(node_cpu_seconds_total), NULL)), node_cpu_seconds_total_F_rhs = COUNT_DISTINCT(cpu) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend) | EVAL Busy_System = (node_cpu_seconds_total_A_lhs / node_cpu_seconds_total_A_rhs) | EVAL Busy_User = (node_cpu_seconds_total_B_lhs / node_cpu_seconds_total_B_rhs) | EVAL Busy_Iowait = (node_cpu_seconds_total_C_lhs / node_cpu_seconds_total_C_rhs) @@ -7860,14 +8027,13 @@ TS metrics-prometheus-* - targets: 6 - field_overrides: 7 +- field_override_properties: 7 - has_description: True -**Warnings:** Grafana panel has 7 field override(s); verify visual mappings manually; Grafana panel description is not carried into the migrated Kibana panel automatically; Approximated nested count(count()) as COUNT_DISTINCT(cpu) +**Warnings:** Approximated nested count(count()) as COUNT_DISTINCT(cpu) **Semantic losses:** Dropped variable-driven label filters during migration; Approximated nested count(count()) as COUNT_DISTINCT(cpu) -**Notes:** Grafana panel has 7 field override(s); verify visual mappings manually; Grafana panel description is not carried into the migrated Kibana panel automatically - **Verdict:** MINOR_ISSUE #### Memory Basic @@ -7886,6 +8052,7 @@ node_memory_MemTotal_bytes{instance="$node",job="$job"} ||| node_memory_MemTotal - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_metric backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -7916,6 +8083,7 @@ node_memory_MemTotal_bytes{instance="$node",job="$job"} ||| node_memory_MemTotal - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -7929,7 +8097,7 @@ node_memory_MemTotal_bytes{instance="$node",job="$job"} ||| node_memory_MemTotal ``` TS metrics-prometheus-* | WHERE node_memory_MemTotal_bytes IS NOT NULL OR node_memory_Cached_bytes IS NOT NULL OR node_memory_Buffers_bytes IS NOT NULL OR node_memory_SReclaimable_bytes IS NOT NULL OR node_memory_MemFree_bytes IS NOT NULL OR node_memory_SwapTotal_bytes IS NOT NULL OR node_memory_SwapFree_bytes IS NOT NULL -| STATS RAM_Total = AVG(LAST_OVER_TIME(node_memory_MemTotal_bytes)), node_memory_Cached_bytes_B_rhs_lhs_lhs = AVG(LAST_OVER_TIME(node_memory_Cached_bytes)), node_memory_Buffers_bytes_B_rhs_lhs_rhs = AVG(LAST_OVER_TIME(node_memory_Buffers_bytes)), node_memory_SReclaimable_bytes_B_rhs_rhs = AVG(LAST_OVER_TIME(node_memory_SReclaimable_bytes)), node_memory_Cached_bytes_C_lhs_lhs = AVG(LAST_OVER_TIME(node_memory_Cached_bytes)), node_memory_Buffers_bytes_C_lhs_rhs = AVG(LAST_OVER_TIME(node_memory_Buffers_bytes)), node_memory_SReclaimable_bytes_C_rhs = AVG(LAST_OVER_TIME(node_memory_SReclaimable_bytes)), RAM_Free = AVG(LAST_OVER_TIME(node_memory_MemFree_bytes)), node_memory_SwapTotal_bytes_E_lhs = AVG(LAST_OVER_TIME(node_memory_SwapTotal_bytes)), node_memory_SwapFree_bytes_E_rhs = AVG(LAST_OVER_TIME(node_memory_SwapFree_bytes)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance, job +| STATS RAM_Total = AVG(LAST_OVER_TIME(node_memory_MemTotal_bytes)), node_memory_Cached_bytes_B_rhs_lhs_lhs = AVG(LAST_OVER_TIME(node_memory_Cached_bytes)), node_memory_Buffers_bytes_B_rhs_lhs_rhs = AVG(LAST_OVER_TIME(node_memory_Buffers_bytes)), node_memory_SReclaimable_bytes_B_rhs_rhs = AVG(LAST_OVER_TIME(node_memory_SReclaimable_bytes)), node_memory_Cached_bytes_C_lhs_lhs = AVG(LAST_OVER_TIME(node_memory_Cached_bytes)), node_memory_Buffers_bytes_C_lhs_rhs = AVG(LAST_OVER_TIME(node_memory_Buffers_bytes)), node_memory_SReclaimable_bytes_C_rhs = AVG(LAST_OVER_TIME(node_memory_SReclaimable_bytes)), RAM_Free = AVG(LAST_OVER_TIME(node_memory_MemFree_bytes)), node_memory_SwapTotal_bytes_E_lhs = AVG(LAST_OVER_TIME(node_memory_SwapTotal_bytes)), node_memory_SwapFree_bytes_E_rhs = AVG(LAST_OVER_TIME(node_memory_SwapFree_bytes)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), instance, job | EVAL RAM_Used = ((RAM_Total - RAM_Free) - ((node_memory_Cached_bytes_B_rhs_lhs_lhs + node_memory_Buffers_bytes_B_rhs_lhs_rhs) + node_memory_SReclaimable_bytes_B_rhs_rhs)) | EVAL RAM_Cache_Buffer = ((node_memory_Cached_bytes_C_lhs_lhs + node_memory_Buffers_bytes_C_lhs_rhs) + node_memory_SReclaimable_bytes_C_rhs) | EVAL SWAP_Used = (node_memory_SwapTotal_bytes_E_lhs - node_memory_SwapFree_bytes_E_rhs) @@ -7964,14 +8132,13 @@ TS metrics-prometheus-* - targets: 5 - field_overrides: 23 +- field_override_properties: 27 - has_description: True -**Warnings:** Grafana panel has 23 field override(s); verify visual mappings manually; Grafana panel description is not carried into the migrated Kibana panel automatically; Composited multi-label grouping (instance, job) into a single XY breakdown column +**Warnings:** Composited multi-label grouping (instance, job) into a single XY breakdown column **Semantic losses:** Dropped variable-driven label filters during migration -**Notes:** Grafana panel has 23 field override(s); verify visual mappings manually; Grafana panel description is not carried into the migrated Kibana panel automatically - **Verdict:** MINOR_ISSUE #### Network Traffic Basic @@ -7990,6 +8157,7 @@ irate(node_network_receive_bytes_total{instance="$node",job="$job"}[$__rate_inte - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=binary_expr backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family binary_expr bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -8011,23 +8179,24 @@ irate(node_network_receive_bytes_total{instance="$node",job="$job"}[$__rate_inte - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` - `query_validators` / `late_bound_group_control` - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` -- `panel_translators` / `xy_panel` → mapped to line panel +- `panel_translators` / `xy_panel` → mapped to area panel -**Translated (line):** +**Translated (area):** ``` TS metrics-prometheus-* | WHERE node_network_receive_bytes_total IS NOT NULL OR node_network_transmit_bytes_total IS NOT NULL -| STATS node_network_receive_bytes_total_A_lhs = IRATE(node_network_receive_bytes_total), node_network_transmit_bytes_total_B_lhs = IRATE(node_network_transmit_bytes_total) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS node_network_receive_bytes_total_A_lhs = AVG(IRATE(node_network_receive_bytes_total)), node_network_transmit_bytes_total_B_lhs = AVG(IRATE(node_network_transmit_bytes_total)) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend), device | EVAL recv = (node_network_receive_bytes_total_A_lhs * 8) -| EVAL trans = (node_network_transmit_bytes_total_B_lhs * 8) -| KEEP time_bucket, recv, trans +| EVAL trans = (-1 * (node_network_transmit_bytes_total_B_lhs * 8)) +| KEEP time_bucket, device, recv, trans | SORT time_bucket ASC ``` @@ -8040,15 +8209,15 @@ TS metrics-prometheus-* - Source lang: `promql` - Target index: `metrics-prometheus-*` - Output metric: `recv` -- Output groups: `time_bucket` +- Output groups: `time_bucket, device` - Semantic losses: Dropped variable-driven label filters during migration **Visual IR:** -- Kibana type: `line` +- Kibana type: `area` - Layout: x=0, y=11, w=24, h=10 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, legend +- Config keys: type, query, dimension, metrics, mode **Operational IR:** @@ -8058,10 +8227,9 @@ TS metrics-prometheus-* - targets: 2 - field_overrides: 24 +- field_override_properties: 24 - has_description: True -**Warnings:** Grafana panel has 24 field override(s); verify visual mappings manually; Grafana panel description is not carried into the migrated Kibana panel automatically - **Semantic losses:** Dropped variable-driven label filters during migration **Verdict:** MINOR_ISSUE @@ -8082,6 +8250,7 @@ TS metrics-prometheus-* - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=binary_expr backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family binary_expr bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -8103,21 +8272,22 @@ TS metrics-prometheus-* - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` - `query_validators` / `late_bound_group_control` - `panel_translators` / `metric_panel` - `panel_translators` / `bargauge_panel` -- `panel_translators` / `xy_panel` → mapped to line panel +- `panel_translators` / `xy_panel` → mapped to area panel -**Translated (line):** +**Translated (area):** ``` TS metrics-prometheus-* | WHERE (NOT (device RLIKE "rootfs") OR (device IS NULL AND NOT ("" RLIKE "rootfs"))) | WHERE node_filesystem_avail_bytes IS NOT NULL OR node_filesystem_size_bytes IS NOT NULL -| STATS node_filesystem_avail_bytes_device_rootfs = AVG(LAST_OVER_TIME(node_filesystem_avail_bytes)), node_filesystem_size_bytes_device_rootfs = AVG(LAST_OVER_TIME(node_filesystem_size_bytes)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), mountpoint +| STATS node_filesystem_avail_bytes_device_rootfs = AVG(LAST_OVER_TIME(node_filesystem_avail_bytes)), node_filesystem_size_bytes_device_rootfs = AVG(LAST_OVER_TIME(node_filesystem_size_bytes)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), mountpoint | EVAL computed_value = (100 - ((node_filesystem_avail_bytes_device_rootfs * 100) / node_filesystem_size_bytes_device_rootfs)) | KEEP time_bucket, mountpoint, computed_value | SORT time_bucket ASC @@ -8137,10 +8307,10 @@ TS metrics-prometheus-* **Visual IR:** -- Kibana type: `line` +- Kibana type: `area` - Layout: x=24, y=11, w=24, h=10 - Presentation kind: `esql` -- Config keys: type, query, dimension, metrics, breakdown +- Config keys: type, query, dimension, metrics, mode **Operational IR:** @@ -8151,12 +8321,10 @@ TS metrics-prometheus-* - targets: 1 - has_description: True -**Warnings:** Grafana panel description is not carried into the migrated Kibana panel automatically; Approximated PromQL arithmetic using same-bucket ES|QL math +**Warnings:** Approximated PromQL arithmetic using same-bucket ES|QL math **Semantic losses:** Approximated PromQL arithmetic using same-bucket ES|QL math; Dropped variable-driven label filters during migration -**Notes:** Grafana panel description is not carried into the migrated Kibana panel automatically - **Verdict:** MINOR_ISSUE
@@ -8179,8 +8347,8 @@ TS metrics-prometheus-* |-------|---------------------|--------|---------|-------------|-----------------| | Untitled | `text` → `markdown` | migrated | **EXPECTED_LIMITATION** | — | — | | Uptime | `singlestat` → `metric` | migrated_with_warnings | **MINOR_ISSUE** | time() - process_start_time_seconds{instance="$instance"} | FROM metrics-prometheus-* \| WHERE process_start_time_seconds IS NOT NULL \| STA... | -| Total count of time series | `singlestat` → `metric` | migrated | **MINOR_ISSUE** | prometheus_tsdb_head_series{instance="$instance"} | TS metrics-prometheus-* \| WHERE prometheus_tsdb_head_series IS NOT NULL \| STAT... | -| Version | `singlestat` → `metric` | migrated | **MINOR_ISSUE** | prometheus_build_info{instance="$instance"} | TS metrics-prometheus-* \| WHERE prometheus_build_info IS NOT NULL \| STATS prom... | +| Total count of time series | `singlestat` → `metric` | migrated_with_warnings | **MINOR_ISSUE** | prometheus_tsdb_head_series{instance="$instance"} | TS metrics-prometheus-* \| WHERE prometheus_tsdb_head_series IS NOT NULL \| STAT... | +| Version | `singlestat` → `metric` | migrated_with_warnings | **MINOR_ISSUE** | prometheus_build_info{instance="$instance"} | TS metrics-prometheus-* \| WHERE prometheus_build_info IS NOT NULL \| STATS prom... | | Actual head block length | `singlestat` → `metric` | migrated_with_warnings | **MINOR_ISSUE** | prometheus_tsdb_head_max_time{instance="$instance"} - prometheus_tsdb_head_min_t... | TS metrics-prometheus-* \| WHERE prometheus_tsdb_head_max_time IS NOT NULL OR pr... | | Untitled | `text` → `markdown` | migrated | **EXPECTED_LIMITATION** | — | — | | 2 | `singlestat` → `metric` | migrated | **CORRECT** | 2 | ROW constant_value = 2.0 | @@ -8190,7 +8358,7 @@ TS metrics-prometheus-* | Scrape delay (counts with 1m scrape interval) | `graph` → `line` | migrated_with_warnings | **MINOR_ISSUE** | prometheus_target_interval_length_seconds{instance="$instance",quantile="0.99"} ... | TS metrics-prometheus-* \| WHERE quantile == "0.99" \| WHERE prometheus_target_i... | | Rule evaulation duration | `graph` → `line` | migrated_with_warnings | **MINOR_ISSUE** | sum(prometheus_evaluator_duration_seconds{instance="$instance"}) by (instance, q... | TS metrics-prometheus-* \| WHERE prometheus_evaluator_duration_seconds IS NOT NU... | | Request count | `graph` → `line` | migrated | **MINOR_ISSUE** | sum(increase(http_requests_total{instance="$instance"}[$aggregation_interval])) ... | TS metrics-prometheus-* \| WHERE http_requests_total IS NOT NULL \| STATS http_r... | -| Request duration per handler | `graph` → `line` | migrated | **MINOR_ISSUE** | max(sum(http_request_duration_microseconds{instance="$instance"}) by (instance, ... | FROM metrics-prometheus-* \| STATS inner_val = SUM(http_request_duration_microse... | +| Request duration per handler | `graph` → `line` | migrated | **MINOR_ISSUE** | max(sum(http_request_duration_microseconds{instance="$instance"}) by (instance, ... | TS metrics-prometheus-* \| WHERE http_request_duration_microseconds IS NOT NULL ... | | Request size by handler | `graph` → `line` | migrated_with_warnings | **MINOR_ISSUE** | sum(increase(http_request_size_bytes{instance="$instance", quantile="0.99"}[$agg... | TS metrics-prometheus-* \| WHERE quantile == "0.99" \| WHERE http_request_size_b... | | Cont of concurent queries | `graph` → `line` | migrated_with_warnings | **MINOR_ISSUE** | sum(prometheus_engine_queries{instance="$instance"}) by (instance, handler) \|\|... | TS metrics-prometheus-* \| WHERE prometheus_engine_queries IS NOT NULL OR promet... | | Alert queue size | `graph` → `line` | migrated | **MINOR_ISSUE** | sum(prometheus_notifications_queue_capacity{instance="$instance"})by (instance) ... | TS metrics-prometheus-* \| WHERE prometheus_notifications_queue_capacity IS NOT ... | @@ -8242,6 +8410,7 @@ time() - process_start_time_seconds{instance="$instance"} - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=uptime backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family uptime bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -8261,6 +8430,7 @@ time() - process_start_time_seconds{instance="$instance"} - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -8325,6 +8495,7 @@ prometheus_tsdb_head_series{instance="$instance"} - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_metric backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -8355,6 +8526,7 @@ prometheus_tsdb_head_series{instance="$instance"} - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -8366,7 +8538,7 @@ prometheus_tsdb_head_series{instance="$instance"} ``` TS metrics-prometheus-* | WHERE prometheus_tsdb_head_series IS NOT NULL -| STATS prometheus_tsdb_head_series = MAX(LAST_OVER_TIME(prometheus_tsdb_head_series)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS prometheus_tsdb_head_series = MAX(LAST_OVER_TIME(prometheus_tsdb_head_series)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | STATS prometheus_tsdb_head_series = LAST(prometheus_tsdb_head_series, time_bucket) | KEEP prometheus_tsdb_head_series ``` @@ -8379,7 +8551,7 @@ TS metrics-prometheus-* - Source lang: `promql` - Target index: `metrics-prometheus-*` - Output metric: `prometheus_tsdb_head_series` -- Semantic losses: Dropped variable-driven label filters during migration +- Semantic losses: Dropped variable-driven label filters during migration, Collapsed all series of `prometheus_tsdb_head_series` into a single MAX line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. **Visual IR:** @@ -8396,7 +8568,9 @@ TS metrics-prometheus-* - targets: 1 -**Semantic losses:** Dropped variable-driven label filters during migration +**Warnings:** Collapsed all series of `prometheus_tsdb_head_series` into a single MAX line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. + +**Semantic losses:** Dropped variable-driven label filters during migration; Collapsed all series of `prometheus_tsdb_head_series` into a single MAX line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. **Verdict:** MINOR_ISSUE @@ -8416,6 +8590,7 @@ prometheus_build_info{instance="$instance"} - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_metric backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -8446,6 +8621,7 @@ prometheus_build_info{instance="$instance"} - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -8457,7 +8633,7 @@ prometheus_build_info{instance="$instance"} ``` TS metrics-prometheus-* | WHERE prometheus_build_info IS NOT NULL -| STATS prometheus_build_info = MAX(LAST_OVER_TIME(prometheus_build_info)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS prometheus_build_info = MAX(LAST_OVER_TIME(prometheus_build_info)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | STATS prometheus_build_info = LAST(prometheus_build_info, time_bucket) | KEEP prometheus_build_info ``` @@ -8470,7 +8646,7 @@ TS metrics-prometheus-* - Source lang: `promql` - Target index: `metrics-prometheus-*` - Output metric: `prometheus_build_info` -- Semantic losses: Dropped variable-driven label filters during migration +- Semantic losses: Dropped variable-driven label filters during migration, Collapsed all series of `prometheus_build_info` into a single MAX line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. **Visual IR:** @@ -8487,7 +8663,9 @@ TS metrics-prometheus-* - targets: 1 -**Semantic losses:** Dropped variable-driven label filters during migration +**Warnings:** Collapsed all series of `prometheus_build_info` into a single MAX line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. + +**Semantic losses:** Dropped variable-driven label filters during migration; Collapsed all series of `prometheus_build_info` into a single MAX line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. **Verdict:** MINOR_ISSUE @@ -8507,6 +8685,7 @@ prometheus_tsdb_head_max_time{instance="$instance"} - prometheus_tsdb_head_min_t - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=binary_expr backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family binary_expr bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -8528,6 +8707,7 @@ prometheus_tsdb_head_max_time{instance="$instance"} - prometheus_tsdb_head_min_t - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -8539,7 +8719,7 @@ prometheus_tsdb_head_max_time{instance="$instance"} - prometheus_tsdb_head_min_t ``` TS metrics-prometheus-* | WHERE prometheus_tsdb_head_max_time IS NOT NULL OR prometheus_tsdb_head_min_time IS NOT NULL -| STATS prometheus_tsdb_head_max_time_instance = AVG(LAST_OVER_TIME(prometheus_tsdb_head_max_time)), prometheus_tsdb_head_min_time_instance = AVG(LAST_OVER_TIME(prometheus_tsdb_head_min_time)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS prometheus_tsdb_head_max_time_instance = AVG(LAST_OVER_TIME(prometheus_tsdb_head_max_time)), prometheus_tsdb_head_min_time_instance = AVG(LAST_OVER_TIME(prometheus_tsdb_head_min_time)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | EVAL computed_value = (prometheus_tsdb_head_max_time_instance - prometheus_tsdb_head_min_time_instance) | STATS computed_value = LAST(computed_value, time_bucket) | KEEP computed_value @@ -8593,6 +8773,7 @@ TS metrics-prometheus-* - `query_preprocessors` / `grafana_macros` - `query_preprocessors` / `parse_fragment` → parsed fragment family=scalar backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family scalar bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -8609,6 +8790,7 @@ TS metrics-prometheus-* - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -8663,6 +8845,7 @@ max(prometheus_engine_query_duration_seconds{instance="$instance"}) by (instance - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -8692,6 +8875,7 @@ max(prometheus_engine_query_duration_seconds{instance="$instance"}) by (instance - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -8705,7 +8889,7 @@ max(prometheus_engine_query_duration_seconds{instance="$instance"}) by (instance ``` TS metrics-prometheus-* | WHERE prometheus_engine_query_duration_seconds IS NOT NULL -| STATS prometheus_engine_query_duration_seconds = MAX(prometheus_engine_query_duration_seconds) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance, slice +| STATS prometheus_engine_query_duration_seconds = MAX(prometheus_engine_query_duration_seconds) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), instance, slice | EVAL series_group = CONCAT(COALESCE(TO_STRING(instance), ""), " / ", COALESCE(TO_STRING(slice), "")) | SORT time_bucket ASC ``` @@ -8760,6 +8944,7 @@ sum(increase(prometheus_tsdb_head_series_created_total{instance="$instance"}[$ag - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=range_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -8788,6 +8973,7 @@ sum(increase(prometheus_tsdb_head_series_created_total{instance="$instance"}[$ag - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -8801,7 +8987,7 @@ sum(increase(prometheus_tsdb_head_series_created_total{instance="$instance"}[$ag ``` TS metrics-prometheus-* | WHERE prometheus_tsdb_head_series_created_total IS NOT NULL OR prometheus_tsdb_head_series_removed_total IS NOT NULL -| STATS created_on = SUM(INCREASE(prometheus_tsdb_head_series_created_total)), prometheus_tsdb_head_series_removed_total_B = SUM(INCREASE(prometheus_tsdb_head_series_removed_total)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance +| STATS created_on = SUM(INCREASE(prometheus_tsdb_head_series_created_total)), prometheus_tsdb_head_series_removed_total_B = SUM(INCREASE(prometheus_tsdb_head_series_removed_total)) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend), instance | EVAL prometheus_tsdb_head_series_removed_total_B_calc = prometheus_tsdb_head_series_removed_total_B * -1 | EVAL removed_on = prometheus_tsdb_head_series_removed_total_B_calc | KEEP time_bucket, instance, created_on, removed_on @@ -8858,6 +9044,7 @@ sum(increase(prometheus_target_scrapes_exceeded_sample_limit_total{instance="$in - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=range_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -8886,6 +9073,7 @@ sum(increase(prometheus_target_scrapes_exceeded_sample_limit_total{instance="$in - `query_postprocessors` / `post_filter` → applied post-aggregation filter > 0 - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -8899,7 +9087,7 @@ sum(increase(prometheus_target_scrapes_exceeded_sample_limit_total{instance="$in ``` TS metrics-prometheus-* | WHERE prometheus_target_scrapes_exceeded_sample_limit_total IS NOT NULL OR prometheus_target_scrapes_sample_duplicate_timestamp_total IS NOT NULL OR prometheus_target_scrapes_sample_out_of_bounds_total IS NOT NULL OR prometheus_target_scrapes_sample_out_of_order_total IS NOT NULL OR prometheus_rule_evaluation_failures_total IS NOT NULL OR prometheus_tsdb_compactions_failed_total IS NOT NULL OR prometheus_tsdb_reloads_failures_total IS NOT NULL OR prometheus_tsdb_head_series_not_found IS NOT NULL OR prometheus_evaluator_iterations_missed_total IS NOT NULL OR prometheus_evaluator_iterations_skipped_total IS NOT NULL -| STATS prometheus_target_scrapes_exceeded_sample_limit_total_A = SUM(INCREASE(prometheus_target_scrapes_exceeded_sample_limit_total)), prometheus_target_scrapes_sample_duplicate_timestamp_total_B = SUM(INCREASE(prometheus_target_scrapes_sample_duplicate_timestamp_total)), prometheus_target_scrapes_sample_out_of_bounds_total_C = SUM(INCREASE(prometheus_target_scrapes_sample_out_of_bounds_total)), prometheus_target_scrapes_sample_out_of_order_total_D = SUM(INCREASE(prometheus_target_scrapes_sample_out_of_order_total)), prometheus_rule_evaluation_failures_total_G = SUM(INCREASE(prometheus_rule_evaluation_failures_total)), prometheus_tsdb_compactions_failed_total_K = SUM(INCREASE(prometheus_tsdb_compactions_failed_total)), prometheus_tsdb_reloads_failures_total_L = SUM(INCREASE(prometheus_tsdb_reloads_failures_total)), prometheus_tsdb_head_series_not_found_N = SUM(MAX_OVER_TIME(TO_DOUBLE(prometheus_tsdb_head_series_not_found), 5m)), prometheus_evaluator_iterations_missed_total_O = SUM(INCREASE(prometheus_evaluator_iterations_missed_total)), prometheus_evaluator_iterations_skipped_total_P = SUM(INCREASE(prometheus_evaluator_iterations_skipped_total)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance +| STATS prometheus_target_scrapes_exceeded_sample_limit_total_A = SUM(INCREASE(prometheus_target_scrapes_exceeded_sample_limit_total)), prometheus_target_scrapes_sample_duplicate_timestamp_total_B = SUM(INCREASE(prometheus_target_scrapes_sample_duplicate_timestamp_total)), prometheus_target_scrapes_sample_out_of_bounds_total_C = SUM(INCREASE(prometheus_target_scrapes_sample_out_of_bounds_total)), prometheus_target_scrapes_sample_out_of_order_total_D = SUM(INCREASE(prometheus_target_scrapes_sample_out_of_order_total)), prometheus_rule_evaluation_failures_total_G = SUM(INCREASE(prometheus_rule_evaluation_failures_total)), prometheus_tsdb_compactions_failed_total_K = SUM(INCREASE(prometheus_tsdb_compactions_failed_total)), prometheus_tsdb_reloads_failures_total_L = SUM(INCREASE(prometheus_tsdb_reloads_failures_total)), prometheus_tsdb_head_series_not_found_N = SUM(MAX_OVER_TIME(TO_DOUBLE(prometheus_tsdb_head_series_not_found), 5m)), prometheus_evaluator_iterations_missed_total_O = SUM(INCREASE(prometheus_evaluator_iterations_missed_total)), prometheus_evaluator_iterations_skipped_total_P = SUM(INCREASE(prometheus_evaluator_iterations_skipped_total)) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend), instance | EVAL exceeded_sample_limit_on = CASE(prometheus_target_scrapes_exceeded_sample_limit_total_A > 0, prometheus_target_scrapes_exceeded_sample_limit_total_A, NULL) | EVAL duplicate_timestamp_on = CASE(prometheus_target_scrapes_sample_duplicate_timestamp_total_B > 0, prometheus_target_scrapes_sample_duplicate_timestamp_total_B, NULL) | EVAL out_of_bounds_on = CASE(prometheus_target_scrapes_sample_out_of_bounds_total_C > 0, prometheus_target_scrapes_sample_out_of_bounds_total_C, NULL) @@ -8966,6 +9154,7 @@ prometheus_target_interval_length_seconds{instance="$instance",quantile="0.99"} - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=binary_expr backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family binary_expr bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -8987,6 +9176,7 @@ prometheus_target_interval_length_seconds{instance="$instance",quantile="0.99"} - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -9001,7 +9191,7 @@ prometheus_target_interval_length_seconds{instance="$instance",quantile="0.99"} TS metrics-prometheus-* | WHERE quantile == "0.99" | WHERE prometheus_target_interval_length_seconds IS NOT NULL -| STATS prometheus_target_interval_length_seconds_quantile_0_99 = MAX(LAST_OVER_TIME(prometheus_target_interval_length_seconds)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS prometheus_target_interval_length_seconds_quantile_0_99 = MAX(LAST_OVER_TIME(prometheus_target_interval_length_seconds)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | EVAL computed_value = (prometheus_target_interval_length_seconds_quantile_0_99 - 0) | KEEP time_bucket, computed_value | SORT time_bucket ASC @@ -9056,6 +9246,7 @@ sum(prometheus_evaluator_duration_seconds{instance="$instance"}) by (instance, q - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -9085,6 +9276,7 @@ sum(prometheus_evaluator_duration_seconds{instance="$instance"}) by (instance, q - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -9098,7 +9290,7 @@ sum(prometheus_evaluator_duration_seconds{instance="$instance"}) by (instance, q ``` TS metrics-prometheus-* | WHERE prometheus_evaluator_duration_seconds IS NOT NULL -| STATS prometheus_evaluator_duration_seconds = SUM(prometheus_evaluator_duration_seconds) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance, quantile +| STATS prometheus_evaluator_duration_seconds = SUM(prometheus_evaluator_duration_seconds) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), instance, quantile | EVAL series_group = CONCAT(COALESCE(TO_STRING(instance), ""), " / ", COALESCE(TO_STRING(quantile), "")) | SORT time_bucket ASC ``` @@ -9153,6 +9345,7 @@ sum(increase(http_requests_total{instance="$instance"}[$aggregation_interval])) - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=range_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -9181,6 +9374,7 @@ sum(increase(http_requests_total{instance="$instance"}[$aggregation_interval])) - `query_postprocessors` / `post_filter` → applied post-aggregation filter > 0 - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -9194,7 +9388,7 @@ sum(increase(http_requests_total{instance="$instance"}[$aggregation_interval])) ``` TS metrics-prometheus-* | WHERE http_requests_total IS NOT NULL -| STATS http_requests_total = SUM(INCREASE(http_requests_total)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance, handler +| STATS http_requests_total = SUM(INCREASE(http_requests_total)) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend), instance, handler | WHERE http_requests_total > 0 | EVAL legend = CONCAT(COALESCE(TO_STRING(handler), ""), " on ", COALESCE(TO_STRING(instance), "")) | SORT time_bucket ASC @@ -9250,6 +9444,7 @@ max(sum(http_request_duration_microseconds{instance="$instance"}) by (instance, - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=nested_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family nested_agg bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -9277,6 +9472,7 @@ max(sum(http_request_duration_microseconds{instance="$instance"}) by (instance, - `query_postprocessors` / `post_filter` → applied post-aggregation filter > 0 - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -9288,8 +9484,9 @@ max(sum(http_request_duration_microseconds{instance="$instance"}) by (instance, **Translated (line):** ``` -FROM metrics-prometheus-* -| STATS inner_val = SUM(http_request_duration_microseconds) BY time_bucket = BUCKET(@timestamp, 50, ?_tstart, ?_tend), instance, handler, quantile +TS metrics-prometheus-* +| WHERE http_request_duration_microseconds IS NOT NULL +| STATS inner_val = SUM(http_request_duration_microseconds) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), instance, handler, quantile | STATS http_request_duration_microseconds_max = MAX(inner_val) BY time_bucket | WHERE http_request_duration_microseconds_max > 0 | SORT time_bucket ASC @@ -9343,6 +9540,7 @@ sum(increase(http_request_size_bytes{instance="$instance", quantile="0.99"}[$agg - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=range_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -9371,6 +9569,7 @@ sum(increase(http_request_size_bytes{instance="$instance", quantile="0.99"}[$agg - `query_postprocessors` / `post_filter` → applied post-aggregation filter > 0 - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -9385,7 +9584,7 @@ sum(increase(http_request_size_bytes{instance="$instance", quantile="0.99"}[$agg TS metrics-prometheus-* | WHERE quantile == "0.99" | WHERE http_request_size_bytes IS NOT NULL -| STATS http_request_size_bytes = SUM(MAX_OVER_TIME(TO_DOUBLE(http_request_size_bytes), 5m)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance, handler +| STATS http_request_size_bytes = SUM(MAX_OVER_TIME(TO_DOUBLE(http_request_size_bytes), 5m)) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend), instance, handler | WHERE http_request_size_bytes > 0 | EVAL legend = CONCAT(COALESCE(TO_STRING(handler), ""), " in ", COALESCE(TO_STRING(instance), "")) | SORT time_bucket ASC @@ -9443,6 +9642,7 @@ sum(prometheus_engine_queries{instance="$instance"}) by (instance, handler) ||| - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -9472,6 +9672,7 @@ sum(prometheus_engine_queries{instance="$instance"}) by (instance, handler) ||| - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -9485,7 +9686,7 @@ sum(prometheus_engine_queries{instance="$instance"}) by (instance, handler) ||| ``` TS metrics-prometheus-* | WHERE prometheus_engine_queries IS NOT NULL OR prometheus_engine_queries_concurrent_max IS NOT NULL -| STATS Current_count = SUM(prometheus_engine_queries), Max_count = SUM(prometheus_engine_queries_concurrent_max) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance, handler +| STATS Current_count = SUM(prometheus_engine_queries), Max_count = SUM(prometheus_engine_queries_concurrent_max) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), instance, handler | EVAL series_group = CONCAT(COALESCE(TO_STRING(instance), ""), " / ", COALESCE(TO_STRING(handler), "")) | KEEP time_bucket, instance, handler, Current_count, Max_count, series_group | SORT time_bucket ASC @@ -9541,6 +9742,7 @@ sum(prometheus_notifications_queue_capacity{instance="$instance"})by (instance) - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -9570,6 +9772,7 @@ sum(prometheus_notifications_queue_capacity{instance="$instance"})by (instance) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -9583,7 +9786,7 @@ sum(prometheus_notifications_queue_capacity{instance="$instance"})by (instance) ``` TS metrics-prometheus-* | WHERE prometheus_notifications_queue_capacity IS NOT NULL OR prometheus_notifications_queue_length IS NOT NULL -| STATS Alert_queue_capacity = SUM(prometheus_notifications_queue_capacity), Alert_queue_size_on = SUM(prometheus_notifications_queue_length) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance +| STATS Alert_queue_capacity = SUM(prometheus_notifications_queue_capacity), Alert_queue_size_on = SUM(prometheus_notifications_queue_length) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), instance | KEEP time_bucket, instance, Alert_queue_capacity, Alert_queue_size_on | SORT time_bucket ASC ``` @@ -9638,7 +9841,7 @@ TS metrics-prometheus-* | Panel | Source Type → Kibana | Status | Verdict | Source Query | Translated Query | |-------|---------------------|--------|---------|-------------|-----------------| | Uptime | `singlestat` → `metric` | migrated | **MINOR_ISSUE** | max(max_over_time(redis_uptime_in_seconds{instance=~"$instance"}[$__interval])) | TS metrics-prometheus-* \| WHERE redis_uptime_in_seconds IS NOT NULL \| STATS re... | -| Clients | `singlestat` → `metric` | migrated | **MINOR_ISSUE** | redis_connected_clients{instance=~"$instance"} | TS metrics-prometheus-* \| WHERE redis_connected_clients IS NOT NULL \| STATS re... | +| Clients | `singlestat` → `metric` | migrated_with_warnings | **MINOR_ISSUE** | redis_connected_clients{instance=~"$instance"} | TS metrics-prometheus-* \| WHERE redis_connected_clients IS NOT NULL \| STATS re... | | Memory Usage | `singlestat` → `metric` | migrated_with_warnings | **MINOR_ISSUE** | 100 * (redis_memory_used_bytes{instance=~"$instance"} / redis_memory_max_bytes{... | TS metrics-prometheus-* \| WHERE redis_memory_used_bytes IS NOT NULL OR redis_me... | | Commands Executed / sec | `graph` → `line` | migrated | **MINOR_ISSUE** | rate(redis_commands_processed_total{instance=~"$instance"}[1m]) | TS metrics-prometheus-* \| WHERE redis_commands_processed_total IS NOT NULL \| S... | | Hits / Misses per Sec | `graph` → `line` | migrated | **MINOR_ISSUE** | irate(redis_keyspace_hits_total{instance=~"$instance"}[5m]) \|\|\| irate(redis_k... | TS metrics-prometheus-* \| WHERE redis_keyspace_hits_total IS NOT NULL OR redis_... | @@ -9669,6 +9872,7 @@ max(max_over_time(redis_uptime_in_seconds{instance=~"$instance"}[$__interval])) - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=range_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -9697,6 +9901,7 @@ max(max_over_time(redis_uptime_in_seconds{instance=~"$instance"}[$__interval])) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -9708,7 +9913,7 @@ max(max_over_time(redis_uptime_in_seconds{instance=~"$instance"}[$__interval])) ``` TS metrics-prometheus-* | WHERE redis_uptime_in_seconds IS NOT NULL -| STATS redis_uptime_in_seconds = MAX(MAX_OVER_TIME(redis_uptime_in_seconds, 5m)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS redis_uptime_in_seconds = MAX(MAX_OVER_TIME(redis_uptime_in_seconds, 5m)) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend) | STATS redis_uptime_in_seconds = LAST(redis_uptime_in_seconds, time_bucket) | KEEP redis_uptime_in_seconds ``` @@ -9761,6 +9966,7 @@ redis_connected_clients{instance=~"$instance"} - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_metric backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -9791,6 +9997,7 @@ redis_connected_clients{instance=~"$instance"} - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -9802,7 +10009,7 @@ redis_connected_clients{instance=~"$instance"} ``` TS metrics-prometheus-* | WHERE redis_connected_clients IS NOT NULL -| STATS redis_connected_clients = MAX(LAST_OVER_TIME(redis_connected_clients)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS redis_connected_clients = MAX(LAST_OVER_TIME(redis_connected_clients)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | STATS redis_connected_clients = LAST(redis_connected_clients, time_bucket) | KEEP redis_connected_clients ``` @@ -9815,14 +10022,14 @@ TS metrics-prometheus-* - Source lang: `promql` - Target index: `metrics-prometheus-*` - Output metric: `redis_connected_clients` -- Semantic losses: Dropped variable-driven label filters during migration +- Semantic losses: Dropped variable-driven label filters during migration, Collapsed all series of `redis_connected_clients` into a single MAX line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. **Visual IR:** - Kibana type: `metric` - Layout: x=4, y=0, w=4, h=11 - Presentation kind: `esql` -- Config keys: type, query, primary +- Config keys: type, query, primary, time_range **Operational IR:** @@ -9832,7 +10039,9 @@ TS metrics-prometheus-* - targets: 1 -**Semantic losses:** Dropped variable-driven label filters during migration +**Warnings:** Collapsed all series of `redis_connected_clients` into a single MAX line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. + +**Semantic losses:** Dropped variable-driven label filters during migration; Collapsed all series of `redis_connected_clients` into a single MAX line; the source selector has no series labels (no legend, by(), or dashboard reference), so per-series detail is dropped. Add a legend/by() or migrate with target access to recover per-series fidelity. **Verdict:** MINOR_ISSUE @@ -9852,6 +10061,7 @@ TS metrics-prometheus-* - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=binary_expr backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family binary_expr bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -9873,6 +10083,7 @@ TS metrics-prometheus-* - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -9884,7 +10095,7 @@ TS metrics-prometheus-* ``` TS metrics-prometheus-* | WHERE redis_memory_used_bytes IS NOT NULL OR redis_memory_max_bytes IS NOT NULL -| STATS redis_memory_used_bytes_instance = AVG(LAST_OVER_TIME(redis_memory_used_bytes)), redis_memory_max_bytes_instance = AVG(LAST_OVER_TIME(redis_memory_max_bytes)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS redis_memory_used_bytes_instance = AVG(LAST_OVER_TIME(redis_memory_used_bytes)), redis_memory_max_bytes_instance = AVG(LAST_OVER_TIME(redis_memory_max_bytes)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | EVAL computed_value = (100 * (redis_memory_used_bytes_instance / redis_memory_max_bytes_instance)) | STATS computed_value = LAST(computed_value, time_bucket) | KEEP computed_value @@ -9906,7 +10117,7 @@ TS metrics-prometheus-* - Kibana type: `metric` - Layout: x=8, y=0, w=8, h=11 - Presentation kind: `esql` -- Config keys: type, query, primary +- Config keys: type, query, primary, time_range **Operational IR:** @@ -9938,6 +10149,7 @@ rate(redis_commands_processed_total{instance=~"$instance"}[1m]) - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=range_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -9966,6 +10178,7 @@ rate(redis_commands_processed_total{instance=~"$instance"}[1m]) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -9979,7 +10192,7 @@ rate(redis_commands_processed_total{instance=~"$instance"}[1m]) ``` TS metrics-prometheus-* | WHERE redis_commands_processed_total IS NOT NULL -| STATS redis_commands_processed_total = AVG(RATE(redis_commands_processed_total)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance +| STATS redis_commands_processed_total = AVG(RATE(redis_commands_processed_total)) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend), instance | SORT time_bucket ASC ``` @@ -10031,6 +10244,7 @@ irate(redis_keyspace_hits_total{instance=~"$instance"}[5m]) ||| irate(redis_keys - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=range_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -10059,6 +10273,7 @@ irate(redis_keyspace_hits_total{instance=~"$instance"}[5m]) ||| irate(redis_keys - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -10072,7 +10287,7 @@ irate(redis_keyspace_hits_total{instance=~"$instance"}[5m]) ||| irate(redis_keys ``` TS metrics-prometheus-* | WHERE redis_keyspace_hits_total IS NOT NULL OR redis_keyspace_misses_total IS NOT NULL -| STATS hits = AVG(IRATE(redis_keyspace_hits_total)), misses = AVG(IRATE(redis_keyspace_misses_total)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance +| STATS hits = AVG(IRATE(redis_keyspace_hits_total)), misses = AVG(IRATE(redis_keyspace_misses_total)) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend), instance | KEEP time_bucket, instance, hits, misses | SORT time_bucket ASC ``` @@ -10125,6 +10340,7 @@ redis_memory_used_bytes{instance=~"$instance"} ||| redis_memory_max_bytes{insta - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_metric backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -10155,6 +10371,7 @@ redis_memory_used_bytes{instance=~"$instance"} ||| redis_memory_max_bytes{insta - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -10168,7 +10385,7 @@ redis_memory_used_bytes{instance=~"$instance"} ||| redis_memory_max_bytes{insta ``` TS metrics-prometheus-* | WHERE redis_memory_used_bytes IS NOT NULL OR redis_memory_max_bytes IS NOT NULL -| STATS used = AVG(LAST_OVER_TIME(redis_memory_used_bytes)), max = AVG(LAST_OVER_TIME(redis_memory_max_bytes)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance +| STATS used = AVG(LAST_OVER_TIME(redis_memory_used_bytes)), max = AVG(LAST_OVER_TIME(redis_memory_max_bytes)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), instance | KEEP time_bucket, instance, used, max | SORT time_bucket ASC ``` @@ -10219,6 +10436,7 @@ rate(redis_net_input_bytes_total{instance=~"$instance"}[5m]) ||| rate(redis_net_ - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=range_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -10247,6 +10465,7 @@ rate(redis_net_input_bytes_total{instance=~"$instance"}[5m]) ||| rate(redis_net_ - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -10260,7 +10479,7 @@ rate(redis_net_input_bytes_total{instance=~"$instance"}[5m]) ||| rate(redis_net_ ``` TS metrics-prometheus-* | WHERE redis_net_input_bytes_total IS NOT NULL OR redis_net_output_bytes_total IS NOT NULL -| STATS input = RATE(redis_net_input_bytes_total), output = RATE(redis_net_output_bytes_total) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS input = RATE(redis_net_input_bytes_total), output = RATE(redis_net_output_bytes_total) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend) | KEEP time_bucket, input, output | SORT time_bucket ASC ``` @@ -10313,6 +10532,7 @@ sum (redis_db_keys{instance=~"$instance"}) by (db) - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -10342,6 +10562,7 @@ sum (redis_db_keys{instance=~"$instance"}) by (db) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -10355,7 +10576,7 @@ sum (redis_db_keys{instance=~"$instance"}) by (db) ``` TS metrics-prometheus-* | WHERE redis_db_keys IS NOT NULL -| STATS redis_db_keys = SUM(redis_db_keys) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), db +| STATS redis_db_keys = SUM(redis_db_keys) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), db | SORT time_bucket ASC ``` @@ -10407,6 +10628,7 @@ sum (redis_db_keys{instance=~"$instance"}) - sum (redis_db_keys_expiring{instanc - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=binary_expr backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family binary_expr bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -10428,6 +10650,7 @@ sum (redis_db_keys{instance=~"$instance"}) - sum (redis_db_keys_expiring{instanc - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -10441,7 +10664,7 @@ sum (redis_db_keys{instance=~"$instance"}) - sum (redis_db_keys_expiring{instanc ``` TS metrics-prometheus-* | WHERE redis_db_keys IS NOT NULL OR redis_db_keys_expiring IS NOT NULL -| STATS redis_db_keys_A_lhs = SUM(redis_db_keys), expiring = SUM(redis_db_keys_expiring) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend) +| STATS redis_db_keys_A_lhs = SUM(redis_db_keys), expiring = SUM(redis_db_keys_expiring) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend) | EVAL not_expiring = (redis_db_keys_A_lhs - expiring) | KEEP time_bucket, not_expiring, expiring | SORT time_bucket ASC @@ -10494,6 +10717,7 @@ sum(rate(redis_expired_keys_total{instance=~"$instance"}[5m])) by (instance) ||| - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=range_agg backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -10522,6 +10746,7 @@ sum(rate(redis_expired_keys_total{instance=~"$instance"}[5m])) by (instance) ||| - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -10535,7 +10760,7 @@ sum(rate(redis_expired_keys_total{instance=~"$instance"}[5m])) by (instance) ||| ``` TS metrics-prometheus-* | WHERE redis_expired_keys_total IS NOT NULL OR redis_evicted_keys_total IS NOT NULL -| STATS expired = SUM(RATE(redis_expired_keys_total)), evicted = SUM(RATE(redis_evicted_keys_total)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance +| STATS expired = SUM(RATE(redis_expired_keys_total)), evicted = SUM(RATE(redis_evicted_keys_total)) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend), instance | KEEP time_bucket, instance, expired, evicted | SORT time_bucket ASC ``` @@ -10592,6 +10817,7 @@ topk(5, irate(redis_commands_total{instance=~"$instance"} [1m])) - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=topk backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` → fragment family topk bypasses unsupported-pattern check - `query_classifiers` / `join_label_enrichment_check` @@ -10614,6 +10840,7 @@ topk(5, irate(redis_commands_total{instance=~"$instance"} [1m])) - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -10627,7 +10854,7 @@ topk(5, irate(redis_commands_total{instance=~"$instance"} [1m])) ``` TS metrics-prometheus-* | WHERE redis_commands_total IS NOT NULL -| STATS value = AVG(IRATE(redis_commands_total)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), cmd +| STATS value = AVG(IRATE(redis_commands_total)) BY time_bucket = TBUCKET(20, ?_tstart, ?_tend), cmd | SORT time_bucket ASC ``` @@ -10681,6 +10908,7 @@ redis_connected_clients{instance="$instance"} - `query_preprocessors` / `grafana_macros` → expanded Grafana macros - `query_preprocessors` / `parse_fragment` → parsed fragment family=simple_metric backend=ast - `query_classifiers` / `colocated_binary_agg_unblock` +- `query_classifiers` / `agg_over_or_operand_drop` - `query_classifiers` / `fragment_guardrails` - `query_classifiers` / `family_classifier` - `query_classifiers` / `join_label_enrichment_check` @@ -10711,6 +10939,7 @@ redis_connected_clients{instance="$instance"} - `query_postprocessors` / `post_filter` - `query_validators` / `metric_name_required` - `query_validators` / `dynamic_metric_name` +- `query_validators` / `template_variable_placeholder_column` - `query_validators` / `time_filter_source_alignment` - `query_validators` / `live_metric_fields_exist` - `query_validators` / `rendered_query_required` @@ -10724,7 +10953,7 @@ redis_connected_clients{instance="$instance"} ``` TS metrics-prometheus-* | WHERE redis_connected_clients IS NOT NULL -| STATS redis_connected_clients = AVG(LAST_OVER_TIME(redis_connected_clients)) BY time_bucket = TBUCKET(100, ?_tstart, ?_tend), instance +| STATS redis_connected_clients = AVG(LAST_OVER_TIME(redis_connected_clients)) BY time_bucket = TBUCKET(75, ?_tstart, ?_tend), instance | SORT time_bucket ASC ``` @@ -10783,8 +11012,8 @@ From the latest trace run: ``` Elements: 289 total (266 panels + 23 rows) Renderable panels: 266 - Migrated: 114 (42.9%) - With warnings: 145 (54.5%) + Migrated: 107 (40.2%) + With warnings: 152 (57.1%) Requires manual: 5 (1.9%) Not feasible: 1 (0.4%) Skipped: 1 (0.4%) @@ -10793,8 +11022,8 @@ Renderable panels: 266 Verdict breakdown: ``` - CORRECT: 11 - MINOR_ISSUE: 238 + CORRECT: 10 + MINOR_ISSUE: 239 EXPECTED_LIMITATION: 40 ``` @@ -10817,4 +11046,4 @@ Every panel marked `not_feasible` in the trace run (1 total): --- -*Last generated: 2026-08-04 18:38 UTC* +*Last generated: 2026-09-02 14:04 UTC* diff --git a/docs/sources/grafana.md b/docs/sources/grafana.md index f6637867..116d093f 100644 --- a/docs/sources/grafana.md +++ b/docs/sources/grafana.md @@ -1017,7 +1017,8 @@ is available at `examples/cue/grafana-rule-pack.cue`. - `label_join(v, dst, separator, src1, src2, ...)` translates to a post-`STATS` `| EVAL dst = CONCAT(src1, "separator", src2, ...)` when all source labels appear in the inner expression's `by()` clause. If any source label is absent from the `by()` clause, the panel stays `not_feasible` (the column would not exist in the `STATS` output and `CONCAT` cannot reference it). - `label_replace(v, dst, replacement, src, regex)` follows the same rule when the replacement references the source label (a `$1`-style capture): the post-`STATS` `| EVAL`/`| GROK` can only read `src` if the inner expression's result still carries it, so forms that aggregate it away stay `not_feasible` rather than emitting a query that fails on an unknown column. This includes nested aggregations, whose grouping comes from the source's own `by()` clauses (issue #382) and whose outer `STATS` drops the inner grouping regardless — `label_replace(max(sum by (namespace) (m)), "ns", "$1", "namespace", "(.*)")` is not feasible even though `namespace` appears in an inner `by()`. Forms that read no source column are unaffected: a literal replacement emits `EVAL dst = "literal"`, and an identity copy (`dst` resolving to the same target field as `src`) emits nothing at all. - Histogram mean idioms `sum(increase|rate(m_sum) / increase|rate(m_count))` approximate as a ratio of aggregates (`sum(m_sum)/sum(m_count)`) with an explicit warning; unrelated per-element ratios stay `not_feasible`. -- An aggregation **wrapping** a binary operator (`agg(A op B)`) is only feasible when a rewrite can prove the operator survives, because PromQL evaluates `A op B` per matching series pair — matching on the operands' full label set — before the aggregation reduces the survivors, and ES|QL has no equivalent stage. The rewrites are: `sum(A ± B)` push-down (`sum` is linear), scalar hoisting (`max(A * 8)`, `avg(A / 100)`), the histogram mean idiom above, and the co-located per-document renderer (`count(A + B)` → `COUNT((A + B))`, for `+ - * /` over operands that share a label set). Everything else stays `not_feasible` with a warning naming the operator and the aggregation: set operators `and` / `unless` (`count(A and B >= threshold)` — issue #377), comparisons between two series (`count(A < B)`; compare against a scalar threshold instead), and arithmetic the renderer does not cover (`%`, `^`, `atan2`, or operands that carry their own `by()` reduction). The refusal is an allowlist of operators that are handed on rather than a list of operators that are refused, so an operator nobody enumerated fails closed instead of silently reaching the fallback. Previously these fell through to the generic single-metric fallback, which rebuilt `agg()` and shipped a plausible but wrong number as `migrated_with_warnings`. `agg(A or B)` is deliberately excluded from this refusal: it keeps its established reductions (the same-metric range-window fallback `max_over_time(M[$interval]) or max_over_time(M[5m])`, and the live-absent operand drop). +- An aggregation **wrapping** a binary operator (`agg(A op B)`) is only feasible when a rewrite can prove the operator survives, because PromQL evaluates `A op B` per matching series pair — matching on the operands' full label set — before the aggregation reduces the survivors, and ES|QL has no equivalent stage. The rewrites are: `sum(A ± B)` push-down (`sum` is linear), scalar hoisting (`max(A * 8)`, `avg(A / 100)`), the histogram mean idiom above, and the co-located per-document renderer (`count(A + B)` → `COUNT((A + B))`, for `+ - * /` over operands that share a label set). Everything else stays `not_feasible` with a warning naming the operator and the aggregation: set operators `and` / `unless` (`count(A and B >= threshold)` — issue #377), comparisons between two series (`count(A < B)`; compare against a scalar threshold instead), and arithmetic the renderer does not cover (`%`, `^`, `atan2`, or operands that carry their own `by()` reduction). The refusal is an allowlist of operators that are handed on rather than a list of operators that are refused, so an operator nobody enumerated fails closed instead of silently reaching the fallback. Previously these fell through to the generic single-metric fallback, which rebuilt `agg()` and shipped a plausible but wrong number as `migrated_with_warnings`. +- `agg(A or B)` is decided one stage later, during classification, because `or` carries two reductions that need live field capabilities the parser does not have: the same-metric range-window fallback (`max_over_time(M[$interval]) or max_over_time(M[5m])`, `rate(M[$interval]) or irate(M[5m])`) and the drop of an operand whose metric is **provably absent** from the target. When either reduction elects a single surviving operand the panel translates as before, and the dropped operand is now named in a warning — the same disclosure the bare (unaggregated) chain already emitted. When neither applies, the panel is `not_feasible` (issue #434). `or` is a set *union*, so an aggregation cannot be distributed over it (`agg(A or B)` is not `agg(A) or agg(B)`), which is why the `COALESCE` / unified `WHERE ... OR` rewrites that keep both operands in the bare form are unavailable under an aggregation. Before this, `count(node_a or node_b)` emitted `COUNT(node_a)` as clean `migrated` with no warning at all, while the bare `node_a or node_b` kept both operands — the wrapper inverted the verdict on the same expression. The refusal covers `by()` grouping, longer `or` chains, every outer aggregation, and enclosing wrappers such as `clamp_max(...)`. Offline (no `--es-url` / no reachable field capabilities) nothing is disprovable, so a cross-metric fallback chain refuses rather than silently reporting only its left operand; re-run with target field capabilities reachable to let the absent-operand reduction apply. - Multi-target XY panels fuse when series share a compatible ES|QL shape. Summary panels (`stat` / `singlestat` / `gauge` / `bargauge` / table) use the same compatibility group and approximate multi-series stats as a summary table when needed. Grouping mismatches where a bare aggregation (no `by()`, e.g. `min`/`avg`/`max`/`sum`) shares its metric field with a grouped sibling target — the disk-graphs "IO Wait per core" Min/Avg/Max-over-CPUs shape (issue #355) — render as two separate ES|QL layers instead of unioning the `BY` fields: one layer keeps the grouped target's own dimensions, the other re-aggregates the bare target across every group (time-bucket only), matching what Grafana always draws as a single cross-series line. When the bare target instead broadcasts an *unrelated* metric alongside a grouped breakdown (e.g. QoS `by (qos_class)` + an ungrouped fleet-wide total) there is no shared per-series computation to split out, so that target's `BY` fields still union with the grouped target's, but the warning now names the changed semantics (which series is affected, the fields it is now grouped by) instead of only describing the mechanism; an unrelated bare target sharing the panel does not disable the split for the pair that *does* share a metric. A lone bare target with no Min/Avg/Max siblings still gets its own summary layer, aliased to its own `legendFormat` text (not the raw metric field name); the same split applies within a data stream even when another fused target lands on a different index partition. Divergent label filters on otherwise identical measures CASE-inline into the shared `STATS` (including window-less `LAST_OVER_TIME`, used by Express-style status-class counters). `legendFormat` `{{label}}` placeholders on `rate`/`irate`/`increase` (and other TS paths covered by issue #99) are display hints — they become series aliases, not `BY` dimensions — so overlays like Redis in/out rates can share one panel. When fused targets carry *different* multi-placeholder `legendFormat` templates (issue #354), the composite `EVAL legend` expression is suppressed so the `STATS` column name carries series identity instead of mislabelling every series with the first target's literal text; targets whose templates agree, or that use only a single placeholder, are unaffected. Targets that remain incompatible (Windows vs Linux metrics, complex `or`/`label_replace` trees) still keep the largest compatible group and warn; Windows-specific drop wording only applies when every dropped target is a `windows_*` metric. - Grafana **stat/gauge tiles are one-per-series** (a status grid). Translation preserves the source PromQL expression's grouping and cardinality rather than inferring `BY` dimensions from `legendFormat` text alone. An ungrouped `up` (or similar) that Grafana would still fan out per scrape target is migrated **with a warning**; outer aggregations such as `sum(rate(...))` stay scalars even when the legend contains `{{job}}`. The same rule governs **nested aggregations** on every panel type: the grouping of `max(sum(m))` comes from the source's own `by()` clauses and nothing else — legend placeholders, legacy table column patterns, and dashboard-wide label inference are all display hints. In a nested aggregation the inner grouping additionally decides what the *outer* aggregation reduces over, so a hint-derived inner `BY` made `max(sum(m))` report the largest single label value instead of the collapsed total, and a hint-derived outer `BY` turned the scalar `count(count by (cpu) (m))` into a constant `1` per CPU (issue #382). - Composite/multi-target series (a fused native-PROMQL `value` column, the general ES|QL translator's scalar-expression `computed_value` column, or a curated-pack override that folds several source metrics into one `value` column with a `series_group` breakdown) never surface that internal column name to the operator. An unambiguous static `legendFormat` shared by every visible target takes priority; mixed or templated legends fall back to the panel title as the metric's label. A single such metric with a breakdown uses that label as the Y-axis title when Grafana left the axis unnamed and no uniform unit title (`%`, `Bytes`, …) can be inferred (issue #351). An explicit Grafana axis label still wins; opaque aliases such as `percentage` are ignored so a unit title can apply. A panel with no usable title/legend text falls back to the prior hidden-title behavior. diff --git a/observability_migration/adapters/source/grafana/promql.py b/observability_migration/adapters/source/grafana/promql.py index ec020bb3..c856ef62 100644 --- a/observability_migration/adapters/source/grafana/promql.py +++ b/observability_migration/adapters/source/grafana/promql.py @@ -11,7 +11,7 @@ import re from dataclasses import dataclass, field from datetime import timedelta -from typing import Any +from typing import Any, NamedTuple from observability_migration.core.metric_mapping import plan_rate_transform from observability_migration.core.verification.field_capabilities import NUMERIC_FIELD_TYPES @@ -2940,6 +2940,30 @@ def _join_rhs_not_plain_selector_reason(right_frag): _AGG_OVER_BINARY_DEFERRED_OPS = frozenset({"or"}) +def _agg_over_or_not_feasible_reason(outer_agg, operands=()): + """Explain why ``agg(A or B)`` cannot keep both operands. + + ``or`` is a set *union*, not a filter: it emits every series of the left + operand plus the right operand's series whose label set the left never + produced, and only then does the enclosing aggregation reduce them. An + aggregation does not distribute over that union -- ``agg(A or B)`` is not + ``agg(A) or agg(B)`` -- so the ``COALESCE``/``WHERE ... OR`` rewrites that + keep both operands in the *bare* form have nowhere to put the outer + aggregation, and every remaining rendering silently drops an operand. + """ + agg = outer_agg or "aggregation" + named = [str(name) for name in operands if name] + listed = f" ({', '.join(named)})" if named else "" + return ( + f"PromQL set operator 'or' inside an aggregation ({agg}(A or B)) unions the " + f"operands' series before {agg}() reduces them, and an aggregation cannot be " + f"distributed over that union; ES|QL has no way to keep both operands{listed} " + f"inside one {agg}(), so translating it would drop the fallback series without " + "saying so; aggregate a single operand, or give each operand its own panel " + "series; marked not_feasible" + ) + + def _agg_over_binary_not_feasible_reason(outer_agg, op): """Explain why ``agg(A B)`` has no honest ES|QL rendering. @@ -2956,6 +2980,10 @@ def _agg_over_binary_not_feasible_reason(outer_agg, op): "cannot be expressed accurately in ES|QL; the operands must be matched on " "their full label set before the aggregation reduces them" ) + # ``or`` is a union rather than a per-pair filter, so it must not inherit the + # "selects which series survive" wording the other set operators use. + if op.lower() == "or": + return _agg_over_or_not_feasible_reason(outer_agg) if op.lower() in _SET_OPERATORS: return ( f"PromQL set operator '{op.lower()}' inside an aggregation " @@ -6132,6 +6160,80 @@ def _reduce_or_operands(frag, resolver) -> tuple[list, list]: return kept, dropped +class AggOverOrReduction(NamedTuple): + """What is left of the ``or`` chain directly under an ``agg(...)``. + + ``preferred`` is the single operand the established reductions elect, or + ``None`` when they cannot elect one -- which means translating the fragment + would have to discard an operand. ``dropped_absent`` and + ``dropped_fallback`` are the operands each reduction removed, so the caller + can disclose them instead of letting them vanish. + """ + + chain: object + preferred: object | None + dropped_absent: list + dropped_fallback: list + survivors: list + + +def _agg_over_or_chain(frag): + """The ``or`` chain directly under ``agg(...)``, or ``None``. + + ``_ast_aggregate_fragment`` stashes the aggregation's child on + ``extra['inner_frag']``. A child that is an ``or`` binary expression is the + shape with no honest single-stage rendering; anything else (including an + ``or`` already collapsed at parse time by ``_strip_or_vector_fallback``) is + somebody else's problem. + """ + if frag is None or not getattr(frag, "outer_agg", ""): + return None + extra = getattr(frag, "extra", None) + if not isinstance(extra, dict): + return None + inner = extra.get("inner_frag") + if inner is None or getattr(inner, "family", "") != "binary_expr": + return None + if (getattr(inner, "binary_op", "") or "").lower() != "or": + return None + return inner + + +def agg_over_or_reduction(frag, resolver): + """Reduce the ``or`` chain under ``agg(...)``; ``None`` if there is no such chain. + + This is the single definition of "can this ``or`` be reduced to one + operand", shared by the classifier that refuses the irreducible case and by + ``colocated_binary_agg_plan``, which renders the reduced one. If the two + ever disagreed, the classifier would refuse a shape the renderer can express + (or wave through a shape it cannot), so they must not compute it twice. + + Deciding this needs a resolver: whether an operand is *absent from the + target* is a live-schema question, which is why the refusal cannot live at + parse time (issue #434). + """ + chain = _agg_over_or_chain(frag) + if chain is None: + return None + operands = _flatten_or_operands(chain) + if not operands: + # Malformed chain: nothing to elect, and nothing safe to emit either. + return AggOverOrReduction(chain, None, [], [], []) + collapsed = _collapse_same_metric_range_fallback_groups(operands) + survivors, dropped_absent = _reduce_or_operands(chain, resolver) + kept_ids = {id(operand) for operand in collapsed} + dropped_fallback = [operand for operand in operands if id(operand) not in kept_ids] + preferred = None + if len(survivors) == 1: + preferred = survivors[0] + elif survivors and _operands_are_same_metric_range_fallback(survivors): + preferred = survivors[0] + dropped_fallback = dropped_fallback + survivors[1:] + return AggOverOrReduction( + chain, preferred, dropped_absent, dropped_fallback, survivors + ) + + def _expr_for_or_collapse_parse(expr: str, rule_pack=None) -> str: """Make Grafana range macros parseable without rewriting label matchers. @@ -7277,17 +7379,15 @@ def colocated_binary_agg_plan(frag, resolver, rule_pack): # ``sum((rate(A)/rate(B)) or (irate(A)/irate(B)) or cloud_metric)`` lands # as unknown+inner OR. Collapse the range-window pair and drop live-absent # cloud fallbacks so the remaining ratio can render as co-located arithmetic. - if getattr(inner, "family", "") == "binary_expr" and (inner.binary_op or "").lower() == "or": - kept, dropped = _reduce_or_operands(inner, resolver) - preferred = None - if len(kept) == 1 or (kept and _operands_are_same_metric_range_fallback(kept)): - preferred = kept[0] - if preferred is None: + # ``agg_over_or_reduction`` owns that decision; the classifier that refuses + # the irreducible chain reads the same result, so a chain this renders is + # never one that was refused. + reduction = agg_over_or_reduction(frag, resolver) + if reduction is not None: + if reduction.preferred is None: return None - extra["inner_frag"] = preferred - inner = preferred - if dropped: - extra["or_chain_dropped_absent"] = True + extra["inner_frag"] = reduction.preferred + inner = reduction.preferred if getattr(inner, "family", "") != "binary_expr": return None if (inner.binary_op or "").lower() in _SET_OPERATORS: diff --git a/observability_migration/adapters/source/grafana/translate.py b/observability_migration/adapters/source/grafana/translate.py index 5a35ebd8..dfda0f18 100644 --- a/observability_migration/adapters/source/grafana/translate.py +++ b/observability_migration/adapters/source/grafana/translate.py @@ -32,11 +32,14 @@ _metric_candidates, ) from .promql import ( + _ABSENT_OR_OPERAND_WARNING, _APPROX_AGG_OVER_SUMMARY_RATIO_WARNING, _COUNTER_UNSAFE_OUTER_AGGS, AGG_FUNCTION_MAP, OUTER_AGG_MAP, PromQLFragment, + _agg_over_or_not_feasible_reason, + _append_not_feasible_reason, _apply_fragment_to_context, _apply_metric_map_to_rate_on_simple, _apply_unit_scale, @@ -62,6 +65,7 @@ _frag_has_incompatible_group_fields, _frag_has_incompatible_target_fields, _frag_source_labels, + _fragment_metric_names, _gauge_can_use_ts, _grouping_parts, _inline_filters_into_stats_expr, @@ -83,6 +87,7 @@ _resolve_metric_field, _same_metric_range_fallback_warning, _summary_mode_from_metadata, + agg_over_or_reduction, classify_promql_complexity, colocated_binary_agg_plan, colocated_metric_fields, @@ -1142,6 +1147,51 @@ def colocated_binary_agg_unblock(context): return "cleared not_feasible for co-located per-element arithmetic" +@QUERY_CLASSIFIERS.register("agg_over_or_operand_drop", priority=0) +def agg_over_or_operand_drop_rule(context): + """Refuse ``agg(A or B)`` when no reduction can elect a single operand. + + The parse-time guard in ``_ast_aggregate_fragment`` hands ``or`` on because + its two reductions -- the same-metric range-window fallback and the + live-absent operand drop -- need a resolver it does not have. Nothing + downstream then claimed the fragment, so the generic + ``fragment_extract``/``stats_expression`` fallback rebuilt + ``agg()`` from the fragment's summary fields and shipped + it clean: ``count(node_a or node_b)`` became ``COUNT(node_a)`` with no + warning (issue #434). The bare chain keeps both operands (``COALESCE`` / + unified ``WHERE ... OR``) or refuses outright, so the wrapper was inverting + the verdict on the identical expression. + + Runs at priority 0, ahead of ``fragment_guardrails`` (1), which turns the + reason into the refusal. Order against ``colocated_binary_agg_unblock`` + (also 0) does not matter: that rule only clears reasons when + ``colocated_binary_agg_plan`` can render, which requires the same reduction + to have elected an operand -- exactly the case this rule stays silent for. + """ + frag = context.fragment + reduction = agg_over_or_reduction(frag, context.resolver) + if reduction is None: + return None + if reduction.preferred is None: + reason = _agg_over_or_not_feasible_reason( + frag.outer_agg, _fragment_metric_names(reduction.chain) + ) + _append_not_feasible_reason(frag, reason) + return reason + # A reduction did elect an operand, but the ones it removed still have to be + # disclosed -- the same warnings the bare path emits for the same drops. + details = [] + if reduction.dropped_absent: + _append_unique(context.warnings, _ABSENT_OR_OPERAND_WARNING) + details.append("disclosed live-absent 'or' operand drop") + if reduction.dropped_fallback: + _append_unique( + context.warnings, _same_metric_range_fallback_warning(reduction.chain) + ) + details.append("disclosed dropped same-metric range-window fallback") + return "; ".join(details) or None + + @QUERY_CLASSIFIERS.register("fragment_guardrails", priority=1) def fragment_guardrails_rule(context): frag = context.fragment diff --git a/tests/test_issue377_agg_over_binary_operator.py b/tests/test_issue377_agg_over_binary_operator.py index 884af2c8..70afa153 100644 --- a/tests/test_issue377_agg_over_binary_operator.py +++ b/tests/test_issue377_agg_over_binary_operator.py @@ -33,6 +33,10 @@ all refuse rather than dropping an operand. 4. The rewrites that *can* preserve the operator still translate, byte for byte — including ``or``, which keeps its established range-window fallback. + +``or`` between two genuinely different metrics was deliberately left open here +and closed later, at classification time, by issue #434 — see +``tests/test_issue434_agg_over_or_operand_drop.py``. """ from __future__ import annotations @@ -218,15 +222,16 @@ def test_no_binary_operator_silently_drops_an_operand(self): (only ``or``) rather than a list of operators it refuses, because a deny-list omitted ``atan2`` and let exactly that through unwarned. - ``or`` is the one operator the guard hands on, so it is not swept here: - it still drops the right operand for a plain cross-metric union. That - gap is older and wider than this fix (it needs a resolver the parser - does not have) and is tracked separately. + ``or`` is still the one operator the *parse-time* guard hands on -- its + two reductions need a resolver the parser does not have -- but it is + swept here too, because ``agg_over_or_operand_drop_rule`` now closes the + same invariant at classification time, where the resolver exists + (issue #434). """ for op in ( "+", "-", "*", "/", "%", "^", "atan2", "==", "!=", ">", "<", ">=", "<=", - "and", "unless", + "and", "unless", "or", ): with self.subTest(op=op): translated = _translate(f"count(node_a {op} node_b)") diff --git a/tests/test_issue434_agg_over_or_operand_drop.py b/tests/test_issue434_agg_over_or_operand_drop.py new file mode 100644 index 00000000..452e295d --- /dev/null +++ b/tests/test_issue434_agg_over_or_operand_drop.py @@ -0,0 +1,379 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one or more contributor license agreements. +# SPDX-License-Identifier: Elastic-2.0 + +"""Tests for issue #434 — ``agg(A or B)`` dropping the right-hand operand. + +Issue #377 closed ``agg(A B)`` for every binary operator *except* ``or``. +``or`` was deferred because it carries two reductions that only a stage holding +a schema resolver can evaluate: the Grafana same-metric range-window fallback +(``rate(M[$interval]) or irate(M[5m])``) and the live-absent operand drop. A +parse-time refusal cannot tell those apart from a genuine cross-metric union, +so it would have refused both. + +The hole that was left is ``or`` between operands that neither reduction +covers. ``count(node_a or node_b)`` emitted ``COUNT(node_a)`` — feasible, +confidence 0.85, **no warning at all** — because no rewrite claimed the +fragment and the generic ``fragment_extract``/``stats_expression`` fallback +rebuilt ``agg()`` from the fragment's summary fields. + +The bare (unaggregated) forms of the very same chains had already settled this: +``node_a or node_b`` keeps both operands as ``COALESCE(node_a, node_b)``, +``node_up{job="a"} or node_up{job="b"}`` keeps both as a unified ``WHERE ... OR``, +and a chain whose operands cannot be aligned is refused outright with "marked +for manual review so no series are silently dropped". Wrapping the identical +chain in ``count(...)`` inverted every one of those verdicts. That wrapped-vs-bare +disagreement is the same defect #377 fixed for ``and``. + +An aggregation cannot be distributed over a union — ``agg(A or B)`` is not +``agg(A) or agg(B)`` — so the union rewrites the bare path uses are unavailable +here and refusing is the honest answer. + +Covered here: + +1. The reproduction refuses, emits no ES|QL, and names both operands. +2. The refusal survives ``by()`` grouping, longer ``or`` chains, every outer + aggregation, every panel type, and an enclosing wrapper such as + ``clamp_max()`` — the follow-ups the issue asked to check. +3. The two reductions the deferral existed to protect still translate. +4. Those reductions now *disclose* the operand they drop, with the same warning + text the bare path already emits. +""" + +from __future__ import annotations + +import unittest + +from observability_migration.adapters.source.grafana import panels, rules, schema +from observability_migration.adapters.source.grafana.promql import ( + _agg_over_binary_not_feasible_reason, + _agg_over_or_not_feasible_reason, +) +from observability_migration.adapters.source.grafana.translate import ( + translate_promql_to_esql, +) + +INDEX = "metrics-*" + +# grafana.com "Node Exporter Full"-style CPU panel: a Linux node_exporter +# expression ``or``-ed with a differently-named cloud metric, each side itself a +# same-metric range-window fallback. The bare chain is already refused as +# unalignable; only the ``avg by (...)`` wrapper let it through. +MIXED_SOURCE_CHAIN = ( + '(avg by (mode) ( ' + '(clamp_max(rate(node_cpu_seconds_total{mode!="idle"}[$interval]),1)) ' + 'or (clamp_max(irate(node_cpu_seconds_total{mode!="idle"}[5m]),1)) ' + "))*100 " + 'or (max_over_time(node_cpu_average{mode=~"user|system"}[$interval]) ' + 'or max_over_time(node_cpu_average{mode=~"user|system"}[5m]))' +) + + +def _resolver(rule_pack, fields=None): + """Offline resolver by default; a live one when *fields* is given.""" + resolver = schema.SchemaResolver(rule_pack) + if fields is not None: + resolver._discovery_attempted = True + resolver._field_cache = dict(fields) + resolver._discovered_mappings = {} + resolver._schema_profile_cache_id = None + resolver._discovery_status = "ok" + return resolver + + +def _translate(expr, panel_type="timeseries", fields=None): + rule_pack = rules.RulePackConfig() + return translate_promql_to_esql( + expr, + datasource_index=INDEX, + esql_index=INDEX, + panel_type=panel_type, + rule_pack=rule_pack, + resolver=_resolver(rule_pack, fields), + translation_hints={"summary_mode": panel_type in {"stat", "singlestat"}}, + ) + + +def _translate_panel(expr, panel_type="stat"): + """Migrate a one-panel dashboard the way a real run does.""" + rule_pack = rules.RulePackConfig() + dashboard = { + "uid": "u-434", + "title": "issue 434", + "panels": [ + { + "id": 7, + "type": panel_type, + "title": "Nodes up", + "gridPos": {"x": 0, "y": 0, "w": 12, "h": 8}, + "targets": [ + {"refId": "A", "expr": expr, "datasource": {"type": "prometheus"}} + ], + } + ], + } + result = panels.translate_dashboard( + dashboard, + datasource_index=INDEX, + esql_index=INDEX, + rule_pack=rule_pack, + resolver=_resolver(rule_pack), + ) + return result.panel_results[0], result + + +class TestIssue434Reproduction(unittest.TestCase): + """``count(node_a or node_b)`` must refuse instead of counting node_a.""" + + def test_count_over_cross_metric_or_is_not_feasible(self): + translated = _translate("count(node_a or node_b)") + + self.assertEqual(translated.feasibility, "not_feasible") + self.assertEqual(translated.confidence, 0.0) + self.assertFalse(translated.esql_query) + + def test_the_exact_symptom_no_count_over_the_left_operand(self): + """``STATS node_a = COUNT(node_a)`` with node_b nowhere in the query.""" + translated = _translate("count(node_a or node_b)") + + self.assertNotIn("COUNT(", translated.esql_query or "") + self.assertNotIn("node_a", translated.esql_query or "") + + def test_refusal_names_the_operator_and_both_operands(self): + joined = " ".join(_translate("count(node_a or node_b)").warnings) + + self.assertIn("set operator 'or' inside an aggregation", joined) + self.assertIn("count(A or B)", joined) + self.assertIn("node_a", joined) + self.assertIn("node_b", joined) + self.assertIn("not_feasible", joined) + + def test_panel_status_is_not_feasible_not_a_plausible_number(self): + panel_result, result = _translate_panel("count(node_a or node_b)") + + self.assertEqual(panel_result.status, "not_feasible") + self.assertEqual(result.not_feasible, 1) + self.assertEqual(result.migrated_with_warnings, 0) + + +class TestAggOverOrRefusals(unittest.TestCase): + """Every ``agg()`` shape must refuse.""" + + def test_every_outer_aggregation_refuses(self): + for agg in ("count", "sum", "max", "min", "avg", "stddev", "stdvar"): + with self.subTest(agg=agg): + translated = _translate(f"{agg}(node_a or node_b)") + self.assertEqual(translated.feasibility, "not_feasible") + self.assertFalse(translated.esql_query) + + def test_grouping_does_not_hide_the_drop(self): + """The issue asked whether ``by()`` reopened the hole. It did.""" + for expr in ( + "count by (job) (node_a or node_b)", + "sum by (instance, job) (node_a or node_b)", + "count(node_a or node_b) by (job)", + ): + with self.subTest(expr=expr): + translated = _translate(expr) + self.assertEqual(translated.feasibility, "not_feasible") + self.assertFalse(translated.esql_query) + + def test_nested_or_chains_refuse(self): + """The issue asked about nested chains. A 3-way union drops two operands.""" + translated = _translate("count(node_a or node_b or node_c)") + + self.assertEqual(translated.feasibility, "not_feasible") + self.assertFalse(translated.esql_query) + joined = " ".join(translated.warnings) + for metric in ("node_a", "node_b", "node_c"): + self.assertIn(metric, joined) + + def test_same_metric_with_differing_matchers_refuses(self): + """``COUNT(node_up)`` with *both* job filters gone was the old output.""" + translated = _translate('count(node_up{job="a"} or node_up{job="b"})') + + self.assertEqual(translated.feasibility, "not_feasible") + self.assertFalse(translated.esql_query) + + def test_quantile_over_a_union_refuses(self): + translated = _translate("quantile(0.9, node_a or node_b)") + + self.assertEqual(translated.feasibility, "not_feasible") + self.assertFalse(translated.esql_query) + + def test_an_enclosing_wrapper_does_not_reopen_the_hole(self): + """``clamp_max(sum(a or b), 100)`` kept the aggregate fragment on top.""" + translated = _translate("clamp_max(sum(node_a or node_b), 100)") + + self.assertEqual(translated.feasibility, "not_feasible") + self.assertFalse(translated.esql_query) + + def test_refusal_does_not_depend_on_panel_type(self): + for panel_type in ("stat", "singlestat", "gauge", "timeseries", "graph", "table"): + with self.subTest(panel_type=panel_type): + self.assertEqual( + _translate("count(node_a or node_b)", panel_type=panel_type).feasibility, + "not_feasible", + ) + + def test_wrapped_and_bare_forms_of_the_same_chain_agree(self): + """The #377 invariant, applied to ``or``: the wrapper cannot flip the verdict.""" + bare = _translate(MIXED_SOURCE_CHAIN) + wrapped = _translate(f"clamp_max(avg by (node_name,mode) ({MIXED_SOURCE_CHAIN}),100)") + + self.assertEqual(bare.feasibility, "not_feasible") + self.assertEqual(wrapped.feasibility, "not_feasible") + self.assertFalse(wrapped.esql_query) + + def test_no_refused_chain_leaks_a_partial_query(self): + for expr in ( + "count(node_a or node_b)", + "sum by (job) (node_a or node_b)", + "avg(node_a or node_b or node_c)", + 'count(node_up{job="a"} or node_up{job="b"})', + ): + with self.subTest(expr=expr): + translated = _translate(expr) + self.assertFalse(translated.esql_query) + self.assertTrue(translated.warnings) + + +class TestAggOverOrStillTranslatable(unittest.TestCase): + """The reductions the ``or`` deferral existed to protect must be untouched.""" + + def test_or_vector_zero_fill_still_translates(self): + translated = _translate("count(node_a or vector(0))") + + self.assertEqual(translated.feasibility, "feasible") + self.assertIn("COUNT(node_a)", translated.esql_query) + + def test_same_metric_range_window_fallback_still_translates(self): + translated = _translate( + "avg by (service_name) (" + "max_over_time(mysql_max_used_connections[$interval])" + " or max_over_time(mysql_max_used_connections[5m]))", + ) + + self.assertEqual(translated.feasibility, "feasible") + self.assertIn("AVG(MAX_OVER_TIME(", translated.esql_query) + + def test_rate_irate_fallback_under_an_aggregation_still_translates(self): + translated = _translate("avg(rate(m_total[10m]) or irate(m_total[5m]))") + + self.assertEqual(translated.feasibility, "feasible") + self.assertIn("AVG(RATE(m_total))", translated.esql_query) + + def test_colocated_ratio_over_a_range_fallback_still_renders(self): + translated = _translate( + "sum(rate(a_total[5m]) / rate(b_total[5m])" + " or irate(a_total[5m]) / irate(b_total[5m]))" + ) + + self.assertEqual(translated.feasibility, "feasible") + self.assertIn("SUM((RATE(a_total) / RATE(b_total)))", translated.esql_query) + + def test_live_absent_operand_still_reduces_to_the_surviving_one(self): + """The resolver-backed drop the issue named as legitimate.""" + translated = _translate( + "count(node_a or node_b)", + fields={"node_a": {"double": {"searchable": True, "aggregatable": True}}}, + ) + + self.assertEqual(translated.feasibility, "feasible") + self.assertIn("COUNT(node_a)", translated.esql_query) + + def test_an_operand_absent_only_offline_is_not_dropped(self): + """Without live caps nothing is disprovable, so the chain still refuses.""" + translated = _translate("count(node_a or node_b)") + + self.assertEqual(translated.feasibility, "not_feasible") + + +class TestAggOverOrDisclosure(unittest.TestCase): + """A reduction that drops an operand has to say so, as the bare path does.""" + + def test_range_window_fallback_drop_is_disclosed(self): + translated = _translate("avg(rate(m_total[10m]) or irate(m_total[5m]))") + + joined = " ".join(translated.warnings) + self.assertIn("PromQL same-metric 'or': preferred left 'rate(...)'", joined) + self.assertIn("irate(...)", joined) + + def test_wrapped_and_bare_disclose_the_same_range_fallback_drop(self): + bare = _translate("rate(m_total[10m]) or irate(m_total[5m])") + wrapped = _translate("avg(rate(m_total[10m]) or irate(m_total[5m]))") + + shared = [w for w in bare.warnings if "same-metric 'or'" in w] + self.assertTrue(shared) + for warning in shared: + self.assertIn(warning, wrapped.warnings) + + def test_colocated_ratio_discloses_the_dropped_window_fallback(self): + translated = _translate( + "sum(rate(a_total[5m]) / rate(b_total[5m])" + " or irate(a_total[5m]) / irate(b_total[5m]))" + ) + + self.assertIn( + "PromQL same-metric 'or': preferred left range-window operand and " + "dropped the alternate-window fallback; Grafana uses the right " + "side only when the left lacks samples", + translated.warnings, + ) + + def test_absent_operand_drop_is_disclosed(self): + translated = _translate( + "count(node_a or node_b)", + fields={"node_a": {"double": {"searchable": True, "aggregatable": True}}}, + ) + + self.assertIn( + "PromQL 'or': dropped operands whose metrics are absent from the live target", + translated.warnings, + ) + + def test_a_chain_that_loses_nothing_stays_clean(self): + """``count(m)`` has no ``or`` to reduce; the rule must not tag it.""" + translated = _translate("count(node_a)") + + self.assertEqual(translated.feasibility, "feasible") + joined = " ".join(translated.warnings) + self.assertNotIn("same-metric 'or'", joined) + self.assertNotIn("absent from the live target", joined) + + +class TestAggOverOrReason(unittest.TestCase): + """The refusal message has to tell an operator what to do next.""" + + def test_reason_describes_union_semantics_not_filtering(self): + reason = _agg_over_or_not_feasible_reason("count") + + self.assertIn("set operator 'or' inside an aggregation", reason) + self.assertIn("count(A or B)", reason) + self.assertIn("union", reason) + self.assertIn("not_feasible", reason) + + def test_reason_lists_the_operand_metrics(self): + reason = _agg_over_or_not_feasible_reason("sum", ["node_a", "node_b"]) + + self.assertIn("node_a", reason) + self.assertIn("node_b", reason) + + def test_reason_tolerates_a_missing_outer_agg(self): + self.assertIn("aggregation(A or B)", _agg_over_or_not_feasible_reason("")) + + def test_binary_reason_helper_routes_or_to_the_union_text(self): + """``or`` must never inherit the ``and``/``unless`` "selects which series" text.""" + reason = _agg_over_binary_not_feasible_reason("count", "or") + + self.assertEqual(reason, _agg_over_or_not_feasible_reason("count")) + self.assertNotIn("selects which series survive", reason) + + def test_and_and_unless_wording_is_unchanged(self): + for op in ("and", "unless"): + with self.subTest(op=op): + reason = _agg_over_binary_not_feasible_reason("count", op) + self.assertIn("selects which series survive", reason) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_migrate.py b/tests/test_migrate.py index 1e0349d9..02e4566d 100644 --- a/tests/test_migrate.py +++ b/tests/test_migrate.py @@ -1958,6 +1958,26 @@ def test_conflicting_group_field_is_dropped_when_target_wildcard_cannot_group_it self.assertIn("Dropped grouping fields with incompatible target field types during migration", translated.warnings) def test_clamp_wrapper_uses_real_output_field_when_panel_drops_unmigrated_target(self): + # Both targets are ``linux_expr or node_cpu_average`` cloud fallbacks. + # Since issue #434 an ``or`` under an aggregation only reduces to its + # left operand when the right one is *provably* absent from the target, + # so seed the caps that prove it: node_cpu_seconds_total exists, + # node_cpu_average does not. Offline the same panel is refused rather + # than silently counting the Linux side alone. + self.seed_field_caps({ + "node_cpu_seconds_total": { + "double": { + "type": "double", + "aggregatable": True, + "searchable": True, + "time_series_metric": "counter", + } + }, + "node_name": {"keyword": {"type": "keyword", "aggregatable": True, "searchable": True}}, + "mode": {"keyword": {"type": "keyword", "aggregatable": True, "searchable": True}}, + "cpu": {"keyword": {"type": "keyword", "aggregatable": True, "searchable": True}}, + }) + self.resolver._discovery_status = "ok" panel = { "id": 22, "title": "$node_name - Overall CPU Utilization", From 17332b5603ae6b66d739824cb60d81eeb41b7722 Mon Sep 17 00:00:00 2001 From: subham sarkar Date: Fri, 4 Sep 2026 13:09:03 +0530 Subject: [PATCH 2/2] fix(grafana): refuse nested and wrapped agg(A or B) unions The classifier only inspected the top fragment's inner child, so sum(count(A or B)), label_replace(sum(A or B), ...), and avg(A or B) > 5 still rebuilt a single-operand aggregation. Walk every nested fragment child and only disclose a range-window drop when the operands actually are that fallback. --- docs/sources/grafana.md | 2 +- .../adapters/source/grafana/promql.py | 71 +++++++++++++- .../adapters/source/grafana/translate.py | 50 +++++----- .../test_issue434_agg_over_or_operand_drop.py | 93 ++++++++++++++++++- 4 files changed, 187 insertions(+), 29 deletions(-) diff --git a/docs/sources/grafana.md b/docs/sources/grafana.md index 116d093f..22476ee2 100644 --- a/docs/sources/grafana.md +++ b/docs/sources/grafana.md @@ -1018,7 +1018,7 @@ is available at `examples/cue/grafana-rule-pack.cue`. - `label_replace(v, dst, replacement, src, regex)` follows the same rule when the replacement references the source label (a `$1`-style capture): the post-`STATS` `| EVAL`/`| GROK` can only read `src` if the inner expression's result still carries it, so forms that aggregate it away stay `not_feasible` rather than emitting a query that fails on an unknown column. This includes nested aggregations, whose grouping comes from the source's own `by()` clauses (issue #382) and whose outer `STATS` drops the inner grouping regardless — `label_replace(max(sum by (namespace) (m)), "ns", "$1", "namespace", "(.*)")` is not feasible even though `namespace` appears in an inner `by()`. Forms that read no source column are unaffected: a literal replacement emits `EVAL dst = "literal"`, and an identity copy (`dst` resolving to the same target field as `src`) emits nothing at all. - Histogram mean idioms `sum(increase|rate(m_sum) / increase|rate(m_count))` approximate as a ratio of aggregates (`sum(m_sum)/sum(m_count)`) with an explicit warning; unrelated per-element ratios stay `not_feasible`. - An aggregation **wrapping** a binary operator (`agg(A op B)`) is only feasible when a rewrite can prove the operator survives, because PromQL evaluates `A op B` per matching series pair — matching on the operands' full label set — before the aggregation reduces the survivors, and ES|QL has no equivalent stage. The rewrites are: `sum(A ± B)` push-down (`sum` is linear), scalar hoisting (`max(A * 8)`, `avg(A / 100)`), the histogram mean idiom above, and the co-located per-document renderer (`count(A + B)` → `COUNT((A + B))`, for `+ - * /` over operands that share a label set). Everything else stays `not_feasible` with a warning naming the operator and the aggregation: set operators `and` / `unless` (`count(A and B >= threshold)` — issue #377), comparisons between two series (`count(A < B)`; compare against a scalar threshold instead), and arithmetic the renderer does not cover (`%`, `^`, `atan2`, or operands that carry their own `by()` reduction). The refusal is an allowlist of operators that are handed on rather than a list of operators that are refused, so an operator nobody enumerated fails closed instead of silently reaching the fallback. Previously these fell through to the generic single-metric fallback, which rebuilt `agg()` and shipped a plausible but wrong number as `migrated_with_warnings`. -- `agg(A or B)` is decided one stage later, during classification, because `or` carries two reductions that need live field capabilities the parser does not have: the same-metric range-window fallback (`max_over_time(M[$interval]) or max_over_time(M[5m])`, `rate(M[$interval]) or irate(M[5m])`) and the drop of an operand whose metric is **provably absent** from the target. When either reduction elects a single surviving operand the panel translates as before, and the dropped operand is now named in a warning — the same disclosure the bare (unaggregated) chain already emitted. When neither applies, the panel is `not_feasible` (issue #434). `or` is a set *union*, so an aggregation cannot be distributed over it (`agg(A or B)` is not `agg(A) or agg(B)`), which is why the `COALESCE` / unified `WHERE ... OR` rewrites that keep both operands in the bare form are unavailable under an aggregation. Before this, `count(node_a or node_b)` emitted `COUNT(node_a)` as clean `migrated` with no warning at all, while the bare `node_a or node_b` kept both operands — the wrapper inverted the verdict on the same expression. The refusal covers `by()` grouping, longer `or` chains, every outer aggregation, and enclosing wrappers such as `clamp_max(...)`. Offline (no `--es-url` / no reachable field capabilities) nothing is disprovable, so a cross-metric fallback chain refuses rather than silently reporting only its left operand; re-run with target field capabilities reachable to let the absent-operand reduction apply. +- `agg(A or B)` is decided one stage later, during classification, because `or` carries two reductions that need live field capabilities the parser does not have: the same-metric range-window fallback (`max_over_time(M[$interval]) or max_over_time(M[5m])`, `rate(M[$interval]) or irate(M[5m])`) and the drop of an operand whose metric is **provably absent** from the target. When either reduction elects a single surviving operand the panel translates as before, and the dropped operand is now named in a warning — the same disclosure the bare (unaggregated) chain already emitted. When neither applies, the panel is `not_feasible` (issue #434). `or` is a set *union*, so an aggregation cannot be distributed over it (`agg(A or B)` is not `agg(A) or agg(B)`), which is why the `COALESCE` / unified `WHERE ... OR` rewrites that keep both operands in the bare form are unavailable under an aggregation. Before this, `count(node_a or node_b)` emitted `COUNT(node_a)` as clean `migrated` with no warning at all, while the bare `node_a or node_b` kept both operands — the wrapper inverted the verdict on the same expression. The refusal covers `by()` grouping, longer `or` chains, every outer aggregation, enclosing wrappers such as `clamp_max(...)`, nested aggregations (`sum(count(A or B))`, `max(sum(A or B))`), `label_replace`/`label_join` around an inner union, comparisons against a scalar (`avg(A or B) > 5`), and `topk`/`bottomk` over a union (`topk(3, A or B)`). Offline (no `--es-url` / no reachable field capabilities) nothing is disprovable, so a cross-metric fallback chain refuses rather than silently reporting only its left operand; re-run with target field capabilities reachable to let the absent-operand reduction apply. - Multi-target XY panels fuse when series share a compatible ES|QL shape. Summary panels (`stat` / `singlestat` / `gauge` / `bargauge` / table) use the same compatibility group and approximate multi-series stats as a summary table when needed. Grouping mismatches where a bare aggregation (no `by()`, e.g. `min`/`avg`/`max`/`sum`) shares its metric field with a grouped sibling target — the disk-graphs "IO Wait per core" Min/Avg/Max-over-CPUs shape (issue #355) — render as two separate ES|QL layers instead of unioning the `BY` fields: one layer keeps the grouped target's own dimensions, the other re-aggregates the bare target across every group (time-bucket only), matching what Grafana always draws as a single cross-series line. When the bare target instead broadcasts an *unrelated* metric alongside a grouped breakdown (e.g. QoS `by (qos_class)` + an ungrouped fleet-wide total) there is no shared per-series computation to split out, so that target's `BY` fields still union with the grouped target's, but the warning now names the changed semantics (which series is affected, the fields it is now grouped by) instead of only describing the mechanism; an unrelated bare target sharing the panel does not disable the split for the pair that *does* share a metric. A lone bare target with no Min/Avg/Max siblings still gets its own summary layer, aliased to its own `legendFormat` text (not the raw metric field name); the same split applies within a data stream even when another fused target lands on a different index partition. Divergent label filters on otherwise identical measures CASE-inline into the shared `STATS` (including window-less `LAST_OVER_TIME`, used by Express-style status-class counters). `legendFormat` `{{label}}` placeholders on `rate`/`irate`/`increase` (and other TS paths covered by issue #99) are display hints — they become series aliases, not `BY` dimensions — so overlays like Redis in/out rates can share one panel. When fused targets carry *different* multi-placeholder `legendFormat` templates (issue #354), the composite `EVAL legend` expression is suppressed so the `STATS` column name carries series identity instead of mislabelling every series with the first target's literal text; targets whose templates agree, or that use only a single placeholder, are unaffected. Targets that remain incompatible (Windows vs Linux metrics, complex `or`/`label_replace` trees) still keep the largest compatible group and warn; Windows-specific drop wording only applies when every dropped target is a `windows_*` metric. - Grafana **stat/gauge tiles are one-per-series** (a status grid). Translation preserves the source PromQL expression's grouping and cardinality rather than inferring `BY` dimensions from `legendFormat` text alone. An ungrouped `up` (or similar) that Grafana would still fan out per scrape target is migrated **with a warning**; outer aggregations such as `sum(rate(...))` stay scalars even when the legend contains `{{job}}`. The same rule governs **nested aggregations** on every panel type: the grouping of `max(sum(m))` comes from the source's own `by()` clauses and nothing else — legend placeholders, legacy table column patterns, and dashboard-wide label inference are all display hints. In a nested aggregation the inner grouping additionally decides what the *outer* aggregation reduces over, so a hint-derived inner `BY` made `max(sum(m))` report the largest single label value instead of the collapsed total, and a hint-derived outer `BY` turned the scalar `count(count by (cpu) (m))` into a constant `1` per CPU (issue #382). - Composite/multi-target series (a fused native-PROMQL `value` column, the general ES|QL translator's scalar-expression `computed_value` column, or a curated-pack override that folds several source metrics into one `value` column with a `series_group` breakdown) never surface that internal column name to the operator. An unambiguous static `legendFormat` shared by every visible target takes priority; mixed or templated legends fall back to the panel title as the metric's label. A single such metric with a breakdown uses that label as the Y-axis title when Grafana left the axis unnamed and no uniform unit title (`%`, `Bytes`, …) can be inferred (issue #351). An explicit Grafana axis label still wins; opaque aliases such as `percentage` are ignored so a unit title can apply. A panel with no usable title/legend text falls back to the prior hidden-title behavior. diff --git a/observability_migration/adapters/source/grafana/promql.py b/observability_migration/adapters/source/grafana/promql.py index c856ef62..1ad7dc96 100644 --- a/observability_migration/adapters/source/grafana/promql.py +++ b/observability_migration/adapters/source/grafana/promql.py @@ -2338,6 +2338,16 @@ def _copy_fragment_summary(target, source): "start_metric", "vector_matching", "wrapped_scalar", + # Comparison-with-scalar and label_replace/label_join copy the left + # summary onto a new fragment. Without these children the issue #434 + # classifier cannot see a nested ``agg(A or B)`` (``avg(A or B) > 5``, + # ``label_replace(sum(A or B), ...)``) and the generic fallback ships + # ``agg()``. + "inner_frag", + "left_frag", + "right_frag", + "lr_inner_frag", + "lj_inner_frag", ): if source.extra.get(key) is not None and key not in target.extra: target.extra[key] = source.extra[key] @@ -2350,11 +2360,15 @@ def _iter_fragment_children(frag): if not frag: return [] children = [] - child = frag.extra.get("inner_frag") - if isinstance(child, PromQLFragment): - children.append(child) - for key in ("left_frag", "right_frag"): - child = frag.extra.get(key) + extra = getattr(frag, "extra", None) or {} + for key in ( + "inner_frag", + "left_frag", + "right_frag", + "lr_inner_frag", + "lj_inner_frag", + ): + child = extra.get(key) if isinstance(extra, dict) else None if isinstance(child, PromQLFragment): children.append(child) if isinstance(frag.binary_rhs, PromQLFragment): @@ -2493,6 +2507,12 @@ def _ast_call_fragment(node, expr): frag = _copy_fragment_summary(_new_fragment(expr, family="topk"), value_frag) frag.extra["topk_limit"] = int(limit_frag.scalar_value) frag.extra["topk_value_expr"] = value_frag.raw_expr + if ( + getattr(value_frag, "family", "") == "binary_expr" + and (getattr(value_frag, "binary_op", "") or "").lower() == "or" + ): + frag.extra["inner_frag"] = value_frag + frag.outer_agg = "topk" return frag if func_name in SUPPORTED_RANGE_FUNCTIONS and len(args) == 1 and type(args[0]).__name__ == "MatrixSelector": @@ -3042,6 +3062,15 @@ def _ast_aggregate_fragment(node, expr): topk_frag.extra["topk_limit"] = 10 topk_frag.extra["topk_value_expr"] = child.raw_expr topk_frag.extra["topk_sort_asc"] = frag.outer_agg == "bottomk" + # Flattening copies the left metric leaf. If the value is an ``or`` + # union, keep that chain on inner_frag so the issue #434 classifier + # can refuse an irreducible drop instead of shipping topk(left only). + if ( + getattr(child, "family", "") == "binary_expr" + and (getattr(child, "binary_op", "") or "").lower() == "or" + ): + topk_frag.extra["inner_frag"] = child + topk_frag.outer_agg = frag.outer_agg return topk_frag # quantile(phi, expr) by (..) == ES|QL PERCENTILE(expr, phi*100). Capture the @@ -6229,11 +6258,43 @@ def agg_over_or_reduction(frag, resolver): elif survivors and _operands_are_same_metric_range_fallback(survivors): preferred = survivors[0] dropped_fallback = dropped_fallback + survivors[1:] + # Identical ``count(node_a or node_a)`` collapses because simple_metric + # identity ignores range_func. That is not a range-window fallback, so + # do not disclose it as one. After macros, a real + # ``max_over_time(M[$interval]) or max_over_time(M[5m])`` still has a + # range shape and keeps the disclosure. + if dropped_fallback and not _operands_are_same_metric_range_fallback(operands): + dropped_fallback = [] return AggOverOrReduction( chain, preferred, dropped_absent, dropped_fallback, survivors ) +def iter_agg_over_or_reductions(frag, resolver): + """Yield ``(fragment, reduction)`` for every ``agg(A or B)`` in *frag*. + + ``agg_over_or_reduction`` looks at one fragment's own inner child. Nested + aggregations such as ``sum(count(A or B))`` store the ``or`` on the inner + fragment, so a top-only walk would miss it and the generic fallback would + rebuild ``agg()`` — the issue #434 silent drop, one + wrapping layer down. Walking ``inner_frag`` / ``left_frag`` / ``right_frag`` + keeps the classifier and the renderer on the same definition of "is this + union reducible". + """ + seen: set[int] = set() + stack = [frag] + while stack: + current = stack.pop() + if current is None or id(current) in seen: + continue + seen.add(id(current)) + reduction = agg_over_or_reduction(current, resolver) + if reduction is not None: + yield current, reduction + for child in _iter_fragment_children(current): + stack.append(child) + + def _expr_for_or_collapse_parse(expr: str, rule_pack=None) -> str: """Make Grafana range macros parseable without rewriting label matchers. diff --git a/observability_migration/adapters/source/grafana/translate.py b/observability_migration/adapters/source/grafana/translate.py index dfda0f18..2bd103a2 100644 --- a/observability_migration/adapters/source/grafana/translate.py +++ b/observability_migration/adapters/source/grafana/translate.py @@ -87,11 +87,11 @@ _resolve_metric_field, _same_metric_range_fallback_warning, _summary_mode_from_metadata, - agg_over_or_reduction, classify_promql_complexity, colocated_binary_agg_plan, colocated_metric_fields, gauge_default_agg_warning, + iter_agg_over_or_reductions, preprocess_grafana_macros, resolve_counter_range_translation, ) @@ -1158,7 +1158,12 @@ def agg_over_or_operand_drop_rule(context): ``fragment_extract``/``stats_expression`` fallback rebuilt ``agg()`` from the fragment's summary fields and shipped it clean: ``count(node_a or node_b)`` became ``COUNT(node_a)`` with no - warning (issue #434). The bare chain keeps both operands (``COALESCE`` / + warning (issue #434). The same fallback still fires when the ``or`` sits + under a *nested* aggregation (``sum(count(A or B))``, ``max(sum(A or B))``), + because the ``or`` is on the inner fragment rather than the root's + ``inner_frag``. This rule therefore walks every nested ``agg(A or B)``. + + The bare chain keeps both operands (``COALESCE`` / unified ``WHERE ... OR``) or refuses outright, so the wrapper was inverting the verdict on the identical expression. @@ -1168,27 +1173,28 @@ def agg_over_or_operand_drop_rule(context): ``colocated_binary_agg_plan`` can render, which requires the same reduction to have elected an operand -- exactly the case this rule stays silent for. """ - frag = context.fragment - reduction = agg_over_or_reduction(frag, context.resolver) - if reduction is None: - return None - if reduction.preferred is None: - reason = _agg_over_or_not_feasible_reason( - frag.outer_agg, _fragment_metric_names(reduction.chain) - ) - _append_not_feasible_reason(frag, reason) - return reason - # A reduction did elect an operand, but the ones it removed still have to be - # disclosed -- the same warnings the bare path emits for the same drops. details = [] - if reduction.dropped_absent: - _append_unique(context.warnings, _ABSENT_OR_OPERAND_WARNING) - details.append("disclosed live-absent 'or' operand drop") - if reduction.dropped_fallback: - _append_unique( - context.warnings, _same_metric_range_fallback_warning(reduction.chain) - ) - details.append("disclosed dropped same-metric range-window fallback") + for current, reduction in iter_agg_over_or_reductions( + context.fragment, context.resolver + ): + if reduction.preferred is None: + reason = _agg_over_or_not_feasible_reason( + current.outer_agg, _fragment_metric_names(reduction.chain) + ) + # Tag the root fragment so fragment_guardrails (priority 1) sees + # the reason. Nested aggregations store the ``or`` on an inner + # fragment whose own not_feasible_reasons would otherwise never + # reach the panel. + _append_not_feasible_reason(context.fragment, reason) + return reason + if reduction.dropped_absent: + _append_unique(context.warnings, _ABSENT_OR_OPERAND_WARNING) + details.append("disclosed live-absent 'or' operand drop") + if reduction.dropped_fallback: + _append_unique( + context.warnings, _same_metric_range_fallback_warning(reduction.chain) + ) + details.append("disclosed dropped same-metric range-window fallback") return "; ".join(details) or None diff --git a/tests/test_issue434_agg_over_or_operand_drop.py b/tests/test_issue434_agg_over_or_operand_drop.py index 452e295d..7986d2bf 100644 --- a/tests/test_issue434_agg_over_or_operand_drop.py +++ b/tests/test_issue434_agg_over_or_operand_drop.py @@ -207,6 +207,62 @@ def test_an_enclosing_wrapper_does_not_reopen_the_hole(self): self.assertEqual(translated.feasibility, "not_feasible") self.assertFalse(translated.esql_query) + def test_nested_aggregation_does_not_reopen_the_hole(self): + """An outer agg around ``count/sum(A or B)`` still has to refuse. + + The classifier originally inspected only the top fragment's inner + child. ``sum(count(A or B))`` stores the ``or`` one level down, so + the generic fallback rebuilt ``SUM(node_a)`` / ``COUNT(node_a)`` at + confidence 0.85 with no warning — the same silent drop issue #434 + closed for the un-nested form. + """ + for expr in ( + "sum(count(node_a or node_b))", + "max(sum(node_a or node_b))", + "avg(count(node_a or node_b))", + "count(sum(node_a or node_b))", + "quantile(0.9, count(node_a or node_b))", + ): + with self.subTest(expr=expr): + translated = _translate(expr) + self.assertEqual(translated.feasibility, "not_feasible") + self.assertFalse(translated.esql_query) + self.assertNotIn("node_a", translated.esql_query or "") + + def test_topk_over_cross_metric_or_does_not_keep_only_the_left_operand(self): + """``topk`` flattened the union to the left metric at parse time.""" + for expr in ( + "topk(3, node_a or node_b)", + "bottomk(3, node_a or node_b)", + ): + with self.subTest(expr=expr): + translated = _translate(expr) + self.assertEqual(translated.feasibility, "not_feasible") + self.assertFalse(translated.esql_query) + + def test_label_replace_does_not_hide_an_inner_union(self): + """``label_replace`` stores the agg on ``lr_inner_frag``, not ``inner_frag``.""" + translated = _translate( + 'label_replace(sum(node_a or node_b), "x", "hello", "job", ".*")' + ) + + self.assertEqual(translated.feasibility, "not_feasible") + self.assertFalse(translated.esql_query) + joined = " ".join(translated.warnings) + self.assertIn("node_a", joined) + self.assertIn("node_b", joined) + + def test_comparison_against_a_scalar_names_the_union(self): + """``avg(A or B) > 5`` used to refuse as 'Could not extract metric name'.""" + translated = _translate("avg(node_a or node_b) > 5") + + self.assertEqual(translated.feasibility, "not_feasible") + self.assertFalse(translated.esql_query) + joined = " ".join(translated.warnings) + self.assertIn("or", joined.lower()) + self.assertIn("node_b", joined) + self.assertNotIn("Could not extract metric name", joined) + def test_refusal_does_not_depend_on_panel_type(self): for panel_type in ("stat", "singlestat", "gauge", "timeseries", "graph", "table"): with self.subTest(panel_type=panel_type): @@ -262,6 +318,22 @@ def test_rate_irate_fallback_under_an_aggregation_still_translates(self): self.assertEqual(translated.feasibility, "feasible") self.assertIn("AVG(RATE(m_total))", translated.esql_query) + def test_topk_over_a_range_fallback_still_translates(self): + translated = _translate("topk(3, rate(m_total[10m]) or irate(m_total[5m]))") + + self.assertEqual(translated.feasibility, "feasible") + self.assertTrue(translated.esql_query) + self.assertIn("m_total", translated.esql_query) + + def test_nested_aggregation_over_a_range_fallback_still_translates(self): + """``sum(avg(rate or irate))`` must keep the reduction, not refuse.""" + translated = _translate( + "sum(avg(rate(m_total[10m]) or irate(m_total[5m])))" + ) + + self.assertEqual(translated.feasibility, "feasible") + self.assertIn("RATE(m_total)", translated.esql_query) + def test_colocated_ratio_over_a_range_fallback_still_renders(self): translated = _translate( "sum(rate(a_total[5m]) / rate(b_total[5m])" @@ -298,6 +370,15 @@ def test_range_window_fallback_drop_is_disclosed(self): self.assertIn("PromQL same-metric 'or': preferred left 'rate(...)'", joined) self.assertIn("irate(...)", joined) + def test_nested_range_window_fallback_drop_is_disclosed(self): + translated = _translate( + "sum(avg(rate(m_total[10m]) or irate(m_total[5m])))" + ) + + joined = " ".join(translated.warnings) + self.assertIn("same-metric 'or'", joined) + self.assertIn("irate(...)", joined) + def test_wrapped_and_bare_disclose_the_same_range_fallback_drop(self): bare = _translate("rate(m_total[10m]) or irate(m_total[5m])") wrapped = _translate("avg(rate(m_total[10m]) or irate(m_total[5m]))") @@ -340,8 +421,18 @@ def test_a_chain_that_loses_nothing_stays_clean(self): self.assertNotIn("same-metric 'or'", joined) self.assertNotIn("absent from the live target", joined) + def test_identical_operands_do_not_claim_a_range_window_drop(self): + """``count(node_a or node_a)`` is a no-op union, not an alternate window.""" + translated = _translate("count(node_a or node_a)") + + self.assertEqual(translated.feasibility, "feasible") + self.assertIn("COUNT(node_a)", translated.esql_query) + joined = " ".join(translated.warnings) + self.assertNotIn("same-metric 'or'", joined) + self.assertNotIn("alternate-window", joined) + -class TestAggOverOrReason(unittest.TestCase): +class TestAggOverOrRefusalReason(unittest.TestCase): """The refusal message has to tell an operator what to do next.""" def test_reason_describes_union_semantics_not_filtering(self):