\n";
165 | }
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/src/xhprof/xhprof_html/js/xhprof_report.js:
--------------------------------------------------------------------------------
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 | /**
17 | * Helper javascript functions for XHProf report tooltips.
18 | *
19 | * @author Kannan Muthukkaruppan
20 | */
21 |
22 | // Take a string which is actually a number in comma separated format
23 | // and return a string representing the absolute value of the number.
24 | function stringAbs(x) {
25 | return x.replace("-", "");
26 | }
27 |
28 | // Takes a number in comma-separated string format, and
29 | // returns a boolean to indicate if the number is negative
30 | // or not.
31 | function isNegative(x) {
32 |
33 | return (x.indexOf("-") == 0);
34 |
35 | }
36 |
37 | function addCommas(nStr)
38 | {
39 | nStr += '';
40 | x = nStr.split('.');
41 | x1 = x[0];
42 | x2 = x.length > 1 ? '.' + x[1] : '';
43 | var rgx = /(\d+)(\d{3})/;
44 | while (rgx.test(x1)) {
45 | x1 = x1.replace(rgx, '$1' + ',' + '$2');
46 | }
47 | return x1 + x2;
48 | }
49 |
50 | // Mouseover tips for parent rows in parent/child report..
51 | function ParentRowToolTip(cell, metric)
52 | {
53 | var metric_val;
54 | var parent_metric_val;
55 | var parent_metric_pct_val;
56 | var col_index;
57 | var diff_text;
58 |
59 | row = cell.parentNode;
60 | tds = row.getElementsByTagName("td");
61 |
62 | parent_func = tds[0].innerHTML; // name
63 |
64 | if (diff_mode) {
65 | diff_text = " diff ";
66 | } else {
67 | diff_text = "";
68 | }
69 |
70 | s = '
';
71 |
72 | if (metric == "ct") {
73 | parent_ct = tds[1].innerHTML; // calls
74 | parent_ct_pct = tds[2].innerHTML;
75 |
76 | func_ct = addCommas(func_ct);
77 |
78 | if (diff_mode) {
79 | s += 'There are ' + stringAbs(parent_ct) +
80 | (isNegative(parent_ct) ? ' fewer ' : ' more ') +
81 | ' calls to ' + func_name + ' from ' + parent_func + ' ';
82 |
83 | text = " of diff in calls ";
84 | } else {
85 | text = " of calls ";
86 | }
87 |
88 | s += parent_ct_pct + text + '(' + parent_ct + '/' + func_ct + ') to '
89 | + func_name + ' are from ' + parent_func + ' ';
90 | } else {
91 |
92 | // help for other metrics such as wall time, user cpu time, memory usage
93 | col_index = metrics_col[metric];
94 | parent_metric_val = tds[col_index].innerHTML;
95 | parent_metric_pct_val = tds[col_index+1].innerHTML;
96 |
97 | metric_val = addCommas(func_metrics[metric]);
98 |
99 | s += parent_metric_pct_val + '(' + parent_metric_val + '/' + metric_val
100 | + ') of ' + metrics_desc[metric] +
101 | (diff_mode ? ((isNegative(parent_metric_val) ?
102 | " decrease" : " increase")) : "") +
103 | ' in ' + func_name + ' is due to calls from ' + parent_func + ' ';
104 | }
105 |
106 | s += '
';
107 |
108 | return s;
109 | }
110 |
111 | // Mouseover tips for child rows in parent/child report..
112 | function ChildRowToolTip(cell, metric)
113 | {
114 | var metric_val;
115 | var child_metric_val;
116 | var child_metric_pct_val;
117 | var col_index;
118 | var diff_text;
119 |
120 | row = cell.parentNode;
121 | tds = row.getElementsByTagName("td");
122 |
123 | child_func = tds[0].innerHTML; // name
124 |
125 | if (diff_mode) {
126 | diff_text = " diff ";
127 | } else {
128 | diff_text = "";
129 | }
130 |
131 | s = '
';
132 |
133 | if (metric == "ct") {
134 |
135 | child_ct = tds[1].innerHTML; // calls
136 | child_ct_pct = tds[2].innerHTML;
137 |
138 | s += func_name + ' called ' + child_func + ' ' + stringAbs(child_ct) +
139 | (diff_mode ? (isNegative(child_ct) ? " fewer" : " more") : "" )
140 | + ' times. ';
141 | s += 'This accounts for ' + child_ct_pct + ' (' + child_ct
142 | + '/' + total_child_ct
143 | + ') of function calls made by ' + func_name + '.';
144 |
145 | } else {
146 |
147 | // help for other metrics such as wall time, user cpu time, memory usage
148 | col_index = metrics_col[metric];
149 | child_metric_val = tds[col_index].innerHTML;
150 | child_metric_pct_val = tds[col_index+1].innerHTML;
151 |
152 | metric_val = addCommas(func_metrics[metric]);
153 |
154 | if (child_func.indexOf("Exclusive Metrics") != -1) {
155 | s += 'The exclusive ' + metrics_desc[metric] + diff_text
156 | + ' for ' + func_name
157 | + ' is ' + child_metric_val + " ";
158 |
159 | s += "which is " + child_metric_pct_val + " of the inclusive "
160 | + metrics_desc[metric]
161 | + diff_text + " for " + func_name + " (" + metric_val + ").";
162 |
163 | } else {
164 |
165 | s += child_func + ' when called from ' + func_name
166 | + ' takes ' + stringAbs(child_metric_val)
167 | + (diff_mode ? (isNegative(child_metric_val) ? " less" : " more") : "")
168 | + " of " + metrics_desc[metric] + " ";
169 |
170 | s += "which is " + child_metric_pct_val + " of the inclusive "
171 | + metrics_desc[metric]
172 | + diff_text + " for " + func_name + " (" + metric_val + ").";
173 | }
174 | }
175 |
176 | s += '
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.
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 |
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 |
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 |
Exclusive Time/Self Time: Measures
176 | time spent in the function itself. Does not include time in descendant
177 | functions.
178 |
179 |
Wall Time: a.k.a. Elapsed time or wall clock time.
180 |
181 |
CPU Time: CPU time in user space + CPU time in kernel space
182 |
183 |
184 |
Naming convention for special functions
185 |
186 |
187 |
main(): a fictitious function that is at the root of the call graph.
188 |
189 |
190 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
776 |
777 |
778 |
xhprof_aggregate_runs():
779 | can be used to aggregate multiple XHProf runs into a single run. This
780 | can be helpful for building a system-wide "function-level" performance
781 | monitoring tool using XHProf. [For example, you might to roll up
782 | XHProf runs sampled from production periodically to generate hourly,
783 | daily, reports.]
784 |
785 |
xhprof_prune_run(): Aggregating large number of
786 | XHProf runs (especially if they correspond to different types of
787 | programs) can result in the callgraph size becoming too large. You can
788 | use xhprof_prune_run function to prune the callgraph data
789 | by editing out subtrees that account for a very small portion of the
790 | total time.
791 |
792 |
JQuery Javascript: For tooltips and function name
803 | typeahead, we make use of JQuery's javascript libraries. JQuery is
804 | available under both a MIT and GPL licencse
805 | (http://docs.jquery.com/Licensing). The relevant JQuery code, used by
806 | XHProf, is in the xhprof_html/jquery subdirectory.
807 |
808 |
dot (image generation utility): The callgraph image
809 | visualization ([View Callgraph]) feature relies on the presence of
810 | Graphviz "dot" utility in your path. "dot" is a utility to
811 | draw/generate an image for a directed graph.
812 |
813 |
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 |
822 |
823 |
824 |
825 |
826 |
827 |
--------------------------------------------------------------------------------
/src/xhprof/xhprof_lib/utils/xhprof_lib.php:
--------------------------------------------------------------------------------
1 | array("Wall", "microsecs", "walltime"),
37 | "ut" => array("User", "microsecs", "user cpu time"),
38 | "st" => array("Sys", "microsecs", "system cpu time"),
39 | "cpu" => array("Cpu", "microsecs", "cpu time"),
40 | "mu" => array("MUse", "bytes", "memory usage"),
41 | "pmu" => array("PMUse", "bytes", "peak memory usage"),
42 | "samples" => array("Samples", "samples", "cpu time"));
43 | return $possible_metrics;
44 | }
45 |
46 | /**
47 | * Initialize the metrics we'll display based on the information
48 | * in the raw data.
49 | *
50 | * @author Kannan
51 | */
52 | function init_metrics($xhprof_data, $rep_symbol, $sort, $diff_report = false) {
53 | global $stats;
54 | global $pc_stats;
55 | global $metrics;
56 | global $diff_mode;
57 | global $sortable_columns;
58 | global $sort_col;
59 | global $display_calls;
60 |
61 | $diff_mode = $diff_report;
62 |
63 | if (!empty($sort)) {
64 | if (array_key_exists($sort, $sortable_columns)) {
65 | $sort_col = $sort;
66 | } else {
67 | print("Invalid Sort Key $sort specified in URL");
68 | }
69 | }
70 |
71 | // For C++ profiler runs, walltime attribute isn't present.
72 | // In that case, use "samples" as the default sort column.
73 | if (!isset($xhprof_data["main()"]["wt"])) {
74 |
75 | if ($sort_col == "wt") {
76 | $sort_col = "samples";
77 | }
78 |
79 | // C++ profiler data doesn't have call counts.
80 | // ideally we should check to see if "ct" metric
81 | // is present for "main()". But currently "ct"
82 | // metric is artificially set to 1. So, relying
83 | // on absence of "wt" metric instead.
84 | $display_calls = false;
85 | } else {
86 | $display_calls = true;
87 | }
88 |
89 | // parent/child report doesn't support exclusive times yet.
90 | // So, change sort hyperlinks to closest fit.
91 | if (!empty($rep_symbol)) {
92 | $sort_col = str_replace("excl_", "", $sort_col);
93 | }
94 |
95 | if ($display_calls) {
96 | $stats = array("fn", "ct", "Calls%");
97 | } else {
98 | $stats = array("fn");
99 | }
100 |
101 | $pc_stats = $stats;
102 |
103 | $possible_metrics = xhprof_get_possible_metrics();
104 | foreach ($possible_metrics as $metric => $desc) {
105 | if (isset($xhprof_data["main()"][$metric])) {
106 | $metrics[] = $metric;
107 | // flat (top-level reports): we can compute
108 | // exclusive metrics reports as well.
109 | $stats[] = $metric;
110 | $stats[] = "I" . $desc[0] . "%";
111 | $stats[] = "excl_" . $metric;
112 | $stats[] = "E" . $desc[0] . "%";
113 |
114 | // parent/child report for a function: we can
115 | // only breakdown inclusive times correctly.
116 | $pc_stats[] = $metric;
117 | $pc_stats[] = "I" . $desc[0] . "%";
118 | }
119 | }
120 | }
121 |
122 | /*
123 | * Get the list of metrics present in $xhprof_data as an array.
124 | *
125 | * @author Kannan
126 | */
127 | function xhprof_get_metrics($xhprof_data) {
128 |
129 | // get list of valid metrics
130 | $possible_metrics = xhprof_get_possible_metrics();
131 |
132 | // return those that are present in the raw data.
133 | // We'll just look at the root of the subtree for this.
134 | $metrics = array();
135 | foreach ($possible_metrics as $metric => $desc) {
136 | if (isset($xhprof_data["main()"][$metric])) {
137 | $metrics[] = $metric;
138 | }
139 | }
140 |
141 | return $metrics;
142 | }
143 |
144 | /**
145 | * Takes a parent/child function name encoded as
146 | * "a==>b" and returns array("a", "b").
147 | *
148 | * @author Kannan
149 | */
150 | function xhprof_parse_parent_child($parent_child) {
151 | $ret = explode("==>", $parent_child);
152 |
153 | // Return if both parent and child are set
154 | if (isset($ret[1])) {
155 | return $ret;
156 | }
157 |
158 | return array(null, $ret[0]);
159 | }
160 |
161 | /**
162 | * Given parent & child function name, composes the key
163 | * in the format present in the raw data.
164 | *
165 | * @author Kannan
166 | */
167 | function xhprof_build_parent_child_key($parent, $child) {
168 | if ($parent) {
169 | return $parent . "==>" . $child;
170 | } else {
171 | return $child;
172 | }
173 | }
174 |
175 |
176 | /**
177 | * Checks if XHProf raw data appears to be valid and not corrupted.
178 | *
179 | * @param int $run_id Run id of run to be pruned.
180 | * [Used only for reporting errors.]
181 | * @param array $raw_data XHProf raw data to be pruned
182 | * & validated.
183 | *
184 | * @return bool true on success, false on failure
185 | *
186 | * @author Kannan
187 | */
188 | function xhprof_valid_run($run_id, $raw_data) {
189 |
190 | $main_info = $raw_data["main()"];
191 | if (empty($main_info)) {
192 | xhprof_error("XHProf: main() missing in raw data for Run ID: $run_id");
193 | return false;
194 | }
195 |
196 | // raw data should contain either wall time or samples information...
197 | if (isset($main_info["wt"])) {
198 | $metric = "wt";
199 | } else if (isset($main_info["samples"])) {
200 | $metric = "samples";
201 | } else {
202 | xhprof_error("XHProf: Wall Time information missing from Run ID: $run_id");
203 | return false;
204 | }
205 |
206 | foreach ($raw_data as $info) {
207 | $val = $info[$metric];
208 |
209 | // basic sanity checks...
210 | if ($val < 0) {
211 | xhprof_error("XHProf: $metric should not be negative: Run ID $run_id"
212 | . serialize($info));
213 | return false;
214 | }
215 | if ($val > (86400000000)) {
216 | xhprof_error("XHProf: $metric > 1 day found in Run ID: $run_id "
217 | . serialize($info));
218 | return false;
219 | }
220 | }
221 | return true;
222 | }
223 |
224 |
225 | /**
226 | * Return a trimmed version of the XHProf raw data. Note that the raw
227 | * data contains one entry for each unique parent/child function
228 | * combination.The trimmed version of raw data will only contain
229 | * entries where either the parent or child function is in the list
230 | * of $functions_to_keep.
231 | *
232 | * Note: Function main() is also always kept so that overall totals
233 | * can still be obtained from the trimmed version.
234 | *
235 | * @param array XHProf raw data
236 | * @param array array of function names
237 | *
238 | * @return array Trimmed XHProf Report
239 | *
240 | * @author Kannan
241 | */
242 | function xhprof_trim_run($raw_data, $functions_to_keep) {
243 |
244 | // convert list of functions to a hash with function as the key
245 | $function_map = array_fill_keys($functions_to_keep, 1);
246 |
247 | // always keep main() as well so that overall totals can still
248 | // be computed if need be.
249 | $function_map['main()'] = 1;
250 |
251 | $new_raw_data = array();
252 | foreach ($raw_data as $parent_child => $info) {
253 | list($parent, $child) = xhprof_parse_parent_child($parent_child);
254 |
255 | if (isset($function_map[$parent]) || isset($function_map[$child])) {
256 | $new_raw_data[$parent_child] = $info;
257 | }
258 | }
259 |
260 | return $new_raw_data;
261 | }
262 |
263 | /**
264 | * Takes raw XHProf data that was aggregated over "$num_runs" number
265 | * of runs averages/nomalizes the data. Essentially the various metrics
266 | * collected are divided by $num_runs.
267 | *
268 | * @author Kannan
269 | */
270 | function xhprof_normalize_metrics($raw_data, $num_runs) {
271 |
272 | if (empty($raw_data) || ($num_runs == 0)) {
273 | return $raw_data;
274 | }
275 |
276 | $raw_data_total = array();
277 |
278 | if (isset($raw_data["==>main()"]) && isset($raw_data["main()"])) {
279 | xhprof_error("XHProf Error: both ==>main() and main() set in raw data...");
280 | }
281 |
282 | foreach ($raw_data as $parent_child => $info) {
283 | foreach ($info as $metric => $value) {
284 | $raw_data_total[$parent_child][$metric] = ($value / $num_runs);
285 | }
286 | }
287 |
288 | return $raw_data_total;
289 | }
290 |
291 |
292 | /**
293 | * Get raw data corresponding to specified array of runs
294 | * aggregated by certain weightage.
295 | *
296 | * Suppose you have run:5 corresponding to page1.php,
297 | * run:6 corresponding to page2.php,
298 | * and run:7 corresponding to page3.php
299 | *
300 | * and you want to accumulate these runs in a 2:4:1 ratio. You
301 | * can do so by calling:
302 | *
303 | * xhprof_aggregate_runs(array(5, 6, 7), array(2, 4, 1));
304 | *
305 | * The above will return raw data for the runs aggregated
306 | * in 2:4:1 ratio.
307 | *
308 | * @param object $xhprof_runs_impl An object that implements
309 | * the iXHProfRuns interface
310 | * @param array $runs run ids of the XHProf runs..
311 | * @param array $wts integral (ideally) weights for $runs
312 | * @param string $source source to fetch raw data for run from
313 | * @param bool $use_script_name If true, a fake edge from main() to
314 | * to __script:: is introduced
315 | * in the raw data so that after aggregations
316 | * the script name is still preserved.
317 | *
318 | * @return array Return aggregated raw data
319 | *
320 | * @author Kannan
321 | */
322 | function xhprof_aggregate_runs($xhprof_runs_impl, $runs,
323 | $wts, $source="phprof",
324 | $use_script_name=false) {
325 |
326 | $raw_data_total = null;
327 | $raw_data = null;
328 | $metrics = array();
329 |
330 | $run_count = count($runs);
331 | $wts_count = count($wts);
332 |
333 | if (($run_count == 0) ||
334 | (($wts_count > 0) && ($run_count != $wts_count))) {
335 | return array('description' => 'Invalid input..',
336 | 'raw' => null);
337 | }
338 |
339 | $bad_runs = array();
340 | foreach ($runs as $idx => $run_id) {
341 |
342 | $raw_data = $xhprof_runs_impl->get_run($run_id, $source, $description);
343 |
344 | // use the first run to derive what metrics to aggregate on.
345 | if ($idx == 0) {
346 | foreach ($raw_data["main()"] as $metric => $val) {
347 | if ($metric != "pmu") {
348 | // for now, just to keep data size small, skip "peak" memory usage
349 | // data while aggregating.
350 | // The "regular" memory usage data will still be tracked.
351 | if (isset($val)) {
352 | $metrics[] = $metric;
353 | }
354 | }
355 | }
356 | }
357 |
358 | if (!xhprof_valid_run($run_id, $raw_data)) {
359 | $bad_runs[] = $run_id;
360 | continue;
361 | }
362 |
363 | if ($use_script_name) {
364 | $page = $description;
365 |
366 | // create a fake function '__script::$page', and have and edge from
367 | // main() to '__script::$page'. We will also need edges to transfer
368 | // all edges originating from main() to now originate from
369 | // '__script::$page' to all function called from main().
370 | //
371 | // We also weight main() ever so slightly higher so that
372 | // it shows up above the new entry in reports sorted by
373 | // inclusive metrics or call counts.
374 | if ($page) {
375 | foreach ($raw_data["main()"] as $metric => $val) {
376 | $fake_edge[$metric] = $val;
377 | $new_main[$metric] = $val + 0.00001;
378 | }
379 | $raw_data["main()"] = $new_main;
380 | $raw_data[xhprof_build_parent_child_key("main()",
381 | "__script::$page")]
382 | = $fake_edge;
383 | } else {
384 | $use_script_name = false;
385 | }
386 | }
387 |
388 | // if no weights specified, use 1 as the default weightage..
389 | $wt = ($wts_count == 0) ? 1 : $wts[$idx];
390 |
391 | // aggregate $raw_data into $raw_data_total with appropriate weight ($wt)
392 | foreach ($raw_data as $parent_child => $info) {
393 | if ($use_script_name) {
394 | // if this is an old edge originating from main(), it now
395 | // needs to be from '__script::$page'
396 | if (substr($parent_child, 0, 9) == "main()==>") {
397 | $child = substr($parent_child, 9);
398 | // ignore the newly added edge from main()
399 | if (substr($child, 0, 10) != "__script::") {
400 | $parent_child = xhprof_build_parent_child_key("__script::$page",
401 | $child);
402 | }
403 | }
404 | }
405 |
406 | if (!isset($raw_data_total[$parent_child])) {
407 | foreach ($metrics as $metric) {
408 | $raw_data_total[$parent_child][$metric] = ($wt * $info[$metric]);
409 | }
410 | } else {
411 | foreach ($metrics as $metric) {
412 | $raw_data_total[$parent_child][$metric] += ($wt * $info[$metric]);
413 | }
414 | }
415 | }
416 | }
417 |
418 | $runs_string = implode(",", $runs);
419 |
420 | if (isset($wts)) {
421 | $wts_string = "in the ratio (" . implode(":", $wts) . ")";
422 | $normalization_count = array_sum($wts);
423 | } else {
424 | $wts_string = "";
425 | $normalization_count = $run_count;
426 | }
427 |
428 | $run_count = $run_count - count($bad_runs);
429 |
430 | $data['description'] = "Aggregated Report for $run_count runs: ".
431 | "$runs_string $wts_string\n";
432 | $data['raw'] = xhprof_normalize_metrics($raw_data_total,
433 | $normalization_count);
434 | $data['bad_runs'] = $bad_runs;
435 |
436 | return $data;
437 | }
438 |
439 |
440 | /**
441 | * Analyze hierarchical raw data, and compute per-function (flat)
442 | * inclusive and exclusive metrics.
443 | *
444 | * Also, store overall totals in the 2nd argument.
445 | *
446 | * @param array $raw_data XHProf format raw profiler data.
447 | * @param array &$overall_totals OUT argument for returning
448 | * overall totals for various
449 | * metrics.
450 | * @return array Returns a map from function name to its
451 | * call count and inclusive & exclusive metrics
452 | * (such as wall time, etc.).
453 | *
454 | * @author Kannan Muthukkaruppan
455 | */
456 | function xhprof_compute_flat_info($raw_data, &$overall_totals) {
457 |
458 | global $display_calls;
459 |
460 | $metrics = xhprof_get_metrics($raw_data);
461 |
462 | $overall_totals = array("ct" => 0,
463 | "wt" => 0,
464 | "ut" => 0,
465 | "st" => 0,
466 | "cpu" => 0,
467 | "mu" => 0,
468 | "pmu" => 0,
469 | "samples" => 0
470 | );
471 |
472 | // compute inclusive times for each function
473 | $symbol_tab = xhprof_compute_inclusive_times($raw_data);
474 |
475 | /* total metric value is the metric value for "main()" */
476 | foreach ($metrics as $metric) {
477 | $overall_totals[$metric] = $symbol_tab["main()"][$metric];
478 | }
479 |
480 | /*
481 | * initialize exclusive (self) metric value to inclusive metric value
482 | * to start with.
483 | * In the same pass, also add up the total number of function calls.
484 | */
485 | foreach ($symbol_tab as $symbol => $info) {
486 | foreach ($metrics as $metric) {
487 | $symbol_tab[$symbol]["excl_" . $metric] = $symbol_tab[$symbol][$metric];
488 | }
489 | if ($display_calls) {
490 | /* keep track of total number of calls */
491 | $overall_totals["ct"] += $info["ct"];
492 | }
493 | }
494 |
495 | /* adjust exclusive times by deducting inclusive time of children */
496 | foreach ($raw_data as $parent_child => $info) {
497 | list($parent, $child) = xhprof_parse_parent_child($parent_child);
498 |
499 | if ($parent) {
500 | foreach ($metrics as $metric) {
501 | // make sure the parent exists hasn't been pruned.
502 | if (isset($symbol_tab[$parent])) {
503 | $symbol_tab[$parent]["excl_" . $metric] -= $info[$metric];
504 | }
505 | }
506 | }
507 | }
508 |
509 | return $symbol_tab;
510 | }
511 |
512 | /**
513 | * Hierarchical diff:
514 | * Compute and return difference of two call graphs: Run2 - Run1.
515 | *
516 | * @author Kannan
517 | */
518 | function xhprof_compute_diff($xhprof_data1, $xhprof_data2) {
519 | global $display_calls;
520 |
521 | // use the second run to decide what metrics we will do the diff on
522 | $metrics = xhprof_get_metrics($xhprof_data2);
523 |
524 | $xhprof_delta = $xhprof_data2;
525 |
526 | foreach ($xhprof_data1 as $parent_child => $info) {
527 |
528 | if (!isset($xhprof_delta[$parent_child])) {
529 |
530 | // this pc combination was not present in run1;
531 | // initialize all values to zero.
532 | if ($display_calls) {
533 | $xhprof_delta[$parent_child] = array("ct" => 0);
534 | } else {
535 | $xhprof_delta[$parent_child] = array();
536 | }
537 | foreach ($metrics as $metric) {
538 | $xhprof_delta[$parent_child][$metric] = 0;
539 | }
540 | }
541 |
542 | if ($display_calls) {
543 | $xhprof_delta[$parent_child]["ct"] -= $info["ct"];
544 | }
545 |
546 | foreach ($metrics as $metric) {
547 | $xhprof_delta[$parent_child][$metric] -= $info[$metric];
548 | }
549 | }
550 |
551 | return $xhprof_delta;
552 | }
553 |
554 |
555 | /**
556 | * Compute inclusive metrics for function. This code was factored out
557 | * of xhprof_compute_flat_info().
558 | *
559 | * The raw data contains inclusive metrics of a function for each
560 | * unique parent function it is called from. The total inclusive metrics
561 | * for a function is therefore the sum of inclusive metrics for the
562 | * function across all parents.
563 | *
564 | * @return array Returns a map of function name to total (across all parents)
565 | * inclusive metrics for the function.
566 | *
567 | * @author Kannan
568 | */
569 | function xhprof_compute_inclusive_times($raw_data) {
570 | global $display_calls;
571 |
572 | $metrics = xhprof_get_metrics($raw_data);
573 |
574 | $symbol_tab = array();
575 |
576 | /*
577 | * First compute inclusive time for each function and total
578 | * call count for each function across all parents the
579 | * function is called from.
580 | */
581 | foreach ($raw_data as $parent_child => $info) {
582 |
583 | list($parent, $child) = xhprof_parse_parent_child($parent_child);
584 |
585 | if ($parent == $child) {
586 | /*
587 | * XHProf PHP extension should never trigger this situation any more.
588 | * Recursion is handled in the XHProf PHP extension by giving nested
589 | * calls a unique recursion-depth appended name (for example, foo@1).
590 | */
591 | xhprof_error("Error in Raw Data: parent & child are both: $parent");
592 | return;
593 | }
594 |
595 | if (!isset($symbol_tab[$child])) {
596 |
597 | if ($display_calls) {
598 | $symbol_tab[$child] = array("ct" => $info["ct"]);
599 | } else {
600 | $symbol_tab[$child] = array();
601 | }
602 | foreach ($metrics as $metric) {
603 | $symbol_tab[$child][$metric] = $info[$metric];
604 | }
605 | } else {
606 | if ($display_calls) {
607 | /* increment call count for this child */
608 | $symbol_tab[$child]["ct"] += $info["ct"];
609 | }
610 |
611 | /* update inclusive times/metric for this child */
612 | foreach ($metrics as $metric) {
613 | $symbol_tab[$child][$metric] += $info[$metric];
614 | }
615 | }
616 | }
617 |
618 | return $symbol_tab;
619 | }
620 |
621 |
622 | /*
623 | * Prunes XHProf raw data:
624 | *
625 | * Any node whose inclusive walltime accounts for less than $prune_percent
626 | * of total walltime is pruned. [It is possible that a child function isn't
627 | * pruned, but one or more of its parents get pruned. In such cases, when
628 | * viewing the child function's hierarchical information, the cost due to
629 | * the pruned parent(s) will be attributed to a special function/symbol
630 | * "__pruned__()".]
631 | *
632 | * @param array $raw_data XHProf raw data to be pruned & validated.
633 | * @param double $prune_percent Any edges that account for less than
634 | * $prune_percent of time will be pruned
635 | * from the raw data.
636 | *
637 | * @return array Returns the pruned raw data.
638 | *
639 | * @author Kannan
640 | */
641 | function xhprof_prune_run($raw_data, $prune_percent) {
642 |
643 | $main_info = $raw_data["main()"];
644 | if (empty($main_info)) {
645 | xhprof_error("XHProf: main() missing in raw data");
646 | return false;
647 | }
648 |
649 | // raw data should contain either wall time or samples information...
650 | if (isset($main_info["wt"])) {
651 | $prune_metric = "wt";
652 | } else if (isset($main_info["samples"])) {
653 | $prune_metric = "samples";
654 | } else {
655 | xhprof_error("XHProf: for main() we must have either wt "
656 | ."or samples attribute set");
657 | return false;
658 | }
659 |
660 | // determine the metrics present in the raw data..
661 | $metrics = array();
662 | foreach ($main_info as $metric => $val) {
663 | if (isset($val)) {
664 | $metrics[] = $metric;
665 | }
666 | }
667 |
668 | $prune_threshold = (($main_info[$prune_metric] * $prune_percent) / 100.0);
669 |
670 | init_metrics($raw_data, null, null, false);
671 | $flat_info = xhprof_compute_inclusive_times($raw_data);
672 |
673 | foreach ($raw_data as $parent_child => $info) {
674 |
675 | list($parent, $child) = xhprof_parse_parent_child($parent_child);
676 |
677 | // is this child's overall total from all parents less than threshold?
678 | if ($flat_info[$child][$prune_metric] < $prune_threshold) {
679 | unset($raw_data[$parent_child]); // prune the edge
680 | } else if ($parent &&
681 | ($parent != "__pruned__()") &&
682 | ($flat_info[$parent][$prune_metric] < $prune_threshold)) {
683 |
684 | // Parent's overall inclusive metric is less than a threshold.
685 | // All edges to the parent node will get nuked, and this child will
686 | // be a dangling child.
687 | // So instead change its parent to be a special function __pruned__().
688 | $pruned_edge = xhprof_build_parent_child_key("__pruned__()", $child);
689 |
690 | if (isset($raw_data[$pruned_edge])) {
691 | foreach ($metrics as $metric) {
692 | $raw_data[$pruned_edge][$metric]+=$raw_data[$parent_child][$metric];
693 | }
694 | } else {
695 | $raw_data[$pruned_edge] = $raw_data[$parent_child];
696 | }
697 |
698 | unset($raw_data[$parent_child]); // prune the edge
699 | }
700 | }
701 |
702 | return $raw_data;
703 | }
704 |
705 |
706 | /**
707 | * Set one key in an array and return the array
708 | *
709 | * @author Kannan
710 | */
711 | function xhprof_array_set($arr, $k, $v) {
712 | $arr[$k] = $v;
713 | return $arr;
714 | }
715 |
716 | /**
717 | * Removes/unsets one key in an array and return the array
718 | *
719 | * @author Kannan
720 | */
721 | function xhprof_array_unset($arr, $k) {
722 | unset($arr[$k]);
723 | return $arr;
724 | }
725 |
726 | /**
727 | * Type definitions for URL params
728 | */
729 | define('XHPROF_STRING_PARAM', 1);
730 | define('XHPROF_UINT_PARAM', 2);
731 | define('XHPROF_FLOAT_PARAM', 3);
732 | define('XHPROF_BOOL_PARAM', 4);
733 |
734 |
735 | /**
736 | * Internal helper function used by various
737 | * xhprof_get_param* flavors for various
738 | * types of parameters.
739 | *
740 | * @param string name of the URL query string param
741 | *
742 | * @author Kannan
743 | */
744 | function xhprof_get_param_helper($param) {
745 | $val = null;
746 | if (isset($_GET[$param]))
747 | $val = $_GET[$param];
748 | else if (isset($_POST[$param])) {
749 | $val = $_POST[$param];
750 | }
751 | return $val;
752 | }
753 |
754 | /**
755 | * Extracts value for string param $param from query
756 | * string. If param is not specified, return the
757 | * $default value.
758 | *
759 | * @author Kannan
760 | */
761 | function xhprof_get_string_param($param, $default = '') {
762 | $val = xhprof_get_param_helper($param);
763 |
764 | if ($val === null)
765 | return $default;
766 |
767 | return $val;
768 | }
769 |
770 | /**
771 | * Extracts value for unsigned integer param $param from
772 | * query string. If param is not specified, return the
773 | * $default value.
774 | *
775 | * If value is not a valid unsigned integer, logs error
776 | * and returns null.
777 | *
778 | * @author Kannan
779 | */
780 | function xhprof_get_uint_param($param, $default = 0) {
781 | $val = xhprof_get_param_helper($param);
782 |
783 | if ($val === null)
784 | $val = $default;
785 |
786 | // trim leading/trailing whitespace
787 | $val = trim($val);
788 |
789 | // if it only contains digits, then ok..
790 | if (ctype_digit($val)) {
791 | return $val;
792 | }
793 |
794 | xhprof_error("$param is $val. It must be an unsigned integer.");
795 | return null;
796 | }
797 |
798 |
799 | /**
800 | * Extracts value for a float param $param from
801 | * query string. If param is not specified, return
802 | * the $default value.
803 | *
804 | * If value is not a valid unsigned integer, logs error
805 | * and returns null.
806 | *
807 | * @author Kannan
808 | */
809 | function xhprof_get_float_param($param, $default = 0) {
810 | $val = xhprof_get_param_helper($param);
811 |
812 | if ($val === null)
813 | $val = $default;
814 |
815 | // trim leading/trailing whitespace
816 | $val = trim($val);
817 |
818 | // TBD: confirm the value is indeed a float.
819 | if (true) // for now..
820 | return (float)$val;
821 |
822 | xhprof_error("$param is $val. It must be a float.");
823 | return null;
824 | }
825 |
826 | /**
827 | * Extracts value for a boolean param $param from
828 | * query string. If param is not specified, return
829 | * the $default value.
830 | *
831 | * If value is not a valid unsigned integer, logs error
832 | * and returns null.
833 | *
834 | * @author Kannan
835 | */
836 | function xhprof_get_bool_param($param, $default = false) {
837 | $val = xhprof_get_param_helper($param);
838 |
839 | if ($val === null)
840 | $val = $default;
841 |
842 | // trim leading/trailing whitespace
843 | $val = trim($val);
844 |
845 | switch (strtolower($val)) {
846 | case '0':
847 | case '1':
848 | $val = (bool)$val;
849 | break;
850 | case 'true':
851 | case 'on':
852 | case 'yes':
853 | $val = true;
854 | break;
855 | case 'false':
856 | case 'off':
857 | case 'no':
858 | $val = false;
859 | break;
860 | default:
861 | xhprof_error("$param is $val. It must be a valid boolean string.");
862 | return null;
863 | }
864 |
865 | return $val;
866 |
867 | }
868 |
869 | /**
870 | * Initialize params from URL query string. The function
871 | * creates globals variables for each of the params
872 | * and if the URL query string doesn't specify a particular
873 | * param initializes them with the corresponding default
874 | * value specified in the input.
875 | *
876 | * @params array $params An array whose keys are the names
877 | * of URL params who value needs to
878 | * be retrieved from the URL query
879 | * string. PHP globals are created
880 | * with these names. The value is
881 | * itself an array with 2-elems (the
882 | * param type, and its default value).
883 | * If a param is not specified in the
884 | * query string the default value is
885 | * used.
886 | * @author Kannan
887 | */
888 | function xhprof_param_init($params) {
889 | /* Create variables specified in $params keys, init defaults */
890 | foreach ($params as $k => $v) {
891 | switch ($v[0]) {
892 | case XHPROF_STRING_PARAM:
893 | $p = xhprof_get_string_param($k, $v[1]);
894 | break;
895 | case XHPROF_UINT_PARAM:
896 | $p = xhprof_get_uint_param($k, $v[1]);
897 | break;
898 | case XHPROF_FLOAT_PARAM:
899 | $p = xhprof_get_float_param($k, $v[1]);
900 | break;
901 | case XHPROF_BOOL_PARAM:
902 | $p = xhprof_get_bool_param($k, $v[1]);
903 | break;
904 | default:
905 | xhprof_error("Invalid param type passed to xhprof_param_init: "
906 | . $v[0]);
907 | exit();
908 | }
909 |
910 | if ($k === 'run') {
911 | $p = implode(',', array_filter(explode(',', $p), 'ctype_xdigit'));
912 | }
913 |
914 | if ($k == 'symbol') {
915 | $p = strip_tags($p);
916 | }
917 |
918 | // create a global variable using the parameter name.
919 | $GLOBALS[$k] = $p;
920 | }
921 | }
922 |
923 |
924 | /**
925 | * Given a partial query string $q return matching function names in
926 | * specified XHProf run. This is used for the type ahead function
927 | * selector.
928 | *
929 | * @author Kannan
930 | */
931 | function xhprof_get_matching_functions($q, $xhprof_data) {
932 |
933 | $matches = array();
934 |
935 | foreach ($xhprof_data as $parent_child => $info) {
936 | list($parent, $child) = xhprof_parse_parent_child($parent_child);
937 | if (stripos($parent, $q) !== false) {
938 | $matches[$parent] = 1;
939 | }
940 | if (stripos($child, $q) !== false) {
941 | $matches[$child] = 1;
942 | }
943 | }
944 |
945 | $res = array_keys($matches);
946 |
947 | // sort it so the answers are in some reliable order...
948 | asort($res);
949 |
950 | return ($res);
951 | }
952 |
--------------------------------------------------------------------------------
/src/xhprof/xhprof_lib/display/xhprof.php:
--------------------------------------------------------------------------------
1 | ";
68 | echo "";
70 | echo "";
72 |
73 | // javascript
74 | echo "";
76 | echo "";
78 | echo "";
80 | echo "";
81 | }
82 |
83 |
84 | /*
85 | * Formats call counts for XHProf reports.
86 | *
87 | * Description:
88 | * Call counts in single-run reports are integer values.
89 | * However, call counts for aggregated reports can be
90 | * fractional. This function will print integer values
91 | * without decimal point, but with commas etc.
92 | *
93 | * 4000 ==> 4,000
94 | *
95 | * It'll round fractional values to decimal precision of 3
96 | * 4000.1212 ==> 4,000.121
97 | * 4000.0001 ==> 4,000
98 | *
99 | */
100 | function xhprof_count_format($num) {
101 | $num = round($num, 3);
102 | if (round($num) == $num) {
103 | return number_format($num);
104 | } else {
105 | return number_format($num, 3);
106 | }
107 | }
108 |
109 | function xhprof_percent_format($s, $precision = 1) {
110 | return sprintf('%.'.$precision.'f%%', 100 * $s);
111 | }
112 |
113 | /**
114 | * Implodes the text for a bunch of actions (such as links, forms,
115 | * into a HTML list and returns the text.
116 | */
117 | function xhprof_render_actions($actions) {
118 | $out = array();
119 |
120 | if (count($actions)) {
121 | $out[] = '