Skip to content

Commit

Permalink
FOUR-21074 Improvements for a better user experience.
Browse files Browse the repository at this point in the history
  • Loading branch information
gproly committed Dec 17, 2024
1 parent a63afa7 commit 91d8a85
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 258 deletions.
194 changes: 50 additions & 144 deletions ProcessMaker/Services/MetricsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
namespace ProcessMaker\Services;

use Exception;
use Illuminate\Support\Facades\Cache;
use Prometheus\Counter;
use Prometheus\Gauge;
use Prometheus\Histogram;
use Prometheus\CollectorRegistry;
use Prometheus\RenderTextFormat;
use Prometheus\Storage\Redis;
Expand All @@ -18,18 +20,21 @@ class MetricsService
*/
private $collectionRegistry;

/**
* The namespace used by the MetricsService.
*
* @var string
*/
private $namespace;

/**
* Initializes the MetricsService with a CollectorRegistry using the provided storage adapter.
* Example:
* $metricsService = new MetricsService(new Redis([
* 'host' => config('database.redis.default.host'),
* 'port' => config('database.redis.default.port'),
* ]));
*
* @param mixed $adapter The storage adapter to use (e.g., Redis).
*/
public function __construct($adapter = null)
{
$this->namespace = config('app.prometheus_namespace', 'app');
try {
// Set up Redis as the adapter if none is provided
if ($adapter === null) {
Expand All @@ -45,105 +50,66 @@ public function __construct($adapter = null)
}

/**
* Sets the CollectorRegistry used by the MetricsService.
* Example:
* $metricsService->setRegistry(new CollectorRegistry(new Redis([
* 'host' => config('database.redis.default.host'),
* 'port' => config('database.redis.default.port'),
* ])));
*
* @param CollectorRegistry $collectionRegistry The CollectorRegistry to set.
*/
public function setRegistry(CollectorRegistry $collectionRegistry): void
{
$this->collectionRegistry = $collectionRegistry;
}

/**
* Returns the CollectorRegistry used by the MetricsService.
*
* @return CollectorRegistry The CollectorRegistry used by the MetricsService.
*/
public function getMetrics(): CollectorRegistry
{
return $this->collectionRegistry;
}

/**
* Retrieves a metric by its name. The app_ prefix is added to the name of the metric.
* Example:
* $metricsService->getMetricByName('app_http_requests_total');
* Registers or retrieves a counter metric.
*
* @param string $name The name of the metric to retrieve.
* @return \Prometheus\MetricFamilySamples|null The metric with the specified name, or null if not found.
* @param string $name The name of the counter.
* @param string|null $help The help text of the counter.
* @param array $labels The labels of the counter.
* @return Counter The registered or retrieved counter.
*/
public function getMetricByName(string $name)
public function counter(string $name, string $help = null, array $labels = []): Counter
{
$metrics = $this->collectionRegistry->getMetricFamilySamples();
foreach ($metrics as $metric) {
if ($metric->getName() === $name) {
return $metric;
}
}

return null;
$help = $help ?? $name;
return $this->collectionRegistry->getOrRegisterCounter(
$this->namespace,
$name,
$help,
$labels
);
}

/**
* Registers a new counter metric.
* Example:
* $metricsService->registerCounter('app_http_requests_total', 'Total HTTP requests', ['method', 'endpoint', 'status']);
* Registers or retrieves a gauge metric.
*
* @param string $name The name of the counter.
* @param string $help The help text of the counter.
* @param array $labels The labels of the counter.
* @return \Prometheus\Counter The registered counter.
* @throws RuntimeException If a metric with the same name already exists.
* @param string $name The name of the gauge.
* @param string|null $help The help text of the gauge.
* @param array $labels The labels of the gauge.
* @return Gauge The registered or retrieved gauge.
*/
public function registerCounter(string $name, string $help, array $labels = []): \Prometheus\Counter
public function gauge(string $name, string $help = null, array $labels = []): Gauge
{
if ($this->getMetricByName($name) !== null) {
throw new RuntimeException("A metric with this name already exists. '{$name}'.");
}

return $this->collectionRegistry->registerCounter($this->namespace, $name, $help, $labels);
$help = $help ?? $name;
return $this->collectionRegistry->getOrRegisterGauge(
$this->namespace,
$name,
$help,
$labels
);
}

/**
* Registers a new histogram metric.
* Example:
* $metricsService->registerHistogram('app_http_request_duration_seconds', 'HTTP request duration in seconds', ['method', 'endpoint'], [0.1, 0.5, 1, 5, 10]);
* Registers or retrieves a histogram metric.
*
* @param string $name The name of the histogram.
* @param string $help The help text of the histogram.
* @param string|null $help The help text of the histogram.
* @param array $labels The labels of the histogram.
* @param array $buckets The buckets of the histogram.
* @return \Prometheus\Histogram The registered histogram.
* @return Histogram The registered or retrieved histogram.
*/
public function registerHistogram(string $name, string $help, array $labels = [], array $buckets = [0.1, 1, 5, 10]): \Prometheus\Histogram
public function histogram(string $name, string $help = null, array $labels = [], array $buckets = [0.1, 1, 5, 10]): Histogram
{
return $this->collectionRegistry->registerHistogram($this->namespace, $name, $help, $labels, $buckets);
}

/**
* Registers a new gauge metric.
* Example:
* $metricsService->registerGauge('app_active_jobs', 'Number of active jobs in the queue', ['queue']);
*
* @param string $name The name of the gauge.
* @param string $help The help text of the gauge.
* @param array $labels The labels of the gauge.
* @return \Prometheus\Gauge The registered gauge.
*/
public function registerGauge(string $name, string $help, array $labels = []): \Prometheus\Gauge
{
return $this->collectionRegistry->registerGauge($this->namespace, $name, $help, $labels);
$help = $help ?? $name;
return $this->collectionRegistry->getOrRegisterHistogram(
$this->namespace,
$name,
$help,
$labels,
$buckets
);
}

/**
* Sets a gauge metric to a specific value.
* Example:
* $metricsService->setGauge('app_active_jobs', 10, ['queue1']);
*
* @param string $name The name of the gauge.
* @param float $value The value to set the gauge to.
Expand All @@ -155,75 +121,15 @@ public function setGauge(string $name, float $value, array $labelValues = []): v
$gauge->set($value, $labelValues);
}

/**
* Increments a counter metric by 1.
* Example:
* $metricsService->incrementCounter('app_http_requests_total', ['GET', '/api/v1/users', '200']);
*
* @param string $name The name of the counter.
* @param array $labelValues The values of the labels for the counter.
* @throws RuntimeException If the counter could not be incremented.
*/
public function incrementCounter(string $name, array $labelValues = []): void
{
try {
$counter = $this->collectionRegistry->getCounter($this->namespace, $name);
$counter->inc($labelValues);
} catch (Exception $e) {
throw new RuntimeException("The counter could not be incremented '{$name}': " . $e->getMessage());
}
}

/**
* Observes a value for a histogram metric.
* Example:
* $metricsService->observeHistogram('app_http_request_duration_seconds', 0.3, ['GET', '/api/v1/users']);
*
* @param string $name The name of the histogram.
* @param float $value The value to observe.
* @param array $labelValues The values of the labels for the histogram.
*/
public function observeHistogram(string $name, float $value, array $labelValues = []): void
{
$histogram = $this->collectionRegistry->getHistogram($this->namespace, $name);
$histogram->observe($value, $labelValues);
}

/**
* Retrieves or increments a value stored in the cache and sets it to a Prometheus gauge.
* Example: This is useful to monitor the number of active jobs in the queue.
* $metricsService->cacheToGauge('app_active_jobs', 'app_active_jobs', 'Number of active jobs in the queue', ['queue'], 1);
*
* @param string $key The cache key.
* @param string $metricName The Prometheus metric name.
* @param string $help The help text for the gauge.
* @param array $labels The labels for the gauge.
* @param float $increment The value to increment.
*/
public function cacheToGauge(string $key, string $metricName, string $help, array $labels = [], float $increment = 0): void
{
// Retrieve and update the cache
$currentValue = Cache::increment($key, $increment);

// Register the gauge if it doesn't exist
if ($this->getMetricByName($metricName) === null) {
$this->registerGauge($metricName, $help, $labels);
}

// Update the gauge with the cached value
$this->setGauge($metricName, $currentValue, []);
}

/**
* Renders the metrics in the Prometheus text format.
*
* @return string The rendered metrics.
*/
public function renderMetrics()
public function renderMetrics(): string
{
$renderer = new RenderTextFormat();
$metrics = $this->collectionRegistry->getMetricFamilySamples();

return $renderer->render($metrics);
}
}
55 changes: 37 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ Make sure the ports 9090 and 3000 on the host are not already in use.

Edit `prometheus.yml` and update the target hostname with your local processmaker instance. You might also need to change the scheme if you are using https.

Run `docker compose up`
Run `docker compose up -d`

Check that prometheus can connect to your local instance at http://localhost:9090/targets

Expand All @@ -466,6 +466,39 @@ When you are finished, run `docker compose down`. To delete all data, run `docke

Now you can use the `Metrics` Facade anywhere in your application to manage metrics.

### **1. Counter**
A **Counter** only **increases** over time or resets to zero. It is used for cumulative events.

- Total number of HTTP requests:
```php
$counter = Metrics::counter('http_requests_total', 'Total HTTP requests', ['method', 'status']);
$counter->inc(['GET', '200']);
$counter->incBy(2, ['GET', '200']);
```
- Number of system errors (e.g., HTTP 5xx).

### **2. Gauge**
A **Gauge** can **increase or decrease**. It is used for values that fluctuate over time.

- Current number of active jobs in a queue:
```php
$gauge = Metrics::gauge('active_jobs', 'Number of active jobs', ['queue']);
$gauge->set(10, ['queue1']);
```
- Memory or CPU usage.

### **3. Histogram**
A **Histogram** measures **value distributions** by organizing them into buckets. It is ideal for latency or size measurements.

- Duration of HTTP requests:
```php
$histogram = Metrics::histogram('http_request_duration_seconds', 'HTTP request duration', ['method'], [0.1, 0.5, 1, 5, 10]);
$histogram->observe(0.3, ['GET']);
```
- File sizes or request durations.

Each type serves a specific role depending on the data being monitored.

#### **Example: Incrementing a Counter**

In a controller:
Expand All @@ -479,10 +512,9 @@ class ExampleController extends Controller
{
public function index()
{
// Register a counter for the total number of requests
Metrics::registerCounter('requests_total', 'Total number of requests', ['method']);
// Increment the counter for the current request
Metrics::incrementCounter('requests_total', ['GET']);
//use metrics counter
$counter = Metrics::counter('http_requests_total', 'Total HTTP requests', ['method', 'status']);
$counter->inc(['GET', '200']); // Incrementa el contador para GET y estado 200.

return response()->json(['message' => 'Hello, world!']);
}
Expand All @@ -493,19 +525,6 @@ To make things even easier, you can run `Metrics::counter('cases')->inc();` or `

You can provide an optional description, for example `Metrics::gauge('active_tasks', 'Total Active Tasks')->...`

#### **Example: Registering and Using a Histogram**

```php
Metrics::registerHistogram(
'request_duration_seconds',
'Request duration time',
['method'],
[0.1, 0.5, 1, 5]
);
$histogram = Metrics::incrementHistogram('request_duration_seconds', ['GET'], microtime(true) - LARAVEL_START);
```


# License

Distributed under the [AGPL Version 3](https://www.gnu.org/licenses/agpl-3.0.en.html)
Expand Down
8 changes: 2 additions & 6 deletions metrics/prometheus/prometheus.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
# Global configuration settings for Prometheus
global:
scrape_interval: 15s
scrape_timeout: 10s
evaluation_interval: 15s

# Scrape configurations
scrape_configs:
# For Prometheus
- job_name: prometheus
- job_name: processmaker
honor_timestamps: true
scrape_interval: 15s
scrape_timeout: 10s
metrics_path: /metrics
scheme: http
static_configs:
- targets:
# Replace this with your local processmaker instance
# Replace this with your local processmaker instance (add port if needed)
- processmaker-b.test

Loading

0 comments on commit 91d8a85

Please sign in to comment.