Files
impala/www/admission_controller.tmpl
Surya Hebbar 37d4a4eb56 IMPALA-13389: Refactor webUI scripts to use ES6 syntax
Currently, the scripts in the entire webUI contain variable and function
declarations using the ES5 standard.

In such declarations, all the variables are attached to the browser's
window object, polluting the global scope. Additionally, many unnamed
functions can be represented with cleaner and concise syntax.

This patch refactors such declarations, using the ES6 syntax.
-> Replacing 'var' declarations with 'let' and 'const'
  - To improve browser's memory utilization
  - For better scoping, immutability and readability
-> Replacing unnamed function declarations with arrow functions
  - Better scoping by binding 'this' object to the surrounding context

These improve maintainability and browser's memory utilization.

Across many instances within the webUI scripts, no particular naming
scheme is being followed for variable and function identifiers.
Hence, they have been revised with the following naming scheme.
-> All function names have been declared using camel case.
-> All constant primitive values and strings have been declared
   in uppercase.
-> All other types of variables have been declared using snake case.

This naming scheme allows easier distinction between functions,
constants and other variables, based on the identifiers.

These changes to code style are further enforced during code review
through IMPALA-13473.

Change-Id: Ie38f2c642ede14956a2c6d551a58e42538204768
Reviewed-on: http://gerrit.cloudera.org:8080/21851
Reviewed-by: Impala Public Jenkins <impala-public-jenkins@cloudera.com>
Tested-by: Impala Public Jenkins <impala-public-jenkins@cloudera.com>
2025-04-10 20:33:49 +00:00

437 lines
14 KiB
Cheetah

<!--
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.
Example of json received from the impala server
"resource_pools_plain_json": <..the whole json below in text.>
"resource_pools": [
{
"pool_name": "default-pool",
"agg_num_running": 1,
"agg_num_queued": 4,
"agg_mem_reserved": 10382760,
"local_mem_admitted": 10382760,
"local_num_admitted_running": 1,
"local_num_queued": 4,
"local_backend_mem_reserved": 10382760,
"local_backend_mem_usage": 16384,
"pool_max_mem_resources": 10485760,
"pool_max_requests": 10,
"pool_max_queued": 10,
"pool_queue_timeout": 60000,
"max_query_mem_limit": 0,
"min_query_mem_limit": 0,
"clamp_mem_limit_query_option": true,
"max_query_cpu_core_per_node_limit": 8,
"max_query_cpu_core_coordinator_limit": 8,
"wait_time_ms_EMA": 0.0,
"histogram": [
[
0,
0
],
.
.
.
[
127,
0
]
],
"queued_queries": [
{
"query_id": "6f49e509bfa5b347:207d8ef900000000",
"mem_limit": 10382760,
"mem_limit_to_admit": 10382760,
"num_backends": 1
},
{
"query_id": "854f954e79f79d87:18483b9400000000",
"mem_limit": 10382760,
"mem_limit_to_admit": 10382760,
"num_backends": 1
},
{
"query_id": "45421dce8bf5664f:6865a45200000000",
"mem_limit": 10382760,
"mem_limit_to_admit": 10382760,
"num_backends": 1
},
{
"query_id": "e249aecff1bf3372:d5527a2700000000",
"mem_limit": 10382760,
"mem_limit_to_admit": 10382760,
"num_backends": 1
}
],
"head_queued_reason": "Not enough aggregate memory available in pool default-pool with max mem resources 10.00 MB. Needed 9.90 MB but only 100.59 KB was available.",
"running_queries": [
{
"query_id": "b94cf355d6df041c:ba3b91400000000",
"mem_limit": 10382760,
"mem_limit_to_admit": 10382760,
"num_backends": 1
}
]
}
],
"statestore_admission_control_time_since_last_update_ms": 745,
"statestore_update_staleness_detail": "Warning: admission control information from statestore is stale: 745ms since last update was received.",
"get_all_pools": true
-->
{{> www/common-header.tmpl }}
{{?statestore_update_staleness_detail}}
<div class="alert alert-danger" role="alert">
<strong>{{statestore_update_staleness_detail}}</strong>
</div>
{{/statestore_update_staleness_detail}}
<script src='{{ __common__.host-url }}/www/Chart-2.9.4.min.js'></script>
<script type="text/javascript">
window.onload = () => {
renderGraph();
formatMemoryColumns();
};
// Workaround for https://github.com/chartjs/Chart.js/issues/6154, where the last X-axis
// label is always omitted. This function bakes in a lot of assumptions for our chart.
function afterFit(axis) {
const ticks = axis.getTicks();
const WIDEST_WIDTH = axis._labelSizes.widest.width;
const CHART_WIDTH = axis.width;
// Apply a rough heuristic for rotation.
const MAX_FIT_COUNT = Math.trunc(3 * CHART_WIDTH / (2 * WIDEST_WIDTH));
// Limit to 20 labels.
const WILL_FIT_COUNT = Math.min(MAX_FIT_COUNT, 20);
// Ensure first and last are always shown.
const valid_label_indices = new Set();
valid_label_indices.add(0);
valid_label_indices.add(ticks.length - 1);
const NUM_LABELS = ticks.length % 2 === 0 ? WILL_FIT_COUNT : WILL_FIT_COUNT - 1;
const INTERVAL = ticks.length / (NUM_LABELS - 1);
for (let i = 1; i < WILL_FIT_COUNT - 1; i += 1) {
valid_label_indices.add(Math.floor(INTERVAL * i));
}
ticks.forEach((tick, index) => {
Object.assign(
tick,
{ label: valid_label_indices.has(index) ? tick.label : null },
);
});
}
// Picks up all the canvas elements associated with each resource pool and renders its
// peak memory usage histogram.
function renderGraph() {
const plain_json = document.getElementById("resource_pools_plain_json");
const json = JSON.parse(plain_json.innerText);
const canvases = document.getElementsByTagName("canvas");
for (let pool_idx = 0; pool_idx < json.length; pool_idx++){
const POOL_NAME = json[pool_idx]['pool_name'];
const histogram = json[pool_idx]["peak_mem_usage_histogram"];
const hist_labels = new Array();
const hist_values = new Array();
for (let i = 0; i < histogram.length; i++) {
const hist_elem = histogram[i];
//hist_labels.push((hist_elem[0]-1) + " - " + hist_elem[0] + " GB");
hist_labels.push((hist_elem[0] + 1) + " GB");
hist_values.push(hist_elem[1]);
}
hist_labels[hist_labels.length-1] += " and above"
// Render the bar chart now.
const chart_data = {
labels: hist_labels,
datasets: [{
label: 'Number of Queries',
backgroundColor: '#2E6595', // Impala logo's color
data: hist_values
}],
};
const ctx = canvases[POOL_NAME].getContext('2d');
window.myBar = new Chart(ctx, {
type: 'bar',
data: chart_data,
options: {
responsive: true,
legend: {
position: 'top',
},
title: {
display: true,
text: 'Peak Memory per Host'
},
scales: {
xAxes: [{
afterFit: afterFit,
ticks: {
autoSkip: false
}
}]
}
}
});
}
}
// Picks up all the elements classified as memory and replaces it with pretty printed
// value.
function formatMemoryColumns() {
const cols = document.getElementsByClassName("memory");
for (let idx = 0; idx < cols.length; idx++) {
cols[idx].innerText = formatMemory(cols[idx].innerText);
}
}
const memory_key = [
{'unit': 'B', 'val': 1},
{'unit': 'KB', 'val': 1024},
{'unit': 'MB', 'val': 1048576},
{'unit': 'GB', 'val': 1073741824}
]
// Helper method that takes in a value (in bytes) and outputs its pretty printed value.
function formatMemory(val) {
const MEM_BYTES = parseInt(val);
for (let idx = memory_key.length - 1; idx >= 0; idx--) {
const RESULT_MEM = parseFloat(MEM_BYTES / memory_key[idx].val);
if (RESULT_MEM < 1) continue;
return RESULT_MEM.toFixed(2) + " " + memory_key[idx].unit;
}
return "0 B";
}
function reset_all() {
if (!confirm('Are you sure you want to reset stats for all resource pools ?')) return;
const xhr = new XMLHttpRequest();
xhr.onload = () => {
window.location.reload(true);
}
xhr.open('GET', make_url("/resource_pool_reset"), true);
xhr.send();
}
function reset_method(pool_name) {
if (!confirm('Are you sure you want to reset stats for ' + pool_name +' ?')) return;
const xhr = new XMLHttpRequest();
xhr.onload = () => {
window.location.reload(true);
}
xhr.open('GET', make_url("/resource_pool_reset?pool_name=" + pool_name), true);
xhr.send();
}
</script>
<h2>Admission Controller
{{?get_all_pools}}
<button class="btn btn-warning btn-xs" onClick="reset_all();">
Reset informational stats for all pools
</button>
{{/get_all_pools}}
</h2>
{{^get_all_pools}}
<p id="show_all_pools" class="lead">
<a href='{{ __common__.host-url }}/admission'> < Show all Resource Pools</a>
</p>
{{/get_all_pools}}
<p class="lead">
This page lists all resource pools to which queries have been submitted
at least once and their corresponding state and statistics.<br>See the
<a href='{{ __common__.host-url }}/backends'>backends</a> debug page for memory admitted and reserved per
backend.
</p>
<p class="lead">
<strong>
Time since last statestore update containing admission control topic state (ms):
</strong>
{{statestore_admission_control_time_since_last_update_ms}}
</p>
{{#resource_pools}}
<div class="container-fluid">
<h3><a href='{{ __common__.host-url }}/admission?pool_name={{pool_name}}'>{{pool_name}}</a></h3>
<h4>Pool Config</h4>
<table class='table table-hover table-border'>
<tr>
<th>Property</th>
<th>Value</th>
</tr>
<tr>
<td>Max memory (cluster wide)</td>
<td class='memory'>{{pool_max_mem_resources}}</td>
</tr>
<tr>
<td>Max concurrent queries</td>
<td>{{pool_max_requests}}</td>
</tr>
<tr>
<td>Max queue size</td>
<td>{{pool_max_queued}}</td>
</tr>
<tr>
<td>Queue Timeout (ms)</td>
<td>{{pool_queue_timeout}}</td>
</tr>
<tr>
<td><b>Min</b> Query MEM_LIMIT range</td>
<td class='memory'>{{min_query_mem_limit}}</td>
</tr>
<tr>
<td><b>Max</b> Query MEM_LIMIT range</td>
<td class='memory'>{{max_query_mem_limit}}</td>
</tr>
<tr>
<td>Clamp MEM_LIMIT query option</td>
<td>{{clamp_mem_limit_query_option}}</td>
</tr>
<tr>
<td>Query CPU core per node</td>
<td>{{max_query_cpu_core_per_node_limit}}</td>
</tr>
<tr>
<td>Query CPU core on coordinator</td>
<td>{{max_query_cpu_core_coordinator_limit}}</td>
</tr>
</table>
<h4>Queued queries in order of being queued (submitted to this coordinator)</h4>
<table class='table table-hover table-border'>
<tr>
<th>Query ID</th>
<th>Memory limit for the executors</th>
<th>Memory admitted on the executors</th>
<th>Memory limit for the coordinator</th>
<th>Memory admitted on the coordinator</th>
<th>Num of backends it will run on</th>
<th>Details</th>
</tr>
{{#queued_queries}}
<tr>
<td>{{query_id}}</td>
<td class='memory'>{{mem_limit}}</td>
<td class='memory'>{{mem_limit_to_admit}}</td>
<td class='memory'>{{coord_mem_limit}}</td>
<td class='memory'>{{coord_mem_to_admit}}</td>
<td>{{num_backends}}</td>
<td><a href='{{ __common__.host-url }}/query_plan?query_id={{query_id}}'>Details</a></td>
</tr>
{{/queued_queries}}
</table>
<h4>Running queries (submitted to this coordinator)</h4>
<table class='table table-hover table-border'>
<tr>
<th>Query ID</th>
<th>Memory limit for the executors</th>
<th>Memory admitted on the executors</th>
<th>Memory limit for the coordinator</th>
<th>Memory admitted on the coordinator</th>
<th>Num of backends it will run on</th>
<th>Details</th>
</tr>
{{#running_queries}}
<tr>
<td>{{query_id}}</td>
<td class='memory'>{{mem_limit}}</td>
<td class='memory'>{{mem_limit_to_admit}}</td>
<td class='memory'>{{coord_mem_limit}}</td>
<td class='memory'>{{coord_mem_to_admit}}</td>
<td>{{num_backends}}</td>
<td><a href='{{ __common__.host-url }}/query_plan?query_id={{query_id}}'>Details</a></td>
</tr>
{{/running_queries}}
</table>
<h4>Pool stats
<button class="btn btn-warning btn-xs" onClick="reset_method('{{pool_name}}');">
Reset informational stats
</button>
</h4>
<table class='table table-hover table-border'>
<tr>
<th>Property</th>
<th>Value</th>
<th>Limit / Max value</th>
</tr>
<tr>
<td>Total queries <b>admitted</b> by this coordinator</td>
<td colspan='2'>{{total_admitted}}</td>
</tr>
<tr>
<td>Total queries <b>rejected</b> by this coordinator</td>
<td colspan='2'>{{total_rejected}}</td>
</tr>
<tr>
<td>Total queries <b>timed out</b> on this coordinator</td>
<td colspan='2'>{{total_timed_out}}</td>
</tr>
<tr>
<td>Queries currently running</td>
<td>{{agg_num_running}}</td>
<td>{{pool_max_requests}}</td>
</tr>
<tr>
<td>Queries currently queued</td>
<td>{{agg_num_queued}}</td>
<td>{{pool_max_queued}}</td>
</tr>
<tr>
<td>Total memory reserved across cluster</td>
<td class='memory'>{{agg_mem_reserved}}</td>
<td class='memory'>{{pool_max_mem_resources}}</td>
</tr>
<tr>
<td>Memory admitted on this coordinator</td>
<td class='memory'>{{local_mem_admitted}}</td>
<td class='memory'>{{pool_max_mem_resources}}</td>
</tr>
<tr>
<td>Queued reason of query at the head of the queue</td>
<td colspan='2'>{{head_queued_reason}}</td>
</tr>
<tr>
<td>Time in queue (exponential moving average)</td>
<td colspan='2'>{{wait_time_ms_ema}} ms</td>
</tr>
<tr>
<td>Users with queries queued or running on this coordinator (visible if pool has user quotas configured)</td>
<td colspan='2'>{{local_current_users}}</td>
</tr>
<tr>
<td>Users with queries queued or running, aggregated across coordinators (visible if pool has user quotas configured)</td>
<td colspan='2'>{{agg_current_users}}</td>
</tr>
<tr>
<td colspan='3'>
<canvas id="{{pool_name}}" style="border:1px solid"></canvas>
</td>
</tr>
</table>
</div>
{{/resource_pools}}
<div id="resource_pools_plain_json" style="display: none;">
{{resource_pools_plain_json}}
</div>
{{> www/common-footer.tmpl }}