PocketMine-MP 5.15.1 git-be6754494fdbbb9dd57c058ba0e33a4a78c4581f
TimingsHandler.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20 */
21
22declare(strict_types=1);
23
24namespace pocketmine\timings;
25
28use function hrtime;
29use function implode;
30use function spl_object_id;
31
33 private const FORMAT_VERSION = 2; //peak timings fix
34
35 private static bool $enabled = false;
36 private static int $timingStart = 0;
37
39 public static function printTimings() : array{
40 $groups = [];
41
42 foreach(TimingsRecord::getAll() as $timings){
43 $time = $timings->getTotalTime();
44 $count = $timings->getCount();
45 if($count === 0){
46 //this should never happen - a timings record shouldn't exist if it hasn't been used
47 continue;
48 }
49
50 $avg = $time / $count;
51
52 $group = $timings->getGroup();
53 $groups[$group][] = implode(" ", [
54 $timings->getName(),
55 "Time: $time",
56 "Count: $count",
57 "Avg: $avg",
58 "Violations: " . $timings->getViolations(),
59 "RecordId: " . $timings->getId(),
60 "ParentRecordId: " . ($timings->getParentId() ?? "none"),
61 "TimerId: " . $timings->getTimerId(),
62 "Ticks: " . $timings->getTicksActive(),
63 "Peak: " . $timings->getPeakTime(),
64 ]);
65 }
66 $result = [];
67
68 foreach(Utils::stringifyKeys($groups) as $groupName => $lines){
69 $result[] = $groupName;
70 foreach($lines as $line){
71 $result[] = " $line";
72 }
73 }
74
75 $result[] = "# Version " . Server::getInstance()->getVersion();
76 $result[] = "# " . Server::getInstance()->getName() . " " . Server::getInstance()->getPocketMineVersion();
77
78 $result[] = "# FormatVersion " . self::FORMAT_VERSION;
79
80 $sampleTime = hrtime(true) - self::$timingStart;
81 $result[] = "Sample time $sampleTime (" . ($sampleTime / 1000000000) . "s)";
82 return $result;
83 }
84
85 public static function isEnabled() : bool{
86 return self::$enabled;
87 }
88
89 public static function setEnabled(bool $enable = true) : void{
90 self::$enabled = $enable;
91 self::reload();
92 }
93
94 public static function getStartTime() : float{
95 return self::$timingStart;
96 }
97
98 public static function reload() : void{
99 TimingsRecord::reset();
100 if(self::$enabled){
101 self::$timingStart = hrtime(true);
102 }
103 }
104
105 public static function tick(bool $measure = true) : void{
106 if(self::$enabled){
107 TimingsRecord::tick($measure);
108 }
109 }
110
111 private ?TimingsRecord $rootRecord = null;
112 private int $timingDepth = 0;
113
118 private array $recordsByParent = [];
119
120 public function __construct(
121 private string $name,
122 private ?TimingsHandler $parent = null,
123 private string $group = Timings::GROUP_MINECRAFT
124 ){}
125
126 public function getName() : string{ return $this->name; }
127
128 public function getGroup() : string{ return $this->group; }
129
130 public function startTiming() : void{
131 if(self::$enabled){
132 $this->internalStartTiming(hrtime(true));
133 }
134 }
135
136 private function internalStartTiming(int $now) : void{
137 if(++$this->timingDepth === 1){
138 if($this->parent !== null){
139 $this->parent->internalStartTiming($now);
140 }
141
142 $current = TimingsRecord::getCurrentRecord();
143 if($current !== null){
144 $record = $this->recordsByParent[spl_object_id($current)] ?? null;
145 if($record === null){
146 $record = new TimingsRecord($this, $current);
147 $this->recordsByParent[spl_object_id($current)] = $record;
148 }
149 }else{
150 if($this->rootRecord === null){
151 $this->rootRecord = new TimingsRecord($this, null);
152 }
153 $record = $this->rootRecord;
154 }
155 $record->startTiming($now);
156 }
157 }
158
159 public function stopTiming() : void{
160 if(self::$enabled){
161 $this->internalStopTiming(hrtime(true));
162 }
163 }
164
165 private function internalStopTiming(int $now) : void{
166 if($this->timingDepth === 0){
167 //TODO: it would be nice to bail here, but since we'd have to track timing depth across resets
168 //and enable/disable, it would have a performance impact. Therefore, considering the limited
169 //usefulness of bailing here anyway, we don't currently bother.
170 return;
171 }
172 if(--$this->timingDepth !== 0){
173 return;
174 }
175
176 $record = TimingsRecord::getCurrentRecord();
177 $timerId = spl_object_id($this);
178 for(; $record !== null && $record->getTimerId() !== $timerId; $record = TimingsRecord::getCurrentRecord()){
179 \GlobalLogger::get()->error("Timer \"" . $record->getName() . "\" should have been stopped before stopping timer \"" . $this->name . "\"");
180 $record->stopTiming($now);
181 }
182 $record?->stopTiming($now);
183 if($this->parent !== null){
184 $this->parent->internalStopTiming($now);
185 }
186 }
187
195 public function time(\Closure $closure){
196 $this->startTiming();
197 try{
198 return $closure();
199 }finally{
200 $this->stopTiming();
201 }
202 }
203
207 public function reset() : void{
208 $this->rootRecord = null;
209 $this->recordsByParent = [];
210 $this->timingDepth = 0;
211 }
212}
static stringifyKeys(array $array)
Definition: Utils.php:605