on-demand release 4.5dev+
[moodle.git] / lib / adodb / adodb-perf.inc.php
blob91610536545953ae5dcd0e1b3cfdd5d803e73c92
1 <?php
2 /**
3 * performance monitoring and tuning.
5 * This file is part of ADOdb, a Database Abstraction Layer library for PHP.
7 * @package ADOdb
8 * @link https://adodb.org Project's web site and documentation
9 * @link https://github.com/ADOdb/ADOdb Source code and issue tracker
11 * The ADOdb Library is dual-licensed, released under both the BSD 3-Clause
12 * and the GNU Lesser General Public Licence (LGPL) v2.1 or, at your option,
13 * any later version. This means you can use it in proprietary products.
14 * See the LICENSE.md file distributed with this source code for details.
15 * @license BSD-3-Clause
16 * @license LGPL-2.1-or-later
18 * @copyright 2000-2013 John Lim
19 * @copyright 2014 Damien Regad, Mark Newnham and the ADOdb community
22 if (!defined('ADODB_DIR')) include_once(dirname(__FILE__).'/adodb.inc.php');
23 include_once(ADODB_DIR.'/tohtml.inc.php');
25 define( 'ADODB_OPT_HIGH', 2);
26 define( 'ADODB_OPT_LOW', 1);
28 global $ADODB_PERF_MIN;
29 $ADODB_PERF_MIN = 0.05; // log only if >= minimum number of secs to run
32 // returns in K the memory of current process, or 0 if not known
33 function adodb_getmem()
35 if (function_exists('memory_get_usage'))
36 return (integer) ((memory_get_usage()+512)/1024);
38 $pid = getmypid();
40 if ( strncmp(strtoupper(PHP_OS),'WIN',3)==0) {
41 $output = array();
43 exec('tasklist /FI "PID eq ' . $pid. '" /FO LIST', $output);
44 return substr($output[5], strpos($output[5], ':') + 1);
47 /* Hopefully UNIX */
48 exec("ps --pid $pid --no-headers -o%mem,size", $output);
49 if (sizeof($output) == 0) return 0;
51 $memarr = explode(' ',$output[0]);
52 if (sizeof($memarr)>=2) return (integer) $memarr[1];
54 return 0;
57 // avoids localization problems where , is used instead of .
58 function adodb_round($n,$prec)
60 return number_format($n, $prec, '.', '');
63 /* obsolete: return microtime value as a float. Retained for backward compat */
64 function adodb_microtime()
66 return microtime(true);
69 /* sql code timing */
70 function adodb_log_sql(&$connx,$sql,$inputarr)
72 $perf_table = adodb_perf::table();
73 $connx->fnExecute = false;
74 $a0 = microtime(true);
75 $rs = $connx->Execute($sql,$inputarr);
76 $a1 = microtime(true);
78 if (!empty($connx->_logsql) && (empty($connx->_logsqlErrors) || !$rs)) {
79 global $ADODB_LOG_CONN;
81 if (!empty($ADODB_LOG_CONN)) {
82 $conn = $ADODB_LOG_CONN;
83 if ($conn->databaseType != $connx->databaseType)
84 $prefix = '/*dbx='.$connx->databaseType .'*/ ';
85 else
86 $prefix = '';
87 } else {
88 $conn = $connx;
89 $prefix = '';
92 $conn->_logsql = false; // disable logsql error simulation
93 $dbT = $conn->databaseType;
95 $time = $a1 - $a0;
97 if (!$rs) {
98 $errM = $connx->ErrorMsg();
99 $errN = $connx->ErrorNo();
100 $conn->lastInsID = 0;
101 $tracer = substr('ERROR: '.htmlspecialchars($errM),0,250);
102 } else {
103 $tracer = '';
104 $errM = '';
105 $errN = 0;
106 $dbg = $conn->debug;
107 $conn->debug = false;
108 if (!is_object($rs) || $rs->dataProvider == 'empty')
109 $conn->_affected = $conn->affected_rows(true);
110 $conn->lastInsID = @$conn->Insert_ID();
111 $conn->debug = $dbg;
113 if (isset($_SERVER['HTTP_HOST'])) {
114 $tracer .= '<br>'.$_SERVER['HTTP_HOST'];
115 if (isset($_SERVER['PHP_SELF'])) $tracer .= htmlspecialchars($_SERVER['PHP_SELF']);
116 } else
117 if (isset($_SERVER['PHP_SELF'])) $tracer .= '<br>'.htmlspecialchars($_SERVER['PHP_SELF']);
118 //$tracer .= (string) adodb_backtrace(false);
120 $tracer = (string) substr($tracer,0,500);
122 if (is_array($inputarr)) {
123 if (is_array(reset($inputarr))) $params = 'Array sizeof='.sizeof($inputarr);
124 else {
125 // Quote string parameters so we can see them in the
126 // performance stats. This helps spot disabled indexes.
127 $xar_params = $inputarr;
128 foreach ($xar_params as $xar_param_key => $xar_param) {
129 if (gettype($xar_param) == 'string')
130 $xar_params[$xar_param_key] = '"' . $xar_param . '"';
132 $params = implode(', ', $xar_params);
133 if (strlen($params) >= 3000) $params = substr($params, 0, 3000);
135 } else {
136 $params = '';
139 if (is_array($sql)) $sql = $sql[0];
140 if ($prefix) $sql = $prefix.$sql;
141 $arr = array('b'=>strlen($sql).'.'.crc32($sql),
142 'c'=>substr($sql,0,3900), 'd'=>$params,'e'=>$tracer,'f'=>adodb_round($time,6));
143 //var_dump($arr);
144 $saved = $conn->debug;
145 $conn->debug = 0;
147 $d = $conn->sysTimeStamp;
148 if (empty($d)) $d = date("'Y-m-d H:i:s'");
149 if ($conn->dataProvider == 'oci8' && $dbT != 'oci8po') {
150 $isql = "insert into $perf_table values($d,:b,:c,:d,:e,:f)";
151 } else if ($dbT == 'mssqlnative' || $dbT == 'odbc_mssql' || $dbT == 'informix' || strncmp($dbT,'odbtp',4)==0) {
152 $timer = $arr['f'];
153 if ($dbT == 'informix') $sql2 = substr($sql2,0,230);
155 $sql1 = $conn->qstr($arr['b']);
156 $sql2 = $conn->qstr($arr['c']);
157 $params = $conn->qstr($arr['d']);
158 $tracer = $conn->qstr($arr['e']);
160 $isql = "insert into $perf_table (created,sql0,sql1,params,tracer,timer) values($d,$sql1,$sql2,$params,$tracer,$timer)";
161 if ($dbT == 'informix') $isql = str_replace(chr(10),' ',$isql);
162 $arr = false;
163 } else {
164 if ($dbT == 'db2') $arr['f'] = (float) $arr['f'];
165 $isql = "insert into $perf_table (created,sql0,sql1,params,tracer,timer) values( $d,?,?,?,?,?)";
168 global $ADODB_PERF_MIN;
169 if ($errN != 0 || $time >= $ADODB_PERF_MIN) {
170 if($conn instanceof ADODB_mysqli && $conn->_queryID) {
171 mysqli_free_result($conn->_queryID);
173 $ok = $conn->Execute($isql,$arr);
174 if($conn instanceof ADODB_mysqli && $conn->_queryID){
175 mysqli_free_result($conn->_queryID);
177 } else
178 $ok = true;
180 $conn->debug = $saved;
182 if ($ok) {
183 $conn->_logsql = true;
184 } else {
185 $err2 = $conn->ErrorMsg();
186 $conn->_logsql = true; // enable logsql error simulation
187 $perf = NewPerfMonitor($conn);
188 if ($perf) {
189 if ($perf->CreateLogTable()) $ok = $conn->Execute($isql,$arr);
190 } else {
191 $ok = $conn->Execute("create table $perf_table (
192 created varchar(50),
193 sql0 varchar(250),
194 sql1 varchar(4000),
195 params varchar(3000),
196 tracer varchar(500),
197 timer decimal(16,6))");
199 if (!$ok) {
200 ADOConnection::outp( "<p><b>LOGSQL Insert Failed</b>: $isql<br>$err2</p>");
201 $conn->_logsql = false;
204 $connx->_errorMsg = $errM;
205 $connx->_errorCode = $errN;
207 $connx->fnExecute = 'adodb_log_sql';
208 return $rs;
213 The settings data structure is an associative array that database parameter per element.
215 Each database parameter element in the array is itself an array consisting of:
217 0: category code, used to group related db parameters
218 1: either
219 a. sql string to retrieve value, eg. "select value from v\$parameter where name='db_block_size'",
220 b. array holding sql string and field to look for, e.g. array('show variables','table_cache'),
221 c. a string prefixed by =, then a PHP method of the class is invoked,
222 e.g. to invoke $this->GetIndexValue(), set this array element to '=GetIndexValue',
223 2: description of the database parameter
226 class adodb_perf {
227 var $conn;
228 var $color = '#F0F0F0';
229 var $table = '<table border=1 bgcolor=white>';
230 var $titles = '<tr><td><b>Parameter</b></td><td><b>Value</b></td><td><b>Description</b></td></tr>';
231 var $warnRatio = 90;
232 var $tablesSQL = false;
233 var $cliFormat = "%32s => %s \r\n";
234 var $sql1 = 'sql1'; // used for casting sql1 to text for mssql
235 var $explain = true;
236 var $helpurl = '<a href="https://adodb.org/dokuwiki/doku.php?id=v5:performance:logsql">LogSQL help</a>';
237 var $createTableSQL = false;
238 var $maxLength = 2000;
240 /** @var array Settings data. */
241 var $settings = [];
243 // Sets the tablename to be used
244 static function table($newtable = false)
246 static $_table;
248 if (!empty($newtable)) $_table = $newtable;
249 if (empty($_table)) $_table = 'adodb_logsql';
250 return $_table;
253 // returns array with info to calculate CPU Load
254 function _CPULoad()
258 cpu 524152 2662 2515228 336057010
259 cpu0 264339 1408 1257951 168025827
260 cpu1 259813 1254 1257277 168031181
261 page 622307 25475680
262 swap 24 1891
263 intr 890153570 868093576 6 0 4 4 0 6 1 2 0 0 0 124 0 8098760 2 13961053 0 0 0 0 0 0 0 0 0 0 0 0 0 16 16 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
264 disk_io: (3,0):(3144904,54369,610378,3090535,50936192) (3,1):(3630212,54097,633016,3576115,50951320)
265 ctxt 66155838
266 btime 1062315585
267 processes 69293
270 // Algorithm is taken from
271 // http://social.technet.microsoft.com/Forums/en-US/winservergen/thread/414b0e1b-499c-411e-8a02-6a12e339c0f1/
272 if (strncmp(PHP_OS,'WIN',3)==0) {
273 static $FAIL = false;
274 if ($FAIL) return false;
276 $objName = "winmgmts:{impersonationLevel=impersonate}!\\\\.\\root\\CIMV2";
277 $myQuery = "SELECT * FROM Win32_PerfFormattedData_PerfOS_Processor WHERE Name = '_Total'";
279 try {
280 @$objWMIService = new COM($objName);
281 if (!$objWMIService) {
282 $FAIL = true;
283 return false;
286 $info[0] = -1;
287 $info[1] = 0;
288 $info[2] = 0;
289 $info[3] = 0;
290 foreach($objWMIService->ExecQuery($myQuery) as $objItem) {
291 $info[0] = $objItem->PercentProcessorTime();
294 } catch(Exception $e) {
295 $FAIL = true;
296 echo $e->getMessage();
297 return false;
300 return $info;
303 // Algorithm - Steve Blinch (BlitzAffe Online, http://www.blitzaffe.com)
304 $statfile = '/proc/stat';
305 if (!file_exists($statfile)) return false;
307 $fd = fopen($statfile,"r");
308 if (!$fd) return false;
310 $statinfo = explode("\n",fgets($fd, 1024));
311 fclose($fd);
312 foreach($statinfo as $line) {
313 $info = explode(" ",$line);
314 if($info[0]=="cpu") {
315 array_shift($info); // pop off "cpu"
316 if(!$info[0]) array_shift($info); // pop off blank space (if any)
317 return $info;
321 return false;
325 /* NOT IMPLEMENTED */
326 function MemInfo()
330 total: used: free: shared: buffers: cached:
331 Mem: 1055289344 917299200 137990144 0 165437440 599773184
332 Swap: 2146775040 11055104 2135719936
333 MemTotal: 1030556 kB
334 MemFree: 134756 kB
335 MemShared: 0 kB
336 Buffers: 161560 kB
337 Cached: 581384 kB
338 SwapCached: 4332 kB
339 Active: 494468 kB
340 Inact_dirty: 322856 kB
341 Inact_clean: 24256 kB
342 Inact_target: 168316 kB
343 HighTotal: 131064 kB
344 HighFree: 1024 kB
345 LowTotal: 899492 kB
346 LowFree: 133732 kB
347 SwapTotal: 2096460 kB
348 SwapFree: 2085664 kB
349 Committed_AS: 348732 kB
355 Remember that this is client load, not db server load!
357 var $_lastLoad;
358 function CPULoad()
360 $info = $this->_CPULoad();
361 if (!$info) return false;
363 if (strncmp(PHP_OS,'WIN',3)==0) {
364 return (integer) $info[0];
365 }else {
366 if (empty($this->_lastLoad)) {
367 sleep(1);
368 $this->_lastLoad = $info;
369 $info = $this->_CPULoad();
372 $last = $this->_lastLoad;
373 $this->_lastLoad = $info;
375 $d_user = $info[0] - $last[0];
376 $d_nice = $info[1] - $last[1];
377 $d_system = $info[2] - $last[2];
378 $d_idle = $info[3] - $last[3];
380 //printf("Delta - User: %f Nice: %f System: %f Idle: %f<br>",$d_user,$d_nice,$d_system,$d_idle);
382 $total=$d_user+$d_nice+$d_system+$d_idle;
383 if ($total<1) $total=1;
384 return 100*($d_user+$d_nice+$d_system)/$total;
388 function Tracer($sql)
390 $perf_table = adodb_perf::table();
391 $saveE = $this->conn->fnExecute;
392 $this->conn->fnExecute = false;
394 global $ADODB_FETCH_MODE;
395 $save = $ADODB_FETCH_MODE;
396 $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
397 if ($this->conn->fetchMode !== false) $savem = $this->conn->SetFetchMode(false);
399 $sqlq = $this->conn->qstr($sql);
400 $arr = $this->conn->GetArray(
401 "select count(*),tracer
402 from $perf_table where sql1=$sqlq
403 group by tracer
404 order by 1 desc");
405 $s = '';
406 if ($arr) {
407 $s .= '<h3>Scripts Affected</h3>';
408 foreach($arr as $k) {
409 $s .= sprintf("%4d",$k[0]).' &nbsp; '.strip_tags($k[1]).'<br>';
413 if (isset($savem)) $this->conn->SetFetchMode($savem);
414 $ADODB_CACHE_MODE = $save;
415 $this->conn->fnExecute = $saveE;
416 return $s;
420 Explain Plan for $sql.
421 If only a snippet of the $sql is passed in, then $partial will hold the crc32 of the
422 actual sql.
424 function Explain($sql,$partial=false)
426 return false;
429 function InvalidSQL($numsql = 10)
432 if (isset($_GET['sql'])) return;
433 $s = '<h3>Invalid SQL</h3>';
434 $saveE = $this->conn->fnExecute;
435 $this->conn->fnExecute = false;
436 $perf_table = adodb_perf::table();
437 $rs = $this->conn->SelectLimit("select distinct count(*),sql1,tracer as error_msg from $perf_table where tracer like 'ERROR:%' group by sql1,tracer order by 1 desc",$numsql);//,$numsql);
438 $this->conn->fnExecute = $saveE;
439 if ($rs) {
440 $s .= rs2html($rs,false,false,false,false);
441 } else
442 return "<p>$this->helpurl. ".$this->conn->ErrorMsg()."</p>";
444 return $s;
449 This script identifies the longest running SQL
451 function _SuspiciousSQL($numsql = 10)
453 global $ADODB_FETCH_MODE;
455 $perf_table = adodb_perf::table();
456 $saveE = $this->conn->fnExecute;
457 $this->conn->fnExecute = false;
459 if (isset($_GET['exps']) && isset($_GET['sql'])) {
460 $partial = !empty($_GET['part']);
461 echo "<a name=explain></a>".$this->Explain($_GET['sql'],$partial)."\n";
464 if (isset($_GET['sql'])) return;
465 $sql1 = $this->sql1;
467 $save = $ADODB_FETCH_MODE;
468 $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
469 if ($this->conn->fetchMode !== false) $savem = $this->conn->SetFetchMode(false);
470 //$this->conn->debug=1;
471 $rs = $this->conn->SelectLimit(
472 "select avg(timer) as avg_timer,$sql1,count(*),max(timer) as max_timer,min(timer) as min_timer
473 from $perf_table
474 where {$this->conn->upperCase}({$this->conn->substr}(sql0,1,5)) not in ('DROP ','INSER','COMMI','CREAT')
475 and (tracer is null or tracer not like 'ERROR:%')
476 group by sql1
477 order by 1 desc",$numsql);
478 if (isset($savem)) $this->conn->SetFetchMode($savem);
479 $ADODB_FETCH_MODE = $save;
480 $this->conn->fnExecute = $saveE;
482 if (!$rs) return "<p>$this->helpurl. ".$this->conn->ErrorMsg()."</p>";
483 $s = "<h3>Suspicious SQL</h3>
484 <font size=1>The following SQL have high average execution times</font><br>
485 <table border=1 bgcolor=white><tr><td><b>Avg Time</b><td><b>Count</b><td><b>SQL</b><td><b>Max</b><td><b>Min</b></tr>\n";
486 $max = $this->maxLength;
487 while (!$rs->EOF) {
488 $sql = $rs->fields[1];
489 $raw = urlencode($sql);
490 if (strlen($raw)>$max-100) {
491 $sql2 = substr($sql,0,$max-500);
492 $raw = urlencode($sql2).'&part='.crc32($sql);
494 $prefix = "<a target=sql".rand()." href=\"?hidem=1&exps=1&sql=".$raw."&x#explain\">";
495 $suffix = "</a>";
496 if ($this->explain == false || strlen($prefix)>$max) {
497 $suffix = ' ... <i>String too long for GET parameter: '.strlen($prefix).'</i>';
498 $prefix = '';
500 $s .= "<tr><td>".adodb_round($rs->fields[0],6)."<td align=right>".$rs->fields[2]."<td><font size=-1>".$prefix.htmlspecialchars($sql).$suffix."</font>".
501 "<td>".$rs->fields[3]."<td>".$rs->fields[4]."</tr>";
502 $rs->MoveNext();
504 return $s."</table>";
508 function CheckMemory()
510 return '';
514 function SuspiciousSQL($numsql=10)
516 return adodb_perf::_SuspiciousSQL($numsql);
519 function ExpensiveSQL($numsql=10)
521 return adodb_perf::_ExpensiveSQL($numsql);
526 This reports the percentage of load on the instance due to the most
527 expensive few SQL statements. Tuning these statements can often
528 make huge improvements in overall system performance.
530 function _ExpensiveSQL($numsql = 10)
532 global $ADODB_FETCH_MODE;
534 $perf_table = adodb_perf::table();
535 $saveE = $this->conn->fnExecute;
536 $this->conn->fnExecute = false;
538 if (isset($_GET['expe']) && isset($_GET['sql'])) {
539 $partial = !empty($_GET['part']);
540 echo "<a name=explain></a>".$this->Explain($_GET['sql'],$partial)."\n";
543 if (isset($_GET['sql'])) return;
545 $sql1 = $this->sql1;
546 $save = $ADODB_FETCH_MODE;
547 $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
548 if ($this->conn->fetchMode !== false) $savem = $this->conn->SetFetchMode(false);
550 $rs = $this->conn->SelectLimit(
551 "select sum(timer) as total,$sql1,count(*),max(timer) as max_timer,min(timer) as min_timer
552 from $perf_table
553 where {$this->conn->upperCase}({$this->conn->substr}(sql0,1,5)) not in ('DROP ','INSER','COMMI','CREAT')
554 and (tracer is null or tracer not like 'ERROR:%')
555 group by sql1
556 having count(*)>1
557 order by 1 desc",$numsql);
558 if (isset($savem)) $this->conn->SetFetchMode($savem);
559 $this->conn->fnExecute = $saveE;
560 $ADODB_FETCH_MODE = $save;
561 if (!$rs) return "<p>$this->helpurl. ".$this->conn->ErrorMsg()."</p>";
562 $s = "<h3>Expensive SQL</h3>
563 <font size=1>Tuning the following SQL could reduce the server load substantially</font><br>
564 <table border=1 bgcolor=white><tr><td><b>Load</b><td><b>Count</b><td><b>SQL</b><td><b>Max</b><td><b>Min</b></tr>\n";
565 $max = $this->maxLength;
566 while (!$rs->EOF) {
567 $sql = $rs->fields[1];
568 $raw = urlencode($sql);
569 if (strlen($raw)>$max-100) {
570 $sql2 = substr($sql,0,$max-500);
571 $raw = urlencode($sql2).'&part='.crc32($sql);
573 $prefix = "<a target=sqle".rand()." href=\"?hidem=1&expe=1&sql=".$raw."&x#explain\">";
574 $suffix = "</a>";
575 if($this->explain == false || strlen($prefix>$max)) {
576 $prefix = '';
577 $suffix = '';
579 $s .= "<tr><td>".adodb_round($rs->fields[0],6)."<td align=right>".$rs->fields[2]."<td><font size=-1>".$prefix.htmlspecialchars($sql).$suffix."</font>".
580 "<td>".$rs->fields[3]."<td>".$rs->fields[4]."</tr>";
581 $rs->MoveNext();
583 return $s."</table>";
587 Raw function to return parameter value from $settings.
589 function DBParameter($param)
591 if (empty($this->settings[$param])) return false;
592 $sql = $this->settings[$param][1];
593 return $this->_DBParameter($sql);
597 Raw function returning array of poll parameters
599 function PollParameters()
601 $arr[0] = (float)$this->DBParameter('data cache hit ratio');
602 $arr[1] = (float)$this->DBParameter('data reads');
603 $arr[2] = (float)$this->DBParameter('data writes');
604 $arr[3] = (integer) $this->DBParameter('current connections');
605 return $arr;
609 Low-level Get Database Parameter
611 function _DBParameter($sql)
613 $savelog = $this->conn->LogSQL(false);
614 if (is_array($sql)) {
615 global $ADODB_FETCH_MODE;
617 $sql1 = $sql[0];
618 $key = $sql[1];
619 if (sizeof($sql)>2) $pos = $sql[2];
620 else $pos = 1;
621 if (sizeof($sql)>3) $coef = $sql[3];
622 else $coef = false;
623 $ret = false;
624 $save = $ADODB_FETCH_MODE;
625 $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
626 if ($this->conn->fetchMode !== false) $savem = $this->conn->SetFetchMode(false);
628 $rs = $this->conn->Execute($sql1);
630 if (isset($savem)) $this->conn->SetFetchMode($savem);
631 $ADODB_FETCH_MODE = $save;
632 if ($rs) {
633 while (!$rs->EOF) {
634 $keyf = reset($rs->fields);
635 if (trim($keyf) == $key) {
636 $ret = $rs->fields[$pos];
637 if ($coef) $ret *= $coef;
638 break;
640 $rs->MoveNext();
642 $rs->Close();
644 $this->conn->LogSQL($savelog);
645 return $ret;
646 } else {
647 if (strncmp($sql,'=',1) == 0) {
648 $fn = substr($sql,1);
649 return $this->$fn();
651 $sql = str_replace('$DATABASE',$this->conn->database,$sql);
652 $ret = $this->conn->GetOne($sql);
653 $this->conn->LogSQL($savelog);
655 return $ret;
660 Warn if cache ratio falls below threshold. Displayed in "Description" column.
662 function WarnCacheRatio($val)
664 if ($val < $this->warnRatio)
665 return '<font color=red><b>Cache ratio should be at least '.$this->warnRatio.'%</b></font>';
666 else return '';
669 function clearsql()
671 $perf_table = adodb_perf::table();
672 $this->conn->Execute("delete from $perf_table where created<".$this->conn->sysTimeStamp);
674 /***********************************************************************************************/
675 // HIGH LEVEL UI FUNCTIONS
676 /***********************************************************************************************/
679 function UI($pollsecs=5)
681 global $ADODB_LOG_CONN;
683 $perf_table = adodb_perf::table();
684 $conn = $this->conn;
686 $app = $conn->host;
687 if ($conn->host && $conn->database) $app .= ', db=';
688 $app .= $conn->database;
690 if ($app) $app .= ', ';
691 $savelog = $this->conn->LogSQL(false);
692 $info = $conn->ServerInfo();
693 if (isset($_GET['clearsql'])) {
694 $this->clearsql();
696 $this->conn->LogSQL($savelog);
698 if (!isset($_SESSION['ADODB_PERF_SQL'])) $nsql = $_SESSION['ADODB_PERF_SQL'] = 10;
699 else $nsql = $_SESSION['ADODB_PERF_SQL'];
701 $app .= $info['description'];
704 if (isset($_GET['do'])) $do = $_GET['do'];
705 else if (isset($_POST['do'])) $do = $_POST['do'];
706 else if (isset($_GET['sql'])) $do = 'viewsql';
707 else $do = 'stats';
709 if (isset($_GET['nsql'])) {
710 if ($_GET['nsql'] > 0) $nsql = $_SESSION['ADODB_PERF_SQL'] = (integer) $_GET['nsql'];
712 echo "<title>ADOdb Performance Monitor on $app</title><body bgcolor=white>";
713 if ($do == 'viewsql') $form = "<td><form># SQL:<input type=hidden value=viewsql name=do> <input type=text size=4 name=nsql value=$nsql><input type=submit value=Go></td></form>";
714 else $form = "<td>&nbsp;</td>";
716 $allowsql = !defined('ADODB_PERF_NO_RUN_SQL');
717 global $ADODB_PERF_MIN;
718 $app .= " (Min sql timing \$ADODB_PERF_MIN=$ADODB_PERF_MIN secs)";
720 if (empty($_GET['hidem']))
721 echo "<table border=1 width=100% bgcolor=lightyellow><tr><td colspan=2>
722 <b><a href=https://adodb.org/dokuwiki/doku.php?id=v5:performance:performance_index>ADOdb</a> Performance Monitor</b> <font size=1>for $app</font></tr><tr><td>
723 <a href=?do=stats><b>Performance Stats</b></a> &nbsp; <a href=?do=viewsql><b>View SQL</b></a>
724 &nbsp; <a href=?do=tables><b>View Tables</b></a> &nbsp; <a href=?do=poll><b>Poll Stats</b></a>",
725 $allowsql ? ' &nbsp; <a href=?do=dosql><b>Run SQL</b></a>' : '',
726 "$form",
727 "</tr></table>";
730 switch ($do) {
731 default:
732 case 'stats':
733 if (empty($ADODB_LOG_CONN))
734 echo "<p>&nbsp; <a href=\"?do=viewsql&clearsql=1\">Clear SQL Log</a><br>";
735 echo $this->HealthCheck();
736 //$this->conn->debug=1;
737 echo $this->CheckMemory();
738 break;
739 case 'poll':
740 $self = htmlspecialchars($_SERVER['PHP_SELF']);
741 echo "<iframe width=720 height=80%
742 src=\"{$self}?do=poll2&hidem=1\"></iframe>";
743 break;
744 case 'poll2':
745 echo "<pre>";
746 $this->Poll($pollsecs);
747 break;
749 case 'dosql':
750 if (!$allowsql) break;
752 $this->DoSQLForm();
753 break;
754 case 'viewsql':
755 if (empty($_GET['hidem']))
756 echo "&nbsp; <a href=\"?do=viewsql&clearsql=1\">Clear SQL Log</a><br>";
757 echo($this->SuspiciousSQL($nsql));
758 echo($this->ExpensiveSQL($nsql));
759 echo($this->InvalidSQL($nsql));
760 break;
761 case 'tables':
762 echo $this->Tables(); break;
764 global $ADODB_vers;
768 Runs in infinite loop, returning real-time statistics
770 function Poll($secs=5)
772 $this->conn->fnExecute = false;
773 //$this->conn->debug=1;
774 if ($secs <= 1) $secs = 1;
775 echo "Accumulating statistics, every $secs seconds...\n";flush();
776 $arro = $this->PollParameters();
777 $cnt = 0;
778 set_time_limit(0);
779 sleep($secs);
780 while (1) {
782 $arr = $this->PollParameters();
784 $hits = sprintf('%2.2f',$arr[0]);
785 $reads = sprintf('%12.4f',($arr[1]-$arro[1])/$secs);
786 $writes = sprintf('%12.4f',($arr[2]-$arro[2])/$secs);
787 $sess = sprintf('%5d',$arr[3]);
789 $load = $this->CPULoad();
790 if ($load !== false) {
791 $oslabel = 'WS-CPU%';
792 $osval = sprintf(" %2.1f ",(float) $load);
793 }else {
794 $oslabel = '';
795 $osval = '';
797 if ($cnt % 10 == 0) echo " Time ".$oslabel." Hit% Sess Reads/s Writes/s\n";
798 $cnt += 1;
799 echo date('H:i:s').' '.$osval."$hits $sess $reads $writes\n";
800 flush();
802 if (connection_aborted()) return;
804 sleep($secs);
805 $arro = $arr;
810 Returns basic health check in a command line interface
812 function HealthCheckCLI()
814 return $this->HealthCheck(true);
819 Returns basic health check as HTML
821 function HealthCheck($cli=false)
823 $saveE = $this->conn->fnExecute;
824 $this->conn->fnExecute = false;
825 if ($cli) $html = '';
826 else $html = $this->table.'<tr><td colspan=3><h3>'.$this->conn->databaseType.'</h3></td></tr>'.$this->titles;
828 $oldc = false;
829 $bgc = '';
830 foreach($this->settings as $name => $arr) {
831 if ($arr === false) break;
833 if (!is_string($name)) {
834 if ($cli) $html .= " -- $arr -- \n";
835 else $html .= "<tr bgcolor=$this->color><td colspan=3><i>$arr</i> &nbsp;</td></tr>";
836 continue;
839 if (!is_array($arr)) break;
840 $category = $arr[0];
841 $how = $arr[1];
842 if (sizeof($arr)>2) $desc = $arr[2];
843 else $desc = ' &nbsp; ';
846 if ($category == 'HIDE') continue;
848 $val = $this->_DBParameter($how);
850 if ($desc && strncmp($desc,"=",1) === 0) {
851 $fn = substr($desc,1);
852 $desc = $this->$fn($val);
855 if ($val === false) {
856 $m = $this->conn->ErrorMsg();
857 $val = "Error: $m";
858 } else {
859 if (is_numeric($val) && $val >= 256*1024) {
860 if ($val % (1024*1024) == 0) {
861 $val /= (1024*1024);
862 $val .= 'M';
863 } else if ($val % 1024 == 0) {
864 $val /= 1024;
865 $val .= 'K';
867 //$val = htmlspecialchars($val);
870 if ($category != $oldc) {
871 $oldc = $category;
872 //$bgc = ($bgc == ' bgcolor='.$this->color) ? ' bgcolor=white' : ' bgcolor='.$this->color;
874 if (strlen($desc)==0) $desc = '&nbsp;';
875 if (strlen($val)==0) $val = '&nbsp;';
876 if ($cli) {
877 $html .= str_replace('&nbsp;','',sprintf($this->cliFormat,strip_tags($name),strip_tags($val),strip_tags($desc)));
879 }else {
880 $html .= "<tr$bgc><td>".$name.'</td><td>'.$val.'</td><td>'.$desc."</td></tr>\n";
884 if (!$cli) $html .= "</table>\n";
885 $this->conn->fnExecute = $saveE;
887 return $html;
890 function Tables($orderby='1')
892 if (!$this->tablesSQL) return false;
894 $savelog = $this->conn->LogSQL(false);
895 $rs = $this->conn->Execute($this->tablesSQL.' order by '.$orderby);
896 $this->conn->LogSQL($savelog);
897 $html = rs2html($rs,false,false,false,false);
898 return $html;
902 function CreateLogTable()
904 if (!$this->createTableSQL) return false;
906 $table = $this->table();
907 $sql = str_replace('adodb_logsql',$table,$this->createTableSQL);
908 $savelog = $this->conn->LogSQL(false);
909 $ok = $this->conn->Execute($sql);
910 $this->conn->LogSQL($savelog);
911 return ($ok) ? true : false;
914 function DoSQLForm()
918 $PHP_SELF = htmlspecialchars($_SERVER['PHP_SELF']);
919 $sql = isset($_REQUEST['sql']) ? $_REQUEST['sql'] : '';
921 if (isset($_SESSION['phplens_sqlrows'])) $rows = $_SESSION['phplens_sqlrows'];
922 else $rows = 3;
924 if (isset($_REQUEST['SMALLER'])) {
925 $rows /= 2;
926 if ($rows < 3) $rows = 3;
927 $_SESSION['phplens_sqlrows'] = $rows;
929 if (isset($_REQUEST['BIGGER'])) {
930 $rows *= 2;
931 $_SESSION['phplens_sqlrows'] = $rows;
936 <form method="POST" action="<?php echo $PHP_SELF ?>">
937 <table><tr>
938 <td> Form size: <input type="submit" value=" &lt; " name="SMALLER"><input type="submit" value=" &gt; &gt; " name="BIGGER">
939 </td>
940 <td align=right>
941 <input type="submit" value=" Run SQL Below " name="RUN"><input type=hidden name=do value=dosql>
942 </td></tr>
943 <tr>
944 <td colspan=2><textarea rows=<?php print $rows; ?> name="sql" cols="80"><?php print htmlspecialchars($sql) ?></textarea>
945 </td>
946 </tr>
947 </table>
948 </form>
950 <?php
951 if (!isset($_REQUEST['sql'])) return;
953 $sql = trim($sql);
954 if (substr($sql,strlen($sql)-1) === ';') {
955 $print = true;
956 $sqla = $this->SplitSQL($sql);
957 } else {
958 $print = false;
959 $sqla = array($sql);
961 foreach($sqla as $sqls) {
963 if (!$sqls) continue;
965 if ($print) {
966 print "<p>".htmlspecialchars($sqls)."</p>";
967 flush();
969 $savelog = $this->conn->LogSQL(false);
970 $rs = $this->conn->Execute($sqls);
971 $this->conn->LogSQL($savelog);
972 if ($rs && is_object($rs) && !$rs->EOF) {
973 rs2html($rs);
974 while ($rs->NextRecordSet()) {
975 print "<table width=98% bgcolor=#C0C0FF><tr><td>&nbsp;</td></tr></table>";
976 rs2html($rs);
978 } else {
979 $e1 = (integer) $this->conn->ErrorNo();
980 $e2 = $this->conn->ErrorMsg();
981 if (($e1) || ($e2)) {
982 if (empty($e1)) $e1 = '-1'; // postgresql fix
983 print ' &nbsp; '.$e1.': '.$e2;
984 } else {
985 print "<p>No Recordset returned<br></p>";
988 } // foreach
991 function SplitSQL($sql)
993 $arr = explode(';',$sql);
994 return $arr;
997 /************************************************************************/
1000 * Reorganise multiple table-indices/statistics/..
1001 * OptimizeMode could be given by last Parameter
1003 * @example
1004 * <pre>
1005 * optimizeTables( 'tableA');
1006 * </pre>
1007 * <pre>
1008 * optimizeTables( 'tableA', 'tableB', 'tableC');
1009 * </pre>
1010 * <pre>
1011 * optimizeTables( 'tableA', 'tableB', ADODB_OPT_LOW);
1012 * </pre>
1014 * @param string table name of the table to optimize
1015 * @param int mode optimization-mode
1016 * <code>ADODB_OPT_HIGH</code> for full optimization
1017 * <code>ADODB_OPT_LOW</code> for CPU-less optimization
1018 * Default is LOW <code>ADODB_OPT_LOW</code>
1019 * @author Markus Staab
1020 * @return bool true on success, false on error
1022 function OptimizeTables()
1024 $args = func_get_args();
1025 $numArgs = func_num_args();
1027 if ( $numArgs == 0) return false;
1029 $mode = ADODB_OPT_LOW;
1030 $lastArg = $args[ $numArgs - 1];
1031 if ( !is_string($lastArg)) {
1032 $mode = $lastArg;
1033 unset( $args[ $numArgs - 1]);
1036 foreach( $args as $table) {
1037 $this->optimizeTable( $table, $mode);
1042 * Reorganise the table-indices/statistics/.. depending on the given mode.
1043 * Default Implementation throws an error.
1045 * @param string table name of the table to optimize
1046 * @param int mode optimization-mode
1047 * <code>ADODB_OPT_HIGH</code> for full optimization
1048 * <code>ADODB_OPT_LOW</code> for CPU-less optimization
1049 * Default is LOW <code>ADODB_OPT_LOW</code>
1050 * @author Markus Staab
1051 * @return bool true on success, false on error
1053 function OptimizeTable( $table, $mode = ADODB_OPT_LOW)
1055 ADOConnection::outp( sprintf( "<p>%s: '%s' not implemented for driver '%s'</p>", __CLASS__, __FUNCTION__, $this->conn->databaseType));
1056 return false;
1060 * Reorganise current database.
1061 * Default implementation loops over all <code>MetaTables()</code> and
1062 * optimize each using <code>optmizeTable()</code>
1064 * @author Markus Staab
1065 * @return bool true on success, false on error
1067 function optimizeDatabase()
1069 $conn = $this->conn;
1070 if ( !$conn) return false;
1072 $tables = $conn->MetaTables( 'TABLES');
1073 if ( !$tables ) return false;
1075 foreach( $tables as $table) {
1076 if ( !$this->optimizeTable( $table)) {
1077 return false;
1081 return true;
1083 // end hack