/* vim: set expandtab sw=4 ts=4 sts=4: */ var runtime = {}, server_time_diff, server_os, is_superuser, server_db_isLocal; 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" } ); } } // 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']; } /* Buttons that are on the top right corner of each chart */ var gridbuttons = { cogButton: { //enabled: true, symbol: 'url(' + pmaThemeImage + 's_cog.png)', x: -36, symbolFill: '#B5C9DF', hoverSymbolFill: '#779ABF', _titleKey: 'settings', menuName: 'gridsettings', menuItems: [{ textKey: 'editChart', onclick: function() { editChart(this); } }, { textKey: 'removeChart', onclick: function() { removeChart(this); } }] } }; $('a[href="#rearrangeCharts"], a[href="#endChartEditMode"]').click(function(event) { event.preventDefault(); editMode = !editMode; if ($(this).attr('href') == '#endChartEditMode') { editMode = false; } // Icon graphics have zIndex 19, 20 and 21. // Let's just hope nothing else has the same zIndex $('#chartGrid div svg').find('*[zIndex=20], *[zIndex=21], *[zIndex=19]').toggle(editMode); $('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 }, events: { // Drop event. The drag child element is moved into the drop element // and vice versa. So the parameters are switched. drop: function(drag, drop, pos) { var dragKey, dropKey, dropRender; var dragRender = $(drag).children().first().attr('id'); if ($(drop).children().length > 0) { dropRender = $(drop).children().first().attr('id'); } // Find the charts in the array $.each(runtime.charts, function(key, value) { if (value.chart.options.chart.renderTo == dragRender) { dragKey = key; } if (dropRender && value.chart.options.chart.renderTo == dropRender) { dropKey = key; } }); // Case 1: drag and drop are charts -> Switch keys if (dropKey) { if (dragKey) { dragChart = runtime.charts[dragKey]; runtime.charts[dragKey] = runtime.charts[dropKey]; runtime.charts[dropKey] = dragChart; } else { // Case 2: drop is a empty cell => just completely rebuild the ids var keys = []; var dropKeyNum = parseInt(dropKey.substr(1)); var insertBefore = pos.col + pos.row * monitorSettings.columns; var values = []; var newChartList = {}; var c = 0; $.each(runtime.charts, function(key, value) { if (key != dropKey) { keys.push(key); } }); keys.sort(); // Rebuilds all ids, with the dragged chart correctly inserted for (var i = 0; i' to any text content -.-
json = $.secureEvalJSON(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'] = $.toJSON(json.monitorCharts);
window.localStorage['monitorSettings'] = $.toJSON(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') + ' '
+ $.sprintf(PMA_messages['strSmallerLongQueryTimeAdvice'], logVars['long_query_time'])
+ '
';
}
if (logVars['long_query_time'] < 2) {
str += PMA_getImage('s_success.png') + ' '
+ $.sprintf(PMA_messages['strLongQueryTimeSet'], logVars['long_query_time'])
+ '
';
}
}
str += '';
if (is_superuser) {
str += '' + PMA_messages['strChangeSettings'] + '';
str += '';
str += PMA_messages['strSettingsAppliedGlobal'] + '
';
var varValue = 'TABLE';
if (logVars['log_output'] == 'TABLE') {
varValue = 'FILE';
}
str += '- '
+ $.sprintf(PMA_messages['strSetLogOutput'], varValue)
+ '
';
if (logVars['general_log'] != 'ON') {
str += '- '
+ $.sprintf(PMA_messages['strEnableVar'], 'general_log')
+ '
';
} else {
str += '- '
+ $.sprintf(PMA_messages['strDisableVar'], 'general_log')
+ '
';
}
if (logVars['slow_query_log'] != 'ON') {
str += '- '
+ $.sprintf(PMA_messages['strEnableVar'], 'slow_query_log')
+ '
';
} else {
str += '- '
+ $.sprintf(PMA_messages['strDisableVar'], 'slow_query_log')
+ '
';
}
varValue = 5;
if (logVars['long_query_time'] > 2) {
varValue = 1;
}
str += '- '
+ $.sprintf(PMA_messages['setSetLongQueryTime'], varValue)
+ '
';
} 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());
}
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('- ' + 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() {
/* 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 (var 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 (var i = 0; i < numMissingCells; i++) {
$('#chartGrid tr:last').append(' ');
}
// Empty cells should keep their size so you can drop onto them
$('#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();
if (oldData) {
$.each(runtime.charts, function(key, chartObj) {
for (var j = 0, l = chartObj.nodes.length; j < l; j++) {
if (oldData[chartObj.nodes[j].dataPoint]) {
chartObj.chart.series[j].setData(oldData[chartObj.nodes[j].dataPoint]);
}
}
});
}
}
/* Calculactes the dynamic chart size that depends on the column width */
function chartSize() {
var wdt = $('#logTable').innerWidth() / monitorSettings.columns - (monitorSettings.columns - 1) * chartSpacing.width;
return {
width: wdt,
height: 0.75 * wdt
};
}
/* Adds a chart to the chart grid */
function addChart(chartObj, initialize) {
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)) {
$('#chartGrid').append(' ');
}
$('#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 tooltipHtml = '';
// x value i.e. time
var timeValue = str.split(",")[0];
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) {
var 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 (var 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++;
}
/* Opens a dialog that allows one to edit the title and series labels of the supplied chart */
function editChart(chartObj) {
var htmlnode = chartObj.options.chart.renderTo;
if (! htmlnode ) {
return;
}
var chart = null;
var chartKey = null;
$.each(runtime.charts, function(key, value) {
if (value.chart.options.chart.renderTo == htmlnode) {
chart = value;
chartKey = key;
return false;
}
});
if (chart == null) {
return;
}
var htmlStr = '' + PMA_messages['strChartTitle'] + ':
';
htmlStr += '
' + PMA_messages['strSeries'] + ':
';
for (var i = 0; i' + chart.nodes[i].dataPoints[0].name + ':
';
}
dlgBtns = {};
dlgBtns[PMA_messages['strSave']] = function() {
runtime.charts[chartKey].title = $('#emptyDialog input[name="chartTitle"]').val();
runtime.charts[chartKey].chart.setTitle({ text: runtime.charts[chartKey].title });
$('#emptyDialog input[name*="chartSerie"]').each(function() {
var $t = $(this);
var idx = $t.attr('name').split('-')[1];
runtime.charts[chartKey].nodes[idx].name = $t.val();
runtime.charts[chartKey].chart.series[idx].name = $t.val();
});
$(this).dialog('close');
saveMonitor();
};
dlgBtns[PMA_messages['strCancel']] = function() {
$(this).dialog('close');
};
$('#emptyDialog').html(htmlStr + '
');
$('#emptyDialog').dialog({
title: PMA_messages['strChartEdit'],
width: 'auto',
height: 'auto',
buttons: dlgBtns
});
}
function PMA_getLogAnalyseDialog(min, max) {
$('#logAnalyseDialog input[name="dateStart"]')
.val(PMA_formatDateTime(min, true));
$('#logAnalyseDialog input[name="dateEnd"]')
.val(PMA_formatDateTime(max, 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
});
}
function loadLog(type, min, max) {
var dateStart = Date.parse($('#logAnalyseDialog input[name="dateStart"]').prop('value')) || min;
var dateEnd = Date.parse($('#logAnalyseDialog input[name="dateEnd"]').prop('value')) || max;
loadLogStatistics({
src: type,
start: dateStart,
end: dateEnd,
removeVariables: $('#removeVariables').prop('checked'),
limitTypes: $('#limitTypes').prop('checked')
});
}
/* Removes a chart from the grid */
function removeChart(chartObj) {
var htmlnode = chartObj.options.chart.renderTo;
if (! htmlnode ) {
return;
}
$.each(runtime.charts, function(key, value) {
if (value.chart.options.chart.renderTo == htmlnode) {
delete runtime.charts[key];
return false;
}
});
buildRequiredDataList();
// Using settimeout() because clicking the remove link fires an onclick event
// which throws an error when the chart is destroyed
setTimeout(function() {
chartObj.destroy();
$('#' + htmlnode).remove();
}, 10);
saveMonitor(); // Save settings
}
/* 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: $.toJSON(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);
}
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 ? 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) {
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;
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(
'