Skip to content

Commit f125401

Browse files
committed
Define metrics with Prometheus integration, also updated cursorrules
Signed-off-by: Tim Li <[email protected]>
1 parent 884c64f commit f125401

File tree

8 files changed

+913
-0
lines changed

8 files changed

+913
-0
lines changed

.cursorrules

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Cursor Rules for Cadence Python Client
2+
3+
## Package Management
4+
- **Always use `uv` for Python package management**
5+
- Use `uv run` for running Python commands instead of `python` directly
6+
- Use `uv sync` for installing dependencies instead of `pip install`
7+
- Use `uv tool run` for running development tools (pytest, mypy, ruff, etc.)
8+
- Only use `pip` or `python` directly when specifically required by the tool or documentation
9+
10+
## Examples
11+
```bash
12+
# ✅ Correct
13+
uv run python scripts/generate_proto.py
14+
uv run python -m pytest tests/
15+
uv tool run mypy cadence/
16+
uv tool run ruff check
17+
18+
# ❌ Avoid
19+
python scripts/generate_proto.py
20+
pip install -e ".[dev]"
21+
```
22+
23+
## Virtual Environment
24+
- The project uses `uv` for virtual environment management
25+
- Always activate the virtual environment using `uv` commands
26+
- Dependencies are managed through `pyproject.toml` and `uv.lock`
27+
28+
## Testing
29+
- Run tests with `uv run python -m pytest`
30+
- Use `uv run` for any Python script execution
31+
- Development tools should be run with `uv tool run`
32+
33+
## Code Generation
34+
- Use `uv run python scripts/generate_proto.py` for protobuf generation
35+
- Use `uv run python scripts/dev.py` for development tasks
36+
37+
## Code Quality
38+
- Run linter with auto-fix: `uv tool run ruff check --fix`
39+
- Run type checking: `uv tool run mypy cadence/`
40+
- Always run both commands before committing code changes
41+
- Use `uv tool run ruff check --fix && uv tool run mypy cadence/` to run both together
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
"""Visibility and metrics collection components for Cadence client."""
2+
3+
from .metrics import MetricsRegistry, get_default_registry
4+
from .prometheus import PrometheusMetrics, PrometheusConfig
5+
6+
__all__ = [
7+
"MetricsRegistry",
8+
"get_default_registry",
9+
"PrometheusMetrics",
10+
"PrometheusConfig",
11+
]
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
"""Core metrics collection interface and registry for Cadence client."""
2+
3+
import logging
4+
from enum import Enum
5+
from typing import Dict, Optional, Protocol, Set
6+
7+
logger = logging.getLogger(__name__)
8+
9+
10+
class MetricType(Enum):
11+
"""Types of metrics that can be collected."""
12+
13+
COUNTER = "counter"
14+
GAUGE = "gauge"
15+
HISTOGRAM = "histogram"
16+
SUMMARY = "summary"
17+
18+
19+
class MetricsCollector(Protocol):
20+
"""Protocol for metrics collection backends."""
21+
22+
def counter(
23+
self, key: str, n: int = 1, tags: Optional[Dict[str, str]] = None
24+
) -> None:
25+
"""Send a counter metric."""
26+
...
27+
28+
def gauge(
29+
self, key: str, value: float, tags: Optional[Dict[str, str]] = None
30+
) -> None:
31+
"""Send a gauge metric."""
32+
...
33+
34+
def timer(
35+
self, key: str, duration: float, tags: Optional[Dict[str, str]] = None
36+
) -> None:
37+
"""Send a timer metric."""
38+
...
39+
40+
def histogram(
41+
self, key: str, value: float, tags: Optional[Dict[str, str]] = None
42+
) -> None:
43+
"""Send a histogram metric."""
44+
...
45+
46+
47+
class NoOpMetricsCollector:
48+
"""No-op metrics collector that discards all metrics."""
49+
50+
def counter(
51+
self, key: str, n: int = 1, tags: Optional[Dict[str, str]] = None
52+
) -> None:
53+
pass
54+
55+
def gauge(
56+
self, key: str, value: float, tags: Optional[Dict[str, str]] = None
57+
) -> None:
58+
pass
59+
60+
def timer(
61+
self, key: str, duration: float, tags: Optional[Dict[str, str]] = None
62+
) -> None:
63+
pass
64+
65+
def histogram(
66+
self, key: str, value: float, tags: Optional[Dict[str, str]] = None
67+
) -> None:
68+
pass
69+
70+
71+
class MetricsRegistry:
72+
"""Registry for managing metrics collection in the Cadence client."""
73+
74+
def __init__(self, collector: Optional[MetricsCollector] = None):
75+
self._collector = collector or NoOpMetricsCollector()
76+
self._registered_metrics: Set[str] = set()
77+
78+
def set_collector(self, collector: MetricsCollector) -> None:
79+
"""Set the metrics collector backend."""
80+
self._collector = collector
81+
logger.info(f"Metrics collector set to {type(collector).__name__}")
82+
83+
def register_metric(self, name: str, metric_type: MetricType) -> None:
84+
"""Register a metric with the registry."""
85+
if name in self._registered_metrics:
86+
logger.warning(f"Metric {name} already registered")
87+
return
88+
89+
self._registered_metrics.add(name)
90+
logger.debug(f"Registered {metric_type.value} metric: {name}")
91+
92+
def counter(
93+
self, key: str, n: int = 1, tags: Optional[Dict[str, str]] = None
94+
) -> None:
95+
"""Send a counter metric."""
96+
try:
97+
self._collector.counter(key, n, tags)
98+
except Exception as e:
99+
logger.error(f"Failed to send counter {key}: {e}")
100+
101+
def gauge(
102+
self, key: str, value: float, tags: Optional[Dict[str, str]] = None
103+
) -> None:
104+
"""Send a gauge metric."""
105+
try:
106+
self._collector.gauge(key, value, tags)
107+
except Exception as e:
108+
logger.error(f"Failed to send gauge {key}: {e}")
109+
110+
def timer(
111+
self, key: str, duration: float, tags: Optional[Dict[str, str]] = None
112+
) -> None:
113+
"""Send a timer metric."""
114+
try:
115+
self._collector.timer(key, duration, tags)
116+
except Exception as e:
117+
logger.error(f"Failed to send timer {key}: {e}")
118+
119+
def histogram(
120+
self, key: str, value: float, tags: Optional[Dict[str, str]] = None
121+
) -> None:
122+
"""Send a histogram metric."""
123+
try:
124+
self._collector.histogram(key, value, tags)
125+
except Exception as e:
126+
logger.error(f"Failed to send histogram {key}: {e}")
127+
128+
129+
# Global default registry
130+
_default_registry: Optional[MetricsRegistry] = None
131+
132+
133+
def get_default_registry() -> MetricsRegistry:
134+
"""Get the default global metrics registry."""
135+
global _default_registry
136+
if _default_registry is None:
137+
_default_registry = MetricsRegistry()
138+
return _default_registry
139+
140+
141+
def set_default_registry(registry: MetricsRegistry) -> None:
142+
"""Set the default global metrics registry."""
143+
global _default_registry
144+
_default_registry = registry

0 commit comments

Comments
 (0)