Skip to content
Merged

Opcache #1092

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

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion css/serverinfo-main.css
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/* extracted by css-entry-points-plugin */
@import './main-B5AoQMcv.chunk.css';
@import './main-CaivFkOz.chunk.css';
24 changes: 12 additions & 12 deletions js/serverinfo-main.mjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/serverinfo-main.mjs.map

Large diffs are not rendered by default.

140 changes: 140 additions & 0 deletions lib/Collector/Opcache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\ServerInfo\Collector;

use bantu\IniGetWrapper\IniGetWrapper;

/**
* @psalm-api
*
* @psalm-type ServerInfoOpcacheStatus = array{
* status: 'ok'|'not_loaded'|'disabled'|'api_restricted'|'status_unavailable',
* memory?: array{used: int, wasted: int, free: int, total: int},
* internedStrings?: array{used: int, free: int, total: int},
* keys?: array{used: int, max: int},
* hitRate?: float,
* cachedScripts?: int,
* oomRestarts?: int,
* cacheFull?: bool,
* revalidateFreq?: int,
* validateTimestamps?: bool,
* lastRestart?: int|null,
* jit?: array{enabled: bool, bufferUsed: int, bufferTotal: int}|null
* }
*/
class Opcache {
public function __construct(
private IniGetWrapper $iniGetWrapper,
) {
}

/**
* @return ServerInfoOpcacheStatus
*/
public function getData(): array {
if (!extension_loaded('Zend OPcache')) {
return ['status' => 'not_loaded'];
}

if (!$this->iniGetWrapper->getBool('opcache.enable')) {
return ['status' => 'disabled'];
}

if (!$this->isApiPermitted()) {
return ['status' => 'api_restricted'];
}

$disabledFunctions = (string)$this->iniGetWrapper->getString('disable_functions');
if (str_contains($disabledFunctions, 'opcache_get_status')) {
return ['status' => 'status_unavailable'];
}

$status = $this->readStatus();
if (!is_array($status)) {
return ['status' => 'status_unavailable'];
}

return array_merge(['status' => 'ok'], $this->mapStatus($status));
}

/**
* @return array|false the raw opcache_get_status(false) result
*/
protected function readStatus(): array|false {
return function_exists('opcache_get_status') ? opcache_get_status(false) : false;
}

/**
* Nextcloud may be denied access to the OPcache API for its own directories,
* see the `opcache.restrict_api` ini setting.
*/
private function isApiPermitted(): bool {
$restrictPath = rtrim((string)$this->iniGetWrapper->getString('opcache.restrict_api'), '/');
return $restrictPath === ''
|| \OC::$SERVERROOT === $restrictPath
|| str_starts_with(\OC::$SERVERROOT, $restrictPath . '/');
}

/**
* @return array{
* memory: array{used: int, wasted: int, free: int, total: int},
* internedStrings: array{used: int, free: int, total: int},
* keys: array{used: int, max: int},
* hitRate: float,
* cachedScripts: int,
* oomRestarts: int,
* cacheFull: bool,
* revalidateFreq: int,
* validateTimestamps: bool,
* lastRestart: int|null,
* jit: array{enabled: bool, bufferUsed: int, bufferTotal: int}|null
* }
*/
private function mapStatus(array $status): array {
$memory = $status['memory_usage'];
$strings = $status['interned_strings_usage'];
$stats = $status['opcache_statistics'];

$jit = null;
if (isset($status['jit']['buffer_size']) && $status['jit']['buffer_size'] > 0) {
$jit = [
'enabled' => (bool)($status['jit']['enabled'] ?? false),
'bufferUsed' => (int)$status['jit']['buffer_size'] - (int)$status['jit']['buffer_free'],
'bufferTotal' => (int)$status['jit']['buffer_size'],
];
}

return [
'memory' => [
'used' => (int)$memory['used_memory'],
'wasted' => (int)$memory['wasted_memory'],
'free' => (int)$memory['free_memory'],
'total' => (int)$memory['used_memory'] + (int)$memory['wasted_memory'] + (int)$memory['free_memory'],
],
'internedStrings' => [
'used' => (int)$strings['used_memory'],
'free' => (int)$strings['free_memory'],
'total' => (int)$strings['buffer_size'],
],
'keys' => [
'used' => (int)$stats['num_cached_keys'],
'max' => (int)$stats['max_cached_keys'],
],
'hitRate' => (float)$stats['opcache_hit_rate'],
'cachedScripts' => (int)$stats['num_cached_scripts'],
'oomRestarts' => (int)$stats['oom_restarts'],
'cacheFull' => (bool)$status['cache_full'],
'revalidateFreq' => (int)$this->iniGetWrapper->getNumeric('opcache.revalidate_freq'),
'validateTimestamps' => (bool)$this->iniGetWrapper->getBool('opcache.validate_timestamps'),
'lastRestart' => ((int)($stats['last_restart_time'] ?? 0)) ?: null,
'jit' => $jit,
];
}
}
64 changes: 64 additions & 0 deletions lib/Collector/Php.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\ServerInfo\Collector;

use bantu\IniGetWrapper\IniGetWrapper;

/**
* @psalm-api
*/
class Php {
public function __construct(
private IniGetWrapper $iniGetWrapper,
) {
}

/**
* @return array{
* version: string,
* sapi: string,
* memoryLimit: int,
* maxExecutionTime: int,
* uploadMaxFilesize: int,
* postMaxSize: int,
* extensions: list<string>|null
* }
*/
public function getData(): array {
return [
'version' => PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION,
'sapi' => PHP_SAPI,
'memoryLimit' => (int)$this->iniGetWrapper->getBytes('memory_limit'),
'maxExecutionTime' => (int)$this->iniGetWrapper->getNumeric('max_execution_time'),
'uploadMaxFilesize' => (int)$this->iniGetWrapper->getBytes('upload_max_filesize'),
'postMaxSize' => (int)$this->iniGetWrapper->getBytes('post_max_size'),
'extensions' => $this->getLoadedPhpExtensions(),
];
}

/**
* @return list<string>|null null if PHP forbids enumeration
*/
private function getLoadedPhpExtensions(): ?array {
if (!function_exists('get_loaded_extensions')) {
return null;
}

// `get_loaded_extensions(true)` returns Zend extensions (OPcache, Xdebug,
// etc.) which are otherwise hidden from the regular call.
$extensions = array_unique(array_map('strtolower', array_merge(
get_loaded_extensions(false),
get_loaded_extensions(true),
)));
natcasesort($extensions);

return array_values($extensions);
}
}
8 changes: 6 additions & 2 deletions lib/StaticData.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

namespace OCA\ServerInfo;

use OCA\ServerInfo\Collector\Opcache;
use OCA\ServerInfo\Collector\Php;
use OCP\AppFramework\Services\IAppConfig;
use OCP\IURLGenerator;

Expand All @@ -17,7 +19,8 @@ public function __construct(
private Os $os,
private IURLGenerator $urlGenerator,
private StorageStatistics $storageStatistics,
private PhpStatistics $phpStatistics,
private Php $php,
private Opcache $opcache,
private FpmStatistics $fpmStatistics,
private DatabaseStatistics $databaseStatistics,
private ShareStatistics $shareStatistics,
Expand Down Expand Up @@ -58,7 +61,8 @@ public function getData(): array {
'ocs' => $this->urlGenerator->getAbsoluteURL('ocs/v2.php/apps/serverinfo/api/v1/info'),
'storage' => $this->storageStatistics->getStorageStatistics(),
'shares' => $this->shareStatistics->getShareStatistics(),
'php' => $this->phpStatistics->getPhpStatistics(),
'php' => $this->php->getData(),
'opcache' => $this->opcache->getData(),
'fpm' => $this->fpmStatistics->getFpmStatistics(),
'database' => $this->databaseStatistics->getDatabaseStatistics(),
'activeUsers' => $this->sessionStatistics->getSessionStatistics(),
Expand Down
Loading
Loading