summaryrefslogtreecommitdiffstats
path: root/library/Icinga/Application/Benchmark.php
diff options
context:
space:
mode:
Diffstat (limited to 'library/Icinga/Application/Benchmark.php')
-rw-r--r--library/Icinga/Application/Benchmark.php300
1 files changed, 300 insertions, 0 deletions
diff --git a/library/Icinga/Application/Benchmark.php b/library/Icinga/Application/Benchmark.php
new file mode 100644
index 0000000..32a05ec
--- /dev/null
+++ b/library/Icinga/Application/Benchmark.php
@@ -0,0 +1,300 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+/**
+ * Icinga\Application\Benchmark class
+ */
+namespace Icinga\Application;
+
+use Icinga\Util\Format;
+
+/**
+ * This class provides a simple and lightweight benchmark class
+ *
+ * <code>
+ * Benchmark::measure('Program started');
+ * // ...do something...
+ * Benchmark::measure('Task finieshed');
+ * Benchmark::dump();
+ * </code>
+ */
+class Benchmark
+{
+ const TIME = 0x01;
+ const MEMORY = 0x02;
+
+ protected static $instance;
+ protected $start;
+ protected $measures = array();
+
+ /**
+ * Add a measurement to your benchmark
+ *
+ * The same identifier can also be used multiple times
+ *
+ * @param string A comment identifying the current measurement
+ * @return void
+ */
+ public static function measure($message)
+ {
+ self::getInstance()->measures[] = (object) array(
+ 'timestamp' => microtime(true),
+ 'memory_real' => memory_get_usage(true),
+ 'memory' => memory_get_usage(),
+ 'message' => $message
+ );
+ }
+
+ /**
+ * Throws all measurements away
+ *
+ * This empties your measurement table and allows you to restart your
+ * benchmark from scratch
+ *
+ * @return void
+ */
+ public static function reset()
+ {
+ self::$instance = null;
+ }
+
+ /**
+ * Rerieve benchmark start time
+ *
+ * This will give you the timestamp of your first measurement
+ *
+ * @return float
+ */
+ public static function getStartTime()
+ {
+ return self::getInstance()->start;
+ }
+
+ /**
+ * Dump benchmark data
+ *
+ * Will dump a text table if running on CLI and a simple HTML table
+ * otherwise. Use Benchmark::TIME and Benchmark::MEMORY to choose whether
+ * you prefer to show either time or memory or both in your output
+ *
+ * @param ?int $what Whether to get time and/or memory summary
+ */
+ public static function dump($what = null)
+ {
+ if (Icinga::app()->isCli()) {
+ echo self::renderToText($what);
+ } else {
+ echo self::renderToHtml($what);
+ }
+ }
+
+ /**
+ * Render benchmark data to a simple text table
+ *
+ * Use Benchmark::TIME and Icinga::MEMORY to choose whether you prefer to
+ * show either time or memory or both in your output
+ *
+ * @param ?int $what Whether to get time and/or memory summary
+ * @return string
+ */
+ public static function renderToText($what = null)
+ {
+ $data = self::prepareDataForRendering($what);
+ $sep = '+';
+ $title = '|';
+ foreach ($data->columns as & $col) {
+ $col->format = ' %'
+ . ($col->align === 'right' ? '' : '-')
+ . $col->maxlen . 's |';
+
+ $sep .= str_repeat('-', $col->maxlen) . '--+';
+ $title .= sprintf($col->format, $col->title);
+ }
+
+ $out = $sep . "\n" . $title . "\n" . $sep . "\n";
+ foreach ($data->rows as & $row) {
+ $r = '|';
+ foreach ($data->columns as $key => & $col) {
+ $r .= sprintf($col->format, $row[$key]);
+ }
+ $out .= $r . "\n";
+ }
+
+ $out .= $sep . "\n";
+ return $out;
+ }
+
+ /**
+ * Render benchmark data to a simple HTML table
+ *
+ * Use Benchmark::TIME and Benchmark::MEMORY to choose whether you prefer
+ * to show either time or memory or both in your output
+ *
+ * @param ?int $what Whether to get time and/or memory summary
+ *
+ * @return string
+ */
+ public static function renderToHtml($what = null)
+ {
+ $data = self::prepareDataForRendering($what);
+
+ // TODO: Move formatting to CSS file
+ $html = '<table class="benchmark">' . "\n" . '<tr>';
+ foreach ($data->columns as & $col) {
+ if ($col->title === 'Time') {
+ continue;
+ }
+ $html .= sprintf(
+ '<td align="%s">%s</td>',
+ $col->align,
+ htmlspecialchars($col->title)
+ );
+ }
+ $html .= "</tr>\n";
+
+ foreach ($data->rows as & $row) {
+ $html .= '<tr>';
+ foreach ($data->columns as $key => & $col) {
+ if ($col->title === 'Time') {
+ continue;
+ }
+ $html .= sprintf(
+ '<td align="%s">%s</td>',
+ $col->align,
+ $row[$key]
+ );
+ }
+ $html .= "</tr>\n";
+ }
+ $html .= "</table>\n";
+ return $html;
+ }
+
+ /**
+ * Prepares benchmark data for output
+ *
+ * Use Benchmark::TIME and Benchmark::MEMORY to choose whether you prefer
+ * to have either time or memory or both in your output
+ *
+ * @param ?int $what Whether to get time and/or memory summary
+ *
+ * @return object
+ */
+ protected static function prepareDataForRendering($what = null)
+ {
+ if ($what === null) {
+ $what = self::TIME | self::MEMORY;
+ }
+
+ $columns = array(
+ (object) array(
+ 'title' => 'Time',
+ 'align' => 'left',
+ 'maxlen' => 4
+ ),
+ (object) array(
+ 'title' => 'Description',
+ 'align' => 'left',
+ 'maxlen' => 11
+ )
+ );
+ if ($what & self::TIME) {
+ $columns[] = (object) array(
+ 'title' => 'Off (ms)',
+ 'align' => 'right',
+ 'maxlen' => 11
+ );
+ $columns[] = (object) array(
+ 'title' => 'Dur (ms)',
+ 'align' => 'right',
+ 'maxlen' => 13
+ );
+ }
+ if ($what & self::MEMORY) {
+ $columns[] = (object) array(
+ 'title' => 'Mem (diff)',
+ 'align' => 'right',
+ 'maxlen' => 10
+ );
+ $columns[] = (object) array(
+ 'title' => 'Mem (total)',
+ 'align' => 'right',
+ 'maxlen' => 11
+ );
+ }
+
+ $bench = self::getInstance();
+ $last = $bench->start;
+ $rows = array();
+ $lastmem = 0;
+ foreach ($bench->measures as $m) {
+ $micro = sprintf(
+ '%03d',
+ round(($m->timestamp - floor($m->timestamp)) * 1000)
+ );
+ $vals = array(
+ date('H:i:s', (int) $m->timestamp) . '.' . $micro,
+ $m->message
+ );
+
+ if ($what & self::TIME) {
+ $m->relative = $m->timestamp - $bench->start;
+ $m->offset = $m->timestamp - $last;
+ $last = $m->timestamp;
+ $vals[] = sprintf('%0.3f', $m->relative * 1000);
+ $vals[] = sprintf('%0.3f', $m->offset * 1000);
+ }
+
+ if ($what & self::MEMORY) {
+ $mem = $m->memory - $lastmem;
+ $lastmem = $m->memory;
+ $vals[] = Format::bytes($mem);
+ $vals[] = Format::bytes($m->memory);
+ }
+
+ $row = & $rows[];
+ foreach ($vals as $col => $val) {
+ $row[$col] = $val;
+ $columns[$col]->maxlen = max(
+ strlen($val),
+ $columns[$col]->maxlen
+ );
+ }
+ }
+
+ return (object) array(
+ 'columns' => $columns,
+ 'rows' => $rows
+ );
+ }
+
+ /**
+ * Singleton
+ *
+ * Benchmark is run only once, but you are not allowed to directly access
+ * the getInstance() method
+ *
+ * @return self
+ */
+ protected static function getInstance()
+ {
+ if (self::$instance === null) {
+ self::$instance = new Benchmark();
+ self::$instance->start = microtime(true);
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Constructor
+ *
+ * Singleton usage is enforced, the only way to instantiate Benchmark is by
+ * starting your measurements
+ *
+ * @return void
+ */
+ protected function __construct()
+ {
+ }
+}