chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:17:40 +08:00
commit f1825c8ceb
10096 changed files with 2364182 additions and 0 deletions
@@ -0,0 +1,615 @@
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Dict, List, Optional
from ray.util.annotations import DeveloperAPI
@DeveloperAPI
@dataclass
class GridPos:
x: int
y: int
w: int
h: int
GRAPH_TARGET_TEMPLATE = {
"exemplar": True,
"expr": "0",
"interval": "",
"legendFormat": "",
"queryType": "randomWalk",
"refId": "A",
}
HEATMAP_TARGET_TEMPLATE = {
"format": "heatmap",
"fullMetaSearch": False,
"includeNullMetadata": True,
"instant": False,
"range": True,
"useBackend": False,
}
HISTOGRAM_BAR_CHART_TARGET_TEMPLATE = {
"exemplar": True,
"format": "heatmap",
"fullMetaSearch": False,
"includeNullMetadata": True,
"instant": True,
"range": False,
"useBackend": False,
}
@DeveloperAPI
class TargetTemplate(Enum):
GRAPH = GRAPH_TARGET_TEMPLATE
HEATMAP = HEATMAP_TARGET_TEMPLATE
HISTOGRAM_BAR_CHART = HISTOGRAM_BAR_CHART_TARGET_TEMPLATE
@DeveloperAPI
@dataclass
class Target:
"""Defines a Grafana target (time-series query) within a panel.
A panel will have one or more targets. By default, all targets are rendered as
stacked area charts, with the exception of legend="MAX", which is rendered as
a blue dotted line. Any legend="FINISHED|FAILED|DEAD|REMOVED" series will also be
rendered hidden by default.
Attributes:
expr: The prometheus query to evaluate.
legend: The legend string to format for each time-series.
"""
expr: str
legend: str
template: Optional[TargetTemplate] = TargetTemplate.GRAPH
HEATMAP_TEMPLATE = {
"datasource": r"${datasource}",
"description": "<Description>",
"fieldConfig": {"defaults": {}, "overrides": []},
"id": 12,
"options": {
"calculate": False,
"cellGap": 1,
"cellValues": {"unit": "none"},
"color": {
"exponent": 0.5,
"fill": "dark-orange",
"min": 0,
"mode": "scheme",
"reverse": False,
"scale": "exponential",
"scheme": "Spectral",
"steps": 64,
},
"exemplars": {"color": "rgba(255,0,255,0.7)"},
"filterValues": {"le": 1e-9},
"legend": {"show": True},
"rowsFrame": {"layout": "auto", "value": "Value"},
"tooltip": {"mode": "single", "showColorScale": False, "yHistogram": True},
"yAxis": {"axisPlacement": "left", "reverse": False, "unit": "none"},
},
"pluginVersion": "11.2.0",
"targets": [],
"title": "<Title>",
"type": "heatmap",
"yaxes": [
{
"$$hashKey": "object:628",
"format": "units",
"label": "",
"logBase": 1,
"max": None,
"min": "0",
"show": True,
},
{
"$$hashKey": "object:629",
"format": "short",
"label": None,
"logBase": 1,
"max": None,
"min": None,
"show": True,
},
],
}
GRAPH_PANEL_TEMPLATE = {
"aliasColors": {},
"bars": False,
"dashLength": 10,
"dashes": False,
"datasource": r"${datasource}",
"description": "<Description>",
"fieldConfig": {"defaults": {}, "overrides": []},
# Setting height and width is important here to ensure the default panel has some size to it.
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 0},
"fill": 10,
"fillGradient": 0,
"hiddenSeries": False,
"id": 26,
"legend": {
"alignAsTable": True,
"avg": True,
"current": True,
"hideEmpty": False,
"hideZero": True,
"max": True,
"min": False,
"rightSide": False,
"show": True,
"sort": "current",
"sortDesc": True,
"total": False,
"values": True,
},
"lines": True,
"linewidth": 1,
"nullPointMode": None,
"options": {"alertThreshold": True},
"percentage": False,
"pluginVersion": "7.5.17",
"pointradius": 2,
"points": False,
"renderer": "flot",
# These series overrides are necessary to make the "MAX" and "MAX + PENDING" dotted lines
# instead of stacked filled areas.
"seriesOverrides": [
{
"$$hashKey": "object:2987",
"alias": "MAX",
"dashes": True,
"color": "#1F60C4",
"fill": 0,
"stack": False,
},
{
"$$hashKey": "object:78",
"alias": "/FINISHED|FAILED|DEAD|REMOVED|Failed Nodes:/",
"hiddenSeries": True,
},
{
"$$hashKey": "object:2987",
"alias": "MAX + PENDING",
"dashes": True,
"color": "#777777",
"fill": 0,
"stack": False,
},
{
"alias": "/Container/",
"hiddenSeries": True,
},
{
"alias": "Container MAX",
"dashes": True,
"color": "#73BF69",
"fill": 0,
"stack": False,
"hiddenSeries": True,
},
],
"spaceLength": 10,
"stack": True,
"steppedLine": False,
"targets": [],
"thresholds": [],
"timeFrom": None,
"timeRegions": [],
"timeShift": None,
"title": "<Title>",
"tooltip": {"shared": True, "sort": 0, "value_type": "individual"},
"type": "graph",
"xaxis": {
"buckets": None,
"mode": "time",
"name": None,
"show": True,
"values": [],
},
"yaxes": [
{
"$$hashKey": "object:628",
"format": "units",
"label": "",
"logBase": 1,
"max": None,
"min": "0",
"show": True,
},
{
"$$hashKey": "object:629",
"format": "short",
"label": None,
"logBase": 1,
"max": None,
"min": None,
"show": True,
},
],
"yaxis": {"align": False, "alignLevel": None},
}
STAT_PANEL_TEMPLATE = {
"datasource": r"${datasource}",
"fieldConfig": {
"defaults": {
"color": {"mode": "thresholds"},
"mappings": [],
"min": 0,
"thresholds": {
"mode": "percentage",
"steps": [
{"color": "super-light-yellow", "value": None},
{"color": "super-light-green", "value": 50},
{"color": "green", "value": 100},
],
},
"unit": "short",
},
"overrides": [],
},
"id": 78,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": False},
"text": {},
"textMode": "auto",
},
"pluginVersion": "7.5.17",
"targets": [],
"timeFrom": None,
"timeShift": None,
"title": "<Title>",
"type": "stat",
"yaxes": [
{
"$$hashKey": "object:628",
"format": "Tokens",
"label": "",
"logBase": 1,
"max": None,
"min": "0",
"show": True,
},
{
"$$hashKey": "object:629",
"format": "short",
"label": None,
"logBase": 1,
"max": None,
"min": None,
"show": True,
},
],
}
GAUGE_PANEL_TEMPLATE = {
"datasource": r"${datasource}",
"fieldConfig": {
"defaults": {
"color": {"mode": "continuous-YlBl"},
"mappings": [],
"thresholds": {
"mode": "percentage",
"steps": [{"color": "rgb(230, 230, 230)", "value": None}],
},
"unit": "short",
},
"overrides": [],
},
"id": 10,
"options": {
"reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": False},
"showThresholdLabels": False,
"showThresholdMarkers": False,
"text": {"titleSize": 12},
},
"pluginVersion": "7.5.17",
"targets": [],
"title": "<Title>",
"type": "gauge",
"yaxes": [
{
"$$hashKey": "object:628",
"format": "Tokens",
"label": "",
"logBase": 1,
"max": None,
"min": "0",
"show": True,
},
{
"$$hashKey": "object:629",
"format": "short",
"label": None,
"logBase": 1,
"max": None,
"min": None,
"show": True,
},
],
}
PIE_CHART_TEMPLATE = {
"datasource": r"${datasource}",
"description": "<Description>",
"fieldConfig": {"defaults": {}, "overrides": []},
"id": 26,
"options": {
"displayLabels": [],
"legend": {
"displayMode": "table",
"placement": "right",
"values": ["percent", "value"],
},
"pieType": "pie",
"reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": False},
"text": {},
},
"pluginVersion": "7.5.17",
"targets": [],
"timeFrom": None,
"timeShift": None,
"title": "<Title>",
"type": "piechart",
"yaxes": [
{
"$$hashKey": "object:628",
"format": "units",
"label": "",
"logBase": 1,
"max": None,
"min": "0",
"show": True,
},
{
"$$hashKey": "object:629",
"format": "short",
"label": None,
"logBase": 1,
"max": None,
"min": None,
"show": True,
},
],
}
BAR_CHART_PANEL_TEMPLATE = {
"aliasColors": {},
"dashLength": 10,
"dashes": False,
"datasource": r"${datasource}",
"description": "<Description>",
"fieldConfig": {"defaults": {}, "overrides": []},
# Setting height and width is important here to ensure the default panel has some size to it.
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 0},
"hiddenSeries": False,
"id": 26,
"legend": {
"alignAsTable": True,
"avg": False,
"current": True,
"hideEmpty": False,
"hideZero": True,
"max": False,
"min": False,
"rightSide": False,
"show": False,
"sort": "current",
"sortDesc": True,
"total": False,
"values": True,
},
"lines": False,
"linewidth": 1,
"bars": True,
"nullPointMode": None,
"options": {
"alertThreshold": True,
"legend": {
"showLegend": False,
"displayMode": "table",
"placement": "bottom",
},
},
"percentage": False,
"pluginVersion": "7.5.17",
"pointradius": 2,
"points": False,
"renderer": "flot",
"spaceLength": 10,
"stack": True,
"steppedLine": False,
"targets": [],
"thresholds": [],
"timeFrom": None,
"timeRegions": [],
"timeShift": None,
"title": "<Title>",
"tooltip": {"shared": True, "sort": 0, "value_type": "individual"},
"type": "graph",
"xaxis": {
"buckets": None,
"mode": "series",
"name": None,
"show": True,
"values": [
"total",
],
},
"yaxes": [
{
"$$hashKey": "object:628",
"format": "units",
"label": "",
"logBase": 1,
"max": None,
"min": "0",
"show": True,
},
{
"$$hashKey": "object:629",
"format": "short",
"label": None,
"logBase": 1,
"max": None,
"min": None,
"show": True,
},
],
"yaxis": {"align": False, "alignLevel": None},
}
TABLE_PANEL_TEMPLATE = {
"datasource": r"${datasource}",
"description": "<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": "<Title>",
"type": "table",
"transformations": [{"id": "organize", "options": {}}],
}
@DeveloperAPI
class PanelTemplate(Enum):
GRAPH = GRAPH_PANEL_TEMPLATE
HEATMAP = HEATMAP_TEMPLATE
PIE_CHART = PIE_CHART_TEMPLATE
STAT = STAT_PANEL_TEMPLATE
GAUGE = GAUGE_PANEL_TEMPLATE
BAR_CHART = BAR_CHART_PANEL_TEMPLATE
TABLE = TABLE_PANEL_TEMPLATE
@DeveloperAPI
@dataclass
class Panel:
"""Defines a Grafana panel (graph) for the Ray dashboard page.
A panel contains one or more targets (time-series queries).
Attributes:
title: Short name of the graph. Note: please keep this in sync with the title
definitions in Metrics.tsx.
description: Long form description of the graph.
id: Integer id used to reference the graph from Metrics.tsx.
unit: The unit to display on the y-axis of the graph.
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
description: str
id: int
unit: str
targets: List[Target]
fill: int = 10
stack: bool = True
linewidth: int = 1
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
@dataclass
class Row:
"""Defines a Grafana row that can contain multiple panels.
Attributes:
title: The title of the row
panels: List of panels contained in this row
collapsed: Whether the row should be collapsed by default
"""
title: str
id: int
panels: List[Panel]
collapsed: bool = False
@DeveloperAPI
@dataclass
class DashboardConfig:
# This dashboard name is an internal key used to determine which env vars
# to check for customization
name: str
# The uid of the dashboard json if not overridden by a user
default_uid: str
# The global filters applied to all graphs in this dashboard. Users can
# add additional global_filters on top of this.
standard_global_filters: List[str]
base_json_file_name: str
# Panels can be specified in `panels`, or nested within `rows`.
# If both are specified, panels will be rendered before rows.
panels: List[Panel] = field(default_factory=list)
rows: List[Row] = field(default_factory=list)
def __post_init__(self):
if not self.panels and not self.rows:
raise ValueError("At least one of panels or rows must be specified")
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,183 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 1,
"iteration": 1667344411089,
"links": [],
"panels": [],
"refresh": false,
"schemaVersion": 27,
"style": "dark",
"tags": [],
"templating": {
"list": [
{
"current": {
"selected": false
},
"description": "Filter queries of a specific Prometheus type.",
"hide": 2,
"includeAll": false,
"multi": false,
"name": "datasource",
"options": [],
"query": "prometheus",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"type": "datasource"
},
{
"allValue": ".+",
"current": {
"selected": false
},
"datasource": "${datasource}",
"definition": "query_result(count by (SessionName)(last_over_time(ray_data_output_bytes{{{global_filters}}}[$__range])))",
"description": "Filter queries to specific ray sessions.",
"error": null,
"hide": 0,
"includeAll": true,
"label": null,
"multi": false,
"name": "SessionName",
"options": [],
"query": {
"query": "query_result(count by (SessionName)(last_over_time(ray_data_output_bytes{{{global_filters}}}[$__range])))",
"refId": "StandardVariableQuery"
},
"refresh": 2,
"regex": "{SessionName=\"(?<value>.*)\".*",
"skipUrlSync": false,
"sort": 2,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"allValue": ".+",
"current": {
"selected": true,
"text": [
"All"
],
"value": [
"$__all"
]
},
"datasource": "${datasource}",
"definition": "query_result(count by (dataset)(last_over_time(ray_data_output_bytes{{SessionName=~\"$SessionName\",{global_filters}}}[$__range])))",
"description": null,
"error": null,
"hide": 0,
"includeAll": true,
"label": null,
"multi": true,
"name": "DatasetID",
"options": [],
"query": {
"query": "query_result(count by (dataset)(last_over_time(ray_data_output_bytes{{SessionName=~\"$SessionName\",{global_filters}}}[$__range])))",
"refId": "Prometheus-Dataset-Variable-Query"
},
"refresh": 2,
"regex": "{dataset=\"(?<value>.*)\".*",
"skipUrlSync": false,
"sort": 0,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"allValue": ".+",
"current": {
"selected": true,
"text": [
"All"
],
"value": [
"$__all"
]
},
"datasource": "${datasource}",
"definition": "query_result(count by (operator)(last_over_time(ray_data_output_bytes{{SessionName=~\"$SessionName\",{global_filters}}}[$__range])))",
"description": null,
"error": null,
"hide": 0,
"includeAll": true,
"label": null,
"multi": true,
"name": "Operator",
"options": [],
"query": {
"query": "query_result(count by (operator)(last_over_time(ray_data_output_bytes{{SessionName=~\"$SessionName\",{global_filters}}}[$__range])))",
"refId": "Prometheus-Dataset-Variable-Query"
},
"refresh": 2,
"regex": "{operator=\"(?<value>.*)\".*",
"skipUrlSync": false,
"sort": 0,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"allValue": ".*",
"current": {
"selected": false
},
"datasource": "${datasource}",
"definition": "label_values(ray_node_network_receive_speed{{{global_filters}}}, ray_io_cluster)",
"description": "Filter queries to specific Ray clusters for KubeRay. When ingesting metrics across multiple ray clusters, the ray_io_cluster label should be set per cluster. For KubeRay users, this is done automatically with Prometheus PodMonitor.",
"error": null,
"hide": 0,
"includeAll": true,
"label": null,
"multi": false,
"name": "Cluster",
"options": [],
"query": {
"query": "label_values(ray_node_network_receive_speed{{{global_filters}}}, ray_io_cluster)",
"refId": "StandardVariableQuery"
},
"refresh": 2,
"regex": "",
"skipUrlSync": false,
"sort": 2,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
}
]
},
"rayMeta": ["excludesSystemRoutes"],
"time": {
"from": "now-30m",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "Data Dashboard",
"uid": "rayDataDashboard",
"version": 1
}
@@ -0,0 +1,325 @@
# ruff: noqa: E501
"""Ray Data LLM Dashboard panels for vLLM metrics visualization.
This dashboard provides visibility into vLLM engine metrics when using Ray Data LLM,
including latency metrics (TTFT, TPOT, E2E), throughput, cache utilization,
prefix cache hit rate, and scheduler state.
"""
from ray.dashboard.modules.metrics.dashboards.common import (
DashboardConfig,
GridPos,
Panel,
Target,
)
DATA_LLM_GRAFANA_PANELS = [
Panel(
id=1,
title="vLLM: Token Throughput",
description="Number of tokens processed per second",
unit="tokens/s",
targets=[
Target(
expr='sum by (model_name, WorkerId) (rate(ray_vllm_request_prompt_tokens_sum{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval]))',
legend="Prompt Tokens/Sec - {{model_name}} - {{WorkerId}}",
),
Target(
expr='sum by (model_name, WorkerId) (rate(ray_vllm_generation_tokens_total{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval]))',
legend="Generation Tokens/Sec - {{model_name}} - {{WorkerId}}",
),
],
fill=1,
linewidth=2,
stack=False,
grid_pos=GridPos(0, 0, 12, 8),
),
Panel(
id=2,
title="vLLM: Time Per Output Token Latency",
description="P50, P90, P95, P99, and Mean TPOT latency",
unit="s",
targets=[
Target(
expr='histogram_quantile(0.99, sum by(le, model_name, WorkerId) (rate(ray_vllm_request_time_per_output_token_seconds_bucket{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])))',
legend="P99 - {{model_name}} - {{WorkerId}}",
),
Target(
expr='histogram_quantile(0.95, sum by(le, model_name, WorkerId) (rate(ray_vllm_request_time_per_output_token_seconds_bucket{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])))',
legend="P95 - {{model_name}} - {{WorkerId}}",
),
Target(
expr='histogram_quantile(0.9, sum by(le, model_name, WorkerId) (rate(ray_vllm_request_time_per_output_token_seconds_bucket{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])))',
legend="P90 - {{model_name}} - {{WorkerId}}",
),
Target(
expr='histogram_quantile(0.5, sum by(le, model_name, WorkerId) (rate(ray_vllm_request_time_per_output_token_seconds_bucket{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])))',
legend="P50 - {{model_name}} - {{WorkerId}}",
),
Target(
expr='(sum by(model_name, WorkerId) (rate(ray_vllm_request_time_per_output_token_seconds_sum{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval]))\n/\nsum by(model_name, WorkerId) (rate(ray_vllm_request_time_per_output_token_seconds_count{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])))',
legend="Mean - {{model_name}} - {{WorkerId}}",
),
],
fill=1,
linewidth=2,
stack=False,
grid_pos=GridPos(12, 0, 12, 8),
),
Panel(
id=3,
title="vLLM: Cache Utilization",
description="Percentage of used KV cache blocks by vLLM.",
unit="percentunit",
targets=[
Target(
expr='sum by (WorkerId) (ray_vllm_kv_cache_usage_perc{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}})',
legend="GPU Cache Usage - {{WorkerId}}",
),
],
fill=1,
linewidth=2,
stack=False,
grid_pos=GridPos(0, 8, 12, 8),
),
Panel(
id=4,
title="vLLM: Prefix Cache Hit Rate",
description="Percentage of prefix cache hits. Higher is better for repeated prefixes.",
unit="percent",
targets=[
Target(
expr='max(100 * (sum by (WorkerId) (rate(ray_vllm_prefix_cache_hits_total{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])) / sum by (WorkerId) (rate(ray_vllm_prefix_cache_queries_total{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval]))))',
legend="Max Hit Rate",
),
Target(
expr='min(100 * (sum by (WorkerId) (rate(ray_vllm_prefix_cache_hits_total{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])) / sum by (WorkerId) (rate(ray_vllm_prefix_cache_queries_total{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval]))))',
legend="Min Hit Rate",
),
Target(
expr='100 * (sum by (WorkerId) (rate(ray_vllm_prefix_cache_hits_total{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])) / sum by (WorkerId) (rate(ray_vllm_prefix_cache_queries_total{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])))',
legend="Hit Rate: worker {{WorkerId}}",
),
],
fill=1,
linewidth=1,
stack=False,
grid_pos=GridPos(12, 8, 12, 8),
),
Panel(
id=5,
title="vLLM: Time To First Token Latency",
description="P50, P90, P95, P99, and Mean TTFT latency",
unit="s",
targets=[
Target(
expr='(sum by(model_name, WorkerId) (rate(ray_vllm_time_to_first_token_seconds_sum{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval]))\n/\nsum by(model_name, WorkerId) (rate(ray_vllm_time_to_first_token_seconds_count{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])))',
legend="Average - {{model_name}} - {{WorkerId}}",
),
Target(
expr='histogram_quantile(0.5, sum by(le, model_name, WorkerId)(rate(ray_vllm_time_to_first_token_seconds_bucket{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])))',
legend="P50 - {{model_name}} - {{WorkerId}}",
),
Target(
expr='histogram_quantile(0.9, sum by(le, model_name, WorkerId)(rate(ray_vllm_time_to_first_token_seconds_bucket{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])))',
legend="P90 - {{model_name}} - {{WorkerId}}",
),
Target(
expr='histogram_quantile(0.95, sum by(le, model_name, WorkerId) (rate(ray_vllm_time_to_first_token_seconds_bucket{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])))',
legend="P95 - {{model_name}} - {{WorkerId}}",
),
Target(
expr='histogram_quantile(0.99, sum by(le, model_name, WorkerId)(rate(ray_vllm_time_to_first_token_seconds_bucket{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])))',
legend="P99 - {{model_name}} - {{WorkerId}}",
),
],
fill=1,
linewidth=2,
stack=False,
grid_pos=GridPos(0, 16, 12, 8),
),
Panel(
id=6,
title="vLLM: E2E Request Latency",
description="End-to-end request latency from arrival to completion.",
unit="s",
targets=[
Target(
expr='sum by(model_name, WorkerId) (rate(ray_vllm_e2e_request_latency_seconds_sum{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval]))\n/\nsum by(model_name, WorkerId) (rate(ray_vllm_e2e_request_latency_seconds_count{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval]))',
legend="Average - {{model_name}} - {{WorkerId}}",
),
Target(
expr='histogram_quantile(0.5, sum by(le, model_name, WorkerId) (rate(ray_vllm_e2e_request_latency_seconds_bucket{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])))',
legend="P50 - {{model_name}} - {{WorkerId}}",
),
Target(
expr='histogram_quantile(0.9, sum by(le, model_name, WorkerId) (rate(ray_vllm_e2e_request_latency_seconds_bucket{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])))',
legend="P90 - {{model_name}} - {{WorkerId}}",
),
Target(
expr='histogram_quantile(0.95, sum by(le, model_name, WorkerId) (rate(ray_vllm_e2e_request_latency_seconds_bucket{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])))',
legend="P95 - {{model_name}} - {{WorkerId}}",
),
Target(
expr='histogram_quantile(0.99, sum by(le, model_name, WorkerId) (rate(ray_vllm_e2e_request_latency_seconds_bucket{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])))',
legend="P99 - {{model_name}} - {{WorkerId}}",
),
],
fill=1,
linewidth=2,
stack=False,
grid_pos=GridPos(12, 16, 12, 8),
),
Panel(
id=7,
title="vLLM: Scheduler State",
description="Number of requests in RUNNING, WAITING, and SWAPPED state",
unit="Requests",
targets=[
Target(
expr='ray_vllm_num_requests_running{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}',
legend="Num Running - {{model_name}} - {{WorkerId}}",
),
Target(
expr='ray_vllm_num_requests_swapped{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}',
legend="Num Swapped - {{model_name}} - {{WorkerId}}",
),
Target(
expr='ray_vllm_num_requests_waiting{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}',
legend="Num Waiting - {{model_name}} - {{WorkerId}}",
),
],
fill=1,
linewidth=2,
stack=False,
grid_pos=GridPos(0, 24, 12, 8),
),
Panel(
id=8,
title="vLLM: Queue Time",
description="P50, P90, P95, P99, and Mean time requests spend waiting in the queue.",
unit="s",
targets=[
Target(
expr='(sum by(model_name, WorkerId) (rate(ray_vllm_request_queue_time_seconds_sum{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval]))\n/\nsum by(model_name, WorkerId) (rate(ray_vllm_request_queue_time_seconds_count{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])))',
legend="Mean - {{model_name}} - {{WorkerId}}",
),
Target(
expr='histogram_quantile(0.5, sum by(le, model_name, WorkerId) (rate(ray_vllm_request_queue_time_seconds_bucket{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])))',
legend="P50 - {{model_name}} - {{WorkerId}}",
),
Target(
expr='histogram_quantile(0.9, sum by(le, model_name, WorkerId) (rate(ray_vllm_request_queue_time_seconds_bucket{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])))',
legend="P90 - {{model_name}} - {{WorkerId}}",
),
Target(
expr='histogram_quantile(0.95, sum by(le, model_name, WorkerId) (rate(ray_vllm_request_queue_time_seconds_bucket{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])))',
legend="P95 - {{model_name}} - {{WorkerId}}",
),
Target(
expr='histogram_quantile(0.99, sum by(le, model_name, WorkerId) (rate(ray_vllm_request_queue_time_seconds_bucket{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])))',
legend="P99 - {{model_name}} - {{WorkerId}}",
),
],
fill=1,
linewidth=2,
stack=False,
grid_pos=GridPos(12, 24, 12, 8),
),
Panel(
id=9,
title="vLLM: Prompt Length",
description="Distribution of prompt token lengths.",
unit="short",
targets=[
Target(
expr='histogram_quantile(0.5, sum by(le, model_name, WorkerId) (rate(ray_vllm_request_prompt_tokens_bucket{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])))',
legend="P50-{{model_name}}-{{WorkerId}}",
),
Target(
expr='histogram_quantile(0.90, sum by(le, model_name, WorkerId) (rate(ray_vllm_request_prompt_tokens_bucket{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])))',
legend="P90-{{model_name}}-{{WorkerId}}",
),
Target(
expr='(sum by(model_name, WorkerId) (rate(ray_vllm_request_prompt_tokens_sum{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval]))\n/\nsum by(model_name, WorkerId) (rate(ray_vllm_request_prompt_tokens_count{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])))',
legend="Average-{{model_name}}-{{WorkerId}}",
),
],
fill=1,
linewidth=1,
stack=False,
grid_pos=GridPos(0, 32, 12, 8),
),
Panel(
id=10,
title="vLLM: Generation Length",
description="Distribution of generated token lengths.",
unit="short",
targets=[
Target(
expr='histogram_quantile(0.50, sum by(le, model_name, WorkerId) (rate(ray_vllm_request_generation_tokens_bucket{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])))',
legend="P50-{{model_name}}-{{WorkerId}}",
),
Target(
expr='histogram_quantile(0.90, sum by(le, model_name, WorkerId) (rate(ray_vllm_request_generation_tokens_bucket{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])))',
legend="P90-{{model_name}}-{{WorkerId}}",
),
Target(
expr=(
'(sum by(model_name, WorkerId) (rate(ray_vllm_request_generation_tokens_sum{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])))'
"\n/\n"
'(sum by(model_name, WorkerId) (rate(ray_vllm_request_generation_tokens_count{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval])))'
),
legend="Average-{{model_name}}-{{WorkerId}}",
),
],
fill=1,
linewidth=1,
stack=False,
grid_pos=GridPos(12, 32, 12, 8),
),
Panel(
id=11,
title="vLLM: Finish Reason",
description="Number of finished requests by their finish reason: EOS token or max length reached.",
unit="Requests",
targets=[
Target(
expr='sum by(finished_reason, model_name, WorkerId) (increase(ray_vllm_request_success_total{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval]))',
legend="{{finished_reason}} - {{model_name}} - {{WorkerId}}",
),
],
fill=1,
linewidth=2,
stack=False,
grid_pos=GridPos(0, 40, 12, 8),
),
Panel(
id=12,
title="vLLM: Prefill and Decode Time",
description="Time spent in prefill vs decode phases.",
unit="s",
targets=[
Target(
expr='sum by(model_name, WorkerId) (rate(ray_vllm_request_prefill_time_seconds_sum{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval]))',
legend="Prefill - {{model_name}} - {{WorkerId}}",
),
Target(
expr='sum by(model_name, WorkerId) (rate(ray_vllm_request_decode_time_seconds_sum{{model_name=~"$vllm_model_name", WorkerId=~"$workerid", ReplicaId=""}}[$interval]))',
legend="Decode - {{model_name}} - {{WorkerId}}",
),
],
fill=1,
linewidth=2,
stack=False,
grid_pos=GridPos(12, 40, 12, 8),
),
]
data_llm_dashboard_config = DashboardConfig(
name="DATA_LLM",
default_uid="rayDataLlmDashboard",
panels=DATA_LLM_GRAFANA_PANELS,
standard_global_filters=[],
base_json_file_name="data_llm_grafana_dashboard_base.json",
)
@@ -0,0 +1,144 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 1,
"iteration": 1667344411089,
"links": [],
"panels": [],
"refresh": false,
"schemaVersion": 27,
"style": "dark",
"tags": [],
"templating": {
"list": [
{
"current": {
"selected": false
},
"description": "Filter queries of a specific Prometheus type.",
"hide": 2,
"includeAll": false,
"multi": false,
"name": "datasource",
"options": [],
"query": "prometheus",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"type": "datasource"
},
{
"name": "vllm_model_name",
"label": "vLLM Model Name",
"type": "query",
"hide": 0,
"datasource": "${datasource}",
"definition": "label_values(ray_vllm_request_prompt_tokens_sum{{{global_filters}}}, model_name)",
"query": {
"query": "label_values(ray_vllm_request_prompt_tokens_sum{{{global_filters}}}, model_name)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"includeAll": true,
"multi": false,
"allValue": ".*",
"current": {
"selected": true,
"text": [
"All"
],
"value": [
"$__all"
]
}
},
{
"name": "workerid",
"label": "Worker ID",
"type": "query",
"hide": 0,
"datasource": "${datasource}",
"definition": "label_values(ray_vllm_request_prompt_tokens_sum{{{global_filters}}}, WorkerId)",
"query": {
"query": "label_values(ray_vllm_request_prompt_tokens_sum{{{global_filters}}}, WorkerId)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"includeAll": true,
"multi": false,
"allValue": ".*",
"current": {
"selected": true,
"text": [
"All"
],
"value": [
"$__all"
]
}
},
{
"name": "interval",
"label": "Interval",
"type": "custom",
"hide": 0,
"includeAll": false,
"multi": false,
"options": [
{
"selected": true,
"text": "30s",
"value": "30s"
},
{
"selected": false,
"text": "1m",
"value": "1m"
},
{
"selected": false,
"text": "5m",
"value": "5m"
},
{
"selected": false,
"text": "10m",
"value": "10m"
},
{
"selected": false,
"text": "15m",
"value": "15m"
}
],
"current": {
"selected": true,
"text": "5m",
"value": "5m"
}
}
]
},
"time": {
"from": "now-30m",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "Data LLM Dashboard",
"uid": "rayDataLlmDashboard",
"version": 1
}
@@ -0,0 +1,797 @@
# ruff: noqa: E501
from ray.dashboard.modules.metrics.dashboards.common import (
DashboardConfig,
Panel,
Row,
Target,
)
"""
Queries for autoscaler resources.
"""
# Note: MAX & USED resources are reported from raylet to provide the most up to date information.
# But MAX + PENDING data is coming from the autoscaler. That said, MAX + PENDING can be
# more outdated. it is harmless because the actual MAX will catch up with MAX + PENDING
# eventually.
MAX_CPUS = 'sum(autoscaler_cluster_resources{{resource="CPU",{global_filters}}})'
PENDING_CPUS = 'sum(autoscaler_pending_resources{{resource="CPU",{global_filters}}})'
MAX_GPUS = 'sum(autoscaler_cluster_resources{{resource="GPU",{global_filters}}})'
PENDING_GPUS = 'sum(autoscaler_pending_resources{{resource="GPU",{global_filters}}})'
MAX_MEMORY = 'sum(autoscaler_cluster_resources{{resource="memory",{global_filters}}})'
PENDING_MEMORY = (
'sum(autoscaler_pending_resources{{resource="memory",{global_filters}}})'
)
def max_plus_pending(max_resource, pending_resource):
return f"({max_resource} or vector(0)) + ({pending_resource} or vector(0))"
MAX_PLUS_PENDING_CPUS = max_plus_pending(MAX_CPUS, PENDING_CPUS)
MAX_PLUS_PENDING_GPUS = max_plus_pending(MAX_GPUS, PENDING_GPUS)
MAX_PLUS_PENDING_MEMORY = max_plus_pending(MAX_MEMORY, PENDING_MEMORY)
MAX_PERCENTAGE_EXPRESSION = (
"100" # To help draw the max limit line on percentage panels
)
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# IMPORTANT: Please keep this in sync with Metrics.tsx and ray-metrics.rst
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
OVERVIEW_AND_HEALTH_PANELS = [
Panel(
id=24,
title="Node Count",
description='Note: not impacted by "Instance" variable.\n\nA total number of active failed, and pending nodes from the cluster. \n\nACTIVE: A node is alive and available.\n\nFAILED: A node is dead and not available. The node is considered dead when the raylet process on the node is terminated. The node will get into the failed state if it cannot be provided (e.g., there\'s no available node from the cloud provider) or failed to setup (e.g., setup_commands have errors). \n\nPending: A node is being started by the Ray cluster launcher. The node is unavailable now because it is being provisioned and initialized.',
unit="nodes",
targets=[
Target(
expr="sum(autoscaler_active_nodes{{{global_filters}}}) by (NodeType)",
legend="Active Nodes: {{NodeType}}",
),
Target(
expr="sum(autoscaler_recently_failed_nodes{{{global_filters}}}) by (NodeType)",
legend="Failed Nodes: {{NodeType}}",
),
Target(
expr="sum(autoscaler_pending_nodes{{{global_filters}}}) by (NodeType)",
legend="Pending Nodes: {{NodeType}}",
),
],
),
Panel(
id=41,
title="Cluster Utilization",
description="Aggregated utilization of all physical resources (CPU, GPU, memory, disk, or etc.) across the cluster.",
unit="%",
targets=[
# CPU
Target(
expr='avg(ray_node_cpu_utilization{{instance=~"$Instance",{global_filters}}})',
legend="CPU (physical)",
),
# GPU
Target(
expr='sum(ray_node_gpus_utilization{{instance=~"$Instance",{global_filters}}}) / on() (sum(ray_node_gpus_available{{instance=~"$Instance",{global_filters}}}) or vector(0))',
legend="GPU (physical)",
),
# Memory
Target(
expr='sum(ray_node_mem_used_host{{instance=~"$Instance",{global_filters}}}) / on() (sum(ray_node_mem_total_host{{instance=~"$Instance",{global_filters}}})) * 100',
legend="Memory (RAM)",
),
# GRAM
Target(
expr='sum(ray_node_gram_used{{instance=~"$Instance",{global_filters}}}) / on() (sum(ray_node_gram_available{{instance=~"$Instance",{global_filters}}}) + sum(ray_node_gram_used{{instance=~"$Instance",{global_filters}}})) * 100',
legend="GRAM",
),
# Object Store
Target(
expr='sum(ray_object_store_memory{{instance=~"$Instance",{global_filters}}}) / on() sum(ray_resources{{Name="object_store_memory",instance=~"$Instance",{global_filters}}}) * 100',
legend="Object Store Memory",
),
# Disk
Target(
expr='sum(ray_node_disk_usage{{instance=~"$Instance",{global_filters}}}) / on() (sum(ray_node_disk_free{{instance=~"$Instance",{global_filters}}}) + sum(ray_node_disk_usage{{instance=~"$Instance",{global_filters}}})) * 100',
legend="Disk",
),
],
fill=0,
stack=False,
),
Panel(
id=44,
title="Ray OOM Kills (Tasks and Actors)",
description="The number of tasks and actors killed by the Ray Out of Memory killer due to high memory pressure. Metrics are broken down by IP and the name. https://docs.ray.io/en/master/ray-core/scheduling/ray-oom-prevention.html. Note: The RayNodeType filter does not work on this graph.",
unit="failures",
targets=[
Target(
expr='sum(ray_memory_manager_worker_eviction_total{{instance=~"$Instance", {global_filters}}}) by (Name, instance)',
legend="OOM Killed: {{Name}}, {{instance}}",
),
],
),
Panel(
id=65,
title="Unexpected System Level Worker Failures",
description="The number of workers (potentially tasks or actors) that disconnected from the raylet unexpectedly. "
"This typically indicates the worker process unexpectedly failed due to "
"a Ray system error or a kernel kill (e.g. OOM, SIGKILL, Bad exit code). "
"Note that this metric only includes OOM kills from the kernel and does not "
"include OOM kills from Ray's memory monitor. "
"If errors of this type is encountered when the node is under memory pressure, "
"The failures are likely OOM kills.",
unit="failures",
targets=[
Target(
expr='sum(ray_node_manager_unexpected_worker_failure_total{{instance=~"$Instance", {global_filters}}}) by (Type, Name, instance)',
legend="Unexpected worker failure: {{Name}}, {{Type}}, {{instance}}",
),
],
),
]
RAY_TASKS_ACTORS_PLACEMENT_GROUPS_PANELS = [
Panel(
id=26,
title="All Tasks by State",
description="Current count of tasks, grouped by scheduler state (e.g., pending, running, finished).\n\nState: the task state, as described by rpc::TaskStatus proto in common.proto. Task resubmissions due to failures or object reconstruction are shown with (retry) in the label.",
unit="tasks",
targets=[
Target(
expr='sum(max_over_time(ray_tasks{{IsRetry="0",State=~"FINISHED|FAILED",instance=~"$Instance",{global_filters}}}[14d])) by (State) or clamp_min(sum(ray_tasks{{IsRetry="0",State!~"FINISHED|FAILED",instance=~"$Instance",{global_filters}}}) by (State), 0)',
legend="{{State}}",
),
Target(
expr='sum(max_over_time(ray_tasks{{IsRetry!="0",State=~"FINISHED|FAILED",instance=~"$Instance",{global_filters}}}[14d])) by (State) or clamp_min(sum(ray_tasks{{IsRetry!="0",State!~"FINISHED|FAILED",instance=~"$Instance",{global_filters}}}) by (State), 0)',
legend="{{State}} (retry)",
),
],
fill=0,
stack=False,
),
Panel(
id=35,
title="Active Tasks by Name",
description="Current count of active tasks (i.e. pending or running; not finished), grouped by task name. Task resubmissions due to failures or object reconstruction are shown with (retry) in the label.",
unit="tasks",
targets=[
Target(
expr='clamp_min(sum(ray_tasks{{IsRetry="0",State!~"FINISHED|FAILED",instance=~"$Instance",{global_filters}}}) by (Name), 0)',
legend="{{Name}}",
),
Target(
expr='clamp_min(sum(ray_tasks{{IsRetry!="0",State!~"FINISHED|FAILED",instance=~"$Instance",{global_filters}}}) by (Name), 0)',
legend="{{Name}} (retry)",
),
],
fill=0,
stack=False,
),
Panel(
id=38,
title="Running Tasks by Name",
description="Current count of tasks that are currently executing, grouped by task name. Task resubmissions due to failures or object reconstruction are shown with (retry) in the label.",
unit="tasks",
targets=[
Target(
expr='clamp_min(sum(ray_tasks{{IsRetry="0",State=~"RUNNING*",instance=~"$Instance",{global_filters}}}) by (Name), 0)',
legend="{{Name}}",
),
Target(
expr='clamp_min(sum(ray_tasks{{IsRetry!="0",State=~"RUNNING*",instance=~"$Instance",{global_filters}}}) by (Name), 0)',
legend="{{Name}} (retry)",
),
],
fill=0,
stack=False,
),
Panel(
id=64,
title="Running Tasks by Node",
description="Current count of tasks that are currently executing, grouped by node.",
unit="tasks",
targets=[
Target(
expr='clamp_min(sum(ray_tasks{{State=~"RUNNING*",Source="executor",instance=~"$Instance",{global_filters}}}) by (instance), 0)',
legend="{{instance}}",
),
],
fill=0,
stack=False,
),
Panel(
id=33,
title="All Actors by State",
description='Note: not impacted by "Instance" variable.\n\nCurrent count of actors, grouped by lifecycle state (e.g., alive, restarting, dead/terminated).\n\nState: the actor state, as described by rpc::ActorTableData proto in gcs.proto.',
unit="actors",
targets=[
Target(
expr='sum(ray_actors{{Source="gcs",{global_filters}}}) by (State)',
legend="{{State}}",
)
],
),
Panel(
id=42,
title="Alive Actors by State",
description="Current count of alive actors (i.e. not dead/terminated), grouped by state.\n\nState: the actor state, as described by rpc::ActorTableData proto in gcs.proto.",
unit="actors",
targets=[
Target(
expr='sum(ray_actors{{Source="executor",NodeAddress=~"$Instance",{global_filters}}}) by (State)',
legend="{{State}}",
)
],
),
Panel(
id=36,
title="Alive Actors by Name",
description="Current count of alive actors, grouped by actor name.",
unit="actors",
targets=[
Target(
expr='sum(ray_actors{{State!="DEAD",Source="executor",NodeAddress=~"$Instance",{global_filters}}}) by (Name)',
legend="{{Name}}",
)
],
),
Panel(
id=40,
title="All Placement Groups by State",
description='Note: not impacted by "Instance" variable.\n\nCurrent count of placement groups, grouped by state.\n\nState: the placement group state, as described by the rpc::PlacementGroupTableData proto in gcs.proto.',
unit="placement groups",
targets=[
Target(
expr="sum(ray_placement_groups{{{global_filters}}}) by (State)",
legend="{{State}}",
)
],
),
Panel(
id=29,
title="Object Store Memory by Location",
description="Object store memory usage by location. The dotted line indicates the object store memory capacity. This metric can go over the max capacity in case of spillage to disk.\n\nLocation: where the memory was allocated, which is MMAP_SHM or MMAP_DISK to indicate memory-mapped page, SPILLED to indicate spillage to disk, and WORKER_HEAP for objects small enough to be inlined in worker memory.",
unit="bytes",
targets=[
Target(
expr='sum(ray_object_store_memory{{instance=~"$Instance",{global_filters}}}) by (Location)',
legend="{{Location}}",
),
Target(
expr='sum(ray_resources{{Name="object_store_memory",instance=~"$Instance",{global_filters}}})',
legend="MAX",
),
],
),
]
RAY_RESOURCES_PANELS = [
Panel(
id=27,
title="Logical CPUs Usage",
description="Logical CPU usage of Ray. The dotted line indicates the total number of CPUs. The logical CPU is allocated by `num_cpus` arguments from tasks and actors. PENDING means the number of CPUs that will be available when new nodes are up after the autoscaler scales up.\n\nNOTE: Ray's logical CPU is different from physical CPU usage. Ray's logical CPU is allocated by `num_cpus` arguments.",
unit="cores",
targets=[
Target(
expr='sum(ray_resources{{Name="CPU",State="USED",instance=~"$Instance",{global_filters}}}) by (instance)',
legend="CPU Usage: {{instance}}",
),
Target(
expr='sum(ray_resources{{Name="CPU",instance=~"$Instance",{global_filters}}})',
legend="MAX",
),
# If max + pending > max, we display this value.
# (A and predicate) means to return A when the predicate satisfies in PromSql.
Target(
expr=f"({MAX_PLUS_PENDING_CPUS} and {MAX_PLUS_PENDING_CPUS} > ({MAX_CPUS} or vector(0)))",
legend="MAX + PENDING",
),
],
),
Panel(
id=28,
title="Logical GPUs Usage",
description="Logical GPU usage of Ray. The dotted line indicates the total number of GPUs. The logical GPU is allocated by `num_gpus` arguments from tasks and actors. PENDING means the number of GPUs that will be available when new nodes are up after the autoscaler scales up.",
unit="GPUs",
targets=[
Target(
expr='sum(ray_resources{{Name="GPU",State="USED",instance=~"$Instance",{global_filters}}}) by (instance)',
legend="GPU Usage: {{instance}}",
),
Target(
expr='sum(ray_resources{{Name="GPU",instance=~"$Instance",{global_filters}}})',
legend="MAX",
),
# If max + pending > max, we display this value.
# (A and predicate) means to return A when the predicate satisfies in PromSql.
Target(
expr=f"({MAX_PLUS_PENDING_GPUS} and {MAX_PLUS_PENDING_GPUS} > ({MAX_GPUS} or vector(0)))",
legend="MAX + PENDING",
),
],
),
Panel(
id=61,
title="Logical Memory Usage",
description="Logical memory usage of Ray by node. The dotted line indicates the total amount of memory available. Logical memory refers to Ray's view of memory resources allocated to tasks and actors. PENDING means the amount of memory that will be available when new nodes are up after the autoscaler scales up.",
unit="bytes",
targets=[
Target(
expr='sum(ray_resources{{Name="memory",State="USED",instance=~"$Instance",{global_filters}}}) by (instance)',
legend="Memory Usage: {{instance}}",
),
Target(
expr='sum(ray_resources{{Name="memory",instance=~"$Instance",{global_filters}}})',
legend="MAX",
),
Target(
expr=f"({MAX_PLUS_PENDING_MEMORY} and {MAX_PLUS_PENDING_MEMORY} > ({MAX_MEMORY} or vector(0)))",
legend="MAX + PENDING",
),
],
),
Panel(
id=58,
title="Object Store Memory Usage",
description="Object store memory usage by instance, including memory that has been spilled to disk. The dotted line indicates the object store memory capacity. This metric can go over the max capacity in case of spillage to disk.",
unit="bytes",
targets=[
Target(
expr='sum(ray_object_store_memory{{instance=~"$Instance",{global_filters}}}) by (instance)',
legend="{{instance}}",
),
Target(
expr='sum(ray_resources{{Name="object_store_memory",instance=~"$Instance",{global_filters}}})',
legend="MAX",
),
],
),
Panel(
id=59,
title="Object Store Memory Usage %",
description="Object store memory usage % by instance, including memory that has been spilled to disk. This metric can go over 100% in case of spillage to disk.",
unit="%",
targets=[
Target(
expr='sum(ray_object_store_memory{{instance=~"$Instance",{global_filters}}}) by (instance) * 100 / sum(ray_resources{{Name="object_store_memory",instance=~"$Instance",{global_filters}}}) by (instance)',
legend="{{instance}}",
),
Target(
expr=MAX_PERCENTAGE_EXPRESSION, # To show the memory limit visually
legend="MAX",
),
],
fill=0,
stack=False,
),
Panel(
id=60,
title="Object Store Memory Spilled to Disk",
description="Object store memory that has been spilled to disk, by instance.",
unit="bytes",
targets=[
Target(
expr='sum(ray_object_store_memory{{instance=~"$Instance",Location="SPILLED",{global_filters}}}) by (instance)',
legend="{{instance}}",
),
],
fill=0,
stack=False,
),
]
NODE_HARDWARE_UTILIZATION_BY_RAY_COMPONENT_PANELS = [
Panel(
id=37,
title="Node CPU Usage by Component",
description="The physical (hardware) CPU usage across the cluster, broken down by component. This reports the summed CPU usage per Ray component. Ray components consist of system components (e.g., raylet, gcs, dashboard, or agent) and the process (that contains method names) names of running tasks/actors.",
unit="cores",
targets=[
Target(
# ray_component_cpu_percentage returns a percentage that can be > 100. It means that it uses more than 1 CPU.
expr='sum(ray_component_cpu_percentage{{instance=~"$Instance",{global_filters}}}) by (Component) / 100',
legend="{{Component}}",
),
Target(
expr='sum(ray_node_cpu_count{{instance=~"$Instance",{global_filters}}})',
legend="MAX",
),
],
),
Panel(
id=34,
title="Node Memory Usage by Component",
description="The physical (hardware) memory usage across the cluster, broken down by component. This reports the summed RSS-SHM per Ray component, which corresponds to an approximate memory usage per proc. Ray components consist of system components (e.g., raylet, gcs, dashboard, or agent) and the process (that contains method names) names of running tasks/actors.",
unit="bytes",
targets=[
Target(
expr='(sum(ray_component_rss_bytes{{instance=~"$Instance",{global_filters}}}) by (Component)) - (sum(ray_component_shared_bytes{{instance=~"$Instance",{global_filters}}}) by (Component))',
legend="{{Component}}",
),
Target(
expr='sum(ray_node_mem_shared_bytes{{instance=~"$Instance",{global_filters}}})',
legend="shared_memory",
),
Target(
expr='min(label_replace(sum(ray_node_mem_total_host{{instance=~"$Instance",{global_filters}}}), "mem_cap_source", "host", "", "") or label_replace(sum(ray_node_cgroup_mem_total{{instance=~"$Instance",{global_filters}}}), "mem_cap_source", "cgroup", "", ""))',
legend="MAX",
),
],
),
Panel(
id=45,
title="Node GPU Usage by Component",
description="The physical (hardware) GPU usage across the cluster, broken down by component. This reports the summed GPU usage per Ray component.",
unit="GPUs",
targets=[
Target(
expr='sum(ray_component_gpu_percentage{{instance=~"$Instance",{global_filters}}} / 100) by (Component)',
legend="{{Component}}",
),
],
),
Panel(
id=46,
title="Node GPU Memory Usage by Component",
description="The physical (hardware) GPU memory usage across the cluster, broken down by component. This reports the summed GPU memory usage per Ray component.",
unit="bytes",
targets=[
Target(
expr='sum(ray_component_gpu_memory_mb{{instance=~"$Instance",{global_filters}}} * 1024 * 1024) by (Component)',
legend="{{Component}}",
),
Target(
expr='(sum(ray_node_gram_available{{instance=~"$Instance",{global_filters}}}) + sum(ray_node_gram_used{{instance=~"$Instance",{global_filters}}})) * 1024 * 1024',
legend="MAX",
),
],
),
]
NODE_HARDWARE_UTILIZATION_PANELS = [
Panel(
id=2,
title="Node CPU Usage",
description="The physical (hardware) CPU usage for each node.",
unit="cores",
targets=[
Target(
expr='sum(ray_node_cpu_utilization{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}} * ray_node_cpu_count{{instance=~"$Instance", RayNodeType=~"$RayNodeType",{global_filters}}} / 100) by (instance, RayNodeType)',
legend="CPU Usage: {{instance}} ({{RayNodeType}})",
),
Target(
expr='sum(ray_node_cpu_count{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}})',
legend="MAX",
),
],
),
Panel(
id=54,
title="Node CPU Usage %",
description="The percentage of physical (hardware) CPU usage for each node.",
unit="%",
targets=[
Target(
expr='sum(ray_node_cpu_utilization{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType)',
legend="CPU Usage: {{instance}} ({{RayNodeType}})",
),
],
fill=0,
stack=False,
),
Panel(
id=4,
title="Node Memory Usage (heap + object store)",
description="The physical (hardware) memory usage for each node. The dotted line means the total amount of memory from the cluster. "
"Node memory is a sum of object store memory (shared memory) and heap memory.\n\n"
"Host targets reflect memory as reported by the host machine. "
"Container targets reflect cgroup-limited memory and are only emitted when the ray node reside within a cgroup.",
unit="bytes",
targets=[
Target(
expr='sum(ray_node_mem_used_host{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType)',
legend="Memory Used (Host): {{instance}} ({{RayNodeType}})",
),
Target(
expr='sum(ray_node_mem_total_host{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}})',
legend="MAX",
),
Target(
expr='sum(ray_node_cgroup_mem_used{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType)',
legend="Memory Used (Container): {{instance}} ({{RayNodeType}})",
),
Target(
expr='sum(ray_node_cgroup_mem_total{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}})',
legend="Container MAX",
),
],
),
Panel(
id=48,
title="Node Memory Usage % (heap + object store)",
description="The percentage of physical (hardware) memory usage for each node.\n\n"
"Host targets reflect memory as reported by the host machine. "
"Container targets reflect cgroup-limited memory and are only emitted when the ray node reside within a cgroup.",
unit="%",
targets=[
Target(
expr='sum(ray_node_mem_used_host{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType) * 100 / sum(ray_node_mem_total_host{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType)',
legend="Memory Used (Host): {{instance}} ({{RayNodeType}})",
),
Target(
expr='sum(ray_node_cgroup_mem_used{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType) * 100 / sum(ray_node_cgroup_mem_total{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType)',
legend="Memory Used (Container): {{instance}} ({{RayNodeType}})",
),
],
fill=0,
stack=False,
),
Panel(
id=6,
title="Node Disk Usage",
description="Node's physical (hardware) disk usage. The dotted line means the total amount of disk space from the cluster.\n\nNOTE: When Ray is deployed within a container, this shows the disk usage from the host machine. ",
unit="bytes",
targets=[
Target(
expr='sum(ray_node_disk_usage{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType)',
legend="Disk Used: {{instance}} ({{RayNodeType}})",
),
Target(
expr='sum(ray_node_disk_free{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) + sum(ray_node_disk_usage{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}})',
legend="MAX",
),
],
),
Panel(
id=57,
title="Node Disk Usage %",
description="Node's physical (hardware) disk usage. \n\nNOTE: When Ray is deployed within a container, this shows the disk usage from the host machine. ",
unit="%",
targets=[
Target(
expr='sum(ray_node_disk_usage{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType) * 100 / (sum(ray_node_disk_free{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType) + sum(ray_node_disk_usage{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType))',
legend="Disk Used: {{instance}} ({{RayNodeType}})",
),
],
fill=0,
stack=False,
),
Panel(
id=8,
title="Node GPU Usage",
description="Node's physical (hardware) GPU usage. The dotted line means the total number of hardware GPUs from the cluster. ",
unit="GPUs",
targets=[
Target(
expr='sum(ray_node_gpus_utilization{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}} / 100) by (instance, RayNodeType, GpuIndex, GpuDeviceName)',
legend="GPU Usage: {{instance}} ({{RayNodeType}}), gpu.{{GpuIndex}}, {{GpuDeviceName}}",
),
Target(
expr='sum(ray_node_gpus_available{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}})',
legend="MAX",
),
],
),
Panel(
id=55,
title="Node GPU Usage %",
description="Node's physical (hardware) GPU usage.",
unit="%",
targets=[
Target(
expr='sum(ray_node_gpus_utilization{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType, GpuIndex, GpuDeviceName)',
legend="GPU Usage: {{instance}} ({{RayNodeType}}), gpu.{{GpuIndex}}, {{GpuDeviceName}}",
),
],
fill=0,
stack=False,
),
Panel(
id=18,
title="Node GPU Memory Usage (GRAM)",
description="The physical (hardware) GPU memory usage for each node. The dotted line means the total amount of GPU memory from the cluster.",
unit="bytes",
targets=[
Target(
expr='sum(ray_node_gram_used{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}} * 1024 * 1024) by (instance, RayNodeType, GpuIndex, GpuDeviceName)',
legend="Used GRAM: {{instance}} ({{RayNodeType}}), gpu.{{GpuIndex}}, {{GpuDeviceName}}",
),
Target(
expr='(sum(ray_node_gram_available{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) + sum(ray_node_gram_used{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}})) * 1024 * 1024',
legend="MAX",
),
],
),
Panel(
id=56,
title="Node GPU Memory Usage (GRAM) %",
description="The percentage of physical (hardware) GPU memory usage for each node.",
unit="%",
targets=[
Target(
expr='sum(ray_node_gram_used{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType, GpuIndex, GpuDeviceName) * 100 / (sum(ray_node_gram_available{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType, GpuIndex, GpuDeviceName) + sum(ray_node_gram_used{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType, GpuIndex, GpuDeviceName))',
legend="Used GRAM: {{instance}} ({{RayNodeType}}), gpu.{{GpuIndex}}, {{GpuDeviceName}}",
),
],
fill=0,
stack=False,
),
Panel(
id=62,
title="Node GPU Power",
description="Current GPU power draw per node. Reported in milliwatts; displayed in watts. Supported on NVIDIA and AMD GPUs.",
unit="mwatt",
targets=[
Target(
expr='sum(ray_node_gpu_power_milliwatts{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType, GpuIndex, GpuDeviceName)',
legend="Power: {{instance}} ({{RayNodeType}}), gpu.{{GpuIndex}}, {{GpuDeviceName}}",
),
],
fill=0,
stack=False,
),
Panel(
id=63,
title="Node GPU Temperature",
description="Current GPU temperature per node in Celsius. Supported on NVIDIA GPUs.",
unit="celsius",
targets=[
Target(
expr='sum(ray_node_gpu_temperature_celsius{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType, GpuIndex, GpuDeviceName)',
legend="Temperature: {{instance}} ({{RayNodeType}}), gpu.{{GpuIndex}}, {{GpuDeviceName}}",
),
],
fill=0,
stack=False,
),
Panel(
id=32,
title="Node Disk IO Speed",
description="Disk IO per node.",
unit="Bps",
targets=[
Target(
expr='sum(ray_node_disk_io_write_speed{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType)',
legend="Write: {{instance}} ({{RayNodeType}})",
),
Target(
expr='sum(ray_node_disk_io_read_speed{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType)',
legend="Read: {{instance}} ({{RayNodeType}})",
),
],
),
Panel(
id=20,
title="Node Network",
description="Network speed per node",
unit="Bps",
targets=[
Target(
expr='sum(ray_node_network_receive_speed{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType)',
legend="Recv: {{instance}} ({{RayNodeType}})",
),
Target(
expr='sum(ray_node_network_send_speed{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType)',
legend="Send: {{instance}} ({{RayNodeType}})",
),
],
),
]
NODE_TPU_UTILIZATION_PANELS = [
Panel(
id=50,
title="Node TPU Tensorcore Utilization %",
description="Percentage of tensorcore utilization for the TPUs on this node. Computed by dividing the number of tensorcore operations by the maximum supported number of operations during the sample period.",
unit="%",
targets=[
Target(
expr='sum(ray_tpu_tensorcore_utilization{{instance=~"$Instance",{global_filters}}}) by (instance, TpuIndex, TpuDeviceName, TpuType, TpuTopology)',
legend="{{instance}}, tpu.{{TpuIndex}}, {{TpuType}}, {{TpuTopology}}",
),
],
),
Panel(
id=51,
title="Node TPU High Bandwidth Memory Utilization %",
description="Percentage of bandwidth memory utilization for the TPUs on this node. Computed by dividing the memory bandwidth used by the maximum supported memory bandwidth limit during the sample period.",
unit="%",
targets=[
Target(
expr='sum(ray_tpu_memory_bandwidth_utilization{{instance=~"$Instance",{global_filters}}}) by (instance, TpuIndex, TpuDeviceName, TpuType, TpuTopology)',
legend="{{instance}}, tpu.{{TpuIndex}}, {{TpuType}}, {{TpuTopology}}",
),
],
),
Panel(
id=52,
title="Node TPU Duty Cycle %",
description="Percentage of time over the sample period during which the TPU is actively processing.",
unit="%",
targets=[
Target(
expr='sum(ray_tpu_duty_cycle{{instance=~"$Instance",{global_filters}}}) by (instance, TpuIndex, TpuDeviceName, TpuType, TpuTopology) or vector(0)',
legend="{{instance}}, tpu.{{TpuIndex}}, {{TpuType}}, {{TpuTopology}}",
),
],
),
Panel(
id=53,
title="Node TPU Memory Used",
description="Total memory used/allocated for the TPUs on this node.",
unit="bytes",
targets=[
Target(
expr='sum(ray_tpu_memory_used{{instance=~"$Instance",{global_filters}}}) by (instance, TpuIndex, TpuDeviceName, TpuType, TpuTopology) or vector(0)',
legend="Memory Used: {{instance}}, tpu.{{TpuIndex}}, {{TpuType}}, {{TpuTopology}}",
),
Target(
expr='sum(ray_tpu_memory_total{{instance=~"$Instance",{global_filters}}}) by (instance, TpuIndex, TpuDeviceName, TpuType, TpuTopology) or vector(0)',
legend="Memory Total: {{instance}}, tpu.{{TpuIndex}}, {{TpuType}}, {{TpuTopology}}",
),
],
),
]
DEFAULT_GRAFANA_ROWS = [
Row(
title="Overview and Health",
id=1001,
panels=OVERVIEW_AND_HEALTH_PANELS,
collapsed=False,
),
Row(
title="Hardware Utilization by Node",
id=1005,
panels=NODE_HARDWARE_UTILIZATION_PANELS,
collapsed=False,
),
Row(
title="Hardware Utilization by Ray Component",
id=1004,
panels=NODE_HARDWARE_UTILIZATION_BY_RAY_COMPONENT_PANELS,
collapsed=False,
),
Row(
title="Ray Resources by Node",
id=1003,
panels=RAY_RESOURCES_PANELS,
collapsed=False,
),
Row(
title="Ray Tasks, Actors and Placement Groups",
id=1002,
panels=RAY_TASKS_ACTORS_PLACEMENT_GROUPS_PANELS,
collapsed=False,
),
Row(
title="TPU Utilization by Node",
id=1006,
panels=NODE_TPU_UTILIZATION_PANELS,
collapsed=True,
),
]
ids = []
for row in DEFAULT_GRAFANA_ROWS:
ids.append(row.id)
ids.extend(panel.id for panel in row.panels)
ids.sort()
assert len(ids) == len(
set(ids)
), f"Duplicated id found. Use unique id for each panel. {ids}"
default_dashboard_config = DashboardConfig(
name="DEFAULT",
default_uid="rayDefaultDashboard",
rows=DEFAULT_GRAFANA_ROWS,
standard_global_filters=[
'SessionName=~"$SessionName"',
'ray_io_cluster=~"$Cluster"',
],
base_json_file_name="default_grafana_dashboard_base.json",
)
@@ -0,0 +1,177 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 1,
"iteration": 1667344411089,
"links": [],
"panels": [],
"refresh": false,
"schemaVersion": 27,
"style": "dark",
"tags": [],
"templating": {
"list": [
{
"current": {
"selected": false
},
"description": "Filter queries of a specific Prometheus type.",
"hide": 2,
"includeAll": false,
"multi": false,
"name": "datasource",
"options": [],
"query": "prometheus",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"type": "datasource"
},
{
"allValue": ".+",
"current": {
"selected": false
},
"datasource": "${datasource}",
"definition": "label_values(ray_node_network_receive_speed{{{global_filters}}}, SessionName)",
"description": "Filter queries to specific Ray sessions.",
"error": null,
"hide": 0,
"includeAll": true,
"label": null,
"multi": false,
"name": "SessionName",
"options": [],
"query": {
"query": "label_values(ray_node_network_receive_speed{{{global_filters}}}, SessionName)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 2,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"allValue": ".+",
"current": {
"selected": true,
"text": ["All"],
"value": ["$__all"]
},
"datasource": "${datasource}",
"definition": "label_values(ray_node_network_receive_speed{{SessionName=~\"$SessionName\",{global_filters}}}, instance)",
"description": "Filter queries to specific Ray nodes by their IP address.",
"error": null,
"hide": 0,
"includeAll": true,
"label": null,
"multi": true,
"name": "Instance",
"options": [],
"query": {
"query": "label_values(ray_node_network_receive_speed{{SessionName=~\"$SessionName\",{global_filters}}}, instance)",
"refId": "Prometheus-Instance-Variable-Query"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"allValue": ".*",
"current": {
"selected": false
},
"datasource": "${datasource}",
"definition": "label_values(ray_node_network_receive_speed{{{global_filters}}}, ray_io_cluster)",
"description": "Filter queries to specific Ray clusters for KubeRay. When ingesting metrics across multiple Ray clusters, the ray_io_cluster label should be set per cluster. For KubeRay users, this is done automatically with Prometheus PodMonitor.",
"error": null,
"hide": 0,
"includeAll": true,
"label": null,
"multi": false,
"name": "Cluster",
"options": [],
"query": {
"query": "label_values(ray_node_network_receive_speed{{{global_filters}}}, ray_io_cluster)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 2,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"current": {
"text": [
"All"
],
"value": [
"$__all"
]
},
"description": "Filter queries to specific Ray node types (head or worker).",
"includeAll": true,
"multi": true,
"name": "RayNodeType",
"options": [
{
"selected": false,
"text": "All",
"value": "$__all"
},
{
"selected": false,
"text": "Head Node",
"value": "head"
},
{
"selected": false,
"text": "Worker Node",
"value": "worker"
}
],
"query": "head, worker",
"type": "custom"
}
]
},
"rayMeta": ["supportsFullGrafanaView"],
"time": {
"from": "now-30m",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "Default Dashboard",
"uid": "rayDefaultDashboard",
"version": 4
}
@@ -0,0 +1,423 @@
# ruff: noqa: E501
from ray.dashboard.modules.metrics.dashboards.common import (
DashboardConfig,
GridPos,
Panel,
Target,
)
SERVE_GRAFANA_PANELS = [
Panel(
id=5,
title="Cluster Utilization",
description="Aggregated utilization of all physical resources (CPU, GPU, memory, disk, or etc.) across the cluster. Ignores application variable.",
unit="%",
targets=[
# CPU
Target(
expr="avg(ray_node_cpu_utilization{{{global_filters}}})",
legend="CPU (physical)",
),
# GPU
Target(
expr="sum(ray_node_gpus_utilization{{{global_filters}}}) / on() (sum(autoscaler_cluster_resources{{resource='GPU',{global_filters}}}) or vector(0))",
legend="GPU (physical)",
),
# Memory
Target(
expr="sum(ray_node_mem_used{{{global_filters}}}) / on() (sum(ray_node_mem_total{{{global_filters}}})) * 100",
legend="Memory (RAM)",
),
# GRAM
Target(
expr="sum(ray_node_gram_used{{{global_filters}}}) / on() (sum(ray_node_gram_available{{{global_filters}}}) + sum(ray_node_gram_used{{{global_filters}}})) * 100",
legend="GRAM",
),
# Object Store
Target(
expr='sum(ray_object_store_memory{{{global_filters}}}) / on() sum(ray_resources{{Name="object_store_memory",{global_filters}}}) * 100',
legend="Object Store Memory",
),
# Disk
Target(
expr="sum(ray_node_disk_usage{{{global_filters}}}) / on() (sum(ray_node_disk_free{{{global_filters}}}) + sum(ray_node_disk_usage{{{global_filters}}})) * 100",
legend="Disk",
),
],
fill=0,
stack=False,
grid_pos=GridPos(0, 0, 12, 8),
),
Panel(
id=7,
title="QPS per application",
description="QPS for each selected application.",
unit="qps",
targets=[
Target(
expr='sum(rate(ray_serve_num_http_requests_total{{application=~"$Application",application!~"",route=~"$HTTP_Route",route!~"/-/.*",{global_filters}}}[5m])) by (application, route)',
legend="{{application, route}}",
),
Target(
expr='sum(rate(ray_serve_num_grpc_requests_total{{application=~"$Application",application!~"",method=~"$gRPC_Method",{global_filters}}}[5m])) by (application, method)',
legend="{{application, method}}",
),
],
grid_pos=GridPos(12, 0, 12, 8),
),
Panel(
id=8,
title="Error QPS per application",
description="Error QPS for each selected application.",
unit="qps",
targets=[
Target(
expr='sum(rate(ray_serve_num_http_error_requests_total{{application=~"$Application",application!~"",route=~"$HTTP_Route",route!~"/-/.*",{global_filters}}}[5m])) by (application, route)',
legend="{{application, route}}",
),
Target(
expr='sum(rate(ray_serve_num_grpc_error_requests_total{{application=~"$Application",application!~"",method=~"$gRPC_Method",{global_filters}}}[5m])) by (application, method)',
legend="{{application, method}}",
),
],
grid_pos=GridPos(0, 1, 12, 8),
),
Panel(
id=17,
title="Error QPS per application per error code",
description="Error QPS for each selected application.",
unit="qps",
targets=[
Target(
expr='sum(rate(ray_serve_num_http_error_requests_total{{application=~"$Application",application!~"",route=~"$HTTP_Route",route!~"/-/.*",{global_filters}}}[5m])) by (application, route, error_code)',
legend="{{application, route, error_code}}",
),
Target(
expr='sum(rate(ray_serve_num_grpc_error_requests_total{{application=~"$Application",application!~"",method=~"$gRPC_Method",{global_filters}}}[5m])) by (application, method, error_code)',
legend="{{application, method, error_code}}",
),
],
grid_pos=GridPos(12, 1, 12, 8),
),
Panel(
id=12,
title="P50 latency per application",
description="P50 latency for selected applications.",
unit="ms",
targets=[
Target(
expr='histogram_quantile(0.5, sum(rate(ray_serve_http_request_latency_ms_bucket{{application=~"$Application",application!~"",route=~"$HTTP_Route",route!~"/-/.*",{global_filters}}}[5m])) by (application, route, le))',
legend="{{application, route}}",
),
Target(
expr='histogram_quantile(0.5, sum(rate(ray_serve_grpc_request_latency_ms_bucket{{application=~"$Application",application!~"",method=~"$gRPC_Method",{global_filters}}}[5m])) by (application, method, le))',
legend="{{application, method}}",
),
Target(
expr='histogram_quantile(0.5, sum(rate({{__name__=~ "ray_serve_(http|grpc)_request_latency_ms_bucket",application=~"$Application",application!~"",{global_filters}}}[5m])) by (le))',
legend="Total",
),
],
fill=0,
stack=False,
grid_pos=GridPos(0, 2, 8, 8),
),
Panel(
id=15,
title="P90 latency per application",
description="P90 latency for selected applications.",
unit="ms",
targets=[
Target(
expr='histogram_quantile(0.9, sum(rate(ray_serve_http_request_latency_ms_bucket{{application=~"$Application",application!~"",route=~"$HTTP_Route",route!~"/-/.*",{global_filters}}}[5m])) by (application, route, le))',
legend="{{application, route}}",
),
Target(
expr='histogram_quantile(0.9, sum(rate(ray_serve_grpc_request_latency_ms_bucket{{application=~"$Application",application!~"",method=~"$gRPC_Method",{global_filters}}}[5m])) by (application, method, le))',
legend="{{application, method}}",
),
Target(
expr='histogram_quantile(0.9, sum(rate({{__name__=~ "ray_serve_(http|grpc)_request_latency_ms_bucket|ray_serve_grpc_request_latency_ms_bucket",application=~"$Application",application!~"",{global_filters}}}[5m])) by (le))',
legend="Total",
),
],
fill=0,
stack=False,
grid_pos=GridPos(8, 2, 8, 8),
),
Panel(
id=16,
title="P99 latency per application",
description="P99 latency for selected applications.",
unit="ms",
targets=[
Target(
expr='histogram_quantile(0.99, sum(rate(ray_serve_http_request_latency_ms_bucket{{application=~"$Application",application!~"",route=~"$HTTP_Route",route!~"/-/.*",{global_filters}}}[5m])) by (application, route, le))',
legend="{{application, route}}",
),
Target(
expr='histogram_quantile(0.99, sum(rate(ray_serve_grpc_request_latency_ms_bucket{{application=~"$Application",application!~"",method=~"$gRPC_Method",{global_filters}}}[5m])) by (application, method, le))',
legend="{{application, method}}",
),
Target(
expr='histogram_quantile(0.99, sum(rate({{__name__=~ "ray_serve_(http|grpc)_request_latency_ms_bucket|ray_serve_grpc_request_latency_ms_bucket",application=~"$Application",application!~"",{global_filters}}}[5m])) by (le))',
legend="Total",
),
],
fill=0,
stack=False,
grid_pos=GridPos(16, 2, 8, 8),
),
Panel(
id=2,
title="Replicas per deployment",
description='Number of replicas per deployment. Ignores "Application" variable.',
unit="replicas",
targets=[
Target(
expr="sum(ray_serve_deployment_replica_healthy{{{global_filters}}}) by (application, deployment)",
legend="{{application, deployment}}",
),
],
grid_pos=GridPos(0, 3, 8, 8),
),
Panel(
id=13,
title="QPS per deployment",
description="QPS for each deployment.",
unit="qps",
targets=[
Target(
expr='sum(rate(ray_serve_deployment_request_counter_total{{application=~"$Application",application!~"",{global_filters}}}[5m])) by (application, deployment)',
legend="{{application, deployment}}",
),
],
grid_pos=GridPos(8, 3, 8, 8),
),
Panel(
id=14,
title="Error QPS per deployment",
description="Error QPS for each deplyoment.",
unit="qps",
targets=[
Target(
expr='sum(rate(ray_serve_deployment_error_counter_total{{application=~"$Application",application!~"",{global_filters}}}[5m])) by (application, deployment)',
legend="{{application, deployment}}",
),
],
grid_pos=GridPos(16, 3, 8, 8),
),
Panel(
id=9,
title="P50 latency per deployment",
description="P50 latency per deployment.",
unit="ms",
targets=[
Target(
expr='histogram_quantile(0.5, sum(rate(ray_serve_deployment_processing_latency_ms_bucket{{application=~"$Application",application!~"",{global_filters}}}[5m])) by (application, deployment, le))',
legend="{{application, deployment}}",
),
Target(
expr='histogram_quantile(0.5, sum(rate(ray_serve_deployment_processing_latency_ms_bucket{{application=~"$Application",application!~"",{global_filters}}}[5m])) by (le))',
legend="Total",
),
],
fill=0,
stack=False,
grid_pos=GridPos(0, 4, 8, 8),
),
Panel(
id=10,
title="P90 latency per deployment",
description="P90 latency per deployment.",
unit="ms",
targets=[
Target(
expr='histogram_quantile(0.9, sum(rate(ray_serve_deployment_processing_latency_ms_bucket{{application=~"$Application",application!~"",{global_filters}}}[5m])) by (application, deployment, le))',
legend="{{application, deployment}}",
),
Target(
expr='histogram_quantile(0.9, sum(rate(ray_serve_deployment_processing_latency_ms_bucket{{application=~"$Application",application!~"",{global_filters}}}[5m])) by (le))',
legend="Total",
),
],
fill=0,
stack=False,
grid_pos=GridPos(8, 4, 8, 8),
),
Panel(
id=11,
title="P99 latency per deployment",
description="P99 latency per deployment.",
unit="ms",
targets=[
Target(
expr='histogram_quantile(0.99, sum(rate(ray_serve_deployment_processing_latency_ms_bucket{{application=~"$Application",application!~"",{global_filters}}}[5m])) by (application, deployment, le))',
legend="{{application, deployment}}",
),
Target(
expr='histogram_quantile(0.99, sum(rate(ray_serve_deployment_processing_latency_ms_bucket{{application=~"$Application",application!~"",{global_filters}}}[5m])) by (le))',
legend="Total",
),
],
fill=0,
stack=False,
grid_pos=GridPos(16, 4, 8, 8),
),
Panel(
id=3,
title="Queue size per deployment",
description='Number of requests queued per deployment. Ignores "Application" variable.',
unit="requests",
targets=[
Target(
expr="sum(ray_serve_deployment_queued_queries{{{global_filters}}}) by (application, deployment)",
legend="{{application, deployment}}",
),
],
fill=0,
stack=False,
grid_pos=GridPos(0, 5, 8, 8),
),
Panel(
id=4,
title="Node count",
description='Number of nodes in this cluster. Ignores "Application" variable.',
unit="nodes",
targets=[
# TODO(aguo): Update this to use autoscaler metrics instead
Target(
expr="sum(autoscaler_active_nodes{{{global_filters}}}) by (NodeType)",
legend="Active Nodes: {{NodeType}}",
),
Target(
expr="sum(autoscaler_recently_failed_nodes{{{global_filters}}}) by (NodeType)",
legend="Failed Nodes: {{NodeType}}",
),
Target(
expr="sum(autoscaler_pending_nodes{{{global_filters}}}) by (NodeType)",
legend="Pending Nodes: {{NodeType}}",
),
],
grid_pos=GridPos(8, 5, 8, 8),
),
Panel(
id=6,
title="Node network",
description='Network speed per node. Ignores "Application" variable.',
unit="Bps",
targets=[
Target(
expr="sum(ray_node_network_receive_speed{{{global_filters}}}) by (instance)",
legend="Recv: {{instance}}",
),
Target(
expr="sum(ray_node_network_send_speed{{{global_filters}}}) by (instance)",
legend="Send: {{instance}}",
),
],
fill=1,
linewidth=2,
stack=False,
grid_pos=GridPos(16, 5, 8, 8),
),
Panel(
id=20,
title="Ongoing HTTP Requests",
description="The number of ongoing requests in the HTTP Proxy.",
unit="requests",
targets=[
Target(
expr="ray_serve_num_ongoing_http_requests{{{global_filters}}}",
legend="Ongoing HTTP Requests",
),
],
grid_pos=GridPos(0, 6, 8, 8),
),
Panel(
id=21,
title="Ongoing gRPC Requests",
description="The number of ongoing requests in the gRPC Proxy.",
unit="requests",
targets=[
Target(
expr="ray_serve_num_ongoing_grpc_requests{{{global_filters}}}",
legend="Ongoing gRPC Requests",
),
],
grid_pos=GridPos(8, 6, 8, 8),
),
Panel(
id=22,
title="Scheduling Tasks",
description="The number of request scheduling tasks in the router.",
unit="tasks",
targets=[
Target(
expr="ray_serve_num_scheduling_tasks{{{global_filters}}}",
legend="Scheduling Tasks",
),
],
grid_pos=GridPos(16, 6, 8, 8),
),
Panel(
id=23,
title="Scheduling Tasks in Backoff",
description="The number of request scheduling tasks in the router that are undergoing backoff.",
unit="tasks",
targets=[
Target(
expr="ray_serve_num_scheduling_tasks_in_backoff{{{global_filters}}}",
legend="Scheduling Tasks in Backoff",
),
],
grid_pos=GridPos(0, 7, 8, 8),
),
Panel(
id=24,
title="Controller Control Loop Duration",
description="The duration of the last control loop.",
unit="seconds",
targets=[
Target(
expr="ray_serve_controller_control_loop_duration_s{{{global_filters}}}",
legend="Control Loop Duration",
),
],
grid_pos=GridPos(8, 7, 8, 8),
),
Panel(
id=25,
title="Number of Control Loops",
description="The number of control loops performed by the controller. Increases monotonically over the controller's lifetime.",
unit="loops",
targets=[
Target(
expr="ray_serve_controller_num_control_loops{{{global_filters}}}",
legend="Control Loops",
),
],
grid_pos=GridPos(16, 7, 8, 8),
),
]
ids = []
for panel in SERVE_GRAFANA_PANELS:
ids.append(panel.id)
ids.sort()
assert len(ids) == len(
set(ids)
), f"Duplicated id found. Use unique id for each panel. {ids}"
serve_dashboard_config = DashboardConfig(
name="SERVE",
default_uid="rayServeDashboard",
panels=SERVE_GRAFANA_PANELS,
standard_global_filters=[
'ray_io_cluster=~"$Cluster"',
],
base_json_file_name="serve_grafana_dashboard_base.json",
)
@@ -0,0 +1,262 @@
# ruff: noqa: E501
from ray.dashboard.modules.metrics.dashboards.common import (
DashboardConfig,
GridPos,
Panel,
Target,
)
SERVE_DEPLOYMENT_GRAFANA_PANELS = [
Panel(
id=1,
title="Replicas per deployment",
description='Number of replicas per deployment. Ignores "Route" variable.',
unit="replicas",
targets=[
Target(
expr="sum(ray_serve_deployment_replica_healthy{{{global_filters}}}) by (application, deployment)",
legend="{{application}}#{{deployment}}#{{replica}}",
),
],
grid_pos=GridPos(0, 0, 8, 8),
),
Panel(
id=2,
title="QPS per replica",
description="QPS for each replica.",
unit="qps",
targets=[
Target(
expr='sum(rate(ray_serve_deployment_request_counter_total{{route=~"$Route",route!~"/-/.*",{global_filters}}}[5m])) by (application, deployment, replica)',
legend="{{application}}#{{deployment}}#{{replica}}",
),
],
grid_pos=GridPos(8, 0, 8, 8),
),
Panel(
id=3,
title="Error QPS per replica",
description="Error QPS for each replica.",
unit="qps",
targets=[
Target(
expr='sum(rate(ray_serve_deployment_error_counter_total{{route=~"$Route",route!~"/-/.*",{global_filters}}}[5m])) by (application, deployment, replica)',
legend="{{application}}#{{deployment}}#{{replica}}",
),
],
grid_pos=GridPos(16, 0, 8, 8),
),
Panel(
id=4,
title="P50 latency per replica",
description="P50 latency per replica.",
unit="ms",
targets=[
Target(
expr='histogram_quantile(0.5, sum(rate(ray_serve_deployment_processing_latency_ms_bucket{{route=~"$Route",route!~"/-/.*",{global_filters}}}[5m])) by (application, deployment, replica, le))',
legend="{{application}}#{{deployment}}#{{replica}}",
),
Target(
expr='histogram_quantile(0.5, sum(rate(ray_serve_deployment_processing_latency_ms_bucket{{route=~"$Route",route!~"/-/.*",{global_filters}}}[5m])) by (le))',
legend="Total",
),
],
fill=0,
stack=False,
grid_pos=GridPos(0, 1, 8, 8),
),
Panel(
id=5,
title="P90 latency per replica",
description="P90 latency per replica.",
unit="ms",
targets=[
Target(
expr='histogram_quantile(0.9, sum(rate(ray_serve_deployment_processing_latency_ms_bucket{{route=~"$Route",route!~"/-/.*",{global_filters}}}[5m])) by (application, deployment, replica, le))',
legend="{{application}}#{{deployment}}#{{replica}}",
),
Target(
expr='histogram_quantile(0.9, sum(rate(ray_serve_deployment_processing_latency_ms_bucket{{route=~"$Route",route!~"/-/.*",{global_filters}}}[5m])) by (le))',
legend="Total",
),
],
fill=0,
stack=False,
grid_pos=GridPos(8, 1, 8, 8),
),
Panel(
id=6,
title="P99 latency per replica",
description="P99 latency per replica.",
unit="ms",
targets=[
Target(
expr='histogram_quantile(0.99, sum(rate(ray_serve_deployment_processing_latency_ms_bucket{{route=~"$Route",route!~"/-/.*",{global_filters}}}[5m])) by (application, deployment, replica, le))',
legend="{{application}}#{{deployment}}#{{replica}}",
),
Target(
expr='histogram_quantile(0.99, sum(rate(ray_serve_deployment_processing_latency_ms_bucket{{route=~"$Route",{global_filters}}}[5m])) by (le))',
legend="Total",
),
],
fill=0,
stack=False,
grid_pos=GridPos(16, 1, 8, 8),
),
Panel(
id=7,
title="Queue size per deployment",
description='Number of requests queued per deployment. Ignores "Replica" and "Route" variable.',
unit="requests",
targets=[
Target(
expr="sum(ray_serve_deployment_queued_queries{{{global_filters}}}) by (application, deployment)",
legend="{{application}}#{{deployment}}#{{replica}}",
),
],
fill=0,
stack=False,
grid_pos=GridPos(0, 2, 12, 8),
),
Panel(
id=8,
title="Running requests per replica",
description="Current running requests for each replica.",
unit="requests",
targets=[
Target(
expr="sum(ray_serve_replica_processing_queries{{{global_filters}}}) by (application, deployment, replica)",
legend="{{application}}#{{deployment}}#{{replica}}",
),
],
fill=0,
stack=False,
grid_pos=GridPos(12, 2, 12, 8),
),
Panel(
id=9,
title="Multiplexed models per replica",
description="The number of multiplexed models for each replica.",
unit="models",
targets=[
Target(
expr="sum(ray_serve_num_multiplexed_models{{{global_filters}}}) by (application, deployment, replica)",
legend="{{application}}#{{deployment}}#{{replica}}",
),
],
fill=0,
stack=False,
grid_pos=GridPos(0, 3, 8, 8),
),
Panel(
id=10,
title="Multiplexed model loads per replica",
description="The number of times of multiplexed models loaded for each replica.",
unit="times",
targets=[
Target(
expr="sum(ray_serve_multiplexed_models_load_counter_total{{{global_filters}}}) by (application, deployment, replica)",
legend="{{application}}#{{deployment}}#{{replica}}",
),
],
fill=0,
stack=False,
grid_pos=GridPos(8, 3, 8, 8),
),
Panel(
id=11,
title="Multiplexed model unloads per replica",
description="The number of times of multiplexed models unloaded for each replica.",
unit="times",
targets=[
Target(
expr="sum(ray_serve_multiplexed_models_unload_counter_total{{{global_filters}}}) by (application, deployment, replica)",
legend="{{application}}#{{deployment}}#{{replica}}",
),
],
fill=0,
stack=False,
grid_pos=GridPos(16, 3, 8, 8),
),
Panel(
id=12,
title="P99 latency of multiplexed model loads per replica",
description="P99 latency of multiplexed model load per replica.",
unit="ms",
targets=[
Target(
expr="histogram_quantile(0.99, sum(rate(ray_serve_multiplexed_model_load_latency_ms_bucket{{{global_filters}}}[5m])) by (application, deployment, replica, le))",
legend="{{application}}#{{deployment}}#{{replica}}",
),
],
fill=0,
stack=False,
grid_pos=GridPos(0, 4, 8, 8),
),
Panel(
id=13,
title="P99 latency of multiplexed model unloads per replica",
description="P99 latency of multiplexed model unload per replica.",
unit="ms",
targets=[
Target(
expr="histogram_quantile(0.99, sum(rate(ray_serve_multiplexed_model_unload_latency_ms_bucket{{{global_filters}}}[5m])) by (application, deployment, replica, le))",
legend="{{application}}#{{deployment}}#{{replica}}",
),
],
fill=0,
stack=False,
grid_pos=GridPos(8, 4, 8, 8),
),
Panel(
id=14,
title="Multiplexed model ids per replica",
description="The ids of multiplexed models for each replica.",
unit="model",
targets=[
Target(
expr="ray_serve_registered_multiplexed_model_id{{{global_filters}}}",
legend="{{replica}}:{{model_id}}",
),
],
grid_pos=GridPos(16, 4, 8, 8),
stack=False,
),
Panel(
id=15,
title="Multiplexed model cache hit rate",
description="The cache hit rate of multiplexed models for the deployment.",
unit="%",
targets=[
Target(
expr="(1 - sum(rate(ray_serve_multiplexed_models_load_counter_total{{{global_filters}}}[5m]))/sum(rate(ray_serve_multiplexed_get_model_requests_counter_total{{{global_filters}}}[5m])))",
legend="{{application}}#{{deployment}}#{{replica}}",
),
],
grid_pos=GridPos(0, 5, 8, 8),
),
]
ids = []
for panel in SERVE_DEPLOYMENT_GRAFANA_PANELS:
ids.append(panel.id)
ids.sort()
assert len(ids) == len(
set(ids)
), f"Duplicated id found. Use unique id for each panel. {ids}"
serve_deployment_dashboard_config = DashboardConfig(
name="SERVE_DEPLOYMENT",
default_uid="rayServeDeploymentDashboard",
panels=SERVE_DEPLOYMENT_GRAFANA_PANELS,
standard_global_filters=[
'application=~"$Application"',
'deployment=~"$Deployment"',
'replica=~"$Replica"',
'ray_io_cluster=~"$Cluster"',
],
base_json_file_name="serve_deployment_grafana_dashboard_base.json",
)
@@ -0,0 +1,208 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 1,
"iteration": 1667344411089,
"links": [],
"panels": [],
"refresh": false,
"schemaVersion": 27,
"style": "dark",
"tags": [],
"templating": {
"list": [
{
"current": {
"selected": false
},
"description": "Filter queries to specific prometheus type.",
"hide": 2,
"includeAll": false,
"multi": false,
"name": "datasource",
"options": [],
"query": "prometheus",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"type": "datasource"
},
{
"allValue": ".*",
"current": {
"selected": true,
"text": ["All"],
"value": ["$__all"]
},
"datasource": "${datasource}",
"definition": "label_values(ray_serve_deployment_replica_healthy{{{global_filters}}}, application)",
"description": null,
"error": null,
"hide": 0,
"includeAll": true,
"label": null,
"multi": true,
"name": "Application",
"options": [],
"query": {
"query": "label_values(ray_serve_deployment_replica_healthy{{{global_filters}}}, application)",
"refId": "Prometheus-Instance-Variable-Query"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"allValue": ".*",
"current": {
"selected": true,
"text": ["All"],
"value": ["$__all"]
},
"datasource": "${datasource}",
"definition": "label_values(ray_serve_deployment_replica_healthy{{application=~\"$Application\",{global_filters}}}, deployment)",
"description": null,
"error": null,
"hide": 0,
"includeAll": true,
"label": null,
"multi": true,
"name": "Deployment",
"options": [],
"query": {
"query": "label_values(ray_serve_deployment_replica_healthy{{application=~\"$Application\",{global_filters}}}, deployment)",
"refId": "Prometheus-Instance-Variable-Query"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"allValue": ".*",
"current": {
"selected": true,
"text": ["All"],
"value": ["$__all"]
},
"datasource": "${datasource}",
"definition": "label_values(ray_serve_deployment_replica_healthy{{application=~\"$Application\",deployment=~\"$Deployment\",{global_filters}}}, replica)",
"description": null,
"error": null,
"hide": 0,
"includeAll": true,
"label": null,
"multi": true,
"name": "Replica",
"options": [],
"query": {
"query": "label_values(ray_serve_deployment_replica_healthy{{application=~\"$Application\",deployment=~\"$Deployment\",{global_filters}}}, replica)",
"refId": "Prometheus-Instance-Variable-Query"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"allValue": ".*",
"current": {
"selected": true,
"text": ["All"],
"value": ["$__all"]
},
"datasource": "${datasource}",
"definition": "label_values(ray_serve_deployment_request_counter{{deployment=~\"$Deployment\",{global_filters}}}, route)",
"description": null,
"error": null,
"hide": 0,
"includeAll": true,
"label": null,
"multi": true,
"name": "Route",
"options": [],
"query": {
"query": "label_values(ray_serve_deployment_request_counter{{deployment=~\"$Deployment\",{global_filters}}}, route)",
"refId": "Prometheus-Instance-Variable-Query"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"allValue": ".*",
"current": {
"selected": false
},
"datasource": "${datasource}",
"definition": "label_values(ray_node_network_receive_speed{{{global_filters}}}, ray_io_cluster)",
"description": "Filter queries to specific Ray clusters for KubeRay. When ingesting metrics across multiple ray clusters, the ray_io_cluster label should be set per cluster. For KubeRay users, this is done automatically with Prometheus PodMonitor.",
"error": null,
"hide": 0,
"includeAll": true,
"label": null,
"multi": false,
"name": "Cluster",
"options": [],
"query": {
"query": "label_values(ray_node_network_receive_speed{{{global_filters}}}, ray_io_cluster)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 2,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
}
]
},
"rayMeta": ["excludesSystemRoutes"],
"time": {
"from": "now-30m",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "Serve Deployment Dashboard",
"uid": "rayServeDeploymentDashboard",
"version": 1
}
@@ -0,0 +1,177 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 1,
"iteration": 1667344411089,
"links": [],
"panels": [],
"refresh": false,
"schemaVersion": 27,
"style": "dark",
"tags": [],
"templating": {
"list": [
{
"current": {
"selected": false
},
"description": "Filter queries of a specific Prometheus type.",
"hide": 2,
"includeAll": false,
"multi": false,
"name": "datasource",
"options": [],
"query": "prometheus",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"type": "datasource"
},
{
"allValue": ".*",
"current": {
"selected": true,
"text": ["All"],
"value": ["$__all"]
},
"datasource": "${datasource}",
"definition": "label_values(ray_serve_deployment_replica_healthy{{{global_filters}}}, application)",
"description": null,
"error": null,
"hide": 0,
"includeAll": true,
"label": null,
"multi": true,
"name": "Application",
"options": [],
"query": {
"query": "label_values(ray_serve_deployment_replica_healthy{{{global_filters}}}, application)",
"refId": "Prometheus-Instance-Variable-Query"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"allValue": ".*",
"current": {
"selected": true,
"text": ["All"],
"value": ["$__all"]
},
"datasource": "${datasource}",
"definition": "label_values(ray_serve_num_http_requests_total{{{global_filters}}}, route)",
"description": null,
"error": null,
"hide": 0,
"includeAll": true,
"label": "HTTP Route",
"multi": true,
"name": "HTTP_Route",
"options": [],
"query": {
"query": "label_values(ray_serve_num_http_requests_total{{{global_filters}}}, route)",
"refId": "Prometheus-Instance-Variable-Query"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"allValue": ".*",
"current": {
"selected": true,
"text": ["All"],
"value": ["$__all"]
},
"datasource": "${datasource}",
"definition": "label_values(ray_serve_num_grpc_requests_total{{{global_filters}}}, method)",
"description": null,
"error": null,
"hide": 0,
"includeAll": true,
"label": "gRPC Service Method",
"multi": true,
"name": "gRPC_Method",
"options": [],
"query": {
"query": "label_values(ray_serve_num_grpc_requests_total{{{global_filters}}}, method)",
"refId": "Prometheus-Instance-Variable-Query"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"allValue": ".*",
"current": {
"selected": false
},
"datasource": "${datasource}",
"definition": "label_values(ray_node_network_receive_speed{{{global_filters}}}, ray_io_cluster)",
"description": "Filter queries to specific Ray clusters for KubeRay. When ingesting metrics across multiple ray clusters, the ray_io_cluster label should be set per cluster. For KubeRay users, this is done automatically with Prometheus PodMonitor.",
"error": null,
"hide": 0,
"includeAll": true,
"label": null,
"multi": false,
"name": "Cluster",
"options": [],
"query": {
"query": "label_values(ray_node_network_receive_speed{{{global_filters}}}, ray_io_cluster)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 2,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
}
]
},
"rayMeta": ["excludesSystemRoutes"],
"time": {
"from": "now-30m",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "Serve Dashboard",
"uid": "rayServeDashboard",
"version": 1
}
@@ -0,0 +1,810 @@
# ruff: noqa: E501
from ray.dashboard.modules.metrics.dashboards.common import (
DashboardConfig,
GridPos,
Panel,
PanelTemplate,
Row,
Target,
)
# ---------------------------------------------------------------------------
# Reusable PromQL fragments
# ---------------------------------------------------------------------------
# WorkerId join: attaches deployment + replica labels to vLLM-only metrics.
# The `* 0 + 1` trick turns the counter into a constant-1 lookup table keyed
# by WorkerId so the join resolves deployment + replica labels without
# affecting the numeric value of the left-hand side.
# Keep `{global_filters}` trailing so an empty substitution leaves a tolerated
# trailing comma instead of a leading/double comma that Prometheus rejects.
_WORKER_JOIN = (
"\n* on(WorkerId) group_left(deployment, replica)"
"\nmax by(WorkerId, deployment, replica) ("
'ray_serve_deployment_request_counter_total{{deployment=~"$deployment", {global_filters}}} * 0 + 1)'
)
# Standard vLLM metric filter
_VLLM_FILTER = 'model_name=~"$vllm_model_name", WorkerId=~"$workerid", {global_filters}'
# vLLM filter scoped to a specific deployment (used for ray_serve_* metrics
# that also carry model_name / WorkerId labels).
_VLLM_DEPLOYMENT_FILTER = 'model_name=~"$vllm_model_name", WorkerId=~"$workerid", deployment=~"$deployment", {global_filters}'
# Legend used by most per-worker panels
_DEP_REPLICA = "{{deployment}}: {{replica}}"
def _mean_with_join(metric_base: str) -> str:
"""Mean = sum(_sum) / sum(_count) with NaN guard + WorkerId join."""
return (
"(\n"
" (\n"
f" sum by(WorkerId) (rate({metric_base}_sum{{{{{_VLLM_FILTER}}}}}[$interval]))\n"
" /\n"
f" sum by(WorkerId) (rate({metric_base}_count{{{{{_VLLM_FILTER}}}}}[$interval]))\n"
" )\n"
" and on(WorkerId)\n"
" (\n"
f" sum by(WorkerId) (rate({metric_base}_count{{{{{_VLLM_FILTER}}}}}[$interval])) > 0\n"
" )\n"
")" + _WORKER_JOIN
)
def _percentile_with_join(metric_base: str, quantile: float) -> str:
"""histogram_quantile with NaN guard + WorkerId join."""
return (
"(\n"
" histogram_quantile(\n"
f" {quantile},\n"
f" sum by (le, WorkerId) (rate({metric_base}_bucket{{{{{_VLLM_FILTER}}}}}[$interval]))\n"
" )\n"
" and on(WorkerId)\n"
" (\n"
f" sum by(WorkerId) (rate({metric_base}_count{{{{{_VLLM_FILTER}}}}}[$interval])) > 0\n"
" )\n"
")" + _WORKER_JOIN
)
def _gauge_with_join(metric: str) -> str:
"""Simple gauge metric with WorkerId join (no rate, no guard)."""
return f"sum by(WorkerId) ({metric}{{{{{_VLLM_FILTER}}}}})" + _WORKER_JOIN
def _rate_with_join(metric: str, agg_fn: str = "rate") -> str:
"""rate() or increase() of a metric summed by WorkerId, with join."""
return (
f"sum by(WorkerId) ({agg_fn}({metric}{{{{{_VLLM_FILTER}}}}}[$interval]))"
+ _WORKER_JOIN
)
def _ratio_with_join_and_guard(
numerator_metric: str,
denominator_metric: str,
*,
scale: str = "",
guard_metric: str | None = None,
) -> str:
"""Ratio of two rate metrics with NaN guard + WorkerId join.
Optionally applies a scale factor (e.g. '* 1000' or '/ 1024 / 1024 / 1024').
"""
guard = guard_metric or denominator_metric
return (
"(\n"
" (\n"
f" sum by(WorkerId) (rate({numerator_metric}{{{{{_VLLM_FILTER}}}}}[$interval]))\n"
" /\n"
f" sum by(WorkerId) (rate({denominator_metric}{{{{{_VLLM_FILTER}}}}}[$interval]))\n"
+ (f" {scale}\n" if scale else "")
+ " )\n"
" and on(WorkerId)\n"
" (\n"
f" sum by(WorkerId) (rate({guard}{{{{{_VLLM_FILTER}}}}}[$interval])) > 0\n"
" )\n"
")" + _WORKER_JOIN
)
# ---------------------------------------------------------------------------
# Histogram helper: generates Mean / P50 / P90 panels for a given metric
# ---------------------------------------------------------------------------
def _histogram_panels(
metric_base: str,
label: str,
ids: tuple,
y: int,
unit: str = "s",
linewidth: int = 2,
description: str = "",
) -> list:
"""Return [Mean, P50, P90] panels for a histogram metric."""
return [
Panel(
id=ids[0],
title=f"{label} -- Mean",
description=description,
unit=unit,
targets=[Target(expr=_mean_with_join(metric_base), legend=_DEP_REPLICA)],
fill=1,
linewidth=linewidth,
stack=False,
grid_pos=GridPos(0, y, 8, 8),
),
Panel(
id=ids[1],
title=f"{label} -- P50",
description=description,
unit=unit,
targets=[
Target(
expr=_percentile_with_join(metric_base, 0.5), legend=_DEP_REPLICA
)
],
fill=1,
linewidth=linewidth,
stack=False,
grid_pos=GridPos(8, y, 8, 8),
),
Panel(
id=ids[2],
title=f"{label} -- P90",
description=description,
unit=unit,
targets=[
Target(
expr=_percentile_with_join(metric_base, 0.9), legend=_DEP_REPLICA
)
],
fill=1,
linewidth=linewidth,
stack=False,
grid_pos=GridPos(16, y, 8, 8),
),
]
# ===================================================================
# Row 1: Throughput
# ===================================================================
_throughput_panels = [
Panel(
id=2,
title="Requests / s",
description="",
unit="short",
targets=[
Target(
expr=f"sum by (deployment, replica) (rate(ray_serve_deployment_request_counter_total{{{{{_VLLM_DEPLOYMENT_FILTER}}}}}[$interval]))",
legend=_DEP_REPLICA,
),
Target(
expr=f"sum(rate(ray_serve_deployment_request_counter_total{{{{{_VLLM_DEPLOYMENT_FILTER}}}}}[$interval]))",
legend="Total QPS",
),
],
fill=1,
linewidth=2,
stack=False,
grid_pos=GridPos(0, 1, 8, 8),
),
Panel(
id=3,
title="Prompt Tokens/s",
description="Number of tokens processed per second",
unit="tokens/s",
targets=[
Target(
expr=_rate_with_join("ray_vllm_request_prompt_tokens_sum"),
legend=_DEP_REPLICA,
),
Target(
expr=f"sum(rate(ray_vllm_request_prompt_tokens_sum{{{{{_VLLM_FILTER}}}}}[$interval]))",
legend="Total",
),
],
fill=1,
linewidth=2,
stack=False,
grid_pos=GridPos(8, 1, 8, 8),
),
Panel(
id=4,
title="Generation Tokens/s",
description="Number of tokens processed per second",
unit="tokens/s",
targets=[
Target(
expr=_rate_with_join("ray_vllm_generation_tokens_total"),
legend=_DEP_REPLICA,
),
Target(
expr=f"sum(rate(ray_vllm_generation_tokens_total{{{{{_VLLM_FILTER}}}}}[$interval]))",
legend="Total",
),
],
fill=1,
linewidth=2,
stack=False,
grid_pos=GridPos(16, 1, 8, 8),
),
]
# ===================================================================
# Row 2: Latency (3x3 grid)
# ===================================================================
_latency_panels_list = [
*_histogram_panels(
"ray_vllm_request_time_per_output_token_seconds", "TPOT", (6, 7, 8), 10
),
*_histogram_panels("ray_vllm_time_to_first_token_seconds", "TTFT", (9, 10, 11), 18),
*_histogram_panels(
"ray_vllm_e2e_request_latency_seconds",
"Request Latency",
(12, 13, 14),
26,
description="Latency from request start to first token returned (in seconds).",
),
]
# ===================================================================
# Row 3: Cache
# ===================================================================
_cache_panels = [
Panel(
id=16,
title="Cache Utilization",
description="Percentage of used cache blocks by vLLM.",
unit="percentunit",
targets=[
Target(
expr=_gauge_with_join("ray_vllm_kv_cache_usage_perc"),
legend=_DEP_REPLICA,
),
],
fill=1,
linewidth=2,
stack=False,
grid_pos=GridPos(0, 35, 12, 8),
),
Panel(
id=17,
title="GPU KV Cache Hit Rate",
description="",
unit="percent",
targets=[
Target(
expr=(
f"100 * ("
f"(sum by(WorkerId) (rate(ray_vllm_prefix_cache_hits_total{{{{{_VLLM_FILTER}}}}}[$interval])) "
f"/ sum by(WorkerId) (rate(ray_vllm_prefix_cache_queries_total{{{{{_VLLM_FILTER}}}}}[$interval])))"
f" and on(WorkerId) "
f"(sum by(WorkerId) (rate(ray_vllm_prefix_cache_queries_total{{{{{_VLLM_FILTER}}}}}[$interval])) > 0))"
+ _WORKER_JOIN
),
legend=_DEP_REPLICA,
),
],
fill=1,
linewidth=1,
stack=False,
grid_pos=GridPos(12, 35, 12, 8),
),
]
# ===================================================================
# Row 4: Request Length
# ===================================================================
_request_length_panels = [
*_histogram_panels(
"ray_vllm_request_prompt_tokens",
"Prompt Length",
(19, 20, 21),
44,
unit="short",
linewidth=1,
),
*_histogram_panels(
"ray_vllm_request_generation_tokens",
"Generation Length",
(22, 23, 24),
52,
unit="short",
linewidth=1,
),
]
# ===================================================================
# Row 5: Scheduler
# ===================================================================
_scheduler_panels = [
Panel(
id=26,
title="Scheduler: Running",
description="",
unit="short",
targets=[
Target(
expr=_gauge_with_join("ray_vllm_num_requests_running"),
legend=_DEP_REPLICA,
)
],
fill=1,
linewidth=1,
stack=False,
grid_pos=GridPos(0, 61, 8, 8),
),
Panel(
id=27,
title="Scheduler: Swapped",
description="",
unit="short",
targets=[
Target(
expr=_gauge_with_join("ray_vllm_num_requests_swapped"),
legend=_DEP_REPLICA,
)
],
fill=1,
linewidth=1,
stack=False,
grid_pos=GridPos(8, 61, 8, 8),
),
Panel(
id=28,
title="Scheduler: Waiting",
description="",
unit="short",
targets=[
Target(
expr=_gauge_with_join("ray_vllm_num_requests_waiting"),
legend=_DEP_REPLICA,
)
],
fill=1,
linewidth=1,
stack=False,
grid_pos=GridPos(16, 61, 8, 8),
),
Panel(
id=29,
title="Finish Reason",
description="Number of finished requests by their finish reason.",
unit="short",
targets=[
Target(
expr=(
f"sum by(finished_reason, WorkerId) (increase(ray_vllm_request_success_total{{{{{_VLLM_FILTER}}}}}[$interval]))"
+ _WORKER_JOIN
),
legend="{{finished_reason}} \u2014 {{deployment}}: {{replica}}",
),
],
fill=1,
linewidth=1,
stack=False,
grid_pos=GridPos(0, 69, 12, 8),
),
Panel(
id=30,
title="Queue Time",
description="",
unit="s",
targets=[
Target(
expr=_rate_with_join("ray_vllm_request_queue_time_seconds_sum"),
legend=_DEP_REPLICA,
)
],
fill=1,
linewidth=1,
stack=False,
grid_pos=GridPos(12, 69, 12, 8),
),
Panel(
id=31,
title="Prefill Time",
description="",
unit="s",
targets=[
Target(
expr=_rate_with_join("ray_vllm_request_prefill_time_seconds_sum"),
legend=_DEP_REPLICA,
)
],
fill=1,
linewidth=1,
stack=False,
grid_pos=GridPos(0, 77, 12, 8),
),
Panel(
id=32,
title="Decode Time",
description="",
unit="s",
targets=[
Target(
expr=_rate_with_join("ray_vllm_request_decode_time_seconds_sum"),
legend=_DEP_REPLICA,
)
],
fill=1,
linewidth=1,
stack=False,
grid_pos=GridPos(12, 77, 12, 8),
),
]
# ===================================================================
# Row 6: NIXL
# ===================================================================
_nixl_panels = [
Panel(
id=34,
title="NIXL: Transfer Latency",
description="Average NIXL KV cache transfer latency in milliseconds.",
unit="ms",
targets=[
Target(
expr=_ratio_with_join_and_guard(
"ray_vllm_nixl_xfer_time_seconds_sum",
"ray_vllm_nixl_xfer_time_seconds_count",
scale="* 1000",
),
legend=_DEP_REPLICA,
),
],
fill=1,
linewidth=1,
stack=False,
grid_pos=GridPos(0, 86, 8, 8),
),
Panel(
id=35,
title="NIXL: Transfer Throughput",
description="NIXL KV cache transfer throughput in GB/s.",
unit="GBs",
targets=[
Target(
expr=_ratio_with_join_and_guard(
"ray_vllm_nixl_bytes_transferred_sum",
"ray_vllm_nixl_xfer_time_seconds_sum",
scale="/ 1024 / 1024 / 1024",
),
legend=_DEP_REPLICA,
),
],
fill=1,
linewidth=1,
stack=False,
grid_pos=GridPos(8, 86, 8, 8),
),
Panel(
id=36,
title="NIXL: Transfer Rate",
description="Number of NIXL KV cache transfers per second.",
unit="ops",
targets=[
Target(
expr=_rate_with_join("ray_vllm_nixl_xfer_time_seconds_count"),
legend=_DEP_REPLICA,
)
],
fill=1,
linewidth=1,
stack=False,
grid_pos=GridPos(16, 86, 8, 8),
),
Panel(
id=37,
title="NIXL: Avg Post Time",
description="Average time to post/initiate a NIXL transfer in milliseconds.",
unit="ms",
targets=[
Target(
expr=_ratio_with_join_and_guard(
"ray_vllm_nixl_post_time_seconds_sum",
"ray_vllm_nixl_post_time_seconds_count",
scale="* 1000",
),
legend=_DEP_REPLICA,
),
],
fill=1,
linewidth=1,
stack=False,
grid_pos=GridPos(0, 94, 8, 8),
),
Panel(
id=38,
title="NIXL: KV Transfer Failures",
description="Number of failed NIXL KV cache transfers. Any non-zero value is concerning and indicates RDMA transfer errors.",
unit="short",
targets=[
Target(
expr=_rate_with_join(
"ray_vllm_nixl_num_failed_transfers", agg_fn="increase"
),
legend=_DEP_REPLICA,
)
],
fill=1,
linewidth=1,
stack=False,
grid_pos=GridPos(8, 94, 8, 8),
),
Panel(
id=39,
title="NIXL: KV Expired Requests",
description="Number of requests whose KV blocks expired before decode consumed them. Spikes indicate prefill is outrunning decode or the timeout is too short.",
unit="short",
targets=[
Target(
expr=_rate_with_join(
"ray_vllm_nixl_num_kv_expired_reqs_total", agg_fn="increase"
),
legend=_DEP_REPLICA,
)
],
fill=1,
linewidth=1,
stack=False,
grid_pos=GridPos(16, 94, 8, 8),
),
]
# ===================================================================
# Row 7: Token Distribution (collapsed)
# ===================================================================
_WORKERID_FILTER = 'WorkerId=~"$workerid", {global_filters}'
_token_distribution_panels = [
Panel(
id=41,
title="Tokens Last 24 Hours",
description="",
unit="short",
targets=[
Target(
expr=f"(sum by (model_name) (delta(ray_vllm_prompt_tokens_total{{{{{_WORKERID_FILTER}}}}}[1d])))",
legend="Input: {{model_name}}",
),
Target(
expr=f"(sum by (model_name) (delta(ray_vllm_generation_tokens_total{{{{{_WORKERID_FILTER}}}}}[1d])))",
legend="Generated: {{model_name}}",
),
],
fill=1,
linewidth=2,
stack=False,
grid_pos=GridPos(0, 103, 12, 8),
template=PanelTemplate.STAT,
),
Panel(
id=42,
title="Tokens Last Hour",
description="",
unit="short",
targets=[
Target(
expr=f"sum by (model_name) (delta(ray_vllm_prompt_tokens_total{{{{{_WORKERID_FILTER}}}}}[1h]))",
legend="Input: {{model_name}}",
),
Target(
expr=f"sum by (model_name) (delta(ray_vllm_generation_tokens_total{{{{{_WORKERID_FILTER}}}}}[1h]))",
legend="Generated: {{model_name}}",
),
],
fill=1,
linewidth=2,
stack=False,
grid_pos=GridPos(12, 103, 12, 8),
template=PanelTemplate.STAT,
),
Panel(
id=43,
title="Ratio Input:Generated Tokens Last 24 Hours",
description="",
unit="short",
targets=[
Target(
expr=f"sum by (model_name) (delta(ray_vllm_prompt_tokens_total{{{{{_WORKERID_FILTER}}}}}[1d])) / sum by (model_name) (delta(ray_vllm_generation_tokens_total{{{{{_WORKERID_FILTER}}}}}[1d]))",
legend="{{model_name}}",
),
],
fill=1,
linewidth=2,
stack=False,
grid_pos=GridPos(0, 111, 12, 8),
template=PanelTemplate.STAT,
),
Panel(
id=44,
title="Distribution of Requests Per Model Last 24 Hours",
description="",
unit="Requests",
targets=[
Target(
expr=f"sum by (model_name) (delta(ray_vllm_request_success_total{{{{{_WORKERID_FILTER}}}}}[1d]))",
legend="{{model_name}}",
),
],
fill=1,
linewidth=2,
stack=False,
grid_pos=GridPos(12, 111, 12, 8),
template=PanelTemplate.PIE_CHART,
),
Panel(
id=45,
title="Peak Tokens Per Second Per Model Last 24 Hours",
description="",
unit="short",
targets=[
Target(
expr=f"max_over_time(sum by (model_name) (rate(ray_vllm_generation_tokens_total{{{{{_WORKERID_FILTER}}}}}[2m]))[24h:1m])",
legend="{{model_name}}",
),
],
fill=1,
linewidth=2,
stack=False,
grid_pos=GridPos(0, 119, 12, 8),
template=PanelTemplate.STAT,
),
Panel(
id=46,
title="Tokens Per Model Last 24 Hours",
description="",
unit="short",
targets=[
Target(
expr=f"sum by (model_name) (delta(ray_vllm_prompt_tokens_total{{{{{_WORKERID_FILTER}}}}}[1d])) + sum by (model_name) (delta(ray_vllm_generation_tokens_total{{{{{_WORKERID_FILTER}}}}}[1d]))",
legend="{{model_name}}",
),
],
fill=1,
linewidth=2,
stack=False,
grid_pos=GridPos(12, 119, 12, 8),
template=PanelTemplate.STAT,
),
Panel(
id=47,
title="Avg Total Tokens Per Request Last 7 Days",
description="",
unit="short",
targets=[
Target(
expr=(
f"(sum by (model_name) (delta(ray_vllm_prompt_tokens_total{{{{{_WORKERID_FILTER}}}}}[1w])) +\n"
f"sum by (model_name) (delta(ray_vllm_generation_tokens_total{{{{{_WORKERID_FILTER}}}}}[1w])))"
f" / sum by (model_name) (delta(ray_vllm_request_success_total{{{{{_WORKERID_FILTER}}}}}[1w]))"
),
legend="{{ model_name}}",
),
],
fill=1,
linewidth=2,
stack=False,
grid_pos=GridPos(0, 127, 12, 8),
template=PanelTemplate.GAUGE,
),
Panel(
id=48,
title="Requests Per Model Last Week",
description="",
unit="short",
targets=[
Target(
expr=f"sum by (model_name) (delta(ray_vllm_request_success_total{{{{{_WORKERID_FILTER}}}}}[1w]))",
legend="{{ model_name}}",
),
],
fill=1,
linewidth=2,
stack=False,
grid_pos=GridPos(12, 127, 12, 8),
template=PanelTemplate.GAUGE,
),
Panel(
id=49,
title="Tokens Per Model Last 7 Days",
description="",
unit="short",
targets=[
Target(
expr=f"sum by (model_name) (delta(ray_vllm_prompt_tokens_total{{{{{_WORKERID_FILTER}}}}}[1w]))",
legend="In: {{ model_name}}",
),
Target(
expr=f"sum by (model_name) (delta(ray_vllm_generation_tokens_total{{{{{_WORKERID_FILTER}}}}}[1w]))",
legend="Out: {{ model_name }}",
),
],
fill=1,
linewidth=2,
stack=False,
grid_pos=GridPos(0, 135, 12, 8),
template=PanelTemplate.GAUGE,
),
Panel(
id=50,
title="Avg Total Tokens Per Request Per Model Last 7 Days",
description="",
unit="short",
targets=[
Target(
expr=(
f"(sum by (model_name) (delta(ray_vllm_prompt_tokens_total{{{{{_WORKERID_FILTER}}}}}[1w])) "
f"+ sum by (model_name) (delta(ray_vllm_generation_tokens_total{{{{{_WORKERID_FILTER}}}}}[1w])))"
f"/ sum by (model_name) (delta(ray_vllm_request_success_total{{{{{_WORKERID_FILTER}}}}}[1w]))"
),
legend="{{ model_name}}",
),
],
fill=1,
linewidth=2,
stack=False,
grid_pos=GridPos(12, 135, 12, 8),
template=PanelTemplate.GAUGE,
),
Panel(
id=51,
title="Tokens Per Request Per Model Last 7 Days",
description="",
unit="short",
targets=[
Target(
expr=f"sum by (model_name) (delta(ray_vllm_prompt_tokens_total{{{{{_WORKERID_FILTER}}}}}[1w])) / sum by (model_name) (delta(ray_vllm_request_success_total{{{{{_WORKERID_FILTER}}}}}[1w]))",
legend="In: {{ model_name}}",
),
Target(
expr=f"sum by (model_name) (delta(ray_vllm_generation_tokens_total{{{{{_WORKERID_FILTER}}}}}[1w])) / sum by (model_name) (delta(ray_vllm_request_success_total{{{{{_WORKERID_FILTER}}}}}[1w]))",
legend="Out: {{ model_name}}",
),
],
fill=1,
linewidth=2,
stack=False,
grid_pos=GridPos(0, 143, 12, 8),
template=PanelTemplate.GAUGE,
),
]
# ===================================================================
# Assemble rows and config
# ===================================================================
_ALL_ROWS = [
Row(title="Throughput", id=501, panels=_throughput_panels),
Row(title="Latency", id=502, panels=_latency_panels_list),
Row(title="Cache", id=503, panels=_cache_panels),
Row(title="Request Length", id=504, panels=_request_length_panels),
Row(title="Scheduler", id=505, panels=_scheduler_panels),
Row(title="NIXL", id=506, panels=_nixl_panels),
Row(
title="Token Distribution",
id=507,
collapsed=True,
panels=_token_distribution_panels,
),
]
# Validate uniqueness of panel IDs across all rows
_all_ids = sorted(panel.id for row in _ALL_ROWS for panel in row.panels)
assert len(_all_ids) == len(
set(_all_ids)
), f"Duplicated id found. Use unique id for each panel. {_all_ids}"
serve_llm_dashboard_config = DashboardConfig(
name="SERVE_LLM",
default_uid="rayServeLlmDashboard",
standard_global_filters=[
'ray_io_cluster=~"$Cluster"',
],
base_json_file_name="serve_llm_grafana_dashboard_base.json",
rows=_ALL_ROWS,
)
@@ -0,0 +1,199 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 1,
"iteration": 1667344411089,
"links": [],
"panels": [],
"refresh": false,
"schemaVersion": 27,
"style": "dark",
"tags": [],
"templating": {
"list": [
{
"current": {
"selected": false
},
"description": "Filter queries of a specific Prometheus type.",
"hide": 2,
"includeAll": false,
"multi": false,
"name": "datasource",
"options": [],
"query": "prometheus",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"type": "datasource"
},
{
"name": "vllm_model_name",
"label": "vLLM Model Name",
"type": "query",
"hide": 0,
"datasource": "${datasource}",
"definition": "label_values(ray_vllm_request_prompt_tokens_sum{{{global_filters}}}, model_name)",
"query": {
"query": "label_values(ray_vllm_request_prompt_tokens_sum{{{global_filters}}}, model_name)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"includeAll": true,
"multi": false,
"allValue": ".*",
"current": {
"selected": true,
"text": [
"All"
],
"value": [
"$__all"
]
}
},
{
"name": "workerid",
"label": "Worker ID",
"type": "query",
"hide": 0,
"datasource": "${datasource}",
"definition": "label_values(ray_vllm_request_prompt_tokens_sum{{{global_filters}}}, WorkerId)",
"query": {
"query": "label_values(ray_vllm_request_prompt_tokens_sum{{{global_filters}}}, WorkerId)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"includeAll": true,
"multi": false,
"allValue": ".*",
"current": {
"selected": true,
"text": [
"All"
],
"value": [
"$__all"
]
}
},
{
"name": "deployment",
"label": "Deployment",
"type": "query",
"hide": 0,
"datasource": "${datasource}",
"definition": "label_values(ray_serve_deployment_request_counter_total{{{global_filters}}}, deployment)",
"query": {
"query": "label_values(ray_serve_deployment_request_counter_total{{{global_filters}}}, deployment)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"includeAll": true,
"multi": true,
"allValue": ".*",
"current": {
"selected": true,
"text": [
"All"
],
"value": [
"$__all"
]
}
},
{
"allValue": ".*",
"current": {
"selected": false
},
"datasource": "${datasource}",
"definition": "label_values(ray_node_network_receive_speed{{{global_filters}}}, ray_io_cluster)",
"description": "Filter queries to specific Ray clusters for KubeRay. When ingesting metrics across multiple ray clusters, the ray_io_cluster label should be set per cluster. For KubeRay users, this is done automatically with Prometheus PodMonitor.",
"error": null,
"hide": 0,
"includeAll": true,
"label": null,
"multi": false,
"name": "Cluster",
"options": [],
"query": {
"query": "label_values(ray_node_network_receive_speed{{{global_filters}}}, ray_io_cluster)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 2,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"name": "interval",
"label": "Interval",
"type": "custom",
"hide": 0,
"includeAll": false,
"multi": false,
"options": [
{
"selected": true,
"text": "30s",
"value": "30s"
},
{
"selected": false,
"text": "1m",
"value": "1m"
},
{
"selected": false,
"text": "5m",
"value": "5m"
},
{
"selected": false,
"text": "10m",
"value": "10m"
},
{
"selected": false,
"text": "15m",
"value": "15m"
}
],
"current": {
"selected": true,
"text": "5m",
"value": "5m"
}
}
]
},
"rayMeta": ["excludesSystemRoutes"],
"time": {
"from": "now-30m",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "Serve LLM Dashboard",
"uid": "rayServeLlmDashboard",
"version": 1
}
@@ -0,0 +1,346 @@
# flake8: noqa E501
from ray.dashboard.modules.metrics.dashboards.common import (
DashboardConfig,
Panel,
Row,
Target,
)
# Ray Train Metrics (Controller)
CONTROLLER_STATE_PANEL = Panel(
id=1,
title="Controller State",
description="Current state of the Ray Train controller.",
unit="",
targets=[
Target(
expr='sum(ray_train_controller_state{{ray_train_run_name=~"$TrainRunName", ray_train_run_id=~"$TrainRunId", {global_filters}}}) by (ray_train_run_name, ray_train_controller_state)',
legend="Run Name: {{ray_train_run_name}}, Controller State: {{ray_train_controller_state}}",
),
],
)
CONTROLLER_OPERATION_TIME_PANEL = Panel(
id=2,
title="Cumulative Worker Group Start/Shutdown Time",
description="Cumulative time the controller spends starting and shutting down worker groups (re-created on worker failures and resizes).",
unit="seconds",
targets=[
Target(
expr='sum(ray_train_worker_group_start_total_time_s{{ray_train_run_name=~"$TrainRunName", ray_train_run_id=~"$TrainRunId", {global_filters}}}) by (ray_train_run_name)',
legend="Run Name: {{ray_train_run_name}}, Worker Group Start Time",
),
Target(
expr='sum(ray_train_worker_group_shutdown_total_time_s{{ray_train_run_name=~"$TrainRunName", ray_train_run_id=~"$TrainRunId", {global_filters}}}) by (ray_train_run_name)',
legend="Run Name: {{ray_train_run_name}}, Worker Group Shutdown Time",
),
],
fill=0,
stack=False,
)
# Ray Train Metrics (Worker)
WORKER_TRAIN_REPORT_TIME_PANEL = Panel(
id=3,
title="Cumulative Time in ray.train.report",
description="Cumulative time workers spend blocked inside `ray.train.report()`. This includes the cross-rank checkpoint directory sync barrier, the checkpoint file transfer to storage, and the time waiting for the report queue ordering. See the Checkpoint Sync and Checkpoint Transfer panels for a breakdown.",
unit="seconds",
targets=[
Target(
expr='sum(ray_train_report_total_blocked_time_s{{ray_train_run_name=~"$TrainRunName", ray_train_run_id=~"$TrainRunId", ray_train_worker_world_rank=~"$TrainWorkerWorldRank", ray_train_worker_actor_id=~"$TrainWorkerActorId", {global_filters}}}) by (ray_train_run_name, ray_train_worker_world_rank, ray_train_worker_actor_id)',
legend="Run Name: {{ray_train_run_name}}, World Rank: {{ray_train_worker_world_rank}}",
)
],
fill=0,
stack=False,
)
WORKER_CHECKPOINT_SYNC_TIME_PANEL = Panel(
id=16,
title="Cumulative Checkpoint Sync Time",
description="Cumulative time spent in the cross-rank barrier that synchronizes the checkpoint directory name across all workers. High values indicate workers are spending significant time waiting for each other to reach the synchronization point.",
unit="seconds",
targets=[
Target(
expr='sum(ray_train_checkpoint_sync_total_time_s{{ray_train_run_name=~"$TrainRunName", ray_train_run_id=~"$TrainRunId", ray_train_worker_world_rank=~"$TrainWorkerWorldRank", ray_train_worker_actor_id=~"$TrainWorkerActorId", {global_filters}}}) by (ray_train_run_name, ray_train_worker_world_rank, ray_train_worker_actor_id)',
legend="Run Name: {{ray_train_run_name}}, World Rank: {{ray_train_worker_world_rank}}",
)
],
fill=0,
stack=False,
)
WORKER_CHECKPOINT_TRANSFER_TIME_PANEL = Panel(
id=17,
title="Cumulative Checkpoint Transfer Time",
description="Cumulative time spent transferring checkpoint files to storage. High values indicate slow storage throughput or large checkpoint sizes.",
unit="seconds",
targets=[
Target(
expr='sum(ray_train_checkpoint_transfer_total_time_s{{ray_train_run_name=~"$TrainRunName", ray_train_run_id=~"$TrainRunId", ray_train_worker_world_rank=~"$TrainWorkerWorldRank", ray_train_worker_actor_id=~"$TrainWorkerActorId", {global_filters}}}) by (ray_train_run_name, ray_train_worker_world_rank, ray_train_worker_actor_id)',
legend="Run Name: {{ray_train_run_name}}, World Rank: {{ray_train_worker_world_rank}}",
)
],
fill=0,
stack=False,
)
# Core System Resources
CPU_UTILIZATION_PANEL = Panel(
id=4,
title="CPU Usage",
description="CPU core utilization across all workers.",
unit="cores",
targets=[
Target(
expr='sum(ray_node_cpu_utilization{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}} * ray_node_cpu_count{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}} / 100) by (instance, RayNodeType)',
legend="CPU Usage: {{instance}} ({{RayNodeType}})",
),
Target(
expr='sum(ray_node_cpu_count{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}})',
legend="MAX",
),
],
)
MEMORY_UTILIZATION_PANEL = Panel(
id=5,
title="Total Memory Usage",
description="Total physical memory used vs total available memory.",
unit="bytes",
targets=[
Target(
expr='sum(ray_node_mem_used{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType)',
legend="Memory Used: {{instance}} ({{RayNodeType}})",
),
Target(
expr='sum(ray_node_mem_total{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}})',
legend="MAX",
),
],
)
MEMORY_DETAILED_PANEL = Panel(
id=6,
title="Memory Allocation Details",
description="Memory allocation details including available and shared memory.",
unit="bytes",
targets=[
Target(
expr='sum(ray_node_mem_available{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType)',
legend="Available Memory: {{instance}} ({{RayNodeType}})",
),
Target(
expr='sum(ray_node_mem_shared_bytes{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType)',
legend="Shared Memory: {{instance}} ({{RayNodeType}})",
),
],
)
# GPU Resources
# TODO: Add GPU Device/Index as a filter.
GPU_UTILIZATION_PANEL = Panel(
id=7,
title="GPU Usage",
description="GPU utilization across all workers.",
unit="GPUs",
targets=[
Target(
expr='sum(ray_node_gpus_utilization{{instance=~"$Instance", RayNodeType=~"$RayNodeType", GpuIndex=~"$GpuIndex", GpuDeviceName=~"$GpuDeviceName", {global_filters}}} / 100) by (instance, RayNodeType, GpuIndex, GpuDeviceName)',
legend="GPU Usage: {{instance}} ({{RayNodeType}}), gpu.{{GpuIndex}}, {{GpuDeviceName}}",
),
Target(
expr='sum(ray_node_gpus_available{{instance=~"$Instance", RayNodeType=~"$RayNodeType", GpuIndex=~"$GpuIndex", GpuDeviceName=~"$GpuDeviceName", {global_filters}}})',
legend="MAX",
),
],
)
GPU_MEMORY_UTILIZATION_PANEL = Panel(
id=8,
title="GPU Memory Usage",
description="GPU memory usage across all workers.",
unit="bytes",
targets=[
Target(
expr='sum(ray_node_gram_used{{instance=~"$Instance", RayNodeType=~"$RayNodeType", GpuIndex=~"$GpuIndex", GpuDeviceName=~"$GpuDeviceName", {global_filters}}} * 1024 * 1024) by (instance, RayNodeType, GpuIndex, GpuDeviceName)',
legend="Used GRAM: {{instance}} ({{RayNodeType}}), gpu.{{GpuIndex}}, {{GpuDeviceName}}",
),
Target(
expr='(sum(ray_node_gram_available{{instance=~"$Instance", RayNodeType=~"$RayNodeType", GpuIndex=~"$GpuIndex", GpuDeviceName=~"$GpuDeviceName", {global_filters}}}) + sum(ray_node_gram_used{{instance=~"$Instance", RayNodeType=~"$RayNodeType", GpuIndex=~"$GpuIndex", GpuDeviceName=~"$GpuDeviceName", {global_filters}}})) * 1024 * 1024',
legend="MAX",
),
],
)
# Storage Resources
DISK_UTILIZATION_PANEL = Panel(
id=9,
title="Disk Space Usage",
description="Disk space usage across all workers.",
unit="bytes",
targets=[
Target(
expr='sum(ray_node_disk_usage{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType)',
legend="Disk Used: {{instance}} ({{RayNodeType}})",
),
Target(
expr='sum(ray_node_disk_free{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) + sum(ray_node_disk_usage{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}})',
legend="MAX",
),
],
)
DISK_THROUGHPUT_PANEL = Panel(
id=10,
title="Disk Throughput",
description="Current disk read/write throughput.",
unit="Bps",
targets=[
Target(
expr='sum(ray_node_disk_io_read_speed{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType)',
legend="Read Speed: {{instance}} ({{RayNodeType}})",
),
Target(
expr='sum(ray_node_disk_io_write_speed{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType)',
legend="Write Speed: {{instance}} ({{RayNodeType}})",
),
],
)
DISK_OPERATIONS_PANEL = Panel(
id=11,
title="Disk Operations",
description="Current disk read/write operations per second.",
unit="ops/s",
targets=[
Target(
expr='sum(ray_node_disk_read_iops{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType)',
legend="Read IOPS: {{instance}} ({{RayNodeType}})",
),
Target(
expr='sum(ray_node_disk_write_iops{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType)',
legend="Write IOPS: {{instance}} ({{RayNodeType}})",
),
],
)
# Network Resources
NETWORK_THROUGHPUT_PANEL = Panel(
id=12,
title="Network Throughput",
description="Current network send/receive throughput.",
unit="Bps",
targets=[
Target(
expr='sum(ray_node_network_receive_speed{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType)',
legend="Receive Speed: {{instance}} ({{RayNodeType}})",
),
Target(
expr='sum(ray_node_network_send_speed{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType)',
legend="Send Speed: {{instance}} ({{RayNodeType}})",
),
],
)
NETWORK_TOTAL_PANEL = Panel(
id=13,
title="Network Total Traffic",
description="Total network traffic sent/received.",
unit="bytes",
targets=[
Target(
expr='sum(ray_node_network_sent{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType)',
legend="Total Sent: {{instance}} ({{RayNodeType}})",
),
Target(
expr='sum(ray_node_network_received{{instance=~"$Instance", RayNodeType=~"$RayNodeType", {global_filters}}}) by (instance, RayNodeType)',
legend="Total Received: {{instance}} ({{RayNodeType}})",
),
],
)
TRAIN_GRAFANA_PANELS = []
TRAIN_GRAFANA_ROWS = [
# Train Metrics Row
Row(
title="Train Metrics",
id=14,
panels=[
# Ray Train Metrics (Controller)
CONTROLLER_STATE_PANEL,
CONTROLLER_OPERATION_TIME_PANEL,
# Ray Train Metrics (Worker)
WORKER_TRAIN_REPORT_TIME_PANEL,
WORKER_CHECKPOINT_SYNC_TIME_PANEL,
WORKER_CHECKPOINT_TRANSFER_TIME_PANEL,
],
collapsed=False,
),
# System Resources Row
Row(
title="Resource Utilization",
id=15,
panels=[
CPU_UTILIZATION_PANEL,
MEMORY_UTILIZATION_PANEL,
MEMORY_DETAILED_PANEL,
# GPU Resources
GPU_UTILIZATION_PANEL,
GPU_MEMORY_UTILIZATION_PANEL,
# Storage Resources
DISK_UTILIZATION_PANEL,
DISK_THROUGHPUT_PANEL,
DISK_OPERATIONS_PANEL,
# Network Resources
NETWORK_THROUGHPUT_PANEL,
NETWORK_TOTAL_PANEL,
],
collapsed=True,
),
]
TRAIN_RUN_PANELS = [
# Ray Train Metrics (Controller)
CONTROLLER_STATE_PANEL,
CONTROLLER_OPERATION_TIME_PANEL,
# Ray Train Metrics (Worker)
WORKER_TRAIN_REPORT_TIME_PANEL,
]
TRAIN_WORKER_PANELS = [
# Ray Train Metrics (Worker)
WORKER_TRAIN_REPORT_TIME_PANEL,
WORKER_CHECKPOINT_SYNC_TIME_PANEL,
WORKER_CHECKPOINT_TRANSFER_TIME_PANEL,
# Core System Resources
CPU_UTILIZATION_PANEL,
MEMORY_UTILIZATION_PANEL,
# GPU Resources
GPU_UTILIZATION_PANEL,
GPU_MEMORY_UTILIZATION_PANEL,
# Storage Resources
DISK_UTILIZATION_PANEL,
# Network Resources
NETWORK_THROUGHPUT_PANEL,
]
# Get all panel IDs from both top-level panels and panels within rows
all_panel_ids = [panel.id for panel in TRAIN_GRAFANA_PANELS]
for row in TRAIN_GRAFANA_ROWS:
all_panel_ids.append(row.id)
all_panel_ids.extend(panel.id for panel in row.panels)
all_panel_ids.sort()
assert len(all_panel_ids) == len(
set(all_panel_ids)
), f"Duplicated id found. Use unique id for each panel. {all_panel_ids}"
train_dashboard_config = DashboardConfig(
name="TRAIN",
default_uid="rayTrainDashboard",
rows=TRAIN_GRAFANA_ROWS,
standard_global_filters=['SessionName=~"$SessionName"'],
base_json_file_name="train_grafana_dashboard_base.json",
)
@@ -0,0 +1,267 @@
{
"title": "Train Dashboard",
"uid": "rayTrainDashboard",
"version": 1,
"schemaVersion": 27,
"style": "dark",
"editable": true,
"graphTooltip": 1,
"refresh": false,
"panels": [],
"time": {
"from": "now-30m",
"to": "now"
},
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"templating": {
"list": [
{
"name": "datasource",
"type": "datasource",
"description": "Filter queries of a specific Prometheus type.",
"datasource": null,
"query": "prometheus",
"refresh": 1,
"hide": 2,
"includeAll": false,
"multi": false,
"current": {
"selected": false
}
},
{
"name": "SessionName",
"type": "query",
"description": "Filter queries to specific ray sessions.",
"datasource": "${datasource}",
"definition": "label_values(ray_train_worker_group_start_total_time_s{{{global_filters}}}, SessionName)",
"query": {
"query": "label_values(ray_train_worker_group_start_total_time_s{{{global_filters}}}, SessionName)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"hide": 0,
"includeAll": true,
"multi": false,
"allValue": ".*",
"sort": 2,
"current": {
"selected": true,
"text": ["All"],
"value": ["$__all"]
}
},
{
"name": "TrainRunName",
"type": "query",
"description": "Filter queries to specific Ray Train run names.",
"datasource": "${datasource}",
"definition": "label_values(ray_train_worker_group_start_total_time_s{{{global_filters}}}, ray_train_run_name)",
"query": {
"query": "label_values(ray_train_worker_group_start_total_time_s{{{global_filters}}}, ray_train_run_name)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"hide": 0,
"includeAll": true,
"multi": false,
"allValue": ".*",
"sort": 2,
"current": {
"selected": true,
"text": ["All"],
"value": ["$__all"]
}
},
{
"name": "TrainRunId",
"type": "query",
"description": "Filter queries to specific Ray Train run ids.",
"datasource": "${datasource}",
"definition": "label_values(ray_train_worker_group_start_total_time_s{{{global_filters}}}, ray_train_run_id)",
"query": {
"query": "label_values(ray_train_worker_group_start_total_time_s{{{global_filters}}}, ray_train_run_id)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"hide": 2,
"includeAll": true,
"multi": false,
"allValue": ".*",
"sort": 2,
"current": {
"selected": true,
"text": ["All"],
"value": ["$__all"]
}
},
{
"name": "TrainWorkerWorldRank",
"type": "query",
"description": "Filter queries to specific Ray Train worker world ranks.",
"datasource": "${datasource}",
"definition": "label_values(ray_train_report_total_blocked_time_s{{{global_filters}}}, ray_train_worker_world_rank)",
"query": {
"query": "label_values(ray_train_report_total_blocked_time_s{{{global_filters}}}, ray_train_worker_world_rank)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"hide": 0,
"includeAll": true,
"multi": false,
"allValue": ".*",
"sort": 2,
"current": {
"selected": true,
"text": ["All"],
"value": ["$__all"]
}
},
{
"name": "TrainWorkerActorId",
"type": "query",
"description": "Filter queries to specific Ray Train worker actor ids.",
"datasource": "${datasource}",
"definition": "label_values(ray_train_report_total_blocked_time_s{{{global_filters}}}, ray_train_worker_actor_id)",
"query": {
"query": "label_values(ray_train_report_total_blocked_time_s{{{global_filters}}}, ray_train_worker_actor_id)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"hide": 2,
"includeAll": true,
"multi": false,
"allValue": ".*",
"sort": 2,
"current": {
"selected": true,
"text": ["All"],
"value": ["$__all"]
}
},
{
"name": "Instance",
"type": "query",
"description": "Filter queries to specific node instances.",
"datasource": "${datasource}",
"definition": "label_values(ray_node_network_receive_speed{{{global_filters}}}, instance)",
"query": {
"query": "label_values(ray_node_network_receive_speed{{{global_filters}}}, instance)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"hide": 2,
"includeAll": true,
"multi": false,
"allValue": ".*",
"sort": 2,
"current": {
"selected": true,
"text": ["All"],
"value": ["$__all"]
}
},
{
"name": "GpuIndex",
"type": "query",
"description": "Filter queries to specific GPU indices.",
"datasource": "${datasource}",
"definition": "label_values(ray_node_gpus_utilization{{{global_filters}}}, GpuIndex)",
"query": {
"query": "label_values(ray_node_gpus_utilization{{{global_filters}}}, GpuIndex)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"hide": 2,
"includeAll": true,
"multi": true,
"allValue": ".*",
"sort": 2,
"current": {
"selected": true,
"text": ["All"],
"value": ["$__all"]
}
},
{
"name": "GpuDeviceName",
"type": "query",
"description": "Filter queries to specific GPU device names.",
"datasource": "${datasource}",
"definition": "label_values(ray_node_gpus_utilization{{{global_filters}}}, GpuDeviceName)",
"query": {
"query": "label_values(ray_node_gpus_utilization{{{global_filters}}}, GpuDeviceName)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"hide": 2,
"includeAll": true,
"multi": true,
"allValue": ".*",
"sort": 2,
"current": {
"selected": true,
"text": ["All"],
"value": ["$__all"]
}
},
{
"current": {
"text": [
"All"
],
"value": [
"$__all"
]
},
"description": "Filter queries to specific Ray node types (head or worker).",
"includeAll": true,
"multi": true,
"name": "RayNodeType",
"options": [
{
"selected": false,
"text": "All",
"value": "$__all"
},
{
"selected": false,
"text": "Head Node",
"value": "head"
},
{
"selected": false,
"text": "Worker Node",
"value": "worker"
}
],
"query": "head, worker",
"type": "custom"
}
]
}
}
@@ -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
@@ -0,0 +1,12 @@
# my global config
global:
scrape_interval: 10s # Set the scrape interval to every 10 seconds. Default is every 1 minute.
evaluation_interval: 10s # Evaluate rules every 10 seconds. The default is every 1 minute.
# scrape_timeout is set to the global default (10s).
scrape_configs:
# Scrape from each Ray node as defined in the service_discovery.json provided by Ray.
- job_name: 'ray'
file_sd_configs:
- files:
- '/tmp/ray/prom_metrics_service_discovery.json'
@@ -0,0 +1,541 @@
import copy
import json
import math
import os
import re
from dataclasses import asdict
from typing import List, Tuple
import ray
from ray.dashboard.modules.metrics.dashboards.common import (
DashboardConfig,
Panel,
PanelTemplate,
)
from ray.dashboard.modules.metrics.dashboards.data_dashboard_panels import (
data_dashboard_config,
)
from ray.dashboard.modules.metrics.dashboards.data_llm_dashboard_panels import (
data_llm_dashboard_config,
)
from ray.dashboard.modules.metrics.dashboards.default_dashboard_panels import (
default_dashboard_config,
)
from ray.dashboard.modules.metrics.dashboards.serve_deployment_dashboard_panels import (
serve_deployment_dashboard_config,
)
from ray.dashboard.modules.metrics.dashboards.serve_llm_dashboard_panels import (
serve_llm_dashboard_config,
)
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 = (
"RAY_GRAFANA_{name}_DASHBOARD_GLOBAL_FILTERS"
)
GRAFANA_DASHBOARD_LOG_LINK_URL_ENV_VAR_TEMPLATE = "RAY_GRAFANA_{name}_LOG_LINK_URL"
# Grafana dashboard layout constants
# Dashboard uses a 24-column grid with 2-column panels
ROW_WIDTH = 24 # Full dashboard width
PANELS_PER_ROW = 2
PANEL_WIDTH = ROW_WIDTH // PANELS_PER_ROW # Width of each panel
PANEL_HEIGHT = 8 # Height of each panel
ROW_HEIGHT = 1 # Height of row container
def _read_configs_for_dashboard(
dashboard_config: DashboardConfig,
) -> Tuple[str, List[str], str]:
"""Reads environment variable configs for overriding uid, global_filters, and the log link URL for a given dashboard.
Args:
dashboard_config: The dashboard whose env-var overrides are read.
``dashboard_config.name`` selects the env-var suffix and
``default_uid`` is used as a fallback.
Returns:
Tuple with format uid, global_filters, log_link_url
"""
uid = (
os.environ.get(
GRAFANA_DASHBOARD_UID_OVERRIDE_ENV_VAR_TEMPLATE.format(
name=dashboard_config.name
)
)
or dashboard_config.default_uid
)
global_filters_str = (
os.environ.get(
GRAFANA_DASHBOARD_GLOBAL_FILTERS_OVERRIDE_ENV_VAR_TEMPLATE.format(
name=dashboard_config.name
)
)
or ""
)
if global_filters_str == "":
global_filters = []
else:
global_filters = global_filters_str.split(",")
log_link_url = (
os.environ.get(
GRAFANA_DASHBOARD_LOG_LINK_URL_ENV_VAR_TEMPLATE.format(
name=dashboard_config.name
)
)
or ""
)
return uid, global_filters, log_link_url
def generate_default_grafana_dashboard() -> Tuple[str, str]:
"""
Generates the dashboard output for the default dashboard and returns
both the content and the uid.
Returns:
Tuple with format content, uid
"""
return _generate_grafana_dashboard(default_dashboard_config)
def generate_serve_grafana_dashboard() -> Tuple[str, str]:
"""
Generates the dashboard output for the serve dashboard and returns
both the content and the uid.
Returns:
Tuple with format content, uid
"""
return _generate_grafana_dashboard(get_serve_dashboard_config())
def generate_serve_deployment_grafana_dashboard() -> Tuple[str, str]:
"""
Generates the dashboard output for the serve dashboard and returns
both the content and the uid.
Returns:
Tuple with format content, uid
"""
return _generate_grafana_dashboard(serve_deployment_dashboard_config)
def generate_serve_llm_grafana_dashboard() -> Tuple[str, str]:
"""
Generates the dashboard output for the serve dashboard and returns
both the content and the uid.
Returns:
Tuple with format content, uid
"""
return _generate_grafana_dashboard(serve_llm_dashboard_config)
def generate_data_grafana_dashboard() -> Tuple[str, str]:
"""
Generates the dashboard output for the data dashboard and returns
both the content and the uid.
Returns:
Tuple with format content, uid
"""
return _generate_grafana_dashboard(data_dashboard_config)
def generate_data_llm_grafana_dashboard() -> Tuple[str, str]:
"""
Generates the dashboard output for the Data LLM dashboard and returns
both the content and the uid.
This dashboard provides vLLM metrics visibility for Ray Data LLM workloads,
including latency (TTFT, TPOT), throughput, cache utilization, and
prefix cache hit rate.
Returns:
Tuple with format content, uid
"""
return _generate_grafana_dashboard(data_llm_dashboard_config)
def generate_train_grafana_dashboard() -> Tuple[str, str]:
"""
Generates the dashboard output for the train dashboard and returns
both the content and the uid.
Returns:
Tuple with format content, uid
"""
return _generate_grafana_dashboard(train_dashboard_config)
def _generate_grafana_dashboard(dashboard_config: DashboardConfig) -> str:
"""Render the Grafana dashboard JSON for the given config.
Args:
dashboard_config: Configuration describing the panels and base
template JSON file to use for rendering.
Returns:
Tuple with format dashboard_content, uid
"""
uid, global_filters, log_link_url = _read_configs_for_dashboard(dashboard_config)
panels = _generate_grafana_panels(dashboard_config, global_filters, log_link_url)
base_file_name = dashboard_config.base_json_file_name
base_json = json.load(
open(os.path.join(os.path.dirname(__file__), "dashboards", base_file_name))
)
base_json["panels"] = panels
# Update variables to use global_filters
global_filters_str = ",".join(global_filters)
variables = base_json.get("templating", {}).get("list", [])
for variable in variables:
if "definition" not in variable:
continue
definition = variable["definition"].format(global_filters=global_filters_str)
query = variable["query"]["query"].format(global_filters=global_filters_str)
if not global_filters_str:
definition = _clean_empty_filters(definition)
query = _clean_empty_filters(query)
variable["definition"] = definition
variable["query"]["query"] = query
tags = base_json.get("tags", []) or []
tags.append(f"rayVersion:{ray.__version__}")
base_json["tags"] = tags
base_json["uid"] = uid
# Ray metadata can be used to put arbitrary metadata
ray_meta = base_json.get("rayMeta", []) or []
ray_meta.append("supportsGlobalFilterOverride")
base_json["rayMeta"] = ray_meta
return json.dumps(base_json, indent=4), uid
def _generate_panel_template(
panel: Panel,
panel_global_filters: List[str],
panel_index: int,
base_y_position: int,
log_link_url: str,
) -> dict:
"""
Helper method to generate a panel template with common configuration.
Args:
panel: The panel configuration
panel_global_filters: List of global filters to apply
panel_index: The index of the panel within its row (0-based)
base_y_position: The base y-coordinate for the row in the dashboard grid
log_link_url: The URL to the log link for the panel
Returns:
dict: The configured panel template
"""
# Create base template from panel configuration
template = copy.deepcopy(panel.template.value)
template.update(
{
"title": panel.title,
"description": panel.description,
"id": panel.id,
"targets": _generate_targets(panel, panel_global_filters),
}
)
# Set panel position and dimensions
if panel.grid_pos:
template["gridPos"] = asdict(panel.grid_pos)
else:
# Calculate panel position in 2-column grid layout
# x: 0 or 12 (left or right column)
# y: base position + (row number * panel height)
row_number = panel_index // PANELS_PER_ROW
template["gridPos"] = {
"h": PANEL_HEIGHT,
"w": PANEL_WIDTH,
"x": PANEL_WIDTH * (panel_index % PANELS_PER_ROW),
"y": base_y_position + (row_number * PANEL_HEIGHT),
}
# 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 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
# Add log link if URL is provided via environment variable.
if log_link_url:
template["links"] = [
{
"targetBlank": True,
"title": "View Logs",
"url": log_link_url,
}
]
return template
def _create_row_panel(row: Panel, y_position: int) -> dict:
"""
Creates a Grafana row panel that spans the full dashboard width.
Row panels can be collapsed to hide their contained panels.
Args:
row: Row config with title, id, and collapse state
y_position: Vertical position in dashboard grid
Returns:
Grafana row panel configuration
"""
return {
"collapsed": row.collapsed,
"gridPos": {"h": ROW_HEIGHT, "w": ROW_WIDTH, "x": 0, "y": y_position},
"id": row.id,
"title": row.title,
"type": "row",
"panels": [],
}
def _calculate_panel_heights(num_panels: int) -> int:
"""
Calculate the total height needed for a set of panels.
Args:
num_panels: Number of panels to position
Returns:
Total height needed for the panels
"""
rows_needed = math.ceil(num_panels / PANELS_PER_ROW)
return rows_needed * PANEL_HEIGHT
def _generate_grafana_panels(
config: DashboardConfig, global_filters: List[str], log_link_url: str
) -> List[dict]:
"""
Generates Grafana panel configurations for a dashboard.
The dashboard layout follows these rules:
- Panels are arranged in 2 columns (12 units wide each)
- Each panel is 8 units high
- Rows are 1 unit high and can be collapsed
- Panels within rows follow the same 2-column layout
- Panel positions can be overridden via panel.grid_pos or auto-calculated
Args:
config: Dashboard configuration containing panels and rows
global_filters: List of filters to apply to all panels
log_link_url: Optional URL for panel log links. When set, each panel
gets a "View Logs" link pointing to this URL.
Returns:
List of Grafana panel configurations for the dashboard
"""
panels = []
panel_global_filters = [*config.standard_global_filters, *global_filters]
current_y_position = 0
# Add top-level panels in 2-column grid
for panel_index, panel in enumerate(config.panels):
panel_template = _generate_panel_template(
panel, panel_global_filters, panel_index, current_y_position, log_link_url
)
panels.append(panel_template)
# Calculate space needed for top-level panels
current_y_position += _calculate_panel_heights(len(config.panels))
# Add rows and their panels
if not config.rows:
return panels
for row in config.rows:
# Create and add row panel
row_panel = _create_row_panel(row, current_y_position)
panels.append(row_panel)
current_y_position += ROW_HEIGHT
# Add panels within row using 2-column grid
for panel_index, panel in enumerate(row.panels):
panel_template = _generate_panel_template(
panel,
panel_global_filters,
panel_index,
current_y_position,
log_link_url,
)
# Add panel to row if collapsed, otherwise to main dashboard
if row.collapsed:
row_panel["panels"].append(panel_template)
else:
panels.append(panel_template)
# Update y position for next row based on actual panel positions
# when explicit grid_pos is used, or fallback to calculated height.
if any(p.grid_pos for p in row.panels):
max_y_bottom = max(
(p.grid_pos.y + p.grid_pos.h for p in row.panels if p.grid_pos),
default=current_y_position,
)
current_y_position = max_y_bottom
else:
current_y_position += _calculate_panel_heights(len(row.panels))
return panels
def _clean_empty_filters(expr: str) -> str:
"""Clean up malformed PromQL when global_filters is empty.
Removes artifacts like trailing/leading commas in label matchers:
", ,"","
", }""}"
"{ ,""{"
"""
expr = re.sub(r",\s*,", ",", expr)
expr = re.sub(r",\s*}", "}", expr)
expr = re.sub(r"{\s*,", "{", expr)
return expr
def gen_incrementing_alphabets(length):
assert 65 + length < 96, "we only support up to 26 targets at a time."
# 65: ascii code of 'A'.
return list(map(chr, range(65, 65 + length)))
def _generate_targets(panel: Panel, panel_global_filters: List[str]) -> List[dict]:
targets = []
for target, ref_id in zip(
panel.targets, gen_incrementing_alphabets(len(panel.targets))
):
template = copy.deepcopy(target.template.value)
global_filters_str = ",".join(panel_global_filters)
expr = target.expr.format(global_filters=global_filters_str)
if not global_filters_str:
expr = _clean_empty_filters(expr)
template.update(
{
"expr": expr,
"legendFormat": target.legend,
"refId": ref_id,
}
)
targets.append(template)
return targets
@@ -0,0 +1,203 @@
import logging
import os
import platform
import subprocess
import sys
import tarfile
from pathlib import Path
import requests
from ray.dashboard.consts import PROMETHEUS_CONFIG_INPUT_PATH
FALLBACK_PROMETHEUS_VERSION = "2.48.1"
DOWNLOAD_BLOCK_SIZE = 8192 # 8 KB
TEST_MODE_ENV_VAR = "RAY_PROMETHEUS_DOWNLOAD_TEST_MODE"
def get_system_info():
os_type = platform.system().lower()
architecture = platform.machine()
if architecture == "x86_64":
# In the Prometheus filename, it's called amd64
architecture = "amd64"
elif architecture == "aarch64":
# In the Prometheus filename, it's called arm64
architecture = "arm64"
return os_type, architecture
def download_file(url, filename):
logging.info(f"Downloading {url} to {Path(filename).absolute()}...")
try:
test_mode = os.environ.get(TEST_MODE_ENV_VAR, False)
request_method = requests.head if test_mode else requests.get
response = request_method(url, stream=True)
response.raise_for_status()
total_size_in_bytes = int(response.headers.get("content-length", 0))
total_size_in_mb = total_size_in_bytes / (1024 * 1024)
downloaded_size_in_mb = 0
block_size = DOWNLOAD_BLOCK_SIZE
with open(filename, "wb") as file:
for chunk in response.iter_content(chunk_size=block_size):
file.write(chunk)
downloaded_size_in_mb += len(chunk) / (1024 * 1024)
print(
f"Downloaded: {downloaded_size_in_mb:.2f} MB / "
f"{total_size_in_mb:.2f} MB",
end="\r",
)
print("\nDownload completed.")
return True
except requests.RequestException as e:
logging.error(f"Error downloading file: {e}")
return False
def install_prometheus(file_path):
try:
with tarfile.open(file_path) as tar:
tar.extractall()
logging.info("Prometheus installed successfully.")
return True
except Exception as e:
logging.error(f"Error installing Prometheus: {e}")
return False
def start_prometheus(prometheus_dir):
# The function assumes the Ray cluster to be monitored by Prometheus uses the
# default configuration with "/tmp/ray" as the default root temporary directory.
#
# This is to support the `ray metrics launch-prometheus` command, when a Ray cluster
# hasn't started yet and the user doesn't have a way to get a `--temp-dir`
# anywhere. So we choose to use a hardcoded default value.
config_file = Path(PROMETHEUS_CONFIG_INPUT_PATH)
if not config_file.exists():
raise FileNotFoundError(f"Prometheus config file not found: {config_file}")
prometheus_cmd = [
f"{prometheus_dir}/prometheus",
"--config.file",
str(config_file),
"--web.enable-lifecycle",
]
try:
process = subprocess.Popen(prometheus_cmd)
logging.info("Prometheus has started.")
return process
except Exception as e:
logging.error(f"Failed to start Prometheus: {e}")
return None
def print_shutdown_message(process_id):
message = (
f"Prometheus is running with PID {process_id}.\n"
"To stop Prometheus, use the command: "
"`ray metrics shutdown-prometheus`, "
f"'kill {process_id}', or if you need to force stop, "
f"use 'kill -9 {process_id}'."
)
print(message)
debug_message = (
"To list all processes running Prometheus, use the command: "
"'ps aux | grep prometheus'."
)
print(debug_message)
def get_latest_prometheus_version():
url = "https://api.github.com/repos/prometheus/prometheus/releases/latest"
try:
response = requests.get(url)
response.raise_for_status()
data = response.json()
# Remove the leading 'v' from the version number
return data["tag_name"].lstrip("v")
except requests.RequestException as e:
logging.error(f"Error fetching latest Prometheus version: {e}")
return None
def get_prometheus_filename(os_type=None, architecture=None, prometheus_version=None):
if os_type is None or architecture is None:
os_type, architecture = get_system_info()
if prometheus_version is None:
prometheus_version = get_latest_prometheus_version()
if prometheus_version is None:
logging.warning(
"Failed to retrieve the latest Prometheus version. Falling "
f"back to {FALLBACK_PROMETHEUS_VERSION}."
)
# Fall back to a hardcoded version
prometheus_version = FALLBACK_PROMETHEUS_VERSION
return (
f"prometheus-{prometheus_version}.{os_type}-{architecture}.tar.gz",
prometheus_version,
)
def get_prometheus_download_url(
os_type=None, architecture=None, prometheus_version=None
):
file_name, prometheus_version = get_prometheus_filename(
os_type, architecture, prometheus_version
)
return (
"https://github.com/prometheus/prometheus/releases/"
f"download/v{prometheus_version}/{file_name}"
)
def download_prometheus(os_type=None, architecture=None, prometheus_version=None):
file_name, _ = get_prometheus_filename(os_type, architecture, prometheus_version)
download_url = get_prometheus_download_url(
os_type, architecture, prometheus_version
)
return download_file(download_url, file_name), file_name
def main():
# Configure logging only when this script is run directly
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
logging.warning("This script is not intended for production use.")
downloaded, file_name = download_prometheus()
if not downloaded:
logging.error("Failed to download Prometheus.")
sys.exit(1)
# TODO: Verify the checksum of the downloaded file
if not install_prometheus(file_name):
logging.error("Installation failed.")
sys.exit(1)
# TODO: Add a check to see if Prometheus is already running
assert file_name.endswith(".tar.gz")
process = start_prometheus(
# remove the .tar.gz extension
prometheus_dir=file_name.rstrip(".tar.gz")
)
if process:
print_shutdown_message(process.pid)
if __name__ == "__main__":
main()
@@ -0,0 +1,436 @@
import json
import logging
import os
import shutil
from urllib.parse import quote
import aiohttp
import ray.dashboard.optional_utils as dashboard_optional_utils
import ray.dashboard.utils as dashboard_utils
from ray._private.ray_constants import (
PROMETHEUS_SERVICE_DISCOVERY_FILE,
SESSION_LATEST,
)
from ray.dashboard.modules.metrics.grafana_dashboard_factory import (
generate_data_grafana_dashboard,
generate_data_llm_grafana_dashboard,
generate_default_grafana_dashboard,
generate_serve_deployment_grafana_dashboard,
generate_serve_grafana_dashboard,
generate_serve_llm_grafana_dashboard,
generate_train_grafana_dashboard,
)
from ray.dashboard.modules.metrics.templates import (
DASHBOARD_PROVISIONING_TEMPLATE,
GRAFANA_DATASOURCE_TEMPLATE,
GRAFANA_INI_TEMPLATE,
PROMETHEUS_YML_TEMPLATE,
)
from ray.dashboard.subprocesses.module import SubprocessModule
from ray.dashboard.subprocesses.routes import SubprocessRouteTable as routes
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
METRICS_OUTPUT_ROOT_ENV_VAR = "RAY_METRICS_OUTPUT_ROOT"
DEFAULT_PROMETHEUS_HOST = "http://localhost:9090"
PROMETHEUS_HOST_ENV_VAR = "RAY_PROMETHEUS_HOST"
DEFAULT_PROMETHEUS_HEADERS = "{}"
PROMETHEUS_HEADERS_ENV_VAR = "RAY_PROMETHEUS_HEADERS"
DEFAULT_PROMETHEUS_NAME = "Prometheus"
PROMETHEUS_NAME_ENV_VAR = "RAY_PROMETHEUS_NAME"
PROMETHEUS_HEALTHCHECK_PATH = "-/healthy"
DEFAULT_GRAFANA_HOST = "http://localhost:3000"
GRAFANA_HOST_ENV_VAR = "RAY_GRAFANA_HOST"
GRAFANA_ORG_ID_ENV_VAR = "RAY_GRAFANA_ORG_ID"
DEFAULT_GRAFANA_ORG_ID = "1"
GRAFANA_CLUSTER_FILTER_ENV_VAR = "RAY_GRAFANA_CLUSTER_FILTER"
GRAFANA_HOST_DISABLED_VALUE = "DISABLED"
GRAFANA_IFRAME_HOST_ENV_VAR = "RAY_GRAFANA_IFRAME_HOST"
GRAFANA_DASHBOARD_OUTPUT_DIR_ENV_VAR = "RAY_METRICS_GRAFANA_DASHBOARD_OUTPUT_DIR"
GRAFANA_HEALTHCHECK_PATH = "api/health"
# parse_prom_headers will make sure the input is in one of the following formats:
# 1. {"H1": "V1", "H2": "V2"}
# 2. [["H1", "V1"], ["H2", "V2"], ["H2", "V3"]]
def parse_prom_headers(prometheus_headers):
parsed = json.loads(prometheus_headers)
if isinstance(parsed, dict):
if all(isinstance(k, str) and isinstance(v, str) for k, v in parsed.items()):
return parsed
if isinstance(parsed, list):
if all(len(e) == 2 and all(isinstance(v, str) for v in e) for e in parsed):
return parsed
raise ValueError(
f"{PROMETHEUS_HEADERS_ENV_VAR} should be a JSON string in one of the formats:\n"
+ "1) An object with string keys and string values.\n"
+ "2) an array of string arrays with 2 string elements each.\n"
+ 'For example, {"H1": "V1", "H2": "V2"} and\n'
+ '[["H1", "V1"], ["H2", "V2"], ["H2", "V3"]] are valid.'
)
class PrometheusQueryError(Exception):
def __init__(self, status, message):
self.message = (
f"Error fetching data from prometheus. status: {status}, message: {message}"
)
super().__init__(self.message)
class MetricsHead(SubprocessModule):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.grafana_host = os.environ.get(GRAFANA_HOST_ENV_VAR, DEFAULT_GRAFANA_HOST)
self.prometheus_host = os.environ.get(
PROMETHEUS_HOST_ENV_VAR, DEFAULT_PROMETHEUS_HOST
)
default_metrics_root = os.path.join(self.session_dir, "metrics")
self.prometheus_headers = parse_prom_headers(
os.environ.get(
PROMETHEUS_HEADERS_ENV_VAR,
DEFAULT_PROMETHEUS_HEADERS,
)
)
session_latest_metrics_root = os.path.join(
self.temp_dir, SESSION_LATEST, "metrics"
)
self._metrics_root = os.environ.get(
METRICS_OUTPUT_ROOT_ENV_VAR, default_metrics_root
)
self._metrics_root_session_latest = os.environ.get(
METRICS_OUTPUT_ROOT_ENV_VAR, session_latest_metrics_root
)
self._grafana_config_output_path = os.path.join(self._metrics_root, "grafana")
self._grafana_session_latest_config_output_path = os.path.join(
self._metrics_root_session_latest, "grafana"
)
self._grafana_dashboard_output_dir = os.environ.get(
GRAFANA_DASHBOARD_OUTPUT_DIR_ENV_VAR,
os.path.join(self._grafana_config_output_path, "dashboards"),
)
self._prometheus_name = os.environ.get(
PROMETHEUS_NAME_ENV_VAR, DEFAULT_PROMETHEUS_NAME
)
self._grafana_org_id = os.environ.get(
GRAFANA_ORG_ID_ENV_VAR, DEFAULT_GRAFANA_ORG_ID
)
self._grafana_cluster_filter = os.environ.get(GRAFANA_CLUSTER_FILTER_ENV_VAR)
# To be set later when dashboards gets generated
self._dashboard_uids = {}
@routes.get("/api/grafana_health")
async def grafana_health(self, req) -> aiohttp.web.Response:
"""
Endpoint that checks if Grafana is running
"""
# If disabled, we don't want to show the metrics tab at all.
if self.grafana_host == GRAFANA_HOST_DISABLED_VALUE:
return dashboard_optional_utils.rest_response(
status_code=dashboard_utils.HTTPStatusCode.OK,
message="Grafana disabled",
grafana_host=GRAFANA_HOST_DISABLED_VALUE,
)
grafana_iframe_host = os.environ.get(
GRAFANA_IFRAME_HOST_ENV_VAR, self.grafana_host
)
path = f"{self.grafana_host}/{GRAFANA_HEALTHCHECK_PATH}"
try:
async with self.http_session.get(path) as resp:
if resp.status != 200:
return dashboard_optional_utils.rest_response(
status_code=dashboard_utils.HTTPStatusCode.INTERNAL_ERROR,
message="Grafana healthcheck failed",
status=resp.status,
)
json = await resp.json()
# Check if the required Grafana services are running.
if json["database"] != "ok":
return dashboard_optional_utils.rest_response(
status_code=dashboard_utils.HTTPStatusCode.INTERNAL_ERROR,
message="Grafana healthcheck failed. Database not ok.",
status=resp.status,
json=json,
)
return dashboard_optional_utils.rest_response(
status_code=dashboard_utils.HTTPStatusCode.OK,
message="Grafana running",
grafana_host=grafana_iframe_host,
grafana_org_id=self._grafana_org_id,
session_name=self.session_name,
dashboard_uids=self._dashboard_uids,
dashboard_datasource=self._prometheus_name,
grafana_cluster_filter=self._grafana_cluster_filter,
)
except Exception as e:
logger.debug(
"Error fetching grafana endpoint. Is grafana running?", exc_info=e
)
return dashboard_optional_utils.rest_response(
status_code=dashboard_utils.HTTPStatusCode.INTERNAL_ERROR,
message="Grafana healthcheck failed",
exception=str(e),
)
@routes.get("/api/prometheus_health")
async def prometheus_health(self, req):
try:
path = f"{self.prometheus_host}/{PROMETHEUS_HEALTHCHECK_PATH}"
async with self.http_session.get(
path, headers=self.prometheus_headers
) as resp:
if resp.status != 200:
return dashboard_optional_utils.rest_response(
status_code=dashboard_utils.HTTPStatusCode.INTERNAL_ERROR,
message="prometheus healthcheck failed.",
status=resp.status,
)
return dashboard_optional_utils.rest_response(
status_code=dashboard_utils.HTTPStatusCode.OK,
message="prometheus running",
)
except Exception as e:
logger.debug(
"Error fetching prometheus endpoint. Is prometheus running?", exc_info=e
)
return dashboard_optional_utils.rest_response(
status_code=dashboard_utils.HTTPStatusCode.INTERNAL_ERROR,
message="prometheus healthcheck failed.",
reason=str(e),
)
def _create_default_grafana_configs(self):
"""
Creates the Grafana configurations that are by default provided by Ray.
"""
# Create Grafana configuration folder
if os.path.exists(self._grafana_config_output_path):
shutil.rmtree(self._grafana_config_output_path)
os.makedirs(self._grafana_config_output_path, exist_ok=True)
# Overwrite Grafana's configuration file
grafana_provisioning_folder = os.path.join(
self._grafana_config_output_path, "provisioning"
)
grafana_prov_folder_with_latest_session = os.path.join(
self._grafana_session_latest_config_output_path, "provisioning"
)
with open(
os.path.join(
self._grafana_config_output_path,
"grafana.ini",
),
"w",
) as f:
f.write(
GRAFANA_INI_TEMPLATE.format(
grafana_provisioning_folder=grafana_prov_folder_with_latest_session
)
)
# Overwrite Grafana's dashboard provisioning directory based on env var
dashboard_provisioning_path = os.path.join(
grafana_provisioning_folder, "dashboards"
)
os.makedirs(
dashboard_provisioning_path,
exist_ok=True,
)
with open(
os.path.join(
dashboard_provisioning_path,
"default.yml",
),
"w",
) as f:
f.write(
DASHBOARD_PROVISIONING_TEMPLATE.format(
dashboard_output_folder=self._grafana_dashboard_output_dir
)
)
# Overwrite Grafana's Prometheus datasource based on env var
prometheus_host = os.environ.get(
PROMETHEUS_HOST_ENV_VAR, DEFAULT_PROMETHEUS_HOST
)
prometheus_headers = parse_prom_headers(
os.environ.get(PROMETHEUS_HEADERS_ENV_VAR, DEFAULT_PROMETHEUS_HEADERS)
)
# parse_prom_headers will make sure the prometheus_headers is either format of:
# 1. {"H1": "V1", "H2": "V2"} or
# 2. [["H1", "V1"], ["H2", "V2"], ["H2", "V3"]]
prometheus_header_pairs = []
if isinstance(prometheus_headers, list):
prometheus_header_pairs = prometheus_headers
elif isinstance(prometheus_headers, dict):
prometheus_header_pairs = list(prometheus_headers.items())
data_sources_path = os.path.join(grafana_provisioning_folder, "datasources")
os.makedirs(
data_sources_path,
exist_ok=True,
)
os.makedirs(
self._grafana_dashboard_output_dir,
exist_ok=True,
)
with open(
os.path.join(
data_sources_path,
"default.yml",
),
"w",
) as f:
f.write(
GRAFANA_DATASOURCE_TEMPLATE(
prometheus_host=prometheus_host,
prometheus_name=self._prometheus_name,
jsonData={
f"httpHeaderName{i + 1}": header
for i, (header, _) in enumerate(prometheus_header_pairs)
},
secureJsonData={
f"httpHeaderValue{i + 1}": value
for i, (_, value) in enumerate(prometheus_header_pairs)
},
)
)
with open(
os.path.join(
self._grafana_dashboard_output_dir,
"default_grafana_dashboard.json",
),
"w",
) as f:
(
content,
self._dashboard_uids["default"],
) = generate_default_grafana_dashboard()
f.write(content)
with open(
os.path.join(
self._grafana_dashboard_output_dir,
"serve_grafana_dashboard.json",
),
"w",
) as f:
content, self._dashboard_uids["serve"] = generate_serve_grafana_dashboard()
f.write(content)
with open(
os.path.join(
self._grafana_dashboard_output_dir,
"serve_deployment_grafana_dashboard.json",
),
"w",
) as f:
(
content,
self._dashboard_uids["serve_deployment"],
) = generate_serve_deployment_grafana_dashboard()
f.write(content)
with open(
os.path.join(
self._grafana_dashboard_output_dir,
"serve_llm_grafana_dashboard.json",
),
"w",
) as f:
(
content,
self._dashboard_uids["serve_llm"],
) = generate_serve_llm_grafana_dashboard()
f.write(content)
with open(
os.path.join(
self._grafana_dashboard_output_dir,
"data_grafana_dashboard.json",
),
"w",
) as f:
(
content,
self._dashboard_uids["data"],
) = generate_data_grafana_dashboard()
f.write(content)
with open(
os.path.join(
self._grafana_dashboard_output_dir,
"data_llm_grafana_dashboard.json",
),
"w",
) as f:
(
content,
self._dashboard_uids["data_llm"],
) = generate_data_llm_grafana_dashboard()
f.write(content)
with open(
os.path.join(
self._grafana_dashboard_output_dir,
"train_grafana_dashboard.json",
),
"w",
) as f:
(
content,
self._dashboard_uids["train"],
) = generate_train_grafana_dashboard()
f.write(content)
def _create_default_prometheus_configs(self):
"""
Creates the Prometheus configurations that are by default provided by Ray.
"""
prometheus_config_output_path = os.path.join(
self._metrics_root, "prometheus", "prometheus.yml"
)
# Generate the default Prometheus configurations
if os.path.exists(prometheus_config_output_path):
os.remove(prometheus_config_output_path)
os.makedirs(os.path.dirname(prometheus_config_output_path), exist_ok=True)
# This code generates the Prometheus config based on the custom temporary root
# path set by the user at Ray cluster start up (via --temp-dir). In contrast,
# start_prometheus in install_and_start_prometheus.py uses a hardcoded
# Prometheus config at PROMETHEUS_CONFIG_INPUT_PATH that always uses "/tmp/ray".
# Other than the root path, the config file generated here is identical to that
# hardcoded config file.
prom_discovery_file_path = os.path.join(
self.session_dir, PROMETHEUS_SERVICE_DISCOVERY_FILE
)
with open(prometheus_config_output_path, "w") as f:
f.write(
PROMETHEUS_YML_TEMPLATE.format(
prom_metrics_service_discovery_file_path=prom_discovery_file_path
)
)
async def run(self):
await super().run()
self._create_default_grafana_configs()
self._create_default_prometheus_configs()
async def _query_prometheus(self, query):
async with self.http_session.get(
f"{self.prometheus_host}/api/v1/query?query={quote(query)}",
headers=self.prometheus_headers,
) as resp:
if resp.status == 200:
prom_data = await resp.json()
return prom_data
message = await resp.text()
raise PrometheusQueryError(resp.status, message)
@@ -0,0 +1,63 @@
import yaml
GRAFANA_INI_TEMPLATE = """
[security]
allow_embedding = true
[auth.anonymous]
enabled = true
org_name = Main Org.
org_role = Viewer
[paths]
provisioning = {grafana_provisioning_folder}
"""
DASHBOARD_PROVISIONING_TEMPLATE = """
apiVersion: 1
providers:
- name: Ray # Default dashboards provided by OSS Ray
folder: Ray
type: file
options:
path: {dashboard_output_folder}
"""
def GRAFANA_DATASOURCE_TEMPLATE(
prometheus_name, prometheus_host, jsonData, secureJsonData
):
return yaml.safe_dump(
{
"apiVersion": 1,
"datasources": [
{
"name": prometheus_name,
"url": prometheus_host,
"type": "prometheus",
"isDefault": True,
"access": "proxy",
"jsonData": jsonData,
"secureJsonData": secureJsonData,
}
],
}
)
PROMETHEUS_YML_TEMPLATE = """# my global config
global:
scrape_interval: 10s # Set the scrape interval to every 10 seconds. Default is every \
1 minute.
evaluation_interval: 10s # Evaluate rules every 10 seconds. The default is every 1 \
minute.
# scrape_timeout is set to the global default (10s).
scrape_configs:
# Scrape from each Ray node as defined in the service_discovery.json provided by Ray.
- job_name: 'ray'
file_sd_configs:
- files:
- '{prom_metrics_service_discovery_file_path}'
"""
@@ -0,0 +1,32 @@
import json
import sys
import pytest
from ray.dashboard.modules.metrics.metrics_head import (
DEFAULT_PROMETHEUS_HEADERS,
parse_prom_headers,
)
@pytest.mark.parametrize(
"headers, raises_error",
[
(DEFAULT_PROMETHEUS_HEADERS, False),
('{"H1": "V1", "H2": "V2"}', False),
('[["H1", "V1"], ["H2", "V2"], ["H2", "V3"]]', False),
('{"H1": "V1", "H2": ["V1", "V2"]}', True),
("not_json", True),
],
)
def test_parse_prom_headers(headers, raises_error):
if raises_error:
with pytest.raises(ValueError):
parse_prom_headers(headers)
else:
result = parse_prom_headers(headers)
assert result == json.loads(headers)
if __name__ == "__main__":
sys.exit(pytest.main(["-vv", __file__]))