├── .env.example ├── .gitignore ├── README.md ├── app ├── App.php ├── Controllers │ ├── Controller.php │ └── Home │ │ └── Index.php ├── Exceptions │ └── Handler.php ├── Helpers │ ├── Config.php │ ├── DebugBar.php │ ├── Helper.php │ ├── Log.php │ ├── Repository.php │ └── function.php ├── Middlewares │ └── AuthMiddleware.php ├── Models │ ├── Models.php │ └── User.php ├── Routes │ └── web.php └── Routing │ └── Router.php ├── composer.json ├── index.php ├── mdcalls ├── Basic │ ├── Exception.php │ └── MdCallsBasic.php ├── Config │ ├── app.php │ ├── database.php │ ├── queue.php │ └── redis.php ├── MdCalls.php └── Service │ └── Kick │ ├── Child │ ├── Queue.php │ ├── Queue │ │ ├── Job.php │ │ └── Worker.php │ └── Storage.php │ └── KickIndex.php ├── public ├── index.php ├── views │ ├── Admin │ │ └── index.html │ └── Home │ │ └── index.html └── xhprof │ ├── xhprof_html │ ├── callgraph.php │ ├── css │ │ └── xhprof.css │ ├── docs │ │ ├── index-fr.html │ │ ├── index.html │ │ ├── sample-callgraph-image.jpg │ │ ├── sample-diff-report-flat-view.jpg │ │ ├── sample-diff-report-parent-child-view.jpg │ │ ├── sample-flat-view.jpg │ │ └── sample-parent-child-view.jpg │ ├── index.php │ ├── jquery │ │ ├── indicator.gif │ │ ├── jquery-1.2.6.js │ │ ├── jquery.autocomplete.css │ │ ├── jquery.autocomplete.js │ │ ├── jquery.tooltip.css │ │ └── jquery.tooltip.js │ ├── js │ │ └── xhprof_report.js │ └── typeahead.php │ └── xhprof_lib │ ├── display │ ├── typeahead_common.php │ └── xhprof.php │ └── utils │ ├── callgraph_utils.php │ ├── xhprof_lib.php │ └── xhprof_runs.php └── storage ├── cache └── .gitignore └── logs └── .gitignore /.env.example: -------------------------------------------------------------------------------- 1 | # web 环境是否为 debug 2 | APP_DEBUG=false 3 | 4 | DB_CONNECTION=default.master 5 | DB_HOST=192.168.10.10 6 | DB_PORT=3306 7 | DB_DATABASE=geermall_psbildr 8 | DB_USERNAME=homestead 9 | DB_PASSWORD=secret 10 | 11 | REDIS_HOST=127.0.0.1 12 | REDIS_PASSWORD=null 13 | REDIS_PORT=6379 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/* 2 | .DS_Store 3 | .idea/* 4 | composer.lock 5 | .env 6 | storage/views/ 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KickPeach 2 | 3 | > 一个提供模块化服务的现代PHP小框架 4 | 5 | 6 | 正如在框架设计的初心上所说,市面上已经那么多的框架了,还造什么轮子,再次重申一下。造轮子不是目的,造轮子的过程中汲取到知识才是目的,共勉。 7 | 8 | 框架的使用文档以及组件文档可访问[https://kickpeach.github.io/](https://kickpeach.github.io/) 9 | 10 | ## 框架示例网站 11 | 12 | [http://wdzs.xhzyxed.cn/](http://wdzs.xhzyxed.cn/) 13 | 14 | 15 | ## 框架的组织方式 16 | 17 | kickpeach项目目录参考Laravel的文件组织方式进行组织,采取组件化开发,灵活扩展升级每个组件,主要由以下几个组件完成: 18 | 19 | 可查看[组织](https://github.com/KickPeach)进行源码的阅读 20 | 21 | ```cmd 22 | Kickpeach  23 | kickPeach----> 最终可以使用的框架,也就是当前所在项目 24 | framework----> 框架的核心文件 25 | queue----> 任务队列组件 26 | database----> 数据库组件 27 | mdCalls----> 提供模块化服务的组件 28 | kickpeach.github.io---->框架的使用文档 29 | ``` 30 | 31 | 其他模块都有模块的文档 32 | 33 | ## 框架的设计思路 34 | 35 | ```cmd 36 | 37 | 入口文件 38 | ----> 注册错误(和异常)处理函数 39 | ----> 加载配置文件 40 | ----> 请求 41 | ----> 路由  42 | ---->中间件 43 | ---->(控制器 <----> 数据模型) 44 | ----> 响应 45 | ----> 视图渲染数据 46 | ``` 47 | 48 | - 阅读源码的方式 49 | 50 | composer create kickpeach/kickpeach testkickpeach,composer update下载载源码下来之后,按照上面流程,可以看到框架源码的具体实现,从index.php入口看起。kickpeach只是framework的扩展,基于framework,完全可以自定义有自己风格的框架。 51 | 核心源码都在framework,在看源码的时候,你就会深入了解到什么叫self,static,继承,抽象,与接口。 52 | 53 | - 模块化组织 54 | 个人觉得Laravel的容器太重,虽然好用,但是还是不太想用这个,因此,这里使用了MdCalls组件来解决依赖方式,可见框架中mdcalls目录,mdcalls支持rpc与框架内调用,内置了database,queue以及redis服务,具体使用方式可见文档 55 | 56 | - 自动加载 57 | 58 | 使用composer和命名空间,遵循psr-4规范 59 | 60 | - 异常模块 61 | 62 | 错误捕获,异常捕获,让错误可以被异常一样捕捉,并且友好的提示错误,关键函数set_error_handler,set_exception_handler,register_shutdown_function 63 | 64 | - 路由 65 | 66 | 解析请求,基于fastroute组件开发,支持自定义路由以及默认路由,为了不占内存,缓存了路由,获得待处理的的模块,控制器和方法 67 | 68 | - 控制器 69 | 70 | 获取每个控制器定义的中间件,先经过中间件解析处理,最后才控制器 71 | 72 | - 中间件 73 | 74 | 借鉴了Laravel的pipeline编写了路由中间件 75 | 76 | - 响应 77 | 78 | 模板引擎使用twig组件,支持变量赋值以及模板,支持json响应 79 | 80 | - 数据库 81 | 82 | 采用第三方database组件方式,利用mdcalls提供服务 83 | 84 | - 其他友好函数 85 | 86 | 日志工具类,文件读取文件,日志读取类,配置文件读取类 87 | 88 | - queue 89 | 90 | 任务队列组件,支持延时队列以及失败任务重试 91 | 92 | ## TODO 93 | 94 | - [x] 请求参数的验证模块 95 | - [x] 集成[php-debugbar](https://github.com/maximebf/php-debugbar) 96 | - [x] 使用kickpeach做一个demo [http://wdzs.xhzyxed.cn/](http://wdzs.xhzyxed.cn/) 97 | 98 | ## 帮助kickpeach改进 99 | 100 | 欢迎给Kickpeach提issue:[https://github.com/KickPeach/kickPeach/issues](https://github.com/KickPeach/kickPeach/issues) 101 | 102 | 103 | # License 104 | 105 | The MIT License (MIT). 106 | -------------------------------------------------------------------------------- /app/App.php: -------------------------------------------------------------------------------- 1 | true, 30 | 'Version' => 'KPramework/1.0', 31 | 'timezone' => 'PRC', 32 | 'view' => [ 33 | 'cache' => __DIR__ . '/../storage/views', 34 | ], 35 | 'xhprof_dir' => __DIR__ . '/../public/xhprof', 36 | 37 | ]; 38 | 39 | protected function __construct() 40 | { 41 | parent::__construct(); 42 | $this->mdc = MdCalls::getInstance(); 43 | $this->config = array_merge($this->config,$this->mdc->Kick->getConf('app')); 44 | 45 | $this->debugbar = new StandardDebugBar(); 46 | 47 | $this->debugbaRender = $this->debugbar->getJavascriptRenderer(); 48 | } 49 | 50 | public function getMdc() 51 | { 52 | return $this->mdc; 53 | } 54 | 55 | protected function setExceptionsHandler() 56 | { 57 | return new Handler($this->config('debug'), '', $this->config('Version')); 58 | } 59 | 60 | protected function initRouter() 61 | { 62 | $this->router = new Router(dirname(__DIR__) . '/storage/cache/route.cache'); 63 | } 64 | 65 | //全局中间件 66 | protected $middleware = [ 67 | // \Kickpeach\Framework\Foundation\Middleware\Xhprof::class, 68 | ]; 69 | } -------------------------------------------------------------------------------- /app/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | db = $app->getMdc()->Kick->loadDB(); 25 | $this->redis = $app->getMdc()->Kick->loadRedis(); 26 | $this->mdc = $app->getMdc(); 27 | } 28 | 29 | //模板渲染 30 | protected function render($tpl,$data=[]){ 31 | $loader = new \Twig_Loader_Filesystem(__DIR__.'/../../public/views'); 32 | $twig = new \Twig_Environment($loader,[ 33 | 'cache'=>$this->app->config('view.cache'), 34 | 'debug'=>$this->app->config('debug') 35 | ]); 36 | 37 | return $twig->load($tpl)->render($data); 38 | } 39 | 40 | //获取db实例 41 | protected function db() 42 | { 43 | return $this->db; 44 | } 45 | 46 | //获取redis实例 47 | protected function redis() 48 | { 49 | return $this->redis; 50 | } 51 | 52 | //获取mdc实例 53 | protected function getMdc() 54 | { 55 | return $this->mdc; 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /app/Controllers/Home/Index.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'App\Middlewares\AuthMiddleware' 21 | ] 22 | ]; 23 | 24 | public function index() 25 | { 26 | try { 27 | $data = Validate::validated(['name'=>''],['name'=>'required']); 28 | }catch (ValidateException $exception){ 29 | Helpers\Helper::fail(101,$exception->getMessage()); 30 | } 31 | $name = 'KickPeach'; 32 | return $this->render('/Home/index.html',compact('name')); } 33 | 34 | public function test() 35 | { 36 | 37 | Helpers\Helper::success(['name'=>'seven']); 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /app/Exceptions/Handler.php: -------------------------------------------------------------------------------- 1 | debug = $debug; 22 | parent::__construct($collapseDir, $cfVersion); 23 | } 24 | 25 | /** 26 | * 错误的日志记录 27 | */ 28 | protected function report($e) 29 | { 30 | $content = $this->getLogOfException($e); 31 | 32 | Log::error($content); 33 | } 34 | 35 | /** 36 | * @param $e 37 | * 渲染错误页面 38 | */ 39 | protected function renderWebException($e) 40 | { 41 | if ($this->debug){ 42 | parent::renderWebException($e); 43 | }else{ 44 | $this->renderError($e); 45 | } 46 | } 47 | 48 | protected function renderError($e) 49 | { 50 | $statusCode = 500; 51 | if ($e instanceof HttpException){ 52 | $statusCode = $e->getStatusCode(); 53 | } 54 | 55 | if (!headers_sent()){ 56 | header("HTTP/1.0 {$statusCode} " . 'Internal Server Error'); 57 | } 58 | echo << 60 | *{ padding: 0; margin: 0; } 61 | body{padding: 24px 48px; background: #fff; font-family: "微软雅黑"; color: #333;} 62 | h1{ font-size: 90px; font-weight: normal;} 63 | p{ line-height: 1.8em; font-size: 24px } 64 | 65 |

:(

66 |

哦豁,服务器开小差了.

67 | EOF; 68 | } 69 | 70 | 71 | } -------------------------------------------------------------------------------- /app/Helpers/Config.php: -------------------------------------------------------------------------------- 1 | configDir = realpath($configDir); 39 | $this->repository = new Repository; 40 | } 41 | /** 42 | * 获取配置 43 | * 44 | * @param $item string 配置选项 45 | * @param mixed $default 默认值 46 | * 47 | * @return mixed 48 | */ 49 | public function get($item, $default = null) 50 | { 51 | $segments = explode('.', $item, 2); 52 | $this->load($segments[0]); 53 | return $this->repository->get($item, $default); 54 | } 55 | /** 56 | * 动态修改配置 57 | * 58 | * @param $name string 如 'main', 59 | * @return void 60 | */ 61 | protected function load($name) 62 | { 63 | if (isset($this->loadedConfigurations[$name])) { 64 | return; 65 | } 66 | $this->loadedConfigurations[$name] = true; 67 | $file = $this->configDir . '/' . $name . '.php'; 68 | $this->repository->set($name, require $file); 69 | } 70 | /** 71 | * 动态修改配置 72 | * 73 | * @param $item string 74 | * @param $config mixed 配置值 75 | * @return void 76 | */ 77 | public function set($item, $config) 78 | { 79 | $segments = explode('.', $item, 2); 80 | $this->load($segments[0]); 81 | $this->repository->set($item, $config); 82 | } 83 | } -------------------------------------------------------------------------------- /app/Helpers/DebugBar.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | debugbaRender->renderHead() ?> 6 | 7 | 8 | 9 | 10 | 11 | debugbaRender->render() ?> 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/Helpers/Helper.php: -------------------------------------------------------------------------------- 1 | 1 && $value[0] === '"' && $value[$valueLength - 1] === '"') { 45 | return substr($value, 1, -1); 46 | } 47 | return $value; 48 | } 49 | 50 | public static function getAllHeader() 51 | { 52 | $headers = []; 53 | foreach ($_SERVER as $name => $value) 54 | { 55 | if (substr($name, 0, 5) == 'HTTP_') 56 | { 57 | $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; 58 | } 59 | } 60 | return $headers; 61 | } 62 | 63 | public static function getUserAgent() 64 | { 65 | $headers = self::getAllHeader(); 66 | $userAgent = isset($headers['User-Agent']) ? $headers['User-Agent'] : (isset($headers['user-agent']) ? $headers['user-agent'] : ''); 67 | 68 | if (empty($userAgent)) 69 | { 70 | return null; 71 | } 72 | 73 | $ua = array(); 74 | $ua['browser'] = $userAgent; 75 | $type = ""; 76 | if (strpos($userAgent, 'MSIE') != false) 77 | { 78 | $ua['ua'] = 'IE'; 79 | //$ua['version'] = self::findUserAgentByKey($userAgent, ); 80 | $type = "MSIE"; 81 | } 82 | else if (strpos($userAgent, 'Gecko/') != false) 83 | { 84 | $ua['ua'] = 'firefox'; 85 | $type = 'Firefox/'; 86 | } 87 | else if (strpos($userAgent, 'AppleWebKit/') != false) 88 | { 89 | if (strpos($userAgent, 'Chrome') != false) 90 | { 91 | $ua['ua'] = 'chrome'; 92 | $type = 'Chrome/'; 93 | } 94 | else 95 | { 96 | $ua['ua'] = 'safari'; 97 | $type = 'Version/'; 98 | } 99 | } 100 | else if (strpos($userAgent, 'Presto/') != false) 101 | { 102 | $ua['ua'] = 'opera'; 103 | $type = 'Opera/'; 104 | } 105 | else if (strpos($userAgent, 'Trident') != false) 106 | { 107 | $ua['ua'] = 'IE'; 108 | $type = 'rv:'; 109 | } 110 | else if (strpos($userAgent, 'MobileSafari') != false) 111 | { 112 | $ua['ua'] = 'MobileSafari'; 113 | $type = 'MobileSafari/'; 114 | } 115 | else 116 | { 117 | $ua['ua'] = 'unknown'; 118 | } 119 | if ($type != '') 120 | { 121 | $ua['version'] = self::findUserAgentByKey($userAgent, $type); 122 | } 123 | return $ua; 124 | } 125 | 126 | public static function findUserAgentByKey($ua, $key) 127 | { 128 | $ua .= " "; 129 | $len = strlen($key); 130 | $start = strpos($ua, $key); 131 | $pos = strpos($ua, ' ', $start + $len + 1); 132 | $version = substr($ua, $start + $len, $pos - $len - $start); 133 | return str_replace(array(';', ')'), '', $version); 134 | } 135 | 136 | public static function isAjaxRequest() 137 | { 138 | return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH']==='XMLHttpRequest'; 139 | 140 | } 141 | 142 | public static function redirect($url) 143 | { 144 | header('Location: '.$url); 145 | exit(); 146 | } 147 | 148 | private static function json($code, $msg, $data = null, $return = false) 149 | { 150 | $result = ['code' => $code, 'msg' => $msg, 'now' => date('Y-m-d H:i:s')]; 151 | 152 | if ($data !== null) 153 | $result['data'] = $data; 154 | 155 | $json = json_encode($result); 156 | 157 | if($json === false && json_last_error() != JSON_ERROR_NONE) { 158 | throw new \Exception(json_last_error_msg()); 159 | } 160 | 161 | if ($return) return $json; 162 | 163 | $ua = \App\Helpers\Helper::getUserAgent(); 164 | 165 | if (isset($ua['ua']) && $ua['ua'] == 'IE' && !self::isAjaxRequest()) 166 | header('Content-type: text/plain'); 167 | else 168 | header('Content-type: application/json'); 169 | 170 | echo $json; 171 | exit(); 172 | } 173 | 174 | public static function success($data, $return = false) 175 | { 176 | return self::json(0, 'success', $data, $return); 177 | } 178 | 179 | public static function fail($code, $msg, $return = false) 180 | { 181 | return self::json($code, $msg, null, $return); 182 | } 183 | } -------------------------------------------------------------------------------- /app/Helpers/Log.php: -------------------------------------------------------------------------------- 1 | items = $items; 31 | } 32 | /** 33 | * Determine if the given configuration value exists. 34 | * 35 | * @param string $key 36 | * @return bool 37 | */ 38 | public function has($key) 39 | { 40 | return Arr::has($this->items, $key); 41 | } 42 | /** 43 | * Get all of the configuration items for the application. 44 | * 45 | * @return array 46 | */ 47 | public function all() 48 | { 49 | return $this->items; 50 | } 51 | /** 52 | * Get the specified configuration value. 53 | * 54 | * @param string $key 55 | * @param mixed $default 56 | * @return mixed 57 | */ 58 | public function get($key = null, $default = null) 59 | { 60 | return Arr::get($this->items, $key, $default); 61 | } 62 | /** 63 | * Set a given configuration value. 64 | * 65 | * @param array|string $key 66 | * @param mixed $value 67 | * @return void 68 | */ 69 | public function set($key, $value = null) 70 | { 71 | if (is_array($key)) { 72 | foreach ($key as $innerKey => $innerValue) { 73 | Arr::set($this->items, $innerKey, $innerValue); 74 | } 75 | } else { 76 | Arr::set($this->items, $key, $value); 77 | } 78 | } 79 | /** 80 | * Prepend a value onto an array configuration value. 81 | * 82 | * @param string $key 83 | * @param mixed $value 84 | * @return void 85 | */ 86 | public function prepend($key, $value) 87 | { 88 | $array = $this->get($key); 89 | array_unshift($array, $value); 90 | $this->set($key, $array); 91 | } 92 | /** 93 | * Push a value onto an array configuration value. 94 | * 95 | * @param string $key 96 | * @param mixed $value 97 | * @return void 98 | */ 99 | public function push($key, $value) 100 | { 101 | $array = $this->get($key); 102 | $array[] = $value; 103 | $this->set($key, $array); 104 | } 105 | /** 106 | * Determine if the given configuration option exists. 107 | * 108 | * @param string $key 109 | * @return bool 110 | */ 111 | public function offsetExists($key) 112 | { 113 | return $this->has($key); 114 | } 115 | /** 116 | * Get a configuration option. 117 | * 118 | * @param string $key 119 | * @return mixed 120 | */ 121 | public function offsetGet($key) 122 | { 123 | return $this->get($key); 124 | } 125 | /** 126 | * Set a configuration option. 127 | * 128 | * @param string $key 129 | * @param mixed $value 130 | * @return void 131 | */ 132 | public function offsetSet($key, $value) 133 | { 134 | $this->set($key, $value); 135 | } 136 | /** 137 | * Unset a configuration option. 138 | * 139 | * @param string $key 140 | * @return void 141 | */ 142 | public function offsetUnset($key) 143 | { 144 | $this->set($key, null); 145 | } 146 | } -------------------------------------------------------------------------------- /app/Helpers/function.php: -------------------------------------------------------------------------------- 1 | db = MdCalls::getInstance()->Kick->loadDB(); 19 | } 20 | 21 | public function db() 22 | { 23 | return $this->db; 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /app/Models/User.php: -------------------------------------------------------------------------------- 1 | db()->select('select * from users order by id desc limit 1'); 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /app/Routes/web.php: -------------------------------------------------------------------------------- 1 | any('/home/{id:[0-9]+}/user/{uid}', [ 10 | // 'uses'=> 'Home\Index@index', 11 | //]); -------------------------------------------------------------------------------- /app/Routing/Router.php: -------------------------------------------------------------------------------- 1 | =5.6", 14 | "kickpeach/framework": "dev-master", 15 | "twig/twig": "~2.0", 16 | "kickpeach/database": "dev-master", 17 | "kickpeach/queue": "dev-master", 18 | "kickpeach/mdcalls": "dev-master", 19 | "vlucas/phpdotenv": "^3.3@dev", 20 | "predis/predis": "^2.0@dev", 21 | "maximebf/debugbar": "^1.15@dev", 22 | "kickpeach/validate": "dev-master" 23 | 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "App\\": "app/", 28 | "MdCalls\\": "mdcalls/" 29 | } 30 | }, 31 | "minimum-stability":"dev" 32 | } 33 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | App\Helpers\Helper::env('APP_DEBUG'), 10 | ]; -------------------------------------------------------------------------------- /mdcalls/Config/database.php: -------------------------------------------------------------------------------- 1 | array( 11 | 'master' => [ //默认数据库 12 | 'driver' => 'mysql', 13 | 'host' => App\Helpers\Helper::env('DB_HOST'), 14 | 'port' => App\Helpers\Helper::env('DB_PORT'), 15 | 'database' => App\Helpers\Helper::env('DB_DATABASE'), 16 | 'username' => App\Helpers\Helper::env('DB_USERNAME'), 17 | 'password' => App\Helpers\Helper::env('DB_PASSWORD'), 18 | 'charset' => 'utf8mb4', 19 | ], 20 | 'slave' => [ 21 | 'driver' => 'mysql', 22 | 'host' => App\Helpers\Helper::env('DB_HOST'), 23 | 'port' => App\Helpers\Helper::env('DB_PORT'), 24 | 'database' => App\Helpers\Helper::env('DB_DATABASE'), 25 | 'username' => App\Helpers\Helper::env('DB_USERNAME'), 26 | 'password' => App\Helpers\Helper::env('DB_PASSWORD'), 27 | 'charset' => 'utf8mb4', 28 | ] 29 | ), 30 | // 'MDB_MISSION' 31 | 'user' => [ 32 | 'master' => [ //默认数据库 33 | 'driver' => 'pgsql', 34 | 'host' => App\Helpers\Helper::env('DB_HOST'), 35 | 'port' => App\Helpers\Helper::env('DB_PORT'), 36 | 'database' => App\Helpers\Helper::env('DB_DATABASE'), 37 | 'username' => App\Helpers\Helper::env('DB_USERNAME'), 38 | 'password' => App\Helpers\Helper::env('DB_PASSWORD'), 39 | ], 40 | 'slave' => [ 41 | 'driver' => 'pgsql', 42 | 'host' => App\Helpers\Helper::env('DB_HOST'), 43 | 'port' => App\Helpers\Helper::env('DB_PORT'), 44 | 'database' => App\Helpers\Helper::env('DB_DATABASE'), 45 | 'username' => App\Helpers\Helper::env('DB_USERNAME'), 46 | 'password' => App\Helpers\Helper::env('DB_PASSWORD'), 47 | ] 48 | ], 49 | ]; -------------------------------------------------------------------------------- /mdcalls/Config/queue.php: -------------------------------------------------------------------------------- 1 | [ 11 | 'driver' => 'beanstalkd', 12 | 'host' => '127.0.0.1', 13 | 'port' => '11300', 14 | ], 15 | ]; -------------------------------------------------------------------------------- /mdcalls/Config/redis.php: -------------------------------------------------------------------------------- 1 | array( //默认redis 11 | 'host' => App\Helpers\Helper::env('REDIS_HOST'), 12 | 'port' => App\Helpers\Helper::env('REDIS_PORT'), 13 | 'timeout' => 3, 14 | ), 15 | ); -------------------------------------------------------------------------------- /mdcalls/MdCalls.php: -------------------------------------------------------------------------------- 1 | mdc->Kick->getConf('queue.' . $conn); 39 | self::$connObj[$conn] = new QueueService(new Beanstalkd($config['host'], $config['port'])); 40 | } 41 | return self::$connObj[$conn]; 42 | } 43 | /** 44 | * @param Job $job 45 | * @return mixed 46 | */ 47 | public function dispatch(Job $job) 48 | { 49 | return $this->getQueue($job->connection)->push($job); 50 | } 51 | /** 52 | * @param $delay 53 | * @param Job $job 54 | * @return mixed 55 | */ 56 | public function laterDispatch($delay, Job $job) 57 | { 58 | return $this->getQueue($job->connection)->later($delay, $job); 59 | } 60 | /** 61 | * 62 | * Listen to the given queue in a loop. 63 | * 64 | * @param $connection 65 | * @param string $queueTube 66 | * @param int $sleep 没有新的有效任务产生时的休眠时间 (单位: 秒) 67 | * @param int $memoryLimit worker 内存限制 (单位: mb) 68 | */ 69 | public function daemon($connection = 'default', $queueTube = '', $sleep = 60, $memoryLimit = 128) 70 | { 71 | $worker = new Worker($this->getQueue($connection), $queueTube); 72 | $worker->setMdCalls($this->mdc); 73 | $worker->daemon($sleep, $memoryLimit); 74 | } 75 | 76 | 77 | } -------------------------------------------------------------------------------- /mdcalls/Service/Kick/Child/Queue/Job.php: -------------------------------------------------------------------------------- 1 | mdc = $mdc; 22 | } 23 | 24 | protected function logProcessError(\Exception $e) 25 | { 26 | parent::logProcessError($e); 27 | } 28 | 29 | protected function handleWithObj(Job $obj) 30 | { 31 | $obj->mdc = $this->mdc; 32 | parent::handleWithObj($obj); 33 | } 34 | 35 | protected function queueShouldRestart($startTime) 36 | { 37 | return false; 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /mdcalls/Service/Kick/Child/Storage.php: -------------------------------------------------------------------------------- 1 | loadDB(); 42 | * $this->loadDB('mission.slave'); 43 | * 44 | * @param string $database 45 | * @return \Jetea\Database\Connection 46 | */ 47 | public function db($database = 'default.master') 48 | { 49 | if (! isset(self::$dbObj[$database])) { 50 | $config = $this->mdc->Kick->getConf('database.' . $database); 51 | if ($config['driver'] == 'mysql') { 52 | $dsn = sprintf('mysql:host=%s;port=%s;dbname=%s;charset=%s', $config['host'], $config['port'], $config['database'], $config['charset']); 53 | $connection = new MySqlConnection($dsn, $config['username'], $config['password']); 54 | } else { //默认Pgsql 55 | $dsn = sprintf('pgsql:host=%s;port=%s;dbname=%s', $config['host'], $config['port'], $config['database']); 56 | $connection = new PostpostgresqlConnection($dsn, $config['username'], $config['password']); 57 | } 58 | self::$dbObj[$database] = $connection; 59 | } 60 | return self::$dbObj[$database]; 61 | } 62 | /** 63 | * redis实例 64 | */ 65 | private static $redisObj = []; 66 | /** 67 | * 加载Redis对象 68 | * @param string $redis 69 | * @return Redis 70 | */ 71 | public function redis($redis = 'default') 72 | { 73 | if (! isset(self::$redisObj[$redis])) { 74 | $config = $this->mdc->Kick->getConf('redis.' . $redis); 75 | self::$redisObj[$redis] = new Redis([ 76 | 'scheme' => 'tcp', 77 | 'host' => $config['host'], 78 | 'port' => $config['port'], 79 | 'timeout' => $config['timeout'], 80 | ]); 81 | } 82 | return self::$redisObj[$redis]; 83 | } 84 | public function __destruct() 85 | { 86 | foreach (self::$redisObj as $redis) { 87 | /** @var $redis Redis */ 88 | $redis->disconnect(); 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /mdcalls/Service/Kick/KickIndex.php: -------------------------------------------------------------------------------- 1 | load();//所有.env文件的变量都可以使用getenv($var)获取到 19 | $this->config = new Config(__DIR__ . '/../../Config'); 20 | $this->storage = $this->loadC('Storage'); 21 | $this->queue = $this->loadC('Queue'); } 22 | 23 | public function invokeRpc($method, $args) 24 | { 25 | // TODO: Implement invokeRpc() method. 26 | } 27 | 28 | private $config; 29 | 30 | private $storage; 31 | 32 | private $queue; 33 | 34 | 35 | /** 36 | * 获取配置 37 | * 38 | * @param $item 39 | * @param mixed $default 40 | * @return mixed 41 | */ 42 | public function getConf($item, $default = null) 43 | { 44 | return $this->config->get($item, $default); 45 | } 46 | /** 47 | * 设置配置 48 | * 49 | * @param $item 50 | * @param mixed $config 51 | * @return void 52 | */ 53 | public function setConf($item, $config = null) 54 | { 55 | $this->config->set($item, $config); 56 | } 57 | 58 | 59 | /** 60 | * 获取mysql 61 | */ 62 | public function loadDB($database = 'default.master') 63 | { 64 | return $this->storage->db($database); 65 | } 66 | /** 67 | * 获取redis 68 | */ 69 | public function loadRedis($redis = 'default') 70 | { 71 | return $this->storage->redis($redis); 72 | } 73 | /** 74 | * @param Job $job 75 | * @return mixed 76 | */ 77 | public function dispatch(Job $job) 78 | { 79 | return $this->queue->dispatch($job); 80 | } 81 | /** 82 | * @param $delay 83 | * @param Job $job 84 | * @return mixed 85 | */ 86 | public function laterDispatch($delay, Job $job) 87 | { 88 | return $this->queue->laterDispatch($delay, $job); 89 | } 90 | /** 91 | * 92 | * Listen to the given queue in a loop. 93 | * 94 | * @param $conn 95 | * @param string $queueTube 96 | * @param int $sleep 没有新的有效任务产生时的休眠时间 (单位: 秒) 97 | * @param int $memoryLimit worker 内存限制 (单位: mb) 98 | */ 99 | public function queueDaemon($conn = 'default', $queueTube = '', $sleep = 60, $memoryLimit = 128) 100 | { 101 | $this->queue->daemon($conn, $queueTube, $sleep, $memoryLimit); 102 | } 103 | 104 | 105 | } -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | run(); 16 | 17 | if (\App\App::getInstance()->config('debug')){ 18 | require_once __DIR__.'/../app/Helpers/DebugBar.php'; 19 | } 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /public/views/Admin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Admin 6 | 7 | 8 | hello {{name}} 9 | 10 | -------------------------------------------------------------------------------- /public/views/Home/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Kickpeach 8 | 9 | 10 | 11 | 12 | 13 | 65 | 66 | 67 | 68 | 69 |
70 | 71 |
72 |
73 | {{name}} 74 |
75 | 76 | 81 |
82 |
83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /public/xhprof/xhprof_html/callgraph.php: -------------------------------------------------------------------------------- 1 | array(XHPROF_STRING_PARAM, ''), 42 | 43 | // source/namespace/type of run 44 | 'source' => array(XHPROF_STRING_PARAM, 'xhprof'), 45 | 46 | // the focus function, if it is set, only directly 47 | // parents/children functions of it will be shown. 48 | 'func' => array(XHPROF_STRING_PARAM, ''), 49 | 50 | // image type, can be 'jpg', 'gif', 'ps', 'png' 51 | 'type' => array(XHPROF_STRING_PARAM, 'png'), 52 | 53 | // only functions whose exclusive time over the total time 54 | // is larger than this threshold will be shown. 55 | // default is 0.01. 56 | 'threshold' => array(XHPROF_FLOAT_PARAM, 0.01), 57 | 58 | // whether to show critical_path 59 | 'critical' => array(XHPROF_BOOL_PARAM, true), 60 | 61 | // first run in diff mode. 62 | 'run1' => array(XHPROF_STRING_PARAM, ''), 63 | 64 | // second run in diff mode. 65 | 'run2' => array(XHPROF_STRING_PARAM, '') 66 | ); 67 | 68 | // pull values of these params, and create named globals for each param 69 | xhprof_param_init($params); 70 | 71 | // if invalid value specified for threshold, then use the default 72 | if ($threshold < 0 || $threshold > 1) { 73 | $threshold = $params['threshold'][1]; 74 | } 75 | 76 | // if invalid value specified for type, use the default 77 | if (!array_key_exists($type, $xhprof_legal_image_types)) { 78 | $type = $params['type'][1]; // default image type. 79 | } 80 | 81 | $xhprof_runs_impl = new XHProfRuns_Default(); 82 | 83 | if (!empty($run)) { 84 | // single run call graph image generation 85 | xhprof_render_image($xhprof_runs_impl, $run, $type, 86 | $threshold, $func, $source, $critical); 87 | } else { 88 | // diff report call graph image generation 89 | xhprof_render_diff_image($xhprof_runs_impl, $run1, $run2, 90 | $type, $threshold, $source); 91 | } 92 | -------------------------------------------------------------------------------- /public/xhprof/xhprof_html/css/xhprof.css: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2009 Facebook 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | td.sorted { 17 | color:#0000FF; 18 | } 19 | 20 | td.vbar, th.vbar { 21 | text-align: right; 22 | border-left: 23 | solid 1px #bdc7d8; 24 | } 25 | 26 | td.vbbar, th.vbar { 27 | text-align: right; 28 | border-left: 29 | solid 1px #bdc7d8; 30 | color:blue; 31 | } 32 | 33 | /* diff reports: display regressions in red */ 34 | td.vrbar { 35 | text-align: right; 36 | border-left:solid 1px #bdc7d8; 37 | color:red; 38 | } 39 | 40 | /* diff reports: display improvements in green */ 41 | td.vgbar { 42 | text-align: right; 43 | border-left: solid 1px #bdc7d8; 44 | color:green; 45 | } 46 | 47 | td.vwbar, th.vwbar { 48 | text-align: right; 49 | border-left: solid 1px white; 50 | } 51 | 52 | td.vwlbar, th.vwlbar { 53 | text-align: left; 54 | border-left: solid 1px white; 55 | } 56 | 57 | p.blue { 58 | color:blue 59 | } 60 | 61 | .bubble { 62 | background-color:#C3D9FF 63 | } 64 | 65 | ul.xhprof_actions { 66 | float: right; 67 | padding-left: 16px; 68 | list-style-image: none; 69 | list-style-type: none; 70 | margin:10px 10px 10px 3em; 71 | position:relative; 72 | } 73 | 74 | ul.xhprof_actions li { 75 | border-bottom:1px solid #D8DFEA; 76 | } 77 | 78 | ul.xhprof_actions li a:hover { 79 | background:#3B5998 none repeat scroll 0 0; 80 | color:#FFFFFF; 81 | } 82 | 83 | -------------------------------------------------------------------------------- /public/xhprof/xhprof_html/docs/index-fr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Documentation XHProf (Brouillon) 7 | 8 | 9 |

Documentation XHProf (Brouillon)

10 | 11 | Sommaire 12 |
    13 |
  1. Introduction
  2. 14 | 15 |
  3. Présentation
  4. 16 | 17 |
  5. Installer l’extension XHProf
  6. 18 | 19 |
  7. Profiler avec XHProf
  8. 20 | 21 |
  9. Définir un environnement graphique pour XHProf
  10. 22 | 23 |
  11. Notes sur l’utilisation d’XHProf en production
  12. 24 | 25 |
  13. Mode aléatoire et léger 26 | 27 |
  14. Fonctionnalités supplémentaires
  15. 28 | 29 |
  16. Dépendences
  17. 30 | 31 |
  18. Remerciements
  19. 32 |
33 | 34 |
    35 |
  1. Introduction

    36 | 37 |

    XHProf est un outil de profilage hiérarchique pour PHP. Il relève 38 | les appels au niveau des fonctions et mesure inclusivement et 39 | exclusivement des métriques telles que le temps écoulé 40 | la charge CPU ou l’usage de la mémoire. Un profil de fonction peut être divisé selon ses appelants, ou ses appelés. Le composant qui extrait les données brutes est écrit en C 41 | et implémenté telle une extension PHP Zend. 42 | xhprof. XHProf a une interface utilisateur simple en HTML, (écrite en PHP). 43 | L’interface permet de visualiser et de partager facilement le résultat des profilages dans un navigateur. 44 | Un rendu sous forme de graphique est également disponible. 45 | 46 |

    Les rapports fournis par XHProf permettent souvent de mieux comprendre 47 | la structure du code qui est éxécuté. 48 | Le rendu hiérarchique des rapports permet par exemple de déterminer 49 | quelle chaîne d’appels mène à une fonction particulière. 50 | 51 |

    XHProf propose également de comparer deux runs (résultat de profilage) 52 | pour analyser les différences ou aggréger les résultat de multiples runs afin 53 | d’analyser des données consolidées. 54 | Les comparaisons et aggrégations de données permettent surtout de visualiser des données plates 55 | 56 |

    XHProf est un outil de profilage très léger. Pendant la phase de collecte 57 | Il garde une trace du nombre d’appels et des métriques inclusives viualisables en courbes dans le graphe d’appels dynamique d’un programme. 58 | Il calcule les métriques exclusives dans la phase de rapport. 59 | XHProf supporte les fonctions recursives en détectant les cycles dans la pile d’appels dès la capture des données et en utilisant un nom unique pour l’invocation principale.

    60 | 61 |

    La nature légère d’XHProf, ses performances et ses possibilités de consolidations de données 62 | en font un outil taillé pour les environnements de production [Voir les notes sur l’usage en production.] 64 | 65 |

      66 |
      67 | 68 |

      XHProfLive (qui ne fait pas partie de ce kit open source), par exemple, 69 | est un système de monitoring de performance utilsé chez Facebook et qui est bâti sur XHProf. 70 | XHProfLive récupère en permanence les données de profilage en production en lançant XHProf sur un échantillon de pages 71 | XHProfLive aggrège ensuite les données suivant des critères tels que le temps, type de pages, et peut aider à répondre à tout type de questions comme : 72 | Quel est le profil de la pile d’appel pour une page spécifique ? Quel est le coût de la méthode "foo" dans toutes les pages, ou sur une page spécifique ? quelles fonctions ont régressé le plus depuis une heure, un jour pou un mois ? Quel est l’historique des tendances, des temps d’executions pour une page ou une fonction … 73 | 74 |


      75 |
    76 | 77 | 78 |

    Développé à l’origine par Facebook, XHProf est maintenant open source depuis mars 2009.

    79 | 80 | 81 | 82 | 83 | 84 |
  2. Présentation

    85 | 86 |

    XHProf offre: 87 | 88 |

      89 |
    • Un rendu tabulaire (copie d’écran) 90 | 91 |

      Un résumé des appels de fonctions avec des informations telles que le nombre d’appels, 92 | inclusivement et exclusivement, les temps, la charge mémoire, et le temps processeur. 93 | 94 |

    • Un rendu hiérarchique (Vue parent/enfant) 95 | (copie d’écran) 96 | 97 |

      Pour chaque fonction, il fournit le détail des appels et le temps par 98 | parent (appelant) & enfant (appelé), tel que : 99 | 100 |

        101 | 102 |
      • quelle fonctions appelle quelle fonction précisement et combien de fois ? 103 | 104 |
      • Quelles fonctions font un appel particulier ? 105 | 106 |
      • Le temps total passé dans une fonction appelé par un parent bien précis. 107 | 108 |
      109 | 110 |

    • Comparateur de rapports 111 | 112 |

      Vous pouvez comparer les données de deux appels à XHProf pour des raisons diverses; 113 | Pour voir ce qui cause une régression entre une version du code et une autre, 114 | Pour évaluer l’impact sur les performances d’une évolution dans le code … 115 | 116 |

      Une comparaison de rapport prends deux runs en entrée et produit à la fois des informations différencielles au niveau de la fonction, mais aussi des informations hiérarchiques (séparation des différences par fonction parente/enfant) pour chaque fonction. 117 | 118 |

      La vue tabulaire (copie d’écran) du rapport différentiel pointe les plus grosses améliorations et régressions. 120 | 121 |

      Cliquer sur un nom de fonction dans la bue tabulaire du rapport différentiel, mène à la vue hiérarchique 122 | (ou vue parent/enfant) différentielle d’une fonction (copie d’écran). On peut ainsi avoir une séparation des différences par fonctions parent/enfant. 124 | 125 |

    • Callgraph View (copie d’écran) 127 | 128 |

      Les données du rapport peuvent également être visualisées sous forme de graphique. 129 | Cette vue permet de mettre en lumière les chemins crtiques du programme. 130 | 131 |

    • Profilage mémoire 132 | 133 |

      Le mode profilage mémoire d’XHProf aide à isoler les fonctions qui occupent trop de mémoire. 134 | 135 |

      On ne peut pas dire qu’XHProf trace exactement chaque opération 136 | d’allocation/libération de mémoire, en effet il utilise un schéma simplistique; 137 | Il trace les hausses et les baisse de besoin en mémoire allouée à PHP à chaque entré ou sortie de fonction. 138 | Il trace aussi les hausses et baisses de pics mémoire alloués à chaque fonction PHP. 139 | 140 |

    • XHProf trace les opération include, include_once, require and 141 | require_once comme si c’était des fonctions. Le nom du fichier inclus est utilisé pour nommer "fausses" fonctions. 143 | 144 | 145 |
    146 | 147 |

    Terminologie

    148 |
      149 | 150 |
    1. Temps inclusive (ou temps du sous-ensemble): 151 | Inclus le temps passé dans la fonction et celui passé dans les fonctions descendantes (filles). 152 | 153 |
    2. Temps exclusive (ou temps "propre"): Mesure le temps passé dans la fonction elle-même et n’inclus pas le temps passé dans les fonctions descendantes. 154 | 155 |
    3. Wall Time: Temps passé ou temps ressenti. 156 | 157 |
    4. CPU Time: Charge CPU sur les process utilisateur + charge CPU sur les process noyaux 158 | 159 |
    160 |

    Convention de nommage pour les fonctions spéciales

    161 | 162 |
      163 |

    1. main(): Une fonction fictive qui est à la racine de la pile d’appel. 164 | 165 | 166 |

    2. load::<filename> 167 | et run_init::<filename>: 168 | 169 |

      XHProf trace les appels include/require comme des appels de fonction. 170 | 171 |

      Par exemple, une inclusion include "lib/common.php"; va donner deux entrées pour XHProf : 172 | 173 |

        174 | 175 |
      • load::lib/common.php - Cela représente le travail fait par l’interpréteur pour charger et compiler le fichier. 176 | [Note: Si vous utilisez un cache d’opcode PHP comme APC, alors la compilation intervient uniquement si le cahce est manquant dans APC.] 177 | 178 |
      • run_init::lib/common.php - Cela répresente le code exécuté au niveau du fichier, soit le résultat de l’inclusion. 179 | 180 |
      181 | 182 |

    3. foo@<n>: Implique un appel récursif de foo(), ou <n> représente le niveau de récursion. 183 | Cette récursion peut être directe comme foo() --> foo()), ou indirecte comme foo() --> goo() --> foo(). 184 | 185 |
    186 | 187 | 188 |

    Limitations

    189 | 190 |

    Un vrai profileur hiérarchique trace toute la pile d’appel pour chaque donnée., et est capables de répondre aux questions comme : Quel était le coût du 3e appel de foo(), ou quel était le coût de bar() quand il était appelé par a()->b()->bar()? 191 | 192 |

    193 | 194 |

    XHProf garde une trace d’un seul niveau dans le contexte de l’appel et est seulement capable de répondre aux questions à propos 195 | d’une fonction qui regarde un niveau en dessus ou en dessous. 196 | Il appraît que dans la majorité des cas c’est bien suffisant. 197 |

    198 | 199 |

    Pour mieux comprendre, regaredez l’exemple suivant : 200 |

    201 | 202 |
    203 | Vous avez:
    204 |  1 appel de a() --> c()
    205 |  1 appel de b() --> c()
    206 |  50 appels de c() --> d()
    207 | 
    208 | 209 |

    Quand XHProf peut vous dire que d() a été appelé par c() 50 fois, il ne peut pas vous dire 210 | combien d’appels dont dus à a() ou b(). 211 | [On peut imaginer que c’est peut être 25 pour a() et 25 pour b(), mais ce n’est pas nécéssairement vrai.] 212 |

    213 | 214 |

    De toutes façons en pratique ce n’est pas vraiment une limitation. 215 |

    216 | 217 |
  3. Installer l’extension XHProf

    218 | 219 |

    L’extension se trouve dans le sous-répertoire "extension/". 220 | 221 |


      222 | 223 |

      Note: Le portage pour Windows n’est pas encore implémenté. Nous avons testé XHProf sur Linux/FreeBSD. 224 | [NDT : Il existe un fork avec un portage Windows sur Github] 225 | 226 |

      La version 0.9.2 et les précédentes sont aussi censées fonctionner sur Mac 227 | OS. [Cela a été testé sur Mac OS 10.5.] 228 | 229 |

      Note: XHProf utilise les insctructions RDTSC (time stamp counter) 230 | pour implémenter un compteur de temps vraiment bas niveau. C’est pourquoi actuellement xhprof fonctionne uniquement sur une architecture x86. 231 | Aussi tant que les valeurs de RDTSC ne pourront pas être synchronisées entre plusieurs CPUs, 232 | xhprof n’en utilisera qu’un seul lors du profilage. 233 | 234 |

      Le timer XHProf bzasé sur RDTSC ne fonctionen pas parfaitement si la techno 235 | SpeedStep est activée. Cette technologie est disponible sur certains processeurs Intel. 236 | [Note: Les Macs ont typiquement cette fonctionnalité d’activée par défaut, il faut donc la désactiver pour utiliser XHProf.] 237 | 238 |


    239 | 240 |

    Les étapes suivantes sont prévues pour un environnement Linux/Unix. 241 | 242 | 243 |

    244 | % cd <repertoire_source_xhprof>/extension/
    245 | % phpize
    246 | % ./configure --with-php-config=<chemin vers php-config>
    247 | % make
    248 | % make install
    249 | % make test
    250 | 
    251 | 252 | 253 |

    php.ini file: Vous pouvez mettre à jour votre fichier 254 | php.ini file afin qu’il charge automatiquement votre extension en ajoutant le code suivant : 255 | 256 |

    257 | [xhprof]
    258 | extension=xhprof.so
    259 | ;
    260 | ; répertoire utilisé par l’implémentation par défaut de l’interface iXHProfRuns
    261 | ; (nommée, XHProfRuns_Default class) pour stocker les runs XHProf.
    262 | ;
    263 | xhprof.output_dir=<repertoire_pour_stocker_les_runs_xhprof>
    264 | 
    265 | 266 | 267 |
  4. Profiler avec XHProf

    268 | 269 |

    Test de génération de donées brutes avec l’exemple simple d’un programme tel que : 270 | 271 |

      272 |

      foo.php 273 |

      274 | <?php
      275 | 
      276 | function bar($x) {
      277 |   if ($x > 0) {
      278 |     bar($x - 1);
      279 |   }
      280 | }
      281 | 
      282 | function foo() {
      283 |   for ($idx = 0; $idx < 2; $idx++) {
      284 |     bar($idx);
      285 |     $x = strlen("abc");
      286 |   }
      287 | }
      288 | 
      289 | // début du profileur
      290 | xhprof_enable();
      291 | 
      292 | // début du programme
      293 | foo();
      294 | 
      295 | // attêt du profileur
      296 | $xhprof_data = xhprof_disable();
      297 | 
      298 | // affichage des données de profilage brutes
      299 | print_r($xhprof_data);
      300 | 
      301 |
    302 | 303 |

    Lancez ce programme : 304 | 305 |

    306 | % php -dextension=xhprof.so foo.php
    307 | 
    308 | 309 |

    Vous devez avoir un résultat tel que : 310 | 311 |

    312 | Array
    313 | (
    314 |     [foo==>bar] => Array
    315 |         (
    316 |             [ct] => 2         # 2 appels de bar() depuis foo()
    317 |             [wt] => 27        # temps inclusif dans bar() quand il est appelé par foo()
    318 |         )
    319 | 
    320 |     [foo==>strlen] => Array
    321 |         (
    322 |             [ct] => 2
    323 |             [wt] => 2
    324 |         )
    325 | 
    326 |     [bar==>bar@1] => Array    # un appelrécursif à bar()
    327 |         (
    328 |             [ct] => 1
    329 |             [wt] => 2
    330 |         )
    331 | 
    332 |     [main()==>foo] => Array
    333 |         (
    334 |             [ct] => 1
    335 |             [wt] => 74
    336 |         )
    337 | 
    338 |     [main()==>xhprof_disable] => Array
    339 |         (
    340 |             [ct] => 1
    341 |             [wt] => 0
    342 |         )
    343 | 
    344 |     [main()] => Array         # fausse fonction représentant la racine
    345 |         (
    346 |             [ct] => 1
    347 |             [wt] => 83
    348 |         )
    349 | 
    350 | )
    351 | 
    352 | 353 |

    Note: Les données brutes contienent uniquement les métriques inclusives. 354 | Par exemple les données brutes du tableau de données temporelles represente les temps inclusifs en microsecondes. 355 | Les temps exclusifs sont calculés pour chaque fonction lors de la phase d’analyse et de rapport. 356 | 357 |

    Note: Par défault suelemnt le nombre d’appel & et le temps passé sont profilés. 358 | Vous pouvez aussi profilerle temps CPU et/ou la charge mémoire. Remplacez, 359 | 360 |

      361 | xhprof_enable();
      362 | 
    363 | dans le programme précédent avec, par exemple : 364 |
      365 | xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY);
      366 | 
    367 | 368 |

    Vous aurez en sortie : 369 | 370 |

    371 | Array
    372 | (
    373 |     [foo==>bar] => Array
    374 |         (
    375 |             [ct] => 2        # nombre d’appel à bar() depuis foo()
    376 |             [wt] => 37       # tempas passé dans bar() quand appel de foo()
    377 |             [cpu] => 0       # temps cpu time dans bar() quand appel de foo()
    378 |             [mu] => 2208     # changement dans l’usage de la mémoire par PHP dans bar() quand appel de foo()
    379 |             [pmu] => 0       # changement dans l’usage de pic mémoire par PHP pour bar() quand appel de foo()
    380 |         )
    381 | 
    382 |     [foo==>strlen] => Array
    383 |         (
    384 |             [ct] => 2
    385 |             [wt] => 3
    386 |             [cpu] => 0
    387 |             [mu] => 624
    388 |             [pmu] => 0
    389 |         )
    390 | 
    391 |     [bar==>bar@1] => Array
    392 |         (
    393 |             [ct] => 1
    394 |             [wt] => 2
    395 |             [cpu] => 0
    396 |             [mu] => 856
    397 |             [pmu] => 0
    398 |         )
    399 | 
    400 |     [main()==>foo] => Array
    401 |         (
    402 |             [ct] => 1
    403 |             [wt] => 104
    404 |             [cpu] => 0
    405 |             [mu] => 4168
    406 |             [pmu] => 0
    407 |         )
    408 | 
    409 |     [main()==>xhprof_disable] => Array
    410 |         (
    411 |             [ct] => 1
    412 |             [wt] => 1
    413 |             [cpu] => 0
    414 |             [mu] => 344
    415 |             [pmu] => 0
    416 |         )
    417 | 
    418 |     [main()] => Array
    419 |         (
    420 |             [ct] => 1
    421 |             [wt] => 139
    422 |             [cpu] => 0
    423 |             [mu] => 5936
    424 |             [pmu] => 0
    425 |         )
    426 | 
    427 | )
    428 | 
    429 | 430 |

    Éviter les fonctions natives lors du profilage 431 | 432 |

    Par défault les fonctions natives de PHP (comme strlen) sont profilées. 433 | Si vous ne voulez pas les profiler (pour simplifier le résultat et la taille des données brutes générées), 434 | Vous pouvez utiliser le drapeau XHPROF_FLAGS_NO_BUILTINS comme dans l’exemple ci-dessous : 435 | 436 |

      437 | // ne pas profiler les fonctions natives
      438 | xhprof_enable(XHPROF_FLAGS_NO_BUILTINS);
      439 | 
    440 | 441 | 442 |

    Ignorer des fonctions spécfiques lors du profilage (0.9.2 ou plus récent) 443 | 444 |

    À partir de la version 0.9.2 d’XHProf, vous pouvez spécifier une liste de 445 | fonctions à ignorer pendant le profilage. Cela vous permet de ne pas prendre en compte par exemple 446 | des fonctions utilisées pour des appels indirects comme call_user_func et call_user_func_array. 447 | Ces fonctions intermédiaires compliquent inutilement la hirarchie des appels et rendent plus ardue l’interprétation des rapports en brouillant les relations parent/enfant. 448 | 449 |

    Pour spécifier cette liste de fonctions à ignorer durant le profilage, il suffit d’utiliser le second paramètre (optionnel) de xhprof_enable. 450 | Par exemple, 451 | 452 |

    453 | 
      454 | 
      455 | // temps passé en profilage; ignore les appels de call_user_func* pendant le profilage
      456 | xhprof_enable(0,
      457 |              array('ignored_functions' =>  array('call_user_func',
      458 |                                                  'call_user_func_array')));
      459 | 
      460 | or,
      461 | 
      462 | // tempas pasé en profilage + profilage mémoire; ignore call_user_func* durant le profilage
      463 | xhprof_enable(XHPROF_FLAGS_MEMORY,
      464 |               array('ignored_functions' =>  array('call_user_func',
      465 |                                                   'call_user_func_array')));
      466 | 
      467 | 
    468 | 469 | 470 |
  5. 471 | 472 |
  6. Définir un environnement graphique pour XHProf

    473 | 474 | 475 |
      476 | 477 |
    1. Structure de la source PHP 478 |

      l’interface graphique d’XHProf est implémentée en PHP. Le code est divisé en deux sous-répertoires, 479 | xhprof_html/ and xhprof_lib/. 480 | 481 |

      Le répertoire xhprof_html contient les 3 pages PHP principales. 482 | 483 |

        484 |
      • index.php: Pour visualiser un run ou un différentiel entre deux runs. 485 |
      • callgraph.php: Pour visualiser sous la forme de graphique avec un rendu en image. 486 |
      • typeahead.php: Utilisé implicitement pour les fonctions de gestion de pile sur un rapport XHProf. 487 |
      488 | 489 |

      Le répertoire xhprof_lib contient le code pour l’analyse et l’affichage. 490 | (calcul sur les informations de profilage, calcul des différentiels, aggrégation de données, etc.). 491 | 492 |

    2. Configuration du server web : Vous devez vous assurer que le répertoire 493 | xhprof_html/ est accessible depuis le serveur web, et qu’il est configuré pour éxécuter des scripts PHP. 494 | 495 |

    3. Gérer les runs XHProf 496 | 497 |

      Les clients web ont une certaine souplesse dans la manière de sauvegarder les données brutes fournies par XHProf. 498 | XHProf expose une interface utilisateur nommée iXHProfRuns (voir xhprof_lib/utils/xhprof_runs.php) que les clients peuvent implémenter. 499 | Cela permet aux clients de préciser comment afficher les donées des runs. 500 | 501 |

      L’interface utilisateur d’XHProf fournit une implementation par défaut nommée, 502 | "XHProfRuns_Default" (aussi dans xhprof_lib/utils/xhprof_runs.php). 503 | L’implementation par d"faut stocke les runs dans le répertoire définit par le paramètre INI : 504 | xhprof.output_dir. 505 | 506 |

      Un run XHProf doit être définit de manière unique par un espace de nom et un identifiant de run. 507 | 508 |

      a) Sauver les données XHProf de façon persistente : 509 | 510 |

      Soit si vous utilisez l’interface par défaut, 511 | XHProfRuns_Default qui implémente 512 | iXHProfRuns, Un run XHProf sauvegardé ressemble au code suivant : 513 | 514 | 515 |

      516 | // début du profilage
      517 | xhprof_enable();
      518 | 
      519 | // lancement du programme
      520 | ...
      521 | 
      522 | // fin du profilage
      523 | $xhprof_data = xhprof_disable();
      524 | 
      525 | //
      526 | // Sauvegarde du run XHProf
      527 | // en utilisant l’implementation par défaut de iXHProfRuns.
      528 | //
      529 | include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_lib.php";
      530 | include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_runs.php";
      531 | 
      532 | $xhprof_runs = new XHProfRuns_Default();
      533 | 
      534 | // sauvegarde du run avec l’espace de nom "xhprof_foo".
      535 | //
      536 | // **NOTE**:
      537 | // par défault save_run() va automatiquement générer un identifiant de run
      538 | // unique. [Vous pouvez surcharger cette donnée en passant l’identifiant en paramètre optionnel 
      539 | // à la méthode save_run().]
      540 | //
      541 | $run_id = $xhprof_runs->save_run($xhprof_data, "xhprof_foo");
      542 | 
      543 | echo "---------------\n".
      544 |      "En partant du principe que vous avez parametré l’interface utilisateur http \n".
      545 |      "XHProf, vous pouvez visualiser les runs avec l’adresse : \n".
      546 |      "http://<adresse-interface-utilisateur-xhprof>/index.php?run=$run_id&source=xhprof_foo\n".
      547 |      "---------------\n";
      548 | 
      549 | 
      550 | 551 |

      La suite permet de sauvegarder le run sous forme d’un fichier dans le répertoire spécifié 552 | par le paramètre ini xhprof.output_dir. Le nom du fichier doit être de la forme 553 | 49bafaa3a3f66.xhprof_foo; Les deux parties du nom sont formées par l’identifiant du run 554 | ("49bafaa3a3f66") et l’espace de nom ("xhprof_foo"). [Si vous souhaitez créer un identifiant de run vous-même 555 | (comme une sequence de base de données, ou un timestamp), vous pouvez explicitementpasser l’identifiant 556 | du run à la méthode save_run. 557 | 558 |

      b) En utilisant votre propre implementation d’iXHProfRuns 559 | 560 |

      Si vous décidez de stocker différement les runs XHProf 561 | (soit dans un format compressé, dans une base de données, 562 | etc.), vous aurez besoin d’implémenter une classe qui implémente l’interface 563 | iXHProfRuns(). 564 | 565 |

      Vous devrez également modifier les 3 pages PHP d’entrée (index.php, 566 | callgraph.php, typeahead.php) dans le répertoire "xhprof_html/" pour utiliser la 567 | nouvelle interface au lieu de celle par défaut (XHProfRuns_Default), 568 | changez cette ligne dans les 3 fichier. 569 | 570 |

      571 | $xhprof_runs_impl = new XHProfRuns_Default();
      572 | 
      573 | 574 |

      Vous aurez aussi besoin d’inclure le fichier qui implémente votre classe dans les fichiers cités. 575 | 576 |

    4. Acceéder aux runs depuis l’interface utilisateur 577 | 578 |

      a) Voir un rapport simple 579 | 580 |

      Pour voir un rapport avec l’identifiant <run_id> et l’espace de nom 581 | <namespace> utilisez une url de la forme : 582 | 583 |

      584 | http://<adresse-interface-utilisateur-xhprof>/index.php?run=<run_id>&source=<namespace> 585 | 586 | 587 |

      Par example, 588 |

      589 | http://<adresse-interface-utilisateur-xhprof>/index.php?run=49bafaa3a3f66&source=xhprof_foo 590 | 591 | 592 |

      b) Voir un rapport différentiel 593 | 594 |

      Pour voir un rapport avec les identifiants <run_id1> et 595 | <run_id2> et l’espace de nom <namespace> utilisez une url de la forme : 596 | 597 |

      598 | http://<adresse-interface-utilisateur-xhprof>/index.php?run1=<run_id1>&run2=<run_id2>&source=<namespace> 599 | 600 | 601 |

      c) Voir un rapport d’aggrégation 602 | 603 |

      Vous pouvez aussi spécifier un ensemble de runspour lesquels vous souhaitez un rapport d’aggrégation. 604 | 605 |

      Si vous avez trois runs XHProf avec les identifiants 1, 2 & 3 pour l’espace de noms 606 | "benchmark". Pour voir l’aggrégation de ces trois runs : 607 | 608 |

        609 | http://<adresse-interface-utilisateur-xhprof>/index.php?run=1,2,3&source=benchmark 610 |

      611 | 612 |

      Aggrégations pondérées: En supposant que les trois runs 613 | correspondent à trois types de programmes p1.php, p2.php and p3.php 614 | qui occupent chacun respectivement 20%, 30% et 50%. Pour voir un rapport d’aggrégation 615 | pondéré par les poids des runs : 616 | 617 |

        618 | http://<adresse-interface-utilisateur-xhprof>/index.php?run=1,2,3&wts=20,30,50&source=benchmark 619 |

      620 | 621 |
    622 | 623 |
  7. Notes sur l’utilisation d’XHProf en production

    624 | 625 |

    Quelques observations qui peuvent faire varier votre expérience : 626 | 627 |

      628 | 629 |
    • Le timer CPU (getrusage) sur Linux peut avoir des dépassements de capacité. Il a également un rendu granuleux 630 | (Une précision à la milliseconde plutôt qu’à la microseconde) pour être efficace au niveau des méthodes. 631 | En conséquence, les valeurs rapportées en utilisant le mode XHPROF_FLAGS_CPU on tendance à être plus élevés. 632 | 633 |

      Nous recommandons d’utiliser le mode de profilage "temps passé" + "memoire" en production. 634 | [Note: Le surplus de temps passé par le mode de profilage mémoire est non significatif.] 635 | 636 |

        
        637 |   // profilage du temps passé (par défault) + profilage mémoire
        638 |   xhprof_enable(XHPROF_FLAGS_MEMORY);
        639 | 
        640 |

      641 | 642 | 643 |
    • Profiler une plage aléatoire de pages/requêtes est efficace pour récupérer des données représentatives 644 | de votre environnement de production. 645 | 646 |

      Pour profiler 1/10000 de vos requêtes, définissez le début du profilage avec un code dans l’esprit de celui-ci : 647 | 648 |

        
        649 |  if (mt_rand(1, 10000) == 1) {
        650 |    xhprof_enable(XHPROF_FLAGS_MEMORY);
        651 |    $xhprof_on = true;
        652 |  }
        653 | 

      654 | 655 |

      À la fin de la requête (ou dans une fonction de finalisation de la requête), vous pouvez faire quelque chose comme : 656 | 657 |

        
        658 |  if ($xhprof_on) {
        659 |    // fin du profilage
        660 |    $xhprof_data = xhprof_disable();
        661 | 
        662 |    // sauvegarde $xhprof_data quelquepart (base de données centralisée …)
        663 |    ...
        664 |  }
        665 | 

      666 | 667 |

      Vous pouvez alors récupérer et aggréger ces profilages par horaire 668 | (par exemple 5 minutes, par jour, par jour …), par page ou type de requête, ou n’importe quel 669 | paramètre utilisé par xhprof_aggregate_runs(). 670 | 671 |

    672 | 673 |
  8. Mode d’échantillonage léger

    674 | 675 |

    L’extension XHProf propose aussi un mode très léger d’échantillonage. 676 | L’intervalle est de 0,1 seconde. Les échantillons enregistrent l’ensemble des données. 677 | Ce mode peut être très utile pour avoir un impact le plus négligeable possible, et permettre 678 | Le mode sample peut être utile si vous désirez un moyen avec peu de dépassement de faire de la surveillance de performances et des diagnostics. 679 | 680 |

    Les très pertinentes fonctions utilisées par l’extension pour utiliser le mode 681 | d’échantillonage sont xhprof_sample_enable() et xhprof_sample_disable(). 682 | 683 |

    [TBD: Documentation plus détaillée pour le mode d’échantillonage.] 684 | 685 |

  9. Fonctionnalités supplémentaires

  10. 686 | 687 |

    Le fichier XHProf_lib/utils/xhprof_lib.php contient 688 | des librairies de fonctions additionellesqui peuvent être utilisées pour manipuler 689 | et aggréger les runs XHProf. 690 | 691 |

    Par exemple: 692 | 693 |

    708 | 709 |
      710 | 711 |
    712 | 713 |
  11. Dependances

  12. 714 | 715 | 726 |
  13. Remerciements

    727 | 728 |

    Le rendu HTML et l’interface de navigation pour consulter les résultat du profilage sont inspirés par un outil similaire 729 | qui existe pour les procédures stockées PL/SQL d’Oracle. Mais c’est là que la comparaison s’arrête; 730 | Le fonctionnement interne du profileur étant assez différent 731 | 732 | [NDT : Merci à Rudy Rigot (@rudyrigot) pour sa relecture attentive ] 733 |

  14. 734 | 735 |
736 | 737 | 738 | 739 | -------------------------------------------------------------------------------- /public/xhprof/xhprof_html/docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | XHProf Documentation (Draft) 4 | 5 | 6 |

XHProf Documentation (Draft)

7 | 8 | Contents 9 |
    10 |
  1. Introduction
  2. 11 | 12 |
  3. XHProf Overview
  4. 13 | 14 |
  5. Installing the XHProf extension
  6. 15 | 16 |
  7. Profiling using XHProf
  8. 17 | 18 |
  9. Setting up the XHProf UI
  10. 19 | 20 |
  11. Notes on using XHProf in production
  12. 21 | 22 |
  13. Lightweight Sampling Mode 23 | 24 |
  14. Additional features
  15. 25 | 26 |
  16. Dependencies
  17. 27 | 28 |
  18. Acknowledgements
  19. 29 |
30 | 31 |
    32 |
  1. Introduction

    33 | 34 |

    XHProf is a hierarchical profiler for PHP. It reports 35 | function-level call counts and inclusive and 36 | exclusive metrics such as wall (elapsed) 37 | time, CPU time and memory usage. A function's profile can be broken 38 | down by callers or callees. The raw data collection component is 39 | implemented in C as a PHP Zend extension called 40 | xhprof. XHProf has a simple HTML based user 41 | interface (written in PHP). The browser based UI for viewing profiler 42 | results makes it easy to view results or to share results with peers. 43 | A callgraph image view is also supported. 44 | 45 |

    XHProf reports can often be helpful in understanding the structure 46 | of the code being executed. The hierarchical nature of the reports can 47 | be used to determine, for example, what chain of calls led to a 48 | particular function getting called. 49 | 50 |

    XHProf supports ability to compare two runs (a.k.a. "diff" reports) 51 | or aggregate data from multiple runs. Diff and aggregate reports, much 52 | like single run reports, offer "flat" as well as "hierarchical" views 53 | of the profile. 54 | 55 |

    XHProf is a light-weight instrumentation based profiler. During the 56 | data collection phase, it keeps track of call counts and inclusive 57 | metrics for arcs in the dynamic callgraph of a program. It computes 58 | exclusive metrics in the reporting/post processing phase. XHProf 59 | handles recursive functions by detecting cycles in the callgraph at 60 | data collection time itself and avoiding the cycles by giving unique 61 | depth qualified names for the recursive invocations. 62 |

    63 | 64 |

    XHProf's light-weight nature and aggregation capabilities make it 65 | well suited for collecting "function-level" performance statistics 66 | from production environments. [See additional notes for use in production.] 68 | 69 |

      70 |
      71 | 72 |

      XHProfLive (not part of the open source kit), for example, is a 73 | system-wide performance monitoring system in use at Facebook that is 74 | built on top of XHProf. XHProfLive continually gathers function-level 75 | profiler data from production tier by running a sample of page 76 | requests under XHProf. XHProfLive then aggregates the profile data 77 | corresponding to individual requests by various dimensions such as 78 | time, page type, and can help answer a variety of questions such as: 79 | What is the function-level profile for a specific page? How expensive 80 | is function "foo" across all pages, or on a specific page? What 81 | functions regressed most in the last hour/day/week? What is the 82 | historical trend for execution time of a page/function? and so on. 83 | 84 |


      85 |
    86 | 87 | 88 |

    Originally developed at Facebook, XHProf was open sourced in Mar, 2009.

    89 | 90 | 91 | 92 | 93 | 94 |
  2. XHProf Overview

    95 | 96 |

    XHProf provides: 97 | 98 |

      99 |
    • Flat profile (screenshot) 100 | 101 |

      Function-level summary information such as number of calls, 102 | inclusive/exclusive wall time, memory usage, and CPU time. 103 | 104 |

    • Hierarchical profile (Parent/Child View) 105 | (screenshot) 106 | 107 |

      For each function, it provides a breakdown of calls and times per 108 | parent (caller) & child (callee), such as: 109 | 110 |

        111 | 112 |
      • what functions call a particular function and how many times? 113 | 114 |
      • what functions does a particular function call? 115 | 116 |
      • The total time spent under a function when called from a particular parent. 117 | 118 |
      119 | 120 |

    • Diff Reports 121 | 122 |

      You may want to compare data from two XHProf runs for various 123 | reasons-- to figure out what's causing a regression between one 124 | version of the code base to another, to evaluate the performance 125 | improvement of a code change you are making, and so on. 126 | 127 |

      A diff report takes two runs as input and provides both flat 128 | function-level diff information, and hierarchical information 129 | (breakdown of diff by parent/children functions) for each function. 130 | 131 |

      The "flat" view (sample screenshot) in the diff report points out the top 133 | regressions & improvements. 134 | 135 |

      Clicking on functions in the "flat" view of the diff report, leads 136 | to the "hierarchical" (or parent/child) diff view of a function (sample screenshot). We can get a 138 | breakdown of the diff by parent/children functions. 139 | 140 | 141 |

    • Callgraph View (sample screenshot) 143 | 144 |

      The profile data can also be viewed as a callgraph. The callgraph 145 | view highlights the critical path of the program. 146 | 147 | 148 |

    • Memory Profile 149 | 150 |

      XHProf's memory profile mode helps track functions that 151 | allocate lots of memory. 152 | 153 |

      It is worth clarifying that that XHProf doesn't strictly track each 154 | allocation/free operation. Rather it uses a more simplistic 155 | scheme. It tracks the increase/decrease in the amount of memory 156 | allocated to PHP between each function's entry and exit. It also 157 | tracks increase/decrease in the amount of peak memory allocated to 158 | PHP for each function. 159 | 160 |

    • XHProf tracks include, include_once, require and 161 | require_once operations as if they were functions. The name of 162 | the file being included is used to generate the name for these "fake" functions. 164 | 165 | 166 |
    167 | 168 |

    Terminology

    169 |
      170 | 171 |
    1. Inclusive Time (or Subtree Time): 172 | Includes time spent in the function as well as in descendant functions 173 | called from a given function. 174 | 175 |
    2. Exclusive Time/Self Time: Measures 176 | time spent in the function itself. Does not include time in descendant 177 | functions. 178 | 179 |
    3. Wall Time: a.k.a. Elapsed time or wall clock time. 180 | 181 |
    4. CPU Time: CPU time in user space + CPU time in kernel space 182 | 183 |
    184 |

    Naming convention for special functions

    185 | 186 |
      187 |

    1. main(): a fictitious function that is at the root of the call graph. 188 | 189 | 190 |

    2. load::<filename> 191 | and run_init::<filename>: 192 | 193 |

      XHProf tracks PHP include/require operations as 194 | function calls. 195 | 196 |

      For example, an include "lib/common.php"; operation will 197 | result in two XHProf function entries: 198 | 199 |

        200 | 201 |
      • load::lib/common.php - This represents the work done by the 202 | interpreter to compile/load the file. [Note: If you are using a PHP 203 | opcode cache like APC, then the compile only happens on a cache miss 204 | in APC.] 205 | 206 |
      • run_init::lib/common.php - This represents 207 | initialization code executed at the file scope as a result of the 208 | include operation. 209 | 210 |
      211 | 212 |

    3. foo@<n>: Implies that this is a 213 | recursive invocation of foo(), where <n> represents 214 | the recursion depth. The recursion may be direct (such as due to 215 | foo() --> foo()), or indirect (such as 216 | due to foo() --> goo() --> foo()). 217 | 218 |
    219 | 220 | 221 |

    Limitations

    222 | 223 |

    True hierarchical profilers keep track of a full call stack at 224 | every data gathering point, and are later able to answer questions 225 | like: what was the cost of the 3rd invokation of foo()? or what was 226 | the cost of bar() when the call stack looked like 227 | a()->b()->bar()? 228 | 229 |

    230 | 231 |

    XHProf keeps track of only 1-level of calling context and is 232 | therefore only able to answer questions about a function looking 233 | either 1-level up or 1-level down. It turns out that in practice this 234 | is sufficient for most use cases. 235 |

    236 | 237 |

    To make this more concrete, take for instance the following 238 | example. 239 |

    240 | 241 |
    242 | Say you have:
    243 |  1 call from a() --> c()
    244 |  1 call from b() --> c()
    245 |  50 calls from c() --> d()
    246 | 
    247 | 248 |

    While XHProf can tell you that d() was called from c() 50 times, it 249 | cannot tell you how many of those calls were triggered due to a() 250 | vs. b(). [We could speculate that perhaps 25 were due to a() and 25 251 | due to b(), but that's not necessarily true.] 252 |

    253 | 254 |

    In practice however, this isn't a very big limitation. 255 |

    256 | 257 |
  3. Installing the XHProf Extension

    258 | 259 |

    The extension lives in the "extension/" sub-directory. 260 | 261 |


      262 | 263 |

      Note: A windows port hasn't been implemented yet. We have 264 | tested xhprof on Linux/FreeBSD so far. 265 | 266 |

      Version 0.9.2 and above of XHProf is also expected to work on Mac 267 | OS. [We have tested on Mac OS 10.5.] 268 | 269 |

      Note: XHProf uses the RDTSC instruction (time stamp counter) 270 | to implement a really low overhead timer for elapsed time. So at the 271 | moment xhprof only works on x86 architecture. 272 | Also, since RDTSC values may not be synchronized across CPUs, 273 | xhprof binds the program to a single CPU during the 274 | profiling period. 275 | 276 |

      XHProf's RDTSC based timer functionality doesn't work correctly if 277 | SpeedStep technology is turned on. This technology is available on 278 | some Intel processors. [Note: Mac desktops and laptops typically have 279 | SpeedStep turned on by default. To use XHProf, you'll need to disable 280 | SpeedStep.] 281 | 282 |


    283 | 284 |

    The steps 285 | below should work for Linux/Unix environments. 286 | 287 | 288 |

    289 | % cd <xhprof_source_directory>/extension/
    290 | % phpize
    291 | % ./configure --with-php-config=<path to php-config>
    292 | % make
    293 | % make install
    294 | % make test
    295 | 
    296 | 297 | 298 |

    php.ini file: You can update your 299 | php.ini file to automatically load your extension. Add the following 300 | to your php.ini file. 301 | 302 |

    303 | [xhprof]
    304 | extension=xhprof.so
    305 | ;
    306 | ; directory used by default implementation of the iXHProfRuns
    307 | ; interface (namely, the XHProfRuns_Default class) for storing
    308 | ; XHProf runs.
    309 | ;
    310 | xhprof.output_dir=<directory_for_storing_xhprof_runs>
    311 | 
    312 | 313 | 314 |
  4. Profiling using XHProf

    315 | 316 |

    Test generating raw profiler data using a sample test program like: 317 | 318 |

      319 |

      foo.php 320 |

      321 | <?php
      322 | 
      323 | function bar($x) {
      324 |   if ($x > 0) {
      325 |     bar($x - 1);
      326 |   }
      327 | }
      328 | 
      329 | function foo() {
      330 |   for ($idx = 0; $idx < 2; $idx++) {
      331 |     bar($idx);
      332 |     $x = strlen("abc");
      333 |   }
      334 | }
      335 | 
      336 | // start profiling
      337 | xhprof_enable();
      338 | 
      339 | // run program
      340 | foo();
      341 | 
      342 | // stop profiler
      343 | $xhprof_data = xhprof_disable();
      344 | 
      345 | // display raw xhprof data for the profiler run
      346 | print_r($xhprof_data);
      347 | 
      348 |
    349 | 350 |

    Run the above test program: 351 | 352 |

    353 | % php -dextension=xhprof.so foo.php
    354 | 
    355 | 356 |

    You should get an output like: 357 | 358 |

    359 | Array
    360 | (
    361 |     [foo==>bar] => Array
    362 |         (
    363 |             [ct] => 2         # 2 calls to bar() from foo()
    364 |             [wt] => 27        # inclusive time in bar() when called from foo()
    365 |         )
    366 | 
    367 |     [foo==>strlen] => Array
    368 |         (
    369 |             [ct] => 2
    370 |             [wt] => 2
    371 |         )
    372 | 
    373 |     [bar==>bar@1] => Array    # a recursive call to bar()
    374 |         (
    375 |             [ct] => 1
    376 |             [wt] => 2
    377 |         )
    378 | 
    379 |     [main()==>foo] => Array
    380 |         (
    381 |             [ct] => 1
    382 |             [wt] => 74
    383 |         )
    384 | 
    385 |     [main()==>xhprof_disable] => Array
    386 |         (
    387 |             [ct] => 1
    388 |             [wt] => 0
    389 |         )
    390 | 
    391 |     [main()] => Array         # fake symbol representing root
    392 |         (
    393 |             [ct] => 1
    394 |             [wt] => 83
    395 |         )
    396 | 
    397 | )
    398 | 
    399 | 400 |

    Note: The raw data only contains "inclusive" metrics. For 401 | example, the wall time metric in the raw data represents inclusive 402 | time in microsecs. Exclusive times for any function are computed 403 | during the analysis/reporting phase. 404 | 405 |

    Note: By default only call counts & elapsed time is profiled. 406 | You can optionally also profile CPU time and/or memory usage. Replace, 407 | 408 |

      409 | xhprof_enable();
      410 | 
    411 | in the above program with, for example: 412 |
      413 | xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY);
      414 | 
    415 | 416 |

    You should now get an output like: 417 | 418 |

    419 | Array
    420 | (
    421 |     [foo==>bar] => Array
    422 |         (
    423 |             [ct] => 2        # number of calls to bar() from foo()
    424 |             [wt] => 37       # time in bar() when called from foo()
    425 |             [cpu] => 0       # cpu time in bar() when called from foo()
    426 |             [mu] => 2208     # change in PHP memory usage in bar() when called from foo()
    427 |             [pmu] => 0       # change in PHP peak memory usage in bar() when called from foo()
    428 |         )
    429 | 
    430 |     [foo==>strlen] => Array
    431 |         (
    432 |             [ct] => 2
    433 |             [wt] => 3
    434 |             [cpu] => 0
    435 |             [mu] => 624
    436 |             [pmu] => 0
    437 |         )
    438 | 
    439 |     [bar==>bar@1] => Array
    440 |         (
    441 |             [ct] => 1
    442 |             [wt] => 2
    443 |             [cpu] => 0
    444 |             [mu] => 856
    445 |             [pmu] => 0
    446 |         )
    447 | 
    448 |     [main()==>foo] => Array
    449 |         (
    450 |             [ct] => 1
    451 |             [wt] => 104
    452 |             [cpu] => 0
    453 |             [mu] => 4168
    454 |             [pmu] => 0
    455 |         )
    456 | 
    457 |     [main()==>xhprof_disable] => Array
    458 |         (
    459 |             [ct] => 1
    460 |             [wt] => 1
    461 |             [cpu] => 0
    462 |             [mu] => 344
    463 |             [pmu] => 0
    464 |         )
    465 | 
    466 |     [main()] => Array
    467 |         (
    468 |             [ct] => 1
    469 |             [wt] => 139
    470 |             [cpu] => 0
    471 |             [mu] => 5936
    472 |             [pmu] => 0
    473 |         )
    474 | 
    475 | )
    476 | 
    477 | 478 |

    Skipping builtin functions during profiling 479 | 480 |

    By default PHP builtin functions (such as strlen) are 481 | profiled. If you do not want to profile builtin functions (to either 482 | reduce the overhead of profiling further or size of generated raw 483 | data), you can use the XHPROF_FLAGS_NO_BUILTINS 484 | flag as in for example: 485 | 486 |

      487 | // do not profile builtin functions
      488 | xhprof_enable(XHPROF_FLAGS_NO_BUILTINS);
      489 | 
    490 | 491 | 492 |

    Ignoring specific functions during profiling (0.9.2 or higher) 493 | 494 |

    Starting with release 0.9.2 of xhprof, you can tell XHProf to 495 | ignore a specified list of functions during profiling. This allows you 496 | to ignore, for example, functions used for indirect function calls 497 | such as call_user_func and 498 | call_user_func_array. These intermediate functions 499 | unnecessarily complicate the call hierarchy and make the XHProf 500 | reports harder to interpret since they muddle the parent-child 501 | relationship for functions called indirectly. 502 | 503 |

    To specify the list of functions to be ignored during profiling 504 | use the 2nd (optional) argument to xhprof_enable. 505 | For example, 506 | 507 |

    508 | 
      509 | 
      510 | // elapsed time profiling; ignore call_user_func* during profiling
      511 | xhprof_enable(0,
      512 |              array('ignored_functions' =>  array('call_user_func',
      513 |                                                  'call_user_func_array')));
      514 | 
      515 | or,
      516 | 
      517 | // elapsed time + memory profiling; ignore call_user_func* during profiling
      518 | xhprof_enable(XHPROF_FLAGS_MEMORY,
      519 |               array('ignored_functions' =>  array('call_user_func',
      520 |                                                   'call_user_func_array')));
      521 | 
      522 | 
    523 | 524 | 525 |
  5. 526 | 527 |
  6. Setting up XHProf UI

    528 | 529 | 530 |
      531 | 532 |
    1. PHP source structure 533 |

      The XHProf UI is implemented in PHP. The code resides in two 534 | subdirectories, xhprof_html/ and xhprof_lib/. 535 | 536 |

      The xhprof_html directory contains the 3 top-level PHP pages. 537 | 538 |

        539 |
      • index.php: For viewing a single run or diff report. 540 |
      • callgraph.php: For viewing a callgraph of a XHProf run as an image. 541 |
      • typeahead.php: Used implicitly for the function typeahead form 542 | on a XHProf report. 543 |
      544 | 545 |

      The xhprof_lib directory contains supporting code for 546 | display as well as analysis (computing flat profile info, computing 547 | diffs, aggregating data from multiple runs, etc.). 548 | 549 |

    2. Web server config: You'll need to make sure that the 550 | xhprof_html/ directory is accessible from your web server, and that 551 | your web server is setup to serve PHP scripts. 552 | 553 |

    3. Managing XHProf Runs 554 | 555 |

      Clients have flexibility in how they save the XHProf raw data 556 | obtained from an XHProf run. The XHProf UI layer exposes an interface 557 | iXHProfRuns (see xhprof_lib/utils/xhprof_runs.php) that clients can 558 | implement. This allows the clients to tell the UI layer how to fetch 559 | the data corresponding to a XHProf run. 560 | 561 |

      The XHProf UI libaries come with a default file based 562 | implementation of the iXHProfRuns interface, namely 563 | "XHProfRuns_Default" (also in xhprof_lib/utils/xhprof_runs.php). 564 | This default implementation stores runs in the directory specified by 565 | xhprof.output_dir INI parameter. 566 | 567 |

      A XHProf run must be uniquely identified by a namespace and a run 568 | id. 569 | 570 | 571 | 572 |

      a) Saving XHProf data persistently: 573 | 574 |

      Assuming you are using the default implementation 575 | XHProfRuns_Default of the 576 | iXHProfRuns interface, a typical XHProf run 577 | followed by the save step might look something like: 578 | 579 | 580 |

      581 | // start profiling
      582 | xhprof_enable();
      583 | 
      584 | // run program
      585 | ....
      586 | 
      587 | // stop profiler
      588 | $xhprof_data = xhprof_disable();
      589 | 
      590 | //
      591 | // Saving the XHProf run
      592 | // using the default implementation of iXHProfRuns.
      593 | //
      594 | include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_lib.php";
      595 | include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_runs.php";
      596 | 
      597 | $xhprof_runs = new XHProfRuns_Default();
      598 | 
      599 | // Save the run under a namespace "xhprof_foo".
      600 | //
      601 | // **NOTE**:
      602 | // By default save_run() will automatically generate a unique
      603 | // run id for you. [You can override that behavior by passing
      604 | // a run id (optional arg) to the save_run() method instead.]
      605 | //
      606 | $run_id = $xhprof_runs->save_run($xhprof_data, "xhprof_foo");
      607 | 
      608 | echo "---------------\n".
      609 |      "Assuming you have set up the http based UI for \n".
      610 |      "XHProf at some address, you can view run at \n".
      611 |      "http://<xhprof-ui-address>/index.php?run=$run_id&source=xhprof_foo\n".
      612 |      "---------------\n";
      613 | 
      614 | 
      615 | 616 |

      The above should save the run as a file in the directory specified 617 | by the xhprof.output_dir INI parameter. The file's 618 | name might be something like 619 | 49bafaa3a3f66.xhprof_foo; the two parts being the 620 | run id ("49bafaa3a3f66") and the namespace ("xhprof_foo"). [If you 621 | want to create/assign run ids yourself (such as a database sequence 622 | number, or a timestamp), you can explicitly pass in the run id to the 623 | save_run method. 624 | 625 | 626 |

      b) Using your own implementation of iXHProfRuns 627 | 628 |

      If you decide you want your XHProf runs to be stored differently 629 | (either in a compressed format, in an alternate place such as DB, 630 | etc.) database, you'll need to implement a class that implements the 631 | iXHProfRuns() interface. 632 | 633 |

      You'll also need to modify the 3 main PHP entry pages (index.php, 634 | callgraph.php, typeahead.php) in the "xhprof_html/" directory to use 635 | the new class instead of the default class XHProfRuns_Default. 636 | Change this line in the 3 files. 637 | 638 |

      639 | $xhprof_runs_impl = new XHProfRuns_Default();
      640 | 
      641 | 642 |

      You'll also need to "include" the file that implements your class in 643 | the above files. 644 | 645 | 646 |

    4. Accessing runs from UI 647 | 648 |

      a) Viewing a Single Run Report 649 | 650 |

      To view the report for run id say <run_id> and namespace 651 | <namespace> use a URL of the form: 652 | 653 |

      654 | http://<xhprof-ui-address>/index.php?run=<run_id>&source=<namespace> 655 | 656 | 657 |

      For example, 658 |

      659 | http://<xhprof-ui-address>/index.php?run=49bafaa3a3f66&source=xhprof_foo 660 | 661 | 662 | 663 |

      b) Viewing a Diff Report 664 | 665 |

      To view the report for run ids say <run_id1> and 666 | <run_id2> in namespace <namespace> use a URL of the form: 667 | 668 |

      669 | http://<xhprof-ui-address>/index.php?run1=<run_id1>&run2=<run_id2>&source=<namespace> 670 | 671 | 672 |

      c) Aggregate Report 673 | 674 |

      You can also specify a set of run ids for which you want an aggregated view/report. 675 | 676 |

      Say you have three XHProf runs with ids 1, 2 & 3 in namespace 677 | "benchmark". To view an aggregate report of these runs: 678 | 679 |

        680 | http://<xhprof-ui-address>/index.php?run=1,2,3&source=benchmark 681 |

      682 | 683 |

      Weighted aggregations: Further suppose that the above three runs 684 | correspond to three types of programs p1.php, p2.php and p3.php that 685 | typically occur in a mix of 20%, 30%, 50% respectively. To view an 686 | aggregate report that corresponds to a weighted average of these runs 687 | using: 688 | 689 |

        690 | http://<xhprof-ui-address>/index.php?run=1,2,3&wts=20,30,50&source=benchmark 691 |

      692 | 693 | 694 |
    695 | 696 |
  7. Notes on using XHProf in production

    697 | 698 |

    Some observations/guidelines. Your mileage may vary: 699 | 700 |

      701 | 702 |
    • CPU timer (getrusage) on Linux has high overheads. It is also 703 | coarse grained (millisec accuracy rather than microsec level) to be 704 | useful at function level. Therefore, the skew in reported numbers 705 | when using XHPROF_FLAGS_CPU mode tends to be higher. 706 | 707 |

      We recommend using elapsed time + memory profiling mode in 708 | production. [Note: The additional overhead of memory profiling 709 | mode is really low.] 710 | 711 |

        
        712 |   // elapsed time profiling (default) + memory profiling
        713 |   xhprof_enable(XHPROF_FLAGS_MEMORY);
        714 | 
        715 |

      716 | 717 | 718 |
    • Profiling a random sample of pages/requests works well in capturing 719 | data that is representative of your production workload. 720 | 721 |

      To profile say 1/10000 of your requests, instrument the beginning of 722 | your request processing with something along the lines of: 723 | 724 |

        
        725 |  if (mt_rand(1, 10000) == 1) {
        726 |    xhprof_enable(XHPROF_FLAGS_MEMORY);
        727 |    $xhprof_on = true;
        728 |  }
        729 | 

      730 | 731 |

      At the end of the request (or in a request shutdown function), you might 732 | then do something like: 733 | 734 |

        
        735 |  if ($xhprof_on) {
        736 |    // stop profiler
        737 |    $xhprof_data = xhprof_disable();
        738 | 
        739 |    // save $xhprof_data somewhere (say a central DB)
        740 |    ...
        741 |  }
        742 | 

      743 | 744 |

      You can then rollup/aggregate these individual profiles by time 745 | (e.g., 5 minutely/hourly/daily basis), page/request type,or other 746 | dimensions using xhprof_aggregate_runs(). 747 | 748 |

    749 | 750 | 751 |
  8. Lightweight Sampling Mode

    752 | 753 |

    The xhprof extension also provides a very light weight sampling 754 | mode. The sampling interval is 0.1 secs. Samples record the full 755 | function call stack. The sampling mode can be useful if an extremely 756 | low overhead means of doing performance monitoring and diagnostics is 757 | desired. 758 | 759 |

    The relevant functions exposed by the extension for using the 760 | sampling mode are xhprof_sample_enable() and 761 | xhprof_sample_disable(). 762 | 763 | 764 |

    [TBD: more detailed documentation on sampling mode.] 765 | 766 | 767 |

  9. Additional Features

  10. 768 | 769 |

    The xhprof_lib/utils/xhprof_lib.php file contains 770 | additional library functions that can be used for manipulating/ 771 | aggregating XHProf runs. 772 | 773 |

    For example: 774 | 775 |

    793 | 794 | 795 |
      796 | 797 |
    798 | 799 |
  11. Dependencies

  12. 800 | 801 | 814 |
  13. Acknowledgements

    815 | 816 |

    The HTML-based navigational interface for browsing profiler results 817 | is inspired by that of a similar tool that exists for Oracle's stored 818 | procedure language, PL/SQL. But that's where the similarity ends; the 819 | internals of the profiler itself are quite different. 820 | 821 |

  14. 822 | 823 |
824 | 825 | 826 | 827 | -------------------------------------------------------------------------------- /public/xhprof/xhprof_html/docs/sample-callgraph-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KickPeach/kickPeach/ec8ae05c34e8adeaa02ac210ceaa5a24ad668503/public/xhprof/xhprof_html/docs/sample-callgraph-image.jpg -------------------------------------------------------------------------------- /public/xhprof/xhprof_html/docs/sample-diff-report-flat-view.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KickPeach/kickPeach/ec8ae05c34e8adeaa02ac210ceaa5a24ad668503/public/xhprof/xhprof_html/docs/sample-diff-report-flat-view.jpg -------------------------------------------------------------------------------- /public/xhprof/xhprof_html/docs/sample-diff-report-parent-child-view.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KickPeach/kickPeach/ec8ae05c34e8adeaa02ac210ceaa5a24ad668503/public/xhprof/xhprof_html/docs/sample-diff-report-parent-child-view.jpg -------------------------------------------------------------------------------- /public/xhprof/xhprof_html/docs/sample-flat-view.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KickPeach/kickPeach/ec8ae05c34e8adeaa02ac210ceaa5a24ad668503/public/xhprof/xhprof_html/docs/sample-flat-view.jpg -------------------------------------------------------------------------------- /public/xhprof/xhprof_html/docs/sample-parent-child-view.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KickPeach/kickPeach/ec8ae05c34e8adeaa02ac210ceaa5a24ad668503/public/xhprof/xhprof_html/docs/sample-parent-child-view.jpg -------------------------------------------------------------------------------- /public/xhprof/xhprof_html/index.php: -------------------------------------------------------------------------------- 1 | array(XHPROF_STRING_PARAM, ''), 42 | 'wts' => array(XHPROF_STRING_PARAM, ''), 43 | 'symbol' => array(XHPROF_STRING_PARAM, ''), 44 | 'sort' => array(XHPROF_STRING_PARAM, 'wt'), // wall time 45 | 'run1' => array(XHPROF_STRING_PARAM, ''), 46 | 'run2' => array(XHPROF_STRING_PARAM, ''), 47 | 'source' => array(XHPROF_STRING_PARAM, 'xhprof'), 48 | 'all' => array(XHPROF_UINT_PARAM, 0), 49 | ); 50 | 51 | // pull values of these params, and create named globals for each param 52 | xhprof_param_init($params); 53 | 54 | /* reset params to be a array of variable names to values 55 | by the end of this page, param should only contain values that need 56 | to be preserved for the next page. unset all unwanted keys in $params. 57 | */ 58 | foreach ($params as $k => $v) { 59 | $params[$k] = $$k; 60 | 61 | // unset key from params that are using default values. So URLs aren't 62 | // ridiculously long. 63 | if ($params[$k] == $v[1]) { 64 | unset($params[$k]); 65 | } 66 | } 67 | 68 | echo ""; 69 | 70 | echo "XHProf: Hierarchical Profiler Report"; 71 | xhprof_include_js_css(); 72 | echo ""; 73 | 74 | echo ""; 75 | 76 | $vbar = ' class="vbar"'; 77 | $vwbar = ' class="vwbar"'; 78 | $vwlbar = ' class="vwlbar"'; 79 | $vbbar = ' class="vbbar"'; 80 | $vrbar = ' class="vrbar"'; 81 | $vgbar = ' class="vgbar"'; 82 | 83 | $xhprof_runs_impl = new XHProfRuns_Default(); 84 | 85 | displayXHProfReport($xhprof_runs_impl, $params, $source, $run, $wts, 86 | $symbol, $sort, $run1, $run2); 87 | 88 | 89 | echo ""; 90 | echo ""; 91 | -------------------------------------------------------------------------------- /public/xhprof/xhprof_html/jquery/indicator.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KickPeach/kickPeach/ec8ae05c34e8adeaa02ac210ceaa5a24ad668503/public/xhprof/xhprof_html/jquery/indicator.gif -------------------------------------------------------------------------------- /public/xhprof/xhprof_html/jquery/jquery.autocomplete.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Autocomplete - jQuery plugin 1.0.2 3 | * 4 | * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer 5 | * 6 | * Dual licensed under the MIT and GPL licenses: 7 | * http://www.opensource.org/licenses/mit-license.php 8 | * http://www.gnu.org/licenses/gpl.html 9 | * 10 | * Revision: $Id$ 11 | * 12 | */ 13 | 14 | .ac_results { 15 | padding: 0px; 16 | border: 1px solid black; 17 | background-color: white; 18 | overflow: hidden; 19 | z-index: 99999; 20 | } 21 | 22 | .ac_results ul { 23 | width: 100%; 24 | list-style-position: outside; 25 | list-style: none; 26 | padding: 0; 27 | margin: 0; 28 | } 29 | 30 | .ac_results li { 31 | margin: 0px; 32 | padding: 2px 5px; 33 | cursor: default; 34 | display: block; 35 | /* 36 | if width will be 100% horizontal scrollbar will apear 37 | when scroll mode will be used 38 | */ 39 | /*width: 100%;*/ 40 | font: menu; 41 | font-size: 12px; 42 | /* 43 | it is very important, if line-height not setted or setted 44 | in relative units scroll will be broken in firefox 45 | */ 46 | line-height: 16px; 47 | overflow: hidden; 48 | } 49 | 50 | .ac_loading { 51 | background: white url('indicator.gif') right center no-repeat; 52 | } 53 | 54 | .ac_odd { 55 | background-color: #eee; 56 | } 57 | 58 | .ac_over { 59 | background-color: #0A246A; 60 | color: white; 61 | } 62 | -------------------------------------------------------------------------------- /public/xhprof/xhprof_html/jquery/jquery.autocomplete.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Autocomplete - jQuery plugin 1.0.2 3 | * 4 | * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer 5 | * 6 | * Dual licensed under the MIT and GPL licenses: 7 | * http://www.opensource.org/licenses/mit-license.php 8 | * http://www.gnu.org/licenses/gpl.html 9 | * 10 | * Revision: $Id: jquery.autocomplete.js,v 1.1.1.1 2009-03-17 18:35:18 kannan Exp $ 11 | * 12 | */ 13 | 14 | ;(function($) { 15 | 16 | $.fn.extend({ 17 | autocomplete: function(urlOrData, options) { 18 | var isUrl = typeof urlOrData == "string"; 19 | options = $.extend({}, $.Autocompleter.defaults, { 20 | url: isUrl ? urlOrData : null, 21 | data: isUrl ? null : urlOrData, 22 | delay: isUrl ? $.Autocompleter.defaults.delay : 10, 23 | max: options && !options.scroll ? 10 : 150 24 | }, options); 25 | 26 | // if highlight is set to false, replace it with a do-nothing function 27 | options.highlight = options.highlight || function(value) { return value; }; 28 | 29 | // if the formatMatch option is not specified, then use formatItem for backwards compatibility 30 | options.formatMatch = options.formatMatch || options.formatItem; 31 | 32 | return this.each(function() { 33 | new $.Autocompleter(this, options); 34 | }); 35 | }, 36 | result: function(handler) { 37 | return this.bind("result", handler); 38 | }, 39 | search: function(handler) { 40 | return this.trigger("search", [handler]); 41 | }, 42 | flushCache: function() { 43 | return this.trigger("flushCache"); 44 | }, 45 | setOptions: function(options){ 46 | return this.trigger("setOptions", [options]); 47 | }, 48 | unautocomplete: function() { 49 | return this.trigger("unautocomplete"); 50 | } 51 | }); 52 | 53 | $.Autocompleter = function(input, options) { 54 | 55 | var KEY = { 56 | UP: 38, 57 | DOWN: 40, 58 | DEL: 46, 59 | TAB: 9, 60 | RETURN: 13, 61 | ESC: 27, 62 | COMMA: 188, 63 | PAGEUP: 33, 64 | PAGEDOWN: 34, 65 | BACKSPACE: 8 66 | }; 67 | 68 | // Create $ object for input element 69 | var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass); 70 | 71 | var timeout; 72 | var previousValue = ""; 73 | var cache = $.Autocompleter.Cache(options); 74 | var hasFocus = 0; 75 | var lastKeyPressCode; 76 | var config = { 77 | mouseDownOnSelect: false 78 | }; 79 | var select = $.Autocompleter.Select(options, input, selectCurrent, config); 80 | 81 | var blockSubmit; 82 | 83 | // prevent form submit in opera when selecting with return key 84 | $.browser.opera && $(input.form).bind("submit.autocomplete", function() { 85 | if (blockSubmit) { 86 | blockSubmit = false; 87 | return false; 88 | } 89 | }); 90 | 91 | // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all 92 | $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) { 93 | // track last key pressed 94 | lastKeyPressCode = event.keyCode; 95 | switch(event.keyCode) { 96 | 97 | case KEY.UP: 98 | event.preventDefault(); 99 | if ( select.visible() ) { 100 | select.prev(); 101 | } else { 102 | onChange(0, true); 103 | } 104 | break; 105 | 106 | case KEY.DOWN: 107 | event.preventDefault(); 108 | if ( select.visible() ) { 109 | select.next(); 110 | } else { 111 | onChange(0, true); 112 | } 113 | break; 114 | 115 | case KEY.PAGEUP: 116 | event.preventDefault(); 117 | if ( select.visible() ) { 118 | select.pageUp(); 119 | } else { 120 | onChange(0, true); 121 | } 122 | break; 123 | 124 | case KEY.PAGEDOWN: 125 | event.preventDefault(); 126 | if ( select.visible() ) { 127 | select.pageDown(); 128 | } else { 129 | onChange(0, true); 130 | } 131 | break; 132 | 133 | // matches also semicolon 134 | case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA: 135 | case KEY.TAB: 136 | case KEY.RETURN: 137 | if( selectCurrent() ) { 138 | // stop default to prevent a form submit, Opera needs special handling 139 | event.preventDefault(); 140 | blockSubmit = true; 141 | return false; 142 | } 143 | break; 144 | 145 | case KEY.ESC: 146 | select.hide(); 147 | break; 148 | 149 | default: 150 | clearTimeout(timeout); 151 | timeout = setTimeout(onChange, options.delay); 152 | break; 153 | } 154 | }).focus(function(){ 155 | // track whether the field has focus, we shouldn't process any 156 | // results if the field no longer has focus 157 | hasFocus++; 158 | }).blur(function() { 159 | hasFocus = 0; 160 | if (!config.mouseDownOnSelect) { 161 | hideResults(); 162 | } 163 | }).click(function() { 164 | // show select when clicking in a focused field 165 | if ( hasFocus++ > 1 && !select.visible() ) { 166 | onChange(0, true); 167 | } 168 | }).bind("search", function() { 169 | // TODO why not just specifying both arguments? 170 | var fn = (arguments.length > 1) ? arguments[1] : null; 171 | function findValueCallback(q, data) { 172 | var result; 173 | if( data && data.length ) { 174 | for (var i=0; i < data.length; i++) { 175 | if( data[i].result.toLowerCase() == q.toLowerCase() ) { 176 | result = data[i]; 177 | break; 178 | } 179 | } 180 | } 181 | if( typeof fn == "function" ) fn(result); 182 | else $input.trigger("result", result && [result.data, result.value]); 183 | } 184 | $.each(trimWords($input.val()), function(i, value) { 185 | request(value, findValueCallback, findValueCallback); 186 | }); 187 | }).bind("flushCache", function() { 188 | cache.flush(); 189 | }).bind("setOptions", function() { 190 | $.extend(options, arguments[1]); 191 | // if we've updated the data, repopulate 192 | if ( "data" in arguments[1] ) 193 | cache.populate(); 194 | }).bind("unautocomplete", function() { 195 | select.unbind(); 196 | $input.unbind(); 197 | $(input.form).unbind(".autocomplete"); 198 | }); 199 | 200 | 201 | function selectCurrent() { 202 | var selected = select.selected(); 203 | if( !selected ) 204 | return false; 205 | 206 | var v = selected.result; 207 | previousValue = v; 208 | 209 | if ( options.multiple ) { 210 | var words = trimWords($input.val()); 211 | if ( words.length > 1 ) { 212 | v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v; 213 | } 214 | v += options.multipleSeparator; 215 | } 216 | 217 | $input.val(v); 218 | hideResultsNow(); 219 | $input.trigger("result", [selected.data, selected.value]); 220 | return true; 221 | } 222 | 223 | function onChange(crap, skipPrevCheck) { 224 | if( lastKeyPressCode == KEY.DEL ) { 225 | select.hide(); 226 | return; 227 | } 228 | 229 | var currentValue = $input.val(); 230 | 231 | if ( !skipPrevCheck && currentValue == previousValue ) 232 | return; 233 | 234 | previousValue = currentValue; 235 | 236 | currentValue = lastWord(currentValue); 237 | if ( currentValue.length >= options.minChars) { 238 | $input.addClass(options.loadingClass); 239 | if (!options.matchCase) 240 | currentValue = currentValue.toLowerCase(); 241 | request(currentValue, receiveData, hideResultsNow); 242 | } else { 243 | stopLoading(); 244 | select.hide(); 245 | } 246 | }; 247 | 248 | function trimWords(value) { 249 | if ( !value ) { 250 | return [""]; 251 | } 252 | var words = value.split( options.multipleSeparator ); 253 | var result = []; 254 | $.each(words, function(i, value) { 255 | if ( $.trim(value) ) 256 | result[i] = $.trim(value); 257 | }); 258 | return result; 259 | } 260 | 261 | function lastWord(value) { 262 | if ( !options.multiple ) 263 | return value; 264 | var words = trimWords(value); 265 | return words[words.length - 1]; 266 | } 267 | 268 | // fills in the input box w/the first match (assumed to be the best match) 269 | // q: the term entered 270 | // sValue: the first matching result 271 | function autoFill(q, sValue){ 272 | // autofill in the complete box w/the first match as long as the user hasn't entered in more data 273 | // if the last user key pressed was backspace, don't autofill 274 | if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) { 275 | // fill in the value (keep the case the user has typed) 276 | $input.val($input.val() + sValue.substring(lastWord(previousValue).length)); 277 | // select the portion of the value not typed by the user (so the next character will erase) 278 | $.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length); 279 | } 280 | }; 281 | 282 | function hideResults() { 283 | clearTimeout(timeout); 284 | timeout = setTimeout(hideResultsNow, 200); 285 | }; 286 | 287 | function hideResultsNow() { 288 | var wasVisible = select.visible(); 289 | select.hide(); 290 | clearTimeout(timeout); 291 | stopLoading(); 292 | if (options.mustMatch) { 293 | // call search and run callback 294 | $input.search( 295 | function (result){ 296 | // if no value found, clear the input box 297 | if( !result ) { 298 | if (options.multiple) { 299 | var words = trimWords($input.val()).slice(0, -1); 300 | $input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") ); 301 | } 302 | else 303 | $input.val( "" ); 304 | } 305 | } 306 | ); 307 | } 308 | if (wasVisible) 309 | // position cursor at end of input field 310 | $.Autocompleter.Selection(input, input.value.length, input.value.length); 311 | }; 312 | 313 | function receiveData(q, data) { 314 | if ( data && data.length && hasFocus ) { 315 | stopLoading(); 316 | select.display(data, q); 317 | autoFill(q, data[0].value); 318 | select.show(); 319 | } else { 320 | hideResultsNow(); 321 | } 322 | }; 323 | 324 | function request(term, success, failure) { 325 | if (!options.matchCase) 326 | term = term.toLowerCase(); 327 | var data = cache.load(term); 328 | // recieve the cached data 329 | if (data && data.length) { 330 | success(term, data); 331 | // if an AJAX url has been supplied, try loading the data now 332 | } else if( (typeof options.url == "string") && (options.url.length > 0) ){ 333 | 334 | var extraParams = { 335 | timestamp: +new Date() 336 | }; 337 | $.each(options.extraParams, function(key, param) { 338 | extraParams[key] = typeof param == "function" ? param() : param; 339 | }); 340 | 341 | $.ajax({ 342 | // try to leverage ajaxQueue plugin to abort previous requests 343 | mode: "abort", 344 | // limit abortion to this input 345 | port: "autocomplete" + input.name, 346 | dataType: options.dataType, 347 | url: options.url, 348 | data: $.extend({ 349 | q: lastWord(term), 350 | limit: options.max 351 | }, extraParams), 352 | success: function(data) { 353 | var parsed = options.parse && options.parse(data) || parse(data); 354 | cache.add(term, parsed); 355 | success(term, parsed); 356 | } 357 | }); 358 | } else { 359 | // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match 360 | select.emptyList(); 361 | failure(term); 362 | } 363 | }; 364 | 365 | function parse(data) { 366 | var parsed = []; 367 | var rows = data.split("\n"); 368 | for (var i=0; i < rows.length; i++) { 369 | var row = $.trim(rows[i]); 370 | if (row) { 371 | row = row.split("|"); 372 | parsed[parsed.length] = { 373 | data: row, 374 | value: row[0], 375 | result: options.formatResult && options.formatResult(row, row[0]) || row[0] 376 | }; 377 | } 378 | } 379 | return parsed; 380 | }; 381 | 382 | function stopLoading() { 383 | $input.removeClass(options.loadingClass); 384 | }; 385 | 386 | }; 387 | 388 | $.Autocompleter.defaults = { 389 | inputClass: "ac_input", 390 | resultsClass: "ac_results", 391 | loadingClass: "ac_loading", 392 | minChars: 1, 393 | delay: 400, 394 | matchCase: false, 395 | matchSubset: true, 396 | matchContains: false, 397 | cacheLength: 10, 398 | max: 100, 399 | mustMatch: false, 400 | extraParams: {}, 401 | selectFirst: true, 402 | formatItem: function(row) { return row[0]; }, 403 | formatMatch: null, 404 | autoFill: false, 405 | width: 0, 406 | multiple: false, 407 | multipleSeparator: ", ", 408 | highlight: function(value, term) { 409 | return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "$1"); 410 | }, 411 | scroll: true, 412 | scrollHeight: 180 413 | }; 414 | 415 | $.Autocompleter.Cache = function(options) { 416 | 417 | var data = {}; 418 | var length = 0; 419 | 420 | function matchSubset(s, sub) { 421 | if (!options.matchCase) 422 | s = s.toLowerCase(); 423 | var i = s.indexOf(sub); 424 | if (i == -1) return false; 425 | return i == 0 || options.matchContains; 426 | }; 427 | 428 | function add(q, value) { 429 | if (length > options.cacheLength){ 430 | flush(); 431 | } 432 | if (!data[q]){ 433 | length++; 434 | } 435 | data[q] = value; 436 | } 437 | 438 | function populate(){ 439 | if( !options.data ) return false; 440 | // track the matches 441 | var stMatchSets = {}, 442 | nullData = 0; 443 | 444 | // no url was specified, we need to adjust the cache length to make sure it fits the local data store 445 | if( !options.url ) options.cacheLength = 1; 446 | 447 | // track all options for minChars = 0 448 | stMatchSets[""] = []; 449 | 450 | // loop through the array and create a lookup structure 451 | for ( var i = 0, ol = options.data.length; i < ol; i++ ) { 452 | var rawValue = options.data[i]; 453 | // if rawValue is a string, make an array otherwise just reference the array 454 | rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue; 455 | 456 | var value = options.formatMatch(rawValue, i+1, options.data.length); 457 | if ( value === false ) 458 | continue; 459 | 460 | var firstChar = value.charAt(0).toLowerCase(); 461 | // if no lookup array for this character exists, look it up now 462 | if( !stMatchSets[firstChar] ) 463 | stMatchSets[firstChar] = []; 464 | 465 | // if the match is a string 466 | var row = { 467 | value: value, 468 | data: rawValue, 469 | result: options.formatResult && options.formatResult(rawValue) || value 470 | }; 471 | 472 | // push the current match into the set list 473 | stMatchSets[firstChar].push(row); 474 | 475 | // keep track of minChars zero items 476 | if ( nullData++ < options.max ) { 477 | stMatchSets[""].push(row); 478 | } 479 | }; 480 | 481 | // add the data items to the cache 482 | $.each(stMatchSets, function(i, value) { 483 | // increase the cache size 484 | options.cacheLength++; 485 | // add to the cache 486 | add(i, value); 487 | }); 488 | } 489 | 490 | // populate any existing data 491 | setTimeout(populate, 25); 492 | 493 | function flush(){ 494 | data = {}; 495 | length = 0; 496 | } 497 | 498 | return { 499 | flush: flush, 500 | add: add, 501 | populate: populate, 502 | load: function(q) { 503 | if (!options.cacheLength || !length) 504 | return null; 505 | /* 506 | * if dealing w/local data and matchContains than we must make sure 507 | * to loop through all the data collections looking for matches 508 | */ 509 | if( !options.url && options.matchContains ){ 510 | // track all matches 511 | var csub = []; 512 | // loop through all the data grids for matches 513 | for( var k in data ){ 514 | // don't search through the stMatchSets[""] (minChars: 0) cache 515 | // this prevents duplicates 516 | if( k.length > 0 ){ 517 | var c = data[k]; 518 | $.each(c, function(i, x) { 519 | // if we've got a match, add it to the array 520 | if (matchSubset(x.value, q)) { 521 | csub.push(x); 522 | } 523 | }); 524 | } 525 | } 526 | return csub; 527 | } else 528 | // if the exact item exists, use it 529 | if (data[q]){ 530 | return data[q]; 531 | } else 532 | if (options.matchSubset) { 533 | for (var i = q.length - 1; i >= options.minChars; i--) { 534 | var c = data[q.substr(0, i)]; 535 | if (c) { 536 | var csub = []; 537 | $.each(c, function(i, x) { 538 | if (matchSubset(x.value, q)) { 539 | csub[csub.length] = x; 540 | } 541 | }); 542 | return csub; 543 | } 544 | } 545 | } 546 | return null; 547 | } 548 | }; 549 | }; 550 | 551 | $.Autocompleter.Select = function (options, input, select, config) { 552 | var CLASSES = { 553 | ACTIVE: "ac_over" 554 | }; 555 | 556 | var listItems, 557 | active = -1, 558 | data, 559 | term = "", 560 | needsInit = true, 561 | element, 562 | list; 563 | 564 | // Create results 565 | function init() { 566 | if (!needsInit) 567 | return; 568 | element = $("
") 569 | .hide() 570 | .addClass(options.resultsClass) 571 | .css("position", "absolute") 572 | .appendTo(document.body); 573 | 574 | list = $("