PocketMine-MP 5.23.3 git-976fc63567edab7a6fb6aeae739f43cf9fe57de4
Loading...
Searching...
No Matches
MemoryDump.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;
25
27use Symfony\Component\Filesystem\Path;
28use function arsort;
29use function count;
30use function fclose;
31use function file_exists;
32use function file_put_contents;
33use function fopen;
34use function fwrite;
35use function gc_disable;
36use function gc_enable;
37use function gc_enabled;
38use function get_class;
39use function get_declared_classes;
40use function get_defined_functions;
41use function ini_get;
42use function ini_set;
43use function is_array;
44use function is_float;
45use function is_object;
46use function is_resource;
47use function is_string;
48use function json_encode;
49use function mkdir;
50use function print_r;
51use function spl_object_hash;
52use function strlen;
53use function substr;
54use const JSON_PRETTY_PRINT;
55use const JSON_THROW_ON_ERROR;
56use const JSON_UNESCAPED_SLASHES;
57use const SORT_NUMERIC;
58
59final class MemoryDump{
60
61 private function __construct(){
62 //NOOP
63 }
64
68 public static function dumpMemory(mixed $startingObject, string $outputFolder, int $maxNesting, int $maxStringSize, \Logger $logger) : void{
69 $hardLimit = Utils::assumeNotFalse(ini_get('memory_limit'), "memory_limit INI directive should always exist");
70 ini_set('memory_limit', '-1');
71 $gcEnabled = gc_enabled();
72 gc_disable();
73
74 if(!file_exists($outputFolder)){
75 mkdir($outputFolder, 0777, true);
76 }
77
78 $obData = Utils::assumeNotFalse(fopen(Path::join($outputFolder, "objects.js"), "wb+"));
79
80 $objects = [];
81
82 $refCounts = [];
83
84 $instanceCounts = [];
85
86 $staticProperties = [];
87 $staticCount = 0;
88
89 $functionStaticVars = [];
90 $functionStaticVarsCount = 0;
91
92 foreach(get_declared_classes() as $className){
93 $reflection = new \ReflectionClass($className);
94 $staticProperties[$className] = [];
95 foreach($reflection->getProperties() as $property){
96 if(!$property->isStatic() || $property->getDeclaringClass()->getName() !== $className){
97 continue;
98 }
99
100 if(!$property->isInitialized()){
101 continue;
102 }
103
104 $staticCount++;
105 $staticProperties[$className][$property->getName()] = self::continueDump($property->getValue(), $objects, $refCounts, 0, $maxNesting, $maxStringSize);
106 }
107
108 if(count($staticProperties[$className]) === 0){
109 unset($staticProperties[$className]);
110 }
111
112 foreach($reflection->getMethods() as $method){
113 if($method->getDeclaringClass()->getName() !== $reflection->getName()){
114 continue;
115 }
116 $methodStatics = [];
117 foreach(Utils::promoteKeys($method->getStaticVariables()) as $name => $variable){
118 $methodStatics[$name] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
119 }
120 if(count($methodStatics) > 0){
121 $functionStaticVars[$className . "::" . $method->getName()] = $methodStatics;
122 $functionStaticVarsCount += count($functionStaticVars);
123 }
124 }
125 }
126
127 file_put_contents(Path::join($outputFolder, "staticProperties.js"), json_encode($staticProperties, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
128 $logger->info("Wrote $staticCount static properties");
129
130 $globalVariables = [];
131 $globalCount = 0;
132
133 $ignoredGlobals = [
134 'GLOBALS' => true,
135 '_SERVER' => true,
136 '_REQUEST' => true,
137 '_POST' => true,
138 '_GET' => true,
139 '_FILES' => true,
140 '_ENV' => true,
141 '_COOKIE' => true,
142 '_SESSION' => true
143 ];
144
145 foreach(Utils::promoteKeys($GLOBALS) as $varName => $value){
146 if(isset($ignoredGlobals[$varName])){
147 continue;
148 }
149
150 $globalCount++;
151 $globalVariables[$varName] = self::continueDump($value, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
152 }
153
154 file_put_contents(Path::join($outputFolder, "globalVariables.js"), json_encode($globalVariables, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
155 $logger->info("Wrote $globalCount global variables");
156
157 foreach(get_defined_functions()["user"] as $function){
158 $reflect = new \ReflectionFunction($function);
159
160 $vars = [];
161 foreach(Utils::promoteKeys($reflect->getStaticVariables()) as $varName => $variable){
162 $vars[$varName] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
163 }
164 if(count($vars) > 0){
165 $functionStaticVars[$function] = $vars;
166 $functionStaticVarsCount += count($vars);
167 }
168 }
169 file_put_contents(Path::join($outputFolder, 'functionStaticVars.js'), json_encode($functionStaticVars, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
170 $logger->info("Wrote $functionStaticVarsCount function static variables");
171
172 $data = self::continueDump($startingObject, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
173
174 do{
175 $continue = false;
176 foreach(Utils::stringifyKeys($objects) as $hash => $object){
177 if(!is_object($object)){
178 continue;
179 }
180 $continue = true;
181
182 $className = get_class($object);
183 if(!isset($instanceCounts[$className])){
184 $instanceCounts[$className] = 1;
185 }else{
186 $instanceCounts[$className]++;
187 }
188
189 $objects[$hash] = true;
190 $info = [
191 "information" => "$hash@$className",
192 ];
193 if($object instanceof \Closure){
194 $info["definition"] = Utils::getNiceClosureName($object);
195 $info["referencedVars"] = [];
196 $reflect = new \ReflectionFunction($object);
197 if(($closureThis = $reflect->getClosureThis()) !== null){
198 $info["this"] = self::continueDump($closureThis, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
199 }
200
201 foreach(Utils::promoteKeys($reflect->getStaticVariables()) as $name => $variable){
202 $info["referencedVars"][$name] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
203 }
204 }else{
205 $reflection = new \ReflectionObject($object);
206
207 $info["properties"] = [];
208
209 for($original = $reflection; $reflection !== false; $reflection = $reflection->getParentClass()){
210 foreach($reflection->getProperties() as $property){
211 if($property->isStatic()){
212 continue;
213 }
214
215 $name = $property->getName();
216 if($reflection !== $original){
217 if($property->isPrivate()){
218 $name = $reflection->getName() . ":" . $name;
219 }else{
220 continue;
221 }
222 }
223 if(!$property->isInitialized($object)){
224 continue;
225 }
226
227 $info["properties"][$name] = self::continueDump($property->getValue($object), $objects, $refCounts, 0, $maxNesting, $maxStringSize);
228 }
229 }
230 }
231
232 fwrite($obData, json_encode($info, JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR) . "\n");
233 }
234
235 }while($continue);
236
237 $logger->info("Wrote " . count($objects) . " objects");
238
239 fclose($obData);
240
241 file_put_contents(Path::join($outputFolder, "serverEntry.js"), json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
242 file_put_contents(Path::join($outputFolder, "referenceCounts.js"), json_encode($refCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
243
244 arsort($instanceCounts, SORT_NUMERIC);
245 file_put_contents(Path::join($outputFolder, "instanceCounts.js"), json_encode($instanceCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
246
247 $logger->info("Finished!");
248
249 ini_set('memory_limit', $hardLimit);
250 if($gcEnabled){
251 gc_enable();
252 }
253 }
254
264 private static function continueDump(mixed $from, array &$objects, array &$refCounts, int $recursion, int $maxNesting, int $maxStringSize) : mixed{
265 if($maxNesting <= 0){
266 return "(error) NESTING LIMIT REACHED";
267 }
268
269 --$maxNesting;
270
271 if(is_object($from)){
272 if(!isset($objects[$hash = spl_object_hash($from)])){
273 $objects[$hash] = $from;
274 $refCounts[$hash] = 0;
275 }
276
277 ++$refCounts[$hash];
278
279 $data = "(object) $hash";
280 }elseif(is_array($from)){
281 if($recursion >= 5){
282 return "(error) ARRAY RECURSION LIMIT REACHED";
283 }
284 $data = [];
285 $numeric = 0;
286 foreach(Utils::promoteKeys($from) as $key => $value){
287 $data[$numeric] = [
288 "k" => self::continueDump($key, $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize),
289 "v" => self::continueDump($value, $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize),
290 ];
291 $numeric++;
292 }
293 }elseif(is_string($from)){
294 $data = "(string) len(" . strlen($from) . ") " . substr(Utils::printable($from), 0, $maxStringSize);
295 }elseif(is_resource($from)){
296 $data = "(resource) " . print_r($from, true);
297 }elseif(is_float($from)){
298 $data = "(float) $from";
299 }else{
300 $data = $from;
301 }
302
303 return $data;
304 }
305}
static dumpMemory(mixed $startingObject, string $outputFolder, int $maxNesting, int $maxStringSize, \Logger $logger)
info($message)