├── Predict ├── Exception.php ├── Vector.php ├── Geodetic.php ├── ObsSet.php ├── QTH.php ├── DeepArg.php ├── Pass.php ├── SGSDPStatic.php ├── PassDetail.php ├── DeepStatic.php ├── Solar.php ├── Math.php ├── SGPObs.php ├── Time.php ├── TLE.php └── Sat.php ├── examples ├── google_maps_iss.png ├── pass_polar_plot.png ├── iss.tle ├── update_iss_tle.php ├── findsun.php ├── solar_position.php ├── benchmark.php └── visible_passes.php ├── xhprof_lib ├── jquery │ ├── indicator.gif │ ├── jquery.tooltip.css │ ├── jquery.autocomplete.css │ ├── jquery.tooltip.js │ └── jquery.autocomplete.js ├── docs │ ├── sample-flat-view.jpg │ ├── sample-callgraph-image.jpg │ ├── sample-parent-child-view.jpg │ ├── sample-diff-report-flat-view.jpg │ ├── sample-diff-report-parent-child-view.jpg │ └── index.html ├── list.php ├── typeahead.php ├── css │ └── xhprof.css ├── display │ └── typeahead_common.php ├── index.php ├── callgraph.php ├── EC │ └── XHProf.php ├── utils │ ├── xhprof_runs.php │ └── callgraph_utils.php └── js │ └── xhprof_report.js ├── tests ├── test-001.tle ├── test-002.tle ├── test-001.php ├── test-002.php └── Table.php ├── composer.json ├── README.md ├── generatePackage.php └── package.xml /Predict/Exception.php: -------------------------------------------------------------------------------- 1 | lat = 37.786759; 19 | $qth->lon = -122.405162; 20 | $qth->alt = 10; // Altitude above sea level in meters 21 | 22 | $sunInfo = Predict_Solar::FindSun($qth, $daynum); 23 | 24 | $output = array( 25 | 'elevation' => $sunInfo->el, 26 | 'azimuth' => $sunInfo->az, 27 | 'timestamp' => $time 28 | ); 29 | 30 | // output results 31 | echo json_encode($output); 32 | -------------------------------------------------------------------------------- /Predict/SGSDPStatic.php: -------------------------------------------------------------------------------- 1 | clear(); 12 | header('Location: ' . $_SERVER['PHP_SELF']); 13 | } 14 | ?> 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |

XHProf Profiler

23 |
24 |

Available XHProf Runs for

25 |
26 |
27 | 28 | 29 | 30 | 31 | getRuns() as $run => $time) { 33 | $url = "index.php?run={$run}&source={$namespace}"; 34 | ?> 35 | 36 | 37 | 38 | 41 |
Run IDDate
42 |
43 |
44 |
45 |
46 | Clear Runs 47 | 48 | 49 | -------------------------------------------------------------------------------- /Predict/PassDetail.php: -------------------------------------------------------------------------------- 1 | pos = new Predict_Vector(); 35 | $this->vel = new Predict_Vector(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /xhprof_lib/typeahead.php: -------------------------------------------------------------------------------- 1 | lat); 29 | $solar_lon = Predict_Math::Degrees($solar_geodetic->lon); 30 | 31 | // Reverse values for night circle center 32 | $dark_lat = -$solar_lat; 33 | $dark_lon = -$solar_lon; 34 | 35 | $output = array( 36 | 'solar_lat' => $solar_lat, 37 | 'solar_lon' => $solar_lon, 38 | 'dark_lat' => $dark_lat, 39 | 'dark_lon' => $dark_lon, 40 | 'timestamp' => $time 41 | ); 42 | 43 | // output results 44 | var_dump(json_encode($output)); 45 | -------------------------------------------------------------------------------- /xhprof_lib/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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Predict is a partial PHP port of the [Gpredict](http://gpredict.oz9aec.net/) program 4 | that allows real-time tracking and orbit prediction of satellites from two line 5 | element sets. It supports the SGP4 and SDP4 [models](http://en.wikipedia.org/wiki/Simplified_perturbations_models) for prediction. 6 | 7 | # Installation 8 | 9 | Just clone this repo and try out the tests and examples from the root of the checkout. 10 | 11 | # Examples/Tests 12 | 13 | The tests directory includes a port of the original sgpsdp test files from 14 | Gpredict. They are pretty close. 15 | 16 | Included in the examples directory is a sample iss.tle (with an update script, which you 17 | should run first). There are two examples, the visible_passes.php script and the benchmark.php 18 | script. The former is for generating visible pass predictions of the ISS, and its output is 19 | similar to what you might get from the Heavens-Above website, and it is heavily commented. 20 | The latter just does predictions for benchmarking with xhprof. 21 | 22 | You can see an image of a Predict/Google Maps API mash-up I did for fun: 23 | 24 | ![Google Maps Mashup](https://raw.github.com/shupp/Predict/master/examples/google_maps_iss.png) 25 | 26 | You can also see an example visible pass plotted from the polar view of the observer: 27 | 28 | ![Google Maps Mashup](https://raw.github.com/shupp/Predict/master/examples/pass_polar_plot.png) 29 | 30 | # About this port 31 | 32 | This port largely maintains the style and organization of the original C code, but 33 | scopes methods into classes rather than leaving everything in the global scope. 34 | The motivation for this is so that changes upstream can more easily be integrated over 35 | time. Only the prediction routines have been ported. 36 | -------------------------------------------------------------------------------- /Predict/DeepStatic.php: -------------------------------------------------------------------------------- 1 | save_run($xhprofData, $xhprofNameSpace); 38 | } 39 | register_shutdown_function('stop_xhprof_profiling'); 40 | } 41 | 42 | $start = microtime(true); 43 | 44 | $predict = new Predict(); 45 | $qth = new Predict_QTH(); 46 | $qth->lat = 37.6550; 47 | $qth->lon = -122.4070; 48 | $qth->alt = 0; 49 | 50 | $tleFile = file('examples/iss.tle'); 51 | $tle = new Predict_TLE($tleFile[0], $tleFile[1], $tleFile[2]); 52 | $sat = new Predict_Sat($tle); 53 | $now = 2459620.2339725; 54 | // $now = Predict_Time::get_current_daynum(); 55 | 56 | $results = $predict->get_passes($sat, $qth, $now, 10); 57 | 58 | echo "Execution time: " . number_format((microtime(true) - $start) * 1000, 2) . "ms\n"; exit; 59 | -------------------------------------------------------------------------------- /xhprof_lib/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 | -------------------------------------------------------------------------------- /generatePackage.php: -------------------------------------------------------------------------------- 1 | setOptions(array( 12 | 'baseinstalldir' => '/', 13 | 'simpleoutput' => true, 14 | 'packagedirectory' => './', 15 | 'filelistgenerator' => 'file', 16 | 'ignore' => array('generatePackage.php', 'xhprof_lib/*'), 17 | 'dir_roles' => array( 18 | 'tests' => 'test', 19 | 'examples' => 'doc' 20 | ), 21 | 'exceptions' => array('README.md' => 'doc'), 22 | )); 23 | 24 | $packagexml->setPackage('Predict'); 25 | $packagexml->setSummary('A partial port of the Gpredict program for satellite tracking'); 26 | $packagexml->setDescription( 27 | 'Predict is a partial PHP port of the Gpredict (http://gpredict.oz9aec.net/) program that ' 28 | . 'allows real-time tracking and orbit prediction of satellites from two line element sets. ' 29 | . 'It supports the SGP4 and SDP4 models for prediction.' 30 | ); 31 | 32 | $packagexml->setChannel('shupp.github.com/pirum'); 33 | $packagexml->setAPIVersion('0.2.2'); 34 | $packagexml->setReleaseVersion('0.2.2'); 35 | 36 | $packagexml->setReleaseStability('alpha'); 37 | 38 | $packagexml->setAPIStability('alpha'); 39 | 40 | $packagexml->setNotes(' 41 | * Addec Predict_TLE::createChecksum() 42 | * Updates to examples 43 | '); 44 | $packagexml->setPackageType('php'); 45 | $packagexml->addRelease(); 46 | 47 | $packagexml->detectDependencies(); 48 | 49 | $packagexml->addMaintainer('lead', 50 | 'shupp', 51 | 'Bill Shupp', 52 | 'shupp@php.net'); 53 | $packagexml->setLicense('GPL v2.1', 54 | 'http://www.opensource.org/licenses/gpl-license.php'); 55 | 56 | $packagexml->setPhpDep('5.2.0'); 57 | $packagexml->setPearinstallerDep('1.4.0b1'); 58 | $packagexml->addExtensionDep('required', 'date'); 59 | 60 | $packagexml->generateContents(); 61 | $packagexml->writePackageFile(); 62 | 63 | ?> 64 | -------------------------------------------------------------------------------- /xhprof_lib/display/typeahead_common.php: -------------------------------------------------------------------------------- 1 | array(XHPROF_STRING_PARAM, ''), 32 | 'run' => array(XHPROF_STRING_PARAM, ''), 33 | 'run1' => array(XHPROF_STRING_PARAM, ''), 34 | 'run2' => array(XHPROF_STRING_PARAM, ''), 35 | 'source' => array(XHPROF_STRING_PARAM, 'xhprof'), 36 | ); 37 | 38 | // pull values of these params, and create named globals for each param 39 | xhprof_param_init($params); 40 | 41 | if (!empty($run)) { 42 | 43 | // single run mode 44 | $raw_data = $xhprof_runs_impl->get_run($run, $source, $desc_unused); 45 | $functions = xhprof_get_matching_functions($q, $raw_data); 46 | 47 | } else if (!empty($run1) && !empty($run2)) { 48 | 49 | // diff mode 50 | $raw_data = $xhprof_runs_impl->get_run($run1, $source, $desc_unused); 51 | $functions1 = xhprof_get_matching_functions($q, $raw_data); 52 | 53 | $raw_data = $xhprof_runs_impl->get_run($run2, $source, $desc_unused); 54 | $functions2 = xhprof_get_matching_functions($q, $raw_data); 55 | 56 | 57 | $functions = array_unique(array_merge($functions1, $functions2)); 58 | asort($functions); 59 | } else { 60 | xhprof_error("no valid runs specified to typeahead endpoint"); 61 | $functions = array(); 62 | } 63 | 64 | // If exact match is present move it to the front 65 | if (in_array($q, $functions)) { 66 | $old_functions = $functions; 67 | 68 | $functions = array($q); 69 | foreach ($old_functions as $f) { 70 | // exact match case has already been added to the front 71 | if ($f != $q) { 72 | $functions[] = $f; 73 | } 74 | } 75 | } 76 | 77 | foreach ($functions as $f) { 78 | echo $f."\n"; 79 | } 80 | 81 | -------------------------------------------------------------------------------- /xhprof_lib/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 | -------------------------------------------------------------------------------- /xhprof_lib/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 | -------------------------------------------------------------------------------- /examples/visible_passes.php: -------------------------------------------------------------------------------- 1 | alt = 0; // Altitude in meters 26 | 27 | // South San Francisco, example west of the meridian 28 | $qth->lat = 37.6550; // Latitude North 29 | $qth->lon = -122.4070; // Longitude East 30 | 31 | // Munich, example east of the meridian 32 | // $qth->lat = 48.1505; // Lat North 33 | // $qth->lon = 11.5809; // Lon East 34 | 35 | // The iss.tle file is the first 3 lines of 36 | // http://celestrak.com/NORAD/elements/stations.txt 37 | // Make sure you update this content, it goes out of date within a day or two 38 | $tleFile = file('examples/iss.tle'); // Load up the ISS data file from NORAD 39 | $tle = new Predict_TLE($tleFile[0], $tleFile[1], $tleFile[2]); // Instantiate it 40 | $sat = new Predict_Sat($tle); // Load up the satellite data 41 | $now = 2459620.2339725; 42 | // $now = Predict_Time::get_current_daynum(); // get the current time as Julian Date (daynum) 43 | 44 | // You can modify some preferences in Predict(), the defaults are below 45 | // 46 | // $predict->minEle = 10; // Minimum elevation for a pass 47 | // $predict->timeRes = 10; // Pass details: time resolution in seconds 48 | // $predict->numEntries = 20; // Pass details: number of entries per pass 49 | // $predict->threshold = -6; // Twilight threshold (sun must be at this lat or lower) 50 | 51 | // Get the passes and filter visible only, takes about 4 seconds for 10 days 52 | $results = $predict->get_passes($sat, $qth, $now, 10); 53 | $filtered = $predict->filterVisiblePasses($results); 54 | 55 | $zone = 'America/Los_Angeles'; // Pacific time zone 56 | $format = 'm-d-Y H:i:s'; // Time format from PHP's date() function 57 | 58 | // Format the output similar to the heavens-above.com website 59 | foreach ($filtered as $pass) { 60 | echo "AOS Daynum: " . $pass->visible_aos . "\n"; 61 | echo "AOS Time: " . Predict_Time::daynum2readable($pass->visible_aos, $zone, $format) . "\n"; 62 | echo "AOS Az: " . $predict->azDegreesToDirection($pass->visible_aos_az) . "\n"; 63 | echo "AOS El: " . round($pass->visible_aos_el) . "\n"; 64 | echo "Max Time: " . Predict_Time::daynum2readable($pass->visible_tca, $zone, $format) . "\n"; 65 | echo "Max Az: " . $predict->azDegreesToDirection($pass->visible_max_el_az) . "\n"; 66 | echo "Max El: " . round($pass->visible_max_el) . "\n"; 67 | echo "LOS Time: " . Predict_Time::daynum2readable($pass->visible_los, $zone, $format) . "\n"; 68 | echo "LOS Az: " . $predict->azDegreesToDirection($pass->visible_los_az) . "\n"; 69 | echo "LOS El: " . round($pass->visible_los_el) . "\n"; 70 | echo "Magnitude: " . number_format($pass->max_apparent_magnitude, 1) . "\n"; 71 | echo "\n"; 72 | } 73 | 74 | // How long did this take? 75 | echo "Execution time: " . number_format((microtime(true) - $start) * 1000, 2) . "ms\n"; exit; 76 | -------------------------------------------------------------------------------- /tests/test-001.php: -------------------------------------------------------------------------------- 1 | 0.0, 16 | 'x' => 2328.97048951, 17 | 'y' => -5995.22076416, 18 | 'z' => 1719.97067261, 19 | 'vx' => 2.91207230, 20 | 'vy' => -0.98341546, 21 | 'vz' => -7.09081703 22 | ), 23 | array( 24 | 'step' => 360.0, 25 | 'x' => 2456.10705566, 26 | 'y' => -6071.93853760, 27 | 'z' => 1222.89727783, 28 | 'vx' => 2.67938992, 29 | 'vy' => -0.44829041, 30 | 'vz' => -7.22879231 31 | ), 32 | array( 33 | 'step' => 720.0, 34 | 'x' => 2567.56195068, 35 | 'y' => -6112.50384522, 36 | 'z' => 713.96397400, 37 | 'vx' => 2.44024599, 38 | 'vy' => 0.09810869, 39 | 'vz' => -7.31995916 40 | ), 41 | array( 42 | 'step' => 1080.0, 43 | 'x' => 2663.09078980, 44 | 'y' => -6115.48229980, 45 | 'z' => 196.39640427, 46 | 'vx' => 2.19611958, 47 | 'vy' => 0.65241995, 48 | 'vz' => -7.36282432 49 | ), 50 | array( 51 | 'step' => 1440.0, 52 | 'x' => 2742.55133057, 53 | 'y' => -6079.67144775, 54 | 'z' => -326.38095856, 55 | 'vx' => 1.94850229, 56 | 'vy' => 1.21106251, 57 | 'vz' => -7.35619372 58 | ) 59 | ); 60 | 61 | $headers = array( 62 | 'step time', 63 | 'label', 64 | 'result', 65 | 'expected', 66 | ); 67 | 68 | $data = array(); 69 | 70 | 71 | $file = file('tests/test-001.tle'); 72 | $tle = new Predict_TLE($file[0], $file[1], $file[2]); 73 | $sat = new Predict_Sat($tle); 74 | $sgpsdp = new Predict_SGPSDP(); 75 | 76 | $count = 0; 77 | foreach ($expected as $e) { 78 | $sgpsdp->SGP4($sat, $e['step']); 79 | Predict_Math::Convert_Sat_State($sat->pos, $sat->vel); 80 | 81 | $count++; 82 | $data[$count] = array(); 83 | $data[$count][0] = $e['step']; 84 | $data[$count][1] = 'X'; 85 | $data[$count][2] = $sat->pos->x; 86 | $data[$count][3] = $e['x']; 87 | 88 | $count++; 89 | $data[$count] = array(); 90 | $data[$count][0] = ''; 91 | $data[$count][1] = 'Y'; 92 | $data[$count][2] = $sat->pos->y; 93 | $data[$count][3] = $e['y']; 94 | 95 | $count++; 96 | $data[$count] = array(); 97 | $data[$count][0] = ''; 98 | $data[$count][1] = 'Z'; 99 | $data[$count][2] = $sat->pos->z; 100 | $data[$count][3] = $e['z']; 101 | 102 | $count++; 103 | $data[$count] = array(); 104 | $data[$count][0] = ''; 105 | $data[$count][1] = 'VX'; 106 | $data[$count][2] = $sat->vel->x; 107 | $data[$count][3] = $e['vx']; 108 | 109 | $count++; 110 | $data[$count] = array(); 111 | $data[$count][0] = ''; 112 | $data[$count][1] = 'VY'; 113 | $data[$count][2] = $sat->vel->y; 114 | $data[$count][3] = $e['vy']; 115 | 116 | $count++; 117 | $data[$count] = array(); 118 | $data[$count][0] = ''; 119 | $data[$count][1] = 'VZ'; 120 | $data[$count][2] = $sat->vel->z; 121 | $data[$count][3] = $e['vz']; 122 | } 123 | // exit; 124 | 125 | $tbl = new Console_Table(); 126 | $tbl->setHeaders($headers); 127 | $tbl->addData($data); 128 | 129 | echo "DEEP_SPACE_EPHEM: " . ($sat->flags & Predict_SGPSDP::DEEP_SPACE_EPHEM_FLAG) . " (expected: 0)\n\n"; 130 | 131 | echo $tbl->getTable(); 132 | 133 | echo "Execution time: " . number_format((microtime(true) - $start) * 1000, 2) . "ms\n"; 134 | -------------------------------------------------------------------------------- /tests/test-002.php: -------------------------------------------------------------------------------- 1 | 0.0, 17 | 'x' => 7473.37066650, 18 | 'y' => 428.95261765, 19 | 'z' => 5828.74786377, 20 | 'vx' => 5.1071513, 21 | 'vy' => 6.44468284, 22 | 'vz' => -0.18613096 23 | ), 24 | array( 25 | 'step' => 360.0, 26 | 'x' => -3305.22537232, 27 | 'y' => 32410.86328125, 28 | 'z' => -24697.17675781, 29 | 'vx' => -1.30113538, 30 | 'vy' => -1.15131518, 31 | 'vz' => -0.28333528 32 | ), 33 | array( 34 | 'step' => 720.0, 35 | 'x' => 14271.28759766, 36 | 'y' => 24110.46411133, 37 | 'z' => -4725.76837158, 38 | 'vx' => -0.32050445, 39 | 'vy' => 2.67984074, 40 | 'vz' => -2.08405289 41 | ), 42 | array( 43 | 'step' => 1080.0, 44 | 'x' => -9990.05883789, 45 | 'y' => 22717.35522461, 46 | 'z' => -23616.890662501, 47 | 'vx' => -1.01667246, 48 | 'vy' => -2.29026759, 49 | 'vz' => 0.72892364 50 | ), 51 | array( 52 | 'step' => 1440.0, 53 | 'x' => 9787.86975097, 54 | 'y' => 33753.34667969, 55 | 'z' => -15030.81176758, 56 | 'vx' => -1.09425966, 57 | 'vy' => 0.92358845, 58 | 'vz' => -1.52230928 59 | ) 60 | ); 61 | 62 | $headers = array( 63 | 'step time', 64 | 'label', 65 | 'result', 66 | 'expected', 67 | ); 68 | 69 | $data = array(); 70 | 71 | 72 | $file = file('tests/test-002.tle'); 73 | $tle = new Predict_TLE($file[0], $file[1], $file[2]); 74 | $sat = new Predict_Sat($tle); 75 | $sgpsdp = new Predict_SGPSDP(); 76 | 77 | $count = 0; 78 | foreach ($expected as $e) { 79 | $sgpsdp->SGP4($sat, $e['step']); 80 | Predict_Math::Convert_Sat_State($sat->pos, $sat->vel); 81 | 82 | $count++; 83 | $data[$count] = array(); 84 | $data[$count][0] = $e['step']; 85 | $data[$count][1] = 'X'; 86 | $data[$count][2] = $sat->pos->x; 87 | $data[$count][3] = $e['x']; 88 | 89 | $count++; 90 | $data[$count] = array(); 91 | $data[$count][0] = ''; 92 | $data[$count][1] = 'Y'; 93 | $data[$count][2] = $sat->pos->y; 94 | $data[$count][3] = $e['y']; 95 | 96 | $count++; 97 | $data[$count] = array(); 98 | $data[$count][0] = ''; 99 | $data[$count][1] = 'Z'; 100 | $data[$count][2] = $sat->pos->z; 101 | $data[$count][3] = $e['z']; 102 | 103 | $count++; 104 | $data[$count] = array(); 105 | $data[$count][0] = ''; 106 | $data[$count][1] = 'VX'; 107 | $data[$count][2] = $sat->vel->x; 108 | $data[$count][3] = $e['vx']; 109 | 110 | $count++; 111 | $data[$count] = array(); 112 | $data[$count][0] = ''; 113 | $data[$count][1] = 'VY'; 114 | $data[$count][2] = $sat->vel->y; 115 | $data[$count][3] = $e['vy']; 116 | 117 | $count++; 118 | $data[$count] = array(); 119 | $data[$count][0] = ''; 120 | $data[$count][1] = 'VZ'; 121 | $data[$count][2] = $sat->vel->z; 122 | $data[$count][3] = $e['vz']; 123 | } 124 | // exit; 125 | 126 | $tbl = new Console_Table(); 127 | $tbl->setHeaders($headers); 128 | $tbl->addData($data); 129 | 130 | 131 | echo "DEEP_SPACE_EPHEM: " . ($sat->flags & Predict_SGPSDP::DEEP_SPACE_EPHEM_FLAG) . " (expected: 64)\n\n"; 132 | 133 | echo $tbl->getTable(); 134 | 135 | echo "Execution time: " . number_format((microtime(true) - $start) * 1000, 2) . "ms\n"; 136 | -------------------------------------------------------------------------------- /Predict/Solar.php: -------------------------------------------------------------------------------- 1 | x = $R * cos($Lsa); 48 | $solar_vector->y = $R * sin($Lsa) * cos($eps); 49 | $solar_vector->z = $R * sin($Lsa) * sin($eps); 50 | $solar_vector->w = $R; 51 | } 52 | 53 | /* Calculates stellite's eclipse status and depth */ 54 | public static function Sat_Eclipsed(Predict_Vector $pos, Predict_Vector $sol, &$depth) 55 | { 56 | $Rho = new Predict_Vector(); 57 | $earth = new Predict_Vector(); 58 | 59 | /* Determine partial eclipse */ 60 | $sd_earth = Predict_Math::ArcSin(Predict::xkmper / $pos->w); 61 | Predict_Math::Vec_Sub($sol, $pos, $Rho); 62 | $sd_sun = Predict_Math::ArcSin(Predict::__sr__ / $Rho->w); 63 | Predict_Math::Scalar_Multiply(-1, $pos, $earth); 64 | $delta = Predict_Math::Angle($sol, $earth); 65 | $depth = $sd_earth - $sd_sun - $delta; 66 | 67 | if ($sd_earth < $sd_sun) { 68 | return 0; 69 | } else if ($depth >= 0) { 70 | return 1; 71 | } else { 72 | return 0; 73 | } 74 | } 75 | 76 | /** 77 | * Finds the current location of the sun based on the observer location 78 | * 79 | * @param Predict_QTH $qth The observer location 80 | * @param int $daynum The daynum or null to use the current daynum 81 | * 82 | * @return Predict_ObsSet 83 | */ 84 | public static function FindSun(Predict_QTH $qth, $daynum = null) 85 | { 86 | if ($daynum === null) { 87 | $daynum = Predict_Time::get_current_daynum(); 88 | } 89 | 90 | $obs_geodetic = new Predict_Geodetic(); 91 | $obs_geodetic->lon = $qth->lon * Predict::de2ra; 92 | $obs_geodetic->lat = $qth->lat * Predict::de2ra; 93 | $obs_geodetic->alt = $qth->alt / 1000.0; 94 | $obs_geodetic->theta = 0; 95 | 96 | $solar_vector = new Predict_Vector(); 97 | $zero_vector = new Predict_Vector(); 98 | $solar_set = new Predict_ObsSet(); 99 | 100 | self::Calculate_Solar_Position($daynum, $solar_vector); 101 | Predict_SGPObs::Calculate_Obs( 102 | $daynum, 103 | $solar_vector, 104 | $zero_vector, 105 | $obs_geodetic, 106 | $solar_set 107 | ); 108 | 109 | $solar_set->az = Predict_Math::Degrees($solar_set->az); 110 | $solar_set->el = Predict_Math::Degrees($solar_set->el); 111 | 112 | return $solar_set; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /xhprof_lib/EC/XHProf.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2010 Empower Campaigns 9 | * @license New BSD 10 | * @link http://github.com/empower/ec_xhprof 11 | */ 12 | 13 | /** 14 | * Helper for parsing xhprof output directory contents. 15 | * 16 | * @category EC_XHProf 17 | * @package EC 18 | * @author Bill Shupp 19 | * @copyright 2010 Empower Campaigns 20 | * @license New BSD 21 | * @link http://github.com/empower/ec_xhprof 22 | */ 23 | class EC_XHProf 24 | { 25 | /** 26 | * The output directory 27 | * 28 | * @var string 29 | */ 30 | protected $_outputDirectory = '/tmp/xhprof'; 31 | 32 | /** 33 | * The namepsace to filter on (vhost) 34 | * 35 | * @var string 36 | */ 37 | protected $_namespace = null; 38 | 39 | /** 40 | * The list of runs found in this namespace. The key is the run id, the 41 | * value is a unix timestamp based on the atime of the run. 42 | * 43 | * @var array 44 | */ 45 | protected $_runs = array(); 46 | 47 | /** 48 | * Optional list of prefixes to use, such as media for media-example.com 49 | * 50 | * @var array 51 | */ 52 | protected $_prefixes = array(); 53 | 54 | /** 55 | * Sets the namespace and output directory, then looks up the runs 56 | * 57 | * @param string $namespace The namespace to filter on. (vhost) 58 | * @param string $outputDirectory Optional output directory, used for 59 | * testing 60 | * @param string $prefixes Optional array of additional prefixes 61 | * 62 | * @return void 63 | */ 64 | public function __construct( 65 | $namespace, $outputDirectory = null, $prefixes = array() 66 | ) 67 | { 68 | if ($outputDirectory !== null) { 69 | $this->_outputDirectory = $outputDirectory; 70 | } 71 | 72 | $this->_namespace = $namespace; 73 | $this->_prefixes = $prefixes; 74 | $this->_lookupRuns(); 75 | } 76 | 77 | /** 78 | * Looks up the runs in this namespace 79 | * 80 | * @return void 81 | */ 82 | protected function _lookupRuns() 83 | { 84 | if (!is_dir($this->_outputDirectory)) { 85 | return; 86 | } 87 | 88 | $skip = array('.', '..'); 89 | $d = dir($this->_outputDirectory); 90 | $path = $d->path; 91 | $list = array(); 92 | 93 | for ($entry = $d->read(); $entry !== false; $entry = $d->read()) { 94 | if (in_array($entry, $skip)) { 95 | continue; 96 | } 97 | 98 | if (!preg_match('!' . $this->_namespace . '.xhprof$!', $entry)) { 99 | continue; 100 | } 101 | 102 | $entryParts = explode('.', $entry); 103 | $run = array_shift($entryParts); 104 | 105 | $stat = stat($path . '/' . $entry); 106 | $list[$run] = $stat['mtime']; 107 | } 108 | asort($list); 109 | $this->_runs = $list; 110 | } 111 | 112 | /** 113 | * Returns an array of runs in this namespace. The key is the run id, the 114 | * value is string formatted time. 115 | * 116 | * @return array 117 | */ 118 | public function getRuns() 119 | { 120 | return array_map(array($this, 'formatTime'), $this->_runs); 121 | } 122 | 123 | /** 124 | * Converts the unix timestamp of the run as to a readable format 125 | * 126 | * @param int $stamp The atime stamp from stat() 127 | * 128 | * @return string 129 | */ 130 | public function formatTime($stamp) 131 | { 132 | return strftime('%c', $stamp); 133 | } 134 | 135 | /** 136 | * Clears out existing runs from the output directory 137 | * 138 | * @return void 139 | */ 140 | public function clear() 141 | { 142 | foreach ($this->_runs as $key => $value) { 143 | $f = $this->_outputDirectory . '/' . $key . '.' . $this->_namespace . '.xhprof'; 144 | 145 | if (!is_readable($f)) { 146 | // Try prefixes 147 | foreach ($this->_prefixes as $prefix) { 148 | $f = $this->_outputDirectory . '/' . $key . '.' 149 | . $prefix . $this->_namespace; 150 | if (is_readable($f)) { 151 | break; 152 | } 153 | } 154 | } 155 | 156 | unlink($f); 157 | unset($this->_runs[$key]); 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /xhprof_lib/utils/xhprof_runs.php: -------------------------------------------------------------------------------- 1 | suffix; 80 | 81 | if (!empty($this->dir)) { 82 | $file = $this->dir . "/" . $file; 83 | } 84 | return $file; 85 | } 86 | 87 | public function __construct($dir = null) { 88 | 89 | // if user hasn't passed a directory location, 90 | // we use the xhprof.output_dir ini setting 91 | // if specified, else we default to the directory 92 | // in which the error_log file resides. 93 | 94 | if (empty($dir)) { 95 | $dir = ini_get("xhprof.output_dir"); 96 | if (empty($dir)) { 97 | 98 | // some default that at least works on unix... 99 | $dir = "/tmp"; 100 | 101 | xhprof_error("Warning: Must specify directory location for XHProf runs. ". 102 | "Trying {$dir} as default. You can either pass the " . 103 | "directory location as an argument to the constructor ". 104 | "for XHProfRuns_Default() or set xhprof.output_dir ". 105 | "ini param."); 106 | } 107 | } 108 | $this->dir = $dir; 109 | } 110 | 111 | public function get_run($run_id, $type, &$run_desc) { 112 | $file_name = $this->file_name($run_id, $type); 113 | 114 | if (!file_exists($file_name)) { 115 | xhprof_error("Could not find file $file_name"); 116 | $run_desc = "Invalid Run Id = $run_id"; 117 | return null; 118 | } 119 | 120 | $contents = file_get_contents($file_name); 121 | $run_desc = "XHProf Run (Namespace=$type)"; 122 | return unserialize($contents); 123 | } 124 | 125 | public function save_run($xhprof_data, $type, $run_id = null) { 126 | 127 | // Use PHP serialize function to store the XHProf's 128 | // raw profiler data. 129 | $xhprof_data = serialize($xhprof_data); 130 | 131 | if ($run_id === null) { 132 | $run_id = $this->gen_run_id($type); 133 | } 134 | 135 | $file_name = $this->file_name($run_id, $type); 136 | $file = fopen($file_name, 'w'); 137 | 138 | if ($file) { 139 | fwrite($file, $xhprof_data); 140 | fclose($file); 141 | } else { 142 | xhprof_error("Could not open $file_name\n"); 143 | } 144 | 145 | // echo "Saved run in {$file_name}.\nRun id = {$run_id}.\n"; 146 | return $run_id; 147 | } 148 | 149 | function list_runs() { 150 | if (is_dir($this->dir)) { 151 | echo "
Existing runs:\n\n"; 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /Predict/Math.php: -------------------------------------------------------------------------------- 1 | 0 ) { 26 | return 1; 27 | } else if ($arg < 0 ) { 28 | return -1; 29 | } else { 30 | return 0; 31 | } 32 | } 33 | 34 | /* Returns the arcsine of the argument */ 35 | public static function ArcSin($arg) 36 | { 37 | if (abs($arg) >= 1 ) { 38 | return (self::Sign($arg) * Predict::pio2); 39 | } else { 40 | return(atan($arg / sqrt(1 - $arg * $arg))); 41 | } 42 | } 43 | 44 | /* Returns arccosine of rgument */ 45 | public static function ArcCos($arg) 46 | { 47 | return Predict::pio2 - self::ArcSin($arg); 48 | } 49 | 50 | /* Adds vectors v1 and v2 together to produce v3 */ 51 | public static function Vec_Add(Predict_Vector $v1, Predict_Vector $v2, Predict_Vector $v3) 52 | { 53 | $v3->x = $v1->x + $v2->x; 54 | $v3->y = $v1->y + $v2->y; 55 | $v3->z = $v1->z + $v2->z; 56 | 57 | $v3->w = sqrt($v3->x * $v3->x + $v3->y * $v3->y + $v3->z * $v3->z); 58 | } 59 | 60 | /* Subtracts vector v2 from v1 to produce v3 */ 61 | public static function Vec_Sub(Predict_Vector $v1, Predict_Vector $v2, Predict_Vector $v3) 62 | { 63 | $v3->x = $v1->x - $v2->x; 64 | $v3->y = $v1->y - $v2->y; 65 | $v3->z = $v1->z - $v2->z; 66 | 67 | $v3->w = sqrt($v3->x * $v3->x + $v3->y * $v3->y + $v3->z * $v3->z); 68 | } 69 | 70 | /* Multiplies the vector v1 by the scalar k to produce the vector v2 */ 71 | public static function Scalar_Multiply($k, Predict_Vector $v1, Predict_Vector $v2) 72 | { 73 | $v2->x = $k * $v1->x; 74 | $v2->y = $k * $v1->y; 75 | $v2->z = $k * $v1->z; 76 | $v2->w = abs($k) * $v1->w; 77 | } 78 | 79 | /* Multiplies the vector v1 by the scalar k */ 80 | public static function Scale_Vector($k, Predict_Vector $v) 81 | { 82 | $v->x *= $k; 83 | $v->y *= $k; 84 | $v->z *= $k; 85 | 86 | $v->w = sqrt($v->x * $v->x + $v->y * $v->y + $v->z * $v->z); 87 | } 88 | 89 | /* Returns the dot product of two vectors */ 90 | public static function Dot(Predict_Vector $v1, Predict_Vector $v2) 91 | { 92 | return ($v1->x * $v2->x + $v1->y * $v2->y + $v1->z * $v2->z); 93 | } 94 | 95 | /* Calculates the angle between vectors v1 and v2 */ 96 | public static function Angle(Predict_Vector $v1, Predict_Vector $v2) 97 | { 98 | $v1->w = sqrt($v1->x * $v1->x + $v1->y * $v1->y + $v1->z * $v1->z); 99 | $v2->w = sqrt($v2->x * $v2->x + $v2->y * $v2->y + $v2->z * $v2->z); 100 | return (self::ArcCos(self::Dot($v1, $v2) / ($v1->w * $v2->w))); 101 | } 102 | 103 | /* Produces cross product of v1 and v2, and returns in v3 */ 104 | public static function Cross(Predict_Vector $v1, Predict_Vector $v2 ,Predict_Vector $v3) 105 | { 106 | $v3->x = $v1->y * $v2->z - $v1->z * $v2->y; 107 | $v3->y = $v1->z * $v2->x - $v1->x * $v2->z; 108 | $v3->z = $v1->x * $v2->y - $v1->y * $v2->x; 109 | 110 | $v3->w = sqrt($v3->x * $v3->x + $v3->y * $v3->y + $v3->z * $v3->z); 111 | } 112 | 113 | /* Normalizes a vector */ 114 | public static function Normalize(Predict_Vector $v ) 115 | { 116 | $v->x /= $v->w; 117 | $v->y /= $v->w; 118 | $v->z /= $v->w; 119 | } 120 | 121 | /* Four-quadrant arctan function */ 122 | public static function AcTan($sinx, $cosx) 123 | { 124 | if ($cosx == 0) { 125 | if ($sinx > 0) { 126 | return Predict::pio2; 127 | } else { 128 | return Predict::x3pio2; 129 | } 130 | } else { 131 | if ($cosx > 0) { 132 | if ($sinx > 0) { 133 | return atan($sinx / $cosx); 134 | } else { 135 | return Predict::twopi + atan($sinx / $cosx); 136 | } 137 | } else { 138 | return Predict::pi + atan($sinx / $cosx); 139 | } 140 | } 141 | } 142 | 143 | /* Returns mod 2pi of argument */ 144 | public static function FMod2p($x) 145 | { 146 | $ret_val = $x; 147 | $i = (int) ($ret_val / Predict::twopi); 148 | $ret_val -= $i * Predict::twopi; 149 | 150 | if ($ret_val < 0) { 151 | $ret_val += Predict::twopi; 152 | } 153 | 154 | return $ret_val; 155 | } 156 | 157 | /* Returns arg1 mod arg2 */ 158 | public static function Modulus($arg1, $arg2) 159 | { 160 | $ret_val = $arg1; 161 | $i = (int) ($ret_val / $arg2); 162 | $ret_val -= $i * $arg2; 163 | 164 | if ($ret_val < 0) { 165 | $ret_val += $arg2; 166 | } 167 | 168 | return $ret_val; 169 | } 170 | 171 | /* Returns fractional part of double argument */ 172 | public static function Frac($arg) 173 | { 174 | return $arg - floor($arg); 175 | } 176 | 177 | /* Converts the satellite's position and velocity */ 178 | /* vectors from normalised values to km and km/sec */ 179 | public static function Convert_Sat_State(Predict_Vector $pos, Predict_Vector $vel) 180 | { 181 | self::Scale_Vector(Predict::xkmper, $pos); 182 | self::Scale_Vector(Predict::xkmper * Predict::xmnpda / Predict::secday, $vel); 183 | } 184 | 185 | /* Returns angle in radians from arg in degrees */ 186 | public static function Radians($arg) 187 | { 188 | return $arg * Predict::de2ra; 189 | } 190 | 191 | /* Returns angle in degrees from arg in rads */ 192 | public static function Degrees($arg) 193 | { 194 | return $arg / Predict::de2ra; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | Predict 7 | shupp.github.com/pirum 8 | A partial port of the Gpredict program for satellite tracking 9 | Predict is a partial PHP port of the Gpredict (http://gpredict.oz9aec.net/) program that allows real-time tracking and orbit prediction of satellites from two line element sets. It supports the SGP4 and SDP4 models for prediction. 10 | 11 | Bill Shupp 12 | shupp 13 | shupp@php.net 14 | yes 15 | 16 | 2014-03-06 17 | 18 | 19 | 0.2.2 20 | 0.2.2 21 | 22 | 23 | alpha 24 | alpha 25 | 26 | GPL v2.1 27 | 28 | * Addec Predict_TLE::createChecksum() 29 | * Updates to examples 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 5.2.0 77 | 78 | 79 | 1.4.0b1 80 | 81 | 82 | date 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 0.1.0 91 | 0.1.0 92 | 93 | 94 | alpha 95 | alpha 96 | 97 | 2011-08-22 98 | GPL v2.1 99 | 100 | * Initial release 101 | 102 | 103 | 104 | 105 | 0.1.1 106 | 0.1.1 107 | 108 | 109 | alpha 110 | alpha 111 | 112 | 2011-09-05 113 | GPL v2.1 114 | 115 | * Fixed minimum elevation bug in visible pass detection 116 | * Updated precision of Pogson's Ratio, refactored calculate magnitude to be more readble, as well as added comments 117 | 118 | 119 | 120 | 121 | 0.1.2 122 | 0.1.2 123 | 124 | 125 | alpha 126 | alpha 127 | 128 | 2011-09-25 129 | GPL v2.1 130 | 131 | * Added Predict_Time::getEpochTimeStamp() 132 | 133 | 134 | 135 | 136 | 0.2.0 137 | 0.2.0 138 | 139 | 140 | alpha 141 | alpha 142 | 143 | 2012-05-21 144 | GPL v2.1 145 | 146 | * Fixed bug in Predict_Time::unix2daynum() 147 | * Updated example iss.tle file 148 | 149 | 150 | 151 | 152 | 0.2.1 153 | 0.2.1 154 | 155 | 156 | alpha 157 | alpha 158 | 159 | 2012-07-04 160 | GPL v2.1 161 | 162 | * Corrected role of README.md 163 | 164 | 165 | 166 | 167 | 0.2.2 168 | 0.2.2 169 | 170 | 171 | alpha 172 | alpha 173 | 174 | 2014-03-06 175 | GPL v2.1 176 | 177 | * Addec Predict_TLE::createChecksum() 178 | * Updates to examples 179 | 180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /xhprof_lib/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 += '
'; 177 | 178 | return s; 179 | } 180 | 181 | $(document).ready(function() { 182 | $('td[@metric]').tooltip( 183 | { bodyHandler: function() { 184 | var type = $(this).attr('type'); 185 | var metric = $(this).attr('metric'); 186 | if (type == 'Parent') { 187 | return ParentRowToolTip(this, metric); 188 | } else if (type == 'Child') { 189 | return ChildRowToolTip(this, metric); 190 | } 191 | }, 192 | showURL : false 193 | }); 194 | var cur_params = {} ; 195 | $.each(location.search.replace('?','').split('&'), function(i, x) { 196 | var y = x.split('='); cur_params[y[0]] = y[1]; 197 | }); 198 | $('input.function_typeahead') 199 | .autocomplete('typeahead.php', { extraParams : cur_params }) 200 | .result(function(event, item) { 201 | cur_params['symbol'] = item; 202 | location.search = '?' + jQuery.param(cur_params); 203 | }); 204 | }); 205 | -------------------------------------------------------------------------------- /Predict/SGPObs.php: -------------------------------------------------------------------------------- 1 | lat); /* Only run sin($geodetic->lat) once */ 39 | 40 | $geodetic->theta = Predict_Math::FMod2p(Predict_Time::ThetaG_JD($_time) + $geodetic->lon);/*LMST*/ 41 | $c = 1 / sqrt(1 + Predict::__f * (Predict::__f - 2) * $sinGeodeticLat * $sinGeodeticLat); 42 | $sq = (1 - Predict::__f) * (1 - Predict::__f) * $c; 43 | $achcp = (Predict::xkmper * $c + $geodetic->alt) * cos($geodetic->lat); 44 | $obs_pos->x = $achcp * cos($geodetic->theta); /*kilometers*/ 45 | $obs_pos->y = $achcp * sin($geodetic->theta); 46 | $obs_pos->z = (Predict::xkmper * $sq + $geodetic->alt) * $sinGeodeticLat; 47 | $obs_vel->x = -Predict::mfactor * $obs_pos->y; /*kilometers/second*/ 48 | $obs_vel->y = Predict::mfactor * $obs_pos->x; 49 | $obs_vel->z = 0; 50 | $obs_pos->w = sqrt($obs_pos->x * $obs_pos->x + $obs_pos->y * $obs_pos->y + $obs_pos->z * $obs_pos->z); 51 | $obs_vel->w = sqrt($obs_vel->x * $obs_vel->x + $obs_vel->y * $obs_vel->y + $obs_vel->z * $obs_vel->z); 52 | } 53 | 54 | /* Procedure Calculate_LatLonAlt will calculate the geodetic */ 55 | /* position of an object given its ECI position pos and time. */ 56 | /* It is intended to be used to determine the ground track of */ 57 | /* a satellite. The calculations assume the earth to be an */ 58 | /* oblate spheroid as defined in WGS '72. */ 59 | public static function Calculate_LatLonAlt($_time, Predict_Vector $pos, Predict_Geodetic $geodetic) 60 | { 61 | /* Reference: The 1992 Astronomical Almanac, page K12. */ 62 | 63 | /* double r,e2,phi,c; */ 64 | 65 | $geodetic->theta = Predict_Math::AcTan($pos->y, $pos->x); /*radians*/ 66 | $geodetic->lon = Predict_Math::FMod2p($geodetic->theta - Predict_Time::ThetaG_JD($_time)); /*radians*/ 67 | $r = sqrt(($pos->x * $pos->x) + ($pos->y * $pos->y)); 68 | $e2 = Predict::__f * (2 - Predict::__f); 69 | $geodetic->lat = Predict_Math::AcTan($pos->z, $r); /*radians*/ 70 | 71 | do { 72 | $phi = $geodetic->lat; 73 | $sinPhi = sin($phi); 74 | $c = 1 / sqrt(1 - $e2 * ($sinPhi * $sinPhi)); 75 | $geodetic->lat = Predict_Math::AcTan($pos->z + Predict::xkmper * $c * $e2 * $sinPhi, $r); 76 | } while (abs($geodetic->lat - $phi) >= 1E-10); 77 | 78 | $geodetic->alt = $r / cos($geodetic->lat) - Predict::xkmper * $c;/*kilometers*/ 79 | 80 | if ($geodetic->lat > Predict::pio2) { 81 | $geodetic->lat -= Predict::twopi; 82 | } 83 | } 84 | 85 | /* The procedures Calculate_Obs and Calculate_RADec calculate */ 86 | /* the *topocentric* coordinates of the object with ECI position, */ 87 | /* {pos}, and velocity, {vel}, from location {geodetic} at {time}. */ 88 | /* The {obs_set} returned for Calculate_Obs consists of azimuth, */ 89 | /* elevation, range, and range rate (in that order) with units of */ 90 | /* radians, radians, kilometers, and kilometers/second, respectively. */ 91 | /* The WGS '72 geoid is used and the effect of atmospheric refraction */ 92 | /* (under standard temperature and pressure) is incorporated into the */ 93 | /* elevation calculation; the effect of atmospheric refraction on */ 94 | /* range and range rate has not yet been quantified. */ 95 | 96 | /* The {obs_set} for Calculate_RADec consists of right ascension and */ 97 | /* declination (in that order) in radians. Again, calculations are */ 98 | /* based on *topocentric* position using the WGS '72 geoid and */ 99 | /* incorporating atmospheric refraction. */ 100 | public static function Calculate_Obs($_time, Predict_Vector $pos, Predict_Vector $vel, Predict_Geodetic $geodetic, Predict_ObsSet $obs_set) 101 | { 102 | $obs_pos = new Predict_Vector(); 103 | $obs_vel = new Predict_Vector(); 104 | $range = new Predict_Vector(); 105 | $rgvel = new Predict_Vector(); 106 | 107 | self::Calculate_User_PosVel($_time, $geodetic, $obs_pos, $obs_vel); 108 | 109 | $range->x = $pos->x - $obs_pos->x; 110 | $range->y = $pos->y - $obs_pos->y; 111 | $range->z = $pos->z - $obs_pos->z; 112 | 113 | $rgvel->x = $vel->x - $obs_vel->x; 114 | $rgvel->y = $vel->y - $obs_vel->y; 115 | $rgvel->z = $vel->z - $obs_vel->z; 116 | 117 | $range->w = sqrt($range->x * $range->x + $range->y * $range->y + $range->z * $range->z); 118 | 119 | $sin_lat = sin($geodetic->lat); 120 | $cos_lat = cos($geodetic->lat); 121 | $sin_theta = sin($geodetic->theta); 122 | $cos_theta = cos($geodetic->theta); 123 | $top_s = $sin_lat * $cos_theta * $range->x 124 | + $sin_lat * $sin_theta * $range->y 125 | - $cos_lat * $range->z; 126 | $top_e = -$sin_theta * $range->x 127 | + $cos_theta * $range->y; 128 | $top_z = $cos_lat * $cos_theta * $range->x 129 | + $cos_lat * $sin_theta * $range->y 130 | + $sin_lat * $range->z; 131 | $azim = atan(-$top_e / $top_s); /*Azimuth*/ 132 | if ($top_s > 0) { 133 | $azim = $azim + Predict::pi; 134 | } 135 | if ($azim < 0 ) { 136 | $azim = $azim + Predict::twopi; 137 | } 138 | $el = Predict_Math::ArcSin($top_z / $range->w); 139 | $obs_set->az = $azim; /* Azimuth (radians) */ 140 | $obs_set->el = $el; /* Elevation (radians)*/ 141 | $obs_set->range = $range->w; /* Range (kilometers) */ 142 | 143 | /* Range Rate (kilometers/second)*/ 144 | $obs_set->range_rate = Predict_Math::Dot($range, $rgvel) / $range->w; 145 | 146 | /* Corrections for atmospheric refraction */ 147 | /* Reference: Astronomical Algorithms by Jean Meeus, pp. 101-104 */ 148 | /* Correction is meaningless when apparent elevation is below horizon */ 149 | // obs_set->el = obs_set->el + Radians((1.02/tan(Radians(Degrees(el)+ 150 | // 10.3/(Degrees(el)+5.11))))/60); 151 | if ($obs_set->el < 0) { 152 | $obs_set->el = $el; /*Reset to true elevation*/ 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /Predict/Time.php: -------------------------------------------------------------------------------- 1 | ds50 = $jd - 2433281.5 + $UT; 111 | 112 | return Predict_Math::FMod2p(6.3003880987 * $deep_arg->ds50 + 1.72944494); 113 | } 114 | 115 | /* See the ThetaG doc block above */ 116 | public static function ThetaG_JD($jd) 117 | { 118 | /* Reference: The 1992 Astronomical Almanac, page B6. */ 119 | $UT = Predict_Math::Frac($jd + 0.5); 120 | $jd = $jd - $UT; 121 | $TU = ($jd - 2451545.0) / 36525; 122 | $GMST = 24110.54841 + $TU * (8640184.812866 + $TU * (0.093104 - $TU * 6.2E-6)); 123 | $GMST = Predict_Math::Modulus($GMST + Predict::secday * Predict::omega_E * $UT, Predict::secday); 124 | 125 | return Predict::twopi * $GMST / Predict::secday; 126 | } 127 | 128 | /** 129 | * Read the system clock and return the current Julian day. From phpPredict 130 | * 131 | * @return float 132 | */ 133 | public static function get_current_daynum() { 134 | // Gets the current decimal day number from microtime 135 | 136 | list($usec, $sec) = explode(' ', microtime()); 137 | return self::unix2daynum($sec, $usec); 138 | } 139 | 140 | /** 141 | * Converts a standard unix timestamp and optional 142 | * milliseconds to a daynum 143 | * 144 | * @param int $sec Seconds from the unix epoch 145 | * @param int $usec Optional milliseconds 146 | * 147 | * @return float 148 | */ 149 | public static function unix2daynum($sec, $usec = 0) 150 | { 151 | $time = ((($sec + $usec) / 86400.0) - 3651.0); 152 | return $time + 2444238.5; 153 | } 154 | 155 | /* The function Delta_ET has been added to allow calculations on */ 156 | /* the position of the sun. It provides the difference between UT */ 157 | /* (approximately the same as UTC) and ET (now referred to as TDT).*/ 158 | /* This function is based on a least squares fit of data from 1950 */ 159 | /* to 1991 and will need to be updated periodically. */ 160 | public static function Delta_ET($year) 161 | { 162 | /* Values determined using data from 1950-1991 in the 1990 163 | Astronomical Almanac. See DELTA_ET.WQ1 for details. */ 164 | 165 | $delta_et = 26.465 + 0.747622 * ($year - 1950) + 166 | 1.886913 * sin(Predict::twopi * ($year - 1975) / 33); 167 | 168 | return $delta_et; 169 | } 170 | 171 | /** 172 | * Converts a daynum to a unix timestamp. From phpPredict. 173 | * 174 | * @param float $dn Julian Daynum 175 | * 176 | * @return float 177 | */ 178 | public static function daynum2unix($dn) { 179 | // Converts a daynum to a UNIX timestamp 180 | 181 | return (86400.0 * ($dn - 2444238.5 + 3651.0)); 182 | } 183 | 184 | /** 185 | * Converts a daynum to a readable time format. 186 | * 187 | * @param float $dn The julian date 188 | * @param string $zone The zone string, defaults to America/Los_Angeles 189 | * @param string $format The date() function's format string. Defaults to m-d-Y H:i:s 190 | * 191 | * @return string 192 | */ 193 | public static function daynum2readable($dn, $zone = 'America/Los_Angeles', $format = 'm-d-Y H:i:s') 194 | { 195 | $unix = self::daynum2unix($dn); 196 | $date = new DateTime("@" . round($unix)); 197 | $dateTimezone = new DateTimezone($zone); 198 | $date->setTimezone($dateTimezone); 199 | return $date->format($format); 200 | } 201 | 202 | /** 203 | * Returns the unix timestamp of a TLE's epoch 204 | * 205 | * @param Predict_TLE $tle The TLE object 206 | * 207 | * @return int 208 | */ 209 | public static function getEpochTimeStamp(Predict_TLE $tle) 210 | { 211 | $year = $tle->epoch_year; 212 | $day = $tle->epoch_day; 213 | $sec = round(86400 * $tle->epoch_fod); 214 | 215 | $zone = new DateTimeZone('GMT'); 216 | $date = new DateTime(); 217 | $date->setTimezone($zone); 218 | $date->setDate($year, 1, 1); 219 | $date->setTime(0, 0, 0); 220 | 221 | return $date->format('U') + (86400 * $day) + $sec - 86400; 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /Predict/TLE.php: -------------------------------------------------------------------------------- 1 | Good_Elements($line1, $line2)) { 53 | throw new Predict_Exception('Invalid TLE contents'); 54 | } 55 | 56 | $this->header = $header; 57 | $this->line1 = $line1; 58 | $this->line2 = $line2; 59 | 60 | /** Decode Card 1 **/ 61 | /* Satellite's catalogue number */ 62 | $this->catnr = (int) substr($line1, 2, 5); 63 | 64 | /* International Designator for satellite */ 65 | $this->idesg = substr($line1, 9, 8); 66 | 67 | /* Epoch time; this is the complete, unconverted epoch. */ 68 | /* Replace spaces with 0 before casting, as leading spaces are allowed */ 69 | $this->epoch = (float) str_replace(' ', '0', substr($line1, 18, 14)); 70 | 71 | /* Now, convert the epoch time into year, day 72 | and fraction of day, according to: 73 | 74 | YYDDD.FFFFFFFF 75 | */ 76 | 77 | // Adjust for 2 digit year through 2056 78 | $this->epoch_year = (int) substr($line1, 18, 2); 79 | if ($this->epoch_year > 56) { 80 | $this->epoch_year = $this->epoch_year + 1900; 81 | } else { 82 | $this->epoch_year = $this->epoch_year + 2000; 83 | } 84 | 85 | /* Epoch day */ 86 | $this->epoch_day = (int) substr($line1, 20, 3); 87 | 88 | /* Epoch fraction of day */ 89 | $this->epoch_fod = (float) substr($line1, 23, 9); 90 | 91 | 92 | /* Satellite's First Time Derivative */ 93 | $this->xndt2o = (float) substr($line1, 33, 10); 94 | 95 | /* Satellite's Second Time Derivative */ 96 | $this->xndd6o = (float) (substr($line1, 44, 1) . '.' . substr($line1, 45, 5) . 'E' . substr($line1, 50, 2)); 97 | 98 | /* Satellite's bstar drag term 99 | FIXME: How about buff[0] ???? 100 | */ 101 | $this->bstar = (float) (substr($line1, 53, 1) . '.' . substr($line1, 54, 5) . 'E' . substr($line1, 59, 2)); 102 | 103 | /* Element Number */ 104 | $this->elset = (int) substr($line1, 64, 4); 105 | 106 | /** Decode Card 2 **/ 107 | /* Satellite's Orbital Inclination (degrees) */ 108 | $this->xincl = (float) substr($line2, 8, 8); 109 | 110 | /* Satellite's RAAN (degrees) */ 111 | $this->xnodeo = (float) substr($line2, 17, 8); 112 | 113 | /* Satellite's Orbital Eccentricity */ 114 | $this->eo = (float) ('.' . substr($line2, 26, 7)); 115 | 116 | /* Satellite's Argument of Perigee (degrees) */ 117 | $this->omegao = (float) substr($line2, 34, 8); 118 | 119 | /* Satellite's Mean Anomaly of Orbit (degrees) */ 120 | $this->xmo = (float) substr($line2, 43, 8); 121 | 122 | /* Satellite's Mean Motion (rev/day) */ 123 | $this->xno = (float) substr($line2, 52, 11); 124 | 125 | /* Satellite's Revolution number at epoch */ 126 | $this->revnum = (float) substr($line2, 63, 5); 127 | } 128 | 129 | /* Calculates the checksum mod 10 of a line from a TLE set and */ 130 | /* returns true if it compares with checksum in column 68, else false.*/ 131 | /* tle_set is a character string holding the two lines read */ 132 | /* from a text file containing NASA format Keplerian elements. */ 133 | /* NOTE!!! The stuff about two lines is not quite true. 134 | The function assumes that tle_set[0] is the begining 135 | of the line and that there are 68 elements - see the consumer 136 | */ 137 | public function Checksum_Good($tle_set) 138 | { 139 | if (strlen($tle_set) < 69) { 140 | return false; 141 | } 142 | 143 | $checksum = 0; 144 | 145 | for ($i = 0; $i < 68; $i++) { 146 | if (($tle_set[$i] >= '0') && ($tle_set[$i] <= '9')) { 147 | $value = $tle_set[$i] - '0'; 148 | } else if ($tle_set[$i] == '-' ) { 149 | $value = 1; 150 | } else { 151 | $value = 0; 152 | } 153 | 154 | $checksum += $value; 155 | } 156 | 157 | $checksum %= 10; 158 | $check_digit = $tle_set[68] - '0'; 159 | 160 | return $checksum == $check_digit; 161 | } 162 | 163 | /* Carries out various checks on a TLE set to verify its validity */ 164 | /* $line1 is the first line of the TLE, $line2 is the second line */ 165 | /* from a text file containing NASA format Keplerian elements. */ 166 | public function Good_Elements($line1, $line2) 167 | { 168 | /* Verify checksum of both lines of a TLE set */ 169 | if (!$this->Checksum_Good($line1) || !$this->Checksum_Good($line2)) { 170 | return false; 171 | } 172 | 173 | /* Check the line number of each line */ 174 | if (($line1[0] != '1') || ($line2[0] != '2')) { 175 | return false; 176 | } 177 | 178 | /* Verify that Satellite Number is same in both lines */ 179 | if (strncmp($line1[2], $line2[2], 5) != 0) { 180 | return false; 181 | } 182 | 183 | /* Check that various elements are in the right place */ 184 | if (($line1[23] != '.') || 185 | ($line1[34] != '.') || 186 | ($line2[11] != '.') || 187 | ($line2[20] != '.') || 188 | ($line2[37] != '.') || 189 | ($line2[46] != '.') || 190 | ($line2[54] != '.') || 191 | (strncmp(substr($line1, 61), ' 0 ', 3) != 0)) { 192 | 193 | return false; 194 | } 195 | 196 | return true; 197 | } 198 | 199 | /** 200 | * A function to allow checksum creation of a line. This is driven by 201 | * the fact that some TLEs from SpaceTrack are missing checksum numbers. 202 | * You can use this to create a checksum for a line, but you should 203 | * probably have confidence that the TLE data itself is good. YMMV. 204 | * 205 | * @throws Predict_Exception if the line is not exactly 68 chars 206 | * @return string 207 | */ 208 | static public function createChecksum($line) 209 | { 210 | if (strlen($line) != 68) { 211 | throw Predict_Exception('Invalid line, needs to e 68 chars'); 212 | } 213 | 214 | $checksum = 0; 215 | 216 | for ($i = 0; $i < 68; $i++) { 217 | if (($line[$i] >= '0') && ($line[$i] <= '9')) { 218 | $value = (int) $line[$i]; 219 | } else if ($line[$i] == '-' ) { 220 | $value = 1; 221 | } else { 222 | $value = 0; 223 | } 224 | 225 | $checksum += $value; 226 | } 227 | 228 | $checksum %= 10; 229 | 230 | return $checksum; 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /xhprof_lib/jquery/jquery.tooltip.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Tooltip plugin 1.3 3 | * 4 | * http://bassistance.de/jquery-plugins/jquery-plugin-tooltip/ 5 | * http://docs.jquery.com/Plugins/Tooltip 6 | * 7 | * Copyright (c) 2006 - 2008 Jörn Zaefferer 8 | * 9 | * $Id: jquery.tooltip.js,v 1.1.1.1 2009-03-17 18:35:18 kannan Exp $ 10 | * 11 | * Dual licensed under the MIT and GPL licenses: 12 | * http://www.opensource.org/licenses/mit-license.php 13 | * http://www.gnu.org/licenses/gpl.html 14 | */ 15 | 16 | ;(function($) { 17 | 18 | // the tooltip element 19 | var helper = {}, 20 | // the current tooltipped element 21 | current, 22 | // the title of the current element, used for restoring 23 | title, 24 | // timeout id for delayed tooltips 25 | tID, 26 | // IE 5.5 or 6 27 | IE = $.browser.msie && /MSIE\s(5\.5|6\.)/.test(navigator.userAgent), 28 | // flag for mouse tracking 29 | track = false; 30 | 31 | $.tooltip = { 32 | blocked: false, 33 | defaults: { 34 | delay: 200, 35 | fade: false, 36 | showURL: true, 37 | extraClass: "", 38 | top: 15, 39 | left: 15, 40 | id: "tooltip" 41 | }, 42 | block: function() { 43 | $.tooltip.blocked = !$.tooltip.blocked; 44 | } 45 | }; 46 | 47 | $.fn.extend({ 48 | tooltip: function(settings) { 49 | settings = $.extend({}, $.tooltip.defaults, settings); 50 | createHelper(settings); 51 | return this.each(function() { 52 | $.data(this, "tooltip", settings); 53 | this.tOpacity = helper.parent.css("opacity"); 54 | // copy tooltip into its own expando and remove the title 55 | this.tooltipText = this.title; 56 | $(this).removeAttr("title"); 57 | // also remove alt attribute to prevent default tooltip in IE 58 | this.alt = ""; 59 | }) 60 | .mouseover(save) 61 | .mouseout(hide) 62 | .click(hide); 63 | }, 64 | fixPNG: IE ? function() { 65 | return this.each(function () { 66 | var image = $(this).css('backgroundImage'); 67 | if (image.match(/^url\(["']?(.*\.png)["']?\)$/i)) { 68 | image = RegExp.$1; 69 | $(this).css({ 70 | 'backgroundImage': 'none', 71 | 'filter': "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='" + image + "')" 72 | }).each(function () { 73 | var position = $(this).css('position'); 74 | if (position != 'absolute' && position != 'relative') 75 | $(this).css('position', 'relative'); 76 | }); 77 | } 78 | }); 79 | } : function() { return this; }, 80 | unfixPNG: IE ? function() { 81 | return this.each(function () { 82 | $(this).css({'filter': '', backgroundImage: ''}); 83 | }); 84 | } : function() { return this; }, 85 | hideWhenEmpty: function() { 86 | return this.each(function() { 87 | $(this)[ $(this).html() ? "show" : "hide" ](); 88 | }); 89 | }, 90 | url: function() { 91 | return this.attr('href') || this.attr('src'); 92 | } 93 | }); 94 | 95 | function createHelper(settings) { 96 | // there can be only one tooltip helper 97 | if( helper.parent ) 98 | return; 99 | // create the helper, h3 for title, div for url 100 | helper.parent = $('

') 101 | // add to document 102 | .appendTo(document.body) 103 | // hide it at first 104 | .hide(); 105 | 106 | // apply bgiframe if available 107 | if ( $.fn.bgiframe ) 108 | helper.parent.bgiframe(); 109 | 110 | // save references to title and url elements 111 | helper.title = $('h3', helper.parent); 112 | helper.body = $('div.body', helper.parent); 113 | helper.url = $('div.url', helper.parent); 114 | } 115 | 116 | function settings(element) { 117 | return $.data(element, "tooltip"); 118 | } 119 | 120 | // main event handler to start showing tooltips 121 | function handle(event) { 122 | // show helper, either with timeout or on instant 123 | if( settings(this).delay ) 124 | tID = setTimeout(show, settings(this).delay); 125 | else 126 | show(); 127 | 128 | // if selected, update the helper position when the mouse moves 129 | track = !!settings(this).track; 130 | $(document.body).bind('mousemove', update); 131 | 132 | // update at least once 133 | update(event); 134 | } 135 | 136 | // save elements title before the tooltip is displayed 137 | function save() { 138 | // if this is the current source, or it has no title (occurs with click event), stop 139 | if ( $.tooltip.blocked || this == current || (!this.tooltipText && !settings(this).bodyHandler) ) 140 | return; 141 | 142 | // save current 143 | current = this; 144 | title = this.tooltipText; 145 | 146 | if ( settings(this).bodyHandler ) { 147 | helper.title.hide(); 148 | var bodyContent = settings(this).bodyHandler.call(this); 149 | if (bodyContent.nodeType || bodyContent.jquery) { 150 | helper.body.empty().append(bodyContent) 151 | } else { 152 | helper.body.html( bodyContent ); 153 | } 154 | helper.body.show(); 155 | } else if ( settings(this).showBody ) { 156 | var parts = title.split(settings(this).showBody); 157 | helper.title.html(parts.shift()).show(); 158 | helper.body.empty(); 159 | for(var i = 0, part; (part = parts[i]); i++) { 160 | if(i > 0) 161 | helper.body.append("
"); 162 | helper.body.append(part); 163 | } 164 | helper.body.hideWhenEmpty(); 165 | } else { 166 | helper.title.html(title).show(); 167 | helper.body.hide(); 168 | } 169 | 170 | // if element has href or src, add and show it, otherwise hide it 171 | if( settings(this).showURL && $(this).url() ) 172 | helper.url.html( $(this).url().replace('http://', '') ).show(); 173 | else 174 | helper.url.hide(); 175 | 176 | // add an optional class for this tip 177 | helper.parent.addClass(settings(this).extraClass); 178 | 179 | // fix PNG background for IE 180 | if (settings(this).fixPNG ) 181 | helper.parent.fixPNG(); 182 | 183 | handle.apply(this, arguments); 184 | } 185 | 186 | // delete timeout and show helper 187 | function show() { 188 | tID = null; 189 | if ((!IE || !$.fn.bgiframe) && settings(current).fade) { 190 | if (helper.parent.is(":animated")) 191 | helper.parent.stop().show().fadeTo(settings(current).fade, current.tOpacity); 192 | else 193 | helper.parent.is(':visible') ? helper.parent.fadeTo(settings(current).fade, current.tOpacity) : helper.parent.fadeIn(settings(current).fade); 194 | } else { 195 | helper.parent.show(); 196 | } 197 | update(); 198 | } 199 | 200 | /** 201 | * callback for mousemove 202 | * updates the helper position 203 | * removes itself when no current element 204 | */ 205 | function update(event) { 206 | if($.tooltip.blocked) 207 | return; 208 | 209 | if (event && event.target.tagName == "OPTION") { 210 | return; 211 | } 212 | 213 | // stop updating when tracking is disabled and the tooltip is visible 214 | if ( !track && helper.parent.is(":visible")) { 215 | $(document.body).unbind('mousemove', update) 216 | } 217 | 218 | // if no current element is available, remove this listener 219 | if( current == null ) { 220 | $(document.body).unbind('mousemove', update); 221 | return; 222 | } 223 | 224 | // remove position helper classes 225 | helper.parent.removeClass("viewport-right").removeClass("viewport-bottom"); 226 | 227 | var left = helper.parent[0].offsetLeft; 228 | var top = helper.parent[0].offsetTop; 229 | if (event) { 230 | // position the helper 15 pixel to bottom right, starting from mouse position 231 | left = event.pageX + settings(current).left; 232 | top = event.pageY + settings(current).top; 233 | var right='auto'; 234 | if (settings(current).positionLeft) { 235 | right = $(window).width() - left; 236 | left = 'auto'; 237 | } 238 | helper.parent.css({ 239 | left: left, 240 | right: right, 241 | top: top 242 | }); 243 | } 244 | 245 | var v = viewport(), 246 | h = helper.parent[0]; 247 | // check horizontal position 248 | if (v.x + v.cx < h.offsetLeft + h.offsetWidth) { 249 | left -= h.offsetWidth + 20 + settings(current).left; 250 | helper.parent.css({left: left + 'px'}).addClass("viewport-right"); 251 | } 252 | // check vertical position 253 | if (v.y + v.cy < h.offsetTop + h.offsetHeight) { 254 | top -= h.offsetHeight + 20 + settings(current).top; 255 | helper.parent.css({top: top + 'px'}).addClass("viewport-bottom"); 256 | } 257 | } 258 | 259 | function viewport() { 260 | return { 261 | x: $(window).scrollLeft(), 262 | y: $(window).scrollTop(), 263 | cx: $(window).width(), 264 | cy: $(window).height() 265 | }; 266 | } 267 | 268 | // hide helper and restore added classes and the title 269 | function hide(event) { 270 | if($.tooltip.blocked) 271 | return; 272 | // clear timeout if possible 273 | if(tID) 274 | clearTimeout(tID); 275 | // no more current element 276 | current = null; 277 | 278 | var tsettings = settings(this); 279 | function complete() { 280 | helper.parent.removeClass( tsettings.extraClass ).hide().css("opacity", ""); 281 | } 282 | if ((!IE || !$.fn.bgiframe) && tsettings.fade) { 283 | if (helper.parent.is(':animated')) 284 | helper.parent.stop().fadeTo(tsettings.fade, 0, complete); 285 | else 286 | helper.parent.stop().fadeOut(tsettings.fade, complete); 287 | } else 288 | complete(); 289 | 290 | if( settings(this).fixPNG ) 291 | helper.parent.unfixPNG(); 292 | } 293 | 294 | })(jQuery); 295 | -------------------------------------------------------------------------------- /Predict/Sat.php: -------------------------------------------------------------------------------- 1 | header); 72 | $this->name = $headerParts[0]; 73 | $this->nickname = $this->name; 74 | $this->tle = $tle; 75 | $this->pos = new Predict_Vector(); 76 | $this->vel = new Predict_Vector(); 77 | $this->sgps = new Predict_SGSDPStatic(); 78 | $this->deep_arg = new Predict_DeepArg(); 79 | $this->dps = new Predict_DeepStatic(); 80 | 81 | $this->select_ephemeris(); 82 | $this->sat_data_init_sat($this); 83 | } 84 | 85 | /* Selects the apropriate ephemeris type to be used */ 86 | /* for predictions according to the data in the TLE */ 87 | /* It also processes values in the tle set so that */ 88 | /* they are apropriate for the sgp4/sdp4 routines */ 89 | public function select_ephemeris() 90 | { 91 | /* Preprocess tle set */ 92 | $this->tle->xnodeo *= Predict::de2ra; 93 | $this->tle->omegao *= Predict::de2ra; 94 | $this->tle->xmo *= Predict::de2ra; 95 | $this->tle->xincl *= Predict::de2ra; 96 | $temp = Predict::twopi / Predict::xmnpda / Predict::xmnpda; 97 | 98 | /* store mean motion before conversion */ 99 | $this->meanmo = $this->tle->xno; 100 | $this->tle->xno = $this->tle->xno * $temp * Predict::xmnpda; 101 | $this->tle->xndt2o *= $temp; 102 | $this->tle->xndd6o = $this->tle->xndd6o * $temp / Predict::xmnpda; 103 | $this->tle->bstar /= Predict::ae; 104 | 105 | /* Period > 225 minutes is deep space */ 106 | $dd1 = Predict::xke / $this->tle->xno; 107 | $dd2 = Predict::tothrd; 108 | $a1 = pow($dd1, $dd2); 109 | $r1 = cos($this->tle->xincl); 110 | $dd1 = 1.0 - $this->tle->eo * $this->tle->eo; 111 | $temp = Predict::ck2 * 1.5 * ($r1 * $r1 * 3.0 - 1.0) / pow($dd1, 1.5); 112 | $del1 = $temp / ($a1 * $a1); 113 | $ao = $a1 * (1.0 - $del1 * (Predict::tothrd * 0.5 + $del1 * 114 | ($del1 * 1.654320987654321 + 1.0))); 115 | $delo = $temp / ($ao * $ao); 116 | $xnodp = $this->tle->xno / ($delo + 1.0); 117 | 118 | /* Select a deep-space/near-earth ephemeris */ 119 | if (Predict::twopi / $xnodp / Predict::xmnpda >= .15625) { 120 | $this->flags |= Predict_SGPSDP::DEEP_SPACE_EPHEM_FLAG; 121 | } else { 122 | $this->flags &= ~Predict_SGPSDP::DEEP_SPACE_EPHEM_FLAG; 123 | } 124 | } 125 | 126 | /** Initialise satellite data. 127 | * @param sat The satellite to initialise. 128 | * @param qth Optional QTH info, use (0,0) if NULL. 129 | * 130 | * This function calculates the satellite data at t = 0, ie. epoch time 131 | * The function is called automatically by gtk_sat_data_read_sat. 132 | */ 133 | public function sat_data_init_sat(Predict_Sat $sat, Predict_QTH $qth = null) 134 | { 135 | $obs_geodetic = new Predict_Geodetic(); 136 | $obs_set = new Predict_ObsSet(); 137 | $sat_geodetic = new Predict_Geodetic(); 138 | /* double jul_utc, age; */ 139 | 140 | $jul_utc = Predict_Time::Julian_Date_of_Epoch($sat->tle->epoch); // => tsince = 0.0 141 | $sat->jul_epoch = $jul_utc; 142 | 143 | /* initialise observer location */ 144 | if ($qth != null) { 145 | $obs_geodetic->lon = $qth->lon * Predict::de2ra; 146 | $obs_geodetic->lat = $qth->lat * Predict::de2ra; 147 | $obs_geodetic->alt = $qth->alt / 1000.0; 148 | $obs_geodetic->theta = 0; 149 | } 150 | else { 151 | $obs_geodetic->lon = 0.0; 152 | $obs_geodetic->lat = 0.0; 153 | $obs_geodetic->alt = 0.0; 154 | $obs_geodetic->theta = 0; 155 | } 156 | 157 | /* execute computations */ 158 | $sdpsgp = Predict_SGPSDP::getInstance($sat); 159 | if ($sat->flags & Predict_SGPSDP::DEEP_SPACE_EPHEM_FLAG) { 160 | $sdpsgp->SDP4($sat, 0.0); 161 | } else { 162 | $sdpsgp->SGP4($sat, 0.0); 163 | } 164 | 165 | /* scale position and velocity to km and km/sec */ 166 | Predict_Math::Convert_Sat_State($sat->pos, $sat->vel); 167 | 168 | /* get the velocity of the satellite */ 169 | $sat->vel->w = sqrt($sat->vel->x * $sat->vel->x + $sat->vel->y * $sat->vel->y + $sat->vel->z * $sat->vel->z); 170 | $sat->velo = $sat->vel->w; 171 | Predict_SGPObs::Calculate_Obs($jul_utc, $sat->pos, $sat->vel, $obs_geodetic, $obs_set); 172 | Predict_SGPObs::Calculate_LatLonAlt($jul_utc, $sat->pos, $sat_geodetic); 173 | 174 | while ($sat_geodetic->lon < -Predict::pi) { 175 | $sat_geodetic->lon += Predict::twopi; 176 | } 177 | 178 | while ($sat_geodetic->lon > Predict::pi) { 179 | $sat_geodetic->lon -= Predict::twopi; 180 | } 181 | 182 | $sat->az = Predict_Math::Degrees($obs_set->az); 183 | $sat->el = Predict_Math::Degrees($obs_set->el); 184 | $sat->range = $obs_set->range; 185 | $sat->range_rate = $obs_set->range_rate; 186 | $sat->ssplat = Predict_Math::Degrees($sat_geodetic->lat); 187 | $sat->ssplon = Predict_Math::Degrees($sat_geodetic->lon); 188 | $sat->alt = $sat_geodetic->alt; 189 | $sat->ma = Predict_Math::Degrees($sat->phase); 190 | $sat->ma *= 256.0 / 360.0; 191 | $sat->footprint = 2.0 * Predict::xkmper * acos (Predict::xkmper/$sat->pos->w); 192 | $age = 0.0; 193 | $sat->orbit = floor(($sat->tle->xno * Predict::xmnpda / Predict::twopi + 194 | $age * $sat->tle->bstar * Predict::ae) * $age + 195 | $sat->tle->xmo / Predict::twopi) + $sat->tle->revnum - 1; 196 | 197 | /* orbit type */ 198 | $sat->otype = $sat->get_orbit_type($sat); 199 | } 200 | 201 | public function get_orbit_type(Predict_Sat $sat) 202 | { 203 | $orbit = Predict_SGPSDP::ORBIT_TYPE_UNKNOWN; 204 | 205 | if ($this->geostationary($sat)) { 206 | $orbit = Predict_SGPSDP::ORBIT_TYPE_GEO; 207 | } else if ($this->decayed($sat)) { 208 | $orbit = Predict_SGPSDP::ORBIT_TYPE_DECAYED; 209 | } else { 210 | $orbit = Predict_SGPSDP::ORBIT_TYPE_UNKNOWN; 211 | } 212 | 213 | return $orbit; 214 | } 215 | 216 | 217 | /** Determinte whether satellite is in geostationary orbit. 218 | * @author John A. Magliacane, KD2BD 219 | * @param sat Pointer to satellite data. 220 | * @return TRUE if the satellite appears to be in geostationary orbit, 221 | * FALSE otherwise. 222 | * 223 | * A satellite is in geostationary orbit if 224 | * 225 | * fabs (sat.meanmotion - 1.0027) < 0.0002 226 | * 227 | * Note: Appearantly, the mean motion can deviate much more from 1.0027 than 0.0002 228 | */ 229 | public function geostationary(Predict_Sat $sat) 230 | { 231 | if (abs($sat->meanmo - 1.0027) < 0.0002) { 232 | return true; 233 | } else { 234 | return false; 235 | } 236 | } 237 | 238 | 239 | /** Determine whether satellite has decayed. 240 | * @author John A. Magliacane, KD2BD 241 | * @author Alexandru Csete, OZ9AEC 242 | * @param sat Pointer to satellite data. 243 | * @return TRUE if the satellite appears to have decayed, FALSE otherwise. 244 | * @bug Modified version of the predict code but it is not tested. 245 | * 246 | * A satellite is decayed if 247 | * 248 | * satepoch + ((16.666666 - sat.meanmo) / (10.0*fabs(sat.drag))) < "now" 249 | * 250 | */ 251 | public function decayed(Predict_Sat $sat) 252 | { 253 | /* tle.xndt2o/(twopi/xmnpda/xmnpda) is the value before converted the 254 | value matches up with the value in predict 2.2.3 */ 255 | /*** FIXME decayed is treated as a static quantity. 256 | It is time dependent. Also sat->jul_utc is often zero 257 | when this function is called 258 | ***/ 259 | if ($sat->jul_epoch + ((16.666666 - $sat->meanmo) / 260 | (10.0 * abs($sat->tle->xndt2o / (Predict::twopi / Predict::xmnpda / Predict::xmnpda)))) < $sat->jul_utc) { 261 | return true; 262 | } else { 263 | return false; 264 | } 265 | } 266 | 267 | /** 268 | * Experimental attempt at calculating apparent magnitude. Known intrinsic 269 | * magnitudes are listed inside the function for now. 270 | * 271 | * @param float $time The daynum the satellite is calculated for 272 | * @param Predict_QTH $qth The observer location 273 | * 274 | * @return null on failure, float otherwise 275 | */ 276 | public function calculateApparentMagnitude($time, Predict_QTH $qth) 277 | { 278 | // Recorded intrinsic magnitudes and their respective 279 | // illumination and distance from heavens-above.com 280 | static $intrinsicMagnitudes = array( 281 | '25544' => array( 282 | 'mag' => -1.3, 283 | 'illum' => .5, 284 | 'distance' => 1000, 285 | ) 286 | ); 287 | 288 | // Return null if we don't have a record of the intrinsic mag 289 | if (!isset($intrinsicMagnitudes[$this->tle->catnr])) { 290 | return null; 291 | } 292 | $imag = $intrinsicMagnitudes[$this->tle->catnr]; 293 | 294 | // Convert the observer's geodetic info to radians and km so 295 | // we can compare vectors 296 | $observerGeo = new Predict_Geodetic(); 297 | $observerGeo->lat = Predict_Math::Radians($qth->lat); 298 | $observerGeo->lon = Predict_Math::Radians($qth->lon); 299 | $observerGeo->alt = $qth->alt * 1000; 300 | 301 | // Now determine the sun and observer positions 302 | $observerPos = new Predict_Vector(); 303 | $observerVel = new Predict_Vector(); 304 | $solarVector = new Predict_Vector(); 305 | Predict_Solar::Calculate_Solar_Position($time, $solarVector); 306 | Predict_SGPObs::Calculate_User_PosVel($time, $observerGeo, $observerPos, $observerVel); 307 | 308 | // Determine the solar phase and and thus the percent illumination 309 | $observerSatPos = new Predict_Vector(); 310 | Predict_Math::Vec_Sub($this->pos, $observerPos, $observerSatPos); 311 | $phaseAngle = Predict_Math::Degrees(Predict_Math::Angle($solarVector, $observerSatPos)); 312 | $illum = $phaseAngle / 180; 313 | 314 | $illuminationChange = $illum / $imag['illum']; 315 | $inverseSquareOfDistanceChange = pow(($imag['distance'] / $this->range), 2); 316 | $changeInMagnitude = log( 317 | $illuminationChange * $inverseSquareOfDistanceChange, 318 | self::POGSONS_RATIO 319 | ); 320 | 321 | return $imag['mag'] - $changeInMagnitude; 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /xhprof_lib/utils/callgraph_utils.php: -------------------------------------------------------------------------------- 1 | 1, 26 | "gif" => 1, 27 | "png" => 1, 28 | "ps" => 1, 29 | ); 30 | 31 | /** 32 | * Send an HTTP header with the response. You MUST use this function instead 33 | * of header() so that we can debug header issues because they're virtually 34 | * impossible to debug otherwise. If you try to commit header(), SVN will 35 | * reject your commit. 36 | * 37 | * @param string HTTP header name, like 'Location' 38 | * @param string HTTP header value, like 'http://www.example.com/' 39 | * 40 | */ 41 | function xhprof_http_header($name, $value) { 42 | 43 | if (!$name) { 44 | xhprof_error('http_header usage'); 45 | return null; 46 | } 47 | 48 | if (!is_string($value)) { 49 | xhprof_error('http_header value not a string'); 50 | } 51 | 52 | header($name.': '.$value, true); 53 | } 54 | 55 | /** 56 | * Genearte and send MIME header for the output image to client browser. 57 | * 58 | * @author cjiang 59 | */ 60 | function xhprof_generate_mime_header($type, $length) { 61 | switch ($type) { 62 | case 'jpg': 63 | $mime = 'image/jpeg'; 64 | break; 65 | case 'gif': 66 | $mime = 'image/gif'; 67 | break; 68 | case 'png': 69 | $mime = 'image/png'; 70 | break; 71 | case 'ps': 72 | $mime = 'application/postscript'; 73 | default: 74 | $mime = false; 75 | } 76 | 77 | if ($mime) { 78 | xhprof_http_header('Content-type', $mime); 79 | xhprof_http_header('Content-length', (string)$length); 80 | } 81 | } 82 | 83 | /** 84 | * Generate image according to DOT script. This function will spawn a process 85 | * with "dot" command and pipe the "dot_script" to it and pipe out the 86 | * generated image content. 87 | * 88 | * @param dot_script, string, the script for DOT to generate the image. 89 | * @param type, one of the supported image types, see 90 | * $xhprof_legal_image_types. 91 | * @returns, binary content of the generated image on success. empty string on 92 | * failure. 93 | * 94 | * @author cjiang 95 | */ 96 | function xhprof_generate_image_by_dot($dot_script, $type) { 97 | $descriptorspec = array( 98 | // stdin is a pipe that the child will read from 99 | 0 => array("pipe", "r"), 100 | // stdout is a pipe that the child will write to 101 | 1 => array("pipe", "w"), 102 | // stderr is a pipe that the child will write to 103 | 2 => array("pipe", "w") 104 | ); 105 | 106 | $cmd = " dot -T".$type; 107 | 108 | $process = proc_open($cmd, $descriptorspec, $pipes, "/tmp", array()); 109 | if (is_resource($process)) { 110 | fwrite($pipes[0], $dot_script); 111 | fclose($pipes[0]); 112 | 113 | $output = stream_get_contents($pipes[1]); 114 | 115 | $err = stream_get_contents($pipes[2]); 116 | if (!empty($err)) { 117 | print "failed to execute cmd: \"$cmd\". stderr: `$err'\n"; 118 | exit; 119 | } 120 | 121 | fclose($pipes[2]); 122 | fclose($pipes[1]); 123 | proc_close($process); 124 | return $output; 125 | } 126 | print "failed to execute cmd \"$cmd\""; 127 | exit(); 128 | } 129 | 130 | /* 131 | * Get the children list of all nodes. 132 | */ 133 | function xhprof_get_children_table($raw_data) { 134 | $children_table = array(); 135 | foreach ($raw_data as $parent_child => $info) { 136 | list($parent, $child) = xhprof_parse_parent_child($parent_child); 137 | if (!isset($children_table[$parent])) { 138 | $children_table[$parent] = array($child); 139 | } else { 140 | $children_table[$parent][] = $child; 141 | } 142 | } 143 | return $children_table; 144 | } 145 | 146 | /** 147 | * Generate DOT script from the given raw phprof data. 148 | * 149 | * @param raw_data, phprof profile data. 150 | * @param threshold, float, the threshold value [0,1). The functions in the 151 | * raw_data whose exclusive wall times ratio are below the 152 | * threshold will be filtered out and won't apprear in the 153 | * generated image. 154 | * @param page, string(optional), the root node name. This can be used to 155 | * replace the 'main()' as the root node. 156 | * @param func, string, the focus function. 157 | * @param critical_path, bool, whether or not to display critical path with 158 | * bold lines. 159 | * @returns, string, the DOT script to generate image. 160 | * 161 | * @author cjiang 162 | */ 163 | function xhprof_generate_dot_script($raw_data, $threshold, $source, $page, 164 | $func, $critical_path, $right=null, 165 | $left=null) { 166 | 167 | $max_width = 5; 168 | $max_height = 3.5; 169 | $max_fontsize = 35; 170 | $max_sizing_ratio = 20; 171 | 172 | $totals; 173 | 174 | if ($left === null) { 175 | // init_metrics($raw_data, null, null); 176 | } 177 | $sym_table = xhprof_compute_flat_info($raw_data, $totals); 178 | 179 | if ($critical_path) { 180 | $children_table = xhprof_get_children_table($raw_data); 181 | $node = "main()"; 182 | $path = array(); 183 | $path_edges = array(); 184 | $visited = array(); 185 | while ($node) { 186 | $visited[$node] = true; 187 | if (isset($children_table[$node])) { 188 | $max_child = null; 189 | foreach ($children_table[$node] as $child) { 190 | 191 | if (isset($visited[$child])) { 192 | continue; 193 | } 194 | if ($max_child === null || 195 | abs($raw_data[xhprof_build_parent_child_key($node, 196 | $child)]["wt"]) > 197 | abs($raw_data[xhprof_build_parent_child_key($node, 198 | $max_child)]["wt"])) { 199 | $max_child = $child; 200 | } 201 | } 202 | if ($max_child !== null) { 203 | $path[$max_child] = true; 204 | $path_edges[xhprof_build_parent_child_key($node, $max_child)] = true; 205 | } 206 | $node = $max_child; 207 | } else { 208 | $node = null; 209 | } 210 | } 211 | } 212 | 213 | // if it is a benchmark callgraph, we make the benchmarked function the root. 214 | if ($source == "bm" && array_key_exists("main()", $sym_table)) { 215 | $total_times = $sym_table["main()"]["ct"]; 216 | $remove_funcs = array("main()", 217 | "hotprofiler_disable", 218 | "call_user_func_array", 219 | "xhprof_disable"); 220 | 221 | foreach ($remove_funcs as $cur_del_func) { 222 | if (array_key_exists($cur_del_func, $sym_table) && 223 | $sym_table[$cur_del_func]["ct"] == $total_times) { 224 | unset($sym_table[$cur_del_func]); 225 | } 226 | } 227 | } 228 | 229 | // use the function to filter out irrelevant functions. 230 | if (!empty($func)) { 231 | $interested_funcs = array(); 232 | foreach ($raw_data as $parent_child => $info) { 233 | list($parent, $child) = xhprof_parse_parent_child($parent_child); 234 | if ($parent == $func || $child == $func) { 235 | $interested_funcs[$parent] = 1; 236 | $interested_funcs[$child] = 1; 237 | } 238 | } 239 | foreach ($sym_table as $symbol => $info) { 240 | if (!array_key_exists($symbol, $interested_funcs)) { 241 | unset($sym_table[$symbol]); 242 | } 243 | } 244 | } 245 | 246 | $result = "digraph call_graph {\n"; 247 | 248 | // Filter out functions whose exclusive time ratio is below threshold, and 249 | // also assign a unique integer id for each function to be generated. In the 250 | // meantime, find the function with the most exclusive time (potentially the 251 | // performance bottleneck). 252 | $cur_id = 0; $max_wt = 0; 253 | foreach ($sym_table as $symbol => $info) { 254 | if (empty($func) && abs($info["wt"] / $totals["wt"]) < $threshold) { 255 | unset($sym_table[$symbol]); 256 | continue; 257 | } 258 | if ($max_wt == 0 || $max_wt < abs($info["excl_wt"])) { 259 | $max_wt = abs($info["excl_wt"]); 260 | } 261 | $sym_table[$symbol]["id"] = $cur_id; 262 | $cur_id ++; 263 | } 264 | 265 | // Generate all nodes' information. 266 | foreach ($sym_table as $symbol => $info) { 267 | if ($info["excl_wt"] == 0) { 268 | $sizing_factor = $max_sizing_ratio; 269 | } else { 270 | $sizing_factor = $max_wt / abs($info["excl_wt"]) ; 271 | if ($sizing_factor > $max_sizing_ratio) { 272 | $sizing_factor = $max_sizing_ratio; 273 | } 274 | } 275 | $fillcolor = (($sizing_factor < 1.5) ? 276 | ", style=filled, fillcolor=red" : ""); 277 | 278 | if ($critical_path) { 279 | // highlight nodes along critical path. 280 | if (!$fillcolor && array_key_exists($symbol, $path)) { 281 | $fillcolor = ", style=filled, fillcolor=yellow"; 282 | } 283 | } 284 | 285 | $fontsize = ", fontsize=" 286 | .(int)($max_fontsize / (($sizing_factor - 1) / 10 + 1)); 287 | 288 | $width = ", width=".sprintf("%.1f", $max_width / $sizing_factor); 289 | $height = ", height=".sprintf("%.1f", $max_height / $sizing_factor); 290 | 291 | if ($symbol == "main()") { 292 | $shape = "octagon"; 293 | $name = "Total: ".($totals["wt"] / 1000.0)." ms\\n"; 294 | $name .= addslashes(isset($page) ? $page : $symbol); 295 | } else { 296 | $shape = "box"; 297 | $name = addslashes($symbol)."\\nInc: ". sprintf("%.3f",$info["wt"] / 1000) . 298 | " ms (" . sprintf("%.1f%%", 100 * $info["wt"] / $totals["wt"]).")"; 299 | } 300 | if ($left === null) { 301 | $label = ", label=\"".$name."\\nExcl: " 302 | .(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms (" 303 | .sprintf("%.1f%%", 100 * $info["excl_wt"] / $totals["wt"]) 304 | . ")\\n".$info["ct"]." total calls\""; 305 | } else { 306 | if (isset($left[$symbol]) && isset($right[$symbol])) { 307 | $label = ", label=\"".addslashes($symbol). 308 | "\\nInc: ".(sprintf("%.3f",$left[$symbol]["wt"] / 1000.0)) 309 | ." ms - " 310 | .(sprintf("%.3f",$right[$symbol]["wt"] / 1000.0))." ms = " 311 | .(sprintf("%.3f",$info["wt"] / 1000.0))." ms". 312 | "\\nExcl: " 313 | .(sprintf("%.3f",$left[$symbol]["excl_wt"] / 1000.0)) 314 | ." ms - ".(sprintf("%.3f",$right[$symbol]["excl_wt"] / 1000.0)) 315 | ." ms = ".(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms". 316 | "\\nCalls: ".(sprintf("%.3f",$left[$symbol]["ct"]))." - " 317 | .(sprintf("%.3f",$right[$symbol]["ct"]))." = " 318 | .(sprintf("%.3f",$info["ct"]))."\""; 319 | } else if (isset($left[$symbol])) { 320 | $label = ", label=\"".addslashes($symbol). 321 | "\\nInc: ".(sprintf("%.3f",$left[$symbol]["wt"] / 1000.0)) 322 | ." ms - 0 ms = ".(sprintf("%.3f",$info["wt"] / 1000.0)) 323 | ." ms"."\\nExcl: " 324 | .(sprintf("%.3f",$left[$symbol]["excl_wt"] / 1000.0)) 325 | ." ms - 0 ms = " 326 | .(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms". 327 | "\\nCalls: ".(sprintf("%.3f",$left[$symbol]["ct"]))." - 0 = " 328 | .(sprintf("%.3f",$info["ct"]))."\""; 329 | } else { 330 | $label = ", label=\"".addslashes($symbol). 331 | "\\nInc: 0 ms - " 332 | .(sprintf("%.3f",$right[$symbol]["wt"] / 1000.0)) 333 | ." ms = ".(sprintf("%.3f",$info["wt"] / 1000.0))." ms". 334 | "\\nExcl: 0 ms - " 335 | .(sprintf("%.3f",$right[$symbol]["excl_wt"] / 1000.0)) 336 | ." ms = ".(sprintf("%.3f",$info["excl_wt"] / 1000.0))." ms". 337 | "\\nCalls: 0 - ".(sprintf("%.3f",$right[$symbol]["ct"])) 338 | ." = ".(sprintf("%.3f",$info["ct"]))."\""; 339 | } 340 | } 341 | $result .= "N" . $sym_table[$symbol]["id"]; 342 | $result .= "[shape=$shape ".$label.$width 343 | .$height.$fontsize.$fillcolor."];\n"; 344 | } 345 | 346 | // Generate all the edges' information. 347 | foreach ($raw_data as $parent_child => $info) { 348 | list($parent, $child) = xhprof_parse_parent_child($parent_child); 349 | 350 | if (isset($sym_table[$parent]) && isset($sym_table[$child]) && 351 | (empty($func) || 352 | (!empty($func) && ($parent == $func || $child == $func)))) { 353 | 354 | $label = $info["ct"] == 1 ? $info["ct"]." call" : $info["ct"]." calls"; 355 | 356 | $headlabel = $sym_table[$child]["wt"] > 0 ? 357 | sprintf("%.1f%%", 100 * $info["wt"] 358 | / $sym_table[$child]["wt"]) 359 | : "0.0%"; 360 | 361 | $taillabel = ($sym_table[$parent]["wt"] > 0) ? 362 | sprintf("%.1f%%", 363 | 100 * $info["wt"] / 364 | ($sym_table[$parent]["wt"] - $sym_table["$parent"]["excl_wt"])) 365 | : "0.0%"; 366 | 367 | $linewidth = 1; 368 | $arrow_size = 1; 369 | 370 | if ($critical_path && 371 | isset($path_edges[xhprof_build_parent_child_key($parent, $child)])) { 372 | $linewidth = 10; $arrow_size = 2; 373 | } 374 | 375 | $result .= "N" . $sym_table[$parent]["id"] . " -> N" 376 | . $sym_table[$child]["id"]; 377 | $result .= "[arrowsize=$arrow_size, style=\"setlinewidth($linewidth)\"," 378 | ." label=\"" 379 | .$label."\", headlabel=\"".$headlabel 380 | ."\", taillabel=\"".$taillabel."\" ]"; 381 | $result .= ";\n"; 382 | 383 | } 384 | } 385 | $result = $result . "\n}"; 386 | 387 | return $result; 388 | } 389 | 390 | function xhprof_render_diff_image($xhprof_runs_impl, $run1, $run2, 391 | $type, $threshold, $source) { 392 | $total1; 393 | $total2; 394 | 395 | $raw_data1 = $xhprof_runs_impl->get_run($run1, $source, $desc_unused); 396 | $raw_data2 = $xhprof_runs_impl->get_run($run2, $source, $desc_unused); 397 | 398 | // init_metrics($raw_data1, null, null); 399 | $children_table1 = xhprof_get_children_table($raw_data1); 400 | $children_table2 = xhprof_get_children_table($raw_data2); 401 | $symbol_tab1 = xhprof_compute_flat_info($raw_data1, $total1); 402 | $symbol_tab2 = xhprof_compute_flat_info($raw_data2, $total2); 403 | $run_delta = xhprof_compute_diff($raw_data1, $raw_data2); 404 | $script = xhprof_generate_dot_script($run_delta, $threshold, $source, 405 | null, null, true, 406 | $symbol_tab1, $symbol_tab2); 407 | $content = xhprof_generate_image_by_dot($script, $type); 408 | 409 | xhprof_generate_mime_header($type, strlen($content)); 410 | echo $content; 411 | } 412 | 413 | /** 414 | * Generate image content from phprof run id. 415 | * 416 | * @param object $xhprof_runs_impl An object that implements 417 | * the iXHProfRuns interface 418 | * @param run_id, integer, the unique id for the phprof run, this is the 419 | * primary key for phprof database table. 420 | * @param type, string, one of the supported image types. See also 421 | * $xhprof_legal_image_types. 422 | * @param threshold, float, the threshold value [0,1). The functions in the 423 | * raw_data whose exclusive wall times ratio are below the 424 | * threshold will be filtered out and won't apprear in the 425 | * generated image. 426 | * @param func, string, the focus function. 427 | * @returns, string, the DOT script to generate image. 428 | * 429 | * @author cjiang 430 | */ 431 | function xhprof_get_content_by_run($xhprof_runs_impl, $run_id, $type, 432 | $threshold, $func, $source, 433 | $critical_path) { 434 | if (!$run_id) 435 | return ""; 436 | 437 | $raw_data = $xhprof_runs_impl->get_run($run_id, $source, $description); 438 | if (!$raw_data) { 439 | xhprof_error("Raw data is empty"); 440 | return ""; 441 | } 442 | 443 | $script = xhprof_generate_dot_script($raw_data, $threshold, $source, 444 | $description, $func, $critical_path); 445 | 446 | $content = xhprof_generate_image_by_dot($script, $type); 447 | return $content; 448 | } 449 | 450 | /** 451 | * Generate image from phprof run id and send it to client. 452 | * 453 | * @param object $xhprof_runs_impl An object that implements 454 | * the iXHProfRuns interface 455 | * @param run_id, integer, the unique id for the phprof run, this is the 456 | * primary key for phprof database table. 457 | * @param type, string, one of the supported image types. See also 458 | * $xhprof_legal_image_types. 459 | * @param threshold, float, the threshold value [0,1). The functions in the 460 | * raw_data whose exclusive wall times ratio are below the 461 | * threshold will be filtered out and won't apprear in the 462 | * generated image. 463 | * @param func, string, the focus function. 464 | * @param bool, does this run correspond to a PHProfLive run or a dev run? 465 | * @author cjiang 466 | */ 467 | function xhprof_render_image($xhprof_runs_impl, $run_id, $type, $threshold, 468 | $func, $source, $critical_path) { 469 | 470 | $content = xhprof_get_content_by_run($xhprof_runs_impl, $run_id, $type, 471 | $threshold, 472 | $func, $source, $critical_path); 473 | if (!$content) { 474 | print "Error: either we can not find profile data for run_id ".$run_id 475 | ." or the threshold ".$threshold." is too small or you do not" 476 | ." have 'dot' image generation utility installed."; 477 | exit(); 478 | } 479 | 480 | xhprof_generate_mime_header($type, strlen($content)); 481 | echo $content; 482 | } 483 | -------------------------------------------------------------------------------- /xhprof_lib/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 = $(" 92 | 93 | 94 |
  • XHProf Overview

    95 | 96 |

    XHProf provides: 97 | 98 |

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

      Provides function-level summary information such 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 |
  • 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 |
  • 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 |
  • 526 | 527 |
  • 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 |
  • 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 |
  • 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 |

  • Additional Features

  • 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 |
  • Dependencies

  • 800 | 801 | 814 |
  • 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 |

  • 822 | 823 | 824 | 825 | 826 | 827 | -------------------------------------------------------------------------------- /tests/Table.php: -------------------------------------------------------------------------------- 1 | 35 | * @author Jan Schneider 36 | * @copyright 2002-2005 Richard Heyes 37 | * @copyright 2006-2008 Jan Schneider 38 | * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) 39 | * @version CVS: $Id: Table.php 268934 2008-11-13 10:35:34Z yunosh $ 40 | * @link http://pear.php.net/package/Console_Table 41 | */ 42 | 43 | define('CONSOLE_TABLE_HORIZONTAL_RULE', 1); 44 | define('CONSOLE_TABLE_ALIGN_LEFT', -1); 45 | define('CONSOLE_TABLE_ALIGN_CENTER', 0); 46 | define('CONSOLE_TABLE_ALIGN_RIGHT', 1); 47 | define('CONSOLE_TABLE_BORDER_ASCII', -1); 48 | 49 | /** 50 | * The main class. 51 | * 52 | * @category Console 53 | * @package Console_Table 54 | * @author Jan Schneider 55 | * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) 56 | * @link http://pear.php.net/package/Console_Table 57 | */ 58 | class Console_Table 59 | { 60 | /** 61 | * The table headers. 62 | * 63 | * @var array 64 | */ 65 | var $_headers = array(); 66 | 67 | /** 68 | * The data of the table. 69 | * 70 | * @var array 71 | */ 72 | var $_data = array(); 73 | 74 | /** 75 | * The maximum number of columns in a row. 76 | * 77 | * @var integer 78 | */ 79 | var $_max_cols = 0; 80 | 81 | /** 82 | * The maximum number of rows in the table. 83 | * 84 | * @var integer 85 | */ 86 | var $_max_rows = 0; 87 | 88 | /** 89 | * Lengths of the columns, calculated when rows are added to the table. 90 | * 91 | * @var array 92 | */ 93 | var $_cell_lengths = array(); 94 | 95 | /** 96 | * Heights of the rows. 97 | * 98 | * @var array 99 | */ 100 | var $_row_heights = array(); 101 | 102 | /** 103 | * How many spaces to use to pad the table. 104 | * 105 | * @var integer 106 | */ 107 | var $_padding = 1; 108 | 109 | /** 110 | * Column filters. 111 | * 112 | * @var array 113 | */ 114 | var $_filters = array(); 115 | 116 | /** 117 | * Columns to calculate totals for. 118 | * 119 | * @var array 120 | */ 121 | var $_calculateTotals; 122 | 123 | /** 124 | * Alignment of the columns. 125 | * 126 | * @var array 127 | */ 128 | var $_col_align = array(); 129 | 130 | /** 131 | * Default alignment of columns. 132 | * 133 | * @var integer 134 | */ 135 | var $_defaultAlign; 136 | 137 | /** 138 | * Character set of the data. 139 | * 140 | * @var string 141 | */ 142 | var $_charset = 'utf-8'; 143 | 144 | /** 145 | * Border character. 146 | * 147 | * @var string 148 | */ 149 | var $_border = CONSOLE_TABLE_BORDER_ASCII; 150 | 151 | /** 152 | * Whether the data has ANSI colors. 153 | * 154 | * @var boolean 155 | */ 156 | var $_ansiColor = false; 157 | 158 | /** 159 | * Constructor. 160 | * 161 | * @param integer $align Default alignment. One of 162 | * CONSOLE_TABLE_ALIGN_LEFT, 163 | * CONSOLE_TABLE_ALIGN_CENTER or 164 | * CONSOLE_TABLE_ALIGN_RIGHT. 165 | * @param string $border The character used for table borders or 166 | * CONSOLE_TABLE_BORDER_ASCII. 167 | * @param integer $padding How many spaces to use to pad the table. 168 | * @param string $charset A charset supported by the mbstring PHP 169 | * extension. 170 | * @param boolean $color Whether the data contains ansi color codes. 171 | */ 172 | function Console_Table($align = CONSOLE_TABLE_ALIGN_LEFT, 173 | $border = CONSOLE_TABLE_BORDER_ASCII, $padding = 1, 174 | $charset = null, $color = false) 175 | { 176 | $this->_defaultAlign = $align; 177 | $this->_border = $border; 178 | $this->_padding = $padding; 179 | $this->_ansiColor = $color; 180 | if ($this->_ansiColor) { 181 | include_once 'Console/Color.php'; 182 | } 183 | if (!empty($charset)) { 184 | $this->setCharset($charset); 185 | } 186 | } 187 | 188 | /** 189 | * Converts an array to a table. 190 | * 191 | * @param array $headers Headers for the table. 192 | * @param array $data A two dimensional array with the table 193 | * data. 194 | * @param boolean $returnObject Whether to return the Console_Table object 195 | * instead of the rendered table. 196 | * 197 | * @static 198 | * 199 | * @return Console_Table|string A Console_Table object or the generated 200 | * table. 201 | */ 202 | function fromArray($headers, $data, $returnObject = false) 203 | { 204 | if (!is_array($headers) || !is_array($data)) { 205 | return false; 206 | } 207 | 208 | $table = new Console_Table(); 209 | $table->setHeaders($headers); 210 | 211 | foreach ($data as $row) { 212 | $table->addRow($row); 213 | } 214 | 215 | return $returnObject ? $table : $table->getTable(); 216 | } 217 | 218 | /** 219 | * Adds a filter to a column. 220 | * 221 | * Filters are standard PHP callbacks which are run on the data before 222 | * table generation is performed. Filters are applied in the order they 223 | * are added. The callback function must accept a single argument, which 224 | * is a single table cell. 225 | * 226 | * @param integer $col Column to apply filter to. 227 | * @param mixed &$callback PHP callback to apply. 228 | * 229 | * @return void 230 | */ 231 | function addFilter($col, &$callback) 232 | { 233 | $this->_filters[] = array($col, &$callback); 234 | } 235 | 236 | /** 237 | * Sets the charset of the provided table data. 238 | * 239 | * @param string $charset A charset supported by the mbstring PHP 240 | * extension. 241 | * 242 | * @return void 243 | */ 244 | function setCharset($charset) 245 | { 246 | $locale = setlocale(LC_CTYPE, 0); 247 | setlocale(LC_CTYPE, 'en_US'); 248 | $this->_charset = strtolower($charset); 249 | setlocale(LC_CTYPE, $locale); 250 | } 251 | 252 | /** 253 | * Sets the alignment for the columns. 254 | * 255 | * @param integer $col_id The column number. 256 | * @param integer $align Alignment to set for this column. One of 257 | * CONSOLE_TABLE_ALIGN_LEFT 258 | * CONSOLE_TABLE_ALIGN_CENTER 259 | * CONSOLE_TABLE_ALIGN_RIGHT. 260 | * 261 | * @return void 262 | */ 263 | function setAlign($col_id, $align = CONSOLE_TABLE_ALIGN_LEFT) 264 | { 265 | switch ($align) { 266 | case CONSOLE_TABLE_ALIGN_CENTER: 267 | $pad = STR_PAD_BOTH; 268 | break; 269 | case CONSOLE_TABLE_ALIGN_RIGHT: 270 | $pad = STR_PAD_LEFT; 271 | break; 272 | default: 273 | $pad = STR_PAD_RIGHT; 274 | break; 275 | } 276 | $this->_col_align[$col_id] = $pad; 277 | } 278 | 279 | /** 280 | * Specifies which columns are to have totals calculated for them and 281 | * added as a new row at the bottom. 282 | * 283 | * @param array $cols Array of column numbers (starting with 0). 284 | * 285 | * @return void 286 | */ 287 | function calculateTotalsFor($cols) 288 | { 289 | $this->_calculateTotals = $cols; 290 | } 291 | 292 | /** 293 | * Sets the headers for the columns. 294 | * 295 | * @param array $headers The column headers. 296 | * 297 | * @return void 298 | */ 299 | function setHeaders($headers) 300 | { 301 | $this->_headers = array(array_values($headers)); 302 | $this->_updateRowsCols($headers); 303 | } 304 | 305 | /** 306 | * Adds a row to the table. 307 | * 308 | * @param array $row The row data to add. 309 | * @param boolean $append Whether to append or prepend the row. 310 | * 311 | * @return void 312 | */ 313 | function addRow($row, $append = true) 314 | { 315 | if ($append) { 316 | $this->_data[] = array_values($row); 317 | } else { 318 | array_unshift($this->_data, array_values($row)); 319 | } 320 | 321 | $this->_updateRowsCols($row); 322 | } 323 | 324 | /** 325 | * Inserts a row after a given row number in the table. 326 | * 327 | * If $row_id is not given it will prepend the row. 328 | * 329 | * @param array $row The data to insert. 330 | * @param integer $row_id Row number to insert before. 331 | * 332 | * @return void 333 | */ 334 | function insertRow($row, $row_id = 0) 335 | { 336 | array_splice($this->_data, $row_id, 0, array($row)); 337 | 338 | $this->_updateRowsCols($row); 339 | } 340 | 341 | /** 342 | * Adds a column to the table. 343 | * 344 | * @param array $col_data The data of the column. 345 | * @param integer $col_id The column index to populate. 346 | * @param integer $row_id If starting row is not zero, specify it here. 347 | * 348 | * @return void 349 | */ 350 | function addCol($col_data, $col_id = 0, $row_id = 0) 351 | { 352 | foreach ($col_data as $col_cell) { 353 | $this->_data[$row_id++][$col_id] = $col_cell; 354 | } 355 | 356 | $this->_updateRowsCols(); 357 | $this->_max_cols = max($this->_max_cols, $col_id + 1); 358 | } 359 | 360 | /** 361 | * Adds data to the table. 362 | * 363 | * @param array $data A two dimensional array with the table data. 364 | * @param integer $col_id Starting column number. 365 | * @param integer $row_id Starting row number. 366 | * 367 | * @return void 368 | */ 369 | function addData($data, $col_id = 0, $row_id = 0) 370 | { 371 | foreach ($data as $row) { 372 | if ($row === CONSOLE_TABLE_HORIZONTAL_RULE) { 373 | $this->_data[$row_id] = CONSOLE_TABLE_HORIZONTAL_RULE; 374 | $row_id++; 375 | continue; 376 | } 377 | $starting_col = $col_id; 378 | foreach ($row as $cell) { 379 | $this->_data[$row_id][$starting_col++] = $cell; 380 | } 381 | $this->_updateRowsCols(); 382 | $this->_max_cols = max($this->_max_cols, $starting_col); 383 | $row_id++; 384 | } 385 | } 386 | 387 | /** 388 | * Adds a horizontal seperator to the table. 389 | * 390 | * @return void 391 | */ 392 | function addSeparator() 393 | { 394 | $this->_data[] = CONSOLE_TABLE_HORIZONTAL_RULE; 395 | } 396 | 397 | /** 398 | * Returns the generated table. 399 | * 400 | * @return string The generated table. 401 | */ 402 | function getTable() 403 | { 404 | $this->_applyFilters(); 405 | $this->_calculateTotals(); 406 | $this->_validateTable(); 407 | 408 | return $this->_buildTable(); 409 | } 410 | 411 | /** 412 | * Calculates totals for columns. 413 | * 414 | * @return void 415 | */ 416 | function _calculateTotals() 417 | { 418 | if (empty($this->_calculateTotals)) { 419 | return; 420 | } 421 | 422 | $this->addSeparator(); 423 | 424 | $totals = array(); 425 | foreach ($this->_data as $row) { 426 | if (is_array($row)) { 427 | foreach ($this->_calculateTotals as $columnID) { 428 | $totals[$columnID] += $row[$columnID]; 429 | } 430 | } 431 | } 432 | 433 | $this->_data[] = $totals; 434 | $this->_updateRowsCols(); 435 | } 436 | 437 | /** 438 | * Applies any column filters to the data. 439 | * 440 | * @return void 441 | */ 442 | function _applyFilters() 443 | { 444 | if (empty($this->_filters)) { 445 | return; 446 | } 447 | 448 | foreach ($this->_filters as $filter) { 449 | $column = $filter[0]; 450 | $callback = $filter[1]; 451 | 452 | foreach ($this->_data as $row_id => $row_data) { 453 | if ($row_data !== CONSOLE_TABLE_HORIZONTAL_RULE) { 454 | $this->_data[$row_id][$column] = 455 | call_user_func($callback, $row_data[$column]); 456 | } 457 | } 458 | } 459 | } 460 | 461 | /** 462 | * Ensures that column and row counts are correct. 463 | * 464 | * @return void 465 | */ 466 | function _validateTable() 467 | { 468 | if (!empty($this->_headers)) { 469 | $this->_calculateRowHeight(-1, $this->_headers[0]); 470 | } 471 | 472 | for ($i = 0; $i < $this->_max_rows; $i++) { 473 | for ($j = 0; $j < $this->_max_cols; $j++) { 474 | if (!isset($this->_data[$i][$j]) && 475 | (!isset($this->_data[$i]) || 476 | $this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE)) { 477 | $this->_data[$i][$j] = ''; 478 | } 479 | 480 | } 481 | $this->_calculateRowHeight($i, $this->_data[$i]); 482 | 483 | if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE) { 484 | ksort($this->_data[$i]); 485 | } 486 | 487 | } 488 | 489 | $this->_splitMultilineRows(); 490 | 491 | // Update cell lengths. 492 | for ($i = 0; $i < count($this->_headers); $i++) { 493 | $this->_calculateCellLengths($this->_headers[$i]); 494 | } 495 | for ($i = 0; $i < $this->_max_rows; $i++) { 496 | $this->_calculateCellLengths($this->_data[$i]); 497 | } 498 | 499 | ksort($this->_data); 500 | } 501 | 502 | /** 503 | * Splits multiline rows into many smaller one-line rows. 504 | * 505 | * @return void 506 | */ 507 | function _splitMultilineRows() 508 | { 509 | ksort($this->_data); 510 | $sections = array(&$this->_headers, &$this->_data); 511 | $max_rows = array(count($this->_headers), $this->_max_rows); 512 | $row_height_offset = array(-1, 0); 513 | 514 | for ($s = 0; $s <= 1; $s++) { 515 | $inserted = 0; 516 | $new_data = $sections[$s]; 517 | 518 | for ($i = 0; $i < $max_rows[$s]; $i++) { 519 | // Process only rows that have many lines. 520 | $height = $this->_row_heights[$i + $row_height_offset[$s]]; 521 | if ($height > 1) { 522 | // Split column data into one-liners. 523 | $split = array(); 524 | for ($j = 0; $j < $this->_max_cols; $j++) { 525 | $split[$j] = preg_split('/\r?\n|\r/', 526 | $sections[$s][$i][$j]); 527 | } 528 | 529 | $new_rows = array(); 530 | // Construct new 'virtual' rows - insert empty strings for 531 | // columns that have less lines that the highest one. 532 | for ($i2 = 0; $i2 < $height; $i2++) { 533 | for ($j = 0; $j < $this->_max_cols; $j++) { 534 | $new_rows[$i2][$j] = !isset($split[$j][$i2]) 535 | ? '' 536 | : $split[$j][$i2]; 537 | } 538 | } 539 | 540 | // Replace current row with smaller rows. $inserted is 541 | // used to take account of bigger array because of already 542 | // inserted rows. 543 | array_splice($new_data, $i + $inserted, 1, $new_rows); 544 | $inserted += count($new_rows) - 1; 545 | } 546 | } 547 | 548 | // Has the data been modified? 549 | if ($inserted > 0) { 550 | $sections[$s] = $new_data; 551 | $this->_updateRowsCols(); 552 | } 553 | } 554 | } 555 | 556 | /** 557 | * Builds the table. 558 | * 559 | * @return string The generated table string. 560 | */ 561 | function _buildTable() 562 | { 563 | if (!count($this->_data)) { 564 | return ''; 565 | } 566 | 567 | $rule = $this->_border == CONSOLE_TABLE_BORDER_ASCII 568 | ? '|' 569 | : $this->_border; 570 | $separator = $this->_getSeparator(); 571 | 572 | $return = array(); 573 | for ($i = 0; $i < count($this->_data); $i++) { 574 | for ($j = 0; $j < count($this->_data[$i]); $j++) { 575 | if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE && 576 | $this->_strlen($this->_data[$i][$j]) < 577 | $this->_cell_lengths[$j]) { 578 | $this->_data[$i][$j] = $this->_strpad($this->_data[$i][$j], 579 | $this->_cell_lengths[$j], 580 | ' ', 581 | $this->_col_align[$j]); 582 | } 583 | } 584 | 585 | if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE) { 586 | $row_begin = $rule . str_repeat(' ', $this->_padding); 587 | $row_end = str_repeat(' ', $this->_padding) . $rule; 588 | $implode_char = str_repeat(' ', $this->_padding) . $rule 589 | . str_repeat(' ', $this->_padding); 590 | $return[] = $row_begin 591 | . implode($implode_char, $this->_data[$i]) . $row_end; 592 | } elseif (!empty($separator)) { 593 | $return[] = $separator; 594 | } 595 | 596 | } 597 | 598 | $return = implode("\r\n", $return); 599 | if (!empty($separator)) { 600 | $return = $separator . "\r\n" . $return . "\r\n" . $separator; 601 | } 602 | $return .= "\r\n"; 603 | 604 | if (!empty($this->_headers)) { 605 | $return = $this->_getHeaderLine() . "\r\n" . $return; 606 | } 607 | 608 | return $return; 609 | } 610 | 611 | /** 612 | * Creates a horizontal separator for header separation and table 613 | * start/end etc. 614 | * 615 | * @return string The horizontal separator. 616 | */ 617 | function _getSeparator() 618 | { 619 | if (!$this->_border) { 620 | return; 621 | } 622 | 623 | if ($this->_border == CONSOLE_TABLE_BORDER_ASCII) { 624 | $rule = '-'; 625 | $sect = '+'; 626 | } else { 627 | $rule = $sect = $this->_border; 628 | } 629 | 630 | $return = array(); 631 | foreach ($this->_cell_lengths as $cl) { 632 | $return[] = str_repeat($rule, $cl); 633 | } 634 | 635 | $row_begin = $sect . str_repeat($rule, $this->_padding); 636 | $row_end = str_repeat($rule, $this->_padding) . $sect; 637 | $implode_char = str_repeat($rule, $this->_padding) . $sect 638 | . str_repeat($rule, $this->_padding); 639 | 640 | return $row_begin . implode($implode_char, $return) . $row_end; 641 | } 642 | 643 | /** 644 | * Returns the header line for the table. 645 | * 646 | * @return string The header line of the table. 647 | */ 648 | function _getHeaderLine() 649 | { 650 | // Make sure column count is correct 651 | for ($j = 0; $j < count($this->_headers); $j++) { 652 | for ($i = 0; $i < $this->_max_cols; $i++) { 653 | if (!isset($this->_headers[$j][$i])) { 654 | $this->_headers[$j][$i] = ''; 655 | } 656 | } 657 | } 658 | 659 | for ($j = 0; $j < count($this->_headers); $j++) { 660 | for ($i = 0; $i < count($this->_headers[$j]); $i++) { 661 | if ($this->_strlen($this->_headers[$j][$i]) < 662 | $this->_cell_lengths[$i]) { 663 | $this->_headers[$j][$i] = 664 | $this->_strpad($this->_headers[$j][$i], 665 | $this->_cell_lengths[$i], 666 | ' ', 667 | $this->_col_align[$i]); 668 | } 669 | } 670 | } 671 | 672 | $rule = $this->_border == CONSOLE_TABLE_BORDER_ASCII 673 | ? '|' 674 | : $this->_border; 675 | $row_begin = $rule . str_repeat(' ', $this->_padding); 676 | $row_end = str_repeat(' ', $this->_padding) . $rule; 677 | $implode_char = str_repeat(' ', $this->_padding) . $rule 678 | . str_repeat(' ', $this->_padding); 679 | 680 | $separator = $this->_getSeparator(); 681 | if (!empty($separator)) { 682 | $return[] = $separator; 683 | } 684 | for ($j = 0; $j < count($this->_headers); $j++) { 685 | $return[] = $row_begin 686 | . implode($implode_char, $this->_headers[$j]) . $row_end; 687 | } 688 | 689 | return implode("\r\n", $return); 690 | } 691 | 692 | /** 693 | * Updates values for maximum columns and rows. 694 | * 695 | * @param array $rowdata Data array of a single row. 696 | * 697 | * @return void 698 | */ 699 | function _updateRowsCols($rowdata = array()) 700 | { 701 | // Update maximum columns. 702 | $this->_max_cols = max($this->_max_cols, count($rowdata)); 703 | 704 | // Update maximum rows. 705 | ksort($this->_data); 706 | $keys = array_keys($this->_data); 707 | $this->_max_rows = end($keys) + 1; 708 | 709 | switch ($this->_defaultAlign) { 710 | case CONSOLE_TABLE_ALIGN_CENTER: 711 | $pad = STR_PAD_BOTH; 712 | break; 713 | case CONSOLE_TABLE_ALIGN_RIGHT: 714 | $pad = STR_PAD_LEFT; 715 | break; 716 | default: 717 | $pad = STR_PAD_RIGHT; 718 | break; 719 | } 720 | 721 | // Set default column alignments 722 | for ($i = count($this->_col_align); $i < $this->_max_cols; $i++) { 723 | $this->_col_align[$i] = $pad; 724 | } 725 | } 726 | 727 | /** 728 | * Calculates the maximum length for each column of a row. 729 | * 730 | * @param array $row The row data. 731 | * 732 | * @return void 733 | */ 734 | function _calculateCellLengths($row) 735 | { 736 | for ($i = 0; $i < count($row); $i++) { 737 | if (!isset($this->_cell_lengths[$i])) { 738 | $this->_cell_lengths[$i] = 0; 739 | } 740 | $this->_cell_lengths[$i] = max($this->_cell_lengths[$i], 741 | $this->_strlen($row[$i])); 742 | } 743 | } 744 | 745 | /** 746 | * Calculates the maximum height for all columns of a row. 747 | * 748 | * @param integer $row_number The row number. 749 | * @param array $row The row data. 750 | * 751 | * @return void 752 | */ 753 | function _calculateRowHeight($row_number, $row) 754 | { 755 | if (!isset($this->_row_heights[$row_number])) { 756 | $this->_row_heights[$row_number] = 1; 757 | } 758 | 759 | // Do not process horizontal rule rows. 760 | if ($row === CONSOLE_TABLE_HORIZONTAL_RULE) { 761 | return; 762 | } 763 | 764 | for ($i = 0, $c = count($row); $i < $c; ++$i) { 765 | $lines = preg_split('/\r?\n|\r/', $row[$i]); 766 | $this->_row_heights[$row_number] = max($this->_row_heights[$row_number], 767 | count($lines)); 768 | } 769 | } 770 | 771 | /** 772 | * Returns the character length of a string. 773 | * 774 | * @param string $str A multibyte or singlebyte string. 775 | * 776 | * @return integer The string length. 777 | */ 778 | function _strlen($str) 779 | { 780 | static $mbstring, $utf8; 781 | 782 | // Strip ANSI color codes if requested. 783 | if ($this->_ansiColor) { 784 | $str = Console_Color::strip($str); 785 | } 786 | 787 | // Cache expensive function_exists() calls. 788 | if (!isset($mbstring)) { 789 | $mbstring = function_exists('mb_strlen'); 790 | } 791 | if (!isset($utf8)) { 792 | $utf8 = function_exists('utf8_decode'); 793 | } 794 | 795 | if ($utf8 && 796 | ($this->_charset == strtolower('utf-8') || 797 | $this->_charset == strtolower('utf8'))) { 798 | return strlen(utf8_decode($str)); 799 | } 800 | if ($mbstring) { 801 | return mb_strlen($str, $this->_charset); 802 | } 803 | 804 | return strlen($str); 805 | } 806 | 807 | /** 808 | * Returns part of a string. 809 | * 810 | * @param string $string The string to be converted. 811 | * @param integer $start The part's start position, zero based. 812 | * @param integer $length The part's length. 813 | * 814 | * @return string The string's part. 815 | */ 816 | function _substr($string, $start, $length = null) 817 | { 818 | static $mbstring; 819 | 820 | // Cache expensive function_exists() calls. 821 | if (!isset($mbstring)) { 822 | $mbstring = function_exists('mb_substr'); 823 | } 824 | 825 | if (is_null($length)) { 826 | $length = $this->_strlen($string); 827 | } 828 | if ($mbstring) { 829 | $ret = @mb_substr($string, $start, $length, $this->_charset); 830 | if (!empty($ret)) { 831 | return $ret; 832 | } 833 | } 834 | return substr($string, $start, $length); 835 | } 836 | 837 | /** 838 | * Returns a string padded to a certain length with another string. 839 | * 840 | * This method behaves exactly like str_pad but is multibyte safe. 841 | * 842 | * @param string $input The string to be padded. 843 | * @param integer $length The length of the resulting string. 844 | * @param string $pad The string to pad the input string with. Must 845 | * be in the same charset like the input string. 846 | * @param const $type The padding type. One of STR_PAD_LEFT, 847 | * STR_PAD_RIGHT, or STR_PAD_BOTH. 848 | * 849 | * @return string The padded string. 850 | */ 851 | function _strpad($input, $length, $pad = ' ', $type = STR_PAD_RIGHT) 852 | { 853 | $mb_length = $this->_strlen($input); 854 | $sb_length = strlen($input); 855 | $pad_length = $this->_strlen($pad); 856 | 857 | /* Return if we already have the length. */ 858 | if ($mb_length >= $length) { 859 | return $input; 860 | } 861 | 862 | /* Shortcut for single byte strings. */ 863 | if ($mb_length == $sb_length && $pad_length == strlen($pad)) { 864 | return str_pad($input, $length, $pad, $type); 865 | } 866 | 867 | switch ($type) { 868 | case STR_PAD_LEFT: 869 | $left = $length - $mb_length; 870 | $output = $this->_substr(str_repeat($pad, ceil($left / $pad_length)), 871 | 0, $left, $this->_charset) . $input; 872 | break; 873 | case STR_PAD_BOTH: 874 | $left = floor(($length - $mb_length) / 2); 875 | $right = ceil(($length - $mb_length) / 2); 876 | $output = $this->_substr(str_repeat($pad, ceil($left / $pad_length)), 877 | 0, $left, $this->_charset) . 878 | $input . 879 | $this->_substr(str_repeat($pad, ceil($right / $pad_length)), 880 | 0, $right, $this->_charset); 881 | break; 882 | case STR_PAD_RIGHT: 883 | $right = $length - $mb_length; 884 | $output = $input . 885 | $this->_substr(str_repeat($pad, ceil($right / $pad_length)), 886 | 0, $right, $this->_charset); 887 | break; 888 | } 889 | 890 | return $output; 891 | } 892 | 893 | } 894 | --------------------------------------------------------------------------------