server_status_monitor.js 84 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181
  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].settings = elem.settings;
  515. gridCopy[key].title = elem.title;
  516. });
  517. var exportData = {
  518. monitorCharts: gridCopy,
  519. monitorSettings: monitorSettings
  520. };
  521. var blob = new Blob([JSON.stringify(exportData)], { type: 'application/octet-stream' });
  522. var url = window.URL.createObjectURL(blob);
  523. window.location.href = url;
  524. window.URL.revokeObjectURL(url);
  525. });
  526. $('a[href="#importMonitorConfig"]').click(function (event) {
  527. event.preventDefault();
  528. $('#emptyDialog').dialog({ title: PMA_messages.strImportDialogTitle });
  529. $('#emptyDialog').html(PMA_messages.strImportDialogMessage + ':<br/><form>' +
  530. '<input type="file" name="file" id="import_file"> </form>');
  531. var dlgBtns = {};
  532. dlgBtns[PMA_messages.strImport] = function () {
  533. var input = $('#emptyDialog').find('#import_file')[0];
  534. var reader = new FileReader();
  535. reader.onerror = function (event) {
  536. alert(PMA_messages.strFailedParsingConfig + '\n' + event.target.error.code);
  537. };
  538. reader.onload = function (e) {
  539. var data = e.target.result;
  540. // Try loading config
  541. try {
  542. json = JSON.parse(data);
  543. } catch (err) {
  544. alert(PMA_messages.strFailedParsingConfig);
  545. $('#emptyDialog').dialog('close');
  546. return;
  547. }
  548. // Basic check, is this a monitor config json?
  549. if (!json || ! json.monitorCharts || ! json.monitorCharts) {
  550. alert(PMA_messages.strFailedParsingConfig);
  551. $('#emptyDialog').dialog('close');
  552. return;
  553. }
  554. // If json ok, try applying config
  555. try {
  556. window.localStorage.monitorCharts = JSON.stringify(json.monitorCharts);
  557. window.localStorage.monitorSettings = JSON.stringify(json.monitorSettings);
  558. rebuildGrid();
  559. } catch (err) {
  560. console.log(err);
  561. alert(PMA_messages.strFailedBuildingGrid);
  562. // If an exception is thrown, load default again
  563. if (isStorageSupported('localStorage')) {
  564. window.localStorage.removeItem('monitorCharts');
  565. window.localStorage.removeItem('monitorSettings');
  566. }
  567. rebuildGrid();
  568. }
  569. $('#emptyDialog').dialog('close');
  570. };
  571. reader.readAsText(input.files[0]);
  572. };
  573. dlgBtns[PMA_messages.strCancel] = function () {
  574. $(this).dialog('close');
  575. };
  576. $('#emptyDialog').dialog({
  577. width: 'auto',
  578. height: 'auto',
  579. buttons: dlgBtns
  580. });
  581. });
  582. $('a[href="#clearMonitorConfig"]').click(function (event) {
  583. event.preventDefault();
  584. if (isStorageSupported('localStorage')) {
  585. window.localStorage.removeItem('monitorCharts');
  586. window.localStorage.removeItem('monitorSettings');
  587. window.localStorage.removeItem('monitorVersion');
  588. }
  589. $(this).hide();
  590. rebuildGrid();
  591. });
  592. $('a[href="#pauseCharts"]').click(function (event) {
  593. event.preventDefault();
  594. runtime.redrawCharts = ! runtime.redrawCharts;
  595. if (! runtime.redrawCharts) {
  596. $(this).html(PMA_getImage('play') + PMA_messages.strResumeMonitor);
  597. } else {
  598. $(this).html(PMA_getImage('pause') + PMA_messages.strPauseMonitor);
  599. if (! runtime.charts) {
  600. initGrid();
  601. $('a[href="#settingsPopup"]').show();
  602. }
  603. }
  604. return false;
  605. });
  606. $('a[href="#monitorInstructionsDialog"]').click(function (event) {
  607. event.preventDefault();
  608. var $dialog = $('#monitorInstructionsDialog');
  609. $dialog.dialog({
  610. width: 595,
  611. height: 'auto'
  612. }).find('img.ajaxIcon').show();
  613. var loadLogVars = function (getvars) {
  614. var vars = { ajax_request: true, logging_vars: true };
  615. if (getvars) {
  616. $.extend(vars, getvars);
  617. }
  618. $.post('server_status_monitor.php' + PMA_commonParams.get('common_query'), vars,
  619. function (data) {
  620. var logVars;
  621. if (typeof data !== 'undefined' && data.success === true) {
  622. logVars = data.message;
  623. } else {
  624. return serverResponseError();
  625. }
  626. var icon = PMA_getImage('s_success');
  627. var msg = '';
  628. var str = '';
  629. if (logVars.general_log === 'ON') {
  630. if (logVars.slow_query_log === 'ON') {
  631. msg = PMA_messages.strBothLogOn;
  632. } else {
  633. msg = PMA_messages.strGenLogOn;
  634. }
  635. }
  636. if (msg.length === 0 && logVars.slow_query_log === 'ON') {
  637. msg = PMA_messages.strSlowLogOn;
  638. }
  639. if (msg.length === 0) {
  640. icon = PMA_getImage('s_error');
  641. msg = PMA_messages.strBothLogOff;
  642. }
  643. str = '<b>' + PMA_messages.strCurrentSettings + '</b><br/><div class="smallIndent">';
  644. str += icon + msg + '<br />';
  645. if (logVars.log_output !== 'TABLE') {
  646. str += PMA_getImage('s_error') + ' ' + PMA_messages.strLogOutNotTable + '<br />';
  647. } else {
  648. str += PMA_getImage('s_success') + ' ' + PMA_messages.strLogOutIsTable + '<br />';
  649. }
  650. if (logVars.slow_query_log === 'ON') {
  651. if (logVars.long_query_time > 2) {
  652. str += PMA_getImage('s_attention') + ' ';
  653. str += PMA_sprintf(PMA_messages.strSmallerLongQueryTimeAdvice, logVars.long_query_time);
  654. str += '<br />';
  655. }
  656. if (logVars.long_query_time < 2) {
  657. str += PMA_getImage('s_success') + ' ';
  658. str += PMA_sprintf(PMA_messages.strLongQueryTimeSet, logVars.long_query_time);
  659. str += '<br />';
  660. }
  661. }
  662. str += '</div>';
  663. if (is_superuser) {
  664. str += '<p></p><b>' + PMA_messages.strChangeSettings + '</b>';
  665. str += '<div class="smallIndent">';
  666. str += PMA_messages.strSettingsAppliedGlobal + '<br/>';
  667. var varValue = 'TABLE';
  668. if (logVars.log_output === 'TABLE') {
  669. varValue = 'FILE';
  670. }
  671. str += '- <a class="set" href="#log_output-' + varValue + '">';
  672. str += PMA_sprintf(PMA_messages.strSetLogOutput, varValue);
  673. str += ' </a><br />';
  674. if (logVars.general_log !== 'ON') {
  675. str += '- <a class="set" href="#general_log-ON">';
  676. str += PMA_sprintf(PMA_messages.strEnableVar, 'general_log');
  677. str += ' </a><br />';
  678. } else {
  679. str += '- <a class="set" href="#general_log-OFF">';
  680. str += PMA_sprintf(PMA_messages.strDisableVar, 'general_log');
  681. str += ' </a><br />';
  682. }
  683. if (logVars.slow_query_log !== 'ON') {
  684. str += '- <a class="set" href="#slow_query_log-ON">';
  685. str += PMA_sprintf(PMA_messages.strEnableVar, 'slow_query_log');
  686. str += ' </a><br />';
  687. } else {
  688. str += '- <a class="set" href="#slow_query_log-OFF">';
  689. str += PMA_sprintf(PMA_messages.strDisableVar, 'slow_query_log');
  690. str += ' </a><br />';
  691. }
  692. varValue = 5;
  693. if (logVars.long_query_time > 2) {
  694. varValue = 1;
  695. }
  696. str += '- <a class="set" href="#long_query_time-' + varValue + '">';
  697. str += PMA_sprintf(PMA_messages.setSetLongQueryTime, varValue);
  698. str += ' </a><br />';
  699. } else {
  700. str += PMA_messages.strNoSuperUser + '<br/>';
  701. }
  702. str += '</div>';
  703. $dialog.find('div.monitorUse').toggle(
  704. logVars.log_output === 'TABLE' && (logVars.slow_query_log === 'ON' || logVars.general_log === 'ON')
  705. );
  706. $dialog.find('div.ajaxContent').html(str);
  707. $dialog.find('img.ajaxIcon').hide();
  708. $dialog.find('a.set').click(function () {
  709. var nameValue = $(this).attr('href').split('-');
  710. loadLogVars({ varName: nameValue[0].substr(1), varValue: nameValue[1] });
  711. $dialog.find('img.ajaxIcon').show();
  712. });
  713. }
  714. );
  715. };
  716. loadLogVars();
  717. return false;
  718. });
  719. $('input[name="chartType"]').change(function () {
  720. $('#chartVariableSettings').toggle(this.checked && this.value === 'variable');
  721. var title = $('input[name="chartTitle"]').val();
  722. if (title === PMA_messages.strChartTitle ||
  723. title === $('label[for="' + $('input[name="chartTitle"]').data('lastRadio') + '"]').text()
  724. ) {
  725. $('input[name="chartTitle"]')
  726. .data('lastRadio', $(this).attr('id'))
  727. .val($('label[for="' + $(this).attr('id') + '"]').text());
  728. }
  729. });
  730. $('input[name="useDivisor"]').change(function () {
  731. $('span.divisorInput').toggle(this.checked);
  732. });
  733. $('input[name="useUnit"]').change(function () {
  734. $('span.unitInput').toggle(this.checked);
  735. });
  736. $('select[name="varChartList"]').change(function () {
  737. if (this.selectedIndex !== 0) {
  738. $('#variableInput').val(this.value);
  739. }
  740. });
  741. $('a[href="#kibDivisor"]').click(function (event) {
  742. event.preventDefault();
  743. $('input[name="valueDivisor"]').val(1024);
  744. $('input[name="valueUnit"]').val(PMA_messages.strKiB);
  745. $('span.unitInput').toggle(true);
  746. $('input[name="useUnit"]').prop('checked', true);
  747. return false;
  748. });
  749. $('a[href="#mibDivisor"]').click(function (event) {
  750. event.preventDefault();
  751. $('input[name="valueDivisor"]').val(1024 * 1024);
  752. $('input[name="valueUnit"]').val(PMA_messages.strMiB);
  753. $('span.unitInput').toggle(true);
  754. $('input[name="useUnit"]').prop('checked', true);
  755. return false;
  756. });
  757. $('a[href="#submitClearSeries"]').click(function (event) {
  758. event.preventDefault();
  759. $('#seriesPreview').html('<i>' + PMA_messages.strNone + '</i>');
  760. newChart = null;
  761. $('#clearSeriesLink').hide();
  762. });
  763. $('a[href="#submitAddSeries"]').click(function (event) {
  764. event.preventDefault();
  765. if ($('#variableInput').val() === '') {
  766. return false;
  767. }
  768. if (newChart === null) {
  769. $('#seriesPreview').html('');
  770. newChart = {
  771. title: $('input[name="chartTitle"]').val(),
  772. nodes: [],
  773. series: [],
  774. maxYLabel: 0
  775. };
  776. }
  777. var serie = {
  778. dataPoints: [{ type: 'statusvar', name: $('#variableInput').val() }],
  779. display: $('input[name="differentialValue"]').prop('checked') ? 'differential' : ''
  780. };
  781. if (serie.dataPoints[0].name === 'Processes') {
  782. serie.dataPoints[0].type = 'proc';
  783. }
  784. if ($('input[name="useDivisor"]').prop('checked')) {
  785. serie.valueDivisor = parseInt($('input[name="valueDivisor"]').val(), 10);
  786. }
  787. if ($('input[name="useUnit"]').prop('checked')) {
  788. serie.unit = $('input[name="valueUnit"]').val();
  789. }
  790. var str = serie.display === 'differential' ? ', ' + PMA_messages.strDifferential : '';
  791. str += serie.valueDivisor ? (', ' + PMA_sprintf(PMA_messages.strDividedBy, serie.valueDivisor)) : '';
  792. str += serie.unit ? (', ' + PMA_messages.strUnit + ': ' + serie.unit) : '';
  793. var newSeries = {
  794. label: $('#variableInput').val().replace(/_/g, ' ')
  795. };
  796. newChart.series.push(newSeries);
  797. $('#seriesPreview').append('- ' + escapeHtml(newSeries.label + str) + '<br/>');
  798. newChart.nodes.push(serie);
  799. $('#variableInput').val('');
  800. $('input[name="differentialValue"]').prop('checked', true);
  801. $('input[name="useDivisor"]').prop('checked', false);
  802. $('input[name="useUnit"]').prop('checked', false);
  803. $('input[name="useDivisor"]').trigger('change');
  804. $('input[name="useUnit"]').trigger('change');
  805. $('select[name="varChartList"]').get(0).selectedIndex = 0;
  806. $('#clearSeriesLink').show();
  807. return false;
  808. });
  809. $('#variableInput').autocomplete({
  810. source: variableNames
  811. });
  812. /* Initializes the monitor, called only once */
  813. function initGrid () {
  814. var i;
  815. /* Apply default values & config */
  816. if (isStorageSupported('localStorage')) {
  817. if (typeof window.localStorage.monitorCharts !== 'undefined') {
  818. runtime.charts = JSON.parse(window.localStorage.monitorCharts);
  819. }
  820. if (typeof window.localStorage.monitorSettings !== 'undefined') {
  821. monitorSettings = JSON.parse(window.localStorage.monitorSettings);
  822. }
  823. $('a[href="#clearMonitorConfig"]').toggle(runtime.charts !== null);
  824. if (runtime.charts !== null
  825. && typeof window.localStorage.monitorVersion !== 'undefined'
  826. && monitorProtocolVersion !== window.localStorage.monitorVersion
  827. ) {
  828. $('#emptyDialog').dialog({ title: PMA_messages.strIncompatibleMonitorConfig });
  829. $('#emptyDialog').html(PMA_messages.strIncompatibleMonitorConfigDescription);
  830. var dlgBtns = {};
  831. dlgBtns[PMA_messages.strClose] = function () {
  832. $(this).dialog('close');
  833. };
  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').find('td:nth-child(2)').offset().left -
  859. $('#chartGrid').find('td:nth-child(1)').offset().left,
  860. height: $('#chartGrid').find('tr:nth-child(2) td:nth-child(2)').offset().top -
  861. $('#chartGrid').find('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').find('.monitorChart').length;
  875. var numMissingCells = (monitorSettings.columns - numCharts % monitorSettings.columns) % monitorSettings.columns;
  876. for (i = 0; i < numMissingCells; i++) {
  877. $('#chartGrid').find('tr:last').append('<td></td>');
  878. }
  879. // Empty cells should keep their size so you can drop onto them
  880. calculateChartSize();
  881. $('#chartGrid').find('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;
  912. var windowWidth = $(window).width();
  913. if (windowWidth > 768) {
  914. wdt = (panelWidth - monitorSettings.columns * chartSpacing.width) / monitorSettings.columns;
  915. }
  916. chartSize = {
  917. width: Math.floor(wdt),
  918. height: Math.floor(0.75 * wdt)
  919. };
  920. }
  921. /* Adds a chart to the chart grid */
  922. function addChart (chartObj, initialize) {
  923. var i;
  924. var settings = {
  925. title: escapeHtml(chartObj.title),
  926. grid: {
  927. drawBorder: false,
  928. shadow: false,
  929. background: 'rgba(0,0,0,0)'
  930. },
  931. axes: {
  932. xaxis: {
  933. renderer: $.jqplot.DateAxisRenderer,
  934. tickOptions: {
  935. formatString: '%H:%M:%S',
  936. showGridline: false
  937. },
  938. min: runtime.xmin,
  939. max: runtime.xmax
  940. },
  941. yaxis: {
  942. min: 0,
  943. max: 100,
  944. tickInterval: 20
  945. }
  946. },
  947. seriesDefaults: {
  948. rendererOptions: {
  949. smooth: true
  950. },
  951. showLine: true,
  952. lineWidth: 2,
  953. markerOptions: {
  954. size: 6
  955. }
  956. },
  957. highlighter: {
  958. show: true
  959. }
  960. };
  961. if (settings.title === PMA_messages.strSystemCPUUsage ||
  962. settings.title === PMA_messages.strQueryCacheEfficiency
  963. ) {
  964. settings.axes.yaxis.tickOptions = {
  965. formatString: '%d %%'
  966. };
  967. } else if (settings.title === PMA_messages.strSystemMemory ||
  968. settings.title === PMA_messages.strSystemSwap
  969. ) {
  970. settings.stackSeries = true;
  971. settings.axes.yaxis.tickOptions = {
  972. formatter: $.jqplot.byteFormatter(2) // MiB
  973. };
  974. } else if (settings.title === PMA_messages.strTraffic) {
  975. settings.axes.yaxis.tickOptions = {
  976. formatter: $.jqplot.byteFormatter(1) // KiB
  977. };
  978. } else if (settings.title === PMA_messages.strQuestions ||
  979. settings.title === PMA_messages.strConnections
  980. ) {
  981. settings.axes.yaxis.tickOptions = {
  982. formatter: function (format, val) {
  983. if (Math.abs(val) >= 1000000) {
  984. return $.jqplot.sprintf('%.3g M', val / 1000000);
  985. } else if (Math.abs(val) >= 1000) {
  986. return $.jqplot.sprintf('%.3g k', val / 1000);
  987. } else {
  988. return $.jqplot.sprintf('%d', val);
  989. }
  990. }
  991. };
  992. }
  993. settings.series = chartObj.series;
  994. if ($('#' + 'gridchart' + runtime.chartAI).length === 0) {
  995. var numCharts = $('#chartGrid').find('.monitorChart').length;
  996. if (numCharts === 0 || (numCharts % monitorSettings.columns === 0)) {
  997. $('#chartGrid').append('<tr></tr>');
  998. }
  999. if (!chartSize) {
  1000. calculateChartSize();
  1001. }
  1002. $('#chartGrid').find('tr:last').append(
  1003. '<td><div id="gridChartContainer' + runtime.chartAI + '" class="">' +
  1004. '<div class="ui-state-default monitorChart"' +
  1005. ' id="gridchart' + runtime.chartAI + '"' +
  1006. ' style="width:' + chartSize.width + 'px; height:' + chartSize.height + 'px;"></div>' +
  1007. '</div></td>'
  1008. );
  1009. }
  1010. // Set series' data as [0,0], smooth lines won't plot with data array having null values.
  1011. // also chart won't plot initially with no data and data comes on refreshChartGrid()
  1012. var series = [];
  1013. for (i in chartObj.series) {
  1014. series.push([[0, 0]]);
  1015. }
  1016. var tempTooltipContentEditor = function (str, seriesIndex, pointIndex, plot) {
  1017. var j;
  1018. // TODO: move style to theme CSS
  1019. var tooltipHtml = '<div style="font-size:12px;background-color:#FFFFFF;' +
  1020. 'opacity:0.95;filter:alpha(opacity=95);padding:5px;">';
  1021. // x value i.e. time
  1022. var timeValue = str.split(',')[0];
  1023. var seriesValue;
  1024. tooltipHtml += 'Time: ' + timeValue;
  1025. tooltipHtml += '<span style="font-weight:bold;">';
  1026. // Add y values to the tooltip per series
  1027. for (j in plot.series) {
  1028. // get y value if present
  1029. if (plot.series[j].data.length > pointIndex) {
  1030. seriesValue = plot.series[j].data[pointIndex][1];
  1031. } else {
  1032. return;
  1033. }
  1034. var seriesLabel = plot.series[j].label;
  1035. var seriesColor = plot.series[j].color;
  1036. // format y value
  1037. if (plot.series[0]._yaxis.tickOptions.formatter) {
  1038. // using formatter function
  1039. seriesValue = plot.series[0]._yaxis.tickOptions.formatter('%s', seriesValue);
  1040. } else if (plot.series[0]._yaxis.tickOptions.formatString) {
  1041. // using format string
  1042. seriesValue = PMA_sprintf(plot.series[0]._yaxis.tickOptions.formatString, seriesValue);
  1043. }
  1044. tooltipHtml += '<br /><span style="color:' + seriesColor + '">' +
  1045. seriesLabel + ': ' + seriesValue + '</span>';
  1046. }
  1047. tooltipHtml += '</span></div>';
  1048. return tooltipHtml;
  1049. };
  1050. // set Tooltip for each series
  1051. for (i in settings.series) {
  1052. settings.series[i].highlighter = {
  1053. show: true,
  1054. tooltipContentEditor: tempTooltipContentEditor
  1055. };
  1056. }
  1057. chartObj.chart = $.jqplot('gridchart' + runtime.chartAI, series, settings);
  1058. // remove [0,0] after plotting
  1059. for (i in chartObj.chart.series) {
  1060. chartObj.chart.series[i].data.shift();
  1061. }
  1062. var $legend = $('<div />').css('padding', '0.5em');
  1063. for (i in chartObj.chart.series) {
  1064. $legend.append(
  1065. $('<div />').append(
  1066. $('<div>').css({
  1067. width: '1em',
  1068. height: '1em',
  1069. background: chartObj.chart.seriesColors[i]
  1070. }).addClass('floatleft')
  1071. ).append(
  1072. $('<div>').text(
  1073. chartObj.chart.series[i].label
  1074. ).addClass('floatleft')
  1075. ).append(
  1076. $('<div class="clearfloat">')
  1077. ).addClass('floatleft')
  1078. );
  1079. }
  1080. $('#gridchart' + runtime.chartAI)
  1081. .parent()
  1082. .append($legend);
  1083. if (initialize !== true) {
  1084. runtime.charts['c' + runtime.chartAI] = chartObj;
  1085. buildRequiredDataList();
  1086. }
  1087. // time span selection
  1088. $('#gridchart' + runtime.chartAI).on('jqplotMouseDown', function (ev, gridpos, datapos, neighbor, plot) {
  1089. drawTimeSpan = true;
  1090. selectionTimeDiff.push(datapos.xaxis);
  1091. if ($('#selection_box').length) {
  1092. $('#selection_box').remove();
  1093. }
  1094. 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;">');
  1095. $(document.body).append(selectionBox);
  1096. selectionStartX = ev.pageX;
  1097. selectionStartY = ev.pageY;
  1098. selectionBox
  1099. .attr({ id: 'selection_box' })
  1100. .css({
  1101. top: selectionStartY - gridpos.y,
  1102. left: selectionStartX
  1103. })
  1104. .fadeIn();
  1105. });
  1106. $('#gridchart' + runtime.chartAI).on('jqplotMouseUp', function (ev, gridpos, datapos, neighbor, plot) {
  1107. if (! drawTimeSpan || editMode) {
  1108. return;
  1109. }
  1110. selectionTimeDiff.push(datapos.xaxis);
  1111. if (selectionTimeDiff[1] <= selectionTimeDiff[0]) {
  1112. selectionTimeDiff = [];
  1113. return;
  1114. }
  1115. // get date from timestamp
  1116. var min = new Date(Math.ceil(selectionTimeDiff[0]));
  1117. var max = new Date(Math.ceil(selectionTimeDiff[1]));
  1118. PMA_getLogAnalyseDialog(min, max);
  1119. selectionTimeDiff = [];
  1120. drawTimeSpan = false;
  1121. });
  1122. $('#gridchart' + runtime.chartAI).on('jqplotMouseMove', function (ev, gridpos, datapos, neighbor, plot) {
  1123. if (! drawTimeSpan || editMode) {
  1124. return;
  1125. }
  1126. if (selectionStartX !== undefined) {
  1127. $('#selection_box')
  1128. .css({
  1129. width: Math.ceil(ev.pageX - selectionStartX)
  1130. })
  1131. .fadeIn();
  1132. }
  1133. });
  1134. $('#gridchart' + runtime.chartAI).on('jqplotMouseLeave', function (ev, gridpos, datapos, neighbor, plot) {
  1135. drawTimeSpan = false;
  1136. });
  1137. $(document.body).mouseup(function () {
  1138. if ($('#selection_box').length) {
  1139. $('#selection_box').remove();
  1140. }
  1141. });
  1142. // Edit, Print icon only in edit mode
  1143. $('#chartGrid').find('div svg').find('*[zIndex=20], *[zIndex=21], *[zIndex=19]').toggle(editMode);
  1144. runtime.chartAI++;
  1145. }
  1146. function PMA_getLogAnalyseDialog (min, max) {
  1147. var $logAnalyseDialog = $('#logAnalyseDialog');
  1148. var $dateStart = $logAnalyseDialog.find('input[name="dateStart"]');
  1149. var $dateEnd = $logAnalyseDialog.find('input[name="dateEnd"]');
  1150. $dateStart.prop('readonly', true);
  1151. $dateEnd.prop('readonly', true);
  1152. var dlgBtns = { };
  1153. dlgBtns[PMA_messages.strFromSlowLog] = function () {
  1154. loadLog('slow', min, max);
  1155. $(this).dialog('close');
  1156. };
  1157. dlgBtns[PMA_messages.strFromGeneralLog] = function () {
  1158. loadLog('general', min, max);
  1159. $(this).dialog('close');
  1160. };
  1161. $logAnalyseDialog.dialog({
  1162. width: 'auto',
  1163. height: 'auto',
  1164. buttons: dlgBtns
  1165. });
  1166. PMA_addDatepicker($dateStart, 'datetime', {
  1167. showMillisec: false,
  1168. showMicrosec: false,
  1169. timeFormat: 'HH:mm:ss'
  1170. });
  1171. PMA_addDatepicker($dateEnd, 'datetime', {
  1172. showMillisec: false,
  1173. showMicrosec: false,
  1174. timeFormat: 'HH:mm:ss'
  1175. });
  1176. $dateStart.datepicker('setDate', min);
  1177. $dateEnd.datepicker('setDate', max);
  1178. }
  1179. function loadLog (type, min, max) {
  1180. var dateStart = Date.parse($('#logAnalyseDialog').find('input[name="dateStart"]').datepicker('getDate')) || min;
  1181. var dateEnd = Date.parse($('#logAnalyseDialog').find('input[name="dateEnd"]').datepicker('getDate')) || max;
  1182. loadLogStatistics({
  1183. src: type,
  1184. start: dateStart,
  1185. end: dateEnd,
  1186. removeVariables: $('#removeVariables').prop('checked'),
  1187. limitTypes: $('#limitTypes').prop('checked')
  1188. });
  1189. }
  1190. /* Called in regular intervals, this function updates the values of each chart in the grid */
  1191. function refreshChartGrid () {
  1192. /* Send to server */
  1193. runtime.refreshRequest = $.post('server_status_monitor.php' + PMA_commonParams.get('common_query'), {
  1194. ajax_request: true,
  1195. chart_data: 1,
  1196. type: 'chartgrid',
  1197. requiredData: JSON.stringify(runtime.dataList),
  1198. server: PMA_commonParams.get('server')
  1199. }, function (data) {
  1200. var chartData;
  1201. if (typeof data !== 'undefined' && data.success === true) {
  1202. chartData = data.message;
  1203. } else {
  1204. return serverResponseError();
  1205. }
  1206. var value;
  1207. var i = 0;
  1208. var diff;
  1209. var total;
  1210. /* Update values in each graph */
  1211. $.each(runtime.charts, function (orderKey, elem) {
  1212. var key = elem.chartID;
  1213. // If newly added chart, we have no data for it yet
  1214. if (! chartData[key]) {
  1215. return;
  1216. }
  1217. // Draw all series
  1218. total = 0;
  1219. for (var j = 0; j < elem.nodes.length; j++) {
  1220. // Update x-axis
  1221. if (i === 0 && j === 0) {
  1222. if (oldChartData === null) {
  1223. diff = chartData.x - runtime.xmax;
  1224. } else {
  1225. diff = parseInt(chartData.x - oldChartData.x, 10);
  1226. }
  1227. runtime.xmin += diff;
  1228. runtime.xmax += diff;
  1229. }
  1230. // elem.chart.xAxis[0].setExtremes(runtime.xmin, runtime.xmax, false);
  1231. /* Calculate y value */
  1232. // If transform function given, use it
  1233. if (elem.nodes[j].transformFn) {
  1234. value = chartValueTransform(
  1235. elem.nodes[j].transformFn,
  1236. chartData[key][j],
  1237. // Check if first iteration (oldChartData==null), or if newly added chart oldChartData[key]==null
  1238. (
  1239. oldChartData === null ||
  1240. oldChartData[key] === null ||
  1241. oldChartData[key] === undefined ? null : oldChartData[key][j]
  1242. )
  1243. );
  1244. // Otherwise use original value and apply differential and divisor if given,
  1245. // in this case we have only one data point per series - located at chartData[key][j][0]
  1246. } else {
  1247. value = parseFloat(chartData[key][j][0].value);
  1248. if (elem.nodes[j].display === 'differential') {
  1249. if (oldChartData === null ||
  1250. oldChartData[key] === null ||
  1251. oldChartData[key] === undefined
  1252. ) {
  1253. continue;
  1254. }
  1255. value -= oldChartData[key][j][0].value;
  1256. }
  1257. if (elem.nodes[j].valueDivisor) {
  1258. value = value / elem.nodes[j].valueDivisor;
  1259. }
  1260. }
  1261. // Set y value, if defined
  1262. if (value !== undefined) {
  1263. elem.chart.series[j].data.push([chartData.x, value]);
  1264. if (value > elem.maxYLabel) {
  1265. elem.maxYLabel = value;
  1266. } else if (elem.maxYLabel === 0) {
  1267. elem.maxYLabel = 0.5;
  1268. }
  1269. // free old data point values and update maxYLabel
  1270. if (elem.chart.series[j].data.length > runtime.gridMaxPoints &&
  1271. elem.chart.series[j].data[0][0] < runtime.xmin
  1272. ) {
  1273. // check if the next freeable point is highest
  1274. if (elem.maxYLabel <= elem.chart.series[j].data[0][1]) {
  1275. elem.chart.series[j].data.splice(0, elem.chart.series[j].data.length - runtime.gridMaxPoints);
  1276. elem.maxYLabel = getMaxYLabel(elem.chart.series[j].data);
  1277. } else {
  1278. elem.chart.series[j].data.splice(0, elem.chart.series[j].data.length - runtime.gridMaxPoints);
  1279. }
  1280. }
  1281. if (elem.title === PMA_messages.strSystemMemory ||
  1282. elem.title === PMA_messages.strSystemSwap
  1283. ) {
  1284. total += value;
  1285. }
  1286. }
  1287. }
  1288. // update chart options
  1289. // keep ticks number/positioning consistent while refreshrate changes
  1290. var tickInterval = (runtime.xmax - runtime.xmin) / 5;
  1291. elem.chart.axes.xaxis.ticks = [(runtime.xmax - tickInterval * 4),
  1292. (runtime.xmax - tickInterval * 3), (runtime.xmax - tickInterval * 2),
  1293. (runtime.xmax - tickInterval), runtime.xmax];
  1294. if (elem.title !== PMA_messages.strSystemCPUUsage &&
  1295. elem.title !== PMA_messages.strQueryCacheEfficiency &&
  1296. elem.title !== PMA_messages.strSystemMemory &&
  1297. elem.title !== PMA_messages.strSystemSwap
  1298. ) {
  1299. elem.chart.axes.yaxis.max = Math.ceil(elem.maxYLabel * 1.1);
  1300. elem.chart.axes.yaxis.tickInterval = Math.ceil(elem.maxYLabel * 1.1 / 5);
  1301. } else if (elem.title === PMA_messages.strSystemMemory ||
  1302. elem.title === PMA_messages.strSystemSwap
  1303. ) {
  1304. elem.chart.axes.yaxis.max = Math.ceil(total * 1.1 / 100) * 100;
  1305. elem.chart.axes.yaxis.tickInterval = Math.ceil(total * 1.1 / 5);
  1306. }
  1307. i++;
  1308. if (runtime.redrawCharts) {
  1309. elem.chart.replot();
  1310. }
  1311. });
  1312. oldChartData = chartData;
  1313. runtime.refreshTimeout = setTimeout(refreshChartGrid, monitorSettings.gridRefresh);
  1314. });
  1315. }
  1316. /* Function to get highest plotted point's y label, to scale the chart,
  1317. * TODO: make jqplot's autoscale:true work here
  1318. */
  1319. function getMaxYLabel (dataValues) {
  1320. var maxY = dataValues[0][1];
  1321. $.each(dataValues, function (k, v) {
  1322. maxY = (v[1] > maxY) ? v[1] : maxY;
  1323. });
  1324. return maxY;
  1325. }
  1326. /* Function that supplies special value transform functions for chart values */
  1327. function chartValueTransform (name, cur, prev) {
  1328. switch (name) {
  1329. case 'cpu-linux':
  1330. if (prev === null) {
  1331. return undefined;
  1332. }
  1333. // cur and prev are datapoint arrays, but containing
  1334. // only 1 element for cpu-linux
  1335. cur = cur[0];
  1336. prev = prev[0];
  1337. var diff_total = cur.busy + cur.idle - (prev.busy + prev.idle);
  1338. var diff_idle = cur.idle - prev.idle;
  1339. return 100 * (diff_total - diff_idle) / diff_total;
  1340. // Query cache efficiency (%)
  1341. case 'qce':
  1342. if (prev === null) {
  1343. return undefined;
  1344. }
  1345. // cur[0].value is Qcache_hits, cur[1].value is Com_select
  1346. var diffQHits = cur[0].value - prev[0].value;
  1347. // No NaN please :-)
  1348. if (cur[1].value - prev[1].value === 0) {
  1349. return 0;
  1350. }
  1351. return diffQHits / (cur[1].value - prev[1].value + diffQHits) * 100;
  1352. // Query cache usage (%)
  1353. case 'qcu':
  1354. if (cur[1].value === 0) {
  1355. return 0;
  1356. }
  1357. // cur[0].value is Qcache_free_memory, cur[1].value is query_cache_size
  1358. return 100 - cur[0].value / cur[1].value * 100;
  1359. }
  1360. return undefined;
  1361. }
  1362. /* Build list of nodes that need to be retrieved from server.
  1363. * It creates something like a stripped down version of the runtime.charts object.
  1364. */
  1365. function buildRequiredDataList () {
  1366. runtime.dataList = {};
  1367. // Store an own id, because the property name is subject of reordering,
  1368. // thus destroying our mapping with runtime.charts <=> runtime.dataList
  1369. var chartID = 0;
  1370. $.each(runtime.charts, function (key, chart) {
  1371. runtime.dataList[chartID] = [];
  1372. for (var i = 0, l = chart.nodes.length; i < l; i++) {
  1373. runtime.dataList[chartID][i] = chart.nodes[i].dataPoints;
  1374. }
  1375. runtime.charts[key].chartID = chartID;
  1376. chartID++;
  1377. });
  1378. }
  1379. /* Loads the log table data, generates the table and handles the filters */
  1380. function loadLogStatistics (opts) {
  1381. var tableStr = '';
  1382. var logRequest = null;
  1383. if (! opts.removeVariables) {
  1384. opts.removeVariables = false;
  1385. }
  1386. if (! opts.limitTypes) {
  1387. opts.limitTypes = false;
  1388. }
  1389. $('#emptyDialog').dialog({ title: PMA_messages.strAnalysingLogsTitle });
  1390. $('#emptyDialog').html(PMA_messages.strAnalysingLogs +
  1391. ' <img class="ajaxIcon" src="' + pmaThemeImage +
  1392. 'ajax_clock_small.gif" alt="">');
  1393. var dlgBtns = {};
  1394. dlgBtns[PMA_messages.strCancelRequest] = function () {
  1395. if (logRequest !== null) {
  1396. logRequest.abort();
  1397. }
  1398. $(this).dialog('close');
  1399. };
  1400. $('#emptyDialog').dialog({
  1401. width: 'auto',
  1402. height: 'auto',
  1403. buttons: dlgBtns
  1404. });
  1405. logRequest = $.post(
  1406. 'server_status_monitor.php' + PMA_commonParams.get('common_query'),
  1407. {
  1408. ajax_request: true,
  1409. log_data: 1,
  1410. type: opts.src,
  1411. time_start: Math.round(opts.start / 1000),
  1412. time_end: Math.round(opts.end / 1000),
  1413. removeVariables: opts.removeVariables,
  1414. limitTypes: opts.limitTypes
  1415. },
  1416. function (data) {
  1417. var logData;
  1418. var dlgBtns = {};
  1419. if (typeof data !== 'undefined' && data.success === true) {
  1420. logData = data.message;
  1421. } else {
  1422. return serverResponseError();
  1423. }
  1424. if (logData.rows.length === 0) {
  1425. $('#emptyDialog').dialog({ title: PMA_messages.strNoDataFoundTitle });
  1426. $('#emptyDialog').html('<p>' + PMA_messages.strNoDataFound + '</p>');
  1427. dlgBtns[PMA_messages.strClose] = function () {
  1428. $(this).dialog('close');
  1429. };
  1430. $('#emptyDialog').dialog('option', 'buttons', dlgBtns);
  1431. return;
  1432. }
  1433. runtime.logDataCols = buildLogTable(logData, opts.removeVariables);
  1434. /* Show some stats in the dialog */
  1435. $('#emptyDialog').dialog({ title: PMA_messages.strLoadingLogs });
  1436. $('#emptyDialog').html('<p>' + PMA_messages.strLogDataLoaded + '</p>');
  1437. $.each(logData.sum, function (key, value) {
  1438. key = key.charAt(0).toUpperCase() + key.slice(1).toLowerCase();
  1439. if (key === 'Total') {
  1440. key = '<b>' + key + '</b>';
  1441. }
  1442. $('#emptyDialog').append(key + ': ' + value + '<br/>');
  1443. });
  1444. /* Add filter options if more than a bunch of rows there to filter */
  1445. if (logData.numRows > 12) {
  1446. $('#logTable').prepend(
  1447. '<fieldset id="logDataFilter">' +
  1448. ' <legend>' + PMA_messages.strFiltersForLogTable + '</legend>' +
  1449. ' <div class="formelement">' +
  1450. ' <label for="filterQueryText">' + PMA_messages.strFilterByWordRegexp + '</label>' +
  1451. ' <input name="filterQueryText" type="text" id="filterQueryText" style="vertical-align: baseline;" />' +
  1452. ' </div>' +
  1453. ((logData.numRows > 250) ? ' <div class="formelement"><button name="startFilterQueryText" id="startFilterQueryText">' + PMA_messages.strFilter + '</button></div>' : '') +
  1454. ' <div class="formelement">' +
  1455. ' <input type="checkbox" id="noWHEREData" name="noWHEREData" value="1" /> ' +
  1456. ' <label for="noWHEREData"> ' + PMA_messages.strIgnoreWhereAndGroup + '</label>' +
  1457. ' </div' +
  1458. '</fieldset>'
  1459. );
  1460. $('#noWHEREData').change(function () {
  1461. filterQueries(true);
  1462. });
  1463. if (logData.numRows > 250) {
  1464. $('#startFilterQueryText').click(filterQueries);
  1465. } else {
  1466. $('#filterQueryText').keyup(filterQueries);
  1467. }
  1468. }
  1469. dlgBtns[PMA_messages.strJumpToTable] = function () {
  1470. $(this).dialog('close');
  1471. $(document).scrollTop($('#logTable').offset().top);
  1472. };
  1473. $('#emptyDialog').dialog('option', 'buttons', dlgBtns);
  1474. }
  1475. );
  1476. /* Handles the actions performed when the user uses any of the
  1477. * log table filters which are the filter by name and grouping
  1478. * with ignoring data in WHERE clauses
  1479. *
  1480. * @param boolean Should be true when the users enabled or disabled
  1481. * to group queries ignoring data in WHERE clauses
  1482. */
  1483. function filterQueries (varFilterChange) {
  1484. var cell;
  1485. var textFilter;
  1486. var val = $('#filterQueryText').val();
  1487. if (val.length === 0) {
  1488. textFilter = null;
  1489. } else {
  1490. try {
  1491. textFilter = new RegExp(val, 'i');
  1492. $('#filterQueryText').removeClass('error');
  1493. } catch (e) {
  1494. if (e instanceof SyntaxError) {
  1495. $('#filterQueryText').addClass('error');
  1496. textFilter = null;
  1497. }
  1498. }
  1499. }
  1500. var rowSum = 0;
  1501. var totalSum = 0;
  1502. var i = 0;
  1503. var q;
  1504. var noVars = $('#noWHEREData').prop('checked');
  1505. var equalsFilter = /([^=]+)=(\d+|((\'|"|).*?[^\\])\4((\s+)|$))/gi;
  1506. var functionFilter = /([a-z0-9_]+)\(.+?\)/gi;
  1507. var filteredQueries = {};
  1508. var filteredQueriesLines = {};
  1509. var hide = false;
  1510. var rowData;
  1511. var queryColumnName = runtime.logDataCols[runtime.logDataCols.length - 2];
  1512. var sumColumnName = runtime.logDataCols[runtime.logDataCols.length - 1];
  1513. var isSlowLog = opts.src === 'slow';
  1514. var columnSums = {};
  1515. // For the slow log we have to count many columns (query_time, lock_time, rows_examined, rows_sent, etc.)
  1516. var countRow = function (query, row) {
  1517. var cells = row.match(/<td>(.*?)<\/td>/gi);
  1518. if (!columnSums[query]) {
  1519. columnSums[query] = [0, 0, 0, 0];
  1520. }
  1521. // lock_time and query_time and displayed in timespan format
  1522. columnSums[query][0] += timeToSec(cells[2].replace(/(<td>|<\/td>)/gi, ''));
  1523. columnSums[query][1] += timeToSec(cells[3].replace(/(<td>|<\/td>)/gi, ''));
  1524. // rows_examind and rows_sent are just numbers
  1525. columnSums[query][2] += parseInt(cells[4].replace(/(<td>|<\/td>)/gi, ''), 10);
  1526. columnSums[query][3] += parseInt(cells[5].replace(/(<td>|<\/td>)/gi, ''), 10);
  1527. };
  1528. // We just assume the sql text is always in the second last column, and that the total count is right of it
  1529. $('#logTable').find('table tbody tr td:nth-child(' + (runtime.logDataCols.length - 1) + ')').each(function () {
  1530. var $t = $(this);
  1531. // If query is a SELECT and user enabled or disabled to group
  1532. // queries ignoring data in where statements, we
  1533. // need to re-calculate the sums of each row
  1534. if (varFilterChange && $t.html().match(/^SELECT/i)) {
  1535. if (noVars) {
  1536. // Group on => Sum up identical columns, and hide all but 1
  1537. q = $t.text().replace(equalsFilter, '$1=...$6').trim();
  1538. q = q.replace(functionFilter, ' $1(...)');
  1539. // Js does not specify a limit on property name length,
  1540. // so we can abuse it as index :-)
  1541. if (filteredQueries[q]) {
  1542. filteredQueries[q] += parseInt($t.next().text(), 10);
  1543. totalSum += parseInt($t.next().text(), 10);
  1544. hide = true;
  1545. } else {
  1546. filteredQueries[q] = parseInt($t.next().text(), 10);
  1547. filteredQueriesLines[q] = i;
  1548. $t.text(q);
  1549. }
  1550. if (isSlowLog) {
  1551. countRow(q, $t.parent().html());
  1552. }
  1553. } else {
  1554. // Group off: Restore original columns
  1555. rowData = $t.parent().data('query');
  1556. // Restore SQL text
  1557. $t.text(rowData[queryColumnName]);
  1558. // Restore total count
  1559. $t.next().text(rowData[sumColumnName]);
  1560. // Restore slow log columns
  1561. if (isSlowLog) {
  1562. $t.parent().children('td:nth-child(3)').text(rowData.query_time);
  1563. $t.parent().children('td:nth-child(4)').text(rowData.lock_time);
  1564. $t.parent().children('td:nth-child(5)').text(rowData.rows_sent);
  1565. $t.parent().children('td:nth-child(6)').text(rowData.rows_examined);
  1566. }
  1567. }
  1568. }
  1569. // If not required to be hidden, do we need
  1570. // to hide because of a not matching text filter?
  1571. if (! hide && (textFilter !== null && ! textFilter.exec($t.text()))) {
  1572. hide = true;
  1573. }
  1574. // Now display or hide this column
  1575. if (hide) {
  1576. $t.parent().css('display', 'none');
  1577. } else {
  1578. totalSum += parseInt($t.next().text(), 10);
  1579. rowSum++;
  1580. $t.parent().css('display', '');
  1581. }
  1582. hide = false;
  1583. i++;
  1584. });
  1585. // We finished summarizing counts => Update count values of all grouped entries
  1586. if (varFilterChange) {
  1587. if (noVars) {
  1588. var numCol;
  1589. var row;
  1590. var $table = $('#logTable').find('table tbody');
  1591. $.each(filteredQueriesLines, function (key, value) {
  1592. if (filteredQueries[key] <= 1) {
  1593. return;
  1594. }
  1595. row = $table.children('tr:nth-child(' + (value + 1) + ')');
  1596. numCol = row.children(':nth-child(' + (runtime.logDataCols.length) + ')');
  1597. numCol.text(filteredQueries[key]);
  1598. if (isSlowLog) {
  1599. row.children('td:nth-child(3)').text(secToTime(columnSums[key][0]));
  1600. row.children('td:nth-child(4)').text(secToTime(columnSums[key][1]));
  1601. row.children('td:nth-child(5)').text(columnSums[key][2]);
  1602. row.children('td:nth-child(6)').text(columnSums[key][3]);
  1603. }
  1604. });
  1605. }
  1606. $('#logTable').find('table').trigger('update');
  1607. setTimeout(function () {
  1608. $('#logTable').find('table').trigger('sorton', [[[runtime.logDataCols.length - 1, 1]]]);
  1609. }, 0);
  1610. }
  1611. // Display some stats at the bottom of the table
  1612. $('#logTable').find('table tfoot tr')
  1613. .html('<th colspan="' + (runtime.logDataCols.length - 1) + '">' +
  1614. PMA_messages.strSumRows + ' ' + rowSum + '<span class="floatright">' +
  1615. PMA_messages.strTotal + '</span></th><th class="right">' + totalSum + '</th>');
  1616. }
  1617. }
  1618. /* Turns a timespan (12:12:12) into a number */
  1619. function timeToSec (timeStr) {
  1620. var time = timeStr.split(':');
  1621. return (parseInt(time[0], 10) * 3600) + (parseInt(time[1], 10) * 60) + parseInt(time[2], 10);
  1622. }
  1623. /* Turns a number into a timespan (100 into 00:01:40) */
  1624. function secToTime (timeInt) {
  1625. var hours = Math.floor(timeInt / 3600);
  1626. timeInt -= hours * 3600;
  1627. var minutes = Math.floor(timeInt / 60);
  1628. timeInt -= minutes * 60;
  1629. if (hours < 10) {
  1630. hours = '0' + hours;
  1631. }
  1632. if (minutes < 10) {
  1633. minutes = '0' + minutes;
  1634. }
  1635. if (timeInt < 10) {
  1636. timeInt = '0' + timeInt;
  1637. }
  1638. return hours + ':' + minutes + ':' + timeInt;
  1639. }
  1640. /* Constructs the log table out of the retrieved server data */
  1641. function buildLogTable (data, groupInserts) {
  1642. var rows = data.rows;
  1643. var cols = [];
  1644. var $table = $('<table class="sortable"></table>');
  1645. var $tBody;
  1646. var $tRow;
  1647. var $tCell;
  1648. $('#logTable').html($table);
  1649. var tempPushKey = function (key, value) {
  1650. cols.push(key);
  1651. };
  1652. var formatValue = function (name, value) {
  1653. if (name === 'user_host') {
  1654. return value.replace(/(\[.*?\])+/g, '');
  1655. }
  1656. return escapeHtml(value);
  1657. };
  1658. for (var i = 0, l = rows.length; i < l; i++) {
  1659. if (i === 0) {
  1660. $.each(rows[0], tempPushKey);
  1661. $table.append('<thead>' +
  1662. '<tr><th class="nowrap">' + cols.join('</th><th class="nowrap">') + '</th></tr>' +
  1663. '</thead>'
  1664. );
  1665. $table.append($tBody = $('<tbody></tbody>'));
  1666. }
  1667. $tBody.append($tRow = $('<tr class="noclick"></tr>'));
  1668. var cl = '';
  1669. for (var j = 0, ll = cols.length; j < ll; j++) {
  1670. // Assuming the query column is the second last
  1671. if (j === cols.length - 2 && rows[i][cols[j]].match(/^SELECT/i)) {
  1672. $tRow.append($tCell = $('<td class="linkElem">' + formatValue(cols[j], rows[i][cols[j]]) + '</td>'));
  1673. $tCell.click(openQueryAnalyzer);
  1674. } else {
  1675. $tRow.append('<td>' + formatValue(cols[j], rows[i][cols[j]]) + '</td>');
  1676. }
  1677. $tRow.data('query', rows[i]);
  1678. }
  1679. }
  1680. $table.append('<tfoot>' +
  1681. '<tr><th colspan="' + (cols.length - 1) + '">' + PMA_messages.strSumRows +
  1682. ' ' + data.numRows + '<span class="floatright">' + PMA_messages.strTotal +
  1683. '</span></th><th class="right">' + data.sum.TOTAL + '</th></tr></tfoot>');
  1684. // Append a tooltip to the count column, if there exist one
  1685. if ($('#logTable').find('tr:first th:last').text().indexOf('#') > -1) {
  1686. $('#logTable').find('tr:first th:last').append('&nbsp;' + PMA_getImage('b_help', '', { 'class': 'qroupedQueryInfoIcon' }));
  1687. var tooltipContent = PMA_messages.strCountColumnExplanation;
  1688. if (groupInserts) {
  1689. tooltipContent += '<p>' + PMA_messages.strMoreCountColumnExplanation + '</p>';
  1690. }
  1691. PMA_tooltip(
  1692. $('img.qroupedQueryInfoIcon'),
  1693. 'img',
  1694. tooltipContent
  1695. );
  1696. }
  1697. $('#logTable').find('table').tablesorter({
  1698. sortList: [[cols.length - 1, 1]],
  1699. widgets: ['fast-zebra']
  1700. });
  1701. $('#logTable').find('table thead th')
  1702. .append('<div class="sorticon"></div>');
  1703. return cols;
  1704. }
  1705. /* Opens the query analyzer dialog */
  1706. function openQueryAnalyzer () {
  1707. var rowData = $(this).parent().data('query');
  1708. var query = rowData.argument || rowData.sql_text;
  1709. if (codemirror_editor) {
  1710. // TODO: somehow PMA_SQLPrettyPrint messes up the query, needs be fixed
  1711. // query = PMA_SQLPrettyPrint(query);
  1712. codemirror_editor.setValue(query);
  1713. // Codemirror is bugged, it doesn't refresh properly sometimes.
  1714. // Following lines seem to fix that
  1715. setTimeout(function () {
  1716. codemirror_editor.refresh();
  1717. }, 50);
  1718. } else {
  1719. $('#sqlquery').val(query);
  1720. }
  1721. var profilingChart = null;
  1722. var dlgBtns = {};
  1723. dlgBtns[PMA_messages.strAnalyzeQuery] = function () {
  1724. loadQueryAnalysis(rowData);
  1725. };
  1726. dlgBtns[PMA_messages.strClose] = function () {
  1727. $(this).dialog('close');
  1728. };
  1729. $('#queryAnalyzerDialog').dialog({
  1730. width: 'auto',
  1731. height: 'auto',
  1732. resizable: false,
  1733. buttons: dlgBtns,
  1734. close: function () {
  1735. if (profilingChart !== null) {
  1736. profilingChart.destroy();
  1737. }
  1738. $('#queryAnalyzerDialog').find('div.placeHolder').html('');
  1739. if (codemirror_editor) {
  1740. codemirror_editor.setValue('');
  1741. } else {
  1742. $('#sqlquery').val('');
  1743. }
  1744. }
  1745. });
  1746. }
  1747. /* Loads and displays the analyzed query data */
  1748. function loadQueryAnalysis (rowData) {
  1749. var db = rowData.db || '';
  1750. $('#queryAnalyzerDialog').find('div.placeHolder').html(
  1751. PMA_messages.strAnalyzing + ' <img class="ajaxIcon" src="' +
  1752. pmaThemeImage + 'ajax_clock_small.gif" alt="">');
  1753. $.post('server_status_monitor.php' + PMA_commonParams.get('common_query'), {
  1754. ajax_request: true,
  1755. query_analyzer: true,
  1756. query: codemirror_editor ? codemirror_editor.getValue() : $('#sqlquery').val(),
  1757. database: db,
  1758. server: PMA_commonParams.get('server')
  1759. }, function (data) {
  1760. var i;
  1761. var l;
  1762. if (typeof data !== 'undefined' && data.success === true) {
  1763. data = data.message;
  1764. }
  1765. if (data.error) {
  1766. if (data.error.indexOf('1146') !== -1 || data.error.indexOf('1046') !== -1) {
  1767. data.error = PMA_messages.strServerLogError;
  1768. }
  1769. $('#queryAnalyzerDialog').find('div.placeHolder').html('<div class="error">' + data.error + '</div>');
  1770. return;
  1771. }
  1772. var totalTime = 0;
  1773. // Float sux, I'll use table :(
  1774. $('#queryAnalyzerDialog').find('div.placeHolder')
  1775. .html('<table width="100%" border="0"><tr><td class="explain"></td><td class="chart"></td></tr></table>');
  1776. var explain = '<b>' + PMA_messages.strExplainOutput + '</b> ' + $('#explain_docu').html();
  1777. if (data.explain.length > 1) {
  1778. explain += ' (';
  1779. for (i = 0; i < data.explain.length; i++) {
  1780. if (i > 0) {
  1781. explain += ', ';
  1782. }
  1783. explain += '<a href="#showExplain-' + i + '">' + i + '</a>';
  1784. }
  1785. explain += ')';
  1786. }
  1787. explain += '<p></p>';
  1788. var tempExplain = function (key, value) {
  1789. value = (value === null) ? 'null' : escapeHtml(value);
  1790. if (key === 'type' && value.toLowerCase() === 'all') {
  1791. value = '<span class="attention">' + value + '</span>';
  1792. }
  1793. if (key === 'Extra') {
  1794. value = value.replace(/(using (temporary|filesort))/gi, '<span class="attention">$1</span>');
  1795. }
  1796. explain += key + ': ' + value + '<br />';
  1797. };
  1798. for (i = 0, l = data.explain.length; i < l; i++) {
  1799. explain += '<div class="explain-' + i + '"' + (i > 0 ? 'style="display:none;"' : '') + '>';
  1800. $.each(data.explain[i], tempExplain);
  1801. explain += '</div>';
  1802. }
  1803. explain += '<p><b>' + PMA_messages.strAffectedRows + '</b> ' + data.affectedRows;
  1804. $('#queryAnalyzerDialog').find('div.placeHolder td.explain').append(explain);
  1805. $('#queryAnalyzerDialog').find('div.placeHolder a[href*="#showExplain"]').click(function () {
  1806. var id = $(this).attr('href').split('-')[1];
  1807. $(this).parent().find('div[class*="explain"]').hide();
  1808. $(this).parent().find('div[class*="explain-' + id + '"]').show();
  1809. });
  1810. if (data.profiling) {
  1811. var chartData = [];
  1812. var numberTable = '<table class="queryNums"><thead><tr><th>' + PMA_messages.strStatus + '</th><th>' + PMA_messages.strTime + '</th></tr></thead><tbody>';
  1813. var duration;
  1814. var otherTime = 0;
  1815. for (i = 0, l = data.profiling.length; i < l; i++) {
  1816. duration = parseFloat(data.profiling[i].duration);
  1817. totalTime += duration;
  1818. numberTable += '<tr><td>' + data.profiling[i].state + ' </td><td> ' + PMA_prettyProfilingNum(duration, 2) + '</td></tr>';
  1819. }
  1820. // Only put those values in the pie which are > 2%
  1821. for (i = 0, l = data.profiling.length; i < l; i++) {
  1822. duration = parseFloat(data.profiling[i].duration);
  1823. if (duration / totalTime > 0.02) {
  1824. chartData.push([PMA_prettyProfilingNum(duration, 2) + ' ' + data.profiling[i].state, duration]);
  1825. } else {
  1826. otherTime += duration;
  1827. }
  1828. }
  1829. if (otherTime > 0) {
  1830. chartData.push([PMA_prettyProfilingNum(otherTime, 2) + ' ' + PMA_messages.strOther, otherTime]);
  1831. }
  1832. numberTable += '<tr><td><b>' + PMA_messages.strTotalTime + '</b></td><td>' + PMA_prettyProfilingNum(totalTime, 2) + '</td></tr>';
  1833. numberTable += '</tbody></table>';
  1834. $('#queryAnalyzerDialog').find('div.placeHolder td.chart').append(
  1835. '<b>' + PMA_messages.strProfilingResults + ' ' + $('#profiling_docu').html() + '</b> ' +
  1836. '(<a href="#showNums">' + PMA_messages.strTable + '</a>, <a href="#showChart">' + PMA_messages.strChart + '</a>)<br/>' +
  1837. numberTable + ' <div id="queryProfiling"></div>');
  1838. $('#queryAnalyzerDialog').find('div.placeHolder a[href="#showNums"]').click(function () {
  1839. $('#queryAnalyzerDialog').find('#queryProfiling').hide();
  1840. $('#queryAnalyzerDialog').find('table.queryNums').show();
  1841. return false;
  1842. });
  1843. $('#queryAnalyzerDialog').find('div.placeHolder a[href="#showChart"]').click(function () {
  1844. $('#queryAnalyzerDialog').find('#queryProfiling').show();
  1845. $('#queryAnalyzerDialog').find('table.queryNums').hide();
  1846. return false;
  1847. });
  1848. profilingChart = PMA_createProfilingChart(
  1849. 'queryProfiling',
  1850. chartData
  1851. );
  1852. // $('#queryProfiling').resizable();
  1853. }
  1854. });
  1855. }
  1856. /* Saves the monitor to localstorage */
  1857. function saveMonitor () {
  1858. var gridCopy = {};
  1859. $.each(runtime.charts, function (key, elem) {
  1860. gridCopy[key] = {};
  1861. gridCopy[key].nodes = elem.nodes;
  1862. gridCopy[key].settings = elem.settings;
  1863. gridCopy[key].title = elem.title;
  1864. gridCopy[key].series = elem.series;
  1865. gridCopy[key].maxYLabel = elem.maxYLabel;
  1866. });
  1867. if (isStorageSupported('localStorage')) {
  1868. window.localStorage.monitorCharts = JSON.stringify(gridCopy);
  1869. window.localStorage.monitorSettings = JSON.stringify(monitorSettings);
  1870. window.localStorage.monitorVersion = monitorProtocolVersion;
  1871. }
  1872. $('a[href="#clearMonitorConfig"]').show();
  1873. }
  1874. });
  1875. // Run the monitor once loaded
  1876. AJAX.registerOnload('server_status_monitor.js', function () {
  1877. $('a[href="#pauseCharts"]').trigger('click');
  1878. });
  1879. function serverResponseError () {
  1880. var btns = {};
  1881. btns[PMA_messages.strReloadPage] = function () {
  1882. window.location.reload();
  1883. };
  1884. $('#emptyDialog').dialog({ title: PMA_messages.strRefreshFailed });
  1885. $('#emptyDialog').html(
  1886. PMA_getImage('s_attention') +
  1887. PMA_messages.strInvalidResponseExplanation
  1888. );
  1889. $('#emptyDialog').dialog({ buttons: btns });
  1890. }
  1891. /* Destroys all monitor related resources */
  1892. function destroyGrid () {
  1893. if (runtime.charts) {
  1894. $.each(runtime.charts, function (key, value) {
  1895. try {
  1896. value.chart.destroy();
  1897. } catch (err) {}
  1898. });
  1899. }
  1900. try {
  1901. runtime.refreshRequest.abort();
  1902. } catch (err) {}
  1903. try {
  1904. clearTimeout(runtime.refreshTimeout);
  1905. } catch (err) {}
  1906. $('#chartGrid').html('');
  1907. runtime.charts = null;
  1908. runtime.chartAI = 0;
  1909. monitorSettings = null; // TODO:this not global variable
  1910. }