diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9107b60
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+Here we should put some instructions on how to install the XHProf extension.
\ No newline at end of file
diff --git a/XHProfLib/XHProfAggregator.php b/XHProfLib/XHProfAggregator.php
deleted file mode 100644
index 2dae522..0000000
--- a/XHProfLib/XHProfAggregator.php
+++ /dev/null
@@ -1,103 +0,0 @@
-<?php
-
-class XHProfAggregator {
-  public $runs = array();
-
-  /**
-   * @param $run_data
-   * @return void
-   */
-  public function addRun($run_data) {
-    $this->runs[] = $run_data;
-  }
-
-  /**
-   * @return array
-   */
-  public function average() {
-    $keys = array();
-    foreach ($this->runs as $data) {
-      $keys = $keys + array_keys($data);
-    }
-    $agg_run = array();
-    $run_count = count($runs);
-    foreach ($keys as $key) {
-      $agg_key = array();
-      // Check which runs have this parent_child function key, collect metrics if so.
-      foreach ($runs as $data) {
-        if (isset($data[$key])) {
-          foreach ($data[$key] as $metric => $val) {
-            $agg_key[$metric][] = $val;
-          }
-        }
-      }
-
-      // Average each metric for the key into the aggregated run.
-      $agg_run[$key] = array();
-      foreach ($agg_key as $metric => $vals) {
-        $sd = self::sd($vals);
-        $mean = (array_sum($vals) / count($vals));
-        $good_vals = array();
-
-        if ($sd == 0) {
-          $agg_run[$key][$metric] = (array_sum($vals) / count($vals));
-        }
-        else {
-          foreach ($vals as $v) {
-            $diff = abs($mean - $v);
-            if (abs($mean - $v) < ($sd * 2)) {
-              $good_vals[] = $v;
-            }
-          }
-          $agg_run[$key][$metric] = (array_sum($good_vals) / count($good_vals));
-        }
-      }
-    }
-
-    return $agg_run;
-  }
-
-
-  /**
-   * @return array
-   */
-  public function sum() {
-    $keys = array();
-    foreach ($this->runs as $data) {
-      $keys = $keys + array_keys($data);
-    }
-
-    $agg_run = array();
-    foreach ($keys as $key) {
-      $agg_key = array();
-      // Check which runs have this parent_child function key, collect metrics if so.
-      foreach ($this->runs as $data) {
-        if (isset($data[$key])) {
-          foreach ($data[$key] as $metric => $val) {
-            $agg_key[$metric][] = $val;
-          }
-        }
-      }
-
-      // Sum each metric for the key into the aggregated run.
-      $agg_run[$key] = array();
-      foreach ($agg_key as $metric => $vals) {
-        $agg_run[$key][$metric] = array_sum($vals);
-      }
-    }
-
-    return $agg_run;
-  }
-
-  public static function sd_square($x, $mean) {
-    return pow($x - $mean,2);
-  }
-
-  /**
-   * Function to calculate standard deviation (uses sd_square)
-   */
-  public static function sd($array) {
-    // square root of sum of squares devided by N-1
-    return sqrt(array_sum(array_map(array('XHProfTools', 'sd_square'), $array, array_fill(0, count($array), (array_sum($array) / count($array))))) / (count($array)-1));
-  }
-}
diff --git a/XHProfLib/XHProfDiffParser.php b/XHProfLib/XHProfDiffParser.php
deleted file mode 100644
index 77af51f..0000000
--- a/XHProfLib/XHProfDiffParser.php
+++ /dev/null
@@ -1,28 +0,0 @@
-<?php
-
-class XHProfDiffParser {
-  public $parser1;
-  public $parser2;
-  public $totals = array();
-  public $symbol_totals = array();
-
-  public function __construct($data1, $data2) {
-    $this->data = $data;
-    $this->parser1 = new XHProfParser($data1);
-    $this->parser2 = new XHProfParser($data2);
-    $this->parser1->getTotals();
-    $this->parser2->getTotals();
-  }
-
-  public function getDiffTotals() {
-    $diff_totals[0] = $this->parser1->getTotals();
-    $diff_totals[1] = $this->parser2->getTotals();
-    $diff_totals['diff'] = array();
-    $diff_totals['diff%'] = array();
-    foreach ($diff_totals[0] as $metric => $value) {
-      $diff_totals['diff'][$metric] = $diff_totals[1][$metric] - $value;
-      $diff_totals['diff%'][$metric] = (($diff_totals[1][$metric] / $value) - 1) * 100;
-    }
-    return $diff_totals;
-  }
-}
diff --git a/XHProfLib/XHProfParser.php b/XHProfLib/XHProfParser.php
deleted file mode 100644
index 1efb060..0000000
--- a/XHProfLib/XHProfParser.php
+++ /dev/null
@@ -1,65 +0,0 @@
-<?php
-
-class XHProfParser {
-  public $data = array();
-  public $totals = array();
-  public $symbol_totals = array();
-
-  public function __construct($data) {
-    $this->data = $data;
-    $this->getTotals();
-  }
-
-  public function getTotals() {
-    $this->totals = $this->data['main()'];
-    $this->totals['ct'] = $this->getCallCount();
-    return $this->totals;
-  }
-
-  public function toXML($totals) {
-    $xml = new SimpleXMLElement('<xhprof_data/>');
-    array_walk_recursive(array_flip($totals), array ($xml, 'addChild'));
-    return $xml->asXML();
-  }
-
-  public function getCallCount() {
-    $call_count = 0;
-    foreach ($this->data as $symbol) {
-      $call_count += $symbol['ct'];
-    }
-    return $call_count;
-  }
-
-  public function getMetrics($symbol) {
-    if (!isset($this->symbol_totals[$symbol])) {
-      $this->symbol_totals[$symbol] = array(
-        'ct' => 0,
-        'wt' => 0,
-        'cpu' => 0,
-        'mu' => 0,
-        'pmu' => 0,
-      );
-    }
-    foreach ($this->data as $key => $symbol_data) {
-      if ($key !== 'main()') {
-        list($caller, $cur_symbol) = explode('==>', $key);
-        if ($cur_symbol == $symbol) {
-          foreach ($symbol_data as $metric => $value) {
-            $this->symbol_totals[$symbol][$metric] += $value;
-          }
-          $this->symbol_totals[$symbol] = $this->calculatePercentages($this->symbol_totals[$symbol]);
-          $this->symbol_totals[$symbol] = $this->symbol_totals[$symbol];
-          return $this->symbol_totals[$symbol];
-        }
-      }
-    }
-  }
-  protected function calculatePercentages($symbol_metrics) {
-    foreach ($symbol_metrics as $metric => $value) {
-      if ($this->totals[$metric] !== 0) {
-        $symbol_metrics[$metric . '%'] = $value / $this->totals[$metric];
-      }
-    }
-    return $symbol_metrics;
-  }
-}
diff --git a/XHProfLib/XHProfRuns.php b/XHProfLib/XHProfRuns.php
deleted file mode 100644
index b08ed77..0000000
--- a/XHProfLib/XHProfRuns.php
+++ /dev/null
@@ -1,62 +0,0 @@
-<?php
-
-class XHProfRunsFile implements XHProfRunsInterface {
-  private $dir;
-  private $suffix;
-
-  public function __construct() {
-    $this->dir = ini_get("xhprof.output_dir") ?: sys_get_temp_dir();
-    $this->suffix = 'xhprof';
-  }
-
-  private function gen_run_id($type) {
-    return uniqid();
-  }
-
-  private function fileName($run_id, $namespace) {
-    $file = implode('.', array($run_id, $namespace, $this->suffix));
-
-    if (!empty($this->dir)) {
-      $file = $this->dir . "/" . $file;
-    }
-    return $file;
-  }
-
-  public function getRun($run_id, $namespace) {
-    $file_name = $this->fileName($run_id, $namespace);
-
-    if (!file_exists($file_name)) {
-      throw new Exception("Could not find file $file_name");
-    }
-
-    $contents = file_get_contents($file_name);
-    return unserialize($contents);
-  }
-
-  public function getRuns($namespace = NULL) {
-    xdebug_break();
-    $files = $this->scanXHProfDir($this->dir, $namespace);
-    $files = array_map(function($f) {
-        $f['date'] = strtotime($f['date']);
-        return $f;
-      }, $files);
-    return $files;
-  }
-
-  public function scanXHProfDir($dir, $namespace = NULL) {
-    if (is_dir($dir)) {
-      $runs = array();
-      foreach (glob("{$this->dir}/*.{$this->suffix}") as $file) {
-        list($run, $source) = explode('.', basename($file));
-        $runs[] = array(
-          'run_id' => $run,
-          'namespace' => $source,
-          'basename' => htmlentities(basename($file)),
-          'date' => date("Y-m-d H:i:s", filemtime($file)),
-        );
-      }
-    }
-    return array_reverse($runs);
-  }
-}
-
diff --git a/XHProfLib/XHProfRunsInterface.php b/XHProfLib/XHProfRunsInterface.php
deleted file mode 100644
index b6cda78..0000000
--- a/XHProfLib/XHProfRunsInterface.php
+++ /dev/null
@@ -1,6 +0,0 @@
-<?php
-
-interface XHProfRunsInterface {
-  public function getRuns();
-  public function getRun($run_id, $namespace);
-}
diff --git a/config/install/xhprof.config.yml b/config/install/xhprof.config.yml
new file mode 100644
index 0000000..07b95c1
--- /dev/null
+++ b/config/install/xhprof.config.yml
@@ -0,0 +1,4 @@
+enabled: false
+interval: ''
+storage: xhprof.file_storage
+exclude: "/contextual/*\r\n/toolbar/*\r\n/edit/*\r\n/admin/*\r\n/profiler/*\r\n*.js\r\n*.css"
diff --git a/config/schema/xhprof.schema.yml b/config/schema/xhprof.schema.yml
new file mode 100644
index 0000000..283f30f
--- /dev/null
+++ b/config/schema/xhprof.schema.yml
@@ -0,0 +1,17 @@
+# Schema for the configuration files of the XHProf module.
+xhprof.config:
+  type: mapping
+  label: 'XHProf configuration'
+  mapping:
+    enabled:
+      type: boolean
+      label: 'XHProf enabled'
+    interval:
+      type: string
+      label: 'The approximate number of requests between XHProf samples. Leave empty to profile all requests'
+    storage:
+      type: string
+      label: 'Choose the XHProf storage class'
+    exclude:
+      type: string
+      label: 'Path to exclude'
diff --git a/css/xhprof.css b/css/xhprof.css
new file mode 100644
index 0000000..12471d3
--- /dev/null
+++ b/css/xhprof.css
@@ -0,0 +1,103 @@
+/* diff reports: display regressions in red */
+.vrbar {
+    text-align: right;
+    color: red;
+}
+
+/* diff reports: display improvements in green */
+.vgbar {
+    text-align: right;
+    color: green;
+}
+
+td.vwbar, th.vwbar {
+    text-align: right;
+}
+
+td.vwlbar, th.vwlbar {
+    text-align: left;
+}
+
+p.blue {
+    color: blue
+}
+
+.xhprof_micro, .xhprof_percent {
+    text-align: right;
+}
+
+thead th {
+    text-transform: none !important;
+}
+
+table th {
+    font-weight: bold;
+}
+
+table td, table th {
+    padding: 9px 10px;
+    text-align: left;
+}
+
+@media only screen and (max-width: 1800px) {
+    table.responsive {
+        margin-bottom: 0;
+    }
+
+    .pinned {
+        position: absolute;
+        left: 0;
+        top: 0;
+        background: #fff;
+        width: 25%;
+        overflow: hidden;
+        overflow-x: scroll;
+        border-right: 1px solid #ccc;
+        border-left: 1px solid #ccc;
+    }
+
+    .pinned table {
+        border-right: none;
+        border-left: none;
+        width: 100%;
+    }
+
+    .pinned table th, .pinned table td {
+        white-space: nowrap;
+    }
+
+    .pinned table th {
+        height: 60px;
+    }
+
+    .pinned td:last-child {
+        border-bottom: 0;
+    }
+
+    div.table-wrapper {
+        position: relative;
+        margin-bottom: 20px;
+        overflow: hidden;
+        border-right: 1px solid #ccc;
+    }
+
+    div.table-wrapper div.scrollable table {
+        margin-left: 25%;
+    }
+
+    div.table-wrapper div.scrollable {
+        overflow: scroll;
+        overflow-y: hidden;
+    }
+
+    table.responsive td, table.responsive th {
+        position: relative;
+        white-space: nowrap;
+        overflow: hidden;
+    }
+
+    table.responsive th:first-child, table.responsive td:first-child,
+    table.responsive td:first-child, table.responsive.pinned td {
+        display: none;
+    }
+}
diff --git a/js/xhprof.js b/js/xhprof.js
new file mode 100644
index 0000000..c198ec6
--- /dev/null
+++ b/js/xhprof.js
@@ -0,0 +1,44 @@
+/**
+ *
+ */
+(function ($, Drupal, drupalSettings) {
+
+    Drupal.behaviors.xhprof = {
+        attach: function (context) {
+            var switched = false;
+            var updateTables = function () {
+                if (($(window).width() < 1800) && !switched) {
+                    switched = true;
+                    $("table.responsive").each(function (i, element) {
+                        splitTable($(element));
+                    });
+                    return true;
+                }
+                else if (switched && ($(window).width() > 1800)) {
+                    switched = false;
+                    $("table.responsive").each(function (i, element) {
+                        unsplitTable($(element));
+                    });
+                }
+            };
+            $(window).load(updateTables);
+            $(window).bind("resize", updateTables);
+            function splitTable(original) {
+                original.wrap("<div class='table-wrapper' />");
+                var copy = original.clone();
+                copy.find("td:not(:first-child), th:not(:first-child)").css("display", "none");
+                copy.removeClass("responsive");
+                original.closest(".table-wrapper").append(copy);
+                copy.wrap("<div class='pinned' />");
+                original.wrap("<div class='scrollable' />");
+            }
+
+            function unsplitTable(original) {
+                original.closest(".table-wrapper").find(".pinned").remove();
+                original.unwrap();
+                original.unwrap();
+            }
+        }
+    }
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/lib/Drupal/xhprof/XHProfRunsFile.php b/lib/Drupal/xhprof/XHProfRunsFile.php
deleted file mode 100644
index f8839b6..0000000
--- a/lib/Drupal/xhprof/XHProfRunsFile.php
+++ /dev/null
@@ -1,125 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\xhprof\XHProfRunsFile;
- */
-
-namespace Drupal\xhprof;
-
-use Drupal\xhprof\XHProfRunsInterface;
-
-/**
- * XHProfRuns_Default is the default implementation of the
- * iXHProfRuns interface for saving/fetching XHProf runs.
- */
-class XHProfRunsFile implements XHProfRunsInterface {
-  private $dir = '';
-  private $suffix = 'xhprof';
-
-  public function getDir() {
-    return $this->dir;
-  }
-
-  private function gen_run_id($type) {
-    return uniqid();
-  }
-
-  private function file_name($run_id, $type) {
-
-    $file = "$run_id.$type." . $this->suffix;
-
-    if (!empty($this->dir)) {
-      $file = $this->dir . "/" . $file;
-    }
-    return $file;
-  }
-
-  public function __construct($dir = NULL) {
-    // if user hasn't passed a directory location,
-    // we use the xhprof.output_dir ini setting
-    // if specified, else we default to the directory
-    // in which the error_log file resides.
-    if (empty($dir)) {
-      $dir = ini_get("xhprof.output_dir");
-      if (empty($dir)) {
-        // some default that at least works on unix...
-        $dir = "/tmp";
-        /* @todo - Put this somewhere more sensible.
-        watchdog("xhprof", "Warning: Must specify directory location for XHProf runs. " .
-                     "Trying {$dir} as default. You can either pass the " .
-                     "directory location as an argument to the constructor " .
-                     "for XHProfRuns_Default() or set xhprof.output_dir " .
-                     "ini param.");
-         */
-      }
-    }
-    $this->dir = $dir;
-  }
-
-  public function get_run($run_id, $type, &$run_desc) {
-    $file_name = $this->file_name($run_id, $type);
-
-    if (!file_exists($file_name)) {
-      watchdog("xhprof", "Could not find file %file_name", array('%file_name' => $file_name));
-      $run_desc = "Invalid Run Id = $run_id";
-      return NULL;
-    }
-
-    $contents = file_get_contents($file_name);
-    $run_desc = "XHProf Run (Namespace=$type)";
-    return unserialize($contents);
-  }
-
-  public function save_run($xhprof_data, $type, $run_id = NULL) {
-
-    // Use PHP serialize function to store the XHProf's
-    // raw profiler data.
-    $xhprof_data = serialize($xhprof_data);
-
-    if ($run_id === NULL) {
-      $run_id = $this->gen_run_id($type);
-    }
-
-    $file_name = $this->file_name($run_id, $type);
-    $file = fopen($file_name, 'w');
-
-    if ($file) {
-      fwrite($file, $xhprof_data);
-      fclose($file);
-    }
-    else {
-      watchdog("xhprof", "Could not open %file_name.", array('%file_name' => $file_name));
-    }
-
-    // echo "Saved run in {$file_name}.\nRun id = {$run_id}.\n";
-    return $run_id;
-  }
-
-  public function getRuns($stats, $limit = 50, $skip = 0) {
-    $files = $this->scanXHProfDir($this->getDir(), variable_get('site_name', ''));
-    foreach ($files as $i => $file) {
-      $file['date'] = strtotime($file['date']);
-      $files[$i] = $file;
-    }
-    return $files;
-  }
-
-  public function getCount() {}
-
-  public function scanXHProfDir($dir, $source = NULL) {
-    if (is_dir($dir)) {
-      $runs = array();
-      foreach (glob("$dir/*.$source.*") as $file) {
-        list($run, $source) = explode('.', basename($file));
-        $runs[] = array(
-          'run_id' => $run,
-          'source' => $source,
-          'basename' => htmlentities(basename($file)),
-          'date' => date("Y-m-d H:i:s", filemtime($file)),
-        );
-      }
-    }
-    return array_reverse($runs);
-  }
-}
diff --git a/lib/Drupal/xhprof/XHProfRunsInterface.php b/lib/Drupal/xhprof/XHProfRunsInterface.php
deleted file mode 100644
index 5f54157..0000000
--- a/lib/Drupal/xhprof/XHProfRunsInterface.php
+++ /dev/null
@@ -1,25 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\xhprof\XHProfRunsInterface;
- */
-
-namespace Drupal\xhprof;
-
-interface XHProfRunsInterface {
-  /**
-  * This function gets runs based on passed parameters, column data as key, value as the value. Values
-  * are escaped automatically. You may also pass limit, order by, group by, or "where" to add those values,
-  * all of which are used as is, no escaping.
-  *
-  * @param array $stats
-   *  Criteria by which to select columns
-   *
-  * @return resource
-  */
-  public function getRuns($stats, $limit = 50, $skip = 0);
-  public function getCount();
-  public function get_run($run_id, $type, &$run_desc);
-  public function save_run($xhprof_data, $type, $run_id = NULL);
-}
diff --git a/modules/xhprof_mongodb/MongodbXHProfRuns.inc b/modules/xhprof_mongodb/MongodbXHProfRuns.inc
index 88556d9..89219d0 100644
--- a/modules/xhprof_mongodb/MongodbXHProfRuns.inc
+++ b/modules/xhprof_mongodb/MongodbXHProfRuns.inc
@@ -29,13 +29,13 @@ class MongodbXHProfRuns implements XHProfRunsInterface {
   }
 
   /**
-  * This function gets runs based on passed parameters, column data as key, value as the value. Values
-  * are escaped automatically. You may also pass limit, order by, group by, or "where" to add those values,
-  * all of which are used as is, no escaping.
-  *
-  * @param array $stats Criteria by which to select columns
-  * @return resource
-  */
+   * This function gets runs based on passed parameters, column data as key, value as the value. Values
+   * are escaped automatically. You may also pass limit, order by, group by, or "where" to add those values,
+   * all of which are used as is, no escaping.
+   *
+   * @param array $stats Criteria by which to select columns
+   * @return resource
+   */
   public function getRuns($stats, $limit = 50, $skip = 0) {
     $collection = mongodb_collection('xhprof');
     $sort = isset($_GET['order']) ? $_GET['order'] : 'date';
@@ -46,11 +46,10 @@ class MongodbXHProfRuns implements XHProfRunsInterface {
     );
 
     $cursor = $collection
-    ->find(array())
-    ->limit($limit)
-    ->skip($skip)
-    ->sort(array(strtolower($sort) => $sort_direction[$sort_direction_str]));
-    ;
+      ->find(array())
+      ->limit($limit)
+      ->skip($skip)
+      ->sort(array(strtolower($sort) => $sort_direction[$sort_direction_str]));;
     $runs = array();
     foreach ($cursor as $id => $run) {
       $run['run_id'] = $id;
@@ -70,7 +69,7 @@ class MongodbXHProfRuns implements XHProfRunsInterface {
     $collection = mongodb_collection('xhprof');
     $run_desc = "XHProf Run (Namespace=$type)";
 
-    $run = $collection->findOne(array('_id' => (string)$run_id));
+    $run = $collection->findOne(array('_id' => (string) $run_id));
     return $run['run_data'];
   }
 
@@ -85,17 +84,16 @@ class MongodbXHProfRuns implements XHProfRunsInterface {
     $collection = mongodb_collection('xhprof');
 
     $entry = array();
-    $entry['_id'] = (string)$mongo_id;
+    $entry['_id'] = (string) $mongo_id;
     $entry['run_data'] = $xhprof_data;
     $entry['get'] = serialize($_GET);
     $entry['cookie'] = serialize($_COOKIE);
     $entry['date'] = $_SERVER['REQUEST_TIME'];
     $entry['pmu'] = isset($xhprof_data['main()']['pmu']) ? $xhprof_data['main()']['pmu'] : '';
-    $entry['wt']  = isset($xhprof_data['main()']['wt'])  ? $xhprof_data['main()']['wt']  : '';
+    $entry['wt'] = isset($xhprof_data['main()']['wt']) ? $xhprof_data['main()']['wt'] : '';
     $entry['cpu'] = isset($xhprof_data['main()']['cpu']) ? $xhprof_data['main()']['cpu'] : '';
 
 
-
     $entry['path'] = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : $_SERVER['PHP_SELF'];
     $entry['servername'] = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '';
 
diff --git a/src/Compiler/StoragePass.php b/src/Compiler/StoragePass.php
new file mode 100644
index 0000000..a2d3a19
--- /dev/null
+++ b/src/Compiler/StoragePass.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Drupal\xhprof\Compiler;
+
+use Drupal\Core\StreamWrapper\PublicStream;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+
+/**
+ * Class StoragePass
+ */
+class StoragePass implements CompilerPassInterface {
+
+  /**
+   * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
+   *
+   * @throws \InvalidArgumentException
+   */
+  public function process(ContainerBuilder $container) {
+    // configure the xhprof.xhprof service
+    if (FALSE === $container->hasDefinition('xhprof.storage_manager')) {
+      return;
+    }
+
+    $definition = $container->getDefinition('xhprof.storage_manager');
+
+    foreach ($container->findTaggedServiceIds('xhprof_storage') as $id => $attributes) {
+      $definition->addMethodCall('addStorage', array($id, new Reference($id)));
+    }
+  }
+}
diff --git a/src/Controller/XHProfController.php b/src/Controller/XHProfController.php
new file mode 100644
index 0000000..12fc3b3
--- /dev/null
+++ b/src/Controller/XHProfController.php
@@ -0,0 +1,394 @@
+<?php
+
+namespace Drupal\xhprof\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\xhprof\XHProfLib\Report\ReportEngine;
+use Drupal\xhprof\XHProfLib\Report\ReportInterface;
+use Drupal\xhprof\XHProfLib\Run;
+use Drupal\xhprof\XHProfLib\XHProf;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Class XHProfController
+ */
+class XHProfController extends ControllerBase {
+
+  /**
+   * @var \Drupal\xhprof\XHProfLib\XHProf
+   */
+  private $xhprof;
+
+  /**
+   * @var \Drupal\xhprof\XHProfLib\Report\ReportEngine
+   */
+  private $reportEngine;
+
+  protected $descriptions = array(
+    "fn" => "Function Name",
+    "ct" => "Calls",
+    "ct_perc" => "Calls%",
+    "wt" => "Incl. Wall Time<br>(microsec)",
+    "wt_perc" => "IWall%",
+    "excl_wt" => "Excl. Wall Time<br>(microsec)",
+    "excl_wt_perc" => "EWall%",
+    "ut" => "Incl. User<br>(microsecs)",
+    "ut_perc" => "IUser%",
+    "excl_ut" => "Excl. User<br>(microsec)",
+    "excl_ut_perc" => "EUser%",
+    "st" => "Incl. Sys <br>(microsec)",
+    "st_perc" => "ISys%",
+    "excl_st" => "Excl. Sys <br>(microsec)",
+    "excl_st_perc" => "ESys%",
+    "cpu" => "Incl. CPU<br>(microsecs)",
+    "cpu_perc" => "ICpu%",
+    "excl_cpu" => "Excl. CPU<br>(microsec)",
+    "excl_cpu_perc" => "ECPU%",
+    "mu" => "Incl.<br>MemUse<br>(bytes)",
+    "mu_perc" => "IMemUse%",
+    "excl_mu" => "Excl.<br>MemUse<br>(bytes)",
+    "excl_mu_perc" => "EMemUse%",
+    "pmu" => "Incl.<br> PeakMemUse<br>(bytes)",
+    "pmu_perc" => "IPeakMemUse%",
+    "excl_pmu" => "Excl.<br>PeakMemUse<br>(bytes)",
+    "excl_pmu_perc" => "EPeakMemUse%",
+    "samples" => "Incl. Samples",
+    "samples_perc" => "ISamples%",
+    "excl_samples" => "Excl. Samples",
+    "excl_samples_perc" => "ESamples%",
+  );
+
+  protected $diff_descriptions = array(
+    "fn" => "Function Name",
+    "ct" => "Calls Diff",
+    "Calls%" => "Calls<br>Diff%",
+    "wt" => "Incl. Wall<br>Diff<br>(microsec)",
+    "IWall%" => "IWall<br> Diff%",
+    "excl_wt" => "Excl. Wall<br>Diff<br>(microsec)",
+    "EWall%" => "EWall<br>Diff%",
+    "ut" => "Incl. User Diff<br>(microsec)",
+    "IUser%" => "IUser<br>Diff%",
+    "excl_ut" => "Excl. User<br>Diff<br>(microsec)",
+    "EUser%" => "EUser<br>Diff%",
+    "cpu" => "Incl. CPU Diff<br>(microsec)",
+    "ICpu%" => "ICpu<br>Diff%",
+    "excl_cpu" => "Excl. CPU<br>Diff<br>(microsec)",
+    "ECpu%" => "ECpu<br>Diff%",
+    "st" => "Incl. Sys Diff<br>(microsec)",
+    "ISys%" => "ISys<br>Diff%",
+    "excl_st" => "Excl. Sys Diff<br>(microsec)",
+    "ESys%" => "ESys<br>Diff%",
+    "mu" => "Incl.<br>MemUse<br>Diff<br>(bytes)",
+    "IMUse%" => "IMemUse<br>Diff%",
+    "excl_mu" => "Excl.<br>MemUse<br>Diff<br>(bytes)",
+    "EMUse%" => "EMemUse<br>Diff%",
+    "pmu" => "Incl.<br> PeakMemUse<br>Diff<br>(bytes)",
+    "IPMUse%" => "IPeakMemUse<br>Diff%",
+    "excl_pmu" => "Excl.<br>PeakMemUse<br>Diff<br>(bytes)",
+    "EPMUse%" => "EPeakMemUse<br>Diff%",
+    "samples" => "Incl. Samples Diff",
+    "ISamples%" => "ISamples Diff%",
+    "excl_samples" => "Excl. Samples Diff",
+    "ESamples%" => "ESamples Diff%",
+  );
+
+  protected $format_cbk = array(
+    "fn" => "",
+    "ct" => array("Drupal\\xhprof\\Controller\\XHProfController", "countFormat"),
+    "ct_perc" => array("Drupal\\xhprof\\Controller\\XHProfController", "percentFormat"),
+    "wt" => "number_format",
+    "wt_perc" => array("Drupal\\xhprof\\Controller\\XHProfController", "percentFormat"),
+    "excl_wt" => "number_format",
+    "excl_wt_perc" => array("Drupal\\xhprof\\Controller\\XHProfController", "percentFormat"),
+    "ut" => "number_format",
+    "ut_perc" => array("Drupal\\xhprof\\Controller\\XHProfController", "percentFormat"),
+    "excl_ut" => "number_format",
+    "excl_ut_perc" => array("Drupal\\xhprof\\Controller\\XHProfController", "percentFormat"),
+    "st" => "number_format",
+    "st_perc" => array("Drupal\\xhprof\\Controller\\XHProfController", "percentFormat"),
+    "excl_st" => "number_format",
+    "excl_st_perc" => array("Drupal\\xhprof\\Controller\\XHProfController", "percentFormat"),
+    "cpu" => "number_format",
+    "cpu_perc" => array("Drupal\\xhprof\\Controller\\XHProfController", "percentFormat"),
+    "excl_cpu" => "number_format",
+    "excl_cpu_perc" => array("Drupal\\xhprof\\Controller\\XHProfController", "percentFormat"),
+    "mu" => "number_format",
+    "mu_perc" => array("Drupal\\xhprof\\Controller\\XHProfController", "percentFormat"),
+    "excl_mu" => "number_format",
+    "excl_mu_perc" => array("Drupal\\xhprof\\Controller\\XHProfController", "percentFormat"),
+    "pmu" => "number_format",
+    "pmu_perc" => array("Drupal\\xhprof\\Controller\\XHProfController", "percentFormat"),
+    "excl_pmu" => "number_format",
+    "excl_pmu_perc" => array("Drupal\\xhprof\\Controller\\XHProfController", "percentFormat"),
+    "samples" => "number_format",
+    "samples_perc" => array("Drupal\\xhprof\\Controller\\XHProfController", "percentFormat"),
+    "excl_samples" => "number_format",
+    "excl_samples_perc" => array("Drupal\\xhprof\\Controller\\XHProfController", "percentFormat"),
+  );
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('xhprof.xhprof'),
+      $container->get('xhprof.report_engine')
+    );
+  }
+
+  /**
+   * @param \Drupal\xhprof\XHProfLib\XHProf $xhprof
+   * @param \Drupal\xhprof\XHProfLib\Report\ReportEngine $reportEngine
+   */
+  public function __construct(XHProf $xhprof, ReportEngine $reportEngine) {
+    $this->xhprof = $xhprof;
+    $this->reportEngine = $reportEngine;
+  }
+
+  /**
+   *
+   */
+  public function runsAction() {
+    $runs = $run = $this->xhprof->getStorage()->getRuns();
+
+    // Table attributes
+    $attributes = array('id' => 'xhprof-runs-table');
+
+    // Table header
+    $header = array();
+    $header[] = array('data' => t('View'));
+    $header[] = array('data' => t('Path'), 'field' => 'path');
+    $header[] = array('data' => t('Date'), 'field' => 'date', 'sort' => 'desc');
+
+    // Table rows
+    $rows = array();
+    foreach ($runs as $run) {
+      $row = array();
+      $link = XHPROF_PATH . '/' . $run['run_id'];
+      $row[] = array('data' => l($run['run_id'], $link));
+      $row[] = array('data' => isset($run['path']) ? $run['path'] : '');
+      $row[] = array('data' => format_date($run['date'], 'small'));
+      $rows[] = $row;
+    }
+
+    $build['table'] = array(
+      '#type' => 'table',
+      '#header' => $header,
+      '#rows' => $rows,
+      '#attributes' => $attributes
+    );
+
+    return $build;
+  }
+
+  /**
+   * @param \Drupal\xhprof\XHProfLib\Run $run
+   *
+   * @return string
+   */
+  public function viewAction(Run $run) {
+    $report = $this->reportEngine->getReport(NULL, NULL, $run, NULL, NULL, 'wt', NULL, NULL);
+
+    $table = $report->getData();
+
+    $build['report'] = array(
+      '#markup' => '<p>' . $this->t('Run report for @id', array('@id' => $run->getId())) . '</p>',
+      '#attached' => array(
+        'library' => array(
+          'xhprof/xhprof',
+        ),
+      ),
+    );
+
+    /*$build['summary'] = array(
+      '#theme' => 'table',
+      '#header' => $run,
+      '#rows' => $summary,
+    );*/
+
+    $build['table'] = array(
+      '#theme' => 'table',
+      '#header' => $this->getHeader($table['symbols']),
+      '#rows' => $this->getRows($table['symbols'], $report),
+      '#attributes' => array('class' => array('responsive'))
+    );
+
+    return $build;
+  }
+
+  /**
+   * @param $table
+   *
+   * @return array
+   */
+  private function getHeader($table) {
+    return array(
+      $this->getDescription('fn'),
+      $this->getDescription('ct'),
+      $this->getDescription('ct_perc'),
+      $this->getDescription('wt'),
+      $this->getDescription('wt_perc'),
+      $this->getDescription('excl_wt'),
+      $this->getDescription('excl_wt_perc'),
+      $this->getDescription('cpu'),
+      $this->getDescription('cpu_perc'),
+      $this->getDescription('excl_cpu'),
+      $this->getDescription('excl_cpu_perc'),
+      $this->getDescription('mu'),
+      $this->getDescription('mu_perc'),
+      $this->getDescription('excl_mu'),
+      $this->getDescription('excl_mu_perc'),
+      $this->getDescription('pmu'),
+      $this->getDescription('pmu_perc'),
+      $this->getDescription('excl_pmu'),
+      $this->getDescription('excl_pmu_perc'),
+    );
+  }
+
+  /**
+   * @param $table
+   * @param $report
+   *
+   * @return array
+   */
+  private function getRows($table, ReportInterface $report) {
+    $rows = array();
+    $totals = $report->getTotals();
+
+    foreach ($table as $key => $value) {
+      $row = array();
+      $row[] = $this->abbrClass($key);
+
+      $row[] = $this->getValue($value['ct'], 'ct');
+      $row[] = $this->getPercentValue($value['ct'], 'ct', $totals['ct']);
+
+      $row[] = $this->getValue($value['wt'], 'wt');
+      $row[] = $this->getPercentValue($value['wt'], 'wt', $totals['wt']);
+
+      $row[] = $this->getValue($value['excl_wt'], 'excl_wt');
+      $row[] = $this->getPercentValue($value['excl_wt'], 'excl_wt', $totals['wt']);
+
+      $row[] = $this->getValue($value['cpu'], 'cpu');
+      $row[] = $this->getPercentValue($value['cpu'], 'cpu', $totals['cpu']);
+
+      $row[] = $this->getValue($value['excl_cpu'], 'excl_cpu');
+      $row[] = $this->getPercentValue($value['excl_cpu'], 'excl_cpu', $totals['cpu']);
+
+      $row[] = $this->getValue($value['mu'], 'mu');
+      $row[] = $this->getPercentValue($value['mu'], 'mu', $totals['mu']);
+
+      $row[] = $this->getValue($value['excl_mu'], 'excl_mu');
+      $row[] = $this->getPercentValue($value['excl_mu'], 'excl_mu', $totals['mu']);
+
+      $row[] = $this->getValue($value['pmu'], 'pmu');
+      $row[] = $this->getPercentValue($value['pmu'], 'pmu', $totals['pmu']);
+
+      $row[] = $this->getValue($value['excl_pmu'], 'excl_pmu');
+      $row[] = $this->getPercentValue($value['excl_pmu'], 'excl_pmu', $totals['pmu']);
+
+      $rows[] = $row;
+    }
+
+    return $rows;
+  }
+
+  /**
+   * @param string $class
+   *
+   * @return string
+   */
+  private function abbrClass($class) {
+    $parts = explode('\\', $class);
+    $short = array_pop($parts);
+
+    if (strlen($short) >= 40) {
+      $short = substr($short, 0, 30) . " ... " . substr($short, -5);
+    }
+
+    return sprintf("<abbr title=\"%s\">%s</abbr>", $class, $short);
+  }
+
+  /**
+   * @param \Drupal\xhprof\XHProfLib\Run $run1
+   * @param \Drupal\xhprof\XHProfLib\Run $run2
+   *
+   * @return string
+   */
+  public function diffAction(Run $run1, Run $run2) {
+    //drupal_add_css(drupal_get_path('module', 'xhprof') . '/xhprof.css');
+
+    return ''; //xhprof_display_run(array($run1, $run2), $symbol = NULL);
+  }
+
+  /**
+   * @param \Drupal\xhprof\XHProfLib\Run $run
+   * @param $symbol
+   *
+   * @return string
+   */
+  public function symbolAction(Run $run, $symbol) {
+    //drupal_add_css(drupal_get_path('module', 'xhprof') . '/xhprof.css');
+
+    return ''; //xhprof_display_run(array($run_id), $symbol);
+  }
+
+  /**
+   * @param $metric
+   *
+   * @return string
+   */
+  private function getDescription($metric) {
+    return $this->t($this->descriptions[$metric]);
+  }
+
+  /**
+   * @param $value
+   * @param $metric
+   *
+   * @return mixed
+   */
+  private function getValue($value, $metric) {
+    return call_user_func($this->format_cbk[$metric], $value);
+  }
+
+  /**
+   * @param $value
+   * @param $metric
+   * @param $totals
+   *
+   * @return mixed|string
+   */
+  private function getPercentValue($value, $metric, $totals) {
+    if ($totals == 0) {
+      $pct = "N/A%";
+    }
+    else {
+      $pct = call_user_func($this->format_cbk[$metric . '_perc'], ($value / abs($totals)));
+    }
+
+    return $pct;
+  }
+
+  /**
+   * @param $num
+   * @return string
+   */
+  private function countFormat($num) {
+    $num = round($num, 3);
+    if (round($num) == $num) {
+      return number_format($num);
+    }
+    else {
+      return number_format($num, 3);
+    }
+  }
+
+  /**
+   * @param $s
+   * @param int $precision
+   * @return string
+   */
+  private function percentFormat($s, $precision = 1) {
+    return sprintf('%.' . $precision . 'f%%', 100 * $s);
+  }
+}
diff --git a/src/DataCollector/XHProfDataCollector.php b/src/DataCollector/XHProfDataCollector.php
new file mode 100644
index 0000000..c9db58d
--- /dev/null
+++ b/src/DataCollector/XHProfDataCollector.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Drupal\xhprof\DataCollector;
+
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\webprofiler\DataCollector\DrupalDataCollectorTrait;
+use Drupal\webprofiler\DrupalDataCollectorInterface;
+use Drupal\xhprof\XHProfLib\XHProf;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\DataCollector\DataCollector;
+
+class XHProfDataCollector extends DataCollector implements DrupalDataCollectorInterface {
+
+  use StringTranslationTrait, DrupalDataCollectorTrait;
+
+  /**
+   * @var \Drupal\xhprof\XHProfLib\XHProf
+   */
+  private $xhprof;
+
+  /**
+   * @param \Drupal\xhprof\XHProfLib\XHProf $xhprof
+   */
+  public function __construct(XHProf $xhprof) {
+    $this->xhprof = $xhprof;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function collect(Request $request, Response $response, \Exception $exception = NULL) {
+    $this->data['run_id'] = $this->xhprof->getRunId();
+  }
+
+  /**
+   * @return string
+   */
+  public function getRunId() {
+    return $this->data['run_id'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getName() {
+    return 'xhprof';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTitle() {
+    return $this->t('Assets');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasPanel() {
+    return FALSE;
+  }
+
+}
diff --git a/src/EventSubscriber/XHProfEventSubscriber.php b/src/EventSubscriber/XHProfEventSubscriber.php
new file mode 100644
index 0000000..6ebe74e
--- /dev/null
+++ b/src/EventSubscriber/XHProfEventSubscriber.php
@@ -0,0 +1,141 @@
+<?php
+
+namespace Drupal\xhprof\EventSubscriber;
+
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\xhprof\XHProfLib\XHProf;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\HttpKernel\Event;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
+use Symfony\Component\HttpKernel\Event\PostResponseEvent;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Class XHProfEventSubscriber
+ */
+class XHProfEventSubscriber implements EventSubscriberInterface {
+
+  /**
+   * @var \Drupal\xhprof\XHProfLib\XHProf
+   */
+  public $xhprof;
+
+  /**
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  private $currentUser;
+
+  /**
+   * @var \Drupal\Core\Routing\UrlGeneratorInterface
+   */
+  protected $urlGenerator;
+
+  /**
+   * @var string
+   */
+  private $xhprofRunId;
+
+  /**
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  private $moduleHandler;
+
+  /**
+   * @param \Drupal\xhprof\XHProfLib\XHProf $xhprof
+   * @param \Drupal\Core\Session\AccountInterface $currentUser
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   */
+  public function __construct(XHProf $xhprof, AccountInterface $currentUser, ModuleHandlerInterface $module_handler) {
+    $this->xhprof = $xhprof;
+    $this->currentUser = $currentUser;
+    $this->moduleHandler = $module_handler;
+  }
+
+  /**
+   * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
+   */
+  public function onKernelRequest(GetResponseEvent $event) {
+    if($this->xhprof->canEnable($event->getRequest())) {
+      $this->xhprof->enable();
+    }
+  }
+
+  /**
+   * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
+   */
+  public function onKernelResponse(FilterResponseEvent $event) {
+    if ($this->xhprof->isEnabled()) {
+      $this->xhprofRunId = $this->xhprof->createRunId();
+
+      // Don't print the link to xhprof run page if
+      // Webprofiler module is enabled, a widget will
+      // be rendered into Webprofiler toolbar.
+      if (!$this->moduleHandler->moduleExists('webprofiler')) {
+        $response = $event->getResponse();
+
+        // Try not to break non html pages.
+        $formats = array(
+          'xml',
+          'javascript',
+          'json',
+          'plain',
+          'image',
+          'application',
+          'csv',
+          'x-comma-separated-values'
+        );
+        foreach ($formats as $format) {
+          if ($response->headers->get($format)) {
+            return;
+          }
+        }
+
+        if ($this->currentUser->hasPermission('access xhprof data')) {
+          $this->injectLink($response, $this->xhprofRunId);
+        }
+      }
+
+      if (function_exists('drush_log')) {
+        drush_log('xhprof link: ' . $this->xhprof->link($this->xhprofRunId, 'url'), 'notice');
+      }
+    }
+  }
+
+  /**
+   * @param \Symfony\Component\HttpKernel\Event\PostResponseEvent $event
+   */
+  public function onKernelTerminate(PostResponseEvent $event) {
+    if ($this->xhprof->isEnabled()) {
+      $this->xhprof->shutdown($this->xhprofRunId);
+    }
+  }
+
+  /**
+   * @return array
+   */
+  static function getSubscribedEvents() {
+    return array(
+      KernelEvents::REQUEST => array('onKernelRequest', 0),
+      KernelEvents::RESPONSE => array('onKernelResponse', 0),
+      KernelEvents::TERMINATE => array('onKernelTerminate', 0),
+    );
+  }
+
+  /**
+   * @param \Symfony\Component\HttpFoundation\Response $response
+   * @param string $xhprofRunId
+   */
+  protected function injectLink(Response $response, $xhprofRunId) {
+    $content = $response->getContent();
+    $pos = mb_strripos($content, '</body>');
+
+    if (FALSE !== $pos) {
+      $output = '<div class="xhprof-ui">' . $this->xhprof->link($xhprofRunId) . '</div>';
+      $content = mb_substr($content, 0, $pos) . $output . mb_substr($content, $pos);
+      $response->setContent($content);
+    }
+  }
+}
diff --git a/src/Form/ConfigForm.php b/src/Form/ConfigForm.php
new file mode 100644
index 0000000..5af4fbf
--- /dev/null
+++ b/src/Form/ConfigForm.php
@@ -0,0 +1,117 @@
+<?php
+
+namespace Drupal\xhprof\Form;
+
+use Drupal\Core\Form\ConfigFormBase;
+use Drupal\xhprof\XHProfLib\Storage\StorageManager;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Class ConfigForm
+ */
+class ConfigForm extends ConfigFormBase {
+
+  /**
+   * @var \Drupal\xhprof\XHProfLib\Storage\StorageManager
+   */
+  private $storageManager;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('xhprof.storage_manager')
+    );
+  }
+
+  /**
+   * @param \Drupal\xhprof\XHProfLib\Storage\StorageManager $storageManager
+   */
+  public function __construct(StorageManager $storageManager) {
+    $this->storageManager = $storageManager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'xhprof_config';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, array &$form_state) {
+    $config = $this->config('xhprof.config');
+
+    $description = extension_loaded('xhprof') ? t('Profile requests with the xhprof php extension.') : '<span class="warning">' . t('You must enable the <a href="!url">xhprof php extension</a> to use this feature.', array('!url' => url('http://techportal.ibuildings.com/2009/12/01/profiling-with-xhprof/'))) . '</span>';
+    $form['enabled'] = array(
+      '#type' => 'checkbox',
+      '#title' => $this->t('Enable profiling of page views and <a href="!drush">drush</a> requests.', array('!drush' => url('https://github.com/drush-ops/drush'))),
+      '#default_value' => $config->get('enabled'),
+      '#description' => $description,
+      '#disabled' => !extension_loaded('xhprof'),
+    );
+
+    $form['settings'] = array(
+      '#title' => $this->t('Profiling settings'),
+      '#type' => 'details',
+      '#open' => TRUE,
+      '#states' => array(
+        'invisible' => array(
+          'input[name="xhprof_enabled"]' => array('checked' => FALSE),
+        ),
+      ),
+    );
+
+    $form['settings']['exclude'] = array(
+      '#type' => 'textarea',
+      '#title' => $this->t('Exclude'),
+      '#default_value' => $config->get('exclude'),
+      '#description' => $this->t('Path to exclude for profiling. One path per line.')
+    );
+
+    $form['settings']['interval'] = array(
+      '#type' => 'textfield',
+      '#title' => 'Profiling interval',
+      '#default_value' => $config->get('interval'),
+      '#description' => $this->t('The approximate number of requests between XHProf samples. Leave empty to profile all requests'),
+    );
+
+    $options = $this->storageManager->getStorages();
+    $form['settings']['storage'] = array(
+      '#type' => 'radios',
+      '#title' => $this->t('XHProf storage'),
+      '#default_value' => $config->get('storage'),
+      '#options' => $options,
+      '#description' => $this->t('Choose the XHProf storage class.'),
+    );
+
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, array &$form_state) {
+    // TODO: Simplify this.
+    if (isset($form_state['values']['interval']) && $form_state['values']['interval'] != '' && (!is_numeric($form_state['values']['interval']) || $form_state['values']['interval'] <= 0 || $form_state['values']['interval'] > mt_getrandmax())) {
+      $this->setFormError('interval', $form_state, $this->t('The profiling interval must be set to a positive integer.'));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, array &$form_state) {
+    $this->config('xhprof.config')
+      ->set('enabled', $form_state['values']['enabled'])
+      ->set('exclude', $form_state['values']['exclude'])
+      ->set('interval', $form_state['values']['interval'])
+      ->set('storage', $form_state['values']['storage'])
+      ->save();
+
+    parent::submitForm($form, $form_state);
+  }
+}
diff --git a/src/RequestMatcher/XHProfRequestMatcher.php b/src/RequestMatcher/XHProfRequestMatcher.php
new file mode 100644
index 0000000..f445d2d
--- /dev/null
+++ b/src/RequestMatcher/XHProfRequestMatcher.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Drupal\xhprof\RequestMatcher;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Path\PathMatcherInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestMatcherInterface;
+
+/**
+ * Class WebprofilerRequestMatcher
+ */
+class XHProfRequestMatcher implements RequestMatcherInterface {
+
+  /**
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  private $configFactory;
+
+  /**
+   * @var \Drupal\Core\Path\PathMatcherInterface
+   */
+  private $pathMatcher;
+
+  /**
+   * @param ConfigFactoryInterface $configFactory
+   * @param \Drupal\Core\Path\PathMatcherInterface $pathMatcher
+   */
+  public function __construct(ConfigFactoryInterface $configFactory, PathMatcherInterface $pathMatcher) {
+    $this->configFactory = $configFactory;
+    $this->pathMatcher = $pathMatcher;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function matches(Request $request) {
+    $path = $request->getPathInfo();
+
+    $patterns = $this->configFactory->get('xhprof.config')->get('exclude');
+
+    // never collect phpinfo page.
+    $patterns .= "\r\n/admin/reports/status/php";
+
+    return !$this->pathMatcher->matchPath($path, $patterns);
+  }
+}
diff --git a/src/Routing/RunConverter.php b/src/Routing/RunConverter.php
new file mode 100644
index 0000000..6d877d0
--- /dev/null
+++ b/src/Routing/RunConverter.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Drupal\xhprof\Routing;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\ParamConverter\ParamConverterInterface;
+use Drupal\xhprof\XHProfLib\XHProf;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+
+class RunConverter implements ParamConverterInterface {
+
+  /**
+   * @var \Drupal\xhprof\XHProfLib\XHProf
+   */
+  private $xhprof;
+
+  /**
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  private $configFactory;
+
+  /**
+   * @param \Drupal\xhprof\XHProfLib\XHProf $xhprof
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   */
+  public function __construct(XHProf $xhprof, ConfigFactoryInterface $config_factory) {
+    $this->xhprof = $xhprof;
+    $this->configFactory = $config_factory;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function convert($value, $definition, $name, array $defaults, Request $request) {
+    try {
+      $namespace = $this->configFactory->get('system.site')->get('name');
+      return $this->xhprof->getStorage()->getRun($value, $namespace);
+    } catch(\Exception $e) {
+      return NULL;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function applies($definition, $name, Route $route) {
+    if (!empty($definition['type']) && $definition['type'] === 'xhprof:run_id') {
+      return TRUE;
+    }
+    return FALSE;
+  }
+}
diff --git a/src/XHProfLib/Aggregator.php b/src/XHProfLib/Aggregator.php
new file mode 100755
index 0000000..0facddc
--- /dev/null
+++ b/src/XHProfLib/Aggregator.php
@@ -0,0 +1,129 @@
+<?php
+
+namespace Drupal\xhprof\XHProfLib;
+
+use Drupal\xhprof\XHProfLib\Storage\FileStorage;
+
+class Aggregator {
+
+  /**
+   * @var array
+   */
+  public $runs = array();
+
+  /**
+   * An instance of a class that implements XHProfRunsInterface.
+   */
+  protected $xhprof_runs_class;
+
+  public function __construct() {
+    $this->xhprof_runs = new FileStorage();
+  }
+
+  /**
+   * @param $run_id
+   * @param $namespace
+   *
+   * @return void
+   */
+  public function addRun($run_id, $namespace) {
+    $this->runs[] = array('run_id' => $run_id, 'namespace' => $namespace);
+  }
+
+  /**
+   * @return array
+   */
+  public function average() {
+    $keys = array();
+    foreach ($this->runs as $data) {
+      $keys = $keys + array_keys($data);
+    }
+    $agg_run = array();
+    $run_count = count($this->runs);
+    foreach ($keys as $key) {
+      $agg_key = array();
+      // Check which runs have this parent_child function key, collect metrics if so.
+      foreach ($this->runs as $data) {
+        if (isset($data[$key])) {
+          foreach ($data[$key] as $metric => $val) {
+            $agg_key[$metric][] = $val;
+          }
+        }
+      }
+
+      // Average each metric for the key into the aggregated run.
+      $agg_run[$key] = array();
+      foreach ($agg_key as $metric => $vals) {
+        $sd = self::sd($vals);
+        $mean = (array_sum($vals) / count($vals));
+        $good_vals = array();
+
+        if ($sd == 0) {
+          $agg_run[$key][$metric] = (array_sum($vals) / count($vals));
+        }
+        else {
+          foreach ($vals as $v) {
+            $diff = abs($mean - $v);
+            if (abs($mean - $v) < ($sd * 2)) {
+              $good_vals[] = $v;
+            }
+          }
+          $agg_run[$key][$metric] = (array_sum($good_vals) / count($good_vals));
+        }
+      }
+    }
+
+    return $agg_run;
+  }
+
+
+  /**
+   * @param bool $skip_bad_runs
+   *  Set to TRUE to prevent this method from failing in the case of a bad run
+   *  (i.e. a corrupt, unserializable file).
+   * @return array
+   */
+  public function sum($skip_bad_runs = FALSE) {
+    $keys = array();
+    $agg_run = array();
+    foreach ($this->runs as $run) {
+      try {
+        $run = $this->xhprof_runs->getRun($run['run_id'], $run['namespace']);
+      }
+      catch (\UnexpectedValueException $e) {
+        if ($skip_bad_runs) {
+          continue;
+        }
+        else {
+          throw $e;
+        }
+      }
+      $keys = $run->getKeys();
+
+      foreach ($keys as $key) {
+        foreach ($run->getMetrics($key) as $metric => $val) {
+          if (isset($agg_run[$key][$metric])) {
+            $agg_run[$key][$metric] += $val;
+          }
+          else {
+            $agg_run[$key][$metric] = $val;
+          }
+        }
+      }
+    }
+    return $agg_run;
+  }
+
+  public static function sd_square($x, $mean) {
+    return pow($x - $mean,2);
+  }
+
+  /**
+   * Function to calculate standard deviation (uses sd_square)
+   */
+  public static function sd($array) {
+    // square root of sum of squares devided by N-1
+    return sqrt(array_sum(array_map(array('XHProfTools', 'sd_square'), $array, array_fill(0, count($array), (array_sum($array) / count($array))))) / (count($array)-1));
+  }
+}
+
diff --git a/src/XHProfLib/Parser/DiffParser.php b/src/XHProfLib/Parser/DiffParser.php
new file mode 100755
index 0000000..6ff7245
--- /dev/null
+++ b/src/XHProfLib/Parser/DiffParser.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Drupal\xhprof\XHProfLib\Parser;
+
+/**
+ * Class DiffParser
+ */
+class DiffParser {
+
+  /**
+   * @var Parser
+   */
+  private $parser1;
+
+  /**
+   * @var Parser
+   */
+  private $parser2;
+
+  /**
+   * @param $data1
+   * @param $data2
+   */
+  public function __construct($data1, $data2) {
+    $this->parser1 = new Parser($data1);
+    $this->parser2 = new Parser($data2);
+  }
+
+  /**
+   * @return mixed
+   */
+  public function getDiffTotals() {
+    $diff_totals[0] = $this->parser1->getTotals();
+    $diff_totals[1] = $this->parser2->getTotals();
+    $diff_totals['diff'] = array();
+    $diff_totals['diff%'] = array();
+
+    foreach ($diff_totals[0] as $metric => $value) {
+      $diff_totals['diff'][$metric] = $diff_totals[1][$metric] - $value;
+      $diff_totals['diff%'][$metric] = (($diff_totals[1][$metric] / $value) - 1) * 100;
+    }
+
+    return $diff_totals;
+  }
+}
diff --git a/src/XHProfLib/Parser/Parser.php b/src/XHProfLib/Parser/Parser.php
new file mode 100755
index 0000000..d312119
--- /dev/null
+++ b/src/XHProfLib/Parser/Parser.php
@@ -0,0 +1,106 @@
+<?php
+
+namespace Drupal\xhprof\XHProfLib\Parser;
+
+use Drupal\xhprof\XHProfLib\Run;
+use Symfony\Component\DependencyInjection\SimpleXMLElement;
+
+class Parser {
+
+  /**
+   * @var \Drupal\xhprof\XHProfLib\Run
+   */
+  private $run;
+
+  /**
+   * @var array
+   */
+  private $totals = array();
+
+  /**
+   * @var array
+   */
+  private $symbol_totals = array();
+
+  /**
+   * @param \Drupal\xhprof\XHProfLib\Run $run
+   */
+  public function __construct(Run $run) {
+    $this->run = $run;
+    $this->getTotals();
+  }
+
+  /**
+   * @return array
+   */
+  public function getTotals() {
+    $this->totals = $this->run->getData()['main()'];
+    $this->totals['ct'] = $this->getCallCount();
+    return $this->totals;
+  }
+
+  /**
+   * @param $totals
+   *
+   * @return mixed
+   */
+  public function toXML($totals) {
+    $xml = new SimpleXMLElement('<xhprof_data/>');
+    array_walk_recursive(array_flip($totals), array($xml, 'addChild'));
+    return $xml->asXML();
+  }
+
+  /**
+   * @return int
+   */
+  public function getCallCount() {
+    $call_count = 0;
+    foreach ($this->run->getData() as $symbol) {
+      $call_count += $symbol['ct'];
+    }
+    return $call_count;
+  }
+
+  /**
+   * @param $symbol
+   *
+   * @return mixed
+   */
+  public function getMetrics($symbol) {
+    if (!isset($this->symbol_totals[$symbol])) {
+      $this->symbol_totals[$symbol] = array(
+        'ct' => 0,
+        'wt' => 0,
+        'cpu' => 0,
+        'mu' => 0,
+        'pmu' => 0,
+      );
+    }
+    foreach ($this->run->getData() as $key => $symbol_data) {
+      if ($key !== 'main()') {
+        list($caller, $cur_symbol) = explode('==>', $key);
+        if ($cur_symbol == $symbol) {
+          foreach ($symbol_data as $metric => $value) {
+            $this->symbol_totals[$symbol][$metric] += $value;
+          }
+          $this->symbol_totals[$symbol] = $this->calculatePercentages($this->symbol_totals[$symbol]);
+          return $this->symbol_totals[$symbol];
+        }
+      }
+    }
+  }
+
+  /**
+   * @param $symbol_metrics
+   *
+   * @return mixed
+   */
+  protected function calculatePercentages($symbol_metrics) {
+    foreach ($symbol_metrics as $metric => $value) {
+      if ($this->totals[$metric] !== 0) {
+        $symbol_metrics[$metric . '%'] = round(100 * ($value / $this->totals[$metric]), 2);
+      }
+    }
+    return $symbol_metrics;
+  }
+}
diff --git a/src/XHProfLib/Report/BaseReport.php b/src/XHProfLib/Report/BaseReport.php
new file mode 100644
index 0000000..2e88060
--- /dev/null
+++ b/src/XHProfLib/Report/BaseReport.php
@@ -0,0 +1,313 @@
+<?php
+
+namespace Drupal\xhprof\XHProfLib\Report;
+
+abstract class BaseReport implements ReportInterface {
+
+  protected $stats;
+  protected $pc_stats;
+  protected $metrics;
+  protected $diff_mode;
+  protected $sort_col;
+  protected $display_calls;
+  protected $totals;
+
+  protected $sortable_columns = array(
+    "fn" => 1,
+    "ct" => 1,
+    "wt" => 1,
+    "excl_wt" => 1,
+    "ut" => 1,
+    "excl_ut" => 1,
+    "st" => 1,
+    "excl_st" => 1,
+    "mu" => 1,
+    "excl_mu" => 1,
+    "pmu" => 1,
+    "excl_pmu" => 1,
+    "cpu" => 1,
+    "excl_cpu" => 1,
+    "samples" => 1,
+    "excl_samples" => 1
+  );
+
+  /**
+   * @param $xhprof_data
+   * @param $rep_symbol
+   * @param $sort
+   * @param bool $diff_report
+   */
+  protected function initMetrics($xhprof_data, $rep_symbol, $sort, $diff_report = FALSE) {
+    $this->diff_mode = $diff_report;
+
+    if (!empty($sort)) {
+      if (array_key_exists($sort, $this->sortable_columns)) {
+        $this->sort_col = $sort;
+      }
+      else {
+        print("Invalid Sort Key $sort specified in URL");
+      }
+    }
+
+    // For C++ profiler runs, walltime attribute isn't present.
+    // In that case, use "samples" as the default sort column.
+    if (!isset($xhprof_data["main()"]["wt"])) {
+
+      if ($this->sort_col == "wt") {
+        $this->sort_col = "samples";
+      }
+
+      // C++ profiler data doesn't have call counts.
+      // ideally we should check to see if "ct" metric
+      // is present for "main()". But currently "ct"
+      // metric is artificially set to 1. So, relying
+      // on absence of "wt" metric instead.
+      $this->display_calls = FALSE;
+    }
+    else {
+      $this->display_calls = TRUE;
+    }
+
+    // parent/child report doesn't support exclusive times yet.
+    // So, change sort hyperlinks to closest fit.
+    if (!empty($rep_symbol)) {
+      $this->sort_col = str_replace("excl_", "", $this->sort_col);
+    }
+
+    if ($this->display_calls) {
+      $this->stats = array("fn", "ct", "Calls%");
+    }
+    else {
+      $this->stats = array("fn");
+    }
+
+    $this->pc_stats = $this->stats;
+
+    $possible_metrics = $this->getPossibleMetrics($xhprof_data);
+    foreach ($possible_metrics as $metric => $desc) {
+      if (isset($xhprof_data["main()"][$metric])) {
+        $metrics[] = $metric;
+        // flat (top-level reports): we can compute
+        // exclusive metrics reports as well.
+        $this->stats[] = $metric;
+        $this->stats[] = "I" . $desc[0] . "%";
+        $this->stats[] = "excl_" . $metric;
+        $this->stats[] = "E" . $desc[0] . "%";
+
+        // parent/child report for a function: we can
+        // only breakdown inclusive times correctly.
+        $this->pc_stats[] = $metric;
+        $this->pc_stats[] = "I" . $desc[0] . "%";
+      }
+    }
+  }
+
+  /**
+   * @return array
+   */
+  protected function getPossibleMetrics() {
+    return array(
+      "wt" => array("Wall", "microsecs", "walltime"),
+      "ut" => array("User", "microsecs", "user cpu time"),
+      "st" => array("Sys", "microsecs", "system cpu time"),
+      "cpu" => array("Cpu", "microsecs", "cpu time"),
+      "mu" => array("MUse", "bytes", "memory usage"),
+      "pmu" => array("PMUse", "bytes", "peak memory usage"),
+      "samples" => array("Samples", "samples", "cpu time")
+    );
+  }
+
+  /**
+   * @param $xhprof_data
+   *
+   * @return array
+   */
+  protected function getMetrics($xhprof_data) {
+    // get list of valid metrics
+    $possible_metrics = $this->getPossibleMetrics();
+
+    // return those that are present in the raw data.
+    // We'll just look at the root of the subtree for this.
+    $metrics = array();
+    foreach ($possible_metrics as $metric => $desc) {
+      if (isset($xhprof_data["main()"][$metric])) {
+        $metrics[] = $metric;
+      }
+    }
+
+    return $metrics;
+  }
+
+  /**
+   * @param $raw_data
+   * @param $overall_totals
+   * @return array
+   */
+  protected function computeFlatInfo($raw_data, &$overall_totals) {
+    $metrics = $this->getMetrics($raw_data);
+    $overall_totals = array(
+      "ct" => 0,
+      "wt" => 0,
+      "ut" => 0,
+      "st" => 0,
+      "cpu" => 0,
+      "mu" => 0,
+      "pmu" => 0,
+      "samples" => 0
+    );
+
+    // Compute inclusive times for each function.
+    $symbol_tab = $this->computeInclusiveTimes($raw_data);
+
+    // Total metric value is the metric value for "main()".
+    foreach ($metrics as $metric) {
+      $overall_totals[$metric] = $symbol_tab["main()"][$metric];
+    }
+
+    // Initialize exclusive (self) metric value to inclusive metric value to start with.
+    // In the same pass, also add up the total number of function calls.
+    foreach ($symbol_tab as $symbol => $info) {
+      foreach ($metrics as $metric) {
+        $symbol_tab[$symbol]["excl_" . $metric] = $symbol_tab[$symbol][$metric];
+      }
+      // Keep track of total number of calls.
+      $overall_totals["ct"] += $info["ct"];
+    }
+
+    // Adjust exclusive times by deducting inclusive time of children.
+    foreach ($raw_data as $parent_child => $info) {
+      list($parent, $child) = $this->parseParentChild($parent_child);
+
+      if ($parent) {
+        foreach ($metrics as $metric) {
+          // make sure the parent exists hasn't been pruned.
+          if (isset($symbol_tab[$parent])) {
+            $symbol_tab[$parent]["excl_" . $metric] -= $info[$metric];
+          }
+        }
+      }
+    }
+
+    return $symbol_tab;
+  }
+
+  /**
+   * @param $parent_child
+   *
+   * @return array
+   */
+  protected function parseParentChild($parent_child) {
+    $ret = explode("==>", $parent_child);
+
+    // Return if both parent and child are set
+    if (isset($ret[1])) {
+      return $ret;
+    }
+
+    return array(NULL, $ret[0]);
+  }
+
+  /**
+   * @param $raw_data
+   *
+   * @return array
+   */
+  protected function computeInclusiveTimes($raw_data) {
+    $metrics = $this->getMetrics($raw_data);
+
+    $symbol_tab = array();
+
+    /*
+     * First compute inclusive time for each function and total
+     * call count for each function across all parents the
+     * function is called from.
+     */
+    foreach ($raw_data as $parent_child => $info) {
+      list($parent, $child) = $this->parseParentChild($parent_child);
+
+      // TODO: is this needed?
+      //if ($parent == $child) {
+      //  /*
+      //   * XHProf PHP extension should never trigger this situation any more.
+      //   * Recursion is handled in the XHProf PHP extension by giving nested
+      //   * calls a unique recursion-depth appended name (for example, foo@1).
+      //   */
+      //  watchdog("Error in Raw Data: parent & child are both: %parent", array('%parent' => $parent));
+      //  return;
+      //}
+
+      if (!isset($symbol_tab[$child])) {
+        $symbol_tab[$child] = array("ct" => $info["ct"]);
+        foreach ($metrics as $metric) {
+          $symbol_tab[$child][$metric] = $info[$metric];
+        }
+      }
+      else {
+        // increment call count for this child
+        $symbol_tab[$child]["ct"] += $info["ct"];
+
+        // update inclusive times/metric for this child
+        foreach ($metrics as $metric) {
+          $symbol_tab[$child][$metric] += $info[$metric];
+        }
+      }
+    }
+
+    return $symbol_tab;
+  }
+
+  /**
+   * @param $raw_data
+   * @param $functions_to_keep
+   * @return array
+   */
+  function trimRun($raw_data, $functions_to_keep) {
+
+    // convert list of functions to a hash with function as the key
+    $function_map = array_fill_keys($functions_to_keep, 1);
+
+    // always keep main() as well so that overall totals can still
+    // be computed if need be.
+    $function_map['main()'] = 1;
+
+    $new_raw_data = array();
+    foreach ($raw_data as $parent_child => $info) {
+      list($parent, $child) = $this->parseParentChild($parent_child);
+
+      if (isset($function_map[$parent]) || isset($function_map[$child])) {
+        $new_raw_data[$parent_child] = $info;
+      }
+    }
+
+    return $new_raw_data;
+  }
+
+  /**
+   * @param $arr
+   * @param $k
+   * @param $v
+   * @return mixed
+   */
+  function arraySet($arr, $k, $v) {
+    $arr[$k] = $v;
+    return $arr;
+  }
+
+  /**
+   * @param $arr
+   * @param $k
+   * @return mixed
+   */
+  function arrayUnset($arr, $k) {
+    unset($arr[$k]);
+    return $arr;
+  }
+
+  /**
+   * @return mixed
+   */
+  public function getTotals() {
+    return $this->totals;
+  }
+
+}
diff --git a/src/XHProfLib/Report/DiffReport.php b/src/XHProfLib/Report/DiffReport.php
new file mode 100644
index 0000000..9cb7a08
--- /dev/null
+++ b/src/XHProfLib/Report/DiffReport.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Drupal\xhprof\XHProfLib\Report;
+
+class DiffReport {
+
+}
diff --git a/src/XHProfLib/Report/Report.php b/src/XHProfLib/Report/Report.php
new file mode 100644
index 0000000..b0b03ca
--- /dev/null
+++ b/src/XHProfLib/Report/Report.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Drupal\xhprof\XHProfLib\Report;
+
+use Drupal\xhprof\XHProfLib\Run;
+
+class Report extends BaseReport {
+
+  private $sort;
+  private $run;
+  private $symbol;
+  private $symbol_tab;
+
+  /**
+   * @param $run
+   * @param $sort
+   */
+  public function __construct(Run $run, $sort, $symbol) {
+    $this->sort = $sort;
+    $this->run = $run;
+    $this->symbol = $symbol;
+
+    $this->initMetrics($run->getData(), NULL, $sort);
+    $this->profilerReport($run, $sort);
+  }
+
+  /**
+   * @param $run
+   * @param $sort
+   */
+  public function profilerReport(Run $run, $sort) {
+    if (!empty($this->symbol)) {
+      $data = $this->trimRun($run->getData(), $this->symbol);
+    }
+    else {
+      $data = $run->getData();
+    }
+
+    $this->symbol_tab = $this->computeFlatInfo($data, $this->totals);
+
+    Sorter::sort($this->symbol_tab, $sort);
+    $this->symbol_tab = array_slice($this->symbol_tab, 0, 100);
+
+  }
+
+  /**
+   * @return array
+   */
+  public function getData() {
+    return array(
+      'symbols' => $this->symbol_tab,
+      'totals' => $this->totals,
+      'possible_metrics' => $this->getPossibleMetrics(),
+      'metrics' => $this->metrics,
+      'display_calls' => $this->display_calls,
+    );
+  }
+
+}
diff --git a/src/XHProfLib/Report/ReportEngine.php b/src/XHProfLib/Report/ReportEngine.php
new file mode 100644
index 0000000..eb0311e
--- /dev/null
+++ b/src/XHProfLib/Report/ReportEngine.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\xhprof\XHProfLib\Report;
+
+use Drupal\xhprof\XHProfLib\Run;
+
+/**
+ * Class ReportEngine
+ */
+class ReportEngine {
+
+  /**
+   * @param $url_params
+   * @param $source
+   * @param Run $run
+   * @param $wts
+   * @param $symbol
+   * @param $sort
+   * @param Run $run1
+   * @param Run $run2
+   *
+   * @return ReportInterface
+   */
+  public function getReport($url_params, $source, Run $run, $wts, $symbol, $sort = 'wt', Run $run1 = NULL, Run $run2 = NULL) {
+    $report = NULL;
+
+    // specific run to display?
+    if ($run) {
+      $report = new Report($run, $sort, $symbol);
+    }
+    // diff report for two runs
+    else {
+      if ($run1 && $run2) {
+        $report = new DiffReport($url_params, $run1->getData(), '', $run2->getData(), '', $symbol, $sort, $run1, $run2);
+      }
+    }
+
+    return $report;
+  }
+}
diff --git a/src/XHProfLib/Report/ReportInterface.php b/src/XHProfLib/Report/ReportInterface.php
new file mode 100644
index 0000000..8acfb2f
--- /dev/null
+++ b/src/XHProfLib/Report/ReportInterface.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Drupal\xhprof\XHProfLib\Report;
+
+interface ReportInterface {
+
+  public function getData();
+
+  public function getTotals();
+}
diff --git a/src/XHProfLib/Report/Sorter.php b/src/XHProfLib/Report/Sorter.php
new file mode 100644
index 0000000..0744a3b
--- /dev/null
+++ b/src/XHProfLib/Report/Sorter.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Drupal\xhprof\XHProfLib\Report;
+
+class Sorter {
+
+  private static $metric;
+
+  static function sort(&$symbols, $metric) {
+    self::$metric = $metric;
+    uasort($symbols, array("Drupal\\xhprof\\XHProfLib\\Report\\Sorter", "cmp_method"));
+  }
+
+  static function cmp_method($a, $b) {
+    $metric = self::$metric;
+
+    if ($metric == "fn") {
+
+      // case insensitive ascending sort for function names
+      $left = strtoupper($a["fn"]);
+      $right = strtoupper($b["fn"]);
+
+      if ($left == $right) {
+        return 0;
+      }
+
+      return ($left < $right) ? -1 : 1;
+    }
+    else {
+      // descending sort for all others
+      $left = $a[$metric];
+      $right = $b[$metric];
+
+      if ($left == $right) {
+        return 0;
+      }
+      return ($left > $right) ? -1 : 1;
+    }
+  }
+
+}
diff --git a/src/XHProfLib/Run.php b/src/XHProfLib/Run.php
new file mode 100755
index 0000000..ab12d32
--- /dev/null
+++ b/src/XHProfLib/Run.php
@@ -0,0 +1,74 @@
+<?php
+
+namespace Drupal\xhprof\XHProfLib;
+
+/**
+ * Class Run
+ */
+class Run {
+
+  /**
+   * @var string
+   */
+  private $run_id;
+
+  /**
+   * @var string
+   */
+  private $namespace;
+
+  /**
+   * @var array
+   */
+  private $data = array();
+
+  /**
+   * @var
+   */
+  private $parser;
+
+  /**
+   * @param string $run_id
+   * @param string $namespace
+   * @param array $data
+   */
+  public function __construct($run_id, $namespace, $data) {
+    $this->run_id = $run_id;
+    $this->namespace = $namespace;
+    $this->data = $data;
+  }
+
+  /**+
+   * @return array
+   */
+  public function getKeys() {
+    return array_keys($this->data);
+  }
+
+  /**
+   * @param string $key
+   *
+   * @return array
+   */
+  public function getMetrics($key) {
+    return $this->data[$key];
+  }
+
+  /**
+   * @return array
+   */
+  public function getData() {
+    return $this->data;
+  }
+
+  /**
+   * @return string
+   */
+  public function getId() {
+    return $this->run_id;
+  }
+
+  public function __toString() {
+    return 'pippo';
+  }
+}
diff --git a/src/XHProfLib/Storage/FileStorage.php b/src/XHProfLib/Storage/FileStorage.php
new file mode 100755
index 0000000..e7c6638
--- /dev/null
+++ b/src/XHProfLib/Storage/FileStorage.php
@@ -0,0 +1,131 @@
+<?php
+
+namespace Drupal\xhprof\XHProfLib\Storage;
+
+use Drupal\xhprof\XHProfLib\Run;
+
+class FileStorage implements StorageInterface {
+
+  /**
+   * @var string
+   */
+  private $dir;
+
+  /**
+   * @var string
+   */
+  private $suffix;
+
+  /**
+   * @param string $dir
+   */
+  public function __construct($dir = NULL) {
+    if ($dir) {
+      $this->dir = $dir;
+    }
+    else {
+      $this->dir = ini_get("xhprof.output_dir") ?: sys_get_temp_dir();
+    }
+    $this->suffix = 'xhprof';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getName() {
+    return 'File Storage';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRun($run_id, $namespace) {
+    $file_name = $this->fileName($run_id, $namespace);
+
+    if (!file_exists($file_name)) {
+      throw new \RuntimeException("Could not find file $file_name");
+    }
+
+    $serialized_contents = file_get_contents($file_name);
+    $contents = @unserialize($serialized_contents);
+
+    if ($contents === FALSE) {
+      throw new \UnexpectedValueException("Unable to unserialize $file_name!");
+    }
+
+    $run = new Run($run_id, $namespace, $contents);
+    return $run;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRuns($namespace = NULL) {
+    $files = $this->scanXHProfDir($this->dir, $namespace);
+    $files = array_map(function ($f) {
+      $f['date'] = strtotime($f['date']);
+      return $f;
+    }, $files);
+    return $files;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function saveRun($data, $namespace, $run_id) {
+    // Use PHP serialize function to store the XHProf's
+    // raw profiler data.
+    $data = serialize($data);
+
+    $file_name = $this->fileName($run_id, $namespace);
+    $file = fopen($file_name, 'w');
+
+    if ($file) {
+      fwrite($file, $data);
+      fclose($file);
+    }
+    else {
+      throw new \Exception("Could not open $file_name\n");
+    }
+
+    return $run_id;
+  }
+
+  /**
+   * @param string $dir
+   * @param string $namespace
+   *
+   * @return array
+   */
+  private function scanXHProfDir($dir, $namespace = NULL) {
+    $runs = array();
+    if (is_dir($dir)) {
+      foreach (glob("{$this->dir}/*.{$this->suffix}") as $file) {
+        preg_match("/(?:(?<run>\w+)\.)(?:(?<namespace>[^.]+)\.)(?<ext>[\w.]+)/", basename($file), $matches);
+        $runs[] = array(
+          'run_id' => $matches['run'],
+          'namespace' => $matches['namespace'],
+          'basename' => htmlentities(basename($file)),
+          'date' => date("Y-m-d H:i:s", filemtime($file)),
+        );
+      }
+    }
+    return array_reverse($runs);
+  }
+
+  /**
+   * @param string $run_id
+   * @param string $namespace
+   *
+   * @return string
+   */
+  private function fileName($run_id, $namespace) {
+    $file = implode('.', array($run_id, $namespace, $this->suffix));
+
+    if (!empty($this->dir)) {
+      $file = $this->dir . "/" . $file;
+    }
+    return $file;
+  }
+}
+
diff --git a/src/XHProfLib/Storage/StorageFactory.php b/src/XHProfLib/Storage/StorageFactory.php
new file mode 100644
index 0000000..fb0d46a
--- /dev/null
+++ b/src/XHProfLib/Storage/StorageFactory.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Drupal\xhprof\XHProfLib\Storage;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface;
+
+/**
+ * Class StorageFactory
+ */
+class StorageFactory {
+
+  /**
+   *
+   */
+  public function __construct() {
+    $this->storages = array();
+  }
+
+  /**
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config
+   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
+   *
+   * @return \Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface
+   */
+  final public static function getStorage(ConfigFactoryInterface $config, ContainerInterface $container) {
+    $storage = $config->get('xhprof.config')->get('storage') ? : 'xhprof.file_storage';
+
+    return $container->get($storage);
+  }
+
+}
diff --git a/src/XHProfLib/Storage/StorageInterface.php b/src/XHProfLib/Storage/StorageInterface.php
new file mode 100755
index 0000000..ecb0c15
--- /dev/null
+++ b/src/XHProfLib/Storage/StorageInterface.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Drupal\xhprof\XHProfLib\Storage;
+
+/**
+ * Interface StorageInterface
+ */
+interface StorageInterface {
+
+  /**
+   * @return mixed
+   */
+  public function getRuns();
+
+  /**
+   * @param string $run_id
+   * @param string $namespace
+   *
+   * @return array
+   */
+  public function getRun($run_id, $namespace);
+
+  /**
+   * @param array $data
+   * @param string $namespace
+   * @param string $run_id
+   *
+   * @return string
+   */
+  public function saveRun($data, $namespace, $run_id);
+
+  /**
+   * @return string
+   */
+  public function getName();
+}
diff --git a/src/XHProfLib/Storage/StorageManager.php b/src/XHProfLib/Storage/StorageManager.php
new file mode 100644
index 0000000..fb03f63
--- /dev/null
+++ b/src/XHProfLib/Storage/StorageManager.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Drupal\xhprof\XHProfLib\Storage;
+
+/**
+ * Class StorageManager
+ */
+class StorageManager {
+
+  /**
+   * @var array
+   */
+  private $storages;
+
+  /**
+   * @return array
+   */
+  public function getStorages() {
+    $output = array();
+
+    /** @var \Drupal\xhprof\XHProfLib\Storage\StorageInterface $storage */
+    foreach ($this->storages as $id => $storage) {
+      $output[$id] = $storage->getName();
+    }
+
+    return $output;
+  }
+
+  /**
+   * @param \Drupal\xhprof\XHProfLib\Storage\StorageInterface $storage
+   */
+  public function addStorage($id, StorageInterface $storage) {
+    $this->storages[$id] = $storage;
+  }
+
+}
diff --git a/src/XHProfLib/XHProf.php b/src/XHProfLib/XHProf.php
new file mode 100644
index 0000000..9fd5ec2
--- /dev/null
+++ b/src/XHProfLib/XHProf.php
@@ -0,0 +1,139 @@
+<?php
+
+namespace Drupal\xhprof\XHProfLib;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\xhprof\XHProfLib\Storage\StorageInterface;
+use Drupal\xhprof\XHProfLib\Report\ReportEngine;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestMatcherInterface;
+
+class XHProf {
+
+  /**
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  private $configFactory;
+
+  /**
+   * @var StorageInterface
+   */
+  private $storage;
+
+  /**
+   * @var \Symfony\Component\HttpFoundation\RequestMatcherInterface
+   */
+  private $requestMatcher;
+
+  /**
+   * @var string
+   */
+  private $runId;
+
+  /**
+   * @var bool
+   */
+  private $enabled = FALSE;
+
+  /**
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
+   * @param \Drupal\xhprof\XHProfLib\Storage\StorageInterface $storage
+   * @param \Symfony\Component\HttpFoundation\RequestMatcherInterface $requestMatcher
+   */
+  public function __construct(ConfigFactoryInterface $configFactory, StorageInterface $storage, RequestMatcherInterface $requestMatcher) {
+    $this->configFactory = $configFactory;
+    $this->storage = $storage;
+    $this->requestMatcher = $requestMatcher;
+  }
+
+  /**
+   * Conditionally enable XHProf profiling.
+   */
+  public function enable() {
+    // @todo: consider a variable per-flag instead.
+    xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY);
+
+    $this->enabled = TRUE;
+  }
+
+  /**
+   * @return array
+   */
+  public function shutdown($runId) {
+    $namespace = $this->configFactory->get('system.site')->get('name');
+    $xhprof_data = xhprof_disable();
+
+    $this->enabled = TRUE;
+
+    return $this->storage->saveRun($xhprof_data, $namespace, $runId);
+  }
+
+  /**
+   * Check whether XHProf is enabled.
+   *
+   * @return boolean
+   */
+  public function isEnabled() {
+    return $this->enabled;
+  }
+
+  /**
+   * @param Request $request
+   *
+   * @return bool
+   */
+  public function canEnable(Request $request) {
+    $config = $this->configFactory->get('xhprof.config');
+
+    if (extension_loaded('xhprof') && $config->get('enabled') && $this->requestMatcher->matches($request)) {
+      $interval = $config->get('interval');
+
+      if ($interval && mt_rand(1, $interval) % $interval != 0) {
+        return FALSE;
+      }
+
+      return TRUE;
+    }
+
+    return FALSE;
+  }
+
+  /**
+   * @param string $run_id
+   * @param string $type
+   *
+   * @return string
+   */
+  public function link($run_id, $type = 'link') {
+    $url = url(XHPROF_PATH . '/' . $run_id, array(
+      'absolute' => TRUE,
+    ));
+    return $type == 'url' ? $url : l(t('XHProf output'), $url);
+  }
+
+  /**
+   * @return \Drupal\xhprof\XHProfLib\Storage\StorageInterface
+   */
+  public function getStorage() {
+    return $this->storage;
+  }
+
+  /**
+   * @return string
+   */
+  public function getRunId() {
+    return $this->runId;
+  }
+
+  /**
+   * @return string
+   */
+  public function createRunId() {
+    if (!$this->runId) {
+      $this->runId = uniqid();
+    }
+
+    return $this->runId;
+  }
+
+}
diff --git a/src/XhprofServiceProvider.php b/src/XhprofServiceProvider.php
new file mode 100644
index 0000000..4d293c0
--- /dev/null
+++ b/src/XhprofServiceProvider.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Drupal\xhprof;
+
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\DependencyInjection\ServiceProviderBase;
+use Drupal\xhprof\Compiler\StoragePass;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * Defines a service profiler for the xhprof module.
+ */
+class XhprofServiceProvider extends ServiceProviderBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function register(ContainerBuilder $container) {
+    $container->addCompilerPass(new StoragePass());
+
+    if (FALSE !== $container->hasDefinition('profiler')) {
+      $container->register('webprofiler.xhprof', 'Drupal\xhprof\DataCollector\XHProfDataCollector')
+        ->addArgument(new Reference(('xhprof.xhprof')))
+        ->addTag('data_collector', array(
+          'template' => '@xhprof/Collector/xhprof.html.twig',
+          'id' => 'xhprof',
+          'title' => 'XHProf',
+          'priority' => 50
+        ));
+    }
+  }
+
+}
diff --git a/templates/Collector/xhprof.html.twig b/templates/Collector/xhprof.html.twig
new file mode 100644
index 0000000..af426bd
--- /dev/null
+++ b/templates/Collector/xhprof.html.twig
@@ -0,0 +1,31 @@
+{% block toolbar %}
+    {% set icon %}
+
+    {% if collector.runId %}
+        <a href="{{ url("xhprof.view", {run: collector.runId}) }}" title="{{ 'XHProf'|t }}">
+            <img width="20" height="28" alt="Asset"
+                 src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAcCAYAAAB2+A+pAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQ5JREFUeNrsVtENgjAQLcYFWKErlAH4gBFkBB1BRpARYAQZQUaQEWQEGQHvknfJWSUhMZGQtMmj5ejdu747CNE0TWaNsTMrjUAciAPx9on3fEnT9F98F8KZUMmJb4Qaa0vg72iG2SlHuX9izXgQDguJmTQhlEJ8IhwRlLNqCR2exZ5zDOSEiLMnXBeQZph7XeNBBciQyNLBSYw4/R0xLNaiiMNpRTWrm6uBQ4tARpVBZDVf7CxzARurVIK8hSIN7ivsYduw8/TvIblVdpE08ojFnqiydFDPiaSY7dzr5ECYI9P6x+4dVFPqJD6IRYoR9c1Up46ezzhj17YC/roUb/uj8AcSiANxIN4c8UuAAQBgXEUcKgS1eAAAAABJRU5ErkJggg=="/>
+        </a>
+    {% else %}
+        <span>
+        <img width="20" height="28" alt="Asset"
+             src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAcCAYAAAB2+A+pAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAflJREFUeNrsVs9LAlEQfqtbCwZWRMcN3aJDEAR5CAuUflyKulXXTkV/QUEQHYKwIohuEkGXID0FEYQiSXQJ9iJBddCoDoEKQmTEhto3MhtbVKcggh34dpg38+abmfeeKFUqFfEX4hB/JDaxTWwT/39imT7BYLBqnCSTv52/CxgFxgBfMBAIQc8BIZkDYkAGmIFTg04DQ7zuQ0F6tcBAgH5ffbzeyHtp3zxiotAtwAgT9QO1liKyTEr7dZN4hsiQOMzOKBLFYQsLgSmNjCHEnO+r6mqDYURg54BmS1wZSAAHwBFyebhg/X3UXDWNIcJJW7+b3Ww63SnR/B4edqDUyft709V853I9X7jdt65SaWGpo2OBuybCM26IhKbWKltymt2G0UnBXPQWi7Gwpgl/Pm/egQ12qTlFEan6+tyL07m52da2bDgcW5wnwlOjc51jm6Y6CEjWjgWT6k2GMZ2pq7vRisWe49NToZTL7wHPTqd4kuWzHY+nN68oE+upVHQgm6361trbl6HiIMuArJvOnbfRaLUvbzWEAqcTyeQdv68V+hDpY03Nhfv1dRfm4XBf3yX0Ep9V4YfbnOGccdb6d++YRhGC0VKSpOs9VRXbXu8iXa4xv38KvnXgimMLn7RVzLUJYJxfwTjbH+Il+x+ITWwT28T/jvhNgAEAnTCjXitoJO0AAAAASUVORK5CYII="/>
+        </span>
+    {% endif %}
+
+    {% endset %}
+    {% set text %}
+    <div class="sf-toolbar-info-piece">
+        <b>{{ 'Run id'|t }}</b>
+        <span>{{ collector.runId }}</span>
+    </div>
+    {% endset %}
+
+    <div class="sf-toolbar-block">
+        <div class="sf-toolbar-icon">{{ icon|default('') }}</div>
+
+        {% if collector.runId %}
+            <div class="sf-toolbar-info">{{ text|default('') }}</div>
+        {% endif %}
+    </div>
+{% endblock %}
diff --git a/xhprof.admin.inc b/xhprof.admin.inc
deleted file mode 100755
index b78b242..0000000
--- a/xhprof.admin.inc
+++ /dev/null
@@ -1,58 +0,0 @@
-<?php
-
-/**
- * @file
- * Admin page callbacks for the XHProf module.
- */
-
-/**
- * Administrative settings form for XHProf module.
- */
-function xhprof_admin_settings() {
-  $description = extension_loaded('xhprof') ? t('Profile requests with the xhprof php extension.') : '<span class="warning">' . t('You must enable the <a href="!url">xhprof php extension</a> to use this feature.', array('!url' => url('http://techportal.ibuildings.com/2009/12/01/profiling-with-xhprof/'))) . '</span>';
-  $form['xhprof_enabled'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Enable profiling of page views and <a href="!drush">drush</a> requests.', array('!drush' => url('http://drush.ws'))),
-    '#default_value' => variable_get('xhprof_enabled', FALSE),
-    '#description' => $description,
-    '#disabled' => !extension_loaded('xhprof'),
-  );
-  $form['settings'] = array(
-    '#title' => t('Profiling settings'),
-    '#type' => 'fieldset',
-    '#states' => array(
-      'invisible' => array(
-        'input[name="xhprof_enabled"]' => array('checked' => FALSE),
-      ),
-    ),
-  );
-  $form['settings']['xhprof_disable_admin_paths'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Disable profiling of admin pages'),
-    '#default_value' => variable_get('xhprof_disable_admin_paths', TRUE),
-  );
-  $form['settings']['xhprof_interval'] = array(
-    '#type' => 'textfield',
-    '#title' => 'Profiling interval',
-    '#default_value' => variable_get('xhprof_interval', ''),
-    '#description' => t('The approximate number of requests between XHProf samples. Leave empty to profile all requests'),
-  );
-
-  $options = drupal_map_assoc(xhprof_get_classes());
-  $form['settings']['xhprof_default_class'] = array(
-    '#type' => 'radios',
-    '#title' => t('XHProf storage'),
-    '#default_value' => variable_get('xhprof_default_class', 'Drupal\xhprof\XHProfRunsFile'),
-    '#options' => $options,
-    '#description' => t('Choose an XHProf runs class.'),
-  );
-
-  return system_settings_form($form);
-}
-
-function xhprof_admin_settings_validate($form, &$form_state) {
-  // TODO: Simplify this.
-  if (isset($form_state['values']['xhprof_interval']) && $form_state['values']['xhprof_interval'] != '' && (!is_numeric($form_state['values']['xhprof_interval']) || $form_state['values']['xhprof_interval'] <= 0 || $form_state['values']['xhprof_interval'] > mt_getrandmax())) {
-    form_set_error('xhprof_interval', 'The profiling interval must be set to a positive integer.');
-  }
-}
diff --git a/xhprof.css b/xhprof.css
deleted file mode 100644
index 5120b6c..0000000
--- a/xhprof.css
+++ /dev/null
@@ -1,27 +0,0 @@
-/* diff reports: display regressions in red */
-.vrbar {
-  text-align: right;
-  color:red;
-}
-
-/* diff reports: display improvements in green */
-.vgbar {
-  text-align: right;
-  color:green;
-}
-
-td.vwbar, th.vwbar {
-  text-align: right;
-}
-
-td.vwlbar, th.vwlbar {
-  text-align: left;
-}
-
-p.blue  {
-  color:blue
-}
-
-.xhprof_micro, .xhprof_percent {
-  text-align: right;
-}
diff --git a/xhprof.drush.inc b/xhprof.drush.inc
index aa97bf8..255df3b 100644
--- a/xhprof.drush.inc
+++ b/xhprof.drush.inc
@@ -10,18 +10,15 @@
 function xhprof_drush_command() {
   $items['xhprof-list'] = array(
     'description' => dt(''),
-    'arguments' => array(
-    ),
+    'arguments' => array(),
   );
   $items['xhprof-combine'] = array(
     'description' => dt(''),
-    'arguments' => array(
-    ),
+    'arguments' => array(),
   );
   $items['xhprof-clear'] = array(
     'description' => dt(''),
-    'arguments' => array(
-    ),
+    'arguments' => array(),
   );
   return $items;
 }
@@ -48,7 +45,9 @@ function drush_xhprof_combine() {
       else {
         // TODO: This doesn't work. Probably going to have to use drush_choice_multiple.
         $ids = explode(",", $choice);
-        foreach ($ids as $id) $run_ids[] = $options[$id];
+        foreach ($ids as $id) {
+          $run_ids[] = $options[$id];
+        }
       }
     }
   }
@@ -109,7 +108,9 @@ function drush_xhprof_list() {
   foreach ($runs as $run) {
     $row = array();
     foreach ($run as $key => $value) {
-      if (!in_array($key, $headers)) $headers[] = $key;
+      if (!in_array($key, $headers)) {
+        $headers[] = $key;
+      }
 
       $row[] = $value;
     }
@@ -142,9 +143,12 @@ function drush_xhprof_get_dir() {
 function drush_xhprof_list_run_files() {
   $dir = drush_xhprof_get_dir();
   $files = array();
-  foreach (glob("{$dir}/*.xhprof") as $file) $files[] = $file;
+  foreach (glob("{$dir}/*.xhprof") as $file) {
+    $files[] = $file;
+  }
   return $files;
 }
+
 function drush_xhprof_get_runs() {
   $dir = drush_xhprof_get_dir();
   $run_ids = array();
diff --git a/xhprof.inc b/xhprof.inc
deleted file mode 100755
index 64d3647..0000000
--- a/xhprof.inc
+++ /dev/null
@@ -1,1719 +0,0 @@
-<?php
-/**
- * Type definitions for URL params
- */
-define('XHPROF_STRING_PARAM', 1);
-define('XHPROF_UINT_PARAM',   2);
-define('XHPROF_FLOAT_PARAM',  3);
-define('XHPROF_BOOL_PARAM',   4);
-// default column to sort on -- wall time
-$sort_col = "wt";
-
-// default is "single run" report
-$diff_mode = FALSE;
-
-// call count data present?
-$display_calls = TRUE;
-
-function xhprof_sortable_columns() {
-// The following column headers are sortable
-  return array(
-    "fn" => 1,
-    "ct" => 1,
-    "wt" => 1,
-    "excl_wt" => 1,
-    "ut" => 1,
-    "excl_ut" => 1,
-    "st" => 1,
-    "excl_st" => 1,
-    "mu" => 1,
-    "excl_mu" => 1,
-    "pmu" => 1,
-    "excl_pmu" => 1,
-    "cpu" => 1,
-    "excl_cpu" => 1,
-    "samples" => 1,
-    "excl_samples" => 1
-  );
-}
-
-// Textual descriptions for column headers in "single run" mode
-$descriptions = array(
-  "fn" => "function xhprof_Name",
-  "ct" =>  "Calls",
-  "Calls%" => "Calls%",
-
-  "wt" => "Incl. Wall Time<br>(microsec)",
-  "IWall%" => "IWall%",
-  "excl_wt" => "Excl. Wall Time<br>(microsec)",
-  "EWall%" => "EWall%",
-
-  "ut" => "Incl. User<br>(microsecs)",
-  "IUser%" => "IUser%",
-  "excl_ut" => "Excl. User<br>(microsec)",
-  "EUser%" => "EUser%",
-
-  "st" => "Incl. Sys <br>(microsec)",
-  "ISys%" => "ISys%",
-  "excl_st" => "Excl. Sys <br>(microsec)",
-  "ESys%" => "ESys%",
-
-  "cpu" => "Incl. CPU<br>(microsecs)",
-  "ICpu%" => "ICpu%",
-  "excl_cpu" => "Excl. CPU<br>(microsec)",
-  "ECpu%" => "ECPU%",
-
-  "mu" => "Incl.<br>MemUse<br>(bytes)",
-  "IMUse%" => "IMemUse%",
-  "excl_mu" => "Excl.<br>MemUse<br>(bytes)",
-  "EMUse%" => "EMemUse%",
-
-  "pmu" => "Incl.<br> PeakMemUse<br>(bytes)",
-  "IPMUse%" => "IPeakMemUse%",
-  "excl_pmu" => "Excl.<br>PeakMemUse<br>(bytes)",
-  "EPMUse%" => "EPeakMemUse%",
-
-  "samples" => "Incl. Samples",
-  "ISamples%" => "ISamples%",
-  "excl_samples" => "Excl. Samples",
-  "ESamples%" => "ESamples%",
-);
-
-// Formatting Callback Functions...
-$format_cbk = array(
-  "fn" => "",
-  "ct" => "xhprof_count_format",
-  "Calls%" => "xhprof_percent_format",
-
-  "wt" => "number_format",
-  "IWall%" => "xhprof_percent_format",
-  "excl_wt" => "number_format",
-  "EWall%" => "xhprof_percent_format",
-
-  "ut" => "number_format",
-  "IUser%" => "xhprof_percent_format",
-  "excl_ut" => "number_format",
-  "EUser%" => "xhprof_percent_format",
-
-  "st" => "number_format",
-  "ISys%" => "xhprof_percent_format",
-  "excl_st" => "number_format",
-  "ESys%" => "xhprof_percent_format",
-
-  "cpu" => "number_format",
-  "ICpu%" => "xhprof_percent_format",
-  "excl_cpu" => "number_format",
-  "ECpu%" => "xhprof_percent_format",
-
-  "mu" => "number_format",
-  "IMUse%" => "xhprof_percent_format",
-  "excl_mu" => "number_format",
-  "EMUse%" => "xhprof_percent_format",
-
-  "pmu" => "number_format",
-  "IPMUse%" => "xhprof_percent_format",
-  "excl_pmu" => "number_format",
-  "EPMUse%" => "xhprof_percent_format",
-
-  "samples" => "number_format",
-  "ISamples%" => "xhprof_percent_format",
-  "excl_samples" => "number_format",
-  "ESamples%" => "xhprof_percent_format",
-);
-
-
-// Textual descriptions for column headers in "diff" mode
-$diff_descriptions = array(
-  "fn" => "function xhprof_Name",
-  "ct" =>  "Calls Diff",
-  "Calls%" => "Calls<br>Diff%",
-
-  "wt" => "Incl. Wall<br>Diff<br>(microsec)",
-  "IWall%" => "IWall<br> Diff%",
-  "excl_wt" => "Excl. Wall<br>Diff<br>(microsec)",
-  "EWall%" => "EWall<br>Diff%",
-
-  "ut" => "Incl. User Diff<br>(microsec)",
-  "IUser%" => "IUser<br>Diff%",
-  "excl_ut" => "Excl. User<br>Diff<br>(microsec)",
-  "EUser%" => "EUser<br>Diff%",
-
-  "cpu" => "Incl. CPU Diff<br>(microsec)",
-  "ICpu%" => "ICpu<br>Diff%",
-  "excl_cpu" => "Excl. CPU<br>Diff<br>(microsec)",
-  "ECpu%" => "ECpu<br>Diff%",
-
-  "st" => "Incl. Sys Diff<br>(microsec)",
-  "ISys%" => "ISys<br>Diff%",
-  "excl_st" => "Excl. Sys Diff<br>(microsec)",
-  "ESys%" => "ESys<br>Diff%",
-
-  "mu" => "Incl.<br>MemUse<br>Diff<br>(bytes)",
-  "IMUse%" => "IMemUse<br>Diff%",
-  "excl_mu" => "Excl.<br>MemUse<br>Diff<br>(bytes)",
-  "EMUse%" => "EMemUse<br>Diff%",
-
-  "pmu" => "Incl.<br> PeakMemUse<br>Diff<br>(bytes)",
-  "IPMUse%" => "IPeakMemUse<br>Diff%",
-  "excl_pmu" => "Excl.<br>PeakMemUse<br>Diff<br>(bytes)",
-  "EPMUse%" => "EPeakMemUse<br>Diff%",
-
-  "samples" => "Incl. Samples Diff",
-  "ISamples%" => "ISamples Diff%",
-  "excl_samples" => "Excl. Samples Diff",
-  "ESamples%" => "ESamples Diff%",
-);
-
-// columns that'll be displayed in a top-level report
-$stats = array();
-
-// columns that'll be displayed in a function's parent/child report
-$pc_stats = array();
-
-// Various total counts
-$totals = 0;
-$totals_1 = 0;
-$totals_2 = 0;
-
-/*
- * The subset of $possible_metrics that is present in the raw profile data.
- */
-$metrics = NULL;
-
-
-//  Copyright (c) 2009 Facebook
-//
-//  Licensed under the Apache License, Version 2.0 (the "License");
-//  you may not use this file except in compliance with the License.
-//  You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-//  Unless required by applicable law or agreed to in writing, software
-//  distributed under the License is distributed on an "AS IS" BASIS,
-//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-//  See the License for the specific language governing permissions and
-//  limitations under the License.
-//
-
-/**
- * Our coding convention disallows relative paths in hrefs.
- * Get the base URL path from the SCRIPT_NAME.
- */
-$base_path = rtrim(dirname($_SERVER['SCRIPT_NAME']), "/");
-
-
-/**
- * Generate references to required stylesheets & javascript.
- *
- * If the calling script (such as index.php) resides in
- * a different location that than 'xhprof_html' directory the
- * caller must provide the URL path to 'xhprof_html' directory
- * so that the correct location of the style sheets/javascript
- * can be specified in the generated HTML.
- *
- */
-function xhprof_xhprof_include_js_css($ui_dir_url_path = NULL) {
-
-  if (empty($ui_dir_url_path)) {
-    $ui_dir_url_path = rtrim(dirname($_SERVER['SCRIPT_NAME']), "/");
-  }
-
-  // style sheets
-  echo "<link href='$ui_dir_url_path/css/xhprof.css' rel='stylesheet' ".
-    " type='text/css'></link>";
-  echo "<link href='$ui_dir_url_path/jquery/jquery.tooltip.css' ".
-    " rel='stylesheet' type='text/css'></link>";
-  echo "<link href='$ui_dir_url_path/jquery/jquery.autocomplete.css' ".
-    " rel='stylesheet' type='text/css'></link>";
-
-  // javascript
-  echo "<script src='$ui_dir_url_path/jquery/jquery-1.2.6.js'>".
-       "</script>";
-  echo "<script src='$ui_dir_url_path/jquery/jquery.tooltip.js'>".
-       "</script>";
-  echo "<script src='$ui_dir_url_path/jquery/jquery.autocomplete.js'>"
-       ."</script>";
-  echo "<script src='$ui_dir_url_path/js/xhprof_report.js'></script>";
-}
-
-
-/*
- * Formats call counts for XHProf reports.
- *
- * Description:
- * Call counts in single-run reports are integer values.
- * However, call counts for aggregated reports can be
- * fractional. This function xhprof_will print integer values
- * without decimal point, but with commas etc.
- *
- *   4000 ==> 4,000
- *
- * It'll round fractional values to decimal precision of 3
- *   4000.1212 ==> 4,000.121
- *   4000.0001 ==> 4,000
- *
- */
-function xhprof_count_format($num) {
-  $num = round($num, 3);
-  if (round($num) == $num) {
-    return number_format($num);
-  } else {
-    return number_format($num, 3);
-  }
-}
-
-function xhprof_percent_format($s, $precision = 1) {
-  return sprintf('%.'.$precision.'f%%', 100*$s);
-}
-
-/**
- * Implodes the text for a bunch of actions (such as links, forms,
- * into a HTML list and returns the text.
- */
-function xhprof_render_actions($actions) {
-  $out = array( );
-
-  if (count($actions)) {
-    $out[] = '<ul class="xhprof_actions">';
-    foreach ($actions as $action) {
-      $out[] = '<li>'.$action.'</li>';
-    }
-    $out[] = '</ul>';
-  }
-
-  return implode('', $out);
-}
-
-
-/**
- * @param html-str $content  the text/image/innerhtml/whatever for the link
- * @param raw-str  $href
- * @param raw-str  $class
- * @param raw-str  $id
- * @param raw-str  $title
- * @param raw-str  $target
- * @param raw-str  $onclick
- * @param raw-str  $style
- * @param raw-str  $access
- * @param raw-str  $onmouseover
- * @param raw-str  $onmouseout
- * @param raw-str  $onmousedown
- * @param raw-str  $dir
- * @param raw-str  $rel
- */
-function xhprof_xhprof_render_link($content, $href, $class='', $id='', $title='',
-                            $target='',
-                            $onclick='', $style='', $access='', $onmouseover='',
-                            $onmouseout='', $onmousedown='') {
-
-  if (!$content) {
-    return '';
-  }
-
-  if ($href) {
-    $link = '<a href="' . ($href) . '"';
-  } else {
-    $link = '<span';
-  }
-
-  if ($class) {
-    $link .= ' class="' . ($class) . '"';
-  }
-  if ($id) {
-    $link .= ' id="' . ($id) . '"';
-  }
-  if ($title) {
-    $link .= ' title="' . ($title) . '"';
-  }
-  if ($target) {
-    $link .= ' target="' . ($target) . '"';
-  }
-  if ($onclick && $href) {
-    $link .= ' onclick="' . ($onclick) . '"';
-  }
-  if ($style && $href) {
-    $link .= ' style="' . ($style) . '"';
-  }
-  if ($access && $href) {
-    $link .= ' accesskey="' . ($access) . '"';
-  }
-  if ($onmouseover) {
-    $link .= ' onmouseover="' . ($onmouseover) . '"';
-  }
-  if ($onmouseout) {
-    $link .= ' onmouseout="' . ($onmouseout) . '"';
-  }
-  if ($onmousedown) {
-    $link .= ' onmousedown="' . ($onmousedown) . '"';
-  }
-
-  $link .= '>';
-  $link .= $content;
-  if ($href) {
-    $link .= '</a>';
-  } else {
-    $link .= '</span>';
-  }
-
-  return $link;
-}
-
-
-/**
- * Callback comparison operator (passed to usort() for sorting array of
- * tuples) that compares array elements based on the sort column
- * specified in $sort_col (global parameter).
- *
- * @author Kannan
- */
-function xhprof_sort_cbk($a, $b) {
-  global $diff_mode;
-
-  $sort_col = isset($_GET['sort']) ? $_GET['sort'] : 'wt';
-
-  if ($sort_col == "fn") {
-    // case insensitive ascending sort for function xhprof_names
-    $left = strtoupper($a["fn"]);
-    $right = strtoupper($b["fn"]);
-
-    if ($left == $right) return 0;
-    return ($left < $right) ? -1 : 1;
-  }
-  else {
-    // descending sort for all others
-    $left = isset($a[$sort_col]) ? $a[$sort_col] : '';
-    $right = isset($b[$sort_col]) ? $b[$sort_col] : '';
-
-    // if diff mode, sort by absolute value of regression/improvement
-    if ($diff_mode) {
-      $left = abs($left);
-      $right = abs($right);
-    }
-
-    if ($left == $right) return 0;
-    return ($left > $right) ? -1 : 1;
-  }
-}
-
-/**
- * Initialize the metrics we'll display based on the information
- * in the raw data.
- *
- * @author Kannan
- */
-function xhprof_init_metrics($xhprof_data, $rep_symbol, $sort, $diff_report = FALSE) {
-  global $stats;
-  global $pc_stats;
-  global $metrics;
-  global $diff_mode;
-  global $sort_col;
-  global $display_calls;
-
-  $diff_mode = $diff_report;
-
-  if (!empty($sort)) {
-    if (array_key_exists($sort, xhprof_sortable_columns())) {
-      $sort_col = $sort;
-    }
-    else {
-      print("Invalid Sort Key $sort specified in URL");
-    }
-  }
-
-  // For C++ profiler runs, walltime attribute isn't present.
-  // In that case, use "samples" as the default sort column.
-  if (!isset($xhprof_data["main()"]["wt"])) {
-
-    if ($sort_col == "wt") {
-      $sort_col = "samples";
-    }
-
-    // C++ profiler data doesn't have call counts.
-    // ideally we should check to see if "ct" metric
-    // is present for "main()". But currently "ct"
-    // metric is artificially set to 1. So, relying
-    // on absence of "wt" metric instead.
-    $display_calls = FALSE;
-  }
-  else {
-    $display_calls = TRUE;
-  }
-
-  // parent/child report doesn't support exclusive times yet.
-  // So, change sort hyperlinks to closest fit.
-  if (!empty($rep_symbol)) {
-    $sort_col = str_replace("excl_", "", $sort_col);
-  }
-
-  if ($display_calls) {
-    $stats = array("fn", "ct", "Calls%");
-  }
-  else {
-    $stats = array("fn");
-  }
-
-  $pc_stats = $stats;
-
-  $possible_metrics = xhprof_get_possible_metrics($xhprof_data);
-  foreach ($possible_metrics as $metric => $desc) {
-    if (isset($xhprof_data["main()"][$metric])) {
-      $metrics[] = $metric;
-      // flat (top-level reports): we can compute
-      // exclusive metrics reports as well.
-      $stats[] = $metric;
-      $stats[] = "I" . $desc[0] . "%";
-      $stats[] = "excl_" . $metric;
-      $stats[] = "E" . $desc[0] . "%";
-
-      // parent/child report for a function: we can
-      // only breakdown inclusive times correctly.
-      $pc_stats[] = $metric;
-      $pc_stats[] = "I" . $desc[0] . "%";
-    }
-  }
-}
-
-/**
- * Get the appropriate description for a statistic
- * (depending upon whether we are in diff report mode
- * or single run report mode).
- *
- * @author Kannan
- */
-function xhprof_stat_description($stat, $desc = FALSE) {
-  global $diff_mode;
-
-  // Textual descriptions for column headers in "single run" mode
-  $descriptions = array(
-    "fn" => "Function Name",
-    "ct" =>  "Calls",
-    "Calls%" => "Calls%",
-
-    "wt" => "Incl. Wall Time (microsec)",
-    "IWall%" => "IWall%",
-    "excl_wt" => "Excl. Wall Time (microsec)",
-    "EWall%" => "EWall%",
-
-    "ut" => "Incl. User (microsecs)",
-    "IUser%" => "IUser%",
-    "excl_ut" => "Excl. User (microsec)",
-    "EUser%" => "EUser%",
-
-    "st" => "Incl. Sys  (microsec)",
-    "ISys%" => "ISys%",
-    "excl_st" => "Excl. Sys  (microsec)",
-    "ESys%" => "ESys%",
-
-    "cpu" => "Incl. CPU (microsecs)",
-    "ICpu%" => "ICpu%",
-    "excl_cpu" => "Excl. CPU (microsec)",
-    "ECpu%" => "ECPU%",
-
-    "mu" => "Incl. MemUse (bytes)",
-    "IMUse%" => "IMemUse%",
-    "excl_mu" => "Excl. MemUse (bytes)",
-    "EMUse%" => "EMemUse%",
-
-    "pmu" => "Incl.  PeakMemUse (bytes)",
-    "IPMUse%" => "IPeakMemUse%",
-    "excl_pmu" => "Excl. PeakMemUse (bytes)",
-    "EPMUse%" => "EPeakMemUse%",
-
-    "samples" => "Incl. Samples",
-    "ISamples%" => "ISamples%",
-    "excl_samples" => "Excl. Samples",
-    "ESamples%" => "ESamples%",
-  );
-
-// Textual descriptions for column headers in "diff" mode
-  $diff_descriptions = array(
-    "fn" => "Function Name",
-    "ct" =>  "Calls Diff",
-    "Calls%" => "Calls Diff%",
-
-    "wt" => "Incl. Wall Diff (microsec)",
-    "IWall%" => "IWall  Diff%",
-    "excl_wt" => "Excl. Wall Diff (microsec)",
-    "EWall%" => "EWall Diff%",
-
-    "ut" => "Incl. User Diff (microsec)",
-    "IUser%" => "IUser Diff%",
-    "excl_ut" => "Excl. User Diff (microsec)",
-    "EUser%" => "EUser Diff%",
-
-    "cpu" => "Incl. CPU Diff (microsec)",
-    "ICpu%" => "ICpu Diff%",
-    "excl_cpu" => "Excl. CPU Diff (microsec)",
-    "ECpu%" => "ECpu Diff%",
-
-    "st" => "Incl. Sys Diff (microsec)",
-    "ISys%" => "ISys Diff%",
-    "excl_st" => "Excl. Sys Diff (microsec)",
-    "ESys%" => "ESys Diff%",
-
-    "mu" => "Incl. MemUse Diff (bytes)",
-    "IMUse%" => "IMemUse Diff%",
-    "excl_mu" => "Excl. MemUse Diff (bytes)",
-    "EMUse%" => "EMemUse Diff%",
-
-    "pmu" => "Incl.  PeakMemUse Diff (bytes)",
-    "IPMUse%" => "IPeakMemUse Diff%",
-    "excl_pmu" => "Excl. PeakMemUse Diff (bytes)",
-    "EPMUse%" => "EPeakMemUse Diff%",
-
-    "samples" => "Incl. Samples Diff",
-    "ISamples%" => "ISamples Diff%",
-    "excl_samples" => "Excl. Samples Diff",
-    "ESamples%" => "ESamples Diff%",
-  );
-  if ($diff_mode) {
-   if ($desc) $diff_descriptions = array_flip($diff_descriptions);
-   return $diff_descriptions[$stat];
-  }
-  else {
-    if ($desc) $descriptions = array_flip($descriptions);
-    return $descriptions[$stat];
-  }
-}
-
-
-/**
- * Analyze raw data & generate the profiler report
- * (common for both single run mode and diff mode).
- *
- * @author: Kannan
- */
-function xhprof_profiler_report($url_params, $rep_symbol, $sort, $run1, $run1_desc,
-                                $run1_data, $run2 = 0, $run2_desc = "", $run2_data = array()) {
-  global $totals;
-  global $totals_1;
-  global $totals_2;
-  global $stats;
-  global $pc_stats;
-  global $diff_mode;
-  global $base_path;
-
-  $output = '';
-
-  // if we are reporting on a specific function, we can trim down
-  // the report(s) to just stuff that is relevant to this function.
-  // That way compute_flat_info()/compute_diff() etc. do not have
-  // to needlessly work hard on churning irrelevant data.
-  if (!empty($rep_symbol)) {
-    $run1_data = xhprof_trim_run($run1_data, array($rep_symbol));
-    if ($diff_mode) {
-      $run2_data = xhprof_trim_run($run2_data, array($rep_symbol));
-    }
-  }
-
-  if ($diff_mode) {
-    $run_delta = xhprof_compute_diff($run1_data, $run2_data);
-    $symbol_tab  = xhprof_compute_flat_info($run_delta, $totals);
-    $symbol_tab1 = xhprof_compute_flat_info($run1_data, $totals_1);
-    $symbol_tab2 = xhprof_compute_flat_info($run2_data, $totals_2);
-  }
-  else {
-    $symbol_tab = xhprof_compute_flat_info($run1_data, $totals);
-  }
-
-  $run1_txt = sprintf("<b>Run #%s:</b> %s", $run1, $run1_desc);
-
-  $base_url_params = xhprof_array_unset(xhprof_array_unset($url_params, 'symbol'), 'all');
-
-  $top_link_query_string = "$base_path/?" . http_build_query($base_url_params);
-
-  if ($diff_mode) {
-    $diff_text = "Diff";
-    $base_url_params = xhprof_array_unset($base_url_params, 'run1');
-    $base_url_params = xhprof_array_unset($base_url_params, 'run2');
-    $run1_link = xhprof_xhprof_render_link('View Run #' . $run1, "$base_path/?" .
-                          http_build_query(xhprof_array_set($base_url_params, 'run', $run1)));
-    $run2_txt = sprintf("<b>Run #%s:</b> %s", $run2, $run2_desc);
-
-    $run2_link = xhprof_xhprof_render_link('View Run #' . $run2, "$base_path/?" .
-                          http_build_query(xhprof_array_set($base_url_params, 'run', $run2)));
-  }
-  else {
-    $diff_text = "Run";
-  }
-
-  // set up the action links for operations that can be done on this report
-  $links = array();
-  $path_parts = explode('/', current_path());
-  array_pop($path_parts);
-  $links[] = l("View Top Level $diff_text Report", implode('/', $path_parts));
-
-  if ($diff_mode) {
-    $inverted_params = $url_params;
-    $inverted_params['run1'] = $url_params['run2'];
-    $inverted_params['run2'] = $url_params['run1'];
-
-    // view the different runs or invert the current diff
-    $links[] = $run1_link;
-    $links[] = $run2_link;
-    $links[] = xhprof_xhprof_render_link('Invert ' . $diff_text . ' Report', "$base_path/?".
-                                            http_build_query($inverted_params));
-  }
-
-  // lookup function xhprof_typeahead form
-  $links[] = '<input class="function_typeahead" ' . ' type="input" size="40" maxlength="100" />';
-
-  $output .= xhprof_render_actions($links);
-
-  $output .= '<dl class=xhprof_report_info>' .
-    '  <dt>' . $diff_text . ' Report</dt>' .
-    '  <dd>' . ($diff_mode ?  $run1_txt . '<br><b>vs.</b><br>' . $run2_txt : $run1_txt) . '  </dd>' .
-    '  <dt>Tip</dt>' .
-    '  <dd>Click a function xhprof_name below to drill down.</dd>' .
-    '</dl>';
-
-  // data tables
-  if (!empty($rep_symbol)) {
-    if (!isset($symbol_tab[$rep_symbol])) {
-      drupal_set_message(t("Symbol <strong>$rep_symbol</strong> not found in XHProf run"));;
-      return $output;
-    }
-
-    // Single function xhprof_report with parent/child information.
-    if ($diff_mode) {
-      $info1 = isset($symbol_tab1[$rep_symbol]) ? $symbol_tab1[$rep_symbol] : NULL;
-      $info2 = isset($symbol_tab2[$rep_symbol]) ? $symbol_tab2[$rep_symbol] : NULL;
-      $output .= xhprof_symbol_report($url_params, $run_delta, $symbol_tab[$rep_symbol], $sort, $rep_symbol,
-                                         $run1, $info1, $run2, $info2);
-    }
-    else {
-      $output .= xhprof_symbol_report($url_params, $run1_data, $symbol_tab[$rep_symbol], $sort, $rep_symbol, $run1);
-    }
-  }
-  else {
-    // flat top-level report of all functions.
-    $output .= xhprof_full_report($url_params, $symbol_tab, $sort, $run1, $run2);
-  }
-  return $output;
-}
-
-/**
- * Given a number, returns the td class to use for display.
- *
- * For instance, negative numbers in diff reports comparing two runs (run1 & run2)
- * represent improvement from run1 to run2. We use green to display those deltas,
- * and red for regression deltas.
- */
-function xhprof_get_print_class($num, $bold) {
-  global $vbar;
-  global $vbbar;
-  global $vrbar;
-  global $vgbar;
-  global $diff_mode;
-
-  if ($bold) {
-    if ($diff_mode) {
-      if ($num <= 0) {
-        $class = 'vgbar'; // green (improvement)
-      } else {
-        $class = 'vrbar'; // red (regression)
-      }
-    } else {
-      $class = 'vbbar'; // blue
-    }
-  }
-  else {
-    $class = 'vbar';  // default (black)
-  }
-
-  return $class;
-}
-
-/**
- * Prints a <td> element with a numeric value.
- */
-function xhprof_print_num($num, $fmt_func = NULL, $bold = FALSE, $attributes = NULL) {
-  $class = xhprof_get_print_class($num, $bold);
-  if (!empty($fmt_func)) {
-    $num = call_user_func($fmt_func, $num);
-  }
-
-  //return "<td $attributes class='$class'>$num</td>\n";
-  $num = number_format($num);
-  return "<span class='$class'>$num</span>\n";
-  //return $num;
-}
-
-/**
- * Prints a <td> element with a pecentage.
- */
-function xhprof_print_pct($numer, $denom, $bold = FALSE, $attributes = NULL) {
-  global $vbar;
-  global $vbbar;
-  global $diff_mode;
-
-  $class = xhprof_get_print_class($numer, $bold);
-
-  if ($denom == 0) {
-    $pct = "N/A%";
-  }
-  else {
-    $pct = xhprof_percent_format($numer / abs($denom));
-  }
-
-  return "<span class='$class'>$pct</span>";
-}
-
-/**
- * Print "flat" data corresponding to one function.
- *
- * @author Kannan
- */
-function xhprof_print_function_info($url_params, $info, $sort, $run1, $run2) {
-  global $totals;
-  global $sort_col;
-  global $metrics;
-  global $format_cbk;
-  global $display_calls;
-  global $base_path;
-
-  $output = '';
-  $href = "$base_path/?" .  http_build_query(xhprof_array_set($url_params, 'symbol', $info["fn"]));
-
-  //$output .= '<td>';
-  //$output .= xhprof_xhprof_render_link($info["fn"], $href);
-
-  if ($display_calls) {
-    // Call Count..
-    $output .= xhprof_print_num($info["ct"], $format_cbk["ct"], ($sort_col == "ct"));
-    $output .= xhprof_print_pct($info["ct"], $totals["ct"], ($sort_col == "ct"));
-  }
-
-  // Other metrics..
-  foreach ($metrics as $metric) {
-    // Inclusive metric
-    $output .= xhprof_print_num($info[$metric], $format_cbk[$metric], ($sort_col == $metric));
-    $output .= xhprof_print_pct($info[$metric], $totals[$metric], ($sort_col == $metric));
-
-    // Exclusive Metric
-    $output .= xhprof_print_num($info["excl_" . $metric], $format_cbk["excl_" . $metric], ($sort_col == "excl_" . $metric));
-    $output .= xhprof_print_pct($info["excl_" . $metric], $totals[$metric], ($sort_col == "excl_" . $metric));
-  }
-
-  return $output;
-}
-
-/**
- * Print non-hierarchical (flat-view) of profiler data.
- *
- * @author Kannan
- */
-function xhprof_print_flat_data($url_params, $title, $flat_data, $sort, $run1, $run2, $limit) {
-  global $stats;
-  global $pc_stats;
-  global $totals;
-  global $vwbar;
-  global $base_path;
-
-  $output = '';
-  // Table attributes
-  $attributes = array('id' => 'xhprof-run-table');
-
-  // Headers.
-  $header = array();
-  foreach ($stats as $stat) {
-    $desc = xhprof_stat_description($stat);
-    if (array_key_exists($stat, xhprof_sortable_columns($stat))) {
-      if (isset($_GET['sort']) && $stat == $_GET['sort']) {
-        $header_desc = l(t($desc), current_path(), array('query' => array('sort' => $stat), t($desc)));
-        $header[] = array('data' => t($header_desc) . theme('tablesort_indicator', array('style' => 'desc')));
-      }
-      else {
-        $header_desc = l(t($desc), current_path(), array('query' => array('sort' => $stat), t($desc)));
-        $header[] = array('data' => t($header_desc));
-      }
-    }
-    else {
-      $header[] = array('data' => t($desc));
-    }
-  }
-
-  // Table rows
-  $rows = array();
-  $trail = menu_get_active_trail();
-  foreach ($flat_data as $data) {
-    $row = array(
-      array('data' => l($data["fn"], $trail[1]["href"] . '/' . $data["fn"])),
-      array('data' => $data['ct'], 'class' => 'xhprof_micro'),
-      array('data' => xhprof_print_pct($data['ct'], $totals['ct']), 'class' => 'xhprof_percent'),
-      array('data' => $data['wt'], 'class' => 'xhprof_micro'),
-      array('data' => xhprof_print_pct($data['wt'], $totals['wt'])),
-      array('data' => $data['excl_wt'], 'class' => 'xhprof_percent'),
-      array('data' => xhprof_print_pct($data['excl_wt'], $totals['wt']), 'class' => 'xhprof_micro'),
-      array('data' => $data['cpu'], 'class' => 'xhprof_percent'),
-      array('data' => xhprof_print_pct($data['cpu'], $totals['cpu']), 'class' => 'xhprof_percent'),
-      array('data' => $data['excl_cpu'], 'class' => 'xhprof_micro'),
-      array('data' => xhprof_print_pct($data['excl_cpu'], $totals['cpu']), 'class' => 'xhprof_percent'),
-      array('data' => $data['mu'], 'class' => 'xhprof_micro'),
-      array('data' => xhprof_print_pct($data['mu'], $totals['mu']), 'class' => 'xhprof_percent'),
-      array('data' => $data['excl_mu'], 'class' => 'xhprof_micro'),
-      array('data' => xhprof_print_pct($data['excl_mu'], $totals['mu']), 'xhprof_percent'),
-      array('data' => $data['pmu'], 'class' => 'xhprof_micro'),
-      array('data' => xhprof_print_pct($data['pmu'], $totals['pmu']), 'xhprof_percent'),
-      array('data' => $data['excl_pmu'], 'xhprof_micro'),
-      array('data' => xhprof_print_pct($data['excl_pmu'], $totals['pmu']), 'class' => 'xhprof_percent'),
-    );
-    $rows[] = $row;
-  }
-
-
-  $size  = count($flat_data);
-  if (!$limit) {              // no limit
-    $limit = $size;
-    $display_link = "";
-  }
-  else {
-    $display_link = xhprof_xhprof_render_link(" [ <b class=bubble>display all </b>]",
-                                       "$base_path/?" .  http_build_query(xhprof_array_set($url_params, 'all', 1)));
-  }
-
-  $output .= "<h3 align=center>$title $display_link</h3><br>";
-  $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => $attributes));
-  return $output;
-}
-
-/**
- * Generates a tabular report for all functions. This is the top-level report.
- *
- * @author Kannan
- */
-function xhprof_full_report($url_params, $symbol_tab, $sort, $run1, $run2) {
-  global $vwbar;
-  global $vbar;
-  global $totals;
-  global $totals_1;
-  global $totals_2;
-  global $metrics;
-  global $diff_mode;
-  global $descriptions;
-  global $sort_col;
-  global $format_cbk;
-  global $display_calls;
-  global $base_path;
-  global $stats;
-
-  $possible_metrics = xhprof_get_possible_metrics();
-  $output = '';
-
-  if ($diff_mode) {
-    $base_url_params = xhprof_array_unset(xhprof_array_unset($url_params, 'run1'), 'run2');
-    $href1 = "$base_path/?" .  http_build_query(xhprof_array_set($base_url_params, 'run', $run1));
-    $href2 = "$base_path/?" .  http_build_query(xhprof_array_set($base_url_params, 'run', $run2));
-
-    $output .= "<h3><center>Overall Diff Summary</center></h3>";
-    ///$output .= '<table border=1 cellpadding=2 cellspacing=1 width="30%" ' .'rules=rows bordercolor="#bdc7d8" align=center>' . "\n";
-    //$output .= '<tr bgcolor="#bdc7d8" align=right>';
-    $headers = array();
-    $headers[] = "";
-    $headers[] = xhprof_xhprof_render_link("Run #$run1", $href1);
-    $headers[] = xhprof_xhprof_render_link("Run #$run2", $href2);
-    $headers[] = 'Diff';
-    $headers[] = 'Diff%';
-
-    $rows = array();
-    if ($display_calls) {
-      $row = array(
-        array('data' => 'Number of function xhprof_Calls'),
-        array('data' => xhprof_print_num($totals_1["ct"], $format_cbk["ct"]), 'class' => 'xhprof_micro'),
-        array('data' => xhprof_print_num($totals_2["ct"], $format_cbk["ct"]), 'class' => 'xhprof_micro'),
-        array('data' => xhprof_print_num($totals_2["ct"] - $totals_1["ct"], $format_cbk["ct"], TRUE), 'class' => 'xhprof_micro'),
-        array('data' => xhprof_print_pct($totals_2["ct"] - $totals_1["ct"], $totals_1["ct"], TRUE), 'class' => 'xhprof_percent'),
-      );
-      $rows[] = $row;
-    }
-
-    foreach ($metrics as $m) {
-      $desc = xhprof_stat_description($m, $desc = FALSE);
-      $row = array(
-        array('data' =>  str_replace("<br>", " ", $desc)),
-        array('data' =>  xhprof_print_num($totals_1[$m], $format_cbk[$m]), 'class' => 'xhprof_micro'),
-        array('data' => xhprof_print_num($totals_2[$m], $format_cbk[$m]), 'class' => 'xhprof_micro'),
-        array('data' => xhprof_print_num($totals_2[$m] - $totals_1[$m], $format_cbk[$m], TRUE), 'class' => 'xhprof_micro'),
-        array('data' => xhprof_print_pct($totals_2[$m] - $totals_1[$m], $totals_1[$m], TRUE), 'class' => 'xhprof_percent'),
-      );
-      $rows[] = $row;
-    }
-    $output .= theme('table', array('header' => $headers, 'rows' => $rows));
-
-    $callgraph_report_title = '[View Regressions/Improvements using Callgraph Diff]';
-  }
-  else {
-    $vars = array(
-      'totals' => $totals,
-      'possible_metrics' => $possible_metrics,
-      'metrics' => $metrics,
-      'display_calls' => $display_calls,
-    );
-    $output .= theme('xhprof_overall_summary', $vars);
-  }
-
-  $output .= "<center><br><h3>";
-  //$output .= xhprof_xhprof_render_link($callgraph_report_title, "$base_path/callgraph.php" . "?" . http_build_query($url_params));
-  $output .= "</h3></center>";
-
-  $flat_data = array();
-  foreach ($symbol_tab as $symbol => $info) {
-    $tmp = $info;
-    $tmp["fn"] = $symbol;
-    $flat_data[] = $tmp;
-  }
-  usort($flat_data, 'xhprof_sort_cbk');
-
-  $output .= "<br>";
-
-  if (!empty($url_params['all'])) {
-    $all = TRUE;
-    $limit = 0;    // display all rows
-  }
-  else {
-    $all = FALSE;
-    $limit = 100;  // display only limited number of rows
-  }
-
-  $desc = str_replace("<br>", " ", $descriptions[$sort_col]);
-
-  if ($diff_mode) {
-    if ($all) {
-      $title = "Total Diff Report: '
-               .'Sorted by absolute value of regression/improvement in $desc";
-    }
-    else {
-      $title = "Top 100 <em style='color:red'>Regressions</em>/" . "<em style='color:green'>Improvements</em>: " . "Sorted by $desc Diff";
-    }
-  }
-  else {
-    if ($all) {
-      $title = "Sorted by $desc";
-    }
-    else {
-      $title = "Displaying top $limit functions: Sorted by $desc";
-    }
-  }
-  $vars = array(
-    'stats' => $stats,
-    'totals' => $totals,
-    'url_params' => $url_params,
-    'title' => $title,
-    'flat_data' => $flat_data,
-    'limit' => $limit,
-    'run1' => $run1,
-    'run2' => $run2,
-  );
-
-  $output .= theme('xhprof_run_table', $vars);
-  return $output;
-}
-
-
-/**
- * Return attribute names and values to be used by javascript tooltip.
- */
-function xhprof_get_tooltip_attributes($type, $metric) {
-  return "type='$type' metric='$metric'";
-}
-
-/**
- * Print info for a parent or child function xhprof_in the
- * parent & children report.
- *
- * @author Kannan
- */
-function xhprof_pc_info($info, $base_ct, $base_info, $parent) {
-  global $sort_col;
-  global $metrics;
-  global $format_cbk;
-  global $display_calls;
-
-  $type = $parent ? "Parent" : "Child";
-
-  if ($display_calls) {
-    $mouseoverct = xhprof_get_tooltip_attributes($type, "ct");
-    // Call count.
-    $row = array(xhprof_print_num($info["ct"], $format_cbk["ct"], ($sort_col == "ct"), $mouseoverct));
-    $row[] = xhprof_print_pct($info["ct"], $base_ct, ($sort_col == "ct"), $mouseoverct);
-  }
-
-  // Inclusive metric values.
-  foreach ($metrics as $metric) {
-    $row[] = xhprof_print_num($info[$metric], $format_cbk[$metric], ($sort_col == $metric), xhprof_get_tooltip_attributes($type, $metric));
-    $row[] = xhprof_print_pct($info[$metric], $base_info[$metric], ($sort_col == $metric), xhprof_get_tooltip_attributes($type, $metric));
-  }
-
-  return $row;
-}
-
-function xhprof_print_pc_array($url_params, $results, $base_ct, $base_info, $parent, $run1, $run2) {
-  global $base_path;
-
-  $rows = array();
-  // Construct section title
-  $title = $parent ? 'Parent function' : 'Child function';
-
-  if (count($results) > 1) {
-    $title = '<strong>' . $title . 's</strong>';
-  }
-
-  // Get the current path info to assemble the symbol links. There is probably
-  // a better way to do this…
-  $path_parts = explode('/', current_path());
-  array_pop($path_parts);
-  $symbol_path = implode('/', $path_parts);
-
-  $rows[] = array(array('data' => $title, 'colspan' => 11));
-  usort($results, 'xhprof_sort_cbk');
-
-  foreach ($results as $info) {
-    $row = array();
-    $row[] = l($info["fn"], $symbol_path . '/' . $info["fn"]);
-    $row = array_merge($row, xhprof_pc_info($info, $base_ct, $base_info, $parent));
-    $rows[] = $row;
-  }
-  return $rows;
-}
-
-
-function xhprof_print_symbol_summary($symbol_info, $stat, $base) {
-
-  $val = $symbol_info[$stat];
-  $desc = str_replace("<br>", " ", xhprof_stat_description($stat));
-
-  $output .= "$desc: </td>";
-  $output .= number_format($val);
-  $output .= " (" . pct($val, $base) . "% of overall)";
-  if (substr($stat, 0, 4) == "excl") {
-    $func_base = $symbol_info[str_replace("excl_", "", $stat)];
-    $output .= " (" . pct($val, $func_base) . "% of this function)";
-  }
-  $output .= "<br>";
-  return $output;
-}
-
-/**
- * Generates a report for a single function/symbol.
- *
- * @author Kannan
- */
-function xhprof_symbol_report($url_params, $run_data, $symbol_info, $sort, $rep_symbol, $run1,
-                                 $symbol_info1 = NULL, $run2 = 0, $symbol_info2 = NULL) {
-  global $vwbar;
-  global $vbar;
-  global $totals;
-  global $pc_stats;
-  global $metrics;
-  global $diff_mode;
-  global $descriptions;
-  global $format_cbk;
-  global $sort_col;
-  global $display_calls;
-  global $base_path;
-
-  $output = '';
-  $possible_metrics = xhprof_get_possible_metrics();
-
-  if ($diff_mode) {
-    $diff_text = "<b>Diff</b>";
-    $regr_impr = "<i style='color:red'>Regression</i>/<i style='color:green'>Improvement</i>";
-  } else {
-    $diff_text = "";
-    $regr_impr = "";
-  }
-
-  if ($diff_mode) {
-    $base_url_params = xhprof_array_unset(xhprof_array_unset($url_params, 'run1'), 'run2');
-    $href1 = "$base_path?" . http_build_query(xhprof_array_set($base_url_params, 'run', $run1));
-    $href2 = "$base_path?" . http_build_query(xhprof_array_set($base_url_params, 'run', $run2));
-
-    $output .= "<h3 align=center>$regr_impr summary for $rep_symbol<br><br></h3>";
-    $output .= '<table border=1 cellpadding=2 cellspacing=1 width="30%" ' .'rules=rows bordercolor="#bdc7d8" align=center>' . "\n";
-    $output .= '<tr bgcolor="#bdc7d8" align=right>';
-    $output .= "<th align=left>$rep_symbol</th>";
-    $output .= "<th $vwbar><a href=" . $href1 . ">Run #$run1</a></th>";
-    $output .= "<th $vwbar><a href=" . $href2 . ">Run #$run2</a></th>";
-    $output .= "<th $vwbar>Diff</th>";
-    $output .= "<th $vwbar>Diff%</th>";
-    $output .= '</tr>';
-    $output .= '<tr>';
-
-    if ($display_calls) {
-      $output .= "<td>Number of function xhprof_Calls</td>";
-      $output .= xhprof_print_num($symbol_info1["ct"], $format_cbk["ct"]);
-      $output .= xhprof_print_num($symbol_info2["ct"], $format_cbk["ct"]);
-      $output .= xhprof_print_num($symbol_info2["ct"] - $symbol_info1["ct"], $format_cbk["ct"], TRUE);
-      $output .= xhprof_print_pct($symbol_info2["ct"] - $symbol_info1["ct"], $symbol_info1["ct"], TRUE);
-      $output .= '</tr>';
-    }
-
-    foreach ($metrics as $metric) {
-      $m = $metric;
-
-      // Inclusive stat for metric
-      $output .= '<tr>';
-      $output .= "<td>" . str_replace("<br>", " ", $descriptions[$m]) . "</td>";
-      $output .= xhprof_print_num($symbol_info1[$m], $format_cbk[$m]);
-      $output .= xhprof_print_num($symbol_info2[$m], $format_cbk[$m]);
-      $output .= xhprof_print_num($symbol_info2[$m] - $symbol_info1[$m], $format_cbk[$m], TRUE);
-      $output .= xhprof_print_pct($symbol_info2[$m] - $symbol_info1[$m], $symbol_info1[$m], TRUE);
-      $output .= '</tr>';
-
-      // AVG (per call) Inclusive stat for metric
-      $output .= '<tr>';
-      $output .= "<td>" . str_replace("<br>", " ", $descriptions[$m]) . " per call </td>";
-      $avg_info1 = 'N/A';
-      $avg_info2 = 'N/A';
-      if ($symbol_info1['ct'] > 0) {
-        $avg_info1 = ($symbol_info1[$m]/$symbol_info1['ct']);
-      }
-      if ($symbol_info2['ct'] > 0) {
-        $avg_info2 = ($symbol_info2[$m]/$symbol_info2['ct']);
-      }
-      $output .= xhprof_print_num($avg_info1, $format_cbk[$m]);
-      $output .= xhprof_print_num($avg_info2, $format_cbk[$m]);
-      $output .= xhprof_print_num($avg_info2 - $avg_info1, $format_cbk[$m], TRUE);
-      $output .= xhprof_print_pct($avg_info2 - $avg_info1, $avg_info1, TRUE);
-      $output .= '</tr>';
-
-      // Exclusive stat for metric
-      $m = "excl_" . $metric;
-      $output .= '<tr style="border-bottom: 1px solid black;">';
-      $output .= "<td>" . str_replace("<br>", " ", $descriptions[$m]) . "</td>";
-      $output .= xhprof_print_num($symbol_info1[$m], $format_cbk[$m]);
-      $output .= xhprof_print_num($symbol_info2[$m], $format_cbk[$m]);
-      $output .= xhprof_print_num($symbol_info2[$m] - $symbol_info1[$m], $format_cbk[$m], TRUE);
-      $output .= xhprof_print_pct($symbol_info2[$m] - $symbol_info1[$m], $symbol_info1[$m], TRUE);
-      $output .= '</tr>';
-    }
-
-    $output .= '</table>';
-  }
-
-  $output .= "<h4>";
-  $output .= "Parent/Child $regr_impr report for <strong>$rep_symbol</strong>";
-  // TODO: Maybe include this?
-  //$callgraph_href = "$base_path/callgraph.php?" . http_build_query(xhprof_array_set($url_params, 'func', $rep_symbol));
-  //$output .= " <a href='$callgraph_href'>[View Callgraph $diff_text]</a>";
-  $output .= "</h4>";
-
-  $headers = array();
-  $rows = array();
-
-  // Headers.
-  foreach ($pc_stats as $stat) {
-    $desc = xhprof_stat_description($stat);
-    if (array_key_exists($stat, xhprof_sortable_columns($stat))) {
-      if ($stat == $sort) {
-        $header_desc = l(t($desc), current_path(), array('query' => array('sort' => $stat), t($desc)));
-        $headers[] = array('data' => t($header_desc) . theme('tablesort_indicator', array('style' => 'desc')));
-      }
-      else {
-        $header_desc = l(t($desc), current_path(), array('query' => array('sort' => $stat), t($desc)));
-        $headers[] = array('data' => t($header_desc));
-      }
-   }
-    else {
-      $headers[] = array('data' => t($desc));
-    }
-  }
-
-  $rows[] = array(array('data' => '<strong>Current Function</strong>', 'colspan' => 11));
-
-  // Make this a self-reference to facilitate copy-pasting snippets to e-mails.
-  $row = array("<a href=''>$rep_symbol</a>");
-
-  if ($display_calls) {
-    // Call Count.
-    $row[] = xhprof_print_num($symbol_info["ct"], $format_cbk["ct"]);
-    $row[] = xhprof_print_pct($symbol_info["ct"], $totals["ct"]);
-  }
-
-  // Inclusive Metrics for current function.
-  foreach ($metrics as $metric) {
-    $row[] = xhprof_print_num($symbol_info[$metric], $format_cbk[$metric], ($sort_col == $metric));
-    $row[] = xhprof_print_pct($symbol_info[$metric], $totals[$metric], ($sort_col == $metric));
-  }
-  $rows[] = $row;
-  $row = array("Exclusive Metrics $diff_text for Current Function");
-
-  if ($display_calls) {
-    // Call Count
-    $row[] = "$vbar";
-    $row[] = "$vbar";
-  }
-
-  // Exclusive Metrics for current function
-  foreach ($metrics as $metric) {
-    $row[] = xhprof_print_num($symbol_info["excl_" . $metric], $format_cbk["excl_" . $metric],
-                 ($sort_col == $metric), xhprof_get_tooltip_attributes("Child", $metric));
-    $row[] = xhprof_print_pct($symbol_info["excl_" . $metric], $symbol_info[$metric],
-                 ($sort_col == $metric), xhprof_get_tooltip_attributes("Child", $metric));
-  }
-  $rows[] = $row;
-
-  // list of callers/parent functions
-  $results = array();
-  $base_ct = $display_calls ? $symbol_info["ct"] : $base_ct = 0;
-
-  foreach ($metrics as $metric) {
-    $base_info[$metric] = $symbol_info[$metric];
-  }
-  foreach ($run_data as $parent_child => $info) {
-    list($parent, $child) = xhprof_parse_parent_child($parent_child);
-    if (($child == $rep_symbol) && ($parent)) {
-      $info_tmp = $info;
-      $info_tmp["fn"] = $parent;
-      $results[] = $info_tmp;
-    }
-  }
-  usort($results, 'xhprof_sort_cbk');
-
-  if (count($results) > 0) {
-    $pc_row = xhprof_print_pc_array($url_params, $results, $base_ct, $base_info, TRUE, $run1, $run2);
-    $rows = array_merge($rows, $pc_row);
-  }
-
-  // list of callees/child functions
-  $results = array();
-  $base_ct = 0;
-  foreach ($run_data as $parent_child => $info) {
-    list($parent, $child) = xhprof_parse_parent_child($parent_child);
-    if ($parent == $rep_symbol) {
-      $info_tmp = $info;
-      $info_tmp["fn"] = $child;
-      $results[] = $info_tmp;
-      if ($display_calls) {
-        $base_ct += $info["ct"];
-      }
-    }
-  }
-  usort($results, 'xhprof_sort_cbk');
-
-  if (count($results)) {
-    $pc_row = xhprof_print_pc_array($url_params, $results, $base_ct, $base_info, FALSE, $run1, $run2);
-    $rows = array_merge($rows, $pc_row);
-  }
-
-  $output .= theme('table', array('header' => $headers, 'rows' => $rows));
-
-  // TODO: Convert tooltips.
-  // These will be used for pop-up tips/help.
-  // Related javascript code is in: xhprof_report.js
-  $output .= "\n";
-  $output .= '<script language="javascript">' . "\n";
-  $output .= "var func_name = '\"" . $rep_symbol . "\"';\n";
-  $output .= "var total_child_ct  = " . $base_ct . ";\n";
-  if ($display_calls) {
-    $output .= "var func_ct   = " . $symbol_info["ct"] . ";\n" ;
-  }
-  $output .= "var func_metrics = new Array();\n";
-  $output .= "var metrics_col  = new Array();\n";
-  $output .= "var metrics_desc  = new Array();\n";
-  if ($diff_mode) {
-    $output .= "var diff_mode = TRUE;\n";
-  } else {
-    $output .= "var diff_mode = FALSE;\n";
-  }
-  $column_index = 3; // First three columns are Func Name, Calls, Calls%
-  foreach ($metrics as $metric) {
-    $output .= "func_metrics[\"" . $metric . "\"] = " . round($symbol_info[$metric]) . ";\n" ;
-    $output .= "metrics_col[\"". $metric . "\"] = " . $column_index . ";\n";
-    $output .= "metrics_desc[\"". $metric . "\"] = \"" . $possible_metrics[$metric][2] . "\";\n";
-
-    // each metric has two columns..
-    $column_index += 2;
-  }
-  $output .= '</script>';
-  $output .= "\n";
-  return $output;
-}
-
-/**
- * Generate the profiler report for a single run.
- *
- * @author Kannan
- */
-/*
-function xhprof_profiler_single_run_report ($url_params, $xhprof_data, $run_desc, $rep_symbol, $sort, $run) {
-  $output = '';
-
-  xhprof_init_metrics($xhprof_data, $rep_symbol, $sort, FALSE);
-  $output .= xhprof_profiler_report($url_params, $rep_symbol, $sort, $run, $run_desc, $xhprof_data);
-  return $output;
-}
-*/
-
-
-
-/**
- * Generate the profiler report for diff mode (delta between two runs).
- *
- * @author Kannan
- */
-//function xhprof_profiler_diff_report($url_params,
-//                              $xhprof_data1,
-//                              $run1_desc,
-//                              $xhprof_data2,
-//                              $run2_desc,
-//                              $rep_symbol,
-//                              $sort,
-//                              $run1,
-//                              $run2) {
-//
-//  $output = '';
-//  // Initialize what metrics we'll display based on data in Run2
-//  $output .= xhprof_init_metrics($xhprof_data2, $rep_symbol, $sort, TRUE);
-//
-//  $output .= xhprof_profiler_report($url_params,
-//                  $rep_symbol,
-//                  $sort,
-//                  $run1,
-//                  $run1_desc,
-//                  $xhprof_data1,
-//                  $run2,
-//                  $run2_desc,
-//                  $xhprof_data2);
-//  return $output;
-//}
-
-
-/**
- * Generate a XHProf Display View given the various URL parameters
- * as arguments. The first argument is an object that implements
- * the iXHProfRuns interface.
- *
- * @param object  $xhprof_runs_impl  An object that implements
- *                                   the iXHProfRuns interface
- *.
- * @param array   $url_params   Array of non-default URL params.
- *
- * @param string  $source       Category/type of the run. The source in
- *                              combination with the run id uniquely
- *                              determines a profiler run.
- *
- * @param string  $run          run id, or comma separated sequence of
- *                              run ids. The latter is used if an aggregate
- *                              report of the runs is desired.
- *
- * @param string  $wts          Comma separate list of integers.
- *                              Represents the weighted ratio in
- *                              which which a set of runs will be
- *                              aggregated. [Used only for aggregate
- *                              reports.]
- *
- * @param string  $symbol       function xhprof_symbol. If non-empty then the
- *                              parent/child view of this function xhprof_is
- *                              displayed. If empty, a flat-profile view
- *                              of the functions is displayed.
- *
- * @param string  $run1         Base run id (for diff reports)
- *
- * @param string  $run2         New run id (for diff reports)
- *
- */
-function xhprof_displayXHProfReport($xhprof_runs_impl, $options) {
-  // Extract options: $url_params, $source, $run, $wts, $symbol, $sort, $run1, $run2;
-  extract($options);
-  if ($run) {                              // specific run to display?
-    // run may be a single run or a comma separate list of runs
-    // that'll be aggregated. If "wts" (a comma separated list
-    // of integral weights is specified), the runs will be
-    // aggregated in that ratio.
-    //
-    $runs_array = explode(",", $run);
-    if (isset($_GET['order'])) {
-      $sort = xhprof_stat_description($_GET['order'], TRUE);
-    }
-    if (count($runs_array) == 1) {
-      $xhprof_data = $xhprof_runs_impl->get_run($runs_array[0], $source, $description, $sort);
-    }
-    else {
-      if (!empty($wts)) {
-        $wts_array  = explode(",", $wts);
-      }
-      else {
-        $wts_array = NULL;
-      }
-      $data = xhprof_aggregate_runs($xhprof_runs_impl, $runs_array, $wts_array, $source, FALSE);
-      $xhprof_data = $data['raw'];
-      $description = $data['description'];
-    }
-    $output .= xhprof_profiler_single_run_report($url_params, $xhprof_data, $description, $symbol, $sort, $run);
-
-  }
-  elseif ($run1 && $run2) {                  // diff report for two runs
-    $xhprof_data1 = $xhprof_runs_impl->get_run($run1, $source, $description1);
-    $xhprof_data2 = $xhprof_runs_impl->get_run($run2, $source, $description2);
-
-    $output .= xhprof_profiler_diff_report($url_params, $xhprof_data1, $description1, $xhprof_data2,
-                                              $description2, $symbol, $sort, $run1, $run2);
-  }
-  else {
-    $output .= "No XHProf runs specified in the URL.";
-  }
-  return $output;
-}
-
-
-/**
- * Set one key in an array and return the array
- *
- * @author Kannan
- */
-function xhprof_array_set($arr, $k, $v) {
-  $arr[$k] = $v;
-  return $arr;
-}
-
-
-/**
- * Removes/unsets one key in an array and return the array
- *
- * @author Kannan
- */
-function xhprof_array_unset($arr, $k) {
-  unset($arr[$k]);
-  return $arr;
-}
-
-
-/**
- * Return a trimmed version of the XHProf raw data. Note that the raw
- * data contains one entry for each unique parent/child function
- * combination.The trimmed version of raw data will only contain
- * entries where either the parent or child function is in the list
- * of $functions_to_keep.
- *
- * Note: Function main() is also always kept so that overall totals
- * can still be obtained from the trimmed version.
- *
- * @param  array  XHProf raw data
- * @param  array  array of function names
- *
- * @return array  Trimmed XHProf Report
- *
- * @author Kannan
- */
-function xhprof_trim_run($raw_data, $functions_to_keep) {
-
-  // convert list of functions to a hash with function as the key
-  $function_map = array_fill_keys($functions_to_keep, 1);
-
-  // always keep main() as well so that overall totals can still
-  // be computed if need be.
-  $function_map['main()'] = 1;
-
-  $new_raw_data = array();
-  foreach ($raw_data as $parent_child => $info) {
-    list($parent, $child) = xhprof_parse_parent_child($parent_child);
-
-    if (isset($function_map[$parent]) || isset($function_map[$child])) {
-      $new_raw_data[$parent_child] = $info;
-    }
-  }
-
-  return $new_raw_data;
-}
-
-
-/**
- * Hierarchical diff:
- * Compute and return difference of two call graphs: Run2 - Run1.
- *
- * @author Kannan
- */
-function xhprof_compute_diff($xhprof_data1, $xhprof_data2) {
-  global $display_calls;
-
-  // use the second run to decide what metrics we will do the diff on
-  $metrics = xhprof_get_metrics($xhprof_data2);
-
-  $xhprof_delta = $xhprof_data2;
-
-  foreach ($xhprof_data1 as $parent_child => $info) {
-
-    if (!isset($xhprof_delta[$parent_child])) {
-
-      // this pc combination was not present in run1;
-      // initialize all values to zero.
-      if ($display_calls) {
-        $xhprof_delta[$parent_child] = array("ct" => 0);
-      } else {
-        $xhprof_delta[$parent_child] = array();
-      }
-      foreach ($metrics as $metric) {
-        $xhprof_delta[$parent_child][$metric] = 0;
-      }
-    }
-
-    if ($display_calls) {
-      $xhprof_delta[$parent_child]["ct"] -= $info["ct"];
-    }
-
-    foreach ($metrics as $metric) {
-      $xhprof_delta[$parent_child][$metric] -= $info[$metric];
-    }
-  }
-
-  return $xhprof_delta;
-}
-
-/*
- * Get the list of metrics present in $xhprof_data as an array.
- *
- * @author Kannan
- */
-function xhprof_get_metrics($xhprof_data) {
-  // get list of valid metrics
-  $possible_metrics = xhprof_get_possible_metrics();
-
-  // return those that are present in the raw data.
-  // We'll just look at the root of the subtree for this.
-  $metrics = array();
-  foreach ($possible_metrics as $metric => $desc) {
-    if (isset($xhprof_data["main()"][$metric])) {
-      $metrics[] = $metric;
-    }
-  }
-
-  return $metrics;
-}
-
-/**
- * Takes a parent/child function name encoded as
- * "a==>b" and returns array("a", "b").
- *
- * @author Kannan
- */
-function xhprof_parse_parent_child($parent_child) {
-  $ret = explode("==>", $parent_child);
-
-  // Return if both parent and child are set
-  if (isset($ret[1])) {
-    return $ret;
-  }
-
-  return array(NULL, $ret[0]);
-}
-
-
-/**
- * Analyze hierarchical raw data, and compute per-function (flat)
- * inclusive and exclusive metrics.
- *
- * Also, store overall totals in the 2nd argument.
- *
- * @param  array $raw_data          XHProf format raw profiler data.
- * @param  array &$overall_totals   OUT argument for returning
- *                                  overall totals for various
- *                                  metrics.
- * @return array Returns a map from function name to its
- *               call count and inclusive & exclusive metrics
- *               (such as wall time, etc.).
- *
- * @author Kannan Muthukkaruppan
- */
-function xhprof_compute_flat_info($raw_data, &$overall_totals) {
-  $metrics = xhprof_get_metrics($raw_data);
-  $overall_totals = array(
-    "ct" => 0, "wt" => 0, "ut" => 0, "st" => 0, "cpu" => 0, "mu" => 0, "pmu" => 0, "samples" => 0
-  );
-
-  // Compute inclusive times for each function.
-  $symbol_tab = xhprof_compute_inclusive_times($raw_data);
-
-  // Total metric value is the metric value for "main()".
-  foreach ($metrics as $metric) {
-    $overall_totals[$metric] = $symbol_tab["main()"][$metric];
-  }
-
-  // Initialize exclusive (self) metric value to inclusive metric value to start with.
-  // In the same pass, also add up the total number of function calls.
-  foreach ($symbol_tab as $symbol => $info) {
-    foreach ($metrics as $metric) {
-      $symbol_tab[$symbol]["excl_" . $metric] = $symbol_tab[$symbol][$metric];
-    }
-    // Keep track of total number of calls.
-    $overall_totals["ct"] += $info["ct"];
-  }
-
-  // Adjust exclusive times by deducting inclusive time of children.
-  foreach ($raw_data as $parent_child => $info) {
-    list($parent, $child) = xhprof_parse_parent_child($parent_child);
-
-    if ($parent) {
-      foreach ($metrics as $metric) {
-        // make sure the parent exists hasn't been pruned.
-        if (isset($symbol_tab[$parent])) {
-          $symbol_tab[$parent]["excl_" . $metric] -= $info[$metric];
-        }
-      }
-    }
-  }
-
-  return $symbol_tab;
-}
-
-function xhprof_compute_inclusive_times($raw_data) {
-  $metrics = xhprof_get_metrics($raw_data);
-
-  $symbol_tab = array();
-
-  /*
-   * First compute inclusive time for each function and total
-   * call count for each function across all parents the
-   * function is called from.
-   */
-  foreach ($raw_data as $parent_child => $info) {
-    list($parent, $child) = xhprof_parse_parent_child($parent_child);
-
-    if ($parent == $child) {
-      /*
-       * XHProf PHP extension should never trigger this situation any more.
-       * Recursion is handled in the XHProf PHP extension by giving nested
-       * calls a unique recursion-depth appended name (for example, foo@1).
-       */
-      watchdog("Error in Raw Data: parent & child are both: %parent", array('%parent' => $parent));
-      return;
-    }
-
-    if (!isset($symbol_tab[$child])) {
-      $symbol_tab[$child] = array("ct" => $info["ct"]);
-      foreach ($metrics as $metric) {
-        $symbol_tab[$child][$metric] = $info[$metric];
-      }
-    }
-    else {
-      // increment call count for this child
-      $symbol_tab[$child]["ct"] += $info["ct"];
-
-      // update inclusive times/metric for this child
-      foreach ($metrics as $metric) {
-        $symbol_tab[$child][$metric] += $info[$metric];
-      }
-    }
-  }
-
-  return $symbol_tab;
-}
-
-/*
- * The list of possible metrics collected as part of XHProf that
- * require inclusive/exclusive handling while reporting.
- *
- * @author Kannan
- */
-function xhprof_get_possible_metrics() {
-  $possible_metrics = array("wt" => array("Wall", "microsecs", "walltime" ),
-        "ut" => array("User", "microsecs", "user cpu time" ),
-        "st" => array("Sys", "microsecs", "system cpu time"),
-        "cpu" => array("Cpu", "microsecs", "cpu time"),
-        "mu" => array("MUse", "bytes", "memory usage"),
-        "pmu" => array("PMUse", "bytes", "peak memory usage"),
-        "samples" => array("Samples", "samples", "cpu time")
-  );
- return $possible_metrics;
-}
diff --git a/xhprof.info b/xhprof.info
deleted file mode 100644
index b0a1d7c..0000000
--- a/xhprof.info
+++ /dev/null
@@ -1,6 +0,0 @@
-name = xhprof
-description = Provides code profiling with XHProf integration.
-package = Development
-core = 8.x
-
-configure = admin/config/development/xhprof
diff --git a/xhprof.info.yml b/xhprof.info.yml
new file mode 100644
index 0000000..b06b89b
--- /dev/null
+++ b/xhprof.info.yml
@@ -0,0 +1,7 @@
+name: XHProf
+type: module
+description: 'Provides code profiling with XHProf integration.'
+package: Development
+core: 8.x
+
+configure: admin/config/development/xhprof
diff --git a/xhprof.libraries.yml b/xhprof.libraries.yml
new file mode 100644
index 0000000..d211226
--- /dev/null
+++ b/xhprof.libraries.yml
@@ -0,0 +1,7 @@
+xhprof:
+  version: 1.0
+  css:
+    component:
+      css/xhprof.css: {}
+  js:
+    js/xhprof.js: {}
diff --git a/xhprof.links.menu.yml b/xhprof.links.menu.yml
new file mode 100644
index 0000000..8490b5e
--- /dev/null
+++ b/xhprof.links.menu.yml
@@ -0,0 +1,11 @@
+xhprof.admin_configure:
+  title: 'XHProf'
+  description: 'Configure XHProf profiler settings.'
+  route_name: xhprof.admin_configure
+  parent: system.admin_config_development
+
+xhprof.runs:
+  title: 'XHProf'
+  description: 'List XHProf collected profiles.'
+  route_name: xhprof.runs
+  parent: system.admin_reports
diff --git a/xhprof.module b/xhprof.module
index 5f121f2..2e22295 100644
--- a/xhprof.module
+++ b/xhprof.module
@@ -3,48 +3,6 @@
 define('XHPROF_PATH', 'admin/reports/xhprof');
 
 /**
- * Implementation of hook_menu().
- */
-function xhprof_menu() {
-  $items = array();
-  $items[XHPROF_PATH] = array(
-    'title' => 'XHProf runs',
-    'page callback' => 'xhprof_run_list',
-    'access arguments' => array('access xhprof data'),
-    'description' => 'View XHProf profiling data.',
-  );
-  $items[XHPROF_PATH . '/%'] = array(
-    'title' => 'XHProf view',
-    'page callback' => 'xhprof_display_page',
-    'page arguments' => array(3),
-    'access arguments' => array('access xhprof data'),
-  );
-  $items[XHPROF_PATH . '/diff/%/%'] = array(
-    'title' => 'XHProf view',
-    'page callback' => 'xhprof_display_diff_page',
-    'page arguments' => array(4, 5),
-    'access arguments' => array('access xhprof data'),
-  );
-  $items[XHPROF_PATH . '/%/symbol/%'] = array(
-    'title' => 'XHProf view',
-    'page callback' => 'xhprof_display_page',
-    'page arguments' => array(3, 5),
-    'access arguments' => array('access xhprof data'),
-  );
-  $items['admin/config/development/xhprof'] = array(
-    'title' => 'XHProf settings',
-    'description' =>  'Configure XHProf profiler settings.',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('xhprof_admin_settings'),
-    'file' => 'xhprof.admin.inc',
-    'access arguments' => array('administer site configuration'),
-  );
-
-  return $items;
-}
-
-
-/**
  * Implementation of hook_permission().
  */
 function xhprof_permission() {
@@ -54,439 +12,3 @@ function xhprof_permission() {
     ),
   );
 }
-
-/**
- * Implementation of hook_theme()
- */
-function xhprof_theme() {
-  return array(
-    'xhprof_overall_summary' => array(
-      'variables' => array('totals' => NULL, 'possible_metrics' => NULL, 'metrics' => NULL, 'display_calls' => NULL),
-    ),
-    'xhprof_run_table' => array(
-      'variables' => array('stats' => NULL, 'totals' => NULL, 'url_params' => NULL, 'title' => NULL, 'flat_data' => NULL, 'limit' => NULL),
-    ),
-  );
-}
-
-/**
- * Implementation of hook_views_api().
- */
-function xhprof_views_api() {
-  return array(
-    'api' => 3,
-    'path' => drupal_get_path('module', 'xhprof'),
-  );
-}
-
-function xhprof_require() {
-  require_once(dirname(__FILE__) . '/xhprof.inc');
-}
-
-/**
- * Conditionally enable XHProf profiling.
- */
-function xhprof_xhprof_enable() {
-  if (xhprof_is_enabled()) {
-    xhprof_require();
-
-    // @todo: consider a variable per-flag instead.
-    xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY);
-    return TRUE;
-  }
-  else {
-    return FALSE;
-  }
-}
-
-/**
- * Check whether XHProf should be enabled for the current request.
- */
-function xhprof_is_enabled() {
-  $enabled = FALSE;
-  if (extension_loaded('xhprof') && variable_get('xhprof_enabled', FALSE)) {
-    $enabled = TRUE;
-    if (arg(0) == 'admin' && variable_get('xhprof_disable_admin_paths', TRUE)) {
-      $enabled = FALSE;
-    }
-    $interval = variable_get('xhprof_interval', 1);
-    if ($interval && mt_rand(1, $interval) % $interval != 0) {
-      $enabled = FALSE;
-    }
-  }
-  return $enabled;
-}
-
-/**
- * Implementation of hook_boot(). Runs even for cached pages.
- */
-function xhprof_boot() {
-  // Initialize XHProf.
-  if (xhprof_xhprof_enable()) {
-    drupal_register_shutdown_function('xhprof_shutdown');
-  }
-}
-
-/**
- * See xhprof_start() which registers this function as a shutdown function.
- */
-function xhprof_shutdown() {
-  global $xhprof_run_id;
-
-  if (xhprof_is_enabled() && $xhprof_run_id = xhprof_shutdown_xhprof()) {
-    // Register the real shutdown function so it runs later than other shutdown functions.
-    drupal_register_shutdown_function('xhprof_shutdown_real');
-
-    if (function_exists('drush_log')) {
-      drush_log('xhprof link: ' . xhprof_link($xhprof_run_id, 'url'), 'notice');
-    }
-  }
-}
-
-/**
- * See xhprof_shutdown() which registers this function as a shutdown function. Displays xhprof link in the footer.
- */
-function xhprof_shutdown_real() {
-  global $user;
-
-  // Try not to break non html pages.
-  if (function_exists('drupal_get_http_header')) {
-    $header = drupal_get_http_header('content-type');
-    if ($header) {
-      $formats = array('xml', 'javascript', 'json', 'plain', 'image', 'application', 'csv', 'x-comma-separated-values');
-      foreach ($formats as $format) {
-        if (strstr($header, $format)) {
-          return;
-        }
-      }
-    }
-  }
-
-  $output = $txt = '';
-  if (isset($user) && user_access('access xhprof data')) {
-    $output .= '<div class="xhprof-ui">' . xhprof_link($GLOBALS['xhprof_run_id']) . '</div>';
-  }
-  if ($output) {
-    print $output;
-  }
-}
-
-function xhprof_shutdown_xhprof() {
-  $namespace = variable_get('site_name', '');  // namespace for your application
-  $xhprof_data = xhprof_disable();
-  $class = variable_get('xhprof_default_class', 'Drupal\xhprof\XHProfRunsFile');
-  $xhprof_runs = new $class();
-  return $xhprof_runs->save_run($xhprof_data, $namespace);
-}
-
-function xhprof_link($run_id, $type = 'link') {
-  $url  = url(XHPROF_PATH . '/' . $run_id, array(
-    'absolute' => TRUE,
-  ));
-  return $type == 'url' ? $url : l(t('XHProf output'), $url);
-}
-
-/**
- * Helper. Make sure expensive module_load_include() does not run needlessly.
- */
-function xhprof_include() {
-  static $included = FALSE;
-  if (!$included) {
-    module_load_include('inc', 'xhprof');
-    module_invoke_all('xhprof_load_classes');
-    $included = TRUE;
-  }
-}
-
-function _xhprof_get_object() {
-  static $xhprof_object = NULL;
-  if (empty($xhprof_object)) {
-    $class = variable_get('xhprof_default_class', 'Drupal\xhprof\XHProfRunsFile');
-    if (class_exists($class)) {
-      $xhprof_object = new $class();
-    }
-    else {
-      watchdog('xhprof', 'Unable to load default class %class!', array('%class' => $class), WATCHDOG_CRITICAL);
-    }
-  }
-  return $xhprof_object;
-}
-
-/**
- * List all available XHProf storage backends.
- */
-function xhprof_get_classes() {
-  xhprof_include();
-  $classes = array('Drupal\xhprof\XHProfRunsFile');
-  drupal_alter('xhprof_classes', $classes);
-  return $classes;
-}
-
-/**
- * Display list of saved XHProf runs.
- */
-function xhprof_run_list() {
-  global $pager_page_array, $pager_total, $pager_total_items;
-  xhprof_include();
-  $page = isset($_GET['page']) ? $_GET['page'] : '';
-  $element = 0;
-  $limit = 50;
-
-  $class = variable_get('xhprof_default_class', 'Drupal\xhprof\XHProfRunsFile');
-  $xhprof_runs_impl = new $class();
-  $pager_page_array = array($page);
-  $pager_total_items[$element] = $xhprof_runs_impl->getCount();
-  $pager_total[$element] = ceil($pager_total_items[$element] / $limit);
-  $pager_start = $page * 50;
-  $pager_end = $pager_start + 50;
-  $runs = $xhprof_runs_impl->getRuns(array(), $limit);
-
-  // Set the pager info in these globals since we need to fake them for
-  // theme_pager.
-  // Table attributes
-  $attributes = array('id' => 'xhprof-runs-table');
-
-  // Table header
-  $header = array();
-  $header[] = array('data' => t('View'));
-  $header[] = array('data' => t('Path'), 'field' => 'path');
-  $header[] = array('data' => t('Date'), 'field' => 'date', 'sort' => 'desc');
-
-  // Table rows
-  $rows = array();
-  foreach ($runs as $run) {
-    $row = array();
-    $link = XHPROF_PATH . '/' . $run['run_id'];
-    $row[] = array('data' => l($run['run_id'], $link));
-    $row[] = array('data' => isset($run['path']) ? $run['path'] : '');
-    $row[] = array('data' => format_date($run['date'], 'small'));
-    $rows[] = $row;
-  }
-
-  $output = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => $attributes));
-  $output .= theme('pager');
-  return $output;
-}
-
-/**
- * Get default URL parameters for XHProf.
- */
-function xhprof_param_defaults() {
-  // param name, its type, and default value
-  return array(
-    'run'        => '',
-    'wts'        => '',
-    'symbol'     => '',
-    'sort'       => 'wt', // wall time
-    'run1'       => '',
-    'run2'       => '',
-    'source'     => 'xhprof',
-    'all'        => 0,
-  );
-}
-
-/**
- * Page callback to display a XHProf run.
- */
-function xhprof_display_page($run_id, $symbol = NULL) {
-  drupal_add_css(drupal_get_path('module', 'xhprof') . '/xhprof.css');
-  return xhprof_display_run(array($run_id), $symbol);
-}
-
-/**
- * Page callback to display a difference of two XHProf runs.
- */
-function xhprof_display_diff_page($run1, $run2, $symbol = NULL) {
-  drupal_add_css(drupal_get_path('module', 'xhprof') . '/xhprof.css');
-  return xhprof_display_run(array($run1, $run2), $symbol = NULL);
-}
-
-/**
- * Display XHProf run report.
- */
-function xhprof_display_run($run_ids, $symbol = NULL) {
-  xhprof_require();
-
-  if (count($run_ids) === 1) {
-    $_GET['run'] = $run_ids[0];
-    $run_id = $run_ids[0];
-  }
-  else {
-    $_GET['run1'] = $run_ids[0];
-    $run1 = $run_ids[0];
-    $_GET['run2'] = $run_ids[1];
-    $run2 = $run_ids[1];
-  }
-  $source = variable_get('site_name', '');
-  $_GET['source'] = $source;
-
-  $url_params = xhprof_param_defaults();
-  $required_params = array('sort');
-  foreach ($url_params as $param => &$value) {
-    if (isset($_GET[$param])) {
-      $value = $_GET[$param];
-    }
-    elseif (!in_array($param, $required_params)) {
-      unset($url_params[$param]);
-    }
-  }
-  // Extract params here instead of making them globals. Gross, I know, but
-  // less gross than this was originally. Should make this less dumb in the
-  // future.
-  extract($url_params);
-
-  $class = variable_get('xhprof_default_class', 'Drupal\xhprof\XHProfRunsFile');
-  $xhprof_runs_impl = new $class();
-  $output = '';
-  if (isset($run_id)) {
-    // run may be a single run or a comma separate list of runs
-    // that'll be aggregated. If "wts" (a comma separated list
-    // of integral weights is specified), the runs will be
-    // aggregated in that ratio.
-    $runs_array = explode(",", $run_id);
-    if (isset($_GET['order'])) {
-      $sort = xhprof_stat_description($_GET['order'], TRUE);
-    }
-    if (count($runs_array) == 1) {
-      $xhprof_data = $xhprof_runs_impl->get_run($runs_array[0], $source, $description, $sort);
-    }
-    else {
-      if (!empty($wts)) {
-        $wts_array  = explode(",", $wts);
-      }
-      else {
-        $wts_array = NULL;
-      }
-      $data = xhprof_aggregate_runs($xhprof_runs_impl, $runs_array, $wts_array, $source, FALSE);
-      $xhprof_data = $data['raw'];
-      $description = $data['description'];
-    }
-    xhprof_init_metrics($xhprof_data, $symbol, $sort, FALSE);
-    $output .= xhprof_profiler_report($url_params, $symbol, $sort, $run_id, $description, $xhprof_data);
-  }
-  elseif ($run1 && $run2) {
-    // Diff report for two runs.
-    $xhprof_data1 = $xhprof_runs_impl->get_run($run1, $source, $description1);
-    $xhprof_data2 = $xhprof_runs_impl->get_run($run2, $source, $description2);
-    // Initialize what metrics we'll display based on data in Run2
-    $output .= xhprof_init_metrics($xhprof_data2, $symbol, $sort, TRUE);
-    $output .= xhprof_profiler_report($url_params, $symbol, $sort, $run1, $description1, $xhprof_data1, $run2, $description2, $xhprof_data2);
-  }
-  else {
-    $output .= "No XHProf runs specified in the URL.";
-  }
-
-  return $output;
-}
-
-function xhprof_scandir($dir, $source) {
-  if (is_dir($dir)) {
-    $runs = array();
-    foreach (glob("$dir/*.$source") as $file) {
-      list($run, $source) = explode('.', basename($file));
-      $runs[] = array(
-        'run_id' => $run,
-        'source' => $source,
-        'basename' => htmlentities(basename($file)),
-        'date' => date("Y-m-d H:i:s", filemtime($file)),
-      );
-    }
-  }
-  return array_reverse($runs);
-}
-
-/**
- * Theme function to display XHProf run summary.
- */
-function theme_xhprof_overall_summary($variables) {
-  $output = '';
-  // Extract variables: $totals, $possible_metrics, $metrics, $display_calls;
-  extract($variables);
-  $rows = array();
-  foreach ($metrics as $metric) {
-    $rows[] = array('<strong>Total ' . xhprof_stat_description($metric) . ':</strong>', number_format($totals[$metric]) . " " . $possible_metrics[$metric][1]);
-  }
-
-  if ($display_calls) {
-    $rows[] = array("<strong>Number of function xhprof_Calls:</strong>", number_format($totals['ct']));
-  }
-  $header = array(array('data' => 'Overall Summary', 'colspan' => 2));
-  $output .= theme('table', array('header' => $header, 'rows' => $rows));
-  return $output;
-}
-
-/**
- * Theme function to display list of XHProf function calls.
- */
-function theme_xhprof_run_table($variables) {
-  // Extract variables: $stats, $totals, $url_params, $title, $flat_data, $limit.
-  extract($variables);
-  global $base_path;
-
-  // Table attributes
-  $attributes = array('id' => 'xhprof-run-table');
-  $output = '';
-  // Headers.
-  $header = array();
-  foreach ($stats as $stat) {
-    $desc = xhprof_stat_description($stat);
-    if (array_key_exists($stat, xhprof_sortable_columns($stat))) {
-      if (isset($_GET['sort']) && $stat == $_GET['sort']) {
-        $header_desc = l(t($desc), current_path(), array('query' => array('sort' => $stat), t($desc)));
-        $header[] = array('data' => t($header_desc) . theme('tablesort_indicator', array('style' => 'desc')));
-      }
-      else {
-        $header_desc = l(t($desc), current_path(), array('query' => array('sort' => $stat), t($desc)));
-        $header[] = array('data' => t($header_desc));
-      }
-    }
-    else {
-      $header[] = array('data' => t($desc));
-    }
-  }
-
-  $path = url(XHPROF_PATH . '/' . $run1, array('absolute' => TRUE)) . '/symbol/';
-  // Table rows
-  $rows = array();
-  $i = 0;
-  foreach ($flat_data as $data) {
-    $row = array(
-      array('data' => l($data["fn"], $path . $data["fn"])),
-      array('data' => xhprof_print_num($data['ct'], NULL, TRUE), 'class' => 'xhprof_micro'),
-      array('data' => xhprof_print_pct($data['ct'], $totals['ct'], TRUE), 'class' => 'xhprof_percent'),
-      array('data' => xhprof_print_num($data['wt'], NULL, TRUE), 'class' => 'xhprof_micro'),
-      array('data' => xhprof_print_pct($data['wt'], $totals['wt'], TRUE), 'class' => 'xhprof_percent'),
-      array('data' => xhprof_print_num($data['excl_wt'], NULL, TRUE), 'class' => 'xhprof_micro'),
-      array('data' => xhprof_print_pct($data['excl_wt'], $totals['wt'], TRUE), 'class' => 'xhprof_percent'),
-      array('data' => xhprof_print_num($data['cpu'], NULL, TRUE), 'class' => 'xhprof_micro'),
-      array('data' => xhprof_print_pct($data['cpu'], $totals['cpu'], TRUE), 'class' => 'xhprof_percent'),
-      array('data' => xhprof_print_num($data['excl_cpu'], NULL, TRUE), 'class' => 'xhprof_micro'),
-      array('data' => xhprof_print_pct($data['excl_cpu'], $totals['cpu'], TRUE), 'class' => 'xhprof_percent'),
-      array('data' => xhprof_print_num($data['mu'], NULL, TRUE), 'class' => 'xhprof_micro'),
-      array('data' => xhprof_print_pct($data['mu'], $totals['mu'], TRUE), 'class' => 'xhprof_percent'),
-      array('data' => xhprof_print_num($data['excl_mu'], NULL, TRUE), 'class' => 'xhprof_micro'),
-      array('data' => xhprof_print_pct($data['excl_mu'], $totals['mu'], TRUE), 'class' => 'xhprof_percent'),
-      array('data' => xhprof_print_num($data['pmu'], NULL, TRUE), 'class' => 'xhprof_micro'),
-      array('data' => xhprof_print_pct($data['pmu'], $totals['pmu'], TRUE), 'class' => 'xhprof_percent'),
-      array('data' => xhprof_print_num($data['excl_pmu'], NULL, TRUE), 'class' => 'xhprof_micro'),
-      array('data' => xhprof_print_pct($data['excl_pmu'], $totals['pmu'], TRUE), 'class' => 'xhprof_percent'),
-    );
-    $rows[] = $row;
-    $i++;
-    if ($limit && $i >= $limit) break;
-  }
-
-  $size = count($flat_data);
-  if (!$limit) {
-    // no limit
-    $limit = $size;
-    $display_link = "";
-  }
-  else {
-    $display_link = l(" [ <strong class=bubble>display all </strong>]", current_path(), array('query' => xhprof_array_set($url_params, 'all', 1), 'html' => TRUE));
-  }
-  $output .= "<h3 align=center>$title $display_link</h3><br>";
-  $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => $attributes));
-
-  return $output;
-}
diff --git a/xhprof.routing.yml b/xhprof.routing.yml
new file mode 100644
index 0000000..52966bb
--- /dev/null
+++ b/xhprof.routing.yml
@@ -0,0 +1,54 @@
+# configure xhprof
+xhprof.admin_configure:
+  path: '/admin/config/development/xhprof'
+  defaults:
+    _form: 'Drupal\xhprof\Form\ConfigForm'
+    _title: 'Configure XHProf'
+  requirements:
+    _permission: 'access xhprof data'
+
+xhprof.runs:
+  path: '/admin/reports/xhprof'
+  defaults:
+    _content: 'Drupal\xhprof\Controller\XHProfController::runsAction'
+    _title: 'XHProf runs'
+  requirements:
+    _permission: 'access xhprof data'
+
+xhprof.view:
+  path: '/admin/reports/xhprof/{run}'
+  defaults:
+    _content: 'Drupal\xhprof\Controller\XHProfController::viewAction'
+    _title: 'XHProf view'
+  options:
+    parameters:
+      run:
+        type: 'xhprof:run_id'
+  requirements:
+    _permission: 'access xhprof data'
+
+xhprof.symbol:
+  path: '/admin/reports/xhprof/{run}/symbol/{symbol}'
+  defaults:
+    _content: 'Drupal\xhprof\Controller\XHProfController::symbolAction'
+    _title: 'XHProf view'
+  options:
+    parameters:
+      run:
+        type: 'xhprof:run_id'
+  requirements:
+    _permission: 'access xhprof data'
+
+xhprof.diff:
+  path: '/admin/reports/xhprof/diff/{run1}/{run2}'
+  defaults:
+    _content: 'Drupal\xhprof\Controller\XHProfController::diffAction'
+    _title: 'XHProf view'
+  options:
+    parameters:
+      run1:
+        type: 'xhprof:run_id'
+      run2:
+        type: 'xhprof:run_id'
+  requirements:
+    _permission: 'access xhprof data'
diff --git a/xhprof.services.yml b/xhprof.services.yml
new file mode 100644
index 0000000..6ccc82d
--- /dev/null
+++ b/xhprof.services.yml
@@ -0,0 +1,38 @@
+services:
+
+  xhprof.file_storage:
+    class: Drupal\xhprof\XHProfLib\Storage\FileStorage
+    tags:
+      - { name: xhprof_storage }
+
+  xhprof.storage:
+    class: Drupal\xhprof\XHProfLib\Storage\StorageInterface
+    factory_class: Drupal\xhprof\XHProfLib\Storage\StorageFactory
+    factory_method: getStorage
+    arguments: ['@config.factory', '@service_container']
+
+  xhprof.storage_manager:
+    class: Drupal\xhprof\XHProfLib\Storage\StorageManager
+
+  xhprof.report_engine:
+    class: Drupal\xhprof\XHProfLib\Report\ReportEngine
+
+  xhprof.matcher:
+    class: Drupal\xhprof\RequestMatcher\XHProfRequestMatcher
+    arguments: ['@config.factory', '@path.matcher']
+
+  xhprof.xhprof:
+    class: Drupal\xhprof\XHProfLib\XHProf
+    arguments: ['@config.factory', '@xhprof.storage', '@xhprof.matcher']
+
+  xhprof.xhprof_event_subscriber:
+    class: Drupal\xhprof\EventSubscriber\XHProfEventSubscriber
+    arguments: ['@xhprof.xhprof', '@current_user', '@module_handler']
+    tags:
+      - { name: event_subscriber }
+
+  xhprof.run_converter:
+    class: Drupal\xhprof\Routing\RunConverter
+    arguments: ['@xhprof.xhprof', '@config.factory']
+    tags:
+      - { name: paramconverter }
