Merge pull request #57165 from nextcloud/feat/openmetrics
commit
62513dfd92
@ -0,0 +1,52 @@ |
||||
<?php |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
namespace OCA\Comments\OpenMetrics; |
||||
|
||||
use Generator; |
||||
use OC\DB\Connection; |
||||
use OCP\OpenMetrics\IMetricFamily; |
||||
use OCP\OpenMetrics\Metric; |
||||
use OCP\OpenMetrics\MetricType; |
||||
use Override; |
||||
|
||||
class CommentsCountMetric implements IMetricFamily { |
||||
public function __construct( |
||||
private Connection $connection, |
||||
) { |
||||
} |
||||
|
||||
#[Override] |
||||
public function name(): string { |
||||
return 'comments'; |
||||
} |
||||
|
||||
#[Override] |
||||
public function type(): MetricType { |
||||
return MetricType::gauge; |
||||
} |
||||
|
||||
#[Override] |
||||
public function unit(): string { |
||||
return 'comments'; |
||||
} |
||||
|
||||
#[Override] |
||||
public function help(): string { |
||||
return 'Number of comments'; |
||||
} |
||||
|
||||
#[Override] |
||||
public function metrics(): Generator { |
||||
$qb = $this->connection->getQueryBuilder(); |
||||
$result = $qb->select($qb->func()->count()) |
||||
->from('comments') |
||||
->where($qb->expr()->eq('verb', $qb->expr()->literal('comment'))) |
||||
->executeQuery(); |
||||
|
||||
yield new Metric($result->fetchOne(), [], time()); |
||||
} |
||||
} |
||||
@ -0,0 +1,75 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OCA\Files_Sharing\OpenMetrics; |
||||
|
||||
use Generator; |
||||
use OCP\DB\QueryBuilder\IQueryBuilder; |
||||
use OCP\IDBConnection; |
||||
use OCP\OpenMetrics\IMetricFamily; |
||||
use OCP\OpenMetrics\Metric; |
||||
use OCP\OpenMetrics\MetricType; |
||||
use OCP\Share\IShare; |
||||
use Override; |
||||
|
||||
/** |
||||
* Count shares by type |
||||
* @since 33.0.0 |
||||
*/ |
||||
class SharesCountMetric implements IMetricFamily { |
||||
public function __construct( |
||||
private IDBConnection $connection, |
||||
) { |
||||
} |
||||
|
||||
#[Override] |
||||
public function name(): string { |
||||
return 'shares'; |
||||
} |
||||
|
||||
#[Override] |
||||
public function type(): MetricType { |
||||
return MetricType::gauge; |
||||
} |
||||
|
||||
#[Override] |
||||
public function unit(): string { |
||||
return 'shares'; |
||||
} |
||||
|
||||
#[Override] |
||||
public function help(): string { |
||||
return 'Number of shares by type'; |
||||
} |
||||
|
||||
#[Override] |
||||
public function metrics(): Generator { |
||||
$types = [ |
||||
IShare::TYPE_USER => 'user', |
||||
IShare::TYPE_GROUP => 'group', |
||||
IShare::TYPE_LINK => 'link', |
||||
IShare::TYPE_EMAIL => 'email', |
||||
]; |
||||
$qb = $this->connection->getQueryBuilder(); |
||||
$result = $qb->select($qb->func()->count('*', 'count'), 'share_type') |
||||
->from('share') |
||||
->where($qb->expr()->in('share_type', $qb->createNamedParameter(array_keys($types), IQueryBuilder::PARAM_INT_ARRAY))) |
||||
->groupBy('share_type') |
||||
->executeQuery(); |
||||
|
||||
if ($result->rowCount() === 0) { |
||||
yield new Metric(0); |
||||
return; |
||||
} |
||||
|
||||
foreach ($result->iterateAssociative() as $row) { |
||||
yield new Metric($row['count'], ['type' => $types[$row['share_type']]]); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,155 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
namespace OC\Core\Controller; |
||||
|
||||
use OC\OpenMetrics\ExporterManager; |
||||
use OC\Security\Ip\Address; |
||||
use OC\Security\Ip\Range; |
||||
use OCP\AppFramework\Controller; |
||||
use OCP\AppFramework\Http; |
||||
use OCP\AppFramework\Http\Attribute\FrontpageRoute; |
||||
use OCP\AppFramework\Http\Attribute\NoCSRFRequired; |
||||
use OCP\AppFramework\Http\Attribute\PublicPage; |
||||
use OCP\IConfig; |
||||
use OCP\IRequest; |
||||
use OCP\OpenMetrics\IMetricFamily; |
||||
use OCP\OpenMetrics\Metric; |
||||
use OCP\OpenMetrics\MetricType; |
||||
use OCP\OpenMetrics\MetricValue; |
||||
use Psr\Log\LoggerInterface; |
||||
|
||||
/** |
||||
* OpenMetrics controller |
||||
* |
||||
* Gather and display metrics |
||||
* |
||||
* @package OC\Core\Controller |
||||
*/ |
||||
class OpenMetricsController extends Controller { |
||||
public function __construct( |
||||
string $appName, |
||||
IRequest $request, |
||||
private IConfig $config, |
||||
private ExporterManager $exporterManager, |
||||
private LoggerInterface $logger, |
||||
) { |
||||
parent::__construct($appName, $request); |
||||
} |
||||
|
||||
#[NoCSRFRequired] |
||||
#[PublicPage] |
||||
#[FrontpageRoute(verb: 'GET', url: '/metrics')] |
||||
public function export(): Http\Response { |
||||
if (!$this->isRemoteAddressAllowed()) { |
||||
return new Http\Response(Http::STATUS_FORBIDDEN); |
||||
} |
||||
|
||||
return new Http\StreamTraversableResponse( |
||||
$this->generate(), |
||||
Http::STATUS_OK, |
||||
[ |
||||
'Content-Type' => 'application/openmetrics-text; version=1.0.0; charset=utf-8', |
||||
] |
||||
); |
||||
} |
||||
|
||||
private function isRemoteAddressAllowed(): bool { |
||||
$clientAddress = new Address($this->request->getRemoteAddress()); |
||||
$allowedRanges = $this->config->getSystemValue('openmetrics_allowed_clients', ['127.0.0.0/16', '::1/128']); |
||||
if (!is_array($allowedRanges)) { |
||||
$this->logger->warning('Invalid configuration for "openmetrics_allowed_clients"'); |
||||
return false; |
||||
} |
||||
|
||||
foreach ($allowedRanges as $range) { |
||||
$range = new Range($range); |
||||
if ($range->contains($clientAddress)) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
private function generate(): \Generator { |
||||
foreach ($this->exporterManager->export() as $family) { |
||||
yield $this->formatFamily($family); |
||||
} |
||||
|
||||
$elapsed = (string)(microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']); |
||||
yield <<<SUMMARY |
||||
# TYPE nextcloud_exporter_duration gauge |
||||
# UNIT nextcloud_exporter_duration seconds |
||||
# HELP nextcloud_exporter_duration Exporter run time |
||||
nextcloud_exporter_duration $elapsed |
||||
|
||||
# EOF |
||||
|
||||
SUMMARY; |
||||
} |
||||
|
||||
private function formatFamily(IMetricFamily $family): string { |
||||
$output = ''; |
||||
$name = $family->name(); |
||||
if ($family->type() !== MetricType::unknown) { |
||||
$output = '# TYPE nextcloud_' . $name . ' ' . $family->type()->name . "\n"; |
||||
} |
||||
if ($family->unit() !== '') { |
||||
$output .= '# UNIT nextcloud_' . $name . ' ' . $family->unit() . "\n"; |
||||
} |
||||
if ($family->help() !== '') { |
||||
$output .= '# HELP nextcloud_' . $name . ' ' . $family->help() . "\n"; |
||||
} |
||||
foreach ($family->metrics() as $metric) { |
||||
$output .= 'nextcloud_' . $name . $this->formatLabels($metric) . ' ' . $this->formatValue($metric); |
||||
if ($metric->timestamp !== null) { |
||||
$output .= ' ' . $this->formatTimestamp($metric); |
||||
} |
||||
$output .= "\n"; |
||||
} |
||||
$output .= "\n"; |
||||
|
||||
return $output; |
||||
} |
||||
|
||||
private function formatLabels(Metric $metric): string { |
||||
if (empty($metric->labels)) { |
||||
return ''; |
||||
} |
||||
|
||||
$labels = []; |
||||
foreach ($metric->labels as $label => $value) { |
||||
$labels[] .= $label . '=' . $this->escapeString((string)$value); |
||||
} |
||||
|
||||
return '{' . implode(',', $labels) . '}'; |
||||
} |
||||
|
||||
private function escapeString(string $string): string { |
||||
return json_encode( |
||||
$string, |
||||
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR, |
||||
1 |
||||
); |
||||
} |
||||
|
||||
private function formatValue(Metric $metric): string { |
||||
if (is_bool($metric->value)) { |
||||
return $metric->value ? '1' : '0'; |
||||
} |
||||
if ($metric->value instanceof MetricValue) { |
||||
return $metric->value->value; |
||||
} |
||||
|
||||
return (string)$metric->value; |
||||
} |
||||
|
||||
private function formatTimestamp(Metric $metric): string { |
||||
return (string)$metric->timestamp; |
||||
} |
||||
} |
||||
@ -0,0 +1,94 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OC\OpenMetrics; |
||||
|
||||
use Generator; |
||||
use OC\Log\PsrLoggerAdapter; |
||||
use OCP\App\IAppManager; |
||||
use OCP\IConfig; |
||||
use OCP\OpenMetrics\IMetricFamily; |
||||
use OCP\Server; |
||||
|
||||
class ExporterManager { |
||||
private array $skippedClasses; |
||||
private const XML_ENTRY = 'openmetrics'; |
||||
|
||||
public function __construct( |
||||
private IAppManager $appManager, |
||||
private PsrLoggerAdapter $logger, |
||||
IConfig $config, |
||||
) { |
||||
// Use values as keys for faster lookups |
||||
$this->skippedClasses = array_fill_keys($config->getSystemValue('openmetrics_skipped_classes', []), true); |
||||
} |
||||
|
||||
public function export(): Generator { |
||||
// Core exporters |
||||
$exporters = [ |
||||
// Basic exporters |
||||
Exporters\InstanceInfo::class, |
||||
Exporters\AppsInfo::class, |
||||
Exporters\AppsCount::class, |
||||
Exporters\Maintenance::class, |
||||
|
||||
// File exporters |
||||
Exporters\FilesByType::class, |
||||
|
||||
// Users exporters |
||||
Exporters\ActiveUsers::class, |
||||
Exporters\ActiveSessions::class, |
||||
Exporters\UsersByBackend::class, |
||||
|
||||
// Jobs |
||||
Exporters\RunningJobs::class, |
||||
]; |
||||
$exporters = array_filter($exporters, fn ($classname) => !isset($this->skippedClasses[$classname])); |
||||
foreach ($exporters as $classname) { |
||||
$exporter = $this->loadExporter($classname); |
||||
if ($exporter !== null) { |
||||
yield $exporter; |
||||
} |
||||
} |
||||
|
||||
// Apps exporters |
||||
foreach ($this->appManager->getEnabledApps() as $appId) { |
||||
$appInfo = $this->appManager->getAppInfo($appId); |
||||
if (!isset($appInfo[self::XML_ENTRY]) || !is_array($appInfo[self::XML_ENTRY])) { |
||||
continue; |
||||
} |
||||
foreach ($appInfo[self::XML_ENTRY] as $classname) { |
||||
if (isset($this->skippedClasses[$classname])) { |
||||
continue; |
||||
} |
||||
$exporter = $this->loadExporter($classname, $appId); |
||||
if ($exporter !== null) { |
||||
yield $exporter; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
private function loadExporter(string $classname, string $appId = 'core'): ?IMetricFamily { |
||||
try { |
||||
return Server::get($classname); |
||||
} catch (\Exception $e) { |
||||
$this->logger->error( |
||||
'Unable to build exporter {exporter}', |
||||
[ |
||||
'app' => $appId, |
||||
'exception' => $e, |
||||
'exporter' => $classname, |
||||
], |
||||
); |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
} |
||||
@ -0,0 +1,58 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OC\OpenMetrics\Exporters; |
||||
|
||||
use Generator; |
||||
use OCP\IDBConnection; |
||||
use OCP\OpenMetrics\IMetricFamily; |
||||
use OCP\OpenMetrics\Metric; |
||||
use OCP\OpenMetrics\MetricType; |
||||
|
||||
class ActiveSessions implements IMetricFamily { |
||||
public function __construct( |
||||
private IDBConnection $connection, |
||||
) { |
||||
} |
||||
|
||||
public function name(): string { |
||||
return 'active_sessions'; |
||||
} |
||||
|
||||
public function type(): MetricType { |
||||
return MetricType::gauge; |
||||
} |
||||
|
||||
public function unit(): string { |
||||
return 'sessions'; |
||||
} |
||||
|
||||
public function help(): string { |
||||
return 'Number of active sessions'; |
||||
} |
||||
|
||||
public function metrics(): Generator { |
||||
$now = time(); |
||||
$timeFrames = [ |
||||
'Last 5 minutes' => $now - 5 * 60, |
||||
'Last 15 minutes' => $now - 15 * 60, |
||||
'Last hour' => $now - 60 * 60, |
||||
'Last day' => $now - 24 * 60 * 60, |
||||
]; |
||||
foreach ($timeFrames as $label => $time) { |
||||
$queryBuilder = $this->connection->getQueryBuilder(); |
||||
$result = $queryBuilder->select($queryBuilder->func()->count('*')) |
||||
->from('authtoken') |
||||
->where($queryBuilder->expr()->gte('last_activity', $queryBuilder->createNamedParameter($time))) |
||||
->executeQuery(); |
||||
|
||||
yield new Metric((int)$result->fetchOne(), ['time' => $label]); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,58 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OC\OpenMetrics\Exporters; |
||||
|
||||
use Generator; |
||||
use OCP\IDBConnection; |
||||
use OCP\OpenMetrics\IMetricFamily; |
||||
use OCP\OpenMetrics\Metric; |
||||
use OCP\OpenMetrics\MetricType; |
||||
|
||||
class ActiveUsers implements IMetricFamily { |
||||
public function __construct( |
||||
private IDBConnection $connection, |
||||
) { |
||||
} |
||||
|
||||
public function name(): string { |
||||
return 'active_users'; |
||||
} |
||||
|
||||
public function type(): MetricType { |
||||
return MetricType::gauge; |
||||
} |
||||
|
||||
public function unit(): string { |
||||
return 'users'; |
||||
} |
||||
|
||||
public function help(): string { |
||||
return 'Number of active users'; |
||||
} |
||||
|
||||
public function metrics(): Generator { |
||||
$now = time(); |
||||
$timeFrames = [ |
||||
'Last 5 minutes' => $now - 5 * 60, |
||||
'Last 15 minutes' => $now - 15 * 60, |
||||
'Last hour' => $now - 60 * 60, |
||||
'Last day' => $now - 24 * 60 * 60, |
||||
]; |
||||
foreach ($timeFrames as $label => $time) { |
||||
$qb = $this->connection->getQueryBuilder(); |
||||
$result = $qb->select($qb->createFunction('COUNT(DISTINCT ' . $qb->getColumnName('uid') . ')')) |
||||
->from('authtoken') |
||||
->where($qb->expr()->gte('last_activity', $qb->createNamedParameter($time))) |
||||
->executeQuery(); |
||||
|
||||
yield new Metric((int)$result->fetchOne(), ['time' => $label]); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,62 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OC\OpenMetrics\Exporters; |
||||
|
||||
use Generator; |
||||
use OCP\App\IAppManager; |
||||
use OCP\OpenMetrics\IMetricFamily; |
||||
use OCP\OpenMetrics\Metric; |
||||
use OCP\OpenMetrics\MetricType; |
||||
use Override; |
||||
|
||||
/** |
||||
* Export statistics about apps |
||||
*/ |
||||
class AppsCount implements IMetricFamily { |
||||
public function __construct( |
||||
private IAppManager $appManager, |
||||
) { |
||||
} |
||||
|
||||
#[Override] |
||||
public function name(): string { |
||||
return 'apps_count'; |
||||
} |
||||
|
||||
#[Override] |
||||
public function type(): MetricType { |
||||
return MetricType::gauge; |
||||
} |
||||
|
||||
#[Override] |
||||
public function unit(): string { |
||||
return 'applications'; |
||||
} |
||||
|
||||
#[Override] |
||||
public function help(): string { |
||||
return 'Number of apps in Nextcloud'; |
||||
} |
||||
|
||||
#[Override] |
||||
public function metrics(): Generator { |
||||
$installedAppsCount = count($this->appManager->getAppInstalledVersions(false)); |
||||
$enabledAppsCount = count($this->appManager->getEnabledApps()); |
||||
$disabledAppsCount = $installedAppsCount - $enabledAppsCount; |
||||
yield new Metric( |
||||
$disabledAppsCount, |
||||
['status' => 'disabled'], |
||||
); |
||||
yield new Metric( |
||||
$enabledAppsCount, |
||||
['status' => 'enabled'], |
||||
); |
||||
} |
||||
} |
||||
@ -0,0 +1,56 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OC\OpenMetrics\Exporters; |
||||
|
||||
use Generator; |
||||
use OCP\App\IAppManager; |
||||
use OCP\OpenMetrics\IMetricFamily; |
||||
use OCP\OpenMetrics\Metric; |
||||
use OCP\OpenMetrics\MetricType; |
||||
use Override; |
||||
|
||||
/** |
||||
* Export information about enabled applications |
||||
*/ |
||||
class AppsInfo implements IMetricFamily { |
||||
public function __construct( |
||||
private IAppManager $appManager, |
||||
) { |
||||
} |
||||
|
||||
#[Override] |
||||
public function name(): string { |
||||
return 'apps_info'; |
||||
} |
||||
|
||||
#[Override] |
||||
public function type(): MetricType { |
||||
return MetricType::info; |
||||
} |
||||
|
||||
#[Override] |
||||
public function unit(): string { |
||||
return ''; |
||||
} |
||||
|
||||
#[Override] |
||||
public function help(): string { |
||||
return 'Enabled applications in Nextcloud'; |
||||
} |
||||
|
||||
#[Override] |
||||
public function metrics(): Generator { |
||||
yield new Metric( |
||||
1, |
||||
$this->appManager->getAppInstalledVersions(true), |
||||
time() |
||||
); |
||||
} |
||||
} |
||||
@ -0,0 +1,58 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OC\OpenMetrics\Exporters; |
||||
|
||||
use Generator; |
||||
use OCP\ICache; |
||||
use OCP\ICacheFactory; |
||||
use OCP\OpenMetrics\IMetricFamily; |
||||
use Override; |
||||
|
||||
/** |
||||
* Cached metrics |
||||
*/ |
||||
abstract class Cached implements IMetricFamily { |
||||
private readonly ICache $cache; |
||||
|
||||
public function __construct( |
||||
ICacheFactory $cacheFactory, |
||||
) { |
||||
$this->cache = $cacheFactory->createDistributed('openmetrics'); |
||||
} |
||||
|
||||
/** |
||||
* Number of seconds to keep the results |
||||
*/ |
||||
abstract public function getTTL(): int; |
||||
|
||||
/** |
||||
* Actually gather the metrics |
||||
* |
||||
* @see metrics |
||||
*/ |
||||
abstract public function gatherMetrics(): Generator; |
||||
|
||||
#[Override] |
||||
public function metrics(): Generator { |
||||
$cacheKey = static::class; |
||||
if ($data = $this->cache->get($cacheKey)) { |
||||
yield from unserialize($data); |
||||
return; |
||||
} |
||||
|
||||
$data = []; |
||||
foreach ($this->gatherMetrics() as $metric) { |
||||
yield $metric; |
||||
$data[] = $metric; |
||||
} |
||||
|
||||
$this->cache->set($cacheKey, serialize($data), $this->getTTL()); |
||||
} |
||||
} |
||||
@ -0,0 +1,80 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OC\OpenMetrics\Exporters; |
||||
|
||||
use Generator; |
||||
use OCP\Files\IMimeTypeLoader; |
||||
use OCP\ICacheFactory; |
||||
use OCP\IDBConnection; |
||||
use OCP\OpenMetrics\Metric; |
||||
use OCP\OpenMetrics\MetricType; |
||||
use Override; |
||||
|
||||
/** |
||||
* Export files count |
||||
* |
||||
* Cached exporter, refreshed every 30 minutes |
||||
*/ |
||||
class FilesByType extends Cached { |
||||
public function __construct( |
||||
ICacheFactory $cacheFactory, |
||||
private IDBConnection $connection, |
||||
private IMimeTypeLoader $mimetypeLoader, |
||||
) { |
||||
parent::__construct($cacheFactory); |
||||
} |
||||
|
||||
#[Override] |
||||
public function name(): string { |
||||
return 'files'; |
||||
} |
||||
|
||||
#[Override] |
||||
public function type(): MetricType { |
||||
return MetricType::gauge; |
||||
} |
||||
|
||||
#[Override] |
||||
public function unit(): string { |
||||
return 'files'; |
||||
} |
||||
|
||||
#[Override] |
||||
public function help(): string { |
||||
return 'Number of files by type'; |
||||
} |
||||
|
||||
#[Override] |
||||
public function getTTL(): int { |
||||
return 30 * 60; |
||||
} |
||||
|
||||
#[Override] |
||||
public function gatherMetrics(): Generator { |
||||
$qb = $this->connection->getQueryBuilder()->runAcrossAllShards(); |
||||
$metrics = $qb->select('mimetype', $qb->func()->count('*', 'count')) |
||||
->from('filecache') |
||||
->groupBy('mimetype') |
||||
->executeQuery(); |
||||
|
||||
if ($metrics->rowCount() === 0) { |
||||
yield new Metric(0); |
||||
return; |
||||
} |
||||
$now = time(); |
||||
foreach ($metrics->iterateAssociative() as $count) { |
||||
yield new Metric( |
||||
$count['count'], |
||||
['mimetype' => $this->mimetypeLoader->getMimetypeById($count['mimetype'])], |
||||
$now, |
||||
); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,63 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OC\OpenMetrics\Exporters; |
||||
|
||||
use Generator; |
||||
use OC\SystemConfig; |
||||
use OCP\OpenMetrics\IMetricFamily; |
||||
use OCP\OpenMetrics\Metric; |
||||
use OCP\OpenMetrics\MetricType; |
||||
use OCP\ServerVersion; |
||||
use Override; |
||||
|
||||
/** |
||||
* Export some basic information about current instance |
||||
*/ |
||||
class InstanceInfo implements IMetricFamily { |
||||
public function __construct( |
||||
private SystemConfig $systemConfig, |
||||
private ServerVersion $serverVersion, |
||||
) { |
||||
} |
||||
|
||||
#[Override] |
||||
public function name(): string { |
||||
return 'instance_info'; |
||||
} |
||||
|
||||
#[Override] |
||||
public function type(): MetricType { |
||||
return MetricType::info; |
||||
} |
||||
|
||||
#[Override] |
||||
public function unit(): string { |
||||
return ''; |
||||
} |
||||
|
||||
#[Override] |
||||
public function help(): string { |
||||
return 'Basic information about Nextcloud'; |
||||
} |
||||
|
||||
#[Override] |
||||
public function metrics(): Generator { |
||||
yield new Metric( |
||||
1, |
||||
[ |
||||
'full version' => $this->serverVersion->getHumanVersion(), |
||||
'major version' => (string)$this->serverVersion->getVersion()[0], |
||||
'build' => $this->serverVersion->getBuild(), |
||||
'installed' => $this->systemConfig->getValue('installed', false) ? '1' : '0', |
||||
], |
||||
time() |
||||
); |
||||
} |
||||
} |
||||
@ -0,0 +1,50 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OC\OpenMetrics\Exporters; |
||||
|
||||
use Generator; |
||||
use OC\SystemConfig; |
||||
use OCP\OpenMetrics\IMetricFamily; |
||||
use OCP\OpenMetrics\Metric; |
||||
use OCP\OpenMetrics\MetricType; |
||||
use OCP\Server; |
||||
use Override; |
||||
|
||||
/** |
||||
* Export maintenance state |
||||
*/ |
||||
class Maintenance implements IMetricFamily { |
||||
public function name(): string { |
||||
return 'maintenance'; |
||||
} |
||||
|
||||
#[Override] |
||||
public function type(): MetricType { |
||||
return MetricType::info; |
||||
} |
||||
|
||||
#[Override] |
||||
public function unit(): string { |
||||
return ''; |
||||
} |
||||
|
||||
#[Override] |
||||
public function help(): string { |
||||
return 'Maintenance status of Nextcloud'; |
||||
} |
||||
|
||||
#[Override] |
||||
public function metrics(): Generator { |
||||
$systemConfig = Server::get(SystemConfig::class); |
||||
yield new Metric( |
||||
(bool)$systemConfig->getValue('maintenance', false) |
||||
); |
||||
} |
||||
} |
||||
@ -0,0 +1,67 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OC\OpenMetrics\Exporters; |
||||
|
||||
use Generator; |
||||
use OCP\IDBConnection; |
||||
use OCP\OpenMetrics\IMetricFamily; |
||||
use OCP\OpenMetrics\Metric; |
||||
use OCP\OpenMetrics\MetricType; |
||||
use Override; |
||||
|
||||
/** |
||||
* Export the number of running jobs by type |
||||
*/ |
||||
class RunningJobs implements IMetricFamily { |
||||
public function __construct( |
||||
private IDBConnection $connection, |
||||
) { |
||||
} |
||||
|
||||
#[Override] |
||||
public function name(): string { |
||||
return 'jobs_running'; |
||||
} |
||||
|
||||
#[Override] |
||||
public function type(): MetricType { |
||||
return MetricType::gauge; |
||||
} |
||||
|
||||
#[Override] |
||||
public function unit(): string { |
||||
return 'jobs'; |
||||
} |
||||
|
||||
#[Override] |
||||
public function help(): string { |
||||
return 'Number of running jobs'; |
||||
} |
||||
|
||||
#[Override] |
||||
public function metrics(): Generator { |
||||
$qb = $this->connection->getQueryBuilder(); |
||||
$result = $qb->select($qb->func()->count('*', 'nb'), 'class') |
||||
->from('jobs') |
||||
->where($qb->expr()->gt('reserved_at', $qb->createNamedParameter(0))) |
||||
->groupBy('class') |
||||
->executeQuery(); |
||||
|
||||
// If no result, return a metric with count '0' |
||||
if ($result->rowCount() === 0) { |
||||
yield new Metric(0); |
||||
return; |
||||
} |
||||
|
||||
foreach ($result->iterateAssociative() as $row) { |
||||
yield new Metric($row['nb'], ['class' => $row['class']]); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,55 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OC\OpenMetrics\Exporters; |
||||
|
||||
use Generator; |
||||
use OCP\IUserManager; |
||||
use OCP\OpenMetrics\IMetricFamily; |
||||
use OCP\OpenMetrics\Metric; |
||||
use OCP\OpenMetrics\MetricType; |
||||
use Override; |
||||
|
||||
/** |
||||
* Count users of each backend which supports it (mapped users only) |
||||
*/ |
||||
class UsersByBackend implements IMetricFamily { |
||||
public function __construct( |
||||
private IUserManager $userManager, |
||||
) { |
||||
} |
||||
|
||||
#[Override] |
||||
public function name(): string { |
||||
return 'users'; |
||||
} |
||||
|
||||
#[Override] |
||||
public function type(): MetricType { |
||||
return MetricType::gauge; |
||||
} |
||||
|
||||
#[Override] |
||||
public function unit(): string { |
||||
return 'users'; |
||||
} |
||||
|
||||
#[Override] |
||||
public function help(): string { |
||||
return 'Number of users by backend'; |
||||
} |
||||
|
||||
#[Override] |
||||
public function metrics(): Generator { |
||||
$userCounts = $this->userManager->countUsers(true); |
||||
foreach ($userCounts as $backend => $count) { |
||||
yield new Metric($count, ['backend' => $backend]); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,51 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
namespace OCP\AppFramework\Http; |
||||
|
||||
use OCP\AppFramework\Http; |
||||
use Override; |
||||
use Traversable; |
||||
|
||||
/** |
||||
* Class StreamResponse |
||||
* |
||||
* @since 33.0.0 |
||||
* @template S of Http::STATUS_* |
||||
* @template H of array<string, mixed> |
||||
* @template-extends Response<Http::STATUS_*, array<string, mixed>> |
||||
*/ |
||||
class StreamTraversableResponse extends Response implements ICallbackResponse { |
||||
/** |
||||
* @param S $status |
||||
* @param H $headers |
||||
* @since 33.0.0 |
||||
*/ |
||||
public function __construct( |
||||
private Traversable $generator, |
||||
int $status = Http::STATUS_OK, |
||||
array $headers = [], |
||||
) { |
||||
parent::__construct($status, $headers); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Streams the generator output |
||||
* |
||||
* @param IOutput $output a small wrapper that handles output |
||||
* @since 33.0.0 |
||||
*/ |
||||
#[Override] |
||||
public function callback(IOutput $output): void { |
||||
foreach ($this->generator as $content) { |
||||
$output->setOutput($content); |
||||
flush(); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,53 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OCP\OpenMetrics; |
||||
|
||||
use Generator; |
||||
use OCP\AppFramework\Attribute\Implementable; |
||||
|
||||
/** |
||||
* @since 33.0.0 |
||||
*/ |
||||
#[Implementable(since: '33.0.0')] |
||||
interface IMetricFamily { |
||||
/** |
||||
* Family name (will be prefixed by nextcloud_) |
||||
* |
||||
* @since 33.0.0 |
||||
*/ |
||||
public function name(): string; |
||||
|
||||
/** |
||||
* Family metric type |
||||
* |
||||
* @since 33.0.0 |
||||
*/ |
||||
public function type(): MetricType; |
||||
|
||||
/** |
||||
* Family unit (can be empty string) |
||||
* @since 33.0.0 |
||||
*/ |
||||
public function unit(): string; |
||||
|
||||
/** |
||||
* Family help text (can be empty string) |
||||
* @since 33.0.0 |
||||
*/ |
||||
public function help(): string; |
||||
|
||||
/** |
||||
* List of metrics |
||||
* |
||||
* @return Generator<Metric> |
||||
* @since 33.0.0 |
||||
*/ |
||||
public function metrics(): Generator; |
||||
} |
||||
@ -0,0 +1,27 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OCP\OpenMetrics; |
||||
|
||||
/** |
||||
* @since 33.0.0 |
||||
*/ |
||||
final readonly class Metric { |
||||
public function __construct( |
||||
public int|float|bool|MetricValue $value = false, |
||||
/** @var string[] */ |
||||
public array $labels = [], |
||||
public int|float|null $timestamp = null, |
||||
) { |
||||
} |
||||
|
||||
public function label(string $name): ?string { |
||||
return $this->labels[$name] ?? null; |
||||
} |
||||
} |
||||
@ -0,0 +1,26 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OCP\OpenMetrics; |
||||
|
||||
/** |
||||
* Metrics types |
||||
* |
||||
* @since 33.0.0 |
||||
*/ |
||||
enum MetricType { |
||||
case counter; |
||||
case gauge; |
||||
case histogram; |
||||
case gaugehistogram; |
||||
case stateset; |
||||
case info; |
||||
case summary; |
||||
case unknown; |
||||
} |
||||
@ -0,0 +1,20 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace OCP\OpenMetrics; |
||||
|
||||
/** |
||||
* Special values for metrics |
||||
* @since 33.0.0 |
||||
*/ |
||||
enum MetricValue: string { |
||||
case NOT_A_NUMBER = 'NaN'; |
||||
case POSITIVE_INFINITY = '+Inf'; |
||||
case NEGATIVE_INFINITY = '-Inf'; |
||||
} |
||||
@ -0,0 +1,78 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace Tests\Core\Controller; |
||||
|
||||
use OC\Core\Controller\OpenMetricsController; |
||||
use OC\OpenMetrics\ExporterManager; |
||||
use OCP\AppFramework\Http\IOutput; |
||||
use OCP\AppFramework\Http\Response; |
||||
use OCP\AppFramework\Http\StreamTraversableResponse; |
||||
use OCP\IConfig; |
||||
use OCP\IRequest; |
||||
use PHPUnit\Framework\MockObject\MockObject; |
||||
use Psr\Log\LoggerInterface; |
||||
use Test\TestCase; |
||||
|
||||
class OpenMetricsControllerTest extends TestCase { |
||||
private IRequest&MockObject $request; |
||||
private IConfig&MockObject $config; |
||||
private ExporterManager&MockObject $exporterManager; |
||||
private LoggerInterface&MockObject $logger; |
||||
private OpenMetricsController $controller; |
||||
|
||||
protected function setUp(): void { |
||||
parent::setUp(); |
||||
$this->request = $this->createMock(IRequest::class); |
||||
$this->request->method('getRemoteAddress') |
||||
->willReturn('192.168.1.1'); |
||||
$this->config = $this->createMock(IConfig::class); |
||||
$this->exporterManager = $this->createMock(ExporterManager::class); |
||||
$this->logger = $this->createMock(LoggerInterface::class); |
||||
$this->controller = new OpenMetricsController('core', $this->request, $this->config, $this->exporterManager, $this->logger); |
||||
} |
||||
|
||||
public function testGetMetrics(): void { |
||||
$output = $this->createMock(IOutput::class); |
||||
$fullOutput = ''; |
||||
$output->method('setOutput') |
||||
->willReturnCallback(function ($output) use (&$fullOutput) { |
||||
$fullOutput .= $output; |
||||
}); |
||||
$this->config->expects($this->once()) |
||||
->method('getSystemValue') |
||||
->with('openmetrics_allowed_clients') |
||||
->willReturn(['192.168.0.0/16']); |
||||
$response = $this->controller->export(); |
||||
$this->assertInstanceOf(StreamTraversableResponse::class, $response); |
||||
$this->assertEquals('200', $response->getStatus()); |
||||
$this->assertEquals('application/openmetrics-text; version=1.0.0; charset=utf-8', $response->getHeaders()['Content-Type']); |
||||
$expected = <<<EXPECTED |
||||
# TYPE nextcloud_exporter_duration gauge |
||||
# UNIT nextcloud_exporter_duration seconds |
||||
# HELP nextcloud_exporter_duration Exporter run time |
||||
nextcloud_exporter_duration %f |
||||
|
||||
# EOF |
||||
|
||||
EXPECTED; |
||||
$response->callback($output); |
||||
$this->assertStringMatchesFormat($expected, $fullOutput); |
||||
} |
||||
|
||||
public function testGetMetricsFromForbiddenIp(): void { |
||||
$this->config->expects($this->once()) |
||||
->method('getSystemValue') |
||||
->with('openmetrics_allowed_clients') |
||||
->willReturn(['1.2.3.4']); |
||||
$response = $this->controller->export(); |
||||
$this->assertInstanceOf(Response::class, $response); |
||||
$this->assertEquals('403', $response->getStatus()); |
||||
} |
||||
} |
||||
@ -0,0 +1,23 @@ |
||||
<?php |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace Test\OpenMetrics; |
||||
|
||||
use OC\OpenMetrics\ExporterManager; |
||||
use OCP\OpenMetrics\IMetricFamily; |
||||
use OCP\Server; |
||||
use Test\TestCase; |
||||
|
||||
class ExporterManagerTest extends TestCase { |
||||
public function testExport(): void { |
||||
$exporter = Server::get(ExporterManager::class); |
||||
$this->assertInstanceOf(ExporterManager::class, $exporter); |
||||
foreach ($exporter->export() as $metric) { |
||||
$this->assertInstanceOf(IMetricFamily::class, $metric); |
||||
}; |
||||
} |
||||
} |
||||
@ -0,0 +1,32 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace Test\OpenMetrics\Exporters; |
||||
|
||||
use OC\OpenMetrics\Exporters\ActiveSessions; |
||||
use OCP\IDBConnection; |
||||
use OCP\OpenMetrics\IMetricFamily; |
||||
use OCP\Server; |
||||
use PHPUnit\Framework\Attributes\Group; |
||||
|
||||
#[Group('DB')] |
||||
class ActiveSessionsTest extends ExporterTestCase { |
||||
protected function getExporter():IMetricFamily { |
||||
return new ActiveSessions(Server::get(IDBConnection::class)); |
||||
} |
||||
|
||||
public function testMetricsLabel(): void { |
||||
$this->assertLabelsAre([ |
||||
['time' => 'Last 5 minutes'], |
||||
['time' => 'Last 15 minutes'], |
||||
['time' => 'Last hour'], |
||||
['time' => 'Last day'], |
||||
]); |
||||
} |
||||
} |
||||
@ -0,0 +1,32 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace Test\OpenMetrics\Exporters; |
||||
|
||||
use OC\OpenMetrics\Exporters\ActiveUsers; |
||||
use OCP\IDBConnection; |
||||
use OCP\OpenMetrics\IMetricFamily; |
||||
use OCP\Server; |
||||
use PHPUnit\Framework\Attributes\Group; |
||||
|
||||
#[Group('DB')] |
||||
class ActiveUsersTest extends ExporterTestCase { |
||||
protected function getExporter():IMetricFamily { |
||||
return new ActiveUsers(Server::get(IDBConnection::class)); |
||||
} |
||||
|
||||
public function testMetricsLabel(): void { |
||||
$this->assertLabelsAre([ |
||||
['time' => 'Last 5 minutes'], |
||||
['time' => 'Last 15 minutes'], |
||||
['time' => 'Last hour'], |
||||
['time' => 'Last day'], |
||||
]); |
||||
} |
||||
} |
||||
@ -0,0 +1,38 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace Test\OpenMetrics\Exporters; |
||||
|
||||
use OC\OpenMetrics\Exporters\AppsCount; |
||||
use OCP\App\IAppManager; |
||||
use OCP\OpenMetrics\IMetricFamily; |
||||
|
||||
class AppsCountTest extends ExporterTestCase { |
||||
private IAppManager $appManager; |
||||
|
||||
protected function getExporter():IMetricFamily { |
||||
$this->appManager = $this->createMock(IAppManager::class); |
||||
$this->appManager->method('getAppInstalledVersions') |
||||
->with(false) |
||||
->willReturn(['app1', 'app2', 'app3', 'app4', 'app5']); |
||||
$this->appManager->method('getEnabledApps') |
||||
->willReturn(['app1', 'app2', 'app3']); |
||||
return new AppsCount($this->appManager); |
||||
} |
||||
|
||||
public function testMetrics(): void { |
||||
foreach ($this->metrics as $metric) { |
||||
$expectedValue = match ($metric->label('status')) { |
||||
'disabled' => 2, |
||||
'enabled' => 3, |
||||
}; |
||||
$this->assertEquals($expectedValue, $metric->value); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,37 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace Test\OpenMetrics\Exporters; |
||||
|
||||
use OC\OpenMetrics\Exporters\AppsInfo; |
||||
use OCP\App\IAppManager; |
||||
use OCP\OpenMetrics\IMetricFamily; |
||||
|
||||
class AppsInfoTest extends ExporterTestCase { |
||||
private IAppManager $appManager; |
||||
private array $appList = [ |
||||
'appA' => '0.1.2', |
||||
'appB' => '1.2.3 beta 4', |
||||
]; |
||||
|
||||
protected function getExporter():IMetricFamily { |
||||
$this->appManager = $this->createMock(IAppManager::class); |
||||
$this->appManager->method('getAppInstalledVersions') |
||||
->with(true) |
||||
->willReturn($this->appList); |
||||
|
||||
return new AppsInfo($this->appManager); |
||||
} |
||||
|
||||
public function testMetrics(): void { |
||||
$this->assertCount(1, $this->metrics); |
||||
$metric = array_pop($this->metrics); |
||||
$this->assertSame($this->appList, $metric->labels); |
||||
} |
||||
} |
||||
@ -0,0 +1,41 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace Test\OpenMetrics\Exporters; |
||||
|
||||
use OCP\OpenMetrics\IMetricFamily; |
||||
use Test\TestCase; |
||||
|
||||
abstract class ExporterTestCase extends TestCase { |
||||
protected IMetricFamily $exporter; |
||||
/** @var IMetric[] */ |
||||
protected array $metrics; |
||||
|
||||
abstract protected function getExporter(): IMetricFamily; |
||||
|
||||
protected function setUp(): void { |
||||
parent::setUp(); |
||||
$this->exporter = $this->getExporter(); |
||||
$this->metrics = iterator_to_array($this->exporter->metrics()); |
||||
} |
||||
|
||||
public function testNotEmptyData() { |
||||
$this->assertNotEmpty($this->exporter->name()); |
||||
$this->assertNotEmpty($this->metrics); |
||||
} |
||||
|
||||
protected function assertLabelsAre(array $expectedLabels) { |
||||
$foundLabels = []; |
||||
foreach ($this->metrics as $metric) { |
||||
$foundLabels[] = $metric->labels; |
||||
} |
||||
|
||||
$this->assertSame($foundLabels, $expectedLabels); |
||||
} |
||||
} |
||||
@ -0,0 +1,29 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace Test\OpenMetrics\Exporters; |
||||
|
||||
use OC\OpenMetrics\Exporters\FilesByType; |
||||
use OCP\Files\IMimeTypeLoader; |
||||
use OCP\ICacheFactory; |
||||
use OCP\IDBConnection; |
||||
use OCP\OpenMetrics\IMetricFamily; |
||||
use OCP\Server; |
||||
use PHPUnit\Framework\Attributes\Group; |
||||
|
||||
#[Group('DB')] |
||||
class FilesByTypeTest extends ExporterTestCase { |
||||
protected function getExporter():IMetricFamily { |
||||
return new FilesByType( |
||||
Server::get(ICacheFactory::class), |
||||
Server::get(IDBConnection::class), |
||||
Server::get(IMimeTypeLoader::class), |
||||
); |
||||
} |
||||
} |
||||
@ -0,0 +1,42 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace Test\OpenMetrics\Exporters; |
||||
|
||||
use OC\OpenMetrics\Exporters\InstanceInfo; |
||||
use OC\SystemConfig; |
||||
use OCP\OpenMetrics\IMetricFamily; |
||||
use OCP\ServerVersion; |
||||
use PHPUnit\Framework\MockObject\MockObject; |
||||
|
||||
class InstanceInfoTest extends ExporterTestCase { |
||||
private SystemConfig&MockObject $systemConfig; |
||||
private ServerVersion&MockObject $serverVersion; |
||||
|
||||
protected function getExporter():IMetricFamily { |
||||
$this->systemConfig = $this->createMock(SystemConfig::class); |
||||
$this->serverVersion = $this->createMock(ServerVersion::class); |
||||
$this->serverVersion->method('getHumanVersion')->willReturn('33.13.17 Gold'); |
||||
$this->serverVersion->method('getVersion')->willReturn([33, 13, 17]); |
||||
$this->serverVersion->method('getBuild')->willReturn('dev'); |
||||
|
||||
return new InstanceInfo($this->systemConfig, $this->serverVersion); |
||||
} |
||||
|
||||
public function testMetrics(): void { |
||||
$this->assertCount(1, $this->metrics); |
||||
$metric = array_pop($this->metrics); |
||||
$this->assertSame([ |
||||
'full version' => '33.13.17 Gold', |
||||
'major version' => '33', |
||||
'build' => 'dev', |
||||
'installed' => '0', |
||||
], $metric->labels); |
||||
} |
||||
} |
||||
@ -0,0 +1,19 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace Test\OpenMetrics\Exporters; |
||||
|
||||
use OC\OpenMetrics\Exporters\Maintenance; |
||||
use OCP\OpenMetrics\IMetricFamily; |
||||
|
||||
class MaintenanceTest extends ExporterTestCase { |
||||
protected function getExporter():IMetricFamily { |
||||
return new Maintenance(); |
||||
} |
||||
} |
||||
@ -0,0 +1,23 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace Test\OpenMetrics\Exporters; |
||||
|
||||
use OC\OpenMetrics\Exporters\RunningJobs; |
||||
use OCP\IDBConnection; |
||||
use OCP\OpenMetrics\IMetricFamily; |
||||
use OCP\Server; |
||||
use PHPUnit\Framework\Attributes\Group; |
||||
|
||||
#[Group('DB')] |
||||
class RunningJobsTest extends ExporterTestCase { |
||||
protected function getExporter():IMetricFamily { |
||||
return new RunningJobs(Server::get(IDBConnection::class)); |
||||
} |
||||
} |
||||
@ -0,0 +1,39 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
namespace Test\OpenMetrics\Exporters; |
||||
|
||||
use OC\OpenMetrics\Exporters\UsersByBackend; |
||||
use OCP\IUserManager; |
||||
use OCP\OpenMetrics\IMetricFamily; |
||||
use PHPUnit\Framework\MockObject\MockObject; |
||||
|
||||
class UsersByBackendTest extends ExporterTestCase { |
||||
private IUserManager&MockObject $userManager; |
||||
private array $backendList = [ |
||||
'backend A' => 42, |
||||
'backend B' => 51, |
||||
'backend C' => 0, |
||||
]; |
||||
|
||||
|
||||
protected function getExporter():IMetricFamily { |
||||
$this->userManager = $this->createMock(IUserManager::class); |
||||
$this->userManager->method('countUsers') |
||||
->with(true) |
||||
->willReturn($this->backendList); |
||||
return new UsersByBackend($this->userManager); |
||||
} |
||||
|
||||
public function testMetrics(): void { |
||||
foreach ($this->metrics as $metric) { |
||||
$this->assertEquals($this->backendList[$metric->label('backend')], $metric->value); |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue