Skip to content

Commit 15932f3

Browse files
authored
Merge pull request #7815 from ProcessMaker/feature/FOUR-20539
FOUR-20539 Optimize Metrics Collection
2 parents edb490f + 922190b commit 15932f3

File tree

5 files changed

+329
-272
lines changed

5 files changed

+329
-272
lines changed

ProcessMaker/Http/Controllers/Designer/DesignerController.php

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
use Illuminate\Http\Request;
77
use Illuminate\Support\Composer;
88
use Illuminate\Support\Facades\Auth;
9-
use ProcessMaker\Facades\Metrics;
109
use ProcessMaker\Http\Controllers\Controller;
1110
use ProcessMaker\Traits\HasControllerAddons;
1211

@@ -21,11 +20,6 @@ class DesignerController extends Controller
2120
*/
2221
public function index(Request $request)
2322
{
24-
// Register a counter for the total number of requests
25-
Metrics::registerCounter('requests_total', 'Total requests made', ['method']);
26-
// Increment the counter for the current request
27-
Metrics::incrementCounter('requests_total', ['GET']);
28-
2923
$hasPackage = false;
3024
if (class_exists(\ProcessMaker\Package\Projects\Models\Project::class)) {
3125
$hasPackage = true;

ProcessMaker/Services/MetricsService.php

Lines changed: 177 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,53 +2,111 @@
22

33
namespace ProcessMaker\Services;
44

5+
use Exception;
6+
use Illuminate\Support\Facades\Cache;
57
use Prometheus\CollectorRegistry;
68
use Prometheus\RenderTextFormat;
79
use Prometheus\Storage\Redis;
10+
use RuntimeException;
811

912
class 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

Comments
 (0)