Skip to content
Draft
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
4 changes: 4 additions & 0 deletions apps/comments/appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@
</providers>
</activity>

<openmetrics>
<exporter>OCA\Comments\OpenMetrics\Comments</exporter>
</openmetrics>

<collaboration>
<plugins>
<plugin type="autocomplete-sort">OCA\Comments\Collaboration\CommentersSorter</plugin>
Expand Down
1 change: 1 addition & 0 deletions apps/comments/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@
'OCA\\Comments\\MaxAutoCompleteResultsInitialState' => $baseDir . '/../lib/MaxAutoCompleteResultsInitialState.php',
'OCA\\Comments\\Notification\\Listener' => $baseDir . '/../lib/Notification/Listener.php',
'OCA\\Comments\\Notification\\Notifier' => $baseDir . '/../lib/Notification/Notifier.php',
'OCA\\Comments\\OpenMetrics\\Comments' => $baseDir . '/../lib/OpenMetrics/Comments.php',
'OCA\\Comments\\Search\\CommentsSearchProvider' => $baseDir . '/../lib/Search/CommentsSearchProvider.php',
);
1 change: 1 addition & 0 deletions apps/comments/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class ComposerStaticInitComments
'OCA\\Comments\\MaxAutoCompleteResultsInitialState' => __DIR__ . '/..' . '/../lib/MaxAutoCompleteResultsInitialState.php',
'OCA\\Comments\\Notification\\Listener' => __DIR__ . '/..' . '/../lib/Notification/Listener.php',
'OCA\\Comments\\Notification\\Notifier' => __DIR__ . '/..' . '/../lib/Notification/Notifier.php',
'OCA\\Comments\\OpenMetrics\\Comments' => __DIR__ . '/..' . '/../lib/OpenMetrics/Comments.php',
'OCA\\Comments\\Search\\CommentsSearchProvider' => __DIR__ . '/..' . '/../lib/Search/CommentsSearchProvider.php',
);

Expand Down
52 changes: 52 additions & 0 deletions apps/comments/lib/OpenMetrics/Comments.php
Original file line number Diff line number Diff line change
@@ -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\MetricTypes;
use Override;

class Comments implements IMetricFamily {
public function __construct(
private Connection $connection,
) {
}

#[Override]
public function name(): string {
return 'comments';
}

#[Override]
public function type(): MetricTypes {
return MetricTypes::gauge;
}

#[Override]
public function unit(): string {
return 'comments';
}

#[Override]
public function help(): string {
return 'Comments counts';
}

#[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());
}
}
4 changes: 4 additions & 0 deletions apps/files_sharing/appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,8 @@ Turning the feature off removes shared files and folders on the server for all s
<public>
<files>public.php</files>
</public>

<openmetrics>
<exporter>OCA\Files_Sharing\OpenMetrics\SharesCount</exporter>
</openmetrics>
</info>
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
'OCA\\Files_Sharing\\MountProvider' => $baseDir . '/../lib/MountProvider.php',
'OCA\\Files_Sharing\\Notification\\Listener' => $baseDir . '/../lib/Notification/Listener.php',
'OCA\\Files_Sharing\\Notification\\Notifier' => $baseDir . '/../lib/Notification/Notifier.php',
'OCA\\Files_Sharing\\OpenMetrics\\SharesCount' => $baseDir . '/../lib/OpenMetrics/SharesCount.php',
'OCA\\Files_Sharing\\OrphanHelper' => $baseDir . '/../lib/OrphanHelper.php',
'OCA\\Files_Sharing\\ResponseDefinitions' => $baseDir . '/../lib/ResponseDefinitions.php',
'OCA\\Files_Sharing\\Scanner' => $baseDir . '/../lib/Scanner.php',
Expand Down
1 change: 1 addition & 0 deletions apps/files_sharing/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ class ComposerStaticInitFiles_Sharing
'OCA\\Files_Sharing\\MountProvider' => __DIR__ . '/..' . '/../lib/MountProvider.php',
'OCA\\Files_Sharing\\Notification\\Listener' => __DIR__ . '/..' . '/../lib/Notification/Listener.php',
'OCA\\Files_Sharing\\Notification\\Notifier' => __DIR__ . '/..' . '/../lib/Notification/Notifier.php',
'OCA\\Files_Sharing\\OpenMetrics\\SharesCount' => __DIR__ . '/..' . '/../lib/OpenMetrics/SharesCount.php',
'OCA\\Files_Sharing\\OrphanHelper' => __DIR__ . '/..' . '/../lib/OrphanHelper.php',
'OCA\\Files_Sharing\\ResponseDefinitions' => __DIR__ . '/..' . '/../lib/ResponseDefinitions.php',
'OCA\\Files_Sharing\\Scanner' => __DIR__ . '/..' . '/../lib/Scanner.php',
Expand Down
75 changes: 75 additions & 0 deletions apps/files_sharing/lib/OpenMetrics/SharesCount.php
Original file line number Diff line number Diff line change
@@ -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\MetricTypes;
use OCP\Share\IShare;
use Override;

/**
* Count shares by type
* @since 33.0.0
*/
class SharesCount implements IMetricFamily {
public function __construct(
private IDBConnection $connection,
) {
}

#[Override]
public function name(): string {
return 'shares';
}

#[Override]
public function type(): MetricTypes {
return MetricTypes::gauge;
}

#[Override]
public function unit(): string {
return 'shares';
}

#[Override]
public function help(): string {
return 'Number of shares';
}

#[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;
}

while ($row = $result->fetch()) {
yield new Metric($row['count'], ['type' => $types[$row['share_type']]]);
}
}
}
25 changes: 25 additions & 0 deletions config/config.sample.php
Original file line number Diff line number Diff line change
Expand Up @@ -2892,4 +2892,29 @@
* Defaults to `\OC::$SERVERROOT . '/resources/config/ca-bundle.crt'`.
*/
'default_certificates_bundle_path' => \OC::$SERVERROOT . '/resources/config/ca-bundle.crt',

/**
* OpenMetrics skipped exporters
* Allows to skip some exporters in the OpenMetrics endpoint ``/metrics``.
*
* Default to ``[]`` (empty array)
*/
'openmetrics_skipped_classes' => [
'OC\OpenMetrics\Exporters\FilesByType',
'OCA\Files_Sharing\OpenMetrics\SharesCount',
],

/**
* OpenMetrics allowed client IP addresses
* Restricts the IP addresses able to make requests on the ``/metrics`` endpoint.
*
* Keep this list as restrictive as possible as metrics can consume a lot of resources.
*
* Default to ``[127.0.0.0/16', '::1/128]`` (allow loopback interface only)
*/
'openmetrics_allowed_clients' => [
'192.168.0.0/16',
'fe80::/10',
'10.0.0.1',
],
];
152 changes: 152 additions & 0 deletions core/Controller/OpenMetricsController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<?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\Exporter;
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\Metric;
use OCP\OpenMetrics\MetricTypes;
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 Exporter $exporter,
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 {
$exporter = $this->exporter;

foreach ($exporter() as $family) {
$output = '';
$name = $family->name();
if ($family->type() !== MetricTypes::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";

yield $output;
}

$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 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;
}
}
4 changes: 4 additions & 0 deletions core/openapi-administration.json
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,10 @@
{
"name": "ocm",
"description": "Controller about the endpoint /ocm-provider/"
},
{
"name": "open_metrics",
"description": "OpenMetrics controller Gather and display metrics"
}
]
}
4 changes: 4 additions & 0 deletions core/openapi-ex_app.json
Original file line number Diff line number Diff line change
Expand Up @@ -1609,6 +1609,10 @@
{
"name": "ocm",
"description": "Controller about the endpoint /ocm-provider/"
},
{
"name": "open_metrics",
"description": "OpenMetrics controller Gather and display metrics"
}
]
}
4 changes: 4 additions & 0 deletions core/openapi-full.json
Original file line number Diff line number Diff line change
Expand Up @@ -12238,6 +12238,10 @@
{
"name": "ocm",
"description": "Controller about the endpoint /ocm-provider/"
},
{
"name": "open_metrics",
"description": "OpenMetrics controller Gather and display metrics"
}
]
}
Loading
Loading