You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@impala.apache.org by ta...@apache.org on 2017/08/24 02:48:10 UTC
[5/6] incubator-impala git commit: IMPALA-5811: Add 'backends' tab to
query details pages
IMPALA-5811: Add 'backends' tab to query details pages
Add a 'backends' tab to query details pages which shows:
* host
* total number of fragment instances for that query on that backend
* number of still-running fragment instances
* if the backend is complete (i.e. all instances finished)
* peak memory consumption
* the time, in ms, since a status report was received at the
* coordinator from that backend.
The table refreshes itself every second, controllable by a check-box. If
the query has completed, no information is displayed.
Testing: Add a new smoketest to test_web_pages.py.
Change-Id: Ib5b3b0fb8f4188da56da593199f41ce6fab99767
Reviewed-on: http://gerrit.cloudera.org:8080/7711
Reviewed-by: Dan Hecht <dh...@cloudera.com>
Tested-by: Impala Public Jenkins
Project: http://git-wip-us.apache.org/repos/asf/incubator-impala/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-impala/commit/ff5e9b6c
Tree: http://git-wip-us.apache.org/repos/asf/incubator-impala/tree/ff5e9b6c
Diff: http://git-wip-us.apache.org/repos/asf/incubator-impala/diff/ff5e9b6c
Branch: refs/heads/master
Commit: ff5e9b6c9a35e2869c0c845d7bbb7beb16b5d45e
Parents: 6f20df8
Author: Henry Robinson <he...@cloudera.com>
Authored: Tue Aug 15 22:21:18 2017 -0700
Committer: Impala Public Jenkins <im...@gerrit.cloudera.org>
Committed: Thu Aug 24 02:40:28 2017 +0000
----------------------------------------------------------------------
be/src/runtime/coordinator-backend-state.cc | 26 +++++++
be/src/runtime/coordinator-backend-state.h | 7 ++
be/src/runtime/coordinator.cc | 12 +++
be/src/runtime/coordinator.h | 19 +++--
be/src/service/impala-http-handler.cc | 22 ++++++
be/src/service/impala-http-handler.h | 5 ++
tests/webserver/test_web_pages.py | 85 ++++++++++++++-------
www/query_backends.tmpl | 94 ++++++++++++++++++++++++
www/query_detail_tabs.tmpl | 1 +
9 files changed, 237 insertions(+), 34 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/ff5e9b6c/be/src/runtime/coordinator-backend-state.cc
----------------------------------------------------------------------
diff --git a/be/src/runtime/coordinator-backend-state.cc b/be/src/runtime/coordinator-backend-state.cc
index 88f7f21..34e0671 100644
--- a/be/src/runtime/coordinator-backend-state.cc
+++ b/be/src/runtime/coordinator-backend-state.cc
@@ -47,6 +47,7 @@
#include "common/names.h"
using namespace impala;
+using namespace rapidjson;
namespace accumulators = boost::accumulators;
Coordinator::BackendState::BackendState(
@@ -249,6 +250,8 @@ bool Coordinator::BackendState::ApplyExecStatusReport(
ProgressUpdater* scan_range_progress) {
lock_guard<SpinLock> l1(exec_summary->lock);
lock_guard<mutex> l2(lock_);
+ last_report_time_ms_ = MonotonicMillis();
+
// If this backend completed previously, don't apply the update.
if (IsDone()) return false;
for (const TFragmentInstanceExecStatus& instance_exec_status:
@@ -561,3 +564,26 @@ void Coordinator::FragmentStats::AddExecStats() {
avg_profile_->AddInfoString("execution rates", rates_label.str());
avg_profile_->AddInfoString("num instances", lexical_cast<string>(num_instances_));
}
+
+void Coordinator::BackendState::ToJson(Value* value, Document* document) {
+ lock_guard<mutex> l(lock_);
+ value->AddMember("num_instances", fragments_.size(), document->GetAllocator());
+ value->AddMember("done", IsDone(), document->GetAllocator());
+ value->AddMember(
+ "peak_mem_consumption", peak_consumption_, document->GetAllocator());
+
+ string host = TNetworkAddressToString(impalad_address());
+ Value val(host.c_str(), document->GetAllocator());
+ value->AddMember("host", val, document->GetAllocator());
+
+ value->AddMember("rpc_latency", rpc_latency(), document->GetAllocator());
+ value->AddMember("time_since_last_heard_from", MonotonicMillis() - last_report_time_ms_,
+ document->GetAllocator());
+
+ string status_str = status_.ok() ? "OK" : status_.GetDetail();
+ Value status_val(status_str.c_str(), document->GetAllocator());
+ value->AddMember("status", status_val, document->GetAllocator());
+
+ value->AddMember(
+ "num_remaining_instances", num_remaining_instances_, document->GetAllocator());
+}
http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/ff5e9b6c/be/src/runtime/coordinator-backend-state.h
----------------------------------------------------------------------
diff --git a/be/src/runtime/coordinator-backend-state.h b/be/src/runtime/coordinator-backend-state.h
index 0846119..ccc3618 100644
--- a/be/src/runtime/coordinator-backend-state.h
+++ b/be/src/runtime/coordinator-backend-state.h
@@ -113,6 +113,10 @@ class Coordinator::BackendState {
/// debugging aid for backend deadlocks.
static void LogFirstInProgress(std::vector<BackendState*> backend_states);
+ /// Serializes backend state to JSON by adding members to 'value', including total
+ /// number of instances, peak memory consumption, host and status amongst others.
+ void ToJson(rapidjson::Value* value, rapidjson::Document* doc);
+
private:
/// Execution stats for a single fragment instance.
/// Not thread-safe.
@@ -213,6 +217,9 @@ class Coordinator::BackendState {
/// peak_consumption()
int64_t peak_consumption_;
+ /// Set in ApplyExecStatusReport(). Uses MonotonicMillis().
+ int64_t last_report_time_ms_ = 0;
+
/// Fill in rpc_params based on state. Uses filter_routing_table to remove filters
/// that weren't selected during its construction.
void SetRpcParams(const DebugOptions& debug_options,
http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/ff5e9b6c/be/src/runtime/coordinator.cc
----------------------------------------------------------------------
diff --git a/be/src/runtime/coordinator.cc b/be/src/runtime/coordinator.cc
index a9936ad..029e0bc 100644
--- a/be/src/runtime/coordinator.cc
+++ b/be/src/runtime/coordinator.cc
@@ -71,6 +71,7 @@
#include "common/names.h"
using namespace apache::thrift;
+using namespace rapidjson;
using namespace strings;
using boost::algorithm::iequals;
using boost::algorithm::is_any_of;
@@ -1221,4 +1222,15 @@ void Coordinator::GetTExecSummary(TExecSummary* exec_summary) {
MemTracker* Coordinator::query_mem_tracker() const {
return query_state()->query_mem_tracker();
}
+
+void Coordinator::BackendsToJson(Document* doc) {
+ lock_guard<mutex> l(lock_);
+ Value states(kArrayType);
+ for (BackendState* state : backend_states_) {
+ Value val(kObjectType);
+ state->ToJson(&val, doc);
+ states.PushBack(val, doc->GetAllocator());
+ }
+ doc->AddMember("backend_states", states, doc->GetAllocator());
+}
}
http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/ff5e9b6c/be/src/runtime/coordinator.h
----------------------------------------------------------------------
diff --git a/be/src/runtime/coordinator.h b/be/src/runtime/coordinator.h
index 03d03df..4edef88 100644
--- a/be/src/runtime/coordinator.h
+++ b/be/src/runtime/coordinator.h
@@ -18,20 +18,21 @@
#ifndef IMPALA_RUNTIME_COORDINATOR_H
#define IMPALA_RUNTIME_COORDINATOR_H
-#include <vector>
#include <string>
-#include <boost/scoped_ptr.hpp>
+#include <vector>
#include <boost/accumulators/accumulators.hpp>
-#include <boost/accumulators/statistics/stats.hpp>
-#include <boost/accumulators/statistics/min.hpp>
+#include <boost/accumulators/statistics/max.hpp>
#include <boost/accumulators/statistics/mean.hpp>
#include <boost/accumulators/statistics/median.hpp>
-#include <boost/accumulators/statistics/max.hpp>
+#include <boost/accumulators/statistics/min.hpp>
+#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/statistics/variance.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/thread/condition_variable.hpp>
+#include <boost/thread/mutex.hpp>
#include <boost/unordered_map.hpp>
#include <boost/unordered_set.hpp>
-#include <boost/thread/mutex.hpp>
-#include <boost/thread/condition_variable.hpp>
+#include <rapidjson/document.h>
#include "common/global-types.h"
#include "common/hdfs.h"
@@ -186,6 +187,10 @@ class Coordinator { // NOLINT: The member variables could be re-ordered to save
/// filter to fragment instances.
void UpdateFilter(const TUpdateFilterParams& params);
+ /// Adds to 'document' a serialized array of all backends in a member named
+ /// 'backend_states'.
+ void BackendsToJson(rapidjson::Document* document);
+
private:
class BackendState;
struct FilterTarget;
http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/ff5e9b6c/be/src/service/impala-http-handler.cc
----------------------------------------------------------------------
diff --git a/be/src/service/impala-http-handler.cc b/be/src/service/impala-http-handler.cc
index 79903b4..e93aacf 100644
--- a/be/src/service/impala-http-handler.cc
+++ b/be/src/service/impala-http-handler.cc
@@ -101,6 +101,9 @@ void ImpalaHttpHandler::RegisterHandlers(Webserver* webserver) {
webserver->RegisterUrlCallback("/query_memory", "query_memory.tmpl",
MakeCallback(this, &ImpalaHttpHandler::QueryMemoryHandler), false);
+ webserver->RegisterUrlCallback("/query_backends", "query_backends.tmpl",
+ MakeCallback(this, &ImpalaHttpHandler::QueryBackendsHandler), false);
+
webserver->RegisterUrlCallback("/cancel_query", "common-pre.tmpl",
MakeCallback(this, &ImpalaHttpHandler::CancelQueryHandler), false);
@@ -691,6 +694,25 @@ void PlanToJson(const vector<TPlanFragment>& fragments, const TExecSummary& summ
}
+void ImpalaHttpHandler::QueryBackendsHandler(
+ const Webserver::ArgumentMap& args, Document* document) {
+ TUniqueId query_id;
+ Status status = ParseIdFromArguments(args, &query_id, "query_id");
+ Value query_id_val(PrintId(query_id).c_str(), document->GetAllocator());
+ document->AddMember("query_id", query_id_val, document->GetAllocator());
+ if (!status.ok()) {
+ // Redact the error message, it may contain part or all of the query.
+ Value json_error(RedactCopy(status.GetDetail()).c_str(), document->GetAllocator());
+ document->AddMember("error", json_error, document->GetAllocator());
+ return;
+ }
+
+ shared_ptr<ClientRequestState> request_state = server_->GetClientRequestState(query_id);
+ if (request_state.get() == nullptr || request_state->coord() == nullptr) return;
+
+ request_state->coord()->BackendsToJson(document);
+}
+
void ImpalaHttpHandler::QuerySummaryHandler(bool include_json_plan, bool include_summary,
const Webserver::ArgumentMap& args, Document* document) {
TUniqueId query_id;
http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/ff5e9b6c/be/src/service/impala-http-handler.h
----------------------------------------------------------------------
diff --git a/be/src/service/impala-http-handler.h b/be/src/service/impala-http-handler.h
index 485f6db..8ad84bd 100644
--- a/be/src/service/impala-http-handler.h
+++ b/be/src/service/impala-http-handler.h
@@ -91,6 +91,11 @@ class ImpalaHttpHandler {
void QuerySummaryHandler(bool include_plan_json, bool include_summary,
const Webserver::ArgumentMap& args, rapidjson::Document* document);
+ /// If 'args' contains a query id, serializes all backend states for that query to
+ /// 'document'.
+ void QueryBackendsHandler(
+ const Webserver::ArgumentMap& args, rapidjson::Document* document);
+
/// Cancels an in-flight query and writes the result to 'contents'.
void CancelQueryHandler(const Webserver::ArgumentMap& args,
rapidjson::Document* document);
http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/ff5e9b6c/tests/webserver/test_web_pages.py
----------------------------------------------------------------------
diff --git a/tests/webserver/test_web_pages.py b/tests/webserver/test_web_pages.py
index 4a2d872..2586399 100644
--- a/tests/webserver/test_web_pages.py
+++ b/tests/webserver/test_web_pages.py
@@ -17,6 +17,7 @@
from tests.common.impala_cluster import ImpalaCluster
from tests.common.impala_test_suite import ImpalaTestSuite
+import json
import requests
class TestWebPage(ImpalaTestSuite):
@@ -28,6 +29,7 @@ class TestWebPage(ImpalaTestSuite):
RESET_GLOG_LOGLEVEL_URL = "http://localhost:{0}/reset_glog_level"
CATALOG_URL = "http://localhost:{0}/catalog"
CATALOG_OBJECT_URL = "http://localhost:{0}/catalog_object"
+ QUERY_BACKENDS_URL = "http://localhost:{0}/query_backends"
# log4j changes do not apply to the statestore since it doesn't
# have an embedded JVM. So we make two sets of ports to test the
# log level endpoints, one without the statestore port and the
@@ -54,89 +56,95 @@ class TestWebPage(ImpalaTestSuite):
result = impalad.service.read_debug_webpage("query_profile_encoded?query_id=123")
assert result.startswith("Could not obtain runtime profile: Query id")
- def get_and_check_status(self, url, string_to_search = "", without_ss = True):
+ def get_and_check_status(self, url, string_to_search = "", ports_to_test = None):
"""Helper method that polls a given url and asserts the return code is ok and
- the response contains the input string. 'without_ss', when true, excludes the
- statestore endpoint of the url. Should be applied only for log4j logging changes."""
- ports_to_test = self.TEST_PORTS_WITHOUT_SS if without_ss else self.TEST_PORTS_WITH_SS
+ the response contains the input string."""
+ if ports_to_test is None:
+ ports_to_test = self.TEST_PORTS_WITH_SS
for port in ports_to_test:
input_url = url.format(port)
response = requests.get(input_url)
assert response.status_code == requests.codes.ok\
and string_to_search in response.text, "Offending url: " + input_url
+ return response.text
+
+ def get_and_check_status_jvm(self, url, string_to_search = ""):
+ """Calls get_and_check_status() for impalad and catalogd only"""
+ return self.get_and_check_status(url, string_to_search,
+ ports_to_test=self.TEST_PORTS_WITHOUT_SS)
def test_log_level(self):
"""Test that the /log_level page outputs are as expected and work well on basic and
malformed inputs. This however does not test that the log level changes are actually
in effect."""
# Check that the log_level end points are accessible.
- self.get_and_check_status(self.GET_JAVA_LOGLEVEL_URL)
- self.get_and_check_status(self.SET_JAVA_LOGLEVEL_URL)
- self.get_and_check_status(self.RESET_JAVA_LOGLEVEL_URL)
- self.get_and_check_status(self.SET_GLOG_LOGLEVEL_URL, without_ss=False)
- self.get_and_check_status(self.RESET_GLOG_LOGLEVEL_URL, without_ss=False)
+ self.get_and_check_status_jvm(self.GET_JAVA_LOGLEVEL_URL)
+ self.get_and_check_status_jvm(self.SET_JAVA_LOGLEVEL_URL)
+ self.get_and_check_status_jvm(self.RESET_JAVA_LOGLEVEL_URL)
+ self.get_and_check_status(self.SET_GLOG_LOGLEVEL_URL)
+ self.get_and_check_status(self.RESET_GLOG_LOGLEVEL_URL)
# Try getting log level of a class.
get_loglevel_url = (self.GET_JAVA_LOGLEVEL_URL + "?class" +
"=org.apache.impala.catalog.HdfsTable")
- self.get_and_check_status(get_loglevel_url, "DEBUG")
+ self.get_and_check_status_jvm(get_loglevel_url, "DEBUG")
# Set the log level of a class to TRACE and confirm the setting is in place
set_loglevel_url = (self.SET_JAVA_LOGLEVEL_URL + "?class" +
"=org.apache.impala.catalog.HdfsTable&level=trace")
- self.get_and_check_status(set_loglevel_url, "Effective log level: TRACE")
+ self.get_and_check_status_jvm(set_loglevel_url, "Effective log level: TRACE")
get_loglevel_url = (self.GET_JAVA_LOGLEVEL_URL + "?class" +
"=org.apache.impala.catalog.HdfsTable")
- self.get_and_check_status(get_loglevel_url, "TRACE")
+ self.get_and_check_status_jvm(get_loglevel_url, "TRACE")
# Check the log level of a different class and confirm it is still DEBUG
get_loglevel_url = (self.GET_JAVA_LOGLEVEL_URL + "?class" +
"=org.apache.impala.catalog.HdfsPartition")
- self.get_and_check_status(get_loglevel_url, "DEBUG")
+ self.get_and_check_status_jvm(get_loglevel_url, "DEBUG")
# Reset Java logging levels and check the logging level of the class again
- self.get_and_check_status(self.RESET_JAVA_LOGLEVEL_URL, "Java log levels reset.")
+ self.get_and_check_status_jvm(self.RESET_JAVA_LOGLEVEL_URL, "Java log levels reset.")
get_loglevel_url = (self.GET_JAVA_LOGLEVEL_URL + "?class" +
"=org.apache.impala.catalog.HdfsTable")
- self.get_and_check_status(get_loglevel_url, "DEBUG")
+ self.get_and_check_status_jvm(get_loglevel_url, "DEBUG")
# Set a new glog level and make sure the setting has been applied.
set_glog_url = (self.SET_GLOG_LOGLEVEL_URL + "?glog=3")
- self.get_and_check_status(set_glog_url, "v set to 3", False)
+ self.get_and_check_status(set_glog_url, "v set to 3")
# Try resetting the glog logging defaults again.
- self.get_and_check_status( self.RESET_GLOG_LOGLEVEL_URL, "v set to ", False)
+ self.get_and_check_status( self.RESET_GLOG_LOGLEVEL_URL, "v set to ")
# Try to get the log level of an empty class input
get_loglevel_url = (self.GET_JAVA_LOGLEVEL_URL + "?class=")
- self.get_and_check_status(get_loglevel_url, without_ss=True)
+ self.get_and_check_status_jvm(get_loglevel_url)
# Same as above, for set log level request
set_loglevel_url = (self.SET_JAVA_LOGLEVEL_URL + "?class=")
- self.get_and_check_status(get_loglevel_url, without_ss=True)
+ self.get_and_check_status_jvm(get_loglevel_url)
# Empty input for setting a glog level request
set_glog_url = (self.SET_GLOG_LOGLEVEL_URL + "?glog=")
- self.get_and_check_status(set_glog_url, without_ss=False)
+ self.get_and_check_status(set_glog_url)
# Try setting a non-existent log level on a valid class. In such cases,
# log4j automatically sets it as DEBUG. This is the behavior of
# Level.toLevel() method.
set_loglevel_url = (self.SET_JAVA_LOGLEVEL_URL + "?class" +
"=org.apache.impala.catalog.HdfsTable&level=foo&")
- self.get_and_check_status(set_loglevel_url, "Effective log level: DEBUG")
+ self.get_and_check_status_jvm(set_loglevel_url, "Effective log level: DEBUG")
# Try setting an invalid glog level.
set_glog_url = self.SET_GLOG_LOGLEVEL_URL + "?glog=foo"
- self.get_and_check_status(set_glog_url, "Bad glog level input", False)
+ self.get_and_check_status(set_glog_url, "Bad glog level input")
# Try a non-existent endpoint on log_level URL.
bad_loglevel_url = self.SET_GLOG_LOGLEVEL_URL + "?badurl=foo"
- self.get_and_check_status(bad_loglevel_url, without_ss=False)
+ self.get_and_check_status(bad_loglevel_url)
def test_catalog(self):
"""Tests the /catalog and /catalog_object endpoints."""
- self.get_and_check_status(self.CATALOG_URL, "functional", without_ss=True)
- self.get_and_check_status(self.CATALOG_URL, "alltypes", without_ss=True)
+ self.get_and_check_status_jvm(self.CATALOG_URL, "functional")
+ self.get_and_check_status_jvm(self.CATALOG_URL, "alltypes")
# IMPALA-5028: Test toThrift() of a partitioned table via the WebUI code path.
self.__test_catalog_object("functional", "alltypes")
self.__test_catalog_object("functional_parquet", "alltypes")
@@ -149,8 +157,31 @@ class TestWebPage(ImpalaTestSuite):
self.client.execute("invalidate metadata %s.%s" % (db_name, tbl_name))
self.get_and_check_status(self.CATALOG_OBJECT_URL +
"?object_type=TABLE&object_name=%s.%s" % (db_name, tbl_name), tbl_name,
- without_ss=True)
+ ports_to_test=self.TEST_PORTS_WITHOUT_SS)
self.client.execute("select count(*) from %s.%s" % (db_name, tbl_name))
self.get_and_check_status(self.CATALOG_OBJECT_URL +
"?object_type=TABLE&object_name=%s.%s" % (db_name, tbl_name), tbl_name,
- without_ss=True)
+ ports_to_test=self.TEST_PORTS_WITHOUT_SS)
+
+ def test_query_details(self, unique_database):
+ """Test that /query_backends returns the list of backend states for DML or queries;
+ nothing for DDL statements"""
+ CROSS_JOIN = ("select count(*) from functional.alltypes a "
+ "CROSS JOIN functional.alltypes b CROSS JOIN functional.alltypes c")
+ for q in [CROSS_JOIN,
+ "CREATE TABLE {0}.foo AS {1}".format(unique_database, CROSS_JOIN),
+ "DESCRIBE functional.alltypes"]:
+ query_handle = self.client.execute_async(q)
+ try:
+ response = self.get_and_check_status(
+ self.QUERY_BACKENDS_URL + "?query_id=%s&json" % query_handle.get_handle().id,
+ ports_to_test=[25000])
+
+ response_json = json.loads(response)
+
+ if "DESCRIBE" not in q:
+ assert len(response_json['backend_states']) > 0
+ else:
+ assert 'backend_states' not in response_json
+ finally:
+ self.client.cancel(query_handle)
http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/ff5e9b6c/www/query_backends.tmpl
----------------------------------------------------------------------
diff --git a/www/query_backends.tmpl b/www/query_backends.tmpl
new file mode 100644
index 0000000..07d1b57
--- /dev/null
+++ b/www/query_backends.tmpl
@@ -0,0 +1,94 @@
+<!--
+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.
+-->
+
+{{> www/common-header.tmpl }}
+{{> www/query_detail_tabs.tmpl }}
+<br/>
+{{?backend_states}}
+<div>
+ <label>
+ <input type="checkbox" checked="true" id="toggle" onClick="toggleRefresh()"/>
+ <span id="refresh_on">Auto-refresh on</span>
+ </label> Last updated: <span id="last-updated"></span>
+</div>
+
+<br/>
+<table id="backends" class='table table-hover table-bordered'>
+ <thead>
+ <tr>
+ <th>Host</th>
+ <th>Num. instances</th>
+ <th>Num. remaining instances</th>
+ <th>Done</th>
+ <th>Peak mem. consumption</th>
+ <th>Time since last report (ms)</th>
+ </tr>
+ </thead>
+ <tbody>
+
+ </tbody>
+</table>
+
+<script>
+document.getElementById("backends-tab").className = "active";
+
+var intervalId = 0;
+var table = null;
+var refresh = function () {
+ table.ajax.reload();
+ document.getElementById("last-updated").textContent = new Date();
+};
+
+$(document).ready(function() {
+ table = $('#backends').DataTable({
+ ajax: { url: "/query_backends?query_id={{query_id}}&json",
+ dataSrc: "backend_states",
+ },
+ "columns": [ {data: 'host'},
+ {data: 'num_instances'},
+ {data: 'num_remaining_instances'},
+ {data: 'done'},
+ {data: 'peak_mem_consumption'},
+ {data: 'time_since_last_heard_from'}],
+ "order": [[ 0, "desc" ]],
+ "pageLength": 100
+ });
+ intervalId = setInterval( refresh, 1000 );
+});
+
+function toggleRefresh() {
+ if (document.getElementById("toggle").checked == true) {
+ intervalId = setInterval(refresh, 1000);
+ document.getElementById("refresh_on").textContent = "Auto-refresh on";
+ } else {
+ clearInterval(intervalId);
+ document.getElementById("refresh_on").textContent = "Auto-refresh off";
+ }
+}
+
+</script>
+{{/backend_states}}
+
+{{^backend_states}}
+<div class="alert alert-info" role="alert">
+Query <strong>{{query_id}}</strong> has completed, or has no backends.
+</div>
+{{/backend_states}}
+
+{{> www/common-footer.tmpl }}
http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/ff5e9b6c/www/query_detail_tabs.tmpl
----------------------------------------------------------------------
diff --git a/www/query_detail_tabs.tmpl b/www/query_detail_tabs.tmpl
index 64781f6..0318761 100644
--- a/www/query_detail_tabs.tmpl
+++ b/www/query_detail_tabs.tmpl
@@ -27,4 +27,5 @@ under the License.
<li id="summary-tab" role="presentation"><a href="/query_summary?query_id={{query_id}}">Summary</a></li>
<li id="profile-tab" role="presentation"><a href="/query_profile?query_id={{query_id}}">Profile</a></li>
<li id="memory-tab" role="presentation"><a href="/query_memory?query_id={{query_id}}">Memory</a></li>
+ <li id="backends-tab" role="presentation"><a href="/query_backends?query_id={{query_id}}">Backends</a></li>
</ul>