Browse Source

Add performance counter system.

pull/35/head
Starbeamrainbowlabs 3 years ago
parent
commit
0006c2a921
Signed by: sbrl
GPG Key ID: 1BE5172E637709C2
  1. 12
      api.php
  2. 10
      di_config.php
  3. 83
      lib/SBRL/PerformanceCounter.php
  4. 12
      logic/Actions/Changelog.php
  5. 24
      logic/Actions/DeviceData.php
  6. 19
      logic/Actions/DeviceDataBounds.php
  7. 20
      logic/Actions/DeviceDataRecent.php
  8. 20
      logic/Actions/DeviceInfo.php
  9. 20
      logic/Actions/FetchData.php
  10. 12
      logic/Actions/Index.php
  11. 17
      logic/Actions/ListDevices.php
  12. 17
      logic/Actions/ListReadingTypes.php
  13. 11
      logic/Actions/Version.php
  14. 17
      logic/Database.php
  15. 27
      logic/PerfFormatter.php

12
api.php

@ -12,16 +12,23 @@ $autoloader->addPrefix("AirQuality", "logic"); @@ -12,16 +12,23 @@ $autoloader->addPrefix("AirQuality", "logic");
$autoloader->addPrefix("SBRL", "lib/SBRL");
$autoloader->register();
// 1.5: Performance counter
$perfcounter = new \SBRL\PerformanceCounter();
$perfcounter->start("total");
// 2: Settings
$perfcounter->start("settings");
$settings = new \SBRL\TomlConfig(
"data/settings.toml",
"settings.default.toml"
);
$perfcounter->end("settings");
// 3: Dependency injection
$perfcounter->start("di");
$di_builder = new DI\ContainerBuilder();
$di_builder->addDefinitions("di_config.php");
if($settings->get("env.mode") == "production") {
@ -37,6 +44,7 @@ if($settings->get("env.mode") == "production") { @@ -37,6 +44,7 @@ if($settings->get("env.mode") == "production") {
$di_container = $di_builder->build();
$perfcounter->end("di");
// 4: Database
@ -68,6 +76,8 @@ if(!class_exists($handler_name)) { @@ -68,6 +76,8 @@ if(!class_exists($handler_name)) {
exit("Error: No action with the name '$action' could be found.");
}
// FUTURE: A PSR-7 request/response system would be rather nice here.
$handler = $di_container->get($handler_name);
$perfcounter->start("handle");
// FUTURE: A PSR-7 request/response system would be rather nice here.
$handler->handle();

10
di_config.php

@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
use Psr\Container\ContainerInterface;
use SBRL\TomlConfig;
use AirQuality\Database;
// use AirQuality\Database;
use AirQuality\Repositories\IDeviceRepository;
use AirQuality\Repositories\MariaDBDeviceRepository;
@ -12,15 +12,23 @@ use AirQuality\Repositories\MariaDBMeasurementDataRepository; @@ -12,15 +12,23 @@ use AirQuality\Repositories\MariaDBMeasurementDataRepository;
use AirQuality\Repositories\IMeasurementTypeRepository;
use AirQuality\Repositories\MariaDBMeasurementTypeRepository;
use SBRL\PerformanceCounter;
return [
"settings.file_default" => "data/settings.toml",
"settings.file_custom" => "settings.default.toml",
// These are created during initalisation, but we want them available via dependency injection too
TomlConfig::class => function(ContainerInterface $c) {
global $settings;
return $settings;
},
PerformanceCounter::class => function(ContainerInterface $c) {
global $perfcounter;
return $perfcounter;
},
// Interfaces that need mapping to their implementations
IDeviceRepository::class => DI\autowire(MariaDBDeviceRepository::class),
IMeasurementDataRepository::class => DI\autowire(MariaDBMeasurementDataRepository::class),
IMeasurementTypeRepository::class => DI\autowire(MariaDBMeasurementTypeRepository::class)

83
lib/SBRL/PerformanceCounter.php

@ -0,0 +1,83 @@ @@ -0,0 +1,83 @@
<?php
namespace SBRL;
use Exception;
/**
* Records performance information.
*/
class PerformanceCounter
{
/**
* An associative array of performance information.
* It should take the form key => [float] | float.
* HACK: If the float is the only item in an array, then it hasn't been ended yet.
* @var array
*/
private $data = [];
/**
* An array of performance counters to hide when rendering.
* Useful to keep some counters around for later, but keep them hidden so as to not clutter everything up.
* @var string[]
*/
private $hidden = [];
/**
* Creates a new performance counter instea
*/
function __construct() {
// code...
}
/**
* Starts recording the time taken for a given key.
* Calling end($key) is optional
* @param string $key The key to start recording the time taken for.
*/
public function start(string $key) {
$this->data[$key] = [microtime(true)];
}
/**
* Ends recording the time taken for a given key.
* @param string $key The key to end counting for.
* @return float The time taken, in milliseconds
*/
public function end(string $key) {
if(empty($this->data[$key]))
throw new Exception("Error: We can't end counting for $key because we haven't started yet");
$this->data[$key] = (microtime(true) - $this->data[$key][0]) * 1000;
return $this->data[$key];
}
/**
* Renders the performance information as a short string.
* Suitable for sending in a HTTP header, for example.
* @return string The rendered performance information.
*/
public function render() {
$result = "";
$parts = [];
foreach($this->data as $key => $value) {
if(in_array($key, $this->hidden))
continue;
if(is_array($value))
$value = $this->end($key);
$render = round($value, 2) . "ms $key";
if($key == "total") {
$result .= "$render | ";
continue;
}
$parts[] = $render;
}
$result .= implode(", ", $parts);
return $result;
}
}

12
logic/Actions/Changelog.php

@ -4,36 +4,40 @@ namespace AirQuality\Actions; @@ -4,36 +4,40 @@ namespace AirQuality\Actions;
use \SBRL\TomlConfig;
use \AirQuality\PerfFormatter;
class Changelog implements IAction {
/** @var TomlConfig */
private $settings;
/** @var \SBRL\PerformanceCounter */
private $perfcounter;
/** @var \ParsedownExtra */
private $parsedown_ext;
public function __construct(
TomlConfig $in_settings,
\ParsedownExtra $in_parsedown_ext) {
\ParsedownExtra $in_parsedown_ext,
\SBRL\PerformanceCounter $in_perfcounter) {
$this->settings = $in_settings;
$this->parsedown_ext = $in_parsedown_ext;
$this->perfcounter = $in_perfcounter;
}
public function handle() : bool {
global $start_time;
$start_handle = microtime(true);
// 1: Parse markdown
$this->perfcounter->start("parse");
$result = $this->parsedown_ext->text(file_get_contents(ROOT_DIR."Changelog.md"));
$this->perfcounter->end("parse");
// 2: Send response
header("content-length: " . strlen($result));
header("content-type: text/html");
header("x-time-taken: " . PerfFormatter::format_perf_data($start_time, $start_handle, null));
header("x-time-taken: " . $this->perfcounter->render());
echo($result);
return true;
}

24
logic/Actions/DeviceData.php

@ -9,11 +9,13 @@ use \AirQuality\Repositories\IMeasurementTypeRepository; @@ -9,11 +9,13 @@ use \AirQuality\Repositories\IMeasurementTypeRepository;
use \AirQuality\ApiResponseSender;
use \AirQuality\Validator;
use \AirQuality\PerfFormatter;
class DeviceData implements IAction {
/** @var TomlConfig */
private $settings;
/** @var \SBRL\PerformanceCounter */
private $perfcounter;
/** @var IMeasurementDataRepository */
private $measurement_repo;
@ -30,11 +32,13 @@ class DeviceData implements IAction { @@ -30,11 +32,13 @@ class DeviceData implements IAction {
TomlConfig $in_settings,
IMeasurementDataRepository $in_measurement_repo,
IMeasurementTypeRepository $in_type_repo,
ApiResponseSender $in_sender) {
ApiResponseSender $in_sender,
\SBRL\PerformanceCounter $in_perfcounter) {
$this->settings = $in_settings;
$this->measurement_repo = $in_measurement_repo;
$this->type_repo = $in_type_repo;
$this->sender = $in_sender;
$this->perfcounter = $in_perfcounter;
$this->validator = new Validator($_GET);
}
@ -42,7 +46,6 @@ class DeviceData implements IAction { @@ -42,7 +46,6 @@ class DeviceData implements IAction {
public function handle() : bool {
global $start_time;
$start_handle = microtime(true);
// 1: Validate params
$this->validator->is_numberish("device-id");
@ -61,7 +64,7 @@ class DeviceData implements IAction { @@ -61,7 +64,7 @@ class DeviceData implements IAction {
if(new \DateTime($_GET["start"]) > new \DateTime($_GET["end"])) {
$this->sender->send_error_plain(
400, "Error: The start date must be earlier than the end date.", [
[ "x-time-taken", PerfFormatter::format_perf_data($start_time, $start_handle, null) ]
[ "x-time-taken", $this->perfcounter->render() ]
]
);
return false;
@ -70,18 +73,19 @@ class DeviceData implements IAction { @@ -70,18 +73,19 @@ class DeviceData implements IAction {
if(!empty($_GET["average-seconds"]) && intval($_GET["average-seconds"]) == 0) {
$this->sender->send_error_plain(
400, "Error: That average-seconds value is invalid (an integer greater than 0 required).", [
[ "x-time-taken", PerfFormatter::format_perf_data($start_time, $start_handle, null) ]
[ "x-time-taken", $this->perfcounter->render() ]
]
);
return false;
}
$this->perfcounter->start("sql");
$reading_type_id = $this->type_repo->get_id($_GET["reading-type"]);
if($reading_type_id == null) {
$this->sender->send_error_plain(
400, "Error: That reading type is invalid.", [
[ "x-time-taken", PerfFormatter::format_perf_data($start_time, $start_handle, null) ]
[ "x-time-taken", $this->perfcounter->render() ]
]
);
return false;
@ -95,19 +99,20 @@ class DeviceData implements IAction { @@ -95,19 +99,20 @@ class DeviceData implements IAction {
new \DateTime($_GET["end"]),
!empty($_GET["average-seconds"]) ? intval($_GET["average-seconds"]) : 1
);
$this->perfcounter->end("sql");
// 2.5: Validate data from database
if(empty($data)) {
http_response_code(404);
header("content-type: text/plain");
header("x-time-taken: " . PerfFormatter::format_perf_data($start_time, $start_handle, null));
header("x-time-taken: " . $this->perfcounter->render());
echo("Error: No data has been recorded from the device id for that measurement type in the selected time scale or it doesn't exist.");
return false;
}
// 3: Serialise data
$start_encode = microtime(true);
$this->perfcounter->start("encode");
$response_type = "application/octet-stream";
$response_suggested_filename = "data-" . date(\DateTime::ATOM) . "";
$response = null;
@ -123,13 +128,14 @@ class DeviceData implements IAction { @@ -123,13 +128,14 @@ class DeviceData implements IAction {
$response = ResponseEncoder::encode_csv($data);
break;
}
$this->perfcounter->end("encode");
// 4: Send response
header("content-length: " . strlen($response));
header("content-type: $response_type");
header("content-disposition: inline; filename=$response_suggested_filename");
header("x-time-taken: " . PerfFormatter::format_perf_data($start_time, $start_handle, $start_encode));
header("x-time-taken: " . $this->perfcounter->render());
echo($response);
return true;
}

19
logic/Actions/DeviceDataBounds.php

@ -7,11 +7,14 @@ use \AirQuality\Repositories\IMeasurementDataRepository; @@ -7,11 +7,14 @@ use \AirQuality\Repositories\IMeasurementDataRepository;
use \AirQuality\ApiResponseSender;
use \AirQuality\Validator;
use \AirQuality\PerfFormatter;
class DeviceDataBounds implements IAction {
/** @var TomlConfig */
private $settings;
/** @var \SBRL\PerformanceCounter */
private $perfcounter;
/** @var IMeasurementDataRepository */
private $measurement_repo;
@ -24,10 +27,12 @@ class DeviceDataBounds implements IAction { @@ -24,10 +27,12 @@ class DeviceDataBounds implements IAction {
public function __construct(
TomlConfig $in_settings,
IMeasurementDataRepository $in_measurement_repo,
ApiResponseSender $in_sender) {
ApiResponseSender $in_sender,
\SBRL\PerformanceCounter $in_perfcounter) {
$this->settings = $in_settings;
$this->measurement_repo = $in_measurement_repo;
$this->sender = $in_sender;
$this->perfcounter = $in_perfcounter;
$this->validator = new Validator($_GET);
}
@ -35,7 +40,6 @@ class DeviceDataBounds implements IAction { @@ -35,7 +40,6 @@ class DeviceDataBounds implements IAction {
public function handle() : bool {
global $start_time;
$start_handle = microtime(true);
// 1: Validate params
$this->validator->is_numberish("device-id");
@ -43,29 +47,32 @@ class DeviceDataBounds implements IAction { @@ -43,29 +47,32 @@ class DeviceDataBounds implements IAction {
// 2: Pull data from database
$this->perfcounter->start("sql");
$data = $this->measurement_repo->get_device_reading_bounds(
intval($_GET["device-id"])
);
$this->perfcounter->end("sql");
// 2.5: Validate data from database
if(empty($data)) {
http_response_code(404);
header("content-type: text/plain");
header("x-time-taken: " . PerfFormatter::format_perf_data($start_time, $start_handle, null));
header("x-time-taken: " . $this->perfcounter->render());
echo("Error: No data has been recorded from the device id or it doesn't exist.");
return false;
}
// 3: Serialise data
$start_encode = microtime(true);
$this->perfcounter->start("encode");
$response = json_encode($data);
$this->perfcounter->end("encode");
// 4: Send response
header("content-length: " . strlen($response));
header("content-type: application/json");
header("x-time-taken: " . PerfFormatter::format_perf_data($start_time, $start_handle, $start_encode));
header("x-time-taken: " . $this->perfcounter->render());
echo($response);
return true;
}

20
logic/Actions/DeviceDataRecent.php

@ -9,7 +9,7 @@ use \AirQuality\Repositories\IMeasurementTypeRepository; @@ -9,7 +9,7 @@ use \AirQuality\Repositories\IMeasurementTypeRepository;
use \AirQuality\ApiResponseSender;
use \AirQuality\Validator;
use \AirQuality\PerfFormatter;
/**
* Action that retrieves recent data for a device.
@ -17,6 +17,8 @@ use \AirQuality\PerfFormatter; @@ -17,6 +17,8 @@ use \AirQuality\PerfFormatter;
class DeviceDataRecent implements IAction {
/** @var TomlConfig */
private $settings;
/** @var \SBRL\PerformanceCounter */
private $perfcounter;
/** @var IMeasurementDataRepository */
private $measurement_repo;
@ -33,11 +35,13 @@ class DeviceDataRecent implements IAction { @@ -33,11 +35,13 @@ class DeviceDataRecent implements IAction {
TomlConfig $in_settings,
IMeasurementDataRepository $in_measurement_repo,
IMeasurementTypeRepository $in_type_repo,
ApiResponseSender $in_sender) {
ApiResponseSender $in_sender,
\SBRL\PerformanceCounter $in_perfcounter) {
$this->settings = $in_settings;
$this->measurement_repo = $in_measurement_repo;
$this->type_repo = $in_type_repo;
$this->sender = $in_sender;
$this->perfcounter = $in_perfcounter;
$this->validator = new Validator($_GET);
}
@ -45,7 +49,6 @@ class DeviceDataRecent implements IAction { @@ -45,7 +49,6 @@ class DeviceDataRecent implements IAction {
public function handle() : bool {
global $start_time;
$start_handle = microtime(true);
// 1: Validate params
$this->validator->is_numberish("device-id");
@ -69,24 +72,26 @@ class DeviceDataRecent implements IAction { @@ -69,24 +72,26 @@ class DeviceDataRecent implements IAction {
if($reading_type_id == null) {
$this->sender->send_error_plain(
400, "Error: That reading type is invalid.", [
[ "x-time-taken", PerfFormatter::format_perf_data($start_time, $start_handle, null) ]
[ "x-time-taken", $this->perfcounter->render() ]
]
);
return false;
}
// 2: Pull data from database
$this->perfcounter->start("sql");
$data = $this->measurement_repo->get_recent_readings(
intval($_GET["device-id"]),
$reading_type_id,
$count
);
$this->perfcounter->end("sql");
// 2.5: Validate data from database
if(empty($data)) {
http_response_code(404);
header("content-type: text/plain");
header("x-time-taken: " . PerfFormatter::format_perf_data($start_time, $start_handle, null));
header("x-time-taken: " . $this->perfcounter->render());
echo("Error: No data has been recorded from the device id for that measurement type in the selected time scale or it doesn't exist.");
return false;
}
@ -96,7 +101,7 @@ class DeviceDataRecent implements IAction { @@ -96,7 +101,7 @@ class DeviceDataRecent implements IAction {
// FUTURE: Refactor this into an AbstractAction or something, but the careful not to shift too much work there
// We might want to consider a HttpResponse container or something if we can find a decent PSR implementation
$start_encode = microtime(true);
$this->perfcounter->start("encode");
$response_type = "application/octet-stream";
$response_suggested_filename = "data-" . date(\DateTime::ATOM) . "";
$response = null;
@ -112,13 +117,14 @@ class DeviceDataRecent implements IAction { @@ -112,13 +117,14 @@ class DeviceDataRecent implements IAction {
$response = ResponseEncoder::encode_csv($data);
break;
}
$this->perfcounter->end("encode");
// 4: Send response
header("content-length: " . strlen($response));
header("content-type: $response_type");
header("content-disposition: inline; filename=$response_suggested_filename");
header("x-time-taken: " . PerfFormatter::format_perf_data($start_time, $start_handle, $start_encode));
header("x-time-taken: " . $this->perfcounter->render());
echo($response);
return true;
}

20
logic/Actions/DeviceInfo.php

@ -4,14 +4,18 @@ namespace AirQuality\Actions; @@ -4,14 +4,18 @@ namespace AirQuality\Actions;
use \SBRL\TomlConfig;
use \AirQuality\Repositories\IDeviceRepository;
use \AirQuality\Repositories\IMeasurementTypeRepository;
use \AirQuality\ApiResponseSender;
use \AirQuality\Validator;
use \AirQuality\PerfFormatter;
class DeviceInfo implements IAction {
/** @var TomlConfig */
private $settings;
/** @var \SBRL\PerformanceCounter */
private $perfcounter;
/** @var IDeviceRepository */
private $device_repo;
@ -27,10 +31,12 @@ class DeviceInfo implements IAction { @@ -27,10 +31,12 @@ class DeviceInfo implements IAction {
public function __construct(
TomlConfig $in_settings,
IDeviceRepository $in_device_repo,
ApiResponseSender $in_sender) {
ApiResponseSender $in_sender,
\SBRL\PerformanceCounter $in_perfcounter) {
$this->settings = $in_settings;
$this->device_repo = $in_device_repo;
$this->sender = $in_sender;
$this->perfcounter = $in_perfcounter;
$this->validator = new Validator($_GET);
}
@ -38,28 +44,30 @@ class DeviceInfo implements IAction { @@ -38,28 +44,30 @@ class DeviceInfo implements IAction {
public function handle() : bool {
global $start_time;
$start_handle = microtime(true);
// 1: Validate params
$this->validator->is_numberish("device-id");
$this->validator->run();
// 2: Pull data from database
$this->perfcounter->start("sql");
$data = $this->device_repo->get_device_info_ext(
$_GET["device-id"]
);
$this->perfcounter->end("sql");
// 2.5: Validate data from database
if(empty($data)) {
$this->sender->send_error_plain(404, "Error: No data could be found for that device id.", [
[ "x-time-taken: ", PerfFormatter::format_perf_data($start_time, $start_handle, null) ]
[ "x-time-taken: ", $this->perfcounter->render() ]
]);
return false;
}
// 3: Serialise data
$start_encode = microtime(true);
$this->perfcounter->start("encode");
$response = json_encode($data);
$this->perfcounter->end("encode");
// 4: Send response
@ -70,7 +78,7 @@ class DeviceInfo implements IAction { @@ -70,7 +78,7 @@ class DeviceInfo implements IAction {
header("content-length: " . strlen($response));
header("content-type: application/json");
header("x-time-taken: " . PerfFormatter::format_perf_data($start_time, $start_handle, $start_encode));
header("x-time-taken: " . $this->perfcounter->render());
echo($response);
return true;
}

20
logic/Actions/FetchData.php

@ -9,11 +9,13 @@ use \AirQuality\Repositories\IMeasurementTypeRepository; @@ -9,11 +9,13 @@ use \AirQuality\Repositories\IMeasurementTypeRepository;
use \AirQuality\ApiResponseSender;
use \AirQuality\Validator;
use \AirQuality\PerfFormatter;
class FetchData implements IAction {
/** @var TomlConfig */
private $settings;
/** @var \SBRL\PerformanceCounter */
private $perfcounter;
/** @var IMeasurementDataRepository */
private $measurement_repo;
@ -30,11 +32,13 @@ class FetchData implements IAction { @@ -30,11 +32,13 @@ class FetchData implements IAction {
TomlConfig $in_settings,
IMeasurementDataRepository $in_measurement_repo,
IMeasurementTypeRepository $in_type_repo,
ApiResponseSender $in_sender) {
ApiResponseSender $in_sender,
\SBRL\PerformanceCounter $in_perfcounter) {
$this->settings = $in_settings;
$this->measurement_repo = $in_measurement_repo;
$this->type_repo = $in_type_repo;
$this->sender = $in_sender;
$this->perfcounter = $in_perfcounter;
$this->validator = new Validator($_GET);
}
@ -42,7 +46,6 @@ class FetchData implements IAction { @@ -42,7 +46,6 @@ class FetchData implements IAction {
public function handle() : bool {
global $start_time;
$start_handle = microtime(true);
// 1: Validate params
$this->validator->is_datetime("datetime");
@ -58,7 +61,7 @@ class FetchData implements IAction { @@ -58,7 +61,7 @@ class FetchData implements IAction {
if($measurement_type_id == null) {
$this->sender->send_error_plain(
400, "Error: That reading type is invalid.", [
[ "x-time-taken", PerfFormatter::format_perf_data($start_time, $start_handle, null) ]
[ "x-time-taken", $this->perfcounter->render() ]
]
);
return false;
@ -66,24 +69,26 @@ class FetchData implements IAction { @@ -66,24 +69,26 @@ class FetchData implements IAction {
// 2: Pull data from database
$this->perfcounter->start("sql");
$data = $this->measurement_repo->get_readings_by_date(
new \DateTime($_GET["datetime"]),
$measurement_type_id
);
$this->perfcounter->end("sql");
// 2.5: Validate data from database
if(empty($data)) {
$this->sender->send_error_plain(404,
"Error: No data could be found for that timestamp.",
[ PerfFormatter::format_perf_data($start_time, $start_handle, null) ]
[ [ "x-time-taken", $this->perfcounter->render() ] ]
);
return false;
}
// 3: Serialise data
$start_encode = microtime(true);
$this->perfcounter->start("encode");
$response_type = "application/octet-stream";
$response_suggested_filename = "data-" . date(\DateTime::ATOM) . "";
$response = null;
@ -99,6 +104,7 @@ class FetchData implements IAction { @@ -99,6 +104,7 @@ class FetchData implements IAction {
$response = ResponseEncoder::encode_csv($data);
break;
}
$this->perfcounter->end("encode");
// 4: Send response
@ -111,7 +117,7 @@ class FetchData implements IAction { @@ -111,7 +117,7 @@ class FetchData implements IAction {
header("content-type: $response_type");
header("content-length: " . strlen($response));
header("content-disposition: inline; filename=$response_suggested_filename");
header("x-time-taken: " . PerfFormatter::format_perf_data($start_time, $start_handle, $start_encode));
header("x-time-taken: " . $this->perfcounter->render());
echo($response);
return true;
}

12
logic/Actions/Index.php

@ -4,25 +4,27 @@ namespace AirQuality\Actions; @@ -4,25 +4,27 @@ namespace AirQuality\Actions;
use \SBRL\TomlConfig;
use \AirQuality\PerfFormatter;
class Index implements IAction {
/** @var TomlConfig */
private $settings;
/** @var \SBRL\PerformanceCounter */
private $perfcounter;
/** @var \ParsedownExtra */
private $parsedown_ext;
public function __construct(
TomlConfig $in_settings) {
TomlConfig $in_settings,
\SBRL\PerformanceCounter $in_perfcounter) {
$this->settings = $in_settings;
$this->perfcounter = $in_perfcounter;
}
public function handle() : bool {
global $start_time;
$start_handle = microtime(true);
$this->perfcounter->start("handle");
// 1: Parse markdown
$result = "Welcome to the Air Quality Web HTTP API!
@ -38,7 +40,7 @@ Note that if you have deployed your own version of the air quality web interface @@ -38,7 +40,7 @@ Note that if you have deployed your own version of the air quality web interface
// 2: Send response
header("content-length: " . strlen($result));
header("content-type: text/plain");
header("x-time-taken: " . PerfFormatter::format_perf_data($start_time, $start_handle, null));
header("x-time-taken: " . $this->perfcounter->render());
echo($result);
return true;
}

17
logic/Actions/ListDevices.php

@ -7,13 +7,14 @@ use \SBRL\ResponseEncoder; @@ -7,13 +7,14 @@ use \SBRL\ResponseEncoder;
use \AirQuality\Repositories\IDeviceRepository;
use \AirQuality\ApiResponseSender;
use \AirQuality\PerfFormatter;
class ListDevices implements IAction {
/** @var TomlConfig */
private $settings;
/** @var IDeviceRepository */
private $device_repo;
/** @var \SBRL\PerformanceCounter */
private $perfcounter;
/** @var ApiResponseSender */
private $sender;
@ -21,16 +22,17 @@ class ListDevices implements IAction { @@ -21,16 +22,17 @@ class ListDevices implements IAction {
public function __construct(
TomlConfig $in_settings,
IDeviceRepository $in_device_repo,
ApiResponseSender $in_sender) {
ApiResponseSender $in_sender,
\SBRL\PerformanceCounter $in_perfcounter) {
$this->settings = $in_settings;
$this->device_repo = $in_device_repo;
$this->sender = $in_sender;
$this->perfcounter = $in_perfcounter;
}
public function handle() : bool {
global $start_time;
$start_handle = microtime(true);
// 1: Parse & validate parameters
$only_with_location = !empty($_GET["only-with-location"]);
@ -45,21 +47,23 @@ class ListDevices implements IAction { @@ -45,21 +47,23 @@ class ListDevices implements IAction {
// 2: Pull data from database
$this->perfcounter->start("sql");
$data = $this->device_repo->get_all_devices($only_with_location);
$this->perfcounter->end("sql");
// 2.5: Validate data from database
if(empty($data)) {
$this->sender->send_error_plain(404,
"Error: No devices are currently present in the system.",
[ "x-time-taken: " . PerfFormatter::format_perf_data($start_time, $start_handle, null) ]
[ "x-time-taken: ", $this->perfcounter->render() ]
);
return false;
}
// 3: Serialise data
$start_encode = microtime(true);
$this->perfcounter->start("encode");
$response = null;
$response_type = "application/octet-stream";
$response_suggested_filename = "data-" . date(\DateTime::ATOM) . "";
@ -75,6 +79,7 @@ class ListDevices implements IAction { @@ -75,6 +79,7 @@ class ListDevices implements IAction {
$response = ResponseEncoder::encode_csv($data);
break;
}
$this->perfcounter->end("encode");
// 4: Send response
@ -84,7 +89,7 @@ class ListDevices implements IAction { @@ -84,7 +89,7 @@ class ListDevices implements IAction {
header("content-length: " . strlen($response));
header("content-type: $response_type");
header("content-disposition: inline; filename=$response_suggested_filename");
header("x-time-taken: " . PerfFormatter::format_perf_data($start_time, $start_handle, $start_encode));
header("x-time-taken: " . $this->perfcounter->render());
echo($response);
return true;
}

17
logic/Actions/ListReadingTypes.php

@ -7,11 +7,13 @@ use \SBRL\ResponseEncoder; @@ -7,11 +7,13 @@ use \SBRL\ResponseEncoder;
use \AirQuality\Repositories\IMeasurementTypeRepository;
use \AirQuality\ApiResponseSender;
use \AirQuality\PerfFormatter;
class ListReadingTypes implements IAction {
/** @var TomlConfig */
private $settings;
/** @var \SBRL\PerformanceCounter */
private $perfcounter;
/** @var IMeasurementTypeRepository */
private $types_repo;
@ -21,16 +23,17 @@ class ListReadingTypes implements IAction { @@ -21,16 +23,17 @@ class ListReadingTypes implements IAction {
public function __construct(
TomlConfig $in_settings,
IMeasurementTypeRepository $in_types_repo,
ApiResponseSender $in_sender) {
ApiResponseSender $in_sender,
\SBRL\PerformanceCounter $in_perfcounter) {
$this->settings = $in_settings;
$this->types_repo = $in_types_repo;
$this->sender = $in_sender;
$this->perfcounter = $in_perfcounter;
}
public function handle() : bool {
global $start_time;
$start_handle = microtime(true);
// 1: Parse & validate parameters
$device_id = !empty($_GET["device-id"]) ? intval($_GET["device-id"]) : null;
@ -45,10 +48,13 @@ class ListReadingTypes implements IAction { @@ -45,10 +48,13 @@ class ListReadingTypes implements IAction {
// 1: Pull data from database
$data = null;
$this->perfcounter->start("sql");
if(!is_int($device_id))
$data = $this->types_repo->get_all_types();
else
$data = $this->types_repo->get_types_by_device($device_id);
$this->perfcounter->end("sql");
// 1.5: Validate data from database
if(empty($data)) {
@ -56,7 +62,7 @@ class ListReadingTypes implements IAction { @@ -56,7 +62,7 @@ class ListReadingTypes implements IAction {
}
// 3: Serialise data
$start_encode = microtime(true);
$this->perfcounter->start("encode");
$response = null;
$response_type = "application/octet-stream";
$response_suggested_filename = "data-" . date(\DateTime::ATOM) . "";
@ -72,6 +78,7 @@ class ListReadingTypes implements IAction { @@ -72,6 +78,7 @@ class ListReadingTypes implements IAction {
$response = ResponseEncoder::encode_csv($data);
break;
}
$this->perfcounter->end("encode");
// 4: Send response
@ -81,7 +88,7 @@ class ListReadingTypes implements IAction { @@ -81,7 +88,7 @@ class ListReadingTypes implements IAction {
header("content-length: " . strlen($response));
header("content-type: $response_type");
header("content-disposition: inline; filename=$response_suggested_filename");
header("x-time-taken: " . PerfFormatter::format_perf_data($start_time, $start_handle, $start_encode));
header("x-time-taken: " . $this->perfcounter->render());
echo($response);
return true;
}

11
logic/Actions/Version.php

@ -4,23 +4,24 @@ namespace AirQuality\Actions; @@ -4,23 +4,24 @@ namespace AirQuality\Actions;
use \SBRL\TomlConfig;
use \AirQuality\PerfFormatter;
class Version implements IAction {
/** @var TomlConfig */
private $settings;
/** @var \SBRL\PerformanceCounter */
private $perfcounter;
public function __construct(
TomlConfig $in_settings) {
TomlConfig $in_settings,
\SBRL\PerformanceCounter $in_perfcounter) {
$this->settings = $in_settings;
$this->perfcounter = $in_perfcounter;
}
public function handle() : bool {
global $start_time;
$start_handle = microtime(true);
// 1: Parse markdown
$result = file_get_contents(ROOT_DIR."version");
@ -29,7 +30,7 @@ class Version implements IAction { @@ -29,7 +30,7 @@ class Version implements IAction {
// 2: Send response
header("content-length: " . strlen($result));
header("content-type: text/plain");
header("x-time-taken: " . PerfFormatter::format_perf_data($start_time, $start_handle, null));
header("x-time-taken: " . $this->perfcounter->render());
echo($result);
return true;
}

17
logic/Database.php

@ -10,8 +10,16 @@ use Psr\Container\ContainerInterface; @@ -10,8 +10,16 @@ use Psr\Container\ContainerInterface;
*/
class Database
{
// Whether the database connection should be read only or not.
// Defaults to true, as we don't need to write _anything_ to the database.
/** @var TomlConfig */
private $settings;
/** @var \SBRL\PerformanceCounter */
private $perfcounter;
/**
* Whether the database connection should be read only or not.
* Defaults to true, as we don't need to write _anything_ to the database.
* @var bool
*/
private const read_only = true;
private $connection;
@ -23,13 +31,15 @@ class Database @@ -23,13 +31,15 @@ class Database
\PDO::ATTR_AUTOCOMMIT => false
];
function __construct(TomlConfig $in_settings) {
function __construct(TomlConfig $in_settings, \SBRL\PerformanceCounter $in_perfcounter) {
$this->settings = $in_settings;
$this->perfcounter = $in_perfcounter;
$this->connect(); // Connect automagically
}
public function connect() {
$this->perfcounter->start("dbconnect");
$this->connection = new \PDO(
$this->get_connection_string(),
$this->settings->get("database.username"),
@ -39,6 +49,7 @@ class Database @@ -39,6 +49,7 @@ class Database
// Make this connection read-only
if(self::read_only)
$this->connection->query("SET SESSION TRANSACTION READ ONLY;");
$this->perfcounter->end("dbconnect");
}
public function query($sql, $variables = []) {

27
logic/PerfFormatter.php

@ -1,27 +0,0 @@ @@ -1,27 +0,0 @@
<?php
namespace AirQuality;
/**
* Formats performance data to be sent in a HTTP header.
*/
class PerfFormatter {
function __construct() {
}
public static function format_perf_data($start_init, $start_handle, $start_encode) {
$now = microtime(true);
$time_total = round(($now - $start_init) * 1000, 2);
$time_setup = round(($start_handle - $start_init) * 1000, 2);
$time_handle = round((($start_encode ?? $now) - $start_handle) * 1000, 2);
$time_encode = round(($now - $start_encode) * 1000, 2);
$result = "{$time_total}ms total | {$time_setup}ms setup, {$time_handle}ms handle";
if(!empty($start_encode))
$result .= ", {$time_encode}ms encode";
return $result;
}
}
Loading…
Cancel
Save