From 01296e98f948c4cdda36f4bc92b9ede3189987c1 Mon Sep 17 00:00:00 2001 From: Philipp Date: Mon, 21 Apr 2025 19:37:37 +0200 Subject: [PATCH 01/10] Add getStats() method for MemoryCaches --- .../Cache/Capability/ICanCacheInMemory.php | 7 ++++++ src/Core/Cache/Type/APCuCache.php | 15 ++++++++++++ src/Core/Cache/Type/ArrayCache.php | 6 +++++ src/Core/Cache/Type/MemcacheCache.php | 17 ++++++++++++++ src/Core/Cache/Type/MemcachedCache.php | 23 +++++++++++++++++++ .../Cache/Type/ProfilerCacheDecorator.php | 10 ++++++++ src/Core/Cache/Type/RedisCache.php | 17 ++++++++++++++ src/Core/Lock/Type/CacheLock.php | 10 ++++++++ tests/src/Core/Cache/APCuCacheTest.php | 14 +++++++++++ tests/src/Core/Cache/MemcacheCacheTest.php | 17 ++++++++++++++ tests/src/Core/Cache/MemcachedCacheTest.php | 17 ++++++++++++++ tests/src/Core/Cache/RedisCacheTest.php | 17 ++++++++++++++ 12 files changed, 170 insertions(+) diff --git a/src/Core/Cache/Capability/ICanCacheInMemory.php b/src/Core/Cache/Capability/ICanCacheInMemory.php index 82492d368a2..550273f5f06 100644 --- a/src/Core/Cache/Capability/ICanCacheInMemory.php +++ b/src/Core/Cache/Capability/ICanCacheInMemory.php @@ -53,4 +53,11 @@ interface ICanCacheInMemory extends ICanCache * @throws CachePersistenceException In case the underlying cache driver has errors during persistence */ public function compareDelete(string $key, $value): bool; + + /** + * Returns some basic statistics of the used Cache instance + * + * @return array Returns an associative array of statistics + */ + public function getStats(): array; } diff --git a/src/Core/Cache/Type/APCuCache.php b/src/Core/Cache/Type/APCuCache.php index f1dde2462c0..3c72e76c983 100644 --- a/src/Core/Cache/Type/APCuCache.php +++ b/src/Core/Cache/Type/APCuCache.php @@ -147,4 +147,19 @@ class APCuCache extends AbstractCache implements ICanCacheInMemory return true; } + + /** {@inheritDoc} */ + public function getStats(): array + { + $apcu = apcu_cache_info(); + $sma = apcu_sma_info(); + + return [ + 'entries' => $apcu['num_entries'] ?? null, + 'used_memory' => $apcu['mem_size'] ?? null, + 'hits' => $apcu['num_hits'] ?? null, + 'misses' => $apcu['num_misses'] ?? null, + 'avail_mem' => $sma['avail_mem'] ?? null, + ]; + } } diff --git a/src/Core/Cache/Type/ArrayCache.php b/src/Core/Cache/Type/ArrayCache.php index 7fd44deb0a9..c6a20d627b6 100644 --- a/src/Core/Cache/Type/ArrayCache.php +++ b/src/Core/Cache/Type/ArrayCache.php @@ -96,4 +96,10 @@ class ArrayCache extends AbstractCache implements ICanCacheInMemory return false; } } + + /** {@inheritDoc} */ + public function getStats(): array + { + return []; + } } diff --git a/src/Core/Cache/Type/MemcacheCache.php b/src/Core/Cache/Type/MemcacheCache.php index 14bd5e310b4..b2142ffd367 100644 --- a/src/Core/Cache/Type/MemcacheCache.php +++ b/src/Core/Cache/Type/MemcacheCache.php @@ -156,4 +156,21 @@ class MemcacheCache extends AbstractCache implements ICanCacheInMemory $cacheKey = $this->getCacheKey($key); return $this->memcache->add($cacheKey, serialize($value), MEMCACHE_COMPRESSED, $ttl); } + + /** {@inheritDoc} */ + public function getStats(): array + { + $stats = $this->memcache->getStats(); + + return [ + 'version' => $stats['version'] ?? null, + 'entries' => $stats['curr_items'] ?? null, + 'used_memory' => $stats['bytes'] ?? null, + 'uptime' => $stats['uptime'] ?? null, + 'connected_clients' => $stats['curr_connections'] ?? null, + 'hits' => $stats['get_hits'] ?? null, + 'misses' => $stats['get_misses'] ?? null, + 'evictions' => $stats['evictions'] ?? null, + ]; + } } diff --git a/src/Core/Cache/Type/MemcachedCache.php b/src/Core/Cache/Type/MemcachedCache.php index 2e970e6078a..53f959fd2b0 100644 --- a/src/Core/Cache/Type/MemcachedCache.php +++ b/src/Core/Cache/Type/MemcachedCache.php @@ -172,4 +172,27 @@ class MemcachedCache extends AbstractCache implements ICanCacheInMemory $cacheKey = $this->getCacheKey($key); return $this->memcached->add($cacheKey, $value, $ttl); } + + /** {@inheritDoc} */ + public function getStats(): array + { + $stats = $this->memcached->getStats(); + + // get statistics of the first instance + foreach ($stats as $value) { + $stats = $value; + break; + } + + return [ + 'version' => $stats['version'] ?? null, + 'entries' => $stats['curr_items'] ?? null, + 'used_memory' => $stats['bytes'] ?? null, + 'uptime' => $stats['uptime'] ?? null, + 'connected_clients' => $stats['curr_connections'] ?? null, + 'hits' => $stats['get_hits'] ?? null, + 'misses' => $stats['get_misses'] ?? null, + 'evictions' => $stats['evictions'] ?? null, + ]; + } } diff --git a/src/Core/Cache/Type/ProfilerCacheDecorator.php b/src/Core/Cache/Type/ProfilerCacheDecorator.php index 8071b79c5e3..113aa766883 100644 --- a/src/Core/Cache/Type/ProfilerCacheDecorator.php +++ b/src/Core/Cache/Type/ProfilerCacheDecorator.php @@ -166,4 +166,14 @@ class ProfilerCacheDecorator implements ICanCache, ICanCacheInMemory { return $this->cache->getName() . ' (with profiler)'; } + + /** {@inheritDoc} */ + public function getStats(): array + { + if ($this->cache instanceof ICanCacheInMemory) { + return $this->cache->getStats(); + } else { + return []; + } + } } diff --git a/src/Core/Cache/Type/RedisCache.php b/src/Core/Cache/Type/RedisCache.php index cf78d362bbd..d0056246413 100644 --- a/src/Core/Cache/Type/RedisCache.php +++ b/src/Core/Cache/Type/RedisCache.php @@ -207,4 +207,21 @@ class RedisCache extends AbstractCache implements ICanCacheInMemory $this->redis->unwatch(); return false; } + + /** {@inheritDoc} */ + public function getStats(): array + { + $info = $this->redis->info(); + + return [ + 'version' => $info['redis_version'] ?? null, + 'entries' => $this->redis->dbSize() ?? null, + 'used_memory' => $info['used_memory'] ?? null, + 'connected_clients' => $info['connected_clients'] ?? null, + 'uptime' => $info['uptime_in_seconds'] ?? null, + 'hits' => $info['keyspace_hits'] ?? null, + 'misses' => $info['keyspace_misses'] ?? null, + 'evictions' => $info['evicted_keys'] ?? null, + ]; + } } diff --git a/src/Core/Lock/Type/CacheLock.php b/src/Core/Lock/Type/CacheLock.php index c3794d06a7e..9fc9fad8f84 100644 --- a/src/Core/Lock/Type/CacheLock.php +++ b/src/Core/Lock/Type/CacheLock.php @@ -156,6 +156,16 @@ class CacheLock extends AbstractLock return $success; } + /** + * Returns stats about the cache provider + * + * @return array + */ + public function getCacheStats(): array + { + return $this->cache->getStats(); + } + /** * @param string $key The original key * diff --git a/tests/src/Core/Cache/APCuCacheTest.php b/tests/src/Core/Cache/APCuCacheTest.php index 117c211b041..47e660b26ad 100644 --- a/tests/src/Core/Cache/APCuCacheTest.php +++ b/tests/src/Core/Cache/APCuCacheTest.php @@ -35,4 +35,18 @@ class APCuCacheTest extends MemoryCacheTestCase $this->cache->clear(false); parent::tearDown(); } + + /** + * @small + */ + public function testStats() + { + $stats = $this->instance->getStats(); + + self::assertNotNull($stats['entries']); + self::assertNotNull($stats['used_memory']); + self::assertNotNull($stats['hits']); + self::assertNotNull($stats['misses']); + self::assertNotNull($stats['avail_mem']); + } } diff --git a/tests/src/Core/Cache/MemcacheCacheTest.php b/tests/src/Core/Cache/MemcacheCacheTest.php index abd073f4838..c622f22216c 100644 --- a/tests/src/Core/Cache/MemcacheCacheTest.php +++ b/tests/src/Core/Cache/MemcacheCacheTest.php @@ -59,4 +59,21 @@ class MemcacheCacheTest extends MemoryCacheTestCase { static::markTestIncomplete('Race condition because of too fast getAllKeys() which uses a workaround'); } + + /** + * @small + */ + public function testStats() + { + $stats = $this->instance->getStats(); + + self::assertNotNull($stats['version']); + self::assertIsNumeric($stats['hits']); + self::assertIsNumeric($stats['misses']); + self::assertIsNumeric($stats['evictions']); + self::assertIsNumeric($stats['entries']); + self::assertIsNumeric($stats['used_memory']); + self::assertGreaterThan(0, $stats['connected_clients']); + self::assertGreaterThan(0, $stats['uptime']); + } } diff --git a/tests/src/Core/Cache/MemcachedCacheTest.php b/tests/src/Core/Cache/MemcachedCacheTest.php index f3b6107b5be..a1c3653f1b3 100644 --- a/tests/src/Core/Cache/MemcachedCacheTest.php +++ b/tests/src/Core/Cache/MemcachedCacheTest.php @@ -58,4 +58,21 @@ class MemcachedCacheTest extends MemoryCacheTestCase { static::markTestIncomplete('Race condition because of too fast getAllKeys() which uses a workaround'); } + + /** + * @small + */ + public function testStats() + { + $stats = $this->instance->getStats(); + + self::assertNotNull($stats['version']); + self::assertIsNumeric($stats['hits']); + self::assertIsNumeric($stats['misses']); + self::assertIsNumeric($stats['evictions']); + self::assertIsNumeric($stats['entries']); + self::assertIsNumeric($stats['used_memory']); + self::assertGreaterThan(0, $stats['connected_clients']); + self::assertGreaterThan(0, $stats['uptime']); + } } diff --git a/tests/src/Core/Cache/RedisCacheTest.php b/tests/src/Core/Cache/RedisCacheTest.php index 6169171f40e..d16bf5a64ce 100644 --- a/tests/src/Core/Cache/RedisCacheTest.php +++ b/tests/src/Core/Cache/RedisCacheTest.php @@ -57,4 +57,21 @@ class RedisCacheTest extends MemoryCacheTestCase $this->cache->clear(false); parent::tearDown(); } + + /** + * @small + */ + public function testStats() + { + $stats = $this->instance->getStats(); + + self::assertNotNull($stats['version']); + self::assertIsNumeric($stats['hits']); + self::assertIsNumeric($stats['misses']); + self::assertIsNumeric($stats['evictions']); + self::assertIsNumeric($stats['entries']); + self::assertIsNumeric($stats['used_memory']); + self::assertGreaterThan(0, $stats['connected_clients']); + self::assertGreaterThan(0, $stats['uptime']); + } } From a20828f6187902b2d5915b1a8690168916271b01 Mon Sep 17 00:00:00 2001 From: Philipp Date: Mon, 21 Apr 2025 20:12:54 +0200 Subject: [PATCH 02/10] Add Caching stats --- src/Module/StatsCaching.php | 98 +++++++++++++++++++++++++++++++++++++ static/routes.config.php | 3 +- 2 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 src/Module/StatsCaching.php diff --git a/src/Module/StatsCaching.php b/src/Module/StatsCaching.php new file mode 100644 index 00000000000..72ba61c67a0 --- /dev/null +++ b/src/Module/StatsCaching.php @@ -0,0 +1,98 @@ +config = $config; + $this->cache = $cache; + $this->lock = $lock; + } + + private function isAllowed(array $request): bool + { + return empty(!$request['key']) && $request['key'] == $this->config->get('system', 'stats_key'); + } + + protected function content(array $request = []): string + { + if (!$this->isAllowed($request)) { + throw new HTTPException\NotFoundException($this->l10n->t('Page not found.')); + } + return ''; + } + + protected function rawContent(array $request = []) + { + if (!$this->isAllowed($request)) { + return; + } + + $data = []; + + // OPcache + if (function_exists('opcache_get_status')) { + $status = opcache_get_status(false); + $data['opcache'] = [ + 'enabled' => $status['opcache_enabled'] ?? false, + 'hit_rate' => $status['opcache_statistics']['opcache_hit_rate'] ?? null, + 'used_memory' => $status['memory_usage']['used_memory'] ?? null, + 'free_memory' => $status['memory_usage']['free_memory'] ?? null, + 'num_cached_scripts' => $status['opcache_statistics']['num_cached_scripts'] ?? null, + ]; + } else { + $data['opcache'] = [ + 'enabled' => false, + ]; + } + + if ($this->cache instanceof ICanCacheInMemory) { + $data['cache'] = [ + 'type' => $this->cache->getName(), + 'stats' => $this->cache->getStats(), + ]; + } else { + $data['cache'] = [ + 'type' => $this->cache->getName(), + ]; + } + + if ($this->lock instanceof CacheLock) { + $data['lock'] = [ + 'type' => $this->lock->getName(), + 'stats' => $this->lock->getCacheStats(), + ]; + } else { + $data['lock'] = [ + 'type' => $this->lock->getName(), + ]; + } + + $this->jsonExit($data); + } +} diff --git a/static/routes.config.php b/static/routes.config.php index 88e642c27a6..4c7b6949ecc 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -642,7 +642,8 @@ return [ ], ], - '/stats' => [Module\Stats::class, [R::GET]], + '/stats' => [Module\Stats::class, [R::GET]], + '/stats/caching' => [Module\StatsCaching::class, [R::GET]], '/network' => [ '[/{content}]' => [Module\Conversation\Network::class, [R::GET]], From 50c720688badc7185c6a71fd185bab0db8e7179d Mon Sep 17 00:00:00 2001 From: Philipp Date: Mon, 21 Apr 2025 20:49:41 +0200 Subject: [PATCH 03/10] Make PHPCS happy --- src/Core/Cache/Type/APCuCache.php | 11 +++++------ src/Core/Cache/Type/ArrayCache.php | 3 +-- src/Core/Cache/Type/MemcacheCache.php | 17 ++++++++--------- src/Core/Cache/Type/MemcachedCache.php | 17 ++++++++--------- src/Core/Lock/Type/CacheLock.php | 1 - src/Module/StatsCaching.php | 12 ++++++------ 6 files changed, 28 insertions(+), 33 deletions(-) diff --git a/src/Core/Cache/Type/APCuCache.php b/src/Core/Cache/Type/APCuCache.php index 3c72e76c983..b269b5aa9db 100644 --- a/src/Core/Cache/Type/APCuCache.php +++ b/src/Core/Cache/Type/APCuCache.php @@ -16,10 +16,9 @@ use Friendica\Core\Cache\Exception\InvalidCacheDriverException; */ class APCuCache extends AbstractCache implements ICanCacheInMemory { - const NAME = 'apcu'; - use CompareSetTrait; use CompareDeleteTrait; + const NAME = 'apcu'; /** * @throws InvalidCacheDriverException @@ -156,10 +155,10 @@ class APCuCache extends AbstractCache implements ICanCacheInMemory return [ 'entries' => $apcu['num_entries'] ?? null, - 'used_memory' => $apcu['mem_size'] ?? null, - 'hits' => $apcu['num_hits'] ?? null, - 'misses' => $apcu['num_misses'] ?? null, - 'avail_mem' => $sma['avail_mem'] ?? null, + 'used_memory' => $apcu['mem_size'] ?? null, + 'hits' => $apcu['num_hits'] ?? null, + 'misses' => $apcu['num_misses'] ?? null, + 'avail_mem' => $sma['avail_mem'] ?? null, ]; } } diff --git a/src/Core/Cache/Type/ArrayCache.php b/src/Core/Cache/Type/ArrayCache.php index c6a20d627b6..148210b4e89 100644 --- a/src/Core/Cache/Type/ArrayCache.php +++ b/src/Core/Cache/Type/ArrayCache.php @@ -15,9 +15,8 @@ use Friendica\Core\Cache\Enum; */ class ArrayCache extends AbstractCache implements ICanCacheInMemory { - const NAME = 'array'; - use CompareDeleteTrait; + const NAME = 'array'; /** @var array Array with the cached data */ protected $cachedData = []; diff --git a/src/Core/Cache/Type/MemcacheCache.php b/src/Core/Cache/Type/MemcacheCache.php index b2142ffd367..b3a65888416 100644 --- a/src/Core/Cache/Type/MemcacheCache.php +++ b/src/Core/Cache/Type/MemcacheCache.php @@ -19,11 +19,10 @@ use Memcache; */ class MemcacheCache extends AbstractCache implements ICanCacheInMemory { - const NAME = 'memcache'; - use CompareSetTrait; use CompareDeleteTrait; use MemcacheCommandTrait; + const NAME = 'memcache'; /** * @var Memcache @@ -163,14 +162,14 @@ class MemcacheCache extends AbstractCache implements ICanCacheInMemory $stats = $this->memcache->getStats(); return [ - 'version' => $stats['version'] ?? null, - 'entries' => $stats['curr_items'] ?? null, - 'used_memory' => $stats['bytes'] ?? null, - 'uptime' => $stats['uptime'] ?? null, + 'version' => $stats['version'] ?? null, + 'entries' => $stats['curr_items'] ?? null, + 'used_memory' => $stats['bytes'] ?? null, + 'uptime' => $stats['uptime'] ?? null, 'connected_clients' => $stats['curr_connections'] ?? null, - 'hits' => $stats['get_hits'] ?? null, - 'misses' => $stats['get_misses'] ?? null, - 'evictions' => $stats['evictions'] ?? null, + 'hits' => $stats['get_hits'] ?? null, + 'misses' => $stats['get_misses'] ?? null, + 'evictions' => $stats['evictions'] ?? null, ]; } } diff --git a/src/Core/Cache/Type/MemcachedCache.php b/src/Core/Cache/Type/MemcachedCache.php index 53f959fd2b0..03ad7d83223 100644 --- a/src/Core/Cache/Type/MemcachedCache.php +++ b/src/Core/Cache/Type/MemcachedCache.php @@ -20,11 +20,10 @@ use Psr\Log\LoggerInterface; */ class MemcachedCache extends AbstractCache implements ICanCacheInMemory { - const NAME = 'memcached'; - use CompareSetTrait; use CompareDeleteTrait; use MemcacheCommandTrait; + const NAME = 'memcached'; /** * @var \Memcached @@ -185,14 +184,14 @@ class MemcachedCache extends AbstractCache implements ICanCacheInMemory } return [ - 'version' => $stats['version'] ?? null, - 'entries' => $stats['curr_items'] ?? null, - 'used_memory' => $stats['bytes'] ?? null, - 'uptime' => $stats['uptime'] ?? null, + 'version' => $stats['version'] ?? null, + 'entries' => $stats['curr_items'] ?? null, + 'used_memory' => $stats['bytes'] ?? null, + 'uptime' => $stats['uptime'] ?? null, 'connected_clients' => $stats['curr_connections'] ?? null, - 'hits' => $stats['get_hits'] ?? null, - 'misses' => $stats['get_misses'] ?? null, - 'evictions' => $stats['evictions'] ?? null, + 'hits' => $stats['get_hits'] ?? null, + 'misses' => $stats['get_misses'] ?? null, + 'evictions' => $stats['evictions'] ?? null, ]; } } diff --git a/src/Core/Lock/Type/CacheLock.php b/src/Core/Lock/Type/CacheLock.php index 9fc9fad8f84..c7fa75e021e 100644 --- a/src/Core/Lock/Type/CacheLock.php +++ b/src/Core/Lock/Type/CacheLock.php @@ -7,7 +7,6 @@ namespace Friendica\Core\Lock\Type; -use Friendica\Core\Cache\Capability\ICanCache; use Friendica\Core\Cache\Capability\ICanCacheInMemory; use Friendica\Core\Cache\Enum\Duration; use Friendica\Core\Cache\Exception\CachePersistenceException; diff --git a/src/Module/StatsCaching.php b/src/Module/StatsCaching.php index 72ba61c67a0..b8d0cd1829f 100644 --- a/src/Module/StatsCaching.php +++ b/src/Module/StatsCaching.php @@ -59,10 +59,10 @@ class StatsCaching extends BaseModule if (function_exists('opcache_get_status')) { $status = opcache_get_status(false); $data['opcache'] = [ - 'enabled' => $status['opcache_enabled'] ?? false, - 'hit_rate' => $status['opcache_statistics']['opcache_hit_rate'] ?? null, - 'used_memory' => $status['memory_usage']['used_memory'] ?? null, - 'free_memory' => $status['memory_usage']['free_memory'] ?? null, + 'enabled' => $status['opcache_enabled'] ?? false, + 'hit_rate' => $status['opcache_statistics']['opcache_hit_rate'] ?? null, + 'used_memory' => $status['memory_usage']['used_memory'] ?? null, + 'free_memory' => $status['memory_usage']['free_memory'] ?? null, 'num_cached_scripts' => $status['opcache_statistics']['num_cached_scripts'] ?? null, ]; } else { @@ -73,7 +73,7 @@ class StatsCaching extends BaseModule if ($this->cache instanceof ICanCacheInMemory) { $data['cache'] = [ - 'type' => $this->cache->getName(), + 'type' => $this->cache->getName(), 'stats' => $this->cache->getStats(), ]; } else { @@ -84,7 +84,7 @@ class StatsCaching extends BaseModule if ($this->lock instanceof CacheLock) { $data['lock'] = [ - 'type' => $this->lock->getName(), + 'type' => $this->lock->getName(), 'stats' => $this->lock->getCacheStats(), ]; } else { From 596957658568769d70260f1d749aae5bcfce0828 Mon Sep 17 00:00:00 2001 From: Philipp Date: Mon, 21 Apr 2025 20:50:26 +0200 Subject: [PATCH 04/10] Add missing license --- src/Module/StatsCaching.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Module/StatsCaching.php b/src/Module/StatsCaching.php index b8d0cd1829f..6247eca9c19 100644 --- a/src/Module/StatsCaching.php +++ b/src/Module/StatsCaching.php @@ -1,5 +1,10 @@ Date: Mon, 21 Apr 2025 20:53:28 +0200 Subject: [PATCH 05/10] Make PHPCS happy - again --- src/Core/Cache/Type/RedisCache.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Core/Cache/Type/RedisCache.php b/src/Core/Cache/Type/RedisCache.php index d0056246413..fc04a5433ac 100644 --- a/src/Core/Cache/Type/RedisCache.php +++ b/src/Core/Cache/Type/RedisCache.php @@ -214,14 +214,14 @@ class RedisCache extends AbstractCache implements ICanCacheInMemory $info = $this->redis->info(); return [ - 'version' => $info['redis_version'] ?? null, - 'entries' => $this->redis->dbSize() ?? null, - 'used_memory' => $info['used_memory'] ?? null, + 'version' => $info['redis_version'] ?? null, + 'entries' => $this->redis->dbSize() ?? null, + 'used_memory' => $info['used_memory'] ?? null, 'connected_clients' => $info['connected_clients'] ?? null, 'uptime' => $info['uptime_in_seconds'] ?? null, - 'hits' => $info['keyspace_hits'] ?? null, - 'misses' => $info['keyspace_misses'] ?? null, - 'evictions' => $info['evicted_keys'] ?? null, + 'hits' => $info['keyspace_hits'] ?? null, + 'misses' => $info['keyspace_misses'] ?? null, + 'evictions' => $info['evicted_keys'] ?? null, ]; } } From b222aa0c48020de168291cd406e92c5f89ced396 Mon Sep 17 00:00:00 2001 From: Philipp Date: Sun, 27 Apr 2025 01:36:30 +0200 Subject: [PATCH 06/10] Add a bunch of tests for StatsCaching --- composer.json | 1 + composer.lock | 67 +++++- src/Module/StatsCaching.php | 9 +- tests/CacheLockTestCase.php | 26 +++ tests/LockTestCase.php | 17 +- tests/src/Core/Cache/ArrayCacheTest.php | 8 + .../Core/Cache/ProfilerCacheDecoratorTest.php | 56 +++++ tests/src/Core/Lock/APCuCacheLockTest.php | 21 +- tests/src/Core/Lock/ArrayCacheLockTest.php | 25 ++- .../src/Core/Lock/DatabaseLockDriverTest.php | 3 +- tests/src/Core/Lock/MemcacheCacheLockTest.php | 28 ++- .../src/Core/Lock/MemcachedCacheLockTest.php | 27 ++- tests/src/Core/Lock/RedisCacheLockTest.php | 24 +- tests/src/Core/Lock/SemaphoreLockTest.php | 3 +- tests/src/Module/StatsCachingTest.php | 205 ++++++++++++++++++ 15 files changed, 477 insertions(+), 43 deletions(-) create mode 100644 tests/CacheLockTestCase.php create mode 100644 tests/src/Core/Cache/ProfilerCacheDecoratorTest.php create mode 100644 tests/src/Module/StatsCachingTest.php diff --git a/composer.json b/composer.json index a2c9eea3c9d..655f987df91 100644 --- a/composer.json +++ b/composer.json @@ -153,6 +153,7 @@ "dms/phpunit-arraysubset-asserts": "^0.3.1", "mikey179/vfsstream": "^1.6", "mockery/mockery": "^1.3", + "php-mock/php-mock-mockery": "^1.5", "php-mock/php-mock-phpunit": "^2.10", "phpmd/phpmd": "^2.15", "phpstan/phpstan": "^2.0", diff --git a/composer.lock b/composer.lock index e12cc6533c6..bae30155dd9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b77bf714197f04022a5feb001bf07852", + "content-hash": "32af97f73ec49df2a6cfe98f11bc1d60", "packages": [ { "name": "asika/simple-console", @@ -5441,6 +5441,71 @@ ], "time": "2024-02-10T21:37:25+00:00" }, + { + "name": "php-mock/php-mock-mockery", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/php-mock/php-mock-mockery.git", + "reference": "291994acdc26daf1e3c659cfbe58b01eeb180b7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-mock/php-mock-mockery/zipball/291994acdc26daf1e3c659cfbe58b01eeb180b7f", + "reference": "291994acdc26daf1e3c659cfbe58b01eeb180b7f", + "shasum": "" + }, + "require": { + "mockery/mockery": "^1", + "php": ">=5.6", + "php-mock/php-mock-integration": "^2.2.1 || ^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4|^5|^8" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpmock\\mockery\\": "classes/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "WTFPL" + ], + "authors": [ + { + "name": "Markus Malkusch", + "email": "markus@malkusch.de", + "homepage": "http://markus.malkusch.de", + "role": "Developer" + } + ], + "description": "Mock built-in PHP functions (e.g. time()) with Mockery. This package relies on PHP's namespace fallback policy. No further extension is needed.", + "homepage": "https://github.com/php-mock/php-mock-mockery", + "keywords": [ + "BDD", + "TDD", + "function", + "mock", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "issues": "https://github.com/php-mock/php-mock-mockery/issues", + "source": "https://github.com/php-mock/php-mock-mockery/tree/1.5.0" + }, + "funding": [ + { + "url": "https://github.com/michalbundyra", + "type": "github" + } + ], + "time": "2025-03-08T19:46:20+00:00" + }, { "name": "php-mock/php-mock-phpunit", "version": "2.10.0", diff --git a/src/Module/StatsCaching.php b/src/Module/StatsCaching.php index 6247eca9c19..668d26e0219 100644 --- a/src/Module/StatsCaching.php +++ b/src/Module/StatsCaching.php @@ -15,6 +15,7 @@ use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\L10n; use Friendica\Core\Lock\Capability\ICanLock; use Friendica\Core\Lock\Type\CacheLock; +use Friendica\Network\HTTPException\NotFoundException; use Friendica\Util\Profiler; use Psr\Log\LoggerInterface; use Friendica\Network\HTTPException; @@ -41,9 +42,12 @@ class StatsCaching extends BaseModule private function isAllowed(array $request): bool { - return empty(!$request['key']) && $request['key'] == $this->config->get('system', 'stats_key'); + return !empty($request['key']) && $request['key'] == $this->config->get('system', 'stats_key'); } + /** + * @throws NotFoundException In case the rquest isn't allowed + */ protected function content(array $request = []): string { if (!$this->isAllowed($request)) { @@ -98,6 +102,7 @@ class StatsCaching extends BaseModule ]; } - $this->jsonExit($data); + $this->response->setType('json', 'application/json; charset=utf-8'); + $this->response->addContent(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)); } } diff --git a/tests/CacheLockTestCase.php b/tests/CacheLockTestCase.php new file mode 100644 index 00000000000..1599391ece2 --- /dev/null +++ b/tests/CacheLockTestCase.php @@ -0,0 +1,26 @@ +getCache()->getStats()), array_keys($this->instance->getCacheStats())); + } +} diff --git a/tests/LockTestCase.php b/tests/LockTestCase.php index 9ce86497b71..1b80e575ef7 100644 --- a/tests/LockTestCase.php +++ b/tests/LockTestCase.php @@ -7,22 +7,21 @@ namespace Friendica\Test; +use Friendica\Core\Cache\Capability\ICanCache; +use Friendica\Core\Cache\Capability\ICanCacheInMemory; use Friendica\Core\Lock\Capability\ICanLock; -use Friendica\Test\MockedTestCase; +use Friendica\Core\Lock\Type\CacheLock; abstract class LockTestCase extends MockedTestCase { /** - * @var int Start time of the mock (used for time operations) + * Start time of the mock (used for time operations) */ - protected $startTime = 1417011228; + protected int $startTime = 1417011228; + protected ICanLock $instance; - /** - * @var ICanLock - */ - protected $instance; + abstract protected function getInstance(): ICanLock; - abstract protected function getInstance(); protected function setUp(): void { @@ -205,4 +204,6 @@ abstract class LockTestCase extends MockedTestCase self::assertFalse($this->instance->isLocked('wrongLock')); self::assertFalse($this->instance->release('wrongLock')); } + + } diff --git a/tests/src/Core/Cache/ArrayCacheTest.php b/tests/src/Core/Cache/ArrayCacheTest.php index 967cb07bce1..50226b09071 100644 --- a/tests/src/Core/Cache/ArrayCacheTest.php +++ b/tests/src/Core/Cache/ArrayCacheTest.php @@ -33,4 +33,12 @@ class ArrayCacheTest extends MemoryCacheTestCase self::markTestSkipped("Array Cache doesn't support TTL"); return true; } + + /** + * @small + */ + public function testGetStats() + { + self::assertEmpty($this->cache->getStats()); + } } diff --git a/tests/src/Core/Cache/ProfilerCacheDecoratorTest.php b/tests/src/Core/Cache/ProfilerCacheDecoratorTest.php new file mode 100644 index 00000000000..3f44bcd0bf2 --- /dev/null +++ b/tests/src/Core/Cache/ProfilerCacheDecoratorTest.php @@ -0,0 +1,56 @@ +shouldReceive('get')->with('system', 'profiler')->once()->andReturn(false); + $config->shouldReceive('get')->with('rendertime', 'callstack')->once()->andReturn(false); + + $this->cache = new ProfilerCacheDecorator(new ArrayCache('localhost'), new Profiler($config)); + return $this->cache; + } + + protected function tearDown(): void + { + $this->cache->clear(false); + parent::tearDown(); + } + + /** + * @doesNotPerformAssertions + */ + public function testTTL() + { + // Array Cache doesn't support TTL + self::markTestSkipped("Array Cache doesn't support TTL"); + return true; + } + + /** + * @small + */ + public function testGetStats() + { + self::assertEmpty($this->cache->getStats()); + } + + public function testGetName() + { + self::assertStringEndsWith(' (with profiler)', $this->instance->getName()); + } +} diff --git a/tests/src/Core/Lock/APCuCacheLockTest.php b/tests/src/Core/Lock/APCuCacheLockTest.php index 3ee0d09661a..42a1fc72d53 100644 --- a/tests/src/Core/Lock/APCuCacheLockTest.php +++ b/tests/src/Core/Lock/APCuCacheLockTest.php @@ -7,26 +7,39 @@ namespace Friendica\Test\src\Core\Lock; +use Friendica\Core\Cache\Capability\ICanCacheInMemory; use Friendica\Core\Cache\Type\APCuCache; +use Friendica\Core\Lock\Capability\ICanLock; use Friendica\Core\Lock\Type\CacheLock; -use Friendica\Test\LockTestCase; +use Friendica\Test\CacheLockTestCase; /** * @group APCU */ -class APCuCacheLockTest extends LockTestCase +class APCuCacheLockTest extends CacheLockTestCase { + private APCuCache $cache; + private ICanLock $lock; + protected function setUp(): void { if (!APCuCache::isAvailable()) { static::markTestSkipped('APCu is not available'); } + $this->cache = new APCuCache('localhost'); + $this->lock = new CacheLock($this->cache); + parent::setUp(); } - protected function getInstance() + protected function getInstance(): CacheLock { - return new CacheLock(new APCuCache('localhost')); + return $this->lock; + } + + protected function getCache(): ICanCacheInMemory + { + return $this->cache; } } diff --git a/tests/src/Core/Lock/ArrayCacheLockTest.php b/tests/src/Core/Lock/ArrayCacheLockTest.php index 19ac7925c61..2aac8e82933 100644 --- a/tests/src/Core/Lock/ArrayCacheLockTest.php +++ b/tests/src/Core/Lock/ArrayCacheLockTest.php @@ -7,15 +7,32 @@ namespace Friendica\Test\src\Core\Lock; +use Friendica\Core\Cache\Capability\ICanCacheInMemory; use Friendica\Core\Cache\Type\ArrayCache; use Friendica\Core\Lock\Type\CacheLock; -use Friendica\Test\LockTestCase; +use Friendica\Test\CacheLockTestCase; -class ArrayCacheLockTest extends LockTestCase +class ArrayCacheLockTest extends CacheLockTestCase { - protected function getInstance() + private CacheLock $lock; + private ArrayCache $cache; + + protected function setUp(): void { - return new CacheLock(new ArrayCache('localhost')); + $this->cache = new ArrayCache('localhost'); + $this->lock = new CacheLock($this->cache); + + parent::setUp(); + } + + protected function getInstance(): CacheLock + { + return $this->lock; + } + + protected function getCache(): ICanCacheInMemory + { + return $this->cache; } /** diff --git a/tests/src/Core/Lock/DatabaseLockDriverTest.php b/tests/src/Core/Lock/DatabaseLockDriverTest.php index ebc2b0090fb..fbfe61762e3 100644 --- a/tests/src/Core/Lock/DatabaseLockDriverTest.php +++ b/tests/src/Core/Lock/DatabaseLockDriverTest.php @@ -7,6 +7,7 @@ namespace Friendica\Test\src\Core\Lock; +use Friendica\Core\Lock\Capability\ICanLock; use Friendica\Core\Lock\Type\DatabaseLock; use Friendica\Test\LockTestCase; use Friendica\Test\Util\CreateDatabaseTrait; @@ -26,7 +27,7 @@ class DatabaseLockDriverTest extends LockTestCase parent::setUp(); } - protected function getInstance() + protected function getInstance(): ICanLock { return new DatabaseLock($this->getDbInstance(), $this->pid); } diff --git a/tests/src/Core/Lock/MemcacheCacheLockTest.php b/tests/src/Core/Lock/MemcacheCacheLockTest.php index 2bb0595cff3..c1dec663f11 100644 --- a/tests/src/Core/Lock/MemcacheCacheLockTest.php +++ b/tests/src/Core/Lock/MemcacheCacheLockTest.php @@ -8,19 +8,23 @@ namespace Friendica\Test\src\Core\Lock; use Exception; +use Friendica\Core\Cache\Capability\ICanCacheInMemory; use Friendica\Core\Cache\Type\MemcacheCache; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Lock\Type\CacheLock; -use Friendica\Test\LockTestCase; +use Friendica\Test\CacheLockTestCase; use Mockery; /** * @requires extension Memcache * @group MEMCACHE */ -class MemcacheCacheLockTest extends LockTestCase +class MemcacheCacheLockTest extends CacheLockTestCase { - protected function getInstance() + private CacheLock $lock; + private MemcacheCache $cache; + + protected function setUp(): void { $configMock = Mockery::mock(IManageConfigValues::class); @@ -36,16 +40,24 @@ class MemcacheCacheLockTest extends LockTestCase ->with('system', 'memcache_port') ->andReturn($port); - $lock = null; - try { - $cache = new MemcacheCache($host, $configMock); - $lock = new CacheLock($cache); + $this->cache = new MemcacheCache($host, $configMock); + $this->lock = new CacheLock($this->cache); } catch (Exception $e) { static::markTestSkipped('Memcache is not available'); } - return $lock; + parent::setUp(); + } + + protected function getInstance(): CacheLock + { + return $this->lock; + } + + protected function getCache(): ICanCacheInMemory + { + return $this->cache; } /** diff --git a/tests/src/Core/Lock/MemcachedCacheLockTest.php b/tests/src/Core/Lock/MemcachedCacheLockTest.php index fb38ec3312b..773e6641085 100644 --- a/tests/src/Core/Lock/MemcachedCacheLockTest.php +++ b/tests/src/Core/Lock/MemcachedCacheLockTest.php @@ -8,9 +8,11 @@ namespace Friendica\Test\src\Core\Lock; use Exception; +use Friendica\Core\Cache\Capability\ICanCacheInMemory; use Friendica\Core\Cache\Type\MemcachedCache; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Lock\Type\CacheLock; +use Friendica\Test\CacheLockTestCase; use Friendica\Test\LockTestCase; use Mockery; use Psr\Log\NullLogger; @@ -19,9 +21,12 @@ use Psr\Log\NullLogger; * @requires extension memcached * @group MEMCACHED */ -class MemcachedCacheLockTest extends LockTestCase +class MemcachedCacheLockTest extends CacheLockTestCase { - protected function getInstance() + private MemcachedCache $cache; + private CacheLock $lock; + + protected function setUp(): void { $configMock = Mockery::mock(IManageConfigValues::class); @@ -35,16 +40,24 @@ class MemcachedCacheLockTest extends LockTestCase $logger = new NullLogger(); - $lock = null; - try { - $cache = new MemcachedCache($host, $configMock, $logger); - $lock = new CacheLock($cache); + $this->cache = new MemcachedCache($host, $configMock, $logger); + $this->lock = new CacheLock($this->cache); } catch (Exception $e) { static::markTestSkipped('Memcached is not available'); } - return $lock; + parent::setUp(); + } + + protected function getInstance(): CacheLock + { + return $this->lock; + } + + protected function getCache(): ICanCacheInMemory + { + return $this->cache; } /** diff --git a/tests/src/Core/Lock/RedisCacheLockTest.php b/tests/src/Core/Lock/RedisCacheLockTest.php index d0237682c3d..3cb4fba4368 100644 --- a/tests/src/Core/Lock/RedisCacheLockTest.php +++ b/tests/src/Core/Lock/RedisCacheLockTest.php @@ -8,9 +8,11 @@ namespace Friendica\Test\src\Core\Lock; use Exception; +use Friendica\Core\Cache\Capability\ICanCacheInMemory; use Friendica\Core\Cache\Type\RedisCache; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Lock\Type\CacheLock; +use Friendica\Test\CacheLockTestCase; use Friendica\Test\LockTestCase; use Mockery; @@ -18,9 +20,9 @@ use Mockery; * @requires extension redis * @group REDIS */ -class RedisCacheLockTest extends LockTestCase +class RedisCacheLockTest extends CacheLockTestCase { - protected function getInstance() + protected function setUp(): void { $configMock = Mockery::mock(IManageConfigValues::class); @@ -45,15 +47,23 @@ class RedisCacheLockTest extends LockTestCase ->with('system', 'redis_password') ->andReturn(null); - $lock = null; - try { - $cache = new RedisCache($host, $configMock); - $lock = new CacheLock($cache); + $this->cache = new RedisCache($host, $configMock); + $this->lock = new CacheLock($this->cache); } catch (Exception $e) { static::markTestSkipped('Redis is not available. Error: ' . $e->getMessage()); } - return $lock; + parent::setUp(); + } + + protected function getInstance(): CAcheLock + { + return $this->lock; + } + + protected function getCache(): ICanCacheInMemory + { + return $this->cache; } } diff --git a/tests/src/Core/Lock/SemaphoreLockTest.php b/tests/src/Core/Lock/SemaphoreLockTest.php index 06b4e02f46a..1ddb2dade75 100644 --- a/tests/src/Core/Lock/SemaphoreLockTest.php +++ b/tests/src/Core/Lock/SemaphoreLockTest.php @@ -12,6 +12,7 @@ use Friendica\App; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Config\Model\ReadOnlyFileConfig; use Friendica\Core\Config\ValueObject\Cache; +use Friendica\Core\Lock\Capability\ICanLock; use Friendica\Core\Lock\Type\SemaphoreLock; use Friendica\Core\System; use Friendica\DI; @@ -40,7 +41,7 @@ class SemaphoreLockTest extends LockTestCase parent::setUp(); } - protected function getInstance() + protected function getInstance(): ICanLock { return new SemaphoreLock(); } diff --git a/tests/src/Module/StatsCachingTest.php b/tests/src/Module/StatsCachingTest.php new file mode 100644 index 00000000000..08d4b8fdb64 --- /dev/null +++ b/tests/src/Module/StatsCachingTest.php @@ -0,0 +1,205 @@ +httpExceptionMock = \Mockery::mock(HTTPException::class); + $this->config = \Mockery::mock(IManageConfigValues::class); + $this->cache = new ArrayCache('localhost'); + $this->lock = new CacheLock($this->cache); + } + + public function testStatsCachingNotAllowed() + { + $this->httpExceptionMock->shouldReceive('content')->andReturn('failed')->once(); + + $response = (new StatsCaching(DI::l10n(), DI::baseUrl(), DI::args(), DI::logger(), DI::profiler(), DI::apiResponse(), [], $this->config, $this->cache, $this->lock, [])) + ->run($this->httpExceptionMock); + + self::assertEquals('404', $response->getStatusCode()); + self::assertEquals('Page not found', $response->getReasonPhrase()); + self::assertEquals('failed', $response->getBody()); + } + + public function testStatsCachingWitMinimumCache() + { + $request = [ + 'key' => '12345', + ]; + $this->config->shouldReceive('get')->with('system', 'stats_key')->twice()->andReturn('12345'); + PHPMockery::mock("Friendica\\Module", "function_exists")->with('opcache_get_status')->once()->andReturn(false); + + $response = (new StatsCaching(DI::l10n(), DI::baseUrl(), DI::args(), DI::logger(), DI::profiler(), DI::apiResponse(), [], $this->config, $this->cache, $this->lock, [])) + ->run($this->httpExceptionMock, $request); + + self::assertJson($response->getBody()); + self::assertEquals(['Content-type' => ['application/json; charset=utf-8'], ICanCreateResponses::X_HEADER => ['json']], $response->getHeaders()); + + $json = json_decode($response->getBody(), true); + + self::assertEquals([ + 'type' => 'array', + 'stats' => [], + ], $json['cache']); + self::assertEquals([ + 'type' => 'array', + 'stats' => [], + ], $json['lock']); + } + + public function testStatsCachingWithDatabase() + { + $request = [ + 'key' => '12345', + ]; + $this->config->shouldReceive('get')->with('system', 'stats_key')->twice()->andReturn('12345'); + + $this->cache = new DatabaseCache('localhost', DI::dba()); + $this->lock = new DatabaseLock(DI::dba()); + PHPMockery::mock("Friendica\\Module", "function_exists")->with('opcache_get_status')->once()->andReturn(false); + + $response = (new StatsCaching(DI::l10n(), DI::baseUrl(), DI::args(), DI::logger(), DI::profiler(), DI::apiResponse(), [], $this->config, $this->cache, $this->lock, [])) + ->run($this->httpExceptionMock, $request); + + self::assertJson($response->getBody()); + self::assertEquals(['Content-type' => ['application/json; charset=utf-8'], ICanCreateResponses::X_HEADER => ['json']], $response->getHeaders()); + + $json = json_decode($response->getBody(), true); + + self::assertEquals(['enabled' => false], $json['opcache']); + self::assertEquals(['type' => 'database'], $json['cache']); + self::assertEquals(['type' => 'database'], $json['lock']); + } + + public function testStatsCachingWithCache() + { + $request = [ + 'key' => '12345', + ]; + $this->config->shouldReceive('get')->with('system', 'stats_key')->twice()->andReturn('12345'); + + $this->cache = new DatabaseCache('localhost', DI::dba()); + $this->lock = new DatabaseLock(DI::dba()); + PHPMockery::mock("Friendica\\Module", "function_exists")->with('opcache_get_status')->once()->andReturn(false); + + $response = (new StatsCaching(DI::l10n(), DI::baseUrl(), DI::args(), DI::logger(), DI::profiler(), DI::apiResponse(), [], $this->config, $this->cache, $this->lock, [])) + ->run($this->httpExceptionMock, $request); + + self::assertJson($response->getBody()); + self::assertEquals(['Content-type' => ['application/json; charset=utf-8'], ICanCreateResponses::X_HEADER => ['json']], $response->getHeaders()); + + $json = json_decode($response->getBody(), true); + + self::assertEquals(['enabled' => false], $json['opcache']); + self::assertEquals(['type' => 'database'], $json['cache']); + self::assertEquals(['type' => 'database'], $json['lock']); + } + + public function testStatsCachingWithOpcacheAndNull() + { + $request = [ + 'key' => '12345', + ]; + $this->config->shouldReceive('get')->with('system', 'stats_key')->twice()->andReturn('12345'); + + $this->cache = new DatabaseCache('localhost', DI::dba()); + $this->lock = new DatabaseLock(DI::dba()); + PHPMockery::mock("Friendica\\Module", "function_exists")->with('opcache_get_status')->once()->andReturn(true); + PHPMockery::mock("Friendica\\Module", "opcache_get_status")->with(false)->once()->andReturn(false); + + $response = (new StatsCaching(DI::l10n(), DI::baseUrl(), DI::args(), DI::logger(), DI::profiler(), DI::apiResponse(), [], $this->config, $this->cache, $this->lock, [])) + ->run($this->httpExceptionMock, $request); + + self::assertJson($response->getBody()); + self::assertEquals(['Content-type' => ['application/json; charset=utf-8'], ICanCreateResponses::X_HEADER => ['json']], $response->getHeaders()); + + $json = json_decode($response->getBody(), true); + + print_r($json); + + self::assertEquals([ + 'enabled' => false, + 'hit_rate' => null, + 'used_memory' => null, + 'free_memory' => null, + 'num_cached_scripts' => null, + ], $json['opcache']); + self::assertEquals(['type' => 'database'], $json['cache']); + self::assertEquals(['type' => 'database'], $json['lock']); + } + + public function testStatsCachingWithOpcacheAndValues() + { + $request = [ + 'key' => '12345', + ]; + $this->config->shouldReceive('get')->with('system', 'stats_key')->twice()->andReturn('12345'); + + $this->cache = new DatabaseCache('localhost', DI::dba()); + $this->lock = new DatabaseLock(DI::dba()); + PHPMockery::mock("Friendica\\Module", "function_exists")->with('opcache_get_status')->once()->andReturn(true); + PHPMockery::mock("Friendica\\Module", "opcache_get_status")->with(false)->once()->andReturn([ + 'opcache_enabled' => true, + 'opcache_statistics' => [ + 'opcache_hit_rate' => 1, + 'num_cached_scripts' => 2, + ], + 'memory_usage' => [ + 'used_memory' => 3, + 'free_memory' => 4, + ] + ]); + + $response = (new StatsCaching(DI::l10n(), DI::baseUrl(), DI::args(), DI::logger(), DI::profiler(), DI::apiResponse(), [], $this->config, $this->cache, $this->lock, [])) + ->run($this->httpExceptionMock, $request); + + self::assertJson($response->getBody()); + self::assertEquals(['Content-type' => ['application/json; charset=utf-8'], ICanCreateResponses::X_HEADER => ['json']], $response->getHeaders()); + + $json = json_decode($response->getBody(), true); + + self::assertEquals([ + 'enabled' => true, + 'hit_rate' => 1, + 'used_memory' => 3, + 'free_memory' => 4, + 'num_cached_scripts' => 2, + ], $json['opcache']); + self::assertEquals(['type' => 'database'], $json['cache']); + self::assertEquals(['type' => 'database'], $json['lock']); + } +} From c92d23909151b3e34b9fa2ef712204fdb7378a73 Mon Sep 17 00:00:00 2001 From: Philipp Date: Sun, 27 Apr 2025 01:58:03 +0200 Subject: [PATCH 07/10] fix test --- tests/src/Module/StatsCachingTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/src/Module/StatsCachingTest.php b/tests/src/Module/StatsCachingTest.php index 08d4b8fdb64..78554929e2a 100644 --- a/tests/src/Module/StatsCachingTest.php +++ b/tests/src/Module/StatsCachingTest.php @@ -149,8 +149,6 @@ class StatsCachingTest extends FixtureTestCase $json = json_decode($response->getBody(), true); - print_r($json); - self::assertEquals([ 'enabled' => false, 'hit_rate' => null, From 03cfa2d066a2201334462cd446f72c3b8170dda3 Mon Sep 17 00:00:00 2001 From: Philipp Date: Sun, 27 Apr 2025 01:58:20 +0200 Subject: [PATCH 08/10] Add doc --- doc/stats.md | 35 +++++++++++++++++++++++++++++++++++ doc/tools.md | 12 ------------ 2 files changed, 35 insertions(+), 12 deletions(-) create mode 100644 doc/stats.md diff --git a/doc/stats.md b/doc/stats.md new file mode 100644 index 00000000000..1b6a2dfd2a8 --- /dev/null +++ b/doc/stats.md @@ -0,0 +1,35 @@ +Monitoring +=========== + +* [Home](help) + +## Endpoints + +Currently, there are two endpoints for statistics available + +- `/stats` Returns some basic statistics of the current node +- `/stats/caching` Returns statistics of cache or lock instances, which are used for the currend node + +### `/stats` + +The statistics contain data about the worker performance, the last cron call, number of reports, inbound and outbound packets, posts and comments. + +### `/stats/caching` + +The statistics contain data about the opcache, the used caching (like memory usage, hits/misses, entries, ...) and the used lock (including the cache data) + +## Configuration + +Please define 'stats_key' in your local.config.php in the 'system' section to be able to access the statistics page at /stats?key=your-defined-stats_key + +## 3rd Party monitoring tools + +### Zabbix + +To monitor the health status of your Friendica installation, you can use for example a tool like Zabbix. + +### Prometheus + +To use [prometheus](https://prometheus.io) for gathering metrics, use the [Friendica exporter](https://git.friendi.ca/friendica/friendica-exporter). + +You can find the installation instructions here: https://git.friendi.ca/friendica/friendica-exporter#installation diff --git a/doc/tools.md b/doc/tools.md index 2a273e36507..fac1f4b392b 100644 --- a/doc/tools.md +++ b/doc/tools.md @@ -78,15 +78,3 @@ The following will compress */var/log/friendica* (assuming this is the location daily rotate 2 } - -### Zabbix - -To monitor the health status of your Friendica installation, you can use for example a tool like Zabbix. Please define 'stats_key' in your local.config.php in the 'system' section to be able to access the statistics page at /stats?key=your-defined-stats_key - -The statistics contain data about the worker performance, the last cron call, number of reports, inbound and outbound packets, posts and comments. - -### Prometheus - -To use [prometheus](https://prometheus.io) for gathering metrics, use the [Friendica exporter](https://git.friendi.ca/friendica/friendica-exporter). - -You can find the installation instructions here: https://git.friendi.ca/friendica/friendica-exporter#installation From 7b39b3b9c07c92e1713c29c0cbeac3f4ca9954ba Mon Sep 17 00:00:00 2001 From: Philipp Date: Sun, 27 Apr 2025 02:08:01 +0200 Subject: [PATCH 09/10] Mak PHPCS happy --- tests/LockTestCase.php | 3 -- tests/src/Core/Lock/APCuCacheLockTest.php | 2 +- tests/src/Core/Lock/ArrayCacheLockTest.php | 2 +- tests/src/Core/Lock/MemcacheCacheLockTest.php | 2 +- .../src/Core/Lock/MemcachedCacheLockTest.php | 3 +- tests/src/Core/Lock/RedisCacheLockTest.php | 3 +- tests/src/Core/Lock/SemaphoreLockTest.php | 2 +- tests/src/Module/StatsCachingTest.php | 38 +++++++++---------- 8 files changed, 25 insertions(+), 30 deletions(-) diff --git a/tests/LockTestCase.php b/tests/LockTestCase.php index 1b80e575ef7..92abf0ca7b7 100644 --- a/tests/LockTestCase.php +++ b/tests/LockTestCase.php @@ -7,10 +7,7 @@ namespace Friendica\Test; -use Friendica\Core\Cache\Capability\ICanCache; -use Friendica\Core\Cache\Capability\ICanCacheInMemory; use Friendica\Core\Lock\Capability\ICanLock; -use Friendica\Core\Lock\Type\CacheLock; abstract class LockTestCase extends MockedTestCase { diff --git a/tests/src/Core/Lock/APCuCacheLockTest.php b/tests/src/Core/Lock/APCuCacheLockTest.php index 42a1fc72d53..3b6c7904b43 100644 --- a/tests/src/Core/Lock/APCuCacheLockTest.php +++ b/tests/src/Core/Lock/APCuCacheLockTest.php @@ -28,7 +28,7 @@ class APCuCacheLockTest extends CacheLockTestCase } $this->cache = new APCuCache('localhost'); - $this->lock = new CacheLock($this->cache); + $this->lock = new CacheLock($this->cache); parent::setUp(); } diff --git a/tests/src/Core/Lock/ArrayCacheLockTest.php b/tests/src/Core/Lock/ArrayCacheLockTest.php index 2aac8e82933..07cd88dd1cf 100644 --- a/tests/src/Core/Lock/ArrayCacheLockTest.php +++ b/tests/src/Core/Lock/ArrayCacheLockTest.php @@ -20,7 +20,7 @@ class ArrayCacheLockTest extends CacheLockTestCase protected function setUp(): void { $this->cache = new ArrayCache('localhost'); - $this->lock = new CacheLock($this->cache); + $this->lock = new CacheLock($this->cache); parent::setUp(); } diff --git a/tests/src/Core/Lock/MemcacheCacheLockTest.php b/tests/src/Core/Lock/MemcacheCacheLockTest.php index c1dec663f11..8915e6d37cf 100644 --- a/tests/src/Core/Lock/MemcacheCacheLockTest.php +++ b/tests/src/Core/Lock/MemcacheCacheLockTest.php @@ -42,7 +42,7 @@ class MemcacheCacheLockTest extends CacheLockTestCase try { $this->cache = new MemcacheCache($host, $configMock); - $this->lock = new CacheLock($this->cache); + $this->lock = new CacheLock($this->cache); } catch (Exception $e) { static::markTestSkipped('Memcache is not available'); } diff --git a/tests/src/Core/Lock/MemcachedCacheLockTest.php b/tests/src/Core/Lock/MemcachedCacheLockTest.php index 773e6641085..522f60c64a9 100644 --- a/tests/src/Core/Lock/MemcachedCacheLockTest.php +++ b/tests/src/Core/Lock/MemcachedCacheLockTest.php @@ -13,7 +13,6 @@ use Friendica\Core\Cache\Type\MemcachedCache; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Lock\Type\CacheLock; use Friendica\Test\CacheLockTestCase; -use Friendica\Test\LockTestCase; use Mockery; use Psr\Log\NullLogger; @@ -42,7 +41,7 @@ class MemcachedCacheLockTest extends CacheLockTestCase try { $this->cache = new MemcachedCache($host, $configMock, $logger); - $this->lock = new CacheLock($this->cache); + $this->lock = new CacheLock($this->cache); } catch (Exception $e) { static::markTestSkipped('Memcached is not available'); } diff --git a/tests/src/Core/Lock/RedisCacheLockTest.php b/tests/src/Core/Lock/RedisCacheLockTest.php index 3cb4fba4368..1136b80c4b9 100644 --- a/tests/src/Core/Lock/RedisCacheLockTest.php +++ b/tests/src/Core/Lock/RedisCacheLockTest.php @@ -13,7 +13,6 @@ use Friendica\Core\Cache\Type\RedisCache; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Lock\Type\CacheLock; use Friendica\Test\CacheLockTestCase; -use Friendica\Test\LockTestCase; use Mockery; /** @@ -49,7 +48,7 @@ class RedisCacheLockTest extends CacheLockTestCase try { $this->cache = new RedisCache($host, $configMock); - $this->lock = new CacheLock($this->cache); + $this->lock = new CacheLock($this->cache); } catch (Exception $e) { static::markTestSkipped('Redis is not available. Error: ' . $e->getMessage()); } diff --git a/tests/src/Core/Lock/SemaphoreLockTest.php b/tests/src/Core/Lock/SemaphoreLockTest.php index 1ddb2dade75..30152ac4277 100644 --- a/tests/src/Core/Lock/SemaphoreLockTest.php +++ b/tests/src/Core/Lock/SemaphoreLockTest.php @@ -32,7 +32,7 @@ class SemaphoreLockTest extends LockTestCase $dice->shouldReceive('create')->with(App::class)->andReturn($app); $configCache = new Cache(['system' => ['temppath' => '/tmp']]); - $configMock = new ReadOnlyFileConfig($configCache); + $configMock = new ReadOnlyFileConfig($configCache); $dice->shouldReceive('create')->with(IManageConfigValues::class)->andReturn($configMock); // @todo Because "get_temppath()" is using static methods, we have to initialize the BaseObject diff --git a/tests/src/Module/StatsCachingTest.php b/tests/src/Module/StatsCachingTest.php index 78554929e2a..58226769a95 100644 --- a/tests/src/Module/StatsCachingTest.php +++ b/tests/src/Module/StatsCachingTest.php @@ -38,9 +38,9 @@ class StatsCachingTest extends FixtureTestCase parent::setUp(); $this->httpExceptionMock = \Mockery::mock(HTTPException::class); - $this->config = \Mockery::mock(IManageConfigValues::class); - $this->cache = new ArrayCache('localhost'); - $this->lock = new CacheLock($this->cache); + $this->config = \Mockery::mock(IManageConfigValues::class); + $this->cache = new ArrayCache('localhost'); + $this->lock = new CacheLock($this->cache); } public function testStatsCachingNotAllowed() @@ -72,11 +72,11 @@ class StatsCachingTest extends FixtureTestCase $json = json_decode($response->getBody(), true); self::assertEquals([ - 'type' => 'array', + 'type' => 'array', 'stats' => [], ], $json['cache']); self::assertEquals([ - 'type' => 'array', + 'type' => 'array', 'stats' => [], ], $json['lock']); } @@ -89,7 +89,7 @@ class StatsCachingTest extends FixtureTestCase $this->config->shouldReceive('get')->with('system', 'stats_key')->twice()->andReturn('12345'); $this->cache = new DatabaseCache('localhost', DI::dba()); - $this->lock = new DatabaseLock(DI::dba()); + $this->lock = new DatabaseLock(DI::dba()); PHPMockery::mock("Friendica\\Module", "function_exists")->with('opcache_get_status')->once()->andReturn(false); $response = (new StatsCaching(DI::l10n(), DI::baseUrl(), DI::args(), DI::logger(), DI::profiler(), DI::apiResponse(), [], $this->config, $this->cache, $this->lock, [])) @@ -113,7 +113,7 @@ class StatsCachingTest extends FixtureTestCase $this->config->shouldReceive('get')->with('system', 'stats_key')->twice()->andReturn('12345'); $this->cache = new DatabaseCache('localhost', DI::dba()); - $this->lock = new DatabaseLock(DI::dba()); + $this->lock = new DatabaseLock(DI::dba()); PHPMockery::mock("Friendica\\Module", "function_exists")->with('opcache_get_status')->once()->andReturn(false); $response = (new StatsCaching(DI::l10n(), DI::baseUrl(), DI::args(), DI::logger(), DI::profiler(), DI::apiResponse(), [], $this->config, $this->cache, $this->lock, [])) @@ -137,7 +137,7 @@ class StatsCachingTest extends FixtureTestCase $this->config->shouldReceive('get')->with('system', 'stats_key')->twice()->andReturn('12345'); $this->cache = new DatabaseCache('localhost', DI::dba()); - $this->lock = new DatabaseLock(DI::dba()); + $this->lock = new DatabaseLock(DI::dba()); PHPMockery::mock("Friendica\\Module", "function_exists")->with('opcache_get_status')->once()->andReturn(true); PHPMockery::mock("Friendica\\Module", "opcache_get_status")->with(false)->once()->andReturn(false); @@ -150,10 +150,10 @@ class StatsCachingTest extends FixtureTestCase $json = json_decode($response->getBody(), true); self::assertEquals([ - 'enabled' => false, - 'hit_rate' => null, - 'used_memory' => null, - 'free_memory' => null, + 'enabled' => false, + 'hit_rate' => null, + 'used_memory' => null, + 'free_memory' => null, 'num_cached_scripts' => null, ], $json['opcache']); self::assertEquals(['type' => 'database'], $json['cache']); @@ -168,12 +168,12 @@ class StatsCachingTest extends FixtureTestCase $this->config->shouldReceive('get')->with('system', 'stats_key')->twice()->andReturn('12345'); $this->cache = new DatabaseCache('localhost', DI::dba()); - $this->lock = new DatabaseLock(DI::dba()); + $this->lock = new DatabaseLock(DI::dba()); PHPMockery::mock("Friendica\\Module", "function_exists")->with('opcache_get_status')->once()->andReturn(true); PHPMockery::mock("Friendica\\Module", "opcache_get_status")->with(false)->once()->andReturn([ - 'opcache_enabled' => true, + 'opcache_enabled' => true, 'opcache_statistics' => [ - 'opcache_hit_rate' => 1, + 'opcache_hit_rate' => 1, 'num_cached_scripts' => 2, ], 'memory_usage' => [ @@ -191,10 +191,10 @@ class StatsCachingTest extends FixtureTestCase $json = json_decode($response->getBody(), true); self::assertEquals([ - 'enabled' => true, - 'hit_rate' => 1, - 'used_memory' => 3, - 'free_memory' => 4, + 'enabled' => true, + 'hit_rate' => 1, + 'used_memory' => 3, + 'free_memory' => 4, 'num_cached_scripts' => 2, ], $json['opcache']); self::assertEquals(['type' => 'database'], $json['cache']); From c6c6640b81cf50399bdc6743f229573ee966ba05 Mon Sep 17 00:00:00 2001 From: Philipp Date: Sun, 27 Apr 2025 22:08:01 +0200 Subject: [PATCH 10/10] Fixup woodpecker --- .woodpecker/.code_standards_check.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.woodpecker/.code_standards_check.yml b/.woodpecker/.code_standards_check.yml index 1217ea3f0b0..d147e8fc698 100644 --- a/.woodpecker/.code_standards_check.yml +++ b/.woodpecker/.code_standards_check.yml @@ -43,14 +43,10 @@ steps: - apt-get update -q - DEBIAN_FRONTEND=noninteractive apt-get install -q -y git - if [ ! -z "$${CI_COMMIT_PULL_REQUEST}" ]; then - git fetch --no-tags origin ${CI_COMMIT_TARGET_BRANCH}; - CHANGED_FILES="$(git diff --name-only --diff-filter=ACMRTUXB $(git merge-base FETCH_HEAD origin/${CI_COMMIT_TARGET_BRANCH})..${CI_COMMIT_SHA})"; + git fetch --no-tags --unshallow origin ${CI_COMMIT_TARGET_BRANCH}:refs/remotes/origin/${CI_COMMIT_TARGET_BRANCH}; + CHANGED_FILES="$(git diff --name-only --diff-filter=ACMRTUXB $(git merge-base ${CI_COMMIT_SHA} origin/${CI_COMMIT_TARGET_BRANCH})..${CI_COMMIT_SHA})"; else CHANGED_FILES="$(git diff --name-only --diff-filter=ACMRTUXB ${CI_COMMIT_SHA})"; fi - - if ! echo "$${CHANGED_FILES}" | grep -qE "^(\\.php-cs-fixer(\\.dist)?\\.php|composer\\.lock)$"; then - EXTRA_ARGS=$(printf -- '--path-mode=intersection\n--\n%s' "$${CHANGED_FILES}"); - else - EXTRA_ARGS=''; - fi + - EXTRA_ARGS="--path-mode=intersection -- $${CHANGED_FILES}"; - ./bin/dev/php-cs-fixer/vendor/bin/php-cs-fixer check --config=.php-cs-fixer.dist.php -v --diff --using-cache=no $${EXTRA_ARGS}