Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async task timings #6333

Merged
merged 17 commits into from
Dec 1, 2024
Merged
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -41,7 +41,7 @@
"pocketmine/callback-validator": "^1.0.2",
"pocketmine/color": "^0.3.0",
"pocketmine/errorhandler": "^0.7.0",
"pocketmine/locale-data": "~2.21.0",
"pocketmine/locale-data": "~2.22.0",
"pocketmine/log": "^0.4.0",
"pocketmine/math": "~1.0.0",
"pocketmine/nbt": "~1.0.0",
14 changes: 7 additions & 7 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 31 additions & 3 deletions src/Server.php
Original file line number Diff line number Diff line change
@@ -89,6 +89,8 @@
use pocketmine\promise\PromiseResolver;
use pocketmine\resourcepacks\ResourcePackManager;
use pocketmine\scheduler\AsyncPool;
use pocketmine\scheduler\TimingsCollectionTask;
use pocketmine\scheduler\TimingsControlTask;
use pocketmine\snooze\SleeperHandler;
use pocketmine\stats\SendUsageTask;
use pocketmine\thread\log\AttachableThreadSafeLogger;
@@ -891,7 +893,36 @@ public function __construct(
$poolSize = max(1, (int) $poolSize);
}

TimingsHandler::setEnabled($this->configGroup->getPropertyBool(Yml::SETTINGS_ENABLE_PROFILING, false));
$this->profilingTickRate = $this->configGroup->getPropertyInt(Yml::SETTINGS_PROFILE_REPORT_TRIGGER, self::TARGET_TICKS_PER_SECOND);

$this->asyncPool = new AsyncPool($poolSize, max(-1, $this->configGroup->getPropertyInt(Yml::MEMORY_ASYNC_WORKER_HARD_LIMIT, 256)), $this->autoloader, $this->logger, $this->tickSleeper);
$this->asyncPool->addWorkerStartHook(function(int $i) : void{
if(TimingsHandler::isEnabled()){
$this->asyncPool->submitTaskToWorker(TimingsControlTask::setEnabled(true), $i);
}
});
TimingsHandler::getToggleCallbacks()->add(function(bool $enable) : void{
foreach($this->asyncPool->getRunningWorkers() as $workerId){
$this->asyncPool->submitTaskToWorker(TimingsControlTask::setEnabled($enable), $workerId);
}
});
TimingsHandler::getReloadCallbacks()->add(function() : void{
foreach($this->asyncPool->getRunningWorkers() as $workerId){
$this->asyncPool->submitTaskToWorker(TimingsControlTask::reload(), $workerId);
}
});
TimingsHandler::getCollectCallbacks()->add(function() : array{
$promises = [];
foreach($this->asyncPool->getRunningWorkers() as $workerId){
$resolver = new PromiseResolver();
$this->asyncPool->submitTaskToWorker(new TimingsCollectionTask($resolver), $workerId);

$promises[] = $resolver->getPromise();
}

return $promises;
});

$netCompressionThreshold = -1;
if($this->configGroup->getPropertyInt(Yml::NETWORK_BATCH_THRESHOLD, 256) >= 0){
@@ -965,9 +996,6 @@ public function __construct(
)));
$this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_license($this->getName())));

TimingsHandler::setEnabled($this->configGroup->getPropertyBool(Yml::SETTINGS_ENABLE_PROFILING, false));
$this->profilingTickRate = $this->configGroup->getPropertyInt(Yml::SETTINGS_PROFILE_REPORT_TRIGGER, self::TARGET_TICKS_PER_SECOND);

DefaultPermissions::registerCorePermissions();

$this->commandMap = new SimpleCommandMap($this);
159 changes: 84 additions & 75 deletions src/command/defaults/TimingsCommand.php
Original file line number Diff line number Diff line change
@@ -26,28 +26,28 @@
use pocketmine\command\Command;
use pocketmine\command\CommandSender;
use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\errorhandler\ErrorToExceptionHandler;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\permission\DefaultPermissionNames;
use pocketmine\player\Player;
use pocketmine\scheduler\BulkCurlTask;
use pocketmine\scheduler\BulkCurlTaskOperation;
use pocketmine\timings\TimingsHandler;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\InternetException;
use pocketmine\utils\InternetRequestResult;
use pocketmine\utils\Utils;
use pocketmine\YmlServerProperties;
use Symfony\Component\Filesystem\Path;
use function count;
use function fclose;
use function file_exists;
use function fopen;
use function fseek;
use function fwrite;
use function http_build_query;
use function implode;
use function is_array;
use function json_decode;
use function mkdir;
use function stream_get_contents;
use function strtolower;
use const CURLOPT_AUTOREFERER;
use const CURLOPT_FOLLOWLOCATION;
@@ -101,82 +101,91 @@ public function execute(CommandSender $sender, string $commandLabel, array $args
TimingsHandler::reload();
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_reset());
}elseif($mode === "merged" || $mode === "report" || $paste){
$timings = "";
if($paste){
$fileTimings = Utils::assumeNotFalse(fopen("php://temp", "r+b"), "Opening php://temp should never fail");
}else{
$index = 0;
$timingFolder = Path::join($sender->getServer()->getDataPath(), "timings");

if(!file_exists($timingFolder)){
mkdir($timingFolder, 0777);
}
$timings = Path::join($timingFolder, "timings.txt");
while(file_exists($timings)){
$timings = Path::join($timingFolder, "timings" . (++$index) . ".txt");
}

$fileTimings = fopen($timings, "a+b");
}
$lines = TimingsHandler::printTimings();
foreach($lines as $line){
fwrite($fileTimings, $line . PHP_EOL);
}

if($paste){
fseek($fileTimings, 0);
$data = [
"browser" => $agent = $sender->getServer()->getName() . " " . $sender->getServer()->getPocketMineVersion(),
"data" => $content = stream_get_contents($fileTimings)
];
fclose($fileTimings);

$host = $sender->getServer()->getConfigGroup()->getPropertyString(YmlServerProperties::TIMINGS_HOST, "timings.pmmp.io");

$sender->getServer()->getAsyncPool()->submitTask(new BulkCurlTask(
[new BulkCurlTaskOperation(
"https://$host?upload=true",
10,
[],
[
CURLOPT_HTTPHEADER => [
"User-Agent: $agent",
"Content-Type: application/x-www-form-urlencoded"
],
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($data),
CURLOPT_AUTOREFERER => false,
CURLOPT_FOLLOWLOCATION => false
]
)],
function(array $results) use ($sender, $host) : void{
/** @phpstan-var array<InternetRequestResult|InternetException> $results */
if($sender instanceof Player && !$sender->isOnline()){ // TODO replace with a more generic API method for checking availability of CommandSender
return;
}
$result = $results[0];
if($result instanceof InternetException){
$sender->getServer()->getLogger()->logException($result);
return;
}
$response = json_decode($result->getBody(), true);
if(is_array($response) && isset($response["id"])){
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsRead(
"https://" . $host . "/?id=" . $response["id"]));
}else{
$sender->getServer()->getLogger()->debug("Invalid response from timings server (" . $result->getCode() . "): " . $result->getBody());
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_pasteError());
}
}
));
}else{
fclose($fileTimings);
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsWrite($timings));
}
$timingsPromise = TimingsHandler::requestPrintTimings();
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_collect());
$timingsPromise->onCompletion(
fn(array $lines) => $paste ? $this->uploadReport($lines, $sender) : $this->createReportFile($lines, $sender),
fn() => throw new AssumptionFailedError("This promise is not expected to be rejected")
);
}else{
throw new InvalidCommandSyntaxException();
}

return true;
}

/**
* @param string[] $lines
* @phpstan-param list<string> $lines
*/
private function createReportFile(array $lines, CommandSender $sender) : void{
$index = 0;
$timingFolder = Path::join($sender->getServer()->getDataPath(), "timings");

if(!file_exists($timingFolder)){
mkdir($timingFolder, 0777);
}
$timings = Path::join($timingFolder, "timings.txt");
while(file_exists($timings)){
$timings = Path::join($timingFolder, "timings" . (++$index) . ".txt");
}

$fileTimings = ErrorToExceptionHandler::trapAndRemoveFalse(fn() => fopen($timings, "a+b"));
foreach($lines as $line){
fwrite($fileTimings, $line . PHP_EOL);
}
fclose($fileTimings);

Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsWrite($timings));
}

/**
* @param string[] $lines
* @phpstan-param list<string> $lines
*/
private function uploadReport(array $lines, CommandSender $sender) : void{
$data = [
"browser" => $agent = $sender->getServer()->getName() . " " . $sender->getServer()->getPocketMineVersion(),
"data" => implode("\n", $lines)
];

$host = $sender->getServer()->getConfigGroup()->getPropertyString(YmlServerProperties::TIMINGS_HOST, "timings.pmmp.io");

$sender->getServer()->getAsyncPool()->submitTask(new BulkCurlTask(
[new BulkCurlTaskOperation(
"https://$host?upload=true",
10,
[],
[
CURLOPT_HTTPHEADER => [
"User-Agent: $agent",
"Content-Type: application/x-www-form-urlencoded"
],
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($data),
CURLOPT_AUTOREFERER => false,
CURLOPT_FOLLOWLOCATION => false
]
)],
function(array $results) use ($sender, $host) : void{
/** @phpstan-var array<InternetRequestResult|InternetException> $results */
if($sender instanceof Player && !$sender->isOnline()){ // TODO replace with a more generic API method for checking availability of CommandSender
return;
}
$result = $results[0];
if($result instanceof InternetException){
$sender->getServer()->getLogger()->logException($result);
return;
}
$response = json_decode($result->getBody(), true);
if(is_array($response) && isset($response["id"])){
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsRead(
"https://" . $host . "/?id=" . $response["id"]));
}else{
$sender->getServer()->getLogger()->debug("Invalid response from timings server (" . $result->getCode() . "): " . $result->getBody());
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_pasteError());
}
}
));
}
}
4 changes: 4 additions & 0 deletions src/lang/KnownTranslationFactory.php
Original file line number Diff line number Diff line change
@@ -1441,6 +1441,10 @@ public static function pocketmine_command_timings_alreadyEnabled() : Translatabl
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_TIMINGS_ALREADYENABLED, []);
}

public static function pocketmine_command_timings_collect() : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_TIMINGS_COLLECT, []);
}

public static function pocketmine_command_timings_description() : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_TIMINGS_DESCRIPTION, []);
}
1 change: 1 addition & 0 deletions src/lang/KnownTranslationKeys.php
Original file line number Diff line number Diff line change
@@ -315,6 +315,7 @@ final class KnownTranslationKeys{
public const POCKETMINE_COMMAND_TIME_DESCRIPTION = "pocketmine.command.time.description";
public const POCKETMINE_COMMAND_TIME_USAGE = "pocketmine.command.time.usage";
public const POCKETMINE_COMMAND_TIMINGS_ALREADYENABLED = "pocketmine.command.timings.alreadyEnabled";
public const POCKETMINE_COMMAND_TIMINGS_COLLECT = "pocketmine.command.timings.collect";
public const POCKETMINE_COMMAND_TIMINGS_DESCRIPTION = "pocketmine.command.timings.description";
public const POCKETMINE_COMMAND_TIMINGS_DISABLE = "pocketmine.command.timings.disable";
public const POCKETMINE_COMMAND_TIMINGS_ENABLE = "pocketmine.command.timings.enable";
10 changes: 9 additions & 1 deletion src/scheduler/AsyncTask.php
Original file line number Diff line number Diff line change
@@ -27,6 +27,7 @@
use pmmp\thread\ThreadSafe;
use pmmp\thread\ThreadSafeArray;
use pocketmine\thread\NonThreadSafeValue;
use pocketmine\timings\Timings;
use function array_key_exists;
use function igbinary_serialize;
use function igbinary_unserialize;
@@ -78,7 +79,14 @@ abstract class AsyncTask extends Runnable{
public function run() : void{
$this->result = null;

$this->onRun();
$timings = Timings::getAsyncTaskRunTimings($this);
$timings->startTiming();

try{
$this->onRun();
}finally{
$timings->stopTiming();
}

$this->finished = true;
AsyncWorker::getNotifier()->wakeupSleeper();
61 changes: 61 additions & 0 deletions src/scheduler/TimingsCollectionTask.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/

declare(strict_types=1);

namespace pocketmine\scheduler;

use pmmp\thread\Thread as NativeThread;
use pocketmine\promise\PromiseResolver;
use pocketmine\timings\TimingsHandler;

/**
* @phpstan-type Resolver PromiseResolver<list<string>>
*/
final class TimingsCollectionTask extends AsyncTask{
private const TLS_KEY_RESOLVER = "resolver";

/**
* @phpstan-param PromiseResolver<list<string>> $promiseResolver
*/
public function __construct(PromiseResolver $promiseResolver){
$this->storeLocal(self::TLS_KEY_RESOLVER, $promiseResolver);
}

public function onRun() : void{
$this->setResult(TimingsHandler::printCurrentThreadRecords(NativeThread::getCurrentThreadId()));
}

public function onCompletion() : void{
/**
* @var string[] $result
* @phpstan-var list<string> $result
*/
$result = $this->getResult();
/**
* @var PromiseResolver $promiseResolver
* @phpstan-var PromiseResolver<list<string>> $promiseResolver
*/
$promiseResolver = $this->fetchLocal(self::TLS_KEY_RESOLVER);

$promiseResolver->resolve($result);
}
}
60 changes: 60 additions & 0 deletions src/scheduler/TimingsControlTask.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/

declare(strict_types=1);

namespace pocketmine\scheduler;

use pocketmine\timings\TimingsHandler;

final class TimingsControlTask extends AsyncTask{

private const ENABLE = 1;
private const DISABLE = 2;
private const RELOAD = 3;

private function __construct(
private int $operation
){}

public static function setEnabled(bool $enable) : self{
return new self($enable ? self::ENABLE : self::DISABLE);
}

public static function reload() : self{
return new self(self::RELOAD);
}

public function onRun() : void{
if($this->operation === self::ENABLE){
TimingsHandler::setEnabled(true);
\GlobalLogger::get()->debug("Enabled timings");
}elseif($this->operation === self::DISABLE){
TimingsHandler::setEnabled(false);
\GlobalLogger::get()->debug("Disabled timings");
}elseif($this->operation === self::RELOAD){
TimingsHandler::reload();
\GlobalLogger::get()->debug("Reset timings");
}else{
throw new \InvalidArgumentException("Invalid operation $this->operation");
}
}
}
21 changes: 21 additions & 0 deletions src/timings/Timings.php
Original file line number Diff line number Diff line change
@@ -123,11 +123,16 @@ abstract class Timings{

/** @var TimingsHandler[] */
private static array $asyncTaskProgressUpdate = [];

/** @var TimingsHandler[] */
private static array $asyncTaskCompletion = [];
/** @var TimingsHandler[] */
private static array $asyncTaskError = [];

private static TimingsHandler $asyncTaskWorkers;
/** @var TimingsHandler[] */
private static array $asyncTaskRun = [];

public static function init() : void{
if(self::$initialized){
return;
@@ -187,6 +192,8 @@ public static function init() : void{
self::$asyncTaskCompletionParent = new TimingsHandler("Async Tasks - Completion Handlers", self::$schedulerAsync);
self::$asyncTaskErrorParent = new TimingsHandler("Async Tasks - Error Handlers", self::$schedulerAsync);

self::$asyncTaskWorkers = new TimingsHandler("Async Task Workers");

self::$playerCommand = new TimingsHandler("Player Command");
self::$craftingDataCacheRebuild = new TimingsHandler("Build CraftingDataPacket Cache");

@@ -352,4 +359,18 @@ public static function getAsyncTaskErrorTimings(AsyncTask $task, string $group =

return self::$asyncTaskError[$taskClass];
}

public static function getAsyncTaskRunTimings(AsyncTask $task, string $group = self::GROUP_MINECRAFT) : TimingsHandler{
$taskClass = $task::class;
if(!isset(self::$asyncTaskRun[$taskClass])){
self::init();
self::$asyncTaskRun[$taskClass] = new TimingsHandler(
"AsyncTask - " . self::shortenCoreClassName($taskClass, "pocketmine\\") . " - Run",
self::$asyncTaskWorkers,
$group
);
}

return self::$asyncTaskRun[$taskClass];
}
}
128 changes: 122 additions & 6 deletions src/timings/TimingsHandler.php
Original file line number Diff line number Diff line change
@@ -23,20 +23,64 @@

namespace pocketmine\timings;

use pocketmine\promise\Promise;
use pocketmine\promise\PromiseResolver;
use pocketmine\Server;
use pocketmine\utils\ObjectSet;
use pocketmine\utils\Utils;
use function array_merge;
use function array_push;
use function hrtime;
use function implode;
use function spl_object_id;

/**
* @phpstan-type CollectPromise Promise<list<string>>
*/
class TimingsHandler{
private const FORMAT_VERSION = 2; //peak timings fix
private const FORMAT_VERSION = 3; //thread timings collection

private static bool $enabled = false;
private static int $timingStart = 0;

/** @return string[] */
public static function printTimings() : array{
/** @phpstan-var ObjectSet<\Closure(bool $enable) : void> */
private static ?ObjectSet $toggleCallbacks = null;
/** @phpstan-var ObjectSet<\Closure() : void> */
private static ?ObjectSet $reloadCallbacks = null;
/** @phpstan-var ObjectSet<\Closure() : list<CollectPromise>> */
private static ?ObjectSet $collectCallbacks = null;

/**
* @phpstan-template T of object
* @phpstan-param ?ObjectSet<T> $where
* @phpstan-param-out ObjectSet<T> $where
* @phpstan-return ObjectSet<T>
*/
private static function lazyGetSet(?ObjectSet &$where) : ObjectSet{
//workaround for phpstan bug - allows us to ignore 1 error instead of 6 without suppressing other errors
return $where ??= new ObjectSet();
}

/**
* @phpstan-return ObjectSet<\Closure(bool $enable) : void>
*/
public static function getToggleCallbacks() : ObjectSet{ return self::lazyGetSet(self::$toggleCallbacks); }

/**
* @phpstan-return ObjectSet<\Closure() : void>
*/
public static function getReloadCallbacks() : ObjectSet{ return self::lazyGetSet(self::$reloadCallbacks); }

/**
* @phpstan-return ObjectSet<\Closure() : list<CollectPromise>>
*/
public static function getCollectCallbacks() : ObjectSet{ return self::lazyGetSet(self::$collectCallbacks); }

/**
* @return string[]
* @phpstan-return list<string>
*/
public static function printCurrentThreadRecords(?int $threadId) : array{
$groups = [];

foreach(TimingsRecord::getAll() as $timings){
@@ -49,7 +93,7 @@ public static function printTimings() : array{

$avg = $time / $count;

$group = $timings->getGroup();
$group = $timings->getGroup() . ($threadId !== null ? " ThreadId: $threadId" : "");
$groups[$group][] = implode(" ", [
$timings->getName(),
"Time: $time",
@@ -72,36 +116,108 @@ public static function printTimings() : array{
}
}

return $result;
}

/**
* @return string[]
*/
private static function printFooter() : array{
$result = [];

$result[] = "# Version " . Server::getInstance()->getVersion();
$result[] = "# " . Server::getInstance()->getName() . " " . Server::getInstance()->getPocketMineVersion();

$result[] = "# FormatVersion " . self::FORMAT_VERSION;

$sampleTime = hrtime(true) - self::$timingStart;
$result[] = "Sample time $sampleTime (" . ($sampleTime / 1000000000) . "s)";

return $result;
}

/**
* @deprecated This only collects timings from the main thread. Collecting timings from all threads is an async
* operation, so it can't be done synchronously.
*
* @return string[]
*/
public static function printTimings() : array{
$records = self::printCurrentThreadRecords(null);
$footer = self::printFooter();

return [...$records, ...$footer];
}

/**
* Collects timings asynchronously, allowing timings from multiple threads to be aggregated into a single report.
*
* NOTE: You need to add a callback to collectCallbacks if you want to include timings from other threads. They
* won't be automatically collected if you don't, since the main thread has no way to access them.
*
* This is an asynchronous operation, and the result is returned as a promise.
* The caller must add a callback to the returned promise to get the complete timings report.
*
* @phpstan-return Promise<list<string>>
*/
public static function requestPrintTimings() : Promise{
$thisThreadRecords = self::printCurrentThreadRecords(null);

$otherThreadRecordPromises = [];
if(self::$collectCallbacks !== null){
foreach(self::$collectCallbacks as $callback){
$callbackPromises = $callback();
array_push($otherThreadRecordPromises, ...$callbackPromises);
}
}

$resolver = new PromiseResolver();
Promise::all($otherThreadRecordPromises)->onCompletion(
function(array $promisedRecords) use ($resolver, $thisThreadRecords) : void{
$resolver->resolve([...$thisThreadRecords, ...array_merge(...$promisedRecords), ...self::printFooter()]);
},
function() : void{
throw new \AssertionError("This promise is not expected to be rejected");
}
);

return $resolver->getPromise();
}

public static function isEnabled() : bool{
return self::$enabled;
}

public static function setEnabled(bool $enable = true) : void{
self::$enabled = $enable;
self::reload();
self::internalReload();
if(self::$toggleCallbacks !== null){
foreach(self::$toggleCallbacks as $callback){
$callback($enable);
}
}
}

public static function getStartTime() : float{
return self::$timingStart;
}

public static function reload() : void{
private static function internalReload() : void{
TimingsRecord::reset();
if(self::$enabled){
self::$timingStart = hrtime(true);
}
}

public static function reload() : void{
self::internalReload();
if(self::$reloadCallbacks !== null){
foreach(self::$reloadCallbacks as $callback){
$callback();
}
}
}

public static function tick(bool $measure = true) : void{
if(self::$enabled){
TimingsRecord::tick($measure);
5 changes: 5 additions & 0 deletions tests/phpstan/configs/phpstan-bugs.neon
Original file line number Diff line number Diff line change
@@ -90,6 +90,11 @@ parameters:
count: 1
path: ../../../src/plugin/PluginManager.php

-
message: "#^Method pocketmine\\\\timings\\\\TimingsHandler\\:\\:lazyGetSet\\(\\) should return pocketmine\\\\utils\\\\ObjectSet\\<T of object\\> but returns pocketmine\\\\utils\\\\ObjectSet\\<object\\>\\.$#"
count: 1
path: ../../../src/timings/TimingsHandler.php

-
message: "#^Casting to int something that's already int\\.$#"
count: 1