Coverage for /Users/antonigmitruk/golf/src/golf/metrics/collector.py: 0%
95 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-08-16 18:46 +0200
« prev ^ index » next coverage.py v7.6.12, created at 2025-08-16 18:46 +0200
1"""Metrics collector for Golf MCP servers."""
3from typing import Optional
5# Global metrics collector instance
6_metrics_collector: Optional["MetricsCollector"] = None
9class MetricsCollector:
10 """Collects metrics for Golf MCP servers using Prometheus client."""
12 def __init__(self, enabled: bool = False) -> None:
13 """Initialize the metrics collector.
15 Args:
16 enabled: Whether metrics collection is enabled
17 """
18 self.enabled = enabled
19 self._metrics = {}
21 if self.enabled:
22 self._init_prometheus_metrics()
24 def _init_prometheus_metrics(self) -> None:
25 """Initialize Prometheus metrics if enabled."""
26 try:
27 from prometheus_client import Counter, Histogram, Gauge
29 # Tool execution metrics
30 self._metrics["tool_executions"] = Counter(
31 "golf_tool_executions_total",
32 "Total number of tool executions",
33 ["tool_name", "status"],
34 )
36 self._metrics["tool_duration"] = Histogram(
37 "golf_tool_duration_seconds",
38 "Tool execution duration in seconds",
39 ["tool_name"],
40 )
42 # HTTP request metrics
43 self._metrics["http_requests"] = Counter(
44 "golf_http_requests_total",
45 "Total number of HTTP requests",
46 ["method", "status_code", "path"],
47 )
49 self._metrics["http_duration"] = Histogram(
50 "golf_http_request_duration_seconds",
51 "HTTP request duration in seconds",
52 ["method", "path"],
53 )
55 # Resource access metrics
56 self._metrics["resource_reads"] = Counter(
57 "golf_resource_reads_total",
58 "Total number of resource reads",
59 ["resource_uri"],
60 )
62 # Prompt generation metrics
63 self._metrics["prompt_generations"] = Counter(
64 "golf_prompt_generations_total",
65 "Total number of prompt generations",
66 ["prompt_name"],
67 )
69 # Sampling metrics
70 self._metrics["sampling_requests"] = Counter(
71 "golf_sampling_requests_total",
72 "Total number of sampling requests",
73 ["sampling_type", "status"],
74 )
76 self._metrics["sampling_duration"] = Histogram(
77 "golf_sampling_duration_seconds",
78 "Sampling request duration in seconds",
79 ["sampling_type"],
80 )
82 self._metrics["sampling_tokens"] = Histogram(
83 "golf_sampling_tokens",
84 "Number of tokens in sampling responses",
85 ["sampling_type"],
86 )
88 # Elicitation metrics
89 self._metrics["elicitation_requests"] = Counter(
90 "golf_elicitation_requests_total",
91 "Total number of elicitation requests",
92 ["elicitation_type", "status"],
93 )
95 self._metrics["elicitation_duration"] = Histogram(
96 "golf_elicitation_duration_seconds",
97 "Elicitation request duration in seconds",
98 ["elicitation_type"],
99 )
101 # Error metrics
102 self._metrics["errors"] = Counter(
103 "golf_errors_total",
104 "Total number of errors",
105 ["component_type", "error_type"],
106 )
108 # Session metrics
109 self._metrics["sessions_total"] = Counter("golf_sessions_total", "Total number of sessions created")
111 self._metrics["session_duration"] = Histogram(
112 "golf_session_duration_seconds", "Session duration in seconds"
113 )
115 # System metrics
116 self._metrics["uptime"] = Gauge("golf_uptime_seconds", "Server uptime in seconds")
118 except ImportError:
119 # Prometheus client not available, disable metrics
120 self.enabled = False
122 def increment_tool_execution(self, tool_name: str, status: str) -> None:
123 """Record a tool execution.
125 Args:
126 tool_name: Name of the tool that was executed
127 status: Execution status ('success' or 'error')
128 """
129 if not self.enabled or "tool_executions" not in self._metrics:
130 return
132 self._metrics["tool_executions"].labels(tool_name=tool_name, status=status).inc()
134 def record_tool_duration(self, tool_name: str, duration: float) -> None:
135 """Record tool execution duration.
137 Args:
138 tool_name: Name of the tool
139 duration: Execution duration in seconds
140 """
141 if not self.enabled or "tool_duration" not in self._metrics:
142 return
144 self._metrics["tool_duration"].labels(tool_name=tool_name).observe(duration)
146 def increment_http_request(self, method: str, status_code: int, path: str) -> None:
147 """Record an HTTP request.
149 Args:
150 method: HTTP method (GET, POST, etc.)
151 status_code: HTTP status code
152 path: Request path
153 """
154 if not self.enabled or "http_requests" not in self._metrics:
155 return
157 self._metrics["http_requests"].labels(method=method, status_code=str(status_code), path=path).inc()
159 def record_http_duration(self, method: str, path: str, duration: float) -> None:
160 """Record HTTP request duration.
162 Args:
163 method: HTTP method
164 path: Request path
165 duration: Request duration in seconds
166 """
167 if not self.enabled or "http_duration" not in self._metrics:
168 return
170 self._metrics["http_duration"].labels(method=method, path=path).observe(duration)
172 def increment_resource_read(self, resource_uri: str) -> None:
173 """Record a resource read.
175 Args:
176 resource_uri: URI of the resource that was read
177 """
178 if not self.enabled or "resource_reads" not in self._metrics:
179 return
181 self._metrics["resource_reads"].labels(resource_uri=resource_uri).inc()
183 def increment_prompt_generation(self, prompt_name: str) -> None:
184 """Record a prompt generation.
186 Args:
187 prompt_name: Name of the prompt that was generated
188 """
189 if not self.enabled or "prompt_generations" not in self._metrics:
190 return
192 self._metrics["prompt_generations"].labels(prompt_name=prompt_name).inc()
194 def increment_error(self, component_type: str, error_type: str) -> None:
195 """Record an error.
197 Args:
198 component_type: Type of component ('tool', 'resource', 'prompt', 'http')
199 error_type: Type of error ('timeout', 'auth_error',
200 'validation_error', etc.)
201 """
202 if not self.enabled or "errors" not in self._metrics:
203 return
205 self._metrics["errors"].labels(component_type=component_type, error_type=error_type).inc()
207 def increment_session(self) -> None:
208 """Record a new session."""
209 if not self.enabled or "sessions_total" not in self._metrics:
210 return
212 self._metrics["sessions_total"].inc()
214 def record_session_duration(self, duration: float) -> None:
215 """Record session duration.
217 Args:
218 duration: Session duration in seconds
219 """
220 if not self.enabled or "session_duration" not in self._metrics:
221 return
223 self._metrics["session_duration"].observe(duration)
225 def set_uptime(self, seconds: float) -> None:
226 """Set the server uptime.
228 Args:
229 seconds: Server uptime in seconds
230 """
231 if not self.enabled or "uptime" not in self._metrics:
232 return
234 self._metrics["uptime"].set(seconds)
236 def increment_sampling(self, sampling_type: str, status: str) -> None:
237 """Record a sampling request.
239 Args:
240 sampling_type: Type of sampling ('sample', 'structured', 'context')
241 status: Request status ('success' or 'error')
242 """
243 if not self.enabled or "sampling_requests" not in self._metrics:
244 return
246 self._metrics["sampling_requests"].labels(sampling_type=sampling_type, status=status).inc()
248 def record_sampling_duration(self, sampling_type: str, duration: float) -> None:
249 """Record sampling request duration.
251 Args:
252 sampling_type: Type of sampling
253 duration: Request duration in seconds
254 """
255 if not self.enabled or "sampling_duration" not in self._metrics:
256 return
258 self._metrics["sampling_duration"].labels(sampling_type=sampling_type).observe(duration)
260 def record_sampling_tokens(self, sampling_type: str, token_count: int) -> None:
261 """Record sampling token count.
263 Args:
264 sampling_type: Type of sampling
265 token_count: Number of tokens in the response
266 """
267 if not self.enabled or "sampling_tokens" not in self._metrics:
268 return
270 self._metrics["sampling_tokens"].labels(sampling_type=sampling_type).observe(token_count)
272 def increment_elicitation(self, elicitation_type: str, status: str) -> None:
273 """Record an elicitation request.
275 Args:
276 elicitation_type: Type of elicitation ('elicit', 'confirmation')
277 status: Request status ('success' or 'error')
278 """
279 if not self.enabled or "elicitation_requests" not in self._metrics:
280 return
282 self._metrics["elicitation_requests"].labels(elicitation_type=elicitation_type, status=status).inc()
284 def record_elicitation_duration(self, elicitation_type: str, duration: float) -> None:
285 """Record elicitation request duration.
287 Args:
288 elicitation_type: Type of elicitation
289 duration: Request duration in seconds
290 """
291 if not self.enabled or "elicitation_duration" not in self._metrics:
292 return
294 self._metrics["elicitation_duration"].labels(elicitation_type=elicitation_type).observe(duration)
297def init_metrics_collector(enabled: bool = False) -> MetricsCollector:
298 """Initialize the global metrics collector.
300 Args:
301 enabled: Whether to enable metrics collection
303 Returns:
304 The initialized metrics collector
305 """
306 global _metrics_collector
307 _metrics_collector = MetricsCollector(enabled=enabled)
308 return _metrics_collector
311def get_metrics_collector() -> MetricsCollector:
312 """Get the global metrics collector instance.
314 Returns:
315 The metrics collector, or a disabled one if not initialized
316 """
317 global _metrics_collector
318 if _metrics_collector is None:
319 _metrics_collector = MetricsCollector(enabled=False)
320 return _metrics_collector