/* 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 (is_superuser) { str += '' + PMA_messages['strChangeSettings'] + ''; str += '
'; 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 += ''; str += PMA_messages['strSettingsAppliedGlobal'] + ''; $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('- ' + escapeHtml(newSeries.label + str) + '
'; 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 += '
'); 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 += ''; 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( $('
' + seriesLabel + ': ' + seriesValue + ''; } tooltipHtml += '').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
'); $('#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'] + '' + 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 + ''); 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( '