Skip to content

Instantly share code, notes, and snippets.

@DavyCraft648
Created November 23, 2025 22:56
Show Gist options
  • Select an option

  • Save DavyCraft648/5ed413f726d86714e117cd8daa2af6e8 to your computer and use it in GitHub Desktop.

Select an option

Save DavyCraft648/5ed413f726d86714e117cd8daa2af6e8 to your computer and use it in GitHub Desktop.
Theis script parses the JSON dump files generated by src/MemoryDump.php and produces a command-line summary of reference counts, instance counts, static/function statics, and top objects. It can also build an object reference graph to show reference chains, detect reference cycles, report string-size statistics, and print detailed information fo…
<?php
$top = 15;
$folder = null;
$script = basename($argv[0]);
$doDetails = false;
$detailHash = null;
$doCycles = false;
$doSizes = false;
for ($i = 1; $i < $argc; $i++) {
$arg = $argv[$i];
if ($arg === '--') {
if (isset($argv[$i + 1])) {
$folder = $argv[$i + 1];
}
break;
}
if (str_starts_with($arg, '--top=')) {
$topVal = substr($arg, strlen('--top='));
if (is_numeric($topVal) && (int)$topVal >= 0) {
$top = (int)$topVal;
} else {
fwrite(STDERR, "Invalid value for --top: $topVal. Must be an integer >= 0. Using default $top.\n");
}
continue;
}
if (str_starts_with($arg, '--details=')) {
$doDetails = true;
$detailHash = substr($arg, strlen('--details='));
continue;
}
if ($arg === '--details') {
$doDetails = true;
if (isset($argv[$i + 1])) {
$i++;
$detailHash = $argv[$i];
} else {
fwrite(STDERR, "Missing value for --details\n");
exit(1);
}
continue;
}
if ($arg === '--cycles') {
$doCycles = true;
continue;
}
if ($arg === '--sizes') {
$doSizes = true;
continue;
}
if ($arg === '--top' || $arg === '-t') {
if (isset($argv[$i + 1])) {
$i++;
$topVal = $argv[$i];
if (is_numeric($topVal) && (int)$topVal >= 0) {
$top = (int)$topVal;
} else {
fwrite(STDERR, "Invalid value for $arg: $topVal. Must be an integer >= 0. Using default $top.\n");
}
} else {
fwrite(STDERR, "Missing value for $arg.\n");
exit(1);
}
continue;
}
if (strlen($arg) > 0 && $arg[0] === '-') {
fwrite(STDERR, "Unknown option: $arg\n");
continue;
}
if ($folder === null) {
$folder = $arg;
}
}
if ($folder === null) {
fwrite(STDERR, "Usage: php $script path\\to\\dump\\folder [--top N] [--details HASH] [--cycles] [--sizes]\n");
exit(1);
}
if (!is_dir($folder) || !is_readable($folder)) {
fwrite(STDERR, "Folder does not exist or is not readable: $folder\n");
exit(1);
}
$files = [
'ref' => $folder . DIRECTORY_SEPARATOR . 'referenceCounts.js',
'inst' => $folder . DIRECTORY_SEPARATOR . 'instanceCounts.js',
'static' => $folder . DIRECTORY_SEPARATOR . 'staticProperties.js',
'funcstatic' => $folder . DIRECTORY_SEPARATOR . 'functionStaticVars.js',
'server' => $folder . DIRECTORY_SEPARATOR . 'serverEntry.js',
'objects' => $folder . DIRECTORY_SEPARATOR . 'objects.js',
'globals' => $folder . DIRECTORY_SEPARATOR . 'globalVariables.js',
];
foreach ($files as $k => $p) {
if (!file_exists($p)) {
fwrite(STDERR, "Missing file: $p\n");
}
}
function loadJsonFile($path) {
if (!file_exists($path)) return null;
$s = file_get_contents($path);
if ($s === false) return null;
$data = json_decode($s, true);
if (json_last_error() !== JSON_ERROR_NONE) {
fwrite(STDERR, "JSON decode error for $path: " . json_last_error_msg() . "\n");
return null;
}
return $data;
}
$refCounts = loadJsonFile($files['ref']) ?? [];
$instanceCounts = loadJsonFile($files['inst']) ?? [];
$staticProps = loadJsonFile($files['static']) ?? [];
$functionStatics = loadJsonFile($files['funcstatic']) ?? [];
$serverEntry = loadJsonFile($files['server']) ?? null;
$globalVariables = loadJsonFile($files['globals']) ?? null;
$objectsIndex = []; // hash => info array
$mostProps = [];
$closures = [];
$lineNo = 0;
if (file_exists($files['objects'])) {
$fh = fopen($files['objects'], 'rb');
if ($fh) {
while (($line = fgets($fh)) !== false) {
$lineNo++;
$o = json_decode($line, true);
if ($o === null) continue;
if (!isset($o['information'])) continue;
// hash@className
$parts = explode('@', $o['information'], 2);
$hash = $parts[0];
$class = $parts[1] ?? '(unknown)';
$objectsIndex[$hash] = $o;
$propCount = 0;
if (isset($o['properties']) && is_array($o['properties'])) {
$propCount = count($o['properties']);
}
$mostProps[$hash] = ['class' => $class, 'props' => $propCount];
if (isset($o['definition']) || isset($o['referencedVars'])) {
$closures[$hash] = [
'class' => $class,
'definition' => $o['definition'] ?? null,
'refs' => isset($o['referencedVars']) ? count($o['referencedVars']) : 0,
'has_this' => isset($o['this']),
];
}
}
fclose($fh);
}
}
function findReferencedObjectHashes(mixed $data, array &$found) : void{
if (is_string($data)){
if (preg_match('/^\(object\)\s*([0-9a-fA-F]+)$/', $data, $m)){
$found[$m[1]] = true;
}
return;
}
if (is_array($data)){
foreach ($data as $v){
findReferencedObjectHashes($v, $found);
}
}
}
function findStringLens(mixed $data, array &$lens) : void{
if (is_string($data)){
if (preg_match('/^\(string\) len\((\d+)\)/', $data, $m)){
$l = (int)$m[1];
$lens[] = $l;
}
return;
}
if (is_array($data)){
foreach ($data as $v){
findStringLens($v, $lens);
}
}
}
function buildReferenceGraph(array $objectsIndex, array $staticProps, array $functionStatics, mixed $serverEntry, mixed $globalVariables) : array{
$adj = [];
foreach ($objectsIndex as $owner => $info){
$found = [];
if (isset($info['properties'])){
findReferencedObjectHashes($info['properties'], $found);
}
if (isset($info['referencedVars'])){
findReferencedObjectHashes($info['referencedVars'], $found);
}
if (isset($info['this'])){
findReferencedObjectHashes($info['this'], $found);
}
if (count($found) > 0){
$adj[$owner] = array_keys($found);
}
}
foreach ($staticProps as $cls => $props){
foreach ($props as $name => $val){
$found = [];
findReferencedObjectHashes($val, $found);
if (count($found) > 0){
$owner = "static:" . $cls;
if (!isset($adj[$owner])) $adj[$owner] = [];
$adj[$owner] = array_unique(array_merge($adj[$owner], array_keys($found)));
}
}
}
foreach ($functionStatics as $fn => $vars){
foreach ($vars as $name => $val){
$found = [];
findReferencedObjectHashes($val, $found);
if (count($found) > 0){
$owner = "func:" . $fn;
if (!isset($adj[$owner])) $adj[$owner] = [];
$adj[$owner] = array_unique(array_merge($adj[$owner], array_keys($found)));
}
}
}
if ($serverEntry !== null){
$found = [];
findReferencedObjectHashes($serverEntry, $found);
if (count($found) > 0){
$adj['serverEntry'] = array_keys($found);
}
}
if ($globalVariables !== null && is_array($globalVariables)){
foreach ($globalVariables as $gname => $val){
$found = [];
findReferencedObjectHashes($val, $found);
if (count($found) > 0){
$owner = "global:" . $gname;
if (!isset($adj[$owner])) $adj[$owner] = [];
$adj[$owner] = array_unique(array_merge($adj[$owner], array_keys($found)));
}
}
}
return $adj;
}
function findPathFromRoots(array $adj, array $roots, string $target) : array{
$queue = [];
$seen = [];
$parent = [];
foreach ($roots as $r) {
$queue[] = $r;
$seen[$r] = true;
$parent[$r] = null;
}
$found = false;
while (!empty($queue)){
$node = array_shift($queue);
$neighbors = $adj[$node] ?? [];
foreach ($neighbors as $n){
if (!isset($seen[$n])){
$seen[$n] = true;
$parent[$n] = $node;
if ($n === $target){
$found = true;
break 2;
}
$queue[] = $n;
}
}
}
if (!$found) return [];
// reconstruct path
$path = [];
$cur = $target;
while ($cur !== null){
$path[] = $cur;
$cur = $parent[$cur] ?? null;
}
return array_reverse($path);
}
function detectCycles(array $adj, int $limit = 20) : array{
$visited = [];
$onstack = [];
$stack = [];
$cycles = [];
$dfs = function($node) use (&$dfs, &$visited, &$onstack, &$stack, &$adj, &$cycles, $limit){
if (isset($visited[$node])) return;
$visited[$node] = true;
$onstack[$node] = count($stack);
$stack[] = $node;
foreach ($adj[$node] ?? [] as $n){
if (!isset($visited[$n])){
$dfs($n);
if (count($cycles) >= $limit) return;
}elseif (isset($onstack[$n])){
$start = $onstack[$n];
$cycle = array_slice($stack, $start);
$cycle[] = $n;
$cycles[] = $cycle;
if (count($cycles) >= $limit) return;
}
}
array_pop($stack);
unset($onstack[$node]);
};
foreach (array_keys($adj) as $n){
if (!isset($visited[$n])){
$dfs($n);
if (count($cycles) >= $limit) break;
}
}
return $cycles;
}
arsort($refCounts, SORT_NUMERIC);
$topRefs = array_slice($refCounts, 0, $top, true);
arsort($instanceCounts, SORT_NUMERIC);
$topClasses = array_slice($instanceCounts, 0, $top, true);
uasort($mostProps, function($a, $b){ return $b['props'] <=> $a['props']; });
$mostProps = array_slice($mostProps, 0, $top, true);
uasort($closures, function($a, $b){ return $b['refs'] <=> $a['refs']; });
$topClosures = array_slice($closures, 0, $top, true);
$graphAdj = null;
$graphRev = null;
$roots = [];
if ($doDetails || $doCycles || $doSizes) {
$graphAdj = buildReferenceGraph($objectsIndex, $staticProps, $functionStatics, $serverEntry, $globalVariables);
$graphRev = [];
foreach ($graphAdj as $owner => $targets) {
foreach ($targets as $t) {
$graphRev[$t][] = $owner;
}
}
if ($serverEntry !== null) {
$found = [];
findReferencedObjectHashes($serverEntry, $found);
foreach (array_keys($found) as $h) $roots[] = $h;
}
if ($globalVariables !== null && is_array($globalVariables)){
foreach ($globalVariables as $g){
$found = [];
findReferencedObjectHashes($g, $found);
foreach (array_keys($found) as $h) $roots[] = $h;
}
}
$roots = array_values(array_unique($roots));
}
if ($doSizes) {
$lens = [];
foreach ($objectsIndex as $info){
if (isset($info['properties'])) findStringLens($info['properties'], $lens);
if (isset($info['referencedVars'])) findStringLens($info['referencedVars'], $lens);
if (isset($info['definition'])) findStringLens($info['definition'], $lens);
}
foreach ($staticProps as $cls => $props) {
foreach ($props as $p) findStringLens($p, $lens);
}
foreach ($functionStatics as $fn => $vars) {
foreach ($vars as $v) findStringLens($v, $lens);
}
if ($serverEntry !== null) findStringLens($serverEntry, $lens);
if ($globalVariables !== null) findStringLens($globalVariables, $lens);
rsort($lens, SORT_NUMERIC);
$totalStrings = count($lens);
$totalBytes = array_sum($lens);
$topStrings = array_slice($lens, 0, $top);
echo "--- String size summary (approx from dump serializer) ---\n";
echo "total strings counted: $totalStrings\n";
echo "total reported string bytes: $totalBytes\n";
echo "top $top longest strings (reported len): " . implode(", ", $topStrings) . "\n\n";
}
if ($doCycles) {
$cycles = detectCycles($graphAdj ?? [], 50);
echo "--- Detected reference cycles (showing up to 50) ---\n";
if (count($cycles) === 0) {
echo "(none)\n\n";
} else {
foreach ($cycles as $idx => $c) {
echo sprintf("%2d) %s\n", $idx + 1, implode(' -> ', $c));
}
echo "\n";
}
}
if ($doDetails) {
if ($detailHash === null) {
fwrite(STDERR, "--details requires an object hash\n");
exit(1);
}
echo "--- Details for object: $detailHash ---\n";
if (!isset($objectsIndex[$detailHash])) {
echo "Object not found in objects.js\n\n";
} else {
$info = $objectsIndex[$detailHash];
$parts = explode('@', $info['information'] ?? "$detailHash@(unknown)", 2);
$cls = $parts[1] ?? '(unknown)';
echo "class: $cls\n";
echo "refCount: " . ($refCounts[$detailHash] ?? 0) . "\n";
$props = $info['properties'] ?? [];
echo "properties (" . count($props) . "):\n";
foreach ($props as $name => $val) {
echo " - $name => ";
if (is_string($val)) echo $val . "\n"; else echo json_encode($val) . "\n";
}
$refsBy = $graphRev[$detailHash] ?? [];
echo "referenced-by (" . count($refsBy) . "):\n";
foreach ($refsBy as $r) {
echo " - $r\n";
}
if (!empty($roots)){
$path = findPathFromRoots($graphAdj ?? [], $roots, $detailHash);
if (!empty($path)){
echo "\npath from root: " . implode(' -> ', $path) . "\n";
} else {
echo "\nNo path from serverEntry/global roots found.\n";
}
}
echo "\n";
}
}
echo "=== Memory Dump Analysis for: {$folder} ===\n\n";
echo "(showing top $top entries where applicable)\n\n";
echo "--- Top referenced objects (by reference count) ---\n";
$i = 0;
foreach ($topRefs as $hash => $count) {
$i++;
$class = isset($objectsIndex[$hash]) ? (explode('@', $objectsIndex[$hash]['information'], 2)[1] ?? '(unknown)') : '(unknown)';
echo sprintf("%2d) %s - refs=%d class=%s\n", $i, $hash, $count, $class);
}
if ($i === 0) echo "(none)\n";
echo "\n";
echo "--- Top instance counts (classes) ---\n";
$i = 0;
foreach ($topClasses as $cls => $count) {
$i++;
echo sprintf("%2d) %s => %d\n", $i, $cls, $count);
}
if ($i === 0) echo "(none)\n";
echo "\n";
echo "--- Objects with most properties ---\n";
$i = 0;
foreach ($mostProps as $hash => $info) {
$i++;
echo sprintf("%2d) %s - props=%d class=%s\n", $i, $hash, $info['props'], $info['class']);
}
if ($i === 0) echo "(none)\n";
echo "\n";
echo "--- Closures with most referenced statics ---\n";
$i = 0;
foreach ($topClosures as $hash => $info) {
$i++;
$def = $info['definition'] ?? '(anonymous)';
echo sprintf("%2d) %s class=%s refs=%d has_this=%s def=%s\n", $i, $hash, $info['class'], $info['refs'], $info['has_this'] ? 'yes' : 'no', $def);
}
if ($i === 0) echo "(none)\n";
echo "\n";
echo "--- Static properties summary ---\n";
$totalStaticClasses = count($staticProps);
$totalStaticProps = 0;
foreach ($staticProps as $cls => $props) $totalStaticProps += count($props);
echo "classes with statics: $totalStaticClasses\n";
echo "total static properties: $totalStaticProps\n\n";
echo "--- Function static vars summary ---\n";
$totalFunc = 0;
foreach ($functionStatics as $fn => $vars) $totalFunc += count($vars);
echo "functions/methods with statics: " . count($functionStatics) . "\n";
echo "total function static vars: $totalFunc\n\n";
echo "--- Server entry root type ---\n";
if ($serverEntry !== null) {
$found = [];
findReferencedObjectHashes($serverEntry, $found);
if (count($found) > 0) {
echo "serverEntry references the following object hashes:\n";
foreach ($found as $hash => $_) {
$cls = isset($objectsIndex[$hash]) ? (explode('@', $objectsIndex[$hash]['information'], 2)[1] ?? '(unknown)') : '(not in objects.js)';
echo " - $hash => $cls\n";
}
} else {
if (is_string($serverEntry) && preg_match('/^\(object\)\s*([0-9a-fA-F]+)$/', $serverEntry, $m)){
$h = $m[1];
$cls = isset($objectsIndex[$h]) ? (explode('@', $objectsIndex[$h]['information'], 2)[1] ?? '(unknown)') : '(not in objects.js)';
echo "serverEntry appears to be an object reference: $h => $cls\n";
}
}
} else {
echo "serverEntry.js not found or failed to parse\n";
}
echo "\n";
echo "Finished.\n";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment