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";
128 private static function printFooter() : array{
131 $result[] =
"# Version " . Server::getInstance()->getVersion();
132 $result[] =
"# " . Server::getInstance()->getName() .
" " . Server::getInstance()->getPocketMineVersion();
134 $result[] =
"# FormatVersion " . self::FORMAT_VERSION;
136 $sampleTime = hrtime(
true) - self::$timingStart;
137 $result[] =
"Sample time $sampleTime (" . ($sampleTime / 1000000000) .
"s)";
149 $records = self::printCurrentThreadRecords();
150 $footer = self::printFooter();
152 return [...$records, ...$footer];
167 $thisThreadRecords = self::printCurrentThreadRecords();
169 $otherThreadRecordPromises = [];
170 if(self::$collectCallbacks !==
null){
171 foreach(self::$collectCallbacks as $callback){
172 $callbackPromises = $callback();
173 array_push($otherThreadRecordPromises, ...$callbackPromises);
179 Promise::all($otherThreadRecordPromises)->onCompletion(
180 function(array $promisedRecords) use ($resolver, $thisThreadRecords) :
void{
181 $resolver->resolve([...$thisThreadRecords, ...array_merge(...$promisedRecords), ...self::printFooter()]);
184 throw new \AssertionError(
"This promise is not expected to be rejected");
188 return $resolver->getPromise();
191 public static function isEnabled() : bool{
192 return self::$enabled;
195 public static function setEnabled(
bool $enable =
true) : void{
196 if($enable === self::$enabled){
199 self::$enabled = $enable;
200 self::internalReload();
201 if(self::$toggleCallbacks !==
null){
202 foreach(self::$toggleCallbacks as $callback){
208 public static function getStartTime() : float{
209 return self::$timingStart;
212 private static function internalReload() : void{
213 TimingsRecord::reset();
215 self::$timingStart = hrtime(
true);
219 public static function reload() : void{
220 self::internalReload();
221 if(self::$reloadCallbacks !==
null){
222 foreach(self::$reloadCallbacks as $callback){
228 public static function tick(
bool $measure =
true) : void{
230 TimingsRecord::tick($measure);
234 private ?TimingsRecord $rootRecord =
null;
235 private int $timingDepth = 0;
241 private array $recordsByParent = [];
243 public function __construct(
244 private string $name,
245 private ?TimingsHandler $parent =
null,
246 private string $group = Timings::GROUP_MINECRAFT
249 public function getName() : string{ return $this->name; }
251 public function getGroup() : string{ return $this->group; }
253 public function startTiming() : void{
255 $this->internalStartTiming(hrtime(
true));
259 private function internalStartTiming(
int $now) : void{
260 if(++$this->timingDepth === 1){
261 if($this->parent !==
null){
262 $this->parent->internalStartTiming($now);
265 $current = TimingsRecord::getCurrentRecord();
266 if($current !==
null){
267 $record = $this->recordsByParent[spl_object_id($current)] ??
null;
268 if($record ===
null){
269 $record =
new TimingsRecord($this, $current);
270 $this->recordsByParent[spl_object_id($current)] = $record;
273 if($this->rootRecord ===
null){
274 $this->rootRecord =
new TimingsRecord($this,
null);
276 $record = $this->rootRecord;
278 $record->startTiming($now);
282 public function stopTiming() : void{
284 $this->internalStopTiming(hrtime(
true));
288 private function internalStopTiming(
int $now) : void{
289 if($this->timingDepth === 0){
295 if(--$this->timingDepth !== 0){
299 $record = TimingsRecord::getCurrentRecord();
300 $timerId = spl_object_id($this);
301 for(; $record !==
null && $record->getTimerId() !== $timerId; $record = TimingsRecord::getCurrentRecord()){
302 \GlobalLogger::get()->error(
"Timer \"" . $record->getName() .
"\" should have been stopped before stopping timer \"" . $this->name .
"\"");
303 $record->stopTiming($now);
305 $record?->stopTiming($now);
306 if($this->parent !==
null){
307 $this->parent->internalStopTiming($now);
318 public function time(\Closure $closure){
319 $this->startTiming();
330 public function reset() : void{
331 $this->rootRecord = null;
332 $this->recordsByParent = [];
333 $this->timingDepth = 0;