kpi.class.inc.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. <?php
  2. // Copyright (C) 2010-2012 Combodo SARL
  3. //
  4. // This file is part of iTop.
  5. //
  6. // iTop is free software; you can redistribute it and/or modify
  7. // it under the terms of the GNU Affero General Public License as published by
  8. // the Free Software Foundation, either version 3 of the License, or
  9. // (at your option) any later version.
  10. //
  11. // iTop is distributed in the hope that it will be useful,
  12. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. // GNU Affero General Public License for more details.
  15. //
  16. // You should have received a copy of the GNU Affero General Public License
  17. // along with iTop. If not, see <http://www.gnu.org/licenses/>
  18. /**
  19. * Measures operations duration, memory usage, etc. (and some other KPIs)
  20. *
  21. * @copyright Copyright (C) 2010-2012 Combodo SARL
  22. * @license http://opensource.org/licenses/AGPL-3.0
  23. */
  24. class ExecutionKPI
  25. {
  26. static protected $m_bEnabled_Duration = false;
  27. static protected $m_bEnabled_Memory = false;
  28. static protected $m_sAllowedUser = '*';
  29. static protected $m_aStats = array(); // Recurrent operations
  30. static protected $m_aExecData = array(); // One shot operations
  31. protected $m_fStarted = null;
  32. protected $m_iInitialMemory = null;
  33. static public function EnableDuration($iLevel)
  34. {
  35. if ($iLevel > 0)
  36. {
  37. self::$m_bEnabled_Duration = true;
  38. }
  39. }
  40. static public function EnableMemory($iLevel)
  41. {
  42. if ($iLevel > 0)
  43. {
  44. self::$m_bEnabled_Memory = true;
  45. }
  46. }
  47. /**
  48. * @param string sUser A user login or * for all users
  49. */
  50. static public function SetAllowedUser($sUser)
  51. {
  52. self::$m_sAllowedUser = $sUser;
  53. }
  54. static public function ReportStats()
  55. {
  56. if (self::$m_sAllowedUser != '*')
  57. {
  58. if (UserRights::GetUser() != self::$m_sAllowedUser)
  59. {
  60. return;
  61. }
  62. }
  63. global $fItopStarted;
  64. $sExecId = microtime(); // id to differentiate the hrefs!
  65. $aBeginTimes = array();
  66. foreach (self::$m_aExecData as $aOpStats)
  67. {
  68. $aBeginTimes[] = $aOpStats['time_begin'];
  69. }
  70. array_multisort($aBeginTimes, self::$m_aExecData);
  71. $sTableStyle = 'background-color: #ccc; margin: 10px;';
  72. self::Report("<hr/>");
  73. self::Report("<div style=\"background-color: grey; padding: 10px;\">");
  74. self::Report("<h3><a name=\"".md5($sExecId)."\">KPIs</a> - ".$_SERVER['REQUEST_URI']." (".$_SERVER['REQUEST_METHOD'].")</h3>");
  75. self::Report("<p>".date('Y-m-d H:i:s', $fItopStarted)."</p>");
  76. self::Report("<p>log_kpi_user_id: ".MetaModel::GetConfig()->Get('log_kpi_user_id')."</p>");
  77. self::Report("<div>");
  78. self::Report("<table border=\"1\" style=\"$sTableStyle\">");
  79. self::Report("<thead>");
  80. self::Report(" <th>Operation</th><th>Begin</th><th>End</th><th>Duration</th><th>Memory start</th><th>Memory end</th><th>Memory peak</th>");
  81. self::Report("</thead>");
  82. foreach (self::$m_aExecData as $aOpStats)
  83. {
  84. $sOperation = $aOpStats['op'];
  85. $sBegin = $sEnd = $sDuration = $sMemBegin = $sMemEnd = $sMemPeak = '?';
  86. $sBegin = round($aOpStats['time_begin'], 3);
  87. $sEnd = round($aOpStats['time_end'], 3);
  88. $fDuration = $aOpStats['time_end'] - $aOpStats['time_begin'];
  89. $sDuration = round($fDuration, 3);
  90. if (isset($aOpStats['mem_begin']))
  91. {
  92. $sMemBegin = self::MemStr($aOpStats['mem_begin']);
  93. $sMemEnd = self::MemStr($aOpStats['mem_end']);
  94. if (isset($aOpStats['mem_peak']))
  95. {
  96. $sMemPeak = self::MemStr($aOpStats['mem_peak']);
  97. }
  98. }
  99. self::Report("<tr>");
  100. self::Report(" <td>$sOperation</td><td>$sBegin</td><td>$sEnd</td><td>$sDuration</td><td>$sMemBegin</td><td>$sMemEnd</td><td>$sMemPeak</td>");
  101. self::Report("</tr>");
  102. }
  103. self::Report("</table>");
  104. self::Report("</div>");
  105. $aConsolidatedStats = array();
  106. foreach (self::$m_aStats as $sOperation => $aOpStats)
  107. {
  108. $fTotalOp = 0;
  109. $iTotalOp = 0;
  110. $fMinOp = null;
  111. $fMaxOp = 0;
  112. $sMaxOpArguments = null;
  113. foreach ($aOpStats as $sArguments => $aEvents)
  114. {
  115. foreach ($aEvents as $fDuration)
  116. {
  117. $fTotalOp += $fDuration;
  118. $iTotalOp++;
  119. $fMinOp = is_null($fMinOp) ? $fDuration : min($fMinOp, $fDuration);
  120. if ($fDuration > $fMaxOp)
  121. {
  122. $sMaxOpArguments = $sArguments;
  123. $fMaxOp = $fDuration;
  124. }
  125. }
  126. }
  127. $aConsolidatedStats[$sOperation] = array(
  128. 'count' => $iTotalOp,
  129. 'duration' => $fTotalOp,
  130. 'min' => $fMinOp,
  131. 'max' => $fMaxOp,
  132. 'avg' => $fTotalOp / $iTotalOp,
  133. 'max_args' => $sMaxOpArguments
  134. );
  135. }
  136. self::Report("<div>");
  137. self::Report("<table border=\"1\" style=\"$sTableStyle\">");
  138. self::Report("<thead>");
  139. self::Report(" <th>Operation</th><th>Count</th><th>Duration</th><th>Min</th><th>Max</th><th>Avg</th>");
  140. self::Report("</thead>");
  141. foreach ($aConsolidatedStats as $sOperation => $aOpStats)
  142. {
  143. $sOperation = '<a href="#'.md5($sExecId.$sOperation).'">'.$sOperation.'</a>';
  144. $sCount = $aOpStats['count'];
  145. $sDuration = round($aOpStats['duration'], 3);
  146. $sMin = round($aOpStats['min'], 3);
  147. $sMax = '<a href="#'.md5($sExecId.$aOpStats['max_args']).'">'.round($aOpStats['max'], 3).'</a>';
  148. $sAvg = round($aOpStats['avg'], 3);
  149. self::Report("<tr>");
  150. self::Report(" <td>$sOperation</td><td>$sCount</td><td>$sDuration</td><td>$sMin</td><td>$sMax</td><td>$sAvg</td>");
  151. self::Report("</tr>");
  152. }
  153. self::Report("</table>");
  154. self::Report("</div>");
  155. self::Report("</div>");
  156. // Report operation details
  157. foreach (self::$m_aStats as $sOperation => $aOpStats)
  158. {
  159. $sOperationHtml = '<a name="'.md5($sExecId.$sOperation).'">'.$sOperation.'</a>';
  160. self::Report("<h4>$sOperationHtml</h4>");
  161. self::Report("<p><a href=\"#".md5($sExecId)."\">Back to page stats</a></p>");
  162. self::Report("<table border=\"1\" style=\"$sTableStyle\">");
  163. self::Report("<thead>");
  164. self::Report(" <th>Operation details</th><th>Count</th><th>Duration</th><th>Min</th><th>Max</th>");
  165. self::Report("</thead>");
  166. foreach ($aOpStats as $sArguments => $aEvents)
  167. {
  168. $sHtmlArguments = '<a name="'.md5($sExecId.$sArguments).'">'.$sArguments.'</a>';
  169. if ($aConsolidatedStats[$sOperation]['max_args'] == $sArguments)
  170. {
  171. $sHtmlArguments = '<span style="color: red;">'.$sHtmlArguments.'</span>';
  172. }
  173. $fTotalInter = 0;
  174. $fMinInter = null;
  175. $fMaxInter = 0;
  176. foreach ($aEvents as $fDuration)
  177. {
  178. $fTotalInter += $fDuration;
  179. $fMinInter = is_null($fMinInter) ? $fDuration : min($fMinInter, $fDuration);
  180. $fMaxInter = max($fMaxInter, $fDuration);
  181. }
  182. $iCountInter = count($aEvents);
  183. $sTotalInter = round($fTotalInter, 3);
  184. $sMinInter = round($fMinInter, 3);
  185. $sMaxInter = round($fMaxInter, 3);
  186. self::Report("<tr>");
  187. self::Report(" <td>$sHtmlArguments</td><td>$iCountInter</td><td>$sTotalInter</td><td>$sMinInter</td><td>$sMaxInter</td>");
  188. self::Report("</tr>");
  189. }
  190. self::Report("</table>");
  191. }
  192. }
  193. public function __construct()
  194. {
  195. $this->ResetCounters();
  196. }
  197. // Get the duration since startup, and reset the counter for the next measure
  198. //
  199. public function ComputeAndReport($sOperationDesc)
  200. {
  201. global $fItopStarted;
  202. $aNewEntry = null;
  203. if (self::$m_bEnabled_Duration)
  204. {
  205. $fStopped = MyHelpers::getmicrotime();
  206. $aNewEntry = array(
  207. 'op' => $sOperationDesc,
  208. 'time_begin' => $this->m_fStarted - $fItopStarted,
  209. 'time_end' => $fStopped - $fItopStarted,
  210. );
  211. // Reset for the next operation (if the object is recycled)
  212. $this->m_fStarted = $fStopped;
  213. }
  214. if (self::$m_bEnabled_Memory)
  215. {
  216. $iCurrentMemory = self::memory_get_usage();
  217. if (is_null($aNewEntry))
  218. {
  219. $aNewEntry = array('op' => $sOperationDesc);
  220. }
  221. $aNewEntry['mem_begin'] = $this->m_iInitialMemory;
  222. $aNewEntry['mem_end'] = $iCurrentMemory;
  223. if (function_exists('memory_get_peak_usage'))
  224. {
  225. $aNewEntry['mem_peak'] = memory_get_peak_usage();
  226. }
  227. // Reset for the next operation (if the object is recycled)
  228. $this->m_iInitialMemory = $iCurrentMemory;
  229. }
  230. if (!is_null($aNewEntry))
  231. {
  232. self::$m_aExecData[] = $aNewEntry;
  233. }
  234. $this->ResetCounters();
  235. }
  236. public function ComputeStats($sOperation, $sArguments)
  237. {
  238. if (self::$m_bEnabled_Duration)
  239. {
  240. $fStopped = MyHelpers::getmicrotime();
  241. $fDuration = $fStopped - $this->m_fStarted;
  242. self::$m_aStats[$sOperation][$sArguments][] = $fDuration;
  243. }
  244. }
  245. protected function ResetCounters()
  246. {
  247. if (self::$m_bEnabled_Duration)
  248. {
  249. $this->m_fStarted = MyHelpers::getmicrotime();
  250. }
  251. if (self::$m_bEnabled_Memory)
  252. {
  253. $this->m_iInitialMemory = self::memory_get_usage();
  254. }
  255. }
  256. const HtmlReportFile = 'log/kpi.html';
  257. static protected function Report($sText)
  258. {
  259. file_put_contents(APPROOT.self::HtmlReportFile, "$sText\n", FILE_APPEND | LOCK_EX);
  260. }
  261. static protected function MemStr($iMemory)
  262. {
  263. return round($iMemory / 1024).' Kb';
  264. }
  265. static protected function memory_get_usage()
  266. {
  267. if (function_exists('memory_get_usage'))
  268. {
  269. return memory_get_usage(true);
  270. }
  271. // Copied from the PHP manual
  272. //
  273. //If its Windows
  274. //Tested on Win XP Pro SP2. Should work on Win 2003 Server too
  275. //Doesn't work for 2000
  276. //If you need it to work for 2000 look at http://us2.php.net/manual/en/function.memory-get-usage.php#54642
  277. if (substr(PHP_OS,0,3) == 'WIN')
  278. {
  279. $output = array();
  280. exec('tasklist /FI "PID eq ' . getmypid() . '" /FO LIST', $output);
  281. return preg_replace( '/[\D]/', '', $output[5] ) * 1024;
  282. }
  283. else
  284. {
  285. //We now assume the OS is UNIX
  286. //Tested on Mac OS X 10.4.6 and Linux Red Hat Enterprise 4
  287. //This should work on most UNIX systems
  288. $pid = getmypid();
  289. exec("ps -eo%mem,rss,pid | grep $pid", $output);
  290. $output = explode(" ", $output[0]);
  291. //rss is given in 1024 byte units
  292. return $output[1] * 1024;
  293. }
  294. }
  295. static public function memory_get_peak_usage($bRealUsage = false)
  296. {
  297. if (function_exists('memory_get_peak_usage'))
  298. {
  299. return memory_get_peak_usage($bRealUsage);
  300. }
  301. // PHP > 5.2.1 - this verb depends on a compilation option
  302. return 0;
  303. }
  304. }