42 private const FORMAT_VERSION = 3;
44 private static bool $enabled =
false;
45 private static int $timingStart = 0;
48 private static ?
ObjectSet $toggleCallbacks =
null;
50 private static ?
ObjectSet $reloadCallbacks =
null;
52 private static ?
ObjectSet $collectCallbacks =
null;
85 $threadId = NativeThread::getCurrentThread()?->getThreadId();
88 foreach(TimingsRecord::getAll() as $timings){
89 $time = $timings->getTotalTime();
90 $count = $timings->getCount();
96 $avg = $time / $count;
98 $group = $timings->getGroup() . ($threadId !==
null ?
" ThreadId: $threadId" :
"");
99 $groups[$group][] = implode(
" ", [
104 "Violations: " . $timings->getViolations(),
105 "RecordId: " . $timings->getId(),
106 "ParentRecordId: " . ($timings->getParentId() ??
"none"),
107 "TimerId: " . $timings->getTimerId(),
108 "Ticks: " . $timings->getTicksActive(),
109 "Peak: " . $timings->getPeakTime(),
114 foreach(Utils::stringifyKeys($groups) as $groupName => $lines){
115 $result[] = $groupName;
116 foreach($lines as $line){
117 $result[] =
" $line";
127 private static function printFooter() : array{
130 $result[] =
"# Version " . Server::getInstance()->getVersion();
131 $result[] =
"# " . Server::getInstance()->getName() .
" " . Server::getInstance()->getPocketMineVersion();
133 $result[] =
"# FormatVersion " . self::FORMAT_VERSION;
135 $sampleTime = hrtime(
true) - self::$timingStart;
136 $result[] =
"Sample time $sampleTime (" . ($sampleTime / 1000000000) .
"s)";
148 $records = self::printCurrentThreadRecords();
149 $footer = self::printFooter();
151 return [...$records, ...$footer];
166 $thisThreadRecords = self::printCurrentThreadRecords();
168 $otherThreadRecordPromises = [];
169 if(self::$collectCallbacks !==
null){
170 foreach(self::$collectCallbacks as $callback){
171 $callbackPromises = $callback();
172 array_push($otherThreadRecordPromises, ...$callbackPromises);
177 Promise::all($otherThreadRecordPromises)->onCompletion(
178 function(array $promisedRecords) use ($resolver, $thisThreadRecords) :
void{
179 $resolver->resolve([...$thisThreadRecords, ...array_merge(...$promisedRecords), ...self::printFooter()]);
182 throw new \AssertionError(
"This promise is not expected to be rejected");
186 return $resolver->getPromise();
189 public static function isEnabled() : bool{
190 return self::$enabled;
193 public static function setEnabled(
bool $enable =
true) : void{
194 if($enable === self::$enabled){
197 self::$enabled = $enable;
198 self::internalReload();
199 if(self::$toggleCallbacks !==
null){
200 foreach(self::$toggleCallbacks as $callback){
206 public static function getStartTime() : float{
207 return self::$timingStart;
210 private static function internalReload() : void{
211 TimingsRecord::reset();
213 self::$timingStart = hrtime(
true);
217 public static function reload() : void{
218 self::internalReload();
219 if(self::$reloadCallbacks !==
null){
220 foreach(self::$reloadCallbacks as $callback){
226 public static function tick(
bool $measure =
true) : void{
228 TimingsRecord::tick($measure);
232 private ?TimingsRecord $rootRecord =
null;
233 private int $timingDepth = 0;
239 private array $recordsByParent = [];
241 public function __construct(
242 private string $name,
243 private ?TimingsHandler $parent =
null,
244 private string $group = Timings::GROUP_MINECRAFT
247 public function getName() : string{ return $this->name; }
249 public function getGroup() : string{ return $this->group; }
251 public function startTiming() : void{
253 $this->internalStartTiming(hrtime(
true));
257 private function internalStartTiming(
int $now) : void{
258 if(++$this->timingDepth === 1){
259 if($this->parent !==
null){
260 $this->parent->internalStartTiming($now);
263 $current = TimingsRecord::getCurrentRecord();
264 if($current !==
null){
265 $record = $this->recordsByParent[spl_object_id($current)] ??
null;
266 if($record ===
null){
267 $record =
new TimingsRecord($this, $current);
268 $this->recordsByParent[spl_object_id($current)] = $record;
271 if($this->rootRecord ===
null){
272 $this->rootRecord =
new TimingsRecord($this,
null);
274 $record = $this->rootRecord;
276 $record->startTiming($now);
280 public function stopTiming() : void{
282 $this->internalStopTiming(hrtime(
true));
286 private function internalStopTiming(
int $now) : void{
287 if($this->timingDepth === 0){
293 if(--$this->timingDepth !== 0){
297 $record = TimingsRecord::getCurrentRecord();
298 $timerId = spl_object_id($this);
299 for(; $record !==
null && $record->getTimerId() !== $timerId; $record = TimingsRecord::getCurrentRecord()){
300 \GlobalLogger::get()->error(
"Timer \"" . $record->getName() .
"\" should have been stopped before stopping timer \"" . $this->name .
"\"");
301 $record->stopTiming($now);
303 $record?->stopTiming($now);
304 if($this->parent !==
null){
305 $this->parent->internalStopTiming($now);
316 public function time(\Closure $closure){
317 $this->startTiming();
328 public function reset() : void{
329 $this->rootRecord = null;
330 $this->recordsByParent = [];
331 $this->timingDepth = 0;