Skip to content

Commit caadeb0

Browse files
committed
Enhance and complement OPcache setup checks
The current OPcache recommendations match the PHP defaults, but the values are much higher than required to run Nextcloud, even with a high number of installed apps. On the other hand, when other applications use the same OPcache instance, the recommended values might not be sufficient. Accurate recommendations need to take into account actual OPcache usage. With this commit, recommendations are shown to raise the config value if more than 90% of max cache size or number of keys is used. The checks whether the module is loaded and whether the OPcache is properly configured have been merged into a single function. This allowed to reduce the overhead of OPcache configuration checks when the module is not loaded. A check has been added whether Nextcloud is permitted to use the OPcache API. Without this, inconsistencies during core or app upgrades may cause errors and OPcache usage cannot be determined for the new usage based checks. OPcache usage based checks are skipped when Nextcloud is not permitted to use the API. Signed-off-by: MichaIng <micha@dietpi.com>
1 parent 25c7bf7 commit caadeb0

3 files changed

Lines changed: 76 additions & 126 deletions

File tree

apps/settings/lib/Controller/CheckSetupController.php

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
* @author timm2k <timm2k@gmx.de>
2626
* @author Timo Förster <tfoerster@webfoersterei.de>
2727
* @author Valdnet <47037905+Valdnet@users.noreply.github.com>
28+
* @author MichaIng <micha@dietpi.com>
2829
*
2930
* @license AGPL-3.0
3031
*
@@ -225,7 +226,7 @@ protected function getCurlVersion() {
225226
}
226227

227228
/**
228-
* Check if the used SSL lib is outdated. Older OpenSSL and NSS versions do
229+
* Check if the used SSL lib is outdated. Older OpenSSL and NSS versions do
229230
* have multiple bugs which likely lead to problems in combination with
230231
* functionality required by ownCloud such as SNI.
231232
*
@@ -433,31 +434,61 @@ public function getFailedIntegrityCheckFiles(): DataDisplayResponse {
433434
}
434435

435436
/**
436-
* Checks whether a PHP opcache is properly set up
437-
* @return bool
437+
* Checks whether a PHP OPcache is properly set up
438+
* @return string[] The list of OPcache setup recommendations
438439
*/
439-
protected function isOpcacheProperlySetup() {
440-
if (!$this->iniGetWrapper->getBool('opcache.enable')) {
441-
return false;
440+
protected function getOpcacheSetupRecommendations(): array {
441+
// If the module is not loaded, return directly to skip inapplicable checks
442+
if (!extension_loaded('Zend OPcache')) {
443+
return ['The PHP OPcache module is not loaded. <a target="_blank" rel="noreferrer noopener" class="external" href="' . $this->urlGenerator->linkToDocs('admin-php-opcache') . '">For better performance it is recommended</a> to load it into your PHP installation.'];
442444
}
443445

444-
if (!$this->iniGetWrapper->getBool('opcache.save_comments')) {
445-
return false;
446-
}
446+
$recommendations = [];
447447

448-
if ($this->iniGetWrapper->getNumeric('opcache.max_accelerated_files') < 10000) {
449-
return false;
448+
// Check whether Nextcloud is allowed to use the OPcache API
449+
$isPermitted = true;
450+
$permittedPath = $this->iniGetWrapper->getString('opcache.restrict_api');
451+
if (isset($permittedPath) && $permittedPath !== '' && !str_starts_with(\OC::$SERVERROOT, $permittedPath)) {
452+
$isPermitted = false;
450453
}
451454

452-
if ($this->iniGetWrapper->getNumeric('opcache.memory_consumption') < 128) {
453-
return false;
454-
}
455+
if (!$this->iniGetWrapper->getBool('opcache.enable')) {
456+
$recommendations[] = 'OPcache is disabled. For better performance, it is recommended to apply <code>opcache.enable=1</code> to your PHP configuration.';
455457

456-
if ($this->iniGetWrapper->getNumeric('opcache.interned_strings_buffer') < 8) {
457-
return false;
458+
// Check for saved comments only when OPcache is currently disabled. If it was enabled, opcache.save_comments=0 would break Nextcloud in the first place.
459+
if (!$this->iniGetWrapper->getBool('opcache.save_comments')) {
460+
$recommendations[] = 'OPcache is configured to remove code comments. With OPcache enabled, <code>opcache.save_comments=1</code> must be set for Nextcloud to function.';
461+
}
462+
463+
if (!$isPermitted) {
464+
$recommendations[] = 'Nextcloud is not allowed to use the OPcache API. With OPcache enabled, it is highly recommended to include all Nextcloud directories with <code>opcache.restrict_api</code> or unset this setting to disable OPcache API restrictions, to prevent errors during Nextcloud core or app upgrades.';
465+
}
466+
} elseif (!$isPermitted) {
467+
$recommendations[] = 'Nextcloud is not allowed to use the OPcache API. It is highly recommended to include all Nextcloud directories with <code>opcache.restrict_api</code> or unset this setting to disable OPcache API restrictions, to prevent errors during Nextcloud core or app upgrades.';
468+
} else {
469+
// Check whether opcache_get_status has been explicitly disabled an in case skip usage based checks
470+
$disabledFunctions = $this->iniGetWrapper->getString('disable_functions');
471+
if (isset($disabledFunctions) && str_contains($disabledFunctions, 'opcache_get_status')) {
472+
return [];
473+
}
474+
475+
$status = opcache_get_status(false);
476+
477+
// Recommend to raise value, if more than 90% of max value is reached
478+
if ($status['opcache_statistics']['num_cached_keys'] / $status['opcache_statistics']['max_cached_keys'] > 0.9) {
479+
$recommendations[] = 'The maximum number of OPcache keys is nearly exceeded. To assure that all scripts can be hold in cache, it is recommended to apply <code>opcache.max_accelerated_files</code> to your PHP configuration with a value higher than <code>' . ($this->iniGetWrapper->getNumeric('opcache.max_accelerated_files') ?: 'currently') . '</code>.';
480+
}
481+
482+
if ($status['memory_usage']['used_memory'] / $status['memory_usage']['free_memory'] > 9) {
483+
$recommendations[] = 'The OPcache buffer is nearly full. To assure that all scripts can be hold in cache, it is recommended to apply <code>opcache.memory_consumption</code> to your PHP configuration with a value higher than <code>' . ($this->iniGetWrapper->getNumeric('opcache.memory_consumption') ?: 'currently') . '</code>.';
484+
}
485+
486+
if ($status['interned_strings_usage']['used_memory'] / $status['interned_strings_usage']['free_memory'] > 9) {
487+
$recommendations[] = 'The OPcache interned strings buffer is nearly full. To assure that repeating strings can be effectively cached, it is recommended to apply <code>opcache.interned_strings_buffer</code> to your PHP configuration with a value higher than <code>' . ($this->iniGetWrapper->getNumeric('opcache.interned_strings_buffer') ?: 'currently') . '</code>.';
488+
}
458489
}
459490

460-
return true;
491+
return $recommendations;
461492
}
462493

463494
/**
@@ -557,10 +588,6 @@ protected function getCronErrors() {
557588
return [];
558589
}
559590

560-
protected function hasOpcacheLoaded(): bool {
561-
return extension_loaded('Zend OPcache');
562-
}
563-
564591
private function isTemporaryDirectoryWritable(): bool {
565592
try {
566593
if (!empty($this->tempManager->getTempBaseDir())) {
@@ -773,9 +800,7 @@ public function check() {
773800
'isCorrectMemcachedPHPModuleInstalled' => $this->isCorrectMemcachedPHPModuleInstalled(),
774801
'hasPassedCodeIntegrityCheck' => $this->checker->hasPassedCheck(),
775802
'codeIntegrityCheckerDocumentation' => $this->urlGenerator->linkToDocs('admin-code-integrity'),
776-
'isOpcacheProperlySetup' => $this->isOpcacheProperlySetup(),
777-
'hasOpcacheLoaded' => $this->hasOpcacheLoaded(),
778-
'phpOpcacheDocumentation' => $this->urlGenerator->linkToDocs('admin-php-opcache'),
803+
'OpcacheSetupRecommendations' => $this->getOpcacheSetupRecommendations(),
779804
'isSettimelimitAvailable' => $this->isSettimelimitAvailable(),
780805
'hasFreeTypeSupport' => $this->hasFreeTypeSupport(),
781806
'missingPrimaryKeys' => $this->hasMissingPrimaryKeys(),

core/js/setupchecks.js

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -322,18 +322,16 @@
322322
type: OC.SetupChecks.MESSAGE_TYPE_ERROR
323323
});
324324
}
325-
if(!data.hasOpcacheLoaded) {
326-
messages.push({
327-
msg: t('core', 'The PHP OPcache module is not loaded. {linkstart}For better performance it is recommended ↗{linkend} to load it into your PHP installation.')
328-
.replace('{linkstart}', '<a target="_blank" rel="noreferrer noopener" class="external" href="' + data.phpOpcacheDocumentation + '">')
329-
.replace('{linkend}', '</a>'),
330-
type: OC.SetupChecks.MESSAGE_TYPE_INFO
325+
if(data.OpcacheSetupRecommendations.length > 0) {
326+
var listOfOPcacheRecommendations = "";
327+
data.OpcacheSetupRecommendations.forEach(function(element){
328+
listOfOPcacheRecommendations += "<li>" + element + "</li>";
331329
});
332-
} else if(!data.isOpcacheProperlySetup) {
333330
messages.push({
334-
msg: t('core', 'The PHP OPcache module is not properly configured. {linkstart}For better performance it is recommended ↗{linkend} to use the following settings in the <code>php.ini</code>:')
335-
.replace('{linkstart}', '<a target="_blank" rel="noreferrer noopener" class="external" href="' + data.phpOpcacheDocumentation + '">')
336-
.replace('{linkend}', '</a>') + "<pre><code>opcache.enable=1\nopcache.interned_strings_buffer=8\nopcache.max_accelerated_files=10000\nopcache.memory_consumption=128\nopcache.save_comments=1\nopcache.revalidate_freq=1</code></pre>",
331+
msg: t(
332+
'core',
333+
'The PHP OPcache module is not properly configured:'
334+
) + "<ul>" + listOfOPcacheRecommendations + "</ul>",
337335
type: OC.SetupChecks.MESSAGE_TYPE_INFO
338336
});
339337
}

core/js/tests/specs/setupchecksSpec.js

Lines changed: 19 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -235,8 +235,7 @@ describe('OC.SetupChecks tests', function() {
235235
forwardedForHeadersWorking: true,
236236
isCorrectMemcachedPHPModuleInstalled: true,
237237
hasPassedCodeIntegrityCheck: true,
238-
isOpcacheProperlySetup: true,
239-
hasOpcacheLoaded: true,
238+
OpcacheSetupRecommendations: [],
240239
isSettimelimitAvailable: true,
241240
hasFreeTypeSupport: true,
242241
missingIndexes: [],
@@ -292,8 +291,7 @@ describe('OC.SetupChecks tests', function() {
292291
forwardedForHeadersWorking: true,
293292
isCorrectMemcachedPHPModuleInstalled: true,
294293
hasPassedCodeIntegrityCheck: true,
295-
isOpcacheProperlySetup: true,
296-
hasOpcacheLoaded: true,
294+
OpcacheSetupRecommendations: [],
297295
isSettimelimitAvailable: true,
298296
hasFreeTypeSupport: true,
299297
missingIndexes: [],
@@ -350,8 +348,7 @@ describe('OC.SetupChecks tests', function() {
350348
forwardedForHeadersWorking: true,
351349
isCorrectMemcachedPHPModuleInstalled: true,
352350
hasPassedCodeIntegrityCheck: true,
353-
isOpcacheProperlySetup: true,
354-
hasOpcacheLoaded: true,
351+
OpcacheSetupRecommendations: [],
355352
isSettimelimitAvailable: true,
356353
hasFreeTypeSupport: true,
357354
missingIndexes: [],
@@ -406,8 +403,7 @@ describe('OC.SetupChecks tests', function() {
406403
forwardedForHeadersWorking: true,
407404
isCorrectMemcachedPHPModuleInstalled: true,
408405
hasPassedCodeIntegrityCheck: true,
409-
isOpcacheProperlySetup: true,
410-
hasOpcacheLoaded: true,
406+
OpcacheSetupRecommendations: [],
411407
isSettimelimitAvailable: true,
412408
hasFreeTypeSupport: true,
413409
missingIndexes: [],
@@ -460,8 +456,7 @@ describe('OC.SetupChecks tests', function() {
460456
forwardedForHeadersWorking: true,
461457
isCorrectMemcachedPHPModuleInstalled: false,
462458
hasPassedCodeIntegrityCheck: true,
463-
isOpcacheProperlySetup: true,
464-
hasOpcacheLoaded: true,
459+
OpcacheSetupRecommendations: [],
465460
isSettimelimitAvailable: true,
466461
hasFreeTypeSupport: true,
467462
missingIndexes: [],
@@ -514,8 +509,7 @@ describe('OC.SetupChecks tests', function() {
514509
forwardedForHeadersWorking: true,
515510
isCorrectMemcachedPHPModuleInstalled: true,
516511
hasPassedCodeIntegrityCheck: true,
517-
isOpcacheProperlySetup: true,
518-
hasOpcacheLoaded: true,
512+
OpcacheSetupRecommendations: [],
519513
isSettimelimitAvailable: true,
520514
hasFreeTypeSupport: true,
521515
missingIndexes: [],
@@ -570,8 +564,7 @@ describe('OC.SetupChecks tests', function() {
570564
reverseProxyDocs: 'https://docs.nextcloud.com/foo/bar.html',
571565
isCorrectMemcachedPHPModuleInstalled: true,
572566
hasPassedCodeIntegrityCheck: true,
573-
isOpcacheProperlySetup: true,
574-
hasOpcacheLoaded: true,
567+
OpcacheSetupRecommendations: [],
575568
isSettimelimitAvailable: true,
576569
hasFreeTypeSupport: true,
577570
missingIndexes: [],
@@ -624,8 +617,7 @@ describe('OC.SetupChecks tests', function() {
624617
reverseProxyDocs: 'https://docs.nextcloud.com/foo/bar.html',
625618
isCorrectMemcachedPHPModuleInstalled: true,
626619
hasPassedCodeIntegrityCheck: true,
627-
isOpcacheProperlySetup: true,
628-
hasOpcacheLoaded: true,
620+
OpcacheSetupRecommendations: [],
629621
isSettimelimitAvailable: false,
630622
hasFreeTypeSupport: true,
631623
missingIndexes: [],
@@ -678,8 +670,7 @@ describe('OC.SetupChecks tests', function() {
678670
reverseProxyDocs: 'https://docs.nextcloud.com/foo/bar.html',
679671
isCorrectMemcachedPHPModuleInstalled: true,
680672
hasPassedCodeIntegrityCheck: true,
681-
isOpcacheProperlySetup: true,
682-
hasOpcacheLoaded: true,
673+
OpcacheSetupRecommendations: [],
683674
isSettimelimitAvailable: true,
684675
hasFreeTypeSupport: true,
685676
missingIndexes: [],
@@ -753,8 +744,7 @@ describe('OC.SetupChecks tests', function() {
753744
phpSupported: {eol: true, version: '5.4.0'},
754745
isCorrectMemcachedPHPModuleInstalled: true,
755746
hasPassedCodeIntegrityCheck: true,
756-
isOpcacheProperlySetup: true,
757-
hasOpcacheLoaded: true,
747+
OpcacheSetupRecommendations: [],
758748
isSettimelimitAvailable: true,
759749
hasFreeTypeSupport: true,
760750
missingIndexes: [],
@@ -807,8 +797,7 @@ describe('OC.SetupChecks tests', function() {
807797
forwardedForHeadersWorking: true,
808798
isCorrectMemcachedPHPModuleInstalled: true,
809799
hasPassedCodeIntegrityCheck: true,
810-
isOpcacheProperlySetup: false,
811-
hasOpcacheLoaded: true,
800+
OpcacheSetupRecommendations: ['recommendation1', 'recommendation2'],
812801
phpOpcacheDocumentation: 'https://example.org/link/to/doc',
813802
isSettimelimitAvailable: true,
814803
hasFreeTypeSupport: true,
@@ -833,62 +822,7 @@ describe('OC.SetupChecks tests', function() {
833822

834823
async.done(function( data, s, x ){
835824
expect(data).toEqual([{
836-
msg: 'The PHP OPcache module is not properly configured. <a target="_blank" rel="noreferrer noopener" class="external" href="https://example.org/link/to/doc">For better performance it is recommended ↗</a> to use the following settings in the <code>php.ini</code>:' + "<pre><code>opcache.enable=1\nopcache.interned_strings_buffer=8\nopcache.max_accelerated_files=10000\nopcache.memory_consumption=128\nopcache.save_comments=1\nopcache.revalidate_freq=1</code></pre>",
837-
type: OC.SetupChecks.MESSAGE_TYPE_INFO
838-
}]);
839-
done();
840-
});
841-
});
842-
843-
it('should return an info if server has no opcache at all', function(done) {
844-
var async = OC.SetupChecks.checkSetup();
845-
846-
suite.server.requests[0].respond(
847-
200,
848-
{
849-
'Content-Type': 'application/json'
850-
},
851-
JSON.stringify({
852-
hasFileinfoInstalled: true,
853-
isGetenvServerWorking: true,
854-
isReadOnlyConfig: false,
855-
hasWorkingFileLocking: true,
856-
hasValidTransactionIsolationLevel: true,
857-
suggestedOverwriteCliURL: '',
858-
isRandomnessSecure: true,
859-
securityDocs: 'https://docs.nextcloud.com/myDocs.html',
860-
serverHasInternetConnectionProblems: false,
861-
isMemcacheConfigured: true,
862-
forwardedForHeadersWorking: true,
863-
isCorrectMemcachedPHPModuleInstalled: true,
864-
hasPassedCodeIntegrityCheck: true,
865-
isOpcacheProperlySetup: true,
866-
hasOpcacheLoaded: false,
867-
phpOpcacheDocumentation: 'https://example.org/link/to/doc',
868-
isSettimelimitAvailable: true,
869-
hasFreeTypeSupport: true,
870-
missingIndexes: [],
871-
missingPrimaryKeys: [],
872-
missingColumns: [],
873-
cronErrors: [],
874-
cronInfo: {
875-
diffInSeconds: 0
876-
},
877-
isMemoryLimitSufficient: true,
878-
appDirsWithDifferentOwner: [],
879-
recommendedPHPModules: [],
880-
pendingBigIntConversionColumns: [],
881-
isMysqlUsedWithoutUTF8MB4: false,
882-
isDefaultPhoneRegionSet: true,
883-
isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed: true,
884-
reverseProxyGeneratedURL: 'https://server',
885-
temporaryDirectoryWritable: true,
886-
})
887-
);
888-
889-
async.done(function( data, s, x ){
890-
expect(data).toEqual([{
891-
msg: 'The PHP OPcache module is not loaded. <a target="_blank" rel="noreferrer noopener" class="external" href="https://example.org/link/to/doc">For better performance it is recommended ↗</a> to load it into your PHP installation.',
825+
msg: 'The PHP OPcache module is not properly configured:<ul><li>recommendation1</li><li>recommendation2</li></ul>',
892826
type: OC.SetupChecks.MESSAGE_TYPE_INFO
893827
}]);
894828
done();
@@ -917,8 +851,7 @@ describe('OC.SetupChecks tests', function() {
917851
forwardedForHeadersWorking: true,
918852
isCorrectMemcachedPHPModuleInstalled: true,
919853
hasPassedCodeIntegrityCheck: true,
920-
isOpcacheProperlySetup: true,
921-
hasOpcacheLoaded: true,
854+
OpcacheSetupRecommendations: [],
922855
phpOpcacheDocumentation: 'https://example.org/link/to/doc',
923856
isSettimelimitAvailable: true,
924857
hasFreeTypeSupport: false,
@@ -972,8 +905,7 @@ describe('OC.SetupChecks tests', function() {
972905
forwardedForHeadersWorking: true,
973906
isCorrectMemcachedPHPModuleInstalled: true,
974907
hasPassedCodeIntegrityCheck: true,
975-
isOpcacheProperlySetup: true,
976-
hasOpcacheLoaded: true,
908+
OpcacheSetupRecommendations: [],
977909
isSettimelimitAvailable: true,
978910
hasFreeTypeSupport: true,
979911
missingIndexes: [],
@@ -1030,8 +962,7 @@ describe('OC.SetupChecks tests', function() {
1030962
forwardedForHeadersWorking: true,
1031963
isCorrectMemcachedPHPModuleInstalled: true,
1032964
hasPassedCodeIntegrityCheck: true,
1033-
isOpcacheProperlySetup: true,
1034-
hasOpcacheLoaded: true,
965+
OpcacheSetupRecommendations: [],
1035966
isSettimelimitAvailable: true,
1036967
hasFreeTypeSupport: true,
1037968
missingIndexes: [],
@@ -1085,8 +1016,7 @@ describe('OC.SetupChecks tests', function() {
10851016
forwardedForHeadersWorking: true,
10861017
isCorrectMemcachedPHPModuleInstalled: true,
10871018
hasPassedCodeIntegrityCheck: true,
1088-
isOpcacheProperlySetup: true,
1089-
hasOpcacheLoaded: true,
1019+
OpcacheSetupRecommendations: [],
10901020
isSettimelimitAvailable: true,
10911021
hasFreeTypeSupport: true,
10921022
missingIndexes: [],
@@ -1137,8 +1067,7 @@ describe('OC.SetupChecks tests', function() {
11371067
forwardedForHeadersWorking: true,
11381068
isCorrectMemcachedPHPModuleInstalled: true,
11391069
hasPassedCodeIntegrityCheck: true,
1140-
isOpcacheProperlySetup: true,
1141-
hasOpcacheLoaded: true,
1070+
OpcacheSetupRecommendations: [],
11421071
isSettimelimitAvailable: true,
11431072
hasFreeTypeSupport: true,
11441073
missingIndexes: [],
@@ -1191,8 +1120,7 @@ describe('OC.SetupChecks tests', function() {
11911120
forwardedForHeadersWorking: true,
11921121
isCorrectMemcachedPHPModuleInstalled: true,
11931122
hasPassedCodeIntegrityCheck: true,
1194-
isOpcacheProperlySetup: true,
1195-
hasOpcacheLoaded: true,
1123+
OpcacheSetupRecommendations: [],
11961124
isSettimelimitAvailable: true,
11971125
hasFreeTypeSupport: true,
11981126
missingIndexes: [],
@@ -1245,8 +1173,7 @@ describe('OC.SetupChecks tests', function() {
12451173
forwardedForHeadersWorking: true,
12461174
isCorrectMemcachedPHPModuleInstalled: true,
12471175
hasPassedCodeIntegrityCheck: true,
1248-
isOpcacheProperlySetup: true,
1249-
hasOpcacheLoaded: true,
1176+
OpcacheSetupRecommendations: [],
12501177
isSettimelimitAvailable: true,
12511178
hasFreeTypeSupport: true,
12521179
missingIndexes: [],

0 commit comments

Comments
 (0)