kpi.class.inc.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  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 IsEnabled()
  55. {
  56. if (self::$m_bEnabled_Duration || self::$m_bEnabled_Memory)
  57. {
  58. if ((self::$m_sAllowedUser == '*') || (UserRights::GetUser() == trim(self::$m_sAllowedUser)))
  59. {
  60. return true;
  61. }
  62. }
  63. return false;
  64. }
  65. static public function GetDescription()
  66. {
  67. $aFeatures = array();
  68. if (self::$m_bEnabled_Duration) $aFeatures[] = 'Duration';
  69. if (self::$m_bEnabled_Memory) $aFeatures[] = 'Memory usage';
  70. $sFeatures = implode(', ', $aFeatures);
  71. $sFor = self::$m_sAllowedUser == '*' ? 'EVERYBODY' : "'".trim(self::$m_sAllowedUser)."'";
  72. return "KPI logging is active for $sFor. Measures: $sFeatures";
  73. }
  74. static public function ReportStats()
  75. {
  76. if (!self::IsEnabled()) return;
  77. global $fItopStarted;
  78. $sExecId = microtime(); // id to differentiate the hrefs!
  79. $aBeginTimes = array();
  80. foreach (self::$m_aExecData as $aOpStats)
  81. {
  82. $aBeginTimes[] = $aOpStats['time_begin'];
  83. }
  84. array_multisort($aBeginTimes, self::$m_aExecData);
  85. $sTableStyle = 'background-color: #ccc; margin: 10px;';
  86. self::Report("<hr/>");
  87. self::Report("<div style=\"background-color: grey; padding: 10px;\">");
  88. self::Report("<h3><a name=\"".md5($sExecId)."\">KPIs</a> - ".$_SERVER['REQUEST_URI']." (".$_SERVER['REQUEST_METHOD'].")</h3>");
  89. self::Report("<p>".date('Y-m-d H:i:s', $fItopStarted)."</p>");
  90. self::Report("<p>log_kpi_user_id: ".MetaModel::GetConfig()->Get('log_kpi_user_id')."</p>");
  91. self::Report("<div>");
  92. self::Report("<table border=\"1\" style=\"$sTableStyle\">");
  93. self::Report("<thead>");
  94. 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>");
  95. self::Report("</thead>");
  96. foreach (self::$m_aExecData as $aOpStats)
  97. {
  98. $sOperation = $aOpStats['op'];
  99. $sBegin = $sEnd = $sDuration = $sMemBegin = $sMemEnd = $sMemPeak = '?';
  100. $sBegin = round($aOpStats['time_begin'], 3);
  101. $sEnd = round($aOpStats['time_end'], 3);
  102. $fDuration = $aOpStats['time_end'] - $aOpStats['time_begin'];
  103. $sDuration = round($fDuration, 3);
  104. if (isset($aOpStats['mem_begin']))
  105. {
  106. $sMemBegin = self::MemStr($aOpStats['mem_begin']);
  107. $sMemEnd = self::MemStr($aOpStats['mem_end']);
  108. if (isset($aOpStats['mem_peak']))
  109. {
  110. $sMemPeak = self::MemStr($aOpStats['mem_peak']);
  111. }
  112. }
  113. self::Report("<tr>");
  114. self::Report(" <td>$sOperation</td><td>$sBegin</td><td>$sEnd</td><td>$sDuration</td><td>$sMemBegin</td><td>$sMemEnd</td><td>$sMemPeak</td>");
  115. self::Report("</tr>");
  116. }
  117. self::Report("</table>");
  118. self::Report("</div>");
  119. $aConsolidatedStats = array();
  120. foreach (self::$m_aStats as $sOperation => $aOpStats)
  121. {
  122. $fTotalOp = 0;
  123. $iTotalOp = 0;
  124. $fMinOp = null;
  125. $fMaxOp = 0;
  126. $sMaxOpArguments = null;
  127. foreach ($aOpStats as $sArguments => $aEvents)
  128. {
  129. foreach ($aEvents as $fDuration)
  130. {
  131. $fTotalOp += $fDuration;
  132. $iTotalOp++;
  133. $fMinOp = is_null($fMinOp) ? $fDuration : min($fMinOp, $fDuration);
  134. if ($fDuration > $fMaxOp)
  135. {
  136. $sMaxOpArguments = $sArguments;
  137. $fMaxOp = $fDuration;
  138. }
  139. }
  140. }
  141. $aConsolidatedStats[$sOperation] = array(
  142. 'count' => $iTotalOp,
  143. 'duration' => $fTotalOp,
  144. 'min' => $fMinOp,
  145. 'max' => $fMaxOp,
  146. 'avg' => $fTotalOp / $iTotalOp,
  147. 'max_args' => $sMaxOpArguments
  148. );
  149. }
  150. self::Report("<div>");
  151. self::Report("<table border=\"1\" style=\"$sTableStyle\">");
  152. self::Report("<thead>");
  153. self::Report(" <th>Operation</th><th>Count</th><th>Duration</th><th>Min</th><th>Max</th><th>Avg</th>");
  154. self::Report("</thead>");
  155. foreach ($aConsolidatedStats as $sOperation => $aOpStats)
  156. {
  157. $sOperation = '<a href="#'.md5($sExecId.$sOperation).'">'.$sOperation.'</a>';
  158. $sCount = $aOpStats['count'];
  159. $sDuration = round($aOpStats['duration'], 3);
  160. $sMin = round($aOpStats['min'], 3);
  161. $sMax = '<a href="#'.md5($sExecId.$aOpStats['max_args']).'">'.round($aOpStats['max'], 3).'</a>';
  162. $sAvg = round($aOpStats['avg'], 3);
  163. self::Report("<tr>");
  164. self::Report(" <td>$sOperation</td><td>$sCount</td><td>$sDuration</td><td>$sMin</td><td>$sMax</td><td>$sAvg</td>");
  165. self::Report("</tr>");
  166. }
  167. self::Report("</table>");
  168. self::Report("</div>");
  169. self::Report("</div>");
  170. // Report operation details
  171. foreach (self::$m_aStats as $sOperation => $aOpStats)
  172. {
  173. $sOperationHtml = '<a name="'.md5($sExecId.$sOperation).'">'.$sOperation.'</a>';
  174. self::Report("<h4>$sOperationHtml</h4>");
  175. self::Report("<p><a href=\"#".md5($sExecId)."\">Back to page stats</a></p>");
  176. self::Report("<table border=\"1\" style=\"$sTableStyle\">");
  177. self::Report("<thead>");
  178. self::Report(" <th>Operation details</th><th>Count</th><th>Duration</th><th>Min</th><th>Max</th>");
  179. self::Report("</thead>");
  180. foreach ($aOpStats as $sArguments => $aEvents)
  181. {
  182. $sHtmlArguments = '<a name="'.md5($sExecId.$sArguments).'">'.$sArguments.'</a>';
  183. if ($aConsolidatedStats[$sOperation]['max_args'] == $sArguments)
  184. {
  185. $sHtmlArguments = '<span style="color: red;">'.$sHtmlArguments.'</span>';
  186. }
  187. $fTotalInter = 0;
  188. $fMinInter = null;
  189. $fMaxInter = 0;
  190. foreach ($aEvents as $fDuration)
  191. {
  192. $fTotalInter += $fDuration;
  193. $fMinInter = is_null($fMinInter) ? $fDuration : min($fMinInter, $fDuration);
  194. $fMaxInter = max($fMaxInter, $fDuration);
  195. }
  196. $iCountInter = count($aEvents);
  197. $sTotalInter = round($fTotalInter, 3);
  198. $sMinInter = round($fMinInter, 3);
  199. $sMaxInter = round($fMaxInter, 3);
  200. self::Report("<tr>");
  201. self::Report(" <td>$sHtmlArguments</td><td>$iCountInter</td><td>$sTotalInter</td><td>$sMinInter</td><td>$sMaxInter</td>");
  202. self::Report("</tr>");
  203. }
  204. self::Report("</table>");
  205. }
  206. }
  207. public function __construct()
  208. {
  209. $this->ResetCounters();
  210. }
  211. // Get the duration since startup, and reset the counter for the next measure
  212. //
  213. public function ComputeAndReport($sOperationDesc)
  214. {
  215. global $fItopStarted;
  216. $aNewEntry = null;
  217. if (self::$m_bEnabled_Duration)
  218. {
  219. $fStopped = MyHelpers::getmicrotime();
  220. $aNewEntry = array(
  221. 'op' => $sOperationDesc,
  222. 'time_begin' => $this->m_fStarted - $fItopStarted,
  223. 'time_end' => $fStopped - $fItopStarted,
  224. );
  225. // Reset for the next operation (if the object is recycled)
  226. $this->m_fStarted = $fStopped;
  227. }
  228. if (self::$m_bEnabled_Memory)
  229. {
  230. $iCurrentMemory = self::memory_get_usage();
  231. if (is_null($aNewEntry))
  232. {
  233. $aNewEntry = array('op' => $sOperationDesc);
  234. }
  235. $aNewEntry['mem_begin'] = $this->m_iInitialMemory;
  236. $aNewEntry['mem_end'] = $iCurrentMemory;
  237. if (function_exists('memory_get_peak_usage'))
  238. {
  239. $aNewEntry['mem_peak'] = memory_get_peak_usage();
  240. }
  241. // Reset for the next operation (if the object is recycled)
  242. $this->m_iInitialMemory = $iCurrentMemory;
  243. }
  244. if (!is_null($aNewEntry))
  245. {
  246. self::$m_aExecData[] = $aNewEntry;
  247. }
  248. $this->ResetCounters();
  249. }
  250. public function ComputeStats($sOperation, $sArguments)
  251. {
  252. if (self::$m_bEnabled_Duration)
  253. {
  254. $fStopped = MyHelpers::getmicrotime();
  255. $fDuration = $fStopped - $this->m_fStarted;
  256. self::$m_aStats[$sOperation][$sArguments][] = $fDuration;
  257. }
  258. }
  259. protected function ResetCounters()
  260. {
  261. if (self::$m_bEnabled_Duration)
  262. {
  263. $this->m_fStarted = MyHelpers::getmicrotime();
  264. }
  265. if (self::$m_bEnabled_Memory)
  266. {
  267. $this->m_iInitialMemory = self::memory_get_usage();
  268. }
  269. }
  270. const HtmlReportFile = 'log/kpi.html';
  271. static protected function Report($sText)
  272. {
  273. file_put_contents(APPROOT.self::HtmlReportFile, "$sText\n", FILE_APPEND | LOCK_EX);
  274. }
  275. static protected function MemStr($iMemory)
  276. {
  277. return round($iMemory / 1024).' Kb';
  278. }
  279. static protected function memory_get_usage()
  280. {
  281. if (function_exists('memory_get_usage'))
  282. {
  283. return memory_get_usage(true);
  284. }
  285. // Copied from the PHP manual
  286. //
  287. //If its Windows
  288. //Tested on Win XP Pro SP2. Should work on Win 2003 Server too
  289. //Doesn't work for 2000
  290. //If you need it to work for 2000 look at http://us2.php.net/manual/en/function.memory-get-usage.php#54642
  291. if (substr(PHP_OS,0,3) == 'WIN')
  292. {
  293. $output = array();
  294. exec('tasklist /FI "PID eq ' . getmypid() . '" /FO LIST', $output);
  295. return preg_replace( '/[\D]/', '', $output[5] ) * 1024;
  296. }
  297. else
  298. {
  299. //We now assume the OS is UNIX
  300. //Tested on Mac OS X 10.4.6 and Linux Red Hat Enterprise 4
  301. //This should work on most UNIX systems
  302. $pid = getmypid();
  303. exec("ps -eo%mem,rss,pid | grep $pid", $output);
  304. $output = explode(" ", $output[0]);
  305. //rss is given in 1024 byte units
  306. return $output[1] * 1024;
  307. }
  308. }
  309. static public function memory_get_peak_usage($bRealUsage = false)
  310. {
  311. if (function_exists('memory_get_peak_usage'))
  312. {
  313. return memory_get_peak_usage($bRealUsage);
  314. }
  315. // PHP > 5.2.1 - this verb depends on a compilation option
  316. return 0;
  317. }
  318. }