mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-07 02:08:21 +00:00
Added capability to dump AsyncWorker memory
This commit is contained in:
@ -25,7 +25,9 @@ namespace pocketmine;
|
|||||||
|
|
||||||
use pocketmine\event\server\LowMemoryEvent;
|
use pocketmine\event\server\LowMemoryEvent;
|
||||||
use pocketmine\event\Timings;
|
use pocketmine\event\Timings;
|
||||||
|
use pocketmine\scheduler\DumpWorkerMemoryTask;
|
||||||
use pocketmine\scheduler\GarbageCollectionTask;
|
use pocketmine\scheduler\GarbageCollectionTask;
|
||||||
|
use pocketmine\utils\MainLogger;
|
||||||
use pocketmine\utils\Utils;
|
use pocketmine\utils\Utils;
|
||||||
|
|
||||||
class MemoryManager{
|
class MemoryManager{
|
||||||
@ -261,6 +263,25 @@ class MemoryManager{
|
|||||||
* @param int $maxStringSize
|
* @param int $maxStringSize
|
||||||
*/
|
*/
|
||||||
public function dumpServerMemory(string $outputFolder, int $maxNesting, int $maxStringSize){
|
public function dumpServerMemory(string $outputFolder, int $maxNesting, int $maxStringSize){
|
||||||
|
MainLogger::getLogger()->notice("[Dump] After the memory dump is done, the server might crash");
|
||||||
|
self::dumpMemory($this->server, $this->server->getLoader(), $outputFolder, $maxNesting, $maxStringSize);
|
||||||
|
|
||||||
|
$scheduler = $this->server->getScheduler();
|
||||||
|
for($i = 0, $size = $scheduler->getAsyncTaskPoolSize(); $i < $size; ++$i){
|
||||||
|
$scheduler->scheduleAsyncTaskToWorker(new DumpWorkerMemoryTask($outputFolder, $maxNesting, $maxStringSize), $i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static memory dumper accessible from any thread.
|
||||||
|
*
|
||||||
|
* @param mixed $startingObject
|
||||||
|
* @param \ClassLoader $loader
|
||||||
|
* @param string $outputFolder
|
||||||
|
* @param int $maxNesting
|
||||||
|
* @param int $maxStringSize
|
||||||
|
*/
|
||||||
|
public static function dumpMemory($startingObject, \ClassLoader $loader, string $outputFolder, int $maxNesting, int $maxStringSize){
|
||||||
$hardLimit = ini_get('memory_limit');
|
$hardLimit = ini_get('memory_limit');
|
||||||
ini_set('memory_limit', '-1');
|
ini_set('memory_limit', '-1');
|
||||||
gc_disable();
|
gc_disable();
|
||||||
@ -269,12 +290,8 @@ class MemoryManager{
|
|||||||
mkdir($outputFolder, 0777, true);
|
mkdir($outputFolder, 0777, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->server->getLogger()->notice("[Dump] After the memory dump is done, the server might crash");
|
|
||||||
|
|
||||||
$obData = fopen($outputFolder . "/objects.js", "wb+");
|
$obData = fopen($outputFolder . "/objects.js", "wb+");
|
||||||
|
|
||||||
$staticProperties = [];
|
|
||||||
|
|
||||||
$data = [];
|
$data = [];
|
||||||
|
|
||||||
$objects = [];
|
$objects = [];
|
||||||
@ -283,8 +300,9 @@ class MemoryManager{
|
|||||||
|
|
||||||
$instanceCounts = [];
|
$instanceCounts = [];
|
||||||
|
|
||||||
|
$staticProperties = [];
|
||||||
$staticCount = 0;
|
$staticCount = 0;
|
||||||
foreach($this->server->getLoader()->getClasses() as $className){
|
foreach($loader->getClasses() as $className){
|
||||||
$reflection = new \ReflectionClass($className);
|
$reflection = new \ReflectionClass($className);
|
||||||
$staticProperties[$className] = [];
|
$staticProperties[$className] = [];
|
||||||
foreach($reflection->getProperties() as $property){
|
foreach($reflection->getProperties() as $property){
|
||||||
@ -297,7 +315,7 @@ class MemoryManager{
|
|||||||
}
|
}
|
||||||
|
|
||||||
$staticCount++;
|
$staticCount++;
|
||||||
$this->continueDump($property->getValue(), $staticProperties[$className][$property->getName()], $objects, $refCounts, 0, $maxNesting, $maxStringSize);
|
self::continueDump($property->getValue(), $staticProperties[$className][$property->getName()], $objects, $refCounts, 0, $maxNesting, $maxStringSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(count($staticProperties[$className]) === 0){
|
if(count($staticProperties[$className]) === 0){
|
||||||
@ -305,9 +323,37 @@ class MemoryManager{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "[Dump] Wrote $staticCount static properties\n";
|
file_put_contents($outputFolder . "/staticProperties.js", json_encode($staticProperties, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
||||||
|
MainLogger::getLogger()->info("[Dump] Wrote $staticCount static properties");
|
||||||
|
|
||||||
$this->continueDump($this->server, $data, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
|
$globalVariables = [];
|
||||||
|
|
||||||
|
$ignoredGlobals = [
|
||||||
|
'GLOBALS' => true,
|
||||||
|
'_SERVER' => true,
|
||||||
|
'_REQUEST' => true,
|
||||||
|
'_POST' => true,
|
||||||
|
'_GET' => true,
|
||||||
|
'_FILES' => true,
|
||||||
|
'_ENV' => true,
|
||||||
|
'_COOKIE' => true,
|
||||||
|
'_SESSION' => true
|
||||||
|
];
|
||||||
|
|
||||||
|
$globalCount = 0;
|
||||||
|
foreach($GLOBALS as $varName => $value){
|
||||||
|
if(isset($ignoredGlobals[$varName])){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$globalCount++;
|
||||||
|
self::continueDump($value, $globalVariables[$varName], $objects, $refCounts, 0, $maxNesting, $maxStringSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
file_put_contents($outputFolder . "/globalVariables.js", json_encode($globalVariables, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
||||||
|
MainLogger::getLogger()->info("[Dump] Wrote $globalCount global variables");
|
||||||
|
|
||||||
|
self::continueDump($startingObject, $data, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
|
||||||
|
|
||||||
do{
|
do{
|
||||||
$continue = false;
|
$continue = false;
|
||||||
@ -349,25 +395,24 @@ class MemoryManager{
|
|||||||
if(!$property->isPublic()){
|
if(!$property->isPublic()){
|
||||||
$property->setAccessible(true);
|
$property->setAccessible(true);
|
||||||
}
|
}
|
||||||
$this->continueDump($property->getValue($object), $info["properties"][$property->getName()], $objects, $refCounts, 0, $maxNesting, $maxStringSize);
|
self::continueDump($property->getValue($object), $info["properties"][$property->getName()], $objects, $refCounts, 0, $maxNesting, $maxStringSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
fwrite($obData, "$hash@$className: " . json_encode($info, JSON_UNESCAPED_SLASHES) . "\n");
|
fwrite($obData, "$hash@$className: " . json_encode($info, JSON_UNESCAPED_SLASHES) . "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "[Dump] Wrote " . count($objects) . " objects\n";
|
MainLogger::getLogger()->info("[Dump] Wrote " . count($objects) . " objects");
|
||||||
}while($continue);
|
}while($continue);
|
||||||
|
|
||||||
fclose($obData);
|
fclose($obData);
|
||||||
|
|
||||||
file_put_contents($outputFolder . "/staticProperties.js", json_encode($staticProperties, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
|
||||||
file_put_contents($outputFolder . "/serverEntry.js", json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
file_put_contents($outputFolder . "/serverEntry.js", json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
||||||
file_put_contents($outputFolder . "/referenceCounts.js", json_encode($refCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
file_put_contents($outputFolder . "/referenceCounts.js", json_encode($refCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
||||||
|
|
||||||
arsort($instanceCounts, SORT_NUMERIC);
|
arsort($instanceCounts, SORT_NUMERIC);
|
||||||
file_put_contents($outputFolder . "/instanceCounts.js", json_encode($instanceCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
file_put_contents($outputFolder . "/instanceCounts.js", json_encode($instanceCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
||||||
|
|
||||||
echo "[Dump] Finished!\n";
|
MainLogger::getLogger()->info("[Dump] Finished!");
|
||||||
|
|
||||||
ini_set('memory_limit', $hardLimit);
|
ini_set('memory_limit', $hardLimit);
|
||||||
gc_enable();
|
gc_enable();
|
||||||
@ -382,7 +427,7 @@ class MemoryManager{
|
|||||||
* @param int $maxNesting
|
* @param int $maxNesting
|
||||||
* @param int $maxStringSize
|
* @param int $maxStringSize
|
||||||
*/
|
*/
|
||||||
private function continueDump($from, &$data, array &$objects, array &$refCounts, int $recursion, int $maxNesting, int $maxStringSize){
|
private static function continueDump($from, &$data, array &$objects, array &$refCounts, int $recursion, int $maxNesting, int $maxStringSize){
|
||||||
if($maxNesting <= 0){
|
if($maxNesting <= 0){
|
||||||
$data = "(error) NESTING LIMIT REACHED";
|
$data = "(error) NESTING LIMIT REACHED";
|
||||||
return;
|
return;
|
||||||
@ -406,7 +451,7 @@ class MemoryManager{
|
|||||||
}
|
}
|
||||||
$data = [];
|
$data = [];
|
||||||
foreach($from as $key => $value){
|
foreach($from as $key => $value){
|
||||||
$this->continueDump($value, $data[$key], $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize);
|
self::continueDump($value, $data[$key], $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize);
|
||||||
}
|
}
|
||||||
}elseif(is_string($from)){
|
}elseif(is_string($from)){
|
||||||
$data = "(string) len(". strlen($from) .") " . substr(Utils::printable($from), 0, $maxStringSize);
|
$data = "(string) len(". strlen($from) .") " . substr(Utils::printable($from), 0, $maxStringSize);
|
||||||
|
@ -64,4 +64,8 @@ class AsyncWorker extends Worker{
|
|||||||
public function getThreadName() : string{
|
public function getThreadName() : string{
|
||||||
return "Asynchronous Worker #" . $this->id;
|
return "Asynchronous Worker #" . $this->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getAsyncWorkerId() : int{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
59
src/pocketmine/scheduler/DumpWorkerMemoryTask.php
Normal file
59
src/pocketmine/scheduler/DumpWorkerMemoryTask.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?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\MemoryManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Task used to dump memory from AsyncWorkers
|
||||||
|
*/
|
||||||
|
class DumpWorkerMemoryTask extends AsyncTask{
|
||||||
|
/** @var string */
|
||||||
|
private $outputFolder;
|
||||||
|
/** @var int */
|
||||||
|
private $maxNesting;
|
||||||
|
/** @var int */
|
||||||
|
private $maxStringSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $outputFolder
|
||||||
|
* @param int $maxNesting
|
||||||
|
* @param int $maxStringSize
|
||||||
|
*/
|
||||||
|
public function __construct(string $outputFolder, int $maxNesting, int $maxStringSize){
|
||||||
|
$this->outputFolder = $outputFolder;
|
||||||
|
$this->maxNesting = $maxNesting;
|
||||||
|
$this->maxStringSize = $maxStringSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onRun(){
|
||||||
|
MemoryManager::dumpMemory(
|
||||||
|
$this->worker,
|
||||||
|
$this->worker->getClassLoader(),
|
||||||
|
$this->outputFolder . DIRECTORY_SEPARATOR . "AsyncWorker#" . $this->worker->getAsyncWorkerId(),
|
||||||
|
$this->maxNesting,
|
||||||
|
$this->maxStringSize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user