You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@flink.apache.org by uc...@apache.org on 2017/01/10 08:48:37 UTC

[01/11] flink git commit: [FLINK-4410] [runtime-web] Rebuild JS/HTML files

Repository: flink
Updated Branches:
  refs/heads/release-1.2 7348424d9 -> 88c7de497


http://git-wip-us.apache.org/repos/asf/flink/blob/88c7de49/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node-list.checkpoints.html
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node-list.checkpoints.html b/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node-list.checkpoints.html
index 690881c..66906dd 100644
--- a/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node-list.checkpoints.html
+++ b/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node-list.checkpoints.html
@@ -17,32 +17,15 @@ See the License for the specific language governing permissions and
 limitations under the License.
 
 -->
-<div ng-if="!jobCheckpointStats">
-  <p><em>No checkpoints</em></p>
-</div>
-<div ng-if="jobCheckpointStats">
-  <h2>Overview</h2>
-  <div ng-include=" 'partials/jobs/job.plan.node.checkpoints.job.html' "></div>
-  <h2>Operators</h2>
-  <table class="table table-body-hover table-clickable table-activable">
-    <thead>
-      <tr>
-        <th>Name</th>
-        <th>Status</th>
-      </tr>
-    </thead>
-    <tbody ng-repeat="v in job.vertices" ng-class="{ active: v.id == nodeid }" ng-click="v.id == nodeid || changeNode(v.id)">
-      <tr ng-if="v.type == 'regular'">
-        <td>{{ v.name | humanizeText }}</td>
-        <td>
-          <bs-label status="{{v.status}}">{{v.status}}</bs-label>
-        </td>
-      </tr>
-      <tr ng-if="nodeid &amp;&amp; v.id == nodeid">
-        <td colspan="10">
-          <div ng-include=" 'partials/jobs/job.plan.node.checkpoints.operator.html' "></div>
-        </td>
-      </tr>
-    </tbody>
-  </table>
+<div class="split">
+  <nav class="navbar navbar-default navbar-secondary-additional">
+    <ul class="nav nav-tabs">
+      <li ui-sref-active="active"><a ui-sref=".overview">Overview</a></li>
+      <li ui-sref-active="active"><a ui-sref=".history">History</a></li>
+      <li ui-sref-active="active"><a ui-sref=".summary">Summary</a></li>
+      <li ui-sref-active="active"><a ui-sref=".config">Configuration</a></li>
+      <li ng-if="checkpointDetails.id != -1" class="active"><a>Details for Checkpoint {{ checkpointDetails.id }}</a></li>
+    </ul>
+  </nav>
+  <div id="checkpoints-view" ui-view="checkpoints-view" class="clean checkpoints-view"></div>
 </div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flink/blob/88c7de49/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoint-history.html
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoint-history.html b/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoint-history.html
new file mode 100644
index 0000000..aa1cc2e
--- /dev/null
+++ b/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoint-history.html
@@ -0,0 +1,57 @@
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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.
+
+-->
+<div ng-if="checkpointStats">
+  <h3>History</h3>
+</div>
+<!--  h3 History-->
+<!--  table.table.table-body-hover.table-clickable-->
+<!--    thead-->
+<!--      tr-->
+<!--        td #[strong ID]-->
+<!--        td #[strong Status]-->
+<!--        td #[strong Acknowledged]-->
+<!--        td #[strong Trigger Time]-->
+<!--        td #[strong Latest Ack]-->
+<!--        td #[strong Duration]-->
+<!--        td #[strong State Size]-->
+<!--        td-->
+<!--    tbody(ng-if="checkpointStats['checkpoints'] && checkpointStats['checkpoints'].length > 0")-->
+<!--      tr(ng-repeat="checkpoint in checkpointStats['checkpoints']" ng-class="{'bg-warning': checkpoint['status'] == 'FAILED'}")-->
+<!--        td {{ checkpoint['id'] }}-->
+<!--        td(ng-if="checkpoint['status'] == 'IN_PROGRESS'") #[i(aria-hidden="true").fa.fa-circle-o-notch.fa-spin.fa-fw] In Progress-->
+<!--        td(ng-if="checkpoint['status'] == 'COMPLETED'") #[i(aria-hidden="true").fa.fa-check]-->
+<!--        td(ng-if="checkpoint['status'] == 'FAILED'") #[i(aria-hidden="true").fa.fa-remove] Failed-->
+<!--        td {{ checkpoint['subtasks']['acknowledged'] }}/{{ checkpoint['subtasks']['count'] }}-->
+<!--          = ' '-->
+<!--          span(ng-if="checkpoint['status'] == 'IN_PROGRESS'") ({{ checkpoint['subtasks']['acknowledged']/checkpoint['subtasks']['count'] | percentage }})-->
+<!--        td {{ checkpoint['trigger_timestamp'] | amDateFormat:'H:mm:ss' }}-->
+<!--        td {{ checkpoint['latest_ack_timestamp'] | amDateFormat:'H:mm:ss' }}-->
+<!--        td {{ checkpoint['duration'] | humanizeDuration }}-->
+<!--        td {{ checkpoint['size'] | humanizeBytes }}-->
+<!--        td-->
+<!--          //a.btn.btn-default(ng-click="toggleCheckpointDetails(checkpoint['id'])")-->
+<!--          a.btn.btn-default(ui-sref="^.details({checkpointId: checkpoint['id']})")-->
+<!--            i(aria-hidden="true").fa.fa-chevron-right-->
+<!--            = ' '-->
+<!--            | <strong>More details</strong>-->
+<!--    tbody(ng-if="!checkpointStats['checkpoints'] || checkpointStats['checkpoints'].length == 0")-->
+<!--      tr-->
+<!--        td(colspan=5) No checkpoints triggered-->
+<!---->
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flink/blob/88c7de49/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.config.html
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.config.html b/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.config.html
new file mode 100644
index 0000000..9ef20a6
--- /dev/null
+++ b/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.config.html
@@ -0,0 +1,59 @@
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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.
+
+-->
+<div ng-if="checkpointConfig">
+  <table class="table">
+    <thead>
+      <tr>
+        <td><strong>Option</strong></td>
+        <td><strong>Value</strong></td>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>Checkpointing Mode</td>
+        <td ng-if="checkpointConfig['mode'] == 'exactly_once'">Exactly Once</td>
+        <td ng-if="checkpointConfig['mode'] != 'exactly_once'">At Least Once</td>
+      </tr>
+      <tr>
+        <td>Interval</td>
+        <td ng-if="checkpointConfig['interval'] == '0x7fffffffffffffff'">Periodic checkpoints disabled</td>
+        <td ng-if="checkpointConfig['interval'] != '0x7fffffffffffffff'">{{ checkpointConfig['interval'] | humanizeDuration }}</td>
+      </tr>
+      <tr>
+        <td>Timeout</td>
+        <td>{{ checkpointConfig['timeout'] | humanizeDuration }}</td>
+      </tr>
+      <tr>
+        <td>Minimum Pause Between Checkpoints</td>
+        <td>{{ checkpointConfig['min_pause'] | humanizeDuration }}</td>
+      </tr>
+      <tr>
+        <td>Maximum Concurrent Checkpoints</td>
+        <td>{{ checkpointConfig['max_concurrent'] }}</td>
+      </tr>
+      <tr>
+        <td>Persist Checkpoints Externally</td>
+        <td ng-if="checkpointConfig['externalization']['enabled']">Enabled <span ng-if="checkpointConfig['externalization']['delete_on_cancellation']">(delete on cancellation)</span><span ng-if="!checkpointConfig['externalization']['delete_on_cancellation']">(retain on cancellation)</span>
+        </td>
+        <td ng-if="!checkpointConfig['externalization']['enabled']">Disabled</td>
+      </tr>
+    </tbody>
+  </table>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flink/blob/88c7de49/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.counts.html
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.counts.html b/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.counts.html
new file mode 100644
index 0000000..3b09269
--- /dev/null
+++ b/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.counts.html
@@ -0,0 +1,51 @@
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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.
+
+-->
+<div ng-if="checkpointStats">
+  <table class="table table-responsive">
+    <thead>
+      <tr>
+        <td width="30%"><strong>Checkpoint Event</strong></td>
+        <td width="70%"><strong>Count</strong></td>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>Triggered</td>
+        <td>{{ checkpointStats['counts']['total'] }}</td>
+      </tr>
+      <tr>
+        <td>In Progress</td>
+        <td>{{ checkpointStats['counts']['in_progress'] }}</td>
+      </tr>
+      <tr>
+        <td>Completed</td>
+        <td>{{ checkpointStats['counts']['completed'] }}</td>
+      </tr>
+      <tr>
+        <td>Failed</td>
+        <td>{{ checkpointStats['counts']['failed'] }}</td>
+      </tr>
+      <tr>
+        <td>Restored</td>
+        <td>{{ checkpointStats['counts']['restored'] }}</td>
+      </tr>
+    </tbody>
+  </table>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flink/blob/88c7de49/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.details.html
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.details.html b/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.details.html
new file mode 100644
index 0000000..d7a3e54
--- /dev/null
+++ b/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.details.html
@@ -0,0 +1,171 @@
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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.
+
+-->
+<div ng-if="checkpoint">
+  <table class="table table-inner">
+    <thead>
+      <tr>
+        <td><strong>ID</strong></td>
+        <td><strong>Status</strong></td>
+        <td><strong>Acknowledged</strong></td>
+        <td><strong>Trigger Time</strong></td>
+        <td><strong>Latest Acknowledgement</strong></td>
+        <td ng-if="checkpoint['failure_timestamp']"><strong>Failure Time</strong></td>
+        <td><strong>End to End Duration</strong></td>
+        <td><strong>State Size</strong></td>
+        <td><strong>Buffered During Alignment</strong></td>
+        <td ng-if="checkpoint['status'] == 'COMPLETED'"><strong>Discarded</strong></td>
+        <td ng-if="checkpoint['external_path']"><strong>Path</strong></td>
+        <td ng-if="checkpoint['failure_message']"><strong>Failure Message</strong></td>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>{{ checkpoint['id'] }}</td>
+        <td ng-if="checkpoint['status'] == 'IN_PROGRESS'"><i aria-hidden="true" class="fa fa-circle-o-notch fa-spin fa-fw"></i> In progress <i ng-if="checkpoint['is_savepoint']">savepoint</i></td>
+        <td ng-if="checkpoint['status'] == 'COMPLETED'"><i aria-hidden="true" class="fa fa-check"></i> Completed <i ng-if="checkpoint['is_savepoint']">savepoint</i></td>
+        <td ng-if="checkpoint['status'] == 'FAILED'"><i aria-hidden="true" class="fa fa-remove"></i> Failed <i ng-if="checkpoint['is_savepoint']">savepoint</i></td>
+        <td>{{ checkpoint['num_acknowledged_subtasks'] }}/{{ checkpoint['num_subtasks'] }} ({{ checkpoint['num_acknowledged_subtasks']/checkpoint['num_subtasks'] | percentage }})</td>
+        <td>{{ checkpoint['trigger_timestamp'] | amDateFormat:'H:mm:ss' }}</td>
+        <td ng-if="checkpoint['latest_ack_timestamp'] &gt;= 0">{{ checkpoint['latest_ack_timestamp'] | amDateFormat:'H:mm:ss' }}</td>
+        <td ng-if="checkpoint['latest_ack_timestamp'] &lt; 0">n/a</td>
+        <td ng-if="checkpoint['failure_timestamp']">{{ checkpoint['failure_timestamp'] | amDateFormat:'H:mm:ss' }}</td>
+        <td ng-if="checkpoint['end_to_end_duration'] &gt;= 0">{{ checkpoint['end_to_end_duration'] | humanizeDuration }}</td>
+        <td ng-if="heckpoint['end_to_end_duration'] &lt; 0">n/a</td>
+        <td>{{ checkpoint['state_size'] | humanizeBytes }}</td>
+        <td> {{ checkpoint['alignment_buffered'] | humanizeBytes }}</td>
+        <td ng-if="checkpoint['status'] == 'COMPLETED'"><span ng-if="checkpoint['discarded']">Yes</span><span ng-if="!checkpoint['discarded']">No</span></td>
+        <td ng-if="checkpoint['external_path']">{{ checkpoint['external_path'] }}</td>
+        <td ng-if="checkpoint['status'] == 'FAILED' &amp;&amp; checkpoint['failure_message']">{{ checkpoint['failure_message'] }}</td>
+        <td ng-if="checkpoint['status'] == 'FAILED' &amp;&amp; !checkpoint['failure_message']">n/a</td>
+      </tr>
+    </tbody>
+  </table>
+  <h4>Operators</h4>
+  <table class="table table-body-hover table-clickable table-activable subtask-details">
+    <thead>
+      <tr>
+        <td><strong>Name</strong></td>
+        <td><strong>Acknowleged</strong></td>
+        <td><strong>Latest Acknowledgment</strong></td>
+        <td><strong>End to End Duration</strong></td>
+        <td><strong>State Size</strong></td>
+        <td><strong>Buffered During Alignment</strong></td>
+        <td></td>
+      </tr>
+    </thead>
+    <tbody ng-repeat="v in job.vertices" ng-class="{ active: v.id == nodeid }" ng-click="changeNode(v.id)">
+      <tr ng-if="v.type == 'regular'">
+        <td>{{ v.name | humanizeText }}</td>
+        <td>{{ checkpoint['tasks'][v.id]['num_acknowledged_subtasks'] }}/{{ checkpoint['tasks'][v.id]['num_subtasks'] }} ({{ checkpoint['tasks'][v.id]['num_acknowledged_subtasks']/checkpoint['tasks'][v.id]['num_subtasks'] | percentage }})</td>
+        <td ng-if="checkpoint['tasks'][v.id]['latest_ack_timestamp'] &gt;= 0">{{ checkpoint['tasks'][v.id]['latest_ack_timestamp'] | amDateFormat:'H:mm:ss' }}</td>
+        <td ng-if="checkpoint['tasks'][v.id]['latest_ack_timestamp'] &lt; 0">n/a</td>
+        <td ng-if="checkpoint['tasks'][v.id]['end_to_end_duration'] &gt;= 0">{{ checkpoint['tasks'][v.id]['end_to_end_duration'] | humanizeDuration }}</td>
+        <td ng-if="checkpoint['tasks'][v.id]['end_to_end_duration'] &lt; 0">n/a</td>
+        <td>{{ checkpoint['tasks'][v.id]['state_size'] | humanizeBytes }}</td>
+        <td>{{ checkpoint['tasks'][v.id]['alignment_buffered'] | humanizeBytes }}</td>
+        <td>
+          <div ng-if="!nodeid || v.id != nodeid"><a ng-click="toggleFold()" class="btn btn-default">Show Subtasks <i class="fa fa-chevron-down"></i></a></div>
+          <div ng-if="nodeid &amp;&amp; v.id == nodeid"><a ng-click="toggleFold()" class="btn btn-default">Hide Subtasks <i class="fa fa-chevron-up"></i></a></div>
+        </td>
+      </tr>
+      <tr ng-if="nodeid &amp;&amp; v.id == nodeid">
+        <td colspan="7">
+          <table class="table table-body-hover table-inner subtask-details">
+            <thead ng-if="subtaskDetails[v.id]['summary']">
+              <tr>
+                <td></td>
+                <td></td>
+                <td><strong>End to End Duration</strong></td>
+                <td><strong>State Size</strong></td>
+                <td><strong>Checkpoint Duration (Sync)</strong></td>
+                <td><strong>Checkpoint Duration (Async)</strong></td>
+                <td><strong>Alignment Buffered</strong></td>
+                <td><strong>Alignment Duration</strong></td>
+              </tr>
+              <tr>
+                <td></td>
+                <td><strong>Minimum</strong></td>
+                <td>{{ subtaskDetails[v.id]['summary']['end_to_end_duration']['min'] | humanizeDuration }}</td>
+                <td>{{ subtaskDetails[v.id]['summary']['state_size']['min'] | humanizeBytes }}</td>
+                <td>{{ subtaskDetails[v.id]['summary']['checkpoint_duration']['sync']['min'] | humanizeDuration }}</td>
+                <td>{{ subtaskDetails[v.id]['summary']['checkpoint_duration']['async']['min'] | humanizeDuration }}</td>
+                <td>{{ subtaskDetails[v.id]['summary']['alignment']['buffered']['min'] | humanizeBytes }}</td>
+                <td>{{ subtaskDetails[v.id]['summary']['alignment']['duration']['min'] | humanizeDuration }}</td>
+              </tr>
+              <tr>
+                <td></td>
+                <td><strong>Average</strong></td>
+                <td>{{ subtaskDetails[v.id]['summary']['end_to_end_duration']['avg'] | humanizeDuration }}</td>
+                <td>{{ subtaskDetails[v.id]['summary']['state_size']['avg'] | humanizeBytes }}</td>
+                <td>{{ subtaskDetails[v.id]['summary']['checkpoint_duration']['sync']['avg'] | humanizeDuration }}</td>
+                <td>{{ subtaskDetails[v.id]['summary']['checkpoint_duration']['async']['avg'] | humanizeDuration }}</td>
+                <td>{{ subtaskDetails[v.id]['summary']['alignment']['buffered']['avg'] | humanizeBytes }}</td>
+                <td>{{ subtaskDetails[v.id]['summary']['alignment']['duration']['avg'] | humanizeDuration }}</td>
+              </tr>
+              <tr>
+                <td></td>
+                <td><strong>Maximum</strong></td>
+                <td>{{ subtaskDetails[v.id]['summary']['end_to_end_duration']['max'] | humanizeDuration }}</td>
+                <td>{{ subtaskDetails[v.id]['summary']['state_size']['max'] | humanizeBytes }}</td>
+                <td>{{ subtaskDetails[v.id]['summary']['checkpoint_duration']['sync']['max'] | humanizeDuration }}</td>
+                <td>{{ subtaskDetails[v.id]['summary']['checkpoint_duration']['async']['max'] | humanizeDuration }}</td>
+                <td>{{ subtaskDetails[v.id]['summary']['alignment']['buffered']['max'] | humanizeBytes }}</td>
+                <td>{{ subtaskDetails[v.id]['summary']['alignment']['duration']['max'] | humanizeDuration }}</td>
+              </tr>
+              <tr class="blank">
+                <td colspan="8"></td>
+              </tr>
+            </thead>
+            <thead>
+              <tr>
+                <td><strong>Subtask #</strong></td>
+                <td><strong>Acknowledgement Time</strong></td>
+                <td><strong>End to End Duration</strong></td>
+                <td><strong>State Size</strong></td>
+                <td><strong>Checkpoint Duration (Sync)</strong></td>
+                <td><strong>Checkpoint Duration (Async)</strong></td>
+                <td><strong>Alignment Buffered</strong></td>
+                <td><strong>Alignment Duration</strong></td>
+              </tr>
+            </thead>
+            <tbody>
+              <tr ng-repeat="subtask in subtaskDetails[v.id]['subtasks']">
+                <td>{{ subtask['index'] + 1 }}</td>
+                <td ng-if-start="subtask['status'] == 'completed'">{{ subtask['ack_timestamp'] | amDateFormat:'H:mm:ss' }}</td>
+                <td>{{ subtask['end_to_end_duration'] | humanizeDuration }}</td>
+                <td>{{ subtask['state_size'] | humanizeBytes }}</td>
+                <td>{{ subtask['checkpoint']['sync'] | humanizeDuration }}</td>
+                <td>{{ subtask['checkpoint']['async'] | humanizeDuration }}</td>
+                <td>{{ subtask['alignment']['buffered'] | humanizeBytes}}</td>
+                <td ng-if-end="ng-if-end">{{ subtask['alignment']['duration'] | humanizeDuration }}</td>
+                <td ng-if="subtask['status'] == 'pending'" colspan="7">n/a</td>
+              </tr>
+            </tbody>
+          </table>
+        </td>
+      </tr>
+    </tbody>
+  </table>
+</div>
+<div ng-if="!checkpoint">
+  <p ng-if="unknown_checkpoint" role="alert" class="alert alert-danger"><strong>Unknown or expired checkpoint ID.</strong></p>
+  <p ng-if="!unknown_checkpoint" role="alert" class="alert alert-info"><strong>Waiting for response from JobManager with checkpoint details...</strong> <i aria-hidden="true" class="fa fa-circle-o-notch fa-spin fa-fw"></i>
+  </p>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flink/blob/88c7de49/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.history.html
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.history.html b/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.history.html
new file mode 100644
index 0000000..651d4f8
--- /dev/null
+++ b/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.history.html
@@ -0,0 +1,65 @@
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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.
+
+-->
+<div ng-if="checkpointStats['history'] &amp;&amp; checkpointStats['history'].length &gt; 0">
+  <table class="table">
+    <thead>
+      <tr>
+        <td><strong>ID</strong></td>
+        <td><strong>Status</strong></td>
+        <td><strong>Acknowledged</strong></td>
+        <td><strong>Trigger Time</strong></td>
+        <td><strong>Latest Acknowledgement</strong></td>
+        <td><strong>End to End Duration</strong></td>
+        <td><strong>State Size</strong></td>
+        <td><strong>Buffered During Alignment</strong></td>
+        <td></td>
+      </tr>
+    </thead>
+    <tbody>
+      <tr ng-repeat="checkpoint in checkpointStats['history']" ng-class="{'bg-danger': checkpoint['status'] == 'FAILED'}">
+        <td>{{ checkpoint['id'] }}</td>
+        <td ng-if="checkpoint['status'] == 'IN_PROGRESS'"><i aria-hidden="true" class="fa fa-circle-o-notch fa-spin fa-fw"></i> <i ng-if="checkpoint['is_savepoint']" aria-hidden="true" class="fa fa-floppy-o"></i></td>
+        <td ng-if="checkpoint['status'] == 'COMPLETED'"><i aria-hidden="true" class="fa fa-check"></i> <i ng-if="checkpoint['is_savepoint']" aria-hidden="true" class="fa fa-floppy-o"></i></td>
+        <td ng-if="checkpoint['status'] == 'FAILED'"><i aria-hidden="true" class="fa fa-remove"></i> <i ng-if="checkpoint['is_savepoint']" aria-hidden="true" class="fa fa-floppy-o"></i></td>
+        <td>{{ checkpoint['num_acknowledged_subtasks'] }}/{{ checkpoint['num_subtasks'] }} <span ng-if="checkpoint['status'] == 'IN_PROGRESS'">({{ checkpoint['num_acknowledged_subtasks']/checkpoint['num_subtasks'] | percentage }})</span>
+        </td>
+        <td>{{ checkpoint['trigger_timestamp'] | amDateFormat:'H:mm:ss' }}</td>
+        <td ng-if="checkpoint['latest_ack_timestamp'] &gt;= 0">{{ checkpoint['latest_ack_timestamp'] | amDateFormat:'H:mm:ss' }}</td>
+        <td ng-if="checkpoint['latest_ack_timestamp'] &lt; 0">n/a</td>
+        <td ng-if="checkpoint['end_to_end_duration'] &gt;= 0">{{ checkpoint['end_to_end_duration'] | humanizeDuration }}</td>
+        <td ng-if="checkpoint['end_to_end_duration'] &lt; 0">n/a</td>
+        <td>{{ checkpoint['state_size'] | humanizeBytes }}</td>
+        <td>{{ checkpoint['alignment_buffered'] | humanizeBytes }}</td>
+        <td><a ui-sref="^.details({checkpointId: checkpoint['id']})" class="btn btn-default"><i aria-hidden="true" class="fa fa-chevron-right"></i> <strong>More details</strong></a></td>
+      </tr>
+    </tbody>
+  </table>
+  <p><strong class="small">Status:</strong>
+    <ul class="small">
+      <li>In Progress: <i aria-hidden="true" class="fa fa-circle-o-notch fa-spin fa-fw"></i></li>
+      <li>Completed: <i aria-hidden="true" class="fa fa-check"></i></li>
+      <li>Failed: <i aria-hidden="true" class="fa fa-remove"></i></li>
+      <li>Savepoint: <i aria-hidden="true" class="fa fa-floppy-o"></i></li>
+    </ul>
+  </p>
+</div>
+<div ng-if="checkpointStats['history'] &amp;&amp; checkpointStats['history'].length == 0">
+  <p role="alert" class="alert alert-info"><strong>No checkpoint history available.</strong></p>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flink/blob/88c7de49/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.html
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.html b/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.html
new file mode 100644
index 0000000..2a3f1e1
--- /dev/null
+++ b/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.html
@@ -0,0 +1,22 @@
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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.
+
+-->
+<div ng-include="'partials/jobs/job.plan.node.checkpoints.overview.html'"></div>
+<div ng-include="'partials/jobs/job.plan.node.checkpoints.history.html'"></div>
+<div ng-include="'partials/jobs/job.plan.node.checkpoints.config.html'"></div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flink/blob/88c7de49/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.job.html
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.job.html b/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.job.html
index d21349c..2754e87 100644
--- a/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.job.html
+++ b/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.job.html
@@ -17,62 +17,88 @@ See the License for the specific language governing permissions and
 limitations under the License.
 
 -->
-<div ng-if="!jobCheckpointStats">
-  <p><em>No checkpoints</em></p>
+<div ng-if="jobCheckpointConfig">
+  <h3>Configuration</h3>
+  <table class="table">
+    <thead>
+      <tr>
+        <td><strong>Interval</strong></td>
+        <td><strong>Timeout</strong></td>
+        <td><strong>Minimum Pause between Checkpoints</strong></td>
+        <td><strong>Maximum Concurrent Checkpoints</strong></td>
+        <td><strong>Persist Checkpoints Externally</strong></td>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>{{ jobCheckpointConfig['interval'] | humanizeDuration }}</td>
+        <td>{{ jobCheckpointConfig['timeout'] | humanizeDuration }}</td>
+        <td>{{ jobCheckpointConfig['min_pause'] | humanizeDuration }}</td>
+        <td>{{ jobCheckpointConfig['max_concurrent'] }}</td>
+        <td ng-if="jobCheckpointConfig['externalization']['enabled']">Enabled <span ng-if="jobCheckpointConfig['externalization']['delete_on_cancellation']">(delete on cancellation)</span><span ng-if="!jobCheckpointConfig['externalization']['delete_on_cancellation']">(retain on cancellation)</span>
+        </td>
+        <td ng-if="!jobCheckpointConfig['externalization']['enabled']">Disabled</td>
+      </tr>
+    </tbody>
+  </table>
 </div>
-<table ng-if="jobCheckpointStats" class="table table-hover table-inner">
-  <tbody>
-    <tr>
-      <td><strong>Count</strong></td>
-      <td colspan="3"><span>{{ jobCheckpointStats['count'] }}</span></td>
-    </tr>
+<table ng-if="checkpointDetails == -1" class="table table-body-hover table-clickable">
+  <thead>
     <tr>
+      <td><strong>ID</strong></td>
+      <td><strong>Acknowledged</strong></td>
+      <td><strong>Trigger Time</strong></td>
+      <td><strong>Latest Ack</strong></td>
       <td><strong>Duration</strong></td>
-      <td>
-        <p><strong>Minimum:</strong><span> {{ jobCheckpointStats['duration']['min'] | humanizeDuration }}</span></p>
-      </td>
-      <td>
-        <p><strong>Maximum:</strong><span> {{ jobCheckpointStats['duration']['max'] | humanizeDuration }}</span></p>
-      </td>
-      <td>
-        <p><strong>Average:</strong><span> {{ jobCheckpointStats['duration']['avg'] | humanizeDuration }}</span></p>
-      </td>
-    </tr>
-    <tr>
       <td><strong>State Size</strong></td>
-      <td>
-        <p><strong>Minimum:</strong><span> {{ jobCheckpointStats['size']['min'] | humanizeBytes }}</span></p>
-      </td>
-      <td>
-        <p><strong>Maximum:</strong><span> {{ jobCheckpointStats['size']['max'] | humanizeBytes }}</span></p>
+      <td></td>
+    </tr>
+  </thead>
+  <!--ng-click="toggleCheckpointDetails(checkpoint['id'])"-->
+  <tbody ng-if="jobCheckpointStats['checkpoints'] &amp;&amp; jobCheckpointStats['checkpoints'].length &gt; 0">
+    <tr ng-repeat="checkpoint in jobCheckpointStats['checkpoints']" ng-class="{'bg-danger': checkpoint['status'] == 'FAILED'}">
+      <td><i ng-if="checkpoint['status'] == 'IN_PROGRESS'" aria-hidden="true" class="fa fa-circle-o-notch fa-spin fa-fw"></i><i ng-if="checkpoint['status'] == 'COMPLETED'" aria-hidden="true" class="fa fa-check"></i><i ng-if="checkpoint['status'] == 'FAILED'" aria-hidden="true" class="fa fa-remove"></i> {{ checkpoint['id'] }}
       </td>
+      <td>{{ checkpoint['subtasks']['acknowledged'] }}/{{ checkpoint['subtasks']['count'] }}</td>
+      <td>{{ checkpoint['trigger_timestamp'] | amDateFormat:'H:mm:ss' }}</td>
+      <td>{{ checkpoint['latest_ack_timestamp'] | amDateFormat:'H:mm:ss' }}</td>
+      <td>{{ checkpoint['duration'] | humanizeDuration }}</td>
+      <td>{{ checkpoint['size'] | humanizeBytes }}</td>
       <td>
-        <p><strong>Average:</strong><span> {{ jobCheckpointStats['size']['avg'] | humanizeBytes }}</span></p>
-      </td>
-    </tr>
-    <tr ng-if="jobCheckpointStats['external-path']">
-      <td colspan="4"><strong>Latest Checkpoint Path:</strong> {{ jobCheckpointStats['external-path'] }}
+        <!--a.btn.btn-default(ng-click="toggleCheckpointDetails(checkpoint['id'])")--><a ui-sref="{checkpointid: checkpoint['id']}" class="btn btn-default"><strong>More details</strong> <i class="fa fa-chevron-down"></i></a>
       </td>
     </tr>
   </tbody>
 </table>
-<div ng-if="!showHistory &amp;&amp; jobCheckpointStats &amp;&amp; jobCheckpointStats['history'].length &gt; 0"><a ng-click="toggleHistory()" class="btn btn-default"><strong>Show history</strong> ({{ jobCheckpointStats['history'].length }}) <i class="fa fa-chevron-down"></i></a></div>
-<div ng-if="showHistory &amp;&amp; jobCheckpointStats &amp;&amp; jobCheckpointStats['history'].length &gt; 0"><a ng-click="toggleHistory()" class="btn btn-default">Hide history ({{ jobCheckpointStats['history'].length }}) <i class="fa fa-chevron-up"></i></a>
-  <table class="table table-hover table-inner">
+<div ng-if="checkpointDetails != -1"><a ng-click="toggleCheckpointDetails(-1)" class="btn btn-default"><strong>Back</strong> <i class="fa fa-chevron-left"></i></a>
+  <table class="table table-body-hover table-clickable table-activable">
     <thead>
       <tr>
-        <td><strong>ID</strong></td>
-        <td><strong>Trigger Time</strong></td>
-        <td><strong>Duration</strong></td>
-        <td><strong>State Size</strong></td>
+        <td rowspan="2">Name</td>
+        <td colspan="4">Size</td>
+        <td rowspan="2">Duration</td>
+        <td rowspan="2">Duration (Operator sync)</td>
+        <td rowspan="2">Status</td>
       </tr>
-    </thead>
-    <tbody ng-if="jobCheckpointStats['history'] &amp;&amp; jobCheckpointStats['history'].length &gt; 0" ng-repeat="historic in jobCheckpointStats['history']">
       <tr>
-        <td>{{ historic['id'] }}</td>
-        <td>{{ historic['timestamp'] | amDateFormat:'H:mm:ss' }}</td>
-        <td>{{ historic['duration'] | humanizeDuration }}</td>
-        <td>{{ historic['size'] | humanizeBytes }}</td>
+        <td>Min</td>
+        <td>Average</td>
+        <td>Max</td>
+        <td>Sum</td>
+      </tr>
+    </thead>
+    <tbody ng-repeat="v in job.vertices" ng-class="{ active: v.id == nodeid }" ng-click="v.id == nodeid || changeNode(v.id)">
+      <tr ng-if="v.type == 'regular'">
+        <td>{{ v.name | humanizeText }}</td>
+        <td>{{ operatorCheckpointStats[checkpoint['id']][v.id]['size']['min'] | humanizeBytes }}</td>
+        <td>{{ operatorCheckpointStats[checkpoint['id']][v.id]['size']['avg'] | humanizeBytes }}</td>
+        <td>{{ operatorCheckpointStats[checkpoint['id']][v.id]['size']['max'] | humanizeBytes }}</td>
+        <td>{{ operatorCheckpointStats[checkpoint['id']][v.id]['size']['sum'] | humanizeBytes }}</td>
+        <td>min: {{ operatorCheckpointStats[checkpoint['id']][v.id]['duration']['coordinator']['min'] | humanizeDuration }}, max: {{ operatorCheckpointStats[checkpoint['id']][v.id]['duration']['coordinator']['max'] | humanizeDuration }}, average: {{ operatorCheckpointStats[checkpoint['id']][v.id]['duration']['coordinator']['avg'] | humanizeDuration }}</td>
+        <td>min: {{ operatorCheckpointStats[checkpoint['id']][v.id]['duration']['operator']['sync']['min'] | humanizeDuration }}, max: {{ operatorCheckpointStats[checkpoint['id']][v.id]['duration']['operator']['sync']['max'] | humanizeDuration }}, average: {{ operatorCheckpointStats[checkpoint['id']][v.id]['duration']['operator']['sync']['avg'] | humanizeDuration }}</td>
+        <td>
+          <bs-label status="{{v.status}}">{{v.status}}</bs-label>
+        </td>
       </tr>
     </tbody>
   </table>

http://git-wip-us.apache.org/repos/asf/flink/blob/88c7de49/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.overview.html
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.overview.html b/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.overview.html
new file mode 100644
index 0000000..e66d741
--- /dev/null
+++ b/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.overview.html
@@ -0,0 +1,49 @@
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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.
+
+-->
+<div ng-if="checkpointStats">
+  <table class="table checkpoint-overview">
+    <thead>
+      <tr>
+        <td><strong>Checkpoint Counts</strong></td>
+        <td>Triggered: {{ checkpointStats['counts']['total'] }}<span>In Progress: {{ checkpointStats['counts']['in_progress'] }}</span><span>Completed: {{ checkpointStats['counts']['completed'] }}</span><span>Failed: {{ checkpointStats['counts']['failed'] }}</span><span>Restored: {{ checkpointStats['counts']['restored'] }}</span></td>
+      </tr>
+      <tr>
+        <td><strong>Latest Completed Checkpoint</strong></td>
+        <td ng-if="checkpointStats['latest']['completed']">ID: {{ checkpointStats['latest']['completed']['id'] }}<span>Completion Time: {{ checkpointStats['latest']['completed']['latest_ack_timestamp'] | amDateFormat:'H:mm:ss' }}</span><span>End to End Duration: {{ checkpointStats['latest']['completed']['end_to_end_duration'] | humanizeDuration }}</span><span>State Size: {{ checkpointStats['latest']['completed']['state_size'] | humanizeBytes }}</span><span><i aria-hidden="true" class="fa fa-caret-square-o-right"></i> <a ui-sref="^.details({checkpointId: checkpointStats['latest']['completed']['id']})">More details</a></span></td>
+        <td ng-if="!checkpointStats['latest']['completed']">None</td>
+      </tr>
+      <tr>
+        <td><strong>Latest Failed Checkpoint</strong></td>
+        <td ng-if="checkpointStats['latest']['failed']">ID: {{ checkpointStats['latest']['failed']['id'] }}<span>Failure Time: {{ checkpointStats['latest']['failed']['failure_timestamp'] | amDateFormat:'H:mm:ss' }}</span><span ng-if="checkpointStats['latest']['failed']['failure_message']">Cause: {{ checkpointStats['latest']['failed']['failure_message'] }}</span><span ng-if="!checkpointStats['latest']['failed']['failure_message']">Cause: n/a</span><span><i aria-hidden="true" class="fa fa-caret-square-o-right"></i> <a ui-sref="^.details({checkpointId: checkpointStats['latest']['failed']['id']})">More details</a></span></td>
+        <td ng-if="!checkpointStats['latest']['failed']">None</td>
+      </tr>
+      <tr>
+        <td><strong>Latest Savepoint</strong></td>
+        <td ng-if="checkpointStats['latest']['savepoint']">ID: {{ checkpointStats['latest']['savepoint']['id'] }}<span>Completion Time: {{ checkpointStats['latest']['savepoint']['latest_ack_timestamp'] | amDateFormat:'H:mm:ss' }}</span><span>State Size: {{ checkpointStats['latest']['savepoint']['state_size'] | humanizeBytes }}</span><span>Path: {{ checkpointStats['latest']['savepoint']['external_path'] }}</span><span><i aria-hidden="true" class="fa fa-caret-square-o-right"></i> <a ui-sref="^.details({checkpointId: checkpointStats['latest']['savepoint']['id']})">More details</a></span></td>
+        <td ng-if="!checkpointStats['latest']['savepoint']">None</td>
+      </tr>
+      <tr>
+        <td><strong>Latest Restore</strong></td>
+        <td ng-if="checkpointStats['latest']['restored']">ID: {{ checkpointStats['latest']['restored']['id'] }}<span>Restore Time: {{ checkpointStats['latest']['restored']['restore_timestamp'] | amDateFormat:'H:mm:ss' }}</span><span ng-if="checkpointStats['latest']['restored']['is_savepoint']">Type: Savepoint</span><span ng-if="!checkpointStats['latest']['restored']['is_savepoint']">Type: Checkpoint</span><span ng-if="checkpointStats['latest']['restored']['external_path']">Path: {{ checkpointStats['latest']['restored']['external_path'] }}</span></td>
+        <td ng-if="!checkpointStats['latest']['restored']">None</td>
+      </tr>
+    </thead>
+  </table>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flink/blob/88c7de49/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.statistics.html
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.statistics.html b/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.statistics.html
new file mode 100644
index 0000000..17326e5
--- /dev/null
+++ b/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.statistics.html
@@ -0,0 +1,56 @@
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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.
+
+-->
+<div ng-if="checkpointStats">
+  <table class="table">
+    <thead>
+      <tr>
+        <td></td>
+        <td><strong>Minimum</strong></td>
+        <td><strong>Average</strong></td>
+        <td><strong>Maximum</strong></td>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td><strong>State Size</strong></td>
+        <td>{{ checkpointStats['summary']['state_size']['min'] | humanizeBytes }}</td>
+        <td>{{ checkpointStats['summary']['state_size']['avg'] | humanizeBytes }}</td>
+        <td>{{ checkpointStats['summary']['state_size']['max'] | humanizeBytes }}</td>
+      </tr>
+      <tr>
+        <td><strong>End to End Duration</strong></td>
+        <td>{{ checkpointStats['summary']['end_to_end_duration']['min'] | humanizeBytes }}</td>
+        <td>{{ checkpointStats['summary']['end_to_end_duration']['avg'] | humanizeBytes }}</td>
+        <td>{{ checkpointStats['summary']['end_to_end_duration']['max'] | humanizeBytes }}</td>
+      </tr>
+      <tr ng-if="isExactlyOnce">
+        <td><strong>Buffered during Alignment</strong></td>
+        <td>{{ checkpointStats['summary']['alignment_buffered']['min'] | humanizeBytes }}</td>
+        <td>{{ checkpointStats['summary']['alignment_buffered']['avg'] | humanizeBytes }}</td>
+        <td>{{ checkpointStats['summary']['alignment_buffered']['max'] | humanizeBytes }}</td>
+      </tr>
+      <tr ng-if="!isExactlyOnce">
+        <td><strong>Buffered during Alignment</strong></td>
+        <td colspan="3">No alignments with at-least-once checkpointing mode</td>
+      </tr>
+    </tbody>
+  </table>
+  <p>These number are computed over <i>all</i> completed checkpoints.</p>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flink/blob/88c7de49/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.summary.html
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.summary.html b/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.summary.html
new file mode 100644
index 0000000..96f6d05
--- /dev/null
+++ b/flink-runtime-web/web-dashboard/web/partials/jobs/job.plan.node.checkpoints.summary.html
@@ -0,0 +1,53 @@
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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.
+
+-->
+<div ng-if="checkpointStats['summary']">
+  <table class="table">
+    <thead>
+      <tr>
+        <td></td>
+        <td><strong>State Size</strong></td>
+        <td><strong>End to End Duration</strong></td>
+        <td><strong>Buffered During Alignment</strong></td>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td><strong>Minimum</strong></td>
+        <td>{{ checkpointStats['summary']['end_to_end_duration']['min'] | humanizeDuration }}</td>
+        <td>{{ checkpointStats['summary']['state_size']['min'] | humanizeBytes }}</td>
+        <td>{{ checkpointStats['summary']['alignment_buffered']['min'] | humanizeBytes }}</td>
+      </tr>
+      <tr>
+        <td><strong>Average</strong></td>
+        <td>{{ checkpointStats['summary']['end_to_end_duration']['avg'] | humanizeDuration }}</td>
+        <td>{{ checkpointStats['summary']['state_size']['avg'] | humanizeBytes }}</td>
+        <td>{{ checkpointStats['summary']['alignment_buffered']['avg'] | humanizeBytes }}</td>
+      </tr>
+      <tr>
+        <td><strong>Maximum</strong></td>
+        <td>{{ checkpointStats['summary']['end_to_end_duration']['max'] | humanizeDuration }}</td>
+        <td>{{ checkpointStats['summary']['state_size']['max'] | humanizeBytes }}</td>
+        <td>{{ checkpointStats['summary']['alignment_buffered']['max'] | humanizeBytes }}</td>
+      </tr>
+    </tbody>
+  </table>
+  <p>These number are computed over <i>all</i> completed checkpoints.</p>
+</div>
+<p ng-if="!checkpointStats['summary']">No checkpoint statistics summary available.</p>
\ No newline at end of file


[05/11] flink git commit: [FLINK-4410] [runtime-web] Add detailed checkpoint stats handlers

Posted by uc...@apache.org.
[FLINK-4410] [runtime-web] Add detailed checkpoint stats handlers


Project: http://git-wip-us.apache.org/repos/asf/flink/repo
Commit: http://git-wip-us.apache.org/repos/asf/flink/commit/1fd2d2e1
Tree: http://git-wip-us.apache.org/repos/asf/flink/tree/1fd2d2e1
Diff: http://git-wip-us.apache.org/repos/asf/flink/diff/1fd2d2e1

Branch: refs/heads/release-1.2
Commit: 1fd2d2e10b482b1d4a342939f192d529769ad5c5
Parents: 0d1f4bc
Author: Ufuk Celebi <uc...@apache.org>
Authored: Fri Dec 23 20:44:12 2016 +0100
Committer: Ufuk Celebi <uc...@apache.org>
Committed: Tue Jan 10 09:47:55 2017 +0100

----------------------------------------------------------------------
 .../runtime/webmonitor/WebRuntimeMonitor.java   |  21 +-
 .../AbstractJobVertexRequestHandler.java        |  34 +-
 .../checkpoints/CheckpointConfigHandler.java    |  77 +++++
 .../checkpoints/CheckpointStatsCache.java       |  80 +++++
 .../CheckpointStatsDetailsHandler.java          | 153 +++++++++
 .../CheckpointStatsDetailsSubtasksHandler.java  | 189 ++++++++++
 .../checkpoints/CheckpointStatsHandler.java     | 235 +++++++++++++
 .../CheckpointConfigHandlerTest.java            | 146 ++++++++
 .../checkpoints/CheckpointStatsCacheTest.java   |  67 ++++
 .../CheckpointStatsDetailsHandlerTest.java      | 286 ++++++++++++++++
 .../checkpoints/CheckpointStatsHandlerTest.java | 303 ++++++++++++++++
 ...heckpointStatsSubtaskDetailsHandlerTest.java | 342 +++++++++++++++++++
 12 files changed, 1916 insertions(+), 17 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/flink/blob/1fd2d2e1/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/WebRuntimeMonitor.java
----------------------------------------------------------------------
diff --git a/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/WebRuntimeMonitor.java b/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/WebRuntimeMonitor.java
index c5b7d35..3080b57 100644
--- a/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/WebRuntimeMonitor.java
+++ b/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/WebRuntimeMonitor.java
@@ -53,7 +53,6 @@ import org.apache.flink.runtime.webmonitor.handlers.JarUploadHandler;
 import org.apache.flink.runtime.webmonitor.handlers.JobAccumulatorsHandler;
 import org.apache.flink.runtime.webmonitor.handlers.JobCancellationHandler;
 import org.apache.flink.runtime.webmonitor.handlers.JobCancellationWithSavepointHandlers;
-import org.apache.flink.runtime.webmonitor.handlers.JobCheckpointsHandler;
 import org.apache.flink.runtime.webmonitor.handlers.JobConfigHandler;
 import org.apache.flink.runtime.webmonitor.handlers.JobDetailsHandler;
 import org.apache.flink.runtime.webmonitor.handlers.JobExceptionsHandler;
@@ -62,7 +61,6 @@ import org.apache.flink.runtime.webmonitor.handlers.JobPlanHandler;
 import org.apache.flink.runtime.webmonitor.handlers.JobStoppingHandler;
 import org.apache.flink.runtime.webmonitor.handlers.JobVertexAccumulatorsHandler;
 import org.apache.flink.runtime.webmonitor.handlers.JobVertexBackPressureHandler;
-import org.apache.flink.runtime.webmonitor.handlers.JobVertexCheckpointsHandler;
 import org.apache.flink.runtime.webmonitor.handlers.JobVertexDetailsHandler;
 import org.apache.flink.runtime.webmonitor.handlers.JobVertexTaskManagersHandler;
 import org.apache.flink.runtime.webmonitor.handlers.RequestHandler;
@@ -73,6 +71,11 @@ import org.apache.flink.runtime.webmonitor.handlers.SubtasksAllAccumulatorsHandl
 import org.apache.flink.runtime.webmonitor.handlers.SubtasksTimesHandler;
 import org.apache.flink.runtime.webmonitor.handlers.TaskManagerLogHandler;
 import org.apache.flink.runtime.webmonitor.handlers.TaskManagersHandler;
+import org.apache.flink.runtime.webmonitor.handlers.checkpoints.CheckpointConfigHandler;
+import org.apache.flink.runtime.webmonitor.handlers.checkpoints.CheckpointStatsCache;
+import org.apache.flink.runtime.webmonitor.handlers.checkpoints.CheckpointStatsDetailsHandler;
+import org.apache.flink.runtime.webmonitor.handlers.checkpoints.CheckpointStatsHandler;
+import org.apache.flink.runtime.webmonitor.handlers.checkpoints.CheckpointStatsDetailsSubtasksHandler;
 import org.apache.flink.runtime.webmonitor.metrics.JobManagerMetricsHandler;
 import org.apache.flink.runtime.webmonitor.metrics.JobMetricsHandler;
 import org.apache.flink.runtime.webmonitor.metrics.JobVertexMetricsHandler;
@@ -273,7 +276,6 @@ public class WebRuntimeMonitor implements WebMonitor {
 			.GET("/jobs/:jobid/vertices/:vertexid/subtasktimes", handler(new SubtasksTimesHandler(currentGraphs)))
 			.GET("/jobs/:jobid/vertices/:vertexid/taskmanagers", handler(new JobVertexTaskManagersHandler(currentGraphs, metricFetcher)))
 			.GET("/jobs/:jobid/vertices/:vertexid/accumulators", handler(new JobVertexAccumulatorsHandler(currentGraphs)))
-			.GET("/jobs/:jobid/vertices/:vertexid/checkpoints", handler(new JobVertexCheckpointsHandler(currentGraphs)))
 			.GET("/jobs/:jobid/vertices/:vertexid/backpressure", handler(new JobVertexBackPressureHandler(
 							currentGraphs,
 							backPressureStatsTracker,
@@ -288,7 +290,6 @@ public class WebRuntimeMonitor implements WebMonitor {
 			.GET("/jobs/:jobid/config", handler(new JobConfigHandler(currentGraphs)))
 			.GET("/jobs/:jobid/exceptions", handler(new JobExceptionsHandler(currentGraphs)))
 			.GET("/jobs/:jobid/accumulators", handler(new JobAccumulatorsHandler(currentGraphs)))
-			.GET("/jobs/:jobid/checkpoints", handler(new JobCheckpointsHandler(currentGraphs)))
 			.GET("/jobs/:jobid/metrics", handler(new JobMetricsHandler(metricFetcher)))
 
 			.GET("/taskmanagers", handler(new TaskManagersHandler(DEFAULT_REQUEST_TIMEOUT, metricFetcher)))
@@ -328,6 +329,18 @@ public class WebRuntimeMonitor implements WebMonitor {
 			// DELETE is the preferred way of stopping a job (Rest-conform)
 			.DELETE("/jobs/:jobid/stop", handler(new JobStoppingHandler()));
 
+		int maxCachedEntries = config.getInteger(
+				ConfigConstants.JOB_MANAGER_WEB_CHECKPOINTS_HISTORY_SIZE,
+			ConfigConstants.DEFAULT_JOB_MANAGER_WEB_CHECKPOINTS_HISTORY_SIZE);
+		CheckpointStatsCache cache = new CheckpointStatsCache(maxCachedEntries);
+
+		// Register the checkpoint stats handlers
+		router
+			.GET("/jobs/:jobid/checkpoints", handler(new CheckpointStatsHandler(currentGraphs)))
+			.GET("/jobs/:jobid/checkpoints/config", handler(new CheckpointConfigHandler(currentGraphs)))
+			.GET("/jobs/:jobid/checkpoints/details/:checkpointid", handler(new CheckpointStatsDetailsHandler(currentGraphs, cache)))
+			.GET("/jobs/:jobid/checkpoints/details/:checkpointid/subtasks/:vertexid", handler(new CheckpointStatsDetailsSubtasksHandler(currentGraphs, cache)));
+
 		if (webSubmitAllow) {
 			router
 				// fetch the list of uploaded jars.

http://git-wip-us.apache.org/repos/asf/flink/blob/1fd2d2e1/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/AbstractJobVertexRequestHandler.java
----------------------------------------------------------------------
diff --git a/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/AbstractJobVertexRequestHandler.java b/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/AbstractJobVertexRequestHandler.java
index a36f94a..38243e5 100644
--- a/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/AbstractJobVertexRequestHandler.java
+++ b/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/AbstractJobVertexRequestHandler.java
@@ -37,26 +37,34 @@ public abstract class AbstractJobVertexRequestHandler extends AbstractExecutionG
 
 	@Override
 	public final String handleRequest(AccessExecutionGraph graph, Map<String, String> params) throws Exception {
-		final String vidString = params.get("vertexid");
-		if (vidString == null) {
-			throw new IllegalArgumentException("vertexId parameter missing");
-		}
-
-		final JobVertexID vid;
-		try {
-			vid = JobVertexID.fromHexString(vidString);
-		}
-		catch (Exception e) {
-			throw new IllegalArgumentException("Invalid JobVertexID string '" + vidString + "': " + e.getMessage());
-		}
+		final JobVertexID vid = parseJobVertexId(params);
 
 		final AccessExecutionJobVertex jobVertex = graph.getJobVertex(vid);
 		if (jobVertex == null) {
-			throw new IllegalArgumentException("No vertex with ID '" + vidString + "' exists.");
+			throw new IllegalArgumentException("No vertex with ID '" + vid + "' exists.");
 		}
 
 		return handleRequest(jobVertex, params);
 	}
+
+	/**
+	 * Returns the job vertex ID parsed from the provided parameters.
+	 *
+	 * @param params Path parameters
+	 * @return Parsed job vertex ID or <code>null</code> if not available.
+	 */
+	public static JobVertexID parseJobVertexId(Map<String, String> params) {
+		String jobVertexIdParam = params.get("vertexid");
+		if (jobVertexIdParam == null) {
+			return null;
+		}
+
+		try {
+			return JobVertexID.fromHexString(jobVertexIdParam);
+		} catch (RuntimeException ignored) {
+			return null;
+		}
+	}
 	
 	public abstract String handleRequest(AccessExecutionJobVertex jobVertex, Map<String, String> params) throws Exception;
 }

http://git-wip-us.apache.org/repos/asf/flink/blob/1fd2d2e1/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointConfigHandler.java
----------------------------------------------------------------------
diff --git a/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointConfigHandler.java b/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointConfigHandler.java
new file mode 100644
index 0000000..1ad5e65
--- /dev/null
+++ b/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointConfigHandler.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.webmonitor.handlers.checkpoints;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsTracker;
+import org.apache.flink.runtime.executiongraph.AccessExecutionGraph;
+import org.apache.flink.runtime.jobgraph.tasks.ExternalizedCheckpointSettings;
+import org.apache.flink.runtime.jobgraph.tasks.JobSnapshottingSettings;
+import org.apache.flink.runtime.webmonitor.ExecutionGraphHolder;
+import org.apache.flink.runtime.webmonitor.handlers.AbstractExecutionGraphRequestHandler;
+import org.apache.flink.runtime.webmonitor.handlers.JsonFactory;
+
+import java.io.StringWriter;
+import java.util.Map;
+
+/**
+ * Handler that returns a job's snapshotting settings.
+ */
+public class CheckpointConfigHandler extends AbstractExecutionGraphRequestHandler {
+
+	public CheckpointConfigHandler(ExecutionGraphHolder executionGraphHolder) {
+		super(executionGraphHolder);
+	}
+
+	@Override
+	public String handleRequest(AccessExecutionGraph graph, Map<String, String> params) throws Exception {
+		StringWriter writer = new StringWriter();
+		JsonGenerator gen = JsonFactory.jacksonFactory.createGenerator(writer);
+
+		CheckpointStatsTracker tracker = graph.getCheckpointStatsTracker();
+		JobSnapshottingSettings settings = tracker.getSnapshottingSettings();
+
+		gen.writeStartObject();
+		{
+			gen.writeStringField("mode", settings.isExactlyOnce() ? "exactly_once" : "at_least_once");
+			gen.writeNumberField("interval", settings.getCheckpointInterval());
+			gen.writeNumberField("timeout", settings.getCheckpointTimeout());
+			gen.writeNumberField("min_pause", settings.getMinPauseBetweenCheckpoints());
+			gen.writeNumberField("max_concurrent", settings.getMaxConcurrentCheckpoints());
+
+			ExternalizedCheckpointSettings externalization = settings.getExternalizedCheckpointSettings();
+			gen.writeObjectFieldStart("externalization");
+			{
+				if (externalization.externalizeCheckpoints()) {
+					gen.writeBooleanField("enabled", true);
+					gen.writeBooleanField("delete_on_cancellation", externalization.deleteOnCancellation());
+				} else {
+					gen.writeBooleanField("enabled", false);
+				}
+			}
+			gen.writeEndObject();
+
+		}
+		gen.writeEndObject();
+
+		gen.close();
+
+		return writer.toString();
+	}
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/1fd2d2e1/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsCache.java
----------------------------------------------------------------------
diff --git a/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsCache.java b/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsCache.java
new file mode 100644
index 0000000..35d529a
--- /dev/null
+++ b/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsCache.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.webmonitor.handlers.checkpoints;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import org.apache.flink.runtime.checkpoint.AbstractCheckpointStats;
+
+import javax.annotation.Nullable;
+
+/**
+ * A size-based cache of accessed checkpoints for completed and failed
+ * checkpoints.
+ *
+ * <p>Having this cache in place for accessed stats improves the user
+ * experience quite a bit as accessed checkpoint stats stay available
+ * and don't expire. For example if you manage to click on the last
+ * checkpoint in the history, it is not available via the stats as soon
+ * as another checkpoint is triggered. With the cache in place, the
+ * checkpoint will still be available for investigation.
+ */
+public class CheckpointStatsCache {
+
+	@Nullable
+	private final Cache<Long, AbstractCheckpointStats> cache;
+
+	public CheckpointStatsCache(int maxNumEntries) {
+		if (maxNumEntries > 0) {
+			this.cache = CacheBuilder.<Long, AbstractCheckpointStats>newBuilder()
+				.maximumSize(maxNumEntries)
+				.build();
+		} else {
+			this.cache = null;
+		}
+	}
+
+	/**
+	 * Try to add the checkpoint to the cache.
+	 *
+	 * @param checkpoint Checkpoint to be added.
+	 */
+	void tryAdd(AbstractCheckpointStats checkpoint) {
+		// Don't add in progress checkpoints as they will be replaced by their
+		// completed/failed version eventually.
+		if (cache != null && checkpoint != null && !checkpoint.getStatus().isInProgress()) {
+			cache.put(checkpoint.getCheckpointId(), checkpoint);
+		}
+	}
+
+	/**
+	 * Try to look up a checkpoint by it's ID in the cache.
+	 *
+	 * @param checkpointId ID of the checkpoint to look up.
+	 * @return The checkpoint or <code>null</code> if checkpoint not found.
+	 */
+	AbstractCheckpointStats tryGet(long checkpointId) {
+		if (cache != null) {
+			return cache.getIfPresent(checkpointId);
+		} else {
+			return null;
+		}
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/1fd2d2e1/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsDetailsHandler.java
----------------------------------------------------------------------
diff --git a/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsDetailsHandler.java b/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsDetailsHandler.java
new file mode 100644
index 0000000..6bb8300
--- /dev/null
+++ b/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsDetailsHandler.java
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.webmonitor.handlers.checkpoints;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import org.apache.flink.runtime.checkpoint.AbstractCheckpointStats;
+import org.apache.flink.runtime.checkpoint.CheckpointProperties;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsSnapshot;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsTracker;
+import org.apache.flink.runtime.checkpoint.CompletedCheckpointStats;
+import org.apache.flink.runtime.checkpoint.FailedCheckpointStats;
+import org.apache.flink.runtime.checkpoint.TaskStateStats;
+import org.apache.flink.runtime.executiongraph.AccessExecutionGraph;
+import org.apache.flink.runtime.webmonitor.ExecutionGraphHolder;
+import org.apache.flink.runtime.webmonitor.handlers.AbstractExecutionGraphRequestHandler;
+import org.apache.flink.runtime.webmonitor.handlers.JsonFactory;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.Map;
+
+/**
+ * Request handler that returns checkpoint stats for a single job vertex.
+ */
+public class CheckpointStatsDetailsHandler extends AbstractExecutionGraphRequestHandler {
+
+	private final CheckpointStatsCache cache;
+
+	public CheckpointStatsDetailsHandler(ExecutionGraphHolder executionGraphHolder, CheckpointStatsCache cache) {
+		super(executionGraphHolder);
+		this.cache = cache;
+	}
+
+	@Override
+	public String handleRequest(AccessExecutionGraph graph, Map<String, String> params) throws Exception {
+		long checkpointId = parseCheckpointId(params);
+		if (checkpointId == -1) {
+			return "{}";
+		}
+
+		CheckpointStatsTracker tracker = graph.getCheckpointStatsTracker();
+		CheckpointStatsSnapshot snapshot = tracker.createSnapshot();
+
+		AbstractCheckpointStats checkpoint = snapshot.getHistory().getCheckpointById(checkpointId);
+
+		if (checkpoint != null) {
+			cache.tryAdd(checkpoint);
+		} else {
+			checkpoint = cache.tryGet(checkpointId);
+
+			if (checkpoint == null) {
+				return "{}";
+			}
+		}
+
+		return writeResponse(checkpoint);
+	}
+
+	private String writeResponse(AbstractCheckpointStats checkpoint) throws IOException {
+		StringWriter writer = new StringWriter();
+		JsonGenerator gen = JsonFactory.jacksonFactory.createGenerator(writer);
+		gen.writeStartObject();
+
+		gen.writeNumberField("id", checkpoint.getCheckpointId());
+		gen.writeStringField("status", checkpoint.getStatus().toString());
+		gen.writeBooleanField("is_savepoint", CheckpointProperties.isSavepoint(checkpoint.getProperties()));
+		gen.writeNumberField("trigger_timestamp", checkpoint.getTriggerTimestamp());
+		gen.writeNumberField("latest_ack_timestamp", checkpoint.getLatestAckTimestamp());
+		gen.writeNumberField("state_size", checkpoint.getStateSize());
+		gen.writeNumberField("end_to_end_duration", checkpoint.getEndToEndDuration());
+		gen.writeNumberField("alignment_buffered", checkpoint.getAlignmentBuffered());
+		gen.writeNumberField("num_subtasks", checkpoint.getNumberOfSubtasks());
+		gen.writeNumberField("num_acknowledged_subtasks", checkpoint.getNumberOfAcknowledgedSubtasks());
+
+		if (checkpoint.getStatus().isCompleted()) {
+			// --- Completed ---
+			CompletedCheckpointStats completed = (CompletedCheckpointStats) checkpoint;
+
+			String externalPath = completed.getExternalPath();
+			if (externalPath != null) {
+				gen.writeStringField("external_path", externalPath);
+			}
+
+			gen.writeBooleanField("discarded", completed.isDiscarded());
+		}
+		else if (checkpoint.getStatus().isFailed()) {
+			// --- Failed ---
+			FailedCheckpointStats failed = (FailedCheckpointStats) checkpoint;
+
+			gen.writeNumberField("failure_timestamp", failed.getFailureTimestamp());
+
+			String failureMsg = failed.getFailureMessage();
+			if (failureMsg != null) {
+				gen.writeStringField("failure_message", failureMsg);
+			}
+		}
+
+		gen.writeObjectFieldStart("tasks");
+		for (TaskStateStats taskStats : checkpoint.getAllTaskStateStats()) {
+			gen.writeObjectFieldStart(taskStats.getJobVertexId().toString());
+
+			gen.writeNumberField("latest_ack_timestamp", taskStats.getLatestAckTimestamp());
+			gen.writeNumberField("state_size", taskStats.getStateSize());
+			gen.writeNumberField("end_to_end_duration", taskStats.getEndToEndDuration(checkpoint.getTriggerTimestamp()));
+			gen.writeNumberField("alignment_buffered", taskStats.getAlignmentBuffered());
+			gen.writeNumberField("num_subtasks", taskStats.getNumberOfSubtasks());
+			gen.writeNumberField("num_acknowledged_subtasks", taskStats.getNumberOfAcknowledgedSubtasks());
+
+			gen.writeEndObject();
+		}
+		gen.writeEndObject();
+
+		gen.writeEndObject();
+		gen.close();
+
+		return writer.toString();
+	}
+
+	/**
+	 * Returns the checkpoint ID parsed from the provided parameters.
+	 *
+	 * @param params Path parameters
+	 * @return Parsed checkpoint ID or <code>-1</code> if not available.
+	 */
+	static long parseCheckpointId(Map<String, String> params) {
+		String param = params.get("checkpointid");
+		if (param == null) {
+			return -1;
+		}
+
+		try {
+			return Long.parseLong(param);
+		} catch (NumberFormatException ignored) {
+			return -1;
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/1fd2d2e1/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsDetailsSubtasksHandler.java
----------------------------------------------------------------------
diff --git a/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsDetailsSubtasksHandler.java b/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsDetailsSubtasksHandler.java
new file mode 100644
index 0000000..3e3088b
--- /dev/null
+++ b/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsDetailsSubtasksHandler.java
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.webmonitor.handlers.checkpoints;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import org.apache.flink.runtime.checkpoint.AbstractCheckpointStats;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsSnapshot;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsTracker;
+import org.apache.flink.runtime.checkpoint.MinMaxAvgStats;
+import org.apache.flink.runtime.checkpoint.SubtaskStateStats;
+import org.apache.flink.runtime.checkpoint.TaskStateStats;
+import org.apache.flink.runtime.executiongraph.AccessExecutionGraph;
+import org.apache.flink.runtime.instance.ActorGateway;
+import org.apache.flink.runtime.jobgraph.JobVertexID;
+import org.apache.flink.runtime.webmonitor.ExecutionGraphHolder;
+import org.apache.flink.runtime.webmonitor.handlers.AbstractExecutionGraphRequestHandler;
+import org.apache.flink.runtime.webmonitor.handlers.AbstractJobVertexRequestHandler;
+import org.apache.flink.runtime.webmonitor.handlers.JsonFactory;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.Map;
+
+import static org.apache.flink.util.Preconditions.checkNotNull;
+
+/**
+ * Request handler that returns checkpoint stats for a single job vertex with
+ * the summary stats and all subtasks.
+ */
+public class CheckpointStatsDetailsSubtasksHandler extends AbstractExecutionGraphRequestHandler {
+
+	private final CheckpointStatsCache cache;
+
+	public CheckpointStatsDetailsSubtasksHandler(ExecutionGraphHolder executionGraphHolder, CheckpointStatsCache cache) {
+		super(executionGraphHolder);
+		this.cache = checkNotNull(cache);
+	}
+
+	@Override
+	public String handleJsonRequest(
+		Map<String, String> pathParams,
+		Map<String, String> queryParams,
+		ActorGateway jobManager) throws Exception {
+		return super.handleJsonRequest(pathParams, queryParams, jobManager);
+	}
+
+	@Override
+	public String handleRequest(AccessExecutionGraph graph, Map<String, String> params) throws Exception {
+		long checkpointId = CheckpointStatsDetailsHandler.parseCheckpointId(params);
+		if (checkpointId == -1) {
+			return "{}";
+		}
+
+		JobVertexID vertexId = AbstractJobVertexRequestHandler.parseJobVertexId(params);
+		if (vertexId == null) {
+			return "{}";
+		}
+
+		CheckpointStatsTracker tracker = graph.getCheckpointStatsTracker();
+		CheckpointStatsSnapshot snapshot = tracker.createSnapshot();
+
+		AbstractCheckpointStats checkpoint = snapshot.getHistory().getCheckpointById(checkpointId);
+
+		if (checkpoint != null) {
+			cache.tryAdd(checkpoint);
+		} else {
+			checkpoint = cache.tryGet(checkpointId);
+
+			if (checkpoint == null) {
+				return "{}";
+			}
+		}
+
+		return writeResponse(checkpoint, vertexId);
+	}
+
+	private String writeResponse(AbstractCheckpointStats checkpoint, JobVertexID vertexId) throws IOException {
+		StringWriter writer = new StringWriter();
+		JsonGenerator gen = JsonFactory.jacksonFactory.createGenerator(writer);
+		gen.writeStartObject();
+
+		TaskStateStats taskStats = checkpoint.getTaskStateStats(vertexId);
+		if (taskStats == null) {
+			return "{}";
+		}
+
+		// Overview
+		gen.writeNumberField("id", checkpoint.getCheckpointId());
+		gen.writeStringField("status", checkpoint.getStatus().toString());
+		gen.writeNumberField("latest_ack_timestamp", taskStats.getLatestAckTimestamp());
+		gen.writeNumberField("state_size", taskStats.getStateSize());
+		gen.writeNumberField("end_to_end_duration", taskStats.getEndToEndDuration(checkpoint.getTriggerTimestamp()));
+		gen.writeNumberField("alignment_buffered", taskStats.getAlignmentBuffered());
+		gen.writeNumberField("num_subtasks", taskStats.getNumberOfSubtasks());
+		gen.writeNumberField("num_acknowledged_subtasks", taskStats.getNumberOfAcknowledgedSubtasks());
+
+		if (taskStats.getNumberOfAcknowledgedSubtasks() > 0) {
+			gen.writeObjectFieldStart("summary");
+			gen.writeObjectFieldStart("state_size");
+			writeMinMaxAvg(gen, taskStats.getSummaryStats().getStateSizeStats());
+			gen.writeEndObject();
+
+			gen.writeObjectFieldStart("end_to_end_duration");
+			MinMaxAvgStats ackTimestampStats = taskStats.getSummaryStats().getAckTimestampStats();
+			gen.writeNumberField("min", Math.max(0, ackTimestampStats.getMinimum() - checkpoint.getTriggerTimestamp()));
+			gen.writeNumberField("max", Math.max(0, ackTimestampStats.getMaximum() - checkpoint.getTriggerTimestamp()));
+			gen.writeNumberField("avg", Math.max(0, ackTimestampStats.getAverage() - checkpoint.getTriggerTimestamp()));
+			gen.writeEndObject();
+
+			gen.writeObjectFieldStart("checkpoint_duration");
+			gen.writeObjectFieldStart("sync");
+			writeMinMaxAvg(gen, taskStats.getSummaryStats().getSyncCheckpointDurationStats());
+			gen.writeEndObject();
+			gen.writeObjectFieldStart("async");
+			writeMinMaxAvg(gen, taskStats.getSummaryStats().getAsyncCheckpointDurationStats());
+			gen.writeEndObject();
+			gen.writeEndObject();
+
+			gen.writeObjectFieldStart("alignment");
+			gen.writeObjectFieldStart("buffered");
+			writeMinMaxAvg(gen, taskStats.getSummaryStats().getAlignmentBufferedStats());
+			gen.writeEndObject();
+			gen.writeObjectFieldStart("duration");
+			writeMinMaxAvg(gen, taskStats.getSummaryStats().getAlignmentDurationStats());
+			gen.writeEndObject();
+			gen.writeEndObject();
+			gen.writeEndObject();
+		}
+
+		SubtaskStateStats[] subtasks = taskStats.getSubtaskStats();
+
+		gen.writeArrayFieldStart("subtasks");
+		for (int i = 0; i < subtasks.length; i++) {
+			SubtaskStateStats subtask = subtasks[i];
+
+			gen.writeStartObject();
+			gen.writeNumberField("index", i);
+
+			if (subtask != null) {
+				gen.writeStringField("status", "completed");
+				gen.writeNumberField("ack_timestamp", subtask.getAckTimestamp());
+				gen.writeNumberField("end_to_end_duration", subtask.getEndToEndDuration(checkpoint.getTriggerTimestamp()));
+				gen.writeNumberField("state_size", subtask.getStateSize());
+
+				gen.writeObjectFieldStart("checkpoint");
+				gen.writeNumberField("sync", subtask.getSyncCheckpointDuration());
+				gen.writeNumberField("async", subtask.getAsyncCheckpointDuration());
+				gen.writeEndObject();
+
+				gen.writeObjectFieldStart("alignment");
+				gen.writeNumberField("buffered", subtask.getAlignmentBuffered());
+				gen.writeNumberField("duration", subtask.getAlignmentDuration());
+				gen.writeEndObject();
+			} else {
+				gen.writeStringField("status", "pending");
+			}
+			gen.writeEndObject();
+		}
+		gen.writeEndArray();
+
+		gen.writeEndObject();
+		gen.close();
+
+		return writer.toString();
+	}
+
+	private void writeMinMaxAvg(JsonGenerator gen, MinMaxAvgStats minMaxAvg) throws IOException {
+		gen.writeNumberField("min", minMaxAvg.getMinimum());
+		gen.writeNumberField("max", minMaxAvg.getMaximum());
+		gen.writeNumberField("avg", minMaxAvg.getAverage());
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/1fd2d2e1/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsHandler.java
----------------------------------------------------------------------
diff --git a/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsHandler.java b/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsHandler.java
new file mode 100644
index 0000000..71f3637
--- /dev/null
+++ b/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsHandler.java
@@ -0,0 +1,235 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.webmonitor.handlers.checkpoints;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import org.apache.flink.runtime.checkpoint.AbstractCheckpointStats;
+import org.apache.flink.runtime.checkpoint.CheckpointProperties;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsCounts;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsHistory;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsSnapshot;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsTracker;
+import org.apache.flink.runtime.checkpoint.CompletedCheckpointStats;
+import org.apache.flink.runtime.checkpoint.CompletedCheckpointStatsSummary;
+import org.apache.flink.runtime.checkpoint.FailedCheckpointStats;
+import org.apache.flink.runtime.checkpoint.MinMaxAvgStats;
+import org.apache.flink.runtime.checkpoint.RestoredCheckpointStats;
+import org.apache.flink.runtime.executiongraph.AccessExecutionGraph;
+import org.apache.flink.runtime.webmonitor.ExecutionGraphHolder;
+import org.apache.flink.runtime.webmonitor.handlers.AbstractExecutionGraphRequestHandler;
+import org.apache.flink.runtime.webmonitor.handlers.JsonFactory;
+
+import javax.annotation.Nullable;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.Map;
+
+/**
+ * Handler that returns checkpoint statistics for a job.
+ */
+public class CheckpointStatsHandler extends AbstractExecutionGraphRequestHandler {
+
+	public CheckpointStatsHandler(ExecutionGraphHolder executionGraphHolder) {
+		super(executionGraphHolder);
+	}
+
+	@Override
+	public String handleRequest(AccessExecutionGraph graph, Map<String, String> params) throws Exception {
+		StringWriter writer = new StringWriter();
+		JsonGenerator gen = JsonFactory.jacksonFactory.createGenerator(writer);
+
+		CheckpointStatsTracker tracker = graph.getCheckpointStatsTracker();
+		CheckpointStatsSnapshot snapshot = tracker.createSnapshot();
+
+		gen.writeStartObject();
+
+		// Counts
+		writeCounts(gen, snapshot.getCounts());
+
+		// Summary
+		writeSummary(gen, snapshot.getSummaryStats());
+
+		CheckpointStatsHistory history = snapshot.getHistory();
+
+		// Latest
+		writeLatestCheckpoints(
+			gen,
+			history.getLatestCompletedCheckpoint(),
+			history.getLatestSavepoint(),
+			history.getLatestFailedCheckpoint(),
+			snapshot.getLatestRestoredCheckpoint());
+
+		// History
+		writeHistory(gen, snapshot.getHistory());
+
+		gen.writeEndObject();
+		gen.close();
+
+		return writer.toString();
+	}
+
+	private void writeCounts(JsonGenerator gen, CheckpointStatsCounts counts) throws IOException {
+		gen.writeObjectFieldStart("counts");
+		gen.writeNumberField("restored", counts.getNumberOfRestoredCheckpoints());
+		gen.writeNumberField("total", counts.getTotalNumberOfCheckpoints());
+		gen.writeNumberField("in_progress", counts.getNumberOfInProgressCheckpoints());
+		gen.writeNumberField("completed", counts.getNumberOfCompletedCheckpoints());
+		gen.writeNumberField("failed", counts.getNumberOfFailedCheckpoints());
+		gen.writeEndObject();
+	}
+
+	private void writeSummary(
+		JsonGenerator gen,
+		CompletedCheckpointStatsSummary summary) throws IOException {
+		gen.writeObjectFieldStart("summary");
+		gen.writeObjectFieldStart("state_size");
+		writeMinMaxAvg(gen, summary.getStateSizeStats());
+		gen.writeEndObject();
+
+		gen.writeObjectFieldStart("end_to_end_duration");
+		writeMinMaxAvg(gen, summary.getEndToEndDurationStats());
+		gen.writeEndObject();
+
+		gen.writeObjectFieldStart("alignment_buffered");
+		writeMinMaxAvg(gen, summary.getAlignmentBufferedStats());
+		gen.writeEndObject();
+		gen.writeEndObject();
+	}
+
+	private void writeMinMaxAvg(JsonGenerator gen, MinMaxAvgStats minMaxAvg) throws IOException {
+		gen.writeNumberField("min", minMaxAvg.getMinimum());
+		gen.writeNumberField("max", minMaxAvg.getMaximum());
+		gen.writeNumberField("avg", minMaxAvg.getAverage());
+	}
+
+	private void writeLatestCheckpoints(
+		JsonGenerator gen,
+		@Nullable CompletedCheckpointStats completed,
+		@Nullable CompletedCheckpointStats savepoint,
+		@Nullable FailedCheckpointStats failed,
+		@Nullable RestoredCheckpointStats restored) throws IOException {
+
+		gen.writeObjectFieldStart("latest");
+		// Completed checkpoint
+		if (completed != null) {
+			gen.writeObjectFieldStart("completed");
+			writeCheckpoint(gen, completed);
+
+			String externalPath = completed.getExternalPath();
+			if (externalPath != null) {
+				gen.writeStringField("external_path", completed.getExternalPath());
+			}
+
+			gen.writeEndObject();
+		}
+
+		// Completed savepoint
+		if (savepoint != null) {
+			gen.writeObjectFieldStart("savepoint");
+			writeCheckpoint(gen, savepoint);
+
+			String externalPath = savepoint.getExternalPath();
+			if (externalPath != null) {
+				gen.writeStringField("external_path", savepoint.getExternalPath());
+			}
+			gen.writeEndObject();
+		}
+
+		// Failed checkpoint
+		if (failed != null) {
+			gen.writeObjectFieldStart("failed");
+			writeCheckpoint(gen, failed);
+
+			gen.writeNumberField("failure_timestamp", failed.getFailureTimestamp());
+			String failureMsg = failed.getFailureMessage();
+			if (failureMsg != null) {
+				gen.writeStringField("failure_message", failureMsg);
+			}
+			gen.writeEndObject();
+		}
+
+		// Restored checkpoint
+		if (restored != null) {
+			gen.writeObjectFieldStart("restored");
+			gen.writeNumberField("id", restored.getCheckpointId());
+			gen.writeNumberField("restore_timestamp", restored.getRestoreTimestamp());
+			gen.writeBooleanField("is_savepoint", CheckpointProperties.isSavepoint(restored.getProperties()));
+
+			String externalPath = restored.getExternalPath();
+			if (externalPath != null) {
+				gen.writeStringField("external_path", externalPath);
+			}
+			gen.writeEndObject();
+		}
+		gen.writeEndObject();
+	}
+
+	private void writeCheckpoint(JsonGenerator gen, AbstractCheckpointStats checkpoint) throws IOException {
+		gen.writeNumberField("id", checkpoint.getCheckpointId());
+		gen.writeNumberField("trigger_timestamp", checkpoint.getTriggerTimestamp());
+		gen.writeNumberField("latest_ack_timestamp", checkpoint.getLatestAckTimestamp());
+		gen.writeNumberField("state_size", checkpoint.getStateSize());
+		gen.writeNumberField("end_to_end_duration", checkpoint.getEndToEndDuration());
+		gen.writeNumberField("alignment_buffered", checkpoint.getAlignmentBuffered());
+
+	}
+
+	private void writeHistory(JsonGenerator gen, CheckpointStatsHistory history) throws IOException {
+		gen.writeArrayFieldStart("history");
+		for (AbstractCheckpointStats checkpoint : history.getCheckpoints()) {
+			gen.writeStartObject();
+			gen.writeNumberField("id", checkpoint.getCheckpointId());
+			gen.writeStringField("status", checkpoint.getStatus().toString());
+			gen.writeBooleanField("is_savepoint", CheckpointProperties.isSavepoint(checkpoint.getProperties()));
+			gen.writeNumberField("trigger_timestamp", checkpoint.getTriggerTimestamp());
+			gen.writeNumberField("latest_ack_timestamp", checkpoint.getLatestAckTimestamp());
+			gen.writeNumberField("state_size", checkpoint.getStateSize());
+			gen.writeNumberField("end_to_end_duration", checkpoint.getEndToEndDuration());
+			gen.writeNumberField("alignment_buffered", checkpoint.getAlignmentBuffered());
+			gen.writeNumberField("num_subtasks", checkpoint.getNumberOfSubtasks());
+			gen.writeNumberField("num_acknowledged_subtasks", checkpoint.getNumberOfAcknowledgedSubtasks());
+
+			if (checkpoint.getStatus().isCompleted()) {
+				// --- Completed ---
+				CompletedCheckpointStats completed = (CompletedCheckpointStats) checkpoint;
+
+				String externalPath = completed.getExternalPath();
+				if (externalPath != null) {
+					gen.writeStringField("external_path", externalPath);
+				}
+
+				gen.writeBooleanField("discarded", completed.isDiscarded());
+			}
+			else if (checkpoint.getStatus().isFailed()) {
+				// --- Failed ---
+				FailedCheckpointStats failed = (FailedCheckpointStats) checkpoint;
+
+				gen.writeNumberField("failure_timestamp", failed.getFailureTimestamp());
+
+				String failureMsg = failed.getFailureMessage();
+				if (failureMsg != null) {
+					gen.writeStringField("failure_message", failureMsg);
+				}
+			}
+
+			gen.writeEndObject();
+		}
+		gen.writeEndArray();
+	}
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/1fd2d2e1/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointConfigHandlerTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointConfigHandlerTest.java b/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointConfigHandlerTest.java
new file mode 100644
index 0000000..410e044
--- /dev/null
+++ b/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointConfigHandlerTest.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.webmonitor.handlers.checkpoints;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsTracker;
+import org.apache.flink.runtime.executiongraph.AccessExecutionGraph;
+import org.apache.flink.runtime.jobgraph.JobVertexID;
+import org.apache.flink.runtime.jobgraph.tasks.ExternalizedCheckpointSettings;
+import org.apache.flink.runtime.jobgraph.tasks.JobSnapshottingSettings;
+import org.apache.flink.runtime.webmonitor.ExecutionGraphHolder;
+import org.junit.Test;
+
+import java.util.Collections;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class CheckpointConfigHandlerTest {
+
+	/**
+	 * Tests a simple config.
+	 */
+	@Test
+	public void testSimpleConfig() throws Exception {
+		long interval = 18231823L;
+		long timeout = 996979L;
+		long minPause = 119191919L;
+		int maxConcurrent = 12929329;
+		ExternalizedCheckpointSettings externalized = ExternalizedCheckpointSettings.none();
+
+		JobSnapshottingSettings settings = new JobSnapshottingSettings(
+			Collections.<JobVertexID>emptyList(),
+			Collections.<JobVertexID>emptyList(),
+			Collections.<JobVertexID>emptyList(),
+			interval,
+			timeout,
+			minPause,
+			maxConcurrent,
+			externalized,
+			true);
+
+		AccessExecutionGraph graph = mock(AccessExecutionGraph.class);
+		CheckpointStatsTracker tracker = mock(CheckpointStatsTracker.class);
+		when(graph.getCheckpointStatsTracker()).thenReturn(tracker);
+		when(tracker.getSnapshottingSettings()).thenReturn(settings);
+
+		CheckpointConfigHandler handler = new CheckpointConfigHandler(mock(ExecutionGraphHolder.class));
+		String json = handler.handleRequest(graph, Collections.<String, String>emptyMap());
+
+		ObjectMapper mapper = new ObjectMapper();
+		JsonNode rootNode = mapper.readTree(json);
+
+		assertEquals("exactly_once", rootNode.get("mode").asText());
+		assertEquals(interval, rootNode.get("interval").asLong());
+		assertEquals(timeout, rootNode.get("timeout").asLong());
+		assertEquals(minPause, rootNode.get("min_pause").asLong());
+		assertEquals(maxConcurrent, rootNode.get("max_concurrent").asInt());
+
+		JsonNode externalizedNode = rootNode.get("externalization");
+		assertNotNull(externalizedNode);
+		assertEquals(false, externalizedNode.get("enabled").asBoolean());
+	}
+
+	/**
+	 * Tests the that the isExactlyOnce flag is respected.
+	 */
+	@Test
+	public void testAtLeastOnce() throws Exception {
+		JobSnapshottingSettings settings = new JobSnapshottingSettings(
+			Collections.<JobVertexID>emptyList(),
+			Collections.<JobVertexID>emptyList(),
+			Collections.<JobVertexID>emptyList(),
+			996979L,
+			1818L,
+			1212L,
+			12,
+			ExternalizedCheckpointSettings.none(),
+			false); // at least once
+
+		AccessExecutionGraph graph = mock(AccessExecutionGraph.class);
+		CheckpointStatsTracker tracker = mock(CheckpointStatsTracker.class);
+		when(graph.getCheckpointStatsTracker()).thenReturn(tracker);
+		when(tracker.getSnapshottingSettings()).thenReturn(settings);
+
+		CheckpointConfigHandler handler = new CheckpointConfigHandler(mock(ExecutionGraphHolder.class));
+		String json = handler.handleRequest(graph, Collections.<String, String>emptyMap());
+
+		ObjectMapper mapper = new ObjectMapper();
+		JsonNode rootNode = mapper.readTree(json);
+
+		assertEquals("at_least_once", rootNode.get("mode").asText());
+	}
+
+	/**
+	 * Tests that the externalized checkpoint settings are forwarded.
+	 */
+	@Test
+	public void testEnabledExternalizedCheckpointSettings() throws Exception {
+		ExternalizedCheckpointSettings externalizedSettings = ExternalizedCheckpointSettings.externalizeCheckpoints(true);
+
+		JobSnapshottingSettings settings = new JobSnapshottingSettings(
+			Collections.<JobVertexID>emptyList(),
+			Collections.<JobVertexID>emptyList(),
+			Collections.<JobVertexID>emptyList(),
+			996979L,
+			1818L,
+			1212L,
+			12,
+			externalizedSettings,
+			false); // at least once
+
+		AccessExecutionGraph graph = mock(AccessExecutionGraph.class);
+		CheckpointStatsTracker tracker = mock(CheckpointStatsTracker.class);
+		when(graph.getCheckpointStatsTracker()).thenReturn(tracker);
+		when(tracker.getSnapshottingSettings()).thenReturn(settings);
+
+		CheckpointConfigHandler handler = new CheckpointConfigHandler(mock(ExecutionGraphHolder.class));
+		String json = handler.handleRequest(graph, Collections.<String, String>emptyMap());
+
+		ObjectMapper mapper = new ObjectMapper();
+		JsonNode externalizedNode = mapper.readTree(json).get("externalization");
+		assertNotNull(externalizedNode);
+		assertEquals(externalizedSettings.externalizeCheckpoints(), externalizedNode.get("enabled").asBoolean());
+		assertEquals(externalizedSettings.deleteOnCancellation(), externalizedNode.get("delete_on_cancellation").asBoolean());
+	}
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/1fd2d2e1/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsCacheTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsCacheTest.java b/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsCacheTest.java
new file mode 100644
index 0000000..0fada97
--- /dev/null
+++ b/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsCacheTest.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.webmonitor.handlers.checkpoints;
+
+import org.apache.flink.runtime.checkpoint.AbstractCheckpointStats;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsStatus;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class CheckpointStatsCacheTest {
+
+	@Test
+	public void testZeroSizeCache() throws Exception {
+		AbstractCheckpointStats checkpoint = createCheckpoint(0, CheckpointStatsStatus.COMPLETED);
+
+		CheckpointStatsCache cache = new CheckpointStatsCache(0);
+		cache.tryAdd(checkpoint);
+		assertNull(cache.tryGet(0L));
+	}
+
+	@Test
+	public void testCacheAddAndGet() throws Exception {
+		AbstractCheckpointStats chk0 = createCheckpoint(0, CheckpointStatsStatus.COMPLETED);
+		AbstractCheckpointStats chk1 = createCheckpoint(1, CheckpointStatsStatus.COMPLETED);
+		AbstractCheckpointStats chk2 = createCheckpoint(2, CheckpointStatsStatus.IN_PROGRESS);
+
+		CheckpointStatsCache cache = new CheckpointStatsCache(1);
+		cache.tryAdd(chk0);
+		assertEquals(chk0, cache.tryGet(0));
+
+		cache.tryAdd(chk1);
+		assertNull(cache.tryGet(0));
+		assertEquals(chk1, cache.tryGet(1));
+
+		cache.tryAdd(chk2);
+		assertNull(cache.tryGet(2));
+		assertNull(cache.tryGet(0));
+		assertEquals(chk1, cache.tryGet(1));
+	}
+
+	private AbstractCheckpointStats createCheckpoint(long id, CheckpointStatsStatus status) {
+		AbstractCheckpointStats checkpoint = mock(AbstractCheckpointStats.class);
+		when(checkpoint.getCheckpointId()).thenReturn(id);
+		when(checkpoint.getStatus()).thenReturn(status);
+		return checkpoint;
+	}
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/1fd2d2e1/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsDetailsHandlerTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsDetailsHandlerTest.java b/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsDetailsHandlerTest.java
new file mode 100644
index 0000000..17c8558
--- /dev/null
+++ b/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsDetailsHandlerTest.java
@@ -0,0 +1,286 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.webmonitor.handlers.checkpoints;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.flink.runtime.checkpoint.AbstractCheckpointStats;
+import org.apache.flink.runtime.checkpoint.CheckpointProperties;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsHistory;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsSnapshot;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsStatus;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsTracker;
+import org.apache.flink.runtime.checkpoint.CompletedCheckpointStats;
+import org.apache.flink.runtime.checkpoint.FailedCheckpointStats;
+import org.apache.flink.runtime.checkpoint.PendingCheckpointStats;
+import org.apache.flink.runtime.checkpoint.TaskStateStats;
+import org.apache.flink.runtime.executiongraph.AccessExecutionGraph;
+import org.apache.flink.runtime.jobgraph.JobVertexID;
+import org.apache.flink.runtime.webmonitor.ExecutionGraphHolder;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ThreadLocalRandom;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class CheckpointStatsDetailsHandlerTest {
+
+	/**
+	 * Tests request with illegal checkpoint ID param.
+	 */
+	@Test
+	public void testIllegalCheckpointId() throws Exception {
+		AccessExecutionGraph graph = mock(AccessExecutionGraph.class);
+		CheckpointStatsDetailsHandler handler = new CheckpointStatsDetailsHandler(mock(ExecutionGraphHolder.class), new CheckpointStatsCache(0));
+		Map<String, String> params = new HashMap<>();
+		params.put("checkpointid", "illegal checkpoint");
+		String json = handler.handleRequest(graph, params);
+
+		assertEquals("{}", json);
+	}
+
+	/**
+	 * Tests request with missing checkpoint ID param.
+	 */
+	@Test
+	public void testNoCheckpointIdParam() throws Exception {
+		AccessExecutionGraph graph = mock(AccessExecutionGraph.class);
+		CheckpointStatsDetailsHandler handler = new CheckpointStatsDetailsHandler(mock(ExecutionGraphHolder.class), new CheckpointStatsCache(0));
+		String json = handler.handleRequest(graph, Collections.<String, String>emptyMap());
+
+		assertEquals("{}", json);
+	}
+
+	/**
+	 * Test lookup of not existing checkpoint in history.
+	 */
+	@Test
+	public void testCheckpointNotFound() throws Exception {
+		CheckpointStatsHistory history = mock(CheckpointStatsHistory.class);
+		when(history.getCheckpointById(anyLong())).thenReturn(null); // not found
+
+		CheckpointStatsSnapshot snapshot = mock(CheckpointStatsSnapshot.class);
+		when(snapshot.getHistory()).thenReturn(history);
+
+		AccessExecutionGraph graph = mock(AccessExecutionGraph.class);
+		CheckpointStatsTracker tracker = mock(CheckpointStatsTracker.class);
+		when(graph.getCheckpointStatsTracker()).thenReturn(tracker);
+		when(tracker.createSnapshot()).thenReturn(snapshot);
+
+		CheckpointStatsDetailsHandler handler = new CheckpointStatsDetailsHandler(mock(ExecutionGraphHolder.class), new CheckpointStatsCache(0));
+		Map<String, String> params = new HashMap<>();
+		params.put("checkpointid", "123");
+		String json = handler.handleRequest(graph, params);
+
+		assertEquals("{}", json);
+		verify(history, times(1)).getCheckpointById(anyLong());
+	}
+
+	/**
+	 * Tests a checkpoint details request for an in progress checkpoint.
+	 */
+	@Test
+	public void testCheckpointDetailsRequestInProgressCheckpoint() throws Exception {
+		PendingCheckpointStats checkpoint = mock(PendingCheckpointStats.class);
+		when(checkpoint.getCheckpointId()).thenReturn(1992139L);
+		when(checkpoint.getStatus()).thenReturn(CheckpointStatsStatus.IN_PROGRESS);
+		when(checkpoint.getProperties()).thenReturn(CheckpointProperties.forStandardCheckpoint());
+		when(checkpoint.getTriggerTimestamp()).thenReturn(1919191900L);
+		when(checkpoint.getLatestAckTimestamp()).thenReturn(1977791901L);
+		when(checkpoint.getStateSize()).thenReturn(111939272822L);
+		when(checkpoint.getEndToEndDuration()).thenReturn(121191L);
+		when(checkpoint.getAlignmentBuffered()).thenReturn(1L);
+		when(checkpoint.getNumberOfSubtasks()).thenReturn(501);
+		when(checkpoint.getNumberOfAcknowledgedSubtasks()).thenReturn(101);
+
+		List<TaskStateStats> taskStats = new ArrayList<>();
+		TaskStateStats task1 = createTaskStateStats();
+		TaskStateStats task2 = createTaskStateStats();
+		taskStats.add(task1);
+		taskStats.add(task2);
+
+		when(checkpoint.getAllTaskStateStats()).thenReturn(taskStats);
+
+		JsonNode rootNode = triggerRequest(checkpoint);
+
+		assertEquals(checkpoint.getCheckpointId(), rootNode.get("id").asLong());
+		assertEquals(checkpoint.getStatus().toString(), rootNode.get("status").asText());
+		assertEquals(CheckpointProperties.isSavepoint(checkpoint.getProperties()), rootNode.get("is_savepoint").asBoolean());
+		assertEquals(checkpoint.getTriggerTimestamp(), rootNode.get("trigger_timestamp").asLong());
+		assertEquals(checkpoint.getLatestAckTimestamp(), rootNode.get("latest_ack_timestamp").asLong());
+		assertEquals(checkpoint.getStateSize(), rootNode.get("state_size").asLong());
+		assertEquals(checkpoint.getEndToEndDuration(), rootNode.get("end_to_end_duration").asLong());
+		assertEquals(checkpoint.getAlignmentBuffered(), rootNode.get("alignment_buffered").asLong());
+		assertEquals(checkpoint.getNumberOfSubtasks(), rootNode.get("num_subtasks").asInt());
+		assertEquals(checkpoint.getNumberOfAcknowledgedSubtasks(), rootNode.get("num_acknowledged_subtasks").asInt());
+
+		verifyTaskNode(task1, rootNode);
+		verifyTaskNode(task2, rootNode);
+	}
+
+	/**
+	 * Tests a checkpoint details request for a completed checkpoint.
+	 */
+	@Test
+	public void testCheckpointDetailsRequestCompletedCheckpoint() throws Exception {
+		CompletedCheckpointStats checkpoint = mock(CompletedCheckpointStats.class);
+		when(checkpoint.getCheckpointId()).thenReturn(1818213L);
+		when(checkpoint.getStatus()).thenReturn(CheckpointStatsStatus.COMPLETED);
+		when(checkpoint.getProperties()).thenReturn(CheckpointProperties.forStandardSavepoint());
+		when(checkpoint.getTriggerTimestamp()).thenReturn(1818L);
+		when(checkpoint.getLatestAckTimestamp()).thenReturn(11029222L);
+		when(checkpoint.getStateSize()).thenReturn(925281L);
+		when(checkpoint.getEndToEndDuration()).thenReturn(181819L);
+		when(checkpoint.getAlignmentBuffered()).thenReturn(1010198L);
+		when(checkpoint.getNumberOfSubtasks()).thenReturn(181271);
+		when(checkpoint.getNumberOfAcknowledgedSubtasks()).thenReturn(29821);
+		when(checkpoint.isDiscarded()).thenReturn(true);
+		when(checkpoint.getExternalPath()).thenReturn("checkpoint-external-path");
+
+		List<TaskStateStats> taskStats = new ArrayList<>();
+		TaskStateStats task1 = createTaskStateStats();
+		TaskStateStats task2 = createTaskStateStats();
+		taskStats.add(task1);
+		taskStats.add(task2);
+
+		when(checkpoint.getAllTaskStateStats()).thenReturn(taskStats);
+
+		JsonNode rootNode = triggerRequest(checkpoint);
+
+		assertEquals(checkpoint.getCheckpointId(), rootNode.get("id").asLong());
+		assertEquals(checkpoint.getStatus().toString(), rootNode.get("status").asText());
+		assertEquals(CheckpointProperties.isSavepoint(checkpoint.getProperties()), rootNode.get("is_savepoint").asBoolean());
+		assertEquals(checkpoint.getTriggerTimestamp(), rootNode.get("trigger_timestamp").asLong());
+		assertEquals(checkpoint.getLatestAckTimestamp(), rootNode.get("latest_ack_timestamp").asLong());
+		assertEquals(checkpoint.getStateSize(), rootNode.get("state_size").asLong());
+		assertEquals(checkpoint.getEndToEndDuration(), rootNode.get("end_to_end_duration").asLong());
+		assertEquals(checkpoint.getAlignmentBuffered(), rootNode.get("alignment_buffered").asLong());
+		assertEquals(checkpoint.isDiscarded(), rootNode.get("discarded").asBoolean());
+		assertEquals(checkpoint.getExternalPath(), rootNode.get("external_path").asText());
+		assertEquals(checkpoint.getNumberOfSubtasks(), rootNode.get("num_subtasks").asInt());
+		assertEquals(checkpoint.getNumberOfAcknowledgedSubtasks(), rootNode.get("num_acknowledged_subtasks").asInt());
+
+		verifyTaskNode(task1, rootNode);
+		verifyTaskNode(task2, rootNode);
+	}
+
+	/**
+	 * Tests a checkpoint details request for a failed checkpoint.
+	 */
+	@Test
+	public void testCheckpointDetailsRequestFailedCheckpoint() throws Exception {
+		FailedCheckpointStats checkpoint = mock(FailedCheckpointStats.class);
+		when(checkpoint.getCheckpointId()).thenReturn(1818213L);
+		when(checkpoint.getStatus()).thenReturn(CheckpointStatsStatus.FAILED);
+		when(checkpoint.getProperties()).thenReturn(CheckpointProperties.forStandardSavepoint());
+		when(checkpoint.getTriggerTimestamp()).thenReturn(1818L);
+		when(checkpoint.getLatestAckTimestamp()).thenReturn(11029222L);
+		when(checkpoint.getStateSize()).thenReturn(925281L);
+		when(checkpoint.getEndToEndDuration()).thenReturn(181819L);
+		when(checkpoint.getAlignmentBuffered()).thenReturn(1010198L);
+		when(checkpoint.getNumberOfSubtasks()).thenReturn(181271);
+		when(checkpoint.getNumberOfAcknowledgedSubtasks()).thenReturn(29821);
+		when(checkpoint.getFailureTimestamp()).thenReturn(123012890312093L);
+		when(checkpoint.getFailureMessage()).thenReturn("failure-message");
+
+		List<TaskStateStats> taskStats = new ArrayList<>();
+		TaskStateStats task1 = createTaskStateStats();
+		TaskStateStats task2 = createTaskStateStats();
+		taskStats.add(task1);
+		taskStats.add(task2);
+
+		when(checkpoint.getAllTaskStateStats()).thenReturn(taskStats);
+
+		JsonNode rootNode = triggerRequest(checkpoint);
+
+		assertEquals(checkpoint.getCheckpointId(), rootNode.get("id").asLong());
+		assertEquals(checkpoint.getStatus().toString(), rootNode.get("status").asText());
+		assertEquals(CheckpointProperties.isSavepoint(checkpoint.getProperties()), rootNode.get("is_savepoint").asBoolean());
+		assertEquals(checkpoint.getTriggerTimestamp(), rootNode.get("trigger_timestamp").asLong());
+		assertEquals(checkpoint.getLatestAckTimestamp(), rootNode.get("latest_ack_timestamp").asLong());
+		assertEquals(checkpoint.getStateSize(), rootNode.get("state_size").asLong());
+		assertEquals(checkpoint.getEndToEndDuration(), rootNode.get("end_to_end_duration").asLong());
+		assertEquals(checkpoint.getAlignmentBuffered(), rootNode.get("alignment_buffered").asLong());
+		assertEquals(checkpoint.getFailureTimestamp(), rootNode.get("failure_timestamp").asLong());
+		assertEquals(checkpoint.getFailureMessage(), rootNode.get("failure_message").asText());
+		assertEquals(checkpoint.getNumberOfSubtasks(), rootNode.get("num_subtasks").asInt());
+		assertEquals(checkpoint.getNumberOfAcknowledgedSubtasks(), rootNode.get("num_acknowledged_subtasks").asInt());
+
+		verifyTaskNode(task1, rootNode);
+		verifyTaskNode(task2, rootNode);
+	}
+
+	// ------------------------------------------------------------------------
+
+	static JsonNode triggerRequest(AbstractCheckpointStats checkpoint) throws Exception {
+		CheckpointStatsHistory history = mock(CheckpointStatsHistory.class);
+		when(history.getCheckpointById(anyLong())).thenReturn(checkpoint);
+		CheckpointStatsSnapshot snapshot = mock(CheckpointStatsSnapshot.class);
+		when(snapshot.getHistory()).thenReturn(history);
+
+		AccessExecutionGraph graph = mock(AccessExecutionGraph.class);
+		CheckpointStatsTracker tracker = mock(CheckpointStatsTracker.class);
+		when(graph.getCheckpointStatsTracker()).thenReturn(tracker);
+		when(tracker.createSnapshot()).thenReturn(snapshot);
+
+		CheckpointStatsDetailsHandler handler = new CheckpointStatsDetailsHandler(mock(ExecutionGraphHolder.class), new CheckpointStatsCache(0));
+		Map<String, String> params = new HashMap<>();
+		params.put("checkpointid", "123");
+		String json = handler.handleRequest(graph, params);
+
+		ObjectMapper mapper = new ObjectMapper();
+		return mapper.readTree(json);
+	}
+
+	static void verifyTaskNode(TaskStateStats task, JsonNode parentNode) {
+		long duration = ThreadLocalRandom.current().nextInt(128);
+
+		JsonNode taskNode = parentNode.get("tasks").get(task.getJobVertexId().toString());
+		assertEquals(task.getLatestAckTimestamp(), taskNode.get("latest_ack_timestamp").asLong());
+		assertEquals(task.getStateSize(), taskNode.get("state_size").asLong());
+		assertEquals(task.getEndToEndDuration(task.getLatestAckTimestamp() - duration), taskNode.get("end_to_end_duration").asLong());
+		assertEquals(task.getAlignmentBuffered(), taskNode.get("alignment_buffered").asLong());
+		assertEquals(task.getNumberOfSubtasks(), taskNode.get("num_subtasks").asInt());
+		assertEquals(task.getNumberOfAcknowledgedSubtasks(), taskNode.get("num_acknowledged_subtasks").asInt());
+	}
+
+	private static TaskStateStats createTaskStateStats() {
+		ThreadLocalRandom rand = ThreadLocalRandom.current();
+
+		TaskStateStats task = mock(TaskStateStats.class);
+		when(task.getJobVertexId()).thenReturn(new JobVertexID());
+		when(task.getLatestAckTimestamp()).thenReturn(rand.nextLong(1024) + 1);
+		when(task.getStateSize()).thenReturn(rand.nextLong(1024) + 1);
+		when(task.getEndToEndDuration(anyLong())).thenReturn(rand.nextLong(1024) + 1);
+		when(task.getAlignmentBuffered()).thenReturn(rand.nextLong(1024) + 1);
+		when(task.getNumberOfSubtasks()).thenReturn(rand.nextInt(1024) + 1);
+		when(task.getNumberOfAcknowledgedSubtasks()).thenReturn(rand.nextInt(1024) + 1);
+		return task;
+	}
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/1fd2d2e1/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsHandlerTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsHandlerTest.java b/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsHandlerTest.java
new file mode 100644
index 0000000..8274b36
--- /dev/null
+++ b/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsHandlerTest.java
@@ -0,0 +1,303 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.webmonitor.handlers.checkpoints;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.flink.runtime.checkpoint.AbstractCheckpointStats;
+import org.apache.flink.runtime.checkpoint.CheckpointProperties;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsCounts;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsHistory;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsSnapshot;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsStatus;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsTracker;
+import org.apache.flink.runtime.checkpoint.CompletedCheckpointStats;
+import org.apache.flink.runtime.checkpoint.CompletedCheckpointStatsSummary;
+import org.apache.flink.runtime.checkpoint.FailedCheckpointStats;
+import org.apache.flink.runtime.checkpoint.MinMaxAvgStats;
+import org.apache.flink.runtime.checkpoint.PendingCheckpointStats;
+import org.apache.flink.runtime.checkpoint.RestoredCheckpointStats;
+import org.apache.flink.runtime.executiongraph.AccessExecutionGraph;
+import org.apache.flink.runtime.webmonitor.ExecutionGraphHolder;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class CheckpointStatsHandlerTest {
+
+	/**
+	 * Tests a complete checkpoint stats snapshot.
+	 */
+	@Test
+	public void testCheckpointStatsRequest() throws Exception {
+		// Counts
+		CheckpointStatsCounts counts = mock(CheckpointStatsCounts.class);
+		when(counts.getNumberOfRestoredCheckpoints()).thenReturn(123123123L);
+		when(counts.getTotalNumberOfCheckpoints()).thenReturn(12981231203L);
+		when(counts.getNumberOfInProgressCheckpoints()).thenReturn(191919);
+		when(counts.getNumberOfCompletedCheckpoints()).thenReturn(882828200L);
+		when(counts.getNumberOfFailedCheckpoints()).thenReturn(99171510L);
+
+		// Summary
+		CompletedCheckpointStatsSummary summary = mock(CompletedCheckpointStatsSummary.class);
+
+		MinMaxAvgStats stateSizeSummary = mock(MinMaxAvgStats.class);
+		when(stateSizeSummary.getMinimum()).thenReturn(81238123L);
+		when(stateSizeSummary.getMaximum()).thenReturn(19919191999L);
+		when(stateSizeSummary.getAverage()).thenReturn(1133L);
+
+		MinMaxAvgStats durationSummary = mock(MinMaxAvgStats.class);
+		when(durationSummary.getMinimum()).thenReturn(1182L);
+		when(durationSummary.getMaximum()).thenReturn(88654L);
+		when(durationSummary.getAverage()).thenReturn(171L);
+
+		MinMaxAvgStats alignmentBufferedSummary = mock(MinMaxAvgStats.class);
+		when(alignmentBufferedSummary.getMinimum()).thenReturn(81818181899L);
+		when(alignmentBufferedSummary.getMaximum()).thenReturn(89999911118654L);
+		when(alignmentBufferedSummary.getAverage()).thenReturn(11203131L);
+
+		when(summary.getStateSizeStats()).thenReturn(stateSizeSummary);
+		when(summary.getEndToEndDurationStats()).thenReturn(durationSummary);
+		when(summary.getAlignmentBufferedStats()).thenReturn(alignmentBufferedSummary);
+
+		// Latest
+		CompletedCheckpointStats latestCompleted = mock(CompletedCheckpointStats.class);
+		when(latestCompleted.getCheckpointId()).thenReturn(1992139L);
+		when(latestCompleted.getTriggerTimestamp()).thenReturn(1919191900L);
+		when(latestCompleted.getLatestAckTimestamp()).thenReturn(1977791901L);
+		when(latestCompleted.getStateSize()).thenReturn(111939272822L);
+		when(latestCompleted.getEndToEndDuration()).thenReturn(121191L);
+		when(latestCompleted.getAlignmentBuffered()).thenReturn(1L);
+		when(latestCompleted.getExternalPath()).thenReturn("latest-completed-external-path");
+
+		CompletedCheckpointStats latestSavepoint = mock(CompletedCheckpointStats.class);
+		when(latestSavepoint.getCheckpointId()).thenReturn(1992139L);
+		when(latestSavepoint.getTriggerTimestamp()).thenReturn(1919191900L);
+		when(latestSavepoint.getLatestAckTimestamp()).thenReturn(1977791901L);
+		when(latestSavepoint.getStateSize()).thenReturn(111939272822L);
+		when(latestSavepoint.getEndToEndDuration()).thenReturn(121191L);
+		when(latestCompleted.getAlignmentBuffered()).thenReturn(182813L);
+		when(latestSavepoint.getExternalPath()).thenReturn("savepoint-external-path");
+
+		FailedCheckpointStats latestFailed = mock(FailedCheckpointStats.class);
+		when(latestFailed.getCheckpointId()).thenReturn(1112L);
+		when(latestFailed.getTriggerTimestamp()).thenReturn(12828L);
+		when(latestFailed.getLatestAckTimestamp()).thenReturn(1901L);
+		when(latestFailed.getFailureTimestamp()).thenReturn(11999976L);
+		when(latestFailed.getStateSize()).thenReturn(111L);
+		when(latestFailed.getEndToEndDuration()).thenReturn(12L);
+		when(latestFailed.getAlignmentBuffered()).thenReturn(2L);
+		when(latestFailed.getFailureMessage()).thenReturn("expected cause");
+
+		RestoredCheckpointStats latestRestored = mock(RestoredCheckpointStats.class);
+		when(latestRestored.getCheckpointId()).thenReturn(1199L);
+		when(latestRestored.getRestoreTimestamp()).thenReturn(434242L);
+		when(latestRestored.getProperties()).thenReturn(CheckpointProperties.forStandardSavepoint());
+		when(latestRestored.getExternalPath()).thenReturn("restored savepoint path");
+
+		// History
+		CheckpointStatsHistory history = mock(CheckpointStatsHistory.class);
+		List<AbstractCheckpointStats> checkpoints = new ArrayList<>();
+
+		PendingCheckpointStats inProgress = mock(PendingCheckpointStats.class);
+		when(inProgress.getCheckpointId()).thenReturn(1992139L);
+		when(inProgress.getStatus()).thenReturn(CheckpointStatsStatus.IN_PROGRESS);
+		when(inProgress.getProperties()).thenReturn(CheckpointProperties.forStandardCheckpoint());
+		when(inProgress.getTriggerTimestamp()).thenReturn(1919191900L);
+		when(inProgress.getLatestAckTimestamp()).thenReturn(1977791901L);
+		when(inProgress.getStateSize()).thenReturn(111939272822L);
+		when(inProgress.getEndToEndDuration()).thenReturn(121191L);
+		when(inProgress.getAlignmentBuffered()).thenReturn(1L);
+		when(inProgress.getNumberOfSubtasks()).thenReturn(501);
+		when(inProgress.getNumberOfAcknowledgedSubtasks()).thenReturn(101);
+
+		CompletedCheckpointStats completedSavepoint = mock(CompletedCheckpointStats.class);
+		when(completedSavepoint.getCheckpointId()).thenReturn(1322139L);
+		when(completedSavepoint.getStatus()).thenReturn(CheckpointStatsStatus.COMPLETED);
+		when(completedSavepoint.getProperties()).thenReturn(CheckpointProperties.forStandardSavepoint());
+		when(completedSavepoint.getTriggerTimestamp()).thenReturn(191900L);
+		when(completedSavepoint.getLatestAckTimestamp()).thenReturn(197791901L);
+		when(completedSavepoint.getStateSize()).thenReturn(1119822L);
+		when(completedSavepoint.getEndToEndDuration()).thenReturn(12191L);
+		when(completedSavepoint.getAlignmentBuffered()).thenReturn(111L);
+		when(completedSavepoint.getNumberOfSubtasks()).thenReturn(33501);
+		when(completedSavepoint.getNumberOfAcknowledgedSubtasks()).thenReturn(211);
+		when(completedSavepoint.isDiscarded()).thenReturn(true);
+		when(completedSavepoint.getExternalPath()).thenReturn("completed-external-path");
+
+		FailedCheckpointStats failed = mock(FailedCheckpointStats.class);
+		when(failed.getCheckpointId()).thenReturn(110719L);
+		when(failed.getStatus()).thenReturn(CheckpointStatsStatus.FAILED);
+		when(failed.getProperties()).thenReturn(CheckpointProperties.forStandardCheckpoint());
+		when(failed.getTriggerTimestamp()).thenReturn(191900L);
+		when(failed.getLatestAckTimestamp()).thenReturn(197791901L);
+		when(failed.getStateSize()).thenReturn(1119822L);
+		when(failed.getEndToEndDuration()).thenReturn(12191L);
+		when(failed.getAlignmentBuffered()).thenReturn(111L);
+		when(failed.getNumberOfSubtasks()).thenReturn(33501);
+		when(failed.getNumberOfAcknowledgedSubtasks()).thenReturn(1);
+		when(failed.getFailureTimestamp()).thenReturn(119230L);
+		when(failed.getFailureMessage()).thenReturn("failure message");
+
+		checkpoints.add(inProgress);
+		checkpoints.add(completedSavepoint);
+		checkpoints.add(failed);
+		when(history.getCheckpoints()).thenReturn(checkpoints);
+		when(history.getLatestCompletedCheckpoint()).thenReturn(latestCompleted);
+		when(history.getLatestSavepoint()).thenReturn(latestSavepoint);
+		when(history.getLatestFailedCheckpoint()).thenReturn(latestFailed);
+
+		CheckpointStatsSnapshot snapshot = mock(CheckpointStatsSnapshot.class);
+		when(snapshot.getCounts()).thenReturn(counts);
+		when(snapshot.getSummaryStats()).thenReturn(summary);
+		when(snapshot.getHistory()).thenReturn(history);
+		when(snapshot.getLatestRestoredCheckpoint()).thenReturn(latestRestored);
+
+		AccessExecutionGraph graph = mock(AccessExecutionGraph.class);
+		CheckpointStatsTracker tracker = mock(CheckpointStatsTracker.class);
+		when(graph.getCheckpointStatsTracker()).thenReturn(tracker);
+		when(tracker.createSnapshot()).thenReturn(snapshot);
+
+		CheckpointStatsHandler handler = new CheckpointStatsHandler(mock(ExecutionGraphHolder.class));
+		String json = handler.handleRequest(graph, Collections.<String, String>emptyMap());
+
+		ObjectMapper mapper = new ObjectMapper();
+		JsonNode rootNode = mapper.readTree(json);
+
+		JsonNode countNode = rootNode.get("counts");
+		assertEquals(counts.getNumberOfRestoredCheckpoints(), countNode.get("restored").asLong());
+		assertEquals(counts.getTotalNumberOfCheckpoints(), countNode.get("total").asLong());
+		assertEquals(counts.getNumberOfInProgressCheckpoints(), countNode.get("in_progress").asLong());
+		assertEquals(counts.getNumberOfCompletedCheckpoints(), countNode.get("completed").asLong());
+		assertEquals(counts.getNumberOfFailedCheckpoints(), countNode.get("failed").asLong());
+
+		JsonNode summaryNode = rootNode.get("summary");
+		JsonNode sizeSummaryNode = summaryNode.get("state_size");
+		assertEquals(stateSizeSummary.getMinimum(), sizeSummaryNode.get("min").asLong());
+		assertEquals(stateSizeSummary.getMaximum(), sizeSummaryNode.get("max").asLong());
+		assertEquals(stateSizeSummary.getAverage(), sizeSummaryNode.get("avg").asLong());
+
+		JsonNode durationSummaryNode = summaryNode.get("end_to_end_duration");
+		assertEquals(durationSummary.getMinimum(), durationSummaryNode.get("min").asLong());
+		assertEquals(durationSummary.getMaximum(), durationSummaryNode.get("max").asLong());
+		assertEquals(durationSummary.getAverage(), durationSummaryNode.get("avg").asLong());
+
+		JsonNode alignmentBufferedNode = summaryNode.get("alignment_buffered");
+		assertEquals(alignmentBufferedSummary.getMinimum(), alignmentBufferedNode.get("min").asLong());
+		assertEquals(alignmentBufferedSummary.getMaximum(), alignmentBufferedNode.get("max").asLong());
+		assertEquals(alignmentBufferedSummary.getAverage(), alignmentBufferedNode.get("avg").asLong());
+
+		JsonNode latestNode = rootNode.get("latest");
+		JsonNode latestCheckpointNode = latestNode.get("completed");
+		assertEquals(latestCompleted.getCheckpointId(), latestCheckpointNode.get("id").asLong());
+		assertEquals(latestCompleted.getTriggerTimestamp(), latestCheckpointNode.get("trigger_timestamp").asLong());
+		assertEquals(latestCompleted.getLatestAckTimestamp(), latestCheckpointNode.get("latest_ack_timestamp").asLong());
+		assertEquals(latestCompleted.getStateSize(), latestCheckpointNode.get("state_size").asLong());
+		assertEquals(latestCompleted.getEndToEndDuration(), latestCheckpointNode.get("end_to_end_duration").asLong());
+		assertEquals(latestCompleted.getAlignmentBuffered(), latestCheckpointNode.get("alignment_buffered").asLong());
+		assertEquals(latestCompleted.getExternalPath(), latestCheckpointNode.get("external_path").asText());
+
+		JsonNode latestSavepointNode = latestNode.get("savepoint");
+		assertEquals(latestSavepoint.getCheckpointId(), latestSavepointNode.get("id").asLong());
+		assertEquals(latestSavepoint.getTriggerTimestamp(), latestSavepointNode.get("trigger_timestamp").asLong());
+		assertEquals(latestSavepoint.getLatestAckTimestamp(), latestSavepointNode.get("latest_ack_timestamp").asLong());
+		assertEquals(latestSavepoint.getStateSize(), latestSavepointNode.get("state_size").asLong());
+		assertEquals(latestSavepoint.getEndToEndDuration(), latestSavepointNode.get("end_to_end_duration").asLong());
+		assertEquals(latestSavepoint.getAlignmentBuffered(), latestSavepointNode.get("alignment_buffered").asLong());
+		assertEquals(latestSavepoint.getExternalPath(), latestSavepointNode.get("external_path").asText());
+
+		JsonNode latestFailedNode = latestNode.get("failed");
+		assertEquals(latestFailed.getCheckpointId(), latestFailedNode.get("id").asLong());
+		assertEquals(latestFailed.getTriggerTimestamp(), latestFailedNode.get("trigger_timestamp").asLong());
+		assertEquals(latestFailed.getLatestAckTimestamp(), latestFailedNode.get("latest_ack_timestamp").asLong());
+		assertEquals(latestFailed.getStateSize(), latestFailedNode.get("state_size").asLong());
+		assertEquals(latestFailed.getEndToEndDuration(), latestFailedNode.get("end_to_end_duration").asLong());
+		assertEquals(latestFailed.getAlignmentBuffered(), latestFailedNode.get("alignment_buffered").asLong());
+		assertEquals(latestFailed.getFailureTimestamp(), latestFailedNode.get("failure_timestamp").asLong());
+		assertEquals(latestFailed.getFailureMessage(), latestFailedNode.get("failure_message").asText());
+
+		JsonNode latestRestoredNode = latestNode.get("restored");
+		assertEquals(latestRestored.getCheckpointId(), latestRestoredNode.get("id").asLong());
+		assertEquals(latestRestored.getRestoreTimestamp(), latestRestoredNode.get("restore_timestamp").asLong());
+		assertEquals(CheckpointProperties.isSavepoint(latestRestored.getProperties()), latestRestoredNode.get("is_savepoint").asBoolean());
+		assertEquals(latestRestored.getExternalPath(), latestRestoredNode.get("external_path").asText());
+
+		JsonNode historyNode = rootNode.get("history");
+		Iterator<JsonNode> it = historyNode.iterator();
+
+		assertTrue(it.hasNext());
+		JsonNode inProgressNode = it.next();
+
+		assertEquals(inProgress.getCheckpointId(), inProgressNode.get("id").asLong());
+		assertEquals(inProgress.getStatus().toString(), inProgressNode.get("status").asText());
+		assertEquals(CheckpointProperties.isSavepoint(inProgress.getProperties()), inProgressNode.get("is_savepoint").asBoolean());
+		assertEquals(inProgress.getTriggerTimestamp(), inProgressNode.get("trigger_timestamp").asLong());
+		assertEquals(inProgress.getLatestAckTimestamp(), inProgressNode.get("latest_ack_timestamp").asLong());
+		assertEquals(inProgress.getStateSize(), inProgressNode.get("state_size").asLong());
+		assertEquals(inProgress.getEndToEndDuration(), inProgressNode.get("end_to_end_duration").asLong());
+		assertEquals(inProgress.getAlignmentBuffered(), inProgressNode.get("alignment_buffered").asLong());
+		assertEquals(inProgress.getNumberOfSubtasks(), inProgressNode.get("num_subtasks").asInt());
+		assertEquals(inProgress.getNumberOfAcknowledgedSubtasks(), inProgressNode.get("num_acknowledged_subtasks").asInt());
+
+		assertTrue(it.hasNext());
+		JsonNode completedSavepointNode = it.next();
+
+		assertEquals(completedSavepoint.getCheckpointId(), completedSavepointNode.get("id").asLong());
+		assertEquals(completedSavepoint.getStatus().toString(), completedSavepointNode.get("status").asText());
+		assertEquals(CheckpointProperties.isSavepoint(completedSavepoint.getProperties()), completedSavepointNode.get("is_savepoint").asBoolean());
+		assertEquals(completedSavepoint.getTriggerTimestamp(), completedSavepointNode.get("trigger_timestamp").asLong());
+		assertEquals(completedSavepoint.getLatestAckTimestamp(), completedSavepointNode.get("latest_ack_timestamp").asLong());
+		assertEquals(completedSavepoint.getStateSize(), completedSavepointNode.get("state_size").asLong());
+		assertEquals(completedSavepoint.getEndToEndDuration(), completedSavepointNode.get("end_to_end_duration").asLong());
+		assertEquals(completedSavepoint.getAlignmentBuffered(), completedSavepointNode.get("alignment_buffered").asLong());
+		assertEquals(completedSavepoint.getNumberOfSubtasks(), completedSavepointNode.get("num_subtasks").asInt());
+		assertEquals(completedSavepoint.getNumberOfAcknowledgedSubtasks(), completedSavepointNode.get("num_acknowledged_subtasks").asInt());
+
+		assertEquals(completedSavepoint.getExternalPath(), completedSavepointNode.get("external_path").asText());
+		assertEquals(completedSavepoint.isDiscarded(), completedSavepointNode.get("discarded").asBoolean());
+
+		assertTrue(it.hasNext());
+		JsonNode failedNode = it.next();
+
+		assertEquals(failed.getCheckpointId(), failedNode.get("id").asLong());
+		assertEquals(failed.getStatus().toString(), failedNode.get("status").asText());
+		assertEquals(CheckpointProperties.isSavepoint(failed.getProperties()), failedNode.get("is_savepoint").asBoolean());
+		assertEquals(failed.getTriggerTimestamp(), failedNode.get("trigger_timestamp").asLong());
+		assertEquals(failed.getLatestAckTimestamp(), failedNode.get("latest_ack_timestamp").asLong());
+		assertEquals(failed.getStateSize(), failedNode.get("state_size").asLong());
+		assertEquals(failed.getEndToEndDuration(), failedNode.get("end_to_end_duration").asLong());
+		assertEquals(failed.getAlignmentBuffered(), failedNode.get("alignment_buffered").asLong());
+		assertEquals(failed.getNumberOfSubtasks(), failedNode.get("num_subtasks").asInt());
+		assertEquals(failed.getNumberOfAcknowledgedSubtasks(), failedNode.get("num_acknowledged_subtasks").asInt());
+
+		assertEquals(failed.getFailureTimestamp(), failedNode.get("failure_timestamp").asLong());
+		assertEquals(failed.getFailureMessage(), failedNode.get("failure_message").asText());
+
+		assertFalse(it.hasNext());
+	}
+}


[06/11] flink git commit: [FLINK-4410] [runtime, runtime-web] Remove old checkpoint stats tracker code

Posted by uc...@apache.org.
[FLINK-4410] [runtime, runtime-web] Remove old checkpoint stats tracker code


Project: http://git-wip-us.apache.org/repos/asf/flink/repo
Commit: http://git-wip-us.apache.org/repos/asf/flink/commit/6ea77ed5
Tree: http://git-wip-us.apache.org/repos/asf/flink/tree/6ea77ed5
Diff: http://git-wip-us.apache.org/repos/asf/flink/diff/6ea77ed5

Branch: refs/heads/release-1.2
Commit: 6ea77ed5ad1d0b79ebcf4df40c4ef993ba2063fc
Parents: 7348424
Author: Ufuk Celebi <uc...@apache.org>
Authored: Fri Dec 23 20:31:29 2016 +0100
Committer: Ufuk Celebi <uc...@apache.org>
Committed: Tue Jan 10 09:47:55 2017 +0100

----------------------------------------------------------------------
 .../handlers/JobCheckpointsHandler.java         |  99 ----
 .../handlers/JobVertexCheckpointsHandler.java   |  73 ---
 .../handlers/JobCheckpointsHandlerTest.java     | 204 --------
 .../JobVertexCheckpointsHandlerTest.java        | 141 ------
 .../jobs/job.plan.node.checkpoints.job.jade     |  98 ----
 .../job.plan.node.checkpoints.operator.jade     |  64 ---
 .../ArchivedCheckpointStatsTracker.java         |  53 ---
 .../checkpoint/stats/CheckpointStats.java       | 131 ------
 .../stats/CheckpointStatsTracker.java           |  58 ---
 .../stats/DisabledCheckpointStatsTracker.java   |  45 --
 .../checkpoint/stats/JobCheckpointStats.java    | 114 -----
 .../stats/OperatorCheckpointStats.java          | 115 -----
 .../stats/SimpleCheckpointStatsTracker.java     | 468 -------------------
 .../CheckpointCoordinatorFailureTest.java       |   4 +-
 .../DisabledCheckpointStatsTrackerTest.java     |  34 --
 .../stats/SimpleCheckpointStatsTrackerTest.java | 381 ---------------
 16 files changed, 1 insertion(+), 2081 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/flink/blob/6ea77ed5/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/JobCheckpointsHandler.java
----------------------------------------------------------------------
diff --git a/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/JobCheckpointsHandler.java b/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/JobCheckpointsHandler.java
deleted file mode 100644
index 404a14e..0000000
--- a/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/JobCheckpointsHandler.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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.
- */
-
-package org.apache.flink.runtime.webmonitor.handlers;
-
-import com.fasterxml.jackson.core.JsonGenerator;
-import org.apache.flink.runtime.checkpoint.stats.CheckpointStats;
-import org.apache.flink.runtime.checkpoint.stats.CheckpointStatsTracker;
-import org.apache.flink.runtime.checkpoint.stats.JobCheckpointStats;
-import org.apache.flink.runtime.executiongraph.AccessExecutionGraph;
-import org.apache.flink.runtime.webmonitor.ExecutionGraphHolder;
-import scala.Option;
-
-import java.io.StringWriter;
-import java.util.Map;
-
-/**
- * Request handler that returns checkpoint stats for a job.
- */
-public class JobCheckpointsHandler extends AbstractExecutionGraphRequestHandler {
-
-	public JobCheckpointsHandler(ExecutionGraphHolder executionGraphHolder) {
-		super(executionGraphHolder);
-	}
-
-	@Override
-	public String handleRequest(AccessExecutionGraph graph, Map<String, String> params) throws Exception {
-		StringWriter writer = new StringWriter();
-		JsonGenerator gen = JsonFactory.jacksonFactory.createGenerator(writer);
-
-		CheckpointStatsTracker tracker = graph.getCheckpointStatsTracker();
-
-		gen.writeStartObject();
-
-		if (tracker != null) {
-			Option<JobCheckpointStats> stats = tracker.getJobStats();
-
-			if (stats.isDefined()) {
-				JobCheckpointStats jobStats = stats.get();
-
-				// Total number of checkpoints
-				gen.writeNumberField("count", jobStats.getCount());
-
-				// Optional external path
-				if (jobStats.getExternalPath() != null) {
-					gen.writeStringField("external-path", jobStats.getExternalPath());
-				}
-
-				// Duration
-				gen.writeFieldName("duration");
-				gen.writeStartObject();
-				gen.writeNumberField("min", jobStats.getMinDuration());
-				gen.writeNumberField("max", jobStats.getMaxDuration());
-				gen.writeNumberField("avg", jobStats.getAverageDuration());
-				gen.writeEndObject();
-
-				// State size
-				gen.writeFieldName("size");
-				gen.writeStartObject();
-				gen.writeNumberField("min", jobStats.getMinStateSize());
-				gen.writeNumberField("max", jobStats.getMaxStateSize());
-				gen.writeNumberField("avg", jobStats.getAverageStateSize());
-				gen.writeEndObject();
-
-				// Recent history
-				gen.writeArrayFieldStart("history");
-				for (CheckpointStats checkpoint : jobStats.getRecentHistory()) {
-					gen.writeStartObject();
-					gen.writeNumberField("id", checkpoint.getCheckpointId());
-					gen.writeNumberField("timestamp", checkpoint.getTriggerTimestamp());
-					gen.writeNumberField("duration", checkpoint.getDuration());
-					gen.writeNumberField("size", checkpoint.getStateSize());
-					gen.writeEndObject();
-				}
-				gen.writeEndArray();
-			}
-		}
-
-		gen.writeEndObject();
-		gen.close();
-
-		return writer.toString();
-	}
-}

http://git-wip-us.apache.org/repos/asf/flink/blob/6ea77ed5/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/JobVertexCheckpointsHandler.java
----------------------------------------------------------------------
diff --git a/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/JobVertexCheckpointsHandler.java b/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/JobVertexCheckpointsHandler.java
deleted file mode 100644
index 8a68ffa..0000000
--- a/flink-runtime-web/src/main/java/org/apache/flink/runtime/webmonitor/handlers/JobVertexCheckpointsHandler.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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.
- */
-
-package org.apache.flink.runtime.webmonitor.handlers;
-
-import com.fasterxml.jackson.core.JsonGenerator;
-import org.apache.flink.runtime.checkpoint.stats.OperatorCheckpointStats;
-import org.apache.flink.runtime.executiongraph.AccessExecutionJobVertex;
-import org.apache.flink.runtime.webmonitor.ExecutionGraphHolder;
-import scala.Option;
-
-import java.io.StringWriter;
-import java.util.Map;
-
-/**
- * Request handler that returns checkpoint stats for a single job vertex.
- */
-public class JobVertexCheckpointsHandler extends AbstractJobVertexRequestHandler {
-
-	public JobVertexCheckpointsHandler(ExecutionGraphHolder executionGraphHolder) {
-		super(executionGraphHolder);
-	}
-
-	@Override
-	public String handleRequest(AccessExecutionJobVertex jobVertex, Map<String, String> params) throws Exception {
-		StringWriter writer = new StringWriter();
-		JsonGenerator gen = JsonFactory.jacksonFactory.createGenerator(writer);
-		gen.writeStartObject();
-
-		Option<OperatorCheckpointStats> statsOption = jobVertex.getCheckpointStats();
-
-		if (statsOption.isDefined()) {
-			OperatorCheckpointStats stats = statsOption.get();
-
-			gen.writeNumberField("id", stats.getCheckpointId());
-			gen.writeNumberField("timestamp", stats.getTriggerTimestamp());
-			gen.writeNumberField("duration", stats.getDuration());
-			gen.writeNumberField("size", stats.getStateSize());
-			gen.writeNumberField("parallelism", stats.getNumberOfSubTasks());
-
-			gen.writeArrayFieldStart("subtasks");
-			for (int i = 0; i < stats.getNumberOfSubTasks(); i++) {
-				gen.writeStartObject();
-				gen.writeNumberField("subtask", i);
-				gen.writeNumberField("duration", stats.getSubTaskDuration(i));
-				gen.writeNumberField("size", stats.getSubTaskStateSize(i));
-				gen.writeEndObject();
-			}
-			gen.writeEndArray();
-		}
-
-		gen.writeEndObject();
-		gen.close();
-
-		return writer.toString();
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/flink/blob/6ea77ed5/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/JobCheckpointsHandlerTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/JobCheckpointsHandlerTest.java b/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/JobCheckpointsHandlerTest.java
deleted file mode 100644
index dfbb9cf..0000000
--- a/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/JobCheckpointsHandlerTest.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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.
- */
-
-package org.apache.flink.runtime.webmonitor.handlers;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.apache.flink.runtime.checkpoint.stats.CheckpointStats;
-import org.apache.flink.runtime.checkpoint.stats.CheckpointStatsTracker;
-import org.apache.flink.runtime.checkpoint.stats.JobCheckpointStats;
-import org.apache.flink.runtime.executiongraph.ExecutionGraph;
-import org.apache.flink.runtime.webmonitor.ExecutionGraphHolder;
-
-import org.junit.Test;
-import scala.Option;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class JobCheckpointsHandlerTest {
-
-	@Test
-	public void testNoCoordinator() throws Exception {
-		JobCheckpointsHandler handler = new JobCheckpointsHandler(
-				mock(ExecutionGraphHolder.class));
-
-		ExecutionGraph graph = mock(ExecutionGraph.class);
-
-		// No coordinator
-		when(graph.getCheckpointStatsTracker()).thenReturn(null);
-
-		String response = handler.handleRequest(graph, Collections.<String, String>emptyMap());
-
-		// Expecting empty response
-		assertEquals("{}", response);
-	}
-
-	@Test
-	public void testNoStats() throws Exception {
-		JobCheckpointsHandler handler = new JobCheckpointsHandler(
-				mock(ExecutionGraphHolder.class));
-
-		ExecutionGraph graph = mock(ExecutionGraph.class);
-		CheckpointStatsTracker tracker = mock(CheckpointStatsTracker.class);
-
-		when(graph.getCheckpointStatsTracker()).thenReturn(tracker);
-
-		// No stats
-		when(tracker.getJobStats()).thenReturn(Option.<JobCheckpointStats>empty());
-
-		String response = handler.handleRequest(graph, Collections.<String, String>emptyMap());
-
-		// Expecting empty response
-		assertEquals("{}", response);
-	}
-
-	@Test
-	public void testStats() throws Exception {
-		JobCheckpointsHandler handler = new JobCheckpointsHandler(
-				mock(ExecutionGraphHolder.class));
-
-		ExecutionGraph graph = mock(ExecutionGraph.class);
-		CheckpointStatsTracker tracker = mock(CheckpointStatsTracker.class);
-
-		when(graph.getCheckpointStatsTracker()).thenReturn(tracker);
-
-		final List<CheckpointStats> history = new ArrayList<>();
-		history.add(new CheckpointStats(0, 1, 1, 124));
-		history.add(new CheckpointStats(1, 5, 177, 0));
-		history.add(new CheckpointStats(2, 6, 8282, 2));
-		history.add(new CheckpointStats(3, 6812, 2800, 1024));
-
-		JobCheckpointStats stats = new JobCheckpointStats() {
-			@Override
-			public List<CheckpointStats> getRecentHistory() {
-				return history;
-			}
-
-			@Override
-			public long getCount() {
-				return 4;
-			}
-
-			@Override
-			public String getExternalPath() {
-				return null;
-			}
-
-			@Override
-			public long getMinDuration() {
-				return 1;
-			}
-
-			@Override
-			public long getMaxDuration() {
-				return 8282;
-			}
-
-			@Override
-			public long getAverageDuration() {
-				return 2815;
-			}
-
-			@Override
-			public long getMinStateSize() {
-				return 0;
-			}
-
-			@Override
-			public long getMaxStateSize() {
-				return 1024;
-			}
-
-			@Override
-			public long getAverageStateSize() {
-				return 287;
-			}
-		};
-
-		when(tracker.getJobStats()).thenReturn(Option.apply(stats));
-
-		// Request stats
-		String response = handler.handleRequest(graph, Collections.<String, String>emptyMap());
-
-		ObjectMapper mapper = new ObjectMapper();
-		JsonNode rootNode = mapper.readTree(response);
-
-		// Count
-		int count = rootNode.get("count").asInt();
-		assertEquals(stats.getCount(), count);
-
-		// Duration
-		JsonNode durationNode = rootNode.get("duration");
-		assertNotNull(durationNode);
-
-		long minDuration = durationNode.get("min").asLong();
-		long maxDuration = durationNode.get("max").asLong();
-		long avgDuration = durationNode.get("avg").asLong();
-
-		assertEquals(stats.getMinDuration(), minDuration);
-		assertEquals(stats.getMaxDuration(), maxDuration);
-		assertEquals(stats.getAverageDuration(), avgDuration);
-
-		// State size
-		JsonNode sizeNode = rootNode.get("size");
-		assertNotNull(sizeNode);
-
-		long minSize = sizeNode.get("min").asLong();
-		long maxSize = sizeNode.get("max").asLong();
-		long avgSize = sizeNode.get("avg").asLong();
-
-		assertEquals(stats.getMinStateSize(), minSize);
-		assertEquals(stats.getMaxStateSize(), maxSize);
-		assertEquals(stats.getAverageStateSize(), avgSize);
-
-		JsonNode historyNode = rootNode.get("history");
-		assertNotNull(historyNode);
-		assertTrue(historyNode.isArray());
-
-		Iterator<JsonNode> it = historyNode.elements();
-
-		for (int i = 0; i < history.size(); i++) {
-			CheckpointStats s = history.get(i);
-
-			JsonNode node = it.next();
-
-			long checkpointId = node.get("id").asLong();
-			long timestamp = node.get("timestamp").asLong();
-			long duration = node.get("duration").asLong();
-			long size = node.get("size").asLong();
-
-			assertEquals(s.getCheckpointId(), checkpointId);
-			assertEquals(s.getTriggerTimestamp(), timestamp);
-			assertEquals(s.getDuration(), duration);
-			assertEquals(s.getStateSize(), size);
-		}
-
-		assertFalse(it.hasNext());
-	}
-}

http://git-wip-us.apache.org/repos/asf/flink/blob/6ea77ed5/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/JobVertexCheckpointsHandlerTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/JobVertexCheckpointsHandlerTest.java b/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/JobVertexCheckpointsHandlerTest.java
deleted file mode 100644
index 18aae35..0000000
--- a/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/JobVertexCheckpointsHandlerTest.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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.
- */
-
-package org.apache.flink.runtime.webmonitor.handlers;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.apache.flink.runtime.checkpoint.stats.OperatorCheckpointStats;
-import org.apache.flink.runtime.executiongraph.ExecutionJobVertex;
-import org.apache.flink.runtime.jobgraph.JobVertexID;
-import org.apache.flink.runtime.webmonitor.ExecutionGraphHolder;
-
-import org.junit.Test;
-import scala.Option;
-
-import java.util.Collections;
-import java.util.Iterator;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class JobVertexCheckpointsHandlerTest {
-
-	@Test
-	public void testNoCoordinator() throws Exception {
-		JobVertexCheckpointsHandler handler = new JobVertexCheckpointsHandler(
-				mock(ExecutionGraphHolder.class));
-
-		ExecutionJobVertex vertex = mock(ExecutionJobVertex.class);
-		when(vertex.getCheckpointStats())
-			.thenReturn(Option.<OperatorCheckpointStats>empty());
-
-		String response = handler.handleRequest(vertex, Collections.<String, String>emptyMap());
-
-		// Expecting empty response
-		assertEquals("{}", response);
-	}
-
-	@Test
-	public void testNoStats() throws Exception {
-		JobVertexCheckpointsHandler handler = new JobVertexCheckpointsHandler(
-				mock(ExecutionGraphHolder.class));
-
-		ExecutionJobVertex vertex = mock(ExecutionJobVertex.class);
-
-		// No stats
-		when(vertex.getCheckpointStats())
-				.thenReturn(Option.<OperatorCheckpointStats>empty());
-
-		String response = handler.handleRequest(vertex, Collections.<String, String>emptyMap());
-
-		// Expecting empty response
-		assertEquals("{}", response);
-	}
-
-	@Test
-	public void testStats() throws Exception {
-		JobVertexCheckpointsHandler handler = new JobVertexCheckpointsHandler(
-				mock(ExecutionGraphHolder.class));
-
-		JobVertexID vertexId = new JobVertexID();
-
-		ExecutionJobVertex vertex = mock(ExecutionJobVertex.class);
-
-		when(vertex.getJobVertexId()).thenReturn(vertexId);
-
-		long[][] subTaskStats = new long[][] {
-				new long[] { 1, 10 },
-				new long[] { 2, 9 },
-				new long[] { 3, 8 },
-				new long[] { 4, 7 },
-				new long[] { 5, 6 },
-				new long[] { 6, 5 },
-				new long[] { 7, 4 },
-				new long[] { 8, 3 },
-				new long[] { 9, 2 },
-				new long[] { 10, 1 } };
-
-		// Stats
-		OperatorCheckpointStats stats = new OperatorCheckpointStats(
-				3, 6812, 2800, 1024, subTaskStats);
-
-		when(vertex.getCheckpointStats())
-				.thenReturn(Option.apply(stats));
-
-		// Request stats
-		String response = handler.handleRequest(vertex, Collections.<String, String>emptyMap());
-
-		ObjectMapper mapper = new ObjectMapper();
-		JsonNode rootNode = mapper.readTree(response);
-
-		// Operator stats
-		long checkpointId = rootNode.get("id").asLong();
-		long timestamp = rootNode.get("timestamp").asLong();
-		long duration = rootNode.get("duration").asLong();
-		long size = rootNode.get("size").asLong();
-		long parallelism = rootNode.get("parallelism").asLong();
-
-		assertEquals(stats.getCheckpointId(), checkpointId);
-		assertEquals(stats.getTriggerTimestamp(), timestamp);
-		assertEquals(stats.getDuration(), duration);
-		assertEquals(stats.getStateSize(), size);
-		assertEquals(subTaskStats.length, parallelism);
-
-		// Sub task stats
-		JsonNode subTasksNode = rootNode.get("subtasks");
-		assertNotNull(subTasksNode);
-		assertTrue(subTasksNode.isArray());
-
-		Iterator<JsonNode> it = subTasksNode.elements();
-
-		for (int i = 0; i < subTaskStats.length; i++) {
-			JsonNode node = it.next();
-
-			assertEquals(i, node.get("subtask").asInt());
-			assertEquals(subTaskStats[i][0], node.get("duration").asLong());
-			assertEquals(subTaskStats[i][1], node.get("size").asLong());
-		}
-
-		assertFalse(it.hasNext());
-	}
-}

http://git-wip-us.apache.org/repos/asf/flink/blob/6ea77ed5/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.job.jade
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.job.jade b/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.job.jade
deleted file mode 100644
index 6d3b6b3..0000000
--- a/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.job.jade
+++ /dev/null
@@ -1,98 +0,0 @@
-//
-  Licensed to the Apache Software Foundation (ASF) under one
-  or more contributor license agreements.  See the NOTICE file
-  distributed with this work for additional information
-  regarding copyright ownership.  The ASF licenses this file
-  to you 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.
-
-div(ng-if="!jobCheckpointStats")
-  p
-    em No checkpoints
-
-table(ng-if="jobCheckpointStats").table.table-hover.table-inner
-  tbody
-    tr
-      td
-        strong Count
-      td(colspan=3)
-        span {{ jobCheckpointStats['count'] }}
-
-    tr
-      td
-        strong Duration
-      td
-        p
-          strong Minimum:
-          span  {{ jobCheckpointStats['duration']['min'] | humanizeDuration }}
-      td
-        p
-          strong Maximum:
-          span  {{ jobCheckpointStats['duration']['max'] | humanizeDuration }}
-
-      td
-        p
-          strong Average:
-          span  {{ jobCheckpointStats['duration']['avg'] | humanizeDuration }}
-
-    tr
-      td
-          strong State Size
-      td
-          p
-            strong Minimum:
-            span  {{ jobCheckpointStats['size']['min'] | humanizeBytes }}
-      td
-          p
-            strong Maximum:
-            span  {{ jobCheckpointStats['size']['max'] | humanizeBytes }}
-      td
-          p
-            strong Average:
-            span  {{ jobCheckpointStats['size']['avg'] | humanizeBytes }}
-
-    tr(ng-if="jobCheckpointStats['external-path']")
-      td(colspan=4)
-        strong Latest Checkpoint Path:
-        =" "
-        | {{ jobCheckpointStats['external-path'] }}
-
-div(ng-if="!showHistory && jobCheckpointStats && jobCheckpointStats['history'].length > 0")
-  a.btn.btn-default(ng-click="toggleHistory()")
-    | <strong>Show history</strong> ({{ jobCheckpointStats['history'].length }})
-    = ' '
-    i.fa.fa-chevron-down
-
-div(ng-if="showHistory && jobCheckpointStats && jobCheckpointStats['history'].length > 0")
-  a.btn.btn-default(ng-click="toggleHistory()")
-    | Hide history ({{ jobCheckpointStats['history'].length }})
-    = ' '
-    i.fa.fa-chevron-up
-
-  table.table.table-hover.table-inner
-    thead
-      tr
-        td
-          strong ID
-        td
-          strong Trigger Time
-        td
-          strong Duration
-        td
-          strong State Size
-
-    tbody(ng-if="jobCheckpointStats['history'] && jobCheckpointStats['history'].length > 0" ng-repeat="historic in jobCheckpointStats['history']")
-      tr
-        td {{ historic['id'] }}
-        td {{ historic['timestamp'] | amDateFormat:'H:mm:ss' }}
-        td {{ historic['duration'] | humanizeDuration }}
-        td {{ historic['size'] | humanizeBytes }}

http://git-wip-us.apache.org/repos/asf/flink/blob/6ea77ed5/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.operator.jade
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.operator.jade b/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.operator.jade
deleted file mode 100644
index 7adc631..0000000
--- a/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.operator.jade
+++ /dev/null
@@ -1,64 +0,0 @@
-//
-  Licensed to the Apache Software Foundation (ASF) under one
-  or more contributor license agreements.  See the NOTICE file
-  distributed with this work for additional information
-  regarding copyright ownership.  The ASF licenses this file
-  to you 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.
-
-div(ng-if="!operatorCheckpointStats")
-  p
-    em No checkpoints
-
-div(ng-if="operatorCheckpointStats")
-  table.table.table-hover.table-clickable.table-activable.table-inner
-    thead
-      tr
-        th ID
-        th Trigger Timestamp
-        th Duration
-        th State Size
-
-    tbody
-      tr
-        td(width="22%") {{ operatorCheckpointStats['id'] }}
-        td(width="22%") {{ operatorCheckpointStats['timestamp'] | amDateFormat:'H:mm:ss' }}
-        td(width="22%") {{ operatorCheckpointStats['duration'] | humanizeDuration }}
-        td(width="22%") {{ operatorCheckpointStats['size'] | humanizeBytes }}
-
-  div(ng-if="!nodeUnfolded && subtasksCheckpointStats && subtasksCheckpointStats.length > 0")
-    a.btn.btn-default(ng-click="toggleFold()")
-      | Show subtasks
-      = ' '
-      i.fa.fa-chevron-down
-
-    a.btn.btn-default.pull-right(ng-click="deactivateNode(); $event.stopPropagation()" title="Fold")
-      i.fa.fa-chevron-up
-
-  div(ng-if="nodeUnfolded && subtasksCheckpointStats && subtasksCheckpointStats.length > 0")
-    a.btn.btn-default(ng-click="toggleFold()")
-      | Hide subtasks
-      = ' '
-      i.fa.fa-chevron-up
-
-    table.table.table-hover.table-clickable.table-activable.table-inner
-      thead
-        tr
-          th Subtask
-          th Duration
-          th Type
-
-      tbody(ng-repeat="subtask in subtasksCheckpointStats")
-        tr
-          td {{ subtask['subtask'] + 1 }}
-          td {{ subtask['duration'] | humanizeDuration }}
-          td {{ subtask['size'] | humanizeBytes }}

http://git-wip-us.apache.org/repos/asf/flink/blob/6ea77ed5/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/ArchivedCheckpointStatsTracker.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/ArchivedCheckpointStatsTracker.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/ArchivedCheckpointStatsTracker.java
deleted file mode 100644
index 92df7d7..0000000
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/ArchivedCheckpointStatsTracker.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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.
- */
-package org.apache.flink.runtime.checkpoint;
-
-import org.apache.flink.runtime.checkpoint.stats.CheckpointStatsTracker;
-import org.apache.flink.runtime.checkpoint.stats.JobCheckpointStats;
-import org.apache.flink.runtime.checkpoint.stats.OperatorCheckpointStats;
-import org.apache.flink.runtime.jobgraph.JobVertexID;
-import scala.Option;
-
-import java.io.Serializable;
-import java.util.Map;
-
-public class ArchivedCheckpointStatsTracker implements CheckpointStatsTracker, Serializable {
-	private static final long serialVersionUID = 1469003563086353555L;
-
-	private final Option<JobCheckpointStats> jobStats;
-	private final Map<JobVertexID, OperatorCheckpointStats> operatorStats;
-
-	public ArchivedCheckpointStatsTracker(Option<JobCheckpointStats> jobStats, Map<JobVertexID, OperatorCheckpointStats> operatorStats) {
-		this.jobStats = jobStats;
-		this.operatorStats = operatorStats;
-	}
-
-	@Override
-	public void onCompletedCheckpoint(CompletedCheckpoint checkpoint) {
-	}
-
-	@Override
-	public Option<JobCheckpointStats> getJobStats() {
-		return jobStats;
-	}
-
-	@Override
-	public Option<OperatorCheckpointStats> getOperatorStats(JobVertexID operatorId) {
-		return Option.apply(operatorStats.get(operatorId));
-	}
-}

http://git-wip-us.apache.org/repos/asf/flink/blob/6ea77ed5/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/stats/CheckpointStats.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/stats/CheckpointStats.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/stats/CheckpointStats.java
deleted file mode 100644
index 64f17d4..0000000
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/stats/CheckpointStats.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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.
- */
-
-package org.apache.flink.runtime.checkpoint.stats;
-
-import java.io.Serializable;
-
-/**
- * Statistics for a specific checkpoint.
- */
-public class CheckpointStats implements Serializable {
-
-	/** ID of the checkpoint. */
-	private final long checkpointId;
-
-	/** Timestamp when the checkpoint was triggered. */
-	private final long triggerTimestamp;
-
-	/** Duration of the checkpoint in milliseconds. */
-	private final long duration;
-
-	/** State size in bytes. */
-	private final long stateSize;
-
-	/**
-	 * Creates a checkpoint statistic.
-	 *
-	 * @param checkpointId     Checkpoint ID
-	 * @param triggerTimestamp Timestamp when the checkpoint was triggered
-	 * @param duration         Duration (in milliseconds)
-	 * @param stateSize        State size (in bytes)
-	 */
-	public CheckpointStats(
-			long checkpointId,
-			long triggerTimestamp,
-			long duration,
-			long stateSize) {
-
-		this.checkpointId = checkpointId;
-		this.triggerTimestamp = triggerTimestamp;
-		this.duration = duration;
-		this.stateSize = stateSize;
-	}
-
-	/**
-	 * Returns the ID of the checkpoint.
-	 *
-	 * @return ID of the checkpoint.
-	 */
-	public long getCheckpointId() {
-		return checkpointId;
-	}
-
-	/**
-	 * Returns the timestamp when the checkpoint was triggered.
-	 *
-	 * @return Timestamp when the checkpoint was triggered.
-	 */
-	public long getTriggerTimestamp() {
-		return triggerTimestamp;
-	}
-
-	/**
-	 * Returns the duration in milliseconds.
-	 *
-	 * @return Duration in milliseconds.
-	 */
-	public long getDuration() {
-		return duration;
-	}
-
-	/**
-	 * Returns the state size in bytes.
-	 *
-	 * @return The state size in bytes.
-	 */
-	public long getStateSize() {
-		return stateSize;
-	}
-
-	@Override
-	public String toString() {
-		return "CheckpointStats{" +
-				"checkpointId=" + checkpointId +
-				", triggerTimestamp=" + triggerTimestamp +
-				", duration=" + duration +
-				", stateSize=" + stateSize +
-				'}';
-	}
-
-	@Override
-	public boolean equals(Object o) {
-		if (this == o) {
-			return true;
-		}
-		if (o == null || getClass() != o.getClass()) {
-			return false;
-		}
-
-		CheckpointStats that = (CheckpointStats) o;
-
-		return checkpointId == that.checkpointId &&
-				triggerTimestamp == that.triggerTimestamp &&
-				duration == that.duration &&
-				stateSize == that.stateSize;
-	}
-
-	@Override
-	public int hashCode() {
-		int result = (int) (checkpointId ^ (checkpointId >>> 32));
-		result = 31 * result + (int) (triggerTimestamp ^ (triggerTimestamp >>> 32));
-		result = 31 * result + (int) (duration ^ (duration >>> 32));
-		result = 31 * result + (int) (stateSize ^ (stateSize >>> 32));
-		return result;
-	}
-}

http://git-wip-us.apache.org/repos/asf/flink/blob/6ea77ed5/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/stats/CheckpointStatsTracker.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/stats/CheckpointStatsTracker.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/stats/CheckpointStatsTracker.java
deleted file mode 100644
index 1277eaa..0000000
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/stats/CheckpointStatsTracker.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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.
- */
-
-package org.apache.flink.runtime.checkpoint.stats;
-
-import org.apache.flink.configuration.ConfigConstants;
-import org.apache.flink.runtime.checkpoint.CompletedCheckpoint;
-import org.apache.flink.runtime.jobgraph.JobVertexID;
-import scala.Option;
-
-/**
- * A tracker for checkpoint statistics.
- *
- * <p>You can disable statistics by setting {@link ConfigConstants#JOB_MANAGER_WEB_CHECKPOINTS_DISABLE}.
- */
-public interface CheckpointStatsTracker {
-
-	/**
-	 * Callback on a completed checkpoint.
-	 *
-	 * @param checkpoint The completed checkpoint.
-	 */
-	void onCompletedCheckpoint(CompletedCheckpoint checkpoint);
-
-	/**
-	 * Returns a snapshot of the checkpoint statistics for a job.
-	 *
-	 * @return Checkpoints stats for the job.
-	 */
-	Option<JobCheckpointStats> getJobStats();
-
-	/**
-	 * Returns a snapshot of the checkpoint statistics for an operator.
-	 *
-	 * @param operatorId The operator ID to gather the stats for.
-	 *
-	 * @return Checkpoint stats for the operator.
-	 *
-	 * @throws IllegalArgumentException If unknown operator ID.
-	 */
-	Option<OperatorCheckpointStats> getOperatorStats(JobVertexID operatorId);
-
-}

http://git-wip-us.apache.org/repos/asf/flink/blob/6ea77ed5/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/stats/DisabledCheckpointStatsTracker.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/stats/DisabledCheckpointStatsTracker.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/stats/DisabledCheckpointStatsTracker.java
deleted file mode 100644
index 4ba9278..0000000
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/stats/DisabledCheckpointStatsTracker.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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.
- */
-
-package org.apache.flink.runtime.checkpoint.stats;
-
-import org.apache.flink.runtime.checkpoint.CompletedCheckpoint;
-import org.apache.flink.runtime.jobgraph.JobVertexID;
-import scala.Option;
-
-/**
- * A tracker for checkpoint statistics when they are disabled.
- */
-public class DisabledCheckpointStatsTracker implements CheckpointStatsTracker {
-
-	@Override
-	public void onCompletedCheckpoint(CompletedCheckpoint checkpoint) {
-		// Nothing to do
-	}
-
-	@Override
-	public Option<JobCheckpointStats> getJobStats() {
-		return Option.empty();
-	}
-
-	@Override
-	public Option<OperatorCheckpointStats> getOperatorStats(JobVertexID operatorId) {
-		return Option.empty();
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/flink/blob/6ea77ed5/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/stats/JobCheckpointStats.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/stats/JobCheckpointStats.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/stats/JobCheckpointStats.java
deleted file mode 100644
index e156c8e..0000000
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/stats/JobCheckpointStats.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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.
- */
-
-package org.apache.flink.runtime.checkpoint.stats;
-
-import org.apache.flink.configuration.ConfigConstants;
-
-import java.io.Serializable;
-import java.util.List;
-
-/**
- * Snapshot of checkpoint statistics for a job.
- */
-public interface JobCheckpointStats extends Serializable {
-
-	// ------------------------------------------------------------------------
-	// General stats
-	// ------------------------------------------------------------------------
-
-	/**
-	 * Returns a list of recently completed checkpoints stats.
-	 *
-	 * <p>The history size is configurable via {@link ConfigConstants#JOB_MANAGER_WEB_CHECKPOINTS_HISTORY_SIZE}.
-	 *
-	 * @return List of recently completed checkpoints stats.
-	 */
-	List<CheckpointStats> getRecentHistory();
-
-	/**
-	 * Returns the total number of completed checkpoints.
-	 *
-	 * @return Total number of completed checkpoints.
-	 */
-	long getCount();
-
-	/**
-	 * Returns the most recent external path of a checkpoint.
-	 *
-	 * @return External checkpoint path or <code>null</code> if none available.
-	 */
-	String getExternalPath();
-
-	// ------------------------------------------------------------------------
-	// Duration
-	// ------------------------------------------------------------------------
-
-	/**
-	 * Returns the minimum checkpoint duration ever seen over all completed
-	 * checkpoints.
-	 *
-	 * @return Minimum checkpoint duration over all completed checkpoints.
-	 */
-	long getMinDuration();
-
-	/**
-	 * Returns the maximum checkpoint duration ever seen over all completed
-	 * checkpoints.
-	 *
-	 * @return Maximum checkpoint duration over all completed checkpoints.
-	 */
-	long getMaxDuration();
-
-	/**
-	 * Returns the average checkpoint duration ever seen over all completed
-	 * checkpoints.
-	 *
-	 * @return Average checkpoint duration over all completed checkpoints.
-	 */
-	long getAverageDuration();
-
-	// ------------------------------------------------------------------------
-	// State size
-	// ------------------------------------------------------------------------
-
-	/**
-	 * Returns the minimum checkpoint state size ever seen over all completed
-	 * checkpoints.
-	 *
-	 * @return Minimum checkpoint state size over all completed checkpoints.
-	 */
-	long getMinStateSize();
-
-	/**
-	 * Returns the maximum checkpoint state size ever seen over all completed
-	 * checkpoints.
-	 *
-	 * @return Maximum checkpoint state size over all completed checkpoints.
-	 */
-	long getMaxStateSize();
-
-	/**
-	 * Average the minimum checkpoint state size ever seen over all completed
-	 * checkpoints.
-	 *
-	 * @return Average checkpoint state size over all completed checkpoints.
-	 */
-	long getAverageStateSize();
-
-}

http://git-wip-us.apache.org/repos/asf/flink/blob/6ea77ed5/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/stats/OperatorCheckpointStats.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/stats/OperatorCheckpointStats.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/stats/OperatorCheckpointStats.java
deleted file mode 100644
index 6c2d497..0000000
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/stats/OperatorCheckpointStats.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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.
- */
-
-package org.apache.flink.runtime.checkpoint.stats;
-
-import java.util.Arrays;
-
-import static org.apache.flink.util.Preconditions.checkNotNull;
-
-/**
- * Statistics for a specific checkpoint per operator.
- */
-public class OperatorCheckpointStats extends CheckpointStats {
-
-	private static final long serialVersionUID = -1594736655739376140L;
-
-	/** Duration in milliseconds and state sizes in bytes per sub task. */
-	private final long[][] subTaskStats;
-
-	/**
-	 * Creates a checkpoint statistic for an operator.
-	 *
-	 * @param checkpointId     Checkpoint ID this statistic belongs to
-	 * @param triggerTimestamp Timestamp when the corresponding checkpoint was triggered
-	 * @param duration         Duration (in milliseconds) to complete this statistic
-	 * @param stateSize        State size (in bytes)
-	 * @param subTaskStats     Stats per subtask ([i][0] and [i][1] encode the duration and state
-	 *                         size for sub task i respectively).
-	 */
-	public OperatorCheckpointStats(
-			long checkpointId,
-			long triggerTimestamp,
-			long duration,
-			long stateSize,
-			long[][] subTaskStats) {
-
-		super(checkpointId, triggerTimestamp, duration, stateSize);
-
-		this.subTaskStats = checkNotNull(subTaskStats);
-	}
-
-	/**
-	 * Returns the number of sub tasks.
-	 *
-	 * @return Number of sub tasks.
-	 */
-	public int getNumberOfSubTasks() {
-		return subTaskStats.length;
-	}
-
-	/**
-	 * Returns the duration of a specific sub task.
-	 *
-	 * @return Duration of the sub task.
-	 */
-	public long getSubTaskDuration(int index) {
-		return subTaskStats[index][0];
-	}
-
-	/**
-	 * Returns the state size of a specific sub task.
-	 *
-	 * @return The state size in bytes.
-	 */
-	public long getSubTaskStateSize(int index) {
-		return subTaskStats[index][1];
-	}
-
-	@Override
-	public String toString() {
-		return "OperatorCheckpointStats{" +
-				"checkpointId=" + getCheckpointId() +
-				", subTaskStats=" + Arrays.deepToString(subTaskStats) +
-				'}';
-	}
-
-	@Override
-	public boolean equals(Object o) {
-		if (this == o) {
-			return true;
-		}
-		if (o == null || getClass() != o.getClass()) {
-			return false;
-		}
-
-		OperatorCheckpointStats that = (OperatorCheckpointStats) o;
-
-		return getCheckpointId() == that.getCheckpointId() &&
-				getTriggerTimestamp() == that.getTriggerTimestamp() &&
-				Arrays.deepEquals(subTaskStats, that.subTaskStats);
-	}
-
-	@Override
-	public int hashCode() {
-		int result = (int) (getCheckpointId() ^ (getCheckpointId() >>> 32));
-		result = 31 * result + (int) (getTriggerTimestamp() ^ (getTriggerTimestamp() >>> 32));
-		result = 31 * result + (int) (subTaskStats.length ^ (subTaskStats.length >>> 32));
-		return result;
-	}
-}

http://git-wip-us.apache.org/repos/asf/flink/blob/6ea77ed5/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/stats/SimpleCheckpointStatsTracker.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/stats/SimpleCheckpointStatsTracker.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/stats/SimpleCheckpointStatsTracker.java
deleted file mode 100644
index 39fbad5..0000000
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/stats/SimpleCheckpointStatsTracker.java
+++ /dev/null
@@ -1,468 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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.
- */
-
-package org.apache.flink.runtime.checkpoint.stats;
-
-import org.apache.flink.metrics.Gauge;
-import org.apache.flink.metrics.MetricGroup;
-import org.apache.flink.runtime.checkpoint.CompletedCheckpoint;
-import org.apache.flink.runtime.checkpoint.SubtaskState;
-import org.apache.flink.runtime.checkpoint.TaskState;
-import org.apache.flink.runtime.executiongraph.ExecutionJobVertex;
-import org.apache.flink.runtime.jobgraph.JobVertexID;
-import scala.Option;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import static org.apache.flink.util.Preconditions.checkArgument;
-
-/**
- * A simple checkpoint stats tracker.
- */
-public class SimpleCheckpointStatsTracker implements CheckpointStatsTracker {
-
-	/** Lock guarding access to the stats */
-	private final Object statsLock = new Object();
-
-	/** The maximum number of recent checkpoint stats to remember. */
-	private final int historySize;
-
-	/** A bounded list of detailed stats. */
-	private final ArrayList<CheckpointStats> history = new ArrayList<>();
-
-	/**
-	 * Expected parallelism of tasks, which acknowledge checkpoints. Used for
-	 * per sub task state size computation.
-	 */
-	private final Map<JobVertexID, Integer> taskParallelism;
-
-	/**
-	 * Stats per operator. The long[parallelism][2], where [i][0] holds the
-	 * duration, [i][1] the state size for sub task i of the operator.
-	 */
-	private Map<JobVertexID, long[][]> subTaskStats;
-
-	/**
-	 * Last computed job-specific statistic. Cleared on every completed
-	 * checkpoint. And computed only on call to {@link #getJobStats()}.
-	 */
-	private JobCheckpointStats lastJobStats;
-
-	/**
-	 * A map caching computed operator-specific statistics. Cleared on every
-	 * completed checkpoint. And computed only on call to {@link #getOperatorStats(JobVertexID)}.
-	 */
-	private Map<JobVertexID, OperatorCheckpointStats> operatorStatsCache = new HashMap<>();
-
-	/**
-	 * The total number of completed checkpoints. This does not always
-	 * equal the last completed ID, because some checkpoints may have been
-	 * cancelled after incrementing the ID counter.
-	 */
-	private long overallCount;
-
-	/** The minimum checkpoint completion duration (over all checkpoints). */
-	private long overallMinDuration = Long.MAX_VALUE;
-
-	/** The maximum checkpoint completion duration (over all checkpoints). */
-	private long overallMaxDuration = Long.MIN_VALUE;
-
-	/**
-	 * The total checkpoint completion duration of all completed checkpoints.
-	 * Used for computing the average duration.
-	 */
-	private long overallTotalDuration;
-
-	/** The minimum checkpoint state size (over all checkpoints). */
-	private long overallMinStateSize = Long.MAX_VALUE;
-
-	/** The maximum checkpoint state size (over all checkpoints). */
-	private long overallMaxStateSize = Long.MIN_VALUE;
-
-	/**
-	 * The total checkpoint state size (over all checkpoints). Used for
-	 * computing the overall average state size.
-	 */
-	private long overallTotalStateSize;
-
-	/**
-	 * The latest completed checkpoint (highest ID) or <code>null</code>.
-	 */
-	private CompletedCheckpoint latestCompletedCheckpoint;
-
-	public SimpleCheckpointStatsTracker(
-			int historySize,
-			List<ExecutionJobVertex> tasksToWaitFor,
-			MetricGroup metrics) {
-
-		checkArgument(historySize >= 0);
-		this.historySize = historySize;
-
-		// We know upfront which tasks will ack the checkpoints
-		if (tasksToWaitFor != null && !tasksToWaitFor.isEmpty()) {
-			taskParallelism = new HashMap<>(tasksToWaitFor.size());
-
-			for (ExecutionJobVertex vertex : tasksToWaitFor) {
-				taskParallelism.put(vertex.getJobVertexId(), vertex.getParallelism());
-			}
-		} else {
-			taskParallelism = Collections.emptyMap();
-		}
-
-		metrics.gauge("lastCheckpointSize", new CheckpointSizeGauge());
-		metrics.gauge("lastCheckpointDuration", new CheckpointDurationGauge());
-		metrics.gauge("lastCheckpointExternalPath", new CheckpointExternalPathGauge());
-	}
-
-	@Override
-	public void onCompletedCheckpoint(CompletedCheckpoint checkpoint) {
-		// Sanity check
-		if (taskParallelism.isEmpty()) {
-			return;
-		}
-
-		synchronized (statsLock) {
-			long overallStateSize;
-			try {
-				overallStateSize = checkpoint.getStateSize();
-			} catch (Exception ex) {
-				throw new RuntimeException(ex);
-			}
-
-			// Operator stats
-			Map<JobVertexID, long[][]> statsForSubTasks = new HashMap<>();
-
-			for (Map.Entry<JobVertexID, TaskState> taskStateEntry: checkpoint.getTaskStates().entrySet()) {
-				JobVertexID jobVertexID = taskStateEntry.getKey();
-				TaskState taskState = taskStateEntry.getValue();
-
-				int parallelism = taskParallelism.get(jobVertexID);
-				long[][] statsPerSubtask = new long[parallelism][2];
-
-				statsForSubTasks.put(jobVertexID, statsPerSubtask);
-
-				for (int i = 0; i < Math.min(taskState.getParallelism(), parallelism); i++) {
-					SubtaskState subtaskState = taskState.getState(i);
-
-					if (subtaskState != null) {
-						statsPerSubtask[i][0] = subtaskState.getDuration();
-						statsPerSubtask[i][1] = subtaskState.getStateSize();
-					}
-				}
-			}
-
-			// It is possible that completed checkpoints are added out of
-			// order. Make sure that in this case the last completed
-			// checkpoint is not updated.
-			boolean isInOrder = latestCompletedCheckpoint != null &&
-					checkpoint.getCheckpointID() > latestCompletedCheckpoint.getCheckpointID();
-
-			// Clear this in each case
-			lastJobStats = null;
-
-			if (overallCount == 0 || isInOrder) {
-				latestCompletedCheckpoint = checkpoint;
-
-				// Clear cached stats
-				operatorStatsCache.clear();
-
-				// Update the stats per sub task
-				subTaskStats = statsForSubTasks;
-			}
-
-			long checkpointId = checkpoint.getCheckpointID();
-			long checkpointTriggerTimestamp = checkpoint.getTimestamp();
-			long checkpointDuration = checkpoint.getDuration();
-
-			overallCount++;
-
-			// Duration stats
-			if (checkpointDuration > overallMaxDuration) {
-				overallMaxDuration = checkpointDuration;
-			}
-
-			if (checkpointDuration < overallMinDuration) {
-				overallMinDuration = checkpointDuration;
-			}
-
-			overallTotalDuration += checkpointDuration;
-
-			// State size stats
-			if (overallStateSize < overallMinStateSize) {
-				overallMinStateSize = overallStateSize;
-			}
-
-			if (overallStateSize > overallMaxStateSize) {
-				overallMaxStateSize = overallStateSize;
-			}
-
-			this.overallTotalStateSize += overallStateSize;
-
-			// Recent history
-			if (historySize > 0) {
-				CheckpointStats stats = new CheckpointStats(
-						checkpointId,
-						checkpointTriggerTimestamp,
-						checkpointDuration,
-						overallStateSize);
-
-				if (isInOrder) {
-					if (history.size() == historySize) {
-						history.remove(0);
-					}
-
-					history.add(stats);
-				}
-				else {
-					final int size = history.size();
-
-					// Only remove it if it the new checkpoint is not too old
-					if (size == historySize) {
-						if (checkpointId > history.get(0).getCheckpointId()) {
-							history.remove(0);
-						}
-					}
-
-					int pos = 0;
-
-					// Find position
-					for (int i = 0; i < size; i++) {
-						pos = i;
-
-						if (checkpointId < history.get(i).getCheckpointId()) {
-							break;
-						}
-					}
-
-					history.add(pos, stats);
-				}
-			}
-		}
-	}
-
-	@SuppressWarnings("unchecked")
-	@Override
-	public Option<JobCheckpointStats> getJobStats() {
-		synchronized (statsLock) {
-			if (lastJobStats != null) {
-				return Option.apply(lastJobStats);
-			}
-			else if (latestCompletedCheckpoint != null) {
-				long overallAverageDuration = overallCount == 0
-						? 0
-						: overallTotalDuration / overallCount;
-
-				long overallAverageStateSize = overallCount == 0
-						? 0
-						: overallTotalStateSize / overallCount;
-
-				lastJobStats = new JobCheckpointStatsSnapshot(
-						// Need to clone in order to have a consistent snapshot.
-						// We can safely update it afterwards.
-						(List<CheckpointStats>) history.clone(),
-						latestCompletedCheckpoint.getExternalPath(),
-						overallCount,
-						overallMinDuration,
-						overallMaxDuration,
-						overallAverageDuration,
-						overallMinStateSize,
-						overallMaxStateSize,
-						overallAverageStateSize);
-
-				return Option.apply(lastJobStats);
-			}
-			else {
-				return Option.empty();
-			}
-		}
-	}
-
-	@Override
-	public Option<OperatorCheckpointStats> getOperatorStats(JobVertexID operatorId) {
-		synchronized (statsLock) {
-			OperatorCheckpointStats stats = operatorStatsCache.get(operatorId);
-
-			if (stats != null) {
-				return Option.apply(stats);
-			}
-			else if (latestCompletedCheckpoint != null && subTaskStats != null) {
-				long[][] subTaskStats = this.subTaskStats.get(operatorId);
-
-				if (subTaskStats == null) {
-					return Option.empty();
-				}
-				else {
-					long maxDuration = Long.MIN_VALUE;
-					long stateSize = 0;
-
-					for (long[] subTaskStat : subTaskStats) {
-						if (subTaskStat[0] > maxDuration) {
-							maxDuration = subTaskStat[0];
-						}
-
-						stateSize += subTaskStat[1];
-					}
-
-					stats = new OperatorCheckpointStats(
-							latestCompletedCheckpoint.getCheckpointID(),
-							latestCompletedCheckpoint.getTimestamp(),
-							maxDuration,
-							stateSize,
-							subTaskStats);
-
-					// Remember this and don't recompute if requested again
-					operatorStatsCache.put(operatorId, stats);
-
-					return Option.apply(stats);
-				}
-			}
-			else {
-				return Option.empty();
-			}
-		}
-	}
-
-	// ------------------------------------------------------------------------
-
-	/**
-	 * A snapshot of checkpoint stats.
-	 */
-	private static class JobCheckpointStatsSnapshot implements JobCheckpointStats {
-
-		private static final long serialVersionUID = 7558212015099742418L;
-
-		// General
-		private final List<CheckpointStats> recentHistory;
-		private final long count;
-		private final String externalPath;
-
-		// Duration
-		private final long minDuration;
-		private final long maxDuration;
-		private final long averageDuration;
-
-		// State size
-		private final long minStateSize;
-		private final long maxStateSize;
-		private final long averageStateSize;
-
-		public JobCheckpointStatsSnapshot(
-				List<CheckpointStats> recentHistory,
-				String externalPath,
-				long count,
-				long minDuration,
-				long maxDuration,
-				long averageDuration,
-				long minStateSize,
-				long maxStateSize,
-				long averageStateSize) {
-
-			this.recentHistory = recentHistory;
-			this.count = count;
-			this.externalPath = externalPath;
-
-			this.minDuration = minDuration;
-			this.maxDuration = maxDuration;
-			this.averageDuration = averageDuration;
-
-			this.minStateSize = minStateSize;
-			this.maxStateSize = maxStateSize;
-			this.averageStateSize = averageStateSize;
-		}
-
-		@Override
-		public List<CheckpointStats> getRecentHistory() {
-			return recentHistory;
-		}
-
-		@Override
-		public long getCount() {
-			return count;
-		}
-
-		@Override
-		public String getExternalPath() {
-			return externalPath;
-		}
-
-		@Override
-		public long getMinDuration() {
-			return minDuration;
-		}
-
-		@Override
-		public long getMaxDuration() {
-			return maxDuration;
-		}
-
-		@Override
-		public long getAverageDuration() {
-			return averageDuration;
-		}
-
-		@Override
-		public long getMinStateSize() {
-			return minStateSize;
-		}
-
-		@Override
-		public long getMaxStateSize() {
-			return maxStateSize;
-		}
-
-		@Override
-		public long getAverageStateSize() {
-			return averageStateSize;
-		}
-	}
-
-	private class CheckpointSizeGauge implements Gauge<Long> {
-		@Override
-		public Long getValue() {
-			try {
-				return latestCompletedCheckpoint == null ? -1 : latestCompletedCheckpoint.getStateSize();
-			} catch (Exception ex) {
-				throw new RuntimeException(ex);
-			}
-		}
-	}
-
-	private class CheckpointDurationGauge implements Gauge<Long> {
-		@Override
-		public Long getValue() {
-			return latestCompletedCheckpoint == null ? -1 : latestCompletedCheckpoint.getDuration();
-		}
-	}
-
-	private class CheckpointExternalPathGauge implements Gauge<String> {
-
-		@Override
-		public String getValue() {
-			CompletedCheckpoint checkpoint = latestCompletedCheckpoint;
-			if (checkpoint != null && checkpoint.getExternalPath() != null) {
-				return checkpoint.getExternalPath();
-			} else {
-				return "n/a";
-			}
-		}
-	}
-}

http://git-wip-us.apache.org/repos/asf/flink/blob/6ea77ed5/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointCoordinatorFailureTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointCoordinatorFailureTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointCoordinatorFailureTest.java
index 26db012..d3a440a 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointCoordinatorFailureTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointCoordinatorFailureTest.java
@@ -19,7 +19,6 @@
 package org.apache.flink.runtime.checkpoint;
 
 import org.apache.flink.api.common.JobID;
-import org.apache.flink.runtime.checkpoint.stats.DisabledCheckpointStatsTracker;
 import org.apache.flink.runtime.concurrent.Executors;
 import org.apache.flink.runtime.executiongraph.ExecutionAttemptID;
 import org.apache.flink.runtime.executiongraph.ExecutionVertex;
@@ -73,7 +72,6 @@ public class CheckpointCoordinatorFailureTest extends TestLogger {
 			new StandaloneCheckpointIDCounter(),
 			new FailingCompletedCheckpointStore(),
 			null,
-			new DisabledCheckpointStatsTracker(),
 			Executors.directExecutor());
 
 		coord.triggerCheckpoint(triggerTimestamp, false);
@@ -84,7 +82,7 @@ public class CheckpointCoordinatorFailureTest extends TestLogger {
 
 		assertFalse(pendingCheckpoint.isDiscarded());
 
-		final long checkpointId =coord.getPendingCheckpoints().keySet().iterator().next();
+		final long checkpointId = coord.getPendingCheckpoints().keySet().iterator().next();
 
 		final CheckpointMetaData checkpointMetaData = new CheckpointMetaData(checkpointId, triggerTimestamp);
 		AcknowledgeCheckpoint acknowledgeMessage = new AcknowledgeCheckpoint(jid, executionAttemptId, checkpointMetaData);

http://git-wip-us.apache.org/repos/asf/flink/blob/6ea77ed5/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/stats/DisabledCheckpointStatsTrackerTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/stats/DisabledCheckpointStatsTrackerTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/stats/DisabledCheckpointStatsTrackerTest.java
deleted file mode 100644
index 50aca83..0000000
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/stats/DisabledCheckpointStatsTrackerTest.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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.
- */
-
-package org.apache.flink.runtime.checkpoint.stats;
-
-import org.apache.flink.runtime.jobgraph.JobVertexID;
-import org.junit.Test;
-
-import static org.junit.Assert.assertFalse;
-
-public class DisabledCheckpointStatsTrackerTest {
-	
-	@Test
-	public void testDisabled() throws Exception {
-		CheckpointStatsTracker tracker = new DisabledCheckpointStatsTracker();
-		assertFalse(tracker.getJobStats().isDefined());
-		assertFalse(tracker.getOperatorStats(new JobVertexID()).isDefined());
-	}
-}

http://git-wip-us.apache.org/repos/asf/flink/blob/6ea77ed5/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/stats/SimpleCheckpointStatsTrackerTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/stats/SimpleCheckpointStatsTrackerTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/stats/SimpleCheckpointStatsTrackerTest.java
deleted file mode 100644
index 50a59a5..0000000
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/stats/SimpleCheckpointStatsTrackerTest.java
+++ /dev/null
@@ -1,381 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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.
- */
-
-package org.apache.flink.runtime.checkpoint.stats;
-
-import org.apache.flink.api.common.JobID;
-import org.apache.flink.core.fs.Path;
-import org.apache.flink.metrics.groups.UnregisteredMetricsGroup;
-import org.apache.flink.runtime.checkpoint.CheckpointProperties;
-import org.apache.flink.runtime.checkpoint.CompletedCheckpoint;
-import org.apache.flink.runtime.checkpoint.SubtaskState;
-import org.apache.flink.runtime.checkpoint.TaskState;
-import org.apache.flink.runtime.executiongraph.ExecutionJobVertex;
-import org.apache.flink.runtime.jobgraph.JobVertexID;
-import org.apache.flink.runtime.state.ChainedStateHandle;
-import org.apache.flink.runtime.state.StreamStateHandle;
-import org.apache.flink.runtime.state.filesystem.FileStateHandle;
-import org.junit.Test;
-
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.Set;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class SimpleCheckpointStatsTrackerTest {
-
-	private static final Random RAND = new Random();
-
-	@Test
-	public void testNoCompletedCheckpointYet() throws Exception {
-		CheckpointStatsTracker tracker = new SimpleCheckpointStatsTracker(
-				0, Collections.<ExecutionJobVertex>emptyList(), new UnregisteredMetricsGroup());
-
-		assertFalse(tracker.getJobStats().isDefined());
-		assertFalse(tracker.getOperatorStats(new JobVertexID()).isDefined());
-	}
-
-	@Test
-	public void testRandomStats() throws Exception {
-		CompletedCheckpoint[] checkpoints = generateRandomCheckpoints(16);
-		List<ExecutionJobVertex> tasksToWaitFor = createTasksToWaitFor(checkpoints[0]);
-		CheckpointStatsTracker tracker = new SimpleCheckpointStatsTracker(10, tasksToWaitFor, new UnregisteredMetricsGroup());
-
-		for (int i = 0; i < checkpoints.length; i++) {
-			CompletedCheckpoint checkpoint = checkpoints[i];
-
-			tracker.onCompletedCheckpoint(checkpoint);
-
-			verifyJobStats(tracker, 10, Arrays.copyOfRange(checkpoints, 0, i + 1));
-			verifySubtaskStats(tracker, tasksToWaitFor, checkpoint);
-		}
-	}
-
-	@Test
-	public void testIllegalOperatorId() throws Exception {
-		CompletedCheckpoint[] checkpoints = generateRandomCheckpoints(16);
-		List<ExecutionJobVertex> tasksToWaitFor = createTasksToWaitFor(checkpoints[0]);
-		CheckpointStatsTracker tracker = new SimpleCheckpointStatsTracker(10, tasksToWaitFor, new UnregisteredMetricsGroup());
-
-		for (CompletedCheckpoint checkpoint : checkpoints) {
-			tracker.onCompletedCheckpoint(checkpoint);
-		}
-
-		assertTrue(tracker.getJobStats().isDefined());
-
-		assertTrue(tracker.getOperatorStats(new JobVertexID()).isEmpty());
-	}
-
-	@Test
-	public void testCompletedCheckpointReordering() throws Exception {
-		CompletedCheckpoint[] checkpoints = generateRandomCheckpoints(2);
-		List<ExecutionJobVertex> tasksToWaitFor = createTasksToWaitFor(checkpoints[0]);
-		CheckpointStatsTracker tracker = new SimpleCheckpointStatsTracker(10, tasksToWaitFor, new UnregisteredMetricsGroup());
-
-		// First the second checkpoint notifies
-		tracker.onCompletedCheckpoint(checkpoints[1]);
-		verifyJobStats(tracker, 10, new CompletedCheckpoint[] { checkpoints[1] });
-		verifySubtaskStats(tracker, tasksToWaitFor, checkpoints[1]);
-
-		// Then the first one
-		tracker.onCompletedCheckpoint(checkpoints[0]);
-		verifyJobStats(tracker, 10, checkpoints);
-
-		// This should not alter the results for the subtasks
-		verifySubtaskStats(tracker, tasksToWaitFor, checkpoints[1]);
-	}
-
-	@Test
-	@SuppressWarnings("unchecked")
-	public void testOperatorStateCachedClearedOnNewCheckpoint() throws Exception {
-		CompletedCheckpoint[] checkpoints = generateRandomCheckpoints(2);
-		List<ExecutionJobVertex> tasksToWaitFor = createTasksToWaitFor(checkpoints[0]);
-		CheckpointStatsTracker tracker = new SimpleCheckpointStatsTracker(10, tasksToWaitFor, new UnregisteredMetricsGroup());
-
-		tracker.onCompletedCheckpoint(checkpoints[0]);
-
-		Set<JobVertexID> jobVerticesID = checkpoints[0].getTaskStates().keySet();
-
-		Iterator<JobVertexID> jobVertexIDIterator = jobVerticesID.iterator();
-
-		JobVertexID operatorId = null;
-
-		if (jobVertexIDIterator.hasNext()) {
-			operatorId = jobVertexIDIterator.next();
-		}
-
-		assertNotNull(operatorId);
-
-		assertNotNull(tracker.getOperatorStats(operatorId));
-
-		// Get the cache
-		Field f = tracker.getClass().getDeclaredField("operatorStatsCache");
-		f.setAccessible(true);
-		Map<JobVertexID, OperatorCheckpointStats> cache =
-				(Map<JobVertexID, OperatorCheckpointStats>) f.get(tracker);
-
-		// Cache contains result
-		assertTrue(cache.containsKey(operatorId));
-
-		// Add new checkpoint
-		tracker.onCompletedCheckpoint(checkpoints[1]);
-
-		assertTrue(cache.isEmpty());
-	}
-
-	// ------------------------------------------------------------------------
-
-	private static void verifyJobStats(
-			CheckpointStatsTracker tracker,
-			int historySize,
-			CompletedCheckpoint[] checkpoints) throws Exception {
-
-		assertTrue(tracker.getJobStats().isDefined());
-		JobCheckpointStats jobStats = tracker.getJobStats().get();
-
-		// History
-		List<CheckpointStats> history = jobStats.getRecentHistory();
-
-		if (historySize > checkpoints.length) {
-			assertEquals(checkpoints.length, history.size());
-		}
-		else {
-			assertEquals(historySize, history.size());
-		}
-
-		// Recently completed checkpoint stats
-		assertTrue(checkpoints.length >= history.size());
-
-		for (int i = 0; i < history.size(); i++) {
-			CheckpointStats actualStats = history.get(history.size() - i - 1);
-
-			CompletedCheckpoint checkpoint = checkpoints[checkpoints.length - 1 - i];
-
-			long stateSize = checkpoint.getStateSize();
-
-			CheckpointStats expectedStats = new CheckpointStats(
-					checkpoint.getCheckpointID(),
-					checkpoint.getTimestamp(),
-					checkpoint.getDuration(),
-					stateSize);
-
-			assertEquals(expectedStats, actualStats);
-		}
-
-		// Stats
-		long minDuration = Long.MAX_VALUE;
-		long maxDuration = Long.MIN_VALUE;
-		long totalDuration = 0;
-
-		long minStateSize = Long.MAX_VALUE;
-		long maxStateSize = Long.MIN_VALUE;
-		long totalStateSize = 0;
-
-		long count = 0;
-
-		// Compute the expected stats
-		for (CompletedCheckpoint checkpoint : checkpoints) {
-			count++;
-
-			if (checkpoint.getDuration() < minDuration) {
-				minDuration = checkpoint.getDuration();
-			}
-
-			if (checkpoint.getDuration() > maxDuration) {
-				maxDuration = checkpoint.getDuration();
-			}
-
-			totalDuration += checkpoint.getDuration();
-
-			long stateSize = checkpoint.getStateSize();
-
-			// State size
-			if (stateSize < minStateSize) {
-				minStateSize = stateSize;
-			}
-
-			if (stateSize > maxStateSize) {
-				maxStateSize = stateSize;
-			}
-
-			totalStateSize += stateSize;
-		}
-
-		// Verify
-		assertEquals(count, jobStats.getCount());
-		assertEquals(minDuration, jobStats.getMinDuration());
-		assertEquals(maxDuration, jobStats.getMaxDuration());
-		assertEquals(totalDuration / count, jobStats.getAverageDuration());
-		assertEquals(minStateSize, jobStats.getMinStateSize());
-		assertEquals(maxStateSize, jobStats.getMaxStateSize());
-		assertEquals(totalStateSize / count, jobStats.getAverageStateSize());
-	}
-
-	private static void verifySubtaskStats(
-			CheckpointStatsTracker tracker,
-			List<ExecutionJobVertex> tasksToWaitFor,
-			CompletedCheckpoint checkpoint) {
-
-		for (ExecutionJobVertex vertex : tasksToWaitFor) {
-			JobVertexID operatorId = vertex.getJobVertexId();
-			int parallelism = vertex.getParallelism();
-			TaskState taskState = checkpoint.getTaskState(operatorId);
-
-			assertNotNull(taskState);
-
-			OperatorCheckpointStats actualStats = tracker.getOperatorStats(operatorId).get();
-
-			long operatorDuration = Long.MIN_VALUE;
-			long operatorStateSize = 0;
-
-			long[][] expectedSubTaskStats = new long[parallelism][2];
-
-			for (int i = 0; i < parallelism; i++) {
-				SubtaskState subtaskState = taskState.getState(i);
-
-				expectedSubTaskStats[i][0] = subtaskState.getDuration();
-				expectedSubTaskStats[i][1] = subtaskState.getStateSize();
-			}
-
-			OperatorCheckpointStats expectedStats = new OperatorCheckpointStats(
-					checkpoint.getCheckpointID(),
-					checkpoint.getTimestamp(),
-					operatorDuration, // we want the max duration of all subtasks
-					operatorStateSize,
-					expectedSubTaskStats);
-
-			assertEquals(expectedStats, actualStats);
-		}
-	}
-
-	private static CompletedCheckpoint[] generateRandomCheckpoints(
-			int numCheckpoints) throws Exception {
-
-		// Config
-		JobID jobId = new JobID();
-		int minNumOperators = 4;
-		int maxNumOperators = 32;
-		int minOperatorParallelism = 4;
-		int maxOperatorParallelism = 16;
-		int maxParallelism = 32;
-
-		// Use yuge numbers here in order to test that summing up state sizes
-		// does not overflow. This was a bug in the initial version, because
-		// the individual state sizes (longs) were summed up in an int.
-		long minStateSize = Integer.MAX_VALUE;
-		long maxStateSize = Long.MAX_VALUE;
-		CompletedCheckpoint[] checkpoints = new CompletedCheckpoint[numCheckpoints];
-
-		int numOperators = RAND.nextInt(maxNumOperators - minNumOperators + 1) + minNumOperators;
-
-		// Setup
-		JobVertexID[] operatorIds = new JobVertexID[numOperators];
-		int[] operatorParallelism = new int[numOperators];
-
-		for (int i = 0; i < numOperators; i++) {
-			operatorIds[i] = new JobVertexID();
-			operatorParallelism[i] = RAND.nextInt(maxOperatorParallelism - minOperatorParallelism + 1) + minOperatorParallelism;
-		}
-
-		// Generate checkpoints
-		for (int i = 0; i < numCheckpoints; i++) {
-			long triggerTimestamp = System.currentTimeMillis();
-			int maxDuration = RAND.nextInt(128 + 1);
-
-			Map<JobVertexID, TaskState> taskGroupStates = new HashMap<>();
-
-			// The maximum random duration is used as time to completion
-			int completionDuration = 0;
-
-			// Generate states for same set of operators
-			for (int operatorIndex = 0; operatorIndex < numOperators; operatorIndex++) {
-				JobVertexID operatorId = operatorIds[operatorIndex];
-				int parallelism = operatorParallelism[operatorIndex];
-
-				TaskState taskState = new TaskState(operatorId, parallelism, maxParallelism, 1);
-
-				taskGroupStates.put(operatorId, taskState);
-
-				for (int subtaskIndex = 0; subtaskIndex < parallelism; subtaskIndex++) {
-					int duration = RAND.nextInt(maxDuration + 1);
-
-					if (duration > completionDuration) {
-						completionDuration = duration;
-					}
-
-					final long proxySize = minStateSize + ((long) (RAND.nextDouble() * (maxStateSize - minStateSize)));
-					StreamStateHandle proxy = new StateHandleProxy(new Path(), proxySize);
-
-					SubtaskState subtaskState = new SubtaskState(
-							new ChainedStateHandle<>(Collections.singletonList(proxy)), null, null, null, null, duration);
-
-					taskState.putState(subtaskIndex, subtaskState);
-				}
-			}
-
-			// Add some random delay
-			final long completionTimestamp = triggerTimestamp + completionDuration + RAND.nextInt(10);
-
-			checkpoints[i] = new CompletedCheckpoint(jobId, i, triggerTimestamp, completionTimestamp, taskGroupStates, CheckpointProperties.forStandardCheckpoint(), null);
-		}
-
-		return checkpoints;
-	}
-
-	private List<ExecutionJobVertex> createTasksToWaitFor(CompletedCheckpoint checkpoint) {
-
-		List<ExecutionJobVertex> jobVertices = new ArrayList<>(checkpoint.getTaskStates().size());
-
-		for (Map.Entry<JobVertexID, TaskState> entry : checkpoint.getTaskStates().entrySet()) {
-			JobVertexID operatorId = entry.getKey();
-			int parallelism = entry.getValue().getParallelism();
-			ExecutionJobVertex v = mock(ExecutionJobVertex.class);
-			when(v.getJobVertexId()).thenReturn(operatorId);
-			when(v.getParallelism()).thenReturn(parallelism);
-
-			jobVertices.add(v);
-		}
-
-		return jobVertices;
-	}
-
-	private static class StateHandleProxy extends FileStateHandle {
-
-		private static final long serialVersionUID = 35356735683568L;
-
-		public StateHandleProxy(Path filePath, long size) {
-			super(filePath, size);
-		}
-
-		@Override
-		public void discardState() {}
-	}
-}


[09/11] flink git commit: [FLINK-4410] [runtime] Rework checkpoint stats tracking

Posted by uc...@apache.org.
http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointStatsCountsTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointStatsCountsTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointStatsCountsTest.java
new file mode 100644
index 0000000..cf1e7f7
--- /dev/null
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointStatsCountsTest.java
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.checkpoint;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class CheckpointStatsCountsTest {
+
+	/**
+	 * Tests that counts are reported correctly.
+	 */
+	@Test
+	public void testCounts() throws Exception {
+		CheckpointStatsCounts counts = new CheckpointStatsCounts();
+		assertEquals(0, counts.getNumberOfRestoredCheckpoints());
+		assertEquals(0, counts.getTotalNumberOfCheckpoints());
+		assertEquals(0, counts.getNumberOfInProgressCheckpoints());
+		assertEquals(0, counts.getNumberOfCompletedCheckpoints());
+		assertEquals(0, counts.getNumberOfFailedCheckpoints());
+
+		counts.incrementRestoredCheckpoints();
+		assertEquals(1, counts.getNumberOfRestoredCheckpoints());
+		assertEquals(0, counts.getTotalNumberOfCheckpoints());
+		assertEquals(0, counts.getNumberOfInProgressCheckpoints());
+		assertEquals(0, counts.getNumberOfCompletedCheckpoints());
+		assertEquals(0, counts.getNumberOfFailedCheckpoints());
+
+		// 1st checkpoint
+		counts.incrementInProgressCheckpoints();
+		assertEquals(1, counts.getNumberOfRestoredCheckpoints());
+		assertEquals(1, counts.getTotalNumberOfCheckpoints());
+		assertEquals(1, counts.getNumberOfInProgressCheckpoints());
+		assertEquals(0, counts.getNumberOfCompletedCheckpoints());
+		assertEquals(0, counts.getNumberOfFailedCheckpoints());
+
+		counts.incrementCompletedCheckpoints();
+		assertEquals(1, counts.getNumberOfRestoredCheckpoints());
+		assertEquals(1, counts.getTotalNumberOfCheckpoints());
+		assertEquals(0, counts.getNumberOfInProgressCheckpoints());
+		assertEquals(1, counts.getNumberOfCompletedCheckpoints());
+		assertEquals(0, counts.getNumberOfFailedCheckpoints());
+
+		// 2nd checkpoint
+		counts.incrementInProgressCheckpoints();
+		assertEquals(1, counts.getNumberOfRestoredCheckpoints());
+		assertEquals(2, counts.getTotalNumberOfCheckpoints());
+		assertEquals(1, counts.getNumberOfInProgressCheckpoints());
+		assertEquals(1, counts.getNumberOfCompletedCheckpoints());
+		assertEquals(0, counts.getNumberOfFailedCheckpoints());
+
+		counts.incrementFailedCheckpoints();
+		assertEquals(1, counts.getNumberOfRestoredCheckpoints());
+		assertEquals(2, counts.getTotalNumberOfCheckpoints());
+		assertEquals(0, counts.getNumberOfInProgressCheckpoints());
+		assertEquals(1, counts.getNumberOfCompletedCheckpoints());
+		assertEquals(1, counts.getNumberOfFailedCheckpoints());
+	}
+
+	/**
+	 * Tests that increment the completed or failed number of checkpoints without
+	 * incrementing the in progress checkpoints before throws an Exception.
+	 */
+	@Test
+	public void testCompleteOrFailWithoutInProgressCheckpoint() throws Exception {
+		CheckpointStatsCounts counts = new CheckpointStatsCounts();
+		try {
+			counts.incrementCompletedCheckpoints();
+			fail("Did not throw expected Exception");
+		} catch (IllegalStateException ignored) {
+		}
+
+		try {
+			counts.incrementFailedCheckpoints();
+			fail("Did not throw expected Exception");
+		} catch (IllegalStateException ignored) {
+		}
+	}
+
+	/**
+	 * Tests that that taking snapshots of the state are independent from the
+	 * parent.
+	 */
+	@Test
+	public void testCreateSnapshot() throws Exception {
+		CheckpointStatsCounts counts = new CheckpointStatsCounts();
+		counts.incrementRestoredCheckpoints();
+		counts.incrementRestoredCheckpoints();
+		counts.incrementRestoredCheckpoints();
+
+		counts.incrementInProgressCheckpoints();
+		counts.incrementCompletedCheckpoints();
+
+		counts.incrementInProgressCheckpoints();
+		counts.incrementCompletedCheckpoints();
+
+		counts.incrementInProgressCheckpoints();
+		counts.incrementCompletedCheckpoints();
+
+		counts.incrementInProgressCheckpoints();
+		counts.incrementCompletedCheckpoints();
+
+		counts.incrementInProgressCheckpoints();
+		counts.incrementFailedCheckpoints();
+
+		long restored = counts.getNumberOfRestoredCheckpoints();
+		long total = counts.getTotalNumberOfCheckpoints();
+		long inProgress = counts.getNumberOfInProgressCheckpoints();
+		long completed = counts.getNumberOfCompletedCheckpoints();
+		long failed = counts.getNumberOfFailedCheckpoints();
+
+		CheckpointStatsCounts snapshot = counts.createSnapshot();
+		assertEquals(restored, snapshot.getNumberOfRestoredCheckpoints());
+		assertEquals(total, snapshot.getTotalNumberOfCheckpoints());
+		assertEquals(inProgress, snapshot.getNumberOfInProgressCheckpoints());
+		assertEquals(completed, snapshot.getNumberOfCompletedCheckpoints());
+		assertEquals(failed, snapshot.getNumberOfFailedCheckpoints());
+
+		// Update the original
+		counts.incrementRestoredCheckpoints();
+		counts.incrementRestoredCheckpoints();
+
+		counts.incrementInProgressCheckpoints();
+		counts.incrementCompletedCheckpoints();
+
+		counts.incrementInProgressCheckpoints();
+		counts.incrementFailedCheckpoints();
+
+		assertEquals(restored, snapshot.getNumberOfRestoredCheckpoints());
+		assertEquals(total, snapshot.getTotalNumberOfCheckpoints());
+		assertEquals(inProgress, snapshot.getNumberOfInProgressCheckpoints());
+		assertEquals(completed, snapshot.getNumberOfCompletedCheckpoints());
+		assertEquals(failed, snapshot.getNumberOfFailedCheckpoints());
+	}
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointStatsHistoryTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointStatsHistoryTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointStatsHistoryTest.java
new file mode 100644
index 0000000..098fe17
--- /dev/null
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointStatsHistoryTest.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.checkpoint;
+
+import org.junit.Test;
+
+import java.util.Iterator;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class CheckpointStatsHistoryTest {
+
+	/**
+	 * Tests a checkpoint history with allowed size 0.
+	 */
+	@Test
+	public void testZeroMaxSizeHistory() throws Exception {
+		CheckpointStatsHistory history = new CheckpointStatsHistory(0);
+
+		history.addInProgressCheckpoint(createPendingCheckpointStats(0));
+		assertFalse(history.replacePendingCheckpointById(createCompletedCheckpointStats(0)));
+
+		CheckpointStatsHistory snapshot = history.createSnapshot();
+
+		int counter = 0;
+		for (AbstractCheckpointStats ignored : snapshot.getCheckpoints()) {
+			counter++;
+		}
+
+		assertEquals(0, counter);
+		assertNotNull(snapshot.getCheckpointById(0));
+	}
+
+	/**
+	 * Tests a checkpoint history with allowed size 1.
+	 */
+	@Test
+	public void testSizeOneHistory() throws Exception {
+		CheckpointStatsHistory history = new CheckpointStatsHistory(1);
+
+		history.addInProgressCheckpoint(createPendingCheckpointStats(0));
+		history.addInProgressCheckpoint(createPendingCheckpointStats(1));
+
+		assertFalse(history.replacePendingCheckpointById(createCompletedCheckpointStats(0)));
+		assertTrue(history.replacePendingCheckpointById(createCompletedCheckpointStats(1)));
+
+		CheckpointStatsHistory snapshot = history.createSnapshot();
+
+		for (AbstractCheckpointStats stats : snapshot.getCheckpoints()) {
+			assertEquals(1, stats.getCheckpointId());
+			assertTrue(stats.getStatus().isCompleted());
+		}
+	}
+
+	/**
+	 * Tests the checkpoint history with multiple checkpoints.
+	 */
+	@Test
+	public void testCheckpointHistory() throws Exception {
+		CheckpointStatsHistory history = new CheckpointStatsHistory(3);
+
+		history.addInProgressCheckpoint(createPendingCheckpointStats(0));
+
+		CheckpointStatsHistory snapshot = history.createSnapshot();
+		for (AbstractCheckpointStats stats : snapshot.getCheckpoints()) {
+			assertEquals(0, stats.getCheckpointId());
+			assertTrue(stats.getStatus().isInProgress());
+		}
+
+		history.addInProgressCheckpoint(createPendingCheckpointStats(1));
+		history.addInProgressCheckpoint(createPendingCheckpointStats(2));
+		history.addInProgressCheckpoint(createPendingCheckpointStats(3));
+
+		snapshot = history.createSnapshot();
+
+		// Check in progress stats.
+		Iterator<AbstractCheckpointStats> it = snapshot.getCheckpoints().iterator();
+		for (int i = 3; i > 0; i--) {
+			assertTrue(it.hasNext());
+			AbstractCheckpointStats stats = it.next();
+			assertEquals(i, stats.getCheckpointId());
+			assertTrue(stats.getStatus().isInProgress());
+		}
+		assertFalse(it.hasNext());
+
+		// Update checkpoints
+		history.replacePendingCheckpointById(createFailedCheckpointStats(1));
+		history.replacePendingCheckpointById(createCompletedCheckpointStats(3));
+		history.replacePendingCheckpointById(createFailedCheckpointStats(2));
+
+		snapshot = history.createSnapshot();
+		it = snapshot.getCheckpoints().iterator();
+
+		assertTrue(it.hasNext());
+		AbstractCheckpointStats stats = it.next();
+		assertEquals(3, stats.getCheckpointId());
+		assertNotNull(snapshot.getCheckpointById(3));
+		assertTrue(stats.getStatus().isCompleted());
+		assertTrue(snapshot.getCheckpointById(3).getStatus().isCompleted());
+
+		assertTrue(it.hasNext());
+		stats = it.next();
+		assertEquals(2, stats.getCheckpointId());
+		assertNotNull(snapshot.getCheckpointById(2));
+		assertTrue(stats.getStatus().isFailed());
+		assertTrue(snapshot.getCheckpointById(2).getStatus().isFailed());
+
+		assertTrue(it.hasNext());
+		stats = it.next();
+		assertEquals(1, stats.getCheckpointId());
+		assertNotNull(snapshot.getCheckpointById(1));
+		assertTrue(stats.getStatus().isFailed());
+		assertTrue(snapshot.getCheckpointById(1).getStatus().isFailed());
+
+		assertFalse(it.hasNext());
+	}
+
+	/**
+	 * Tests that a snapshot cannot be modified or copied.
+	 */
+	@Test
+	public void testModifySnapshot() throws Exception {
+		CheckpointStatsHistory history = new CheckpointStatsHistory(3);
+
+		history.addInProgressCheckpoint(createPendingCheckpointStats(0));
+		history.addInProgressCheckpoint(createPendingCheckpointStats(1));
+		history.addInProgressCheckpoint(createPendingCheckpointStats(2));
+
+		CheckpointStatsHistory snapshot = history.createSnapshot();
+
+		try {
+			snapshot.addInProgressCheckpoint(createPendingCheckpointStats(4));
+			fail("Did not throw expected Exception");
+		} catch (UnsupportedOperationException ignored) {
+		}
+
+		try {
+			snapshot.replacePendingCheckpointById(createCompletedCheckpointStats(2));
+			fail("Did not throw expected Exception");
+		} catch (UnsupportedOperationException ignored) {
+		}
+
+		try {
+			snapshot.createSnapshot();
+			fail("Did not throw expected Exception");
+		} catch (UnsupportedOperationException ignored) {
+		}
+	}
+
+	// ------------------------------------------------------------------------
+
+	private PendingCheckpointStats createPendingCheckpointStats(long checkpointId) {
+		PendingCheckpointStats pending = mock(PendingCheckpointStats.class);
+		when(pending.getStatus()).thenReturn(CheckpointStatsStatus.IN_PROGRESS);
+		when(pending.getCheckpointId()).thenReturn(checkpointId);
+		return pending;
+	}
+
+	private CompletedCheckpointStats createCompletedCheckpointStats(long checkpointId) {
+		CompletedCheckpointStats completed = mock(CompletedCheckpointStats.class);
+		when(completed.getStatus()).thenReturn(CheckpointStatsStatus.COMPLETED);
+		when(completed.getCheckpointId()).thenReturn(checkpointId);
+		return completed;
+	}
+
+	private FailedCheckpointStats createFailedCheckpointStats(long checkpointId) {
+		FailedCheckpointStats failed = mock(FailedCheckpointStats.class);
+		when(failed.getStatus()).thenReturn(CheckpointStatsStatus.FAILED);
+		when(failed.getCheckpointId()).thenReturn(checkpointId);
+		return failed;
+	}
+
+
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointStatsStatusTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointStatsStatusTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointStatsStatusTest.java
new file mode 100644
index 0000000..0e16109
--- /dev/null
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointStatsStatusTest.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.checkpoint;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class CheckpointStatsStatusTest {
+
+	/**
+	 * Tests the getters of each status.
+	 */
+	@Test
+	public void testStatusValues() throws Exception {
+		CheckpointStatsStatus inProgress = CheckpointStatsStatus.IN_PROGRESS;
+		assertTrue(inProgress.isInProgress());
+		assertFalse(inProgress.isCompleted());
+		assertFalse(inProgress.isFailed());
+
+		CheckpointStatsStatus completed = CheckpointStatsStatus.COMPLETED;
+		assertFalse(completed.isInProgress());
+		assertTrue(completed.isCompleted());
+		assertFalse(completed.isFailed());
+
+		CheckpointStatsStatus failed = CheckpointStatsStatus.FAILED;
+		assertFalse(failed.isInProgress());
+		assertFalse(failed.isCompleted());
+		assertTrue(failed.isFailed());
+	}
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointStatsTrackerTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointStatsTrackerTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointStatsTrackerTest.java
new file mode 100644
index 0000000..9a39182
--- /dev/null
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointStatsTrackerTest.java
@@ -0,0 +1,327 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.checkpoint;
+
+import org.apache.flink.metrics.Gauge;
+import org.apache.flink.metrics.MetricGroup;
+import org.apache.flink.metrics.groups.UnregisteredMetricsGroup;
+import org.apache.flink.runtime.executiongraph.ExecutionJobVertex;
+import org.apache.flink.runtime.jobgraph.JobVertexID;
+import org.apache.flink.runtime.jobgraph.tasks.ExternalizedCheckpointSettings;
+import org.apache.flink.runtime.jobgraph.tasks.JobSnapshottingSettings;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.Iterator;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class CheckpointStatsTrackerTest {
+
+	/**
+	 * Tests access to the snapshotting settings.
+	 */
+	@Test
+	public void testGetSnapshottingSettings() throws Exception {
+		ExecutionJobVertex jobVertex = mock(ExecutionJobVertex.class);
+		when(jobVertex.getJobVertexId()).thenReturn(new JobVertexID());
+		when(jobVertex.getParallelism()).thenReturn(1);
+
+		JobSnapshottingSettings snapshottingSettings = new JobSnapshottingSettings(
+			Collections.singletonList(new JobVertexID()),
+			Collections.singletonList(new JobVertexID()),
+			Collections.singletonList(new JobVertexID()),
+			181238123L,
+			19191992L,
+			191929L,
+			123,
+			ExternalizedCheckpointSettings.none(),
+			false);
+
+		CheckpointStatsTracker tracker = new CheckpointStatsTracker(
+			0,
+			Collections.singletonList(jobVertex),
+			snapshottingSettings,
+			new UnregisteredMetricsGroup());
+
+		assertEquals(snapshottingSettings, tracker.getSnapshottingSettings());
+	}
+
+	/**
+	 * Tests that the number of remembered checkpoints configuration
+	 * is respected.
+	 */
+	@Test
+	public void testTrackerWithoutHistory() throws Exception {
+		int numberOfSubtasks = 3;
+
+		ExecutionJobVertex jobVertex = mock(ExecutionJobVertex.class);
+		when(jobVertex.getJobVertexId()).thenReturn(new JobVertexID());
+		when(jobVertex.getParallelism()).thenReturn(numberOfSubtasks);
+
+		CheckpointStatsTracker tracker = new CheckpointStatsTracker(
+			0,
+			Collections.singletonList(jobVertex),
+			mock(JobSnapshottingSettings.class),
+			new UnregisteredMetricsGroup());
+
+		PendingCheckpointStats pending = tracker.reportPendingCheckpoint(
+			0,
+			1,
+			CheckpointProperties.forStandardCheckpoint());
+
+		pending.reportSubtaskStats(jobVertex.getJobVertexId(), createSubtaskStats(0));
+		pending.reportSubtaskStats(jobVertex.getJobVertexId(), createSubtaskStats(1));
+		pending.reportSubtaskStats(jobVertex.getJobVertexId(), createSubtaskStats(2));
+
+		pending.reportCompletedCheckpoint(null);
+
+		CheckpointStatsSnapshot snapshot = tracker.createSnapshot();
+		// History should be empty
+		assertFalse(snapshot.getHistory().getCheckpoints().iterator().hasNext());
+
+		// Counts should be available
+		CheckpointStatsCounts counts = snapshot.getCounts();
+		assertEquals(1, counts.getNumberOfCompletedCheckpoints());
+		assertEquals(1, counts.getTotalNumberOfCheckpoints());
+
+		// Summary should be available
+		CompletedCheckpointStatsSummary summary = snapshot.getSummaryStats();
+		assertEquals(1, summary.getStateSizeStats().getCount());
+		assertEquals(1, summary.getEndToEndDurationStats().getCount());
+		assertEquals(1, summary.getAlignmentBufferedStats().getCount());
+
+		// Latest completed checkpoint
+		assertNotNull(snapshot.getHistory().getLatestCompletedCheckpoint());
+		assertEquals(0, snapshot.getHistory().getLatestCompletedCheckpoint().getCheckpointId());
+	}
+
+	/**
+	 * Tests tracking of checkpoints.
+	 */
+	@Test
+	public void testCheckpointTracking() throws Exception {
+		int numberOfSubtasks = 3;
+
+		ExecutionJobVertex jobVertex = mock(ExecutionJobVertex.class);
+		when(jobVertex.getJobVertexId()).thenReturn(new JobVertexID());
+		when(jobVertex.getParallelism()).thenReturn(numberOfSubtasks);
+
+		CheckpointStatsTracker tracker = new CheckpointStatsTracker(
+			10,
+			Collections.singletonList(jobVertex),
+			mock(JobSnapshottingSettings.class),
+			new UnregisteredMetricsGroup());
+
+		// Completed checkpoint
+		PendingCheckpointStats completed1 = tracker.reportPendingCheckpoint(
+			0,
+			1,
+			CheckpointProperties.forStandardCheckpoint());
+
+		completed1.reportSubtaskStats(jobVertex.getJobVertexId(), createSubtaskStats(0));
+		completed1.reportSubtaskStats(jobVertex.getJobVertexId(), createSubtaskStats(1));
+		completed1.reportSubtaskStats(jobVertex.getJobVertexId(), createSubtaskStats(2));
+
+		completed1.reportCompletedCheckpoint(null);
+
+		// Failed checkpoint
+		PendingCheckpointStats failed = tracker.reportPendingCheckpoint(
+			1,
+			1,
+			CheckpointProperties.forStandardCheckpoint());
+
+		failed.reportFailedCheckpoint(12, null);
+
+		// Completed savepoint
+		PendingCheckpointStats savepoint = tracker.reportPendingCheckpoint(
+			2,
+			1,
+			CheckpointProperties.forStandardSavepoint());
+
+		savepoint.reportSubtaskStats(jobVertex.getJobVertexId(), createSubtaskStats(0));
+		savepoint.reportSubtaskStats(jobVertex.getJobVertexId(), createSubtaskStats(1));
+		savepoint.reportSubtaskStats(jobVertex.getJobVertexId(), createSubtaskStats(2));
+
+		savepoint.reportCompletedCheckpoint(null);
+
+		// In Progress
+		PendingCheckpointStats inProgress = tracker.reportPendingCheckpoint(
+			3,
+			1,
+			CheckpointProperties.forStandardCheckpoint());
+
+		RestoredCheckpointStats restored = new RestoredCheckpointStats(81, CheckpointProperties.forStandardCheckpoint(), 123, null);
+		tracker.reportRestoredCheckpoint(restored);
+
+		CheckpointStatsSnapshot snapshot = tracker.createSnapshot();
+
+		// Counts
+		CheckpointStatsCounts counts = snapshot.getCounts();
+		assertEquals(4, counts.getTotalNumberOfCheckpoints());
+		assertEquals(1, counts.getNumberOfInProgressCheckpoints());
+		assertEquals(2, counts.getNumberOfCompletedCheckpoints());
+		assertEquals(1, counts.getNumberOfFailedCheckpoints());
+
+		// Summary stats
+		CompletedCheckpointStatsSummary summary = snapshot.getSummaryStats();
+		assertEquals(2, summary.getStateSizeStats().getCount());
+		assertEquals(2, summary.getEndToEndDurationStats().getCount());
+		assertEquals(2, summary.getAlignmentBufferedStats().getCount());
+
+		// History
+		CheckpointStatsHistory history = snapshot.getHistory();
+		Iterator<AbstractCheckpointStats> it = history.getCheckpoints().iterator();
+
+		assertTrue(it.hasNext());
+		AbstractCheckpointStats stats = it.next();
+		assertEquals(3, stats.getCheckpointId());
+		assertTrue(stats.getStatus().isInProgress());
+
+		assertTrue(it.hasNext());
+		stats = it.next();
+		assertEquals(2, stats.getCheckpointId());
+		assertTrue(stats.getStatus().isCompleted());
+
+		assertTrue(it.hasNext());
+		stats = it.next();
+		assertEquals(1, stats.getCheckpointId());
+		assertTrue(stats.getStatus().isFailed());
+
+		assertTrue(it.hasNext());
+		stats = it.next();
+		assertEquals(0, stats.getCheckpointId());
+		assertTrue(stats.getStatus().isCompleted());
+
+		assertFalse(it.hasNext());
+
+		// Latest checkpoints
+		assertEquals(completed1.getCheckpointId(), snapshot.getHistory().getLatestCompletedCheckpoint().getCheckpointId());
+		assertEquals(savepoint.getCheckpointId(), snapshot.getHistory().getLatestSavepoint().getCheckpointId());
+		assertEquals(failed.getCheckpointId(), snapshot.getHistory().getLatestFailedCheckpoint().getCheckpointId());
+		assertEquals(restored, snapshot.getLatestRestoredCheckpoint());
+	}
+
+	/**
+	 * Tests that snapshots are only created if a new snapshot has been reported
+	 * or updated.
+	 */
+	@Test
+	public void testCreateSnapshot() throws Exception {
+		ExecutionJobVertex jobVertex = mock(ExecutionJobVertex.class);
+		when(jobVertex.getJobVertexId()).thenReturn(new JobVertexID());
+		when(jobVertex.getParallelism()).thenReturn(1);
+
+		CheckpointStatsTracker tracker = new CheckpointStatsTracker(
+			10,
+			Collections.singletonList(jobVertex),
+			mock(JobSnapshottingSettings.class),
+			new UnregisteredMetricsGroup());
+
+		CheckpointStatsSnapshot snapshot1 = tracker.createSnapshot();
+
+		// Pending checkpoint => new snapshot
+		PendingCheckpointStats pending = tracker.reportPendingCheckpoint(
+			0,
+			1,
+			CheckpointProperties.forStandardCheckpoint());
+
+		pending.reportSubtaskStats(jobVertex.getJobVertexId(), createSubtaskStats(0));
+
+		CheckpointStatsSnapshot snapshot2 = tracker.createSnapshot();
+		assertNotEquals(snapshot1, snapshot2);
+
+		assertEquals(snapshot2, tracker.createSnapshot());
+
+		// Complete checkpoint => new snapshot
+		pending.reportCompletedCheckpoint(null);
+
+		CheckpointStatsSnapshot snapshot3 = tracker.createSnapshot();
+		assertNotEquals(snapshot2, snapshot3);
+
+		// Restore operation => new snapshot
+		tracker.reportRestoredCheckpoint(new RestoredCheckpointStats(12, CheckpointProperties.forStandardCheckpoint(), 12, null));
+
+		CheckpointStatsSnapshot snapshot4 = tracker.createSnapshot();
+		assertNotEquals(snapshot3, snapshot4);
+		assertEquals(snapshot4, tracker.createSnapshot());
+	}
+
+	/**
+	 * Tests the registered metrics.
+	 */
+	@Test
+	public void testMetrics() throws Exception {
+		MetricGroup metricGroup = mock(MetricGroup.class);
+
+		ExecutionJobVertex jobVertex = mock(ExecutionJobVertex.class);
+		when(jobVertex.getJobVertexId()).thenReturn(new JobVertexID());
+		when(jobVertex.getParallelism()).thenReturn(1);
+
+		new CheckpointStatsTracker(
+			0,
+			Collections.singletonList(jobVertex),
+			mock(JobSnapshottingSettings.class),
+			metricGroup);
+
+		verify(metricGroup, times(1)).gauge(eq(CheckpointStatsTracker.NUMBER_OF_CHECKPOINTS_METRIC), any(Gauge.class));
+		verify(metricGroup, times(1)).gauge(eq(CheckpointStatsTracker.NUMBER_OF_IN_PROGRESS_CHECKPOINTS_METRIC), any(Gauge.class));
+		verify(metricGroup, times(1)).gauge(eq(CheckpointStatsTracker.NUMBER_OF_COMPLETED_CHECKPOINTS_METRIC), any(Gauge.class));
+		verify(metricGroup, times(1)).gauge(eq(CheckpointStatsTracker.NUMBER_OF_FAILED_CHECKPOINTS_METRIC), any(Gauge.class));
+		verify(metricGroup, times(1)).gauge(eq(CheckpointStatsTracker.LATEST_RESTORED_CHECKPOINT_TIMESTAMP_METRIC), any(Gauge.class));
+		verify(metricGroup, times(1)).gauge(eq(CheckpointStatsTracker.LATEST_COMPLETED_CHECKPOINT_SIZE_METRIC), any(Gauge.class));
+		verify(metricGroup, times(1)).gauge(eq(CheckpointStatsTracker.LATEST_COMPLETED_CHECKPOINT_DURATION_METRIC), any(Gauge.class));
+		verify(metricGroup, times(1)).gauge(eq(CheckpointStatsTracker.LATEST_COMPLETED_CHECKPOINT_ALIGNMENT_BUFFERED_METRIC), any(Gauge.class));
+		verify(metricGroup, times(1)).gauge(eq(CheckpointStatsTracker.LATEST_COMPLETED_CHECKPOINT_EXTERNAL_PATH_METRIC), any(Gauge.class));
+
+		// Make sure this test is adjusted when further metrics are added
+		verify(metricGroup, times(9)).gauge(any(String.class), any(Gauge.class));
+	}
+
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Creates a "disabled" checkpoint tracker for tests.
+	 */
+	static CheckpointStatsTracker createTestTracker() {
+		ExecutionJobVertex jobVertex = mock(ExecutionJobVertex.class);
+		when(jobVertex.getJobVertexId()).thenReturn(new JobVertexID());
+		when(jobVertex.getParallelism()).thenReturn(1);
+
+		return new CheckpointStatsTracker(
+			0,
+			Collections.singletonList(jobVertex),
+			mock(JobSnapshottingSettings.class),
+			new UnregisteredMetricsGroup());
+	}
+
+	private SubtaskStateStats createSubtaskStats(int index) {
+		return new SubtaskStateStats(index, 0, 0, 0, 0, 0, 0);
+	}
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CompletedCheckpointStatsSummaryTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CompletedCheckpointStatsSummaryTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CompletedCheckpointStatsSummaryTest.java
new file mode 100644
index 0000000..dd9c2c8
--- /dev/null
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CompletedCheckpointStatsSummaryTest.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.checkpoint;
+
+import org.apache.flink.runtime.jobgraph.JobVertexID;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class CompletedCheckpointStatsSummaryTest {
+
+	/**
+	 * Tests simple updates of the completed checkpoint stats.
+	 */
+	@Test
+	public void testSimpleUpdates() throws Exception {
+		long triggerTimestamp = 123123L;
+		long ackTimestamp = 123123 + 1212312399L;
+		long stateSize = Integer.MAX_VALUE + 17787L;
+		long alignmentBuffered = Integer.MAX_VALUE + 123123L;
+
+		CompletedCheckpointStatsSummary summary = new CompletedCheckpointStatsSummary();
+		assertEquals(0, summary.getStateSizeStats().getCount());
+		assertEquals(0, summary.getEndToEndDurationStats().getCount());
+		assertEquals(0, summary.getAlignmentBufferedStats().getCount());
+
+		int numCheckpoints = 10;
+
+		for (int i = 0; i < numCheckpoints; i++) {
+			CompletedCheckpointStats completed = createCompletedCheckpoint(
+				i,
+				triggerTimestamp,
+				ackTimestamp + i,
+				stateSize + i,
+				alignmentBuffered + i);
+
+			summary.updateSummary(completed);
+
+			assertEquals(i + 1, summary.getStateSizeStats().getCount());
+			assertEquals(i + 1, summary.getEndToEndDurationStats().getCount());
+			assertEquals(i + 1, summary.getAlignmentBufferedStats().getCount());
+		}
+
+		MinMaxAvgStats stateSizeStats = summary.getStateSizeStats();
+		assertEquals(stateSize, stateSizeStats.getMinimum());
+		assertEquals(stateSize + numCheckpoints - 1, stateSizeStats.getMaximum());
+
+		MinMaxAvgStats durationStats = summary.getEndToEndDurationStats();
+		assertEquals(ackTimestamp - triggerTimestamp, durationStats.getMinimum());
+		assertEquals(ackTimestamp - triggerTimestamp + numCheckpoints - 1, durationStats.getMaximum());
+
+		MinMaxAvgStats alignmentBufferedStats = summary.getAlignmentBufferedStats();
+		assertEquals(alignmentBuffered, alignmentBufferedStats.getMinimum());
+		assertEquals(alignmentBuffered + numCheckpoints - 1, alignmentBufferedStats.getMaximum());
+	}
+
+	private CompletedCheckpointStats createCompletedCheckpoint(
+		long checkpointId,
+		long triggerTimestamp,
+		long ackTimestamp,
+		long stateSize,
+		long alignmentBuffered) {
+
+		SubtaskStateStats latest = mock(SubtaskStateStats.class);
+		when(latest.getAckTimestamp()).thenReturn(ackTimestamp);
+
+		Map<JobVertexID, TaskStateStats> taskStats = new HashMap<>();
+		JobVertexID jobVertexId = new JobVertexID();
+		taskStats.put(jobVertexId, new TaskStateStats(jobVertexId, 1));
+
+		return new CompletedCheckpointStats(
+			checkpointId,
+			triggerTimestamp,
+			CheckpointProperties.forStandardCheckpoint(),
+			1,
+			taskStats,
+			1,
+			stateSize,
+			alignmentBuffered,
+			latest,
+			null);
+	}
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CompletedCheckpointStoreTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CompletedCheckpointStoreTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CompletedCheckpointStoreTest.java
index 9f4bdba..725b85f 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CompletedCheckpointStoreTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CompletedCheckpointStoreTest.java
@@ -206,7 +206,7 @@ public abstract class CompletedCheckpointStoreTest extends TestLogger {
 			ChainedStateHandle<StreamStateHandle> stateHandle = CheckpointCoordinatorTest.generateChainedStateHandle(
 					new CheckpointMessagesTest.MyHandle());
 
-			taskState.putState(i, new SubtaskState(stateHandle, null, null, null, null, 0L));
+			taskState.putState(i, new SubtaskState(stateHandle, null, null, null, null));
 		}
 
 		return new TestCompletedCheckpoint(new JobID(), id, 0, taskGroupStates, props);

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CompletedCheckpointTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CompletedCheckpointTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CompletedCheckpointTest.java
index 25a4703..e466dc7 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CompletedCheckpointTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CompletedCheckpointTest.java
@@ -118,4 +118,29 @@ public class CompletedCheckpointTest {
 			verify(state, times(1)).discardState();
 		}
 	}
+
+	/**
+	 * Tests that the stats callbacks happen if the callback is registered.
+	 */
+	@Test
+	public void testCompletedCheckpointStatsCallbacks() throws Exception {
+		TaskState state = mock(TaskState.class);
+		Map<JobVertexID, TaskState> taskStates = new HashMap<>();
+		taskStates.put(new JobVertexID(), state);
+
+		CompletedCheckpoint completed = new CompletedCheckpoint(
+			new JobID(),
+			0,
+			0,
+			1,
+			new HashMap<>(taskStates),
+			CheckpointProperties.forStandardCheckpoint(),
+			null);
+
+		CompletedCheckpointStats.DiscardCallback callback = mock(CompletedCheckpointStats.DiscardCallback.class);
+		completed.setDiscardCallback(callback);
+
+		completed.discard(JobStatus.FINISHED);
+		verify(callback, times(1)).notifyDiscardedCheckpoint();
+	}
 }

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CoordinatorShutdownTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CoordinatorShutdownTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CoordinatorShutdownTest.java
index 777ba9b..6c63f53 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CoordinatorShutdownTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CoordinatorShutdownTest.java
@@ -64,7 +64,7 @@ public class CoordinatorShutdownTest {
 			
 			JobGraph testGraph = new JobGraph("test job", vertex);
 			testGraph.setSnapshotSettings(new JobSnapshottingSettings(vertexIdList, vertexIdList, vertexIdList, 
-					5000, 60000, 0L, Integer.MAX_VALUE, ExternalizedCheckpointSettings.none()));
+					5000, 60000, 0L, Integer.MAX_VALUE, ExternalizedCheckpointSettings.none(), true));
 			
 			ActorGateway jmGateway = cluster.getLeaderGateway(TestingUtils.TESTING_DURATION());
 
@@ -123,7 +123,7 @@ public class CoordinatorShutdownTest {
 
 			JobGraph testGraph = new JobGraph("test job", vertex);
 			testGraph.setSnapshotSettings(new JobSnapshottingSettings(vertexIdList, vertexIdList, vertexIdList,
-					5000, 60000, 0L, Integer.MAX_VALUE, ExternalizedCheckpointSettings.none()));
+					5000, 60000, 0L, Integer.MAX_VALUE, ExternalizedCheckpointSettings.none(), true));
 			
 			ActorGateway jmGateway = cluster.getLeaderGateway(TestingUtils.TESTING_DURATION());
 

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/ExecutionGraphCheckpointCoordinatorTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/ExecutionGraphCheckpointCoordinatorTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/ExecutionGraphCheckpointCoordinatorTest.java
index c6c7ae5..bc95de7 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/ExecutionGraphCheckpointCoordinatorTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/ExecutionGraphCheckpointCoordinatorTest.java
@@ -27,7 +27,6 @@ import org.apache.flink.configuration.Configuration;
 import org.apache.flink.metrics.groups.UnregisteredMetricsGroup;
 import org.apache.flink.runtime.akka.AkkaUtils;
 import org.apache.flink.runtime.blob.BlobKey;
-import org.apache.flink.runtime.checkpoint.stats.DisabledCheckpointStatsTracker;
 import org.apache.flink.runtime.executiongraph.ExecutionGraph;
 import org.apache.flink.runtime.executiongraph.ExecutionJobVertex;
 import org.apache.flink.runtime.executiongraph.restart.NoRestartStrategy;
@@ -40,11 +39,9 @@ import org.apache.flink.util.SerializedValue;
 import org.junit.AfterClass;
 import org.junit.Test;
 import org.mockito.Matchers;
-import scala.concurrent.duration.FiniteDuration;
 
 import java.net.URL;
 import java.util.Collections;
-import java.util.concurrent.TimeUnit;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
@@ -121,7 +118,7 @@ public class ExecutionGraphCheckpointCoordinatorTest {
 				counter,
 				store,
 				null,
-				new DisabledCheckpointStatsTracker());
+				CheckpointStatsTrackerTest.createTestTracker());
 
 		JobVertex jobVertex = new JobVertex("MockVertex");
 		jobVertex.setInvokableClass(AbstractInvokable.class);

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/FailedCheckpointStatsTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/FailedCheckpointStatsTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/FailedCheckpointStatsTest.java
new file mode 100644
index 0000000..683c1c9
--- /dev/null
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/FailedCheckpointStatsTest.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.checkpoint;
+
+import org.apache.flink.runtime.jobgraph.JobVertexID;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+
+public class FailedCheckpointStatsTest {
+
+	/**
+	 * Tests that the end to end duration of a failed checkpoint is the duration
+	 * until the failure.
+	 */
+	@Test
+	public void testEndToEndDuration() throws Exception {
+		long duration = 123912931293L;
+		long triggerTimestamp = 10123;
+		long failureTimestamp = triggerTimestamp + duration;
+
+		Map<JobVertexID, TaskStateStats> taskStats = new HashMap<>();
+		JobVertexID jobVertexId = new JobVertexID();
+		taskStats.put(jobVertexId, new TaskStateStats(jobVertexId, 1));
+
+		FailedCheckpointStats failed = new FailedCheckpointStats(
+			0,
+			triggerTimestamp,
+			CheckpointProperties.forStandardCheckpoint(),
+			1,
+			taskStats,
+			0,
+			0,
+			0,
+			failureTimestamp,
+			null,
+			null);
+
+		assertEquals(duration, failed.getEndToEndDuration());
+	}
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/MinMaxAvgStatsTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/MinMaxAvgStatsTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/MinMaxAvgStatsTest.java
new file mode 100644
index 0000000..5bb7492
--- /dev/null
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/MinMaxAvgStatsTest.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.checkpoint;
+
+import org.junit.Test;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+import static org.junit.Assert.assertEquals;
+
+public class MinMaxAvgStatsTest {
+
+	/**
+	 * Test the initial/empty state.
+	 */
+	@Test
+	public void testInitialState() throws Exception {
+		MinMaxAvgStats mma = new MinMaxAvgStats();
+
+		assertEquals(0, mma.getMinimum());
+		assertEquals(0, mma.getMaximum());
+		assertEquals(0, mma.getSum());
+		assertEquals(0, mma.getCount());
+		assertEquals(0, mma.getAverage());
+	}
+
+	/**
+	 * Test that non-positive numbers are not counted.
+	 */
+	@Test
+	public void testAddNonPositiveStats() throws Exception {
+		MinMaxAvgStats mma = new MinMaxAvgStats();
+		mma.add(-1);
+
+		assertEquals(0, mma.getMinimum());
+		assertEquals(0, mma.getMaximum());
+		assertEquals(0, mma.getSum());
+		assertEquals(0, mma.getCount());
+		assertEquals(0, mma.getAverage());
+
+		mma.add(0);
+
+		assertEquals(0, mma.getMinimum());
+		assertEquals(0, mma.getMaximum());
+		assertEquals(0, mma.getSum());
+		assertEquals(1, mma.getCount());
+		assertEquals(0, mma.getAverage());
+	}
+
+	/**
+	 * Test sequence of random numbers.
+	 */
+	@Test
+	public void testAddRandomNumbers() throws Exception {
+		ThreadLocalRandom rand = ThreadLocalRandom.current();
+
+		MinMaxAvgStats mma = new MinMaxAvgStats();
+
+		long count = 13;
+		long sum = 0;
+		long min = Integer.MAX_VALUE;
+		long max = Integer.MIN_VALUE;
+
+		for (int i = 0; i < count; i++) {
+			int number = rand.nextInt(124) + 1;
+			sum += number;
+			min = Math.min(min, number);
+			max = Math.max(max, number);
+
+			mma.add(number);
+		}
+
+		assertEquals(min, mma.getMinimum());
+		assertEquals(max, mma.getMaximum());
+		assertEquals(sum, mma.getSum());
+		assertEquals(count, mma.getCount());
+		assertEquals(sum / count, mma.getAverage());
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/PendingCheckpointStatsTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/PendingCheckpointStatsTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/PendingCheckpointStatsTest.java
new file mode 100644
index 0000000..854e106
--- /dev/null
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/PendingCheckpointStatsTest.java
@@ -0,0 +1,256 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.checkpoint;
+
+import org.apache.flink.runtime.jobgraph.JobVertexID;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static junit.framework.TestCase.assertFalse;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+public class PendingCheckpointStatsTest {
+
+	/**
+	 * Tests reporting of subtask stats.
+	 */
+	@Test
+	public void testReportSubtaskStats() throws Exception {
+		long checkpointId = Integer.MAX_VALUE + 1222L;
+		long triggerTimestamp = Integer.MAX_VALUE - 1239L;
+		CheckpointProperties props = CheckpointProperties.forStandardCheckpoint();
+		TaskStateStats task1 = new TaskStateStats(new JobVertexID(), 3);
+		TaskStateStats task2 = new TaskStateStats(new JobVertexID(), 4);
+		int totalSubtaskCount = task1.getNumberOfSubtasks() + task2.getNumberOfSubtasks();
+
+		Map<JobVertexID, TaskStateStats> taskStats = new HashMap<>();
+		taskStats.put(task1.getJobVertexId(), task1);
+		taskStats.put(task2.getJobVertexId(), task2);
+
+		CheckpointStatsTracker.PendingCheckpointStatsCallback callback = mock(
+			CheckpointStatsTracker.PendingCheckpointStatsCallback.class);
+
+		PendingCheckpointStats pending = new PendingCheckpointStats(
+				checkpointId,
+				triggerTimestamp,
+				props,
+				totalSubtaskCount,
+				taskStats,
+				callback);
+
+		// Check initial state
+		assertEquals(checkpointId, pending.getCheckpointId());
+		assertEquals(triggerTimestamp, pending.getTriggerTimestamp());
+		assertEquals(props, pending.getProperties());
+		assertEquals(CheckpointStatsStatus.IN_PROGRESS, pending.getStatus());
+		assertEquals(0, pending.getNumberOfAcknowledgedSubtasks());
+		assertEquals(0, pending.getStateSize());
+		assertEquals(totalSubtaskCount, pending.getNumberOfSubtasks());
+		assertNull(pending.getLatestAcknowledgedSubtaskStats());
+		assertEquals(-1, pending.getLatestAckTimestamp());
+		assertEquals(-1, pending.getEndToEndDuration());
+		assertEquals(task1, pending.getTaskStateStats(task1.getJobVertexId()));
+		assertEquals(task2, pending.getTaskStateStats(task2.getJobVertexId()));
+		assertNull(pending.getTaskStateStats(new JobVertexID()));
+
+		// Report subtasks and check getters
+		assertFalse(pending.reportSubtaskStats(new JobVertexID(), createSubtaskStats(0)));
+
+		long stateSize = 0;
+		long alignmentBuffered = 0;
+
+		// Report 1st task
+		for (int i = 0; i < task1.getNumberOfSubtasks(); i++) {
+			SubtaskStateStats subtask = createSubtaskStats(i);
+			stateSize += subtask.getStateSize();
+			alignmentBuffered += subtask.getAlignmentBuffered();
+
+			pending.reportSubtaskStats(task1.getJobVertexId(), subtask);
+
+			assertEquals(subtask, pending.getLatestAcknowledgedSubtaskStats());
+			assertEquals(subtask.getAckTimestamp(), pending.getLatestAckTimestamp());
+			assertEquals(subtask.getAckTimestamp() - triggerTimestamp, pending.getEndToEndDuration());
+			assertEquals(stateSize, pending.getStateSize());
+			assertEquals(alignmentBuffered, pending.getAlignmentBuffered());
+		}
+
+		// Don't allow overwrite
+		assertFalse(pending.reportSubtaskStats(task1.getJobVertexId(), task1.getSubtaskStats()[0]));
+
+		// Report 2nd task
+		for (int i = 0; i < task2.getNumberOfSubtasks(); i++) {
+			SubtaskStateStats subtask = createSubtaskStats(i);
+			stateSize += subtask.getStateSize();
+			alignmentBuffered += subtask.getAlignmentBuffered();
+
+			pending.reportSubtaskStats(task2.getJobVertexId(), subtask);
+
+			assertEquals(subtask, pending.getLatestAcknowledgedSubtaskStats());
+			assertEquals(subtask.getAckTimestamp(), pending.getLatestAckTimestamp());
+			assertEquals(subtask.getAckTimestamp() - triggerTimestamp, pending.getEndToEndDuration());
+			assertEquals(stateSize, pending.getStateSize());
+			assertEquals(alignmentBuffered, pending.getAlignmentBuffered());
+		}
+
+		assertEquals(task1.getNumberOfSubtasks(), task1.getNumberOfAcknowledgedSubtasks());
+		assertEquals(task2.getNumberOfSubtasks(), task2.getNumberOfAcknowledgedSubtasks());
+	}
+
+	/**
+	 * Test reporting of a completed checkpoint.
+	 */
+	@Test
+	public void testReportCompletedCheckpoint() throws Exception {
+		TaskStateStats task1 = new TaskStateStats(new JobVertexID(), 3);
+		TaskStateStats task2 = new TaskStateStats(new JobVertexID(), 4);
+
+		Map<JobVertexID, TaskStateStats> taskStats = new HashMap<>();
+		taskStats.put(task1.getJobVertexId(), task1);
+		taskStats.put(task2.getJobVertexId(), task2);
+
+		CheckpointStatsTracker.PendingCheckpointStatsCallback callback = mock(
+			CheckpointStatsTracker.PendingCheckpointStatsCallback.class);
+
+		PendingCheckpointStats pending = new PendingCheckpointStats(
+			0,
+			1,
+			CheckpointProperties.forStandardCheckpoint(),
+			task1.getNumberOfSubtasks() + task2.getNumberOfSubtasks(),
+			taskStats,
+			callback);
+
+		// Report subtasks
+		for (int i = 0; i < task1.getNumberOfSubtasks(); i++) {
+			pending.reportSubtaskStats(task1.getJobVertexId(), createSubtaskStats(i));
+		}
+
+		for (int i = 0; i < task2.getNumberOfSubtasks(); i++) {
+			pending.reportSubtaskStats(task2.getJobVertexId(), createSubtaskStats(i));
+		}
+
+		// Report completed
+		String externalPath = "asdjkasdjkasd";
+
+		CompletedCheckpointStats.DiscardCallback discardCallback = pending.reportCompletedCheckpoint(externalPath);
+
+		ArgumentCaptor<CompletedCheckpointStats> args = ArgumentCaptor.forClass(CompletedCheckpointStats.class);
+		verify(callback).reportCompletedCheckpoint(args.capture());
+
+		CompletedCheckpointStats completed = args.getValue();
+
+		assertNotNull(completed);
+		assertEquals(CheckpointStatsStatus.COMPLETED, completed.getStatus());
+		assertFalse(completed.isDiscarded());
+		assertEquals(discardCallback, completed.getDiscardCallback());
+		discardCallback.notifyDiscardedCheckpoint();
+		assertTrue(completed.isDiscarded());
+		assertEquals(externalPath, completed.getExternalPath());
+
+		assertEquals(pending.getCheckpointId(), completed.getCheckpointId());
+		assertEquals(pending.getNumberOfAcknowledgedSubtasks(), completed.getNumberOfAcknowledgedSubtasks());
+		assertEquals(pending.getLatestAcknowledgedSubtaskStats(), completed.getLatestAcknowledgedSubtaskStats());
+		assertEquals(pending.getLatestAckTimestamp(), completed.getLatestAckTimestamp());
+		assertEquals(pending.getEndToEndDuration(), completed.getEndToEndDuration());
+		assertEquals(pending.getStateSize(), completed.getStateSize());
+		assertEquals(pending.getAlignmentBuffered(), completed.getAlignmentBuffered());
+		assertEquals(task1, completed.getTaskStateStats(task1.getJobVertexId()));
+		assertEquals(task2, completed.getTaskStateStats(task2.getJobVertexId()));
+	}
+
+	/**
+	 * Test reporting of a failed checkpoint.
+	 */
+	@Test
+	public void testReportFailedCheckpoint() throws Exception {
+		TaskStateStats task1 = new TaskStateStats(new JobVertexID(), 3);
+		TaskStateStats task2 = new TaskStateStats(new JobVertexID(), 4);
+
+		Map<JobVertexID, TaskStateStats> taskStats = new HashMap<>();
+		taskStats.put(task1.getJobVertexId(), task1);
+		taskStats.put(task2.getJobVertexId(), task2);
+
+		CheckpointStatsTracker.PendingCheckpointStatsCallback callback = mock(
+			CheckpointStatsTracker.PendingCheckpointStatsCallback.class);
+
+		long triggerTimestamp = 123123;
+		PendingCheckpointStats pending = new PendingCheckpointStats(
+			0,
+			triggerTimestamp,
+			CheckpointProperties.forStandardCheckpoint(),
+			task1.getNumberOfSubtasks() + task2.getNumberOfSubtasks(),
+			taskStats,
+			callback);
+
+		// Report subtasks
+		for (int i = 0; i < task1.getNumberOfSubtasks(); i++) {
+			pending.reportSubtaskStats(task1.getJobVertexId(), createSubtaskStats(i));
+		}
+
+		for (int i = 0; i < task2.getNumberOfSubtasks(); i++) {
+			pending.reportSubtaskStats(task2.getJobVertexId(), createSubtaskStats(i));
+		}
+
+		// Report failed
+		Exception cause = new Exception("test exception");
+		long failureTimestamp = 112211137;
+		pending.reportFailedCheckpoint(failureTimestamp, cause);
+
+		ArgumentCaptor<FailedCheckpointStats> args = ArgumentCaptor.forClass(FailedCheckpointStats.class);
+		verify(callback).reportFailedCheckpoint(args.capture());
+
+		FailedCheckpointStats failed = args.getValue();
+
+		assertNotNull(failed);
+		assertEquals(CheckpointStatsStatus.FAILED, failed.getStatus());
+		assertEquals(failureTimestamp, failed.getFailureTimestamp());
+		assertEquals(cause.getMessage(), failed.getFailureMessage());
+
+		assertEquals(pending.getCheckpointId(), failed.getCheckpointId());
+		assertEquals(pending.getNumberOfAcknowledgedSubtasks(), failed.getNumberOfAcknowledgedSubtasks());
+		assertEquals(pending.getLatestAcknowledgedSubtaskStats(), failed.getLatestAcknowledgedSubtaskStats());
+		assertEquals(pending.getLatestAckTimestamp(), failed.getLatestAckTimestamp());
+		assertEquals(failureTimestamp - triggerTimestamp, failed.getEndToEndDuration());
+		assertEquals(pending.getStateSize(), failed.getStateSize());
+		assertEquals(pending.getAlignmentBuffered(), failed.getAlignmentBuffered());
+		assertEquals(task1, failed.getTaskStateStats(task1.getJobVertexId()));
+		assertEquals(task2, failed.getTaskStateStats(task2.getJobVertexId()));
+	}
+
+
+	// ------------------------------------------------------------------------
+
+	private SubtaskStateStats createSubtaskStats(int index) {
+		return new SubtaskStateStats(
+			index,
+			Integer.MAX_VALUE + (long) index,
+			Integer.MAX_VALUE + (long) index,
+			Integer.MAX_VALUE + (long) index,
+			Integer.MAX_VALUE + (long) index,
+			Integer.MAX_VALUE + (long) index,
+			Integer.MAX_VALUE + (long) index);
+	}
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/PendingCheckpointTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/PendingCheckpointTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/PendingCheckpointTest.java
index be7b033..71013bd 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/PendingCheckpointTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/PendingCheckpointTest.java
@@ -38,7 +38,10 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
@@ -89,8 +92,7 @@ public class PendingCheckpointTest {
 		CheckpointProperties persisted = new CheckpointProperties(false, true, false, false, false, false, false);
 
 		PendingCheckpoint pending = createPendingCheckpoint(persisted, tmp.getAbsolutePath());
-		pending.acknowledgeTask(ATTEMPT_ID, null);
-
+		pending.acknowledgeTask(ATTEMPT_ID, null, new CheckpointMetaData(pending.getCheckpointId(), pending.getCheckpointTimestamp()));
 		assertEquals(0, tmp.listFiles().length);
 		pending.finalizeCheckpoint();
 		assertEquals(1, tmp.listFiles().length);
@@ -98,7 +100,7 @@ public class PendingCheckpointTest {
 		// Ephemeral checkpoint
 		CheckpointProperties ephemeral = new CheckpointProperties(false, false, true, true, true, true, true);
 		pending = createPendingCheckpoint(ephemeral, null);
-		pending.acknowledgeTask(ATTEMPT_ID, null);
+		pending.acknowledgeTask(ATTEMPT_ID, null, new CheckpointMetaData(pending.getCheckpointId(), pending.getCheckpointTimestamp()));
 
 		assertEquals(1, tmp.listFiles().length);
 		pending.finalizeCheckpoint();
@@ -143,7 +145,7 @@ public class PendingCheckpointTest {
 		future = pending.getCompletionFuture();
 
 		assertFalse(future.isDone());
-		pending.acknowledgeTask(ATTEMPT_ID, null);
+		pending.acknowledgeTask(ATTEMPT_ID, null, new CheckpointMetaData(pending.getCheckpointId(), pending.getCheckpointTimestamp()));
 		pending.finalizeCheckpoint();
 		assertTrue(future.isDone());
 
@@ -206,6 +208,65 @@ public class PendingCheckpointTest {
 		verify(state, times(1)).discardState();
 	}
 
+	/**
+	 * Tests that the stats callbacks happen if the callback is registered.
+	 */
+	@Test
+	public void testPendingCheckpointStatsCallbacks() throws Exception {
+		{
+			// Complete sucessfully
+			PendingCheckpointStats callback = mock(PendingCheckpointStats.class);
+			PendingCheckpoint pending = createPendingCheckpoint(CheckpointProperties.forStandardCheckpoint(), null);
+			pending.setStatsCallback(callback);
+
+			pending.acknowledgeTask(ATTEMPT_ID, null, new CheckpointMetaData(pending.getCheckpointId(), pending.getCheckpointTimestamp()));
+			verify(callback, times(1)).reportSubtaskStats(any(JobVertexID.class), any(SubtaskStateStats.class));
+
+			pending.finalizeCheckpoint();
+			verify(callback, times(1)).reportCompletedCheckpoint(any(String.class));
+		}
+
+		{
+			// Fail subsumed
+			PendingCheckpointStats callback = mock(PendingCheckpointStats.class);
+			PendingCheckpoint pending = createPendingCheckpoint(CheckpointProperties.forStandardCheckpoint(), null);
+			pending.setStatsCallback(callback);
+
+			pending.abortSubsumed();
+			verify(callback, times(1)).reportFailedCheckpoint(anyLong(), any(Exception.class));
+		}
+
+		{
+			// Fail subsumed
+			PendingCheckpointStats callback = mock(PendingCheckpointStats.class);
+			PendingCheckpoint pending = createPendingCheckpoint(CheckpointProperties.forStandardCheckpoint(), null);
+			pending.setStatsCallback(callback);
+
+			pending.abortDeclined();
+			verify(callback, times(1)).reportFailedCheckpoint(anyLong(), any(Exception.class));
+		}
+
+		{
+			// Fail subsumed
+			PendingCheckpointStats callback = mock(PendingCheckpointStats.class);
+			PendingCheckpoint pending = createPendingCheckpoint(CheckpointProperties.forStandardCheckpoint(), null);
+			pending.setStatsCallback(callback);
+
+			pending.abortError(new Exception("Expected test error"));
+			verify(callback, times(1)).reportFailedCheckpoint(anyLong(), any(Exception.class));
+		}
+
+		{
+			// Fail subsumed
+			PendingCheckpointStats callback = mock(PendingCheckpointStats.class);
+			PendingCheckpoint pending = createPendingCheckpoint(CheckpointProperties.forStandardCheckpoint(), null);
+			pending.setStatsCallback(callback);
+
+			pending.abortExpired();
+			verify(callback, times(1)).reportFailedCheckpoint(anyLong(), any(Exception.class));
+		}
+	}
+
 	// ------------------------------------------------------------------------
 
 	private static PendingCheckpoint createPendingCheckpoint(CheckpointProperties props, String targetDirectory) {
@@ -215,7 +276,6 @@ public class PendingCheckpointTest {
 			0,
 			1,
 			ackTasks,
-			false,
 			props,
 			targetDirectory,
 			Executors.directExecutor());

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/RestoredCheckpointStatsTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/RestoredCheckpointStatsTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/RestoredCheckpointStatsTest.java
new file mode 100644
index 0000000..85b1516
--- /dev/null
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/RestoredCheckpointStatsTest.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.checkpoint;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class RestoredCheckpointStatsTest {
+
+	/**
+	 * Tests simple access to restore properties.
+	 */
+	@Test
+	public void testSimpleAccess() throws Exception {
+		long checkpointId = Integer.MAX_VALUE + 1L;
+		long triggerTimestamp = Integer.MAX_VALUE + 1L;
+		CheckpointProperties props = new CheckpointProperties(true, true, false, false, true, false, true);
+		long restoreTimestamp = Integer.MAX_VALUE + 1L;
+		String externalPath = "external-path";
+
+		RestoredCheckpointStats restored = new RestoredCheckpointStats(
+				checkpointId,
+				props,
+				restoreTimestamp,
+			externalPath);
+
+		assertEquals(checkpointId, restored.getCheckpointId());
+		assertEquals(props, restored.getProperties());
+		assertEquals(restoreTimestamp, restored.getRestoreTimestamp());
+		assertEquals(externalPath, restored.getExternalPath());
+	}
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/SubtaskStateStatsTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/SubtaskStateStatsTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/SubtaskStateStatsTest.java
new file mode 100644
index 0000000..75c40c5
--- /dev/null
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/SubtaskStateStatsTest.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.checkpoint;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class SubtaskStateStatsTest {
+
+	/**
+	 * Tests simple access via the getters.
+	 */
+	@Test
+	public void testSimpleAccess() throws Exception {
+		SubtaskStateStats stats = new SubtaskStateStats(
+			0,
+			Integer.MAX_VALUE + 1L,
+			Integer.MAX_VALUE + 2L,
+			Integer.MAX_VALUE + 3L,
+			Integer.MAX_VALUE + 4L,
+			Integer.MAX_VALUE + 5L,
+			Integer.MAX_VALUE + 6L);
+
+		assertEquals(0, stats.getSubtaskIndex());
+		assertEquals(Integer.MAX_VALUE + 1L, stats.getAckTimestamp());
+		assertEquals(Integer.MAX_VALUE + 2L, stats.getStateSize());
+		assertEquals(Integer.MAX_VALUE + 3L, stats.getSyncCheckpointDuration());
+		assertEquals(Integer.MAX_VALUE + 4L, stats.getAsyncCheckpointDuration());
+		assertEquals(Integer.MAX_VALUE + 5L, stats.getAlignmentBuffered());
+		assertEquals(Integer.MAX_VALUE + 6L, stats.getAlignmentDuration());
+
+		// Check duration helper
+		long ackTimestamp = stats.getAckTimestamp();
+		long triggerTimestamp = ackTimestamp - 10123;
+		assertEquals(10123, stats.getEndToEndDuration(triggerTimestamp));
+
+		// Trigger timestamp < ack timestamp
+		assertEquals(0, stats.getEndToEndDuration(ackTimestamp + 1));
+	}
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/TaskStateStatsTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/TaskStateStatsTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/TaskStateStatsTest.java
new file mode 100644
index 0000000..94edd9e
--- /dev/null
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/TaskStateStatsTest.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.checkpoint;
+
+import org.apache.flink.runtime.jobgraph.JobVertexID;
+import org.junit.Test;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class TaskStateStatsTest {
+
+	/**
+	 * Tests that subtask stats are correctly collected.
+	 */
+	@Test
+	public void testHandInSubtasks() throws Exception {
+		JobVertexID jobVertexId = new JobVertexID();
+		SubtaskStateStats[] subtasks = new SubtaskStateStats[7];
+
+		TaskStateStats taskStats = new TaskStateStats(jobVertexId, subtasks.length);
+
+		assertEquals(jobVertexId, taskStats.getJobVertexId());
+		assertEquals(subtasks.length, taskStats.getNumberOfSubtasks());
+		assertEquals(0, taskStats.getNumberOfAcknowledgedSubtasks());
+		assertNull(taskStats.getLatestAcknowledgedSubtaskStats());
+		assertEquals(-1, taskStats.getLatestAckTimestamp());
+		assertArrayEquals(subtasks, taskStats.getSubtaskStats());
+
+		ThreadLocalRandom rand = ThreadLocalRandom.current();
+
+		long stateSize = 0;
+		long alignmentBuffered = 0;
+
+		// Hand in some subtasks
+		for (int i = 0; i < subtasks.length; i++) {
+			subtasks[i] = new SubtaskStateStats(
+				i,
+				rand.nextInt(128),
+				rand.nextInt(128),
+				rand.nextInt(128),
+				rand.nextInt(128),
+				rand.nextInt(128),
+				rand.nextInt(128));
+
+			stateSize += subtasks[i].getStateSize();
+			alignmentBuffered += subtasks[i].getAlignmentBuffered();
+
+			assertTrue(taskStats.reportSubtaskStats(subtasks[i]));
+			assertEquals(i + 1, taskStats.getNumberOfAcknowledgedSubtasks());
+			assertEquals(subtasks[i], taskStats.getLatestAcknowledgedSubtaskStats());
+			assertEquals(subtasks[i].getAckTimestamp(), taskStats.getLatestAckTimestamp());
+			int duration = rand.nextInt(128);
+			assertEquals(duration, taskStats.getEndToEndDuration(subtasks[i].getAckTimestamp() - duration));
+			assertEquals(stateSize, taskStats.getStateSize());
+			assertEquals(alignmentBuffered, taskStats.getAlignmentBuffered());
+		}
+
+		assertFalse(taskStats.reportSubtaskStats(new SubtaskStateStats(0, 0, 0, 0, 0, 0, 0)));
+
+		// Test that all subtasks are taken into the account for the summary.
+		// The correctness of the actual results is checked in the test of the
+		// MinMaxAvgStats.
+		TaskStateStats.TaskStateStatsSummary summary = taskStats.getSummaryStats();
+		assertEquals(subtasks.length, summary.getStateSizeStats().getCount());
+		assertEquals(subtasks.length, summary.getAckTimestampStats().getCount());
+		assertEquals(subtasks.length, summary.getSyncCheckpointDurationStats().getCount());
+		assertEquals(subtasks.length, summary.getAsyncCheckpointDurationStats().getCount());
+		assertEquals(subtasks.length, summary.getAlignmentBufferedStats().getCount());
+		assertEquals(subtasks.length, summary.getAlignmentDurationStats().getCount());
+	}
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/savepoint/SavepointV1Test.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/savepoint/SavepointV1Test.java b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/savepoint/SavepointV1Test.java
index 1ae74ff..db5c35b 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/savepoint/SavepointV1Test.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/savepoint/SavepointV1Test.java
@@ -140,8 +140,7 @@ public class SavepointV1Test {
 						new ChainedStateHandle<>(operatorStatesBackend),
 						new ChainedStateHandle<>(operatorStatesStream),
 						keyedStateStream,
-						keyedStateBackend,
-						subtaskIdx * 10L));
+						keyedStateBackend));
 			}
 
 			taskStates.add(taskState);

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/test/java/org/apache/flink/runtime/executiongraph/ArchivedExecutionGraphTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/executiongraph/ArchivedExecutionGraphTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/executiongraph/ArchivedExecutionGraphTest.java
index 4cba4e3..6d7427a 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/executiongraph/ArchivedExecutionGraphTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/executiongraph/ArchivedExecutionGraphTest.java
@@ -17,6 +17,7 @@
  */
 package org.apache.flink.runtime.executiongraph;
 
+import org.apache.flink.api.common.ArchivedExecutionConfig;
 import org.apache.flink.api.common.ExecutionConfig;
 import org.apache.flink.api.common.ExecutionMode;
 import org.apache.flink.api.common.JobID;
@@ -25,28 +26,25 @@ import org.apache.flink.api.common.accumulators.LongCounter;
 import org.apache.flink.api.common.restartstrategy.RestartStrategies;
 import org.apache.flink.configuration.Configuration;
 import org.apache.flink.core.testutils.CommonTestUtils;
+import org.apache.flink.metrics.groups.UnregisteredMetricsGroup;
 import org.apache.flink.runtime.accumulators.StringifiedAccumulatorResult;
 import org.apache.flink.runtime.akka.AkkaUtils;
-import org.apache.flink.runtime.checkpoint.CompletedCheckpoint;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsSnapshot;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsTracker;
 import org.apache.flink.runtime.checkpoint.StandaloneCheckpointIDCounter;
 import org.apache.flink.runtime.checkpoint.StandaloneCompletedCheckpointStore;
-import org.apache.flink.runtime.checkpoint.stats.CheckpointStats;
-import org.apache.flink.runtime.checkpoint.stats.CheckpointStatsTracker;
-import org.apache.flink.runtime.checkpoint.stats.JobCheckpointStats;
-import org.apache.flink.runtime.checkpoint.stats.OperatorCheckpointStats;
 import org.apache.flink.runtime.execution.ExecutionState;
-import org.apache.flink.api.common.ArchivedExecutionConfig;
 import org.apache.flink.runtime.executiongraph.restart.NoRestartStrategy;
 import org.apache.flink.runtime.jobgraph.JobStatus;
 import org.apache.flink.runtime.jobgraph.JobVertex;
 import org.apache.flink.runtime.jobgraph.JobVertexID;
 import org.apache.flink.runtime.jobgraph.tasks.AbstractInvokable;
 import org.apache.flink.runtime.jobgraph.tasks.ExternalizedCheckpointSettings;
+import org.apache.flink.runtime.jobgraph.tasks.JobSnapshottingSettings;
 import org.apache.flink.runtime.testingUtils.TestingUtils;
 import org.apache.flink.util.SerializedValue;
 import org.junit.BeforeClass;
 import org.junit.Test;
-import scala.Option;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -61,6 +59,7 @@ import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
 
 public class ArchivedExecutionGraphTest {
 	private static JobVertexID v1ID = new JobVertexID();
@@ -107,6 +106,12 @@ public class ArchivedExecutionGraphTest {
 			new NoRestartStrategy());
 		runtimeGraph.attachJobGraph(vertices);
 
+		CheckpointStatsTracker statsTracker = new CheckpointStatsTracker(
+				0,
+				Collections.<ExecutionJobVertex>emptyList(),
+				mock(JobSnapshottingSettings.class),
+				new UnregisteredMetricsGroup());
+
 		runtimeGraph.enableSnapshotCheckpointing(
 			100,
 			100,
@@ -119,7 +124,7 @@ public class ArchivedExecutionGraphTest {
 			new StandaloneCheckpointIDCounter(),
 			new StandaloneCompletedCheckpointStore(1),
 			null,
-			new TestCheckpointStatsTracker());
+			statsTracker);
 
 		Map<String, Accumulator<?, ?>> userAccumulators = new HashMap<>();
 		userAccumulators.put("userAcc", new LongCounter(64));
@@ -165,19 +170,25 @@ public class ArchivedExecutionGraphTest {
 		assertEquals(runtimeGraph.isStoppable(), archivedGraph.isStoppable());
 
 		// -------------------------------------------------------------------------------------------------------------
-		// JobCheckpointStats
+		// CheckpointStats
 		// -------------------------------------------------------------------------------------------------------------
-		JobCheckpointStats runtimeStats = runtimeGraph.getCheckpointStatsTracker().getJobStats().get();
-		JobCheckpointStats archivedStats = archivedGraph.getCheckpointStatsTracker().getJobStats().get();
-
-		assertEquals(runtimeStats.getAverageDuration(), archivedStats.getAverageDuration());
-		assertEquals(runtimeStats.getMinDuration(), archivedStats.getMinDuration());
-		assertEquals(runtimeStats.getMaxDuration(), archivedStats.getMaxDuration());
-		assertEquals(runtimeStats.getAverageStateSize(), archivedStats.getAverageStateSize());
-		assertEquals(runtimeStats.getMinStateSize(), archivedStats.getMinStateSize());
-		assertEquals(runtimeStats.getMaxStateSize(), archivedStats.getMaxStateSize());
-		assertEquals(runtimeStats.getCount(), archivedStats.getCount());
-		assertEquals(runtimeStats.getRecentHistory(), archivedStats.getRecentHistory());
+		CheckpointStatsTracker runtimeStats = runtimeGraph.getCheckpointStatsTracker();
+		CheckpointStatsTracker archivedStats = archivedGraph.getCheckpointStatsTracker();
+
+		CheckpointStatsSnapshot runtimeSnapshot = runtimeStats.createSnapshot();
+		CheckpointStatsSnapshot archivedSnapshot = archivedStats.createSnapshot();
+
+		assertEquals(runtimeSnapshot.getSummaryStats().getEndToEndDurationStats().getAverage(), archivedSnapshot.getSummaryStats().getEndToEndDurationStats().getAverage());
+		assertEquals(runtimeSnapshot.getSummaryStats().getEndToEndDurationStats().getMinimum(), archivedSnapshot.getSummaryStats().getEndToEndDurationStats().getMinimum());
+		assertEquals(runtimeSnapshot.getSummaryStats().getEndToEndDurationStats().getMaximum(), archivedSnapshot.getSummaryStats().getEndToEndDurationStats().getMaximum());
+
+		assertEquals(runtimeSnapshot.getSummaryStats().getStateSizeStats().getAverage(), archivedSnapshot.getSummaryStats().getStateSizeStats().getAverage());
+		assertEquals(runtimeSnapshot.getSummaryStats().getStateSizeStats().getMinimum(), archivedSnapshot.getSummaryStats().getStateSizeStats().getMinimum());
+		assertEquals(runtimeSnapshot.getSummaryStats().getStateSizeStats().getMaximum(), archivedSnapshot.getSummaryStats().getStateSizeStats().getMaximum());
+
+		assertEquals(runtimeSnapshot.getCounts().getTotalNumberOfCheckpoints(), archivedSnapshot.getCounts().getTotalNumberOfCheckpoints());
+		assertEquals(runtimeSnapshot.getCounts().getNumberOfCompletedCheckpoints(), archivedSnapshot.getCounts().getNumberOfCompletedCheckpoints());
+		assertEquals(runtimeSnapshot.getCounts().getNumberOfInProgressCheckpoints(), archivedSnapshot.getCounts().getNumberOfInProgressCheckpoints());
 
 		// -------------------------------------------------------------------------------------------------------------
 		// ArchivedExecutionConfig
@@ -217,14 +228,6 @@ public class ArchivedExecutionGraphTest {
 		}
 
 		// -------------------------------------------------------------------------------------------------------------
-		// OperatorCheckpointStats
-		// -------------------------------------------------------------------------------------------------------------
-		CheckpointStatsTracker runtimeTracker = runtimeGraph.getCheckpointStatsTracker();
-		CheckpointStatsTracker archivedTracker = archivedGraph.getCheckpointStatsTracker();
-		compareOperatorCheckpointStats(runtimeTracker.getOperatorStats(v1ID).get(), archivedTracker.getOperatorStats(v1ID).get());
-		compareOperatorCheckpointStats(runtimeTracker.getOperatorStats(v2ID).get(), archivedTracker.getOperatorStats(v2ID).get());
-
-		// -------------------------------------------------------------------------------------------------------------
 		// ExecutionVertices
 		// -------------------------------------------------------------------------------------------------------------
 		Iterator<? extends AccessExecutionVertex> runtimeExecutionVertices = runtimeGraph.getAllExecutionVertices().iterator();
@@ -243,8 +246,6 @@ public class ArchivedExecutionGraphTest {
 		assertEquals(runtimeJobVertex.getJobVertexId(), archivedJobVertex.getJobVertexId());
 		assertEquals(runtimeJobVertex.getAggregateState(), archivedJobVertex.getAggregateState());
 
-		compareOperatorCheckpointStats(runtimeJobVertex.getCheckpointStats().get(), archivedJobVertex.getCheckpointStats().get());
-
 		compareStringifiedAccumulators(runtimeJobVertex.getAggregatedUserAccumulatorsStringified(), archivedJobVertex.getAggregatedUserAccumulatorsStringified());
 
 		AccessExecutionVertex[] runtimeExecutionVertices = runtimeJobVertex.getTaskVertices();
@@ -315,96 +316,11 @@ public class ArchivedExecutionGraphTest {
 		}
 	}
 
-	private static void compareOperatorCheckpointStats(OperatorCheckpointStats runtimeStats, OperatorCheckpointStats archivedStats) {
-		assertEquals(runtimeStats.getNumberOfSubTasks(), archivedStats.getNumberOfSubTasks());
-		assertEquals(runtimeStats.getCheckpointId(), archivedStats.getCheckpointId());
-		assertEquals(runtimeStats.getDuration(), archivedStats.getDuration());
-		assertEquals(runtimeStats.getStateSize(), archivedStats.getStateSize());
-		assertEquals(runtimeStats.getTriggerTimestamp(), archivedStats.getTriggerTimestamp());
-		assertEquals(runtimeStats.getSubTaskDuration(0), archivedStats.getSubTaskDuration(0));
-		assertEquals(runtimeStats.getSubTaskStateSize(0), archivedStats.getSubTaskStateSize(0));
-	}
-
 	private static void verifySerializability(ArchivedExecutionGraph graph) throws IOException, ClassNotFoundException {
 		ArchivedExecutionGraph copy = CommonTestUtils.createCopySerializable(graph);
 		compareExecutionGraph(graph, copy);
 	}
 
-
-	private static class TestCheckpointStatsTracker implements CheckpointStatsTracker {
-
-		@Override
-		public void onCompletedCheckpoint(CompletedCheckpoint checkpoint) {
-		}
-
-		@Override
-		public Option<JobCheckpointStats> getJobStats() {
-			return Option.<JobCheckpointStats>apply(new TestJobCheckpointStats());
-		}
-
-		@Override
-		public Option<OperatorCheckpointStats> getOperatorStats(JobVertexID operatorId) {
-			return Option.<OperatorCheckpointStats>apply(new TestOperatorCheckpointStats(operatorId.getUpperPart()));
-		}
-	}
-
-	private static class TestJobCheckpointStats implements JobCheckpointStats {
-		private static final long serialVersionUID = -2630234917947292836L;
-
-		@Override
-		public List<CheckpointStats> getRecentHistory() {
-			return Collections.emptyList();
-		}
-
-		@Override
-		public long getCount() {
-			return 1;
-		}
-
-		@Override
-		public String getExternalPath() {
-			return null;
-		}
-
-		@Override
-		public long getMinDuration() {
-			return 2;
-		}
-
-		@Override
-		public long getMaxDuration() {
-			return 4;
-		}
-
-		@Override
-		public long getAverageDuration() {
-			return 3;
-		}
-
-		@Override
-		public long getMinStateSize() {
-			return 5;
-		}
-
-		@Override
-		public long getMaxStateSize() {
-			return 7;
-		}
-
-		@Override
-		public long getAverageStateSize() {
-			return 6;
-		}
-	}
-
-	private static class TestOperatorCheckpointStats extends OperatorCheckpointStats {
-		private static final long serialVersionUID = -2798640928349528644L;
-
-		public TestOperatorCheckpointStats(long offset) {
-			super(1 + offset, 2 + offset, 3 + offset, 4 + offset, new long[][]{new long[]{5 + offset, 6 + offset}});
-		}
-	}
-
 	private static class TestJobParameters extends ExecutionConfig.GlobalJobParameters {
 		private static final long serialVersionUID = -8118611781035212808L;
 		private Map<String, String> parameters;

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/test/java/org/apache/flink/runtime/jobmanager/JobManagerHARecoveryTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/jobmanager/JobManagerHARecoveryTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/jobmanager/JobManagerHARecoveryTest.java
index 36412f5..398505f 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/jobmanager/JobManagerHARecoveryTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/jobmanager/JobManagerHARecoveryTest.java
@@ -228,7 +228,8 @@ public class JobManagerHARecoveryTest {
 					10 * 60 * 1000,
 					0,
 					1,
-					ExternalizedCheckpointSettings.none()));
+					ExternalizedCheckpointSettings.none(),
+					true));
 
 			BlockingStatefulInvokable.initializeStaticHelpers(slots);
 
@@ -617,7 +618,7 @@ public class JobManagerHARecoveryTest {
 			ChainedStateHandle<StreamStateHandle> chainedStateHandle =
 					new ChainedStateHandle<StreamStateHandle>(Collections.singletonList(byteStreamStateHandle));
 			SubtaskState checkpointStateHandles =
-					new SubtaskState(chainedStateHandle, null, null, null, null, 0L);
+					new SubtaskState(chainedStateHandle, null, null, null, null);
 
 			getEnvironment().acknowledgeCheckpoint(
 					new CheckpointMetaData(checkpointMetaData.getCheckpointId(), -1, 0L, 0L, 0L, 0L),

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/test/java/org/apache/flink/runtime/jobmanager/JobManagerTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/jobmanager/JobManagerTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/jobmanager/JobManagerTest.java
index 6d8c70b..1fea6f6 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/jobmanager/JobManagerTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/jobmanager/JobManagerTest.java
@@ -824,7 +824,8 @@ public class JobManagerTest {
 					3600000,
 					0,
 					Integer.MAX_VALUE,
-					ExternalizedCheckpointSettings.none());
+					ExternalizedCheckpointSettings.none(),
+					true);
 
 			jobGraph.setSnapshotSettings(snapshottingSettings);
 
@@ -952,7 +953,8 @@ public class JobManagerTest {
 					360000,
 					0,
 					Integer.MAX_VALUE,
-					ExternalizedCheckpointSettings.none());
+					ExternalizedCheckpointSettings.none(),
+					true);
 
 			jobGraph.setSnapshotSettings(snapshottingSettings);
 
@@ -1053,7 +1055,8 @@ public class JobManagerTest {
 					360000,
 					0,
 					Integer.MAX_VALUE,
-					ExternalizedCheckpointSettings.none());
+					ExternalizedCheckpointSettings.none(),
+					true);
 
 			jobGraph.setSnapshotSettings(snapshottingSettings);
 
@@ -1098,7 +1101,8 @@ public class JobManagerTest {
 					360000,
 					0,
 					Integer.MAX_VALUE,
-					ExternalizedCheckpointSettings.none());
+					ExternalizedCheckpointSettings.none(),
+					true);
 
 			newJobGraph.setSnapshotSettings(newSnapshottingSettings);
 


[08/11] flink git commit: [FLINK-4410] [runtime] Rework checkpoint stats tracking

Posted by uc...@apache.org.
http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/test/java/org/apache/flink/runtime/jobmanager/JobSubmitTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/jobmanager/JobSubmitTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/jobmanager/JobSubmitTest.java
index 1b8f0c3..a78fda7 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/jobmanager/JobSubmitTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/jobmanager/JobSubmitTest.java
@@ -19,11 +19,6 @@
 package org.apache.flink.runtime.jobmanager;
 
 import akka.actor.ActorSystem;
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
 import org.apache.flink.configuration.ConfigConstants;
 import org.apache.flink.configuration.Configuration;
 import org.apache.flink.runtime.akka.AkkaUtils;
@@ -50,8 +45,14 @@ import scala.concurrent.Await;
 import scala.concurrent.Future;
 import scala.concurrent.duration.FiniteDuration;
 
-import static org.junit.Assert.assertTrue;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 /**
@@ -224,7 +225,7 @@ public class JobSubmitTest {
 
 		JobGraph jg = new JobGraph("test job", jobVertex);
 		jg.setSnapshotSettings(new JobSnapshottingSettings(vertexIdList, vertexIdList, vertexIdList,
-			5000, 5000, 0L, 10, ExternalizedCheckpointSettings.none()));
+			5000, 5000, 0L, 10, ExternalizedCheckpointSettings.none(), true));
 		return jg;
 	}
 }

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/test/java/org/apache/flink/runtime/messages/CheckpointMessagesTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/messages/CheckpointMessagesTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/messages/CheckpointMessagesTest.java
index 3521630..9aa35e0 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/messages/CheckpointMessagesTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/messages/CheckpointMessagesTest.java
@@ -73,8 +73,7 @@ public class CheckpointMessagesTest {
 							CheckpointCoordinatorTest.generateChainedPartitionableStateHandle(new JobVertexID(), 0, 2, 8, false),
 							null,
 							CheckpointCoordinatorTest.generateKeyGroupState(keyGroupRange, Collections.singletonList(new MyHandle())),
-							null,
-							0L);
+							null);
 
 			AcknowledgeCheckpoint withState = new AcknowledgeCheckpoint(
 					new JobID(),

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/test/scala/org/apache/flink/runtime/jobmanager/JobManagerITCase.scala
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/scala/org/apache/flink/runtime/jobmanager/JobManagerITCase.scala b/flink-runtime/src/test/scala/org/apache/flink/runtime/jobmanager/JobManagerITCase.scala
index 0569297..fa5db64 100644
--- a/flink-runtime/src/test/scala/org/apache/flink/runtime/jobmanager/JobManagerITCase.scala
+++ b/flink-runtime/src/test/scala/org/apache/flink/runtime/jobmanager/JobManagerITCase.scala
@@ -816,7 +816,12 @@ class JobManagerITCase(_system: ActorSystem)
             java.util.Collections.emptyList(),
             java.util.Collections.emptyList(),
             java.util.Collections.emptyList(),
-            60000, 60000, 60000, 1, ExternalizedCheckpointSettings.none))
+            60000,
+            60000,
+            60000,
+            1,
+            ExternalizedCheckpointSettings.none,
+            true))
 
           // Submit job...
           jobManager.tell(SubmitJob(jobGraph, ListeningBehaviour.DETACHED), testActor)
@@ -870,7 +875,12 @@ class JobManagerITCase(_system: ActorSystem)
             java.util.Collections.emptyList(),
             java.util.Collections.emptyList(),
             java.util.Collections.emptyList(),
-            60000, 60000, 60000, 1, ExternalizedCheckpointSettings.none))
+            60000,
+            60000,
+            60000,
+            1,
+            ExternalizedCheckpointSettings.none,
+            true))
 
           // Submit job...
           jobManager.tell(SubmitJob(jobGraph, ListeningBehaviour.DETACHED), testActor)
@@ -932,7 +942,12 @@ class JobManagerITCase(_system: ActorSystem)
             java.util.Collections.emptyList(),
             java.util.Collections.emptyList(),
             java.util.Collections.emptyList(),
-            60000, 60000, 60000, 1, ExternalizedCheckpointSettings.none))
+            60000,
+            60000,
+            60000,
+            1,
+            ExternalizedCheckpointSettings.none,
+            true))
 
           // Submit job...
           jobManager.tell(SubmitJob(jobGraph, ListeningBehaviour.DETACHED), testActor)

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-streaming-java/src/main/java/org/apache/flink/streaming/api/graph/StreamingJobGraphGenerator.java
----------------------------------------------------------------------
diff --git a/flink-streaming-java/src/main/java/org/apache/flink/streaming/api/graph/StreamingJobGraphGenerator.java b/flink-streaming-java/src/main/java/org/apache/flink/streaming/api/graph/StreamingJobGraphGenerator.java
index da69b49..0cb7d9a 100644
--- a/flink-streaming-java/src/main/java/org/apache/flink/streaming/api/graph/StreamingJobGraphGenerator.java
+++ b/flink-streaming-java/src/main/java/org/apache/flink/streaming/api/graph/StreamingJobGraphGenerator.java
@@ -524,11 +524,26 @@ public class StreamingJobGraphGenerator {
 			externalizedCheckpointSettings = ExternalizedCheckpointSettings.none();
 		}
 
+		CheckpointingMode mode = cfg.getCheckpointingMode();
+
+		boolean isExactlyOnce;
+		if (mode == CheckpointingMode.EXACTLY_ONCE) {
+			isExactlyOnce = true;
+		} else if (mode == CheckpointingMode.AT_LEAST_ONCE) {
+			isExactlyOnce = false;
+		} else {
+			throw new IllegalStateException("Unexpected checkpointing mode. " +
+				"Did not expect there to be another checkpointing mode besides " +
+				"exactly-once or at-least-once.");
+		}
+
 		JobSnapshottingSettings settings = new JobSnapshottingSettings(
 				triggerVertices, ackVertices, commitVertices, interval,
 				cfg.getCheckpointTimeout(), cfg.getMinPauseBetweenCheckpoints(),
 				cfg.getMaxConcurrentCheckpoints(),
-				externalizedCheckpointSettings);
+				externalizedCheckpointSettings,
+				isExactlyOnce);
+
 		jobGraph.setSnapshotSettings(settings);
 	}
 }


[03/11] flink git commit: [FLINK-4410] [runtime-web] Rebuild JS/HTML files

Posted by uc...@apache.org.
[FLINK-4410] [runtime-web] Rebuild JS/HTML files


Project: http://git-wip-us.apache.org/repos/asf/flink/repo
Commit: http://git-wip-us.apache.org/repos/asf/flink/commit/88c7de49
Tree: http://git-wip-us.apache.org/repos/asf/flink/tree/88c7de49
Diff: http://git-wip-us.apache.org/repos/asf/flink/diff/88c7de49

Branch: refs/heads/release-1.2
Commit: 88c7de4975683c1ba26890747a5a33d9522a996a
Parents: 3f2e996
Author: Ufuk Celebi <uc...@apache.org>
Authored: Fri Dec 23 20:47:02 2016 +0100
Committer: Ufuk Celebi <uc...@apache.org>
Committed: Tue Jan 10 09:47:55 2017 +0100

----------------------------------------------------------------------
 .../web-dashboard/web/css/index.css             |  12 +
 flink-runtime-web/web-dashboard/web/js/index.js | 271 ++++++++++++-------
 .../jobs/job.plan.node-list.checkpoints.html    |  39 +--
 .../jobs/job.plan.node.checkpoint-history.html  |  57 ++++
 .../jobs/job.plan.node.checkpoints.config.html  |  59 ++++
 .../jobs/job.plan.node.checkpoints.counts.html  |  51 ++++
 .../jobs/job.plan.node.checkpoints.details.html | 171 ++++++++++++
 .../jobs/job.plan.node.checkpoints.history.html |  65 +++++
 .../jobs/job.plan.node.checkpoints.html         |  22 ++
 .../jobs/job.plan.node.checkpoints.job.html     | 110 +++++---
 .../job.plan.node.checkpoints.overview.html     |  49 ++++
 .../job.plan.node.checkpoints.statistics.html   |  56 ++++
 .../jobs/job.plan.node.checkpoints.summary.html |  53 ++++
 13 files changed, 850 insertions(+), 165 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/flink/blob/88c7de49/flink-runtime-web/web-dashboard/web/css/index.css
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/web/css/index.css b/flink-runtime-web/web-dashboard/web/css/index.css
index 14e58ec..2730c52 100644
--- a/flink-runtime-web/web-dashboard/web/css/index.css
+++ b/flink-runtime-web/web-dashboard/web/css/index.css
@@ -569,6 +569,18 @@ livechart {
   font-family: inherit;
   margin-top: -2px;
 }
+.checkpoints-view {
+  padding-top: 1em;
+}
+.subtask-details .blank {
+  height: 2em;
+}
+.checkpoint-overview td span {
+  padding-left: 2em;
+}
+.checkpoint-overview a {
+  color: #000;
+}
 svg.graph {
   overflow: hidden;
   height: 100%;


[04/11] flink git commit: [FLINK-4410] [runtime-web] Add detailed checkpoint stats handlers

Posted by uc...@apache.org.
http://git-wip-us.apache.org/repos/asf/flink/blob/1fd2d2e1/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsSubtaskDetailsHandlerTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsSubtaskDetailsHandlerTest.java b/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsSubtaskDetailsHandlerTest.java
new file mode 100644
index 0000000..70212fc
--- /dev/null
+++ b/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/handlers/checkpoints/CheckpointStatsSubtaskDetailsHandlerTest.java
@@ -0,0 +1,342 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.webmonitor.handlers.checkpoints;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.flink.runtime.checkpoint.AbstractCheckpointStats;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsHistory;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsSnapshot;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsStatus;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsTracker;
+import org.apache.flink.runtime.checkpoint.MinMaxAvgStats;
+import org.apache.flink.runtime.checkpoint.PendingCheckpointStats;
+import org.apache.flink.runtime.checkpoint.SubtaskStateStats;
+import org.apache.flink.runtime.checkpoint.TaskStateStats;
+import org.apache.flink.runtime.executiongraph.AccessExecutionGraph;
+import org.apache.flink.runtime.jobgraph.JobVertexID;
+import org.apache.flink.runtime.webmonitor.ExecutionGraphHolder;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ThreadLocalRandom;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class CheckpointStatsSubtaskDetailsHandlerTest {
+
+	/**
+	 * Tests a subtask details request.
+	 */
+	@Test
+	public void testSubtaskRequest() throws Exception {
+		PendingCheckpointStats checkpoint = mock(PendingCheckpointStats.class);
+		when(checkpoint.getCheckpointId()).thenReturn(1992139L);
+		when(checkpoint.getStatus()).thenReturn(CheckpointStatsStatus.IN_PROGRESS);
+		when(checkpoint.getTriggerTimestamp()).thenReturn(0L); // ack timestamp = duration
+
+		TaskStateStats task = createTaskStateStats(1237);
+		when(checkpoint.getTaskStateStats(any(JobVertexID.class))).thenReturn(task);
+
+		JsonNode rootNode = triggerRequest(checkpoint);
+		assertEquals(checkpoint.getCheckpointId(), rootNode.get("id").asLong());
+		assertEquals(checkpoint.getStatus().toString(), rootNode.get("status").asText());
+
+		verifyTaskNode(rootNode, task, checkpoint.getTriggerTimestamp());
+	}
+
+	/**
+	 * Tests a subtask details request.
+	 */
+	@Test
+	public void testSubtaskRequestNoSummary() throws Exception {
+		PendingCheckpointStats checkpoint = mock(PendingCheckpointStats.class);
+		when(checkpoint.getCheckpointId()).thenReturn(1992139L);
+		when(checkpoint.getStatus()).thenReturn(CheckpointStatsStatus.IN_PROGRESS);
+		when(checkpoint.getTriggerTimestamp()).thenReturn(0L); // ack timestamp = duration
+
+		TaskStateStats task = createTaskStateStats(0); // no acknowledged
+		when(checkpoint.getTaskStateStats(any(JobVertexID.class))).thenReturn(task);
+
+		JsonNode rootNode = triggerRequest(checkpoint);
+		assertNull(rootNode.get("summary"));
+	}
+
+	/**
+	 * Tests request with illegal checkpoint ID param.
+	 */
+	@Test
+	public void testIllegalCheckpointId() throws Exception {
+		AccessExecutionGraph graph = mock(AccessExecutionGraph.class);
+		CheckpointStatsDetailsSubtasksHandler handler = new CheckpointStatsDetailsSubtasksHandler(mock(ExecutionGraphHolder.class), new CheckpointStatsCache(0));
+		Map<String, String> params = new HashMap<>();
+		params.put("checkpointid", "illegal checkpoint");
+		String json = handler.handleRequest(graph, params);
+
+		assertEquals("{}", json);
+	}
+
+	/**
+	 * Tests request with missing checkpoint ID param.
+	 */
+	@Test
+	public void testNoCheckpointIdParam() throws Exception {
+		AccessExecutionGraph graph = mock(AccessExecutionGraph.class);
+		CheckpointStatsDetailsSubtasksHandler handler = new CheckpointStatsDetailsSubtasksHandler(mock(ExecutionGraphHolder.class), new CheckpointStatsCache(0));
+		String json = handler.handleRequest(graph, Collections.<String, String>emptyMap());
+
+		assertEquals("{}", json);
+	}
+
+	/**
+	 * Test lookup of not existing checkpoint in history.
+	 */
+	@Test
+	public void testCheckpointNotFound() throws Exception {
+		CheckpointStatsHistory history = mock(CheckpointStatsHistory.class);
+		when(history.getCheckpointById(anyLong())).thenReturn(null); // not found
+
+		CheckpointStatsSnapshot snapshot = mock(CheckpointStatsSnapshot.class);
+		when(snapshot.getHistory()).thenReturn(history);
+
+		AccessExecutionGraph graph = mock(AccessExecutionGraph.class);
+		CheckpointStatsTracker tracker = mock(CheckpointStatsTracker.class);
+		when(graph.getCheckpointStatsTracker()).thenReturn(tracker);
+		when(tracker.createSnapshot()).thenReturn(snapshot);
+
+		CheckpointStatsDetailsSubtasksHandler handler = new CheckpointStatsDetailsSubtasksHandler(mock(ExecutionGraphHolder.class), new CheckpointStatsCache(0));
+		Map<String, String> params = new HashMap<>();
+		params.put("checkpointid", "123");
+		params.put("vertexid", new JobVertexID().toString());
+		String json = handler.handleRequest(graph, params);
+
+		assertEquals("{}", json);
+		verify(history, times(1)).getCheckpointById(anyLong());
+	}
+
+	/**
+	 * Tests request with illegal job vertex ID param.
+	 */
+	@Test
+	public void testIllegalJobVertexIdParam() throws Exception {
+		AccessExecutionGraph graph = mock(AccessExecutionGraph.class);
+		CheckpointStatsDetailsSubtasksHandler handler = new CheckpointStatsDetailsSubtasksHandler(mock(ExecutionGraphHolder.class), new CheckpointStatsCache(0));
+		Map<String, String> params = new HashMap<>();
+		params.put("checkpointid", "1");
+		params.put("vertexid", "illegal vertex id");
+		String json = handler.handleRequest(graph, params);
+
+		assertEquals("{}", json);
+	}
+
+	/**
+	 * Tests request with missing job vertex ID param.
+	 */
+	@Test
+	public void testNoJobVertexIdParam() throws Exception {
+		AccessExecutionGraph graph = mock(AccessExecutionGraph.class);
+		CheckpointStatsDetailsSubtasksHandler handler = new CheckpointStatsDetailsSubtasksHandler(mock(ExecutionGraphHolder.class), new CheckpointStatsCache(0));
+		Map<String, String> params = new HashMap<>();
+		params.put("checkpointid", "1");
+		String json = handler.handleRequest(graph, params);
+
+		assertEquals("{}", json);
+	}
+
+	/**
+	 * Test lookup of not existing job vertex ID in checkpoint.
+	 */
+	@Test
+	public void testJobVertexNotFound() throws Exception {
+		PendingCheckpointStats inProgress = mock(PendingCheckpointStats.class);
+		when(inProgress.getTaskStateStats(any(JobVertexID.class))).thenReturn(null); // not found
+		CheckpointStatsHistory history = mock(CheckpointStatsHistory.class);
+		when(history.getCheckpointById(anyLong())).thenReturn(inProgress);
+
+		CheckpointStatsSnapshot snapshot = mock(CheckpointStatsSnapshot.class);
+		when(snapshot.getHistory()).thenReturn(history);
+
+		AccessExecutionGraph graph = mock(AccessExecutionGraph.class);
+		CheckpointStatsTracker tracker = mock(CheckpointStatsTracker.class);
+		when(graph.getCheckpointStatsTracker()).thenReturn(tracker);
+		when(tracker.createSnapshot()).thenReturn(snapshot);
+
+		CheckpointStatsDetailsSubtasksHandler handler = new CheckpointStatsDetailsSubtasksHandler(mock(ExecutionGraphHolder.class), new CheckpointStatsCache(0));
+		Map<String, String> params = new HashMap<>();
+		params.put("checkpointid", "123");
+		params.put("vertexid", new JobVertexID().toString());
+		String json = handler.handleRequest(graph, params);
+
+		assertEquals("{}", json);
+		verify(inProgress, times(1)).getTaskStateStats(any(JobVertexID.class));
+	}
+
+	// ------------------------------------------------------------------------
+
+	private static JsonNode triggerRequest(AbstractCheckpointStats checkpoint) throws Exception {
+		CheckpointStatsHistory history = mock(CheckpointStatsHistory.class);
+		when(history.getCheckpointById(anyLong())).thenReturn(checkpoint);
+		CheckpointStatsSnapshot snapshot = mock(CheckpointStatsSnapshot.class);
+		when(snapshot.getHistory()).thenReturn(history);
+
+		AccessExecutionGraph graph = mock(AccessExecutionGraph.class);
+		CheckpointStatsTracker tracker = mock(CheckpointStatsTracker.class);
+		when(graph.getCheckpointStatsTracker()).thenReturn(tracker);
+		when(tracker.createSnapshot()).thenReturn(snapshot);
+
+		CheckpointStatsDetailsSubtasksHandler handler = new CheckpointStatsDetailsSubtasksHandler(mock(ExecutionGraphHolder.class), new CheckpointStatsCache(0));
+		Map<String, String> params = new HashMap<>();
+		params.put("checkpointid", "123");
+		params.put("vertexid", new JobVertexID().toString());
+		String json = handler.handleRequest(graph, params);
+
+		ObjectMapper mapper = new ObjectMapper();
+		return mapper.readTree(json);
+	}
+
+	private static TaskStateStats createTaskStateStats(int numAcknowledged) {
+		ThreadLocalRandom rand = ThreadLocalRandom.current();
+
+		TaskStateStats task = mock(TaskStateStats.class);
+		when(task.getJobVertexId()).thenReturn(new JobVertexID());
+		when(task.getLatestAckTimestamp()).thenReturn(rand.nextLong(1024) + 1);
+		when(task.getStateSize()).thenReturn(rand.nextLong(1024) + 1);
+		when(task.getEndToEndDuration(anyLong())).thenReturn(rand.nextLong(1024) + 1);
+		when(task.getAlignmentBuffered()).thenReturn(rand.nextLong(1024) + 1);
+		when(task.getNumberOfSubtasks()).thenReturn(rand.nextInt(1024) + 1);
+		when(task.getNumberOfAcknowledgedSubtasks()).thenReturn(numAcknowledged);
+
+		TaskStateStats.TaskStateStatsSummary summary = mock(TaskStateStats.TaskStateStatsSummary.class);
+
+		doReturn(createMinMaxAvgStats(rand)).when(summary).getStateSizeStats();
+		doReturn(createMinMaxAvgStats(rand)).when(summary).getAckTimestampStats();
+		doReturn(createMinMaxAvgStats(rand)).when(summary).getAlignmentBufferedStats();
+		doReturn(createMinMaxAvgStats(rand)).when(summary).getAlignmentDurationStats();
+		doReturn(createMinMaxAvgStats(rand)).when(summary).getSyncCheckpointDurationStats();
+		doReturn(createMinMaxAvgStats(rand)).when(summary).getAsyncCheckpointDurationStats();
+
+		when(task.getSummaryStats()).thenReturn(summary);
+
+		SubtaskStateStats[] subtasks = new SubtaskStateStats[3];
+		subtasks[0] = createSubtaskStats(0, rand);
+		subtasks[1] = createSubtaskStats(1, rand);
+		subtasks[2] = null;
+
+		when(task.getSubtaskStats()).thenReturn(subtasks);
+
+		return task;
+	}
+
+	private static void verifyTaskNode(JsonNode taskNode, TaskStateStats task, long triggerTimestamp) {
+		long duration = ThreadLocalRandom.current().nextInt(128);
+
+		assertEquals(task.getLatestAckTimestamp(), taskNode.get("latest_ack_timestamp").asLong());
+		assertEquals(task.getStateSize(), taskNode.get("state_size").asLong());
+		assertEquals(task.getEndToEndDuration(task.getLatestAckTimestamp() - duration), taskNode.get("end_to_end_duration").asLong());
+		assertEquals(task.getAlignmentBuffered(), taskNode.get("alignment_buffered").asLong());
+		assertEquals(task.getNumberOfSubtasks(), taskNode.get("num_subtasks").asInt());
+		assertEquals(task.getNumberOfAcknowledgedSubtasks(), taskNode.get("num_acknowledged_subtasks").asInt());
+
+		TaskStateStats.TaskStateStatsSummary summary = task.getSummaryStats();
+		verifyMinMaxAvgStats(summary.getStateSizeStats(), taskNode.get("summary").get("state_size"));
+		verifyMinMaxAvgStats(summary.getSyncCheckpointDurationStats(), taskNode.get("summary").get("checkpoint_duration").get("sync"));
+		verifyMinMaxAvgStats(summary.getAsyncCheckpointDurationStats(), taskNode.get("summary").get("checkpoint_duration").get("async"));
+		verifyMinMaxAvgStats(summary.getAlignmentBufferedStats(), taskNode.get("summary").get("alignment").get("buffered"));
+		verifyMinMaxAvgStats(summary.getAlignmentDurationStats(), taskNode.get("summary").get("alignment").get("duration"));
+
+		JsonNode endToEndDurationNode = taskNode.get("summary").get("end_to_end_duration");
+		assertEquals(summary.getAckTimestampStats().getMinimum() - triggerTimestamp, endToEndDurationNode.get("min").asLong());
+		assertEquals(summary.getAckTimestampStats().getMaximum() - triggerTimestamp, endToEndDurationNode.get("max").asLong());
+		assertEquals((long) summary.getAckTimestampStats().getAverage() - triggerTimestamp, endToEndDurationNode.get("avg").asLong());
+
+		SubtaskStateStats[] subtasks = task.getSubtaskStats();
+		Iterator<JsonNode> it = taskNode.get("subtasks").iterator();
+
+		assertTrue(it.hasNext());
+		verifySubtaskStats(it.next(), 0, subtasks[0]);
+
+		assertTrue(it.hasNext());
+		verifySubtaskStats(it.next(), 1, subtasks[1]);
+
+		assertTrue(it.hasNext());
+		verifySubtaskStats(it.next(), 2, subtasks[2]);
+
+		assertFalse(it.hasNext());
+	}
+
+	private static SubtaskStateStats createSubtaskStats(int index, ThreadLocalRandom rand) {
+		SubtaskStateStats subtask = mock(SubtaskStateStats.class);
+		when(subtask.getSubtaskIndex()).thenReturn(index);
+		when(subtask.getAckTimestamp()).thenReturn(rand.nextLong(1024));
+		when(subtask.getAlignmentBuffered()).thenReturn(rand.nextLong(1024));
+		when(subtask.getAlignmentDuration()).thenReturn(rand.nextLong(1024));
+		when(subtask.getSyncCheckpointDuration()).thenReturn(rand.nextLong(1024));
+		when(subtask.getAsyncCheckpointDuration()).thenReturn(rand.nextLong(1024));
+		when(subtask.getAckTimestamp()).thenReturn(rand.nextLong(1024));
+		when(subtask.getStateSize()).thenReturn(rand.nextLong(1024));
+		when(subtask.getEndToEndDuration(anyLong())).thenReturn(rand.nextLong(1024));
+		return subtask;
+	}
+
+	private static void verifySubtaskStats(JsonNode subtaskNode, int index, SubtaskStateStats subtask) {
+		if (subtask == null) {
+			assertEquals(index, subtaskNode.get("index").asInt());
+			assertEquals("pending", subtaskNode.get("status").asText());
+		} else {
+			assertEquals(subtask.getSubtaskIndex(), subtaskNode.get("index").asInt());
+			assertEquals("completed", subtaskNode.get("status").asText());
+			assertEquals(subtask.getAckTimestamp(), subtaskNode.get("ack_timestamp").asLong());
+			assertEquals(subtask.getEndToEndDuration(0), subtaskNode.get("end_to_end_duration").asLong());
+			assertEquals(subtask.getStateSize(), subtaskNode.get("state_size").asLong());
+			assertEquals(subtask.getSyncCheckpointDuration(), subtaskNode.get("checkpoint").get("sync").asLong());
+			assertEquals(subtask.getAsyncCheckpointDuration(), subtaskNode.get("checkpoint").get("async").asLong());
+			assertEquals(subtask.getAlignmentBuffered(), subtaskNode.get("alignment").get("buffered").asLong());
+			assertEquals(subtask.getAlignmentDuration(), subtaskNode.get("alignment").get("duration").asLong());
+		}
+	}
+
+	private static MinMaxAvgStats createMinMaxAvgStats(ThreadLocalRandom rand) {
+		MinMaxAvgStats mma = mock(MinMaxAvgStats.class);
+		when(mma.getMinimum()).thenReturn(rand.nextLong(1024));
+		when(mma.getMaximum()).thenReturn(rand.nextLong(1024));
+		when(mma.getAverage()).thenReturn(rand.nextLong(1024));
+
+		return mma;
+	}
+
+	private static void verifyMinMaxAvgStats(MinMaxAvgStats expected, JsonNode node) {
+		assertEquals(expected.getMinimum(), node.get("min").asLong());
+		assertEquals(expected.getMaximum(), node.get("max").asLong());
+		assertEquals(expected.getAverage(), node.get("avg").asLong());
+	}
+
+}


[07/11] flink git commit: [FLINK-4410] [runtime-web] Add new layout for checkpoint stats

Posted by uc...@apache.org.
[FLINK-4410] [runtime-web] Add new layout for checkpoint stats


Project: http://git-wip-us.apache.org/repos/asf/flink/repo
Commit: http://git-wip-us.apache.org/repos/asf/flink/commit/3f2e9961
Tree: http://git-wip-us.apache.org/repos/asf/flink/tree/3f2e9961
Diff: http://git-wip-us.apache.org/repos/asf/flink/diff/3f2e9961

Branch: refs/heads/release-1.2
Commit: 3f2e9961ad90da7c0204bf29e4db5ed04a51416d
Parents: 1fd2d2e
Author: Ufuk Celebi <uc...@apache.org>
Authored: Fri Dec 23 20:44:59 2016 +0100
Committer: Ufuk Celebi <uc...@apache.org>
Committed: Tue Jan 10 09:47:55 2017 +0100

----------------------------------------------------------------------
 .../jobs/job.plan.node-list.checkpoints.jade    |  38 ++---
 .../jobs/job.plan.node.checkpoints.config.jade  |  49 ++++++
 .../jobs/job.plan.node.checkpoints.details.jade | 156 +++++++++++++++++++
 .../jobs/job.plan.node.checkpoints.history.jade |  61 ++++++++
 .../job.plan.node.checkpoints.overview.jade     |  69 ++++++++
 .../jobs/job.plan.node.checkpoints.summary.jade |  44 ++++++
 .../app/scripts/common/filters.coffee           |   3 +
 .../web-dashboard/app/scripts/index.coffee      |  36 +++++
 .../app/scripts/modules/jobs/jobs.ctrl.coffee   |  70 ++++++---
 .../app/scripts/modules/jobs/jobs.svc.coffee    |  62 +++++---
 .../web-dashboard/app/styles/job.styl           |  19 +++
 11 files changed, 543 insertions(+), 64 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/flink/blob/3f2e9961/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node-list.checkpoints.jade
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node-list.checkpoints.jade b/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node-list.checkpoints.jade
index 229d878..5db3d28 100644
--- a/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node-list.checkpoints.jade
+++ b/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node-list.checkpoints.jade
@@ -15,29 +15,19 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 
-div(ng-if="!jobCheckpointStats")
-  p
-    em No checkpoints
+.split
+  nav.navbar.navbar-default.navbar-secondary-additional
+    ul.nav.nav-tabs
+      li(ui-sref-active='active')
+        a(ui-sref=".overview") Overview
+      li(ui-sref-active='active')
+        a(ui-sref=".history") History
+      li(ui-sref-active='active')
+        a(ui-sref=".summary") Summary
+      li(ui-sref-active='active')
+        a(ui-sref=".config") Configuration
+      li(ng-if="checkpointDetails.id != -1").active
+        a Details for Checkpoint {{ checkpointDetails.id }}
 
-div(ng-if="jobCheckpointStats")
-  h2 Overview
+  .clean.checkpoints-view#checkpoints-view(ui-view="checkpoints-view")
 
-  div(ng-include=" 'partials/jobs/job.plan.node.checkpoints.job.html' ")
-
-  h2 Operators
-
-  table.table.table-body-hover.table-clickable.table-activable
-    thead
-      tr
-        th Name
-        th Status
-
-    tbody(ng-repeat="v in job.vertices" ng-class="{ active: v.id == nodeid }" ng-click="v.id == nodeid || changeNode(v.id)")
-      tr(ng-if="v.type == 'regular'")
-        td {{ v.name | humanizeText }}
-        td
-          bs-label(status="{{v.status}}") {{v.status}}
-
-      tr(ng-if="nodeid && v.id == nodeid")
-        td(colspan="10")
-          div(ng-include=" 'partials/jobs/job.plan.node.checkpoints.operator.html' ")

http://git-wip-us.apache.org/repos/asf/flink/blob/3f2e9961/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.config.jade
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.config.jade b/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.config.jade
new file mode 100644
index 0000000..4cd43f3
--- /dev/null
+++ b/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.config.jade
@@ -0,0 +1,49 @@
+//
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you 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.
+
+div(ng-if="checkpointConfig")
+  table.table
+    thead
+      tr
+        td #[strong Option]
+        td #[strong Value]
+    tbody
+      tr
+        td Checkpointing Mode
+        td(ng-if="checkpointConfig['mode'] == 'exactly_once'") Exactly Once
+        td(ng-if="checkpointConfig['mode'] != 'exactly_once'") At Least Once
+      tr
+        td Interval
+        td(ng-if="checkpointConfig['interval'] == '0x7fffffffffffffff'") Periodic checkpoints disabled
+        td(ng-if="checkpointConfig['interval'] != '0x7fffffffffffffff'") {{ checkpointConfig['interval'] | humanizeDuration }}
+      tr
+        td Timeout
+        td {{ checkpointConfig['timeout'] | humanizeDuration }}
+      tr
+        td Minimum Pause Between Checkpoints
+        td {{ checkpointConfig['min_pause'] | humanizeDuration }}
+      tr
+        td Maximum Concurrent Checkpoints
+        td {{ checkpointConfig['max_concurrent'] }}
+      tr
+        td Persist Checkpoints Externally
+        td(ng-if="checkpointConfig['externalization']['enabled']")
+          | Enabled
+          = ' '
+          span(ng-if="checkpointConfig['externalization']['delete_on_cancellation']") (delete on cancellation)
+          span(ng-if="!checkpointConfig['externalization']['delete_on_cancellation']") (retain on cancellation)
+        td(ng-if="!checkpointConfig['externalization']['enabled']") Disabled

http://git-wip-us.apache.org/repos/asf/flink/blob/3f2e9961/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.details.jade
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.details.jade b/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.details.jade
new file mode 100644
index 0000000..e2b1038
--- /dev/null
+++ b/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.details.jade
@@ -0,0 +1,156 @@
+//
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you 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.
+
+div(ng-if="checkpoint")
+  table.table.table-inner
+    thead
+      tr
+        td #[strong ID]
+        td #[strong Status]
+        td #[strong Acknowledged]
+        td #[strong Trigger Time]
+        td #[strong Latest Acknowledgement]
+        td(ng-if="checkpoint['failure_timestamp']") #[strong Failure Time]
+        td #[strong End to End Duration]
+        td #[strong State Size]
+        td #[strong Buffered During Alignment]
+        td(ng-if="checkpoint['status'] == 'COMPLETED'") #[strong Discarded]
+        td(ng-if="checkpoint['external_path']") #[strong Path]
+        td(ng-if="checkpoint['failure_message']") #[strong Failure Message]
+    tbody
+      tr
+        td {{ checkpoint['id'] }}
+        td(ng-if="checkpoint['status'] == 'IN_PROGRESS'") #[i(aria-hidden="true").fa.fa-circle-o-notch.fa-spin.fa-fw] In progress #[i(ng-if="checkpoint['is_savepoint']") savepoint]
+        td(ng-if="checkpoint['status'] == 'COMPLETED'") #[i(aria-hidden="true").fa.fa-check] Completed #[i(ng-if="checkpoint['is_savepoint']") savepoint]
+        td(ng-if="checkpoint['status'] == 'FAILED'") #[i(aria-hidden="true").fa.fa-remove] Failed #[i(ng-if="checkpoint['is_savepoint']") savepoint]
+        td {{ checkpoint['num_acknowledged_subtasks'] }}/{{ checkpoint['num_subtasks'] }} ({{ checkpoint['num_acknowledged_subtasks']/checkpoint['num_subtasks'] | percentage }})
+        td {{ checkpoint['trigger_timestamp'] | amDateFormat:'H:mm:ss' }}
+        td(ng-if="checkpoint['latest_ack_timestamp'] >= 0") {{ checkpoint['latest_ack_timestamp'] | amDateFormat:'H:mm:ss' }}
+        td(ng-if="checkpoint['latest_ack_timestamp'] < 0") n/a
+        td(ng-if="checkpoint['failure_timestamp']") {{ checkpoint['failure_timestamp'] | amDateFormat:'H:mm:ss' }}
+        td(ng-if="checkpoint['end_to_end_duration'] >= 0") {{ checkpoint['end_to_end_duration'] | humanizeDuration }}
+        td(ng-if="heckpoint['end_to_end_duration'] < 0") n/a
+        td {{ checkpoint['state_size'] | humanizeBytes }}
+        td  {{ checkpoint['alignment_buffered'] | humanizeBytes }}
+        td(ng-if="checkpoint['status'] == 'COMPLETED'") #[span(ng-if="checkpoint['discarded']") Yes]#[span(ng-if="!checkpoint['discarded']") No]
+        td(ng-if="checkpoint['external_path']") {{ checkpoint['external_path'] }}
+        td(ng-if="checkpoint['status'] == 'FAILED' && checkpoint['failure_message']") {{ checkpoint['failure_message'] }}
+        td(ng-if="checkpoint['status'] == 'FAILED' && !checkpoint['failure_message']") n/a
+
+  h4 Operators
+  table.table.table-body-hover.table-clickable.table-activable.subtask-details
+    thead
+      tr
+        td #[strong Name]
+        td #[strong Acknowleged]
+        td #[strong Latest Acknowledgment]
+        td #[strong End to End Duration]
+        td #[strong State Size]
+        td #[strong Buffered During Alignment]
+        td
+    tbody(ng-repeat="v in job.vertices" ng-class="{ active: v.id == nodeid }" ng-click="changeNode(v.id)")
+      tr(ng-if="v.type == 'regular'")
+        td {{ v.name | humanizeText }}
+        td {{ checkpoint['tasks'][v.id]['num_acknowledged_subtasks'] }}/{{ checkpoint['tasks'][v.id]['num_subtasks'] }} ({{ checkpoint['tasks'][v.id]['num_acknowledged_subtasks']/checkpoint['tasks'][v.id]['num_subtasks'] | percentage }})
+        td(ng-if="checkpoint['tasks'][v.id]['latest_ack_timestamp'] >= 0") {{ checkpoint['tasks'][v.id]['latest_ack_timestamp'] | amDateFormat:'H:mm:ss' }}
+        td(ng-if="checkpoint['tasks'][v.id]['latest_ack_timestamp'] < 0") n/a
+        td(ng-if="checkpoint['tasks'][v.id]['end_to_end_duration'] >= 0") {{ checkpoint['tasks'][v.id]['end_to_end_duration'] | humanizeDuration }}
+        td(ng-if="checkpoint['tasks'][v.id]['end_to_end_duration'] < 0") n/a
+        td {{ checkpoint['tasks'][v.id]['state_size'] | humanizeBytes }}
+        td {{ checkpoint['tasks'][v.id]['alignment_buffered'] | humanizeBytes }}
+        td
+          div(ng-if="!nodeid || v.id != nodeid")
+            a.btn.btn-default(ng-click="toggleFold()")
+              | Show Subtasks
+              = ' '
+              i.fa.fa-chevron-down
+          div(ng-if="nodeid && v.id == nodeid")
+            a.btn.btn-default(ng-click="toggleFold()")
+              | Hide Subtasks
+              = ' '
+              i.fa.fa-chevron-up
+      tr(ng-if="nodeid && v.id == nodeid")
+        td(colspan=7)
+          table.table.table-body-hover.table-inner.subtask-details
+            thead(ng-if="subtaskDetails[v.id]['summary']")
+              tr
+                td
+                td
+                td #[strong End to End Duration]
+                td #[strong State Size]
+                td #[strong Checkpoint Duration (Sync)]
+                td #[strong Checkpoint Duration (Async)]
+                td #[strong Alignment Buffered]
+                td #[strong Alignment Duration]
+              tr
+                td
+                td #[strong Minimum]
+                td {{ subtaskDetails[v.id]['summary']['end_to_end_duration']['min'] | humanizeDuration }}
+                td {{ subtaskDetails[v.id]['summary']['state_size']['min'] | humanizeBytes }}
+                td {{ subtaskDetails[v.id]['summary']['checkpoint_duration']['sync']['min'] | humanizeDuration }}
+                td {{ subtaskDetails[v.id]['summary']['checkpoint_duration']['async']['min'] | humanizeDuration }}
+                td {{ subtaskDetails[v.id]['summary']['alignment']['buffered']['min'] | humanizeBytes }}
+                td {{ subtaskDetails[v.id]['summary']['alignment']['duration']['min'] | humanizeDuration }}
+              tr
+                td
+                td #[strong Average]
+                td {{ subtaskDetails[v.id]['summary']['end_to_end_duration']['avg'] | humanizeDuration }}
+                td {{ subtaskDetails[v.id]['summary']['state_size']['avg'] | humanizeBytes }}
+                td {{ subtaskDetails[v.id]['summary']['checkpoint_duration']['sync']['avg'] | humanizeDuration }}
+                td {{ subtaskDetails[v.id]['summary']['checkpoint_duration']['async']['avg'] | humanizeDuration }}
+                td {{ subtaskDetails[v.id]['summary']['alignment']['buffered']['avg'] | humanizeBytes }}
+                td {{ subtaskDetails[v.id]['summary']['alignment']['duration']['avg'] | humanizeDuration }}
+              tr
+                td
+                td #[strong Maximum]
+                td {{ subtaskDetails[v.id]['summary']['end_to_end_duration']['max'] | humanizeDuration }}
+                td {{ subtaskDetails[v.id]['summary']['state_size']['max'] | humanizeBytes }}
+                td {{ subtaskDetails[v.id]['summary']['checkpoint_duration']['sync']['max'] | humanizeDuration }}
+                td {{ subtaskDetails[v.id]['summary']['checkpoint_duration']['async']['max'] | humanizeDuration }}
+                td {{ subtaskDetails[v.id]['summary']['alignment']['buffered']['max'] | humanizeBytes }}
+                td {{ subtaskDetails[v.id]['summary']['alignment']['duration']['max'] | humanizeDuration }}
+              tr.blank
+                td(colspan=8)
+            thead
+              tr
+                td #[strong Subtask #]
+                td #[strong Acknowledgement Time]
+                td #[strong End to End Duration]
+                td #[strong State Size]
+                td #[strong Checkpoint Duration (Sync)]
+                td #[strong Checkpoint Duration (Async)]
+                td #[strong Alignment Buffered]
+                td #[strong Alignment Duration]
+            tbody
+              tr(ng-repeat="subtask in subtaskDetails[v.id]['subtasks']")
+                td {{ subtask['index'] + 1 }}
+                td(ng-if-start="subtask['status'] == 'completed'") {{ subtask['ack_timestamp'] | amDateFormat:'H:mm:ss' }}
+                td {{ subtask['end_to_end_duration'] | humanizeDuration }}
+                td {{ subtask['state_size'] | humanizeBytes }}
+                td {{ subtask['checkpoint']['sync'] | humanizeDuration }}
+                td {{ subtask['checkpoint']['async'] | humanizeDuration }}
+                td {{ subtask['alignment']['buffered'] | humanizeBytes}}
+                td(ng-if-end) {{ subtask['alignment']['duration'] | humanizeDuration }}
+                td(ng-if="subtask['status'] == 'pending'" colspan=7) n/a
+
+div(ng-if="!checkpoint")
+  p(ng-if="unknown_checkpoint" role="alert").alert.alert-danger
+    strong Unknown or expired checkpoint ID.
+  p(ng-if="!unknown_checkpoint" role="alert").alert.alert-info
+    strong Waiting for response from JobManager with checkpoint details...
+    = ' '
+    i(aria-hidden="true").fa.fa-circle-o-notch.fa-spin.fa-fw

http://git-wip-us.apache.org/repos/asf/flink/blob/3f2e9961/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.history.jade
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.history.jade b/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.history.jade
new file mode 100644
index 0000000..44cd3db
--- /dev/null
+++ b/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.history.jade
@@ -0,0 +1,61 @@
+//
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you 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.
+
+div(ng-if="checkpointStats['history'] && checkpointStats['history'].length > 0")
+  table.table
+    thead
+      tr
+        td #[strong ID]
+        td #[strong Status]
+        td #[strong Acknowledged]
+        td #[strong Trigger Time]
+        td #[strong Latest Acknowledgement]
+        td #[strong End to End Duration]
+        td #[strong State Size]
+        td #[strong Buffered During Alignment]
+        td
+    tbody
+      tr(ng-repeat="checkpoint in checkpointStats['history']" ng-class="{'bg-danger': checkpoint['status'] == 'FAILED'}")
+        td {{ checkpoint['id'] }}
+        td(ng-if="checkpoint['status'] == 'IN_PROGRESS'") #[i(aria-hidden="true").fa.fa-circle-o-notch.fa-spin.fa-fw] #[i(ng-if="checkpoint['is_savepoint']" aria-hidden="true").fa.fa-floppy-o]
+        td(ng-if="checkpoint['status'] == 'COMPLETED'") #[i(aria-hidden="true").fa.fa-check] #[i(ng-if="checkpoint['is_savepoint']" aria-hidden="true").fa.fa-floppy-o]
+        td(ng-if="checkpoint['status'] == 'FAILED'") #[i(aria-hidden="true").fa.fa-remove] #[i(ng-if="checkpoint['is_savepoint']" aria-hidden="true").fa.fa-floppy-o]
+        td {{ checkpoint['num_acknowledged_subtasks'] }}/{{ checkpoint['num_subtasks'] }}
+          = ' '
+          span(ng-if="checkpoint['status'] == 'IN_PROGRESS'") ({{ checkpoint['num_acknowledged_subtasks']/checkpoint['num_subtasks'] | percentage }})
+        td {{ checkpoint['trigger_timestamp'] | amDateFormat:'H:mm:ss' }}
+        td(ng-if="checkpoint['latest_ack_timestamp'] >= 0") {{ checkpoint['latest_ack_timestamp'] | amDateFormat:'H:mm:ss' }}
+        td(ng-if="checkpoint['latest_ack_timestamp'] < 0") n/a
+        td(ng-if="checkpoint['end_to_end_duration'] >= 0") {{ checkpoint['end_to_end_duration'] | humanizeDuration }}
+        td(ng-if="checkpoint['end_to_end_duration'] < 0") n/a
+        td {{ checkpoint['state_size'] | humanizeBytes }}
+        td {{ checkpoint['alignment_buffered'] | humanizeBytes }}
+        td
+          a.btn.btn-default(ui-sref="^.details({checkpointId: checkpoint['id']})")
+            i(aria-hidden="true").fa.fa-chevron-right
+            = ' '
+            strong More details
+  p
+    strong.small Status:
+    ul.small
+      li In Progress: #[i(aria-hidden="true").fa.fa-circle-o-notch.fa-spin.fa-fw]
+      li Completed: #[i(aria-hidden="true").fa.fa-check]
+      li Failed: #[i(aria-hidden="true").fa.fa-remove]
+      li Savepoint: #[i(aria-hidden="true").fa.fa-floppy-o]
+
+div(ng-if="checkpointStats['history'] && checkpointStats['history'].length == 0")
+  p(role="alert").alert.alert-info #[strong No checkpoint history available.]

http://git-wip-us.apache.org/repos/asf/flink/blob/3f2e9961/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.overview.jade
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.overview.jade b/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.overview.jade
new file mode 100644
index 0000000..2a1af51
--- /dev/null
+++ b/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.overview.jade
@@ -0,0 +1,69 @@
+//
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you 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.
+
+div(ng-if="checkpointStats")
+  table.table.checkpoint-overview
+    thead
+      tr
+        td #[strong Checkpoint Counts]
+        td Triggered: {{ checkpointStats['counts']['total'] }}
+          span In Progress: {{ checkpointStats['counts']['in_progress'] }}
+          span Completed: {{ checkpointStats['counts']['completed'] }}
+          span Failed: {{ checkpointStats['counts']['failed'] }}
+          span Restored: {{ checkpointStats['counts']['restored'] }}
+      tr
+        td #[strong Latest Completed Checkpoint]
+        td(ng-if="checkpointStats['latest']['completed']") ID: {{ checkpointStats['latest']['completed']['id'] }}
+          span Completion Time: {{ checkpointStats['latest']['completed']['latest_ack_timestamp'] | amDateFormat:'H:mm:ss' }}
+          span End to End Duration: {{ checkpointStats['latest']['completed']['end_to_end_duration'] | humanizeDuration }}
+          span State Size: {{ checkpointStats['latest']['completed']['state_size'] | humanizeBytes }}
+          span
+            i(aria-hidden="true").fa.fa-caret-square-o-right
+            = ' '
+            a(ui-sref="^.details({checkpointId: checkpointStats['latest']['completed']['id']})") More details
+        td(ng-if="!checkpointStats['latest']['completed']") None
+      tr
+        td #[strong Latest Failed Checkpoint]
+        td(ng-if="checkpointStats['latest']['failed']")
+          | ID: {{ checkpointStats['latest']['failed']['id'] }}
+          span Failure Time: {{ checkpointStats['latest']['failed']['failure_timestamp'] | amDateFormat:'H:mm:ss' }}
+          span(ng-if="checkpointStats['latest']['failed']['failure_message']") Cause: {{ checkpointStats['latest']['failed']['failure_message'] }}
+          span(ng-if="!checkpointStats['latest']['failed']['failure_message']") Cause: n/a
+          span
+            i(aria-hidden="true").fa.fa-caret-square-o-right
+            = ' '
+            a(ui-sref="^.details({checkpointId: checkpointStats['latest']['failed']['id']})") More details
+        td(ng-if="!checkpointStats['latest']['failed']") None
+      tr
+        td #[strong Latest Savepoint]
+        td(ng-if="checkpointStats['latest']['savepoint']") ID: {{ checkpointStats['latest']['savepoint']['id'] }}
+          span Completion Time: {{ checkpointStats['latest']['savepoint']['latest_ack_timestamp'] | amDateFormat:'H:mm:ss' }}
+          span State Size: {{ checkpointStats['latest']['savepoint']['state_size'] | humanizeBytes }}
+          span Path: {{ checkpointStats['latest']['savepoint']['external_path'] }}
+          span
+            i(aria-hidden="true").fa.fa-caret-square-o-right
+            = ' '
+            a(ui-sref="^.details({checkpointId: checkpointStats['latest']['savepoint']['id']})") More details
+        td(ng-if="!checkpointStats['latest']['savepoint']") None
+      tr
+        td #[strong Latest Restore]
+        td(ng-if="checkpointStats['latest']['restored']") ID: {{ checkpointStats['latest']['restored']['id'] }}
+          span Restore Time: {{ checkpointStats['latest']['restored']['restore_timestamp'] | amDateFormat:'H:mm:ss' }}
+          span(ng-if="checkpointStats['latest']['restored']['is_savepoint']") Type: Savepoint
+          span(ng-if="!checkpointStats['latest']['restored']['is_savepoint']") Type: Checkpoint
+          span(ng-if="checkpointStats['latest']['restored']['external_path']") Path: {{ checkpointStats['latest']['restored']['external_path'] }}
+        td(ng-if="!checkpointStats['latest']['restored']") None

http://git-wip-us.apache.org/repos/asf/flink/blob/3f2e9961/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.summary.jade
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.summary.jade b/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.summary.jade
new file mode 100644
index 0000000..503c243
--- /dev/null
+++ b/flink-runtime-web/web-dashboard/app/partials/jobs/job.plan.node.checkpoints.summary.jade
@@ -0,0 +1,44 @@
+//
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you 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.
+
+div(ng-if="checkpointStats['summary']")
+  table.table
+    thead
+      tr
+        td
+        td #[strong State Size]
+        td #[strong End to End Duration]
+        td #[strong Buffered During Alignment]
+    tbody
+      tr
+        td #[strong Minimum]
+        td {{ checkpointStats['summary']['end_to_end_duration']['min'] | humanizeDuration }}
+        td {{ checkpointStats['summary']['state_size']['min'] | humanizeBytes }}
+        td {{ checkpointStats['summary']['alignment_buffered']['min'] | humanizeBytes }}
+      tr
+        td #[strong Average]
+        td {{ checkpointStats['summary']['end_to_end_duration']['avg'] | humanizeDuration }}
+        td {{ checkpointStats['summary']['state_size']['avg'] | humanizeBytes }}
+        td {{ checkpointStats['summary']['alignment_buffered']['avg'] | humanizeBytes }}
+      tr
+        td #[strong Maximum]
+        td {{ checkpointStats['summary']['end_to_end_duration']['max'] | humanizeDuration }}
+        td {{ checkpointStats['summary']['state_size']['max'] | humanizeBytes }}
+        td {{ checkpointStats['summary']['alignment_buffered']['max'] | humanizeBytes }}
+  p These number are computed over #[i all] completed checkpoints.
+
+p(ng-if="!checkpointStats['summary']") No checkpoint statistics summary available.

http://git-wip-us.apache.org/repos/asf/flink/blob/3f2e9961/flink-runtime-web/web-dashboard/app/scripts/common/filters.coffee
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/app/scripts/common/filters.coffee b/flink-runtime-web/web-dashboard/app/scripts/common/filters.coffee
index c3030a9..e7e831c 100644
--- a/flink-runtime-web/web-dashboard/app/scripts/common/filters.coffee
+++ b/flink-runtime-web/web-dashboard/app/scripts/common/filters.coffee
@@ -78,3 +78,6 @@ angular.module('flinkApp')
 
 .filter "toUpperCase", ->
   (text) -> text.toUpperCase()
+
+.filter "percentage", ->
+  (number) -> (number * 100).toFixed(0) + '%'

http://git-wip-us.apache.org/repos/asf/flink/blob/3f2e9961/flink-runtime-web/web-dashboard/app/scripts/index.coffee
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/app/scripts/index.coffee b/flink-runtime-web/web-dashboard/app/scripts/index.coffee
index 98ce76a..95bb356 100644
--- a/flink-runtime-web/web-dashboard/app/scripts/index.coffee
+++ b/flink-runtime-web/web-dashboard/app/scripts/index.coffee
@@ -130,11 +130,47 @@ angular.module('flinkApp', ['ui.router', 'angularMoment', 'dndLists'])
 
   .state "single-job.plan.checkpoints",
     url: "/checkpoints"
+    redirectTo: "single-job.plan.checkpoints.overview"
     views:
       'node-details':
         templateUrl: "partials/jobs/job.plan.node-list.checkpoints.html"
         controller: 'JobPlanCheckpointsController'
 
+  .state "single-job.plan.checkpoints.overview",
+    url: "/overview"
+    views:
+      'checkpoints-view':
+        templateUrl: "partials/jobs/job.plan.node.checkpoints.overview.html"
+        controller: 'JobPlanCheckpointsController'
+
+  .state "single-job.plan.checkpoints.summary",
+    url: "/summary"
+    views:
+      'checkpoints-view':
+        templateUrl: "partials/jobs/job.plan.node.checkpoints.summary.html"
+        controller: 'JobPlanCheckpointsController'
+
+  .state "single-job.plan.checkpoints.history",
+    url: "/history"
+    views:
+      'checkpoints-view':
+        templateUrl: "partials/jobs/job.plan.node.checkpoints.history.html"
+        controller: 'JobPlanCheckpointsController'
+
+  .state "single-job.plan.checkpoints.config",
+    url: "/config"
+    views:
+      'checkpoints-view':
+        templateUrl: "partials/jobs/job.plan.node.checkpoints.config.html"
+        controller: 'JobPlanCheckpointsController'
+
+  .state "single-job.plan.checkpoints.details",
+    url: "/details/{checkpointId}"
+    views:
+      'checkpoints-view':
+        templateUrl: "partials/jobs/job.plan.node.checkpoints.details.html"
+        controller: 'JobPlanCheckpointDetailsController'
+
   .state "single-job.plan.backpressure",
     url: "/backpressure"
     views:

http://git-wip-us.apache.org/repos/asf/flink/blob/3f2e9961/flink-runtime-web/web-dashboard/app/scripts/modules/jobs/jobs.ctrl.coffee
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/app/scripts/modules/jobs/jobs.ctrl.coffee b/flink-runtime-web/web-dashboard/app/scripts/modules/jobs/jobs.ctrl.coffee
index 2bcbc13..bbb57c5 100644
--- a/flink-runtime-web/web-dashboard/app/scripts/modules/jobs/jobs.ctrl.coffee
+++ b/flink-runtime-web/web-dashboard/app/scripts/modules/jobs/jobs.ctrl.coffee
@@ -47,8 +47,6 @@ angular.module('flinkApp')
   $scope.job = null
   $scope.plan = null
   $scope.vertices = null
-  $scope.jobCheckpointStats = null
-  $scope.showHistory = false
   $scope.backPressureOperatorStats = {}
 
   JobsService.loadJob($stateParams.jobid).then (data) ->
@@ -69,7 +67,6 @@ angular.module('flinkApp')
     $scope.job = null
     $scope.plan = null
     $scope.vertices = null
-    $scope.jobCheckpointStats = null
     $scope.backPressureOperatorStats = null
 
     $interval.cancel(refresher)
@@ -84,9 +81,6 @@ angular.module('flinkApp')
     JobsService.stopJob($stateParams.jobid).then (data) ->
       {}
 
-  $scope.toggleHistory = ->
-    $scope.showHistory = !$scope.showHistory
-
 # --------------------------------------
 
 .controller 'JobPlanController', ($scope, $state, $stateParams, $window, JobsService) ->
@@ -166,26 +160,60 @@ angular.module('flinkApp')
 
 # --------------------------------------
 
-.controller 'JobPlanCheckpointsController', ($scope, JobsService) ->
-  getJobCheckpointStats = ->
-    JobsService.getJobCheckpointStats($scope.jobid).then (data) ->
-      $scope.jobCheckpointStats = data
+.controller 'JobPlanCheckpointsController', ($scope, $state, $stateParams, JobsService) ->
+  # Updated by the details handler for the sub checkpoints nav bar.
+  $scope.checkpointDetails = {}
+  $scope.checkpointDetails.id = -1
 
-  getOperatorCheckpointStats = ->
-    JobsService.getOperatorCheckpointStats($scope.nodeid).then (data) ->
-      $scope.operatorCheckpointStats = data.operatorStats
-      $scope.subtasksCheckpointStats = data.subtasksStats
+  # Request the config once (it's static)
+  JobsService.getCheckpointConfig().then (data) ->
+    $scope.checkpointConfig = data
 
-  # Get the per job stats
-  getJobCheckpointStats()
+  # General stats like counts, history, etc.
+  getGeneralCheckpointStats = ->
+    JobsService.getCheckpointStats().then (data) ->
+      if (data != null)
+        $scope.checkpointStats = data
 
-  # Get the per operator stats
-  if $scope.nodeid and (!$scope.vertex or !$scope.vertex.operatorCheckpointStats)
-    getOperatorCheckpointStats()
+  # Trigger request
+  getGeneralCheckpointStats()
 
   $scope.$on 'reload', (event) ->
-    getJobCheckpointStats()
-    getOperatorCheckpointStats() if $scope.nodeid
+    # Retrigger request
+    getGeneralCheckpointStats()
+
+# --------------------------------------
+
+.controller 'JobPlanCheckpointDetailsController', ($scope, $state, $stateParams, JobsService) ->
+  $scope.subtaskDetails = {}
+  $scope.checkpointDetails.id = $stateParams.checkpointId
+
+  # Detailed stats for a single checkpoint
+  getCheckpointDetails = (checkpointId) ->
+    JobsService.getCheckpointDetails(checkpointId).then (data) ->
+      if (data != null)
+        $scope.checkpoint = data
+      else
+        $scope.unknown_checkpoint = true
+
+  getCheckpointSubtaskDetails = (checkpointId, vertexId) ->
+    JobsService.getCheckpointSubtaskDetails(checkpointId, vertexId).then (data) ->
+      if (data != null)
+        $scope.subtaskDetails[vertexId] = data
+
+  getCheckpointDetails($stateParams.checkpointId)
+
+  if ($scope.nodeid)
+    getCheckpointSubtaskDetails($stateParams.checkpointId, $scope.nodeid)
+
+  $scope.$on 'reload', (event) ->
+    getCheckpointDetails($stateParams.checkpointId)
+
+    if ($scope.nodeid)
+      getCheckpointSubtaskDetails($stateParams.checkpointId, $scope.nodeid)
+
+  $scope.$on '$destroy', ->
+    $scope.checkpointDetails.id = -1
 
 # --------------------------------------
 

http://git-wip-us.apache.org/repos/asf/flink/blob/3f2e9961/flink-runtime-web/web-dashboard/app/scripts/modules/jobs/jobs.svc.coffee
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/app/scripts/modules/jobs/jobs.svc.coffee b/flink-runtime-web/web-dashboard/app/scripts/modules/jobs/jobs.svc.coffee
index 71f0921..7351de8 100644
--- a/flink-runtime-web/web-dashboard/app/scripts/modules/jobs/jobs.svc.coffee
+++ b/flink-runtime-web/web-dashboard/app/scripts/modules/jobs/jobs.svc.coffee
@@ -199,7 +199,7 @@ angular.module('flinkApp')
 
     deferreds.job.promise.then (data) =>
       # vertex = @seekVertex(vertexid)
-
+      console.log(currentJob.jid)
       $http.get flinkConfig.jobServer + "jobs/" + currentJob.jid + "/vertices/" + vertexid + "/accumulators"
       .success (data) ->
         accumulators = data['user-accumulators']
@@ -212,37 +212,61 @@ angular.module('flinkApp')
 
     deferred.promise
 
-  # Job-level checkpoint stats
-  @getJobCheckpointStats = (jobid) ->
+  # Checkpoint config
+  @getCheckpointConfig =  ->
     deferred = $q.defer()
 
-    $http.get flinkConfig.jobServer + "jobs/" + jobid + "/checkpoints"
-    .success (data, status, headers, config) =>
-      if (angular.equals({}, data))
-        deferred.resolve(deferred.resolve(null))
-      else
-        deferred.resolve(data)
+    deferreds.job.promise.then (data) =>
+      $http.get flinkConfig.jobServer + "jobs/" + currentJob.jid + "/checkpoints/config"
+      .success (data) ->
+        if (angular.equals({}, data))
+          deferred.resolve(null)
+        else
+          deferred.resolve(data)
 
     deferred.promise
 
-  # Operator-level checkpoint stats
-  @getOperatorCheckpointStats = (vertexid) ->
+  # General checkpoint stats like counts, history, etc.
+  @getCheckpointStats = ->
     deferred = $q.defer()
 
     deferreds.job.promise.then (data) =>
-      $http.get flinkConfig.jobServer + "jobs/" + currentJob.jid + "/vertices/" + vertexid + "/checkpoints"
+      $http.get flinkConfig.jobServer + "jobs/" + currentJob.jid + "/checkpoints"
+      .success (data, status, headers, config) =>
+        if (angular.equals({}, data))
+          deferred.resolve(null)
+        else
+          deferred.resolve(data)
+
+    deferred.promise
+
+  # Detailed checkpoint stats for a single checkpoint
+  @getCheckpointDetails = (checkpointid) ->
+    deferred = $q.defer()
+
+    deferreds.job.promise.then (data) =>
+      $http.get flinkConfig.jobServer + "jobs/" + currentJob.jid + "/checkpoints/details/" + checkpointid
       .success (data) ->
         # If no data available, we are done.
         if (angular.equals({}, data))
-          deferred.resolve({ operatorStats: null, subtasksStats: null })
+          deferred.resolve(null)
         else
-          operatorStats = { id: data['id'], timestamp: data['timestamp'], duration: data['duration'], size: data['size'] }
+          deferred.resolve(data)
+
+    deferred.promise
+
+  # Detailed subtask stats for a single checkpoint
+  @getCheckpointSubtaskDetails = (checkpointid, vertexid) ->
+    deferred = $q.defer()
 
-          if (angular.equals({}, data['subtasks']))
-            deferred.resolve({ operatorStats: operatorStats, subtasksStats: null })
-          else
-            subtaskStats = data['subtasks']
-            deferred.resolve({ operatorStats: operatorStats, subtasksStats: subtaskStats })
+    deferreds.job.promise.then (data) =>
+      $http.get flinkConfig.jobServer + "jobs/" + currentJob.jid + "/checkpoints/details/" + checkpointid + "/subtasks/" + vertexid
+      .success (data) ->
+        # If no data available, we are done.
+        if (angular.equals({}, data))
+          deferred.resolve(null)
+        else
+          deferred.resolve(data)
 
     deferred.promise
 

http://git-wip-us.apache.org/repos/asf/flink/blob/3f2e9961/flink-runtime-web/web-dashboard/app/styles/job.styl
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/app/styles/job.styl b/flink-runtime-web/web-dashboard/app/styles/job.styl
index e0b11fc..fc61d50 100644
--- a/flink-runtime-web/web-dashboard/app/styles/job.styl
+++ b/flink-runtime-web/web-dashboard/app/styles/job.styl
@@ -50,3 +50,22 @@
   font-family: inherit
   margin-top: -2px
 
+.checkpoints-view {
+  padding-top: 1em
+}
+
+.subtask-details {
+  .blank {
+    height: 2em
+  }
+}
+
+.checkpoint-overview {
+  td span {
+    padding-left: 2em
+  }
+
+  a {
+    color: black;
+  }
+}


[11/11] flink git commit: [FLINK-4410] [runtime] Rework checkpoint stats tracking

Posted by uc...@apache.org.
[FLINK-4410] [runtime] Rework checkpoint stats tracking


Project: http://git-wip-us.apache.org/repos/asf/flink/repo
Commit: http://git-wip-us.apache.org/repos/asf/flink/commit/0d1f4bcb
Tree: http://git-wip-us.apache.org/repos/asf/flink/tree/0d1f4bcb
Diff: http://git-wip-us.apache.org/repos/asf/flink/diff/0d1f4bcb

Branch: refs/heads/release-1.2
Commit: 0d1f4bcbb37c5aa18f7bfcb886d3914b2f680bf0
Parents: 6ea77ed
Author: Ufuk Celebi <uc...@apache.org>
Authored: Fri Dec 23 20:37:08 2016 +0100
Committer: Ufuk Celebi <uc...@apache.org>
Committed: Tue Jan 10 09:47:55 2017 +0100

----------------------------------------------------------------------
 .../flink/configuration/ConfigConstants.java    |  10 +-
 .../jobmanager/JMXJobManagerMetricTest.java     |   2 +-
 .../checkpoint/AbstractCheckpointStats.java     | 192 ++++++++
 .../checkpoint/CheckpointCoordinator.java       |  43 +-
 .../checkpoint/CheckpointProperties.java        |  59 ++-
 .../checkpoint/CheckpointStatsCounts.java       | 184 ++++++++
 .../checkpoint/CheckpointStatsHistory.java      | 386 ++++++++++++++++
 .../checkpoint/CheckpointStatsSnapshot.java     | 102 +++++
 .../checkpoint/CheckpointStatsStatus.java       |  62 +++
 .../checkpoint/CheckpointStatsTracker.java      | 447 +++++++++++++++++++
 .../runtime/checkpoint/CompletedCheckpoint.java |  18 +
 .../checkpoint/CompletedCheckpointStats.java    | 174 ++++++++
 .../CompletedCheckpointStatsSummary.java        | 107 +++++
 .../checkpoint/FailedCheckpointStats.java       | 153 +++++++
 .../runtime/checkpoint/MinMaxAvgStats.java      | 130 ++++++
 .../runtime/checkpoint/PendingCheckpoint.java   |  97 +++-
 .../checkpoint/PendingCheckpointStats.java      | 190 ++++++++
 .../checkpoint/RestoredCheckpointStats.java     | 103 +++++
 .../flink/runtime/checkpoint/SubtaskState.java  |  37 +-
 .../runtime/checkpoint/SubtaskStateStats.java   | 176 ++++++++
 .../runtime/checkpoint/TaskStateStats.java      | 277 ++++++++++++
 .../savepoint/SavepointV1Serializer.java        |  10 +-
 .../executiongraph/AccessExecutionGraph.java    |   2 +-
 .../AccessExecutionJobVertex.java               |  10 +-
 .../executiongraph/ArchivedExecutionGraph.java  |   7 +-
 .../ArchivedExecutionJobVertex.java             |  14 +-
 .../runtime/executiongraph/ExecutionGraph.java  |  32 +-
 .../executiongraph/ExecutionGraphBuilder.java   |  30 +-
 .../executiongraph/ExecutionJobVertex.java      |  13 -
 .../jobgraph/tasks/JobSnapshottingSettings.java |  17 +-
 .../checkpoint/AcknowledgeCheckpoint.java       |  17 +-
 .../checkpoint/CheckpointCoordinatorTest.java   | 182 +++++---
 .../checkpoint/CheckpointPropertiesTest.java    |  27 ++
 .../checkpoint/CheckpointStateRestoreTest.java  |   6 +-
 .../checkpoint/CheckpointStatsCountsTest.java   | 153 +++++++
 .../checkpoint/CheckpointStatsHistoryTest.java  | 196 ++++++++
 .../checkpoint/CheckpointStatsStatusTest.java   |  48 ++
 .../checkpoint/CheckpointStatsTrackerTest.java  | 327 ++++++++++++++
 .../CompletedCheckpointStatsSummaryTest.java    | 105 +++++
 .../CompletedCheckpointStoreTest.java           |   2 +-
 .../checkpoint/CompletedCheckpointTest.java     |  25 ++
 .../checkpoint/CoordinatorShutdownTest.java     |   4 +-
 ...ExecutionGraphCheckpointCoordinatorTest.java |   5 +-
 .../checkpoint/FailedCheckpointStatsTest.java   |  60 +++
 .../runtime/checkpoint/MinMaxAvgStatsTest.java  |  96 ++++
 .../checkpoint/PendingCheckpointStatsTest.java  | 256 +++++++++++
 .../checkpoint/PendingCheckpointTest.java       |  70 ++-
 .../checkpoint/RestoredCheckpointStatsTest.java |  49 ++
 .../checkpoint/SubtaskStateStatsTest.java       |  57 +++
 .../runtime/checkpoint/TaskStateStatsTest.java  |  93 ++++
 .../checkpoint/savepoint/SavepointV1Test.java   |   3 +-
 .../ArchivedExecutionGraphTest.java             | 146 ++----
 .../jobmanager/JobManagerHARecoveryTest.java    |   5 +-
 .../runtime/jobmanager/JobManagerTest.java      |  12 +-
 .../flink/runtime/jobmanager/JobSubmitTest.java |  15 +-
 .../messages/CheckpointMessagesTest.java        |   3 +-
 .../runtime/jobmanager/JobManagerITCase.scala   |  21 +-
 .../api/graph/StreamingJobGraphGenerator.java   |  17 +-
 58 files changed, 4679 insertions(+), 405 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-core/src/main/java/org/apache/flink/configuration/ConfigConstants.java
----------------------------------------------------------------------
diff --git a/flink-core/src/main/java/org/apache/flink/configuration/ConfigConstants.java b/flink-core/src/main/java/org/apache/flink/configuration/ConfigConstants.java
index 8a9d594..eabb754 100644
--- a/flink-core/src/main/java/org/apache/flink/configuration/ConfigConstants.java
+++ b/flink-core/src/main/java/org/apache/flink/configuration/ConfigConstants.java
@@ -588,7 +588,12 @@ public final class ConfigConstants {
 	/** Config parameter indicating whether jobs can be uploaded and run from the web-frontend. */
 	public static final String JOB_MANAGER_WEB_SUBMIT_ENABLED_KEY = "jobmanager.web.submit.enable";
 
-	/** Flag to disable checkpoint stats. */
+	/**
+	 * Flag to disable checkpoint stats.
+	 *
+	 * @deprecated Not possible to disable any longer. Use history size of 0.
+	 */
+	@Deprecated
 	public static final String JOB_MANAGER_WEB_CHECKPOINTS_DISABLE = "jobmanager.web.checkpoints.disable";
 
 	/** Config parameter defining the number of checkpoints to remember for recent history. */
@@ -1226,7 +1231,8 @@ public final class ConfigConstants {
 	/** By default, submitting jobs from the web-frontend is allowed. */
 	public static final boolean DEFAULT_JOB_MANAGER_WEB_SUBMIT_ENABLED = true;
 
-	/** Default flag to disable checkpoint stats. */
+	/** Config key has been deprecated. Therefore, no default value required. */
+	@Deprecated
 	public static final boolean DEFAULT_JOB_MANAGER_WEB_CHECKPOINTS_DISABLE = false;
 
 	/** Default number of checkpoints to remember for recent history. */

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-metrics/flink-metrics-jmx/src/test/java/org/apache/flink/runtime/jobmanager/JMXJobManagerMetricTest.java
----------------------------------------------------------------------
diff --git a/flink-metrics/flink-metrics-jmx/src/test/java/org/apache/flink/runtime/jobmanager/JMXJobManagerMetricTest.java b/flink-metrics/flink-metrics-jmx/src/test/java/org/apache/flink/runtime/jobmanager/JMXJobManagerMetricTest.java
index 3ae224f..b3b7dfc 100644
--- a/flink-metrics/flink-metrics-jmx/src/test/java/org/apache/flink/runtime/jobmanager/JMXJobManagerMetricTest.java
+++ b/flink-metrics/flink-metrics-jmx/src/test/java/org/apache/flink/runtime/jobmanager/JMXJobManagerMetricTest.java
@@ -74,7 +74,7 @@ public class JMXJobManagerMetricTest {
 				Collections.<JobVertexID>emptyList(),
 				Collections.<JobVertexID>emptyList(),
 				Collections.<JobVertexID>emptyList(),
-				500, 500, 50, 5, ExternalizedCheckpointSettings.none()));
+				500, 500, 50, 5, ExternalizedCheckpointSettings.none(), true));
 
 			flink.waitForActorsToBeAlive();
 

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/AbstractCheckpointStats.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/AbstractCheckpointStats.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/AbstractCheckpointStats.java
new file mode 100644
index 0000000..6c261a5
--- /dev/null
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/AbstractCheckpointStats.java
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.checkpoint;
+
+import org.apache.flink.runtime.jobgraph.JobVertexID;
+
+import javax.annotation.Nullable;
+import java.util.Collection;
+import java.util.Map;
+
+import static org.apache.flink.util.Preconditions.checkArgument;
+import static org.apache.flink.util.Preconditions.checkNotNull;
+
+/**
+ * Base class for checkpoint statistics.
+ */
+public abstract class AbstractCheckpointStats {
+
+	/** ID of this checkpoint. */
+	final long checkpointId;
+
+	/** Timestamp when the checkpoint was triggered at the coordinator. */
+	final long triggerTimestamp;
+
+	/** {@link TaskStateStats} accessible by their ID. */
+	final Map<JobVertexID, TaskStateStats> taskStats;
+
+	/** Total number of subtasks over all tasks. */
+	final int numberOfSubtasks;
+
+	/** Properties of the checkpoint. */
+	final CheckpointProperties props;
+
+	AbstractCheckpointStats(
+			long checkpointId,
+			long triggerTimestamp,
+			CheckpointProperties props,
+			int numberOfSubtasks,
+			Map<JobVertexID, TaskStateStats> taskStats) {
+
+		this.checkpointId = checkpointId;
+		this.triggerTimestamp = triggerTimestamp;
+		this.taskStats = checkNotNull(taskStats);
+		checkArgument(taskStats.size() > 0, "Empty task stats");
+		checkArgument(numberOfSubtasks > 0, "Non-positive number of subtasks");
+		this.numberOfSubtasks = numberOfSubtasks;
+		this.props = checkNotNull(props);
+	}
+
+	/**
+	 * Returns the status of this checkpoint.
+	 *
+	 * @return Status of this checkpoint
+	 */
+	public abstract CheckpointStatsStatus getStatus();
+
+	/**
+	 * Returns the number of acknowledged subtasks.
+	 *
+	 * @return The number of acknowledged subtasks.
+	 */
+	public abstract int getNumberOfAcknowledgedSubtasks();
+
+	/**
+	 * Returns the total checkpoint state size over all subtasks.
+	 *
+	 * @return Total checkpoint state size over all subtasks.
+	 */
+	public abstract long getStateSize();
+
+	/**
+	 * Returns the total buffered bytes during alignment over all subtasks.
+	 *
+	 * <p>Can return <code>-1</code> if the runtime did not report this.
+	 *
+	 * @return Total buffered bytes during alignment over all subtasks.
+	 */
+	public abstract long getAlignmentBuffered();
+
+	/**
+	 * Returns the latest acknowledged subtask stats or <code>null</code> if
+	 * none was acknowledged yet.
+	 *
+	 * @return Latest acknowledged subtask stats or <code>null</code>
+	 */
+	@Nullable
+	public abstract SubtaskStateStats getLatestAcknowledgedSubtaskStats();
+
+	/**
+	 * Returns the ID of this checkpoint.
+	 *
+	 * @return ID of this checkpoint.
+	 */
+	public long getCheckpointId() {
+		return checkpointId;
+	}
+
+	/**
+	 * Returns the timestamp when the checkpoint was triggered.
+	 *
+	 * @return Timestamp when the checkpoint was triggered.
+	 */
+	public long getTriggerTimestamp() {
+		return triggerTimestamp;
+	}
+
+	/**
+	 * Returns the properties of this checkpoint.
+	 *
+	 * @return Properties of this checkpoint.
+	 */
+	public CheckpointProperties getProperties() {
+		return props;
+	}
+
+	/**
+	 * Returns the total number of subtasks involved in this checkpoint.
+	 *
+	 * @return Total number of subtasks involved in this checkpoint.
+	 */
+	public int getNumberOfSubtasks() {
+		return numberOfSubtasks;
+	}
+
+	/**
+	 * Returns the task state stats for the given job vertex ID or
+	 * <code>null</code> if no task with such an ID is available.
+	 *
+	 * @param jobVertexId Job vertex ID of the task stats to look up.
+	 * @return The task state stats instance for the given ID or <code>null</code>.
+	 */
+	public TaskStateStats getTaskStateStats(JobVertexID jobVertexId) {
+		return taskStats.get(jobVertexId);
+	}
+
+	/**
+	 * Returns all task state stats instances.
+	 *
+	 * @return All task state stats instances.
+	 */
+	public Collection<TaskStateStats> getAllTaskStateStats() {
+		return taskStats.values();
+	}
+
+	/**
+	 * Returns the ack timestamp of the latest acknowledged subtask or
+	 * <code>-1</code> if none was acknowledged yet.
+	 *
+	 * @return Ack timestamp of the latest acknowledged subtask or <code>-1</code>.
+	 */
+	public long getLatestAckTimestamp() {
+		SubtaskStateStats subtask = getLatestAcknowledgedSubtaskStats();
+		if (subtask != null) {
+			return subtask.getAckTimestamp();
+		} else {
+			return -1;
+		}
+	}
+
+	/**
+	 * Returns the duration of this checkpoint calculated as the time since
+	 * triggering until the latest acknowledged subtask or <code>-1</code> if
+	 * no subtask was acknowledged yet.
+	 *
+	 * @return Duration of this checkpoint or <code>-1</code> if no subtask was acknowledged yet.
+	 */
+	public long getEndToEndDuration() {
+		SubtaskStateStats subtask = getLatestAcknowledgedSubtaskStats();
+		if (subtask != null) {
+			return Math.max(0, subtask.getAckTimestamp() - triggerTimestamp);
+		} else {
+			return -1;
+		}
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointCoordinator.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointCoordinator.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointCoordinator.java
index 3ce7a5a..9132897 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointCoordinator.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointCoordinator.java
@@ -21,7 +21,6 @@ package org.apache.flink.runtime.checkpoint;
 import org.apache.flink.annotation.VisibleForTesting;
 import org.apache.flink.api.common.JobID;
 import org.apache.flink.configuration.ConfigConstants;
-import org.apache.flink.runtime.checkpoint.stats.CheckpointStatsTracker;
 import org.apache.flink.runtime.concurrent.Future;
 import org.apache.flink.runtime.concurrent.impl.FlinkCompletableFuture;
 import org.apache.flink.runtime.execution.ExecutionState;
@@ -40,6 +39,7 @@ import org.apache.flink.runtime.state.TaskStateHandles;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.annotation.Nullable;
 import java.util.ArrayDeque;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -147,8 +147,9 @@ public class CheckpointCoordinator {
 	/** Flag marking the coordinator as shut down (not accepting any messages any more) */
 	private volatile boolean shutdown;
 
-	/** Helper for tracking checkpoint statistics  */
-	private final CheckpointStatsTracker statsTracker;
+	/** Optional tracker for checkpoint statistics. */
+	@Nullable
+	private CheckpointStatsTracker statsTracker;
 
 	/** Default checkpoint properties **/
 	private final CheckpointProperties checkpointProperties;
@@ -170,7 +171,6 @@ public class CheckpointCoordinator {
 			CheckpointIDCounter checkpointIDCounter,
 			CompletedCheckpointStore completedCheckpointStore,
 			String checkpointDirectory,
-			CheckpointStatsTracker statsTracker,
 			Executor executor) {
 
 		// sanity checks
@@ -209,7 +209,6 @@ public class CheckpointCoordinator {
 		this.completedCheckpointStore = checkNotNull(completedCheckpointStore);
 		this.checkpointDirectory = checkpointDirectory;
 		this.recentPendingCheckpoints = new ArrayDeque<>(NUM_GHOST_CHECKPOINT_IDS);
-		this.statsTracker = checkNotNull(statsTracker);
 
 		this.timer = new Timer("Checkpoint Timer", true);
 
@@ -231,6 +230,15 @@ public class CheckpointCoordinator {
 		this.executor = checkNotNull(executor);
 	}
 
+	/**
+	 * Sets the checkpoint stats tracker.
+	 *
+	 * @param statsTracker The checkpoint stats tracker.
+	 */
+	public void setCheckpointStatsTracker(@Nullable CheckpointStatsTracker statsTracker) {
+		this.statsTracker = statsTracker;
+	}
+
 	// --------------------------------------------------------------------------------------------
 	//  Clean shutdown
 	// --------------------------------------------------------------------------------------------
@@ -428,11 +436,19 @@ public class CheckpointCoordinator {
 				checkpointID,
 				timestamp,
 				ackTasks,
-				isPeriodic,
 				props,
 				targetDirectory,
 				executor);
 
+			if (statsTracker != null) {
+				PendingCheckpointStats callback = statsTracker.reportPendingCheckpoint(
+					checkpointID,
+					timestamp,
+					props);
+
+				checkpoint.setStatsCallback(callback);
+			}
+
 			// schedule the timer that will clean up the expired checkpoints
 			TimerTask canceller = new TimerTask() {
 				@Override
@@ -632,7 +648,7 @@ public class CheckpointCoordinator {
 
 			if (checkpoint != null && !checkpoint.isDiscarded()) {
 
-				switch (checkpoint.acknowledgeTask(message.getTaskExecutionId(), message.getSubtaskState())) {
+				switch (checkpoint.acknowledgeTask(message.getTaskExecutionId(), message.getSubtaskState(), message.getCheckpointMetaData())) {
 					case SUCCESS:
 						LOG.debug("Received acknowledge message for checkpoint {} from task {} of job {}.",
 							checkpointId, message.getTaskExecutionId(), message.getJob());
@@ -769,8 +785,6 @@ public class CheckpointCoordinator {
 				ee.notifyCheckpointComplete(checkpointId, timestamp);
 			}
 		}
-
-		statsTracker.onCompletedCheckpoint(completedCheckpoint);
 	}
 
 	private void rememberRecentCheckpointId(long id) {
@@ -876,6 +890,17 @@ public class CheckpointCoordinator {
 
 			stateAssignmentOperation.assignStates();
 
+			if (statsTracker != null) {
+				long restoreTimestamp = System.currentTimeMillis();
+				RestoredCheckpointStats restored = new RestoredCheckpointStats(
+					latest.getCheckpointID(),
+					latest.getProperties(),
+					restoreTimestamp,
+					latest.getExternalPath());
+
+				statsTracker.reportRestoredCheckpoint(restored);
+			}
+
 			return true;
 		}
 	}

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointProperties.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointProperties.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointProperties.java
index 68a4998..4d8bab2 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointProperties.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointProperties.java
@@ -179,7 +179,6 @@ public class CheckpointProperties implements Serializable {
 
 	// ------------------------------------------------------------------------
 
-
 	@Override
 	public boolean equals(Object o) {
 		if (this == o) {
@@ -227,6 +226,42 @@ public class CheckpointProperties implements Serializable {
 
 	// ------------------------------------------------------------------------
 
+	private static final CheckpointProperties STANDARD_SAVEPOINT = new CheckpointProperties(
+			true,
+			true,
+			false,
+			false,
+			false,
+			false,
+			false);
+
+	private static final CheckpointProperties STANDARD_CHECKPOINT = new CheckpointProperties(
+			false,
+			false,
+			true,
+			true,
+			true,
+			true,
+			true);
+
+	private static final CheckpointProperties EXTERNALIZED_CHECKPOINT_RETAINED = new CheckpointProperties(
+			false,
+			true,
+			true,
+			true,
+			false, // Retain on cancellation
+			false,
+			false); // Retain on suspension
+
+	private static final CheckpointProperties EXTERNALIZED_CHECKPOINT_DELETED = new CheckpointProperties(
+			false,
+			true,
+			true,
+			true,
+			true, // Delete on cancellation
+			false,
+			true); // Delete on suspension
+
 	/**
 	 * Creates the checkpoint properties for a (manually triggered) savepoint.
 	 *
@@ -236,7 +271,7 @@ public class CheckpointProperties implements Serializable {
 	 * @return Checkpoint properties for a (manually triggered) savepoint.
 	 */
 	public static CheckpointProperties forStandardSavepoint() {
-		return new CheckpointProperties(true, true, false, false, false, false, false);
+		return STANDARD_SAVEPOINT;
 	}
 
 	/**
@@ -248,7 +283,7 @@ public class CheckpointProperties implements Serializable {
 	 * @return Checkpoint properties for a regular checkpoint.
 	 */
 	public static CheckpointProperties forStandardCheckpoint() {
-		return new CheckpointProperties(false, false, true, true, true, true, true);
+		return STANDARD_CHECKPOINT;
 	}
 
 	/**
@@ -264,8 +299,20 @@ public class CheckpointProperties implements Serializable {
 	 * @return Checkpoint properties for an external checkpoint.
 	 */
 	public static CheckpointProperties forExternalizedCheckpoint(boolean deleteOnCancellation) {
-		// Handle suspension like cancellation as graceful cluster shut down
-		// suspends all jobs (non-HA).
-		return new CheckpointProperties(false, true, true, true, deleteOnCancellation, false, deleteOnCancellation);
+		if (deleteOnCancellation) {
+			return EXTERNALIZED_CHECKPOINT_DELETED;
+		} else {
+			return EXTERNALIZED_CHECKPOINT_RETAINED;
+		}
+	}
+
+	/**
+	 * Returns whether the checkpoint properties describe a standard savepoint.
+	 *
+	 * @param props Checkpoint properties to check.
+	 * @return <code>true</code> if the properties describe a savepoint, <code>false</code> otherwise.
+	 */
+	public static boolean isSavepoint(CheckpointProperties props) {
+		return STANDARD_SAVEPOINT.equals(props);
 	}
 }

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointStatsCounts.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointStatsCounts.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointStatsCounts.java
new file mode 100644
index 0000000..dad45eb
--- /dev/null
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointStatsCounts.java
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.checkpoint;
+
+import java.io.Serializable;
+
+import static org.apache.flink.util.Preconditions.checkArgument;
+
+/**
+ * Counts of checkpoints.
+ */
+public class CheckpointStatsCounts implements Serializable {
+
+	private static final long serialVersionUID = -5229425063269482528L;
+
+	/** Number of restored checkpoints. */
+	private long numRestoredCheckpoints;
+
+	/** Number of total checkpoints (in progress, completed, failed). */
+	private long numTotalCheckpoints;
+
+	/** Number of in progress checkpoints. */
+	private int numInProgressCheckpoints;
+
+	/** Number of successfully completed checkpoints. */
+	private long numCompletedCheckpoints;
+
+	/** Number of failed checkpoints. */
+	private long numFailedCheckpoints;
+
+	/**
+	 * Creates the initial zero checkpoint counts.
+	 */
+	CheckpointStatsCounts() {
+		this(0, 0, 0, 0, 0);
+	}
+
+	/**
+	 * Creates the checkpoint counts with the given counts.
+	 *
+	 * @param numRestoredCheckpoints Number of restored checkpoints.
+	 * @param numTotalCheckpoints Number of total checkpoints (in progress, completed, failed).
+	 * @param numInProgressCheckpoints Number of in progress checkpoints.
+	 * @param numCompletedCheckpoints Number of successfully completed checkpoints.
+	 * @param numFailedCheckpoints Number of failed checkpoints.
+	 */
+	private CheckpointStatsCounts(
+			long numRestoredCheckpoints,
+			long numTotalCheckpoints,
+			int numInProgressCheckpoints,
+			long numCompletedCheckpoints,
+			long numFailedCheckpoints) {
+
+		checkArgument(numRestoredCheckpoints >= 0, "Negative number of restored checkpoints");
+		checkArgument(numTotalCheckpoints >= 0, "Negative total number of checkpoints");
+		checkArgument(numInProgressCheckpoints >= 0, "Negative number of in progress checkpoints");
+		checkArgument(numCompletedCheckpoints >= 0, "Negative number of completed checkpoints");
+		checkArgument(numFailedCheckpoints >= 0, "Negative number of failed checkpoints");
+
+		this.numRestoredCheckpoints = numRestoredCheckpoints;
+		this.numTotalCheckpoints = numTotalCheckpoints;
+		this.numInProgressCheckpoints = numInProgressCheckpoints;
+		this.numCompletedCheckpoints = numCompletedCheckpoints;
+		this.numFailedCheckpoints = numFailedCheckpoints;
+	}
+
+	/**
+	 * Returns the number of restored checkpoints.
+	 *
+	 * @return Number of restored checkpoints.
+	 */
+	public long getNumberOfRestoredCheckpoints() {
+		return numRestoredCheckpoints;
+	}
+
+	/**
+	 * Returns the total number of checkpoints (in progress, completed, failed).
+	 *
+	 * @return Total number of checkpoints.
+	 */
+	public long getTotalNumberOfCheckpoints() {
+		return numTotalCheckpoints;
+	}
+
+	/**
+	 * Returns the number of in progress checkpoints.
+	 *
+	 * @return Number of in progress checkpoints.
+	 */
+	public int getNumberOfInProgressCheckpoints() {
+		return numInProgressCheckpoints;
+	}
+
+	/**
+	 * Returns the number of completed checkpoints.
+	 *
+	 * @return Number of completed checkpoints.
+	 */
+	public long getNumberOfCompletedCheckpoints() {
+		return numCompletedCheckpoints;
+	}
+
+	/**
+	 * Returns the number of failed checkpoints.
+	 *
+	 * @return Number of failed checkpoints.
+	 */
+	public long getNumberOfFailedCheckpoints() {
+		return numFailedCheckpoints;
+	}
+
+	/**
+	 * Increments the number of restored checkpoints.
+	 */
+	void incrementRestoredCheckpoints() {
+		numRestoredCheckpoints++;
+	}
+
+	/**
+	 * Increments the number of total and in progress checkpoints.
+	 */
+	void incrementInProgressCheckpoints() {
+		numInProgressCheckpoints++;
+		numTotalCheckpoints++;
+	}
+
+	/**
+	 * Increments the number of successfully completed checkpoints.
+	 *
+	 * <p>It is expected that this follows a previous call to
+	 * {@link #incrementInProgressCheckpoints()}.
+	 */
+	void incrementCompletedCheckpoints() {
+		if (--numInProgressCheckpoints < 0) {
+			throw new IllegalStateException("Incremented the completed number of checkpoints " +
+				"without incrementing the in progress checkpoints before.");
+		}
+		numCompletedCheckpoints++;
+	}
+
+	/**
+	 * Increments the number of failed checkpoints.
+	 *
+	 * <p>It is expected that this follows a previous call to
+	 * {@link #incrementInProgressCheckpoints()}.
+	 */
+	void incrementFailedCheckpoints() {
+		if (--numInProgressCheckpoints < 0) {
+			throw new IllegalStateException("Incremented the completed number of checkpoints " +
+				"without incrementing the in progress checkpoints before.");
+		}
+		numFailedCheckpoints++;
+	}
+
+	/**
+	 * Creates a snapshot of the current state.
+	 *
+	 * @return Snapshot of the current state.
+	 */
+	CheckpointStatsCounts createSnapshot() {
+		return new CheckpointStatsCounts(
+			numRestoredCheckpoints,
+			numTotalCheckpoints,
+			numInProgressCheckpoints,
+			numCompletedCheckpoints,
+			numFailedCheckpoints);
+	}
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointStatsHistory.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointStatsHistory.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointStatsHistory.java
new file mode 100644
index 0000000..56fc9c1
--- /dev/null
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointStatsHistory.java
@@ -0,0 +1,386 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.checkpoint;
+
+import javax.annotation.Nullable;
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+import static org.apache.flink.util.Preconditions.checkArgument;
+import static org.apache.flink.util.Preconditions.checkNotNull;
+
+/**
+ * An array based history of checkpoint stats.
+ *
+ * <p>The size of the array is constrained by the maximum allowed size. The
+ * array starts empty an grows with each added checkpoint until it reaches
+ * the maximum number of elements. At this point, the elements wrap around
+ * and the least recently added entry is overwritten.
+ *
+ * <p>Access happens via an checkpointsIterable over the statistics and a map that
+ * exposes the checkpoint by their ID. Both of these are only guaranteed
+ * to reflect the latest state after a call to {@link #createSnapshot()}.
+ *
+ * <p>Furthermore the history tracks the latest completed and latest failed
+ * checkpoint as well as the latest savepoint.
+ */
+public class CheckpointStatsHistory implements Serializable {
+
+	private static final long serialVersionUID = 7090320677606528415L;
+
+	/** Iterable over all available stats. Only updated on {@link #createSnapshot()}. */
+	private final Iterable<AbstractCheckpointStats> checkpointsIterable;
+
+	/** Map of all available stats keyed by their ID. Only updated on {@link #createSnapshot()}. */
+	private final Map<Long, AbstractCheckpointStats> checkpointsById;
+
+	/** Maximum array size. */
+	private final int maxSize;
+
+	/** Flag indicating whether this the history is read-only. */
+	private final boolean readOnly;
+
+	/** Array of checkpointsArray. Writes go aginst this array. */
+	private transient AbstractCheckpointStats[] checkpointsArray;
+
+	/** Next position in {@link #checkpointsArray} to write to. */
+	private transient int nextPos;
+
+	/** The latest successfully completed checkpoint. */
+	@Nullable
+	private CompletedCheckpointStats latestCompletedCheckpoint;
+
+	/** The latest failed checkpoint. */
+	@Nullable
+	private FailedCheckpointStats latestFailedCheckpoint;
+
+	/** The latest successfully completed savepoint. */
+	@Nullable
+	private CompletedCheckpointStats latestSavepoint;
+
+	/**
+	 * Creates a writeable checkpoint history with the given maximum size.
+	 *
+	 * <p>The read views are only updated on calls to {@link #createSnapshot()}.
+	 * Initially they are empty.
+	 *
+	 * @param maxSize Maximum history size.
+	 */
+	CheckpointStatsHistory(int maxSize) {
+		this(
+			false,
+			maxSize,
+			new AbstractCheckpointStats[0],
+			Collections.<AbstractCheckpointStats>emptyList(),
+			Collections.<Long, AbstractCheckpointStats>emptyMap(),
+			null,
+			null,
+			null);
+	}
+
+	/**
+	 * Creates a checkpoint history with the given maximum size and state.
+	 *
+	 * <p>The read views are only updated on calls to {@link #createSnapshot()}.
+	 * Initially they are empty.
+	 *
+	 * @param readOnly Flag indicating whether the history is read-only.
+	 * @param maxSize Maximum history size.
+	 * @param checkpointsIterable Checkpoints iterable.
+	 * @param checkpointsById Checkpoints by ID.
+	 */
+	private CheckpointStatsHistory(
+			boolean readOnly,
+			int maxSize,
+			AbstractCheckpointStats[] checkpointArray,
+			Iterable<AbstractCheckpointStats> checkpointsIterable,
+			Map<Long, AbstractCheckpointStats> checkpointsById,
+			CompletedCheckpointStats latestCompletedCheckpoint,
+			FailedCheckpointStats latestFailedCheckpoint,
+			CompletedCheckpointStats latestSavepoint) {
+
+		this.readOnly = readOnly;
+		checkArgument(maxSize >= 0, "Negative maximum size");
+		this.maxSize = maxSize;
+		this.checkpointsArray = checkpointArray;
+		this.checkpointsIterable = checkNotNull(checkpointsIterable);
+		this.checkpointsById = checkNotNull(checkpointsById);
+		this.latestCompletedCheckpoint = latestCompletedCheckpoint;
+		this.latestFailedCheckpoint = latestFailedCheckpoint;
+		this.latestSavepoint = latestSavepoint;
+	}
+
+	public Iterable<AbstractCheckpointStats> getCheckpoints() {
+		return checkpointsIterable;
+	}
+
+	public AbstractCheckpointStats getCheckpointById(long checkpointId) {
+		return checkpointsById.get(checkpointId);
+	}
+
+	@Nullable
+	public CompletedCheckpointStats getLatestCompletedCheckpoint() {
+		return latestCompletedCheckpoint;
+	}
+
+	@Nullable
+	public FailedCheckpointStats getLatestFailedCheckpoint() {
+		return latestFailedCheckpoint;
+	}
+
+	@Nullable
+	public CompletedCheckpointStats getLatestSavepoint() {
+		return latestSavepoint;
+	}
+
+	/**
+	 * Creates a snapshot of the current state.
+	 *
+	 * @return Snapshot of the current state.
+	 */
+	CheckpointStatsHistory createSnapshot() {
+		if (readOnly) {
+			throw new UnsupportedOperationException("Can't create a snapshot of a read-only history.");
+		}
+
+		Iterable<AbstractCheckpointStats> checkpointsIterable;
+		Map<Long, AbstractCheckpointStats> checkpointsById;
+
+		checkpointsById = new HashMap<>(checkpointsArray.length);
+
+		if (maxSize == 0) {
+			checkpointsIterable = Collections.emptyList();
+		} else {
+			// Create snapshot iterator (copies the array)
+			checkpointsIterable = new CheckpointsStatsHistoryIterable(checkpointsArray, nextPos);
+
+			for (AbstractCheckpointStats checkpoint : checkpointsIterable) {
+				checkpointsById.put(checkpoint.getCheckpointId(), checkpoint);
+			}
+		}
+
+		if (latestCompletedCheckpoint != null) {
+			checkpointsById.put(latestCompletedCheckpoint.getCheckpointId(), latestCompletedCheckpoint);
+		}
+
+		if (latestFailedCheckpoint != null) {
+			checkpointsById.put(latestFailedCheckpoint.getCheckpointId(), latestFailedCheckpoint);
+		}
+
+		if (latestSavepoint != null) {
+			checkpointsById.put(latestSavepoint.getCheckpointId(), latestSavepoint);
+		}
+
+		return new CheckpointStatsHistory(
+			true,
+			maxSize,
+			null,
+			checkpointsIterable,
+			checkpointsById,
+			latestCompletedCheckpoint,
+			latestFailedCheckpoint,
+			latestSavepoint);
+	}
+
+	/**
+	 * Adds an in progress checkpoint to the checkpoint history.
+	 *
+	 * @param pending In progress checkpoint to add.
+	 */
+	void addInProgressCheckpoint(PendingCheckpointStats pending) {
+		if (readOnly) {
+			throw new UnsupportedOperationException("Can't create a snapshot of a read-only history.");
+		}
+
+		if (maxSize == 0) {
+			return;
+		}
+
+		checkNotNull(pending, "Pending checkpoint");
+
+		// Grow the array if required. This happens only for the first entries
+		// and makes the iterator logic easier, because we don't have any
+		// null elements with the growing array.
+		if (checkpointsArray.length < maxSize) {
+			checkpointsArray = Arrays.copyOf(checkpointsArray, checkpointsArray.length + 1);
+		}
+
+		// Wrap around if we are at the end. The next pos is the least recently
+		// added checkpoint.
+		if (nextPos == checkpointsArray.length) {
+			nextPos = 0;
+		}
+
+		checkpointsArray[nextPos++] = pending;
+	}
+
+	/**
+	 * Searches for the in progress checkpoint with the given ID and replaces
+	 * it with the given completed or failed checkpoint.
+	 *
+	 * <p>This is bounded by the maximum number of concurrent in progress
+	 * checkpointsArray, which means that the runtime of this is constant.
+	 *
+	 * @param completedOrFailed The completed or failed checkpoint to replace the in progress checkpoint with.
+	 * @return <code>true</code> if the checkpoint was replaced or <code>false</code> otherwise.
+	 */
+	boolean replacePendingCheckpointById(AbstractCheckpointStats completedOrFailed) {
+		checkArgument(!completedOrFailed.getStatus().isInProgress(), "Not allowed to replace with in progress checkpoints.");
+
+		if (readOnly) {
+			throw new UnsupportedOperationException("Can't create a snapshot of a read-only history.");
+		}
+
+		// Update the latest checkpoint stats
+		if (completedOrFailed.getStatus().isCompleted()) {
+			CompletedCheckpointStats completed = (CompletedCheckpointStats) completedOrFailed;
+			if (CheckpointProperties.isSavepoint(completed.getProperties()) &&
+				(latestSavepoint == null ||
+					completed.getCheckpointId() > latestSavepoint.getCheckpointId())) {
+
+				latestSavepoint = completed;
+			} else if (latestCompletedCheckpoint == null ||
+				completed.getCheckpointId() > latestCompletedCheckpoint.getCheckpointId()) {
+
+				latestCompletedCheckpoint = completed;
+			}
+		} else if (completedOrFailed.getStatus().isFailed()) {
+			FailedCheckpointStats failed = (FailedCheckpointStats) completedOrFailed;
+			if (latestFailedCheckpoint == null ||
+				failed.getCheckpointId() > latestFailedCheckpoint.getCheckpointId()) {
+
+				latestFailedCheckpoint = failed;
+			}
+		}
+
+		if (maxSize == 0) {
+			return false;
+		}
+
+		long checkpointId = completedOrFailed.getCheckpointId();
+
+		// We start searching from the last inserted position. Since the entries
+		// wrap around the array we search until we are at index 0 and then from
+		// the end of the array until (start pos + 1).
+		int startPos = nextPos == checkpointsArray.length ? checkpointsArray.length - 1 : nextPos - 1;
+
+		for (int i = startPos; i >= 0; i--) {
+			if (checkpointsArray[i].getCheckpointId() == checkpointId) {
+				checkpointsArray[i] = completedOrFailed;
+				return true;
+			}
+		}
+
+		for (int i = checkpointsArray.length - 1; i > startPos; i--) {
+			if (checkpointsArray[i].getCheckpointId() == checkpointId) {
+				checkpointsArray[i] = completedOrFailed;
+				return true;
+			}
+		}
+
+		return false;
+	}
+
+	/**
+	 * Iterable over the current checkpoint history.
+	 *
+	 * <p>The iteration order is in reverse insertion order.
+	 */
+	private static class CheckpointsStatsHistoryIterable implements Iterable<AbstractCheckpointStats> {
+
+		/** Copy of the checkpointsArray array at the point when this iterable was created. */
+		private final AbstractCheckpointStats[] checkpointsArray;
+
+		/** The starting position from which to iterate over the array. */
+		private final int startPos;
+
+		/**
+		 * Creates the iterable by creating a copy of the checkpoints array.
+		 *
+		 * @param checkpointsArray Checkpoints to iterate over. This array is copied.
+		 * @param nextPos The next write position for the array
+		 */
+		CheckpointsStatsHistoryIterable(AbstractCheckpointStats[] checkpointsArray, int nextPos) {
+			// Copy the array
+			this.checkpointsArray = Arrays.copyOf(checkpointsArray, checkpointsArray.length);
+
+			// Start from nextPos, because that's were the oldest element is
+			this.startPos = nextPos == checkpointsArray.length ? checkpointsArray.length - 1 : nextPos - 1;
+		}
+
+		@Override
+		public Iterator<AbstractCheckpointStats> iterator() {
+			return new CheckpointsSnapshotIterator();
+		}
+
+		/**
+		 * Iterator over the checkpoints array.
+		 */
+		private class CheckpointsSnapshotIterator implements Iterator<AbstractCheckpointStats> {
+
+			/** The current position. */
+			private int currentPos;
+
+			/** The remaining number of elements to iterate over. */
+			private int remaining;
+
+			/**
+			 * Creates the iterator.
+			 */
+			CheckpointsSnapshotIterator() {
+				this.currentPos = startPos;
+				this.remaining = checkpointsArray.length;
+			}
+
+			@Override
+			public boolean hasNext() {
+				return remaining > 0;
+			}
+
+			@Override
+			public AbstractCheckpointStats next() {
+				if (hasNext()) {
+					AbstractCheckpointStats stats = checkpointsArray[currentPos--];
+
+					// Wrap around if needed
+					if (currentPos == -1) {
+						currentPos = checkpointsArray.length - 1;
+					}
+
+					remaining--;
+
+					return stats;
+				} else {
+					throw new NoSuchElementException();
+				}
+			}
+
+			@Override
+			public void remove() {
+				throw new UnsupportedOperationException();
+			}
+		}
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointStatsSnapshot.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointStatsSnapshot.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointStatsSnapshot.java
new file mode 100644
index 0000000..e0bfed7
--- /dev/null
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointStatsSnapshot.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.checkpoint;
+
+import javax.annotation.Nullable;
+
+import java.io.Serializable;
+
+import static org.apache.flink.util.Preconditions.checkNotNull;
+
+/**
+ * A snapshot of the checkpoint stats.
+ */
+public class CheckpointStatsSnapshot implements Serializable {
+
+	private static final long serialVersionUID = 8914278419087217964L;
+
+	/** Snapshot of the checkpoint counts. */
+	private final CheckpointStatsCounts counts;
+
+	/** Snapshot of the completed checkpoints summary stats. */
+	private final CompletedCheckpointStatsSummary summary;
+
+	/** Snapshot of the checkpoint history. */
+	private final CheckpointStatsHistory history;
+
+	/** The latest restored checkpoint operation. */
+	@Nullable
+	private final RestoredCheckpointStats latestRestoredCheckpoint;
+
+	/**
+	 * Creates a stats snapshot.
+	 *
+	 * @param counts Snapshot of the checkpoint counts.
+	 * @param summary Snapshot of the completed checkpoints summary stats.
+	 * @param history Snapshot of the checkpoint history.
+	 * @param latestRestoredCheckpoint The latest restored checkpoint operation.
+	 */
+	CheckpointStatsSnapshot(
+			CheckpointStatsCounts counts,
+			CompletedCheckpointStatsSummary summary,
+			CheckpointStatsHistory history,
+			@Nullable RestoredCheckpointStats latestRestoredCheckpoint) {
+
+		this.counts = checkNotNull(counts);
+		this.summary= checkNotNull(summary);
+		this.history = checkNotNull(history);
+		this.latestRestoredCheckpoint = latestRestoredCheckpoint;
+	}
+
+	/**
+	 * Returns the snapshotted checkpoint counts.
+	 *
+	 * @return Snapshotted checkpoint counts.
+	 */
+	public CheckpointStatsCounts getCounts() {
+		return counts;
+	}
+
+	/**
+	 * Returns the snapshotted completed checkpoint summary stats.
+	 *
+	 * @return Snapshotted completed checkpoint summary stats.
+	 */
+	public CompletedCheckpointStatsSummary getSummaryStats() {
+		return summary;
+	}
+
+	/**
+	 * Returns the snapshotted checkpoint history.
+	 *
+	 * @return Snapshotted checkpoint history.
+	 */
+	public CheckpointStatsHistory getHistory() {
+		return history;
+	}
+
+	/**
+	 * Returns the latest restored checkpoint.
+	 *
+	 * @return Latest restored checkpoint or <code>null</code>.
+	 */
+	public RestoredCheckpointStats getLatestRestoredCheckpoint() {
+		return latestRestoredCheckpoint;
+	}
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointStatsStatus.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointStatsStatus.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointStatsStatus.java
new file mode 100644
index 0000000..670a6e4
--- /dev/null
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointStatsStatus.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.checkpoint;
+
+/**
+ * Status of the tracked checkpoint.
+ */
+public enum CheckpointStatsStatus {
+
+	/** Checkpoint that is still in progress. */
+	IN_PROGRESS,
+
+	/** Checkpoint that has successfully completed. */
+	COMPLETED,
+
+	/** Checkpoint that failed. */
+	FAILED;
+
+	/**
+	 * Returns whether the checkpoint is in progress.
+	 *
+	 * @return <code>true</code> if checkpoint is in progress, <code>false</code> otherwise.
+	 */
+	public boolean isInProgress() {
+		return this == IN_PROGRESS;
+	}
+
+	/**
+	 * Returns whether the checkpoint has completed successfully.
+	 *
+	 * @return <code>true</code> if checkpoint has completed, <code>false</code> otherwise.
+	 */
+	public boolean isCompleted() {
+		return this == COMPLETED;
+	}
+
+	/**
+	 * Returns whether the checkpoint has failed.
+	 *
+	 * @return <code>true</code> if checkpoint has failed, <code>false</code> otherwise.
+	 */
+	public boolean isFailed() {
+		return this == FAILED;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointStatsTracker.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointStatsTracker.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointStatsTracker.java
new file mode 100644
index 0000000..92f707f
--- /dev/null
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CheckpointStatsTracker.java
@@ -0,0 +1,447 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.checkpoint;
+
+import org.apache.flink.annotation.VisibleForTesting;
+import org.apache.flink.metrics.Gauge;
+import org.apache.flink.metrics.Metric;
+import org.apache.flink.metrics.MetricGroup;
+import org.apache.flink.runtime.executiongraph.ExecutionJobVertex;
+import org.apache.flink.runtime.jobgraph.JobVertexID;
+import org.apache.flink.runtime.jobgraph.tasks.JobSnapshottingSettings;
+
+import javax.annotation.Nullable;
+import java.io.Serializable;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.ReentrantLock;
+
+import static org.apache.flink.util.Preconditions.checkArgument;
+import static org.apache.flink.util.Preconditions.checkNotNull;
+
+/**
+ * Tracker for checkpoint statistics.
+ *
+ * <p>This is tightly integrated with the {@link CheckpointCoordinator} in
+ * order to ease the gathering of fine-grained statistics.
+ *
+ * <p>The tracked stats include summary counts, a detailed history of recent
+ * and in progress checkpoints as well as summaries about the size, duration
+ * and more of recent checkpoints.
+ *
+ * <p>Data is gathered via callbacks in the {@link CheckpointCoordinator} and
+ * related classes like {@link PendingCheckpoint} and {@link CompletedCheckpoint},
+ * which receive the raw stats data in the first place.
+ *
+ * <p>The statistics are accessed via {@link #createSnapshot()} and exposed via
+ * both the web frontend and the {@link Metric} system.
+ */
+public class CheckpointStatsTracker implements Serializable {
+
+	private static final long serialVersionUID = 1694085244807339288L;
+
+	/**
+	 * Lock used to update stats and creating snapshots. Updates always happen
+	 * from a single Thread at a time and there can be multiple concurrent read
+	 * accesses to the latest stats snapshot.
+	 *
+	 * Currently, writes are executed by whatever Thread executes the coordinator
+	 * actions (which already happens in locked scope). Reads can come from
+	 * multiple concurrent Netty event loop Threads of the web runtime monitor.
+	 */
+	private final ReentrantLock statsReadWriteLock = new ReentrantLock();
+
+	/** The job vertices taking part in the checkpoints. */
+	private final List<ExecutionJobVertex> jobVertices;
+
+	/** Total number of subtasks to checkpoint. */
+	private final int totalSubtaskCount;
+
+	/** Snapshotting settings created from the CheckpointConfig. */
+	private final JobSnapshottingSettings jobSnapshottingSettings;
+
+	/** Checkpoint counts. */
+	private final CheckpointStatsCounts counts = new CheckpointStatsCounts();
+
+	/** A summary of the completed checkpoint stats. */
+	private final CompletedCheckpointStatsSummary summary = new CompletedCheckpointStatsSummary();
+
+	/** History of checkpoints. */
+	private final CheckpointStatsHistory history;
+
+	/** The latest restored checkpoint. */
+	@Nullable
+	private RestoredCheckpointStats latestRestoredCheckpoint;
+
+	/** Latest created snapshot. */
+	private volatile CheckpointStatsSnapshot latestSnapshot;
+
+	/**
+	 * Flag indicating whether a new snapshot needs to be created. This is true
+	 * if a new checkpoint was triggered or updated (completed successfully or
+	 * failed).
+	 */
+	private volatile boolean dirty;
+
+	/**
+	 * Creates a new checkpoint stats tracker.
+	 *
+	 * @param numRememberedCheckpoints Maximum number of checkpoints to remember, including in progress ones.
+	 * @param jobVertices Job vertices involved in the checkpoints.
+	 * @param jobSnapshottingSettings Snapshotting settings created from the CheckpointConfig.
+	 * @param metricGroup Metric group for exposed metrics
+	 */
+	public CheckpointStatsTracker(
+		int numRememberedCheckpoints,
+		List<ExecutionJobVertex> jobVertices,
+		JobSnapshottingSettings jobSnapshottingSettings,
+		MetricGroup metricGroup) {
+
+		checkArgument(numRememberedCheckpoints >= 0, "Negative number of remembered checkpoints");
+		this.history = new CheckpointStatsHistory(numRememberedCheckpoints);
+		this.jobVertices = checkNotNull(jobVertices, "JobVertices");
+		this.jobSnapshottingSettings = checkNotNull(jobSnapshottingSettings);
+
+		// Compute the total subtask count. We do this here in order to only
+		// do it once.
+		int count = 0;
+		for (ExecutionJobVertex vertex : jobVertices) {
+			count += vertex.getParallelism();
+		}
+		this.totalSubtaskCount = count;
+
+		// Latest snapshot is empty
+		latestSnapshot = new CheckpointStatsSnapshot(
+			counts.createSnapshot(),
+			summary.createSnapshot(),
+			history.createSnapshot(),
+			null);
+
+		// Register the metrics
+		registerMetrics(metricGroup);
+	}
+
+	/**
+	 * Returns the job's snapshotting settings which are derived from the
+	 * CheckpointConfig.
+	 *
+	 * @return The job's snapshotting settings.
+	 */
+	public JobSnapshottingSettings getSnapshottingSettings() {
+		return jobSnapshottingSettings;
+	}
+
+	/**
+	 * Creates a new snapshot of the available stats.
+	 *
+	 * @return The latest statistics snapshot.
+	 */
+	public CheckpointStatsSnapshot createSnapshot() {
+		CheckpointStatsSnapshot snapshot = latestSnapshot;
+
+		// Only create a new snapshot if dirty and no update in progress,
+		// because we don't want to block the coordinator.
+		if (dirty && statsReadWriteLock.tryLock()) {
+			try {
+				// Create a new snapshot
+				snapshot = new CheckpointStatsSnapshot(
+					counts.createSnapshot(),
+					summary.createSnapshot(),
+					history.createSnapshot(),
+					latestRestoredCheckpoint);
+
+				latestSnapshot = snapshot;
+
+				dirty = false;
+			} finally {
+				statsReadWriteLock.unlock();
+			}
+		}
+
+		return snapshot;
+	}
+
+	// ------------------------------------------------------------------------
+	// Callbacks
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Creates a new pending checkpoint tracker.
+	 *
+	 * @param checkpointId ID of the checkpoint.
+	 * @param triggerTimestamp Trigger timestamp of the checkpoint.
+	 * @param props The checkpoint properties.
+	 * @return Tracker for statistics gathering.
+	 */
+	PendingCheckpointStats reportPendingCheckpoint(
+			long checkpointId,
+			long triggerTimestamp,
+			CheckpointProperties props) {
+
+		ConcurrentHashMap<JobVertexID, TaskStateStats> taskStateStats = createEmptyTaskStateStatsMap();
+
+		PendingCheckpointStats pending = new PendingCheckpointStats(
+				checkpointId,
+				triggerTimestamp,
+				props,
+				totalSubtaskCount,
+				taskStateStats,
+				new PendingCheckpointStatsCallback());
+
+		statsReadWriteLock.lock();
+		try {
+			counts.incrementInProgressCheckpoints();
+			history.addInProgressCheckpoint(pending);
+
+			dirty = true;
+		} finally {
+			statsReadWriteLock.unlock();
+		}
+
+		return pending;
+	}
+
+	void reportRestoredCheckpoint(RestoredCheckpointStats restored) {
+		checkNotNull(restored, "Restored checkpoint");
+
+		statsReadWriteLock.lock();
+		try {
+			counts.incrementRestoredCheckpoints();
+			latestRestoredCheckpoint = restored;
+
+			dirty = true;
+		} finally {
+			statsReadWriteLock.unlock();
+		}
+	}
+
+	/**
+	 * Callback when a checkpoint completes.
+	 *
+	 * @param completed The completed checkpoint stats.
+	 */
+	private void reportCompletedCheckpoint(CompletedCheckpointStats completed) {
+		statsReadWriteLock.lock();
+		try {
+			counts.incrementCompletedCheckpoints();
+			history.replacePendingCheckpointById(completed);
+
+			summary.updateSummary(completed);
+
+			dirty = true;
+		} finally {
+			statsReadWriteLock.unlock();
+		}
+	}
+
+	/**
+	 * Callback when a checkpoint fails.
+	 *
+	 * @param failed The failed checkpoint stats.
+	 */
+	private void reportFailedCheckpoint(FailedCheckpointStats failed) {
+		statsReadWriteLock.lock();
+		try {
+			counts.incrementFailedCheckpoints();
+			history.replacePendingCheckpointById(failed);
+
+			dirty = true;
+		} finally {
+			statsReadWriteLock.unlock();
+		}
+	}
+
+	/**
+	 * Creates an empty map with a {@link TaskStateStats} instance per task
+	 * that is involved in the checkpoint.
+	 *
+	 * @return An empty map with an {@link TaskStateStats} entry for each task that is involved in the checkpoint.
+	 */
+	private ConcurrentHashMap<JobVertexID, TaskStateStats> createEmptyTaskStateStatsMap() {
+		ConcurrentHashMap<JobVertexID, TaskStateStats> taskStatsMap = new ConcurrentHashMap<>(jobVertices.size());
+		for (ExecutionJobVertex vertex : jobVertices) {
+			TaskStateStats taskStats = new TaskStateStats(vertex.getJobVertexId(), vertex.getParallelism());
+			taskStatsMap.put(vertex.getJobVertexId(), taskStats);
+		}
+		return taskStatsMap;
+	}
+
+	/**
+	 * Callback for finalization of a pending checkpoint.
+	 */
+	class PendingCheckpointStatsCallback {
+
+		/**
+		 * Report a completed checkpoint.
+		 *
+		 * @param completed The completed checkpoint.
+		 */
+		void reportCompletedCheckpoint(CompletedCheckpointStats completed) {
+			CheckpointStatsTracker.this.reportCompletedCheckpoint(completed);
+		}
+
+		/**
+		 * Report a failed checkpoint.
+		 *
+		 * @param failed The failed checkpoint.
+		 */
+		void reportFailedCheckpoint(FailedCheckpointStats failed) {
+			CheckpointStatsTracker.this.reportFailedCheckpoint(failed);
+		}
+
+	}
+
+	// ------------------------------------------------------------------------
+	// Metrics
+	// ------------------------------------------------------------------------
+
+	@VisibleForTesting
+	static final String NUMBER_OF_CHECKPOINTS_METRIC = "totalNumberOfCheckpoints";
+
+	@VisibleForTesting
+	static final String NUMBER_OF_IN_PROGRESS_CHECKPOINTS_METRIC = "numberOfInProgressCheckpoints";
+
+	@VisibleForTesting
+	static final String NUMBER_OF_COMPLETED_CHECKPOINTS_METRIC = "numberOfCompletedCheckpoints";
+
+	@VisibleForTesting
+	static final String NUMBER_OF_FAILED_CHECKPOINTS_METRIC = "numberOfFailedCheckpoints";
+
+	@VisibleForTesting
+	static final String LATEST_RESTORED_CHECKPOINT_TIMESTAMP_METRIC = "lastCheckpointRestoreTimestamp";
+
+	@VisibleForTesting
+	static final String LATEST_COMPLETED_CHECKPOINT_SIZE_METRIC = "lastCheckpointSize";
+
+	@VisibleForTesting
+	static final String LATEST_COMPLETED_CHECKPOINT_DURATION_METRIC = "lastCheckpointDuration";
+
+	@VisibleForTesting
+	static final String LATEST_COMPLETED_CHECKPOINT_ALIGNMENT_BUFFERED_METRIC = "lastCheckpointAlignmentBuffered";
+
+	@VisibleForTesting
+	static final String LATEST_COMPLETED_CHECKPOINT_EXTERNAL_PATH_METRIC = "lastCheckpointExternalPath";
+
+	/**
+	 * Register the exposed metrics.
+	 *
+	 * @param metricGroup Metric group to use for the metrics.
+	 */
+	private void registerMetrics(MetricGroup metricGroup) {
+		metricGroup.gauge(NUMBER_OF_CHECKPOINTS_METRIC, new CheckpointsCounter());
+		metricGroup.gauge(NUMBER_OF_IN_PROGRESS_CHECKPOINTS_METRIC, new InProgressCheckpointsCounter());
+		metricGroup.gauge(NUMBER_OF_COMPLETED_CHECKPOINTS_METRIC, new CompletedCheckpointsCounter());
+		metricGroup.gauge(NUMBER_OF_FAILED_CHECKPOINTS_METRIC, new FailedCheckpointsCounter());
+		metricGroup.gauge(LATEST_RESTORED_CHECKPOINT_TIMESTAMP_METRIC, new LatestRestoredCheckpointTimestampGauge());
+		metricGroup.gauge(LATEST_COMPLETED_CHECKPOINT_SIZE_METRIC, new LatestCompletedCheckpointSizeGauge());
+		metricGroup.gauge(LATEST_COMPLETED_CHECKPOINT_DURATION_METRIC, new LatestCompletedCheckpointDurationGauge());
+		metricGroup.gauge(LATEST_COMPLETED_CHECKPOINT_ALIGNMENT_BUFFERED_METRIC, new LatestCompletedCheckpointAlignmentBufferedGauge());
+		metricGroup.gauge(LATEST_COMPLETED_CHECKPOINT_EXTERNAL_PATH_METRIC, new LatestCompletedCheckpointExternalPathGauge());
+	}
+
+	private class CheckpointsCounter implements Gauge<Long> {
+		@Override
+		public Long getValue() {
+			return counts.getTotalNumberOfCheckpoints();
+		}
+	}
+
+	private class InProgressCheckpointsCounter implements Gauge<Integer> {
+		@Override
+		public Integer getValue() {
+			return counts.getNumberOfInProgressCheckpoints();
+		}
+	}
+
+	private class CompletedCheckpointsCounter implements Gauge<Long> {
+		@Override
+		public Long getValue() {
+			return counts.getNumberOfCompletedCheckpoints();
+		}
+	}
+
+	private class FailedCheckpointsCounter implements Gauge<Long> {
+		@Override
+		public Long getValue() {
+			return counts.getNumberOfFailedCheckpoints();
+		}
+	}
+
+	private class LatestRestoredCheckpointTimestampGauge implements Gauge<Long> {
+		@Override
+		public Long getValue() {
+			RestoredCheckpointStats restored = latestRestoredCheckpoint;
+			if (restored != null) {
+				return restored.getRestoreTimestamp();
+			} else {
+				return -1L;
+			}
+		}
+	}
+
+	private class LatestCompletedCheckpointSizeGauge implements Gauge<Long> {
+		@Override
+		public Long getValue() {
+			CompletedCheckpointStats completed = latestSnapshot.getHistory().getLatestCompletedCheckpoint();
+			if (completed != null) {
+				return completed.getStateSize();
+			} else {
+				return -1L;
+			}
+		}
+	}
+
+	private class LatestCompletedCheckpointDurationGauge implements Gauge<Long> {
+		@Override
+		public Long getValue() {
+			CompletedCheckpointStats completed = latestSnapshot.getHistory().getLatestCompletedCheckpoint();
+			if (completed != null) {
+				return completed.getEndToEndDuration();
+			} else {
+				return -1L;
+			}
+		}
+	}
+
+
+	private class LatestCompletedCheckpointAlignmentBufferedGauge implements Gauge<Long> {
+		@Override
+		public Long getValue() {
+			CompletedCheckpointStats completed = latestSnapshot.getHistory().getLatestCompletedCheckpoint();
+			if (completed != null) {
+				return completed.getAlignmentBuffered();
+			} else {
+				return -1L;
+			}
+		}
+	}
+
+	private class LatestCompletedCheckpointExternalPathGauge implements Gauge<String> {
+		@Override
+		public String getValue() {
+			CompletedCheckpointStats completed = latestSnapshot.getHistory().getLatestCompletedCheckpoint();
+			if (completed != null) {
+				return completed.getExternalPath();
+			} else {
+				return "n/a";
+			}
+		}
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CompletedCheckpoint.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CompletedCheckpoint.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CompletedCheckpoint.java
index 0e70b1a..52f2a6a 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CompletedCheckpoint.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CompletedCheckpoint.java
@@ -26,6 +26,7 @@ import org.apache.flink.runtime.state.StateUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.annotation.Nullable;
 import java.io.Serializable;
 import java.util.Map;
 import java.util.Objects;
@@ -62,6 +63,10 @@ public class CompletedCheckpoint implements Serializable {
 	/** External path if persisted checkpoint; <code>null</code> otherwise. */
 	private final String externalPath;
 
+	/** Optional stats tracker callback for discard. */
+	@Nullable
+	private transient CompletedCheckpointStats.DiscardCallback discardCallback;
+
 	// ------------------------------------------------------------------------
 
 	public CompletedCheckpoint(
@@ -160,6 +165,10 @@ public class CompletedCheckpoint implements Serializable {
 			StateUtil.bestEffortDiscardAllStateObjects(taskStates.values());
 		} finally {
 			taskStates.clear();
+
+			if (discardCallback != null) {
+				discardCallback.notifyDiscardedCheckpoint();
+			}
 		}
 	}
 
@@ -185,6 +194,15 @@ public class CompletedCheckpoint implements Serializable {
 		return externalPath;
 	}
 
+	/**
+	 * Sets the callback for tracking when this checkpoint is discarded.
+	 *
+	 * @param discardCallback Callback to call when the checkpoint is discarded.
+	 */
+	void setDiscardCallback(@Nullable CompletedCheckpointStats.DiscardCallback discardCallback) {
+		this.discardCallback = discardCallback;
+	}
+
 	// --------------------------------------------------------------------------------------------
 
 	@Override

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CompletedCheckpointStats.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CompletedCheckpointStats.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CompletedCheckpointStats.java
new file mode 100644
index 0000000..4d2d995
--- /dev/null
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CompletedCheckpointStats.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.checkpoint;
+
+import org.apache.flink.runtime.jobgraph.JobVertexID;
+
+import javax.annotation.Nullable;
+import java.util.Map;
+
+import static org.apache.flink.util.Preconditions.checkArgument;
+import static org.apache.flink.util.Preconditions.checkNotNull;
+
+/**
+ * Statistics for a successfully completed checkpoint.
+ *
+ * <p>The reported statistics are immutable except for the discarded flag, which
+ * is updated via the {@link DiscardCallback} and the {@link CompletedCheckpoint}
+ * after an instance of this class has been created.
+ */
+public class CompletedCheckpointStats extends AbstractCheckpointStats {
+
+	/** Callback for the {@link CompletedCheckpoint} instance to notify about discard. */
+	private final DiscardCallback discardCallback;
+
+	/** Total checkpoint state size over all subtasks. */
+	private final long stateSize;
+
+	/** Buffered bytes during alignment over all subtasks. */
+	private final long alignmentBuffered;
+
+	/** The latest acknowledged subtask stats. */
+	private final SubtaskStateStats latestAcknowledgedSubtask;
+
+	/** Optional external path if persisted externally. */
+	@Nullable
+	private final String externalPath;
+
+	/** Flag indicating whether the checkpoint was discarded. */
+	private volatile boolean discarded;
+
+	/**
+	 * Creates a tracker for a {@link CompletedCheckpoint}.
+	 *
+	 * @param checkpointId ID of the checkpoint.
+	 * @param triggerTimestamp Timestamp when the checkpoint was triggered.
+	 * @param props Checkpoint properties of the checkpoint.
+	 * @param totalSubtaskCount Total number of subtasks for the checkpoint.
+	 * @param taskStats Task stats for each involved operator.
+	 * @param numAcknowledgedSubtasks Number of acknowledged subtasks.
+	 * @param stateSize Total checkpoint state size over all subtasks.
+	 * @param alignmentBuffered Buffered bytes during alignment over all subtasks.
+	 * @param latestAcknowledgedSubtask The latest acknowledged subtask stats.
+	 * @param externalPath Optional external path if persisted externally.
+	 */
+	CompletedCheckpointStats(
+		long checkpointId,
+		long triggerTimestamp,
+		CheckpointProperties props,
+		int totalSubtaskCount,
+		Map<JobVertexID, TaskStateStats> taskStats,
+		int numAcknowledgedSubtasks,
+		long stateSize,
+		long alignmentBuffered,
+		SubtaskStateStats latestAcknowledgedSubtask,
+		@Nullable String externalPath) {
+
+		super(checkpointId, triggerTimestamp, props, totalSubtaskCount, taskStats);
+		checkArgument(numAcknowledgedSubtasks == totalSubtaskCount, "Did not acknowledge all subtasks.");
+		checkArgument(stateSize >= 0, "Negative state size");
+		this.stateSize = stateSize;
+		this.alignmentBuffered = alignmentBuffered;
+		this.latestAcknowledgedSubtask = checkNotNull(latestAcknowledgedSubtask);
+		this.externalPath = externalPath;
+		this.discardCallback = new DiscardCallback();
+	}
+
+	@Override
+	public CheckpointStatsStatus getStatus() {
+		return CheckpointStatsStatus.COMPLETED;
+	}
+
+	@Override
+	public int getNumberOfAcknowledgedSubtasks() {
+		return numberOfSubtasks;
+	}
+
+	@Override
+	public long getStateSize() {
+		return stateSize;
+	}
+
+	@Override
+	public long getAlignmentBuffered() {
+		return alignmentBuffered;
+	}
+
+	@Override
+	@Nullable
+	public SubtaskStateStats getLatestAcknowledgedSubtaskStats() {
+		return latestAcknowledgedSubtask;
+	}
+
+	// ------------------------------------------------------------------------
+	// Completed checkpoint specific methods
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Returns the external path if this checkpoint was persisted externally.
+	 *
+	 * @return External path of this checkpoint or <code>null</code>.
+	 */
+	@Nullable
+	public String getExternalPath() {
+		return externalPath;
+	}
+
+	/**
+	 * Returns whether the checkpoint has been discarded.
+	 *
+	 * @return <code>true</code> if the checkpoint has been discarded, <code>false</code> otherwise.
+	 */
+	public boolean isDiscarded() {
+		return discarded;
+	}
+
+	/**
+	 * Returns the callback for the {@link CompletedCheckpoint}.
+	 *
+	 * @return Callback for the {@link CompletedCheckpoint}.
+	 */
+	DiscardCallback getDiscardCallback() {
+		return discardCallback;
+	}
+
+	/**
+	 * Callback for the {@link CompletedCheckpoint} instance to notify about
+	 * disposal of the checkpoint (most commonly when the checkpoint has been
+	 * subsumed by a newer one).
+	 */
+	class DiscardCallback {
+
+		/**
+		 * Updates the discarded flag of the checkpoint stats.
+		 *
+		 * <p>After this notification, {@link #isDiscarded()} will return
+		 * <code>true</code>.
+		 */
+		void notifyDiscardedCheckpoint() {
+			discarded = true;
+		}
+
+	}
+
+	@Override
+	public String toString() {
+		return "CompletedCheckpoint(id=" + getCheckpointId() + ")";
+	}
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CompletedCheckpointStatsSummary.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CompletedCheckpointStatsSummary.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CompletedCheckpointStatsSummary.java
new file mode 100644
index 0000000..7e3f4b4
--- /dev/null
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/CompletedCheckpointStatsSummary.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.checkpoint;
+
+import java.io.Serializable;
+
+import static org.apache.flink.util.Preconditions.checkNotNull;
+
+/**
+ * Summary over <strong>all</strong> completed checkpoints.
+ */
+public class CompletedCheckpointStatsSummary implements Serializable {
+
+	private static final long serialVersionUID = 5784360461635814038L;
+
+	/** State size statistics for all completed checkpoints. */
+	private final MinMaxAvgStats stateSize;
+
+	/** Duration statistics for all completed checkpoints. */
+	private final MinMaxAvgStats duration;
+
+	/** Byte buffered during alignment for all completed checkpoints. */
+	private final MinMaxAvgStats alignmentBuffered;
+
+	CompletedCheckpointStatsSummary() {
+		this(new MinMaxAvgStats(), new MinMaxAvgStats(), new MinMaxAvgStats());
+	}
+
+	private CompletedCheckpointStatsSummary(
+			MinMaxAvgStats stateSize,
+			MinMaxAvgStats duration,
+			MinMaxAvgStats alignmentBuffered) {
+
+		this.stateSize = checkNotNull(stateSize);
+		this.duration = checkNotNull(duration);
+		this.alignmentBuffered = checkNotNull(alignmentBuffered);
+	}
+
+	/**
+	 * Updates the summary with the given completed checkpoint.
+	 *
+	 * @param completed Completed checkpoint to update the summary with.
+	 */
+	void updateSummary(CompletedCheckpointStats completed) {
+		stateSize.add(completed.getStateSize());
+		duration.add(completed.getEndToEndDuration());
+		alignmentBuffered.add(completed.getAlignmentBuffered());
+	}
+
+	/**
+	 * Creates a snapshot of the current state.
+	 *
+	 * @return A snapshot of the current state.
+	 */
+	CompletedCheckpointStatsSummary createSnapshot() {
+		return new CompletedCheckpointStatsSummary(
+				stateSize.createSnapshot(),
+				duration.createSnapshot(),
+				alignmentBuffered.createSnapshot());
+	}
+
+	/**
+	 * Returns the summary stats for the state size of completed checkpoints.
+	 *
+	 * @return Summary stats for the state size.
+	 */
+	public MinMaxAvgStats getStateSizeStats() {
+		return stateSize;
+	}
+
+	/**
+	 * Returns the summary stats for the duration of completed checkpoints.
+	 *
+	 * @return Summary stats for the duration.
+	 */
+	public MinMaxAvgStats getEndToEndDurationStats() {
+		return duration;
+	}
+
+	/**
+	 * Returns the summary stats for the bytes buffered during alignment.
+	 *
+	 * <p>If no alignments are reported or happen (at least once mode), the
+	 * returned stats are in their initial state.
+	 *
+	 * @return Summary stats for the bytes buffered during alignment.
+	 */
+	public MinMaxAvgStats getAlignmentBufferedStats() {
+		return alignmentBuffered;
+	}
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/FailedCheckpointStats.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/FailedCheckpointStats.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/FailedCheckpointStats.java
new file mode 100644
index 0000000..83d7c3d
--- /dev/null
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/FailedCheckpointStats.java
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.checkpoint;
+
+import org.apache.flink.runtime.jobgraph.JobVertexID;
+
+import javax.annotation.Nullable;
+import java.util.Map;
+
+import static org.apache.flink.util.Preconditions.checkArgument;
+
+/**
+ * Statistics for a failed checkpoint.
+ *
+ * <p>The reported statistics are immutable.
+ */
+public class FailedCheckpointStats extends AbstractCheckpointStats {
+
+	/** Number of acknowledged tasks. */
+	private final int numAcknowledgedSubtasks;
+
+	/** Total checkpoint state size over all subtasks. */
+	private final long stateSize;
+
+	/** Buffered bytes during alignment over all subtasks. */
+	private final long alignmentBuffered;
+
+	/** Timestamp when the checkpoint was failed at the coordinator. */
+	private final long failureTimestamp;
+
+	/**
+	 * The latest acknowledged subtask stats if any subtask was acknowledged
+	 * before failing the checkpoint.
+	 */
+	@Nullable
+	private final SubtaskStateStats latestAcknowledgedSubtask;
+
+	/** Optional failure message. */
+	@Nullable
+	private final String failureMsg;
+
+	/**
+	 * Creates a tracker for a failed checkpoint.
+	 *
+	 * @param checkpointId ID of the checkpoint.
+	 * @param triggerTimestamp Timestamp when the checkpoint was triggered.
+	 * @param props Checkpoint properties of the checkpoint.
+	 * @param totalSubtaskCount Total number of subtasks for the checkpoint.
+	 * @param taskStats Task stats for each involved operator.
+	 * @param numAcknowledgedSubtasks Number of acknowledged subtasks.
+	 * @param stateSize Total checkpoint state size over all subtasks.
+	 * @param alignmentBuffered Buffered bytes during alignment over all subtasks.
+	 * @param failureTimestamp Timestamp when this checkpoint failed.
+	 * @param latestAcknowledgedSubtask The latest acknowledged subtask stats or <code>null</code>.
+	 * @param cause Cause of the checkpoint failure or <code>null</code>.
+	 */
+	FailedCheckpointStats(
+		long checkpointId,
+		long triggerTimestamp,
+		CheckpointProperties props,
+		int totalSubtaskCount,
+		Map<JobVertexID, TaskStateStats> taskStats,
+		int numAcknowledgedSubtasks,
+		long stateSize,
+		long alignmentBuffered,
+		long failureTimestamp,
+		@Nullable SubtaskStateStats latestAcknowledgedSubtask,
+		@Nullable Throwable cause) {
+
+		super(checkpointId, triggerTimestamp, props, totalSubtaskCount, taskStats);
+		checkArgument(numAcknowledgedSubtasks >= 0, "Negative number of ACKs");
+		this.numAcknowledgedSubtasks = numAcknowledgedSubtasks;
+		checkArgument(stateSize >= 0, "Negative state size");
+		this.stateSize = stateSize;
+		this.alignmentBuffered = alignmentBuffered;
+		this.failureTimestamp = failureTimestamp;
+		this.latestAcknowledgedSubtask = latestAcknowledgedSubtask;
+		this.failureMsg = cause != null ? cause.getMessage() : null;
+	}
+
+	@Override
+	public CheckpointStatsStatus getStatus() {
+		return CheckpointStatsStatus.FAILED;
+	}
+
+	@Override
+	public int getNumberOfAcknowledgedSubtasks() {
+		return numAcknowledgedSubtasks;
+	}
+
+	@Override
+	public long getStateSize() {
+		return stateSize;
+	}
+
+	@Override
+	public long getAlignmentBuffered() {
+		return alignmentBuffered;
+	}
+
+	@Override
+	@Nullable
+	public SubtaskStateStats getLatestAcknowledgedSubtaskStats() {
+		return latestAcknowledgedSubtask;
+	}
+
+	/**
+	 * Returns the end to end duration until the checkpoint failure.
+	 */
+	@Override
+	public long getEndToEndDuration() {
+		return Math.max(0, failureTimestamp - triggerTimestamp);
+	}
+
+	// ------------------------------------------------------------------------
+	// Failed checkpoint specific methods
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Returns the timestamp when this checkpoint failed.
+	 *
+	 * @return Timestamp when the checkpoint failed.
+	 */
+	public long getFailureTimestamp() {
+		return failureTimestamp;
+	}
+
+	/**
+	 * Returns the failure message or <code>null</code> if no cause was provided.
+	 *
+	 * @return Failure message of the checkpoint failure or <code>null</code>.
+	 */
+	@Nullable
+	public String getFailureMessage() {
+		return failureMsg;
+	}
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/MinMaxAvgStats.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/MinMaxAvgStats.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/MinMaxAvgStats.java
new file mode 100644
index 0000000..9d4c116
--- /dev/null
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/MinMaxAvgStats.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.checkpoint;
+
+import java.io.Serializable;
+
+/**
+ * Helper for keeping track of min/max/average summaries.
+ */
+public class MinMaxAvgStats implements Serializable {
+
+	private static final long serialVersionUID = 1769601903483446707L;
+
+	/** Current min value. */
+	private long min;
+
+	/** Current max value. */
+	private long max;
+
+	/** Sum of all added values. */
+	private long sum;
+
+	/** Count of added values. */
+	private long count;
+
+	MinMaxAvgStats() {
+	}
+
+	private MinMaxAvgStats(long min, long max, long sum, long count) {
+		this.min = min;
+		this.max = max;
+		this.sum = sum;
+		this.count = count;
+	}
+
+	/**
+	 * Adds the value to the stats if it is >= 0.
+	 *
+	 * @param value Value to add for min/max/avg stats..
+	 */
+	void add(long value) {
+		if (value >= 0) {
+			if (count > 0) {
+				min =  Math.min(min, value);
+				max = Math.max(max, value);
+			} else {
+				min = value;
+				max = value;
+			}
+
+			count++;
+			sum += value;
+		}
+	}
+
+	/**
+	 * Returns a snapshot of the current state.
+	 *
+	 * @return A snapshot of the current state.
+	 */
+	MinMaxAvgStats createSnapshot() {
+		return new MinMaxAvgStats(min, max, sum, count);
+	}
+
+	/**
+	 * Returns the minimum seen value.
+	 *
+	 * @return The current minimum value.
+	 */
+	public long getMinimum() {
+		return min;
+	}
+
+	/**
+	 * Returns the maximum seen value.
+	 *
+	 * @return The current maximum value.
+	 */
+	public long getMaximum() {
+		return max;
+	}
+
+	/**
+	 * Returns the sum of all seen values.
+	 *
+	 * @return Sum of all values.
+	 */
+	public long getSum() {
+		return sum;
+	}
+
+	/**
+	 * Returns the count of all seen values.
+	 *
+	 * @return Count of all values.
+	 */
+	public long getCount() {
+		return count;
+	}
+
+	/**
+	 * Calculates the average over all seen values.
+	 *
+	 * @return Average over all seen values.
+	 */
+	public long getAverage() {
+		if (count == 0) {
+			return 0;
+		} else {
+			return sum / count;
+		}
+	}
+
+}


[10/11] flink git commit: [FLINK-4410] [runtime] Rework checkpoint stats tracking

Posted by uc...@apache.org.
http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/PendingCheckpoint.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/PendingCheckpoint.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/PendingCheckpoint.java
index e7df5bc..1d97e12 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/PendingCheckpoint.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/PendingCheckpoint.java
@@ -35,6 +35,7 @@ import org.apache.flink.util.Preconditions;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.annotation.Nullable;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -72,9 +73,6 @@ public class PendingCheckpoint {
 	/** Set of acknowledged tasks */
 	private final Set<ExecutionAttemptID> acknowledgedTasks;
 
-	/** Flag indicating whether the checkpoint is triggered as part of periodic scheduling. */
-	private final boolean isPeriodic;
-
 	/**
 	 * The checkpoint properties. If the checkpoint should be persisted
 	 * externally, it happens in {@link #finalizeCheckpoint()}.
@@ -93,6 +91,10 @@ public class PendingCheckpoint {
 
 	private boolean discarded;
 
+	/** Optional stats tracker callback. */
+	@Nullable
+	private PendingCheckpointStats statsCallback;
+
 	// --------------------------------------------------------------------------------------------
 
 	public PendingCheckpoint(
@@ -100,7 +102,6 @@ public class PendingCheckpoint {
 			long checkpointId,
 			long checkpointTimestamp,
 			Map<ExecutionAttemptID, ExecutionVertex> verticesToConfirm,
-			boolean isPeriodic,
 			CheckpointProperties props,
 			String targetDirectory,
 			Executor executor) {
@@ -108,7 +109,6 @@ public class PendingCheckpoint {
 		this.checkpointId = checkpointId;
 		this.checkpointTimestamp = checkpointTimestamp;
 		this.notYetAcknowledgedTasks = checkNotNull(verticesToConfirm);
-		this.isPeriodic = isPeriodic;
 		this.taskStates = new HashMap<>();
 		this.props = checkNotNull(props);
 		this.targetDirectory = targetDirectory;
@@ -163,10 +163,6 @@ public class PendingCheckpoint {
 		return discarded;
 	}
 
-	boolean isPeriodic() {
-		return isPeriodic;
-	}
-
 	/**
 	 * Checks whether this checkpoint can be subsumed or whether it should always continue, regardless
 	 * of newer checkpoints in progress.
@@ -186,6 +182,15 @@ public class PendingCheckpoint {
 		return targetDirectory;
 	}
 
+	/**
+	 * Sets the callback for tracking this pending checkpoint.
+	 *
+	 * @param trackerCallback Callback for collecting subtask stats.
+	 */
+	void setStatsCallback(@Nullable PendingCheckpointStats trackerCallback) {
+		this.statsCallback = checkNotNull(trackerCallback);
+	}
+
 	// ------------------------------------------------------------------------
 	//  Progress and Completion
 	// ------------------------------------------------------------------------
@@ -227,6 +232,13 @@ public class PendingCheckpoint {
 
 			onCompletionPromise.complete(completed);
 
+			if (statsCallback != null) {
+				// Finalize the statsCallback and give the completed checkpoint a
+				// callback for discards.
+				CompletedCheckpointStats.DiscardCallback discardCallback = statsCallback.reportCompletedCheckpoint(externalPath);
+				completed.setDiscardCallback(discardCallback);
+			}
+
 			dispose(false);
 
 			return completed;
@@ -238,14 +250,15 @@ public class PendingCheckpoint {
 	 *
 	 * @param executionAttemptId of the acknowledged task
 	 * @param subtaskState of the acknowledged task
+	 * @param checkpointMetaData Checkpoint meta data
 	 * @return TaskAcknowledgeResult of the operation
 	 */
 	public TaskAcknowledgeResult acknowledgeTask(
 			ExecutionAttemptID executionAttemptId,
-			SubtaskState subtaskState) {
+			SubtaskState subtaskState,
+			CheckpointMetaData checkpointMetaData) {
 
 		synchronized (lock) {
-
 			if (discarded) {
 				return TaskAcknowledgeResult.DISCARDED;
 			}
@@ -262,10 +275,12 @@ public class PendingCheckpoint {
 				acknowledgedTasks.add(executionAttemptId);
 			}
 
-			if (null != subtaskState) {
+			JobVertexID jobVertexID = vertex.getJobvertexId();
+			int subtaskIndex = vertex.getParallelSubtaskIndex();
+			long ackTimestamp = System.currentTimeMillis();
 
-				JobVertexID jobVertexID = vertex.getJobvertexId();
-				int subtaskIndex = vertex.getParallelSubtaskIndex();
+			long stateSize = 0;
+			if (null != subtaskState) {
 				TaskState taskState = taskStates.get(jobVertexID);
 
 				if (null == taskState) {
@@ -292,14 +307,30 @@ public class PendingCheckpoint {
 					taskStates.put(jobVertexID, taskState);
 				}
 
-				long duration = System.currentTimeMillis() - checkpointTimestamp;
-				subtaskState.setDuration(duration);
-
 				taskState.putState(subtaskIndex, subtaskState);
+				stateSize = subtaskState.getStateSize();
 			}
 
 			++numAcknowledgedTasks;
 
+			if (statsCallback != null) {
+				CheckpointMetrics metrics = checkpointMetaData.getMetrics();
+
+				// Do this in millis because the web frontend works with them
+				long alignmentDurationMillis = metrics.getAlignmentDurationNanos() / 1_000_000;
+
+				SubtaskStateStats subtaskStateStats = new SubtaskStateStats(
+					subtaskIndex,
+					ackTimestamp,
+					stateSize,
+					metrics.getSyncDurationMillis(),
+					metrics.getAsyncDurationMillis(),
+					metrics.getBytesBufferedInAlignment(),
+					alignmentDurationMillis);
+
+				statsCallback.reportSubtaskStats(jobVertexID, subtaskStateStats);
+			}
+
 			return TaskAcknowledgeResult.SUCCESS;
 		}
 	}
@@ -323,7 +354,9 @@ public class PendingCheckpoint {
 	 */
 	public void abortExpired() {
 		try {
-			onCompletionPromise.completeExceptionally(new Exception("Checkpoint expired before completing"));
+			Exception cause = new Exception("Checkpoint expired before completing");
+			onCompletionPromise.completeExceptionally(cause);
+			reportFailedCheckpoint(cause);
 		} finally {
 			dispose(true);
 		}
@@ -334,12 +367,12 @@ public class PendingCheckpoint {
 	 */
 	public void abortSubsumed() {
 		try {
-			if (props.forceCheckpoint()) {
-				onCompletionPromise.completeExceptionally(new Exception("Bug: forced checkpoints must never be subsumed"));
+			Exception cause = new Exception("Checkpoints has been subsumed");
+			onCompletionPromise.completeExceptionally(cause);
+			reportFailedCheckpoint(cause);
 
+			if (props.forceCheckpoint()) {
 				throw new IllegalStateException("Bug: forced checkpoints must never be subsumed");
-			} else {
-				onCompletionPromise.completeExceptionally(new Exception("Checkpoints has been subsumed"));
 			}
 		} finally {
 			dispose(true);
@@ -348,7 +381,9 @@ public class PendingCheckpoint {
 
 	public void abortDeclined() {
 		try {
-			onCompletionPromise.completeExceptionally(new Exception("Checkpoint was declined (tasks not ready)"));
+			Exception cause = new Exception("Checkpoint was declined (tasks not ready)");
+			onCompletionPromise.completeExceptionally(cause);
+			reportFailedCheckpoint(cause);
 		} finally {
 			dispose(true);
 		}
@@ -360,7 +395,9 @@ public class PendingCheckpoint {
 	 */
 	public void abortError(Throwable cause) {
 		try {
-			onCompletionPromise.completeExceptionally(new Exception("Checkpoint failed: " + cause.getMessage(), cause));
+			Exception failure = new Exception("Checkpoint failed: " + cause.getMessage(), cause);
+			onCompletionPromise.completeExceptionally(failure);
+			reportFailedCheckpoint(failure);
 		} finally {
 			dispose(true);
 		}
@@ -393,6 +430,18 @@ public class PendingCheckpoint {
 		}
 	}
 
+	/**
+	 * Reports a failed checkpoint with the given optional cause.
+	 *
+	 * @param cause The failure cause or <code>null</code>.
+	 */
+	private void reportFailedCheckpoint(Exception cause) {
+		if (statsCallback != null) {
+			long failureTimestamp = System.currentTimeMillis();
+			statsCallback.reportFailedCheckpoint(failureTimestamp, cause);
+		}
+	}
+
 	// --------------------------------------------------------------------------------------------
 
 	@Override

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/PendingCheckpointStats.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/PendingCheckpointStats.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/PendingCheckpointStats.java
new file mode 100644
index 0000000..e6fa80f
--- /dev/null
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/PendingCheckpointStats.java
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.checkpoint;
+
+import org.apache.flink.runtime.jobgraph.JobVertexID;
+
+import javax.annotation.Nullable;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.apache.flink.util.Preconditions.checkNotNull;
+
+/**
+ * Statistics for a pending checkpoint that is still in progress.
+ *
+ * <p>This is the starting point for all checkpoint tracking. The life cycle
+ * of instances of this class is tightly coupled to a {@link PendingCheckpoint}
+ * instance, which forwards statistics about acknowledged subtasks
+ * via {@link #reportSubtaskStats(JobVertexID, SubtaskStateStats)}.
+ *
+ * <p>Depending on whether the {@link PendingCheckpoint} is finalized
+ * successfully or aborted, we replace ourselves with a {@link CompletedCheckpointStats}
+ * or {@link FailedCheckpointStats} and notify the {@link CheckpointStatsTracker}.
+ *
+ * <p>The statistics gathered here are all live updated.
+ */
+public class PendingCheckpointStats extends AbstractCheckpointStats {
+
+	/** Tracker callback when the pending checkpoint is finalized or aborted. */
+	private final CheckpointStatsTracker.PendingCheckpointStatsCallback trackerCallback;
+
+	/** The current number of acknowledged subtasks. */
+	private volatile int currentNumAcknowledgedSubtasks;
+
+	/** Current checkpoint state size over all collected subtasks. */
+	private volatile long currentStateSize;
+
+	/** Current buffered bytes during alignment over all collected subtasks. */
+	private volatile long currentAlignmentBuffered;
+
+	/** Stats of the latest acknowleged subtask. */
+	private volatile SubtaskStateStats latestAcknowledgedSubtask;
+
+	/**
+	 * Creates a tracker for a {@link PendingCheckpoint}.
+	 *
+	 * @param checkpointId ID of the checkpoint.
+	 * @param triggerTimestamp Timestamp when the checkpoint was triggered.
+	 * @param props Checkpoint properties of the checkpoint.
+	 * @param totalSubtaskCount Total number of subtasks for the checkpoint.
+	 * @param taskStats Task stats for each involved operator.
+	 * @param trackerCallback Callback for the {@link CheckpointStatsTracker}.
+	 */
+	PendingCheckpointStats(
+			long checkpointId,
+			long triggerTimestamp,
+			CheckpointProperties props,
+			int totalSubtaskCount,
+			Map<JobVertexID, TaskStateStats> taskStats,
+			CheckpointStatsTracker.PendingCheckpointStatsCallback trackerCallback) {
+
+		super(checkpointId, triggerTimestamp, props, totalSubtaskCount, taskStats);
+		this.trackerCallback = checkNotNull(trackerCallback);
+	}
+
+	@Override
+	public CheckpointStatsStatus getStatus() {
+		return CheckpointStatsStatus.IN_PROGRESS;
+	}
+
+	@Override
+	public int getNumberOfAcknowledgedSubtasks() {
+		return currentNumAcknowledgedSubtasks;
+	}
+
+	@Override
+	public long getStateSize() {
+		return currentStateSize;
+	}
+
+	@Override
+	public long getAlignmentBuffered() {
+		return currentAlignmentBuffered;
+	}
+
+	@Override
+	public SubtaskStateStats getLatestAcknowledgedSubtaskStats() {
+		return latestAcknowledgedSubtask;
+	}
+
+	// ------------------------------------------------------------------------
+	// Callbacks from the PendingCheckpoint instance
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Reports statistics for a single subtask.
+	 *
+	 * @param jobVertexId ID of the task/operator the subtask belongs to.
+	 * @param subtask The statistics for the subtask.
+	 * @return <code>true</code> if successfully reported or <code>false</code> otherwise.
+	 */
+	boolean reportSubtaskStats(JobVertexID jobVertexId, SubtaskStateStats subtask) {
+		TaskStateStats taskStateStats = taskStats.get(jobVertexId);
+
+		if (taskStateStats != null && taskStateStats.reportSubtaskStats(subtask)) {
+			currentNumAcknowledgedSubtasks++;
+			latestAcknowledgedSubtask = subtask;
+
+			currentStateSize += subtask.getStateSize();
+
+			long alignmentBuffered = subtask.getAlignmentBuffered();
+			if (alignmentBuffered > 0) {
+				currentAlignmentBuffered += alignmentBuffered;
+			}
+
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 * Reports a successfully completed pending checkpoint.
+	 *
+	 * @param externalPath Optional external storage path if checkpoint was externalized.
+	 * @return Callback for the {@link CompletedCheckpoint} instance to notify about disposal.
+	 */
+	CompletedCheckpointStats.DiscardCallback reportCompletedCheckpoint(@Nullable String externalPath) {
+		CompletedCheckpointStats completed = new CompletedCheckpointStats(
+			checkpointId,
+			triggerTimestamp,
+			props,
+			numberOfSubtasks,
+			new HashMap<>(taskStats),
+			currentNumAcknowledgedSubtasks,
+			currentStateSize,
+			currentAlignmentBuffered,
+			latestAcknowledgedSubtask,
+			externalPath);
+
+		trackerCallback.reportCompletedCheckpoint(completed);
+
+		return completed.getDiscardCallback();
+	}
+
+	/**
+	 * Reports a failed pending checkpoint.
+	 *
+	 * @param failureTimestamp Timestamp of the failure.
+	 * @param cause Optional cause of the failure.
+	 */
+	void reportFailedCheckpoint(long failureTimestamp, @Nullable Throwable cause) {
+		FailedCheckpointStats failed = new FailedCheckpointStats(
+			checkpointId,
+			triggerTimestamp,
+			props,
+			numberOfSubtasks,
+			new HashMap<>(taskStats),
+			currentNumAcknowledgedSubtasks,
+			currentStateSize,
+			currentAlignmentBuffered,
+			failureTimestamp,
+			latestAcknowledgedSubtask,
+			cause);
+
+		trackerCallback.reportFailedCheckpoint(failed);
+	}
+
+	@Override
+	public String toString() {
+		return "PendingCheckpoint(id=" + getCheckpointId() + ")";
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/RestoredCheckpointStats.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/RestoredCheckpointStats.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/RestoredCheckpointStats.java
new file mode 100644
index 0000000..c21937a
--- /dev/null
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/RestoredCheckpointStats.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.checkpoint;
+
+import javax.annotation.Nullable;
+
+import java.io.Serializable;
+
+import static org.apache.flink.util.Preconditions.checkNotNull;
+
+/**
+ * Statistics for a restored checkpoint.
+ */
+public class RestoredCheckpointStats implements Serializable {
+
+	private static final long serialVersionUID = 2305815319666360821L;
+
+	/** ID of the restored checkpoint. */
+	private final long checkpointId;
+
+	/** Properties of the restored checkpoint. */
+	private final CheckpointProperties props;
+
+	/** Timestamp when the checkpoint was restored at the coordinator. */
+	private final long restoreTimestamp;
+
+	/** Optional external path. */
+	@Nullable
+	private final String externalPath;
+
+	/**
+	 * Creates a new restored checkpoint stats.
+	 *
+	 * @param checkpointId ID of the checkpoint.
+	 * @param props Checkpoint properties of the checkpoint.
+	 * @param restoreTimestamp Timestamp when the checkpoint was restored.
+	 * @param externalPath Optional external path if persisted externally.
+	 */
+	RestoredCheckpointStats(
+			long checkpointId,
+			CheckpointProperties props,
+			long restoreTimestamp,
+			String externalPath) {
+
+		this.checkpointId = checkpointId;
+		this.props = checkNotNull(props, "Checkpoint Properties");
+		this.restoreTimestamp = restoreTimestamp;
+		this.externalPath = externalPath;
+	}
+
+	/**
+	 * Returns the ID of this checkpoint.
+	 *
+	 * @return ID of this checkpoint.
+	 */
+	public long getCheckpointId() {
+		return checkpointId;
+	}
+
+	/**
+	 * Returns the properties of the restored checkpoint.
+	 *
+	 * @return Properties of the restored checkpoint.
+	 */
+	public CheckpointProperties getProperties() {
+		return props;
+	}
+
+	/**
+	 * Returns the timestamp when the checkpoint was restored.
+	 *
+	 * @return Timestamp when the checkpoint was restored.
+	 */
+	public long getRestoreTimestamp() {
+		return restoreTimestamp;
+	}
+
+	/**
+	 * Returns the external path if this checkpoint was persisted externally.
+	 *
+	 * @return External path of this checkpoint or <code>null</code>.
+	 */
+	@Nullable
+	public String getExternalPath() {
+		return externalPath;
+	}
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/SubtaskState.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/SubtaskState.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/SubtaskState.java
index ca51e1a..1393e32 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/SubtaskState.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/SubtaskState.java
@@ -70,39 +70,19 @@ public class SubtaskState implements StateObject {
 	 */
 	private final long stateSize;
 
-	/**
-	 * The duration of the checkpoint (ack timestamp - trigger timestamp).
-	 */
-	private long duration;
-
 	public SubtaskState(
 			ChainedStateHandle<StreamStateHandle> legacyOperatorState,
 			ChainedStateHandle<OperatorStateHandle> managedOperatorState,
 			ChainedStateHandle<OperatorStateHandle> rawOperatorState,
 			KeyGroupsStateHandle managedKeyedState,
 			KeyGroupsStateHandle rawKeyedState) {
-		this(legacyOperatorState,
-				managedOperatorState,
-				rawOperatorState,
-				managedKeyedState,
-				rawKeyedState,
-				0L);
-	}
-
-	public SubtaskState(
-			ChainedStateHandle<StreamStateHandle> legacyOperatorState,
-			ChainedStateHandle<OperatorStateHandle> managedOperatorState,
-			ChainedStateHandle<OperatorStateHandle> rawOperatorState,
-			KeyGroupsStateHandle managedKeyedState,
-			KeyGroupsStateHandle rawKeyedState,
-			long duration) {
 
 		this.legacyOperatorState = checkNotNull(legacyOperatorState, "State");
 		this.managedOperatorState = managedOperatorState;
 		this.rawOperatorState = rawOperatorState;
 		this.managedKeyedState = managedKeyedState;
 		this.rawKeyedState = rawKeyedState;
-		this.duration = duration;
+
 		try {
 			long calculateStateSize = getSizeNullSafe(legacyOperatorState);
 			calculateStateSize += getSizeNullSafe(managedOperatorState);
@@ -147,10 +127,6 @@ public class SubtaskState implements StateObject {
 		return stateSize;
 	}
 
-	public long getDuration() {
-		return duration;
-	}
-
 	@Override
 	public void discardState() throws Exception {
 		StateUtil.bestEffortDiscardAllStateObjects(
@@ -162,13 +138,8 @@ public class SubtaskState implements StateObject {
 						rawKeyedState));
 	}
 
-	public void setDuration(long duration) {
-		this.duration = duration;
-	}
-
 	// --------------------------------------------------------------------------------------------
 
-
 	@Override
 	public boolean equals(Object o) {
 		if (this == o) {
@@ -183,9 +154,7 @@ public class SubtaskState implements StateObject {
 		if (stateSize != that.stateSize) {
 			return false;
 		}
-		if (duration != that.duration) {
-			return false;
-		}
+
 		if (legacyOperatorState != null ?
 				!legacyOperatorState.equals(that.legacyOperatorState)
 				: that.legacyOperatorState != null) {
@@ -220,7 +189,6 @@ public class SubtaskState implements StateObject {
 		result = 31 * result + (managedKeyedState != null ? managedKeyedState.hashCode() : 0);
 		result = 31 * result + (rawKeyedState != null ? rawKeyedState.hashCode() : 0);
 		result = 31 * result + (int) (stateSize ^ (stateSize >>> 32));
-		result = 31 * result + (int) (duration ^ (duration >>> 32));
 		return result;
 	}
 
@@ -233,7 +201,6 @@ public class SubtaskState implements StateObject {
 				", keyedStateFromBackend=" + managedKeyedState +
 				", keyedStateHandleFromStream=" + rawKeyedState +
 				", stateSize=" + stateSize +
-				", duration=" + duration +
 				'}';
 	}
 }

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/SubtaskStateStats.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/SubtaskStateStats.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/SubtaskStateStats.java
new file mode 100644
index 0000000..3a66032
--- /dev/null
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/SubtaskStateStats.java
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.checkpoint;
+
+import static org.apache.flink.util.Preconditions.checkArgument;
+
+/**
+ * Statistics for a single subtask that is part of a checkpoint.
+ *
+ * <p>Collects data that is spread over different close places:
+ * {@link CheckpointMetaData},
+ * {@link SubtaskState}, and
+ * {@link PendingCheckpoint}.
+ *
+ * <p>This is the smallest immutable unit of the stats.
+ */
+public class SubtaskStateStats {
+	
+	/** Index of this sub task. */
+	private final int subtaskIndex;
+
+	/**
+	 * Timestamp when the ack from this sub task was received at the
+	 * coordinator.
+	 */
+	private final long ackTimestamp;
+
+	/** Size of the checkpointed state at this subtask. */
+	private final long stateSize;
+
+	/** Checkpoint duration at the operator (sync part) in milliseconds. */
+	private final long syncCheckpointDuration;
+
+	/** Checkpoint duration at the operator (async part) in milliseconds. */
+	private final long asyncCheckpointDuration;
+
+	/** Number of buffered bytes during alignment. */
+	private final long alignmentBuffered;
+
+	/** Alignment duration in . */
+	private final long alignmentDuration;
+
+	/**
+	 * Creates the stats for a single subtask.
+	 *
+	 * @param subtaskIndex Index of the subtask.
+	 * @param ackTimestamp Timestamp when the acknowledgement of this subtask was received at the coordinator.
+	 * @param stateSize Size of the checkpointed state at this subtask.
+	 * @param syncCheckpointDuration Checkpoint duration at the task (synchronous part)
+	 * @param asyncCheckpointDuration  Checkpoint duration at the task (asynchronous part)
+	 * @param alignmentBuffered Bytes buffered during stream alignment (for exactly-once only).
+	 * @param alignmentDuration Duration of the stream alignment (for exactly-once only).
+	 */
+	SubtaskStateStats(
+			int subtaskIndex,
+			long ackTimestamp,
+			long stateSize,
+			long syncCheckpointDuration,
+			long asyncCheckpointDuration,
+			long alignmentBuffered,
+			long alignmentDuration) {
+
+		checkArgument(subtaskIndex >= 0, "Negative subtask index");
+		this.subtaskIndex = subtaskIndex;
+		checkArgument(stateSize >= 0, "Negative state size");
+		this.stateSize = stateSize;
+		this.ackTimestamp = ackTimestamp;
+		this.syncCheckpointDuration = syncCheckpointDuration;
+		this.asyncCheckpointDuration = asyncCheckpointDuration;
+		this.alignmentBuffered = alignmentBuffered;
+		this.alignmentDuration = alignmentDuration;
+	}
+
+	/**
+	 * Returns the subtask index.
+	 *
+	 * @return Subtask index.
+	 */
+	public int getSubtaskIndex() {
+		return subtaskIndex;
+	}
+
+	/**
+	 * Returns the size of the checkpointed state at this subtask.
+	 *
+	 * @return Checkpoint state size of the sub task.
+	 */
+	public long getStateSize() {
+		return stateSize;
+	}
+
+	/**
+	 * Returns the timestamp when the acknowledgement of this subtask was
+	 * received at the coordinator.
+	 *
+	 * @return ACK timestamp at the coordinator.
+	 */
+	public long getAckTimestamp() {
+		return ackTimestamp;
+	}
+
+	/**
+	 * Computes the duration since the given trigger timestamp.
+	 *
+	 * <p>If the trigger timestamp is greater than the ACK timestamp, this
+	 * returns <code>0</code>.
+	 *
+	 * @param triggerTimestamp Trigger timestamp of the checkpoint.
+	 * @return Duration since the given trigger timestamp.
+	 */
+	public long getEndToEndDuration(long triggerTimestamp) {
+		return Math.max(0, ackTimestamp - triggerTimestamp);
+	}
+
+	/**
+	 * Returns the duration of the synchronous part of the checkpoint.
+	 *
+	 * <p>Can return <code>-1</code> if the runtime did not report this.
+	 *
+	 * @return Duration of the synchronous part of the checkpoint or <code>-1</code>.
+	 */
+	public long getSyncCheckpointDuration() {
+		return syncCheckpointDuration;
+	}
+
+	/**
+	 * Returns the duration of the asynchronous part of the checkpoint.
+	 *
+	 * <p>Can return <code>-1</code> if the runtime did not report this.
+	 *
+	 * @return Duration of the asynchronous part of the checkpoint or <code>-1</code>.
+	 */
+	public long getAsyncCheckpointDuration() {
+		return asyncCheckpointDuration;
+	}
+
+	/**
+	 * Returns the number of bytes buffered during stream alignment (for
+	 * exactly-once only).
+	 *
+	 * <p>Can return <code>-1</code> if the runtime did not report this.
+	 *
+	 * @return Number of bytes buffered during stream alignment or <code>-1</code>.
+	 */
+	public long getAlignmentBuffered() {
+		return alignmentBuffered;
+	}
+
+	/**
+	 * Returns the duration of the stream alignment (for exactly-once only).
+	 *
+	 * <p>Can return <code>-1</code> if the runtime did not report this.
+	 *
+	 * @return Duration of the stream alignment or <code>-1</code>.
+	 */
+	public long getAlignmentDuration() {
+		return alignmentDuration;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/TaskStateStats.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/TaskStateStats.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/TaskStateStats.java
new file mode 100644
index 0000000..fc118d9
--- /dev/null
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/TaskStateStats.java
@@ -0,0 +1,277 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+package org.apache.flink.runtime.checkpoint;
+
+import org.apache.flink.runtime.jobgraph.JobVertexID;
+
+import javax.annotation.Nullable;
+
+import static org.apache.flink.util.Preconditions.checkArgument;
+import static org.apache.flink.util.Preconditions.checkNotNull;
+
+/**
+ * Statistics for a single task/operator that gathers all statistics of its
+ * subtasks and provides summary statistics about all subtasks.
+ */
+public class TaskStateStats {
+
+	/** ID of the task the stats belong to. */
+	private final JobVertexID jobVertexId;
+
+	/** Stats for each subtask */
+	private final SubtaskStateStats[] subtaskStats;
+
+	/** A summary of the subtask stats. */
+	private final TaskStateStatsSummary summaryStats = new TaskStateStatsSummary();
+
+	/** Number of acknowledged subtasks. */
+	private int numAcknowledgedSubtasks;
+
+	/** The latest acknowledged subtask stats. */
+	@Nullable
+	private SubtaskStateStats latestAckedSubtaskStats;
+
+	TaskStateStats(JobVertexID jobVertexId, int numSubtasks) {
+		this.jobVertexId = checkNotNull(jobVertexId, "JobVertexID");
+		checkArgument(numSubtasks > 0, "Number of subtasks <= 0");
+		this.subtaskStats = new SubtaskStateStats[numSubtasks];
+	}
+
+	/**
+	 * Hands in the stats for a subtask.
+	 *
+	 * @param subtask Stats for the sub task to hand in.
+	 */
+	boolean reportSubtaskStats(SubtaskStateStats subtask) {
+		checkNotNull(subtask, "Subtask stats");
+		int subtaskIndex = subtask.getSubtaskIndex();
+
+		if (subtaskIndex < 0 || subtaskIndex >= subtaskStats.length) {
+			return false;
+		}
+
+		if (subtaskStats[subtaskIndex] == null) {
+			subtaskStats[subtaskIndex] = subtask;
+
+			latestAckedSubtaskStats = subtask;
+			numAcknowledgedSubtasks++;
+
+			summaryStats.updateSummary(subtask);
+
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 * Returns the ID of the operator the statistics belong to.
+	 *
+	 * @return ID of the operator the statistics belong to.
+	 */
+	public JobVertexID getJobVertexId() {
+		return jobVertexId;
+	}
+
+	/**
+	 * Returns the number of subtasks.
+	 *
+	 * @return Number of subtasks.
+	 */
+	public int getNumberOfSubtasks() {
+		return subtaskStats.length;
+	}
+
+	/**
+	 * Returns the number of acknowledged subtasks.
+	 *
+	 * @return Number of acknowledged subtasks.
+	 */
+	public int getNumberOfAcknowledgedSubtasks() {
+		return numAcknowledgedSubtasks;
+	}
+
+	/**
+	 * Returns the latest acknowledged subtask stats or <code>null</code>
+	 * if none was acknowledged yet.
+	 *
+	 * @return The latest acknowledged subtask stats.
+	 */
+	@Nullable
+	public SubtaskStateStats getLatestAcknowledgedSubtaskStats() {
+		return latestAckedSubtaskStats;
+	}
+
+	/**
+	 * Returns the ack timestamp of the latest acknowledged subtask or
+	 * <code>-1</code> if none was acknowledged yet.
+	 *
+	 * @return Ack timestamp of the latest acknowledged subtask or <code>-1</code>.
+	 */
+	public long getLatestAckTimestamp() {
+		SubtaskStateStats subtask = latestAckedSubtaskStats;
+		if (subtask != null) {
+			return subtask.getAckTimestamp();
+		} else {
+			return -1;
+		}
+	}
+
+	/**
+	 * Returns the total checkpoint state size over all subtasks.
+	 *
+	 * @return Total checkpoint state size over all subtasks.
+	 */
+	public long getStateSize() {
+		return summaryStats.getStateSizeStats().getSum();
+	}
+
+	/**
+	 * Returns the total buffered bytes during alignment over all subtasks.
+	 *
+	 * <p>Can return <code>-1</code> if the runtime did not report this.
+	 *
+	 * @return Total buffered bytes during alignment over all subtasks.
+	 */
+	public long getAlignmentBuffered() {
+		return summaryStats.getAlignmentBufferedStats().getSum();
+	}
+
+	/**
+	 * Returns the duration of this checkpoint at the task/operator calculated
+	 * as the time since triggering until the latest acknowledged subtask
+	 * or <code>-1</code> if no subtask was acknowledged yet.
+	 *
+	 * @return Duration of this checkpoint at the task/operator or <code>-1</code> if no subtask was acknowledged yet.
+	 */
+	public long getEndToEndDuration(long triggerTimestamp) {
+		SubtaskStateStats subtask = getLatestAcknowledgedSubtaskStats();
+		if (subtask != null) {
+			return Math.max(0, subtask.getAckTimestamp() - triggerTimestamp);
+		} else {
+			return -1;
+		}
+	}
+
+	/**
+	 * Returns the stats for all subtasks.
+	 *
+	 * <p>Elements of the returned array are <code>null</code> if no stats are
+	 * available yet for the respective subtask.
+	 *
+	 * <p>Note: The returned array must not be modified.
+	 *
+	 * @return Array of subtask stats (elements are <code>null</code> if no stats available yet).
+	 */
+	public SubtaskStateStats[] getSubtaskStats() {
+		return subtaskStats;
+	}
+
+	/**
+	 * Returns the summary of the subtask stats.
+	 *
+	 * @return Summary of the subtask stats.
+	 */
+	public TaskStateStatsSummary getSummaryStats() {
+		return summaryStats;
+	}
+
+	/**
+	 * Summary of the subtask stats of a single task/operator.
+	 */
+	public static class TaskStateStatsSummary {
+
+		private MinMaxAvgStats stateSize = new MinMaxAvgStats();
+		private MinMaxAvgStats ackTimestamp = new MinMaxAvgStats();
+		private MinMaxAvgStats syncCheckpointDuration = new MinMaxAvgStats();
+		private MinMaxAvgStats asyncCheckpointDuration = new MinMaxAvgStats();
+		private MinMaxAvgStats alignmentBuffered = new MinMaxAvgStats();
+		private MinMaxAvgStats alignmentDuration = new MinMaxAvgStats();
+
+		/**
+		 * Updates the summary with the given subtask.
+		 *
+		 * @param subtaskStats Subtask stats to update the summary with.
+		 */
+		void updateSummary(SubtaskStateStats subtaskStats) {
+			stateSize.add(subtaskStats.getStateSize());
+			ackTimestamp.add(subtaskStats.getAckTimestamp());
+			syncCheckpointDuration.add(subtaskStats.getSyncCheckpointDuration());
+			asyncCheckpointDuration.add(subtaskStats.getAsyncCheckpointDuration());
+			alignmentBuffered.add(subtaskStats.getAlignmentBuffered());
+			alignmentDuration.add(subtaskStats.getAlignmentDuration());
+		}
+
+		/**
+		 * Returns the summary stats for the state size.
+		 *
+		 * @return Summary stats for the state size.
+		 */
+		public MinMaxAvgStats getStateSizeStats() {
+			return stateSize;
+		}
+
+		/**
+		 * Returns the summary stats for the ACK timestamps.
+		 *
+		 * @return Summary stats for the state size.
+		 */
+		public MinMaxAvgStats getAckTimestampStats() {
+			return ackTimestamp;
+		}
+
+		/**
+		 * Returns the summary stats for the sync checkpoint duration.
+		 *
+		 * @return Summary stats for the sync checkpoint duration.
+		 */
+		public MinMaxAvgStats getSyncCheckpointDurationStats() {
+			return syncCheckpointDuration;
+		}
+
+		/**
+		 * Returns the summary stats for the async checkpoint duration.
+		 *
+		 * @return Summary stats for the async checkpoint duration.
+		 */
+		public MinMaxAvgStats getAsyncCheckpointDurationStats() {
+			return asyncCheckpointDuration;
+		}
+
+		/**
+		 * Returns the summary stats for the buffered bytes during alignments.
+		 *
+		 * @return Summary stats for the buffered state size during alignment.
+		 */
+		public MinMaxAvgStats getAlignmentBufferedStats() {
+			return alignmentBuffered;
+		}
+
+		/**
+		 * Returns the summary stats for the alignment duration.
+		 *
+		 * @return Summary stats for the duration of the alignment.
+		 */
+		public MinMaxAvgStats getAlignmentDurationStats() {
+			return alignmentDuration;
+		}
+
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/savepoint/SavepointV1Serializer.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/savepoint/SavepointV1Serializer.java b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/savepoint/SavepointV1Serializer.java
index 4d16c13..48324ca 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/savepoint/SavepointV1Serializer.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/checkpoint/savepoint/SavepointV1Serializer.java
@@ -126,7 +126,7 @@ class SavepointV1Serializer implements SavepointSerializer<SavepointV1> {
 
 	private static void serializeSubtaskState(SubtaskState subtaskState, DataOutputStream dos) throws IOException {
 
-		dos.writeLong(subtaskState.getDuration());
+		dos.writeLong(-1);
 
 		ChainedStateHandle<StreamStateHandle> nonPartitionableState = subtaskState.getLegacyOperatorState();
 
@@ -160,12 +160,11 @@ class SavepointV1Serializer implements SavepointSerializer<SavepointV1> {
 
 		KeyGroupsStateHandle keyedStateStream = subtaskState.getRawKeyedState();
 		serializeKeyGroupStateHandle(keyedStateStream, dos);
-
 	}
 
 	private static SubtaskState deserializeSubtaskState(DataInputStream dis) throws IOException {
-
-		long duration = dis.readLong();
+		// Duration field has been removed from SubtaskState
+		long ignoredDuration = dis.readLong();
 
 		int len = dis.readInt();
 		List<StreamStateHandle> nonPartitionableState = new ArrayList<>(len);
@@ -207,8 +206,7 @@ class SavepointV1Serializer implements SavepointSerializer<SavepointV1> {
 				operatorStateBackendChain,
 				operatorStateStreamChain,
 				keyedStateBackend,
-				keyedStateStream,
-				duration);
+				keyedStateStream);
 	}
 
 	private static void serializeKeyGroupStateHandle(

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/AccessExecutionGraph.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/AccessExecutionGraph.java b/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/AccessExecutionGraph.java
index e7fe1b0..3490dc8 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/AccessExecutionGraph.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/AccessExecutionGraph.java
@@ -20,7 +20,7 @@ package org.apache.flink.runtime.executiongraph;
 import org.apache.flink.api.common.JobID;
 import org.apache.flink.runtime.accumulators.StringifiedAccumulatorResult;
 import org.apache.flink.runtime.checkpoint.CheckpointCoordinator;
-import org.apache.flink.runtime.checkpoint.stats.CheckpointStatsTracker;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsTracker;
 import org.apache.flink.api.common.ArchivedExecutionConfig;
 import org.apache.flink.runtime.jobgraph.JobStatus;
 import org.apache.flink.runtime.jobgraph.JobVertexID;

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/AccessExecutionJobVertex.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/AccessExecutionJobVertex.java b/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/AccessExecutionJobVertex.java
index 92af0c8..43b5889 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/AccessExecutionJobVertex.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/AccessExecutionJobVertex.java
@@ -18,10 +18,8 @@
 package org.apache.flink.runtime.executiongraph;
 
 import org.apache.flink.runtime.accumulators.StringifiedAccumulatorResult;
-import org.apache.flink.runtime.checkpoint.stats.OperatorCheckpointStats;
 import org.apache.flink.runtime.execution.ExecutionState;
 import org.apache.flink.runtime.jobgraph.JobVertexID;
-import scala.Option;
 
 /**
  * Common interface for the runtime {@link ExecutionJobVertex} and {@link ArchivedExecutionJobVertex}.
@@ -70,16 +68,10 @@ public interface AccessExecutionJobVertex {
 	ExecutionState getAggregateState();
 
 	/**
-	 * Returns the {@link OperatorCheckpointStats} for this job vertex.
-	 *
-	 * @return checkpoint stats for this job vertex.
-	 */
-	Option<OperatorCheckpointStats> getCheckpointStats();
-
-	/**
 	 * Returns the aggregated user-defined accumulators as strings.
 	 *
 	 * @return aggregated user-defined accumulators as strings.
 	 */
 	StringifiedAccumulatorResult[] getAggregatedUserAccumulatorsStringified();
+
 }

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ArchivedExecutionGraph.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ArchivedExecutionGraph.java b/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ArchivedExecutionGraph.java
index 0bd5319..440ecda 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ArchivedExecutionGraph.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ArchivedExecutionGraph.java
@@ -20,9 +20,8 @@ package org.apache.flink.runtime.executiongraph;
 import org.apache.flink.api.common.ArchivedExecutionConfig;
 import org.apache.flink.api.common.JobID;
 import org.apache.flink.runtime.accumulators.StringifiedAccumulatorResult;
-import org.apache.flink.runtime.checkpoint.ArchivedCheckpointStatsTracker;
 import org.apache.flink.runtime.checkpoint.CheckpointCoordinator;
-import org.apache.flink.runtime.checkpoint.stats.CheckpointStatsTracker;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsTracker;
 import org.apache.flink.runtime.jobgraph.JobStatus;
 import org.apache.flink.runtime.jobgraph.JobVertexID;
 import org.apache.flink.util.SerializedValue;
@@ -77,7 +76,7 @@ public class ArchivedExecutionGraph implements AccessExecutionGraph, Serializabl
 	private final ArchivedExecutionConfig archivedExecutionConfig;
 	private final boolean isStoppable;
 	private final Map<String, SerializedValue<Object>> serializedUserAccumulators;
-	private final ArchivedCheckpointStatsTracker tracker;
+	private final CheckpointStatsTracker tracker;
 
 	public ArchivedExecutionGraph(
 		JobID jobID,
@@ -92,7 +91,7 @@ public class ArchivedExecutionGraph implements AccessExecutionGraph, Serializabl
 		Map<String, SerializedValue<Object>> serializedUserAccumulators,
 		ArchivedExecutionConfig executionConfig,
 		boolean isStoppable,
-		ArchivedCheckpointStatsTracker tracker
+		CheckpointStatsTracker tracker
 	) {
 		this.jobID = jobID;
 		this.jobName = jobName;

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ArchivedExecutionJobVertex.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ArchivedExecutionJobVertex.java b/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ArchivedExecutionJobVertex.java
index e30f45a..c744907 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ArchivedExecutionJobVertex.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ArchivedExecutionJobVertex.java
@@ -18,11 +18,8 @@
 package org.apache.flink.runtime.executiongraph;
 
 import org.apache.flink.runtime.accumulators.StringifiedAccumulatorResult;
-import org.apache.flink.runtime.checkpoint.stats.CheckpointStatsTracker;
-import org.apache.flink.runtime.checkpoint.stats.OperatorCheckpointStats;
 import org.apache.flink.runtime.execution.ExecutionState;
 import org.apache.flink.runtime.jobgraph.JobVertexID;
-import scala.Option;
 
 import java.io.Serializable;
 
@@ -41,7 +38,6 @@ public class ArchivedExecutionJobVertex implements AccessExecutionJobVertex, Ser
 
 	private final int maxParallelism;
 
-	private final Option<OperatorCheckpointStats> checkpointStats;
 	private final StringifiedAccumulatorResult[] archivedUserAccumulators;
 
 	public ArchivedExecutionJobVertex(ExecutionJobVertex jobVertex) {
@@ -56,10 +52,6 @@ public class ArchivedExecutionJobVertex implements AccessExecutionJobVertex, Ser
 		this.name = jobVertex.getJobVertex().getName();
 		this.parallelism = jobVertex.getParallelism();
 		this.maxParallelism = jobVertex.getMaxParallelism();
-		CheckpointStatsTracker tracker = jobVertex.getGraph().getCheckpointStatsTracker();
-		checkpointStats = tracker != null
-			? tracker.getOperatorStats(this.id)
-			: Option.<OperatorCheckpointStats>empty();
 	}
 
 	// --------------------------------------------------------------------------------------------
@@ -106,12 +98,8 @@ public class ArchivedExecutionJobVertex implements AccessExecutionJobVertex, Ser
 	// --------------------------------------------------------------------------------------------
 
 	@Override
-	public Option<OperatorCheckpointStats> getCheckpointStats() {
-		return checkpointStats;
-	}
-
-	@Override
 	public StringifiedAccumulatorResult[] getAggregatedUserAccumulatorsStringified() {
 		return archivedUserAccumulators;
 	}
+
 }

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ExecutionGraph.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ExecutionGraph.java b/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ExecutionGraph.java
index cbb4c7e..058872a 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ExecutionGraph.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ExecutionGraph.java
@@ -18,6 +18,7 @@
 
 package org.apache.flink.runtime.executiongraph;
 
+import org.apache.flink.api.common.Archiveable;
 import org.apache.flink.api.common.ArchivedExecutionConfig;
 import org.apache.flink.api.common.ExecutionConfig;
 import org.apache.flink.api.common.JobID;
@@ -33,16 +34,12 @@ import org.apache.flink.runtime.StoppingException;
 import org.apache.flink.runtime.accumulators.AccumulatorSnapshot;
 import org.apache.flink.runtime.accumulators.StringifiedAccumulatorResult;
 import org.apache.flink.runtime.blob.BlobKey;
-import org.apache.flink.runtime.checkpoint.ArchivedCheckpointStatsTracker;
 import org.apache.flink.runtime.checkpoint.CheckpointCoordinator;
 import org.apache.flink.runtime.checkpoint.CheckpointIDCounter;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsTracker;
 import org.apache.flink.runtime.checkpoint.CompletedCheckpointStore;
-import org.apache.flink.runtime.checkpoint.stats.CheckpointStatsTracker;
-import org.apache.flink.runtime.checkpoint.stats.JobCheckpointStats;
-import org.apache.flink.runtime.checkpoint.stats.OperatorCheckpointStats;
 import org.apache.flink.runtime.execution.ExecutionState;
 import org.apache.flink.runtime.execution.SuppressRestartsException;
-import org.apache.flink.api.common.Archiveable;
 import org.apache.flink.runtime.executiongraph.restart.RestartStrategy;
 import org.apache.flink.runtime.instance.SlotProvider;
 import org.apache.flink.runtime.io.network.partition.ResultPartitionID;
@@ -63,8 +60,6 @@ import org.apache.flink.util.SerializedValue;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import scala.Option;
-
 import java.io.IOException;
 import java.net.URL;
 import java.util.ArrayList;
@@ -77,7 +72,6 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
-import java.util.Objects;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executor;
@@ -369,7 +363,7 @@ public class ExecutionGraph implements AccessExecutionGraph, Archiveable<Archive
 			LOG.error("Error while shutting down checkpointer.");
 		}
 
-		checkpointStatsTracker = Objects.requireNonNull(statsTracker, "Checkpoint stats tracker");
+		checkpointStatsTracker = checkNotNull(statsTracker, "CheckpointStatsTracker");
 
 		// create the coordinator that triggers and commits checkpoints and holds the state
 		checkpointCoordinator = new CheckpointCoordinator(
@@ -385,9 +379,10 @@ public class ExecutionGraph implements AccessExecutionGraph, Archiveable<Archive
 			checkpointIDCounter,
 			checkpointStore,
 			checkpointDir,
-			checkpointStatsTracker,
 			ioExecutor);
 
+		checkpointCoordinator.setCheckpointStatsTracker(checkpointStatsTracker);
+
 		// interval of max long value indicates disable periodic checkpoint,
 		// the CheckpointActivatorDeactivator should be created only if the interval is not max value
 		if (interval != Long.MAX_VALUE) {
@@ -1291,28 +1286,14 @@ public class ExecutionGraph implements AccessExecutionGraph, Archiveable<Archive
 
 	@Override
 	public ArchivedExecutionGraph archive() {
-		Map<JobVertexID, OperatorCheckpointStats> operatorStats = new HashMap<>();
 		Map<JobVertexID, ArchivedExecutionJobVertex> archivedTasks = new HashMap<>();
 		List<ArchivedExecutionJobVertex> archivedVerticesInCreationOrder = new ArrayList<>();
 		for (ExecutionJobVertex task : verticesInCreationOrder) {
 			ArchivedExecutionJobVertex archivedTask = task.archive();
 			archivedVerticesInCreationOrder.add(archivedTask);
 			archivedTasks.put(task.getJobVertexId(), archivedTask);
-			Option<OperatorCheckpointStats> statsOption = task.getCheckpointStats();
-			if (statsOption.isDefined()) {
-				operatorStats.put(task.getJobVertexId(), statsOption.get());
-			}
 		}
 
-		Option<JobCheckpointStats> jobStats;
-		if (getCheckpointStatsTracker() == null) {
-			jobStats = Option.empty();
-		} else {
-			jobStats = getCheckpointStatsTracker().getJobStats();
-		}
-
-		ArchivedCheckpointStatsTracker statsTracker = new ArchivedCheckpointStatsTracker(jobStats, operatorStats);
-
 		Map<String, SerializedValue<Object>> serializedUserAccumulators;
 		try {
 			serializedUserAccumulators = getAccumulatorsSerialized();
@@ -1334,7 +1315,6 @@ public class ExecutionGraph implements AccessExecutionGraph, Archiveable<Archive
 			serializedUserAccumulators,
 			getArchivedExecutionConfig(),
 			isStoppable(),
-			statsTracker
-		);
+			getCheckpointStatsTracker());
 	}
 }

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ExecutionGraphBuilder.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ExecutionGraphBuilder.java b/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ExecutionGraphBuilder.java
index a1d7385..386f202 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ExecutionGraphBuilder.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ExecutionGraphBuilder.java
@@ -28,9 +28,7 @@ import org.apache.flink.runtime.JobException;
 import org.apache.flink.runtime.checkpoint.CheckpointIDCounter;
 import org.apache.flink.runtime.checkpoint.CheckpointRecoveryFactory;
 import org.apache.flink.runtime.checkpoint.CompletedCheckpointStore;
-import org.apache.flink.runtime.checkpoint.stats.CheckpointStatsTracker;
-import org.apache.flink.runtime.checkpoint.stats.DisabledCheckpointStatsTracker;
-import org.apache.flink.runtime.checkpoint.stats.SimpleCheckpointStatsTracker;
+import org.apache.flink.runtime.checkpoint.CheckpointStatsTracker;
 import org.apache.flink.runtime.client.JobExecutionException;
 import org.apache.flink.runtime.client.JobSubmissionException;
 import org.apache.flink.runtime.executiongraph.restart.RestartStrategy;
@@ -176,24 +174,18 @@ public class ExecutionGraphBuilder {
 				throw new JobExecutionException(jobId, "Failed to initialize high-availability checkpoint handler", e);
 			}
 
-			// Checkpoint stats tracker
-			boolean isStatsDisabled = jobManagerConfig.getBoolean(
-					ConfigConstants.JOB_MANAGER_WEB_CHECKPOINTS_DISABLE,
-					ConfigConstants.DEFAULT_JOB_MANAGER_WEB_CHECKPOINTS_DISABLE);
+			// Maximum number of remembered checkpoints
+			int historySize = jobManagerConfig.getInteger(
+					ConfigConstants.JOB_MANAGER_WEB_CHECKPOINTS_HISTORY_SIZE,
+					ConfigConstants.DEFAULT_JOB_MANAGER_WEB_CHECKPOINTS_HISTORY_SIZE);
 
-			CheckpointStatsTracker checkpointStatsTracker;
-			if (isStatsDisabled) {
-				checkpointStatsTracker = new DisabledCheckpointStatsTracker();
-			}
-			else {
-				int historySize = jobManagerConfig.getInteger(
-						ConfigConstants.JOB_MANAGER_WEB_CHECKPOINTS_HISTORY_SIZE,
-						ConfigConstants.DEFAULT_JOB_MANAGER_WEB_CHECKPOINTS_HISTORY_SIZE);
-
-				checkpointStatsTracker = new SimpleCheckpointStatsTracker(historySize, ackVertices, metrics);
-			}
+			CheckpointStatsTracker checkpointStatsTracker = new CheckpointStatsTracker(
+					historySize,
+					ackVertices,
+					snapshotSettings,
+					metrics);
 
-			/** The default directory for externalized checkpoints. */
+			// The default directory for externalized checkpoints
 			String externalizedCheckpointsDir = jobManagerConfig.getString(
 					ConfigConstants.CHECKPOINTS_DIRECTORY_KEY, null);
 

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ExecutionJobVertex.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ExecutionJobVertex.java b/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ExecutionJobVertex.java
index 7f2545c..fbab572 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ExecutionJobVertex.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ExecutionJobVertex.java
@@ -30,8 +30,6 @@ import org.apache.flink.core.io.InputSplitSource;
 import org.apache.flink.core.io.LocatableInputSplit;
 import org.apache.flink.runtime.JobException;
 import org.apache.flink.runtime.accumulators.StringifiedAccumulatorResult;
-import org.apache.flink.runtime.checkpoint.stats.CheckpointStatsTracker;
-import org.apache.flink.runtime.checkpoint.stats.OperatorCheckpointStats;
 import org.apache.flink.runtime.execution.ExecutionState;
 import org.apache.flink.runtime.instance.SlotProvider;
 import org.apache.flink.runtime.jobgraph.IntermediateDataSet;
@@ -47,7 +45,6 @@ import org.apache.flink.runtime.util.SerializableObject;
 import org.apache.flink.util.Preconditions;
 import org.apache.flink.util.SerializedValue;
 import org.slf4j.Logger;
-import scala.Option;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -289,16 +286,6 @@ public class ExecutionJobVertex implements AccessExecutionJobVertex, Archiveable
 		
 		return getAggregateJobVertexState(num, parallelism);
 	}
-	
-	@Override
-	public Option<OperatorCheckpointStats> getCheckpointStats() {
-		CheckpointStatsTracker tracker = getGraph().getCheckpointStatsTracker();
-		if (tracker == null) {
-			return Option.empty();
-		} else {
-			return tracker.getOperatorStats(getJobVertexId());
-		}
-	}
 
 	//---------------------------------------------------------------------------------------------
 	

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/jobgraph/tasks/JobSnapshottingSettings.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/jobgraph/tasks/JobSnapshottingSettings.java b/flink-runtime/src/main/java/org/apache/flink/runtime/jobgraph/tasks/JobSnapshottingSettings.java
index 7d6b36e..561ba89 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/jobgraph/tasks/JobSnapshottingSettings.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/jobgraph/tasks/JobSnapshottingSettings.java
@@ -49,6 +49,15 @@ public class JobSnapshottingSettings implements java.io.Serializable {
 
 	/** Settings for externalized checkpoints. */
 	private final ExternalizedCheckpointSettings externalizedCheckpointSettings;
+
+	/**
+	 * Flag indicating whether exactly once checkpoint mode has been configured.
+	 * If <code>false</code>, at least once mode has been configured. This is
+	 * not a necessary attribute, because the checkpointing mode is only relevant
+	 * for the stream tasks, but we expose it here to forward it to the web runtime
+	 * UI.
+	 */
+	private final boolean isExactlyOnce;
 	
 	public JobSnapshottingSettings(
 			List<JobVertexID> verticesToTrigger,
@@ -58,7 +67,8 @@ public class JobSnapshottingSettings implements java.io.Serializable {
 			long checkpointTimeout,
 			long minPauseBetweenCheckpoints,
 			int maxConcurrentCheckpoints,
-			ExternalizedCheckpointSettings externalizedCheckpointSettings) {
+			ExternalizedCheckpointSettings externalizedCheckpointSettings,
+			boolean isExactlyOnce) {
 
 		// sanity checks
 		if (checkpointInterval < 1 || checkpointTimeout < 1 ||
@@ -74,6 +84,7 @@ public class JobSnapshottingSettings implements java.io.Serializable {
 		this.minPauseBetweenCheckpoints = minPauseBetweenCheckpoints;
 		this.maxConcurrentCheckpoints = maxConcurrentCheckpoints;
 		this.externalizedCheckpointSettings = requireNonNull(externalizedCheckpointSettings);
+		this.isExactlyOnce = isExactlyOnce;
 	}
 	
 	// --------------------------------------------------------------------------------------------
@@ -110,6 +121,10 @@ public class JobSnapshottingSettings implements java.io.Serializable {
 		return externalizedCheckpointSettings;
 	}
 
+	public boolean isExactlyOnce() {
+		return isExactlyOnce;
+	}
+
 	// --------------------------------------------------------------------------------------------
 	
 	@Override

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/main/java/org/apache/flink/runtime/messages/checkpoint/AcknowledgeCheckpoint.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/messages/checkpoint/AcknowledgeCheckpoint.java b/flink-runtime/src/main/java/org/apache/flink/runtime/messages/checkpoint/AcknowledgeCheckpoint.java
index c63bac5..7ec3efa 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/messages/checkpoint/AcknowledgeCheckpoint.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/messages/checkpoint/AcknowledgeCheckpoint.java
@@ -37,7 +37,6 @@ public class AcknowledgeCheckpoint extends AbstractCheckpointMessage implements
 
 	private static final long serialVersionUID = -7606214777192401493L;
 
-
 	private final SubtaskState subtaskState;
 
 	private final CheckpointMetaData checkpointMetaData;
@@ -76,20 +75,8 @@ public class AcknowledgeCheckpoint extends AbstractCheckpointMessage implements
 		return subtaskState;
 	}
 
-	public long getSynchronousDurationMillis() {
-		return checkpointMetaData.getSyncDurationMillis();
-	}
-
-	public long getAsynchronousDurationMillis() {
-		return checkpointMetaData.getAsyncDurationMillis();
-	}
-
-	public long getBytesBufferedInAlignment() {
-		return checkpointMetaData.getBytesBufferedInAlignment();
-	}
-
-	public long getAlignmentDurationNanos() {
-		return checkpointMetaData.getAlignmentDurationNanos();
+	public CheckpointMetaData getCheckpointMetaData() {
+		return checkpointMetaData;
 	}
 
 	// --------------------------------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointCoordinatorTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointCoordinatorTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointCoordinatorTest.java
index 463c2ae..daacbfb 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointCoordinatorTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointCoordinatorTest.java
@@ -24,7 +24,6 @@ import org.apache.flink.api.common.time.Time;
 import org.apache.flink.api.java.tuple.Tuple2;
 import org.apache.flink.core.fs.FSDataInputStream;
 import org.apache.flink.core.fs.Path;
-import org.apache.flink.runtime.checkpoint.stats.DisabledCheckpointStatsTracker;
 import org.apache.flink.runtime.concurrent.Executors;
 import org.apache.flink.runtime.concurrent.Future;
 import org.apache.flink.runtime.execution.ExecutionState;
@@ -83,6 +82,7 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.eq;
@@ -131,7 +131,6 @@ public class CheckpointCoordinatorTest {
 				new StandaloneCheckpointIDCounter(),
 				new StandaloneCompletedCheckpointStore(1),
 				null,
-				new DisabledCheckpointStatsTracker(),
 				Executors.directExecutor());
 
 			// nothing should be happening
@@ -185,7 +184,6 @@ public class CheckpointCoordinatorTest {
 				new StandaloneCheckpointIDCounter(),
 				new StandaloneCompletedCheckpointStore(1),
 				null,
-				new DisabledCheckpointStatsTracker(),
 				Executors.directExecutor());
 
 			// nothing should be happening
@@ -237,7 +235,6 @@ public class CheckpointCoordinatorTest {
 				new StandaloneCheckpointIDCounter(),
 				new StandaloneCompletedCheckpointStore(1),
 				null,
-				new DisabledCheckpointStatsTracker(),
 				Executors.directExecutor());
 
 			// nothing should be happening
@@ -290,7 +287,6 @@ public class CheckpointCoordinatorTest {
 				new StandaloneCheckpointIDCounter(),
 				new StandaloneCompletedCheckpointStore(1),
 				null,
-				new DisabledCheckpointStatsTracker(),
 				Executors.directExecutor());
 
 			assertEquals(0, coord.getNumberOfPendingCheckpoints());
@@ -389,7 +385,6 @@ public class CheckpointCoordinatorTest {
 				new StandaloneCheckpointIDCounter(),
 				new StandaloneCompletedCheckpointStore(1),
 				null,
-				new DisabledCheckpointStatsTracker(),
 				Executors.directExecutor());
 
 			assertEquals(0, coord.getNumberOfPendingCheckpoints());
@@ -507,7 +502,6 @@ public class CheckpointCoordinatorTest {
 				new StandaloneCheckpointIDCounter(),
 				new StandaloneCompletedCheckpointStore(1),
 				null,
-				new DisabledCheckpointStatsTracker(),
 				Executors.directExecutor());
 
 			assertEquals(0, coord.getNumberOfPendingCheckpoints());
@@ -654,7 +648,6 @@ public class CheckpointCoordinatorTest {
 				new StandaloneCheckpointIDCounter(),
 				new StandaloneCompletedCheckpointStore(2),
 				null,
-				new DisabledCheckpointStatsTracker(),
 				Executors.directExecutor());
 
 			assertEquals(0, coord.getNumberOfPendingCheckpoints());
@@ -789,7 +782,6 @@ public class CheckpointCoordinatorTest {
 				new StandaloneCheckpointIDCounter(),
 				new StandaloneCompletedCheckpointStore(10),
 				null,
-				new DisabledCheckpointStatsTracker(),
 				Executors.directExecutor());
 
 			assertEquals(0, coord.getNumberOfPendingCheckpoints());
@@ -911,7 +903,6 @@ public class CheckpointCoordinatorTest {
 				new StandaloneCheckpointIDCounter(),
 				new StandaloneCompletedCheckpointStore(2),
 				null,
-				new DisabledCheckpointStatsTracker(),
 				Executors.directExecutor());
 
 			// trigger a checkpoint, partially acknowledged
@@ -980,7 +971,6 @@ public class CheckpointCoordinatorTest {
 				new StandaloneCheckpointIDCounter(),
 				new StandaloneCompletedCheckpointStore(2),
 				null,
-				new DisabledCheckpointStatsTracker(),
 				Executors.directExecutor());
 
 			assertTrue(coord.triggerCheckpoint(timestamp, false));
@@ -1046,7 +1036,6 @@ public class CheckpointCoordinatorTest {
 			new StandaloneCheckpointIDCounter(),
 			new StandaloneCompletedCheckpointStore(1),
 			null,
-			new DisabledCheckpointStatsTracker(),
 			Executors.directExecutor());
 
 		assertTrue(coord.triggerCheckpoint(timestamp, false));
@@ -1136,28 +1125,28 @@ public class CheckpointCoordinatorTest {
 			final AtomicInteger numCalls = new AtomicInteger();
 
 			final Execution execution = triggerVertex.getCurrentExecutionAttempt();
-			
+
 			doAnswer(new Answer<Void>() {
-				
+
 				private long lastId = -1;
 				private long lastTs = -1;
-				
+
 				@Override
 				public Void answer(InvocationOnMock invocation) throws Throwable {
 					long id = (Long) invocation.getArguments()[0];
 					long ts = (Long) invocation.getArguments()[1];
-					
+
 					assertTrue(id > lastId);
 					assertTrue(ts >= lastTs);
 					assertTrue(ts >= start);
-					
+
 					lastId = id;
 					lastTs = ts;
 					numCalls.incrementAndGet();
 					return null;
 				}
 			}).when(execution).triggerCheckpoint(anyLong(), anyLong());
-			
+
 			CheckpointCoordinator coord = new CheckpointCoordinator(
 				jid,
 				10,        // periodic interval is 10 ms
@@ -1171,22 +1160,21 @@ public class CheckpointCoordinatorTest {
 				new StandaloneCheckpointIDCounter(),
 				new StandaloneCompletedCheckpointStore(2),
 				null,
-				new DisabledCheckpointStatsTracker(),
 				Executors.directExecutor());
 
-			
+
 			coord.startCheckpointScheduler();
-			
+
 			long timeout = System.currentTimeMillis() + 60000;
 			do {
 				Thread.sleep(20);
 			}
 			while (timeout > System.currentTimeMillis() && numCalls.get() < 5);
 			assertTrue(numCalls.get() >= 5);
-			
+
 			coord.stopCheckpointScheduler();
-			
-			
+
+
 			// for 400 ms, no further calls may come.
 			// there may be the case that one trigger was fired and about to
 			// acquire the lock, such that after cancelling it will still do
@@ -1195,7 +1183,7 @@ public class CheckpointCoordinatorTest {
 			Thread.sleep(400);
 			assertTrue(numCallsSoFar == numCalls.get() ||
 					numCallsSoFar+1 == numCalls.get());
-			
+
 			// start another sequence of periodic scheduling
 			numCalls.set(0);
 			coord.startCheckpointScheduler();
@@ -1206,7 +1194,7 @@ public class CheckpointCoordinatorTest {
 			}
 			while (timeout > System.currentTimeMillis() && numCalls.get() < 5);
 			assertTrue(numCalls.get() >= 5);
-			
+
 			coord.stopCheckpointScheduler();
 
 			// for 400 ms, no further calls may come
@@ -1264,7 +1252,6 @@ public class CheckpointCoordinatorTest {
 				new StandaloneCheckpointIDCounter(),
 				new StandaloneCompletedCheckpointStore(2),
 				"dummy-path",
-				new DisabledCheckpointStatsTracker(),
 				Executors.directExecutor());
 
 		try {
@@ -1313,7 +1300,7 @@ public class CheckpointCoordinatorTest {
 	public void testMaxConcurrentAttempts5() {
 		testMaxConcurrentAttempts(5);
 	}
-	
+
 	@Test
 	public void testTriggerAndConfirmSimpleSavepoint() throws Exception {
 		final JobID jid = new JobID();
@@ -1339,7 +1326,6 @@ public class CheckpointCoordinatorTest {
 			new StandaloneCheckpointIDCounter(),
 			new StandaloneCompletedCheckpointStore(1),
 			null,
-			new DisabledCheckpointStatsTracker(),
 			Executors.directExecutor());
 
 		assertEquals(0, coord.getNumberOfPendingCheckpoints());
@@ -1475,7 +1461,6 @@ public class CheckpointCoordinatorTest {
 			counter,
 			new StandaloneCompletedCheckpointStore(10),
 			null,
-			new DisabledCheckpointStatsTracker(),
 			Executors.directExecutor());
 
 		String savepointDir = tmpFolder.newFolder().getAbsolutePath();
@@ -1579,7 +1564,6 @@ public class CheckpointCoordinatorTest {
 				new StandaloneCheckpointIDCounter(),
 				new StandaloneCompletedCheckpointStore(2),
 				null,
-				new DisabledCheckpointStatsTracker(),
 				Executors.directExecutor());
 
 			coord.startCheckpointScheduler();
@@ -1594,15 +1578,15 @@ public class CheckpointCoordinatorTest {
 			}
 			while ((now = System.currentTimeMillis()) < minDuration ||
 					(numCalls.get() < maxConcurrentAttempts && now < timeout));
-			
+
 			assertEquals(maxConcurrentAttempts, numCalls.get());
-			
+
 			verify(triggerVertex.getCurrentExecutionAttempt(), times(maxConcurrentAttempts))
 					.triggerCheckpoint(anyLong(), anyLong());
-			
+
 			// now, once we acknowledge one checkpoint, it should trigger the next one
 			coord.receiveAcknowledgeMessage(new AcknowledgeCheckpoint(jid, ackAttemptID, new CheckpointMetaData(1L, 0L)));
-			
+
 			// this should have immediately triggered a new checkpoint
 			now = System.currentTimeMillis();
 			timeout = now + 60000;
@@ -1612,11 +1596,11 @@ public class CheckpointCoordinatorTest {
 			while (numCalls.get() < maxConcurrentAttempts + 1 && now < timeout);
 
 			assertEquals(maxConcurrentAttempts + 1, numCalls.get());
-			
+
 			// no further checkpoints should happen
 			Thread.sleep(200);
 			assertEquals(maxConcurrentAttempts + 1, numCalls.get());
-			
+
 			coord.shutdown(JobStatus.FINISHED);
 		}
 		catch (Exception e) {
@@ -1653,7 +1637,6 @@ public class CheckpointCoordinatorTest {
 				new StandaloneCheckpointIDCounter(),
 				new StandaloneCompletedCheckpointStore(2),
 				null,
-				new DisabledCheckpointStatsTracker(),
 				Executors.directExecutor());
 
 			coord.startCheckpointScheduler();
@@ -1668,7 +1651,7 @@ public class CheckpointCoordinatorTest {
 			}
 			while ((now = System.currentTimeMillis()) < minDuration ||
 					(coord.getNumberOfPendingCheckpoints() < maxConcurrentAttempts && now < timeout));
-			
+
 			// validate that the pending checkpoints are there
 			assertEquals(maxConcurrentAttempts, coord.getNumberOfPendingCheckpoints());
 			assertNotNull(coord.getPendingCheckpoints().get(1L));
@@ -1684,14 +1667,14 @@ public class CheckpointCoordinatorTest {
 			do {
 				Thread.sleep(20);
 			}
-			while (coord.getPendingCheckpoints().get(4L) == null && 
+			while (coord.getPendingCheckpoints().get(4L) == null &&
 					System.currentTimeMillis() < newTimeout);
-			
+
 			// do the final check
 			assertEquals(maxConcurrentAttempts, coord.getNumberOfPendingCheckpoints());
 			assertNotNull(coord.getPendingCheckpoints().get(3L));
 			assertNotNull(coord.getPendingCheckpoints().get(4L));
-			
+
 			coord.shutdown(JobStatus.FINISHED);
 		}
 		catch (Exception e) {
@@ -1699,7 +1682,7 @@ public class CheckpointCoordinatorTest {
 			fail(e.getMessage());
 		}
 	}
-	
+
 	@Test
 	public void testPeriodicSchedulingWithInactiveTasks() {
 		try {
@@ -1722,7 +1705,7 @@ public class CheckpointCoordinatorTest {
 							return currentState.get();
 						}
 					});
-			
+
 			CheckpointCoordinator coord = new CheckpointCoordinator(
 				jid,
 				10,        // periodic interval is 10 ms
@@ -1736,24 +1719,23 @@ public class CheckpointCoordinatorTest {
 				new StandaloneCheckpointIDCounter(),
 				new StandaloneCompletedCheckpointStore(2),
 				null,
-				new DisabledCheckpointStatsTracker(),
 				Executors.directExecutor());
-			
+
 			coord.startCheckpointScheduler();
 
 			// no checkpoint should have started so far
 			Thread.sleep(200);
 			assertEquals(0, coord.getNumberOfPendingCheckpoints());
-			
+
 			// now move the state to RUNNING
 			currentState.set(ExecutionState.RUNNING);
-			
+
 			// the coordinator should start checkpointing now
 			final long timeout = System.currentTimeMillis() + 10000;
 			do {
 				Thread.sleep(20);
 			}
-			while (System.currentTimeMillis() < timeout && 
+			while (System.currentTimeMillis() < timeout &&
 					coord.getNumberOfPendingCheckpoints() == 0);
 
 			assertTrue(coord.getNumberOfPendingCheckpoints() > 0);
@@ -1789,7 +1771,6 @@ public class CheckpointCoordinatorTest {
 			checkpointIDCounter,
 			new StandaloneCompletedCheckpointStore(2),
 			null,
-			new DisabledCheckpointStatsTracker(),
 			Executors.directExecutor());
 
 		List<Future<CompletedCheckpoint>> savepointFutures = new ArrayList<>();
@@ -1843,7 +1824,6 @@ public class CheckpointCoordinatorTest {
 			new StandaloneCheckpointIDCounter(),
 			new StandaloneCompletedCheckpointStore(2),
 			null,
-			new DisabledCheckpointStatsTracker(),
 			Executors.directExecutor());
 
 		String savepointDir = tmpFolder.newFolder().getAbsolutePath();
@@ -1887,7 +1867,7 @@ public class CheckpointCoordinatorTest {
 		allExecutionVertices.addAll(Arrays.asList(jobVertex1.getTaskVertices()));
 		allExecutionVertices.addAll(Arrays.asList(jobVertex2.getTaskVertices()));
 
-		ExecutionVertex[] arrayExecutionVertices = 
+		ExecutionVertex[] arrayExecutionVertices =
 				allExecutionVertices.toArray(new ExecutionVertex[allExecutionVertices.size()]);
 
 		// set up the coordinator and validate the initial state
@@ -1904,7 +1884,6 @@ public class CheckpointCoordinatorTest {
 			new StandaloneCheckpointIDCounter(),
 			new StandaloneCompletedCheckpointStore(1),
 			null,
-			new DisabledCheckpointStatsTracker(),
 			Executors.directExecutor());
 
 		// trigger the checkpoint
@@ -1922,7 +1901,7 @@ public class CheckpointCoordinatorTest {
 			ChainedStateHandle<OperatorStateHandle> partitionableState = generateChainedPartitionableStateHandle(jobVertexID1, index, 2, 8, false);
 			KeyGroupsStateHandle partitionedKeyGroupState = generateKeyGroupState(jobVertexID1, keyGroupPartitions1.get(index), false);
 
-			SubtaskState checkpointStateHandles = new SubtaskState(nonPartitionedState, partitionableState, null, partitionedKeyGroupState, null, 0);
+			SubtaskState checkpointStateHandles = new SubtaskState(nonPartitionedState, partitionableState, null, partitionedKeyGroupState, null);
 			AcknowledgeCheckpoint acknowledgeCheckpoint = new AcknowledgeCheckpoint(
 					jid,
 					jobVertex1.getTaskVertices()[index].getCurrentExecutionAttempt().getAttemptId(),
@@ -1936,7 +1915,7 @@ public class CheckpointCoordinatorTest {
 			ChainedStateHandle<StreamStateHandle> nonPartitionedState = generateStateForVertex(jobVertexID2, index);
 			ChainedStateHandle<OperatorStateHandle> partitionableState = generateChainedPartitionableStateHandle(jobVertexID2, index, 2, 8, false);
 			KeyGroupsStateHandle partitionedKeyGroupState = generateKeyGroupState(jobVertexID2, keyGroupPartitions2.get(index), false);
-			SubtaskState checkpointStateHandles = new SubtaskState(nonPartitionedState, partitionableState, null, partitionedKeyGroupState, null, 0);
+			SubtaskState checkpointStateHandles = new SubtaskState(nonPartitionedState, partitionableState, null, partitionedKeyGroupState, null);
 			AcknowledgeCheckpoint acknowledgeCheckpoint = new AcknowledgeCheckpoint(
 					jid,
 					jobVertex2.getTaskVertices()[index].getCurrentExecutionAttempt().getAttemptId(),
@@ -2010,7 +1989,6 @@ public class CheckpointCoordinatorTest {
 			new StandaloneCheckpointIDCounter(),
 			new StandaloneCompletedCheckpointStore(1),
 			null,
-			new DisabledCheckpointStatsTracker(),
 			Executors.directExecutor());
 
 		// trigger the checkpoint
@@ -2026,7 +2004,7 @@ public class CheckpointCoordinatorTest {
 		for (int index = 0; index < jobVertex1.getParallelism(); index++) {
 			ChainedStateHandle<StreamStateHandle> valueSizeTuple = generateStateForVertex(jobVertexID1, index);
 			KeyGroupsStateHandle keyGroupState = generateKeyGroupState(jobVertexID1, keyGroupPartitions1.get(index), false);
-			SubtaskState checkpointStateHandles = new SubtaskState(valueSizeTuple, null, null, keyGroupState, null, 0);
+			SubtaskState checkpointStateHandles = new SubtaskState(valueSizeTuple, null, null, keyGroupState, null);
 			AcknowledgeCheckpoint acknowledgeCheckpoint = new AcknowledgeCheckpoint(
 					jid,
 					jobVertex1.getTaskVertices()[index].getCurrentExecutionAttempt().getAttemptId(),
@@ -2040,7 +2018,7 @@ public class CheckpointCoordinatorTest {
 		for (int index = 0; index < jobVertex2.getParallelism(); index++) {
 			ChainedStateHandle<StreamStateHandle> valueSizeTuple = generateStateForVertex(jobVertexID2, index);
 			KeyGroupsStateHandle keyGroupState = generateKeyGroupState(jobVertexID2, keyGroupPartitions2.get(index), false);
-			SubtaskState checkpointStateHandles = new SubtaskState(valueSizeTuple, null, null, keyGroupState, null, 0);
+			SubtaskState checkpointStateHandles = new SubtaskState(valueSizeTuple, null, null, keyGroupState, null);
 			AcknowledgeCheckpoint acknowledgeCheckpoint = new AcknowledgeCheckpoint(
 					jid,
 					jobVertex2.getTaskVertices()[index].getCurrentExecutionAttempt().getAttemptId(),
@@ -2109,7 +2087,7 @@ public class CheckpointCoordinatorTest {
 		allExecutionVertices.addAll(Arrays.asList(jobVertex1.getTaskVertices()));
 		allExecutionVertices.addAll(Arrays.asList(jobVertex2.getTaskVertices()));
 
-		ExecutionVertex[] arrayExecutionVertices = 
+		ExecutionVertex[] arrayExecutionVertices =
 				allExecutionVertices.toArray(new ExecutionVertex[allExecutionVertices.size()]);
 
 		// set up the coordinator and validate the initial state
@@ -2126,7 +2104,6 @@ public class CheckpointCoordinatorTest {
 			new StandaloneCheckpointIDCounter(),
 			new StandaloneCompletedCheckpointStore(1),
 			null,
-			new DisabledCheckpointStatsTracker(),
 			Executors.directExecutor());
 
 		// trigger the checkpoint
@@ -2146,7 +2123,7 @@ public class CheckpointCoordinatorTest {
 			KeyGroupsStateHandle keyGroupState = generateKeyGroupState(
 					jobVertexID1, keyGroupPartitions1.get(index), false);
 
-			SubtaskState checkpointStateHandles = new SubtaskState(valueSizeTuple, null, null, keyGroupState, null, 0);
+			SubtaskState checkpointStateHandles = new SubtaskState(valueSizeTuple, null, null, keyGroupState, null);
 			AcknowledgeCheckpoint acknowledgeCheckpoint = new AcknowledgeCheckpoint(
 					jid,
 					jobVertex1.getTaskVertices()[index].getCurrentExecutionAttempt().getAttemptId(),
@@ -2163,7 +2140,7 @@ public class CheckpointCoordinatorTest {
 			KeyGroupsStateHandle keyGroupState = generateKeyGroupState(
 					jobVertexID2, keyGroupPartitions2.get(index), false);
 
-			SubtaskState checkpointStateHandles = new SubtaskState(state, null, null, keyGroupState, null, 0);
+			SubtaskState checkpointStateHandles = new SubtaskState(state, null, null, keyGroupState, null);
 			AcknowledgeCheckpoint acknowledgeCheckpoint = new AcknowledgeCheckpoint(
 					jid,
 					jobVertex2.getTaskVertices()[index].getCurrentExecutionAttempt().getAttemptId(),
@@ -2245,7 +2222,7 @@ public class CheckpointCoordinatorTest {
 		allExecutionVertices.addAll(Arrays.asList(jobVertex1.getTaskVertices()));
 		allExecutionVertices.addAll(Arrays.asList(jobVertex2.getTaskVertices()));
 
-		ExecutionVertex[] arrayExecutionVertices = 
+		ExecutionVertex[] arrayExecutionVertices =
 				allExecutionVertices.toArray(new ExecutionVertex[allExecutionVertices.size()]);
 
 		// set up the coordinator and validate the initial state
@@ -2262,7 +2239,6 @@ public class CheckpointCoordinatorTest {
 			new StandaloneCheckpointIDCounter(),
 			new StandaloneCompletedCheckpointStore(1),
 			null,
-			new DisabledCheckpointStatsTracker(),
 			Executors.directExecutor());
 
 		// trigger the checkpoint
@@ -2285,7 +2261,7 @@ public class CheckpointCoordinatorTest {
 			KeyGroupsStateHandle keyedStateRaw = generateKeyGroupState(jobVertexID1, keyGroupPartitions1.get(index), true);
 
 
-			SubtaskState checkpointStateHandles = new SubtaskState(valueSizeTuple, opStateBackend, null, keyedStateBackend, keyedStateRaw , 0);
+			SubtaskState checkpointStateHandles = new SubtaskState(valueSizeTuple, opStateBackend, null, keyedStateBackend, keyedStateRaw);
 			AcknowledgeCheckpoint acknowledgeCheckpoint = new AcknowledgeCheckpoint(
 					jid,
 					jobVertex1.getTaskVertices()[index].getCurrentExecutionAttempt().getAttemptId(),
@@ -2307,7 +2283,7 @@ public class CheckpointCoordinatorTest {
 			expectedOpStatesRaw.add(opStateRaw);
 			SubtaskState checkpointStateHandles =
 					new SubtaskState(new ChainedStateHandle<>(
-							Collections.<StreamStateHandle>singletonList(null)), opStateBackend, opStateRaw, keyedStateBackend, keyedStateRaw, 0);
+							Collections.<StreamStateHandle>singletonList(null)), opStateBackend, opStateRaw, keyedStateBackend, keyedStateRaw);
 			AcknowledgeCheckpoint acknowledgeCheckpoint = new AcknowledgeCheckpoint(
 					jid,
 					jobVertex2.getTaskVertices()[index].getCurrentExecutionAttempt().getAttemptId(),
@@ -2394,7 +2370,6 @@ public class CheckpointCoordinatorTest {
 				new StandaloneCheckpointIDCounter(),
 				new StandaloneCompletedCheckpointStore(1),
 				"fake-directory",
-				new DisabledCheckpointStatsTracker(),
 				Executors.directExecutor());
 
 			assertTrue(coord.triggerCheckpoint(timestamp, false));
@@ -2771,7 +2746,6 @@ public class CheckpointCoordinatorTest {
 			new StandaloneCheckpointIDCounter(),
 			new StandaloneCompletedCheckpointStore(1),
 			null,
-			new DisabledCheckpointStatsTracker(),
 			Executors.directExecutor());
 
 		// Periodic
@@ -2915,4 +2889,76 @@ public class CheckpointCoordinatorTest {
 		Assert.assertEquals(expectedTotalPartitions, actualTotalPartitions);
 		Assert.assertEquals(expected, actual);
 	}
+
+	/**
+	 * Tests that the pending checkpoint stats callbacks are created.
+	 */
+	@Test
+	public void testCheckpointStatsTrackerPendingCheckpointCallback() {
+		final long timestamp = System.currentTimeMillis();
+		ExecutionVertex vertex1 = mockExecutionVertex(new ExecutionAttemptID());
+
+		// set up the coordinator and validate the initial state
+		CheckpointCoordinator coord = new CheckpointCoordinator(
+			new JobID(),
+			600000,
+			600000,
+			0,
+			Integer.MAX_VALUE,
+			ExternalizedCheckpointSettings.none(),
+			new ExecutionVertex[]{vertex1},
+			new ExecutionVertex[]{vertex1},
+			new ExecutionVertex[]{vertex1},
+			new StandaloneCheckpointIDCounter(),
+			new StandaloneCompletedCheckpointStore(1),
+			null,
+			Executors.directExecutor());
+
+		CheckpointStatsTracker tracker = mock(CheckpointStatsTracker.class);
+		coord.setCheckpointStatsTracker(tracker);
+
+		when(tracker.reportPendingCheckpoint(anyLong(), anyLong(), any(CheckpointProperties.class)))
+			.thenReturn(mock(PendingCheckpointStats.class));
+
+		// Trigger a checkpoint and verify callback
+		assertTrue(coord.triggerCheckpoint(timestamp, false));
+
+		verify(tracker, times(1))
+			.reportPendingCheckpoint(eq(1L), eq(timestamp), eq(CheckpointProperties.forStandardCheckpoint()));
+	}
+
+	/**
+	 * Tests that the restore callbacks are called if registered.
+	 */
+	@Test
+	public void testCheckpointStatsTrackerRestoreCallback() throws Exception {
+		ExecutionVertex vertex1 = mockExecutionVertex(new ExecutionAttemptID());
+
+		StandaloneCompletedCheckpointStore store = new StandaloneCompletedCheckpointStore(1);
+		store.addCheckpoint(new CompletedCheckpoint(new JobID(), 0, 0, 0, Collections.<JobVertexID, TaskState>emptyMap()));
+
+		// set up the coordinator and validate the initial state
+		CheckpointCoordinator coord = new CheckpointCoordinator(
+			new JobID(),
+			600000,
+			600000,
+			0,
+			Integer.MAX_VALUE,
+			ExternalizedCheckpointSettings.none(),
+			new ExecutionVertex[]{vertex1},
+			new ExecutionVertex[]{vertex1},
+			new ExecutionVertex[]{vertex1},
+			new StandaloneCheckpointIDCounter(),
+			store,
+			null,
+			Executors.directExecutor());
+
+		CheckpointStatsTracker tracker = mock(CheckpointStatsTracker.class);
+		coord.setCheckpointStatsTracker(tracker);
+
+		assertTrue(coord.restoreLatestCheckpointedState(Collections.<JobVertexID, ExecutionJobVertex>emptyMap(), false, true));
+
+		verify(tracker, times(1))
+			.reportRestoredCheckpoint(any(RestoredCheckpointStats.class));
+	}
 }

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointPropertiesTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointPropertiesTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointPropertiesTest.java
index 11bddb9..fb3bd65 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointPropertiesTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointPropertiesTest.java
@@ -85,4 +85,31 @@ public class CheckpointPropertiesTest {
 		assertFalse(props.discardOnJobFailed());
 		assertFalse(props.discardOnJobSuspended());
 	}
+
+	/**
+	 * Tests the isSavepoint utility works as expected.
+	 */
+	@Test
+	public void testIsSavepoint() throws Exception {
+		{
+			CheckpointProperties props = CheckpointProperties.forStandardCheckpoint();
+			assertFalse(CheckpointProperties.isSavepoint(props));
+		}
+
+		{
+			CheckpointProperties props = CheckpointProperties.forExternalizedCheckpoint(true);
+			assertFalse(CheckpointProperties.isSavepoint(props));
+		}
+
+		{
+			CheckpointProperties props = CheckpointProperties.forExternalizedCheckpoint(false);
+			assertFalse(CheckpointProperties.isSavepoint(props));
+		}
+
+		{
+			CheckpointProperties props = CheckpointProperties.forStandardSavepoint();
+			assertTrue(CheckpointProperties.isSavepoint(props));
+		}
+
+	}
 }

http://git-wip-us.apache.org/repos/asf/flink/blob/0d1f4bcb/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointStateRestoreTest.java
----------------------------------------------------------------------
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointStateRestoreTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointStateRestoreTest.java
index 7cea130..0e20ebc8 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointStateRestoreTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/checkpoint/CheckpointStateRestoreTest.java
@@ -19,7 +19,6 @@
 package org.apache.flink.runtime.checkpoint;
 
 import org.apache.flink.api.common.JobID;
-import org.apache.flink.runtime.checkpoint.stats.DisabledCheckpointStatsTracker;
 import org.apache.flink.runtime.concurrent.Executors;
 import org.apache.flink.runtime.execution.ExecutionState;
 import org.apache.flink.runtime.executiongraph.Execution;
@@ -109,7 +108,6 @@ public class CheckpointStateRestoreTest {
 				new StandaloneCheckpointIDCounter(),
 				new StandaloneCompletedCheckpointStore(1),
 				null,
-				new DisabledCheckpointStatsTracker(),
 				Executors.directExecutor());
 
 			// create ourselves a checkpoint with state
@@ -119,7 +117,7 @@ public class CheckpointStateRestoreTest {
 			PendingCheckpoint pending = coord.getPendingCheckpoints().values().iterator().next();
 			final long checkpointId = pending.getCheckpointId();
 
-			SubtaskState checkpointStateHandles = new SubtaskState(serializedState, null, null, serializedKeyGroupStates, null, 0L);
+			SubtaskState checkpointStateHandles = new SubtaskState(serializedState, null, null, serializedKeyGroupStates, null);
 			CheckpointMetaData checkpointMetaData = new CheckpointMetaData(checkpointId, 0L);
 			coord.receiveAcknowledgeMessage(new AcknowledgeCheckpoint(jid, statefulExec1.getAttemptId(), checkpointMetaData, checkpointStateHandles));
 			coord.receiveAcknowledgeMessage(new AcknowledgeCheckpoint(jid, statefulExec2.getAttemptId(), checkpointMetaData, checkpointStateHandles));
@@ -185,7 +183,6 @@ public class CheckpointStateRestoreTest {
 				new StandaloneCheckpointIDCounter(),
 				new StandaloneCompletedCheckpointStore(1),
 				null,
-				new DisabledCheckpointStatsTracker(),
 				Executors.directExecutor());
 
 			try {
@@ -241,7 +238,6 @@ public class CheckpointStateRestoreTest {
 			new StandaloneCheckpointIDCounter(),
 			new StandaloneCompletedCheckpointStore(1),
 			null,
-			new DisabledCheckpointStatsTracker(),
 			Executors.directExecutor());
 
 		ChainedStateHandle<StreamStateHandle> serializedState = CheckpointCoordinatorTest


[02/11] flink git commit: [FLINK-4410] [runtime-web] Rebuild JS/HTML files

Posted by uc...@apache.org.
http://git-wip-us.apache.org/repos/asf/flink/blob/88c7de49/flink-runtime-web/web-dashboard/web/js/index.js
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/web/js/index.js b/flink-runtime-web/web-dashboard/web/js/index.js
index 83e7145..6d9df04 100644
--- a/flink-runtime-web/web-dashboard/web/js/index.js
+++ b/flink-runtime-web/web-dashboard/web/js/index.js
@@ -101,12 +101,53 @@ angular.module('flinkApp', ['ui.router', 'angularMoment', 'dndLists']).run(["$ro
     }
   }).state("single-job.plan.checkpoints", {
     url: "/checkpoints",
+    redirectTo: "single-job.plan.checkpoints.overview",
     views: {
       'node-details': {
         templateUrl: "partials/jobs/job.plan.node-list.checkpoints.html",
         controller: 'JobPlanCheckpointsController'
       }
     }
+  }).state("single-job.plan.checkpoints.overview", {
+    url: "/overview",
+    views: {
+      'checkpoints-view': {
+        templateUrl: "partials/jobs/job.plan.node.checkpoints.overview.html",
+        controller: 'JobPlanCheckpointsController'
+      }
+    }
+  }).state("single-job.plan.checkpoints.summary", {
+    url: "/summary",
+    views: {
+      'checkpoints-view': {
+        templateUrl: "partials/jobs/job.plan.node.checkpoints.summary.html",
+        controller: 'JobPlanCheckpointsController'
+      }
+    }
+  }).state("single-job.plan.checkpoints.history", {
+    url: "/history",
+    views: {
+      'checkpoints-view': {
+        templateUrl: "partials/jobs/job.plan.node.checkpoints.history.html",
+        controller: 'JobPlanCheckpointsController'
+      }
+    }
+  }).state("single-job.plan.checkpoints.config", {
+    url: "/config",
+    views: {
+      'checkpoints-view': {
+        templateUrl: "partials/jobs/job.plan.node.checkpoints.config.html",
+        controller: 'JobPlanCheckpointsController'
+      }
+    }
+  }).state("single-job.plan.checkpoints.details", {
+    url: "/details/{checkpointId}",
+    views: {
+      'checkpoints-view': {
+        templateUrl: "partials/jobs/job.plan.node.checkpoints.details.html",
+        controller: 'JobPlanCheckpointDetailsController'
+      }
+    }
   }).state("single-job.plan.backpressure", {
     url: "/backpressure",
     views: {
@@ -375,6 +416,10 @@ angular.module('flinkApp').filter("amDurationFormatExtended", ["angularMomentCon
   return function(text) {
     return text.toUpperCase();
   };
+}).filter("percentage", function() {
+  return function(number) {
+    return (number * 100).toFixed(0) + '%';
+  };
 });
 
 angular.module('flinkApp').service('MainService', ["$http", "flinkConfig", "$q", function($http, flinkConfig, $q) {
@@ -463,45 +508,6 @@ angular.module('flinkApp').service('JobManagerConfigService', ["$http", "flinkCo
   return this;
 }]);
 
-angular.module('flinkApp').controller('OverviewController', ["$scope", "OverviewService", "JobsService", "$interval", "flinkConfig", function($scope, OverviewService, JobsService, $interval, flinkConfig) {
-  var refresh;
-  $scope.jobObserver = function() {
-    $scope.runningJobs = JobsService.getJobs('running');
-    return $scope.finishedJobs = JobsService.getJobs('finished');
-  };
-  JobsService.registerObserver($scope.jobObserver);
-  $scope.$on('$destroy', function() {
-    return JobsService.unRegisterObserver($scope.jobObserver);
-  });
-  $scope.jobObserver();
-  OverviewService.loadOverview().then(function(data) {
-    return $scope.overview = data;
-  });
-  refresh = $interval(function() {
-    return OverviewService.loadOverview().then(function(data) {
-      return $scope.overview = data;
-    });
-  }, flinkConfig["refresh-interval"]);
-  return $scope.$on('$destroy', function() {
-    return $interval.cancel(refresh);
-  });
-}]);
-
-angular.module('flinkApp').service('OverviewService', ["$http", "flinkConfig", "$q", function($http, flinkConfig, $q) {
-  var overview;
-  overview = {};
-  this.loadOverview = function() {
-    var deferred;
-    deferred = $q.defer();
-    $http.get(flinkConfig.jobServer + "overview").success(function(data, status, headers, config) {
-      overview = data;
-      return deferred.resolve(data);
-    });
-    return deferred.promise;
-  };
-  return this;
-}]);
-
 angular.module('flinkApp').controller('RunningJobsController', ["$scope", "$state", "$stateParams", "JobsService", function($scope, $state, $stateParams, JobsService) {
   $scope.jobObserver = function() {
     return $scope.jobs = JobsService.getJobs('running');
@@ -526,8 +532,6 @@ angular.module('flinkApp').controller('RunningJobsController', ["$scope", "$stat
   $scope.job = null;
   $scope.plan = null;
   $scope.vertices = null;
-  $scope.jobCheckpointStats = null;
-  $scope.showHistory = false;
   $scope.backPressureOperatorStats = {};
   JobsService.loadJob($stateParams.jobid).then(function(data) {
     $scope.job = data;
@@ -545,7 +549,6 @@ angular.module('flinkApp').controller('RunningJobsController', ["$scope", "$stat
     $scope.job = null;
     $scope.plan = null;
     $scope.vertices = null;
-    $scope.jobCheckpointStats = null;
     $scope.backPressureOperatorStats = null;
     return $interval.cancel(refresher);
   });
@@ -555,15 +558,12 @@ angular.module('flinkApp').controller('RunningJobsController', ["$scope", "$stat
       return {};
     });
   };
-  $scope.stopJob = function(stopEvent) {
+  return $scope.stopJob = function(stopEvent) {
     angular.element(stopEvent.currentTarget).removeClass("btn").removeClass("btn-default").html('Stopping...');
     return JobsService.stopJob($stateParams.jobid).then(function(data) {
       return {};
     });
   };
-  return $scope.toggleHistory = function() {
-    return $scope.showHistory = !$scope.showHistory;
-  };
 }]).controller('JobPlanController', ["$scope", "$state", "$stateParams", "$window", "JobsService", function($scope, $state, $stateParams, $window, JobsService) {
   $scope.nodeid = null;
   $scope.nodeUnfolded = false;
@@ -643,29 +643,57 @@ angular.module('flinkApp').controller('RunningJobsController', ["$scope", "$stat
       return getAccumulators();
     }
   });
-}]).controller('JobPlanCheckpointsController', ["$scope", "JobsService", function($scope, JobsService) {
-  var getJobCheckpointStats, getOperatorCheckpointStats;
-  getJobCheckpointStats = function() {
-    return JobsService.getJobCheckpointStats($scope.jobid).then(function(data) {
-      return $scope.jobCheckpointStats = data;
+}]).controller('JobPlanCheckpointsController', ["$scope", "$state", "$stateParams", "JobsService", function($scope, $state, $stateParams, JobsService) {
+  var getGeneralCheckpointStats;
+  $scope.checkpointDetails = {};
+  $scope.checkpointDetails.id = -1;
+  JobsService.getCheckpointConfig().then(function(data) {
+    return $scope.checkpointConfig = data;
+  });
+  getGeneralCheckpointStats = function() {
+    return JobsService.getCheckpointStats().then(function(data) {
+      if (data !== null) {
+        return $scope.checkpointStats = data;
+      }
     });
   };
-  getOperatorCheckpointStats = function() {
-    return JobsService.getOperatorCheckpointStats($scope.nodeid).then(function(data) {
-      $scope.operatorCheckpointStats = data.operatorStats;
-      return $scope.subtasksCheckpointStats = data.subtasksStats;
+  getGeneralCheckpointStats();
+  return $scope.$on('reload', function(event) {
+    return getGeneralCheckpointStats();
+  });
+}]).controller('JobPlanCheckpointDetailsController', ["$scope", "$state", "$stateParams", "JobsService", function($scope, $state, $stateParams, JobsService) {
+  var getCheckpointDetails, getCheckpointSubtaskDetails;
+  $scope.subtaskDetails = {};
+  $scope.checkpointDetails.id = $stateParams.checkpointId;
+  getCheckpointDetails = function(checkpointId) {
+    return JobsService.getCheckpointDetails(checkpointId).then(function(data) {
+      if (data !== null) {
+        return $scope.checkpoint = data;
+      } else {
+        return $scope.unknown_checkpoint = true;
+      }
     });
   };
-  getJobCheckpointStats();
-  if ($scope.nodeid && (!$scope.vertex || !$scope.vertex.operatorCheckpointStats)) {
-    getOperatorCheckpointStats();
+  getCheckpointSubtaskDetails = function(checkpointId, vertexId) {
+    return JobsService.getCheckpointSubtaskDetails(checkpointId, vertexId).then(function(data) {
+      if (data !== null) {
+        return $scope.subtaskDetails[vertexId] = data;
+      }
+    });
+  };
+  getCheckpointDetails($stateParams.checkpointId);
+  if ($scope.nodeid) {
+    getCheckpointSubtaskDetails($stateParams.checkpointId, $scope.nodeid);
   }
-  return $scope.$on('reload', function(event) {
-    getJobCheckpointStats();
+  $scope.$on('reload', function(event) {
+    getCheckpointDetails($stateParams.checkpointId);
     if ($scope.nodeid) {
-      return getOperatorCheckpointStats();
+      return getCheckpointSubtaskDetails($stateParams.checkpointId, $scope.nodeid);
     }
   });
+  return $scope.$on('$destroy', function() {
+    return $scope.checkpointDetails.id = -1;
+  });
 }]).controller('JobPlanBackPressureController', ["$scope", "JobsService", function($scope, JobsService) {
   var getOperatorBackPressure;
   getOperatorBackPressure = function() {
@@ -1401,6 +1429,7 @@ angular.module('flinkApp').service('JobsService', ["$http", "flinkConfig", "$log
     deferred = $q.defer();
     deferreds.job.promise.then((function(_this) {
       return function(data) {
+        console.log(currentJob.jid);
         return $http.get(flinkConfig.jobServer + "jobs/" + currentJob.jid + "/vertices/" + vertexid + "/accumulators").success(function(data) {
           var accumulators;
           accumulators = data['user-accumulators'];
@@ -1417,51 +1446,64 @@ angular.module('flinkApp').service('JobsService', ["$http", "flinkConfig", "$log
     })(this));
     return deferred.promise;
   };
-  this.getJobCheckpointStats = function(jobid) {
+  this.getCheckpointConfig = function() {
     var deferred;
     deferred = $q.defer();
-    $http.get(flinkConfig.jobServer + "jobs/" + jobid + "/checkpoints").success((function(_this) {
-      return function(data, status, headers, config) {
-        if (angular.equals({}, data)) {
-          return deferred.resolve(deferred.resolve(null));
-        } else {
-          return deferred.resolve(data);
-        }
+    deferreds.job.promise.then((function(_this) {
+      return function(data) {
+        return $http.get(flinkConfig.jobServer + "jobs/" + currentJob.jid + "/checkpoints/config").success(function(data) {
+          if (angular.equals({}, data)) {
+            return deferred.resolve(null);
+          } else {
+            return deferred.resolve(data);
+          }
+        });
       };
     })(this));
     return deferred.promise;
   };
-  this.getOperatorCheckpointStats = function(vertexid) {
+  this.getCheckpointStats = function() {
     var deferred;
     deferred = $q.defer();
     deferreds.job.promise.then((function(_this) {
       return function(data) {
-        return $http.get(flinkConfig.jobServer + "jobs/" + currentJob.jid + "/vertices/" + vertexid + "/checkpoints").success(function(data) {
-          var operatorStats, subtaskStats;
+        return $http.get(flinkConfig.jobServer + "jobs/" + currentJob.jid + "/checkpoints").success(function(data, status, headers, config) {
           if (angular.equals({}, data)) {
-            return deferred.resolve({
-              operatorStats: null,
-              subtasksStats: null
-            });
+            return deferred.resolve(null);
           } else {
-            operatorStats = {
-              id: data['id'],
-              timestamp: data['timestamp'],
-              duration: data['duration'],
-              size: data['size']
-            };
-            if (angular.equals({}, data['subtasks'])) {
-              return deferred.resolve({
-                operatorStats: operatorStats,
-                subtasksStats: null
-              });
-            } else {
-              subtaskStats = data['subtasks'];
-              return deferred.resolve({
-                operatorStats: operatorStats,
-                subtasksStats: subtaskStats
-              });
-            }
+            return deferred.resolve(data);
+          }
+        });
+      };
+    })(this));
+    return deferred.promise;
+  };
+  this.getCheckpointDetails = function(checkpointid) {
+    var deferred;
+    deferred = $q.defer();
+    deferreds.job.promise.then((function(_this) {
+      return function(data) {
+        return $http.get(flinkConfig.jobServer + "jobs/" + currentJob.jid + "/checkpoints/details/" + checkpointid).success(function(data) {
+          if (angular.equals({}, data)) {
+            return deferred.resolve(null);
+          } else {
+            return deferred.resolve(data);
+          }
+        });
+      };
+    })(this));
+    return deferred.promise;
+  };
+  this.getCheckpointSubtaskDetails = function(checkpointid, vertexid) {
+    var deferred;
+    deferred = $q.defer();
+    deferreds.job.promise.then((function(_this) {
+      return function(data) {
+        return $http.get(flinkConfig.jobServer + "jobs/" + currentJob.jid + "/checkpoints/details/" + checkpointid + "/subtasks/" + vertexid).success(function(data) {
+          if (angular.equals({}, data)) {
+            return deferred.resolve(null);
+          } else {
+            return deferred.resolve(data);
           }
         });
       };
@@ -1854,6 +1896,45 @@ angular.module('flinkApp').service('MetricsService', ["$http", "$q", "flinkConfi
   return this;
 }]);
 
+angular.module('flinkApp').controller('OverviewController', ["$scope", "OverviewService", "JobsService", "$interval", "flinkConfig", function($scope, OverviewService, JobsService, $interval, flinkConfig) {
+  var refresh;
+  $scope.jobObserver = function() {
+    $scope.runningJobs = JobsService.getJobs('running');
+    return $scope.finishedJobs = JobsService.getJobs('finished');
+  };
+  JobsService.registerObserver($scope.jobObserver);
+  $scope.$on('$destroy', function() {
+    return JobsService.unRegisterObserver($scope.jobObserver);
+  });
+  $scope.jobObserver();
+  OverviewService.loadOverview().then(function(data) {
+    return $scope.overview = data;
+  });
+  refresh = $interval(function() {
+    return OverviewService.loadOverview().then(function(data) {
+      return $scope.overview = data;
+    });
+  }, flinkConfig["refresh-interval"]);
+  return $scope.$on('$destroy', function() {
+    return $interval.cancel(refresh);
+  });
+}]);
+
+angular.module('flinkApp').service('OverviewService', ["$http", "flinkConfig", "$q", function($http, flinkConfig, $q) {
+  var overview;
+  overview = {};
+  this.loadOverview = function() {
+    var deferred;
+    deferred = $q.defer();
+    $http.get(flinkConfig.jobServer + "overview").success(function(data, status, headers, config) {
+      overview = data;
+      return deferred.resolve(data);
+    });
+    return deferred.promise;
+  };
+  return this;
+}]);
+
 angular.module('flinkApp').controller('JobSubmitController', ["$scope", "JobSubmitService", "$interval", "flinkConfig", "$state", "$location", function($scope, JobSubmitService, $interval, flinkConfig, $state, $location) {
   var refresh;
   $scope.yarn = $location.absUrl().indexOf("/proxy/application_") !== -1;
@@ -2165,4 +2246,4 @@ angular.module('flinkApp').service('TaskManagersService', ["$http", "flinkConfig
   return this;
 }]);
 
-//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImluZGV4LmNvZmZlZSIsImluZGV4LmpzIiwiY29tbW9uL2RpcmVjdGl2ZXMuY29mZmVlIiwiY29tbW9uL2RpcmVjdGl2ZXMuanMiLCJjb21tb24vZmlsdGVycy5jb2ZmZWUiLCJjb21tb24vZmlsdGVycy5qcyIsImNvbW1vbi9zZXJ2aWNlcy5jb2ZmZWUiLCJjb21tb24vc2VydmljZXMuanMiLCJtb2R1bGVzL2pvYm1hbmFnZXIvam9ibWFuYWdlci5jdHJsLmNvZmZlZSIsIm1vZHVsZXMvam9ibWFuYWdlci9qb2JtYW5hZ2VyLmN0cmwuanMiLCJtb2R1bGVzL2pvYm1hbmFnZXIvam9ibWFuYWdlci5zdmMuY29mZmVlIiwibW9kdWxlcy9qb2JtYW5hZ2VyL2pvYm1hbmFnZXIuc3ZjLmpzIiwibW9kdWxlcy9vdmVydmlldy9vdmVydmlldy5jdHJsLmNvZmZlZSIsIm1vZHVsZXMvb3ZlcnZpZXcvb3ZlcnZpZXcuY3RybC5qcyIsIm1vZHVsZXMvb3ZlcnZpZXcvb3ZlcnZpZXcuc3ZjLmNvZmZlZSIsIm1vZHVsZXMvb3ZlcnZpZXcvb3ZlcnZpZXcuc3ZjLmpzIiwibW9kdWxlcy9qb2JzL2pvYnMuY3RybC5jb2ZmZWUiLCJtb2R1bGVzL2pvYnMvam9icy5jdHJsLmpzIiwibW9kdWxlcy9qb2JzL2pvYnMuZGlyLmNvZmZlZSIsIm1vZHVsZXMvam9icy9qb2JzLmRpci5qcyIsIm1vZHVsZXMvam9icy9qb2JzLnN2Yy5jb2ZmZWUiLCJtb2R1bGVzL2pvYnMvam9icy5zdmMuanMiLCJtb2R1bGVzL2pvYnMvbWV0cm
 ljcy5kaXIuY29mZmVlIiwibW9kdWxlcy9qb2JzL21ldHJpY3MuZGlyLmpzIiwibW9kdWxlcy9qb2JzL21ldHJpY3Muc3ZjLmNvZmZlZSIsIm1vZHVsZXMvam9icy9tZXRyaWNzLnN2Yy5qcyIsIm1vZHVsZXMvc3VibWl0L3N1Ym1pdC5jdHJsLmNvZmZlZSIsIm1vZHVsZXMvc3VibWl0L3N1Ym1pdC5jdHJsLmpzIiwibW9kdWxlcy9zdWJtaXQvc3VibWl0LnN2Yy5jb2ZmZWUiLCJtb2R1bGVzL3N1Ym1pdC9zdWJtaXQuc3ZjLmpzIiwibW9kdWxlcy90YXNrbWFuYWdlci90YXNrbWFuYWdlci5jdHJsLmNvZmZlZSIsIm1vZHVsZXMvdGFza21hbmFnZXIvdGFza21hbmFnZXIuY3RybC5qcyIsIm1vZHVsZXMvdGFza21hbmFnZXIvdGFza21hbmFnZXIuc3ZjLmNvZmZlZSIsIm1vZHVsZXMvdGFza21hbmFnZXIvdGFza21hbmFnZXIuc3ZjLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQWtCQSxRQUFRLE9BQU8sWUFBWSxDQUFDLGFBQWEsaUJBQWlCLGFBSXpELG1CQUFJLFNBQUMsWUFBRDtFQUNILFdBQVcsaUJBQWlCO0VDckI1QixPRHNCQSxXQUFXLGNBQWMsV0FBQTtJQUN2QixXQUFXLGlCQUFpQixDQUFDLFdBQVc7SUNyQnhDLE9Ec0JBLFdBQVcsZUFBZTs7SUFJN0IsTUFBTSxlQUFlO0VBQ3BCLFdBQVc7RUFFWCxvQkFBb0I7R0FLckIsK0RBQUksU0FBQyxhQUFhLGFBQWEsYUFBYSxXQUF4QztFQzVCSCxPRDZCQSxZQUFZLGFBQWEsS0FBSyxTQUFDLFFBQUQ7SUFDNUIsUUFBUSxPQUFPLGFBQWE7SUFFNUIsWUFBWTtJQzd
 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
 XLFNBQUMsZUFBRDtJQUNWLElBQUE7SUFBQSxXQUFXLEdBQUc7SUFFZCxNQUFNLElBQUksWUFBWSxZQUFZLGtCQUFrQixnQkFBZ0IsUUFDbkUsUUFBUSxTQUFDLE1BQU0sUUFBUSxTQUFTLFFBQXhCO01DdkJQLE9Ed0JBLFNBQVMsUUFBUTs7SUN0Qm5CLE9Ed0JBLFNBQVM7O0VBRVgsS0FBQyxhQUFhLFNBQUMsZUFBRDtJQUNaLElBQUE7SUFBQSxXQUFXLEdBQUc7SUFFZCxNQUFNLElBQUksWUFBWSxZQUFZLGtCQUFrQixnQkFBZ0IsV0FDbkUsUUFBUSxTQUFDLE1BQU0sUUFBUSxTQUFTLFFBQXhCO01DeEJQLE9EeUJBLFNBQVMsUUFBUTs7SUN2Qm5CLE9EeUJBLFNBQVM7O0VDdkJYLE9EeUJBOztBQ3ZCRiIsImZpbGUiOiJpbmRleC5qcyIsInNvdXJjZXNDb250ZW50IjpbIiNcbiMgTGljZW5zZWQgdG8gdGhlIEFwYWNoZSBTb2Z0d2FyZSBGb3VuZGF0aW9uIChBU0YpIHVuZGVyIG9uZVxuIyBvciBtb3JlIGNvbnRyaWJ1dG9yIGxpY2Vuc2UgYWdyZWVtZW50cy4gIFNlZSB0aGUgTk9USUNFIGZpbGVcbiMgZGlzdHJpYnV0ZWQgd2l0aCB0aGlzIHdvcmsgZm9yIGFkZGl0aW9uYWwgaW5mb3JtYXRpb25cbiMgcmVnYXJkaW5nIGNvcHlyaWdodCBvd25lcnNoaXAuICBUaGUgQVNGIGxpY2Vuc2VzIHRoaXMgZmlsZVxuIyB0byB5b3UgdW5kZXIgdGhlIEFwYWNoZSBMaWNlbnNlLCBWZXJzaW9uIDIuMCAodGhlXG4jIFwiTGljZW5zZVwiKTsgeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZVxuIyB3
 aXRoIHRoZSBMaWNlbnNlLiAgWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0XG4jXG4jICAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjBcbiNcbiMgVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZVxuIyBkaXN0cmlidXRlZCB1bmRlciB0aGUgTGljZW5zZSBpcyBkaXN0cmlidXRlZCBvbiBhbiBcIkFTIElTXCIgQkFTSVMsXG4jIFdJVEhPVVQgV0FSUkFOVElFUyBPUiBDT05ESVRJT05TIE9GIEFOWSBLSU5ELCBlaXRoZXIgZXhwcmVzcyBvciBpbXBsaWVkLlxuIyBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kXG4jIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLlxuI1xuXG5hbmd1bGFyLm1vZHVsZSgnZmxpbmtBcHAnLCBbJ3VpLnJvdXRlcicsICdhbmd1bGFyTW9tZW50JywgJ2RuZExpc3RzJ10pXG5cbiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cblxuLnJ1biAoJHJvb3RTY29wZSkgLT5cbiAgJHJvb3RTY29wZS5zaWRlYmFyVmlzaWJsZSA9IGZhbHNlXG4gICRyb290U2NvcGUuc2hvd1NpZGViYXIgPSAtPlxuICAgICRyb290U2NvcGUuc2lkZWJhclZpc2libGUgPSAhJHJvb3RTY29wZS5zaWRlYmFyVmlzaWJsZVxuICAgICRyb290U2NvcGUuc2lkZWJhckNsYXNzID0gJ2ZvcmNlL
 XNob3cnXG5cbiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cblxuLnZhbHVlICdmbGlua0NvbmZpZycsIHtcbiAgam9iU2VydmVyOiAnJ1xuIyAgam9iU2VydmVyOiAnaHR0cDovL2xvY2FsaG9zdDo4MDgxLydcbiAgXCJyZWZyZXNoLWludGVydmFsXCI6IDEwMDAwXG59XG5cbiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cblxuLnJ1biAoSm9ic1NlcnZpY2UsIE1haW5TZXJ2aWNlLCBmbGlua0NvbmZpZywgJGludGVydmFsKSAtPlxuICBNYWluU2VydmljZS5sb2FkQ29uZmlnKCkudGhlbiAoY29uZmlnKSAtPlxuICAgIGFuZ3VsYXIuZXh0ZW5kIGZsaW5rQ29uZmlnLCBjb25maWdcblxuICAgIEpvYnNTZXJ2aWNlLmxpc3RKb2JzKClcblxuICAgICRpbnRlcnZhbCAtPlxuICAgICAgSm9ic1NlcnZpY2UubGlzdEpvYnMoKVxuICAgICwgZmxpbmtDb25maWdbXCJyZWZyZXNoLWludGVydmFsXCJdXG5cblxuIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuXG4uY29uZmlnICgkdWlWaWV3U2Nyb2xsUHJvdmlkZXIpIC0+XG4gICR1aVZpZXdTY3JvbGxQcm92aWRlci51c2VBbmNob3JTY3JvbGwoKVxuXG4jIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cbi5ydW4gKCRyb290U2NvcGUsICRzdGF0ZSkgLT5cbiAgJHJvb3RTY29wZS4kb24gJyRzdGF0ZUNoYW5nZVN0YXJ0JywgKGV2ZW50LCB0b1N0YXRlLCB0b1BhcmFtcy
 wgZnJvbVN0YXRlKSAtPlxuICAgIGlmIHRvU3RhdGUucmVkaXJlY3RUb1xuICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKVxuICAgICAgJHN0YXRlLmdvIHRvU3RhdGUucmVkaXJlY3RUbywgdG9QYXJhbXNcblxuIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuXG4uY29uZmlnICgkc3RhdGVQcm92aWRlciwgJHVybFJvdXRlclByb3ZpZGVyKSAtPlxuICAkc3RhdGVQcm92aWRlci5zdGF0ZSBcIm92ZXJ2aWV3XCIsXG4gICAgdXJsOiBcIi9vdmVydmlld1wiXG4gICAgdmlld3M6XG4gICAgICBtYWluOlxuICAgICAgICB0ZW1wbGF0ZVVybDogXCJwYXJ0aWFscy9vdmVydmlldy5odG1sXCJcbiAgICAgICAgY29udHJvbGxlcjogJ092ZXJ2aWV3Q29udHJvbGxlcidcblxuICAuc3RhdGUgXCJydW5uaW5nLWpvYnNcIixcbiAgICB1cmw6IFwiL3J1bm5pbmctam9ic1wiXG4gICAgdmlld3M6XG4gICAgICBtYWluOlxuICAgICAgICB0ZW1wbGF0ZVVybDogXCJwYXJ0aWFscy9qb2JzL3J1bm5pbmctam9icy5odG1sXCJcbiAgICAgICAgY29udHJvbGxlcjogJ1J1bm5pbmdKb2JzQ29udHJvbGxlcidcblxuICAuc3RhdGUgXCJjb21wbGV0ZWQtam9ic1wiLFxuICAgIHVybDogXCIvY29tcGxldGVkLWpvYnNcIlxuICAgIHZpZXdzOlxuICAgICAgbWFpbjpcbiAgICAgICAgdGVtcGxhdGVVcmw6IFwicGFydGlhbHMvam9icy9jb21wbGV0ZWQtam9icy5odG1sXCJcbiAgICAgICAgY29udHJvbGxlcjogJ0N
 vbXBsZXRlZEpvYnNDb250cm9sbGVyJ1xuXG4gIC5zdGF0ZSBcInNpbmdsZS1qb2JcIixcbiAgICB1cmw6IFwiL2pvYnMve2pvYmlkfVwiXG4gICAgYWJzdHJhY3Q6IHRydWVcbiAgICB2aWV3czpcbiAgICAgIG1haW46XG4gICAgICAgIHRlbXBsYXRlVXJsOiBcInBhcnRpYWxzL2pvYnMvam9iLmh0bWxcIlxuICAgICAgICBjb250cm9sbGVyOiAnU2luZ2xlSm9iQ29udHJvbGxlcidcblxuICAuc3RhdGUgXCJzaW5nbGUtam9iLnBsYW5cIixcbiAgICB1cmw6IFwiXCJcbiAgICByZWRpcmVjdFRvOiBcInNpbmdsZS1qb2IucGxhbi5zdWJ0YXNrc1wiXG4gICAgdmlld3M6XG4gICAgICBkZXRhaWxzOlxuICAgICAgICB0ZW1wbGF0ZVVybDogXCJwYXJ0aWFscy9qb2JzL2pvYi5wbGFuLmh0bWxcIlxuICAgICAgICBjb250cm9sbGVyOiAnSm9iUGxhbkNvbnRyb2xsZXInXG5cbiAgLnN0YXRlIFwic2luZ2xlLWpvYi5wbGFuLnN1YnRhc2tzXCIsXG4gICAgdXJsOiBcIlwiXG4gICAgdmlld3M6XG4gICAgICAnbm9kZS1kZXRhaWxzJzpcbiAgICAgICAgdGVtcGxhdGVVcmw6IFwicGFydGlhbHMvam9icy9qb2IucGxhbi5ub2RlLWxpc3Quc3VidGFza3MuaHRtbFwiXG4gICAgICAgIGNvbnRyb2xsZXI6ICdKb2JQbGFuU3VidGFza3NDb250cm9sbGVyJ1xuXG4gIC5zdGF0ZSBcInNpbmdsZS1qb2IucGxhbi5tZXRyaWNzXCIsXG4gICAgdXJsOiBcIi9tZXRyaWNzXCJcbiAgICB2aWV3czpcbiAgICAgICdub2RlLWRldGFpbHMnOlxu
 ICAgICAgICB0ZW1wbGF0ZVVybDogXCJwYXJ0aWFscy9qb2JzL2pvYi5wbGFuLm5vZGUtbGlzdC5tZXRyaWNzLmh0bWxcIlxuICAgICAgICBjb250cm9sbGVyOiAnSm9iUGxhbk1ldHJpY3NDb250cm9sbGVyJ1xuXG4gIC5zdGF0ZSBcInNpbmdsZS1qb2IucGxhbi50YXNrbWFuYWdlcnNcIixcbiAgICB1cmw6IFwiL3Rhc2ttYW5hZ2Vyc1wiXG4gICAgdmlld3M6XG4gICAgICAnbm9kZS1kZXRhaWxzJzpcbiAgICAgICAgdGVtcGxhdGVVcmw6IFwicGFydGlhbHMvam9icy9qb2IucGxhbi5ub2RlLWxpc3QudGFza21hbmFnZXJzLmh0bWxcIlxuICAgICAgICBjb250cm9sbGVyOiAnSm9iUGxhblRhc2tNYW5hZ2Vyc0NvbnRyb2xsZXInXG5cbiAgLnN0YXRlIFwic2luZ2xlLWpvYi5wbGFuLmFjY3VtdWxhdG9yc1wiLFxuICAgIHVybDogXCIvYWNjdW11bGF0b3JzXCJcbiAgICB2aWV3czpcbiAgICAgICdub2RlLWRldGFpbHMnOlxuICAgICAgICB0ZW1wbGF0ZVVybDogXCJwYXJ0aWFscy9qb2JzL2pvYi5wbGFuLm5vZGUtbGlzdC5hY2N1bXVsYXRvcnMuaHRtbFwiXG4gICAgICAgIGNvbnRyb2xsZXI6ICdKb2JQbGFuQWNjdW11bGF0b3JzQ29udHJvbGxlcidcblxuICAuc3RhdGUgXCJzaW5nbGUtam9iLnBsYW4uY2hlY2twb2ludHNcIixcbiAgICB1cmw6IFwiL2NoZWNrcG9pbnRzXCJcbiAgICB2aWV3czpcbiAgICAgICdub2RlLWRldGFpbHMnOlxuICAgICAgICB0ZW1wbGF0ZVVybDogXCJwYXJ0aWFscy9qb2JzL2pvYi5wb
 GFuLm5vZGUtbGlzdC5jaGVja3BvaW50cy5odG1sXCJcbiAgICAgICAgY29udHJvbGxlcjogJ0pvYlBsYW5DaGVja3BvaW50c0NvbnRyb2xsZXInXG5cbiAgLnN0YXRlIFwic2luZ2xlLWpvYi5wbGFuLmJhY2twcmVzc3VyZVwiLFxuICAgIHVybDogXCIvYmFja3ByZXNzdXJlXCJcbiAgICB2aWV3czpcbiAgICAgICdub2RlLWRldGFpbHMnOlxuICAgICAgICB0ZW1wbGF0ZVVybDogXCJwYXJ0aWFscy9qb2JzL2pvYi5wbGFuLm5vZGUtbGlzdC5iYWNrcHJlc3N1cmUuaHRtbFwiXG4gICAgICAgIGNvbnRyb2xsZXI6ICdKb2JQbGFuQmFja1ByZXNzdXJlQ29udHJvbGxlcidcblxuICAuc3RhdGUgXCJzaW5nbGUtam9iLnRpbWVsaW5lXCIsXG4gICAgdXJsOiBcIi90aW1lbGluZVwiXG4gICAgdmlld3M6XG4gICAgICBkZXRhaWxzOlxuICAgICAgICB0ZW1wbGF0ZVVybDogXCJwYXJ0aWFscy9qb2JzL2pvYi50aW1lbGluZS5odG1sXCJcblxuICAuc3RhdGUgXCJzaW5nbGUtam9iLnRpbWVsaW5lLnZlcnRleFwiLFxuICAgIHVybDogXCIve3ZlcnRleElkfVwiXG4gICAgdmlld3M6XG4gICAgICB2ZXJ0ZXg6XG4gICAgICAgIHRlbXBsYXRlVXJsOiBcInBhcnRpYWxzL2pvYnMvam9iLnRpbWVsaW5lLnZlcnRleC5odG1sXCJcbiAgICAgICAgY29udHJvbGxlcjogJ0pvYlRpbWVsaW5lVmVydGV4Q29udHJvbGxlcidcblxuICAuc3RhdGUgXCJzaW5nbGUtam9iLmV4Y2VwdGlvbnNcIixcbiAgICB1cmw6IFwiL2V4Y2VwdGlvbnNcIl
 xuICAgIHZpZXdzOlxuICAgICAgZGV0YWlsczpcbiAgICAgICAgdGVtcGxhdGVVcmw6IFwicGFydGlhbHMvam9icy9qb2IuZXhjZXB0aW9ucy5odG1sXCJcbiAgICAgICAgY29udHJvbGxlcjogJ0pvYkV4Y2VwdGlvbnNDb250cm9sbGVyJ1xuXG4gIC5zdGF0ZSBcInNpbmdsZS1qb2IuY29uZmlnXCIsXG4gICAgdXJsOiBcIi9jb25maWdcIlxuICAgIHZpZXdzOlxuICAgICAgZGV0YWlsczpcbiAgICAgICAgdGVtcGxhdGVVcmw6IFwicGFydGlhbHMvam9icy9qb2IuY29uZmlnLmh0bWxcIlxuXG4gIC5zdGF0ZSBcImFsbC1tYW5hZ2VyXCIsXG4gICAgdXJsOiBcIi90YXNrbWFuYWdlcnNcIlxuICAgIHZpZXdzOlxuICAgICAgbWFpbjpcbiAgICAgICAgdGVtcGxhdGVVcmw6IFwicGFydGlhbHMvdGFza21hbmFnZXIvaW5kZXguaHRtbFwiXG4gICAgICAgIGNvbnRyb2xsZXI6ICdBbGxUYXNrTWFuYWdlcnNDb250cm9sbGVyJ1xuXG4gIC5zdGF0ZSBcInNpbmdsZS1tYW5hZ2VyXCIsXG4gICAgICB1cmw6IFwiL3Rhc2ttYW5hZ2VyL3t0YXNrbWFuYWdlcmlkfVwiXG4gICAgICBhYnN0cmFjdDogdHJ1ZVxuICAgICAgdmlld3M6XG4gICAgICAgIG1haW46XG4gICAgICAgICAgdGVtcGxhdGVVcmw6IFwicGFydGlhbHMvdGFza21hbmFnZXIvdGFza21hbmFnZXIuaHRtbFwiXG4gICAgICAgICAgY29udHJvbGxlcjogJ1NpbmdsZVRhc2tNYW5hZ2VyQ29udHJvbGxlcidcblxuICAuc3RhdGUgXCJzaW5nbGUtbWFuYWdlci5tZXRyaWN
 zXCIsXG4gICAgdXJsOiBcIi9tZXRyaWNzXCJcbiAgICB2aWV3czpcbiAgICAgIGRldGFpbHM6XG4gICAgICAgIHRlbXBsYXRlVXJsOiBcInBhcnRpYWxzL3Rhc2ttYW5hZ2VyL3Rhc2ttYW5hZ2VyLm1ldHJpY3MuaHRtbFwiXG5cbiAgLnN0YXRlIFwic2luZ2xlLW1hbmFnZXIuc3Rkb3V0XCIsXG4gICAgdXJsOiBcIi9zdGRvdXRcIlxuICAgIHZpZXdzOlxuICAgICAgZGV0YWlsczpcbiAgICAgICAgdGVtcGxhdGVVcmw6IFwicGFydGlhbHMvdGFza21hbmFnZXIvdGFza21hbmFnZXIuc3Rkb3V0Lmh0bWxcIlxuICAgICAgICBjb250cm9sbGVyOiAnU2luZ2xlVGFza01hbmFnZXJTdGRvdXRDb250cm9sbGVyJ1xuXG4gIC5zdGF0ZSBcInNpbmdsZS1tYW5hZ2VyLmxvZ1wiLFxuICAgIHVybDogXCIvbG9nXCJcbiAgICB2aWV3czpcbiAgICAgIGRldGFpbHM6XG4gICAgICAgIHRlbXBsYXRlVXJsOiBcInBhcnRpYWxzL3Rhc2ttYW5hZ2VyL3Rhc2ttYW5hZ2VyLmxvZy5odG1sXCJcbiAgICAgICAgY29udHJvbGxlcjogJ1NpbmdsZVRhc2tNYW5hZ2VyTG9nc0NvbnRyb2xsZXInXG5cbiAgLnN0YXRlIFwiam9ibWFuYWdlclwiLFxuICAgICAgdXJsOiBcIi9qb2JtYW5hZ2VyXCJcbiAgICAgIHZpZXdzOlxuICAgICAgICBtYWluOlxuICAgICAgICAgIHRlbXBsYXRlVXJsOiBcInBhcnRpYWxzL2pvYm1hbmFnZXIvaW5kZXguaHRtbFwiXG5cbiAgLnN0YXRlIFwiam9ibWFuYWdlci5jb25maWdcIixcbiAgICB1cmw6IFwiL2NvbmZp
 Z1wiXG4gICAgdmlld3M6XG4gICAgICBkZXRhaWxzOlxuICAgICAgICB0ZW1wbGF0ZVVybDogXCJwYXJ0aWFscy9qb2JtYW5hZ2VyL2NvbmZpZy5odG1sXCJcbiAgICAgICAgY29udHJvbGxlcjogJ0pvYk1hbmFnZXJDb25maWdDb250cm9sbGVyJ1xuXG4gIC5zdGF0ZSBcImpvYm1hbmFnZXIuc3Rkb3V0XCIsXG4gICAgdXJsOiBcIi9zdGRvdXRcIlxuICAgIHZpZXdzOlxuICAgICAgZGV0YWlsczpcbiAgICAgICAgdGVtcGxhdGVVcmw6IFwicGFydGlhbHMvam9ibWFuYWdlci9zdGRvdXQuaHRtbFwiXG4gICAgICAgIGNvbnRyb2xsZXI6ICdKb2JNYW5hZ2VyU3Rkb3V0Q29udHJvbGxlcidcblxuICAuc3RhdGUgXCJqb2JtYW5hZ2VyLmxvZ1wiLFxuICAgIHVybDogXCIvbG9nXCJcbiAgICB2aWV3czpcbiAgICAgIGRldGFpbHM6XG4gICAgICAgIHRlbXBsYXRlVXJsOiBcInBhcnRpYWxzL2pvYm1hbmFnZXIvbG9nLmh0bWxcIlxuICAgICAgICBjb250cm9sbGVyOiAnSm9iTWFuYWdlckxvZ3NDb250cm9sbGVyJ1xuXG4gIC5zdGF0ZSBcInN1Ym1pdFwiLFxuICAgICAgdXJsOiBcIi9zdWJtaXRcIlxuICAgICAgdmlld3M6XG4gICAgICAgIG1haW46XG4gICAgICAgICAgdGVtcGxhdGVVcmw6IFwicGFydGlhbHMvc3VibWl0Lmh0bWxcIlxuICAgICAgICAgIGNvbnRyb2xsZXI6IFwiSm9iU3VibWl0Q29udHJvbGxlclwiXG5cbiAgJHVybFJvdXRlclByb3ZpZGVyLm90aGVyd2lzZSBcIi9vdmVydmlld1wiXG4iLCJhbmd1bGFyL
 m1vZHVsZSgnZmxpbmtBcHAnLCBbJ3VpLnJvdXRlcicsICdhbmd1bGFyTW9tZW50JywgJ2RuZExpc3RzJ10pLnJ1bihmdW5jdGlvbigkcm9vdFNjb3BlKSB7XG4gICRyb290U2NvcGUuc2lkZWJhclZpc2libGUgPSBmYWxzZTtcbiAgcmV0dXJuICRyb290U2NvcGUuc2hvd1NpZGViYXIgPSBmdW5jdGlvbigpIHtcbiAgICAkcm9vdFNjb3BlLnNpZGViYXJWaXNpYmxlID0gISRyb290U2NvcGUuc2lkZWJhclZpc2libGU7XG4gICAgcmV0dXJuICRyb290U2NvcGUuc2lkZWJhckNsYXNzID0gJ2ZvcmNlLXNob3cnO1xuICB9O1xufSkudmFsdWUoJ2ZsaW5rQ29uZmlnJywge1xuICBqb2JTZXJ2ZXI6ICcnLFxuICBcInJlZnJlc2gtaW50ZXJ2YWxcIjogMTAwMDBcbn0pLnJ1bihmdW5jdGlvbihKb2JzU2VydmljZSwgTWFpblNlcnZpY2UsIGZsaW5rQ29uZmlnLCAkaW50ZXJ2YWwpIHtcbiAgcmV0dXJuIE1haW5TZXJ2aWNlLmxvYWRDb25maWcoKS50aGVuKGZ1bmN0aW9uKGNvbmZpZykge1xuICAgIGFuZ3VsYXIuZXh0ZW5kKGZsaW5rQ29uZmlnLCBjb25maWcpO1xuICAgIEpvYnNTZXJ2aWNlLmxpc3RKb2JzKCk7XG4gICAgcmV0dXJuICRpbnRlcnZhbChmdW5jdGlvbigpIHtcbiAgICAgIHJldHVybiBKb2JzU2VydmljZS5saXN0Sm9icygpO1xuICAgIH0sIGZsaW5rQ29uZmlnW1wicmVmcmVzaC1pbnRlcnZhbFwiXSk7XG4gIH0pO1xufSkuY29uZmlnKGZ1bmN0aW9uKCR1aVZpZXdTY3JvbGxQcm92aWRlcikge1xuICByZXR1cm
 4gJHVpVmlld1Njcm9sbFByb3ZpZGVyLnVzZUFuY2hvclNjcm9sbCgpO1xufSkucnVuKGZ1bmN0aW9uKCRyb290U2NvcGUsICRzdGF0ZSkge1xuICByZXR1cm4gJHJvb3RTY29wZS4kb24oJyRzdGF0ZUNoYW5nZVN0YXJ0JywgZnVuY3Rpb24oZXZlbnQsIHRvU3RhdGUsIHRvUGFyYW1zLCBmcm9tU3RhdGUpIHtcbiAgICBpZiAodG9TdGF0ZS5yZWRpcmVjdFRvKSB7XG4gICAgICBldmVudC5wcmV2ZW50RGVmYXVsdCgpO1xuICAgICAgcmV0dXJuICRzdGF0ZS5nbyh0b1N0YXRlLnJlZGlyZWN0VG8sIHRvUGFyYW1zKTtcbiAgICB9XG4gIH0pO1xufSkuY29uZmlnKGZ1bmN0aW9uKCRzdGF0ZVByb3ZpZGVyLCAkdXJsUm91dGVyUHJvdmlkZXIpIHtcbiAgJHN0YXRlUHJvdmlkZXIuc3RhdGUoXCJvdmVydmlld1wiLCB7XG4gICAgdXJsOiBcIi9vdmVydmlld1wiLFxuICAgIHZpZXdzOiB7XG4gICAgICBtYWluOiB7XG4gICAgICAgIHRlbXBsYXRlVXJsOiBcInBhcnRpYWxzL292ZXJ2aWV3Lmh0bWxcIixcbiAgICAgICAgY29udHJvbGxlcjogJ092ZXJ2aWV3Q29udHJvbGxlcidcbiAgICAgIH1cbiAgICB9XG4gIH0pLnN0YXRlKFwicnVubmluZy1qb2JzXCIsIHtcbiAgICB1cmw6IFwiL3J1bm5pbmctam9ic1wiLFxuICAgIHZpZXdzOiB7XG4gICAgICBtYWluOiB7XG4gICAgICAgIHRlbXBsYXRlVXJsOiBcInBhcnRpYWxzL2pvYnMvcnVubmluZy1qb2JzLmh0bWxcIixcbiAgICAgICAgY29udHJvbGxlcjogJ1J1bm5pbmdKb2J
 zQ29udHJvbGxlcidcbiAgICAgIH1cbiAgICB9XG4gIH0pLnN0YXRlKFwiY29tcGxldGVkLWpvYnNcIiwge1xuICAgIHVybDogXCIvY29tcGxldGVkLWpvYnNcIixcbiAgICB2aWV3czoge1xuICAgICAgbWFpbjoge1xuICAgICAgICB0ZW1wbGF0ZVVybDogXCJwYXJ0aWFscy9qb2JzL2NvbXBsZXRlZC1qb2JzLmh0bWxcIixcbiAgICAgICAgY29udHJvbGxlcjogJ0NvbXBsZXRlZEpvYnNDb250cm9sbGVyJ1xuICAgICAgfVxuICAgIH1cbiAgfSkuc3RhdGUoXCJzaW5nbGUtam9iXCIsIHtcbiAgICB1cmw6IFwiL2pvYnMve2pvYmlkfVwiLFxuICAgIGFic3RyYWN0OiB0cnVlLFxuICAgIHZpZXdzOiB7XG4gICAgICBtYWluOiB7XG4gICAgICAgIHRlbXBsYXRlVXJsOiBcInBhcnRpYWxzL2pvYnMvam9iLmh0bWxcIixcbiAgICAgICAgY29udHJvbGxlcjogJ1NpbmdsZUpvYkNvbnRyb2xsZXInXG4gICAgICB9XG4gICAgfVxuICB9KS5zdGF0ZShcInNpbmdsZS1qb2IucGxhblwiLCB7XG4gICAgdXJsOiBcIlwiLFxuICAgIHJlZGlyZWN0VG86IFwic2luZ2xlLWpvYi5wbGFuLnN1YnRhc2tzXCIsXG4gICAgdmlld3M6IHtcbiAgICAgIGRldGFpbHM6IHtcbiAgICAgICAgdGVtcGxhdGVVcmw6IFwicGFydGlhbHMvam9icy9qb2IucGxhbi5odG1sXCIsXG4gICAgICAgIGNvbnRyb2xsZXI6ICdKb2JQbGFuQ29udHJvbGxlcidcbiAgICAgIH1cbiAgICB9XG4gIH0pLnN0YXRlKFwic2luZ2xlLWpvYi5wbGFuLnN1YnRhc2tzXCIs
 IHtcbiAgICB1cmw6IFwiXCIsXG4gICAgdmlld3M6IHtcbiAgICAgICdub2RlLWRldGFpbHMnOiB7XG4gICAgICAgIHRlbXBsYXRlVXJsOiBcInBhcnRpYWxzL2pvYnMvam9iLnBsYW4ubm9kZS1saXN0LnN1YnRhc2tzLmh0bWxcIixcbiAgICAgICAgY29udHJvbGxlcjogJ0pvYlBsYW5TdWJ0YXNrc0NvbnRyb2xsZXInXG4gICAgICB9XG4gICAgfVxuICB9KS5zdGF0ZShcInNpbmdsZS1qb2IucGxhbi5tZXRyaWNzXCIsIHtcbiAgICB1cmw6IFwiL21ldHJpY3NcIixcbiAgICB2aWV3czoge1xuICAgICAgJ25vZGUtZGV0YWlscyc6IHtcbiAgICAgICAgdGVtcGxhdGVVcmw6IFwicGFydGlhbHMvam9icy9qb2IucGxhbi5ub2RlLWxpc3QubWV0cmljcy5odG1sXCIsXG4gICAgICAgIGNvbnRyb2xsZXI6ICdKb2JQbGFuTWV0cmljc0NvbnRyb2xsZXInXG4gICAgICB9XG4gICAgfVxuICB9KS5zdGF0ZShcInNpbmdsZS1qb2IucGxhbi50YXNrbWFuYWdlcnNcIiwge1xuICAgIHVybDogXCIvdGFza21hbmFnZXJzXCIsXG4gICAgdmlld3M6IHtcbiAgICAgICdub2RlLWRldGFpbHMnOiB7XG4gICAgICAgIHRlbXBsYXRlVXJsOiBcInBhcnRpYWxzL2pvYnMvam9iLnBsYW4ubm9kZS1saXN0LnRhc2ttYW5hZ2Vycy5odG1sXCIsXG4gICAgICAgIGNvbnRyb2xsZXI6ICdKb2JQbGFuVGFza01hbmFnZXJzQ29udHJvbGxlcidcbiAgICAgIH1cbiAgICB9XG4gIH0pLnN0YXRlKFwic2luZ2xlLWpvYi5wbGFuLmFjY3VtdWxhdG9yc1wiL
 CB7XG4gICAgdXJsOiBcIi9hY2N1bXVsYXRvcnNcIixcbiAgICB2aWV3czoge1xuICAgICAgJ25vZGUtZGV0YWlscyc6IHtcbiAgICAgICAgdGVtcGxhdGVVcmw6IFwicGFydGlhbHMvam9icy9qb2IucGxhbi5ub2RlLWxpc3QuYWNjdW11bGF0b3JzLmh0bWxcIixcbiAgICAgICAgY29udHJvbGxlcjogJ0pvYlBsYW5BY2N1bXVsYXRvcnNDb250cm9sbGVyJ1xuICAgICAgfVxuICAgIH1cbiAgfSkuc3RhdGUoXCJzaW5nbGUtam9iLnBsYW4uY2hlY2twb2ludHNcIiwge1xuICAgIHVybDogXCIvY2hlY2twb2ludHNcIixcbiAgICB2aWV3czoge1xuICAgICAgJ25vZGUtZGV0YWlscyc6IHtcbiAgICAgICAgdGVtcGxhdGVVcmw6IFwicGFydGlhbHMvam9icy9qb2IucGxhbi5ub2RlLWxpc3QuY2hlY2twb2ludHMuaHRtbFwiLFxuICAgICAgICBjb250cm9sbGVyOiAnSm9iUGxhbkNoZWNrcG9pbnRzQ29udHJvbGxlcidcbiAgICAgIH1cbiAgICB9XG4gIH0pLnN0YXRlKFwic2luZ2xlLWpvYi5wbGFuLmJhY2twcmVzc3VyZVwiLCB7XG4gICAgdXJsOiBcIi9iYWNrcHJlc3N1cmVcIixcbiAgICB2aWV3czoge1xuICAgICAgJ25vZGUtZGV0YWlscyc6IHtcbiAgICAgICAgdGVtcGxhdGVVcmw6IFwicGFydGlhbHMvam9icy9qb2IucGxhbi5ub2RlLWxpc3QuYmFja3ByZXNzdXJlLmh0bWxcIixcbiAgICAgICAgY29udHJvbGxlcjogJ0pvYlBsYW5CYWNrUHJlc3N1cmVDb250cm9sbGVyJ1xuICAgICAgfVxuICAgIH1cbiAgfSkuc3
 RhdGUoXCJzaW5nbGUtam9iLnRpbWVsaW5lXCIsIHtcbiAgICB1cmw6IFwiL3RpbWVsaW5lXCIsXG4gICAgdmlld3M6IHtcbiAgICAgIGRldGFpbHM6IHtcbiAgICAgICAgdGVtcGxhdGVVcmw6IFwicGFydGlhbHMvam9icy9qb2IudGltZWxpbmUuaHRtbFwiXG4gICAgICB9XG4gICAgfVxuICB9KS5zdGF0ZShcInNpbmdsZS1qb2IudGltZWxpbmUudmVydGV4XCIsIHtcbiAgICB1cmw6IFwiL3t2ZXJ0ZXhJZH1cIixcbiAgICB2aWV3czoge1xuICAgICAgdmVydGV4OiB7XG4gICAgICAgIHRlbXBsYXRlVXJsOiBcInBhcnRpYWxzL2pvYnMvam9iLnRpbWVsaW5lLnZlcnRleC5odG1sXCIsXG4gICAgICAgIGNvbnRyb2xsZXI6ICdKb2JUaW1lbGluZVZlcnRleENvbnRyb2xsZXInXG4gICAgICB9XG4gICAgfVxuICB9KS5zdGF0ZShcInNpbmdsZS1qb2IuZXhjZXB0aW9uc1wiLCB7XG4gICAgdXJsOiBcIi9leGNlcHRpb25zXCIsXG4gICAgdmlld3M6IHtcbiAgICAgIGRldGFpbHM6IHtcbiAgICAgICAgdGVtcGxhdGVVcmw6IFwicGFydGlhbHMvam9icy9qb2IuZXhjZXB0aW9ucy5odG1sXCIsXG4gICAgICAgIGNvbnRyb2xsZXI6ICdKb2JFeGNlcHRpb25zQ29udHJvbGxlcidcbiAgICAgIH1cbiAgICB9XG4gIH0pLnN0YXRlKFwic2luZ2xlLWpvYi5jb25maWdcIiwge1xuICAgIHVybDogXCIvY29uZmlnXCIsXG4gICAgdmlld3M6IHtcbiAgICAgIGRldGFpbHM6IHtcbiAgICAgICAgdGVtcGxhdGVVcmw6IFwicGFydGlhbHM
 vam9icy9qb2IuY29uZmlnLmh0bWxcIlxuICAgICAgfVxuICAgIH1cbiAgfSkuc3RhdGUoXCJhbGwtbWFuYWdlclwiLCB7XG4gICAgdXJsOiBcIi90YXNrbWFuYWdlcnNcIixcbiAgICB2aWV3czoge1xuICAgICAgbWFpbjoge1xuICAgICAgICB0ZW1wbGF0ZVVybDogXCJwYXJ0aWFscy90YXNrbWFuYWdlci9pbmRleC5odG1sXCIsXG4gICAgICAgIGNvbnRyb2xsZXI6ICdBbGxUYXNrTWFuYWdlcnNDb250cm9sbGVyJ1xuICAgICAgfVxuICAgIH1cbiAgfSkuc3RhdGUoXCJzaW5nbGUtbWFuYWdlclwiLCB7XG4gICAgdXJsOiBcIi90YXNrbWFuYWdlci97dGFza21hbmFnZXJpZH1cIixcbiAgICBhYnN0cmFjdDogdHJ1ZSxcbiAgICB2aWV3czoge1xuICAgICAgbWFpbjoge1xuICAgICAgICB0ZW1wbGF0ZVVybDogXCJwYXJ0aWFscy90YXNrbWFuYWdlci90YXNrbWFuYWdlci5odG1sXCIsXG4gICAgICAgIGNvbnRyb2xsZXI6ICdTaW5nbGVUYXNrTWFuYWdlckNvbnRyb2xsZXInXG4gICAgICB9XG4gICAgfVxuICB9KS5zdGF0ZShcInNpbmdsZS1tYW5hZ2VyLm1ldHJpY3NcIiwge1xuICAgIHVybDogXCIvbWV0cmljc1wiLFxuICAgIHZpZXdzOiB7XG4gICAgICBkZXRhaWxzOiB7XG4gICAgICAgIHRlbXBsYXRlVXJsOiBcInBhcnRpYWxzL3Rhc2ttYW5hZ2VyL3Rhc2ttYW5hZ2VyLm1ldHJpY3MuaHRtbFwiXG4gICAgICB9XG4gICAgfVxuICB9KS5zdGF0ZShcInNpbmdsZS1tYW5hZ2VyLnN0ZG91dFwiLCB7XG4gICAgdXJs
 OiBcIi9zdGRvdXRcIixcbiAgICB2aWV3czoge1xuICAgICAgZGV0YWlsczoge1xuICAgICAgICB0ZW1wbGF0ZVVybDogXCJwYXJ0aWFscy90YXNrbWFuYWdlci90YXNrbWFuYWdlci5zdGRvdXQuaHRtbFwiLFxuICAgICAgICBjb250cm9sbGVyOiAnU2luZ2xlVGFza01hbmFnZXJTdGRvdXRDb250cm9sbGVyJ1xuICAgICAgfVxuICAgIH1cbiAgfSkuc3RhdGUoXCJzaW5nbGUtbWFuYWdlci5sb2dcIiwge1xuICAgIHVybDogXCIvbG9nXCIsXG4gICAgdmlld3M6IHtcbiAgICAgIGRldGFpbHM6IHtcbiAgICAgICAgdGVtcGxhdGVVcmw6IFwicGFydGlhbHMvdGFza21hbmFnZXIvdGFza21hbmFnZXIubG9nLmh0bWxcIixcbiAgICAgICAgY29udHJvbGxlcjogJ1NpbmdsZVRhc2tNYW5hZ2VyTG9nc0NvbnRyb2xsZXInXG4gICAgICB9XG4gICAgfVxuICB9KS5zdGF0ZShcImpvYm1hbmFnZXJcIiwge1xuICAgIHVybDogXCIvam9ibWFuYWdlclwiLFxuICAgIHZpZXdzOiB7XG4gICAgICBtYWluOiB7XG4gICAgICAgIHRlbXBsYXRlVXJsOiBcInBhcnRpYWxzL2pvYm1hbmFnZXIvaW5kZXguaHRtbFwiXG4gICAgICB9XG4gICAgfVxuICB9KS5zdGF0ZShcImpvYm1hbmFnZXIuY29uZmlnXCIsIHtcbiAgICB1cmw6IFwiL2NvbmZpZ1wiLFxuICAgIHZpZXdzOiB7XG4gICAgICBkZXRhaWxzOiB7XG4gICAgICAgIHRlbXBsYXRlVXJsOiBcInBhcnRpYWxzL2pvYm1hbmFnZXIvY29uZmlnLmh0bWxcIixcbiAgICAgICAgY29udHJvb
 GxlcjogJ0pvYk1hbmFnZXJDb25maWdDb250cm9sbGVyJ1xuICAgICAgfVxuICAgIH1cbiAgfSkuc3RhdGUoXCJqb2JtYW5hZ2VyLnN0ZG91dFwiLCB7XG4gICAgdXJsOiBcIi9zdGRvdXRcIixcbiAgICB2aWV3czoge1xuICAgICAgZGV0YWlsczoge1xuICAgICAgICB0ZW1wbGF0ZVVybDogXCJwYXJ0aWFscy9qb2JtYW5hZ2VyL3N0ZG91dC5odG1sXCIsXG4gICAgICAgIGNvbnRyb2xsZXI6ICdKb2JNYW5hZ2VyU3Rkb3V0Q29udHJvbGxlcidcbiAgICAgIH1cbiAgICB9XG4gIH0pLnN0YXRlKFwiam9ibWFuYWdlci5sb2dcIiwge1xuICAgIHVybDogXCIvbG9nXCIsXG4gICAgdmlld3M6IHtcbiAgICAgIGRldGFpbHM6IHtcbiAgICAgICAgdGVtcGxhdGVVcmw6IFwicGFydGlhbHMvam9ibWFuYWdlci9sb2cuaHRtbFwiLFxuICAgICAgICBjb250cm9sbGVyOiAnSm9iTWFuYWdlckxvZ3NDb250cm9sbGVyJ1xuICAgICAgfVxuICAgIH1cbiAgfSkuc3RhdGUoXCJzdWJtaXRcIiwge1xuICAgIHVybDogXCIvc3VibWl0XCIsXG4gICAgdmlld3M6IHtcbiAgICAgIG1haW46IHtcbiAgICAgICAgdGVtcGxhdGVVcmw6IFwicGFydGlhbHMvc3VibWl0Lmh0bWxcIixcbiAgICAgICAgY29udHJvbGxlcjogXCJKb2JTdWJtaXRDb250cm9sbGVyXCJcbiAgICAgIH1cbiAgICB9XG4gIH0pO1xuICByZXR1cm4gJHVybFJvdXRlclByb3ZpZGVyLm90aGVyd2lzZShcIi9vdmVydmlld1wiKTtcbn0pO1xuIiwiI1xuIyBMaWNlbnNlZCB0by
 B0aGUgQXBhY2hlIFNvZnR3YXJlIEZvdW5kYXRpb24gKEFTRikgdW5kZXIgb25lXG4jIG9yIG1vcmUgY29udHJpYnV0b3IgbGljZW5zZSBhZ3JlZW1lbnRzLiAgU2VlIHRoZSBOT1RJQ0UgZmlsZVxuIyBkaXN0cmlidXRlZCB3aXRoIHRoaXMgd29yayBmb3IgYWRkaXRpb25hbCBpbmZvcm1hdGlvblxuIyByZWdhcmRpbmcgY29weXJpZ2h0IG93bmVyc2hpcC4gIFRoZSBBU0YgbGljZW5zZXMgdGhpcyBmaWxlXG4jIHRvIHlvdSB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGVcbiMgXCJMaWNlbnNlXCIpOyB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlXG4jIHdpdGggdGhlIExpY2Vuc2UuICBZb3UgbWF5IG9idGFpbiBhIGNvcHkgb2YgdGhlIExpY2Vuc2UgYXRcbiNcbiMgICAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMFxuI1xuIyBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlXG4jIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuIFwiQVMgSVNcIiBCQVNJUyxcbiMgV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuXG4jIFNlZSB0aGUgTGljZW5zZSBmb3IgdGhlIHNwZWNpZmljIGxhbmd1YWdlIGdvdmVybmluZyBwZXJtaXNzaW9
 ucyBhbmRcbiMgbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuXG4jXG5cbmFuZ3VsYXIubW9kdWxlKCdmbGlua0FwcCcpXG5cbiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuXG4uZGlyZWN0aXZlICdic0xhYmVsJywgKEpvYnNTZXJ2aWNlKSAtPlxuICB0cmFuc2NsdWRlOiB0cnVlXG4gIHJlcGxhY2U6IHRydWVcbiAgc2NvcGU6IFxuICAgIGdldExhYmVsQ2xhc3M6IFwiJlwiXG4gICAgc3RhdHVzOiBcIkBcIlxuXG4gIHRlbXBsYXRlOiBcIjxzcGFuIHRpdGxlPSd7e3N0YXR1c319JyBuZy1jbGFzcz0nZ2V0TGFiZWxDbGFzcygpJz48bmctdHJhbnNjbHVkZT48L25nLXRyYW5zY2x1ZGU+PC9zcGFuPlwiXG4gIFxuICBsaW5rOiAoc2NvcGUsIGVsZW1lbnQsIGF0dHJzKSAtPlxuICAgIHNjb3BlLmdldExhYmVsQ2xhc3MgPSAtPlxuICAgICAgJ2xhYmVsIGxhYmVsLScgKyBKb2JzU2VydmljZS50cmFuc2xhdGVMYWJlbFN0YXRlKGF0dHJzLnN0YXR1cylcblxuIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cbi5kaXJlY3RpdmUgJ2JwTGFiZWwnLCAoSm9ic1NlcnZpY2UpIC0+XG4gIHRyYW5zY2x1ZGU6IHRydWVcbiAgcmVwbGFjZTogdHJ1ZVxuICBzY29wZTpcbiAgICBnZXRCYWNrUHJlc3N1cmVMYWJlbENsYXNzOiBcIiZcIlxuICAgIHN0YXR1czogXCJAXCJcblxuICB0ZW1wbGF0ZTogXCI8c3BhbiB0aXRsZT0ne3tzdGF0dXN9
 fScgbmctY2xhc3M9J2dldEJhY2tQcmVzc3VyZUxhYmVsQ2xhc3MoKSc+PG5nLXRyYW5zY2x1ZGU+PC9uZy10cmFuc2NsdWRlPjwvc3Bhbj5cIlxuXG4gIGxpbms6IChzY29wZSwgZWxlbWVudCwgYXR0cnMpIC0+XG4gICAgc2NvcGUuZ2V0QmFja1ByZXNzdXJlTGFiZWxDbGFzcyA9IC0+XG4gICAgICAnbGFiZWwgbGFiZWwtJyArIEpvYnNTZXJ2aWNlLnRyYW5zbGF0ZUJhY2tQcmVzc3VyZUxhYmVsU3RhdGUoYXR0cnMuc3RhdHVzKVxuXG4jIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cblxuLmRpcmVjdGl2ZSAnaW5kaWNhdG9yUHJpbWFyeScsIChKb2JzU2VydmljZSkgLT5cbiAgcmVwbGFjZTogdHJ1ZVxuICBzY29wZTogXG4gICAgZ2V0TGFiZWxDbGFzczogXCImXCJcbiAgICBzdGF0dXM6ICdAJ1xuXG4gIHRlbXBsYXRlOiBcIjxpIHRpdGxlPSd7e3N0YXR1c319JyBuZy1jbGFzcz0nZ2V0TGFiZWxDbGFzcygpJyAvPlwiXG4gIFxuICBsaW5rOiAoc2NvcGUsIGVsZW1lbnQsIGF0dHJzKSAtPlxuICAgIHNjb3BlLmdldExhYmVsQ2xhc3MgPSAtPlxuICAgICAgJ2ZhIGZhLWNpcmNsZSBpbmRpY2F0b3IgaW5kaWNhdG9yLScgKyBKb2JzU2VydmljZS50cmFuc2xhdGVMYWJlbFN0YXRlKGF0dHJzLnN0YXR1cylcblxuIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cbi5kaXJlY3RpdmUgJ3RhYmxlUHJvcGVydHknLCAtPlxuICByZXBsYWNlO
 iB0cnVlXG4gIHNjb3BlOlxuICAgIHZhbHVlOiAnPSdcblxuICB0ZW1wbGF0ZTogXCI8dGQgdGl0bGU9XFxcInt7dmFsdWUgfHwgJ05vbmUnfX1cXFwiPnt7dmFsdWUgfHwgJ05vbmUnfX08L3RkPlwiXG4iLCJhbmd1bGFyLm1vZHVsZSgnZmxpbmtBcHAnKS5kaXJlY3RpdmUoJ2JzTGFiZWwnLCBmdW5jdGlvbihKb2JzU2VydmljZSkge1xuICByZXR1cm4ge1xuICAgIHRyYW5zY2x1ZGU6IHRydWUsXG4gICAgcmVwbGFjZTogdHJ1ZSxcbiAgICBzY29wZToge1xuICAgICAgZ2V0TGFiZWxDbGFzczogXCImXCIsXG4gICAgICBzdGF0dXM6IFwiQFwiXG4gICAgfSxcbiAgICB0ZW1wbGF0ZTogXCI8c3BhbiB0aXRsZT0ne3tzdGF0dXN9fScgbmctY2xhc3M9J2dldExhYmVsQ2xhc3MoKSc+PG5nLXRyYW5zY2x1ZGU+PC9uZy10cmFuc2NsdWRlPjwvc3Bhbj5cIixcbiAgICBsaW5rOiBmdW5jdGlvbihzY29wZSwgZWxlbWVudCwgYXR0cnMpIHtcbiAgICAgIHJldHVybiBzY29wZS5nZXRMYWJlbENsYXNzID0gZnVuY3Rpb24oKSB7XG4gICAgICAgIHJldHVybiAnbGFiZWwgbGFiZWwtJyArIEpvYnNTZXJ2aWNlLnRyYW5zbGF0ZUxhYmVsU3RhdGUoYXR0cnMuc3RhdHVzKTtcbiAgICAgIH07XG4gICAgfVxuICB9O1xufSkuZGlyZWN0aXZlKCdicExhYmVsJywgZnVuY3Rpb24oSm9ic1NlcnZpY2UpIHtcbiAgcmV0dXJuIHtcbiAgICB0cmFuc2NsdWRlOiB0cnVlLFxuICAgIHJlcGxhY2U6IHRydWUsXG4gICAgc2NvcGU6IHtcbiAgIC
 AgIGdldEJhY2tQcmVzc3VyZUxhYmVsQ2xhc3M6IFwiJlwiLFxuICAgICAgc3RhdHVzOiBcIkBcIlxuICAgIH0sXG4gICAgdGVtcGxhdGU6IFwiPHNwYW4gdGl0bGU9J3t7c3RhdHVzfX0nIG5nLWNsYXNzPSdnZXRCYWNrUHJlc3N1cmVMYWJlbENsYXNzKCknPjxuZy10cmFuc2NsdWRlPjwvbmctdHJhbnNjbHVkZT48L3NwYW4+XCIsXG4gICAgbGluazogZnVuY3Rpb24oc2NvcGUsIGVsZW1lbnQsIGF0dHJzKSB7XG4gICAgICByZXR1cm4gc2NvcGUuZ2V0QmFja1ByZXNzdXJlTGFiZWxDbGFzcyA9IGZ1bmN0aW9uKCkge1xuICAgICAgICByZXR1cm4gJ2xhYmVsIGxhYmVsLScgKyBKb2JzU2VydmljZS50cmFuc2xhdGVCYWNrUHJlc3N1cmVMYWJlbFN0YXRlKGF0dHJzLnN0YXR1cyk7XG4gICAgICB9O1xuICAgIH1cbiAgfTtcbn0pLmRpcmVjdGl2ZSgnaW5kaWNhdG9yUHJpbWFyeScsIGZ1bmN0aW9uKEpvYnNTZXJ2aWNlKSB7XG4gIHJldHVybiB7XG4gICAgcmVwbGFjZTogdHJ1ZSxcbiAgICBzY29wZToge1xuICAgICAgZ2V0TGFiZWxDbGFzczogXCImXCIsXG4gICAgICBzdGF0dXM6ICdAJ1xuICAgIH0sXG4gICAgdGVtcGxhdGU6IFwiPGkgdGl0bGU9J3t7c3RhdHVzfX0nIG5nLWNsYXNzPSdnZXRMYWJlbENsYXNzKCknIC8+XCIsXG4gICAgbGluazogZnVuY3Rpb24oc2NvcGUsIGVsZW1lbnQsIGF0dHJzKSB7XG4gICAgICByZXR1cm4gc2NvcGUuZ2V0TGFiZWxDbGFzcyA9IGZ1bmN0aW9uKCkge1xuICAgICAgICByZXR
 1cm4gJ2ZhIGZhLWNpcmNsZSBpbmRpY2F0b3IgaW5kaWNhdG9yLScgKyBKb2JzU2VydmljZS50cmFuc2xhdGVMYWJlbFN0YXRlKGF0dHJzLnN0YXR1cyk7XG4gICAgICB9O1xuICAgIH1cbiAgfTtcbn0pLmRpcmVjdGl2ZSgndGFibGVQcm9wZXJ0eScsIGZ1bmN0aW9uKCkge1xuICByZXR1cm4ge1xuICAgIHJlcGxhY2U

<TRUNCATED>