diff --git a/src/GarbageCollector.php b/src/GarbageCollector.php index df5d59517..49ff66514 100644 --- a/src/GarbageCollector.php +++ b/src/GarbageCollector.php @@ -63,9 +63,11 @@ final class GarbageCollector } self::$started = true; - EventLoop::unreference(EventLoop::repeat(1, static function (): void { + $counter = Magic::getCounter("", "explicit_gc_count", "Number of times the GC was explicitly invoked", []); + EventLoop::unreference(EventLoop::repeat(1, static function () use ($counter): void { $currentMemory = self::getMemoryConsumption(); if ($currentMemory > self::$memoryConsumption + self::$memoryDiffMb) { + $counter(); gc_collect_cycles(); self::$memoryConsumption = self::getMemoryConsumption(); /*self::$memoryConsumption = self::getMemoryConsumption(); diff --git a/src/InternalDoc.php b/src/InternalDoc.php index 4614462b9..1b455d002 100644 --- a/src/InternalDoc.php +++ b/src/InternalDoc.php @@ -994,6 +994,46 @@ abstract class InternalDoc { return $this->wrapper->getAPI()->getPlugin($class); } + /** + * Returns a closure linked to the specified prometheus counter. + * + * @return Closure(): void Call to increment the counter + */ + final public function getPromCounter(string $namespace, string $name, string $help, array $labels = [ + ]): \Closure + { + return $this->wrapper->getAPI()->getPromCounter($namespace, $name, $help, $labels); + } + /** + * Returns a closure linked to the specified prometheus gauge. + * + * @return Closure(float): void + */ + final public function getPromGauge(string $namespace, string $name, string $help, array $labels = [ + ]): \Closure + { + return $this->wrapper->getAPI()->getPromGauge($namespace, $name, $help, $labels); + } + /** + * Returns a closure linked to the specified prometheus histogram. + * + * @return Closure(float): void + */ + final public function getPromHistogram(string $namespace, string $name, string $help, $labels = [ + ], ?array $buckets = null): \Closure + { + return $this->wrapper->getAPI()->getPromHistogram($namespace, $name, $help, $labels, $buckets); + } + /** + * Returns a closure linked to the specified prometheus summary. + * + * @return Closure(float): void + */ + final public function getPromSummary(string $namespace, string $name, string $help, $labels = [ + ], int $maxAgeSeconds = 600, ?array $quantiles = null): \Closure + { + return $this->wrapper->getAPI()->getPromSummary($namespace, $name, $help, $labels, $maxAgeSeconds, $quantiles); + } /** * Gets info of the propic of a user. */ diff --git a/src/Ipc/AbstractServer.php b/src/Ipc/AbstractServer.php index afc22e9fc..492e2ef0f 100644 --- a/src/Ipc/AbstractServer.php +++ b/src/Ipc/AbstractServer.php @@ -25,12 +25,14 @@ use Amp\DeferredFuture; use Amp\Future; use Amp\Ipc\IpcServer; use Amp\Ipc\Sync\ChannelledSocket; +use Closure; use danog\Loop\Loop; use danog\MadelineProto\Exception; use danog\MadelineProto\Ipc\Runner\ProcessRunner; use danog\MadelineProto\Ipc\Runner\WebRunner; use danog\MadelineProto\Logger; use danog\MadelineProto\Loop\InternalLoop; +use danog\MadelineProto\MTProto; use danog\MadelineProto\SessionPaths; use danog\MadelineProto\Settings\Ipc; use danog\MadelineProto\Shutdown; @@ -48,7 +50,18 @@ use function Amp\delay; */ abstract class AbstractServer extends Loop { - use InternalLoop; + use InternalLoop { + __construct as private internalInit; + } + /** + * @var Closure(int): void + */ + private Closure $connectionGauge; + public function __construct(MTProto $API) + { + $this->internalInit($API); + $this->connectionGauge = $API->getPromGauge("", "ipc_server_connections", "Number of IPC server connections"); + } /** * Server version. */ @@ -204,6 +217,7 @@ abstract class AbstractServer extends Loop { $this->API->waitForInit(); $this->API->logger('Accepted IPC client connection!'); + ($this->connectionGauge)(1); $id = 0; $payload = null; @@ -214,6 +228,7 @@ abstract class AbstractServer extends Loop } catch (Throwable $e) { Logger::log("Exception in IPC connection: $e"); } finally { + ($this->connectionGauge)(-1); EventLoop::queue(function () use ($socket, $payload): void { try { $socket->disconnect(); diff --git a/src/Ipc/Server.php b/src/Ipc/Server.php index b19ddfce5..adb1e47ba 100644 --- a/src/Ipc/Server.php +++ b/src/Ipc/Server.php @@ -20,13 +20,12 @@ declare(strict_types=1); namespace danog\MadelineProto\Ipc; -use danog\MadelineProto\MTProto; - /** * IPC server. * * @internal */ -final class Server extends AbstractServer { +final class Server extends AbstractServer +{ -} \ No newline at end of file +} diff --git a/src/Ipc/ServerCallback.php b/src/Ipc/ServerCallback.php index ed2332391..9366d8985 100644 --- a/src/Ipc/ServerCallback.php +++ b/src/Ipc/ServerCallback.php @@ -31,7 +31,7 @@ use Revolt\EventLoop; * * @internal */ -final class ServerCallback extends Server +final class ServerCallback extends AbstractServer { /** * Timeout watcher list, indexed by socket ID. diff --git a/src/MTProto.php b/src/MTProto.php index 3522d5905..32c4e413a 100644 --- a/src/MTProto.php +++ b/src/MTProto.php @@ -32,6 +32,7 @@ use Amp\Http\Client\Request; use Amp\SignalException; use Amp\Sync\LocalKeyedMutex; use Amp\Sync\LocalMutex; +use Closure; use danog\AsyncOrm\Annotations\OrmMappedArray; use danog\AsyncOrm\DbArray; use danog\AsyncOrm\DbArrayBuilder; @@ -73,6 +74,10 @@ use danog\MadelineProto\Wrappers\Events; use danog\MadelineProto\Wrappers\Login; use danog\MadelineProto\Wrappers\Loop; use danog\MadelineProto\Wrappers\Start; +use Prometheus\Counter; +use Prometheus\Gauge; +use Prometheus\Histogram; +use Prometheus\Summary; use Psr\Log\LoggerInterface; use Revolt\EventLoop; use SplQueue; @@ -506,6 +511,77 @@ final class MTProto implements TLCallback, LoggerGetter, SettingsGetter } } + /** + * Returns a closure linked to the specified prometheus gauge. + * + * @internal + * + * @return Closure(int): void + */ + public function getPromGauge(string $namespace, string $name, string $help, array $labels = []): Closure + { + return Magic::getGauge( + $namespace, + $name, + $help, + $labels + ['session' => $this->getSessionName(), 'session_id' => (string) ($this->getSelf()['id'] ?? '')], + ); + } + + /** + * Returns a closure linked to the specified prometheus counter. + * + * @internal + * + * @return Closure(): void Call to increment the counter + */ + public function getPromCounter(string $namespace, string $name, string $help, array $labels = []): Closure + { + return Magic::getCounter( + $namespace, + $name, + $help, + $labels + ['session' => $this->getSessionName(), 'session_id' => (string) ($this->getSelf()['id'] ?? '')], + ); + } + + /** + * Returns a closure linked to the specified prometheus summary. + * + * @internal + * + * @return Closure(float): void + */ + public function getPromSummary(string $namespace, string $name, string $help, $labels = [], int $maxAgeSeconds = 600, ?array $quantiles = null): Closure + { + return Magic::getSummary( + $namespace, + $name, + $help, + $labels + ['session' => $this->getSessionName(), 'session_id' => (string) ($this->getSelf()['id'] ?? '')], + $maxAgeSeconds, + $quantiles + ); + } + + /** + * Returns a closure linked to the specified prometheus histogram. + * + * @internal + * + * @return Closure(float): void + */ + public function getPromHistogram(string $namespace, string $name, string $help, $labels = [], ?array $buckets = null): Closure + { + return Magic::getHistogram( + $namespace, + $name, + $help, + $labels + ['session' => $this->getSessionName(), 'session_id' => (string) ($this->getSelf()['id'] ?? '')], + $buckets + ); + } + /** * Initialization function. * diff --git a/src/Magic.php b/src/Magic.php index e0840b9d5..ce80c13be 100644 --- a/src/Magic.php +++ b/src/Magic.php @@ -22,6 +22,7 @@ namespace danog\MadelineProto; use Amp\DeferredFuture; use Amp\SignalException; +use Closure; use danog\MadelineProto\TL\Conversion\Extension; use phpseclib3\Math\BigInteger; use Prometheus\CollectorRegistry; @@ -206,7 +207,8 @@ final class Magic * Whether there's a basedir limitation. */ public static bool $hasBasedirLimitation = false; - public static CollectorRegistry $prometheus; + private static CollectorRegistry $prometheus; + private static array $promLabels; /** * Encoded emojis. * @@ -341,16 +343,72 @@ final class Magic } } self::$prometheus = new CollectorRegistry(new InMemory); - $alloc = self::$prometheus->registerGauge("", "php_memstats_alloc_bytes", "RAM allocated by the PHP memory pool", ["pid"]); - $inuse = self::$prometheus->registerGauge("", "php_memstats_inuse_bytes", "RAM actually used by PHP", ["pid"]); - $labels = self::$pid ? [(string) self::$pid] : []; - EventLoop::repeat(1.0, function () use ($alloc, $inuse, $labels): void { - $alloc->set((float) memory_get_usage(true), $labels); - $inuse->set((float) memory_get_usage(false), $labels); - }); + self::$promLabels = ['release' => API::RELEASE]; + if (self::$pid !== null) { + self::$promLabels['pid'] = (string) self::$pid; + } + + $alloc = self::getGauge("", "php_memstats_alloc_bytes", "RAM allocated by the PHP memory pool", []); + $inuse = self::getGauge("", "php_memstats_inuse_bytes", "RAM actually used by PHP", []); + EventLoop::unreference(EventLoop::repeat(1.0, static function () use ($alloc, $inuse): void { + $alloc((float) memory_get_usage(true)); + $inuse((float) memory_get_usage(false)); + })); GarbageCollector::start(); self::$inited = true; } + /** + * @param array $labels + * @return Closure(int): void + */ + public static function getGauge(string $namespace, string $name, string $help, array $labels): Closure + { + $labels += self::$promLabels; + $gauge = self::$prometheus->getOrRegisterGauge($namespace, $name, $help, array_keys($labels)); + $labels = array_values($labels); + return static function (int $by) use ($labels, $gauge): void { + $gauge->incBy($by); + }; + } + /** + * @param array $labels + * @return Closure(): void + */ + public static function getCounter(string $namespace, string $name, string $help, array $labels): Closure + { + $labels += self::$promLabels; + $gauge = self::$prometheus->getOrRegisterCounter($namespace, $name, $help, array_keys($labels)); + $labels = array_values($labels); + return static function () use ($labels, $gauge): void { + $gauge->inc($labels); + }; + } + /** + * @param array $labels + * @return Closure(float): void + */ + public static function getHistogram(string $namespace, string $name, string $help, array $labels, ?array $buckets = null): Closure + { + $labels += self::$promLabels; + $gauge = self::$prometheus->getOrRegisterHistogram($namespace, $name, $help, array_keys($labels), $buckets); + $labels = array_values($labels); + return static function ($value) use ($labels, $gauge): void { + $gauge->observe($value, $labels); + }; + } + /** + * @param array $labels + * @return Closure(float): void + */ + public static function getSummary(string $namespace, string $name, string $help, array $labels, int $maxAgeSeconds = 600, ?array $quantiles = null): Closure + { + $labels += self::$promLabels; + $gauge = self::$prometheus->getOrRegisterSummary($namespace, $name, $help, array_keys($labels), $maxAgeSeconds, $quantiles); + $labels = array_values($labels); + return static function ($value) use ($labels, $gauge): void { + $gauge->observe($value, $labels); + }; + } /** * Check if this is a POSIX fork of the main PHP process. */