Permalink
Browse files

Initial import of MemLog code.

  • Loading branch information...
0 parents commit ae1c7d504c17a3748a771133d24a82be9a94d6ec Bob Somers committed Jan 27, 2012
Showing with 336 additions and 0 deletions.
  1. +124 −0 MemLog.php
  2. +1 −0 viewer/dygraph-combined.js
  3. +137 −0 viewer/index.php
  4. +74 −0 viewer/memlog.js
@@ -0,0 +1,124 @@
+<?php
+
+/**
+ * This class can be used to log memory usage of your PHP code over time,
+ * with the intention of helping you locate where you're running into memory
+ * usage problems. It is not a full-featured profiling tool like Xdebug or
+ * Webgrind.
+ *
+ * MemLog writes out data to a log file, which can be analyzed with another
+ * tool (or by hand if you wish, it's just pseudo-JSON). Each line is a JSON
+ * array with the following fields:
+ *
+ * [time since logging began, in seconds (float),
+ * current PHP memory usage, in bytes (integer),
+ * file name of the currently executing code, if any (string),
+ * class name of the currently executing code, if any (string),
+ * function name of the currently executing code, if any (string)]
+ *
+ * Basic usage is as follows:
+ *
+ * $memlog = new MemLog();
+ * $memlog->start('My Terrible Code');
+ *
+ * ...your bad code eats gobs of memory...
+ *
+ * $memlog->stop();
+ *
+ * Log files are saved with a base path of MemLog::$basePath, Unless you have
+ * a compelling reason, /tmp is a good choice for $basePath.
+ *
+ * @author Bob Somers
+ */
+class MemLog {
+ public static $basePath = '/tmp';
+ private static $maxBufferSize = 100;
+
+ private $isLogging = false;
+ private $fp = null;
+ private $buffer = array();
+ private $startTime = 0.0;
+
+ public function __construct() {
+ // Tweak this if necessary.
+ declare(ticks=1);
+ }
+
+ public function __destruct() {
+ if ($this->isLogging) {
+ $this->stop();
+ }
+ }
+
+ public function start($name = 'MemLog') {
+ if ($this->isLogging) return;
+
+ $name .= ' - ' . date('r');
+
+ list($usec, $time) = explode(' ', microtime());
+ list($junk, $usec) = explode('.', $usec);
+ $fileName = 'php_mem_usage.' . date('Y-m-d-H-i-s', $time) . "-$usec.log";
+
+ $this->fp = fopen(self::$basePath . '/' . $fileName, 'w');
+ fwrite($this->fp, $name);
+ $this->buffer = array();
+
+ $this->isLogging = true;
+
+ $this->startTime = microtime(true);
+ register_tick_function(array(&$this, 'tick'));
+ }
+
+ public function stop() {
+ if (!$this->isLogging) return;
+
+ unregister_tick_function(array(&$this, 'tick'));
+
+ $this->flush();
+ fclose($this->fp);
+
+ $this->isLogging = false;
+ }
+
+ public function tick() {
+ $timestamp = microtime(true) - $this->startTime;
+ $memory = memory_get_usage(true);
+
+ $file = '';
+ $class = '';
+ $function = '';
+ $backtrace = debug_backtrace();
+ if (isset($backtrace[1])) {
+ if (isset($backtrace[1]['file'])) {
+ $file = basename($backtrace[1]['file']);
+ }
+ if (isset($backtrace[1]['class'])) {
+ $class = $backtrace[1]['class'];
+ }
+ if (isset($backtrace[1]['function'])) {
+ $function = $backtrace[1]['function'];
+ }
+ }
+
+ $this->buffer[] = array(
+ $timestamp,
+ $memory,
+ $file,
+ $class,
+ $function
+ );
+
+ if (count($this->buffer) > self::$maxBufferSize) {
+ $this->flush();
+ }
+ }
+
+ private function flush() {
+ for ($i = 0; $i < count($this->buffer); $i++) {
+ fwrite($this->fp, "\n" . json_encode($this->buffer[$i]));
+ }
+ $this->buffer = array();
+ }
+}
+
+?>

Large diffs are not rendered by default.

Oops, something went wrong.
@@ -0,0 +1,137 @@
+<?php
+
+/*
+ * DO NOT USE THIS ON A PRODUCTION MACHINE.
+ *
+ * There is an easy way to get this script to basically dump out any file on
+ * the machine that the web server user has read access to, which is absolutely
+ * atrociously bad for security.
+ *
+ * This is a developer tool. Use it on a machine that only your developers have
+ * access to. Failure to heed this advice means you are willing to accept
+ * whatever punishment the intertubes inflict upon you.
+ *
+ * YOU HAVE BEEN WARNED.
+ *
+ * @author Bob Somers
+ */
+
+if (isset($_POST['logfile'])) {
+ $logfile = base64_decode($_POST['logfile']);
+} else if (isset($_GET['logfile'])) {
+ header('Content-type: application/json');
+
+ // HERE BE DRAGONS. This is insanely bad. Never ever do this.
+ $lines = file(base64_decode($_GET['logfile']), FILE_IGNORE_NEW_LINES);
+
+ $name = array_shift($lines);
+
+ echo "{\n";
+ echo "\t\"name\": " . json_encode($name) . ",\n";
+ echo "\t\"data\": [\n";
+ $first = true;
+ foreach ($lines as $line) {
+ if (!$first) {
+ echo ",\n";
+ } else {
+ $first = false;
+ }
+ echo "\t\t" . $line;
+ }
+ echo "\n\t]\n";
+ echo "}\n";
+
+ exit();
+}
+
+?>
+<!doctype html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>MemLog Viewer</title>
+
+ <link href="http://fonts.googleapis.com/css?family=Ubuntu:700"
+ rel="stylesheet" type="text/css">
+ <link href="http://fonts.googleapis.com/css?family=Anonymous+Pro:400,700"
+ rel="stylesheet" type="text/css">
+
+ <script src="https://ajax.googleapis.com/ajax/libs/mootools/1.4.1/mootools-yui-compressed.js"
+ type="text/javascript"></script>
+ <script src="dygraph-combined.js"
+ type="text/javascript"></script>
+ <script src="memlog.js"
+ type="text/javascript"></script>
+
+ <?php if (isset($logfile)): ?>
+ <script type="text/javascript">
+ window.addEvent('domready', function() {
+ new Request.JSON({
+ url: '?logfile=<?php echo base64_encode($logfile); ?>',
+ onSuccess: function(resp) {
+ $('name').set('text', resp.name);
+ memlog(resp.data);
+ }
+ }).get();
+ });
+ </script>
+ <?php endif; ?>
+ <style type="text/css">
+ #codeloc {
+ font: 24px 'Anonymous Pro';
+ width 960px;
+ text-align: right;
+ line-height: 30px;
+ width: 960px;
+ height: 30px;
+ }
+
+ #choose {
+ position: absolute;
+ top: 28px;
+ left: 480px;
+ }
+
+ h1 {
+ font: bold 48px 'Ubuntu';
+ margin: 0;
+ padding-left: 20px;
+ height: 64px;
+ line-height: 64px;
+ border-bottom: 1px solid #aaa;
+ }
+
+ h2 {
+ font: bold 24px 'Ubuntu';
+ margin: 0;
+ padding: 10px 0 0 10px;
+ }
+ </style>
+ </head>
+ <body>
+ <h1>MemLog Viewer</h1>
+ <form id="choose" method="post">
+ <select name="logfile">
+ <?php
+ $files = glob('/tmp/php_mem_usage*.log');
+ rsort($files);
+ foreach ($files as $file) {
+ echo '<option value="' . base64_encode($file) . '"';
+ if ($logfile == $file) {
+ echo ' selected';
+ }
+ echo ">$file</option>\n";
+ }
+ ?>
+ </select>
+ <input type="submit" value="View">
+ </form>
+<?php if (isset($logfile)): ?>
+
+ <h2 id="name">Loading...</h2>
+ <div id="codeloc"></div>
+ <div id="graph"></div>
+
+<?php endif; ?>
+ </body>
+</html>
@@ -0,0 +1,74 @@
+function memlog(data) {
+ var series = [];
+ var details = {};
+ for (var i = 0; i < data.length; i++) {
+ var sample = data[i];
+
+ var timestamp = sample[0] * 1000; // convert to ms
+ var memory = sample[1];
+ var file = sample[2];
+ var klass = sample[3];
+ var func = sample[4];
+
+ series.push([timestamp, memory]);
+ details[String(timestamp)] = {
+ file: file,
+ klass: klass,
+ func: func
+ };
+ }
+
+ // Helper functions for formatting. Thanks to:
+ // http://dygraphs.com/tests/labelsKMB.html
+ function round(num, places) {
+ var shift = Math.pow(10, places);
+ return Math.round(num * shift)/shift;
+ }
+
+ function formatBytes(v) {
+ var suffixes = ['', 'k', 'M', 'G', 'T'];
+ if (v < 1000) return v;
+
+ var magnitude = Math.floor(String(Math.floor(v)).length / 3);
+ if (magnitude > suffixes.length - 1) {
+ magnitude = suffixes.length - 1;
+ }
+
+ return String(round(v / Math.pow(10, magnitude * 3), 2)) + suffixes[magnitude];
+ }
+
+ function formatMs(v) {
+ return String(round(v, 3) + " ms");
+ }
+
+ var codeloc = $('codeloc');
+ function showDetails(e, x) {
+ var info = details[String(x)];
+ var where = '';
+ if (info.file) {
+ where += '<strong>' + info.file + '</strong>:&nbsp;';
+ }
+ if (info.klass) {
+ where += info.klass + '::';
+ }
+ if (info.func) {
+ where += info.func + '()';
+ }
+ codeloc.set('html', where);
+ }
+
+ var graph = new Dygraph(document.getElementById('graph'), series, {
+ width: 960,
+ height: 300,
+ fillGraph: true,
+ stepPlot: true,
+ labels: ['Time', 'Mem'],
+ xlabel: 'Time (ms)',
+ ylabel: 'Memory Usage',
+ labelsKMG2: true,
+ xValueFormatter: formatMs,
+ yValueFormatter: formatBytes,
+ highlightCallback: showDetails,
+ colors: ["#336DA8"]
+ });
+}

0 comments on commit ae1c7d5

Please sign in to comment.