server_status_monitor.js 84 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197
  1. /* vim: set expandtab sw=4 ts=4 sts=4: */
  2. var runtime = {};
  3. var server_time_diff;
  4. var server_os;
  5. var is_superuser;
  6. var server_db_isLocal;
  7. var chartSize;
  8. AJAX.registerOnload('server_status_monitor.js', function () {
  9. var $js_data_form = $('#js_data');
  10. server_time_diff = new Date().getTime() - $js_data_form.find('input[name=server_time]').val();
  11. server_os = $js_data_form.find('input[name=server_os]').val();
  12. is_superuser = $js_data_form.find('input[name=is_superuser]').val();
  13. server_db_isLocal = $js_data_form.find('input[name=server_db_isLocal]').val();
  14. });
  15. /**
  16. * Unbind all event handlers before tearing down a page
  17. */
  18. AJAX.registerTeardown('server_status_monitor.js', function () {
  19. $('#emptyDialog').remove();
  20. $('#addChartDialog').remove();
  21. $('a.popupLink').off('click');
  22. $('body').off('click');
  23. });
  24. /**
  25. * Popup behaviour
  26. */
  27. AJAX.registerOnload('server_status_monitor.js', function () {
  28. $('<div />')
  29. .attr('id', 'emptyDialog')
  30. .appendTo('#page_content');
  31. $('#addChartDialog')
  32. .appendTo('#page_content');
  33. $('a.popupLink').click(function () {
  34. var $link = $(this);
  35. $('div.' + $link.attr('href').substr(1))
  36. .show()
  37. .offset({ top: $link.offset().top + $link.height() + 5, left: $link.offset().left })
  38. .addClass('openedPopup');
  39. return false;
  40. });
  41. $('body').click(function (event) {
  42. $('div.openedPopup').each(function () {
  43. var $cnt = $(this);
  44. var pos = $cnt.offset();
  45. // Hide if the mouseclick is outside the popupcontent
  46. if (event.pageX < pos.left ||
  47. event.pageY < pos.top ||
  48. event.pageX > pos.left + $cnt.outerWidth() ||
  49. event.pageY > pos.top + $cnt.outerHeight()
  50. ) {
  51. $cnt.hide().removeClass('openedPopup');
  52. }
  53. });
  54. });
  55. });
  56. AJAX.registerTeardown('server_status_monitor.js', function () {
  57. $('a[href="#rearrangeCharts"], a[href="#endChartEditMode"]').off('click');
  58. $('div.popupContent select[name="chartColumns"]').off('change');
  59. $('div.popupContent select[name="gridChartRefresh"]').off('change');
  60. $('a[href="#addNewChart"]').off('click');
  61. $('a[href="#exportMonitorConfig"]').off('click');
  62. $('a[href="#importMonitorConfig"]').off('click');
  63. $('a[href="#clearMonitorConfig"]').off('click');
  64. $('a[href="#pauseCharts"]').off('click');
  65. $('a[href="#monitorInstructionsDialog"]').off('click');
  66. $('input[name="chartType"]').off('click');
  67. $('input[name="useDivisor"]').off('click');
  68. $('input[name="useUnit"]').off('click');
  69. $('select[name="varChartList"]').off('click');
  70. $('a[href="#kibDivisor"]').off('click');
  71. $('a[href="#mibDivisor"]').off('click');
  72. $('a[href="#submitClearSeries"]').off('click');
  73. $('a[href="#submitAddSeries"]').off('click');
  74. // $("input#variableInput").destroy();
  75. $('#chartPreset').off('click');
  76. $('#chartStatusVar').off('click');
  77. destroyGrid();
  78. });
  79. AJAX.registerOnload('server_status_monitor.js', function () {
  80. // Show tab links
  81. $('div.tabLinks').show();
  82. $('#loadingMonitorIcon').remove();
  83. // Codemirror is loaded on demand so we might need to initialize it
  84. if (! codemirror_editor) {
  85. var $elm = $('#sqlquery');
  86. if ($elm.length > 0 && typeof CodeMirror !== 'undefined') {
  87. codemirror_editor = CodeMirror.fromTextArea(
  88. $elm[0],
  89. {
  90. lineNumbers: true,
  91. matchBrackets: true,
  92. indentUnit: 4,
  93. mode: 'text/x-mysql',
  94. lineWrapping: true
  95. }
  96. );
  97. }
  98. }
  99. // Timepicker is loaded on demand so we need to initialize
  100. // datetime fields from the 'load log' dialog
  101. $('#logAnalyseDialog').find('.datetimefield').each(function () {
  102. PMA_addDatepicker($(this));
  103. });
  104. /** ** Monitor charting implementation ****/
  105. /* Saves the previous ajax response for differential values */
  106. var oldChartData = null;
  107. // Holds about to be created chart
  108. var newChart = null;
  109. var chartSpacing;
  110. // Whenever the monitor object (runtime.charts) or the settings object
  111. // (monitorSettings) changes in a way incompatible to the previous version,
  112. // increase this number. It will reset the users monitor and settings object
  113. // in his localStorage to the default configuration
  114. var monitorProtocolVersion = '1.0';
  115. // Runtime parameter of the monitor, is being fully set in initGrid()
  116. runtime = {
  117. // Holds all visible charts in the grid
  118. charts: null,
  119. // Stores the timeout handler so it can be cleared
  120. refreshTimeout: null,
  121. // Stores the GET request to refresh the charts
  122. refreshRequest: null,
  123. // Chart auto increment
  124. chartAI: 0,
  125. // To play/pause the monitor
  126. redrawCharts: false,
  127. // Object that contains a list of nodes that need to be retrieved
  128. // from the server for chart updates
  129. dataList: [],
  130. // Current max points per chart (needed for auto calculation)
  131. gridMaxPoints: 20,
  132. // displayed time frame
  133. xmin: -1,
  134. xmax: -1
  135. };
  136. var monitorSettings = null;
  137. var defaultMonitorSettings = {
  138. columns: 3,
  139. chartSize: { width: 295, height: 250 },
  140. // Max points in each chart. Settings it to 'auto' sets
  141. // gridMaxPoints to (chartwidth - 40) / 12
  142. gridMaxPoints: 'auto',
  143. /* Refresh rate of all grid charts in ms */
  144. gridRefresh: 5000
  145. };
  146. // Allows drag and drop rearrange and print/edit icons on charts
  147. var editMode = false;
  148. /* List of preconfigured charts that the user may select */
  149. var presetCharts = {
  150. // Query cache efficiency
  151. 'qce': {
  152. title: PMA_messages.strQueryCacheEfficiency,
  153. series: [{
  154. label: PMA_messages.strQueryCacheEfficiency
  155. }],
  156. nodes: [{
  157. dataPoints: [{ type: 'statusvar', name: 'Qcache_hits' }, { type: 'statusvar', name: 'Com_select' }],
  158. transformFn: 'qce'
  159. }],
  160. maxYLabel: 0
  161. },
  162. // Query cache usage
  163. 'qcu': {
  164. title: PMA_messages.strQueryCacheUsage,
  165. series: [{
  166. label: PMA_messages.strQueryCacheUsed
  167. }],
  168. nodes: [{
  169. dataPoints: [{ type: 'statusvar', name: 'Qcache_free_memory' }, { type: 'servervar', name: 'query_cache_size' }],
  170. transformFn: 'qcu'
  171. }],
  172. maxYLabel: 0
  173. }
  174. };
  175. // time span selection
  176. var selectionTimeDiff = [];
  177. var selectionStartX;
  178. var selectionStartY;
  179. var selectionEndX;
  180. var selectionEndY;
  181. var drawTimeSpan = false;
  182. // chart tooltip
  183. var tooltipBox;
  184. /* Add OS specific system info charts to the preset chart list */
  185. switch (server_os) {
  186. case 'WINNT':
  187. $.extend(presetCharts, {
  188. 'cpu': {
  189. title: PMA_messages.strSystemCPUUsage,
  190. series: [{
  191. label: PMA_messages.strAverageLoad
  192. }],
  193. nodes: [{
  194. dataPoints: [{ type: 'cpu', name: 'loadavg' }]
  195. }],
  196. maxYLabel: 100
  197. },
  198. 'memory': {
  199. title: PMA_messages.strSystemMemory,
  200. series: [{
  201. label: PMA_messages.strTotalMemory,
  202. fill: true
  203. }, {
  204. dataType: 'memory',
  205. label: PMA_messages.strUsedMemory,
  206. fill: true
  207. }],
  208. nodes: [{ dataPoints: [{ type: 'memory', name: 'MemTotal' }], valueDivisor: 1024 },
  209. { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 }
  210. ],
  211. maxYLabel: 0
  212. },
  213. 'swap': {
  214. title: PMA_messages.strSystemSwap,
  215. series: [{
  216. label: PMA_messages.strTotalSwap,
  217. fill: true
  218. }, {
  219. label: PMA_messages.strUsedSwap,
  220. fill: true
  221. }],
  222. nodes: [{ dataPoints: [{ type: 'memory', name: 'SwapTotal' }] },
  223. { dataPoints: [{ type: 'memory', name: 'SwapUsed' }] }
  224. ],
  225. maxYLabel: 0
  226. }
  227. });
  228. break;
  229. case 'Linux':
  230. $.extend(presetCharts, {
  231. 'cpu': {
  232. title: PMA_messages.strSystemCPUUsage,
  233. series: [{
  234. label: PMA_messages.strAverageLoad
  235. }],
  236. nodes: [{ dataPoints: [{ type: 'cpu', name: 'irrelevant' }], transformFn: 'cpu-linux' }],
  237. maxYLabel: 0
  238. },
  239. 'memory': {
  240. title: PMA_messages.strSystemMemory,
  241. series: [
  242. { label: PMA_messages.strBufferedMemory, fill: true },
  243. { label: PMA_messages.strUsedMemory, fill: true },
  244. { label: PMA_messages.strCachedMemory, fill: true },
  245. { label: PMA_messages.strFreeMemory, fill: true }
  246. ],
  247. nodes: [
  248. { dataPoints: [{ type: 'memory', name: 'Buffers' }], valueDivisor: 1024 },
  249. { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 },
  250. { dataPoints: [{ type: 'memory', name: 'Cached' }], valueDivisor: 1024 },
  251. { dataPoints: [{ type: 'memory', name: 'MemFree' }], valueDivisor: 1024 }
  252. ],
  253. maxYLabel: 0
  254. },
  255. 'swap': {
  256. title: PMA_messages.strSystemSwap,
  257. series: [
  258. { label: PMA_messages.strCachedSwap, fill: true },
  259. { label: PMA_messages.strUsedSwap, fill: true },
  260. { label: PMA_messages.strFreeSwap, fill: true }
  261. ],
  262. nodes: [
  263. { dataPoints: [{ type: 'memory', name: 'SwapCached' }], valueDivisor: 1024 },
  264. { dataPoints: [{ type: 'memory', name: 'SwapUsed' }], valueDivisor: 1024 },
  265. { dataPoints: [{ type: 'memory', name: 'SwapFree' }], valueDivisor: 1024 }
  266. ],
  267. maxYLabel: 0
  268. }
  269. });
  270. break;
  271. case 'SunOS':
  272. $.extend(presetCharts, {
  273. 'cpu': {
  274. title: PMA_messages.strSystemCPUUsage,
  275. series: [{
  276. label: PMA_messages.strAverageLoad
  277. }],
  278. nodes: [{
  279. dataPoints: [{ type: 'cpu', name: 'loadavg' }]
  280. }],
  281. maxYLabel: 0
  282. },
  283. 'memory': {
  284. title: PMA_messages.strSystemMemory,
  285. series: [
  286. { label: PMA_messages.strUsedMemory, fill: true },
  287. { label: PMA_messages.strFreeMemory, fill: true }
  288. ],
  289. nodes: [
  290. { dataPoints: [{ type: 'memory', name: 'MemUsed' }], valueDivisor: 1024 },
  291. { dataPoints: [{ type: 'memory', name: 'MemFree' }], valueDivisor: 1024 }
  292. ],
  293. maxYLabel: 0
  294. },
  295. 'swap': {
  296. title: PMA_messages.strSystemSwap,
  297. series: [
  298. { label: PMA_messages.strUsedSwap, fill: true },
  299. { label: PMA_messages.strFreeSwap, fill: true }
  300. ],
  301. nodes: [
  302. { dataPoints: [{ type: 'memory', name: 'SwapUsed' }], valueDivisor: 1024 },
  303. { dataPoints: [{ type: 'memory', name: 'SwapFree' }], valueDivisor: 1024 }
  304. ],
  305. maxYLabel: 0
  306. }
  307. });
  308. break;
  309. }
  310. // Default setting for the chart grid
  311. var defaultChartGrid = {
  312. 'c0': {
  313. title: PMA_messages.strQuestions,
  314. series: [
  315. { label: PMA_messages.strQuestions }
  316. ],
  317. nodes: [
  318. { dataPoints: [{ type: 'statusvar', name: 'Questions' }], display: 'differential' }
  319. ],
  320. maxYLabel: 0
  321. },
  322. 'c1': {
  323. title: PMA_messages.strChartConnectionsTitle,
  324. series: [
  325. { label: PMA_messages.strConnections },
  326. { label: PMA_messages.strProcesses }
  327. ],
  328. nodes: [
  329. { dataPoints: [{ type: 'statusvar', name: 'Connections' }], display: 'differential' },
  330. { dataPoints: [{ type: 'proc', name: 'processes' }] }
  331. ],
  332. maxYLabel: 0
  333. },
  334. 'c2': {
  335. title: PMA_messages.strTraffic,
  336. series: [
  337. { label: PMA_messages.strBytesSent },
  338. { label: PMA_messages.strBytesReceived }
  339. ],
  340. nodes: [
  341. { dataPoints: [{ type: 'statusvar', name: 'Bytes_sent' }], display: 'differential', valueDivisor: 1024 },
  342. { dataPoints: [{ type: 'statusvar', name: 'Bytes_received' }], display: 'differential', valueDivisor: 1024 }
  343. ],
  344. maxYLabel: 0
  345. }
  346. };
  347. // Server is localhost => We can add cpu/memory/swap to the default chart
  348. if (server_db_isLocal && typeof presetCharts.cpu !== 'undefined') {
  349. defaultChartGrid.c3 = presetCharts.cpu;
  350. defaultChartGrid.c4 = presetCharts.memory;
  351. defaultChartGrid.c5 = presetCharts.swap;
  352. }
  353. $('a[href="#rearrangeCharts"], a[href="#endChartEditMode"]').click(function (event) {
  354. event.preventDefault();
  355. editMode = !editMode;
  356. if ($(this).attr('href') === '#endChartEditMode') {
  357. editMode = false;
  358. }
  359. $('a[href="#endChartEditMode"]').toggle(editMode);
  360. if (editMode) {
  361. // Close the settings popup
  362. $('div.popupContent').hide().removeClass('openedPopup');
  363. $('#chartGrid').sortableTable({
  364. ignoreRect: {
  365. top: 8,
  366. left: chartSize.width - 63,
  367. width: 54,
  368. height: 24
  369. }
  370. });
  371. } else {
  372. $('#chartGrid').sortableTable('destroy');
  373. }
  374. saveMonitor(); // Save settings
  375. return false;
  376. });
  377. // global settings
  378. $('div.popupContent select[name="chartColumns"]').change(function () {
  379. monitorSettings.columns = parseInt(this.value, 10);
  380. calculateChartSize();
  381. // Empty cells should keep their size so you can drop onto them
  382. $('#chartGrid').find('tr td').css('width', chartSize.width + 'px');
  383. $('#chartGrid').find('.monitorChart').css({
  384. width: chartSize.width + 'px',
  385. height: chartSize.height + 'px'
  386. });
  387. /* Reorder all charts that it fills all column cells */
  388. var numColumns;
  389. var $tr = $('#chartGrid').find('tr:first');
  390. var row = 0;
  391. var tempManageCols = function () {
  392. if (numColumns > monitorSettings.columns) {
  393. if ($tr.next().length === 0) {
  394. $tr.after('<tr></tr>');
  395. }
  396. $tr.next().prepend($(this));
  397. }
  398. numColumns++;
  399. };
  400. var tempAddCol = function () {
  401. if ($(this).next().length !== 0) {
  402. $(this).append($(this).next().find('td:first'));
  403. }
  404. };
  405. while ($tr.length !== 0) {
  406. numColumns = 1;
  407. // To many cells in one row => put into next row
  408. $tr.find('td').each(tempManageCols);
  409. // To little cells in one row => for each cell to little,
  410. // move all cells backwards by 1
  411. if ($tr.next().length > 0) {
  412. var cnt = monitorSettings.columns - $tr.find('td').length;
  413. for (var i = 0; i < cnt; i++) {
  414. $tr.append($tr.next().find('td:first'));
  415. $tr.nextAll().each(tempAddCol);
  416. }
  417. }
  418. $tr = $tr.next();
  419. row++;
  420. }
  421. if (monitorSettings.gridMaxPoints === 'auto') {
  422. runtime.gridMaxPoints = Math.round((chartSize.width - 40) / 12);
  423. }
  424. runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
  425. runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh;
  426. if (editMode) {
  427. $('#chartGrid').sortableTable('refresh');
  428. }
  429. refreshChartGrid();
  430. saveMonitor(); // Save settings
  431. });
  432. $('div.popupContent select[name="gridChartRefresh"]').change(function () {
  433. monitorSettings.gridRefresh = parseInt(this.value, 10) * 1000;
  434. clearTimeout(runtime.refreshTimeout);
  435. if (runtime.refreshRequest) {
  436. runtime.refreshRequest.abort();
  437. }
  438. runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
  439. // fixing chart shift towards left on refresh rate change
  440. // runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh;
  441. runtime.refreshTimeout = setTimeout(refreshChartGrid, monitorSettings.gridRefresh);
  442. saveMonitor(); // Save settings
  443. });
  444. $('a[href="#addNewChart"]').click(function (event) {
  445. event.preventDefault();
  446. var dlgButtons = { };
  447. dlgButtons[PMA_messages.strAddChart] = function () {
  448. var type = $('input[name="chartType"]:checked').val();
  449. if (type === 'preset') {
  450. newChart = presetCharts[$('#addChartDialog').find('select[name="presetCharts"]').prop('value')];
  451. } else {
  452. // If user builds his own chart, it's being set/updated
  453. // each time he adds a series
  454. // So here we only warn if he didn't add a series yet
  455. if (! newChart || ! newChart.nodes || newChart.nodes.length === 0) {
  456. alert(PMA_messages.strAddOneSeriesWarning);
  457. return;
  458. }
  459. }
  460. newChart.title = $('input[name="chartTitle"]').val();
  461. // Add a cloned object to the chart grid
  462. addChart($.extend(true, {}, newChart));
  463. newChart = null;
  464. saveMonitor(); // Save settings
  465. $(this).dialog('close');
  466. };
  467. dlgButtons[PMA_messages.strClose] = function () {
  468. newChart = null;
  469. $('span#clearSeriesLink').hide();
  470. $('#seriesPreview').html('');
  471. $(this).dialog('close');
  472. };
  473. var $presetList = $('#addChartDialog').find('select[name="presetCharts"]');
  474. if ($presetList.html().length === 0) {
  475. $.each(presetCharts, function (key, value) {
  476. $presetList.append('<option value="' + key + '">' + value.title + '</option>');
  477. });
  478. $presetList.change(function () {
  479. $('input[name="chartTitle"]').val(
  480. $presetList.find(':selected').text()
  481. );
  482. $('#chartPreset').prop('checked', true);
  483. });
  484. $('#chartPreset').click(function () {
  485. $('input[name="chartTitle"]').val(
  486. $presetList.find(':selected').text()
  487. );
  488. });
  489. $('#chartStatusVar').click(function () {
  490. $('input[name="chartTitle"]').val(
  491. $('#chartSeries').find(':selected').text().replace(/_/g, ' ')
  492. );
  493. });
  494. $('#chartSeries').change(function () {
  495. $('input[name="chartTitle"]').val(
  496. $('#chartSeries').find(':selected').text().replace(/_/g, ' ')
  497. );
  498. });
  499. }
  500. $('#addChartDialog').dialog({
  501. width: 'auto',
  502. height: 'auto',
  503. buttons: dlgButtons
  504. });
  505. $('#seriesPreview').html('<i>' + PMA_messages.strNone + '</i>');
  506. return false;
  507. });
  508. $('a[href="#exportMonitorConfig"]').click(function (event) {
  509. event.preventDefault();
  510. var gridCopy = {};
  511. $.each(runtime.charts, function (key, elem) {
  512. gridCopy[key] = {};
  513. gridCopy[key].nodes = elem.nodes;
  514. gridCopy[key].series = elem.series;
  515. gridCopy[key].settings = elem.settings;
  516. gridCopy[key].title = elem.title;
  517. gridCopy[key].maxYLabel = elem.maxYLabel;
  518. });
  519. var exportData = {
  520. monitorCharts: gridCopy,
  521. monitorSettings: monitorSettings
  522. };
  523. var blob = new Blob([JSON.stringify(exportData)], { type: 'application/octet-stream' });
  524. var url = null;
  525. var fileName = 'monitor-config.json';
  526. if (window.navigator && window.navigator.msSaveOrOpenBlob) {
  527. window.navigator.msSaveOrOpenBlob(blob, fileName);
  528. } else {
  529. url = URL.createObjectURL(blob);
  530. window.location.href = url;
  531. }
  532. setTimeout(function () {
  533. // For some browsers it is necessary to delay revoking the ObjectURL
  534. if (url !== null) {
  535. window.URL.revokeObjectURL(url);
  536. }
  537. url = undefined;
  538. blob = undefined;
  539. }, 100);
  540. });
  541. $('a[href="#importMonitorConfig"]').click(function (event) {
  542. event.preventDefault();
  543. $('#emptyDialog').dialog({ title: PMA_messages.strImportDialogTitle });
  544. $('#emptyDialog').html(PMA_messages.strImportDialogMessage + ':<br/><form>' +
  545. '<input type="file" name="file" id="import_file"> </form>');
  546. var dlgBtns = {};
  547. dlgBtns[PMA_messages.strImport] = function () {
  548. var input = $('#emptyDialog').find('#import_file')[0];
  549. var reader = new FileReader();
  550. reader.onerror = function (event) {
  551. alert(PMA_messages.strFailedParsingConfig + '\n' + event.target.error.code);
  552. };
  553. reader.onload = function (e) {
  554. var data = e.target.result;
  555. var json = null;
  556. // Try loading config
  557. try {
  558. json = JSON.parse(data);
  559. } catch (err) {
  560. alert(PMA_messages.strFailedParsingConfig);
  561. $('#emptyDialog').dialog('close');
  562. return;
  563. }
  564. // Basic check, is this a monitor config json?
  565. if (!json || ! json.monitorCharts || ! json.monitorCharts) {
  566. alert(PMA_messages.strFailedParsingConfig);
  567. $('#emptyDialog').dialog('close');
  568. return;
  569. }
  570. // If json ok, try applying config
  571. try {
  572. if (isStorageSupported('localStorage')) {
  573. window.localStorage.monitorCharts = JSON.stringify(json.monitorCharts);
  574. window.localStorage.monitorSettings = JSON.stringify(json.monitorSettings);
  575. }
  576. rebuildGrid();
  577. } catch (err) {
  578. alert(PMA_messages.strFailedBuildingGrid);
  579. // If an exception is thrown, load default again
  580. if (isStorageSupported('localStorage')) {
  581. window.localStorage.removeItem('monitorCharts');
  582. window.localStorage.removeItem('monitorSettings');
  583. }
  584. rebuildGrid();
  585. }
  586. $('#emptyDialog').dialog('close');
  587. };
  588. reader.readAsText(input.files[0]);
  589. };
  590. dlgBtns[PMA_messages.strCancel] = function () {
  591. $(this).dialog('close');
  592. };
  593. $('#emptyDialog').dialog({
  594. width: 'auto',
  595. height: 'auto',
  596. buttons: dlgBtns
  597. });
  598. });
  599. $('a[href="#clearMonitorConfig"]').click(function (event) {
  600. event.preventDefault();
  601. if (isStorageSupported('localStorage')) {
  602. window.localStorage.removeItem('monitorCharts');
  603. window.localStorage.removeItem('monitorSettings');
  604. window.localStorage.removeItem('monitorVersion');
  605. }
  606. $(this).hide();
  607. rebuildGrid();
  608. });
  609. $('a[href="#pauseCharts"]').click(function (event) {
  610. event.preventDefault();
  611. runtime.redrawCharts = ! runtime.redrawCharts;
  612. if (! runtime.redrawCharts) {
  613. $(this).html(PMA_getImage('play') + PMA_messages.strResumeMonitor);
  614. } else {
  615. $(this).html(PMA_getImage('pause') + PMA_messages.strPauseMonitor);
  616. if (! runtime.charts) {
  617. initGrid();
  618. $('a[href="#settingsPopup"]').show();
  619. }
  620. }
  621. return false;
  622. });
  623. $('a[href="#monitorInstructionsDialog"]').click(function (event) {
  624. event.preventDefault();
  625. var $dialog = $('#monitorInstructionsDialog');
  626. $dialog.dialog({
  627. width: '60%',
  628. height: 'auto'
  629. }).find('img.ajaxIcon').show();
  630. var loadLogVars = function (getvars) {
  631. var vars = { ajax_request: true, logging_vars: true };
  632. if (getvars) {
  633. $.extend(vars, getvars);
  634. }
  635. $.post('server_status_monitor.php' + PMA_commonParams.get('common_query'), vars,
  636. function (data) {
  637. var logVars;
  638. if (typeof data !== 'undefined' && data.success === true) {
  639. logVars = data.message;
  640. } else {
  641. return serverResponseError();
  642. }
  643. var icon = PMA_getImage('s_success');
  644. var msg = '';
  645. var str = '';
  646. if (logVars.general_log === 'ON') {
  647. if (logVars.slow_query_log === 'ON') {
  648. msg = PMA_messages.strBothLogOn;
  649. } else {
  650. msg = PMA_messages.strGenLogOn;
  651. }
  652. }
  653. if (msg.length === 0 && logVars.slow_query_log === 'ON') {
  654. msg = PMA_messages.strSlowLogOn;
  655. }
  656. if (msg.length === 0) {
  657. icon = PMA_getImage('s_error');
  658. msg = PMA_messages.strBothLogOff;
  659. }
  660. str = '<b>' + PMA_messages.strCurrentSettings + '</b><br/><div class="smallIndent">';
  661. str += icon + msg + '<br />';
  662. if (logVars.log_output !== 'TABLE') {
  663. str += PMA_getImage('s_error') + ' ' + PMA_messages.strLogOutNotTable + '<br />';
  664. } else {
  665. str += PMA_getImage('s_success') + ' ' + PMA_messages.strLogOutIsTable + '<br />';
  666. }
  667. if (logVars.slow_query_log === 'ON') {
  668. if (logVars.long_query_time > 2) {
  669. str += PMA_getImage('s_attention') + ' ';
  670. str += PMA_sprintf(PMA_messages.strSmallerLongQueryTimeAdvice, logVars.long_query_time);
  671. str += '<br />';
  672. }
  673. if (logVars.long_query_time < 2) {
  674. str += PMA_getImage('s_success') + ' ';
  675. str += PMA_sprintf(PMA_messages.strLongQueryTimeSet, logVars.long_query_time);
  676. str += '<br />';
  677. }
  678. }
  679. str += '</div>';
  680. if (is_superuser) {
  681. str += '<p></p><b>' + PMA_messages.strChangeSettings + '</b>';
  682. str += '<div class="smallIndent">';
  683. str += PMA_messages.strSettingsAppliedGlobal + '<br/>';
  684. var varValue = 'TABLE';
  685. if (logVars.log_output === 'TABLE') {
  686. varValue = 'FILE';
  687. }
  688. str += '- <a class="set" href="#log_output-' + varValue + '">';
  689. str += PMA_sprintf(PMA_messages.strSetLogOutput, varValue);
  690. str += ' </a><br />';
  691. if (logVars.general_log !== 'ON') {
  692. str += '- <a class="set" href="#general_log-ON">';
  693. str += PMA_sprintf(PMA_messages.strEnableVar, 'general_log');
  694. str += ' </a><br />';
  695. } else {
  696. str += '- <a class="set" href="#general_log-OFF">';
  697. str += PMA_sprintf(PMA_messages.strDisableVar, 'general_log');
  698. str += ' </a><br />';
  699. }
  700. if (logVars.slow_query_log !== 'ON') {
  701. str += '- <a class="set" href="#slow_query_log-ON">';
  702. str += PMA_sprintf(PMA_messages.strEnableVar, 'slow_query_log');
  703. str += ' </a><br />';
  704. } else {
  705. str += '- <a class="set" href="#slow_query_log-OFF">';
  706. str += PMA_sprintf(PMA_messages.strDisableVar, 'slow_query_log');
  707. str += ' </a><br />';
  708. }
  709. varValue = 5;
  710. if (logVars.long_query_time > 2) {
  711. varValue = 1;
  712. }
  713. str += '- <a class="set" href="#long_query_time-' + varValue + '">';
  714. str += PMA_sprintf(PMA_messages.setSetLongQueryTime, varValue);
  715. str += ' </a><br />';
  716. } else {
  717. str += PMA_messages.strNoSuperUser + '<br/>';
  718. }
  719. str += '</div>';
  720. $dialog.find('div.monitorUse').toggle(
  721. logVars.log_output === 'TABLE' && (logVars.slow_query_log === 'ON' || logVars.general_log === 'ON')
  722. );
  723. $dialog.find('div.ajaxContent').html(str);
  724. $dialog.find('img.ajaxIcon').hide();
  725. $dialog.find('a.set').click(function () {
  726. var nameValue = $(this).attr('href').split('-');
  727. loadLogVars({ varName: nameValue[0].substr(1), varValue: nameValue[1] });
  728. $dialog.find('img.ajaxIcon').show();
  729. });
  730. }
  731. );
  732. };
  733. loadLogVars();
  734. return false;
  735. });
  736. $('input[name="chartType"]').change(function () {
  737. $('#chartVariableSettings').toggle(this.checked && this.value === 'variable');
  738. var title = $('input[name="chartTitle"]').val();
  739. if (title === PMA_messages.strChartTitle ||
  740. title === $('label[for="' + $('input[name="chartTitle"]').data('lastRadio') + '"]').text()
  741. ) {
  742. $('input[name="chartTitle"]')
  743. .data('lastRadio', $(this).attr('id'))
  744. .val($('label[for="' + $(this).attr('id') + '"]').text());
  745. }
  746. });
  747. $('input[name="useDivisor"]').change(function () {
  748. $('span.divisorInput').toggle(this.checked);
  749. });
  750. $('input[name="useUnit"]').change(function () {
  751. $('span.unitInput').toggle(this.checked);
  752. });
  753. $('select[name="varChartList"]').change(function () {
  754. if (this.selectedIndex !== 0) {
  755. $('#variableInput').val(this.value);
  756. }
  757. });
  758. $('a[href="#kibDivisor"]').click(function (event) {
  759. event.preventDefault();
  760. $('input[name="valueDivisor"]').val(1024);
  761. $('input[name="valueUnit"]').val(PMA_messages.strKiB);
  762. $('span.unitInput').toggle(true);
  763. $('input[name="useUnit"]').prop('checked', true);
  764. return false;
  765. });
  766. $('a[href="#mibDivisor"]').click(function (event) {
  767. event.preventDefault();
  768. $('input[name="valueDivisor"]').val(1024 * 1024);
  769. $('input[name="valueUnit"]').val(PMA_messages.strMiB);
  770. $('span.unitInput').toggle(true);
  771. $('input[name="useUnit"]').prop('checked', true);
  772. return false;
  773. });
  774. $('a[href="#submitClearSeries"]').click(function (event) {
  775. event.preventDefault();
  776. $('#seriesPreview').html('<i>' + PMA_messages.strNone + '</i>');
  777. newChart = null;
  778. $('#clearSeriesLink').hide();
  779. });
  780. $('a[href="#submitAddSeries"]').click(function (event) {
  781. event.preventDefault();
  782. if ($('#variableInput').val() === '') {
  783. return false;
  784. }
  785. if (newChart === null) {
  786. $('#seriesPreview').html('');
  787. newChart = {
  788. title: $('input[name="chartTitle"]').val(),
  789. nodes: [],
  790. series: [],
  791. maxYLabel: 0
  792. };
  793. }
  794. var serie = {
  795. dataPoints: [{ type: 'statusvar', name: $('#variableInput').val() }],
  796. display: $('input[name="differentialValue"]').prop('checked') ? 'differential' : ''
  797. };
  798. if (serie.dataPoints[0].name === 'Processes') {
  799. serie.dataPoints[0].type = 'proc';
  800. }
  801. if ($('input[name="useDivisor"]').prop('checked')) {
  802. serie.valueDivisor = parseInt($('input[name="valueDivisor"]').val(), 10);
  803. }
  804. if ($('input[name="useUnit"]').prop('checked')) {
  805. serie.unit = $('input[name="valueUnit"]').val();
  806. }
  807. var str = serie.display === 'differential' ? ', ' + PMA_messages.strDifferential : '';
  808. str += serie.valueDivisor ? (', ' + PMA_sprintf(PMA_messages.strDividedBy, serie.valueDivisor)) : '';
  809. str += serie.unit ? (', ' + PMA_messages.strUnit + ': ' + serie.unit) : '';
  810. var newSeries = {
  811. label: $('#variableInput').val().replace(/_/g, ' ')
  812. };
  813. newChart.series.push(newSeries);
  814. $('#seriesPreview').append('- ' + escapeHtml(newSeries.label + str) + '<br/>');
  815. newChart.nodes.push(serie);
  816. $('#variableInput').val('');
  817. $('input[name="differentialValue"]').prop('checked', true);
  818. $('input[name="useDivisor"]').prop('checked', false);
  819. $('input[name="useUnit"]').prop('checked', false);
  820. $('input[name="useDivisor"]').trigger('change');
  821. $('input[name="useUnit"]').trigger('change');
  822. $('select[name="varChartList"]').get(0).selectedIndex = 0;
  823. $('#clearSeriesLink').show();
  824. return false;
  825. });
  826. $('#variableInput').autocomplete({
  827. source: variableNames
  828. });
  829. /* Initializes the monitor, called only once */
  830. function initGrid () {
  831. var i;
  832. /* Apply default values & config */
  833. if (isStorageSupported('localStorage')) {
  834. if (typeof window.localStorage.monitorCharts !== 'undefined') {
  835. runtime.charts = JSON.parse(window.localStorage.monitorCharts);
  836. }
  837. if (typeof window.localStorage.monitorSettings !== 'undefined') {
  838. monitorSettings = JSON.parse(window.localStorage.monitorSettings);
  839. }
  840. $('a[href="#clearMonitorConfig"]').toggle(runtime.charts !== null);
  841. if (runtime.charts !== null
  842. && typeof window.localStorage.monitorVersion !== 'undefined'
  843. && monitorProtocolVersion !== window.localStorage.monitorVersion
  844. ) {
  845. $('#emptyDialog').dialog({ title: PMA_messages.strIncompatibleMonitorConfig });
  846. $('#emptyDialog').html(PMA_messages.strIncompatibleMonitorConfigDescription);
  847. var dlgBtns = {};
  848. dlgBtns[PMA_messages.strClose] = function () {
  849. $(this).dialog('close');
  850. };
  851. $('#emptyDialog').dialog({
  852. width: 400,
  853. buttons: dlgBtns
  854. });
  855. }
  856. }
  857. if (runtime.charts === null) {
  858. runtime.charts = defaultChartGrid;
  859. }
  860. if (monitorSettings === null) {
  861. monitorSettings = defaultMonitorSettings;
  862. }
  863. $('select[name="gridChartRefresh"]').val(monitorSettings.gridRefresh / 1000);
  864. $('select[name="chartColumns"]').val(monitorSettings.columns);
  865. if (monitorSettings.gridMaxPoints === 'auto') {
  866. runtime.gridMaxPoints = Math.round((monitorSettings.chartSize.width - 40) / 12);
  867. } else {
  868. runtime.gridMaxPoints = monitorSettings.gridMaxPoints;
  869. }
  870. runtime.xmin = new Date().getTime() - server_time_diff - runtime.gridMaxPoints * monitorSettings.gridRefresh;
  871. runtime.xmax = new Date().getTime() - server_time_diff + monitorSettings.gridRefresh;
  872. /* Calculate how much spacing there is between each chart */
  873. $('#chartGrid').html('<tr><td></td><td></td></tr><tr><td></td><td></td></tr>');
  874. chartSpacing = {
  875. width: $('#chartGrid').find('td:nth-child(2)').offset().left -
  876. $('#chartGrid').find('td:nth-child(1)').offset().left,
  877. height: $('#chartGrid').find('tr:nth-child(2) td:nth-child(2)').offset().top -
  878. $('#chartGrid').find('tr:nth-child(1) td:nth-child(1)').offset().top
  879. };
  880. $('#chartGrid').html('');
  881. /* Add all charts - in correct order */
  882. var keys = [];
  883. $.each(runtime.charts, function (key, value) {
  884. keys.push(key);
  885. });
  886. keys.sort();
  887. for (i = 0; i < keys.length; i++) {
  888. addChart(runtime.charts[keys[i]], true);
  889. }
  890. /* Fill in missing cells */
  891. var numCharts = $('#chartGrid').find('.monitorChart').length;
  892. var numMissingCells = (monitorSettings.columns - numCharts % monitorSettings.columns) % monitorSettings.columns;
  893. for (i = 0; i < numMissingCells; i++) {
  894. $('#chartGrid').find('tr:last').append('<td></td>');
  895. }
  896. // Empty cells should keep their size so you can drop onto them
  897. calculateChartSize();
  898. $('#chartGrid').find('tr td').css('width', chartSize.width + 'px');
  899. buildRequiredDataList();
  900. refreshChartGrid();
  901. }
  902. /* Calls destroyGrid() and initGrid(), but before doing so it saves the chart
  903. * data from each chart and restores it after the monitor is initialized again */
  904. function rebuildGrid () {
  905. var oldData = null;
  906. if (runtime.charts) {
  907. oldData = {};
  908. $.each(runtime.charts, function (key, chartObj) {
  909. for (var i = 0, l = chartObj.nodes.length; i < l; i++) {
  910. oldData[chartObj.nodes[i].dataPoint] = [];
  911. for (var j = 0, ll = chartObj.chart.series[i].data.length; j < ll; j++) {
  912. oldData[chartObj.nodes[i].dataPoint].push([chartObj.chart.series[i].data[j].x, chartObj.chart.series[i].data[j].y]);
  913. }
  914. }
  915. });
  916. }
  917. destroyGrid();
  918. initGrid();
  919. }
  920. /* Calculactes the dynamic chart size that depends on the column width */
  921. function calculateChartSize () {
  922. var panelWidth;
  923. if ($('body').height() > $(window).height()) { // has vertical scroll bar
  924. panelWidth = $('#logTable').innerWidth();
  925. } else {
  926. panelWidth = $('#logTable').innerWidth() - 10; // leave some space for vertical scroll bar
  927. }
  928. var wdt = panelWidth;
  929. var windowWidth = $(window).width();
  930. if (windowWidth > 768) {
  931. wdt = (panelWidth - monitorSettings.columns * chartSpacing.width) / monitorSettings.columns;
  932. }
  933. chartSize = {
  934. width: Math.floor(wdt),
  935. height: Math.floor(0.75 * wdt)
  936. };
  937. }
  938. /* Adds a chart to the chart grid */
  939. function addChart (chartObj, initialize) {
  940. var i;
  941. var settings = {
  942. title: escapeHtml(chartObj.title),
  943. grid: {
  944. drawBorder: false,
  945. shadow: false,
  946. background: 'rgba(0,0,0,0)'
  947. },
  948. axes: {
  949. xaxis: {
  950. renderer: $.jqplot.DateAxisRenderer,
  951. tickOptions: {
  952. formatString: '%H:%M:%S',
  953. showGridline: false
  954. },
  955. min: runtime.xmin,
  956. max: runtime.xmax
  957. },
  958. yaxis: {
  959. min: 0,
  960. max: 100,
  961. tickInterval: 20
  962. }
  963. },
  964. seriesDefaults: {
  965. rendererOptions: {
  966. smooth: true
  967. },
  968. showLine: true,
  969. lineWidth: 2,
  970. markerOptions: {
  971. size: 6
  972. }
  973. },
  974. highlighter: {
  975. show: true
  976. }
  977. };
  978. if (settings.title === PMA_messages.strSystemCPUUsage ||
  979. settings.title === PMA_messages.strQueryCacheEfficiency
  980. ) {
  981. settings.axes.yaxis.tickOptions = {
  982. formatString: '%d %%'
  983. };
  984. } else if (settings.title === PMA_messages.strSystemMemory ||
  985. settings.title === PMA_messages.strSystemSwap
  986. ) {
  987. settings.stackSeries = true;
  988. settings.axes.yaxis.tickOptions = {
  989. formatter: $.jqplot.byteFormatter(2) // MiB
  990. };
  991. } else if (settings.title === PMA_messages.strTraffic) {
  992. settings.axes.yaxis.tickOptions = {
  993. formatter: $.jqplot.byteFormatter(1) // KiB
  994. };
  995. } else if (settings.title === PMA_messages.strQuestions ||
  996. settings.title === PMA_messages.strConnections
  997. ) {
  998. settings.axes.yaxis.tickOptions = {
  999. formatter: function (format, val) {
  1000. if (Math.abs(val) >= 1000000) {
  1001. return $.jqplot.sprintf('%.3g M', val / 1000000);
  1002. } else if (Math.abs(val) >= 1000) {
  1003. return $.jqplot.sprintf('%.3g k', val / 1000);
  1004. } else {
  1005. return $.jqplot.sprintf('%d', val);
  1006. }
  1007. }
  1008. };
  1009. }
  1010. settings.series = chartObj.series;
  1011. if ($('#' + 'gridchart' + runtime.chartAI).length === 0) {
  1012. var numCharts = $('#chartGrid').find('.monitorChart').length;
  1013. if (numCharts === 0 || (numCharts % monitorSettings.columns === 0)) {
  1014. $('#chartGrid').append('<tr></tr>');
  1015. }
  1016. if (!chartSize) {
  1017. calculateChartSize();
  1018. }
  1019. $('#chartGrid').find('tr:last').append(
  1020. '<td><div id="gridChartContainer' + runtime.chartAI + '" class="">' +
  1021. '<div class="ui-state-default monitorChart"' +
  1022. ' id="gridchart' + runtime.chartAI + '"' +
  1023. ' style="width:' + chartSize.width + 'px; height:' + chartSize.height + 'px;"></div>' +
  1024. '</div></td>'
  1025. );
  1026. }
  1027. // Set series' data as [0,0], smooth lines won't plot with data array having null values.
  1028. // also chart won't plot initially with no data and data comes on refreshChartGrid()
  1029. var series = [];
  1030. for (i in chartObj.series) {
  1031. series.push([[0, 0]]);
  1032. }
  1033. var tempTooltipContentEditor = function (str, seriesIndex, pointIndex, plot) {
  1034. var j;
  1035. // TODO: move style to theme CSS
  1036. var tooltipHtml = '<div style="font-size:12px;background-color:#FFFFFF;' +
  1037. 'opacity:0.95;filter:alpha(opacity=95);padding:5px;">';
  1038. // x value i.e. time
  1039. var timeValue = str.split(',')[0];
  1040. var seriesValue;
  1041. tooltipHtml += 'Time: ' + timeValue;
  1042. tooltipHtml += '<span style="font-weight:bold;">';
  1043. // Add y values to the tooltip per series
  1044. for (j in plot.series) {
  1045. // get y value if present
  1046. if (plot.series[j].data.length > pointIndex) {
  1047. seriesValue = plot.series[j].data[pointIndex][1];
  1048. } else {
  1049. return;
  1050. }
  1051. var seriesLabel = plot.series[j].label;
  1052. var seriesColor = plot.series[j].color;
  1053. // format y value
  1054. if (plot.series[0]._yaxis.tickOptions.formatter) {
  1055. // using formatter function
  1056. seriesValue = plot.series[0]._yaxis.tickOptions.formatter('%s', seriesValue);
  1057. } else if (plot.series[0]._yaxis.tickOptions.formatString) {
  1058. // using format string
  1059. seriesValue = PMA_sprintf(plot.series[0]._yaxis.tickOptions.formatString, seriesValue);
  1060. }
  1061. tooltipHtml += '<br /><span style="color:' + seriesColor + '">' +
  1062. seriesLabel + ': ' + seriesValue + '</span>';
  1063. }
  1064. tooltipHtml += '</span></div>';
  1065. return tooltipHtml;
  1066. };
  1067. // set Tooltip for each series
  1068. for (i in settings.series) {
  1069. settings.series[i].highlighter = {
  1070. show: true,
  1071. tooltipContentEditor: tempTooltipContentEditor
  1072. };
  1073. }
  1074. chartObj.chart = $.jqplot('gridchart' + runtime.chartAI, series, settings);
  1075. // remove [0,0] after plotting
  1076. for (i in chartObj.chart.series) {
  1077. chartObj.chart.series[i].data.shift();
  1078. }
  1079. var $legend = $('<div />').css('padding', '0.5em');
  1080. for (i in chartObj.chart.series) {
  1081. $legend.append(
  1082. $('<div />').append(
  1083. $('<div>').css({
  1084. width: '1em',
  1085. height: '1em',
  1086. background: chartObj.chart.seriesColors[i]
  1087. }).addClass('floatleft')
  1088. ).append(
  1089. $('<div>').text(
  1090. chartObj.chart.series[i].label
  1091. ).addClass('floatleft')
  1092. ).append(
  1093. $('<div class="clearfloat">')
  1094. ).addClass('floatleft')
  1095. );
  1096. }
  1097. $('#gridchart' + runtime.chartAI)
  1098. .parent()
  1099. .append($legend);
  1100. if (initialize !== true) {
  1101. runtime.charts['c' + runtime.chartAI] = chartObj;
  1102. buildRequiredDataList();
  1103. }
  1104. // time span selection
  1105. $('#gridchart' + runtime.chartAI).on('jqplotMouseDown', function (ev, gridpos, datapos, neighbor, plot) {
  1106. drawTimeSpan = true;
  1107. selectionTimeDiff.push(datapos.xaxis);
  1108. if ($('#selection_box').length) {
  1109. $('#selection_box').remove();
  1110. }
  1111. var selectionBox = $('<div id="selection_box" style="z-index:1000;height:205px;position:absolute;background-color:#87CEEB;opacity:0.4;filter:alpha(opacity=40);pointer-events:none;">');
  1112. $(document.body).append(selectionBox);
  1113. selectionStartX = ev.pageX;
  1114. selectionStartY = ev.pageY;
  1115. selectionBox
  1116. .attr({ id: 'selection_box' })
  1117. .css({
  1118. top: selectionStartY - gridpos.y,
  1119. left: selectionStartX
  1120. })
  1121. .fadeIn();
  1122. });
  1123. $('#gridchart' + runtime.chartAI).on('jqplotMouseUp', function (ev, gridpos, datapos, neighbor, plot) {
  1124. if (! drawTimeSpan || editMode) {
  1125. return;
  1126. }
  1127. selectionTimeDiff.push(datapos.xaxis);
  1128. if (selectionTimeDiff[1] <= selectionTimeDiff[0]) {
  1129. selectionTimeDiff = [];
  1130. return;
  1131. }
  1132. // get date from timestamp
  1133. var min = new Date(Math.ceil(selectionTimeDiff[0]));
  1134. var max = new Date(Math.ceil(selectionTimeDiff[1]));
  1135. PMA_getLogAnalyseDialog(min, max);
  1136. selectionTimeDiff = [];
  1137. drawTimeSpan = false;
  1138. });
  1139. $('#gridchart' + runtime.chartAI).on('jqplotMouseMove', function (ev, gridpos, datapos, neighbor, plot) {
  1140. if (! drawTimeSpan || editMode) {
  1141. return;
  1142. }
  1143. if (selectionStartX !== undefined) {
  1144. $('#selection_box')
  1145. .css({
  1146. width: Math.ceil(ev.pageX - selectionStartX)
  1147. })
  1148. .fadeIn();
  1149. }
  1150. });
  1151. $('#gridchart' + runtime.chartAI).on('jqplotMouseLeave', function (ev, gridpos, datapos, neighbor, plot) {
  1152. drawTimeSpan = false;
  1153. });
  1154. $(document.body).mouseup(function () {
  1155. if ($('#selection_box').length) {
  1156. $('#selection_box').remove();
  1157. }
  1158. });
  1159. // Edit, Print icon only in edit mode
  1160. $('#chartGrid').find('div svg').find('*[zIndex=20], *[zIndex=21], *[zIndex=19]').toggle(editMode);
  1161. runtime.chartAI++;
  1162. }
  1163. function PMA_getLogAnalyseDialog (min, max) {
  1164. var $logAnalyseDialog = $('#logAnalyseDialog');
  1165. var $dateStart = $logAnalyseDialog.find('input[name="dateStart"]');
  1166. var $dateEnd = $logAnalyseDialog.find('input[name="dateEnd"]');
  1167. $dateStart.prop('readonly', true);
  1168. $dateEnd.prop('readonly', true);
  1169. var dlgBtns = { };
  1170. dlgBtns[PMA_messages.strFromSlowLog] = function () {
  1171. loadLog('slow', min, max);
  1172. $(this).dialog('close');
  1173. };
  1174. dlgBtns[PMA_messages.strFromGeneralLog] = function () {
  1175. loadLog('general', min, max);
  1176. $(this).dialog('close');
  1177. };
  1178. $logAnalyseDialog.dialog({
  1179. width: 'auto',
  1180. height: 'auto',
  1181. buttons: dlgBtns
  1182. });
  1183. PMA_addDatepicker($dateStart, 'datetime', {
  1184. showMillisec: false,
  1185. showMicrosec: false,
  1186. timeFormat: 'HH:mm:ss'
  1187. });
  1188. PMA_addDatepicker($dateEnd, 'datetime', {
  1189. showMillisec: false,
  1190. showMicrosec: false,
  1191. timeFormat: 'HH:mm:ss'
  1192. });
  1193. $dateStart.datepicker('setDate', min);
  1194. $dateEnd.datepicker('setDate', max);
  1195. }
  1196. function loadLog (type, min, max) {
  1197. var dateStart = Date.parse($('#logAnalyseDialog').find('input[name="dateStart"]').datepicker('getDate')) || min;
  1198. var dateEnd = Date.parse($('#logAnalyseDialog').find('input[name="dateEnd"]').datepicker('getDate')) || max;
  1199. loadLogStatistics({
  1200. src: type,
  1201. start: dateStart,
  1202. end: dateEnd,
  1203. removeVariables: $('#removeVariables').prop('checked'),
  1204. limitTypes: $('#limitTypes').prop('checked')
  1205. });
  1206. }
  1207. /* Called in regular intervals, this function updates the values of each chart in the grid */
  1208. function refreshChartGrid () {
  1209. /* Send to server */
  1210. runtime.refreshRequest = $.post('server_status_monitor.php' + PMA_commonParams.get('common_query'), {
  1211. ajax_request: true,
  1212. chart_data: 1,
  1213. type: 'chartgrid',
  1214. requiredData: JSON.stringify(runtime.dataList),
  1215. server: PMA_commonParams.get('server')
  1216. }, function (data) {
  1217. var chartData;
  1218. if (typeof data !== 'undefined' && data.success === true) {
  1219. chartData = data.message;
  1220. } else {
  1221. return serverResponseError();
  1222. }
  1223. var value;
  1224. var i = 0;
  1225. var diff;
  1226. var total;
  1227. /* Update values in each graph */
  1228. $.each(runtime.charts, function (orderKey, elem) {
  1229. var key = elem.chartID;
  1230. // If newly added chart, we have no data for it yet
  1231. if (! chartData[key]) {
  1232. return;
  1233. }
  1234. // Draw all series
  1235. total = 0;
  1236. for (var j = 0; j < elem.nodes.length; j++) {
  1237. // Update x-axis
  1238. if (i === 0 && j === 0) {
  1239. if (oldChartData === null) {
  1240. diff = chartData.x - runtime.xmax;
  1241. } else {
  1242. diff = parseInt(chartData.x - oldChartData.x, 10);
  1243. }
  1244. runtime.xmin += diff;
  1245. runtime.xmax += diff;
  1246. }
  1247. // elem.chart.xAxis[0].setExtremes(runtime.xmin, runtime.xmax, false);
  1248. /* Calculate y value */
  1249. // If transform function given, use it
  1250. if (elem.nodes[j].transformFn) {
  1251. value = chartValueTransform(
  1252. elem.nodes[j].transformFn,
  1253. chartData[key][j],
  1254. // Check if first iteration (oldChartData==null), or if newly added chart oldChartData[key]==null
  1255. (
  1256. oldChartData === null ||
  1257. oldChartData[key] === null ||
  1258. oldChartData[key] === undefined ? null : oldChartData[key][j]
  1259. )
  1260. );
  1261. // Otherwise use original value and apply differential and divisor if given,
  1262. // in this case we have only one data point per series - located at chartData[key][j][0]
  1263. } else {
  1264. value = parseFloat(chartData[key][j][0].value);
  1265. if (elem.nodes[j].display === 'differential') {
  1266. if (oldChartData === null ||
  1267. oldChartData[key] === null ||
  1268. oldChartData[key] === undefined
  1269. ) {
  1270. continue;
  1271. }
  1272. value -= oldChartData[key][j][0].value;
  1273. }
  1274. if (elem.nodes[j].valueDivisor) {
  1275. value = value / elem.nodes[j].valueDivisor;
  1276. }
  1277. }
  1278. // Set y value, if defined
  1279. if (value !== undefined) {
  1280. elem.chart.series[j].data.push([chartData.x, value]);
  1281. if (value > elem.maxYLabel) {
  1282. elem.maxYLabel = value;
  1283. } else if (elem.maxYLabel === 0) {
  1284. elem.maxYLabel = 0.5;
  1285. }
  1286. // free old data point values and update maxYLabel
  1287. if (elem.chart.series[j].data.length > runtime.gridMaxPoints &&
  1288. elem.chart.series[j].data[0][0] < runtime.xmin
  1289. ) {
  1290. // check if the next freeable point is highest
  1291. if (elem.maxYLabel <= elem.chart.series[j].data[0][1]) {
  1292. elem.chart.series[j].data.splice(0, elem.chart.series[j].data.length - runtime.gridMaxPoints);
  1293. elem.maxYLabel = getMaxYLabel(elem.chart.series[j].data);
  1294. } else {
  1295. elem.chart.series[j].data.splice(0, elem.chart.series[j].data.length - runtime.gridMaxPoints);
  1296. }
  1297. }
  1298. if (elem.title === PMA_messages.strSystemMemory ||
  1299. elem.title === PMA_messages.strSystemSwap
  1300. ) {
  1301. total += value;
  1302. }
  1303. }
  1304. }
  1305. // update chart options
  1306. // keep ticks number/positioning consistent while refreshrate changes
  1307. var tickInterval = (runtime.xmax - runtime.xmin) / 5;
  1308. elem.chart.axes.xaxis.ticks = [(runtime.xmax - tickInterval * 4),
  1309. (runtime.xmax - tickInterval * 3), (runtime.xmax - tickInterval * 2),
  1310. (runtime.xmax - tickInterval), runtime.xmax];
  1311. if (elem.title !== PMA_messages.strSystemCPUUsage &&
  1312. elem.title !== PMA_messages.strQueryCacheEfficiency &&
  1313. elem.title !== PMA_messages.strSystemMemory &&
  1314. elem.title !== PMA_messages.strSystemSwap
  1315. ) {
  1316. elem.chart.axes.yaxis.max = Math.ceil(elem.maxYLabel * 1.1);
  1317. elem.chart.axes.yaxis.tickInterval = Math.ceil(elem.maxYLabel * 1.1 / 5);
  1318. } else if (elem.title === PMA_messages.strSystemMemory ||
  1319. elem.title === PMA_messages.strSystemSwap
  1320. ) {
  1321. elem.chart.axes.yaxis.max = Math.ceil(total * 1.1 / 100) * 100;
  1322. elem.chart.axes.yaxis.tickInterval = Math.ceil(total * 1.1 / 5);
  1323. }
  1324. i++;
  1325. if (runtime.redrawCharts) {
  1326. elem.chart.replot();
  1327. }
  1328. });
  1329. oldChartData = chartData;
  1330. runtime.refreshTimeout = setTimeout(refreshChartGrid, monitorSettings.gridRefresh);
  1331. });
  1332. }
  1333. /* Function to get highest plotted point's y label, to scale the chart,
  1334. * TODO: make jqplot's autoscale:true work here
  1335. */
  1336. function getMaxYLabel (dataValues) {
  1337. var maxY = dataValues[0][1];
  1338. $.each(dataValues, function (k, v) {
  1339. maxY = (v[1] > maxY) ? v[1] : maxY;
  1340. });
  1341. return maxY;
  1342. }
  1343. /* Function that supplies special value transform functions for chart values */
  1344. function chartValueTransform (name, cur, prev) {
  1345. switch (name) {
  1346. case 'cpu-linux':
  1347. if (prev === null) {
  1348. return undefined;
  1349. }
  1350. // cur and prev are datapoint arrays, but containing
  1351. // only 1 element for cpu-linux
  1352. cur = cur[0];
  1353. prev = prev[0];
  1354. var diff_total = cur.busy + cur.idle - (prev.busy + prev.idle);
  1355. var diff_idle = cur.idle - prev.idle;
  1356. return 100 * (diff_total - diff_idle) / diff_total;
  1357. // Query cache efficiency (%)
  1358. case 'qce':
  1359. if (prev === null) {
  1360. return undefined;
  1361. }
  1362. // cur[0].value is Qcache_hits, cur[1].value is Com_select
  1363. var diffQHits = cur[0].value - prev[0].value;
  1364. // No NaN please :-)
  1365. if (cur[1].value - prev[1].value === 0) {
  1366. return 0;
  1367. }
  1368. return diffQHits / (cur[1].value - prev[1].value + diffQHits) * 100;
  1369. // Query cache usage (%)
  1370. case 'qcu':
  1371. if (cur[1].value === 0) {
  1372. return 0;
  1373. }
  1374. // cur[0].value is Qcache_free_memory, cur[1].value is query_cache_size
  1375. return 100 - cur[0].value / cur[1].value * 100;
  1376. }
  1377. return undefined;
  1378. }
  1379. /* Build list of nodes that need to be retrieved from server.
  1380. * It creates something like a stripped down version of the runtime.charts object.
  1381. */
  1382. function buildRequiredDataList () {
  1383. runtime.dataList = {};
  1384. // Store an own id, because the property name is subject of reordering,
  1385. // thus destroying our mapping with runtime.charts <=> runtime.dataList
  1386. var chartID = 0;
  1387. $.each(runtime.charts, function (key, chart) {
  1388. runtime.dataList[chartID] = [];
  1389. for (var i = 0, l = chart.nodes.length; i < l; i++) {
  1390. runtime.dataList[chartID][i] = chart.nodes[i].dataPoints;
  1391. }
  1392. runtime.charts[key].chartID = chartID;
  1393. chartID++;
  1394. });
  1395. }
  1396. /* Loads the log table data, generates the table and handles the filters */
  1397. function loadLogStatistics (opts) {
  1398. var tableStr = '';
  1399. var logRequest = null;
  1400. if (! opts.removeVariables) {
  1401. opts.removeVariables = false;
  1402. }
  1403. if (! opts.limitTypes) {
  1404. opts.limitTypes = false;
  1405. }
  1406. $('#emptyDialog').dialog({ title: PMA_messages.strAnalysingLogsTitle });
  1407. $('#emptyDialog').html(PMA_messages.strAnalysingLogs +
  1408. ' <img class="ajaxIcon" src="' + pmaThemeImage +
  1409. 'ajax_clock_small.gif" alt="">');
  1410. var dlgBtns = {};
  1411. dlgBtns[PMA_messages.strCancelRequest] = function () {
  1412. if (logRequest !== null) {
  1413. logRequest.abort();
  1414. }
  1415. $(this).dialog('close');
  1416. };
  1417. $('#emptyDialog').dialog({
  1418. width: 'auto',
  1419. height: 'auto',
  1420. buttons: dlgBtns
  1421. });
  1422. logRequest = $.post(
  1423. 'server_status_monitor.php' + PMA_commonParams.get('common_query'),
  1424. {
  1425. ajax_request: true,
  1426. log_data: 1,
  1427. type: opts.src,
  1428. time_start: Math.round(opts.start / 1000),
  1429. time_end: Math.round(opts.end / 1000),
  1430. removeVariables: opts.removeVariables,
  1431. limitTypes: opts.limitTypes
  1432. },
  1433. function (data) {
  1434. var logData;
  1435. var dlgBtns = {};
  1436. if (typeof data !== 'undefined' && data.success === true) {
  1437. logData = data.message;
  1438. } else {
  1439. return serverResponseError();
  1440. }
  1441. if (logData.rows.length === 0) {
  1442. $('#emptyDialog').dialog({ title: PMA_messages.strNoDataFoundTitle });
  1443. $('#emptyDialog').html('<p>' + PMA_messages.strNoDataFound + '</p>');
  1444. dlgBtns[PMA_messages.strClose] = function () {
  1445. $(this).dialog('close');
  1446. };
  1447. $('#emptyDialog').dialog('option', 'buttons', dlgBtns);
  1448. return;
  1449. }
  1450. runtime.logDataCols = buildLogTable(logData, opts.removeVariables);
  1451. /* Show some stats in the dialog */
  1452. $('#emptyDialog').dialog({ title: PMA_messages.strLoadingLogs });
  1453. $('#emptyDialog').html('<p>' + PMA_messages.strLogDataLoaded + '</p>');
  1454. $.each(logData.sum, function (key, value) {
  1455. key = key.charAt(0).toUpperCase() + key.slice(1).toLowerCase();
  1456. if (key === 'Total') {
  1457. key = '<b>' + key + '</b>';
  1458. }
  1459. $('#emptyDialog').append(key + ': ' + value + '<br/>');
  1460. });
  1461. /* Add filter options if more than a bunch of rows there to filter */
  1462. if (logData.numRows > 12) {
  1463. $('#logTable').prepend(
  1464. '<fieldset id="logDataFilter">' +
  1465. ' <legend>' + PMA_messages.strFiltersForLogTable + '</legend>' +
  1466. ' <div class="formelement">' +
  1467. ' <label for="filterQueryText">' + PMA_messages.strFilterByWordRegexp + '</label>' +
  1468. ' <input name="filterQueryText" type="text" id="filterQueryText" style="vertical-align: baseline;" />' +
  1469. ' </div>' +
  1470. ((logData.numRows > 250) ? ' <div class="formelement"><button name="startFilterQueryText" id="startFilterQueryText">' + PMA_messages.strFilter + '</button></div>' : '') +
  1471. ' <div class="formelement">' +
  1472. ' <input type="checkbox" id="noWHEREData" name="noWHEREData" value="1" /> ' +
  1473. ' <label for="noWHEREData"> ' + PMA_messages.strIgnoreWhereAndGroup + '</label>' +
  1474. ' </div' +
  1475. '</fieldset>'
  1476. );
  1477. $('#noWHEREData').change(function () {
  1478. filterQueries(true);
  1479. });
  1480. if (logData.numRows > 250) {
  1481. $('#startFilterQueryText').click(filterQueries);
  1482. } else {
  1483. $('#filterQueryText').keyup(filterQueries);
  1484. }
  1485. }
  1486. dlgBtns[PMA_messages.strJumpToTable] = function () {
  1487. $(this).dialog('close');
  1488. $(document).scrollTop($('#logTable').offset().top);
  1489. };
  1490. $('#emptyDialog').dialog('option', 'buttons', dlgBtns);
  1491. }
  1492. );
  1493. /* Handles the actions performed when the user uses any of the
  1494. * log table filters which are the filter by name and grouping
  1495. * with ignoring data in WHERE clauses
  1496. *
  1497. * @param boolean Should be true when the users enabled or disabled
  1498. * to group queries ignoring data in WHERE clauses
  1499. */
  1500. function filterQueries (varFilterChange) {
  1501. var cell;
  1502. var textFilter;
  1503. var val = $('#filterQueryText').val();
  1504. if (val.length === 0) {
  1505. textFilter = null;
  1506. } else {
  1507. try {
  1508. textFilter = new RegExp(val, 'i');
  1509. $('#filterQueryText').removeClass('error');
  1510. } catch (e) {
  1511. if (e instanceof SyntaxError) {
  1512. $('#filterQueryText').addClass('error');
  1513. textFilter = null;
  1514. }
  1515. }
  1516. }
  1517. var rowSum = 0;
  1518. var totalSum = 0;
  1519. var i = 0;
  1520. var q;
  1521. var noVars = $('#noWHEREData').prop('checked');
  1522. var equalsFilter = /([^=]+)=(\d+|((\'|"|).*?[^\\])\4((\s+)|$))/gi;
  1523. var functionFilter = /([a-z0-9_]+)\(.+?\)/gi;
  1524. var filteredQueries = {};
  1525. var filteredQueriesLines = {};
  1526. var hide = false;
  1527. var rowData;
  1528. var queryColumnName = runtime.logDataCols[runtime.logDataCols.length - 2];
  1529. var sumColumnName = runtime.logDataCols[runtime.logDataCols.length - 1];
  1530. var isSlowLog = opts.src === 'slow';
  1531. var columnSums = {};
  1532. // For the slow log we have to count many columns (query_time, lock_time, rows_examined, rows_sent, etc.)
  1533. var countRow = function (query, row) {
  1534. var cells = row.match(/<td>(.*?)<\/td>/gi);
  1535. if (!columnSums[query]) {
  1536. columnSums[query] = [0, 0, 0, 0];
  1537. }
  1538. // lock_time and query_time and displayed in timespan format
  1539. columnSums[query][0] += timeToSec(cells[2].replace(/(<td>|<\/td>)/gi, ''));
  1540. columnSums[query][1] += timeToSec(cells[3].replace(/(<td>|<\/td>)/gi, ''));
  1541. // rows_examind and rows_sent are just numbers
  1542. columnSums[query][2] += parseInt(cells[4].replace(/(<td>|<\/td>)/gi, ''), 10);
  1543. columnSums[query][3] += parseInt(cells[5].replace(/(<td>|<\/td>)/gi, ''), 10);
  1544. };
  1545. // We just assume the sql text is always in the second last column, and that the total count is right of it
  1546. $('#logTable').find('table tbody tr td:nth-child(' + (runtime.logDataCols.length - 1) + ')').each(function () {
  1547. var $t = $(this);
  1548. // If query is a SELECT and user enabled or disabled to group
  1549. // queries ignoring data in where statements, we
  1550. // need to re-calculate the sums of each row
  1551. if (varFilterChange && $t.html().match(/^SELECT/i)) {
  1552. if (noVars) {
  1553. // Group on => Sum up identical columns, and hide all but 1
  1554. q = $t.text().replace(equalsFilter, '$1=...$6').trim();
  1555. q = q.replace(functionFilter, ' $1(...)');
  1556. // Js does not specify a limit on property name length,
  1557. // so we can abuse it as index :-)
  1558. if (filteredQueries[q]) {
  1559. filteredQueries[q] += parseInt($t.next().text(), 10);
  1560. totalSum += parseInt($t.next().text(), 10);
  1561. hide = true;
  1562. } else {
  1563. filteredQueries[q] = parseInt($t.next().text(), 10);
  1564. filteredQueriesLines[q] = i;
  1565. $t.text(q);
  1566. }
  1567. if (isSlowLog) {
  1568. countRow(q, $t.parent().html());
  1569. }
  1570. } else {
  1571. // Group off: Restore original columns
  1572. rowData = $t.parent().data('query');
  1573. // Restore SQL text
  1574. $t.text(rowData[queryColumnName]);
  1575. // Restore total count
  1576. $t.next().text(rowData[sumColumnName]);
  1577. // Restore slow log columns
  1578. if (isSlowLog) {
  1579. $t.parent().children('td:nth-child(3)').text(rowData.query_time);
  1580. $t.parent().children('td:nth-child(4)').text(rowData.lock_time);
  1581. $t.parent().children('td:nth-child(5)').text(rowData.rows_sent);
  1582. $t.parent().children('td:nth-child(6)').text(rowData.rows_examined);
  1583. }
  1584. }
  1585. }
  1586. // If not required to be hidden, do we need
  1587. // to hide because of a not matching text filter?
  1588. if (! hide && (textFilter !== null && ! textFilter.exec($t.text()))) {
  1589. hide = true;
  1590. }
  1591. // Now display or hide this column
  1592. if (hide) {
  1593. $t.parent().css('display', 'none');
  1594. } else {
  1595. totalSum += parseInt($t.next().text(), 10);
  1596. rowSum++;
  1597. $t.parent().css('display', '');
  1598. }
  1599. hide = false;
  1600. i++;
  1601. });
  1602. // We finished summarizing counts => Update count values of all grouped entries
  1603. if (varFilterChange) {
  1604. if (noVars) {
  1605. var numCol;
  1606. var row;
  1607. var $table = $('#logTable').find('table tbody');
  1608. $.each(filteredQueriesLines, function (key, value) {
  1609. if (filteredQueries[key] <= 1) {
  1610. return;
  1611. }
  1612. row = $table.children('tr:nth-child(' + (value + 1) + ')');
  1613. numCol = row.children(':nth-child(' + (runtime.logDataCols.length) + ')');
  1614. numCol.text(filteredQueries[key]);
  1615. if (isSlowLog) {
  1616. row.children('td:nth-child(3)').text(secToTime(columnSums[key][0]));
  1617. row.children('td:nth-child(4)').text(secToTime(columnSums[key][1]));
  1618. row.children('td:nth-child(5)').text(columnSums[key][2]);
  1619. row.children('td:nth-child(6)').text(columnSums[key][3]);
  1620. }
  1621. });
  1622. }
  1623. $('#logTable').find('table').trigger('update');
  1624. setTimeout(function () {
  1625. $('#logTable').find('table').trigger('sorton', [[[runtime.logDataCols.length - 1, 1]]]);
  1626. }, 0);
  1627. }
  1628. // Display some stats at the bottom of the table
  1629. $('#logTable').find('table tfoot tr')
  1630. .html('<th colspan="' + (runtime.logDataCols.length - 1) + '">' +
  1631. PMA_messages.strSumRows + ' ' + rowSum + '<span class="floatright">' +
  1632. PMA_messages.strTotal + '</span></th><th class="right">' + totalSum + '</th>');
  1633. }
  1634. }
  1635. /* Turns a timespan (12:12:12) into a number */
  1636. function timeToSec (timeStr) {
  1637. var time = timeStr.split(':');
  1638. return (parseInt(time[0], 10) * 3600) + (parseInt(time[1], 10) * 60) + parseInt(time[2], 10);
  1639. }
  1640. /* Turns a number into a timespan (100 into 00:01:40) */
  1641. function secToTime (timeInt) {
  1642. var hours = Math.floor(timeInt / 3600);
  1643. timeInt -= hours * 3600;
  1644. var minutes = Math.floor(timeInt / 60);
  1645. timeInt -= minutes * 60;
  1646. if (hours < 10) {
  1647. hours = '0' + hours;
  1648. }
  1649. if (minutes < 10) {
  1650. minutes = '0' + minutes;
  1651. }
  1652. if (timeInt < 10) {
  1653. timeInt = '0' + timeInt;
  1654. }
  1655. return hours + ':' + minutes + ':' + timeInt;
  1656. }
  1657. /* Constructs the log table out of the retrieved server data */
  1658. function buildLogTable (data, groupInserts) {
  1659. var rows = data.rows;
  1660. var cols = [];
  1661. var $table = $('<table class="sortable"></table>');
  1662. var $tBody;
  1663. var $tRow;
  1664. var $tCell;
  1665. $('#logTable').html($table);
  1666. var tempPushKey = function (key, value) {
  1667. cols.push(key);
  1668. };
  1669. var formatValue = function (name, value) {
  1670. if (name === 'user_host') {
  1671. return value.replace(/(\[.*?\])+/g, '');
  1672. }
  1673. return escapeHtml(value);
  1674. };
  1675. for (var i = 0, l = rows.length; i < l; i++) {
  1676. if (i === 0) {
  1677. $.each(rows[0], tempPushKey);
  1678. $table.append('<thead>' +
  1679. '<tr><th class="nowrap">' + cols.join('</th><th class="nowrap">') + '</th></tr>' +
  1680. '</thead>'
  1681. );
  1682. $table.append($tBody = $('<tbody></tbody>'));
  1683. }
  1684. $tBody.append($tRow = $('<tr class="noclick"></tr>'));
  1685. var cl = '';
  1686. for (var j = 0, ll = cols.length; j < ll; j++) {
  1687. // Assuming the query column is the second last
  1688. if (j === cols.length - 2 && rows[i][cols[j]].match(/^SELECT/i)) {
  1689. $tRow.append($tCell = $('<td class="linkElem">' + formatValue(cols[j], rows[i][cols[j]]) + '</td>'));
  1690. $tCell.click(openQueryAnalyzer);
  1691. } else {
  1692. $tRow.append('<td>' + formatValue(cols[j], rows[i][cols[j]]) + '</td>');
  1693. }
  1694. $tRow.data('query', rows[i]);
  1695. }
  1696. }
  1697. $table.append('<tfoot>' +
  1698. '<tr><th colspan="' + (cols.length - 1) + '">' + PMA_messages.strSumRows +
  1699. ' ' + data.numRows + '<span class="floatright">' + PMA_messages.strTotal +
  1700. '</span></th><th class="right">' + data.sum.TOTAL + '</th></tr></tfoot>');
  1701. // Append a tooltip to the count column, if there exist one
  1702. if ($('#logTable').find('tr:first th:last').text().indexOf('#') > -1) {
  1703. $('#logTable').find('tr:first th:last').append('&nbsp;' + PMA_getImage('b_help', '', { 'class': 'qroupedQueryInfoIcon' }));
  1704. var tooltipContent = PMA_messages.strCountColumnExplanation;
  1705. if (groupInserts) {
  1706. tooltipContent += '<p>' + PMA_messages.strMoreCountColumnExplanation + '</p>';
  1707. }
  1708. PMA_tooltip(
  1709. $('img.qroupedQueryInfoIcon'),
  1710. 'img',
  1711. tooltipContent
  1712. );
  1713. }
  1714. $('#logTable').find('table').tablesorter({
  1715. sortList: [[cols.length - 1, 1]],
  1716. widgets: ['fast-zebra']
  1717. });
  1718. $('#logTable').find('table thead th')
  1719. .append('<div class="sorticon"></div>');
  1720. return cols;
  1721. }
  1722. /* Opens the query analyzer dialog */
  1723. function openQueryAnalyzer () {
  1724. var rowData = $(this).parent().data('query');
  1725. var query = rowData.argument || rowData.sql_text;
  1726. if (codemirror_editor) {
  1727. // TODO: somehow PMA_SQLPrettyPrint messes up the query, needs be fixed
  1728. // query = PMA_SQLPrettyPrint(query);
  1729. codemirror_editor.setValue(query);
  1730. // Codemirror is bugged, it doesn't refresh properly sometimes.
  1731. // Following lines seem to fix that
  1732. setTimeout(function () {
  1733. codemirror_editor.refresh();
  1734. }, 50);
  1735. } else {
  1736. $('#sqlquery').val(query);
  1737. }
  1738. var profilingChart = null;
  1739. var dlgBtns = {};
  1740. dlgBtns[PMA_messages.strAnalyzeQuery] = function () {
  1741. loadQueryAnalysis(rowData);
  1742. };
  1743. dlgBtns[PMA_messages.strClose] = function () {
  1744. $(this).dialog('close');
  1745. };
  1746. $('#queryAnalyzerDialog').dialog({
  1747. width: 'auto',
  1748. height: 'auto',
  1749. resizable: false,
  1750. buttons: dlgBtns,
  1751. close: function () {
  1752. if (profilingChart !== null) {
  1753. profilingChart.destroy();
  1754. }
  1755. $('#queryAnalyzerDialog').find('div.placeHolder').html('');
  1756. if (codemirror_editor) {
  1757. codemirror_editor.setValue('');
  1758. } else {
  1759. $('#sqlquery').val('');
  1760. }
  1761. }
  1762. });
  1763. }
  1764. /* Loads and displays the analyzed query data */
  1765. function loadQueryAnalysis (rowData) {
  1766. var db = rowData.db || '';
  1767. $('#queryAnalyzerDialog').find('div.placeHolder').html(
  1768. PMA_messages.strAnalyzing + ' <img class="ajaxIcon" src="' +
  1769. pmaThemeImage + 'ajax_clock_small.gif" alt="">');
  1770. $.post('server_status_monitor.php' + PMA_commonParams.get('common_query'), {
  1771. ajax_request: true,
  1772. query_analyzer: true,
  1773. query: codemirror_editor ? codemirror_editor.getValue() : $('#sqlquery').val(),
  1774. database: db,
  1775. server: PMA_commonParams.get('server')
  1776. }, function (data) {
  1777. var i;
  1778. var l;
  1779. if (typeof data !== 'undefined' && data.success === true) {
  1780. data = data.message;
  1781. }
  1782. if (data.error) {
  1783. if (data.error.indexOf('1146') !== -1 || data.error.indexOf('1046') !== -1) {
  1784. data.error = PMA_messages.strServerLogError;
  1785. }
  1786. $('#queryAnalyzerDialog').find('div.placeHolder').html('<div class="error">' + data.error + '</div>');
  1787. return;
  1788. }
  1789. var totalTime = 0;
  1790. // Float sux, I'll use table :(
  1791. $('#queryAnalyzerDialog').find('div.placeHolder')
  1792. .html('<table width="100%" border="0"><tr><td class="explain"></td><td class="chart"></td></tr></table>');
  1793. var explain = '<b>' + PMA_messages.strExplainOutput + '</b> ' + $('#explain_docu').html();
  1794. if (data.explain.length > 1) {
  1795. explain += ' (';
  1796. for (i = 0; i < data.explain.length; i++) {
  1797. if (i > 0) {
  1798. explain += ', ';
  1799. }
  1800. explain += '<a href="#showExplain-' + i + '">' + i + '</a>';
  1801. }
  1802. explain += ')';
  1803. }
  1804. explain += '<p></p>';
  1805. var tempExplain = function (key, value) {
  1806. value = (value === null) ? 'null' : escapeHtml(value);
  1807. if (key === 'type' && value.toLowerCase() === 'all') {
  1808. value = '<span class="attention">' + value + '</span>';
  1809. }
  1810. if (key === 'Extra') {
  1811. value = value.replace(/(using (temporary|filesort))/gi, '<span class="attention">$1</span>');
  1812. }
  1813. explain += key + ': ' + value + '<br />';
  1814. };
  1815. for (i = 0, l = data.explain.length; i < l; i++) {
  1816. explain += '<div class="explain-' + i + '"' + (i > 0 ? 'style="display:none;"' : '') + '>';
  1817. $.each(data.explain[i], tempExplain);
  1818. explain += '</div>';
  1819. }
  1820. explain += '<p><b>' + PMA_messages.strAffectedRows + '</b> ' + data.affectedRows;
  1821. $('#queryAnalyzerDialog').find('div.placeHolder td.explain').append(explain);
  1822. $('#queryAnalyzerDialog').find('div.placeHolder a[href*="#showExplain"]').click(function () {
  1823. var id = $(this).attr('href').split('-')[1];
  1824. $(this).parent().find('div[class*="explain"]').hide();
  1825. $(this).parent().find('div[class*="explain-' + id + '"]').show();
  1826. });
  1827. if (data.profiling) {
  1828. var chartData = [];
  1829. var numberTable = '<table class="queryNums"><thead><tr><th>' + PMA_messages.strStatus + '</th><th>' + PMA_messages.strTime + '</th></tr></thead><tbody>';
  1830. var duration;
  1831. var otherTime = 0;
  1832. for (i = 0, l = data.profiling.length; i < l; i++) {
  1833. duration = parseFloat(data.profiling[i].duration);
  1834. totalTime += duration;
  1835. numberTable += '<tr><td>' + data.profiling[i].state + ' </td><td> ' + PMA_prettyProfilingNum(duration, 2) + '</td></tr>';
  1836. }
  1837. // Only put those values in the pie which are > 2%
  1838. for (i = 0, l = data.profiling.length; i < l; i++) {
  1839. duration = parseFloat(data.profiling[i].duration);
  1840. if (duration / totalTime > 0.02) {
  1841. chartData.push([PMA_prettyProfilingNum(duration, 2) + ' ' + data.profiling[i].state, duration]);
  1842. } else {
  1843. otherTime += duration;
  1844. }
  1845. }
  1846. if (otherTime > 0) {
  1847. chartData.push([PMA_prettyProfilingNum(otherTime, 2) + ' ' + PMA_messages.strOther, otherTime]);
  1848. }
  1849. numberTable += '<tr><td><b>' + PMA_messages.strTotalTime + '</b></td><td>' + PMA_prettyProfilingNum(totalTime, 2) + '</td></tr>';
  1850. numberTable += '</tbody></table>';
  1851. $('#queryAnalyzerDialog').find('div.placeHolder td.chart').append(
  1852. '<b>' + PMA_messages.strProfilingResults + ' ' + $('#profiling_docu').html() + '</b> ' +
  1853. '(<a href="#showNums">' + PMA_messages.strTable + '</a>, <a href="#showChart">' + PMA_messages.strChart + '</a>)<br/>' +
  1854. numberTable + ' <div id="queryProfiling"></div>');
  1855. $('#queryAnalyzerDialog').find('div.placeHolder a[href="#showNums"]').click(function () {
  1856. $('#queryAnalyzerDialog').find('#queryProfiling').hide();
  1857. $('#queryAnalyzerDialog').find('table.queryNums').show();
  1858. return false;
  1859. });
  1860. $('#queryAnalyzerDialog').find('div.placeHolder a[href="#showChart"]').click(function () {
  1861. $('#queryAnalyzerDialog').find('#queryProfiling').show();
  1862. $('#queryAnalyzerDialog').find('table.queryNums').hide();
  1863. return false;
  1864. });
  1865. profilingChart = PMA_createProfilingChart(
  1866. 'queryProfiling',
  1867. chartData
  1868. );
  1869. // $('#queryProfiling').resizable();
  1870. }
  1871. });
  1872. }
  1873. /* Saves the monitor to localstorage */
  1874. function saveMonitor () {
  1875. var gridCopy = {};
  1876. $.each(runtime.charts, function (key, elem) {
  1877. gridCopy[key] = {};
  1878. gridCopy[key].nodes = elem.nodes;
  1879. gridCopy[key].settings = elem.settings;
  1880. gridCopy[key].title = elem.title;
  1881. gridCopy[key].series = elem.series;
  1882. gridCopy[key].maxYLabel = elem.maxYLabel;
  1883. });
  1884. if (isStorageSupported('localStorage')) {
  1885. window.localStorage.monitorCharts = JSON.stringify(gridCopy);
  1886. window.localStorage.monitorSettings = JSON.stringify(monitorSettings);
  1887. window.localStorage.monitorVersion = monitorProtocolVersion;
  1888. }
  1889. $('a[href="#clearMonitorConfig"]').show();
  1890. }
  1891. });
  1892. // Run the monitor once loaded
  1893. AJAX.registerOnload('server_status_monitor.js', function () {
  1894. $('a[href="#pauseCharts"]').trigger('click');
  1895. });
  1896. function serverResponseError () {
  1897. var btns = {};
  1898. btns[PMA_messages.strReloadPage] = function () {
  1899. window.location.reload();
  1900. };
  1901. $('#emptyDialog').dialog({ title: PMA_messages.strRefreshFailed });
  1902. $('#emptyDialog').html(
  1903. PMA_getImage('s_attention') +
  1904. PMA_messages.strInvalidResponseExplanation
  1905. );
  1906. $('#emptyDialog').dialog({ buttons: btns });
  1907. }
  1908. /* Destroys all monitor related resources */
  1909. function destroyGrid () {
  1910. if (runtime.charts) {
  1911. $.each(runtime.charts, function (key, value) {
  1912. try {
  1913. value.chart.destroy();
  1914. } catch (err) {}
  1915. });
  1916. }
  1917. try {
  1918. runtime.refreshRequest.abort();
  1919. } catch (err) {}
  1920. try {
  1921. clearTimeout(runtime.refreshTimeout);
  1922. } catch (err) {}
  1923. $('#chartGrid').html('');
  1924. runtime.charts = null;
  1925. runtime.chartAI = 0;
  1926. monitorSettings = null; // TODO:this not global variable
  1927. }