/* vim: set expandtab sw=4 ts=4 sts=4: */ var runtime = {}, server_time_diff, server_os, is_superuser, server_db_isLocal, chartSize; AJAX.registerOnload('server_status_monitor.js', function () { var $js_data_form = $('#js_data'); server_time_diff = new Date().getTime() - $js_data_form.find("input[name=server_time]").val(); server_os = $js_data_form.find("input[name=server_os]").val(); is_superuser = $js_data_form.find("input[name=is_superuser]").val(); server_db_isLocal = $js_data_form.find("input[name=server_db_isLocal]").val(); }); /** * Unbind all event handlers before tearing down a page */ AJAX.registerTeardown('server_status_monitor.js', function () { $('#emptyDialog').remove(); $('#addChartDialog').remove(); $('a.popupLink').unbind('click'); $('body').unbind('click'); }); /** * Popup behaviour */ AJAX.registerOnload('server_status_monitor.js', function () { $('
') .attr('id', 'emptyDialog') .appendTo('#page_content'); $('#addChartDialog') .appendTo('#page_content'); $('a.popupLink').click(function () { var $link = $(this); $('div.' + $link.attr('href').substr(1)) .show() .offset({ top: $link.offset().top + $link.height() + 5, left: $link.offset().left }) .addClass('openedPopup'); return false; }); $('body').click(function (event) { $('div.openedPopup').each(function () { var $cnt = $(this); var pos = $cnt.offset(); // Hide if the mouseclick is outside the popupcontent if (event.pageX < pos.left || event.pageY < pos.top || event.pageX > pos.left + $cnt.outerWidth() || event.pageY > pos.top + $cnt.outerHeight() ) { $cnt.hide().removeClass('openedPopup'); } }); }); }); AJAX.registerTeardown('server_status_monitor.js', function () { $('a[href="#rearrangeCharts"], a[href="#endChartEditMode"]').unbind('click'); $('div.popupContent select[name="chartColumns"]').unbind('change'); $('div.popupContent select[name="gridChartRefresh"]').unbind('change'); $('a[href="#addNewChart"]').unbind('click'); $('a[href="#exportMonitorConfig"]').unbind('click'); $('a[href="#importMonitorConfig"]').unbind('click'); $('a[href="#clearMonitorConfig"]').unbind('click'); $('a[href="#pauseCharts"]').unbind('click'); $('a[href="#monitorInstructionsDialog"]').unbind('click'); $('input[name="chartType"]').unbind('click'); $('input[name="useDivisor"]').unbind('click'); $('input[name="useUnit"]').unbind('click'); $('select[name="varChartList"]').unbind('click'); $('a[href="#kibDivisor"]').unbind('click'); $('a[href="#mibDivisor"]').unbind('click'); $('a[href="#submitClearSeries"]').unbind('click'); $('a[href="#submitAddSeries"]').unbind('click'); // $("input#variableInput").destroy(); $('#chartPreset').unbind('click'); $('#chartStatusVar').unbind('click'); destroyGrid(); }); AJAX.registerOnload('server_status_monitor.js', function () { // Show tab links $('div.tabLinks').show(); $('#loadingMonitorIcon').remove(); // Codemirror is loaded on demand so we might need to initialize it if (! codemirror_editor) { var $elm = $('#sqlquery'); if ($elm.length > 0 && typeof CodeMirror != 'undefined') { codemirror_editor = CodeMirror.fromTextArea( $elm[0], { lineNumbers: true, matchBrackets: true, indentUnit: 4, mode: "text/x-mysql", lineWrapping: true } ); } } // Timepicker is loaded on demand so we need to initialize // datetime fields from the 'load log' dialog $('#logAnalyseDialog .datetimefield').each(function () { PMA_addDatepicker($(this)); }); /**** Monitor charting implementation ****/ /* Saves the previous ajax response for differential values */ var oldChartData = null; // Holds about to be created chart var newChart = null; var chartSpacing; // Whenever the monitor object (runtime.charts) or the settings object // (monitorSettings) changes in a way incompatible to the previous version, // increase this number. It will reset the users monitor and settings object // in his localStorage to the default configuration var monitorProtocolVersion = '1.0'; // Runtime parameter of the monitor, is being fully set in initGrid() runtime = { // Holds all visible charts in the grid charts: null, // Stores the timeout handler so it can be cleared refreshTimeout: null, // Stores the GET request to refresh the charts refreshRequest: null, // Chart auto increment chartAI: 0, // To play/pause the monitor redrawCharts: false, // Object that contains a list of nodes that need to be retrieved // from the server for chart updates dataList: [], // Current max points per chart (needed for auto calculation) gridMaxPoints: 20, // displayed time frame xmin: -1, xmax: -1 }; var monitorSettings = null; var defaultMonitorSettings = { columns: 3, chartSize: { width: 295, height: 250 }, // Max points in each chart. Settings it to 'auto' sets // gridMaxPoints to (chartwidth - 40) / 12 gridMaxPoints: 'auto', /* Refresh rate of all grid charts in ms */ gridRefresh: 5000 }; // Allows drag and drop rearrange and print/edit icons on charts var editMode = false; /* List of preconfigured charts that the user may select */ var presetCharts = { // Query cache efficiency 'qce': { title: PMA_messages.strQueryCacheEfficiency, series: [ { label: PMA_messages.strQueryCacheEfficiency } ], nodes: [ { dataPoints: [{type: 'statusvar', name: 'Qcache_hits'}, {type: 'statusvar', name: 'Com_select'}], transformFn: 'qce' } ], maxYLabel: 0 }, // Query cache usage 'qcu': { title: PMA_messages.strQueryCacheUsage, series: [ { label: PMA_messages.strQueryCacheUsed } ], nodes: [ { dataPoints: [{type: 'statusvar', name: 'Qcache_free_memory'}, {type: 'servervar', name: 'query_cache_size'}], transformFn: 'qcu' } ], maxYLabel: 0 } }; // time span selection var selectionTimeDiff = []; var selectionStartX, selectionStartY, selectionEndX, selectionEndY; var drawTimeSpan = false; // chart tooltip var tooltipBox; /* Add OS specific system info charts to the preset chart list */ switch (server_os) { case 'WINNT': $.extend(presetCharts, { 'cpu': { title: PMA_messages.strSystemCPUUsage, series: [ { label: PMA_messages.strAverageLoad } ], nodes: [ { dataPoints: [{ type: 'cpu', name: 'loadavg'}] } ], maxYLabel: 100 }, 'memory': { title: PMA_messages.strSystemMemory, series: [ { label: PMA_messages.strTotalMemory, fill: true }, { dataType: 'memory', label: PMA_messages.strUsedMemory, fill: true } ], nodes: [{ dataPoints: [{ type: 'memory', name: 'MemTotal' }], valueDivisor: 1024 }, { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 } ], maxYLabel: 0 }, 'swap': { title: PMA_messages.strSystemSwap, series: [ { label: PMA_messages.strTotalSwap, fill: true }, { label: PMA_messages.strUsedSwap, fill: true } ], nodes: [{ dataPoints: [{ type: 'memory', name: 'SwapTotal' }]}, { dataPoints: [{ type: 'memory', name: 'SwapUsed' }]} ], maxYLabel: 0 } }); break; case 'Linux': $.extend(presetCharts, { 'cpu': { title: PMA_messages.strSystemCPUUsage, series: [ { label: PMA_messages.strAverageLoad } ], nodes: [{ dataPoints: [{ type: 'cpu', name: 'irrelevant' }], transformFn: 'cpu-linux'}], maxYLabel: 0 }, 'memory': { title: PMA_messages.strSystemMemory, series: [ { label: PMA_messages.strBufferedMemory, fill: true}, { label: PMA_messages.strUsedMemory, fill: true}, { label: PMA_messages.strCachedMemory, fill: true}, { label: PMA_messages.strFreeMemory, fill: true} ], nodes: [ { dataPoints: [{ type: 'memory', name: 'Buffers' }], valueDivisor: 1024 }, { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 }, { dataPoints: [{ type: 'memory', name: 'Cached' }], valueDivisor: 1024 }, { dataPoints: [{ type: 'memory', name: 'MemFree' }], valueDivisor: 1024 } ], maxYLabel: 0 }, 'swap': { title: PMA_messages.strSystemSwap, series: [ { label: PMA_messages.strCachedSwap, fill: true}, { label: PMA_messages.strUsedSwap, fill: true}, { label: PMA_messages.strFreeSwap, fill: true} ], nodes: [ { dataPoints: [{ type: 'memory', name: 'SwapCached' }], valueDivisor: 1024 }, { dataPoints: [{ type: 'memory', name: 'SwapUsed' }], valueDivisor: 1024 }, { dataPoints: [{ type: 'memory', name: 'SwapFree' }], valueDivisor: 1024 } ], maxYLabel: 0 } }); break; case 'SunOS': $.extend(presetCharts, { 'cpu': { title: PMA_messages.strSystemCPUUsage, series: [ { label: PMA_messages.strAverageLoad } ], nodes: [ { dataPoints: [{ type: 'cpu', name: 'loadavg'}] } ], maxYLabel: 0 }, 'memory': { title: PMA_messages.strSystemMemory, series: [ { label: PMA_messages.strUsedMemory, fill: true }, { label: PMA_messages.strFreeMemory, fill: true } ], nodes: [ { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 }, { dataPoints: [{ type: 'memory', name: 'MemFree' }], valueDivisor: 1024 } ], maxYLabel: 0 }, 'swap': { title: PMA_messages.strSystemSwap, series: [ { label: PMA_messages.strUsedSwap, fill: true }, { label: PMA_messages.strFreeSwap, fill: true } ], nodes: [ { dataPoints: [{ type: 'memory', name: 'SwapUsed' }], valueDivisor: 1024 }, { dataPoints: [{ type: 'memory', name: 'SwapFree' }], valueDivisor: 1024 } ], maxYLabel: 0 } }); break; } // Default setting for the chart grid var defaultChartGrid = { 'c0': { title: PMA_messages.strQuestions, series: [ {label: PMA_messages.strQuestions} ], nodes: [ {dataPoints: [{ type: 'statusvar', name: 'Questions' }], display: 'differential' } ], maxYLabel: 0 }, 'c1': { title: PMA_messages.strChartConnectionsTitle, series: [ {label: PMA_messages.strConnections}, {label: PMA_messages.strProcesses} ], nodes: [ {dataPoints: [{ type: 'statusvar', name: 'Connections' }], display: 'differential' }, {dataPoints: [{ type: 'proc', name: 'processes' }] } ], maxYLabel: 0 }, 'c2': { title: PMA_messages.strTraffic, series: [ {label: PMA_messages.strBytesSent}, {label: PMA_messages.strBytesReceived} ], nodes: [ {dataPoints: [{ type: 'statusvar', name: 'Bytes_sent' }], display: 'differential', valueDivisor: 1024 }, {dataPoints: [{ type: 'statusvar', name: 'Bytes_received' }], display: 'differential', valueDivisor: 1024 } ], maxYLabel: 0 } }; // Server is localhost => We can add cpu/memory/swap to the default chart if (server_db_isLocal) { defaultChartGrid['c3'] = presetCharts['cpu']; defaultChartGrid['c4'] = presetCharts['memory']; defaultChartGrid['c5'] = presetCharts['swap']; } $('a[href="#rearrangeCharts"], a[href="#endChartEditMode"]').click(function (event) { event.preventDefault(); editMode = !editMode; if ($(this).attr('href') == '#endChartEditMode') { editMode = false; } $('a[href="#endChartEditMode"]').toggle(editMode); if (editMode) { // Close the settings popup $('div.popupContent').hide().removeClass('openedPopup'); $("#chartGrid").sortableTable({ ignoreRect: { top: 8, left: chartSize.width - 63, width: 54, height: 24 } }); } else { $("#chartGrid").sortableTable('destroy'); } saveMonitor(); // Save settings return false; }); // global settings $('div.popupContent select[name="chartColumns"]').change(function () { monitorSettings.columns = parseInt(this.value, 10); calculateChartSize(); // Empty cells should keep their size so you can drop onto them $('#chartGrid tr td').css('width', chartSize.width + 'px'); $('#chartGrid .monitorChart').css({ width: chartSize.width + 'px', height: chartSize.height + 'px' }); /* Reorder all charts that it fills all column cells */ var numColumns; var $tr = $('#chartGrid tr:first'); var row = 0; while ($tr.length !== 0) { numColumns = 1; // To many cells in one row => put into next row $tr.find('td').each(function () { if (numColumns > monitorSettings.columns) { if ($tr.next().length === 0) { $tr.after('' to any text content -.-
json = $.parseJSON(data.substring(data.indexOf("{"), data.lastIndexOf("}") + 1));
} catch (err) {
alert(PMA_messages.strFailedParsingConfig);
$('#emptyDialog').dialog('close');
return;
}
// Basic check, is this a monitor config json?
if (!json || ! json.monitorCharts || ! json.monitorCharts) {
alert(PMA_messages.strFailedParsingConfig);
$('#emptyDialog').dialog('close');
return;
}
// If json ok, try applying config
try {
window.localStorage['monitorCharts'] = JSON.stringify(json.monitorCharts);
window.localStorage['monitorSettings'] = JSON.stringify(json.monitorSettings);
rebuildGrid();
} catch (err) {
alert(PMA_messages.strFailedBuildingGrid);
// If an exception is thrown, load default again
window.localStorage.removeItem('monitorCharts');
window.localStorage.removeItem('monitorSettings');
rebuildGrid();
}
$('#emptyDialog').dialog('close');
});
$("body", d).append($form = $('#emptyDialog').find('form'));
$form.submit();
$('#emptyDialog').append('
');
};
dlgBtns[PMA_messages.strCancel] = function () {
$(this).dialog('close');
};
$('#emptyDialog').dialog({
width: 'auto',
height: 'auto',
buttons: dlgBtns
});
});
$('a[href="#clearMonitorConfig"]').click(function (event) {
event.preventDefault();
window.localStorage.removeItem('monitorCharts');
window.localStorage.removeItem('monitorSettings');
window.localStorage.removeItem('monitorVersion');
$(this).hide();
rebuildGrid();
});
$('a[href="#pauseCharts"]').click(function (event) {
event.preventDefault();
runtime.redrawCharts = ! runtime.redrawCharts;
if (! runtime.redrawCharts) {
$(this).html(PMA_getImage('play.png') + ' ' + PMA_messages.strResumeMonitor);
} else {
$(this).html(PMA_getImage('pause.png') + ' ' + PMA_messages.strPauseMonitor);
if (! runtime.charts) {
initGrid();
$('a[href="#settingsPopup"]').show();
}
}
return false;
});
$('a[href="#monitorInstructionsDialog"]').click(function (event) {
event.preventDefault();
var $dialog = $('#monitorInstructionsDialog');
$dialog.dialog({
width: 595,
height: 'auto'
}).find('img.ajaxIcon').show();
var loadLogVars = function (getvars) {
var vars = { ajax_request: true, logging_vars: true };
if (getvars) {
$.extend(vars, getvars);
}
$.get('server_status_monitor.php?' + PMA_commonParams.get('common_query'), vars,
function (data) {
var logVars;
if (data.success === true) {
logVars = data.message;
} else {
return serverResponseError();
}
var icon = PMA_getImage('s_success.png'), msg = '', str = '';
if (logVars['general_log'] == 'ON') {
if (logVars['slow_query_log'] == 'ON') {
msg = PMA_messages.strBothLogOn;
} else {
msg = PMA_messages.strGenLogOn;
}
}
if (msg.length === 0 && logVars['slow_query_log'] == 'ON') {
msg = PMA_messages.strSlowLogOn;
}
if (msg.length === 0) {
icon = PMA_getImage('s_error.png');
msg = PMA_messages.strBothLogOff;
}
str = '' + PMA_messages.strCurrentSettings + '
';
str += icon + msg + '
';
if (logVars['log_output'] != 'TABLE') {
str += PMA_getImage('s_error.png') + ' ' + PMA_messages.strLogOutNotTable + '
';
} else {
str += PMA_getImage('s_success.png') + ' ' + PMA_messages.strLogOutIsTable + '
';
}
if (logVars['slow_query_log'] == 'ON') {
if (logVars['long_query_time'] > 2) {
str += PMA_getImage('s_attention.png') + ' ';
str += $.sprintf(PMA_messages.strSmallerLongQueryTimeAdvice, logVars['long_query_time']);
str += '
';
}
if (logVars['long_query_time'] < 2) {
str += PMA_getImage('s_success.png') + ' ';
str += $.sprintf(PMA_messages.strLongQueryTimeSet, logVars['long_query_time']);
str += '
';
}
}
str += '';
if (is_superuser) {
str += '' + PMA_messages.strChangeSettings + '';
str += '';
str += PMA_messages.strSettingsAppliedGlobal + '
';
var varValue = 'TABLE';
if (logVars['log_output'] == 'TABLE') {
varValue = 'FILE';
}
str += '- ';
str += $.sprintf(PMA_messages.strSetLogOutput, varValue);
str += '
';
if (logVars['general_log'] != 'ON') {
str += '- ';
str += $.sprintf(PMA_messages.strEnableVar, 'general_log');
str += '
';
} else {
str += '- ';
str += $.sprintf(PMA_messages.strDisableVar, 'general_log');
str += '
';
}
if (logVars['slow_query_log'] != 'ON') {
str += '- ';
str += $.sprintf(PMA_messages.strEnableVar, 'slow_query_log');
str += '
';
} else {
str += '- ';
str += $.sprintf(PMA_messages.strDisableVar, 'slow_query_log');
str += '
';
}
varValue = 5;
if (logVars['long_query_time'] > 2) {
varValue = 1;
}
str += '- ';
str += $.sprintf(PMA_messages.setSetLongQueryTime, varValue);
str += '
';
} else {
str += PMA_messages.strNoSuperUser + '
';
}
str += '';
$dialog.find('div.monitorUse').toggle(
logVars['log_output'] == 'TABLE' && (logVars['slow_query_log'] == 'ON' || logVars['general_log'] == 'ON')
);
$dialog.find('div.ajaxContent').html(str);
$dialog.find('img.ajaxIcon').hide();
$dialog.find('a.set').click(function () {
var nameValue = $(this).attr('href').split('-');
loadLogVars({ varName: nameValue[0].substr(1), varValue: nameValue[1]});
$dialog.find('img.ajaxIcon').show();
});
}
);
};
loadLogVars();
return false;
});
$('input[name="chartType"]').change(function () {
$('#chartVariableSettings').toggle(this.checked && this.value == 'variable');
var title = $('input[name="chartTitle"]').val();
if (title == PMA_messages.strChartTitle ||
title == $('label[for="' + $('input[name="chartTitle"]').data('lastRadio') + '"]').text()
) {
$('input[name="chartTitle"]')
.data('lastRadio', $(this).attr('id'))
.val($('label[for="' + $(this).attr('id') + '"]').text());
}
});
$('input[name="useDivisor"]').change(function () {
$('span.divisorInput').toggle(this.checked);
});
$('input[name="useUnit"]').change(function () {
$('span.unitInput').toggle(this.checked);
});
$('select[name="varChartList"]').change(function () {
if (this.selectedIndex !== 0) {
$('#variableInput').val(this.value);
}
});
$('a[href="#kibDivisor"]').click(function (event) {
event.preventDefault();
$('input[name="valueDivisor"]').val(1024);
$('input[name="valueUnit"]').val(PMA_messages.strKiB);
$('span.unitInput').toggle(true);
$('input[name="useUnit"]').prop('checked', true);
return false;
});
$('a[href="#mibDivisor"]').click(function (event) {
event.preventDefault();
$('input[name="valueDivisor"]').val(1024 * 1024);
$('input[name="valueUnit"]').val(PMA_messages.strMiB);
$('span.unitInput').toggle(true);
$('input[name="useUnit"]').prop('checked', true);
return false;
});
$('a[href="#submitClearSeries"]').click(function (event) {
event.preventDefault();
$('#seriesPreview').html('' + PMA_messages.strNone + '');
newChart = null;
$('#clearSeriesLink').hide();
});
$('a[href="#submitAddSeries"]').click(function (event) {
event.preventDefault();
if ($('#variableInput').val() === "") {
return false;
}
if (newChart === null) {
$('#seriesPreview').html('');
newChart = {
title: $('input[name="chartTitle"]').val(),
nodes: [],
series: [],
maxYLabel: 0
};
}
var serie = {
dataPoints: [{ type: 'statusvar', name: $('#variableInput').val() }],
display: $('input[name="differentialValue"]').prop('checked') ? 'differential' : ''
};
if (serie.dataPoints[0].name == 'Processes') {
serie.dataPoints[0].type = 'proc';
}
if ($('input[name="useDivisor"]').prop('checked')) {
serie.valueDivisor = parseInt($('input[name="valueDivisor"]').val(), 10);
}
if ($('input[name="useUnit"]').prop('checked')) {
serie.unit = $('input[name="valueUnit"]').val();
}
var str = serie.display == 'differential' ? ', ' + PMA_messages.strDifferential : '';
str += serie.valueDivisor ? (', ' + $.sprintf(PMA_messages.strDividedBy, serie.valueDivisor)) : '';
str += serie.unit ? (', ' + PMA_messages.strUnit + ': ' + serie.unit) : '';
var newSeries = {
label: $('#variableInput').val().replace(/_/g, " ")
};
newChart.series.push(newSeries);
$('#seriesPreview').append('- ' + escapeHtml(newSeries.label + str) + '
');
newChart.nodes.push(serie);
$('#variableInput').val('');
$('input[name="differentialValue"]').prop('checked', true);
$('input[name="useDivisor"]').prop('checked', false);
$('input[name="useUnit"]').prop('checked', false);
$('input[name="useDivisor"]').trigger('change');
$('input[name="useUnit"]').trigger('change');
$('select[name="varChartList"]').get(0).selectedIndex = 0;
$('#clearSeriesLink').show();
return false;
});
$("#variableInput").autocomplete({
source: variableNames
});
/* Initializes the monitor, called only once */
function initGrid() {
var i;
/* Apply default values & config */
if (window.localStorage) {
if (window.localStorage['monitorCharts']) {
runtime.charts = $.parseJSON(window.localStorage['monitorCharts']);
}
if (window.localStorage['monitorSettings']) {
monitorSettings = $.parseJSON(window.localStorage['monitorSettings']);
}
$('a[href="#clearMonitorConfig"]').toggle(runtime.charts !== null);
if (runtime.charts !== null && monitorProtocolVersion != window.localStorage['monitorVersion']) {
$('#emptyDialog').dialog({title: PMA_messages.strIncompatibleMonitorConfig});
$('#emptyDialog').html(PMA_messages.strIncompatibleMonitorConfigDescription);
var dlgBtns = {};
dlgBtns[PMA_messages.strClose] = function () { $(this).dialog('close'); };
$('#emptyDialog').dialog({
width: 400,
buttons: dlgBtns
});
}
}
if (runtime.charts === null) {
runtime.charts = defaultChartGrid;
}
if (monitorSettings === null) {
monitorSettings = defaultMonitorSettings;
}
$('select[name="gridChartRefresh"]').val(monitorSettings.gridRefresh / 1000);
$('select[name="chartColumns"]').val(monitorSettings.columns);
if (monitorSettings.gridMaxPoints == 'auto') {
runtime.gridMaxPoints = Math.round((monitorSettings.chartSize.width - 40) / 12);
} else {
runtime.gridMaxPoints = monitorSettings.gridMaxPoints;
}
runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh;
/* Calculate how much spacing there is between each chart */
$('#chartGrid').html(' ');
chartSpacing = {
width: $('#chartGrid td:nth-child(2)').offset().left -
$('#chartGrid td:nth-child(1)').offset().left,
height: $('#chartGrid tr:nth-child(2) td:nth-child(2)').offset().top -
$('#chartGrid tr:nth-child(1) td:nth-child(1)').offset().top
};
$('#chartGrid').html('');
/* Add all charts - in correct order */
var keys = [];
$.each(runtime.charts, function (key, value) {
keys.push(key);
});
keys.sort();
for (i = 0; i < keys.length; i++) {
addChart(runtime.charts[keys[i]], true);
}
/* Fill in missing cells */
var numCharts = $('#chartGrid .monitorChart').length;
var numMissingCells = (monitorSettings.columns - numCharts % monitorSettings.columns) % monitorSettings.columns;
for (i = 0; i < numMissingCells; i++) {
$('#chartGrid tr:last').append(' ');
}
// Empty cells should keep their size so you can drop onto them
calculateChartSize();
$('#chartGrid tr td').css('width', chartSize.width + 'px');
buildRequiredDataList();
refreshChartGrid();
}
/* Calls destroyGrid() and initGrid(), but before doing so it saves the chart
* data from each chart and restores it after the monitor is initialized again */
function rebuildGrid() {
var oldData = null;
if (runtime.charts) {
oldData = {};
$.each(runtime.charts, function (key, chartObj) {
for (var i = 0, l = chartObj.nodes.length; i < l; i++) {
oldData[chartObj.nodes[i].dataPoint] = [];
for (var j = 0, ll = chartObj.chart.series[i].data.length; j < ll; j++) {
oldData[chartObj.nodes[i].dataPoint].push([chartObj.chart.series[i].data[j].x, chartObj.chart.series[i].data[j].y]);
}
}
});
}
destroyGrid();
initGrid();
}
/* Calculactes the dynamic chart size that depends on the column width */
function calculateChartSize() {
var panelWidth;
if ($("body").height() > $(window).height()) { // has vertical scroll bar
panelWidth = $('#logTable').innerWidth();
} else {
panelWidth = $('#logTable').innerWidth() - 10; // leave some space for vertical scroll bar
}
var wdt = (panelWidth - monitorSettings.columns * chartSpacing.width) / monitorSettings.columns;
chartSize = {
width: Math.floor(wdt),
height: Math.floor(0.75 * wdt)
};
}
/* Adds a chart to the chart grid */
function addChart(chartObj, initialize) {
var i;
var settings = {
title: escapeHtml(chartObj.title),
grid: {
drawBorder: false,
shadow: false,
background: 'rgba(0,0,0,0)'
},
axes: {
xaxis: {
renderer: $.jqplot.DateAxisRenderer,
tickOptions: {
formatString: '%H:%M:%S',
showGridline: false
},
min: runtime.xmin,
max: runtime.xmax
},
yaxis: {
min: 0,
max: 100,
tickInterval: 20
}
},
seriesDefaults: {
rendererOptions: {
smooth: true
},
showLine: true,
lineWidth: 2
},
highlighter: {
show: true
}
};
if (settings.title === PMA_messages.strSystemCPUUsage ||
settings.title === PMA_messages.strQueryCacheEfficiency
) {
settings.axes.yaxis.tickOptions = {
formatString: "%d %%"
};
} else if (settings.title === PMA_messages.strSystemMemory ||
settings.title === PMA_messages.strSystemSwap
) {
settings.stackSeries = true;
settings.axes.yaxis.tickOptions = {
formatter: $.jqplot.byteFormatter(2) // MiB
};
} else if (settings.title === PMA_messages.strTraffic) {
settings.axes.yaxis.tickOptions = {
formatter: $.jqplot.byteFormatter(1) // KiB
};
} else if (settings.title === PMA_messages.strQuestions ||
settings.title === PMA_messages.strConnections
) {
settings.axes.yaxis.tickOptions = {
formatter: function (format, val) {
if (Math.abs(val) >= 1000000) {
return $.jqplot.sprintf("%.3g M", val / 1000000);
} else if (Math.abs(val) >= 1000) {
return $.jqplot.sprintf("%.3g k", val / 1000);
} else {
return $.jqplot.sprintf("%d", val);
}
}
};
}
settings.series = chartObj.series;
if ($('#' + 'gridchart' + runtime.chartAI).length === 0) {
var numCharts = $('#chartGrid .monitorChart').length;
if (numCharts === 0 || (numCharts % monitorSettings.columns === 0)) {
$('#chartGrid').append(' ');
}
if (!chartSize) {
calculateChartSize();
}
$('#chartGrid tr:last').append(
'' +
'' +
' '
);
}
// Set series' data as [0,0], smooth lines won't plot with data array having null values.
// also chart won't plot initially with no data and data comes on refreshChartGrid()
var series = [];
for (i in chartObj.series) {
series.push([[0, 0]]);
}
// set Tooltip for each series
for (i in settings.series) {
settings.series[i].highlighter = {
show: true,
tooltipContentEditor: function (str, seriesIndex, pointIndex, plot) {
var j;
// TODO: move style to theme CSS
var tooltipHtml = '';
// x value i.e. time
var timeValue = str.split(",")[0];
var seriesValue;
tooltipHtml += 'Time: ' + timeValue;
tooltipHtml += '';
// Add y values to the tooltip per series
for (j in plot.series) {
// get y value if present
if (plot.series[j].data.length > pointIndex) {
seriesValue = plot.series[j].data[pointIndex][1];
} else {
return;
}
var seriesLabel = plot.series[j].label;
var seriesColor = plot.series[j].color;
// format y value
if (plot.series[0]._yaxis.tickOptions.formatter) {
// using formatter function
seriesValue = plot.series[0]._yaxis.tickOptions.formatter('%s', seriesValue);
} else if (plot.series[0]._yaxis.tickOptions.formatString) {
// using format string
seriesValue = $.sprintf(plot.series[0]._yaxis.tickOptions.formatString, seriesValue);
}
tooltipHtml += '
' +
seriesLabel + ': ' + seriesValue + '';
}
tooltipHtml += '';
return tooltipHtml;
}
};
}
chartObj.chart = $.jqplot('gridchart' + runtime.chartAI, series, settings);
// remove [0,0] after plotting
for (i in chartObj.chart.series) {
chartObj.chart.series[i].data.shift();
}
var $legend = $('').css('padding', '0.5em');
for (i in chartObj.chart.series) {
$legend.append(
$('').append(
$('').css({
width: '1em',
height: '1em',
background: chartObj.chart.seriesColors[i]
}).addClass('floatleft')
).append(
$('').text(
chartObj.chart.series[i].label
).addClass('floatleft')
).append(
$('')
).addClass('floatleft')
);
}
$('#gridchart' + runtime.chartAI)
.parent()
.append($legend);
if (initialize !== true) {
runtime.charts['c' + runtime.chartAI] = chartObj;
buildRequiredDataList();
}
// time span selection
$('#gridchart' + runtime.chartAI).bind('jqplotMouseDown', function (ev, gridpos, datapos, neighbor, plot) {
drawTimeSpan = true;
selectionTimeDiff.push(datapos.xaxis);
if ($('#selection_box').length) {
$('#selection_box').remove();
}
selectionBox = $('');
$(document.body).append(selectionBox);
selectionStartX = ev.pageX;
selectionStartY = ev.pageY;
selectionBox
.attr({id: 'selection_box'})
.css({
top: selectionStartY - gridpos.y,
left: selectionStartX
})
.fadeIn();
});
$('#gridchart' + runtime.chartAI).bind('jqplotMouseUp', function (ev, gridpos, datapos, neighbor, plot) {
if (! drawTimeSpan || editMode) {
return;
}
selectionTimeDiff.push(datapos.xaxis);
if (selectionTimeDiff[1] <= selectionTimeDiff[0]) {
selectionTimeDiff = [];
return;
}
//get date from timestamp
var min = new Date(Math.ceil(selectionTimeDiff[0]));
var max = new Date(Math.ceil(selectionTimeDiff[1]));
PMA_getLogAnalyseDialog(min, max);
selectionTimeDiff = [];
drawTimeSpan = false;
});
$('#gridchart' + runtime.chartAI).bind('jqplotMouseMove', function (ev, gridpos, datapos, neighbor, plot) {
if (! drawTimeSpan || editMode) {
return;
}
if (selectionStartX !== undefined) {
$('#selection_box')
.css({
width: Math.ceil(ev.pageX - selectionStartX)
})
.fadeIn();
}
});
$('#gridchart' + runtime.chartAI).bind('jqplotMouseLeave', function (ev, gridpos, datapos, neighbor, plot) {
drawTimeSpan = false;
});
$(document.body).mouseup(function () {
if ($('#selection_box').length) {
selectionBox.remove();
}
});
// Edit, Print icon only in edit mode
$('#chartGrid div svg').find('*[zIndex=20], *[zIndex=21], *[zIndex=19]').toggle(editMode);
runtime.chartAI++;
}
function PMA_getLogAnalyseDialog(min, max) {
var $dateStart = $('#logAnalyseDialog input[name="dateStart"]');
var $dateEnd = $('#logAnalyseDialog input[name="dateEnd"]');
$dateStart.prop("readonly", true);
$dateEnd.prop("readonly", true);
var dlgBtns = { };
dlgBtns[PMA_messages.strFromSlowLog] = function () {
loadLog('slow', min, max);
$(this).dialog("close");
};
dlgBtns[PMA_messages.strFromGeneralLog] = function () {
loadLog('general', min, max);
$(this).dialog("close");
};
$('#logAnalyseDialog').dialog({
width: 'auto',
height: 'auto',
buttons: dlgBtns
});
PMA_addDatepicker($dateStart, 'datetime', {
showMillisec: false,
showMicrosec: false,
timeFormat: 'HH:mm:ss'
});
PMA_addDatepicker($dateEnd, 'datetime', {
showMillisec: false,
showMicrosec: false,
timeFormat: 'HH:mm:ss'
});
$('#logAnalyseDialog input[name="dateStart"]').datepicker('setDate', min);
$('#logAnalyseDialog input[name="dateEnd"]').datepicker('setDate', max);
}
function loadLog(type, min, max) {
var dateStart = Date.parse($('#logAnalyseDialog input[name="dateStart"]').datepicker('getDate')) || min;
var dateEnd = Date.parse($('#logAnalyseDialog input[name="dateEnd"]').datepicker('getDate')) || max;
loadLogStatistics({
src: type,
start: dateStart,
end: dateEnd,
removeVariables: $('#removeVariables').prop('checked'),
limitTypes: $('#limitTypes').prop('checked')
});
}
/* Called in regular intervalls, this function updates the values of each chart in the grid */
function refreshChartGrid() {
/* Send to server */
runtime.refreshRequest = $.post('server_status_monitor.php?' + PMA_commonParams.get('common_query'), {
ajax_request: true,
chart_data: 1,
type: 'chartgrid',
requiredData: JSON.stringify(runtime.dataList)
}, function (data) {
var chartData;
if (data.success === true) {
chartData = data.message;
} else {
return serverResponseError();
}
var value, i = 0;
var diff;
var total;
/* Update values in each graph */
$.each(runtime.charts, function (orderKey, elem) {
var key = elem.chartID;
// If newly added chart, we have no data for it yet
if (! chartData[key]) {
return;
}
// Draw all series
total = 0;
for (var j = 0; j < elem.nodes.length; j++) {
// Update x-axis
if (i === 0 && j === 0) {
if (oldChartData === null) {
diff = chartData.x - runtime.xmax;
} else {
diff = parseInt(chartData.x - oldChartData.x, 10);
}
runtime.xmin += diff;
runtime.xmax += diff;
}
//elem.chart.xAxis[0].setExtremes(runtime.xmin, runtime.xmax, false);
/* Calculate y value */
// If transform function given, use it
if (elem.nodes[j].transformFn) {
value = chartValueTransform(
elem.nodes[j].transformFn,
chartData[key][j],
// Check if first iteration (oldChartData==null), or if newly added chart oldChartData[key]==null
(
oldChartData === null ||
oldChartData[key] === null ||
oldChartData[key] === undefined ? null : oldChartData[key][j]
)
);
// Otherwise use original value and apply differential and divisor if given,
// in this case we have only one data point per series - located at chartData[key][j][0]
} else {
value = parseFloat(chartData[key][j][0].value);
if (elem.nodes[j].display == 'differential') {
if (oldChartData === null ||
oldChartData[key] === null ||
oldChartData[key] === undefined
) {
continue;
}
value -= oldChartData[key][j][0].value;
}
if (elem.nodes[j].valueDivisor) {
value = value / elem.nodes[j].valueDivisor;
}
}
// Set y value, if defined
if (value !== undefined) {
elem.chart.series[j].data.push([chartData.x, value]);
if (value > elem.maxYLabel) {
elem.maxYLabel = value;
} else if (elem.maxYLabel === 0) {
elem.maxYLabel = 0.5;
}
// free old data point values and update maxYLabel
if (elem.chart.series[j].data.length > runtime.gridMaxPoints &&
elem.chart.series[j].data[0][0] < runtime.xmin
) {
// check if the next freeable point is highest
if (elem.maxYLabel <= elem.chart.series[j].data[0][1]) {
elem.chart.series[j].data.splice(0, elem.chart.series[j].data.length - runtime.gridMaxPoints);
elem.maxYLabel = getMaxYLabel(elem.chart.series[j].data);
} else {
elem.chart.series[j].data.splice(0, elem.chart.series[j].data.length - runtime.gridMaxPoints);
}
}
if (elem.title === PMA_messages.strSystemMemory ||
elem.title === PMA_messages.strSystemSwap
) {
total += value;
}
}
}
// update chart options
// keep ticks number/positioning consistent while refreshrate changes
var tickInterval = (runtime.xmax - runtime.xmin) / 5;
elem.chart['axes']['xaxis'].ticks = [(runtime.xmax - tickInterval * 4),
(runtime.xmax - tickInterval * 3), (runtime.xmax - tickInterval * 2),
(runtime.xmax - tickInterval), runtime.xmax];
if (elem.title !== PMA_messages.strSystemCPUUsage &&
elem.title !== PMA_messages.strQueryCacheEfficiency &&
elem.title !== PMA_messages.strSystemMemory &&
elem.title !== PMA_messages.strSystemSwap
) {
elem.chart['axes']['yaxis']['max'] = Math.ceil(elem.maxYLabel * 1.1);
elem.chart['axes']['yaxis']['tickInterval'] = Math.ceil(elem.maxYLabel * 1.1 / 5);
} else if (elem.title === PMA_messages.strSystemMemory ||
elem.title === PMA_messages.strSystemSwap
) {
elem.chart['axes']['yaxis']['max'] = Math.ceil(total * 1.1 / 100) * 100;
elem.chart['axes']['yaxis']['tickInterval'] = Math.ceil(total * 1.1 / 5);
}
i++;
if (runtime.redrawCharts) {
elem.chart.replot();
}
});
oldChartData = chartData;
runtime.refreshTimeout = setTimeout(refreshChartGrid, monitorSettings.gridRefresh);
});
}
/* Function to get highest plotted point's y label, to scale the chart,
* TODO: make jqplot's autoscale:true work here
*/
function getMaxYLabel(dataValues) {
var maxY = dataValues[0][1];
$.each(dataValues, function (k, v) {
maxY = (v[1] > maxY) ? v[1] : maxY;
});
return maxY;
}
/* Function that supplies special value transform functions for chart values */
function chartValueTransform(name, cur, prev) {
switch (name) {
case 'cpu-linux':
if (prev === null) {
return undefined;
}
// cur and prev are datapoint arrays, but containing
// only 1 element for cpu-linux
cur = cur[0];
prev = prev[0];
var diff_total = cur.busy + cur.idle - (prev.busy + prev.idle);
var diff_idle = cur.idle - prev.idle;
return 100 * (diff_total - diff_idle) / diff_total;
// Query cache efficiency (%)
case 'qce':
if (prev === null) {
return undefined;
}
// cur[0].value is Qcache_hits, cur[1].value is Com_select
var diffQHits = cur[0].value - prev[0].value;
// No NaN please :-)
if (cur[1].value - prev[1].value === 0) {
return 0;
}
return diffQHits / (cur[1].value - prev[1].value + diffQHits) * 100;
// Query cache usage (%)
case 'qcu':
if (cur[1].value === 0) {
return 0;
}
// cur[0].value is Qcache_free_memory, cur[1].value is query_cache_size
return 100 - cur[0].value / cur[1].value * 100;
}
return undefined;
}
/* Build list of nodes that need to be retrieved from server.
* It creates something like a stripped down version of the runtime.charts object.
*/
function buildRequiredDataList() {
runtime.dataList = {};
// Store an own id, because the property name is subject of reordering,
// thus destroying our mapping with runtime.charts <=> runtime.dataList
var chartID = 0;
$.each(runtime.charts, function (key, chart) {
runtime.dataList[chartID] = [];
for (var i = 0, l = chart.nodes.length; i < l; i++) {
runtime.dataList[chartID][i] = chart.nodes[i].dataPoints;
}
runtime.charts[key].chartID = chartID;
chartID++;
});
}
/* Loads the log table data, generates the table and handles the filters */
function loadLogStatistics(opts) {
var tableStr = '';
var logRequest = null;
if (! opts.removeVariables) {
opts.removeVariables = false;
}
if (! opts.limitTypes) {
opts.limitTypes = false;
}
$('#emptyDialog').dialog({title: PMA_messages.strAnalysingLogsTitle});
$('#emptyDialog').html(PMA_messages.strAnalysingLogs +
'
');
var dlgBtns = {};
dlgBtns[PMA_messages.strCancelRequest] = function () {
if (logRequest !== null) {
logRequest.abort();
}
$(this).dialog("close");
};
$('#emptyDialog').dialog({
width: 'auto',
height: 'auto',
buttons: dlgBtns
});
logRequest = $.get('server_status_monitor.php?' + PMA_commonParams.get('common_query'),
{ ajax_request: true,
log_data: 1,
type: opts.src,
time_start: Math.round(opts.start / 1000),
time_end: Math.round(opts.end / 1000),
removeVariables: opts.removeVariables,
limitTypes: opts.limitTypes
},
function (data) {
var logData;
var dlgBtns = {};
if (data.success === true) {
logData = data.message;
} else {
return serverResponseError();
}
if (logData.rows.length !== 0) {
runtime.logDataCols = buildLogTable(logData);
/* Show some stats in the dialog */
$('#emptyDialog').dialog({title: PMA_messages.strLoadingLogs});
$('#emptyDialog').html('' + PMA_messages.strLogDataLoaded + '
');
$.each(logData.sum, function (key, value) {
key = key.charAt(0).toUpperCase() + key.slice(1).toLowerCase();
if (key == 'Total') {
key = '' + key + '';
}
$('#emptyDialog').append(key + ': ' + value + '
');
});
/* Add filter options if more than a bunch of rows there to filter */
if (logData.numRows > 12) {
$('#logTable').prepend(
'