diff options
Diffstat (limited to 'lib/private/Memcache')
-rw-r--r-- | lib/private/Memcache/APCu.php | 5 | ||||
-rw-r--r-- | lib/private/Memcache/ArrayCache.php | 2 | ||||
-rw-r--r-- | lib/private/Memcache/Factory.php | 88 | ||||
-rw-r--r-- | lib/private/Memcache/LoggerWrapperCache.php | 177 | ||||
-rw-r--r-- | lib/private/Memcache/Memcached.php | 2 | ||||
-rw-r--r-- | lib/private/Memcache/NullCache.php | 2 | ||||
-rw-r--r-- | lib/private/Memcache/ProfilerWrapperCache.php | 220 | ||||
-rw-r--r-- | lib/private/Memcache/Redis.php | 124 |
8 files changed, 479 insertions, 141 deletions
diff --git a/lib/private/Memcache/APCu.php b/lib/private/Memcache/APCu.php index 56345890bf2..f0eb98b9db2 100644 --- a/lib/private/Memcache/APCu.php +++ b/lib/private/Memcache/APCu.php @@ -148,10 +148,7 @@ class APCu extends Cache implements IMemcache { } } - /** - * @return bool - */ - public static function isAvailable() { + public static function isAvailable(): bool { if (!extension_loaded('apcu')) { return false; } elseif (!\OC::$server->get(IniGetWrapper::class)->getBool('apc.enabled')) { diff --git a/lib/private/Memcache/ArrayCache.php b/lib/private/Memcache/ArrayCache.php index b89aff0b7ed..13597a068b3 100644 --- a/lib/private/Memcache/ArrayCache.php +++ b/lib/private/Memcache/ArrayCache.php @@ -153,7 +153,7 @@ class ArrayCache extends Cache implements IMemcache { /** * {@inheritDoc} */ - public static function isAvailable() { + public static function isAvailable(): bool { return true; } } diff --git a/lib/private/Memcache/Factory.php b/lib/private/Memcache/Factory.php index 73206aac011..604f764c03c 100644 --- a/lib/private/Memcache/Factory.php +++ b/lib/private/Memcache/Factory.php @@ -31,6 +31,7 @@ */ namespace OC\Memcache; +use OCP\Profiler\IProfiler; use OCP\ICache; use OCP\ICacheFactory; use OCP\IMemcache; @@ -39,39 +40,39 @@ use Psr\Log\LoggerInterface; class Factory implements ICacheFactory { public const NULL_CACHE = NullCache::class; - /** - * @var string $globalPrefix - */ - private $globalPrefix; + private string $globalPrefix; private LoggerInterface $logger; /** - * @var string $localCacheClass + * @var ?class-string<ICache> $localCacheClass + */ + private ?string $localCacheClass; + + /** + * @var ?class-string<ICache> $distributedCacheClass */ - private $localCacheClass; + private ?string $distributedCacheClass; /** - * @var string $distributedCacheClass + * @var ?class-string<IMemcache> $lockingCacheClass */ - private $distributedCacheClass; + private ?string $lockingCacheClass; + + private string $logFile; + + private IProfiler $profiler; /** - * @var string $lockingCacheClass + * @param string $globalPrefix + * @param LoggerInterface $logger + * @param ?class-string<ICache> $localCacheClass + * @param ?class-string<ICache> $distributedCacheClass + * @param ?class-string<IMemcache> $lockingCacheClass + * @param string $logFile */ - private $lockingCacheClass; - - /** @var string */ - private $logFile; - - public function __construct( - string $globalPrefix, - LoggerInterface $logger, - ?string $localCacheClass = null, - ?string $distributedCacheClass = null, - ?string $lockingCacheClass = null, - string $logFile = '' - ) { + public function __construct(string $globalPrefix, LoggerInterface $logger, IProfiler $profiler, + ?string $localCacheClass = null, ?string $distributedCacheClass = null, ?string $lockingCacheClass = null, string $logFile = '') { $this->logger = $logger; $this->logFile = $logFile; $this->globalPrefix = $globalPrefix; @@ -103,6 +104,7 @@ class Factory implements ICacheFactory { $this->localCacheClass = $localCacheClass; $this->distributedCacheClass = $distributedCacheClass; $this->lockingCacheClass = $lockingCacheClass; + $this->profiler = $profiler; } /** @@ -112,7 +114,19 @@ class Factory implements ICacheFactory { * @return IMemcache */ public function createLocking(string $prefix = ''): IMemcache { - return new $this->lockingCacheClass($this->globalPrefix . '/' . $prefix, $this->logFile); + assert($this->lockingCacheClass !== null); + $cache = new $this->lockingCacheClass($this->globalPrefix . '/' . $prefix); + if ($this->profiler->isEnabled() && $this->lockingCacheClass === '\OC\Memcache\Redis') { + // We only support the profiler with Redis + $cache = new ProfilerWrapperCache($cache, 'Locking'); + $this->profiler->add($cache); + } + + if ($this->lockingCacheClass === Redis::class && + $this->logFile !== '' && is_writable(dirname($this->logFile)) && (!file_exists($this->logFile) || is_writable($this->logFile))) { + $cache = new LoggerWrapperCache($cache, $this->logFile); + } + return $cache; } /** @@ -122,7 +136,19 @@ class Factory implements ICacheFactory { * @return ICache */ public function createDistributed(string $prefix = ''): ICache { - return new $this->distributedCacheClass($this->globalPrefix . '/' . $prefix, $this->logFile); + assert($this->distributedCacheClass !== null); + $cache = new $this->distributedCacheClass($this->globalPrefix . '/' . $prefix); + if ($this->profiler->isEnabled() && $this->distributedCacheClass === '\OC\Memcache\Redis') { + // We only support the profiler with Redis + $cache = new ProfilerWrapperCache($cache, 'Distributed'); + $this->profiler->add($cache); + } + + if ($this->distributedCacheClass === Redis::class && $this->logFile !== '' + && is_writable(dirname($this->logFile)) && (!file_exists($this->logFile) || is_writable($this->logFile))) { + $cache = new LoggerWrapperCache($cache, $this->logFile); + } + return $cache; } /** @@ -132,7 +158,19 @@ class Factory implements ICacheFactory { * @return ICache */ public function createLocal(string $prefix = ''): ICache { - return new $this->localCacheClass($this->globalPrefix . '/' . $prefix, $this->logFile); + assert($this->localCacheClass !== null); + $cache = new $this->localCacheClass($this->globalPrefix . '/' . $prefix); + if ($this->profiler->isEnabled() && $this->localCacheClass === '\OC\Memcache\Redis') { + // We only support the profiler with Redis + $cache = new ProfilerWrapperCache($cache, 'Local'); + $this->profiler->add($cache); + } + + if ($this->localCacheClass === Redis::class && $this->logFile !== '' + && is_writable(dirname($this->logFile)) && (!file_exists($this->logFile) || is_writable($this->logFile))) { + $cache = new LoggerWrapperCache($cache, $this->logFile); + } + return $cache; } /** diff --git a/lib/private/Memcache/LoggerWrapperCache.php b/lib/private/Memcache/LoggerWrapperCache.php new file mode 100644 index 00000000000..55c0e76db79 --- /dev/null +++ b/lib/private/Memcache/LoggerWrapperCache.php @@ -0,0 +1,177 @@ +<?php + +declare(strict_types = 1); +/** + * @copyright 2022 Carl Schwan <carl@carlschwan.eu> + * + * @author Carl Schwan <carl@carlschwan.eu> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Memcache; + +use OCP\IMemcacheTTL; + +/** + * Cache wrapper that logs the cache operation in a log file + */ +class LoggerWrapperCache extends Cache implements IMemcacheTTL { + /** @var Redis */ + protected $wrappedCache; + + /** @var string $logFile */ + private $logFile; + + /** @var string $prefix */ + protected $prefix; + + public function __construct(Redis $wrappedCache, string $logFile) { + parent::__construct($wrappedCache->getPrefix()); + $this->wrappedCache = $wrappedCache; + $this->logFile = $logFile; + } + + /** + * @return string Prefix used for caching purposes + */ + public function getPrefix() { + return $this->prefix; + } + + protected function getNameSpace() { + return $this->prefix; + } + + /** @inheritDoc */ + public function get($key) { + file_put_contents( + $this->logFile, + $this->getNameSpace() . '::get::' . $key . "\n", + FILE_APPEND + ); + return $this->wrappedCache->get($key); + } + + /** @inheritDoc */ + public function set($key, $value, $ttl = 0) { + file_put_contents( + $this->logFile, + $this->getNameSpace() . '::set::' . $key . '::' . $ttl . '::' . json_encode($value) . "\n", + FILE_APPEND + ); + + return $this->wrappedCache->set($key, $value, $$ttl); + } + + /** @inheritDoc */ + public function hasKey($key) { + file_put_contents( + $this->logFile, + $this->getNameSpace() . '::hasKey::' . $key . "\n", + FILE_APPEND + ); + + return $this->wrappedCache->hasKey($key); + } + + /** @inheritDoc */ + public function remove($key) { + file_put_contents( + $this->logFile, + $this->getNameSpace() . '::remove::' . $key . "\n", + FILE_APPEND + ); + + return $this->wrappedCache->remove($key); + } + + /** @inheritDoc */ + public function clear($prefix = '') { + file_put_contents( + $this->logFile, + $this->getNameSpace() . '::clear::' . $prefix . "\n", + FILE_APPEND + ); + + return $this->wrappedCache->clear($prefix); + } + + /** @inheritDoc */ + public function add($key, $value, $ttl = 0) { + file_put_contents( + $this->logFile, + $this->getNameSpace() . '::add::' . $key . '::' . $value . "\n", + FILE_APPEND + ); + + return $this->wrappedCache->add($key, $value, $ttl); + } + + /** @inheritDoc */ + public function inc($key, $step = 1) { + file_put_contents( + $this->logFile, + $this->getNameSpace() . '::inc::' . $key . "\n", + FILE_APPEND + ); + + return $this->wrappedCache->inc($key, $step); + } + + /** @inheritDoc */ + public function dec($key, $step = 1) { + file_put_contents( + $this->logFile, + $this->getNameSpace() . '::dec::' . $key . "\n", + FILE_APPEND + ); + + return $this->wrappedCache->dec($key, $step); + } + + /** @inheritDoc */ + public function cas($key, $old, $new) { + file_put_contents( + $this->logFile, + $this->getNameSpace() . '::cas::' . $key . "\n", + FILE_APPEND + ); + + return $this->wrappedCache->cas($key, $old, $new); + } + + /** @inheritDoc */ + public function cad($key, $old) { + file_put_contents( + $this->logFile, + $this->getNameSpace() . '::cad::' . $key . "\n", + FILE_APPEND + ); + + return $this->wrappedCache->cad($key, $old); + } + + /** @inheritDoc */ + public function setTTL($key, $ttl) { + $this->wrappedCache->setTTL($key, $ttl); + } + + public static function isAvailable(): bool { + return true; + } +} diff --git a/lib/private/Memcache/Memcached.php b/lib/private/Memcache/Memcached.php index f78be581d63..db4aa7ba9cc 100644 --- a/lib/private/Memcache/Memcached.php +++ b/lib/private/Memcache/Memcached.php @@ -196,7 +196,7 @@ class Memcached extends Cache implements IMemcache { return $result; } - public static function isAvailable() { + public static function isAvailable(): bool { return extension_loaded('memcached'); } diff --git a/lib/private/Memcache/NullCache.php b/lib/private/Memcache/NullCache.php index 7b56ec932f4..fc41595dfe1 100644 --- a/lib/private/Memcache/NullCache.php +++ b/lib/private/Memcache/NullCache.php @@ -67,7 +67,7 @@ class NullCache extends Cache implements \OCP\IMemcache { return true; } - public static function isAvailable() { + public static function isAvailable(): bool { return true; } } diff --git a/lib/private/Memcache/ProfilerWrapperCache.php b/lib/private/Memcache/ProfilerWrapperCache.php new file mode 100644 index 00000000000..8e9b160ba0e --- /dev/null +++ b/lib/private/Memcache/ProfilerWrapperCache.php @@ -0,0 +1,220 @@ +<?php + +declare(strict_types = 1); +/** + * @copyright 2022 Carl Schwan <carl@carlschwan.eu> + * + * @author Carl Schwan <carl@carlschwan.eu> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Memcache; + +use OC\AppFramework\Http\Request; +use OCP\AppFramework\Http\Response; +use OCP\DataCollector\AbstractDataCollector; +use OCP\IMemcacheTTL; + +/** + * Cache wrapper that logs profiling information + */ +class ProfilerWrapperCache extends AbstractDataCollector implements IMemcacheTTL, \ArrayAccess { + /** @var Redis $wrappedCache*/ + protected $wrappedCache; + + /** @var string $prefix */ + protected $prefix; + + /** @var string $type */ + private $type; + + public function __construct(Redis $wrappedCache, string $type) { + $this->prefix = $wrappedCache->getPrefix(); + $this->wrappedCache = $wrappedCache; + $this->type = $type; + $this->data['queries'] = []; + $this->data['cacheHit'] = 0; + $this->data['cacheMiss'] = 0; + } + + public function getPrefix(): string { + return $this->prefix; + } + + /** @inheritDoc */ + public function get($key) { + $start = microtime(true); + $ret = $this->wrappedCache->get($key); + if ($ret === null) { + $this->data['cacheMiss']++; + } else { + $this->data['cacheHit']++; + } + $this->data['queries'][] = [ + 'start' => $start, + 'end' => microtime(true), + 'op' => $this->getPrefix() . '::get::' . $key, + ]; + return $ret; + } + + /** @inheritDoc */ + public function set($key, $value, $ttl = 0) { + $start = microtime(true); + $ret = $this->wrappedCache->set($key, $value, $ttl); + $this->data['queries'][] = [ + 'start' => $start, + 'end' => microtime(true), + 'op' => $this->getPrefix() . '::set::' . $key, + ]; + return $ret; + } + + /** @inheritDoc */ + public function hasKey($key) { + $start = microtime(true); + $ret = $this->wrappedCache->hasKey($key); + $this->data['queries'][] = [ + 'start' => $start, + 'end' => microtime(true), + 'op' => $this->getPrefix() . '::hasKey::' . $key, + ]; + return $ret; + } + + /** @inheritDoc */ + public function remove($key) { + $start = microtime(true); + $ret = $this->wrappedCache->remove($key); + $this->data['queries'][] = [ + 'start' => $start, + 'end' => microtime(true), + 'op' => $this->getPrefix() . '::remove::' . $key, + ]; + return $ret; + } + + /** @inheritDoc */ + public function clear($prefix = '') { + $start = microtime(true); + $ret = $this->wrappedCache->clear($prefix); + $this->data['queries'][] = [ + 'start' => $start, + 'end' => microtime(true), + 'op' => $this->getPrefix() . '::clear::' . $prefix, + ]; + return $ret; + } + + /** @inheritDoc */ + public function add($key, $value, $ttl = 0) { + $start = microtime(true); + $ret = $this->wrappedCache->add($key, $value, $ttl); + $this->data['queries'][] = [ + 'start' => $start, + 'end' => microtime(true), + 'op' => $this->getPrefix() . '::add::' . $key, + ]; + return $ret; + } + + /** @inheritDoc */ + public function inc($key, $step = 1) { + $start = microtime(true); + $ret = $this->wrappedCache->inc($key, $step); + $this->data['queries'][] = [ + 'start' => $start, + 'end' => microtime(true), + 'op' => $this->getPrefix() . '::inc::' . $key, + ]; + return $ret; + } + + /** @inheritDoc */ + public function dec($key, $step = 1) { + $start = microtime(true); + $ret = $this->wrappedCache->dec($key, $step); + $this->data['queries'][] = [ + 'start' => $start, + 'end' => microtime(true), + 'op' => $this->getPrefix() . '::dev::' . $key, + ]; + return $ret; + } + + /** @inheritDoc */ + public function cas($key, $old, $new) { + $start = microtime(true); + $ret = $this->wrappedCache->cas($key, $old, $new); + $this->data['queries'][] = [ + 'start' => $start, + 'end' => microtime(true), + 'op' => $this->getPrefix() . '::cas::' . $key, + ]; + return $ret; + } + + /** @inheritDoc */ + public function cad($key, $old) { + $start = microtime(true); + $ret = $this->wrappedCache->cad($key, $old); + $this->data['queries'][] = [ + 'start' => $start, + 'end' => microtime(true), + 'op' => $this->getPrefix() . '::cad::' . $key, + ]; + return $ret; + } + + /** @inheritDoc */ + public function setTTL($key, $ttl) { + $this->wrappedCache->setTTL($key, $ttl); + } + + public function offsetExists($offset): bool { + return $this->hasKey($offset); + } + + public function offsetSet($offset, $value): void { + $this->set($offset, $value); + } + + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) { + return $this->get($offset); + } + + public function offsetUnset($offset): void { + $this->remove($offset); + } + + public function collect(Request $request, Response $response, \Throwable $exception = null): void { + // Nothing to do here $data is already ready + } + + public function getName(): string { + return 'cache/' . $this->type . '/' . $this->prefix; + } + + public static function isAvailable(): bool { + return true; + } +} diff --git a/lib/private/Memcache/Redis.php b/lib/private/Memcache/Redis.php index 63180dd8066..9b07da2d99c 100644 --- a/lib/private/Memcache/Redis.php +++ b/lib/private/Memcache/Redis.php @@ -37,38 +37,16 @@ class Redis extends Cache implements IMemcacheTTL { */ private static $cache = null; - private $logFile; - public function __construct($prefix = '', string $logFile = '') { parent::__construct($prefix); - $this->logFile = $logFile; if (is_null(self::$cache)) { self::$cache = \OC::$server->getGetRedisFactory()->getInstance(); } } - /** - * entries in redis get namespaced to prevent collisions between ownCloud instances and users - */ - protected function getNameSpace() { - return $this->prefix; - } - - private function logEnabled(): bool { - return $this->logFile !== '' && is_writable(dirname($this->logFile)) && (!file_exists($this->logFile) || is_writable($this->logFile)); - } - public function get($key) { - if ($this->logEnabled()) { - file_put_contents( - $this->logFile, - $this->getNameSpace() . '::get::' . $key . "\n", - FILE_APPEND - ); - } - - $result = self::$cache->get($this->getNameSpace() . $key); - if ($result === false && !self::$cache->exists($this->getNameSpace() . $key)) { + $result = self::$cache->get($this->getPrefix() . $key); + if ($result === false && !self::$cache->exists($this->getPrefix() . $key)) { return null; } else { return json_decode($result, true); @@ -76,43 +54,19 @@ class Redis extends Cache implements IMemcacheTTL { } public function set($key, $value, $ttl = 0) { - if ($this->logEnabled()) { - file_put_contents( - $this->logFile, - $this->getNameSpace() . '::set::' . $key . '::' . $ttl . '::' . json_encode($value) . "\n", - FILE_APPEND - ); - } - if ($ttl > 0) { - return self::$cache->setex($this->getNameSpace() . $key, $ttl, json_encode($value)); + return self::$cache->setex($this->getPrefix() . $key, $ttl, json_encode($value)); } else { - return self::$cache->set($this->getNameSpace() . $key, json_encode($value)); + return self::$cache->set($this->getPrefix() . $key, json_encode($value)); } } public function hasKey($key) { - if ($this->logEnabled()) { - file_put_contents( - $this->logFile, - $this->getNameSpace() . '::hasKey::' . $key . "\n", - FILE_APPEND - ); - } - - return (bool)self::$cache->exists($this->getNameSpace() . $key); + return (bool)self::$cache->exists($this->getPrefix() . $key); } public function remove($key) { - if ($this->logEnabled()) { - file_put_contents( - $this->logFile, - $this->getNameSpace() . '::remove::' . $key . "\n", - FILE_APPEND - ); - } - - if (self::$cache->del($this->getNameSpace() . $key)) { + if (self::$cache->del($this->getPrefix() . $key)) { return true; } else { return false; @@ -120,15 +74,7 @@ class Redis extends Cache implements IMemcacheTTL { } public function clear($prefix = '') { - if ($this->logEnabled()) { - file_put_contents( - $this->logFile, - $this->getNameSpace() . '::clear::' . $prefix . "\n", - FILE_APPEND - ); - } - - $prefix = $this->getNameSpace() . $prefix . '*'; + $prefix = $this->getPrefix() . $prefix . '*'; $keys = self::$cache->keys($prefix); $deleted = self::$cache->del($keys); @@ -153,14 +99,6 @@ class Redis extends Cache implements IMemcacheTTL { if ($ttl !== 0 && is_int($ttl)) { $args['ex'] = $ttl; } - if ($this->logEnabled()) { - file_put_contents( - $this->logFile, - $this->getNameSpace() . '::add::' . $key . '::' . $value . "\n", - FILE_APPEND - ); - } - return self::$cache->set($this->getPrefix() . $key, $value, $args); } @@ -173,15 +111,7 @@ class Redis extends Cache implements IMemcacheTTL { * @return int | bool */ public function inc($key, $step = 1) { - if ($this->logEnabled()) { - file_put_contents( - $this->logFile, - $this->getNameSpace() . '::inc::' . $key . "\n", - FILE_APPEND - ); - } - - return self::$cache->incrBy($this->getNameSpace() . $key, $step); + return self::$cache->incrBy($this->getPrefix() . $key, $step); } /** @@ -192,18 +122,10 @@ class Redis extends Cache implements IMemcacheTTL { * @return int | bool */ public function dec($key, $step = 1) { - if ($this->logEnabled()) { - file_put_contents( - $this->logFile, - $this->getNameSpace() . '::dec::' . $key . "\n", - FILE_APPEND - ); - } - if (!$this->hasKey($key)) { return false; } - return self::$cache->decrBy($this->getNameSpace() . $key, $step); + return self::$cache->decrBy($this->getPrefix() . $key, $step); } /** @@ -215,21 +137,13 @@ class Redis extends Cache implements IMemcacheTTL { * @return bool */ public function cas($key, $old, $new) { - if ($this->logEnabled()) { - file_put_contents( - $this->logFile, - $this->getNameSpace() . '::cas::' . $key . "\n", - FILE_APPEND - ); - } - if (!is_int($new)) { $new = json_encode($new); } - self::$cache->watch($this->getNameSpace() . $key); + self::$cache->watch($this->getPrefix() . $key); if ($this->get($key) === $old) { $result = self::$cache->multi() - ->set($this->getNameSpace() . $key, $new) + ->set($this->getPrefix() . $key, $new) ->exec(); return $result !== false; } @@ -245,18 +159,10 @@ class Redis extends Cache implements IMemcacheTTL { * @return bool */ public function cad($key, $old) { - if ($this->logEnabled()) { - file_put_contents( - $this->logFile, - $this->getNameSpace() . '::cad::' . $key . "\n", - FILE_APPEND - ); - } - - self::$cache->watch($this->getNameSpace() . $key); + self::$cache->watch($this->getPrefix() . $key); if ($this->get($key) === $old) { $result = self::$cache->multi() - ->del($this->getNameSpace() . $key) + ->del($this->getPrefix() . $key) ->exec(); return $result !== false; } @@ -265,10 +171,10 @@ class Redis extends Cache implements IMemcacheTTL { } public function setTTL($key, $ttl) { - self::$cache->expire($this->getNameSpace() . $key, $ttl); + self::$cache->expire($this->getPrefix() . $key, $ttl); } - public static function isAvailable() { + public static function isAvailable(): bool { return \OC::$server->getGetRedisFactory()->isAvailable(); } } |