1
0
mirror of https://github.com/danog/MadelineProto.git synced 2025-01-23 00:51:12 +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\FilterAllowAll;
use danog\MadelineProto\EventHandler\Update;
use danog\MadelineProto\Settings\Prometheus;
use danog\MadelineProto\Settings\Metrics;
use Generator;
use PhpParser\Node\Name;
use ReflectionAttribute;
@ -130,16 +130,19 @@ abstract class EventHandler extends AbstractAPI
self::cachePlugins(static::class);
$settings ??= new SettingsEmpty;
$API = new API($session, $settings);
$prometheus = false;
if ($settings instanceof Settings) {
$settings = $settings->getPrometheus();
$settings = $settings->getMetrics();
}
if ($settings instanceof Prometheus) {
$prometheus = $settings->getReturnMetricsFromStartAndLoop();
}
if (isset($_GET['metrics']) && $prometheus) {
Tools::closeConnection($API->renderPromStats());
return;
if ($settings instanceof Metrics
&& $settings->getReturnMetricsFromStartAndLoop()
) {
if (isset($_GET['metrics'])) {
Tools::closeConnection($API->renderPromStats());
return;
} elseif (isset($_GET['pprof'])) {
Tools::closeConnection($API->getMemoryProfile());
return;
}
}
$API->startAndLoopInternal(static::class);
}

View File

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

View File

@ -20,6 +20,7 @@ declare(strict_types=1);
namespace danog\MadelineProto;
use Amp\ByteStream\ReadableBuffer;
use Amp\Cache\Cache;
use Amp\Cache\LocalCache;
use Amp\Cancellation;
@ -65,6 +66,7 @@ use danog\MadelineProto\MTProtoTools\PasswordCalculator;
use danog\MadelineProto\MTProtoTools\PeerDatabase;
use danog\MadelineProto\MTProtoTools\PeerHandler;
use danog\MadelineProto\MTProtoTools\ReferenceDatabase;
use danog\MadelineProto\MTProtoTools\ResponseInfo;
use danog\MadelineProto\MTProtoTools\UpdateHandler;
use danog\MadelineProto\Settings\Database\DriverDatabaseAbstract;
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
{
if (!$this->getSettings()->getPrometheus()->getEnableCollection()) {
if (!$this->getSettings()->getMetrics()->getEnablePrometheusCollection()) {
return null;
}
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
{
if (!$this->getSettings()->getPrometheus()->getEnableCollection()) {
if (!$this->getSettings()->getMetrics()->getEnablePrometheusCollection()) {
return null;
}
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
{
if (!$this->getSettings()->getPrometheus()->getEnableCollection()) {
if (!$this->getSettings()->getMetrics()->getEnablePrometheusCollection()) {
return null;
}
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
{
if (!$this->getSettings()->getPrometheus()->getEnableCollection()) {
if (!$this->getSettings()->getMetrics()->getEnablePrometheusCollection()) {
return null;
}
return GarbageCollector::$prometheus->getOrRegisterHistogram(
@ -938,7 +940,7 @@ final class MTProto implements TLCallback, LoggerGetter, SettingsGetter
'php_version' => PHP_VERSION,
'madeline_version' => API::RELEASE,
]);
$endpoint = $this->getSettings()->getPrometheus()->getMetricsBindTo();
$endpoint = $this->getSettings()->getMetrics()->getMetricsBindTo();
$this->promServer?->stop();
if ($endpoint === null) {
$this->promServer = null;
@ -955,10 +957,25 @@ final class MTProto implements TLCallback, LoggerGetter, SettingsGetter
}
public function handleRequest(ServerRequest $request): ServerResponse
{
if ($request->getUri()->getPath() === '/metrics') {
return new ServerResponse(
status: HttpStatus::OK,
headers: ['Content-Type' => 'text/plain'],
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(
status: HttpStatus::OK,
headers: ['Content-Type' => 'text/plain'],
body: $this->API->renderPromStats(),
$result->getCode(),
$result->getHeaders(),
$result->getCodeExplanation()
);
}
}, new DefaultErrorHandler);
@ -1317,10 +1334,10 @@ final class MTProto implements TLCallback, LoggerGetter, SettingsGetter
$this->cleanupProperties();
$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->cleanupProperties();
$this->settings->getPrometheus()->applyChanges();
$this->settings->getMetrics()->applyChanges();
}
if ($this->settings->getSerialization()->hasChanged()) {
$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')) {
throw Exception::extension('memprof');
@ -1910,14 +1927,24 @@ final class MTProto implements TLCallback, LoggerGetter, SettingsGetter
if (!memprof_enabled()) {
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+');
memprof_dump_pprof($file);
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 = [
'_' => 'inputMediaUploadedDocument',
'file' => $file,
'file' => new ReadableBuffer($pprof),
'attributes' => [
['_' => 'documentAttributeFilename', 'file_name' => 'report.pprof'],
],

View File

@ -49,6 +49,8 @@ final class ResponseInfo
private int $code = HttpStatus::OK;
/**
* Header array.
*
* @var array<non-empty-string, string|list<string>>
*/
private array $headers = [];
/**
@ -124,7 +126,7 @@ final class ResponseInfo
} elseif ($size > 0) {
$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['Content-Transfer-Encoding'] = 'Binary';
$this->headers['Accept-Ranges'] = 'bytes';
@ -207,7 +209,7 @@ final class ResponseInfo
/**
* Get header array.
*
* @return array Header array
* @return array<non-empty-string, string|list<string>> Header 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\Ipc;
use danog\MadelineProto\Settings\Logger;
use danog\MadelineProto\Settings\Metrics;
use danog\MadelineProto\Settings\Peer;
use danog\MadelineProto\Settings\Prometheus;
use danog\MadelineProto\Settings\RPC;
use danog\MadelineProto\Settings\SecretChats;
use danog\MadelineProto\Settings\Serialization;
@ -55,9 +55,9 @@ final class Settings extends SettingsAbstract
*/
protected Files $files;
/**
* Prometheus settings.
* Metrics settings.
*/
protected Prometheus $prometheus;
protected Metrics $metrics;
/**
* IPC server settings.
*/
@ -110,7 +110,7 @@ final class Settings extends SettingsAbstract
$this->files = new Files;
$this->logger = new Logger;
$this->peer = new Peer;
$this->prometheus = new Prometheus;
$this->metrics = new Metrics;
$this->rpc = new RPC;
$this->secretChats = new SecretChats;
$this->serialization = new Serialization;
@ -125,8 +125,8 @@ final class Settings extends SettingsAbstract
if (!isset($this->voip)) {
$this->voip = new VoIP;
}
if (!isset($this->prometheus)) {
$this->prometheus = new Prometheus;
if (!isset($this->metrics)) {
$this->metrics = new Metrics;
}
}
/**
@ -145,8 +145,8 @@ final class Settings extends SettingsAbstract
$this->connection->merge($settings);
} elseif ($settings instanceof Files) {
$this->files->merge($settings);
} elseif ($settings instanceof Prometheus) {
$this->prometheus->merge($settings);
} elseif ($settings instanceof Metrics) {
$this->metrics->merge($settings);
} elseif ($settings instanceof Logger) {
$this->logger->merge($settings);
} elseif ($settings instanceof Peer) {
@ -178,7 +178,7 @@ final class Settings extends SettingsAbstract
$this->auth->merge($settings->auth);
$this->connection->merge($settings->connection);
$this->files->merge($settings->files);
$this->prometheus->merge($settings->prometheus);
$this->metrics->merge($settings->metrics);
$this->logger->merge($settings->logger);
$this->peer->merge($settings->peer);
$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;
}

View File

@ -20,25 +20,30 @@ use Amp\Socket\SocketAddress;
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;
/**
* 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;
/**
* 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
{
@ -46,7 +51,7 @@ final class Prometheus extends SettingsAbstract
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
{
@ -56,21 +61,37 @@ final class Prometheus extends SettingsAbstract
/**
* 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;
}
/**
* 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
{
@ -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
{