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
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:
@@ -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')
|
||||
+356
@@ -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]
|
||||
})
|
||||
Reference in New Issue
Block a user