From 2a3fa03749e74847a243df4a780f9062e0945d3c Mon Sep 17 00:00:00 2001
From: diosmosis <benakamoorthi@fastmail.fm>
Date: Wed, 20 Aug 2014 12:05:32 -0700
Subject: [PATCH] Get xhprof integration to work. Only setup xhprof once per
 php execution and correctly aggregate xhprof runs (xhprof aggregation will
 always normalize so we must aggregate ourselves).

---
 core/Profiler.php           | 100 ++++++++++++++++++++++++++++--------
 tests/PHPUnit/bootstrap.php |   4 +-
 2 files changed, 81 insertions(+), 23 deletions(-)

diff --git a/core/Profiler.php b/core/Profiler.php
index 04d2443082..2000701bff 100644
--- a/core/Profiler.php
+++ b/core/Profiler.php
@@ -9,6 +9,7 @@
 namespace Piwik;
 
 use Exception;
+use XHProfRuns_Default;
 
 /**
  * Class Profiler helps with measuring memory, and profiling the database.
@@ -23,6 +24,13 @@ use Exception;
  */
 class Profiler
 {
+    /**
+     * Whether xhprof has been setup or not.
+     *
+     * @var bool
+     */
+    private static $isXhprofSetup = false;
+
     /**
      * Returns memory usage
      *
@@ -197,11 +205,24 @@ class Profiler
             return;
         }
 
+        if (self::$isXhprofSetup) {
+            return;
+        }
+
+        $xhProfPath = PIWIK_INCLUDE_PATH . '/vendor/facebook/xhprof/extension/modules/xhprof.so';
+        if (!file_exists($xhProfPath)) {
+            throw new Exception("Cannot find xhprof, run 'composer update' and build the extension."); // TODO: automate building w/ composer
+        }
+
         if (!function_exists('xhprof_enable')) {
-            throw new Exception("Cannot find xhprof, run 'composer update'.");
+            throw new Exception("Cannot find xhprof_enable, make sure to add 'extension=$xhProfPath' to your php.ini.");
         }
 
-        if (!is_writable(ini_get("xhprof.output_dir"))) {
+        $outputDir = ini_get("xhprof.output_dir");
+        if (empty($outputDir)) {
+            throw new Exception("The profiler output dir is not set. Add 'xhprof.output_dir=...' to your php.ini.");
+        }
+        if (!is_writable($outputDir)) {
             throw new Exception("The profiler output dir '" . ini_get("xhprof.output_dir") . "' should exist and be writable.");
         }
 
@@ -223,40 +244,75 @@ class Profiler
             self::setProfilingRunIds(array());
         }
 
-        register_shutdown_function(function () use($profilerNamespace, $mainRun) {
+        $baseUrlStored = SettingsPiwik::getPiwikUrl();
+
+        register_shutdown_function(function () use($profilerNamespace, $mainRun, $baseUrlStored) {
             $xhprofData = xhprof_disable();
-            $xhprofRuns = new \XHProfRuns_Default();
+            $xhprofRuns = new XHProfRuns_Default();
             $runId = $xhprofRuns->save_run($xhprofData, $profilerNamespace);
 
-            if(empty($runId)) {
+            if (empty($runId)) {
                 die('could not write profiler run');
             }
-            $runs = self::getProfilingRunIds();
-            $runs[] = $runId;
 
-            if($mainRun) {
-                $runIds = implode(',', $runs);
+            $runs = Profiler::getProfilingRunIds();
+            array_unshift($runs, $runId);
+
+            if ($mainRun) {
+                self::aggregateXhprofRuns($runs, $profilerNamespace, $saveTo = $runId);
+
                 $out = "\n\n";
                 $baseUrl = "http://" . @$_SERVER['HTTP_HOST'] . "/" . @$_SERVER['REQUEST_URI'];
-                $baseUrlStored = SettingsPiwik::getPiwikUrl();
-                if(strlen($baseUrlStored) > strlen($baseUrl)) {
+                if (strlen($baseUrlStored) > strlen($baseUrl)) {
                     $baseUrl = $baseUrlStored;
                 }
-                $baseUrl = "\n" . $baseUrl
-                    ."tests/lib/xhprof-0.9.4/xhprof_html/?source=$profilerNamespace&run=";
-
-                $out .= "Profiler report is available at:";
-                $out .= $baseUrl . $runId;
-                if($runId != $runIds) {
-                    $out .= "\n\nProfiler Report aggregating all runs triggered from this process: ";
-                    $out .= $baseUrl . $runIds;
-                }
+                $baseUrl = $baseUrlStored . "vendor/facebook/xhprof/xhprof_html/?source=$profilerNamespace&run=$runId";
+
+                $out .= "Profiler report is available at:\n";
+                $out .= $baseUrl;
                 $out .= "\n\n";
-                echo ($out);
+
+                echo $out;
             } else {
-                self::setProfilingRunIds($runs);
+                Profiler::setProfilingRunIds($runs);
             }
         });
+
+        self::$isXhprofSetup = true;
+    }
+
+    /**
+     * Aggregates xhprof runs w/o normalizing (xhprof_aggregate_runs will always average data which
+     * does not fit Piwik's use case).
+     */
+    private static function aggregateXhprofRuns($runIds, $profilerNamespace, $saveToRunId)
+    {
+        $xhprofRuns = new XHProfRuns_Default();
+
+        $aggregatedData = array();
+
+        foreach ($runIds as $runId) {
+            $xhprofRunData = $xhprofRuns->get_run($runId, $profilerNamespace, $description);
+
+            foreach ($xhprofRunData as $key => $data) {
+                if (empty($aggregatedData[$key])) {
+                    $aggregatedData[$key] = $data;
+                } else {
+                    // don't aggregate main() metrics since only the super run has the correct metrics for the entire run
+                    if ($key == "main()") {
+                        continue;
+                    }
+
+                    $aggregatedData[$key]["ct"] += $data["ct"];
+                    $aggregatedData[$key]["wt"] += $data["wt"];
+                    $aggregatedData[$key]["cpu"] += $data["cpu"];
+                    $aggregatedData[$key]["mu"] += $data["mu"];
+                    $aggregatedData[$key]["pmu"] = max($aggregatedData[$key]["pmu"], $data["pmu"]);
+                }
+            }
+        }
+
+        $xhprofRuns->save_run($aggregatedData, $profilerNamespace, $saveToRunId);
     }
 
     private static function setProfilingRunIds($ids)
diff --git a/tests/PHPUnit/bootstrap.php b/tests/PHPUnit/bootstrap.php
index 6a80c72755..dc16386563 100644
--- a/tests/PHPUnit/bootstrap.php
+++ b/tests/PHPUnit/bootstrap.php
@@ -44,7 +44,9 @@ require_once PIWIK_INCLUDE_PATH . '/tests/PHPUnit/Impl/TestRequestCollection.php
 require_once PIWIK_INCLUDE_PATH . '/tests/PHPUnit/Impl/TestRequestResponse.php';
 require_once PIWIK_INCLUDE_PATH . '/tests/PHPUnit/Impl/ApiTestConfig.php';
 
-\Piwik\Profiler::setupProfilerXHProf( $mainRun = true );
+if (getenv('PIWIK_USE_XHPROF') == 1) {
+    \Piwik\Profiler::setupProfilerXHProf();
+}
 
 // require test fixtures
 require_once PIWIK_INCLUDE_PATH . '/tests/PHPUnit/Fixture.php';
-- 
GitLab