server_status_monitor.js 84 KB

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