Skip to content

Commit 1edaf30

Browse files
CopilotBorda
andcommitted
Address PR review feedback - code quality improvements
- Use absolute imports in base.py (comment 2744902663) - Move prometheus example instructions to module docstring (comment 2744908071) - Use contextlib.suppress for exception handling (comments 2744912772, SIM105) - Remove trailing commas for 120 line length (comments 2744919532, 2744929433) - Add comment explaining yields in collector (comment 2744926357) - Use single formatted string appends (comment 2744927877) - Fix README prometheus_client mode documentation (comment 2744928794) - Clarify cache size metrics backend support (comment 2744928804) - Pass host parameter to start_http_server (comment 2744928825) - Fix metric names consistency with _total suffix (comment 2744928839) - Remove unused _last_seen dict (comment 2744928850) - Use monotonic clock for windowed latency calculations (comment 2744928866) - Record miss on stale hit for accurate hit rate (comment 2744928891) - Add explanatory comment to except clause (comment 2744928901) - Don't swallow exceptions in start() method (comment 2744928818) All 21 tests passing Co-authored-by: Borda <6035284+Borda@users.noreply.github.com>
1 parent fad7009 commit 1edaf30

5 files changed

Lines changed: 161 additions & 165 deletions

File tree

Lines changed: 115 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -1,144 +1,126 @@
1-
"""Demonstration of Prometheus metrics exporter for cachier.
1+
"""Prometheus Exporter Example for Cachier.
22
3-
This example shows how to export cachier metrics to Prometheus for monitoring. The exporter can work with or without the
4-
prometheus_client library.
3+
This example demonstrates using the PrometheusExporter to export cache metrics
4+
to Prometheus for monitoring and alerting.
55
6-
"""
7-
8-
import time
9-
10-
from cachier import cachier
11-
from cachier.exporters import PrometheusExporter
6+
Usage with Prometheus
7+
---------------------
128
13-
print("=" * 60)
14-
print("Cachier Prometheus Exporter Demo")
15-
print("=" * 60)
16-
17-
18-
# Define some cached functions with metrics enabled
19-
@cachier(backend="memory", enable_metrics=True)
20-
def calculate_square(x):
21-
"""Calculate square of a number."""
22-
time.sleep(0.01) # Simulate computation
23-
return x**2
24-
25-
26-
@cachier(backend="memory", enable_metrics=True)
27-
def calculate_cube(x):
28-
"""Calculate cube of a number."""
29-
time.sleep(0.01) # Simulate computation
30-
return x**3
31-
32-
33-
# Create a Prometheus exporter
34-
# Set use_prometheus_client=False to use built-in text format
35-
exporter = PrometheusExporter(port=9100, use_prometheus_client=False)
36-
37-
# Register functions to export
38-
print("\nRegistering functions with exporter...")
39-
exporter.register_function(calculate_square)
40-
exporter.register_function(calculate_cube)
41-
print("✓ Functions registered")
42-
43-
# Generate some cache activity
44-
print("\nGenerating cache activity...")
45-
calculate_square.clear_cache()
46-
calculate_cube.clear_cache()
47-
48-
# Create some metrics
49-
for i in range(20):
50-
calculate_square(i % 5) # Will create hits and misses
51-
52-
for i in range(15):
53-
calculate_cube(i % 3)
54-
55-
print("✓ Generated activity on both functions")
56-
57-
# Display metrics for each function
58-
print("\n" + "=" * 60)
59-
print("Metrics Summary")
60-
print("=" * 60)
61-
62-
square_stats = calculate_square.metrics.get_stats()
63-
print("\ncalculate_square:")
64-
print(f" Hits: {square_stats.hits}")
65-
print(f" Misses: {square_stats.misses}")
66-
print(f" Hit rate: {square_stats.hit_rate:.1f}%")
67-
print(f" Total calls: {square_stats.total_calls}")
68-
69-
cube_stats = calculate_cube.metrics.get_stats()
70-
print("\ncalculate_cube:")
71-
print(f" Hits: {cube_stats.hits}")
72-
print(f" Misses: {cube_stats.misses}")
73-
print(f" Hit rate: {cube_stats.hit_rate:.1f}%")
74-
print(f" Total calls: {cube_stats.total_calls}")
75-
76-
# Generate Prometheus text format
77-
print("\n" + "=" * 60)
78-
print("Prometheus Text Format Export")
79-
print("=" * 60)
80-
81-
metrics_text = exporter._generate_text_metrics()
82-
print("\nSample of exported metrics:")
83-
print("-" * 60)
84-
# Print first 20 lines
85-
lines = metrics_text.split("\n")[:20]
86-
for line in lines:
87-
print(line)
88-
print("...")
89-
print(f"\nTotal lines exported: {len(metrics_text.split(chr(10)))}")
90-
91-
# Instructions for using with Prometheus
92-
print("\n" + "=" * 60)
93-
print("Usage with Prometheus")
94-
print("=" * 60)
95-
print("""
969
To use this exporter with Prometheus:
9710
9811
1. Start the exporter HTTP server:
9912
>>> exporter.start()
10013
101-
2. Add to your prometheus.yml:
14+
2. Configure Prometheus to scrape the metrics endpoint.
15+
Add this to your prometheus.yml:
16+
10217
scrape_configs:
10318
- job_name: 'cachier'
10419
static_configs:
105-
- targets: ['localhost:9100']
106-
107-
3. Access metrics at http://localhost:9100/metrics
108-
109-
4. Query in Prometheus:
110-
- cachier_cache_hit_rate
111-
- rate(cachier_cache_hits_total[5m])
112-
- cachier_entry_count
113-
114-
Alternative: Use with prometheus_client
115-
---------------------------------------
116-
If you have prometheus_client installed:
117-
118-
>>> from prometheus_client import start_http_server
119-
>>> exporter = PrometheusExporter(port=9100, use_prometheus_client=True)
120-
>>> exporter.register_function(my_cached_func)
121-
>>> exporter.start()
122-
123-
This provides additional features like:
124-
- Automatic metric registration
125-
- Built-in histograms
126-
- Gauges and counters
127-
- Integration with Prometheus pushgateway
128-
""")
129-
130-
print("\n" + "=" * 60)
131-
print("Demo Complete")
132-
print("=" * 60)
133-
print("""
134-
Key Benefits:
135-
• Track cache performance in production
136-
• Identify optimization opportunities
137-
• Set up alerts for low hit rates
138-
• Monitor cache effectiveness over time
139-
• Integrate with existing monitoring infrastructure
140-
""")
141-
142-
# Clean up
143-
calculate_square.clear_cache()
144-
calculate_cube.clear_cache()
20+
- targets: ['localhost:9090']
21+
22+
3. Access metrics at http://localhost:9090/metrics
23+
24+
4. Create dashboards in Grafana or set up alerts based on:
25+
- cachier_cache_hit_rate (target: > 80%)
26+
- cachier_cache_misses_total (alert on spikes)
27+
- cachier_avg_latency_ms (monitor performance)
28+
29+
Available Metrics
30+
-----------------
31+
- cachier_cache_hits_total: Total number of cache hits
32+
- cachier_cache_misses_total: Total number of cache misses
33+
- cachier_cache_hit_rate: Cache hit rate percentage
34+
- cachier_avg_latency_ms: Average cache operation latency
35+
- cachier_stale_hits_total: Total stale cache hits
36+
- cachier_recalculations_total: Total cache recalculations
37+
- cachier_entry_count: Current number of cache entries
38+
- cachier_cache_size_bytes: Total cache size in bytes
39+
- cachier_size_limit_rejections_total: Entries rejected due to size limit
40+
41+
"""
42+
43+
import time
44+
45+
from cachier import cachier
46+
from cachier.exporters import PrometheusExporter
47+
48+
49+
def demo_basic_metrics():
50+
"""Demonstrate basic metrics collection."""
51+
print("\n=== Basic Metrics Collection ===")
52+
53+
@cachier(backend="memory", enable_metrics=True)
54+
def compute(x):
55+
time.sleep(0.1) # Simulate work
56+
return x * 2
57+
58+
compute.clear_cache()
59+
60+
# Generate some traffic
61+
for i in range(5):
62+
result = compute(i)
63+
print(f" compute({i}) = {result}")
64+
65+
# Access hits create cache hits
66+
for i in range(3):
67+
compute(i)
68+
69+
stats = compute.metrics.get_stats()
70+
print("\nMetrics:")
71+
print(f" Hits: {stats.hits}")
72+
print(f" Misses: {stats.misses}")
73+
print(f" Hit Rate: {stats.hit_rate:.1f}%")
74+
print(f" Avg Latency: {stats.avg_latency_ms:.2f}ms")
75+
76+
compute.clear_cache()
77+
78+
79+
def demo_prometheus_export():
80+
"""Demonstrate exporting metrics to Prometheus."""
81+
print("\n=== Prometheus Export ===")
82+
83+
@cachier(backend="memory", enable_metrics=True)
84+
def calculate(x, y):
85+
return x + y
86+
87+
calculate.clear_cache()
88+
89+
# Create exporter
90+
exporter = PrometheusExporter(port=9090, use_prometheus_client=False)
91+
exporter.register_function(calculate)
92+
93+
# Generate some metrics
94+
calculate(1, 2)
95+
calculate(1, 2) # hit
96+
calculate(3, 4) # miss
97+
98+
# Show text format metrics
99+
metrics_text = exporter._generate_text_metrics()
100+
print("\nGenerated Prometheus metrics:")
101+
print(metrics_text[:500] + "...")
102+
103+
print("\nNote: In production, call exporter.start() to serve metrics")
104+
print(" Metrics would be available at http://localhost:9090/metrics")
105+
106+
calculate.clear_cache()
107+
108+
109+
def main():
110+
"""Run all demonstrations."""
111+
print("Cachier Prometheus Exporter Demo")
112+
print("=" * 60)
113+
114+
# Print usage instructions from module docstring
115+
if __doc__:
116+
print(__doc__)
117+
118+
demo_basic_metrics()
119+
demo_prometheus_export()
120+
121+
print("\n" + "=" * 60)
122+
print("✓ All demonstrations completed!")
123+
124+
125+
if __name__ == "__main__":
126+
main()

src/cachier/core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ def cachier(
302302
hash_func=hash_func,
303303
wait_for_calc_timeout=wait_for_calc_timeout,
304304
entry_size_limit=size_limit_bytes,
305-
metrics=cache_metrics,
305+
metrics=cache_metrics
306306
)
307307
elif backend == "sql":
308308
core = _SQLCore(

src/cachier/cores/base.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616

1717
from pympler import asizeof # type: ignore
1818

19-
from .._types import HashFunc
20-
from ..config import CacheEntry, _update_with_defaults
19+
from cachier._types import HashFunc
20+
from cachier.config import CacheEntry, _update_with_defaults
2121

2222
if TYPE_CHECKING:
23-
from ..metrics import CacheMetrics
23+
from cachier.metrics import CacheMetrics
2424

2525

2626
class RecalculationNeeded(Exception):
@@ -122,14 +122,14 @@ def _update_size_metrics(self) -> None:
122122
"""
123123
if self.metrics is None:
124124
return
125-
try:
126-
# Get cache size - subclasses should override if they can provide this
125+
from contextlib import suppress
126+
127+
# Get cache size - subclasses should override if they can provide this
128+
# Suppress errors if subclass doesn't implement size tracking
129+
with suppress(AttributeError, NotImplementedError):
127130
entry_count = self._get_entry_count()
128131
total_size = self._get_total_size()
129132
self.metrics.update_size_metrics(entry_count, total_size)
130-
except (AttributeError, NotImplementedError):
131-
# Silently skip if subclass doesn't implement size tracking
132-
pass
133133

134134
def _get_entry_count(self) -> int:
135135
"""Get the number of entries in the cache.

0 commit comments

Comments
 (0)