yahoo-eng-team team mailing list archive
-
yahoo-eng-team team
-
Mailing list archive
-
Message #32308
[Bug 1447781] [NEW] Exponential ajax calls when refreshing multiple rows in horizon tables.
Public bug reported:
Exponential ajax calls when refreshing multiple rows in horizon tables.
When you launch multiple instances, each instance status refresh makes
an ajax call to any status_unknown instance, when the number is big
(>50) it kill the client CPU.
horizon/static/horizon/js/horizon.tables.js
Original:
horizon.datatables = {
update: function () {
var $rows_to_update = $('tr.status_unknown.ajax-update');
if ($rows_to_update.length) {
var interval = $rows_to_update.attr('data-update-interval'),
$table = $rows_to_update.closest('table'),
decay_constant = $table.attr('decay_constant');
// Do not update this row if the action column is expanded
if ($rows_to_update.find('.actions_column .btn-group.open').length) {
// Wait and try to update again in next interval instead
setTimeout(horizon.datatables.update, interval);
// Remove interval decay, since this will not hit server
$table.removeAttr('decay_constant');
return;
}
// Trigger the update handlers.
$rows_to_update.each(function(index, row) {
var $row = $(this),
$table = $row.closest('table.datatable');
horizon.ajax.queue({
url: $row.attr('data-update-url'),
error: function (jqXHR, textStatus, errorThrown) {
switch (jqXHR.status) {
// A 404 indicates the object is gone, and should be removed from the table
case 404:
// Update the footer count and reset to default empty row if needed
var $footer, row_count, footer_text, colspan, template, params, $empty_row;
// existing count minus one for the row we're removing
row_count = horizon.datatables.update_footer_count($table, -1);
if(row_count === 0) {
colspan = $table.find('th[colspan]').attr('colspan');
template = horizon.templates.compiled_templates["#empty_row_template"];
params = {
"colspan": colspan,
no_items_label: gettext("No items to display.")
};
empty_row = template.render(params);
$row.replaceWith(empty_row);
} else {
$row.remove();
}
// Reset tablesorter's data cache.
$table.trigger("update");
// Enable launch action if quota is not exceeded
horizon.datatables.update_actions();
break;
default:
horizon.utils.log(gettext("An error occurred while updating."));
$row.removeClass("ajax-update");
$row.find("i.ajax-updating").remove();
break;
}
},
success: function (data, textStatus, jqXHR) {
var $new_row = $(data);
if ($new_row.hasClass('status_unknown')) {
var spinner_elm = $new_row.find("td.status_unknown:last");
var imagePath = $new_row.find('.btn-action-required').length > 0 ?
"dashboard/img/action_required.png":
"dashboard/img/loading.gif";
imagePath = STATIC_URL + imagePath;
spinner_elm.prepend(
$("<div>")
.addClass("loading_gif")
.append($("<img>").attr("src", imagePath)));
}
// Only replace row if the html content has changed
if($new_row.html() !== $row.html()) {
if($row.find('.table-row-multi-select:checkbox').is(':checked')) {
// Preserve the checkbox if it's already clicked
$new_row.find('.table-row-multi-select:checkbox').prop('checked', true);
}
$row.replaceWith($new_row);
// Reset tablesorter's data cache.
$table.trigger("update");
// Reset decay constant.
$table.removeAttr('decay_constant');
// Check that quicksearch is enabled for this table
// Reset quicksearch's data cache.
if ($table.attr('id') in horizon.datatables.qs) {
horizon.datatables.qs[$table.attr('id')].cache();
}
}
},
complete: function (jqXHR, textStatus) {
// Revalidate the button check for the updated table
horizon.datatables.validate_button();
// Set interval decay to this table, and increase if it already exist
if(decay_constant === undefined) {
decay_constant = 1;
} else {
decay_constant++;
}
$table.attr('decay_constant', decay_constant);
// Poll until there are no rows in an "unknown" state on the page.
next_poll = interval * decay_constant;
// Limit the interval to 30 secs
if(next_poll > 30 * 1000) { next_poll = 30 * 1000; }
setTimeout(horizon.datatables.update, next_poll);
}
});
});
}
},
___________________________________________________________________________
Our fix:
horizon.datatables = {
update: function (row_id) {
var $rows_to_update = undefined;
//Filter by row_id to prevent exponential ajax calls
if(row_id!=undefined) {
$rows_to_update = $('tr#' + row_id + '.status_unknown.ajax-update');
}
else {
$rows_to_update = $('tr.status_unknown.ajax-update');
}
if ($rows_to_update.length) {
var interval = $rows_to_update.attr('data-update-interval'),
$table = $rows_to_update.closest('table'),
decay_constant = $table.attr('decay_constant');
// Do not update this row if the action column is expanded
if ($rows_to_update.find('.actions_column .btn-group.open').length) {
// Wait and try to update again in next interval instead
setTimeout(horizon.datatables.update, interval);
return;
}
// Set interval decay to this table, and increase if it already exist
if(decay_constant === undefined) {
decay_constant = 1;
} else {
decay_constant++;
}
$table.attr('decay_constant', decay_constant);
// Poll until there are no rows in an "unknown" state on the page.
next_poll = interval * decay_constant;
// Limit the interval to 30 secs
if(next_poll > 30 * 1000) { next_poll = 30 * 1000; }
// Trigger the update handlers.
$rows_to_update.each(function(index, row) {
var $row = $(this),
$table = $row.closest('table.datatable');
horizon.ajax.queue({
url: $row.attr('data-update-url'),
error: function (jqXHR, textStatus, errorThrown) {
switch (jqXHR.status) {
// A 404 indicates the object is gone, and should be removed from the table
case 404:
// Update the footer count and reset to default empty row if needed
var $footer, row_count, footer_text, colspan, template, params, $empty_row;
// existing count minus one for the row we're removing
row_count = horizon.datatables.update_footer_count($table, -1);
if(row_count === 0) {
colspan = $table.find('th[colspan]').attr('colspan');
template = horizon.templates.compiled_templates["#empty_row_template"];
params = {
"colspan": colspan,
no_items_label: gettext("No items to display.")
};
empty_row = template.render(params);
$row.replaceWith(empty_row);
} else {
$row.remove();
}
// Reset tablesorter's data cache.
$table.trigger("update");
// Enable launch action if quota is not exceeded
horizon.datatables.update_actions();
break;
default:
horizon.utils.log(gettext("An error occurred while updating."));
$row.removeClass("ajax-update");
$row.find("i.ajax-updating").remove();
break;
}
},
success: function (data, textStatus, jqXHR) {
var $new_row = $(data);
if ($new_row.hasClass('status_unknown')) {
var spinner_elm = $new_row.find("td.status_unknown:last");
var imagePath = $new_row.find('.btn-action-required').length > 0 ?
"dashboard/img/action_required.png":
"dashboard/img/loading.gif";
imagePath = STATIC_URL + imagePath;
spinner_elm.prepend(
$("<div>")
.addClass("loading_gif")
.append($("<img>").attr("src", imagePath)));
}
// Only replace row if the html content has changed
if($new_row.html() !== $row.html()) {
if($row.find('.table-row-multi-select:checkbox').is(':checked')) {
// Preserve the checkbox if it's already clicked
$new_row.find('.table-row-multi-select:checkbox').prop('checked', true);
}
$row.replaceWith($new_row);
// Reset tablesorter's data cache.
$table.trigger("update");
// Check that quicksearch is enabled for this table
// Reset quicksearch's data cache.
if ($table.attr('id') in horizon.datatables.qs) {
horizon.datatables.qs[$table.attr('id')].cache();
}
}
},
complete: function (jqXHR, textStatus) {
// Revalidate the button check for the updated table
horizon.datatables.validate_button();
setTimeout(horizon.datatables.update($row.attr('id')), next_poll);
}
});
});
}
},
** Affects: horizon
Importance: Undecided
Status: New
--
You received this bug notification because you are a member of Yahoo!
Engineering Team, which is subscribed to OpenStack Dashboard (Horizon).
https://bugs.launchpad.net/bugs/1447781
Title:
Exponential ajax calls when refreshing multiple rows in horizon
tables.
Status in OpenStack Dashboard (Horizon):
New
Bug description:
Exponential ajax calls when refreshing multiple rows in horizon
tables. When you launch multiple instances, each instance status
refresh makes an ajax call to any status_unknown instance, when the
number is big (>50) it kill the client CPU.
horizon/static/horizon/js/horizon.tables.js
Original:
horizon.datatables = {
update: function () {
var $rows_to_update = $('tr.status_unknown.ajax-update');
if ($rows_to_update.length) {
var interval = $rows_to_update.attr('data-update-interval'),
$table = $rows_to_update.closest('table'),
decay_constant = $table.attr('decay_constant');
// Do not update this row if the action column is expanded
if ($rows_to_update.find('.actions_column .btn-group.open').length) {
// Wait and try to update again in next interval instead
setTimeout(horizon.datatables.update, interval);
// Remove interval decay, since this will not hit server
$table.removeAttr('decay_constant');
return;
}
// Trigger the update handlers.
$rows_to_update.each(function(index, row) {
var $row = $(this),
$table = $row.closest('table.datatable');
horizon.ajax.queue({
url: $row.attr('data-update-url'),
error: function (jqXHR, textStatus, errorThrown) {
switch (jqXHR.status) {
// A 404 indicates the object is gone, and should be removed from the table
case 404:
// Update the footer count and reset to default empty row if needed
var $footer, row_count, footer_text, colspan, template, params, $empty_row;
// existing count minus one for the row we're removing
row_count = horizon.datatables.update_footer_count($table, -1);
if(row_count === 0) {
colspan = $table.find('th[colspan]').attr('colspan');
template = horizon.templates.compiled_templates["#empty_row_template"];
params = {
"colspan": colspan,
no_items_label: gettext("No items to display.")
};
empty_row = template.render(params);
$row.replaceWith(empty_row);
} else {
$row.remove();
}
// Reset tablesorter's data cache.
$table.trigger("update");
// Enable launch action if quota is not exceeded
horizon.datatables.update_actions();
break;
default:
horizon.utils.log(gettext("An error occurred while updating."));
$row.removeClass("ajax-update");
$row.find("i.ajax-updating").remove();
break;
}
},
success: function (data, textStatus, jqXHR) {
var $new_row = $(data);
if ($new_row.hasClass('status_unknown')) {
var spinner_elm = $new_row.find("td.status_unknown:last");
var imagePath = $new_row.find('.btn-action-required').length > 0 ?
"dashboard/img/action_required.png":
"dashboard/img/loading.gif";
imagePath = STATIC_URL + imagePath;
spinner_elm.prepend(
$("<div>")
.addClass("loading_gif")
.append($("<img>").attr("src", imagePath)));
}
// Only replace row if the html content has changed
if($new_row.html() !== $row.html()) {
if($row.find('.table-row-multi-select:checkbox').is(':checked')) {
// Preserve the checkbox if it's already clicked
$new_row.find('.table-row-multi-select:checkbox').prop('checked', true);
}
$row.replaceWith($new_row);
// Reset tablesorter's data cache.
$table.trigger("update");
// Reset decay constant.
$table.removeAttr('decay_constant');
// Check that quicksearch is enabled for this table
// Reset quicksearch's data cache.
if ($table.attr('id') in horizon.datatables.qs) {
horizon.datatables.qs[$table.attr('id')].cache();
}
}
},
complete: function (jqXHR, textStatus) {
// Revalidate the button check for the updated table
horizon.datatables.validate_button();
// Set interval decay to this table, and increase if it already exist
if(decay_constant === undefined) {
decay_constant = 1;
} else {
decay_constant++;
}
$table.attr('decay_constant', decay_constant);
// Poll until there are no rows in an "unknown" state on the page.
next_poll = interval * decay_constant;
// Limit the interval to 30 secs
if(next_poll > 30 * 1000) { next_poll = 30 * 1000; }
setTimeout(horizon.datatables.update, next_poll);
}
});
});
}
},
___________________________________________________________________________
Our fix:
horizon.datatables = {
update: function (row_id) {
var $rows_to_update = undefined;
//Filter by row_id to prevent exponential ajax calls
if(row_id!=undefined) {
$rows_to_update = $('tr#' + row_id + '.status_unknown.ajax-update');
}
else {
$rows_to_update = $('tr.status_unknown.ajax-update');
}
if ($rows_to_update.length) {
var interval = $rows_to_update.attr('data-update-interval'),
$table = $rows_to_update.closest('table'),
decay_constant = $table.attr('decay_constant');
// Do not update this row if the action column is expanded
if ($rows_to_update.find('.actions_column .btn-group.open').length) {
// Wait and try to update again in next interval instead
setTimeout(horizon.datatables.update, interval);
return;
}
// Set interval decay to this table, and increase if it already exist
if(decay_constant === undefined) {
decay_constant = 1;
} else {
decay_constant++;
}
$table.attr('decay_constant', decay_constant);
// Poll until there are no rows in an "unknown" state on the page.
next_poll = interval * decay_constant;
// Limit the interval to 30 secs
if(next_poll > 30 * 1000) { next_poll = 30 * 1000; }
// Trigger the update handlers.
$rows_to_update.each(function(index, row) {
var $row = $(this),
$table = $row.closest('table.datatable');
horizon.ajax.queue({
url: $row.attr('data-update-url'),
error: function (jqXHR, textStatus, errorThrown) {
switch (jqXHR.status) {
// A 404 indicates the object is gone, and should be removed from the table
case 404:
// Update the footer count and reset to default empty row if needed
var $footer, row_count, footer_text, colspan, template, params, $empty_row;
// existing count minus one for the row we're removing
row_count = horizon.datatables.update_footer_count($table, -1);
if(row_count === 0) {
colspan = $table.find('th[colspan]').attr('colspan');
template = horizon.templates.compiled_templates["#empty_row_template"];
params = {
"colspan": colspan,
no_items_label: gettext("No items to display.")
};
empty_row = template.render(params);
$row.replaceWith(empty_row);
} else {
$row.remove();
}
// Reset tablesorter's data cache.
$table.trigger("update");
// Enable launch action if quota is not exceeded
horizon.datatables.update_actions();
break;
default:
horizon.utils.log(gettext("An error occurred while updating."));
$row.removeClass("ajax-update");
$row.find("i.ajax-updating").remove();
break;
}
},
success: function (data, textStatus, jqXHR) {
var $new_row = $(data);
if ($new_row.hasClass('status_unknown')) {
var spinner_elm = $new_row.find("td.status_unknown:last");
var imagePath = $new_row.find('.btn-action-required').length > 0 ?
"dashboard/img/action_required.png":
"dashboard/img/loading.gif";
imagePath = STATIC_URL + imagePath;
spinner_elm.prepend(
$("<div>")
.addClass("loading_gif")
.append($("<img>").attr("src", imagePath)));
}
// Only replace row if the html content has changed
if($new_row.html() !== $row.html()) {
if($row.find('.table-row-multi-select:checkbox').is(':checked')) {
// Preserve the checkbox if it's already clicked
$new_row.find('.table-row-multi-select:checkbox').prop('checked', true);
}
$row.replaceWith($new_row);
// Reset tablesorter's data cache.
$table.trigger("update");
// Check that quicksearch is enabled for this table
// Reset quicksearch's data cache.
if ($table.attr('id') in horizon.datatables.qs) {
horizon.datatables.qs[$table.attr('id')].cache();
}
}
},
complete: function (jqXHR, textStatus) {
// Revalidate the button check for the updated table
horizon.datatables.validate_button();
setTimeout(horizon.datatables.update($row.attr('id')), next_poll);
}
});
});
}
},
To manage notifications about this bug go to:
https://bugs.launchpad.net/horizon/+bug/1447781/+subscriptions
Follow ups
References