Added ability to dump memory

This commit is contained in:
Shoghi Cervantes 2015-06-14 11:06:10 +02:00
parent 33e312c7d0
commit b166628940
No known key found for this signature in database
GPG Key ID: 78464DB0A7837F89
5 changed files with 186 additions and 1 deletions

View File

@ -260,4 +260,128 @@ class MemoryManager{
"object" => $includeObject ? $object : null
];
}
public function dumpServerMemory($outputFolder, $maxNesting, $maxStringSize){
gc_enable();
gc_collect_cycles(); //Cleanup counts
if(!file_exists($outputFolder)){
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+");
$staticProperties = [];
$data = [];
$objects = [];
$refCounts = [];
$this->continueDump($this->server, $data, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
do{
$continue = false;
foreach($objects as $hash => $object){
if(!is_object($object)){
continue;
}
$continue = true;
$className = get_class($object);
$objects[$hash] = true;
$reflection = new \ReflectionObject($object);
$info = [
"information" => "$hash@$className",
"properties" => []
];
if($reflection->getParentClass()){
$info["parent"] = $reflection->getParentClass()->getName();
}
if(count($reflection->getInterfaceNames()) > 0){
$info["implements"] = implode(", ", $reflection->getInterfaceNames());
}
foreach($reflection->getProperties() as $property){
if($property->isStatic()){
continue;
}
if(!$property->isPublic()){
$property->setAccessible(true);
}
$this->continueDump($property->getValue($object), $info["properties"][$property->getName()], $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
fwrite($obData, "$hash@$className: ". json_encode($info, JSON_UNESCAPED_SLASHES) . "\n");
if(!isset($objects["staticProperties"][$className])){
$staticProperties[$className] = [];
foreach($reflection->getProperties() as $property){
if(!$property->isStatic() or $property->getDeclaringClass()->getName() !== $className){
continue;
}
if(!$property->isPublic()){
$property->setAccessible(true);
}
$this->continueDump($property->getValue($object), $staticProperties[$className][$property->getName()], $objects, $refCounts, 0, $maxNesting, $maxStringSize);
}
}
}
echo "[Dump] Wrote " . count($objects) . " objects\n";
}while($continue);
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 . "/referenceCounts.js", json_encode($refCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
echo "[Dump] Finished!\n";
$this->server->forceShutdown();
}
private function continueDump($from, &$data, &$objects, &$refCounts, $recursion, $maxNesting, $maxStringSize){
if($maxNesting <= 0){
$data = "(error) NESTING LIMIT REACHED";
return;
}
--$maxNesting;
if(is_object($from)){
if(!isset($objects[$hash = spl_object_hash($from)])){
$objects[$hash] = $from;
$refCounts[$hash] = 0;
}
++$refCounts[$hash];
$data = "(object) $hash@" . get_class($from);
}elseif(is_array($from)){
if($recursion >= 5){
$data = "(error) ARRAY RECURSION LIMIT REACHED";
return;
}
$data = [];
foreach($from as $key => $value){
$this->continueDump($value, $data[$key], $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize);
}
}elseif(is_string($from)){
$data = "(string) len(".strlen($from).") " . substr(Utils::printable($from), 0, $maxStringSize);
}elseif(is_resource($from)){
$data = "(resource) " . print_r($from, true);
}else{
$data = $from;
}
}
}

View File

@ -27,6 +27,7 @@ use pocketmine\command\defaults\BanListCommand;
use pocketmine\command\defaults\DefaultGamemodeCommand;
use pocketmine\command\defaults\DeopCommand;
use pocketmine\command\defaults\DifficultyCommand;
use pocketmine\command\defaults\DumpMemoryCommand;
use pocketmine\command\defaults\EffectCommand;
use pocketmine\command\defaults\GamemodeCommand;
use pocketmine\command\defaults\GarbageCollectorCommand;
@ -117,6 +118,7 @@ class SimpleCommandMap implements CommandMap{
if($this->server->getProperty("debug.commands", false)){
$this->register("pocketmine", new StatusCommand("status"));
$this->register("pocketmine", new GarbageCollectorCommand("gc"));
$this->register("pocketmine", new DumpMemoryCommand("dumpmemory"));
}
}

View File

@ -0,0 +1,58 @@
<?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/
*
*
*/
namespace pocketmine\command\defaults;
use pocketmine\command\CommandSender;
use pocketmine\utils\TextFormat;
use pocketmine\utils\Utils;
class DumpMemoryCommand extends VanillaCommand{
private static $executions = 0;
public function __construct($name){
parent::__construct(
$name,
"Dumps the memory",
"/$name <TOKEN (run once to get it)> [path]"
);
$this->setPermission("pocketmine.command.dumpmemory");
}
public function execute(CommandSender $sender, $currentAlias, array $args){
if(!$this->testPermission($sender)){
return true;
}
$token = strtoupper(substr(sha1(BOOTUP_RANDOM . ":" . $sender->getServer()->getServerUniqueId() . ":" . self::$executions), 6, 6));
if(count($args) < 1 or strtoupper($args[0]) !== $token){
$sender->sendMessage("Usage: /" . $this->getName() . " " . $token);
return true;
}
++self::$executions;
$sender->getServer()->getMemoryManager()->dumpServerMemory(isset($args[1]) ? $args[1] : $sender->getServer()->getDataPath() . "/memoryDump_$token", 48, 80);
return true;
}
}

View File

@ -41,7 +41,7 @@ class StatusCommand extends VanillaCommand{
return true;
}
$rUsage = Utils::getRealMemoryUsage(true);
$rUsage = Utils::getRealMemoryUsage();
$mUsage = Utils::getMemoryUsage(true);
$server = $sender->getServer();

View File

@ -117,6 +117,7 @@ abstract class DefaultPermissions{
self::registerPermission(new Permission(self::ROOT . ".command.seed", "Allows the user to view the seed of the world", Permission::DEFAULT_OP), $commands);
self::registerPermission(new Permission(self::ROOT . ".command.status", "Allows the user to view the server performance", Permission::DEFAULT_OP), $commands);
self::registerPermission(new Permission(self::ROOT . ".command.gc", "Allows the user to fire garbage collection tasks", Permission::DEFAULT_OP), $commands);
self::registerPermission(new Permission(self::ROOT . ".command.dumpmemory", "Allows the user to dump memory contents", Permission::DEFAULT_OP), $commands);
self::registerPermission(new Permission(self::ROOT . ".command.timings", "Allows the user to records timings for all plugin events", Permission::DEFAULT_OP), $commands);
self::registerPermission(new Permission(self::ROOT . ".command.spawnpoint", "Allows the user to change player's spawnpoint", Permission::DEFAULT_OP), $commands);
self::registerPermission(new Permission(self::ROOT . ".command.setworldspawn", "Allows the user to change the world spawn", Permission::DEFAULT_OP), $commands);