Browse Source

Code dump

master
Adam Pippin 4 years ago
commit
94d89594e6
  1. 111
      README.md
  2. 80
      bench.php
  3. 33
      common.php
  4. 2
      generator.php
  5. 43
      results.php
  6. 14
      settings.ini

111
README.md

@ -0,0 +1,111 @@
# dbbench
Stupid simple db benchmarking utility.
## Why?
pgbench, dbhammer, etc are all hard. I just need a ballpark idea of whether a database will fall over under a certain
load and to get a order-of-magnitude idea of how it will perform.
## What?
Two scripts:
* `bench.php` -- fork a bunch of processes and run a bunch of queries against a database
* `results.php` -- read the results that the workers have written out and provide aggregate statistics
## Limitations
* Right now this only supports Postgres. It would be trivial to modify to use anything else supported by PHP's PDO.
* It's very unscientific. Don't rely on this for anything more than a vague notion of how something is performing.
* The results script loads the entire set of queries + timings into RAM to process them. If you run this too long and
generate too many results it will not be very performant or require a huge amount of RAM to actually generate the results.
## How do I use this?
### Set up test environment
The settings will be read from the current folder, and the results written back out to it. So probably create an empty
folder to run it from.
1. Create a `settings.ini` file based off of the one included. Adjust workers/duration if desired. Configure your
database's connection information.
2. Create a generator file. This is just a plain PHP file that, at some point, `return`s a SQL query to run.
#### Examples
**settings.ini**
```
workers=16
duration=30
[db]
host=127.0.0.1
port=5432
username=postgres
password=postgres
database=my_database
```
**generator.php**
```
<?php
return "select * from my_table where id=".mt_rand(1, 65000);
```
### Run bench.php
Run the benchmarking tool.
```
$ php ~/path/to/bench.php
[Worker 0] Start
[Worker 1] Start
[Worker 2] Start
[Master] Start
[Worker 3] Start
[Worker 3] Stop
[Worker 1] Stop
[Worker 2] Stop
[Master] Stop
[Worker 0] Stop
$
```
Results are written to `worker.<number>.csv`. Each file will contain the query generated as well as the timing in
seconds.
### Analyze results
Run the results tool to aggregate all of the worker outputs.
```
$ php ~/path/to/results.php
5 percentile: 0.0417s
25 percentile: 0.042s
50 percentile: 0.0424s
75 percentile: 0.0431s
95 percentile: 0.0459s
99 percentile: 0.0505s
QPS: 93.6
```
You're done.
## License
```
Copyright (c) 2020 Adam Pippin. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software must display the following acknowledgement:
This product includes software developed by Adam Pippin.
4. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
```

80
bench.php

@ -0,0 +1,80 @@
<?php
require('common.php');
if ($argc < 2)
{
die('Syntax:'.PHP_EOL."\t".basename($argv[0]).' <generator-sql>'.PHP_EOL);
}
// Load the generator
$generator_code = file_get_contents($argv[1]);
if ($generator_code === false)
{
die('Could not read generator code.'.PHP_EOL);
}
if (strlen($generator_code) > 5 && substr($generator_code, 0, 5) == '<?php')
{
$generator_code = substr($generator_code, 5);
}
// Spawn workers
$children = [];
for ($i=0; $i<WORKERS; $i++)
{
// Reseed the rng for each worker so they don't all generate the same
// queries
srand((int)(microtime(true)*($i+1)));
$pid = pcntl_fork();
if ($pid == 0)
{
define('WORKER_NUMBER', $i);
break;
}
$children[] = $pid;
}
if (sizeof($children) == WORKERS)
{
echo "[Master] Start".PHP_EOL;
// Wait for all children to exit
while (isset($children) && sizeof($children))
{
$status = null;
$pid = pcntl_waitpid(0, $status);
$idx = array_search($pid, $children);
if ($idx !== null)
unset($children, $idx);
}
echo "[Master] Stop".PHP_EOL;
}
else
{
echo "[Worker ".WORKER_NUMBER."] Start".PHP_EOL;
// Connect to database and open log file
$pdo = new PDO('pgsql:host='.DB_HOST.';port='.DB_PORT.';dbname='.DB_DATABASE, DB_USERNAME, DB_PASSWORD);
$fp = fopen('worker.'.WORKER_NUMBER.'.csv', 'w');
fputcsv($fp, ['Query', 'Duration']);
$start = microtime(true);
while (microtime(true) < $start + DURATION)
{
// Generate a query, run it
$sql = eval($generator_code);
$queryStart = microtime(true);
$result = $pdo->query($sql);
$queryDuration = microtime(true) - $queryStart;
$result->closeCursor();
// Write result to log file
fputcsv($fp, [$sql, $queryDuration]);
}
fclose($fp);
echo "[Worker ".WORKER_NUMBER."] Stop".PHP_EOL;
}

33
common.php

@ -0,0 +1,33 @@
<?php
function init()
{
load_settings();
}
function load_settings()
{
$settings = @parse_ini_file(getcwd().DIRECTORY_SEPARATOR.'settings.ini', true, INI_SCANNER_RAW);
if ($settings === false)
{
die('Could not read or parse settings.ini'.PHP_EOL);
}
foreach ($settings as $section_key=>$section_value)
{
$section_key = strtoupper($section_key);
if (is_array($section_value))
{
foreach ($section_value as $key=>$value)
{
define($section_key.'_'.strtoupper($key), $value);
}
}
else
{
define($section_key, $section_value);
}
}
}
init();

2
generator.php

@ -0,0 +1,2 @@
<?php
return "select count(*) from my_table";

43
results.php

@ -0,0 +1,43 @@
<?php
require('common.php');
$results = [];
// Load all queries from all workers
for ($i=0; $i<WORKERS; $i++)
{
$fp = fopen('worker.'.$i.'.csv', 'r');
$header = fgetcsv($fp);
while ($line = fgetcsv($fp))
{
$results[] = [
'query' => $line[0],
'duration' => $line[1]
];
}
fclose($fp);
}
// Sort them by duration
usort($results, function($a, $b) {
if ($a['duration'] == $b['duration']) return 0;
if ($a['duration'] < $b['duration']) return -1;
if ($a['duration'] > $b['duration']) return 1;
});
// Output percentile times
$percentiles = [5, 25, 50, 75, 95, 99];
foreach ($percentiles as $percent)
{
$percent /= 100;
$idx = (int)(sizeof($results) * $percent);
echo str_pad($percent * 100, 3, ' ', STR_PAD_LEFT).' percentile: '.round($results[$idx]['duration'], 4).'s'.PHP_EOL;
}
// Output overall QPS
echo "QPS: ".round(sizeof($results) / DURATION, 2).PHP_EOL;

14
settings.ini

@ -0,0 +1,14 @@
; How many processes to fork
workers=16
; How long to allow the processes to run
; This is only checked after each query is completed, and if the elapsed time
; has been exceeded, the worker will exit.
duration=30
[db]
host=127.0.0.1
port=5432
username=postgres
password=postgres
database=my_database
Loading…
Cancel
Save