engelsystem/src/Controllers/Metrics/Controller.php

288 lines
12 KiB
PHP

<?php
namespace Engelsystem\Controllers\Metrics;
use Engelsystem\Config\Config;
use Engelsystem\Controllers\BaseController;
use Engelsystem\Helpers\Version;
use Engelsystem\Http\Exceptions\HttpForbidden;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
use Illuminate\Support\Collection;
use Psr\Log\LogLevel;
class Controller extends BaseController
{
/** @var Config */
protected $config;
/** @var MetricsEngine */
protected $engine;
/** @var Request */
protected $request;
/** @var Response */
protected $response;
/** @var Stats */
protected $stats;
/** @var Version */
protected $version;
/**
* @param Response $response
* @param MetricsEngine $engine
* @param Config $config
* @param Request $request
* @param Stats $stats
* @param Version $version
*/
public function __construct(
Response $response,
MetricsEngine $engine,
Config $config,
Request $request,
Stats $stats,
Version $version
) {
$this->config = $config;
$this->engine = $engine;
$this->request = $request;
$this->response = $response;
$this->stats = $stats;
$this->version = $version;
}
/**
* @return Response
*/
public function metrics()
{
$now = microtime(true);
$this->checkAuth();
$metrics = $this->config->get('metrics');
foreach (['work', 'voucher'] as $type) {
sort($metrics[$type]);
$metrics[$type] = array_merge($metrics[$type], ['+Inf']);
}
$userTshirtSizes = $this->formatStats($this->stats->tshirtSizes(), 'tshirt_sizes', 'shirt_size', 'size');
$userLocales = $this->formatStats($this->stats->languages(), 'locales', 'language', 'locale');
$userThemes = $this->formatStats($this->stats->themes(), 'themes', 'theme');
$userOauth = $this->formatStats($this->stats->oauth(), 'oauth', 'provider');
$themes = $this->config->get('themes');
foreach ($userThemes as $key => $theme) {
$userThemes[$key]['labels']['name'] = $themes[$theme['labels']['theme']]['name'];
}
$oauthProviders = $this->config->get('oauth');
foreach ($userOauth as $key => $oauth) {
$provider = $oauth['labels']['provider'];
$name = isset($oauthProviders[$provider]['name']) ? $oauthProviders[$provider]['name'] : $provider;
$userOauth[$key]['labels']['name'] = $name;
}
$data = [
$this->config->get('app_name') . ' stats',
'info' => [
'type' => 'gauge',
'help' => 'About the environment',
[
'labels' => [
'os' => PHP_OS_FAMILY,
'php' => implode('.', [PHP_MAJOR_VERSION, PHP_MINOR_VERSION]),
'version' => $this->version->getVersion(),
],
'value' => 1,
],
],
'users' => [
'type' => 'gauge',
['labels' => ['state' => 'incoming'], 'value' => $this->stats->newUsers()],
['labels' => ['state' => 'arrived', 'working' => 'no'], 'value' => $this->stats->arrivedUsers(false)],
['labels' => ['state' => 'arrived', 'working' => 'yes'], 'value' => $this->stats->arrivedUsers(true)],
],
'users_force_active' => ['type' => 'gauge', $this->stats->forceActiveUsers()],
'users_pronouns' => ['type' => 'gauge', $this->stats->usersPronouns()],
'licenses' => [
'type' => 'gauge',
'help' => 'The total number of licenses',
['labels' => ['type' => 'has_car'], 'value' => $this->stats->licenses('has_car')],
['labels' => ['type' => 'forklift'], 'value' => $this->stats->licenses('forklift')],
['labels' => ['type' => 'car'], 'value' => $this->stats->licenses('car')],
['labels' => ['type' => '3.5t'], 'value' => $this->stats->licenses('3.5t')],
['labels' => ['type' => '7.5t'], 'value' => $this->stats->licenses('7.5t')],
['labels' => ['type' => '12t'], 'value' => $this->stats->licenses('12t')],
],
'users_email' => [
'type' => 'gauge',
['labels' => ['type' => 'system'], 'value' => $this->stats->email('system')],
['labels' => ['type' => 'humans'], 'value' => $this->stats->email('humans')],
['labels' => ['type' => 'goody'], 'value' => $this->stats->email('goody')],
['labels' => ['type' => 'news'], 'value' => $this->stats->email('news')],
],
'users_working' => [
'type' => 'gauge',
['labels' => ['freeloader' => false], $this->stats->currentlyWorkingUsers(false)],
['labels' => ['freeloader' => true], $this->stats->currentlyWorkingUsers(true)],
],
'work_seconds' => [
'help' => 'Working users',
'type' => 'histogram',
[
'labels' => ['state' => 'done'],
'value' => $this->stats->workBuckets($metrics['work'], true, false),
'sum' => $this->stats->workSeconds(true, false),
],
[
'labels' => ['state' => 'planned'],
'value' => $this->stats->workBuckets($metrics['work'], false, false),
'sum' => $this->stats->workSeconds(false, false),
],
[
'labels' => ['state' => 'freeloaded'],
'value' => $this->stats->workBuckets($metrics['work'], null, true),
'sum' => $this->stats->workSeconds(null, true),
],
],
'worklog_seconds' => [
'type' => 'histogram',
$this->stats->worklogBuckets($metrics['work']) + ['sum' => $this->stats->worklogSeconds()],
],
'vouchers' => [
'type' => 'histogram',
$this->stats->vouchersBuckets($metrics['voucher']) + ['sum' => $this->stats->vouchers()],
],
'tshirts_issued' => ['type' => 'counter', 'help' => 'Issued T-Shirts', $this->stats->tshirts()],
'tshirt_sizes' => [
'type' => 'gauge',
'help' => 'The sizes users have configured'
] + $userTshirtSizes,
'locales' => ['type' => 'gauge', 'help' => 'The locales users have configured'] + $userLocales,
'themes' => ['type' => 'gauge', 'help' => 'The themes users have configured'] + $userThemes,
'rooms' => ['type' => 'gauge', $this->stats->rooms()],
'shifts' => ['type' => 'gauge', $this->stats->shifts()],
'announcements' => [
'type' => 'gauge',
['labels' => ['type' => 'news'], 'value' => $this->stats->announcements(false)],
['labels' => ['type' => 'meeting'], 'value' => $this->stats->announcements(true)],
],
'comments' => ['type' => 'gauge', $this->stats->comments()],
'questions' => [
'type' => 'gauge',
['labels' => ['state' => 'answered'], 'value' => $this->stats->questions(true)],
['labels' => ['state' => 'pending'], 'value' => $this->stats->questions(false)],
],
'faq' => ['type' => 'gauge', $this->stats->faq()],
'messages' => ['type' => 'gauge', $this->stats->messages()],
'password_resets' => ['type' => 'gauge', $this->stats->passwordResets()],
'registration_enabled' => ['type' => 'gauge', $this->config->get('registration_enabled')],
'database' => [
'type' => 'gauge',
['labels' => ['type' => 'read'], 'value' => $this->stats->databaseRead()],
['labels' => ['type' => 'write'], 'value' => $this->stats->databaseWrite()],
],
'sessions' => ['type' => 'gauge', $this->stats->sessions()],
'oauth' => ['type' => 'gauge', 'help' => 'The configured OAuth providers'] + $userOauth,
'log_entries' => [
'type' => 'counter',
[
'labels' => ['level' => LogLevel::EMERGENCY],
'value' => $this->stats->logEntries(LogLevel::EMERGENCY),
],
['labels' => ['level' => LogLevel::ALERT], 'value' => $this->stats->logEntries(LogLevel::ALERT)],
['labels' => ['level' => LogLevel::CRITICAL], 'value' => $this->stats->logEntries(LogLevel::CRITICAL)],
['labels' => ['level' => LogLevel::ERROR], 'value' => $this->stats->logEntries(LogLevel::ERROR)],
['labels' => ['level' => LogLevel::WARNING], 'value' => $this->stats->logEntries(LogLevel::WARNING)],
['labels' => ['level' => LogLevel::NOTICE], 'value' => $this->stats->logEntries(LogLevel::NOTICE)],
['labels' => ['level' => LogLevel::INFO], 'value' => $this->stats->logEntries(LogLevel::INFO)],
['labels' => ['level' => LogLevel::DEBUG], 'value' => $this->stats->logEntries(LogLevel::DEBUG)],
],
];
$data['scrape_duration_seconds'] = [
'type' => 'gauge',
'help' => 'Duration of the current request',
microtime(true) - $this->request->server->get('REQUEST_TIME_FLOAT', $now),
];
$data['scrape_memory_bytes'] = [
'type' => 'gauge',
'help' => 'Memory usage of the current request',
memory_get_usage(false),
];
return $this->response
->withHeader('Content-Type', 'text/plain; version=0.0.4')
->withContent($this->engine->get('/metrics', $data));
}
/**
* @return Response
*/
public function stats()
{
$this->checkAuth(true);
$data = [
'user_count' => $this->stats->newUsers() + $this->stats->arrivedUsers(),
'arrived_user_count' => $this->stats->arrivedUsers(),
'done_work_hours' => round($this->stats->workSeconds(true) / 60 / 60, 0),
'users_in_action' => $this->stats->currentlyWorkingUsers(),
];
return $this->response
->withHeader('Content-Type', 'application/json')
->withContent(json_encode($data));
}
/**
* Ensure that the if the request is authorized
*
* @param bool $isJson
*/
protected function checkAuth($isJson = false)
{
$apiKey = $this->config->get('api_key');
if (empty($apiKey) || $this->request->get('api_key') == $apiKey) {
return;
}
$message = 'The api_key is invalid';
$headers = [];
if ($isJson) {
$message = json_encode(['error' => $message]);
$headers['Content-Type'] = 'application/json';
}
throw new HttpForbidden($message, $headers);
}
/**
* Formats the stats collection as stats data
*
* @param Collection $data
* @param string $config
* @param string $dataField
* @param string|null $label
* @return array
*/
protected function formatStats(Collection $data, string $config, string $dataField, ?string $label = null): array
{
$return = [];
foreach ($this->config->get($config) as $name => $description) {
$count = $data->where($dataField, '=', $name)->sum('count');
$return[] = [
'labels' => [($label ?: $dataField) => $name],
$count,
];
}
return $return;
}
}