PageRenderTime 50ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/modules/profilertoolbar/classes/kohana/profilertoolbar.php

https://bitbucket.org/ekiwookie/juss
PHP | 637 lines | 449 code | 69 blank | 119 comment | 59 complexity | b94dd09ec16d24fb81e477183915cd5c MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php defined('SYSPATH') or die('No direct script access.');
  2. /**
  3. * @author Aleksey Ivanov <alertdevelop@gmail.com>
  4. * @see http://alertdevelop.ru/projects/profilertoolbar
  5. * @see https://github.com/Alert/profilertoolbar
  6. */
  7. class Kohana_ProfilerToolbar {
  8. public static $version = '0.2.8';
  9. public static $kohana_version = '3.2';
  10. private static $_cfg = null;
  11. /* @var FirePHP */
  12. private static $_fb = null;
  13. private static $_CACHE = array();
  14. private static $_SQL = array();
  15. private static $_CUSTOM = array();
  16. /**
  17. * using in HMVC for collect data from last request
  18. * @var string (request name)
  19. */
  20. private static $_data_collect_current_route = '';
  21. // loaded xdebug extension or not
  22. private static $_xdebug = null;
  23. // data for output
  24. public static $DATA_APP_TIME = null;
  25. public static $DATA_APP_MEMORY = null;
  26. public static $DATA_SQL = null;
  27. public static $DATA_CACHE = null;
  28. public static $DATA_POST = null;
  29. public static $DATA_GET = null;
  30. public static $DATA_FILES = null;
  31. public static $DATA_COOKIE = null;
  32. public static $DATA_SESSION = null;
  33. public static $DATA_SERVER = null;
  34. public static $DATA_ROUTES = null;
  35. public static $DATA_INC_FILES = null;
  36. public static $DATA_CUSTOM = null;
  37. /**
  38. * Collect all data
  39. * @static
  40. * @return void
  41. */
  42. private static function collectData(){
  43. if(Route::name(Request::current()->route()) == self::$_data_collect_current_route) return;
  44. self::$DATA_APP_TIME = self::getAppTime();
  45. self::$DATA_APP_MEMORY = self::getAppMemory();
  46. self::$DATA_SQL = self::getSql();
  47. self::$DATA_CACHE = self::getCache();
  48. self::$DATA_POST = self::getPost();
  49. self::$DATA_GET = self::getGet();
  50. self::$DATA_FILES = self::getFiles();
  51. self::$DATA_COOKIE = self::getCookie();
  52. self::$DATA_SESSION = self::getSession();
  53. self::$DATA_SERVER = self::getServer();
  54. self::$DATA_ROUTES = self::getRoutes();
  55. self::$DATA_INC_FILES = self::getIncFiles();
  56. self::$DATA_CUSTOM = self::getCustom();
  57. self::$_data_collect_current_route = Route::name(Request::current()->route());
  58. }
  59. /**
  60. * Render data to html
  61. * @static
  62. * @param bool $print - echo rendered data
  63. * @return string
  64. */
  65. public static function render($print = false){
  66. if(!self::cfg('html.enabled')) return '';
  67. self::collectData();
  68. $html = View::factory('PTB/tpl')->render();
  69. if($print) echo $html;
  70. return $html;
  71. }
  72. /**
  73. * Send data to FireBug
  74. * @static
  75. * @return void
  76. */
  77. public static function firebug(){
  78. if(!self::cfg('firebug.enabled')) return;
  79. self::collectData();
  80. self::$_fb = FirePHP::getInstance(true);
  81. // set FireBug settings
  82. $options = self::$_fb->getOptions();
  83. self::$_fb->setOption('maxObjectDepth',self::cfg('firebug.maxObjectDepth',8));
  84. self::$_fb->setOption('maxArrayDepth',self::cfg('firebug.maxArrayDepth',8));
  85. self::$_fb->setOption('maxDepth',self::cfg('firebug.maxDepth',10));
  86. // append info about module
  87. self::$_fb->info('========== ProfilerToolbar v'.self::$version.' for Kohana v'.self::$kohana_version.' ==========');
  88. // append other data
  89. if(self::cfg('firebug.showTotalInfo')) self::appendTotalInfo();
  90. if(self::cfg('firebug.showSql')) self::appendSql();
  91. if(self::cfg('firebug.showCache')) self::appendCache();
  92. if(self::cfg('firebug.showVars')) self::appendVars();
  93. if(self::cfg('firebug.showRoutes')) self::appendRoutes();
  94. if(self::cfg('firebug.showIncFiles')) self::appendIncFiles();
  95. if(self::cfg('firebug.showCustom')) self::appendCustom();
  96. // end
  97. self::$_fb->log('============================================================');
  98. }
  99. // =============== methods for collect data ======================================
  100. private static function getAppTime(){
  101. $tmp = Profiler::application();
  102. return $tmp['current']['time'];
  103. }
  104. private static function getAppMemory(){
  105. // $tmp = Profiler::application();
  106. // return $tmp['current']['memory'];
  107. return memory_get_peak_usage(true);
  108. }
  109. private static function getSql(){
  110. // calc explain
  111. if(self::cfg('html.showSqlExplain') && self::cfg('html.showSqlExplain')){
  112. foreach(self::$_SQL as $instance => $query){
  113. foreach($query as $sql => $data){
  114. if(stripos($sql,'select') === 0){
  115. $expl = Database::instance($instance)->query(Database::SELECT,'EXPLAIN '.$sql)->as_array();
  116. self::$_SQL[$instance][$sql]['explain'] = $expl;
  117. }
  118. }
  119. }
  120. }
  121. // collect data
  122. $sql = array();
  123. $groups = Profiler::groups();
  124. foreach ($groups as $groupName => $benchmarks) {
  125. if(strpos($groupName,'database') !== 0) continue;
  126. $sqlGroup = substr($groupName,strpos($groupName,'(')+1,strpos($groupName,')')-strpos($groupName,'(')-1);
  127. $sql[$sqlGroup] = array('data'=>array(),'total'=>array('time'=>0,'memory'=>0,'count'=>0));
  128. foreach ($benchmarks as $benchName => $tokens) {
  129. foreach ($tokens as $token) {
  130. $stats = Profiler::stats(array($token));
  131. $sql[$sqlGroup]['data'][] = array(
  132. 'sql' => $benchName,
  133. 'time' => $stats['total']['time'],
  134. 'memory' => $stats['total']['memory'],
  135. 'rows' => (isset(self::$_SQL[$sqlGroup][$benchName]))?self::$_SQL[$sqlGroup][$benchName]['rows']:null,
  136. 'explain' => (isset(self::$_SQL[$sqlGroup][$benchName]))?self::$_SQL[$sqlGroup][$benchName]['explain']:null,
  137. );
  138. $sql[$sqlGroup]['total']['time'] += $stats['total']['time'];
  139. $sql[$sqlGroup]['total']['memory'] += $stats['total']['memory'];
  140. $sql[$sqlGroup]['total']['count']++;
  141. }
  142. }
  143. }
  144. return $sql;
  145. }
  146. private static function getCache(){
  147. if(!isset(self::$_CACHE['total'])) self::$_CACHE['total'] = array('get'=>0,'set'=>0,'del'=>0);
  148. if(!isset(self::$_CACHE['data'])) {
  149. self::$_CACHE['data']['default'] = array(
  150. 'total' => array('get'=>0,'set'=>0,'del'=>0),
  151. 'data' => array(),
  152. );
  153. }
  154. return self::$_CACHE;
  155. }
  156. private static function getPost(){
  157. return (isset($_POST))?$_POST:array();
  158. }
  159. private static function getGet(){
  160. return (isset($_GET))?$_GET:array();
  161. }
  162. private static function getFiles(){
  163. $all = array();
  164. foreach ($_FILES as $k=>$file) {
  165. if(is_array($file['name'])){
  166. $count = count($file['name']);
  167. for($i=0;$i<$count;$i++){
  168. $all[$k." [$i]"] = array(
  169. 'name'=>$file['name'][$i],
  170. 'type'=>$file['type'][$i],
  171. 'tmp_name'=>$file['tmp_name'][$i],
  172. 'error'=>$file['error'][$i],
  173. 'size'=>$file['size'][$i]
  174. );
  175. }
  176. }else{
  177. $all[$k] = $file;
  178. }
  179. }
  180. return $all;
  181. }
  182. private static function getCookie(){
  183. return (isset($_COOKIE))?$_COOKIE:array();
  184. }
  185. private static function getSession(){
  186. return Session::instance()->as_array();
  187. }
  188. private static function getServer(){
  189. return (isset($_SERVER))?$_SERVER:array();
  190. }
  191. private static function getRoutes(){
  192. $res = array('data'=>array(),'total'=>array('count'=>0));
  193. $res['data'] = Route::all();
  194. $res['total']['count'] = count($res['data']);
  195. return $res;
  196. }
  197. private static function getIncFiles(){
  198. $files = get_included_files();
  199. $res = array('data'=>array(),'total'=>array('size'=>0,'lines'=>0,'count'=>0));
  200. foreach ($files as $file) {
  201. $size = filesize($file);
  202. $lines = substr_count(file_get_contents($file),"\n");
  203. $res['total']['size'] += $size;
  204. $res['total']['lines'] += $lines;
  205. $res['total']['count']++;
  206. $res['data'][] = array(
  207. 'name'=>$file,
  208. 'size'=>$size,
  209. 'lines'=>$lines,
  210. 'lastModified'=>filemtime($file),
  211. );
  212. }
  213. return $res;
  214. }
  215. private static function getCustom(){
  216. return self::$_CUSTOM;
  217. }
  218. // =============== /methods for collect data ======================================
  219. // =============== methods for append data to Firebug =============================
  220. private static function appendTotalInfo(){
  221. self::$_fb->log('[AppTime: '.self::formatTime(self::$DATA_APP_TIME).'] [AppMemory: '.self::formatMemory(self::$DATA_APP_MEMORY).']');
  222. }
  223. private static function appendSql(){
  224. $sql = array('count'=>0,'time'=>0,'memory'=>0);
  225. foreach(self::$DATA_SQL as $inst) {
  226. $sql['count'] += $inst['total']['count'];
  227. $sql['time'] += $inst['total']['time'];
  228. $sql['memory'] += $inst['total']['memory'];
  229. }
  230. self::$_fb->group('SQL [count: '.$sql['count'].'] [time: '.self::formatTime($sql['time']).'] [memory: '.self::formatMemory($sql['memory']).']',array('Collapsed'=>true));
  231. $tbl = array(0=>array('â„–','query','rows','time','memory'));
  232. $tbl_explain = array();
  233. $num = 0;
  234. foreach(self::$DATA_SQL as $inst){
  235. foreach ($inst['data'] as $q) {
  236. $tbl[] = array(++$num,$q['sql'],$q['rows'],self::formatTime($q['time'],true,false),self::formatMemory($q['memory']));
  237. if(!empty($q['explain'])){
  238. $tbl_explain[$num] = array(0=>array('id','select_type','table','type','possible_keys','key','key_len','ref','rows','Extra'));
  239. foreach($q['explain'] as $val){
  240. $tbl_explain[$num][] = array($val['id'],$val['select_type'],$val['table'],$val['type'],$val['possible_keys'],$val['key'],$val['key_len'],$val['ref'],$val['rows'],$val['Extra']);
  241. }
  242. }
  243. }
  244. }
  245. self::$_fb->table('SQL queries',$tbl);
  246. if(self::cfg('firebug.showSqlExplain')){
  247. self::$_fb->group('Explains',array('Collapsed'=>true));
  248. foreach($tbl_explain as $num=>$tbl){
  249. self::$_fb->table('EXPLAIN for query â„– '.$num,$tbl);
  250. }
  251. self::$_fb->groupEnd();
  252. }
  253. self::$_fb->groupEnd();
  254. unset($sql);
  255. }
  256. private static function appendCache(){
  257. self::$_fb->group('Cache [GET: '.self::$DATA_CACHE['total']['get'].'] [SET: '.self::$DATA_CACHE['total']['set'].'] [DEL: '.self::$DATA_CACHE['total']['del'].']',array('Collapsed'=>true));
  258. $num = 0;
  259. foreach(self::$DATA_CACHE['data'] as $instance=>$data){
  260. $tbl = array(0=>array('â„–','action','id','lifetime'));
  261. foreach($data['data'] as $val){
  262. $tbl[] = array(++$num,$val['action'],$val['id'],$val['lifetime']);
  263. }
  264. self::$_fb->table($instance,$tbl);
  265. }
  266. self::$_fb->groupEnd();
  267. }
  268. private static function appendVars(){
  269. $count = array(
  270. 'post'=>count(self::$DATA_POST),
  271. 'get'=>count(self::$DATA_GET),
  272. 'files'=>count(self::$DATA_FILES),
  273. 'cookie'=>count(self::$DATA_COOKIE),
  274. 'session'=>count(self::$DATA_SESSION),
  275. );
  276. self::$_fb->group("Vars [POST: {$count['post']}] [GET: {$count['get']}] [FILES: {$count['files']}] [COOKIE: {$count['cookie']}] [SESSION: {$count['session']}]",array('Collapsed'=>true));
  277. self::$_fb->log(self::$DATA_POST,'POST ('.$count['post'].')');
  278. self::$_fb->log(self::$DATA_GET,'GET ('.$count['get'].')');
  279. self::$_fb->log(self::$DATA_FILES,'FILES ('.$count['files'].')');
  280. self::$_fb->log(self::$DATA_COOKIE,'COOKIE ('.$count['cookie'].')');
  281. self::$_fb->log(self::$DATA_SESSION,'SESSION ('.$count['session'].')');
  282. self::$_fb->log(self::$DATA_SERVER,'SERVER');
  283. self::$_fb->groupEnd();
  284. unset($count);
  285. }
  286. private static function appendRoutes(){
  287. $tbl = array(0=>array('â„–','name','controller','action','params'));
  288. $num = $useNum = 0;
  289. foreach(ProfilerToolbar::$DATA_ROUTES['data'] as $name=>$route){
  290. if(Request::$current->route() == $route){
  291. $params = array();
  292. foreach(Request::$current->param() as $k=>$v) $params[] = $k.': '.$v;
  293. $tbl[] = array(++$num,'✔ '.$name,Request::$current->controller(),Request::$current->action(),implode('; ',$params));
  294. $useNum = $num;
  295. }else{
  296. $tbl[] = array(++$num,$name,'','','');
  297. }
  298. }
  299. $useRouteStr = "{$tbl[$useNum][1]} | controller: {$tbl[$useNum][2]} | action: {$tbl[$useNum][3]} | params: {$tbl[$useNum][4]}";
  300. self::$_fb->group('Routes ('.self::$DATA_ROUTES['total']['count'].') '.$useRouteStr,array('Collapsed'=>true));
  301. self::$_fb->table('Routes',$tbl);
  302. self::$_fb->groupEnd();
  303. }
  304. private static function appendIncFiles(){
  305. self::$_fb->group('Files ('.self::$DATA_INC_FILES['total']['count'].')',array('Collapsed'=>true));
  306. $tbl = array(0=>array('â„–','file','size','lines','last modified'));
  307. $num = 0;
  308. foreach (self::$DATA_INC_FILES['data'] as $file) {
  309. $tbl[] = array(++$num,$file['name'],ProfilerToolbar::formatMemory($file['size']),number_format($file['lines']),date("Y.m.d H:i:s",$file['lastModified']));
  310. }
  311. self::$_fb->table('Files',$tbl);
  312. self::$_fb->groupEnd();
  313. }
  314. private static function appendCustom(){
  315. $custom_count = 0;
  316. foreach(ProfilerToolbar::$DATA_CUSTOM as $v) $custom_count += count($v);
  317. self::$_fb->group('YOUR ('.$custom_count.')',array('Collapsed'=>true));
  318. foreach(self::$DATA_CUSTOM as $k=>$data){
  319. self::$_fb->group($k,array('Collapsed'=>true));
  320. $num = 0;
  321. foreach($data as $item){
  322. self::$_fb->log($item,++$num);
  323. }
  324. self::$_fb->groupEnd();
  325. }
  326. self::$_fb->groupEnd();
  327. }
  328. // =============== /methods for append data to Firebug =============================
  329. /**
  330. * Collect sql queries
  331. * Used in database classes
  332. * @static
  333. * @param $instance
  334. * @param $sql
  335. * @param null $rows
  336. * @return void
  337. */
  338. public static function setSqlData($instance,$sql,$rows = null){
  339. self::$_SQL[$instance][$sql] = array(
  340. 'rows' => $rows,
  341. 'explain' => null,
  342. );
  343. }
  344. /**
  345. * Collect Cache log item
  346. * Used in Cache classes
  347. * @static
  348. * @param $action
  349. * @param $instalce
  350. * @param $id
  351. * @param null $lifetime
  352. * @return void
  353. */
  354. public static function cacheLog($action,$instalce,$id,$lifetime = null){
  355. if(!in_array($action,array('get','set','del'))) return;
  356. self::$_CACHE['data'][$instalce]['data'][] = array(
  357. 'action'=>$action,
  358. 'id'=>$id,
  359. 'lifetime'=>$lifetime
  360. );
  361. if(!isset(self::$_CACHE['total'])) self::$_CACHE['total'] = array('get'=>0,'set'=>0,'del'=>0);
  362. if(!isset(self::$_CACHE['data'][$instalce]['total'])) self::$_CACHE['data'][$instalce]['total'] = array('get'=>0,'set'=>0,'del'=>0);
  363. self::$_CACHE['total'][$action]++;
  364. self::$_CACHE['data'][$instalce]['total'][$action]++;
  365. }
  366. /**
  367. * Add YOUR custom data
  368. * @static
  369. * @param $data mixed
  370. * @param string $tabName
  371. * @return void
  372. */
  373. public static function addData($data,$tabName = 'default'){
  374. $tabName = URL::title($tabName);
  375. self::$_CUSTOM[$tabName][] = $data;
  376. }
  377. /**
  378. * Get module config param
  379. * @static
  380. * @param string $param
  381. * @param bool $default
  382. * @return mixed
  383. */
  384. public static function cfg($param = '',$default = false){
  385. if(self::$_cfg === null) self::$_cfg = Kohana::$config->load('profilertoolbar');
  386. if(empty($param)) return self::$_cfg;
  387. return Arr::path(self::$_cfg,$param,$default);
  388. }
  389. // ============================= help functions ==========================================
  390. public static function formatTime($time, $addUnit = true, $spaceBeforeUnit = true){
  391. $decimals = 6;
  392. if(($p = self::cfg('format.time')) == 'ms') {$time *= 1000; $decimals = 3;}
  393. $res = number_format($time,$decimals);
  394. if($addUnit) $res .= ($spaceBeforeUnit)?' '.$p:$p;
  395. return $res;
  396. }
  397. public static function formatMemory($memory, $addUnit = true, $spaceBeforeUnit = true){
  398. if(($p = self::cfg('format.memory')) == 'kb') $memory /= 1024;
  399. else $memory /= 1024*1024;
  400. $res = number_format($memory);
  401. if($addUnit) $res .= ($spaceBeforeUnit)?' '.$p:$p;
  402. return $res;
  403. }
  404. /**
  405. * is xdebug loaded or not
  406. * @static
  407. * @return bool
  408. */
  409. private static function isXdebug(){
  410. if(self::$_xdebug === null){
  411. self::$_xdebug = extension_loaded('xdebug');
  412. }
  413. return self::$_xdebug;
  414. }
  415. /**
  416. * Override system var_dump
  417. * @static
  418. * @param $var
  419. * @return string
  420. */
  421. public static function varDump($var){
  422. if(self::isXdebug()){
  423. ob_start();
  424. var_dump($var);
  425. return ob_get_clean();
  426. }
  427. if(is_bool($var)) return ($var)?'true':'false';
  428. elseif(is_scalar($var)) return HTML::chars($var);
  429. else{
  430. ob_start();
  431. var_dump($var);
  432. $data = ob_get_clean();
  433. $data = preg_replace('/=>\n\s+/', ' => ',$data);
  434. $data = HTML::chars($data);
  435. return '<pre>'.$data.'</pre>';
  436. }
  437. }
  438. /**
  439. * Highlite code
  440. * @static
  441. * @param $source
  442. * @param $lang (sql|php)
  443. * @return string
  444. */
  445. public static function highlight($source, $lang){
  446. if(!in_array($lang,array('sql','php'))) return $source;
  447. $geshi = new GeSHi($source,$lang);
  448. $geshi->enable_classes();
  449. $geshi->enable_keyword_links(false);
  450. $res = $geshi->parse_code();
  451. $res = str_replace('<pre class="'.$lang.'">','',$res);
  452. $res = str_replace('</pre>','',$res);
  453. $res = trim(rtrim($res),"\n");
  454. return $res;
  455. }
  456. /**
  457. * get part of file source by line
  458. * and can highlite it
  459. * @static
  460. * @param $file
  461. * @param $line_number
  462. * @param int $padding
  463. * @param bool $highlite
  464. * @param string $lang
  465. * @return string
  466. */
  467. public static function debugSource($file, $line_number, $padding = 5, $highlite = false, $lang = ''){
  468. if(!$file OR !is_readable($file)) return '';
  469. $file = fopen($file, 'r');
  470. $line = 0;
  471. $range = array('start' => $line_number - $padding, 'end' => $line_number + $padding);
  472. $format = '% '.strlen($range['end']).'d';
  473. $source = '';
  474. while(($row = fgets($file)) !== FALSE){
  475. if(++$line > $range['end']) break;
  476. if($line >= $range['start']) $source .= $row;
  477. }
  478. fclose($file);
  479. if($highlite){
  480. $source = self::highlight($source,$lang);
  481. }
  482. $source = explode("\n",$source);
  483. $line = $range['start'];
  484. $result = '';
  485. foreach($source as $row){
  486. $hRow = ($line == $line_number)?' highlight':'';
  487. if($line === $line_number){
  488. self::addData($row,'sec');
  489. }
  490. $result .= '<span class="line'.$hRow.'"><span class="num">'.sprintf($format, $line).' </span>'.$row.'</span>';
  491. $line++;
  492. }
  493. return $result;
  494. }
  495. /**
  496. * get source by line in all files of trace
  497. * @static
  498. * @param array|null $trace
  499. * @param bool $highlight
  500. * @param string $lang
  501. * @return array
  502. */
  503. public static function debugTrace(array $trace = NULL, $highlight = false, $lang = ''){
  504. if ($trace === NULL) $trace = debug_backtrace();
  505. // Non-standard function calls
  506. $statements = array('include', 'include_once', 'require', 'require_once');
  507. $output = array();
  508. foreach($trace as $step){
  509. if(!isset($step['function'])) continue;
  510. if(isset($step['file']) AND isset($step['line'])){
  511. // Include the source of this step
  512. $source = self::debugSource($step['file'], $step['line'],5,$highlight,$lang);
  513. }
  514. if(isset($step['file'])){
  515. $file = $step['file'];
  516. if(isset($step['line'])){
  517. $line = $step['line'];
  518. }
  519. }
  520. // function()
  521. $function = $step['function'];
  522. if(in_array($step['function'], $statements)){
  523. if(empty($step['args'])) $args = array();
  524. else $args = array($step['args'][0]);
  525. }elseif(isset($step['args'])){
  526. if(!function_exists($step['function']) OR strpos($step['function'], '{closure}') !== FALSE){
  527. // Introspection on closures or language constructs in a stack trace is impossible
  528. $params = NULL;
  529. }else{
  530. if(isset($step['class'])){
  531. if(method_exists($step['class'], $step['function'])){
  532. $reflection = new ReflectionMethod($step['class'], $step['function']);
  533. }else{
  534. $reflection = new ReflectionMethod($step['class'], '__call');
  535. }
  536. }else{
  537. $reflection = new ReflectionFunction($step['function']);
  538. }
  539. // Get the function parameters
  540. $params = $reflection->getParameters();
  541. }
  542. $args = array();
  543. foreach($step['args'] as $i => $arg){
  544. if(isset($params[$i])){
  545. // Assign the argument by the parameter name
  546. $args[$params[$i]->name] = $arg;
  547. }else{
  548. // Assign the argument by number
  549. $args[$i] = $arg;
  550. }
  551. }
  552. }
  553. if(isset($step['class'])){
  554. // Class->method() or Class::method()
  555. $function = $step['class'].$step['type'].$step['function'];
  556. }
  557. $output[] = array(
  558. 'function' => $function,
  559. 'args' => isset($args)?$args:NULL,
  560. 'file' => isset($file)?$file:NULL,
  561. 'line' => isset($line)?$line:NULL,
  562. 'source' => isset($source)?$source:NULL,
  563. );
  564. unset($function, $args, $file, $line, $source);
  565. }
  566. return $output;
  567. }
  568. }