> ## Documentation Index
> Fetch the complete documentation index at: https://docs.siflow.cn/llms.txt
> Use this file to discover all available pages before exploring further.

# 接入训练任务自定义指标

> 在训练任务中暴露自定义 metrics，配置多端口采集，并在训练任务详情页查看自定义指标。

如果训练任务需要上报请求数、队列长度、训练 loss 或其他业务指标，可以让训练代码暴露 metrics 端点，并在创建训练任务时配置指标采集端点。任务运行后，平台会采集这些指标，并在训练任务详情页的 **监控 > 自定义** 中展示。

## 1. 在训练代码中暴露指标端点

训练代码需要暴露端口和路径，并确保端口和路径与创建任务时配置的指标端点一致。以下示例在同一个训练任务中通过两个端口暴露 `/metrics`。

| 指标端口 | 指标路径 | 示例指标 |
| - | - | - |
| `8000` | `/metrics` | 应用指标，例如请求数、连接数、队列长度。 |
| `8001` | `/metrics` | 系统指标，例如 CPU 使用率、内存使用量、运行时长。 |

**代码示例：**

示例中的指标更新逻辑仅用于演示。实际接入时，请替换为训练代码中的真实业务数据，并保持代码暴露的端口、路径与创建任务时填写的指标端点一致。

> [accordion] 查看 Python 示例代码
>
> ```python theme={null}
> #!/usr/bin/env python3
> # -*- coding: utf-8 -*-
> """
> Prometheus 风格的指标导出器（双端口）。
>
> - 端口 8000: 应用指标 (app_registry)
> - 端口 8001: 系统指标 (sys_registry)
> """
>
> import time
> import random
> import threading
> import logging
> from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
> from urllib.parse import urlparse
>
> logging.basicConfig(
>     level=logging.INFO,
>     format='%(asctime)s %(message)s',
> )
> L = logging.getLogger(__name__)
>
>
> class Metric:
>     """指标基类。"""
>
>     def __init__(self, name, help_text, mtype, labelnames=None):
>         self.name = name
>         self.help = help_text
>         self.type = mtype
>         self.labelnames = labelnames or []
>         self._lock = threading.Lock()
>
>     def _fmt(self, labels):
>         if not labels:
>             return ''
>         parts = [f'{k}="{str(labels.get(k, ""))}"' for k in self.labelnames]
>         return '{' + ','.join(parts) + '}'
>
>     def hdr(self):
>         return [
>             f'# HELP {self.name} {self.help}',
>             f'# TYPE {self.name} {self.type}',
>         ]
>
>
> class Counter(Metric):
>     """单调递增计数器。"""
>
>     def __init__(self, name, help_text, labelnames=None):
>         super().__init__(name, help_text, 'counter', labelnames)
>         self._values = {}
>
>     def _key(self, labels):
>         return tuple(labels.get(k, '') for k in self.labelnames)
>
>     def inc(self, amount=1, **labels):
>         with self._lock:
>             k = self._key(labels)
>             self._values[k] = self._values.get(k, 0) + amount
>
>     def expose(self):
>         lines = self.hdr()
>         with self._lock:
>             if not self._values and not self.labelnames:
>                 lines.append(f'{self.name} 0')
>             for k, v in self._values.items():
>                 lbl = dict(zip(self.labelnames, k))
>                 lines.append(f'{self.name}{self._fmt(lbl)} {v}')
>         return lines
>
>
> class Gauge(Metric):
>     """可增可减的瞬时值。"""
>
>     def __init__(self, name, help_text, labelnames=None):
>         super().__init__(name, help_text, 'gauge', labelnames)
>         self._values = {}
>
>     def _key(self, labels):
>         return tuple(labels.get(k, '') for k in self.labelnames)
>
>     def set(self, value, **labels):
>         with self._lock:
>             self._values[self._key(labels)] = value
>
>     def expose(self):
>         lines = self.hdr()
>         with self._lock:
>             if not self._values and not self.labelnames:
>                 lines.append(f'{self.name} 0')
>             for k, v in self._values.items():
>                 lbl = dict(zip(self.labelnames, k))
>                 lines.append(f'{self.name}{self._fmt(lbl)} {v}')
>         return lines
>
>
> class Registry:
>     """指标注册表，负责收集并输出所有指标的文本格式。"""
>
>     def __init__(self):
>         self._metrics = []
>         self._lock = threading.Lock()
>
>     def register(self, metric):
>         with self._lock:
>             self._metrics.append(metric)
>         return metric
>
>     def expose(self):
>         lines = []
>         with self._lock:
>             for m in self._metrics:
>                 lines.extend(m.expose())
>         return '\n'.join(lines) + '\n'
>
>
> # ---------- 应用指标 ----------
> app_registry = Registry()
> http_requests = app_registry.register(
>     Counter('myapp_http_requests_total', 'Total requests', ['method', 'status'])
> )
> active_conns = app_registry.register(
>     Gauge('myapp_active_connections', 'Active connections')
> )
> queue = app_registry.register(
>     Gauge('myapp_queue_size', 'Queue size')
> )
>
> # ---------- 系统指标 ----------
> sys_registry = Registry()
> cpu = sys_registry.register(
>     Gauge('system_cpu_usage_percent', 'CPU usage', ['core'])
> )
> mem = sys_registry.register(
>     Gauge('system_memory_usage_bytes', 'Memory usage', ['type'])
> )
> uptime_metric = sys_registry.register(
>     Gauge('system_uptime_seconds', 'Uptime')
> )
>
>
> class MetricsHandler(BaseHTTPRequestHandler):
>     registry = None
>
>     def do_GET(self):
>         path = urlparse(self.path).path
>         if path == '/metrics':
>             body = self.registry.expose().encode('utf-8')
>             self.send_response(200)
>             self.send_header('Content-Type', 'text/plain')
>             self.send_header('Content-Length', str(len(body)))
>             self.end_headers()
>             self.wfile.write(body)
>         else:
>             self.send_response(404)
>             self.end_headers()
>
>     def log_message(self, fmt, *args):
>         # 关闭默认访问日志
>         pass
>
>
> def make_handler(registry):
>     """为指定的 registry 生成一个 handler 类。"""
>
>     class _Handler(MetricsHandler):
>         pass
>
>     _Handler.registry = registry
>     return _Handler
>
>
> def update_app():
>     """模拟更新应用指标。"""
>     while True:
>         try:
>             http_requests.inc(
>                 method=random.choice(['GET', 'POST']),
>                 status=random.choice(['200', '500']),
>             )
>             active_conns.set(random.randint(10, 200))
>             queue.set(random.randint(0, 50))
>             time.sleep(1)
>         except Exception as e:
>             L.error(f'app:{e}')
>             time.sleep(1)
>
>
> def update_sys():
>     """模拟更新系统指标。"""
>     start = time.time()
>     while True:
>         try:
>             for i in range(4):
>                 cpu.set(random.uniform(0, 100), core=f'cpu{i}')
>             total = 16 * 1024 ** 3
>             mem.set(total, type='total')
>             mem.set(total * random.uniform(0.3, 0.8), type='used')
>             uptime_metric.set(time.time() - start)
>             time.sleep(2)
>         except Exception as e:
>             L.error(f'sys:{e}')
>             time.sleep(1)
>
>
> def start_server(port, registry):
>     """启动一个 HTTP 服务，暴露指定 registry 的 /metrics。"""
>     httpd = ThreadingHTTPServer(('0.0.0.0', port), make_handler(registry))
>     threading.Thread(target=httpd.serve_forever, daemon=True).start()
>     L.info(f'Server started: port {port}')
>
>
> def main():
>     L.info('Starting dual-port exporter')
>     start_server(8000, app_registry)
>     start_server(8001, sys_registry)
>     threading.Thread(target=update_app, daemon=True).start()
>     threading.Thread(target=update_sys, daemon=True).start()
>     while True:
>         time.sleep(1)
>
>
> if __name__ == '__main__':
>     main()
> ```

## 2. 创建训练任务并配置指标端点

创建训练任务时，在 **高级选项** 中开启 **监控看板**，并为训练代码暴露的端口配置指标端点。

| 配置项 | 说明 |
| - | - |
| **监控看板** | 开启后，平台采集训练代码暴露的自定义 metrics。平台内置资源指标默认采集，不需要在此配置。 |
| **指标端口** | 训练容器暴露的指标端口，例如 `8000` 或 `8001`。 |
| **指标路径** | 指标采集路径，例如 `/metrics`。 |

如果需要采集多个端点，单击 **添加指标端点** 逐个添加。同一训练任务内，**指标端口** 与 **指标路径** 的组合不可重复。按照上面的示例，需要添加以下两个端点。

| 指标端口 | 指标路径 |
| - | - |
| `8000` | `/metrics` |
| `8001` | `/metrics` |

创建训练任务的其他配置，请参见 [创建训练任务](https://docs.siflow.cn/ai-platform/training/create-training-tasks.md)。

## 3. 验证指标端点

任务运行后，可在训练任务容器内访问对应 metrics 端点，确认端点能够返回指标样本。

```bash theme={null}
curl localhost:8000/metrics
curl localhost:8001/metrics
```

> [accordion] 查看返回示例
>
> ```bash theme={null}
> root@e2e-multiport-v3-7e9039ba-master-0:/workspace# curl localhost:8000/metrics
> # HELP myapp_http_requests_total Total requests
> # TYPE myapp_http_requests_total counter
> myapp_http_requests_total{method="GET",status="500"} 461
> myapp_http_requests_total{method="POST",status="500"} 431
> myapp_http_requests_total{method="GET",status="200"} 423
> myapp_http_requests_total{method="POST",status="200"} 413
> # HELP myapp_active_connections Active connections
> # TYPE myapp_active_connections gauge
> myapp_active_connections 178
> # HELP myapp_queue_size Queue size
> # TYPE myapp_queue_size gauge
> myapp_queue_size 42
>
> root@e2e-multiport-v3-7e9039ba-master-0:/workspace# curl localhost:8001/metrics
> # HELP system_cpu_usage_percent CPU usage
> # TYPE system_cpu_usage_percent gauge
> system_cpu_usage_percent{core="cpu0"} 60.21940599671388
> system_cpu_usage_percent{core="cpu1"} 21.640438681845218
> system_cpu_usage_percent{core="cpu2"} 9.09251066506329
> system_cpu_usage_percent{core="cpu3"} 55.24733876700996
> # HELP system_memory_usage_bytes Memory usage
> # TYPE system_memory_usage_bytes gauge
> system_memory_usage_bytes{type="total"} 17179869184
> system_memory_usage_bytes{type="used"} 6094192600.644531
> # HELP system_uptime_seconds Uptime
> # TYPE system_uptime_seconds gauge
> system_uptime_seconds 1730.1035702228546
> ```

如果配置了多个指标端点，请分别检查每个端口和路径。端点能够返回指标样本，说明训练代码侧已经暴露指标；如果访问失败或没有返回预期指标，请先检查训练代码、端口和路径配置。

## 4. 配置并查看自定义指标图表

指标端点采集到数据后，进入训练任务详情页，打开 **监控** 页签，在左侧选择 **自定义**，基于指标名称和 label 配置图表，才能查看对应曲线。不同环境中的可选模块、子模块和分类以控制台展示为准。

配置时重点关注以下字段。

| 配置项 | 说明 |
| - | - |
| **模块** | 选择训练任务相关模块。 |
| **子模块** | 选择用于承载自定义指标的子模块。 |
| **分类** | 相同分类的图表会在业务页面中归类展示。建议按业务场景或指标类型命名。 |
| **图例** | 图表曲线的展示名称。可使用 label 变量，例如 `{{pod}} {{endpoint}}`，用于区分不同 Pod、采集端点或业务维度。 |
| **PromQL** | 填写与自定义指标匹配的查询表达式。指标名称、label 和可用范围以当前任务实际暴露的数据为准。 |

配置图例和 PromQL 时，可以使用指标自身的业务 label，也可以使用平台补充的 Pod、采集端点、容器等 label。常用 label 如下。

| Label | 说明 |
| - | - |
| `pod` | 指标所属 Pod。 |
| `endpoint` | 指标采集端点。 |
| `container` | 指标所属容器。 |
| 业务 label | 训练代码在 metrics 中自行暴露的 label，例如 `method`、`status`。 |

可用 label 以当前任务实际暴露的指标和控制台展示为准。

以下示例展示如何为 `myapp_http_requests_total` 配置图表：

```json theme={null}
[
  {
    "module": "training",
    "subModule": "Custom",
    "class": "myapp_http_requests_total",
    "format": "TimeSeries",
    "title": "HTTP 请求数",
    "description": "查看训练任务暴露的请求指标。",
    "metrics": [
      {
        "legend": "{{pod}} {{endpoint}}",
        "query": "myapp_http_requests_total{}"
      }
    ]
  }
]
```

配置完成后，选择需要查看的指标或图表，按需设置时间范围，并单击 **刷新**。

更多看板创建和更新方法，请参见 [管理监控看板](https://docs.siflow.cn/ai-platform/configuration/manage-monitoring-dashboards.md)。

## 排查自定义指标暂无数据

如果 **监控 > 自定义** 中暂无数据，请按以下顺序排查：

1. 确认任务创建时已开启 **监控看板**，且端口和路径与训练代码一致。
2. 确认训练代码已经暴露 metrics 端点，并且任务运行期间产生了指标。
3. 在训练任务容器内访问对应 metrics 端点，确认端点能够返回指标样本。
4. 如果配置了多个指标端点，请分别检查每个端口和路径。
5. 稍后刷新或扩大时间范围后查看。

## 相关文档

* [创建训练任务](https://docs.siflow.cn/ai-platform/training/create-training-tasks.md)
* [训练任务高级配置](https://docs.siflow.cn/ai-platform/training/advanced-training-task-configuration.md)
* [查看训练任务监控](https://docs.siflow.cn/ai-platform/training/view-training-task-monitoring.md)
* [管理监控看板](https://docs.siflow.cn/ai-platform/configuration/manage-monitoring-dashboards.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.