Skip to content

Commit 5b3f83a

Browse files
committed
perf: speedup redis histogram storage
1 parent 9d0a066 commit 5b3f83a

File tree

13 files changed

+384
-7
lines changed

13 files changed

+384
-7
lines changed

.gitattributes

+1
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@
1313
/phpstan.neon export-ignore
1414
/.releaserc.json export-ignore
1515
/.cspell.json export-ignore
16+
/phpbench.json export-ignore

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@ composer.lock
66
phpunit.xml
77
.phpunit.result.cache
88
junit.xml
9+
10+
# PHPBench
11+
.phpbench/

composer.json

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"ergebnis/composer-normalize": "dev-main",
2121
"mockery/mockery": "^1.5",
2222
"orchestra/testbench": "^8.5 || ^9.0",
23+
"phpbench/phpbench": "^1.3",
2324
"phpcompatibility/php-compatibility": "^9.3",
2425
"phpmd/phpmd": "^2.13",
2526
"phpstan/extension-installer": "^1.3",

phpbench.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"$schema": "./vendor/phpbench/phpbench/phpbench.schema.json",
3+
"runner.bootstrap": "tests/Laravel/bootstrap.php"
4+
}

src/Storage/RedisStorage/HistogramRedisStorage.php

+18-7
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,24 @@ public function updateHistogram(UpdateHistogram $command): void
5252
}
5353
}
5454

55-
// TODO: Might be optimized by using EVAL with prepared LUA script. We have to add a performance test for this.
56-
$this->connection->command('HINCRBY', [self::HISTOGRAM_COUNT_HASH_NAME, $keyWithLabels, 1]);
57-
$this->connection->command('HINCRBYFLOAT', [self::HISTOGRAM_SUM_HASH_NAME, $keyWithLabels, $command->value]);
58-
59-
foreach ($bucketsToUpdate as $bucket) {
60-
$this->connection->command('HINCRBY', [$metricHashName, $bucket, 1]);
61-
}
55+
$this->connection->command('EVAL', [
56+
<<<LUA
57+
redis.call('HINCRBY', KEYS[1], ARGV[1], 1);
58+
redis.call('HINCRBYFLOAT', KEYS[2], ARGV[1], ARGV[2]);
59+
for i = 3, #ARGV do
60+
redis.call('HINCRBY', KEYS[3], ARGV[i], 1)
61+
end
62+
LUA,
63+
[
64+
self::HISTOGRAM_COUNT_HASH_NAME,
65+
self::HISTOGRAM_SUM_HASH_NAME,
66+
$metricHashName,
67+
$keyWithLabels,
68+
$command->value,
69+
...$bucketsToUpdate,
70+
],
71+
3,
72+
]);
6273
}
6374

6475
public function fetchHistograms(): iterable
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Zlodes\PrometheusClient\Laravel\Tests\Benchmark;
6+
7+
use Zlodes\PrometheusClient\Laravel\Storage\RedisStorage\HistogramRedisStorage;
8+
use Zlodes\PrometheusClient\Storage\Commands\UpdateHistogram;
9+
use Zlodes\PrometheusClient\Storage\DTO\MetricNameWithLabels;
10+
11+
class RedisHistogramStorageBench
12+
{
13+
/**
14+
* @Revs(1000)
15+
* @Iterations(10)
16+
* @Warmup(5)
17+
*/
18+
public function benchWriteDirectCalls(): void
19+
{
20+
app()->make(HistogramRedisStorage::class)
21+
->updateHistogram(
22+
new UpdateHistogram(
23+
new MetricNameWithLabels('foo'),
24+
['label' => 'value'],
25+
1.5
26+
)
27+
);
28+
}
29+
}

tests/Laravel/bootstrap.php

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Illuminate\Contracts\Console\Kernel;
6+
7+
require __DIR__ . '/../../vendor/autoload.php';
8+
9+
$app = require __DIR__ . '/bootstrap/app.php';
10+
11+
/** @var Kernel $kernel */
12+
$kernel = $app->make(Kernel::class);
13+
$kernel->bootstrap();

tests/Laravel/bootstrap/app.php

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
$app = new Illuminate\Foundation\Application(
6+
dirname(__DIR__),
7+
);
8+
9+
$app->singleton(
10+
Illuminate\Contracts\Console\Kernel::class,
11+
Illuminate\Foundation\Console\Kernel::class
12+
);
13+
14+
$app->singleton(
15+
Illuminate\Contracts\Debug\ExceptionHandler::class,
16+
Illuminate\Foundation\Exceptions\Handler::class,
17+
);
18+
19+
return $app;
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*
2+
!.gitignore

tests/Laravel/config/app.php

+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
<?php
2+
3+
use Illuminate\Support\Facades\Facade;
4+
use Illuminate\Support\ServiceProvider;
5+
6+
return [
7+
8+
/*
9+
|--------------------------------------------------------------------------
10+
| Application Name
11+
|--------------------------------------------------------------------------
12+
|
13+
| This value is the name of your application. This value is used when the
14+
| framework needs to place the application's name in a notification or
15+
| any other location as required by the application or its packages.
16+
|
17+
*/
18+
19+
'name' => env('APP_NAME', 'Laravel'),
20+
21+
/*
22+
|--------------------------------------------------------------------------
23+
| Application Environment
24+
|--------------------------------------------------------------------------
25+
|
26+
| This value determines the "environment" your application is currently
27+
| running in. This may determine how you prefer to configure various
28+
| services the application utilizes. Set this in your ".env" file.
29+
|
30+
*/
31+
32+
'env' => env('APP_ENV', 'workbench'),
33+
34+
/*
35+
|--------------------------------------------------------------------------
36+
| Application Debug Mode
37+
|--------------------------------------------------------------------------
38+
|
39+
| When your application is in debug mode, detailed error messages with
40+
| stack traces will be shown on every error that occurs within your
41+
| application. If disabled, a simple generic error page is shown.
42+
|
43+
*/
44+
45+
'debug' => (bool) env('APP_DEBUG', false),
46+
47+
/*
48+
|--------------------------------------------------------------------------
49+
| Application URL
50+
|--------------------------------------------------------------------------
51+
|
52+
| This URL is used by the console to properly generate URLs when using
53+
| the Artisan command line tool. You should set this to the root of
54+
| your application so that it is used when running Artisan tasks.
55+
|
56+
*/
57+
58+
'url' => env('APP_URL', 'http://localhost'),
59+
60+
'asset_url' => env('ASSET_URL'),
61+
62+
/*
63+
|--------------------------------------------------------------------------
64+
| Application Timezone
65+
|--------------------------------------------------------------------------
66+
|
67+
| Here you may specify the default timezone for your application, which
68+
| will be used by the PHP date and date-time functions. We have gone
69+
| ahead and set this to a sensible default for you out of the box.
70+
|
71+
*/
72+
73+
'timezone' => 'UTC',
74+
75+
/*
76+
|--------------------------------------------------------------------------
77+
| Application Locale Configuration
78+
|--------------------------------------------------------------------------
79+
|
80+
| The application locale determines the default locale that will be used
81+
| by the translation service provider. You are free to set this value
82+
| to any of the locales which will be supported by the application.
83+
|
84+
*/
85+
86+
'locale' => 'en',
87+
88+
/*
89+
|--------------------------------------------------------------------------
90+
| Application Fallback Locale
91+
|--------------------------------------------------------------------------
92+
|
93+
| The fallback locale determines the locale to use when the current one
94+
| is not available. You may change the value to correspond to any of
95+
| the language folders that are provided through your application.
96+
|
97+
*/
98+
99+
'fallback_locale' => 'en',
100+
101+
/*
102+
|--------------------------------------------------------------------------
103+
| Faker Locale
104+
|--------------------------------------------------------------------------
105+
|
106+
| This locale will be used by the Faker PHP library when generating fake
107+
| data for your database seeds. For example, this will be used to get
108+
| localized telephone numbers, street address information and more.
109+
|
110+
*/
111+
112+
'faker_locale' => 'en_US',
113+
114+
/*
115+
|--------------------------------------------------------------------------
116+
| Encryption Key
117+
|--------------------------------------------------------------------------
118+
|
119+
| This key is used by the Illuminate encrypter service and should be set
120+
| to a random, 32 character string, otherwise these encrypted strings
121+
| will not be safe. Please do this before deploying an application!
122+
|
123+
*/
124+
125+
'key' => env('APP_KEY'),
126+
127+
'cipher' => 'AES-256-CBC',
128+
129+
/*
130+
|--------------------------------------------------------------------------
131+
| Maintenance Mode Driver
132+
|--------------------------------------------------------------------------
133+
|
134+
| These configuration options determine the driver used to determine and
135+
| manage Laravel's "maintenance mode" status. The "cache" driver will
136+
| allow maintenance mode to be controlled across multiple machines.
137+
|
138+
| Supported drivers: "file", "cache"
139+
|
140+
*/
141+
142+
'maintenance' => [
143+
'driver' => 'file',
144+
// 'store' => 'redis',
145+
],
146+
147+
/*
148+
|--------------------------------------------------------------------------
149+
| Autoloaded Service Providers
150+
|--------------------------------------------------------------------------
151+
|
152+
| The service providers listed here will be automatically loaded on the
153+
| request to your application. Feel free to add your own services to
154+
| this array to grant expanded functionality to your applications.
155+
|
156+
*/
157+
158+
'providers' => ServiceProvider::defaultProviders()->merge([
159+
/*
160+
* Package Service Providers...
161+
*/
162+
163+
/*
164+
* Application Service Providers...
165+
*/
166+
// App\Providers\AppServiceProvider::class,
167+
// App\Providers\AuthServiceProvider::class,
168+
// App\Providers\BroadcastServiceProvider::class,
169+
// App\Providers\EventServiceProvider::class,
170+
// App\Providers\RouteServiceProvider::class,
171+
])->toArray(),
172+
173+
/*
174+
|--------------------------------------------------------------------------
175+
| Class Aliases
176+
|--------------------------------------------------------------------------
177+
|
178+
| This array of class aliases will be registered when this application
179+
| is started. However, feel free to register as many as you wish as
180+
| the aliases are "lazy" loaded so they don't hinder performance.
181+
|
182+
*/
183+
184+
'aliases' => Facade::defaultAliases()->merge([
185+
// 'Example' => App\Facades\Example::class,
186+
])->toArray(),
187+
188+
];

0 commit comments

Comments
 (0)