From 0ee9b1c05ed0c7d70a7875ba6a363221f32a6f8c Mon Sep 17 00:00:00 2001 From: abrar Date: Fri, 9 Jan 2026 22:38:47 +0000 Subject: [PATCH 1/2] [Dashboard] Support more panels in dashboard Signed-off-by: abrar --- .../modules/metrics/dashboards/common.py | 65 +++++++++- .../dashboard/modules/metrics/default_impl.py | 12 ++ .../metrics/grafana_dashboard_factory.py | 121 ++++++++++++++++-- python/ray/tests/test_metrics_head.py | 14 +- 4 files changed, 195 insertions(+), 17 deletions(-) create mode 100644 python/ray/dashboard/modules/metrics/default_impl.py diff --git a/python/ray/dashboard/modules/metrics/dashboards/common.py b/python/ray/dashboard/modules/metrics/dashboards/common.py index 0ad6829c394e..52d76e13599f 100644 --- a/python/ray/dashboard/modules/metrics/dashboards/common.py +++ b/python/ray/dashboard/modules/metrics/dashboards/common.py @@ -1,6 +1,6 @@ from dataclasses import dataclass, field from enum import Enum -from typing import List, Optional +from typing import Any, Dict, List, Optional from ray.util.annotations import DeveloperAPI @@ -92,7 +92,7 @@ class Target: "exemplars": {"color": "rgba(255,0,255,0.7)"}, "filterValues": {"le": 1e-9}, "legend": {"show": True}, - "rowsFrame": {"layout": "auto", "value": "Request count"}, + "rowsFrame": {"layout": "auto", "value": "Value"}, "tooltip": {"mode": "single", "showColorScale": False, "yHistogram": True}, "yAxis": {"axisPlacement": "left", "reverse": False, "unit": "none"}, }, @@ -461,6 +461,36 @@ class Target: "yaxis": {"align": False, "alignLevel": None}, } +TABLE_PANEL_TEMPLATE = { + "datasource": r"${datasource}", + "description": "", + "fieldConfig": { + "defaults": { + "custom": { + "align": "auto", + "displayMode": "auto", + }, + "mappings": [], + }, + "overrides": [], + }, + "gridPos": {"h": 8, "w": 12, "x": 0, "y": 0}, + "id": 26, + "options": { + "showHeader": True, + "footer": { + "show": False, + "reducer": ["sum"], + "fields": "", + }, + }, + "pluginVersion": "7.5.17", + "targets": [], + "title": "", + "type": "table", + "transformations": [{"id": "organize", "options": {}}], +} + @DeveloperAPI class PanelTemplate(Enum): @@ -470,6 +500,7 @@ class PanelTemplate(Enum): STAT = STAT_PANEL_TEMPLATE GAUGE = GAUGE_PANEL_TEMPLATE BAR_CHART = BAR_CHART_PANEL_TEMPLATE + TABLE = TABLE_PANEL_TEMPLATE @DeveloperAPI @@ -488,6 +519,26 @@ class Panel: targets: List of query targets. fill: Whether or not the graph will be filled by a color. stack: Whether or not the lines in the graph will be stacked. + linewidth: Width of the lines in the graph. + grid_pos: Grid position of the panel. + template: The panel template to use. + hideXAxis: Whether to hide the x-axis. + thresholds: Custom threshold configuration for stat/gauge panels. + Example: [ + {"color": "green", "value": None}, + {"color": "yellow", "value": 70}, + {"color": "red", "value": 90} + ] + value_mappings: Value mappings for displaying text instead of numbers. + Used for status panels. + color_mode: Color mode for stat panels ("value", "background", "none"). + legend_mode: Legend display mode ("list", "table", "hidden"). + min_val: Minimum value for gauge/graph y-axis. + max_val: Maximum value for gauge/graph y-axis. + reduce_calc: Reduce calculation method for stat panels (default: "lastNotNull"). + heatmap_color_scheme: Color scheme for heatmap panels (e.g., "Spectral", "RdYlGn"). + heatmap_color_reverse: Whether to reverse the heatmap color scheme. + heatmap_yaxis_label: Y-axis label for heatmap panels. """ title: str @@ -501,6 +552,16 @@ class Panel: grid_pos: Optional[GridPos] = None template: Optional[PanelTemplate] = PanelTemplate.GRAPH hideXAxis: bool = False + thresholds: Optional[List[Dict[str, Any]]] = None + value_mappings: Optional[List[Dict[str, Any]]] = None + color_mode: Optional[str] = None + legend_mode: Optional[str] = None + min_val: Optional[float] = None + max_val: Optional[float] = None + reduce_calc: Optional[str] = None + heatmap_color_scheme: Optional[str] = None + heatmap_color_reverse: Optional[bool] = None + heatmap_yaxis_label: Optional[str] = None @DeveloperAPI diff --git a/python/ray/dashboard/modules/metrics/default_impl.py b/python/ray/dashboard/modules/metrics/default_impl.py new file mode 100644 index 000000000000..104ff90dc3b6 --- /dev/null +++ b/python/ray/dashboard/modules/metrics/default_impl.py @@ -0,0 +1,12 @@ +from ray.dashboard.modules.metrics.dashboards.common import DashboardConfig + + +def get_serve_dashboard_config() -> DashboardConfig: + from ray.dashboard.modules.metrics.dashboards.serve_dashboard_panels import ( + serve_dashboard_config, + ) + + return serve_dashboard_config + + +# Anyscale overrides diff --git a/python/ray/dashboard/modules/metrics/grafana_dashboard_factory.py b/python/ray/dashboard/modules/metrics/grafana_dashboard_factory.py index edb7db66b369..5d5986f59afc 100644 --- a/python/ray/dashboard/modules/metrics/grafana_dashboard_factory.py +++ b/python/ray/dashboard/modules/metrics/grafana_dashboard_factory.py @@ -9,6 +9,7 @@ from ray.dashboard.modules.metrics.dashboards.common import ( DashboardConfig, Panel, + PanelTemplate, ) from ray.dashboard.modules.metrics.dashboards.data_dashboard_panels import ( data_dashboard_config, @@ -16,9 +17,6 @@ from ray.dashboard.modules.metrics.dashboards.default_dashboard_panels import ( default_dashboard_config, ) -from ray.dashboard.modules.metrics.dashboards.serve_dashboard_panels import ( - serve_dashboard_config, -) from ray.dashboard.modules.metrics.dashboards.serve_deployment_dashboard_panels import ( serve_deployment_dashboard_config, ) @@ -28,6 +26,7 @@ from ray.dashboard.modules.metrics.dashboards.train_dashboard_panels import ( train_dashboard_config, ) +from ray.dashboard.modules.metrics.default_impl import get_serve_dashboard_config GRAFANA_DASHBOARD_UID_OVERRIDE_ENV_VAR_TEMPLATE = "RAY_GRAFANA_{name}_DASHBOARD_UID" GRAFANA_DASHBOARD_GLOBAL_FILTERS_OVERRIDE_ENV_VAR_TEMPLATE = ( @@ -96,7 +95,7 @@ def generate_serve_grafana_dashboard() -> Tuple[str, str]: Returns: Tuple with format content, uid """ - return _generate_grafana_dashboard(serve_dashboard_config) + return _generate_grafana_dashboard(get_serve_dashboard_config()) def generate_serve_deployment_grafana_dashboard() -> Tuple[str, str]: @@ -224,17 +223,117 @@ def _generate_panel_template( "y": base_y_position + (row_number * PANEL_HEIGHT), } - template["yaxes"][0]["format"] = panel.unit - template["fill"] = panel.fill - template["stack"] = panel.stack - template["linewidth"] = panel.linewidth + # Set unit format for legacy graph-style panels (GRAPH, HEATMAP, STAT, GAUGE, PIE_CHART, BAR_CHART) + if panel.template in ( + PanelTemplate.GRAPH, + PanelTemplate.HEATMAP, + PanelTemplate.STAT, + PanelTemplate.GAUGE, + PanelTemplate.PIE_CHART, + PanelTemplate.BAR_CHART, + ): + template["yaxes"][0]["format"] = panel.unit + + # Set fieldConfig unit (for newer panel types with fieldConfig.defaults) + if panel.template in ( + PanelTemplate.STAT, + PanelTemplate.GAUGE, + PanelTemplate.HEATMAP, + PanelTemplate.PIE_CHART, + PanelTemplate.BAR_CHART, + PanelTemplate.TABLE, + PanelTemplate.GRAPH, + ): + template["fieldConfig"]["defaults"]["unit"] = panel.unit + + # Set fill, stack, linewidth, nullPointMode (only for GRAPH panels) + if panel.template == PanelTemplate.GRAPH: + template["fill"] = panel.fill + template["stack"] = panel.stack + template["linewidth"] = panel.linewidth + if panel.stack is True: + template["nullPointMode"] = "connected" if panel.hideXAxis: template.setdefault("xaxis", {})["show"] = False - # Handle stacking visualization - if panel.stack is True: - template["nullPointMode"] = "connected" + # Handle optional panel customization fields + + # Thresholds (for panels with fieldConfig.defaults.thresholds) + if panel.thresholds is not None: + if panel.template in (PanelTemplate.STAT, PanelTemplate.GAUGE): + template["fieldConfig"]["defaults"]["thresholds"][ + "steps" + ] = panel.thresholds + + # Value mappings (for panels with fieldConfig.defaults.mappings) + if panel.value_mappings is not None: + if panel.template in ( + PanelTemplate.STAT, + PanelTemplate.GAUGE, + PanelTemplate.TABLE, + ): + template["fieldConfig"]["defaults"]["mappings"] = panel.value_mappings + + # Color mode (for STAT panels with options.colorMode) + if panel.color_mode is not None: + if panel.template == PanelTemplate.STAT: + template["options"]["colorMode"] = panel.color_mode + + # Legend mode + if panel.legend_mode is not None: + if panel.template in (PanelTemplate.GRAPH, PanelTemplate.BAR_CHART): + # For graph panels (legacy format with top-level legend object) + template["legend"]["show"] = panel.legend_mode != "hidden" + template["legend"]["alignAsTable"] = panel.legend_mode == "table" + elif panel.template == PanelTemplate.PIE_CHART: + # For PIE_CHART (options.legend.displayMode) + template["options"]["legend"]["displayMode"] = panel.legend_mode + + # Min/max values (for panels with fieldConfig.defaults) + if panel.min_val is not None or panel.max_val is not None: + if panel.template in ( + PanelTemplate.STAT, + PanelTemplate.GAUGE, + PanelTemplate.HEATMAP, + PanelTemplate.PIE_CHART, + PanelTemplate.BAR_CHART, + PanelTemplate.TABLE, + PanelTemplate.GRAPH, + ): + if panel.min_val is not None: + template["fieldConfig"]["defaults"]["min"] = panel.min_val + if panel.max_val is not None: + template["fieldConfig"]["defaults"]["max"] = panel.max_val + + # Reduce calculation (for panels with options.reduceOptions) + if panel.reduce_calc is not None: + if panel.template in ( + PanelTemplate.STAT, + PanelTemplate.GAUGE, + PanelTemplate.PIE_CHART, + ): + template["options"]["reduceOptions"]["calcs"] = [panel.reduce_calc] + + # Handle heatmap-specific options + if panel.heatmap_color_scheme is not None: + if panel.template == PanelTemplate.HEATMAP: + template["options"]["color"]["scheme"] = panel.heatmap_color_scheme + + if panel.heatmap_color_reverse is not None: + if panel.template == PanelTemplate.HEATMAP: + template["options"]["color"]["reverse"] = panel.heatmap_color_reverse + + if panel.heatmap_yaxis_label is not None: + if panel.template in ( + PanelTemplate.GRAPH, + PanelTemplate.HEATMAP, + PanelTemplate.STAT, + PanelTemplate.GAUGE, + PanelTemplate.PIE_CHART, + PanelTemplate.BAR_CHART, + ): + template["yaxes"][0]["label"] = panel.heatmap_yaxis_label return template diff --git a/python/ray/tests/test_metrics_head.py b/python/ray/tests/test_metrics_head.py index 2df3d64bdbe5..01cf74a0af77 100644 --- a/python/ray/tests/test_metrics_head.py +++ b/python/ray/tests/test_metrics_head.py @@ -7,7 +7,7 @@ import pytest -from ray._common.utils import get_default_ray_temp_dir +from ray._common.utils import get_ray_temp_dir from ray._private.ray_constants import SESSION_LATEST from ray.dashboard.modules.metrics.dashboards.default_dashboard_panels import ( DEFAULT_GRAFANA_ROWS, @@ -45,7 +45,7 @@ def test_metrics_folder_and_content(is_temp_dir_set, temp_dir_val): include_dashboard=True, _temp_dir=temp_dir_val if is_temp_dir_set else None ) as context: session_dir = context["session_dir"] - temp_dir = temp_dir_val if is_temp_dir_set else get_default_ray_temp_dir() + temp_dir = temp_dir_val if is_temp_dir_set else get_ray_temp_dir() assert os.path.exists( f"{session_dir}/metrics/grafana/provisioning/dashboards/default.yml" ) @@ -181,8 +181,14 @@ def test_metrics_folder_with_dashboard_override( contents = json.loads(f.read()) assert contents["uid"] == serve_uid for panel in contents["panels"]: - for target in panel["targets"]: - assert serve_global_filters in target["expr"] + if panel["type"] == "row": + # Row panels contain nested panels, not targets directly + for nested_panel in panel.get("panels", []): + for target in nested_panel["targets"]: + assert serve_global_filters in target["expr"] + else: + for target in panel["targets"]: + assert serve_global_filters in target["expr"] for variable in contents["templating"]["list"]: if variable["name"] == "datasource": continue From 82a98bc8c34c6628cc6941c020c9a087cd83cb10 Mon Sep 17 00:00:00 2001 From: abrar <abrar@anyscale.com> Date: Sat, 10 Jan 2026 00:46:27 +0000 Subject: [PATCH 2/2] fix import Signed-off-by: abrar <abrar@anyscale.com> --- python/ray/tests/test_metrics_head.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/ray/tests/test_metrics_head.py b/python/ray/tests/test_metrics_head.py index 01cf74a0af77..2450a5f46076 100644 --- a/python/ray/tests/test_metrics_head.py +++ b/python/ray/tests/test_metrics_head.py @@ -7,7 +7,7 @@ import pytest -from ray._common.utils import get_ray_temp_dir +from ray._common.utils import get_default_ray_temp_dir from ray._private.ray_constants import SESSION_LATEST from ray.dashboard.modules.metrics.dashboards.default_dashboard_panels import ( DEFAULT_GRAFANA_ROWS, @@ -45,7 +45,7 @@ def test_metrics_folder_and_content(is_temp_dir_set, temp_dir_val): include_dashboard=True, _temp_dir=temp_dir_val if is_temp_dir_set else None ) as context: session_dir = context["session_dir"] - temp_dir = temp_dir_val if is_temp_dir_set else get_ray_temp_dir() + temp_dir = temp_dir_val if is_temp_dir_set else get_default_ray_temp_dir() assert os.path.exists( f"{session_dir}/metrics/grafana/provisioning/dashboards/default.yml" )