Skip to content

FOUR-21032 Task Execution Time Metrics Collection #7874

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jan 17, 2025
22 changes: 19 additions & 3 deletions ProcessMaker/Cache/Monitoring/CacheMetricsDecorator.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use ProcessMaker\Cache\CacheInterface;
use ProcessMaker\Cache\Monitoring\CacheMetricsInterface;
use ProcessMaker\Contracts\PrometheusMetricInterface;

/**
* Decorator class that adds metrics tracking about cache operations
Expand Down Expand Up @@ -64,11 +65,18 @@ public function get(string $key, mixed $default = null): mixed
$endTime = microtime(true);
$duration = $endTime - $startTime;

// Get extra labels for metrics
$labels = [];
if ($value instanceof PrometheusMetricInterface) {
$labels['label'] = $value->getPrometheusMetricLabel();
} else {
$labels['label'] = $key;
}
// Record metrics based on key existence, not value comparison
if ($exists) {
$this->metrics->recordHit($key, $duration);
$this->metrics->recordHit($key, $duration, $labels);
} else {
$this->metrics->recordMiss($key, $duration);
$this->metrics->recordMiss($key, $duration, $labels);
}

return $value;
Expand All @@ -88,10 +96,18 @@ public function set(string $key, mixed $value, null|int|\DateInterval $ttl = nul
{
$result = $this->cache->set($key, $value, $ttl);

// Get extra labels for metrics
$labels = [];
if ($value instanceof PrometheusMetricInterface) {
$labels['label'] = $value->getPrometheusMetricLabel();
} else {
$labels['label'] = $key;
}

if ($result) {
// Calculate approximate size in bytes
$size = $this->calculateSize($value);
$this->metrics->recordWrite($key, $size);
$this->metrics->recordWrite($key, $size, $labels);
}

return $result;
Expand Down
7 changes: 4 additions & 3 deletions ProcessMaker/Cache/Monitoring/CacheMetricsInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,23 @@ interface CacheMetricsInterface
*
* @param string $key Cache key that was accessed
* @param float $microtime Time taken for the operation in microseconds
* @param array $labels Additional labels to attach to the metric
*/
public function recordHit(string $key, $microtime): void;
public function recordHit(string $key, $microtime, array $labels = []): void;

/**
* Record a cache miss event
*
* @param string $key Cache key that was accessed
* @param float $microtime Time taken for the operation in microseconds
*/
public function recordMiss(string $key, $microtime): void;
public function recordMiss(string $key, $microtime, array $labels = []): void;

/**
* Record a cache write operation
*
* @param string $key Cache key that was written
* @param int $size Size of the cached data in bytes
*/
public function recordWrite(string $key, int $size): void;
public function recordWrite(string $key, int $size, array $labels = []): void;
}
29 changes: 16 additions & 13 deletions ProcessMaker/Cache/Monitoring/PrometheusMetricsManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,22 @@ public function __construct(string $namespace = 'cache')
* @param string $key Cache key
* @param float $microtime Time taken in microseconds
*/
public function recordHit(string $key, $microtime): void
public function recordHit(string $key, $microtime, array $labels = []): void
{
$sanitizedKey = $this->sanitizeKey($key);
$labelKeys = array_keys($labels);

$this->metrics->counter(
'cache_hits_total',
'Total number of cache hits',
['cache_key']
)->inc(['cache_key' => $sanitizedKey]);
['cache_key', ...$labelKeys]
)->inc(['cache_key' => $sanitizedKey, ...$labels]);
// record the last write timestamp
$this->metrics->gauge(
'cache_last_write_timestamp',
'Last write timestamp',
['cache_key']
)->set($microtime, ['cache_key' => $sanitizedKey]);
['cache_key', ...$labelKeys]
)->set($microtime, ['cache_key' => $sanitizedKey, ...$labels]);
}

/**
Expand All @@ -58,22 +59,23 @@ public function recordHit(string $key, $microtime): void
* @param string $key Cache key
* @param float $microtime Time taken in microseconds
*/
public function recordMiss(string $key, $microtime): void
public function recordMiss(string $key, $microtime, array $labels = []): void
{
$sanitizedKey = $this->sanitizeKey($key);
$labelKeys = array_keys($labels);

$this->metrics->counter(
'cache_misses_total',
'Total number of cache misses',
['cache_key']
)->inc(['cache_key' => $sanitizedKey]);
['cache_key', ...$labelKeys]
)->inc(['cache_key' => $sanitizedKey, ...$labels]);

// record the last write timestamp
$this->metrics->gauge(
'cache_last_write_timestamp',
'Last write timestamp',
['cache_key']
)->set($microtime, ['cache_key' => $sanitizedKey]);
['cache_key', ...$labelKeys]
)->set($microtime, ['cache_key' => $sanitizedKey, ...$labels]);
}

/**
Expand All @@ -82,15 +84,16 @@ public function recordMiss(string $key, $microtime): void
* @param string $key Cache key
* @param int $size Size in bytes
*/
public function recordWrite(string $key, int $size): void
public function recordWrite(string $key, int $size, array $labels = []): void
{
$sanitizedKey = $this->sanitizeKey($key);
$labelKeys = array_keys($labels);

$this->metrics->gauge(
'cache_memory_bytes',
'Memory usage in bytes',
['cache_key']
)->set($size, ['cache_key' => $sanitizedKey]);
['cache_key', ...$labelKeys]
)->set($size, ['cache_key' => $sanitizedKey, ...$labels]);
}

/**
Expand Down
6 changes: 3 additions & 3 deletions ProcessMaker/Cache/Monitoring/RedisMetricsManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class RedisMetricsManager implements CacheMetricsInterface
* @param string $key Cache key
* @param float $microtime Time taken in microseconds
*/
public function recordHit(string $key, $microtime): void
public function recordHit(string $key, $microtime, array $labels = []): void
{
$baseKey = self::METRICS_PREFIX . $key;
Redis::pipeline(function ($pipe) use ($baseKey, $microtime) {
Expand All @@ -42,7 +42,7 @@ public function recordHit(string $key, $microtime): void
* @param string $key Cache key
* @param float $microtime Time taken in microseconds
*/
public function recordMiss(string $key, $microtime): void
public function recordMiss(string $key, $microtime, array $labels = []): void
{
$baseKey = self::METRICS_PREFIX . $key;
Redis::pipeline(function ($pipe) use ($baseKey, $microtime) {
Expand All @@ -58,7 +58,7 @@ public function recordMiss(string $key, $microtime): void
* @param string $key Cache key
* @param int $size Size in bytes
*/
public function recordWrite(string $key, int $size): void
public function recordWrite(string $key, int $size, array $labels = []): void
{
$baseKey = self::METRICS_PREFIX . $key;
Redis::pipeline(function ($pipe) use ($baseKey, $size) {
Expand Down
30 changes: 30 additions & 0 deletions ProcessMaker/Cache/Screens/ScreenCache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace ProcessMaker\Cache\Screens;

use Illuminate\Support\Collection;
use ProcessMaker\Contracts\PrometheusMetricInterface;

class ScreenCache extends Collection implements PrometheusMetricInterface
{

public string $label;

public static function makeFrom(PrometheusMetricInterface $screen, $items): ScreenCache
{
$self = new static($items);
$self->label = $screen->getPrometheusMetricLabel();

return $self;
}

/**
* Returns a legible or friendly name for Prometheus metrics.
*
* @return string
*/
public function getPrometheusMetricLabel(): string
{
return $this->label;
}
}
13 changes: 13 additions & 0 deletions ProcessMaker/Contracts/PrometheusMetricInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace ProcessMaker\Contracts;

interface PrometheusMetricInterface
{
/**
* Returns a legible or friendly name for Prometheus metrics.
*
* @return string
*/
public function getPrometheusMetricLabel(): string;
}
8 changes: 8 additions & 0 deletions ProcessMaker/Facades/Metrics.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
use Illuminate\Support\Facades\Facade;
use ProcessMaker\Services\MetricsService;

/**
* @method static \Prometheus\Counter counter(string $name, string $help = null, array $labels = [])
* @method static \Prometheus\Gauge gauge(string $name, string $help = null, array $labels = [])
* @method static \Prometheus\Histogram histogram(string $name, string $help = null, array $labels = [], array $buckets = [0.1, 1, 5, 10])
* @method static void setGauge(string $name, float $value, array $labelValues = [])
* @method static string renderMetrics()
* @method static \Prometheus\CollectorRegistry getCollectionRegistry()
*/
class Metrics extends Facade
{
/**
Expand Down
20 changes: 19 additions & 1 deletion ProcessMaker/Jobs/CompleteActivity.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace ProcessMaker\Jobs;

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\DatabaseManager;
use ProcessMaker\Facades\Metrics;
use ProcessMaker\Managers\DataManager;
use ProcessMaker\Models\Process as Definitions;
use ProcessMaker\Models\ProcessRequestToken;
Expand Down Expand Up @@ -46,5 +46,23 @@ public function action(ProcessRequestToken $token, ActivityInterface $element, a
$manager->updateData($token, $data);
$this->engine->runToNextState();
$element->complete($token);

Metrics::counter(
'activity_completed_total',
'Total number of activities completed',
[
'activity_id',
'activity_name',
'process_id',
'request_id',
]
)->inc(
[
'activity_id' => $element->getId(),
'activity_name' => $element->getName(),
'process_id' => $this->definitionsId,
'request_id' => $this->instanceId,
]
);
}
}
13 changes: 12 additions & 1 deletion ProcessMaker/Models/Screen.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use ProcessMaker\Assets\ScreensInScreen;
use ProcessMaker\Contracts\PrometheusMetricInterface;
use ProcessMaker\Contracts\ScreenInterface;
use ProcessMaker\Events\TranslationChanged;
use ProcessMaker\Traits\Exportable;
Expand Down Expand Up @@ -63,7 +64,7 @@
* @OA\Property(property="url", type="string"),
* )
*/
class Screen extends ProcessMakerModel implements ScreenInterface
class Screen extends ProcessMakerModel implements ScreenInterface, PrometheusMetricInterface
{
use SerializeToIso8601;
use HideSystemResources;
Expand Down Expand Up @@ -283,4 +284,14 @@ public function scopeFilter($query, $filterStr)

return $query;
}

/**
* Return the label to be used in grafana reports
*
* @return string
*/
public function getPrometheusMetricLabel(): string
{
return 'screen.' . $this->id;
}
}
8 changes: 7 additions & 1 deletion ProcessMaker/Models/ScreenVersion.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
namespace ProcessMaker\Models;

use Illuminate\Database\Eloquent\Builder;
use ProcessMaker\Contracts\PrometheusMetricInterface;
use ProcessMaker\Contracts\ScreenInterface;
use ProcessMaker\Events\TranslationChanged;
use ProcessMaker\Traits\HasCategories;
use ProcessMaker\Traits\HasScreenFields;

class ScreenVersion extends ProcessMakerModel implements ScreenInterface
class ScreenVersion extends ProcessMakerModel implements ScreenInterface, PrometheusMetricInterface
{
use HasCategories;
use HasScreenFields;
Expand Down Expand Up @@ -77,4 +78,9 @@ public function scopePublished(Builder $query)
{
return $query->where('draft', false);
}

public function getPrometheusMetricLabel(): string
{
return 'screen.' . $this->screen_id;
}
}
13 changes: 12 additions & 1 deletion ProcessMaker/Models/Setting.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Illuminate\Validation\Rule;
use Log;
use ProcessMaker\Cache\Settings\SettingCacheFactory;
use ProcessMaker\Contracts\PrometheusMetricInterface;
use ProcessMaker\Traits\ExtendedPMQL;
use ProcessMaker\Traits\SerializeToIso8601;
use Spatie\MediaLibrary\HasMedia;
Expand Down Expand Up @@ -48,7 +49,7 @@
* },
* )
*/
class Setting extends ProcessMakerModel implements HasMedia
class Setting extends ProcessMakerModel implements HasMedia, PrometheusMetricInterface
{
use ExtendedPMQL;
use InteractsWithMedia;
Expand Down Expand Up @@ -497,4 +498,14 @@ public static function updateAllSettingsGroupId()
}
});
}

/**
* Get the label used in grafana reports
*
* @return string
*/
public function getPrometheusMetricLabel(): string
{
return 'settings.' . $this->key;
}
}
12 changes: 11 additions & 1 deletion ProcessMaker/Services/MetricsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class MetricsService
*
* @param mixed $adapter The storage adapter to use (e.g., Redis).
*/
public function __construct($adapter = null)
public function __construct(private $adapter = null)
{
$this->namespace = config('app.prometheus_namespace', 'app');
try {
Expand All @@ -49,6 +49,16 @@ public function __construct($adapter = null)
}
}

/**
* Get the collection registry.
*
* @return CollectorRegistry The collection registry instance.
*/
public function getCollectionRegistry(): CollectorRegistry
{
return $this->collectionRegistry;
}

/**
* Registers or retrieves a counter metric.
*
Expand Down
Loading
Loading