1
0
mirror of https://github.com/danog/MadelineProto.git synced 2025-01-23 06:31:11 +01:00

Add support for pyroscope memory profiling

This commit is contained in:
Daniil Gentili 2024-05-20 17:42:04 +02:00
parent a2c759cd0b
commit 20f308c49d
6 changed files with 116 additions and 56 deletions

View File

@ -31,7 +31,7 @@ use danog\MadelineProto\EventHandler\Filter\Combinator\FiltersAnd;
use danog\MadelineProto\EventHandler\Filter\Filter; use danog\MadelineProto\EventHandler\Filter\Filter;
use danog\MadelineProto\EventHandler\Filter\FilterAllowAll; use danog\MadelineProto\EventHandler\Filter\FilterAllowAll;
use danog\MadelineProto\EventHandler\Update; use danog\MadelineProto\EventHandler\Update;
use danog\MadelineProto\Settings\Prometheus; use danog\MadelineProto\Settings\Metrics;
use Generator; use Generator;
use PhpParser\Node\Name; use PhpParser\Node\Name;
use ReflectionAttribute; use ReflectionAttribute;
@ -130,16 +130,19 @@ abstract class EventHandler extends AbstractAPI
self::cachePlugins(static::class); self::cachePlugins(static::class);
$settings ??= new SettingsEmpty; $settings ??= new SettingsEmpty;
$API = new API($session, $settings); $API = new API($session, $settings);
$prometheus = false;
if ($settings instanceof Settings) { if ($settings instanceof Settings) {
$settings = $settings->getPrometheus(); $settings = $settings->getMetrics();
} }
if ($settings instanceof Prometheus) { if ($settings instanceof Metrics
$prometheus = $settings->getReturnMetricsFromStartAndLoop(); && $settings->getReturnMetricsFromStartAndLoop()
} ) {
if (isset($_GET['metrics']) && $prometheus) { if (isset($_GET['metrics'])) {
Tools::closeConnection($API->renderPromStats()); Tools::closeConnection($API->renderPromStats());
return; return;
} elseif (isset($_GET['pprof'])) {
Tools::closeConnection($API->getMemoryProfile());
return;
}
} }
$API->startAndLoopInternal(static::class); $API->startAndLoopInternal(static::class);
} }

View File

@ -939,6 +939,13 @@ abstract class InternalDoc
{ {
return \danog\MadelineProto\Tools::getMaxMaps(); return \danog\MadelineProto\Tools::getMaxMaps();
} }
/**
* Get memory profile with memprof.
*/
final public function getMemoryProfile(): string
{
return $this->wrapper->getAPI()->getMemoryProfile();
}
/** /**
* Get TL namespaces. * Get TL namespaces.
*/ */

View File

@ -20,6 +20,7 @@ declare(strict_types=1);
namespace danog\MadelineProto; namespace danog\MadelineProto;
use Amp\ByteStream\ReadableBuffer;
use Amp\Cache\Cache; use Amp\Cache\Cache;
use Amp\Cache\LocalCache; use Amp\Cache\LocalCache;
use Amp\Cancellation; use Amp\Cancellation;
@ -65,6 +66,7 @@ use danog\MadelineProto\MTProtoTools\PasswordCalculator;
use danog\MadelineProto\MTProtoTools\PeerDatabase; use danog\MadelineProto\MTProtoTools\PeerDatabase;
use danog\MadelineProto\MTProtoTools\PeerHandler; use danog\MadelineProto\MTProtoTools\PeerHandler;
use danog\MadelineProto\MTProtoTools\ReferenceDatabase; use danog\MadelineProto\MTProtoTools\ReferenceDatabase;
use danog\MadelineProto\MTProtoTools\ResponseInfo;
use danog\MadelineProto\MTProtoTools\UpdateHandler; use danog\MadelineProto\MTProtoTools\UpdateHandler;
use danog\MadelineProto\Settings\Database\DriverDatabaseAbstract; use danog\MadelineProto\Settings\Database\DriverDatabaseAbstract;
use danog\MadelineProto\Settings\TLSchema; use danog\MadelineProto\Settings\TLSchema;
@ -543,7 +545,7 @@ final class MTProto implements TLCallback, LoggerGetter, SettingsGetter
*/ */
public function getPromGauge(string $namespace, string $name, string $help, array $labels = []): ?BetterGauge public function getPromGauge(string $namespace, string $name, string $help, array $labels = []): ?BetterGauge
{ {
if (!$this->getSettings()->getPrometheus()->getEnableCollection()) { if (!$this->getSettings()->getMetrics()->getEnablePrometheusCollection()) {
return null; return null;
} }
return GarbageCollector::$prometheus->getOrRegisterGauge( return GarbageCollector::$prometheus->getOrRegisterGauge(
@ -563,7 +565,7 @@ final class MTProto implements TLCallback, LoggerGetter, SettingsGetter
*/ */
public function getPromCounter(string $namespace, string $name, string $help, array $labels = []): ?BetterCounter public function getPromCounter(string $namespace, string $name, string $help, array $labels = []): ?BetterCounter
{ {
if (!$this->getSettings()->getPrometheus()->getEnableCollection()) { if (!$this->getSettings()->getMetrics()->getEnablePrometheusCollection()) {
return null; return null;
} }
return GarbageCollector::$prometheus->getOrRegisterCounter( return GarbageCollector::$prometheus->getOrRegisterCounter(
@ -584,7 +586,7 @@ final class MTProto implements TLCallback, LoggerGetter, SettingsGetter
*/ */
public function getPromSummary(string $namespace, string $name, string $help, array $labels = [], int $maxAgeSeconds = 600, ?array $quantiles = null): ?BetterSummary public function getPromSummary(string $namespace, string $name, string $help, array $labels = [], int $maxAgeSeconds = 600, ?array $quantiles = null): ?BetterSummary
{ {
if (!$this->getSettings()->getPrometheus()->getEnableCollection()) { if (!$this->getSettings()->getMetrics()->getEnablePrometheusCollection()) {
return null; return null;
} }
return GarbageCollector::$prometheus->getOrRegisterSummary( return GarbageCollector::$prometheus->getOrRegisterSummary(
@ -607,7 +609,7 @@ final class MTProto implements TLCallback, LoggerGetter, SettingsGetter
*/ */
public function getPromHistogram(string $namespace, string $name, string $help, array $labels = [], ?array $buckets = null): ?BetterHistogram public function getPromHistogram(string $namespace, string $name, string $help, array $labels = [], ?array $buckets = null): ?BetterHistogram
{ {
if (!$this->getSettings()->getPrometheus()->getEnableCollection()) { if (!$this->getSettings()->getMetrics()->getEnablePrometheusCollection()) {
return null; return null;
} }
return GarbageCollector::$prometheus->getOrRegisterHistogram( return GarbageCollector::$prometheus->getOrRegisterHistogram(
@ -938,7 +940,7 @@ final class MTProto implements TLCallback, LoggerGetter, SettingsGetter
'php_version' => PHP_VERSION, 'php_version' => PHP_VERSION,
'madeline_version' => API::RELEASE, 'madeline_version' => API::RELEASE,
]); ]);
$endpoint = $this->getSettings()->getPrometheus()->getMetricsBindTo(); $endpoint = $this->getSettings()->getMetrics()->getMetricsBindTo();
$this->promServer?->stop(); $this->promServer?->stop();
if ($endpoint === null) { if ($endpoint === null) {
$this->promServer = null; $this->promServer = null;
@ -955,12 +957,27 @@ final class MTProto implements TLCallback, LoggerGetter, SettingsGetter
} }
public function handleRequest(ServerRequest $request): ServerResponse public function handleRequest(ServerRequest $request): ServerResponse
{ {
if ($request->getUri()->getPath() === '/metrics') {
return new ServerResponse( return new ServerResponse(
status: HttpStatus::OK, status: HttpStatus::OK,
headers: ['Content-Type' => 'text/plain'], headers: ['Content-Type' => 'text/plain'],
body: $this->API->renderPromStats(), body: $this->API->renderPromStats(),
); );
} }
if ($request->getUri()->getPath() === '/debug/pprof') {
return new ServerResponse(
status: HttpStatus::OK,
headers: ['Content-Type' => 'text/plain'],
body: $this->API->getMemoryProfile(),
);
}
$result = ResponseInfo::error(HttpStatus::NOT_FOUND);
return new ServerResponse(
$result->getCode(),
$result->getHeaders(),
$result->getCodeExplanation()
);
}
}, new DefaultErrorHandler); }, new DefaultErrorHandler);
} }
$this->updateCtr = $this->getPromCounter("MadelineProto", "update_count", "Number of received updates since the session was created"); $this->updateCtr = $this->getPromCounter("MadelineProto", "update_count", "Number of received updates since the session was created");
@ -1317,10 +1334,10 @@ final class MTProto implements TLCallback, LoggerGetter, SettingsGetter
$this->cleanupProperties(); $this->cleanupProperties();
$this->settings->getDb()->applyChanges(); $this->settings->getDb()->applyChanges();
} }
if ($this->settings->getPrometheus()->hasChanged()) { if ($this->settings->getMetrics()->hasChanged()) {
$this->logger->logger("The prometheus settings have changed!", Logger::WARNING); $this->logger->logger("The prometheus settings have changed!", Logger::WARNING);
$this->cleanupProperties(); $this->cleanupProperties();
$this->settings->getPrometheus()->applyChanges(); $this->settings->getMetrics()->applyChanges();
} }
if ($this->settings->getSerialization()->hasChanged()) { if ($this->settings->getSerialization()->hasChanged()) {
$this->logger->logger("The serialization settings have changed!", Logger::WARNING); $this->logger->logger("The serialization settings have changed!", Logger::WARNING);
@ -1900,9 +1917,9 @@ final class MTProto implements TLCallback, LoggerGetter, SettingsGetter
} }
} }
/** /**
* Report memory profile with memprof. * Get memory profile with memprof.
*/ */
public function reportMemoryProfile(): void public function getMemoryProfile(): string
{ {
if (!\extension_loaded('memprof')) { if (!\extension_loaded('memprof')) {
throw Exception::extension('memprof'); throw Exception::extension('memprof');
@ -1910,14 +1927,24 @@ final class MTProto implements TLCallback, LoggerGetter, SettingsGetter
if (!memprof_enabled()) { if (!memprof_enabled()) {
throw new Exception("Memory profiling is not enabled, set the MEMPROF_PROFILE=1 environment variable or GET parameter to enable it."); throw new Exception("Memory profiling is not enabled, set the MEMPROF_PROFILE=1 environment variable or GET parameter to enable it.");
} }
$current = "Current memory usage: ".round(memory_get_usage()/1024/1024, 1) . " MB";
$file = fopen('php://memory', 'r+'); $file = fopen('php://memory', 'r+');
memprof_dump_pprof($file); memprof_dump_pprof($file);
fseek($file, 0); fseek($file, 0);
return stream_get_contents($file);
}
/**
* Report memory profile with memprof.
*/
public function reportMemoryProfile(): void
{
$pprof = $this->getMemoryProfile();
$current = "Current memory usage: ".round(memory_get_usage()/1024/1024, 1) . " MB";
$file = [ $file = [
'_' => 'inputMediaUploadedDocument', '_' => 'inputMediaUploadedDocument',
'file' => $file, 'file' => new ReadableBuffer($pprof),
'attributes' => [ 'attributes' => [
['_' => 'documentAttributeFilename', 'file_name' => 'report.pprof'], ['_' => 'documentAttributeFilename', 'file_name' => 'report.pprof'],
], ],

View File

@ -49,6 +49,8 @@ final class ResponseInfo
private int $code = HttpStatus::OK; private int $code = HttpStatus::OK;
/** /**
* Header array. * Header array.
*
* @var array<non-empty-string, string|list<string>>
*/ */
private array $headers = []; private array $headers = [];
/** /**
@ -124,7 +126,7 @@ final class ResponseInfo
} elseif ($size > 0) { } elseif ($size > 0) {
$this->headers['Content-Length'] = (string) $size; $this->headers['Content-Length'] = (string) $size;
} }
$this->headers['Content-Type'] = $messageMedia['mime']; $this->headers['Content-Type'] = (string) $messageMedia['mime'];
$this->headers['Cache-Control'] = 'max-age=31556926'; $this->headers['Cache-Control'] = 'max-age=31556926';
$this->headers['Content-Transfer-Encoding'] = 'Binary'; $this->headers['Content-Transfer-Encoding'] = 'Binary';
$this->headers['Accept-Ranges'] = 'bytes'; $this->headers['Accept-Ranges'] = 'bytes';
@ -207,7 +209,7 @@ final class ResponseInfo
/** /**
* Get header array. * Get header array.
* *
* @return array Header array * @return array<non-empty-string, string|list<string>> Header array
*/ */
public function getHeaders(): array public function getHeaders(): array
{ {

View File

@ -24,8 +24,8 @@ use danog\MadelineProto\Settings\DatabaseAbstract;
use danog\MadelineProto\Settings\Files; use danog\MadelineProto\Settings\Files;
use danog\MadelineProto\Settings\Ipc; use danog\MadelineProto\Settings\Ipc;
use danog\MadelineProto\Settings\Logger; use danog\MadelineProto\Settings\Logger;
use danog\MadelineProto\Settings\Metrics;
use danog\MadelineProto\Settings\Peer; use danog\MadelineProto\Settings\Peer;
use danog\MadelineProto\Settings\Prometheus;
use danog\MadelineProto\Settings\RPC; use danog\MadelineProto\Settings\RPC;
use danog\MadelineProto\Settings\SecretChats; use danog\MadelineProto\Settings\SecretChats;
use danog\MadelineProto\Settings\Serialization; use danog\MadelineProto\Settings\Serialization;
@ -55,9 +55,9 @@ final class Settings extends SettingsAbstract
*/ */
protected Files $files; protected Files $files;
/** /**
* Prometheus settings. * Metrics settings.
*/ */
protected Prometheus $prometheus; protected Metrics $metrics;
/** /**
* IPC server settings. * IPC server settings.
*/ */
@ -110,7 +110,7 @@ final class Settings extends SettingsAbstract
$this->files = new Files; $this->files = new Files;
$this->logger = new Logger; $this->logger = new Logger;
$this->peer = new Peer; $this->peer = new Peer;
$this->prometheus = new Prometheus; $this->metrics = new Metrics;
$this->rpc = new RPC; $this->rpc = new RPC;
$this->secretChats = new SecretChats; $this->secretChats = new SecretChats;
$this->serialization = new Serialization; $this->serialization = new Serialization;
@ -125,8 +125,8 @@ final class Settings extends SettingsAbstract
if (!isset($this->voip)) { if (!isset($this->voip)) {
$this->voip = new VoIP; $this->voip = new VoIP;
} }
if (!isset($this->prometheus)) { if (!isset($this->metrics)) {
$this->prometheus = new Prometheus; $this->metrics = new Metrics;
} }
} }
/** /**
@ -145,8 +145,8 @@ final class Settings extends SettingsAbstract
$this->connection->merge($settings); $this->connection->merge($settings);
} elseif ($settings instanceof Files) { } elseif ($settings instanceof Files) {
$this->files->merge($settings); $this->files->merge($settings);
} elseif ($settings instanceof Prometheus) { } elseif ($settings instanceof Metrics) {
$this->prometheus->merge($settings); $this->metrics->merge($settings);
} elseif ($settings instanceof Logger) { } elseif ($settings instanceof Logger) {
$this->logger->merge($settings); $this->logger->merge($settings);
} elseif ($settings instanceof Peer) { } elseif ($settings instanceof Peer) {
@ -178,7 +178,7 @@ final class Settings extends SettingsAbstract
$this->auth->merge($settings->auth); $this->auth->merge($settings->auth);
$this->connection->merge($settings->connection); $this->connection->merge($settings->connection);
$this->files->merge($settings->files); $this->files->merge($settings->files);
$this->prometheus->merge($settings->prometheus); $this->metrics->merge($settings->metrics);
$this->logger->merge($settings->logger); $this->logger->merge($settings->logger);
$this->peer->merge($settings->peer); $this->peer->merge($settings->peer);
$this->rpc->merge($settings->rpc); $this->rpc->merge($settings->rpc);
@ -277,21 +277,21 @@ final class Settings extends SettingsAbstract
} }
/** /**
* Get prometheus settings. * Get metrics settings.
*/ */
public function getPrometheus(): Prometheus public function getMetrics(): Metrics
{ {
return $this->prometheus; return $this->metrics;
} }
/** /**
* Set prometheus settings. * Set metrics settings.
* *
* @param Prometheus $prometheus File management settings. * @param Metrics $metrics File management settings.
*/ */
public function setPrometheus(Prometheus $prometheus): self public function setMetrics(Metrics $metrics): self
{ {
$this->prometheus = $prometheus; $this->metrics = $metrics;
return $this; return $this;
} }

View File

@ -20,25 +20,30 @@ use Amp\Socket\SocketAddress;
use danog\MadelineProto\SettingsAbstract; use danog\MadelineProto\SettingsAbstract;
/** /**
* Prometheus settings. * Metric settings.
*/ */
final class Prometheus extends SettingsAbstract final class Metrics extends SettingsAbstract
{ {
/** /**
* Whether to enable prometheus stat collection for this session. * Whether to enable additional prometheus stat collection for this session.
*/ */
protected bool $enableCollection = false; protected bool $enablePrometheusCollection = false;
/** /**
* Whether to expose prometheus metrics on the specified endpoint via HTTP. * Whether to enable memprof memory stat collection for this session.
*/
protected bool $enableMemprofCollection = false;
/**
* Whether to expose metrics on the specified endpoint via HTTP.
*/ */
protected ?SocketAddress $metricsBindTo = null; protected ?SocketAddress $metricsBindTo = null;
/** /**
* Whether to expose prometheus metrics with startAndLoop, by providing a ?metrics query string. * Whether to expose metrics with startAndLoop, by providing a ?metrics or ?pprof query string.
*/ */
protected bool $returnMetricsFromStartAndLoop = false; protected bool $returnMetricsFromStartAndLoop = false;
/** /**
* Whether to expose prometheus metrics with startAndLoop, by providing a ?metrics query string. * Whether to expose prometheus/memprof metrics with startAndLoop, by providing a ?metrics or ?pprof query string.
*/ */
public function setReturnMetricsFromStartAndLoop(bool $enable): self public function setReturnMetricsFromStartAndLoop(bool $enable): self
{ {
@ -46,7 +51,7 @@ final class Prometheus extends SettingsAbstract
return $this; return $this;
} }
/** /**
* Whether to expose prometheus metrics with startAndLoop, by providing a ?metrics query string. * Whether to expose prometheus/memprof metrics with startAndLoop, by providing a ?metrics or ?pprof query string.
*/ */
public function getReturnMetricsFromStartAndLoop(): bool public function getReturnMetricsFromStartAndLoop(): bool
{ {
@ -56,21 +61,37 @@ final class Prometheus extends SettingsAbstract
/** /**
* Whether to enable additional prometheus stat collection for this session. * Whether to enable additional prometheus stat collection for this session.
*/ */
public function setEnableCollection(bool $enable): self public function setEnablePrometheusCollection(bool $enable): self
{ {
$this->enableCollection = $enable; $this->enablePrometheusCollection = $enable;
return $this; return $this;
} }
/** /**
* Whether additional prometheus stat collection is enabled for this session. * Whether additional prometheus stat collection is enabled for this session.
*/ */
public function getEnableCollection(): bool public function getEnablePrometheusCollection(): bool
{ {
return $this->enableCollection; return $this->enablePrometheusCollection;
} }
/** /**
* Whether to expose prometheus metrics on the specified endpoint via HTTP. * Whether to enable memprof memory stat collection for this session.
*/
public function setEnableMemprofCollection(bool $enable): self
{
$this->enableMemprofCollection = $enable;
return $this;
}
/**
* Whether to enable memprof memory stat collection for this session.
*/
public function getEnableMemprofCollection(): bool
{
return $this->enableMemprofCollection;
}
/**
* Whether to expose metrics on the specified endpoint via HTTP.
*/ */
public function setMetricsBindTo(?SocketAddress $metricsBindTo): self public function setMetricsBindTo(?SocketAddress $metricsBindTo): self
{ {
@ -79,7 +100,7 @@ final class Prometheus extends SettingsAbstract
} }
/** /**
* Whether to expose prometheus metrics on the specified endpoint via HTTP. * Whether to expose metrics on the specified endpoint via HTTP.
*/ */
public function getMetricsBindTo(): ?SocketAddress public function getMetricsBindTo(): ?SocketAddress
{ {