]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/SugarXHprof/SugarXHprof.php
Release 6.5.10
[Github/sugarcrm.git] / include / SugarXHprof / SugarXHprof.php
1 <?php
2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4  * SugarCRM Community Edition is a customer relationship management program developed by
5  * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
6  * 
7  * This program is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU Affero General Public License version 3 as published by the
9  * Free Software Foundation with the addition of the following permission added
10  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11  * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
13  * 
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
17  * details.
18  * 
19  * You should have received a copy of the GNU Affero General Public License along with
20  * this program; if not, see http://www.gnu.org/licenses or write to the Free
21  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22  * 02110-1301 USA.
23  * 
24  * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25  * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
26  * 
27  * The interactive user interfaces in modified source and object code versions
28  * of this program must display Appropriate Legal Notices, as required under
29  * Section 5 of the GNU Affero General Public License version 3.
30  * 
31  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32  * these Appropriate Legal Notices must retain the display of the "Powered by
33  * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34  * technical reasons, the Appropriate Legal Notices must display the words
35  * "Powered by SugarCRM".
36  ********************************************************************************/
37
38
39 /**
40  * Class allows us to use XHprof for profiling
41  * To enable profiling you should add next properties to config_override.php
42  *
43  * @see SugarXHprof::$enable            for $sugar_config['xhprof_config']['enable']
44  * @see SugarXHprof::$manager           for $sugar_config['xhprof_config']['manager']
45  * @see SugarXHprof::$log_to            for $sugar_config['xhprof_config']['log_to']
46  * @see SugarXHprof::$sample_rate       for $sugar_config['xhprof_config']['sample_rate']
47  * @see SugarXHprof::$ignored_functions for $sugar_config['xhprof_config']['ignored_functions']
48  * @see SugarXHprof::$flags             for $sugar_config['xhprof_config']['flags']
49  *
50  * To run profiler you should call SugarXHprof::getInstance()->start();
51  * To stop profiler you should call SugarXHprof::getInstance()->end();
52  * 'start' method registers 'end' method as shutdown function because of it call of 'end' method is unnecessary if you want profile all calls
53  * Also 'start' method is called automatically in entryPoint.php file
54  *
55  * Names of generated files are prefix.microtime.module.action for modules and prefix.microtime.'entryPoint'.entryPoint for entry points
56  * If you want to see reports you should install https://github.com/facebook/xhprof to some directory and run it as http://your.domain/path2xhprof/xhprof_html/?run=prefix.microtime&source=module.action
57  * For 507bf986e44d9.1350302086.9285.Leads.listview.xhprof file url will be look like http://your.domain/path2xhprof/xhprof_html/?run=507bf986e44d9.1350302086.9285&source=Leads.listview
58  *
59  * If you want to customize SugarXHprof you should create file in custom/include/SugarXHprof/ folder and name file as name of your custom class
60  * Change $sugar_config['xhprof_config']['manager'] to be name of your custom class
61  * Custom class has to extend from SugarXHprof
62  * If custom class doesn't exist or doesn't extend from SugarXHprof then SugarXHprof be used
63  */
64 class SugarXHprof
65 {
66     /**
67      * @var SugarXHprof instance of profiler
68      */
69     protected static $instance = null;
70
71     /**
72      * Because of unregister_shutdown_function is not present in php we have to skip calls of 'end' method if that property equals to false
73      *
74      * @var bool is shutdown function registered or not
75      */
76     protected $registered = false;
77
78     /**
79      * @var bool enable profiler or not, it will be disabled by some reasons
80      * @see SugarXHprof::loadConfig()
81      */
82     protected static $enable = false;
83
84     /**
85      * @var string class of manager for customization, has to extend from SugarXHprof class
86      */
87     protected static $manager = __CLASS__;
88
89     /**
90      * @var string path to directory for logs, if log_to is empty then xhprof.output_dir be used
91      */
92     protected static $log_to = '';
93
94     /**
95      * @var int where value is a number and 1/value requests are profiled. So to sample all requests set it to 1
96      */
97     protected static $sample_rate = 10;
98
99     /**
100      * @var array array of function names to ignore from the profile (pass into xhprof_enable)
101      */
102     protected static $ignored_functions = array();
103
104     /**
105      * @var int flags for xhprof
106      * @see http://www.php.net/manual/xhprof.constants.php
107      */
108     protected static $flags = 0;
109
110     /**
111      * Populates configuration from $sugar_config to self properties
112      */
113     protected static function loadConfig()
114     {
115         if (!empty($GLOBALS['sugar_config']['xhprof_config']))
116         {
117             foreach($GLOBALS['sugar_config']['xhprof_config'] as $k => $v)
118             {
119                 if (isset($v) && property_exists(__CLASS__, $k))
120                 {
121                     self::${$k} = $v;
122                 }
123             }
124         }
125
126         // disabling profiler if XHprof extension is not loaded
127         if (extension_loaded('xhprof') == false)
128         {
129             self::$enable = false;
130         }
131
132         // using default directory for profiler if it is not set
133         if (empty(self::$log_to))
134         {
135             self::$log_to = ini_get('xhprof.output_dir');
136         }
137
138         // disabling profiler if directory is not exist or is not writable
139         if (is_dir(self::$log_to) == false || is_writable(self::$log_to) == false)
140         {
141             self::$enable = false;
142         }
143     }
144
145     /**
146      * Tries to load custom profiler. If it doesn't exist then use itself
147      *
148      * @return SugarXHprof
149      */
150     public static function getInstance()
151     {
152         if (self::$instance != null)
153         {
154             return self::$instance;
155         }
156
157         self::loadConfig();
158
159         if (is_file('custom/include/SugarXHprof/' . self::$manager . '.php'))
160         {
161             require_once 'custom/include/SugarXHprof/' . self::$manager . '.php';
162         }
163         elseif (is_file('include/SugarXHprof/' . self::$manager . '.php'))
164         {
165             require_once 'include/SugarXHprof/' . self::$manager . '.php';
166         }
167         if (class_exists(self::$manager) && is_subclass_of(self::$manager, __CLASS__))
168         {
169             self::$instance = new self::$manager();
170         }
171         else
172         {
173             self::$instance = new self();
174         }
175         return self::$instance;
176     }
177
178     /**
179      * Method tries to detect entryPoint, service, module & action and returns it as string
180      *
181      * @return string action
182      */
183     static public function detectAction()
184     {
185         $action = '';
186
187         // index.php
188         if (!empty($GLOBALS['app']) && $GLOBALS['app'] instanceof SugarApplication && $GLOBALS['app']->controller instanceof SugarController)
189         {
190             if (!empty($_REQUEST['entryPoint']))
191             {
192                 if (!empty($GLOBALS['app']->controller->entry_point_registry) && !empty($GLOBALS['app']->controller->entry_point_registry[$_REQUEST['entryPoint']]))
193                 {
194                     $action .= '.entryPoint.' . $_REQUEST['entryPoint'];
195                 }
196                 else
197                 {
198                     $action .= '.entryPoint.unknown';
199                 }
200             }
201             else
202             {
203                 $action .= '.' . $GLOBALS['app']->controller->module . '.' . $GLOBALS['app']->controller->action;
204             }
205         }
206         // soap.php
207         elseif (!empty($GLOBALS['server']) && $GLOBALS['server'] instanceof soap_server)
208         {
209             if ($GLOBALS['server']->methodname)
210             {
211                 $action .= '.soap.' . $GLOBALS['server']->methodname;
212             }
213             else
214             {
215                 $action .= '.soap.wsdl';
216             }
217         }
218         // service soap
219         elseif (!empty($GLOBALS['service_object']) && $GLOBALS['service_object'] instanceof SugarSoapService)
220         {
221             $action .= '.soap.' . $GLOBALS['service_object']->getRegisteredClass();
222             if ($GLOBALS['service_object']->getServer() instanceof soap_server)
223             {
224                 if ($GLOBALS['service_object']->getServer()->methodname)
225                 {
226                     $action .= '.' . $GLOBALS['service_object']->getServer()->methodname;
227                 }
228                 else
229                 {
230                     $action .= '.wsdl';
231                 }
232             }
233             else
234             {
235                 $action .= '.unknown';
236             }
237         }
238         // service rest
239         elseif (!empty($GLOBALS['service_object']) && $GLOBALS['service_object'] instanceof SugarRestService)
240         {
241             $action .= '.rest.' . $GLOBALS['service_object']->getRegisteredImplClass();
242             if (!empty($_REQUEST['method']) && method_exists($GLOBALS['service_object']->implementation, $_REQUEST['method']))
243             {
244                 $action .= '.' . $_REQUEST['method'];
245             }
246             elseif (empty($_REQUEST['method']))
247             {
248                 $action .= '.index';
249             }
250             else
251             {
252                 $action .= '.unknown';
253             }
254         }
255         // unknown
256         else
257         {
258             $action .= '.' . basename($_SERVER['SCRIPT_FILENAME']);
259         }
260
261         return $action;
262     }
263
264     /**
265      * Tries to enabled xhprof if all settings were passed
266      */
267     public function start()
268     {
269         if (self::$enable == false)
270         {
271             return;
272         }
273
274         if (self::$sample_rate == 0)
275         {
276             return;
277         }
278
279         $rate = 1 / self::$sample_rate * 100;
280         if (rand(0, 100) > $rate)
281         {
282             return;
283         }
284
285         register_shutdown_function(array(
286             $this,
287             'end'
288         ));
289         $this->registered = true;
290
291         xhprof_enable(self::$flags, array(
292             'ignored_functions' => self::$ignored_functions
293         ));
294     }
295
296     /**
297      * Tries to collect data from XHprof after call of 'start' method
298      */
299     public function end()
300     {
301         if ($this->registered == false)
302         {
303             return;
304         }
305         $this->registered = false;
306
307         if (self::$enable == false)
308         {
309             return;
310         }
311
312         $data = xhprof_disable();
313         $namespace = microtime(1) . self::detectAction();
314
315         require_once 'include/SugarXHprof/xhprof_lib/utils/xhprof_runs.php';
316         $xhprof_runs = new XHProfRuns_Default(self::$log_to);
317         $xhprof_runs->save_run($data, $namespace);
318     }
319 }