diff --git a/src/applications/differential/controller/DifferentialRevisionStatsController.php b/src/applications/differential/controller/DifferentialRevisionStatsController.php
index 1389ba7d66..613ba1e4b2 100644
--- a/src/applications/differential/controller/DifferentialRevisionStatsController.php
+++ b/src/applications/differential/controller/DifferentialRevisionStatsController.php
@@ -1,166 +1,183 @@
 <?php
 
 /*
  * Copyright 2012 Facebook, Inc.
  *
  * 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.
  */
 
 final class DifferentialRevisionStatsController extends DifferentialController {
   private $filter;
 
   private function loadRevisions($phid) {
     $table = new DifferentialRevision();
     $conn_r = $table->establishConnection('r');
     $rows = queryfx_all(
       $conn_r,
       'SELECT revisions.* FROM %T revisions ' .
       'JOIN %T comments ON comments.revisionID = revisions.id ' .
       'JOIN (' .
       ' SELECT revisionID FROM %T WHERE objectPHID = %s ' .
       ' UNION ALL ' .
       ' SELECT id from differential_revision WHERE authorPHID = %s) rel ' .
       'ON (comments.revisionID = rel.revisionID)' .
       'WHERE comments.action = %s' .
       'AND comments.authorPHID = %s',
       $table->getTableName(),
       id(new DifferentialComment())->getTableName(),
       DifferentialRevision::RELATIONSHIP_TABLE,
       $phid,
       $phid,
       $this->filter,
       $phid
     );
     return $table->loadAllFromArray($rows);
   }
 
   private function loadComments($phid) {
     $table = new DifferentialComment();
     $conn_r = $table->establishConnection('r');
     $rows = queryfx_all(
       $conn_r,
       'SELECT comments.* FROM %T comments ' .
       'JOIN (' .
       ' SELECT revisionID FROM %T WHERE objectPHID = %s ' .
       ' UNION ALL ' .
       ' SELECT id from differential_revision WHERE authorPHID = %s) rel ' .
       'ON (comments.revisionID = rel.revisionID)' .
       'WHERE comments.action = %s' .
       'AND comments.authorPHID = %s',
       $table->getTableName(),
       DifferentialRevision::RELATIONSHIP_TABLE,
       $phid,
       $phid,
       $this->filter,
       $phid
     );
 
     return $table->loadAllFromArray($rows);
   }
+
+  private function loadDiffs(array $revisions) {
+    if (!$revisions) {
+      return array();
+    }
+
+    $diff_teml = new DifferentialDiff();
+    $diffs = $diff_teml->loadAllWhere(
+      'revisionID in (%Ld)',
+      array_keys($revisions)
+    );
+    return $diffs;
+  }
+
   public function willProcessRequest(array $data) {
     $this->filter = idx($data, 'filter');
   }
 
   public function processRequest() {
     $request = $this->getRequest();
     $user = $request->getUser();
 
     if ($request->isFormPost()) {
       $phid_arr = $request->getArr('view_user');
       $view_target = head($phid_arr);
       return id(new AphrontRedirectResponse())
         ->setURI($request->getRequestURI()->alter('phid', $view_target));
     }
 
     $params = array_filter(
       array(
         'phid' => $request->getStr('phid'),
       ));
 
     // Fill in the defaults we'll actually use for calculations if any
     // parameters are missing.
     $params += array(
       'phid' => $user->getPHID(),
     );
 
     $side_nav = new AphrontSideNavFilterView();
     $side_nav->setBaseURI(id(new PhutilURI('/differential/stats/'))
                           ->alter('phid', $params['phid']));
     foreach (array(
                DifferentialAction::ACTION_CLOSE,
                DifferentialAction::ACTION_ACCEPT,
                DifferentialAction::ACTION_REJECT,
                DifferentialAction::ACTION_UPDATE,
                DifferentialAction::ACTION_COMMENT,
              ) as $action) {
       $verb = ucfirst(DifferentialAction::getActionPastTenseVerb($action));
       $side_nav->addFilter($action, $verb);
     }
     $this->filter =
       $side_nav->selectFilter($this->filter,
                               DifferentialAction::ACTION_CLOSE);
 
     $panels = array();
     $handles = id(new PhabricatorObjectHandleData(array($params['phid'])))
                   ->loadHandles();
 
     $filter_form = id(new AphrontFormView())
       ->setAction('/differential/stats/'.$this->filter.'/')
       ->setUser($user);
 
     $filter_form->appendChild(
       $this->renderControl($params['phid'], $handles));
     $filter_form->appendChild(id(new AphrontFormSubmitControl())
                               ->setValue('Filter Revisions'));
 
     $side_nav->appendChild($filter_form);
 
     $comments = $this->loadComments($params['phid']);
     $revisions = $this->loadRevisions($params['phid']);
+    $diffs = $this->loadDiffs($revisions);
 
     $panel = new AphrontPanelView();
     $panel->setHeader('Differential rate analysis');
     $panel->appendChild(
       id(new DifferentialRevisionStatsView())
       ->setComments($comments)
+      ->setFilter($this->filter)
       ->setRevisions($revisions)
+      ->setDiffs($diffs)
       ->setUser($user));
     $panels[] = $panel;
 
     foreach ($panels as $panel) {
       $side_nav->appendChild($panel);
     }
 
     return $this->buildStandardPageResponse(
       $side_nav,
       array(
         'title' => 'Differential statistics',
       ));
   }
 
   private function renderControl($view_phid, $handles) {
     $value = array();
     if ($view_phid) {
       $value = array(
         $view_phid => $handles[$view_phid]->getFullName(),
       );
     }
     return id(new AphrontFormTokenizerControl())
       ->setDatasource('/typeahead/common/users/')
       ->setLabel('View User')
       ->setName('view_user')
       ->setValue($value)
       ->setLimit(1);
   }
 
 }
diff --git a/src/applications/differential/view/DifferentialRevisionStatsView.php b/src/applications/differential/view/DifferentialRevisionStatsView.php
index 66eb51aecf..a455a1aeae 100644
--- a/src/applications/differential/view/DifferentialRevisionStatsView.php
+++ b/src/applications/differential/view/DifferentialRevisionStatsView.php
@@ -1,156 +1,234 @@
 <?php
 
 /*
  * Copyright 2012 Facebook, Inc.
  *
  * 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.
  */
 
 /**
  * Render some distracting statistics on revisions
  */
 final class DifferentialRevisionStatsView extends AphrontView {
   private $comments;
   private $revisions;
+  private $diffs;
   private $user;
+  private $filter;
 
   public function setRevisions(array $revisions) {
     assert_instances_of($revisions, 'DifferentialRevision');
     $this->revisions = $revisions;
     return $this;
   }
 
   public function setComments(array $comments) {
     assert_instances_of($comments, 'DifferentialComment');
     $this->comments = $comments;
     return $this;
   }
 
+  public function setDiffs(array $diffs) {
+    assert_instances_of($diffs, 'DifferentialDiff');
+    $this->diffs = $diffs;
+    return $this;
+  }
+
+  public function setFilter($filter) {
+    $this->filter = $filter;
+    return $this;
+  }
+
   public function setUser($user) {
     $this->user = $user;
     return $this;
   }
 
   public function render() {
     $user = $this->user;
     if (!$user) {
       throw new Exception("Call setUser() before render()!");
     }
 
     $id_to_revision_map = array();
     foreach ($this->revisions as $rev) {
       $id_to_revision_map[$rev->getID()] = $rev;
     }
     $revisions_seen = array();
 
     $dates = array();
     $counts = array();
     $lines = array();
-    $boosts = array();
     $days_with_diffs = array();
     $count_active = array();
+    $response_time = array();
+    $response_count = array();
     $now = time();
     $row_array = array();
 
     foreach (array(
                '1 week', '2 weeks', '3 weeks',
                '1 month', '2 months', '3 months', '6 months', '9 months',
                '1 year', '18 months',
                '2 years', '3 years', '4 years', '5 years',
              ) as $age) {
       $dates[$age] = strtotime($age . ' ago 23:59:59');
       $counts[$age] = 0;
       $lines[$age] = 0;
       $count_active[$age] = 0;
+      $response_time[$age] = array();
+    }
+
+    $revision_diffs_map = mgroup($this->diffs, 'getRevisionID');
+    foreach ($revision_diffs_map as $revision_id => $diffs) {
+      $revision_diffs_map[$revision_id] = msort($diffs, 'getID');
     }
 
     foreach ($this->comments as $comment) {
-      $rev_date = $comment->getDateCreated();
+      $comment_date = $comment->getDateCreated();
 
-      $day = phabricator_date($rev_date, $user);
+      $day = phabricator_date($comment_date, $user);
       $old_daycount = idx($days_with_diffs, $day, 0);
       $days_with_diffs[$day] = $old_daycount + 1;
 
       $rev_id = $comment->getRevisionID();
 
       if (idx($revisions_seen, $rev_id)) {
-        continue;
+        $revision_seen = true;
+        $rev = null;
+      } else {
+        $revision_seen = false;
+        $rev = $id_to_revision_map[$rev_id];
+        $revisions_seen[$rev_id] = true;
       }
-      $rev = $id_to_revision_map[$rev_id];
-      $revisions_seen[$rev_id] = true;
 
       foreach ($dates as $age => $cutoff) {
-        if ($cutoff >= $rev_date) {
+        if ($cutoff >= $comment_date) {
           continue;
         }
-        if ($rev) {
-          $lines[$age] += $rev->getLineCount();
+
+        if (!$revision_seen) {
+          if ($rev) {
+            $lines[$age] += $rev->getLineCount();
+          }
+          $counts[$age]++;
+          if (!$old_daycount) {
+            $count_active[$age]++;
+          }
         }
-        $counts[$age]++;
-        if (!$old_daycount) {
-          $count_active[$age]++;
+
+        $diffs = $revision_diffs_map[$rev_id];
+        $target_diff = $this->findTargetDiff($diffs, $comment);
+        if ($target_diff) {
+          $response_time[$age][] =
+            $comment_date - $target_diff->getDateCreated();
         }
       }
     }
 
     $old_count = 0;
     foreach (array_reverse($dates) as $age => $cutoff) {
       $weeks = ceil(($now - $cutoff) / (60 * 60 * 24)) / 7;
       if ($old_count == $counts[$age] && count($row_array) == 1) {
         unset($dates[last_key($row_array)]);
         $row_array = array();
       }
       $old_count = $counts[$age];
 
       $row_array[$age] = array(
         'Revisions per week' => number_format($counts[$age] / $weeks, 2),
         'Lines per week' => number_format($lines[$age] / $weeks, 1),
         'Active days per week' =>
           number_format($count_active[$age] / $weeks, 1),
         'Revisions' => number_format($counts[$age]),
         'Lines' => number_format($lines[$age]),
         'Lines per diff' => number_format($lines[$age] /
                                           ($counts[$age] + 0.0001)),
         'Active days' => number_format($count_active[$age]),
       );
+
+      switch ($this->filter) {
+        case DifferentialAction::ACTION_CLOSE:
+        case DifferentialAction::ACTION_UPDATE:
+        case DifferentialAction::ACTION_COMMENT:
+          break;
+        case DifferentialAction::ACTION_ACCEPT:
+        case DifferentialAction::ACTION_REJECT:
+          $count = count($response_time[$age]);
+          if ($count) {
+            rsort($response_time[$age]);
+            $median = $response_time[$age][round($count / 2) - 1];
+            $average = array_sum($response_time[$age]) / $count;
+          } else {
+            $median = 0;
+            $average = 0;
+          }
+
+          $row_array[$age]['Response hours (median|average)'] =
+            number_format($median / 3600, 1).
+            ' | '.
+            number_format($average / 3600, 1);
+          break;
+      }
     }
 
     $rows = array();
     $row_names = array_keys(head($row_array));
     foreach ($row_names as $row_name) {
       $rows[] = array($row_name);
     }
     foreach (array_keys($dates) as $age) {
       $i = 0;
       foreach ($row_names as $row_name) {
         $rows[$i][] = idx(idx($row_array, $age), $row_name, '-');
         ++$i;
       }
     }
 
     $table = new AphrontTableView($rows);
     $table->setColumnClasses(
       array(
         'wide pri',
       ));
 
     $table->setHeaders(
       array_merge(
         array(
           'Metric',
         ),
         array_keys($dates)));
 
     return $table->render();
   }
+
+  private function findTargetDiff(array $diffs,
+                                         DifferentialComment $comment) {
+    switch ($this->filter) {
+      case DifferentialAction::ACTION_CLOSE:
+      case DifferentialAction::ACTION_UPDATE:
+      case DifferentialAction::ACTION_COMMENT:
+        return null;
+      case DifferentialAction::ACTION_ACCEPT:
+      case DifferentialAction::ACTION_REJECT:
+        $result = head($diffs);
+        foreach ($diffs as $diff) {
+          if ($diff->getDateCreated() >= $comment->getDateCreated()) {
+            break;
+          }
+          $result = $diff;
+        }
+
+        return $result;
+    }
+  }
 }