Skip to content

Commit adf61a9

Browse files
committed
fix(metrics): avoid leaking scrape connections
Run metrics scrape queries through a cloned force-sync Manticore client so the shared async client does not retain one internal 127.0.0.1:9312 socket per /metrics request. Related issue #686
1 parent 880f6cc commit adf61a9

3 files changed

Lines changed: 130 additions & 2 deletions

File tree

src/Plugin/Metrics/Handler.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ public function run(): Task {
5252
$definitions = $decoded;
5353
$store = new MetricStore($definitions);
5454
$context = new MetricsScrapeContext();
55-
$settings = $client->getSettings();
55+
$metricsClient = clone $client;
56+
$metricsClient->setForceSync(true);
57+
$settings = $metricsClient->getSettings();
5658
$context->settings = [
5759
'searchd.data_dir' => (string)($settings->searchdDataDir ?? ''),
5860
'searchd.binlog_path' => (string)($settings->searchdBinlogPath ?? ''),
@@ -71,7 +73,7 @@ public function run(): Task {
7173

7274
foreach ($collectors as $collector) {
7375
try {
74-
$collector->collect($client, $store, $context);
76+
$collector->collect($metricsClient, $store, $context);
7577
} catch (ManticoreSearchClientError | ManticoreSearchResponseError $e) {
7678
Buddy::warning('Metrics: collector failed (' . $collector::class . '): ' . $e->getMessage());
7779

test/Plugin/Metrics/MetricsContentTypeTest.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@
99
program; if you did not, you can find it at http://www.gnu.org/
1010
*/
1111

12+
use Manticoresearch\Buddy\Base\Plugin\Metrics\Handler;
1213
use Manticoresearch\Buddy\Base\Plugin\Metrics\Payload;
1314
use Manticoresearch\Buddy\Core\ManticoreSearch\Endpoint;
1415
use Manticoresearch\Buddy\Core\ManticoreSearch\RequestFormat;
1516
use Manticoresearch\Buddy\Core\Network\Request;
1617
use Manticoresearch\Buddy\Core\Task\TaskResult;
1718
use Manticoresearch\Buddy\Core\Tool\Buddy;
19+
use Manticoresearch\BuddyTest\Plugin\Metrics\RecordingMetricsClient;
1820
use PHPUnit\Framework\TestCase;
21+
use Swoole\Event;
1922

2023
/**
2124
* Unit test for Metrics plugin content type verification
@@ -112,6 +115,37 @@ public function testPayloadFromRequest(): void {
112115
*
113116
* @return void
114117
*/
118+
public function testMetricsHandlerUsesCloseOnResponseClientForInternalScrapeQueries(): void {
119+
RecordingMetricsClient::reset();
120+
121+
$payload = new Payload();
122+
$handler = new Handler($payload);
123+
$client = new RecordingMetricsClient('original');
124+
$handler->setManticoreClient($client);
125+
126+
$failure = null;
127+
go(
128+
static function () use ($handler, &$failure): void {
129+
try {
130+
$task = $handler->run();
131+
$task->wait(true);
132+
} catch (Throwable $e) {
133+
$failure = $e;
134+
}
135+
}
136+
);
137+
Event::wait();
138+
139+
if ($failure instanceof Throwable) {
140+
throw $failure;
141+
}
142+
143+
$this->assertSame(0, $client->sendRequestCount, 'The shared async client must not handle metrics scrape SQL');
144+
$this->assertInstanceOf(RecordingMetricsClient::class, RecordingMetricsClient::$lastClone);
145+
$this->assertTrue(RecordingMetricsClient::$lastClone->forceSyncWasEnabled);
146+
$this->assertGreaterThan(0, RecordingMetricsClient::$lastClone->sendRequestCount);
147+
}
148+
115149
public function testMetricsEndpointEnumValue(): void {
116150
$this->assertEquals('metrics', Endpoint::Metrics->value);
117151
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php declare(strict_types=1);
2+
3+
/*
4+
Copyright (c) 2024, Manticore Software LTD (https://manticoresearch.com)
5+
6+
This program is free software; you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License version 3 or any later
8+
version. You should have received a copy of the GPL license along with this
9+
program; if you did not, you can find it at http://www.gnu.org/
10+
*/
11+
12+
namespace Manticoresearch\BuddyTest\Plugin\Metrics;
13+
14+
use Manticoresearch\Buddy\Core\ManticoreSearch\Client;
15+
use Manticoresearch\Buddy\Core\ManticoreSearch\Response;
16+
use Manticoresearch\Buddy\Core\ManticoreSearch\Settings;
17+
18+
final class RecordingMetricsClient extends Client {
19+
20+
public static ?self $lastClone = null;
21+
public int $sendRequestCount = 0;
22+
public bool $forceSyncWasEnabled = false;
23+
24+
/** @var array<int, string> */
25+
public array $queries = [];
26+
27+
public function __construct(public string $name) {
28+
}
29+
30+
public static function reset(): void {
31+
self::$lastClone = null;
32+
}
33+
34+
public function __clone() {
35+
$this->name = 'clone';
36+
$this->sendRequestCount = 0;
37+
$this->queries = [];
38+
self::$lastClone = $this;
39+
}
40+
41+
public function setForceSync(bool $value = true): static {
42+
$this->forceSyncWasEnabled = $value;
43+
return parent::setForceSync($value);
44+
}
45+
46+
public function getSettings(): Settings {
47+
$settings = new Settings();
48+
$settings->searchdDataDir = '';
49+
$settings->searchdBinlogPath = '';
50+
$settings->searchdLog = '';
51+
return $settings;
52+
}
53+
54+
public function sendRequest(
55+
string $request,
56+
?string $path = null,
57+
bool $disableAgentHeader = false,
58+
string $requestMethod = 'POST',
59+
): Response {
60+
unset($path, $disableAgentHeader, $requestMethod);
61+
62+
++$this->sendRequestCount;
63+
$this->queries[] = $request;
64+
65+
return Response::fromBody(json_encode([$this->responseFor($request)], JSON_THROW_ON_ERROR));
66+
}
67+
68+
/**
69+
* @return array{data: array<int, array<string, mixed>>, error: string, warning: string, total: int}
70+
*/
71+
private function responseFor(string $request): array {
72+
$data = match ($request) {
73+
'SHOW THREADS' => [
74+
['Name' => 'work_0', 'This/prev job time' => '0us', 'Info' => ''],
75+
],
76+
'SHOW STATUS' => [
77+
['Counter' => 'uptime', 'Value' => '1'],
78+
['Counter' => 'connections', 'Value' => '1'],
79+
['Counter' => 'version', 'Value' => 'Manticore 0.0.0 test'],
80+
],
81+
'SHOW TABLES' => [],
82+
default => [],
83+
};
84+
85+
return [
86+
'data' => $data,
87+
'error' => '',
88+
'warning' => '',
89+
'total' => sizeof($data),
90+
];
91+
}
92+
}

0 commit comments

Comments
 (0)