Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions ProcessMaker/Console/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ protected function schedule(Schedule $schedule)
$schedule->command('metrics:clear')->cron("*/{$clearInterval} * * * *");
break;
}

// 5 minutes is recommended in https://laravel.com/docs/12.x/horizon#metrics
$schedule->command('horizon:snapshot')->everyFiveMinutes();
}

/**
Expand Down
10 changes: 10 additions & 0 deletions ProcessMaker/Exception/MultitenancyAccessedLandlord.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,21 @@
use Exception;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use ProcessMaker\Facades\Metrics;

class MultitenancyAccessedLandlord extends Exception
{
public function render(Request $request): Response
{
// If we're trying to access the /metrics route, collect landlord metrics and render them
if ($request->path() === 'metrics') {
Metrics::collectQueueMetrics();

return response(Metrics::renderMetrics(), 200, [
'Content-Type' => 'text/plain; version=0.0.4',
]);
}

return response()->view('multitenancy.landlord-landing-page');
}

Expand Down
2 changes: 1 addition & 1 deletion ProcessMaker/Jobs/ErrorHandling.php
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ public static function convertResponseToException($result)
if (str_starts_with($result['message'], 'Command exceeded timeout of')) {
throw new ScriptTimeoutException($result['message']);
}
throw new ScriptException($result['message']);
throw new ScriptException(json_encode($result, JSON_PRETTY_PRINT));
}
}
}
2 changes: 1 addition & 1 deletion ProcessMaker/Multitenancy/SwitchTenant.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ private function overrideConfigs(Application $app, IsTenant $tenant)

if (!isset($tenant->config['app.docker_host_url'])) {
// There is no specific override in the tenant's config so set it to the app url
$newConfig['app.docker_host_url'] = config('app.url');
$newConfig['app.docker_host_url'] = config('app.docker_host_url', config('app.url'));
}

// Set config from the entry in the tenants table
Expand Down
20 changes: 6 additions & 14 deletions ProcessMaker/Multitenancy/TenantBootstrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace ProcessMaker\Multitenancy;

use Dotenv\Dotenv;
use Illuminate\Encryption\Encrypter;
use Illuminate\Http\Request;
use Illuminate\Support\Env;
Expand Down Expand Up @@ -34,6 +35,7 @@ class TenantBootstrapper
'REDIS_PREFIX',
'CACHE_SETTING_PREFIX',
'SCRIPT_MICROSERVICE_CALLBACK',
'CACHE_PREFIX',
];

public function bootstrap(Application $app)
Expand All @@ -43,8 +45,6 @@ public function bootstrap(Application $app)
}
$this->app = $app;

self::saveLandlordValues($app);

$tenantData = null;

// Try to find tenant by ID first if TENANT env var is set
Expand Down Expand Up @@ -86,6 +86,7 @@ private function setTenantEnvironmentVariables($tenantData)
$this->set('APP_KEY', $this->decrypt($config['app.key']));
$this->set('DB_DATABASE', $tenantData['database']);
$this->set('DB_USERNAME', $tenantData['username'] ?? $this->getOriginalValue('DB_USERNAME'));
$this->set('CACHE_PREFIX', $this->getOriginalValue('CACHE_PREFIX') . 'tenant_' . $tenantData['id'] . ':');

$encryptedPassword = $tenantData['password'];
$password = null;
Expand All @@ -99,21 +100,12 @@ private function setTenantEnvironmentVariables($tenantData)
$this->set('LOG_PATH', $this->app->storagePath('logs/processmaker.log'));
}

public static function saveLandlordValues($app)
private function getOriginalValue($key)
{
if ($app->has('landlordValues')) {
self::$landlordValues = $app->make('landlordValues');

return;
if (self::$landlordValues === []) {
self::$landlordValues = Dotenv::parse(file_get_contents(base_path('.env')));
}

foreach (self::$landlordKeysToSave as $key) {
self::$landlordValues[$key] = $_SERVER[$key] ?? '';
}
}

private function getOriginalValue($key)
{
if (!isset(self::$landlordValues[$key])) {
return '';
}
Expand Down
47 changes: 42 additions & 5 deletions ProcessMaker/Services/MetricsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@
namespace ProcessMaker\Services;

use Exception;
use Laravel\Horizon\Contracts\JobRepository;
use Laravel\Horizon\Contracts\MetricsRepository;
use Laravel\Horizon\Contracts\WorkloadRepository;
use ProcessMaker\Facades\Metrics;
use ProcessMaker\Multitenancy\Tenant;
use Prometheus\CollectorRegistry;
use Prometheus\Counter;
use Prometheus\Gauge;
use Prometheus\Histogram;
use Prometheus\RenderTextFormat;
use Prometheus\Storage\Redis;
use Prometheus\Storage\Redis as PrometheusRedis;
use Redis;
use RuntimeException;

class MetricsService
Expand Down Expand Up @@ -39,7 +44,12 @@ public function __construct(private $adapter = null)
try {
// Set up Redis as the adapter if none is provided
if ($adapter === null) {
$adapter = Redis::fromExistingConnection(app('redis')->client());
$redis = app('redis')->client();
$adapter = PrometheusRedis::fromExistingConnection($redis);
if (app()->has(Tenant::BOOTSTRAPPED_TENANT)) {
$tenantInfo = app(Tenant::BOOTSTRAPPED_TENANT);
$adapter->setPrefix('tenant_' . $tenantInfo['id'] . ':PROMETHEUS_');
}
}
$this->collectionRegistry = new CollectorRegistry($adapter);
} catch (Exception $e) {
Expand All @@ -65,7 +75,7 @@ public function getCollectionRegistry(): CollectorRegistry
* @param array $labels The labels of the counter.
* @return Counter The registered or retrieved counter.
*/
public function counter(string $name, string $help = null, array $labels = []): Counter
public function counter(string $name, string|null $help = null, array $labels = []): Counter
{
$help = $help ?? $name;

Expand All @@ -85,7 +95,7 @@ public function counter(string $name, string $help = null, array $labels = []):
* @param array $labels The labels of the gauge.
* @return Gauge The registered or retrieved gauge.
*/
public function gauge(string $name, string $help = null, array $labels = []): Gauge
public function gauge(string $name, string|null $help = null, array $labels = []): Gauge
{
$help = $help ?? $name;

Expand Down Expand Up @@ -201,7 +211,9 @@ public function addSystemLabels(array $labels)
// Add system labels
$labels['app_version'] = $this->getApplicationVersion();
$labels['app_name'] = config('app.name');
$labels['app_custom_label'] = config('app.prometheus_custom_label');
if (config('app.prometheus_custom_label')) {
$labels['app_custom_label'] = config('app.prometheus_custom_label');
}

return $labels;
}
Expand All @@ -223,4 +235,29 @@ private function getApplicationVersion()

return $composer_json_path->version ?? '4.0.0';
}

/**
* These are collected every time the /metrics route is accessed.
*
* @return void
*/
public function collectQueueMetrics(): void
{
$metricsRepository = app(MetricsRepository::class);
$jobsRepository = app(JobRepository::class);
$workloadRepository = app(WorkloadRepository::class);

$this->gauge('horizon_jobs_per_minute', 'Jobs processed per minute')->set($metricsRepository->jobsProcessedPerMinute());
$this->gauge('horizon_failed_jobs_per_hour', 'Failed jobs per hour')->set($jobsRepository->countRecentlyFailed());

foreach ($workloadRepository->get() as $workload) {
$name = $workload['name'];
foreach (['length', 'wait', 'processes'] as $type) {
$this->gauge(
'horizon_workload_' . $name . '_' . $type,
'Workload ' . $name . ' ' . $type
)->set($workload[$type]);
}
}
}
}
1 change: 0 additions & 1 deletion config/multitenancy.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
*/
'switch_tenant_tasks' => [
SwitchTenant::class,
Spatie\Multitenancy\Tasks\PrefixCacheTask::class,
],

/*
Expand Down
4 changes: 4 additions & 0 deletions routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,10 @@

// Metrics Route
Route::get('/metrics', function () {
if (!config('app.multitenancy')) {
Metrics::collectQueueMetrics();
}

return response(Metrics::renderMetrics(), 200, [
'Content-Type' => 'text/plain; version=0.0.4',
]);
Expand Down
Loading