ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s

This commit is contained in:
2026-08-20 13:12:50 +00:00
commit b119135836
10275 changed files with 3284984 additions and 0 deletions
@@ -0,0 +1,80 @@
# Metrics Module
## Overview
The `aworld.core.metrics` module provides a unified interface for collecting and exporting metrics. It supports various types of metrics (e.g., counters, histograms) and allows exporting data to different monitoring systems (e.g., Prometheus).
## Key Features
- **Metric Types**:
- `Counter`: A cumulative metric that represents a single numerical value that only ever increases.
- `UpDownCounter`: A cumulative metric that can increase or decrease.
- `Gauge`: A metric that represents a single numerical value that can arbitrarily go up and down.
- `Histogram`: A metric that represents the distribution of a set of values.
- **Adapters**:
- `PrometheusAdapter`: Exports metrics to Prometheus.
- `ConsoleAdapter`: Prints metrics to the console (for debugging purposes).
## Usage Example
```python
import random
import time
from aworld.core.metrics.metric import set_metric_provider, MetricType
from aworld.core.metrics.prometheus.prometheus_adapter import PrometheusConsoleMetricExporter,
PrometheusMetricProvider
from aworld.core.metrics.context_manager import MetricContext, ApiMetricTracker
from aworld.core.metrics.template import MetricTemplate
# Set OpenTelemetry as the metric provider
# set_metric_provider(OpentelemetryMetricProvider())
# Set Prometheus as the metric provider
set_metric_provider(PrometheusMetricProvider(PrometheusConsoleMetricExporter(out_interval_secs=2)))
# Define metric templates
my_counter = MetricTemplate(
type=MetricType.COUNTER,
name="my_counter",
description="My custom counter",
unit="1"
)
my_gauge = MetricTemplate(
type=MetricType.GAUGE,
name="my_gauge"
)
my_histogram = MetricTemplate(
type=MetricType.HISTOGRAM,
name="my_histogram",
buckets=[2, 4, 6, 8, 10]
)
# Track API metrics using decorator
@ApiMetricTracker()
def test_api():
time.sleep(random.uniform(0, 1))
# Track custom code block using context manager
def test_custom_code():
with ApiMetricTracker("test_custom_code"):
time.sleep(random.uniform(0, 1))
# Main loop to generate and record metrics
while 1:
MetricContext.count(my_counter, random.randint(1, 10))
MetricContext.gauge_set(my_gauge, random.randint(1, 10))
MetricContext.histogram_record(my_histogram, random.randint(1, 10))
test_api()
test_custom_code()
time.sleep(random.random())
```
## Notes
- Before using metrics, you must set a metric provider ( set_metric_provider ).
- Different metric types serve different purposes; choose the appropriate type based on your needs.
- For production environments, it is recommended to use Prometheus as the exporter.
@@ -0,0 +1,9 @@
# coding: utf-8
# Copyright (c) 2025 inclusionAI.
import os
from aworld.metrics.context_manager import MetricContext
# MetricContext.configure(provider="otlp",
# backend="logfire",
# write_token=os.getenv("LOGFIRE_WRITE_TOKEN")
# )
@@ -0,0 +1,186 @@
import time
import asyncio
from typing import Callable
from functools import wraps
from aworld.metrics.metric import get_metric_provider, MetricType, BaseMetric
from aworld.metrics.template import MetricTemplate, MetricTemplates
_GLOBAL_METIRCS = {}
class MetricContext:
_initialized = False
@classmethod
def configure(cls,
provider: str,
backend: str,
base_url: str = None,
write_token: str = None,
**kwargs):
"""
Configure the metric provider.
Args:
provider: The provider of the metric provider.
backend: The backend of the metric provider.
base_url: The base url of the metric provider.
write_token: The write token of the metric provider.
export_console: Whether to export the metrics to console.
**kwargs: The other parameters of the metric provider.
"""
if cls._initialized:
cls.shutdown()
if provider == "prometheus":
from aworld.metrics.prometheus.prometheus_adapter import configure_prometheus_provider
configure_prometheus_provider(
backend, base_url, write_token, **kwargs)
elif provider == "otlp":
from aworld.metrics.opentelemetry.opentelemetry_adapter import configure_otlp_provider
configure_otlp_provider(backend, base_url, write_token, **kwargs)
cls._initialized = True
@classmethod
def metric_initialized(cls):
return cls._initialized
@staticmethod
def get_or_create_metric(template: MetricTemplate):
if template.name in _GLOBAL_METIRCS:
return _GLOBAL_METIRCS[template.name]
metric = None
if template.type == MetricType.COUNTER:
metric = get_metric_provider().create_counter(template.name, template.description, template.unit,
template.labels)
elif template.type == MetricType.UPDOWNCOUNTER:
metric = get_metric_provider().create_un_down_counter(template.name, template.description, template.unit,
template.labels)
elif template.type == MetricType.GAUGE:
metric = get_metric_provider().create_gauge(template.name, template.description, template.unit,
template.labels)
elif template.type == MetricType.HISTOGRAM:
metric = get_metric_provider().create_histogram(template.name, template.description, template.unit,
template.buckets, template.labels)
_GLOBAL_METIRCS[template.name] = metric
return metric
@classmethod
def _validate_type(cls, metric: BaseMetric, type: str):
if type != metric._type:
raise ValueError(f"metric type {metric._type} is not {type}")
@classmethod
def count(cls, template: MetricTemplate, value: int, labels: dict = None):
"""
Increment a counter metric.
"""
metric = cls.get_or_create_metric(template)
cls._validate_type(metric, MetricType.COUNTER)
metric.add(value, labels)
@classmethod
def inc(cls, template: MetricTemplate, value: int, labels: dict = None):
"""
Increment a updowncounter metric.
"""
metric = cls.get_or_create_metric(template)
cls._validate_type(metric, MetricType.UPDOWNCOUNTER)
metric.inc(value, labels)
@classmethod
def dec(cls, template: MetricTemplate, value: int, labels: dict = None):
"""
Decrement a updowncounter metric.
"""
metric = cls.get_or_create_metric(template)
cls._validate_type(metric, MetricType.UPDOWNCOUNTER)
metric.dec(value, labels)
@classmethod
def gauge_set(cls, template: MetricTemplate, value: int, labels: dict = None):
"""
Set a value to a gauge metric.
"""
metric = cls.get_or_create_metric(template)
cls._validate_type(metric, MetricType.GAUGE)
metric.set(value, labels)
@classmethod
def histogram_record(cls, template: MetricTemplate, value: int, labels: dict = None):
"""
Set a value to a histogram metric.
"""
metric = cls.get_or_create_metric(template)
cls._validate_type(metric, MetricType.HISTOGRAM)
metric.record(value, labels)
@classmethod
def shutdown(cls):
"""
Shutdown the metric provider.
"""
provider = get_metric_provider()
if provider:
provider.shutdown()
cls._initialized = False
class ApiMetricTracker:
"""
Decorator to track API metrics.
"""
def __init__(self, api_name: str = None, func: Callable = None):
self.start_time = None
self.status = "success"
self.func = func
self.api_name = api_name
if self.api_name is None and self.func is not None:
self.api_name = self.func.__name__
def _new_tracker(self, func: Callable):
return self.__class__(func=func)
def __enter__(self):
self.start_time = time.time() * 1000
def __exit__(self, exc_type, value, traceback):
if exc_type is None:
self.status = "success"
else:
self.status = "failure"
self._record_metrics(self.api_name, self.start_time, self.status)
def __call__(self, func: Callable = None) -> Callable:
if func is None:
return self
return self.decorator(func)
def _record_metrics(self, api_name: str, start_time: float, status: str) -> None:
"""
Record metrics for the API.
"""
elapsed_time = time.time() * 1000 - start_time
MetricContext.count(MetricTemplates.REQUEST_COUNT, 1,
labels={"method": api_name, "status": status})
MetricContext.histogram_record(MetricTemplates.REQUEST_LATENCY, elapsed_time,
labels={"method": api_name, "status": status})
def decorator(self, func):
"""
Decorator to track API metrics.
"""
@wraps(func)
async def async_wrapper(*args, **kwargs):
with self._new_tracker(func):
return await func(*args, **kwargs)
@wraps(func)
def wrapper(*args, **kwargs):
with self._new_tracker(func):
return func(*args, **kwargs)
return async_wrapper if asyncio.iscoroutinefunction(func) else wrapper
@@ -0,0 +1,287 @@
from abc import ABC, abstractmethod
from typing import Optional, Sequence
class MetricType:
"""
MetricType is a class for defining the type of a metric.
"""
COUNTER = "counter"
UPDOWNCOUNTER = "updowncounter"
GAUGE = "gauge"
HISTOGRAM = "histogram"
class MetricProvider(ABC):
"""
MeterProvider is the entry point of the API.
"""
def __init__(self):
# list of exporters
self._exporters = []
@abstractmethod
def shutdown(self):
"""
shutdown the metric provider.
"""
def add_exporter(self, exporter):
"""
Add an exporter to the list of exporters.
"""
self._exporters.append(exporter)
@abstractmethod
def create_counter(self, name: str, description: str, unit: str,
label_names: Optional[Sequence[str]] = None) -> "Counter":
"""
Create a counter.
Args:
name: The name of the instrument to be created
description: A description for this instrument and what it measures.
unit: The unit for observations this instrument reports. For
example, ``By`` for bytes. UCUM units are recommended.
"""
@abstractmethod
def create_un_down_counter(self, name: str, description: str, unit: str,
label_names: Optional[Sequence[str]] = None) -> "UnDownCounter":
"""
Create a un-down counter.
Args:
name: The name of the instrument to be created
description: A description for this instrument and what it measures.
unit: The unit for observations this instrument reports. For
example, ``By`` for bytes. UCUM units are recommended.
"""
@abstractmethod
def create_gauge(self, name: str, description: str, unit: str,
label_names: Optional[Sequence[str]] = None) -> "Gauge":
"""
Create a gauge.
Args:
name: The name of the instrument to be created
description: A description for this instrument and what it measures.
unit: The unit for observations this instrument reports. For
example, ``By`` for bytes. UCUM units are recommended.
"""
@abstractmethod
def create_histogram(self,
name: str,
description: str,
unit: str,
buckets: Optional[Sequence[float]] = None,
label_names: Optional[Sequence[str]] = None) -> "Histogram":
"""
Create a histogram.
Args:
name: The name of the instrument to be created
description: A description for this instrument and what it measures.
unit: The unit for observations this instrument reports. For
example, ``By`` for bytes. UCUM units are recommended.
"""
class BaseMetric(ABC):
"""
Metric is the base class for all metrics.
Args:
name: The name of the metric.
description: The description of the metric.
unit: The unit of the metric.
provider: The provider of the metric.
"""
def __init__(self,
name: str,
description: str,
unit: str,
provider: MetricProvider,
label_names: Optional[Sequence[str]] = None):
self._name = name
self._description = description
self._unit = unit
self._provider = provider
self._label_names = label_names
self._type = None
class Counter(BaseMetric):
"""
Counter is a subclass of BaseMetric, representing a counter metric.
A counter is a cumulative metric that represents a single numerical value that only ever goes up.
"""
def __init__(self,
name: str,
description: str,
unit: str,
provider: MetricProvider,
label_names: Optional[Sequence[str]] = None):
"""
Initialize the Counter.
Args:
name: The name of the metric.
description: The description of the metric.
unit: The unit of the metric.
provider: The provider of the metric.
"""
super().__init__(name, description, unit, provider, label_names)
self._type = MetricType.COUNTER
@abstractmethod
def add(self, value: int, labels: dict = None) -> None:
"""
Add a value to the counter.
Args:
value: The value to add to the counter.
labels: The labels to associate with the value.
"""
class UpDownCounter(BaseMetric):
"""
UpDownCounter is a subclass of BaseMetric, representing an un-down counter metric.
An un-down counter is a cumulative metric that represents a single numerical value that only ever goes up.
"""
def __init__(self,
name: str,
description: str,
unit: str,
provider: MetricProvider,
label_names: Optional[Sequence[str]] = None):
"""
Initialize the UnDownCounter.
Args:
name: The name of the metric.
description: The description of the metric.
unit: The unit of the metric.
provider: The provider of the metric.
"""
super().__init__(name, description, unit, provider, label_names)
self._type = MetricType.UPDOWNCOUNTER
@abstractmethod
def inc(self, value: int, labels: dict = None) -> None:
"""
Add a value to the gauge.
Args:
value: The value to add to the gauge.
labels: The labels to associate with the value.
"""
@abstractmethod
def dec(self, value: int, labels: dict = None) -> None:
"""
Subtract a value from the gauge.
Args:
value: The value to subtract from the gauge.
labels: The labels to associate with the value.
"""
class Gauge(BaseMetric):
"""
Gauge is a subclass of BaseMetric, representing a gauge metric.
A gauge is a metric that represents a single numerical value that can arbitrarily go up and down.
"""
def __init__(self,
name: str,
description: str,
unit: str,
provider: MetricProvider,
label_names: Optional[Sequence[str]] = None):
"""
Initialize the Gauge.
Args:
name: The name of the metric.
description: The description of the metric.
unit: The unit of the metric.
provider: The provider of the metric.
"""
super().__init__(name, description, unit, provider, label_names)
self._type = MetricType.GAUGE
@abstractmethod
def set(self, value: int, labels: dict = None) -> None:
"""
Set the value of the gauge.
Args:
value: The value to set the gauge to.
labels: The labels to associate with the value.
"""
class Histogram(BaseMetric):
"""
Histogram is a subclass of BaseMetric, representing a histogram metric.
A histogram is a metric that represents the distribution of a set of values.
"""
def __init__(self,
name: str,
description: str,
unit: str,
provider: MetricProvider,
buckets: Sequence[float] = None,
label_names: Optional[Sequence[str]] = None):
"""
Initialize the Histogram.
Args:
name: The name of the metric.
description: The description of the metric.
unit: The unit of the metric.
provider: The provider of the metric.
buckets: The buckets of the histogram.
"""
super().__init__(name, description, unit, provider, label_names)
self._type = MetricType.HISTOGRAM
self._buckets = buckets
@abstractmethod
def record(self, value: int, labels: dict = None) -> None:
"""
Record a value in the histogram.
Args:
value: The value to record in the histogram.
labels: The labels to associate with the value.
"""
class MetricExporter(ABC):
"""
MetricExporter is the base class for all metric exporters.
"""
@abstractmethod
def shutdown(self):
"""
Export the metrics.
"""
_GLOBAL_METRIC_PROVIDER: Optional[MetricProvider] = None
def set_metric_provider(provider):
"""
Set the global metric provider.
"""
global _GLOBAL_METRIC_PROVIDER
_GLOBAL_METRIC_PROVIDER = provider
def get_metric_provider():
"""
Get the global metric provider.
"""
global _GLOBAL_METRIC_PROVIDER
if _GLOBAL_METRIC_PROVIDER is None:
raise ValueError("No metric provider has been set.")
return _GLOBAL_METRIC_PROVIDER
@@ -0,0 +1,6 @@
# coding: utf-8
# Copyright (c) 2025 inclusionAI.
from aworld.utils.import_package import import_package
import_package('opentelemetry.instrumentation.system_metrics',
install_name='opentelemetry-instrumentation-system-metrics', version='0.53b1')
@@ -0,0 +1,356 @@
import os
from urllib.parse import urljoin
from typing import Optional, Sequence
from typing_extensions import LiteralString
from uuid import uuid4
from opentelemetry import metrics
from opentelemetry.sdk.resources import Resource
from opentelemetry.semconv.resource import ResourceAttributes
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader, ConsoleMetricExporter
from aworld.metrics.metric import (
Gauge,
Histogram,
MetricProvider,
Counter,
MetricExporter,
UpDownCounter,
get_metric_provider,
set_metric_provider
)
MEMORY_FIELDS: list[LiteralString] = 'available used free active inactive buffers cached shared wired slab'.split()
"""
The fields of the memory information returned by psutil.virtual_memory().
"""
class OpentelemetryMetricProvider(MetricProvider):
"""
MetricProvider is a class for providing metrics.
"""
def __init__(self, exporter: MetricExporter = None):
"""Initialize the MetricProvider.
Args:
exporter: The exporter of the metric.
"""
super().__init__()
if not exporter:
exporter = ConsoleMetricExporter()
self._exporter = exporter
self._otel_provider = MeterProvider(
metric_readers=[PeriodicExportingMetricReader(
exporter=self._exporter, export_interval_millis=5000)],
resource=build_otel_resource()
)
metrics.set_meter_provider(self._otel_provider)
self._meter = self._otel_provider.get_meter("aworld")
def create_counter(self,
name: str,
description: str,
unit: str,
labelnames: Optional[Sequence[str]] = None) -> Counter:
"""
Create a counter.
Args:
name: The name of the counter.
description: The description of the counter.
unit: The unit of the counter.
"""
return OpentelemetryCounter(name, description, unit, self)
def create_un_down_counter(self,
name: str,
description: str,
unit: str,
labelnames: Optional[Sequence[str]] = None) -> UpDownCounter:
"""
Create a un-down counter.
Args:
name: The name of the counter.
description: The description of the counter.
unit: The unit of the counter.
"""
return OpentelemetryUpDownCounter(name, description, unit, self)
def create_gauge(self,
name: str,
description: str,
unit: str,
labelnames: Optional[Sequence[str]] = None) -> Gauge:
"""
Create a gauge.
Args:
name: The name of the gauge.
description: The description of the gauge.
unit: The unit of the gauge.
"""
return OpentelemetryGauge(name, description, unit, self)
def create_histogram(self,
name: str,
description: str,
unit: str,
buckets: Optional[Sequence[float]] = None,
labelnames: Optional[Sequence[str]] = None) -> Histogram:
"""
Create a histogram.
Args:
name: The name of the histogram.
description: The description of the histogram.
unit: The unit of the histogram.
buckets: The buckets of the histogram.
"""
return OpentelemetryHistogram(name, description, unit, self, buckets)
def shutdown(self):
"""
Shutdown the metric provider.
"""
self._exporter.shutdown()
self._otel_provider.shutdown()
class OpentelemetryCounter(Counter):
"""
OpentelemetryCounter is a subclass of Counter, representing a counter metric.
A counter is a cumulative metric that represents a single numerical value that only ever goes up.
"""
def __init__(self,
name: str,
description: str,
unit: str,
provider: OpentelemetryMetricProvider):
"""
Initialize the Counter.
Args:
name: The name of the counter.
description: The description of the counter.
unit: The unit of the counter.
provider: The provider of the counter.
"""
super().__init__(name, description, unit, provider)
self._counter = provider._meter.create_counter(
name=name, description=description, unit=unit)
def add(self, value: int, labels: dict = None) -> None:
"""
Add a value to the counter.
Args:
value: The value to add to the counter.
labels: The labels to associate with the value.
"""
if labels is None:
labels = {}
self._counter.add(value, labels)
class OpentelemetryUpDownCounter(UpDownCounter):
"""
OpentelemetryUpDownCounter is a subclass of UpDownCounter, representing an un-down counter metric.
An un-down counter is a cumulative metric that represents a single numerical value that only ever goes up.
"""
def __init__(self,
name: str,
description: str,
unit: str,
provider: OpentelemetryMetricProvider):
"""
Initialize the UnDownCounter.
Args:
name: The name of the counter.
description: The description of the counter.
unit: The unit of the counter.
provider: The provider of the counter.
"""
super().__init__(name, description, unit, provider)
self._counter = provider._meter.create_up_down_counter(
name=name, description=description, unit=unit)
def inc(self, value: int, labels: dict = None) -> None:
"""
Add a value to the counter.
Args:
value: The value to add to the counter.
labels: The labels to associate with the value.
"""
if labels is None:
labels = {}
self._counter.add(value, labels)
def dec(self, value: int, labels: dict = None) -> None:
"""
Subtract a value from the counter.
Args:
value: The value to subtract from the counter.
labels: The labels to associate with the value.
"""
if labels is None:
labels = {}
self._counter.add(-value, labels)
class OpentelemetryGauge(Gauge):
"""
OpentelemetryGauge is a subclass of Gauge, representing a gauge metric.
A gauge is a metric that represents a single numerical value that can arbitrarily go up and down.
"""
def __init__(self,
name: str,
description: str,
unit: str,
provider: OpentelemetryMetricProvider):
"""
Initialize the Gauge.
Args:
name: The name of the gauge.
description: The description of the gauge.
unit: The unit of the gauge.
provider: The provider of the gauge.
"""
super().__init__(name, description, unit, provider)
self._gauge = provider._meter.create_gauge(
name=name, description=description, unit=unit)
def set(self, value: int, labels: dict = None) -> None:
"""
Set the value of the gauge.
Args:
value: The value to set the gauge to.
labels: The labels to associate with the value.
"""
if labels is None:
labels = {}
self._gauge.set(value, labels)
class OpentelemetryHistogram(Histogram):
"""
OpentelemetryHistogram is a subclass of Histogram, representing a histogram metric.
A histogram is a metric that represents the distribution of a set of values.
"""
def __init__(self,
name: str,
description: str,
unit: str,
provider: OpentelemetryMetricProvider,
buckets: Sequence[float] = None):
"""
Initialize the Histogram.
Args:
name: The name of the histogram.
description: The description of the histogram.
unit: The unit of the histogram.
provider: The provider of the histogram.
buckets: The buckets of the histogram.
"""
super().__init__(name, description, unit, provider, buckets)
self._histogram = provider._meter.create_histogram(name=name,
description=description,
unit=unit,
explicit_bucket_boundaries_advisory=buckets)
def record(self, value: int, labels: dict = None) -> None:
"""
Record a value in the histogram.
Args:
value: The value to record in the histogram.
labels: The labels to associate with the value.
"""
if labels is None:
labels = {}
self._histogram.record(value, labels)
def configure_otlp_provider(backend: Sequence[str] = None,
base_url: str = None,
write_token: str = None,
**kwargs
) -> None:
"""
Configure the OpenTelemetry provider.
Args:
backends: The backends to use.
base_url: The base URL of the backend.
write_token: The write token of the backend.
**kwargs: The keyword arguments to pass to the backend.
"""
import requests
from opentelemetry.exporter.otlp.proto.http import Compression
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
if backend == "console":
set_metric_provider(OpentelemetryMetricProvider())
elif backend == "logfire":
base_url = base_url or "https://logfire-us.pydantic.dev"
headers = {'User-Agent': f'logfire/3.14.0',
'Authorization': write_token}
session = requests.Session()
session.headers.update(headers)
exporter = OTLPMetricExporter(
endpoint=urljoin(base_url, '/v1/metrics'),
session=session,
compression=Compression.Gzip,
)
set_metric_provider(OpentelemetryMetricProvider(exporter))
elif backend == "antmonitor":
ant_otlp_endpoint = os.getenv("ANT_OTEL_ENDPOINT")
base_url = base_url or ant_otlp_endpoint
session = requests.Session()
session.timeout = 30
exporter = OTLPMetricExporter(
endpoint=base_url,
session=session,
compression=Compression.Gzip,
timeout=30
)
set_metric_provider(OpentelemetryMetricProvider(exporter))
metrics_system_enabled = kwargs.get("metrics_system_enabled") or os.getenv(
"METRICS_SYSTEM_ENABLED") or "false"
if metrics_system_enabled.lower() == "true":
instrument_system_metrics()
def instrument_system_metrics():
"""
Instrument system metrics.
"""
try:
from opentelemetry.instrumentation.system_metrics import (
_DEFAULT_CONFIG,
SystemMetricsInstrumentor
)
except ImportError:
raise ImportError(
"Could not import opentelemetry.instrumentation.system_metrics, please install it with `pip install opentelemetry-instrumentation-system-metrics`"
)
config = _DEFAULT_CONFIG.copy()
config['system.memory.usage'] = MEMORY_FIELDS + ['total']
config['system.memory.utilization'] = MEMORY_FIELDS
config['system.swap.utilization'] = ['used']
instrumentor = SystemMetricsInstrumentor(config=config)
otel_provider = get_metric_provider()._otel_provider
instrumentor.instrument(meter_provider=otel_provider)
def build_otel_resource():
"""
Build the OpenTelemetry resource.
"""
service_name = os.getenv("MONITOR_SERVICE_NAME") or "aworld"
return Resource(
attributes={
ResourceAttributes.SERVICE_NAME: service_name,
ResourceAttributes.SERVICE_NAMESPACE: "aworld",
ResourceAttributes.SERVICE_INSTANCE_ID: uuid4().hex
}
)
@@ -0,0 +1,5 @@
# coding: utf-8
# Copyright (c) 2025 inclusionAI.
from aworld.utils.import_package import import_packages
import_packages(['prometheus_client'])
@@ -0,0 +1,386 @@
import time
import threading
from typing import Sequence, Optional, Dict, List
from prometheus_client import Counter as PCounter, Gauge as PGauge, Histogram as PHistogram, CollectorRegistry
from prometheus_client import start_http_server, REGISTRY
from aworld.metrics.metric import(
MetricProvider,
Counter,
UpDownCounter,
MetricExporter,
Gauge,
Histogram,
set_metric_provider
)
class PrometheusMetricProvider(MetricProvider):
"""
PrometheusMetricProvider is a subclass of MetricProvider, representing a metric provider for Prometheus.
"""
def __init__(self, exporter: MetricExporter):
"""
Initialize the PrometheusMetricProvider.
Args:
port: The port to use for the Prometheus server.
"""
super().__init__()
self.exporter = exporter
def shutdown(self) -> None:
"""
Shutdown the PrometheusMetricProvider.
"""
self.exporter.shutdown()
def create_counter(self, name: str, description: str, unit: str,
labelnames: Optional[Sequence[str]] = None) -> Counter:
"""
Create a counter metric.
Args:
name: The name of the metric.
description: The description of the metric.
unit: The unit of the metric.
Returns:
The counter metric.
"""
return PrometheusCounter(name, description, unit, self, labelnames)
def create_un_down_counter(self, name: str, description: str, unit: str,
labelnames: Optional[Sequence[str]] = None) -> UpDownCounter:
"""
Create an up-down counter metric.
Args:
name: The name of the metric.
description: The description of the metric.
unit: The unit of the metric.
Returns:
The up-down counter metric.
"""
return PrometheusUpDownCounter(name, description, unit, self, labelnames)
def create_gauge(self, name: str, description: str, unit: str, labelnames: Optional[Sequence[str]] = None) -> Gauge:
"""
Create a gauge metric.
Args:
name: The name of the metric.
description: The description of the metric.
unit: The unit of the metric.
Returns:
The gauge metric.
"""
return PrometheusGauge(name, description, unit, self, labelnames)
def create_histogram(self,
name: str,
description: str,
unit: str,
buckets: Optional[Sequence[float]] = None,
labelnames: Optional[Sequence[str]] = None) -> Histogram:
"""
Create a histogram metric.
Args:
name: The name of the metric.
description: The description of the metric.
unit: The unit of the metric.
buckets: The buckets of the histogram.
Returns:
The histogram metric.
"""
return PrometheusHistogram(name, description, unit, self, buckets, labelnames)
class PrometheusCounter(Counter):
"""
PrometheusCounter is a subclass of Counter, representing a counter metric for Prometheus.
"""
def __init__(self,
name: str,
description: str,
unit: str,
provider: MetricProvider,
labelnames: Optional[Sequence[str]] = None):
"""
Initialize the PrometheusCounter.
Args:
name: The name of the metric.
description: The description of the metric.
unit: The unit of the metric.
provider: The provider of the metric.
"""
labelnames = labelnames or []
super().__init__(name, description, unit, provider, labelnames)
self._counter = PCounter(name=name, documentation=description, labelnames=labelnames, unit=unit)
def add(self, value: int, labels: dict = None) -> None:
"""
Add a value to the counter.
Args:
value: The value to add to the counter.
labels: The labels to associate with the value.
"""
if labels:
self._counter.labels(**labels).inc(value)
else:
self._counter.inc(value)
class PrometheusUpDownCounter(UpDownCounter):
"""
PrometheusUpDownCounter is a subclass of UpDownCounter, representing an up-down counter metric for Prometheus.
"""
def __init__(self,
name: str,
description: str,
unit: str,
provider: MetricProvider,
labelnames: Optional[Sequence[str]] = None):
"""
Initialize the PrometheusUpDownCounter.
Args:
name: The name of the metric.
description: The description of the metric.
unit: The unit of the metric.
provider: The provider of the metric.
"""
labelnames = labelnames or []
super().__init__(name, description, unit, provider, labelnames)
self._gauge = PGauge(name=name, documentation=description, labelnames=labelnames, unit=unit)
def inc(self, value: int, labels: dict = None) -> None:
"""
Add a value to the counter.
Args:
value: The value to add to the counter.
labels: The labels to associate with the value.
"""
if labels:
self._gauge.labels(**labels).inc(value)
else:
self._gauge.inc(value)
def dec(self, value: int, labels: dict = None) -> None:
"""
Subtract a value from the counter.
Args:
value: The value to subtract from the counter.
labels: The labels to associate with the value.
"""
if labels:
self._gauge.labels(**labels).dec(value)
else:
self._gauge.dec(value)
class PrometheusGauge(Gauge):
"""
PrometheusGauge is a subclass of Gauge, representing a gauge metric for Prometheus.
"""
def __init__(self,
name: str,
description: str,
unit: str,
provider: MetricProvider,
labelnames: Optional[Sequence[str]] = None):
"""
Initialize the PrometheusGauge.
Args:
name: The name of the metric.
description: The description of the metric.
unit: The unit of the metric.
provider: The provider of the metric.
"""
labelnames = labelnames or []
super().__init__(name, description, unit, provider, labelnames)
self._gauge = PGauge(name=name, documentation=description, labelnames=labelnames, unit=unit)
def set(self, value: int, labels: dict = None) -> None:
"""
Set the value of the gauge.
Args:
value: The value to set the gauge to.
labels: The labels to associate with the value.
"""
if labels:
self._gauge.labels(**labels).set(value)
else:
self._gauge.set(value)
def inc(self, value: int, labels: dict = None) -> None:
"""
Add a value to the gauge.
Args:
value: The value to add to the gauge.
labels: The labels to associate with the value.
"""
if labels:
self._gauge.labels(**labels).inc(value)
else:
self._gauge.inc(value)
def dec(self, value: int, labels: dict = None) -> None:
"""
Subtract a value from the gauge.
Args:
value: The value to subtract from the gauge.
labels: The labels to associate with the value.
"""
if labels:
self._gauge.labels(**labels).dec(value)
else:
self._gauge.dec(value)
class PrometheusHistogram(Histogram):
"""
PrometheusHistogram is a subclass of Histogram, representing a histogram metric for Prometheus.
"""
def __init__(self,
name: str,
description: str,
unit: str,
provider: MetricProvider,
buckets: Sequence[float] = None,
labelnames: Optional[Sequence[str]] = None):
"""
Initialize the PrometheusHistogram.
Args:
name: The name of the metric.
description: The description of the metric.
unit: The unit of the metric.
provider: The provider of the metric.
"""
labelnames = labelnames or []
super().__init__(name, description, unit, provider, buckets, labelnames)
if buckets:
self._histogram = PHistogram(name=name, documentation=description, labelnames=labelnames, unit=unit,
buckets=buckets)
else:
self._histogram = PHistogram(name=name, documentation=description, labelnames=labelnames, unit=unit)
def record(self, value: int, labels: dict = None) -> None:
"""
Record a value in the histogram.
Args:
value: The value to record in the histogram.
labels: The labels to associate with the value.
"""
if labels:
self._histogram.labels(**labels).observe(value)
else:
self._histogram.observe(value)
class PrometheusMetricExporter(MetricExporter):
"""
PrometheusMetricExporter is a class for exporting metrics to Prometheus.
"""
def __init__(self, port: int = 8000):
"""
Initialize the PrometheusMetricExporter.
Args:
port: The port to use for the Prometheus server.
"""
self.port = port
server, server_thread = start_http_server(self.port)
self.server = server
self.server_thread = server_thread
def shutdown(self) -> None:
"""
Shutdown the PrometheusMetricExporter.
"""
self.server.shutdown()
self.server_thread.join()
class PrometheusConsoleMetricExporter(MetricExporter):
"""Implementation of :class:`MetricExporter` that prints metrics to the
console.
This class can be used for diagnostic purposes. It prints the exported
metrics to the console STDOUT.
"""
def __init__(self, out_interval_secs: float = 1.0):
"""Initialize the console exporter."""
self._should_shutdown = False
self.out_interval_secs = out_interval_secs
self.metrics_thread = threading.Thread(target=self._output_metrics_to_console)
self.metrics_thread.daemon = True
self.metrics_thread.start()
def generate_latest(self, registry: CollectorRegistry = REGISTRY) -> bytes:
"""Returns the metrics from the registry in latest text format as a string."""
def sample_line(line):
if line.labels:
labelstr = '{{{0}}}'.format(','.join(
['{}="{}"'.format(
k, v.replace('\\', r'\\').replace('\n', r'\n').replace('"', r'\"'))
for k, v in sorted(line.labels.items())]))
else:
labelstr = ''
timestamp = ''
if line.timestamp is not None:
# Convert to milliseconds.
timestamp = f' {int(float(line.timestamp) * 1000):d}'
return f'{line.name}{labelstr} {line.value}{timestamp}\n'
output = []
for metric in registry.collect():
try:
om_samples: Dict[str, List[str]] = {}
for s in metric.samples:
for suffix in ['_gsum', '_gcount']:
if s.name == metric.name + suffix:
# OpenMetrics specific sample, put in a gauge at the end.
om_samples.setdefault(suffix, []).append(sample_line(s))
break
else:
output.append(sample_line(s))
except Exception as exception:
exception.args = (exception.args or ('',)) + (metric,)
raise
for suffix, lines in sorted(om_samples.items()):
output.extend(lines)
return ''.join(output).encode('utf-8')
def _output_metrics_to_console(self):
while not self._should_shutdown:
metrics_text = self.generate_latest(REGISTRY)
print(metrics_text.decode('utf-8'))
time.sleep(self.out_interval_secs)
def shutdown(self) -> None:
"""
Shutdown the PrometheusConsoleMetricExporter.
"""
self._should_shutdown = True
def configure_prometheus_provider(backend: str,
base_url: str = None,
write_token: str = None,
**kwargs
):
"""
Initialize the prometheus metric provider.
Args:
backend: The backend of the metric provider.
base_url: The base url of the metric provider.
write_token: The write token of the metric provider.
"""
if backend == "console":
exporter = PrometheusConsoleMetricExporter(out_interval_secs=2)
set_metric_provider(PrometheusMetricProvider(exporter))
elif backend == "prometheus":
exporter = PrometheusMetricExporter()
set_metric_provider(PrometheusMetricProvider(exporter))
@@ -0,0 +1,43 @@
from typing import Sequence
from pydantic import BaseModel, Field, model_validator
from typing import Optional
class MetricTemplate(BaseModel):
"""
MetricTemplate is a class for defining a metric template.
"""
type: str
name: str
description: Optional[str] = None
unit: Optional[str] = Field(default="1")
labels: Optional[list[str]] = None
buckets: Optional[Sequence[float]] = None
@model_validator(mode='before')
def set_default_description(cls, values):
"""
Set the default description if it is not set.
"""
if 'description' not in values or values['description'] is None:
values['description'] = values['name']
return values
class MetricTemplates:
REQUEST_COUNT = MetricTemplate(**{
"type": "counter",
"name": "request_count",
"description": "The number of requests received",
"unit": "1",
"labels": ["method", "status"]
})
REQUEST_LATENCY = MetricTemplate(**{
"type": "histogram",
"name": "request_latency",
"description": "The latency of requests",
"unit": "ms",
"labels": ["method", "status"],
# "buckets": [0.01, 0.05, 0.1, 0.5, 1, 5, 10, 50, 100, 500, 1000]
})