22
33namespace ProcessMaker \Services ;
44
5+ use Exception ;
6+ use Illuminate \Support \Facades \Cache ;
57use Prometheus \CollectorRegistry ;
68use Prometheus \RenderTextFormat ;
79use Prometheus \Storage \Redis ;
10+ use RuntimeException ;
811
912class MetricsService
1013{
11- private $ registry ;
14+ /**
15+ * The CollectorRegistry instance used by the MetricsService.
16+ *
17+ * @var \Prometheus\CollectorRegistry
18+ */
19+ private $ collectionRegistry ;
20+
21+ /**
22+ * The namespace used by the MetricsService.
23+ *
24+ * @var string
25+ */
26+ private $ namespace = 'app ' ;
1227
1328 /**
1429 * Initializes the MetricsService with a CollectorRegistry using the provided storage adapter.
30+ * Example:
31+ * $metricsService = new MetricsService(new Redis([
32+ * 'host' => config('database.redis.default.host'),
33+ * 'port' => config('database.redis.default.port'),
34+ * ]));
1535 *
1636 * @param mixed $adapter The storage adapter to use (e.g., Redis).
1737 */
1838 public function __construct ($ adapter = null )
1939 {
20- // Set up Redis as the adapter if none is provided
21- if ($ adapter === null ) {
22- $ adapter = new Redis ([
23- 'host ' => env ('REDIS_HOST ' , '127.0.0.1 ' ),
24- 'port ' => env ('REDIS_PORT ' , '6379 ' )
25- ]);
40+ try {
41+ // Set up Redis as the adapter if none is provided
42+ if ($ adapter === null ) {
43+ $ adapter = new Redis ([
44+ 'host ' => config ('database.redis.default.host ' ),
45+ 'port ' => config ('database.redis.default.port ' ),
46+ ]);
47+ }
48+ $ this ->collectionRegistry = new CollectorRegistry ($ adapter );
49+ } catch (Exception $ e ) {
50+ throw new RuntimeException ('Error initializing the metrics adapter: ' . $ e ->getMessage ());
2651 }
27- $ this ->registry = new CollectorRegistry ($ adapter );
52+ }
53+
54+ /**
55+ * Returns the namespace used by the MetricsService.
56+ *
57+ * @return string The namespace used by the MetricsService.
58+ */
59+ public function getNamespace (): string
60+ {
61+ return $ this ->namespace ;
62+ }
63+
64+ /**
65+ * Sets the namespace used by the MetricsService.
66+ *
67+ * @param string $namespace The namespace to set.
68+ */
69+ public function setNamespace (string $ namespace ): void
70+ {
71+ $ this ->namespace = $ namespace ;
72+ }
73+
74+ /**
75+ * Sets the CollectorRegistry used by the MetricsService.
76+ * Example:
77+ * $metricsService->setRegistry(new CollectorRegistry(new Redis([
78+ * 'host' => config('database.redis.default.host'),
79+ * 'port' => config('database.redis.default.port'),
80+ * ])));
81+ *
82+ * @param \Prometheus\CollectorRegistry $collectionRegistry The CollectorRegistry to set.
83+ */
84+ public function setRegistry (CollectorRegistry $ collectionRegistry ): void
85+ {
86+ $ this ->collectionRegistry = $ collectionRegistry ;
2887 }
2988
3089 /**
3190 * Returns the CollectorRegistry used by the MetricsService.
3291 *
3392 * @return \Prometheus\CollectorRegistry The CollectorRegistry used by the MetricsService.
3493 */
35- public function getMetrics ()
94+ public function getMetrics (): CollectorRegistry
3695 {
37- return $ this ->registry ;
96+ return $ this ->collectionRegistry ;
3897 }
3998
4099 /**
41- * Retrieves a metric by its name.
42- *
43- * This method iterates through all registered metrics and returns the first metric that matches the given name.
44- * If no metric with the specified name is found, it returns null.
100+ * Retrieves a metric by its name. The app_ prefix is added to the name of the metric.
101+ * Example:
102+ * $metricsService->getMetricByName('app_http_requests_total');
45103 *
46104 * @param string $name The name of the metric to retrieve.
47105 * @return \Prometheus\MetricFamilySamples|null The metric with the specified name, or null if not found.
48106 */
49107 public function getMetricByName (string $ name )
50108 {
51- $ metrics = $ this ->registry ->getMetricFamilySamples ();
109+ $ metrics = $ this ->collectionRegistry ->getMetricFamilySamples ();
52110 foreach ($ metrics as $ metric ) {
53111 if ($ metric ->getName () === $ name ) {
54112 return $ metric ;
@@ -59,41 +117,126 @@ public function getMetricByName(string $name)
59117
60118 /**
61119 * Registers a new counter metric.
120+ * Example:
121+ * $metricsService->registerCounter('app_http_requests_total', 'Total HTTP requests', ['method', 'endpoint', 'status']);
62122 *
63123 * @param string $name The name of the counter.
64124 * @param string $help The help text of the counter.
65125 * @param array $labels The labels of the counter.
66126 * @return \Prometheus\Counter The registered counter.
127+ * @throws \RuntimeException If a metric with the same name already exists.
67128 */
68- public function registerCounter (string $ name , string $ help , array $ labels = [])
129+ public function registerCounter (string $ name , string $ help , array $ labels = []): \ Prometheus \ Counter
69130 {
70- return $ this ->registry ->registerCounter ('app ' , $ name , $ help , $ labels );
131+ if ($ this ->getMetricByName ($ name ) !== null ) {
132+ throw new RuntimeException ("A metric with this name already exists. ' {$ name }'. " );
133+ }
134+ return $ this ->collectionRegistry ->registerCounter ($ this ->namespace , $ name , $ help , $ labels );
71135 }
72136
73137 /**
74138 * Registers a new histogram metric.
139+ * Example:
140+ * $metricsService->registerHistogram('app_http_request_duration_seconds', 'HTTP request duration in seconds', ['method', 'endpoint'], [0.1, 0.5, 1, 5, 10]);
75141 *
76142 * @param string $name The name of the histogram.
77143 * @param string $help The help text of the histogram.
78144 * @param array $labels The labels of the histogram.
79145 * @param array $buckets The buckets of the histogram.
80146 * @return \Prometheus\Histogram The registered histogram.
81147 */
82- public function registerHistogram (string $ name , string $ help , array $ labels = [], array $ buckets = [])
148+ public function registerHistogram (string $ name , string $ help , array $ labels = [], array $ buckets = [0.1 , 1 , 5 , 10 ]): \Prometheus \Histogram
149+ {
150+ return $ this ->collectionRegistry ->registerHistogram ($ this ->namespace , $ name , $ help , $ labels , $ buckets );
151+ }
152+
153+ /**
154+ * Registers a new gauge metric.
155+ * Example:
156+ * $metricsService->registerGauge('app_active_jobs', 'Number of active jobs in the queue', ['queue']);
157+ *
158+ * @param string $name The name of the gauge.
159+ * @param string $help The help text of the gauge.
160+ * @param array $labels The labels of the gauge.
161+ * @return \Prometheus\Gauge The registered gauge.
162+ */
163+ public function registerGauge (string $ name , string $ help , array $ labels = []): \Prometheus \Gauge
164+ {
165+ return $ this ->collectionRegistry ->registerGauge ($ this ->namespace , $ name , $ help , $ labels );
166+ }
167+
168+ /**
169+ * Sets a gauge metric to a specific value.
170+ * Example:
171+ * $metricsService->setGauge('app_active_jobs', 10, ['queue1']);
172+ *
173+ * @param string $name The name of the gauge.
174+ * @param float $value The value to set the gauge to.
175+ * @param array $labelValues The values of the labels for the gauge.
176+ */
177+ public function setGauge (string $ name , float $ value , array $ labelValues = []): void
83178 {
84- return $ this ->registry ->registerHistogram ('app ' , $ name , $ help , $ labels , $ buckets );
179+ $ gauge = $ this ->collectionRegistry ->getGauge ($ this ->namespace , $ name );
180+ $ gauge ->set ($ value , $ labelValues );
85181 }
86182
87183 /**
88184 * Increments a counter metric by 1.
185+ * Example:
186+ * $metricsService->incrementCounter('app_http_requests_total', ['GET', '/api/v1/users', '200']);
89187 *
90188 * @param string $name The name of the counter.
91189 * @param array $labelValues The values of the labels for the counter.
190+ * @throws \RuntimeException If the counter could not be incremented.
92191 */
93- public function incrementCounter (string $ name , array $ labelValues = [])
192+ public function incrementCounter (string $ name , array $ labelValues = []): void
94193 {
95- $ counter = $ this ->registry ->getCounter ('app ' , $ name );
96- $ counter ->inc ($ labelValues );
194+ try {
195+ $ counter = $ this ->collectionRegistry ->getCounter ($ this ->namespace , $ name );
196+ $ counter ->inc ($ labelValues );
197+ } catch (Exception $ e ) {
198+ throw new RuntimeException ("The counter could not be incremented ' {$ name }': " . $ e ->getMessage ());
199+ }
200+ }
201+
202+ /**
203+ * Observes a value for a histogram metric.
204+ * Example:
205+ * $metricsService->observeHistogram('app_http_request_duration_seconds', 0.3, ['GET', '/api/v1/users']);
206+ *
207+ * @param string $name The name of the histogram.
208+ * @param float $value The value to observe.
209+ * @param array $labelValues The values of the labels for the histogram.
210+ */
211+ public function observeHistogram (string $ name , float $ value , array $ labelValues = []): void
212+ {
213+ $ histogram = $ this ->collectionRegistry ->getHistogram ($ this ->namespace , $ name );
214+ $ histogram ->observe ($ value , $ labelValues );
215+ }
216+
217+ /**
218+ * Retrieves or increments a value stored in the cache and sets it to a Prometheus gauge.
219+ * Example: This is useful to monitor the number of active jobs in the queue.
220+ * $metricsService->cacheToGauge('app_active_jobs', 'app_active_jobs', 'Number of active jobs in the queue', ['queue'], 1);
221+ *
222+ * @param string $key The cache key.
223+ * @param string $metricName The Prometheus metric name.
224+ * @param string $help The help text for the gauge.
225+ * @param array $labels The labels for the gauge.
226+ * @param float $increment The value to increment.
227+ */
228+ public function cacheToGauge (string $ key , string $ metricName , string $ help , array $ labels = [], float $ increment = 0 ): void
229+ {
230+ // Retrieve and update the cache
231+ $ currentValue = Cache::increment ($ key , $ increment );
232+
233+ // Register the gauge if it doesn't exist
234+ if ($ this ->getMetricByName ($ metricName ) === null ) {
235+ $ this ->registerGauge ($ metricName , $ help , $ labels );
236+ }
237+
238+ // Update the gauge with the cached value
239+ $ this ->setGauge ($ metricName , $ currentValue , []);
97240 }
98241
99242 /**
@@ -104,7 +247,18 @@ public function incrementCounter(string $name, array $labelValues = [])
104247 public function renderMetrics ()
105248 {
106249 $ renderer = new RenderTextFormat ();
107- $ metrics = $ this ->registry ->getMetricFamilySamples ();
250+ $ metrics = $ this ->collectionRegistry ->getMetricFamilySamples ();
108251 return $ renderer ->render ($ metrics );
109252 }
253+
254+ /**
255+ * Helper to register a default set of metrics for common Laravel use cases.
256+ */
257+ public function registerDefaultMetrics (): void
258+ {
259+ $ this ->registerCounter ('http_requests_total ' , 'Total HTTP requests ' , ['method ' , 'endpoint ' , 'status ' ]);
260+ $ this ->registerHistogram ('http_request_duration_seconds ' , 'HTTP request duration in seconds ' , ['method ' , 'endpoint ' ], [0.1 , 0.5 , 1 , 5 , 10 ]);
261+ $ this ->registerGauge ('active_jobs ' , 'Number of active jobs in the queue ' , ['queue ' ]);
262+ $ this ->registerCounter ('job_failures_total ' , 'Total number of failed jobs ' , ['queue ' ]);
263+ }
110264}
0 commit comments