Location: PHPKode > scripts > Script timer > script-timer/script_timer.inc.php
<?php
/**
 * File: script_timer.inc.php
 * @package		script_timer
 */

/**
 * Script timing class
 *
 * Records the time it takes various parts of your script to run and displays
 * the results in a table so you can easily analyse which parts of your script
 * may need optimization.
 *
 * With script timing you will always have some overhead for the taking of the
 * measurements and processing thereof.<br>
 * This class minimizes that overhead by excluding virtually all processing within
 * this class from your timing calculations.
 *
 * *********************************************************************<br>
 * <b>Features</b><br>
 * *********************************************************************
 *
 * - Add markers recorded before class instantiation
 * - Temporarily pause the timer
 * - Group markers by script
 * - Optionally add subtotals per script (group)
 * - Uses bcmath functions whenever possible for better calculation precision
 * - Adjustable precision for the calculations
 * - Adjustable precision for the output
 * - Provides the following measurements:<br>
 * 		* run time for the marker (excluding processing by this class)<br>
 * 		* run time for the marker as a percentage of the total run time<br>
 * 		* cumulative run time up to the marker<br>
 * 		* cumulative percentage
 * - Choose how you want to receive the results:<br>
 * 		* a two-dimentional array so you can process the results yourself<br>
 * 		* a html string which you can assign to whichever templating system you use<br>
 * 		* output straight to screen as a table
 * - HTML output can easily be adjusted to your liking as the html used is contained in a template<br>
 * 		* the html can be adjusted in the template file (./templates/script_timing.tpl)<br>
 * 		* both the table as well as the rows are tagged with class names, through the use
 * 		  of CSS you can easily change the appearance of the table (as done in the example file)
 *
 * *********************************************************************<br>
 * <b>Basics on how to use the class</b><br>
 * *********************************************************************
 *
 * Include and instantiate the class :
 * <code> include_once( 'script_timer.inc.php' );
 * $timer = new script_timer();</code>
 *
 * Start the timer :
 * <code> $timer->start_timer();</code>
 *
 * Set a marker :
 * <code> $timer->set_marker( 'task x done' );</code>
 *
 * Optionally add a script (group) name to group a set of tasks together :
 * <code> $timer->set_marker( 'task y done', 'sub script y' );</code>
 *
 * Optionally pause the script after a marker:
 * <code> $timer->set_marker( 'task z done', 'script z', true );</code>
 *  ... Do something you don't want timed ...<br>
 * Unpause the timer when you want to start timing again
 * <code> $timer->unpause();</code>
 *
 *
 * Retrieve the results :
 *
 * Method 1a - as a two-dimentional array without subtotals:
 * <code> $timer->end_timer( false );
 * $timer_array = $timer->scripttiming;</code>
 *
 * Method 1b - as a two-dimentional array with subtotals and custom marker name for the end marker:
 * <code> $timer->end_timer( true, 'My end marker name', 'My script name' );
 * $timer_array = $timer->scripttiming;</code>
 *
 * Method 2 - as a html string :
 * (example uses a precision of 6 digits and ISO-8859-1 encoding for the html string):
 * <code> $html = $timer->get_output( false, 6, 'ISO-8859-1' );</code>
 *
 * Method 3 - output to screen :
 * (example uses a precision of 4 digits and the default (utf-8) encoding for the html string):
 * <code> $timer->get_output( true, 4 );</code>
 *
 *
 * Oftentimes you may want to start timing a script before you include any files /
 * classes.<br>
 * This class lets you do so and this is how:
 *
 * At the start of your script, record a start time:
 * <code> $start = microtime();</code>
 *
 * Store any markers you want in an array:
 * <code> $time_array[] = array( microtime(), 'Start up task x done', 'optional script name' );</code>
 *
 * Once you have included and instantiated the class, pass these markers and your start time
 * to the class object:
 * <code> $timer->add_markers( $time_array, $start );</code>
 * Do not call the {@link start_timer()} method after this as {@link add_markers()} will
 * start your timer at the first recorded time or at the start time if you pass one.
 *
 * After adding your manually recorded markers, you can use the class like above to set additional
 * markers and get the results.
 *
 *
 * After using the timer, you may want to clean up, for instance if you want to time
 * several scripts seperately :
 * <code> $timer->reset();</code>
 *
 * {@internal For more information on the options, please have a look at the documentation included with
 * this package.}}
 *
 *
 * *********************************************************************<br>
 * <b>Version management information</b><br>
 * *********************************************************************
 *
 * + v1.0 First public release
 *
 * *********************************************************************
 *
 * @package		script_timer
 * @author		Juliette Reinders Folmer, Advies en zo - <hide@address.com>
 *
 * @version		1.0
 * @since		2006-09-08 // Last changed: by Juliette Reinders Folmer
 * @copyright	©2006 {@link http://www.adviesenzo.nl/ Advies en zo },
 * 				<hide@address.com>
 * @license		http://www.opensource.org/licenses/lgpl-license.php GNU Lesser General Public License
 * @license		http://opensource.org/licenses/academic Academic Free License Version 1.2
 * @example		example/script_timer_example.php
 * @link 		http://www.php.net/function.microtime
 *
 * @uses		phpBB's template class for displaying information / parsing the html to string
 * @link		http://www.phpbb.com/kb/article.php?article_id=200 phpbb template tutorial
 */
class script_timer {

	/**********************************************************************
	 * INITIATION OF THE VARIABLES USED IN THE CLASS
	 *********************************************************************/

	/* ******** Basic variables ******** */
	/**
	 * Directory in which this script resides
	 *
	 * @access	private
	 * @var		string	$dir
	 */
	var $dir;

	/**
	 * Precision for the bcmath functions and floats
	 *
	 * Can be set through the class instantiation method
	 *
	 * @link 	http://www.php.net/ref.bc
	 * @link 	http://www.php.net/ini.core
	 * @var		int		$calc_precision
	 */
	var $calc_precision = 10;


	/* ******** Variables used for displaying results ******** */
	/**
	 * Default precision for output of results
	 *
	 * Can be overruled by setting the $precision parameter in the {@link get_output()} method
	 *
	 * @see 	get_output()
	 * @link 	http://www.php.net/function.round
	 * @var		int		$output_precision
	 */
	var $output_precision = 4;

	/**
	 * Default character encoding for output of results
	 *
	 * Can be overruled by setting the $encoding parameter in the {@link get_output()} method
	 *
	 * @see 	get_output()
	 * @link 	http://www.php.net/function.htmlspecialchars
	 * @var		string	$output_encoding
	 */
	var $output_encoding = 'utf-8';

	/**
	 * Path to the file which hold the template class used for displaying results
	 *
	 * Path relative to this file
	 *
	 * @access 	private
	 * @var		string	$template_class
	 */
	var $template_class = '/classes/phpbbtemplate.inc';

	/**
	 * Path to the html template files
	 *
	 * Path relative to this file
	 * @access 	private
	 * @var		string	$template_path
	 */
	var $template_path = '/templates/';

	/**
	 * Filename for the html template file for the script timing output table
	 *
	 * @access 	private
	 * @var		string	$template_file_timer
	 */
	var $template_file_timer = 'script_timing.tpl';

	/**
	 * Variable to hold the template object
	 *
	 * @access 	private
	 * @var		object	$template
	 */
	var $template;


	/* ******** Variables used by the timer ******** */
	/**
	 * Start time for the next marker
	 *
	 * {@internal using this variable we can correct for the time the functions in this
	 * script take to run}}
	 *
	 * @access 	private
	 * @var		string	$start_next
	 */
	var $start_next;

	/**
	 * Sum of the timings taken (so far)
	 *
	 * Whether this is a string or float depends on whether the bcmath extension is loaded.
	 *
	 * @var		string|float	$timer_total
	 */
	var $timer_total = 0;

	/**
	 * Result array for script timing
	 *
	 * The result array will (eventually) contain a two-dimentional array of information.
	 * Each record will hold the following information:
	 * <pre>[#] => array(
	 * 	[microtime_end]		=> string	microtime end time for marker
	 * 	[microtime_elapsed]	=> string|float	microtime between start and end
	 * 						for this marker
	 * 	[microtime_start]	=> string	microtime start time for timing
	 * 						of this marker
	 * 	[marker_name]		=> string	marker name
	 * 	[script_name] 		=> string|null	[optional] script name
	 * 	[perc_elapsed]		=> string|float	percentage of this script part
	 * 						compared to the whole script
	 * 	[subtotal]		=> string|float	microtime since beginning of timing
	 * 	[cum_perc]		=> string|float	cumulative percentage script run since
	 * 						beginning of timing
	 * )</pre>
	 *
	 * Whether calculated figures are stored as a string or float depends on whether
	 * the bcmath extension is loaded.
	 *
	 * @var		array	$scripttiming
	 */
	var $scripttiming = array();

	/**
	 * Variable to keep track of whether the timer is in pause mode or not
	 *
	 * @see 	set_marker()
	 * @see 	unpause()
	 * @var		bool	$timer_paused
	 */
	var $timer_paused = false;

	/**
	 * Variable to ensure that the {@link end_timer()} method is not run
	 * twice on the same dataset.
	 *
	 * {@internal This is done for two reasons:
	 * - efficiency
	 * - to avoid that subtotals are added twice}}
	 *
	 * @access 	private
	 * @var		bool	$timer_stopped
	 */
	var $timer_stopped = false;

	/**
	 * Variable which contains the setting used for $add_subtotals in
	 * the {@link end_timer()} method.
	 *
	 * @var		bool	$subtotals_added
	 */
	var $subtotals_added;


	/**
	 * Language strings used by the class
	 *
	 * Only contains language strings which *may* be used in the output.
	 * Except for <i>subtotal</i>, they can be overruled by setting optional parameters when calling the
	 * related methods.
	 *
	 * The only other language strings in this class are error messages and those are
	 * hard coded in English for obvious reasons.
	 *
	 * @see 	end_timer()
	 * @see 	add_markers()
	 * @var		array	$lang_strings
	 */
	var $lang_strings = array(
		'subtotal'	=>	'Subtotal of :', 		// used in end_timer()
		'end_timer'	=>	'End of script timing', // used in end_timer()
		'interim_marker'	=>	'Processing between setting of last manual marker and calling add_markers method' 							// used in add_markers()
	);


	/**********************************************************************
	 * CLASS METHODS
	 *********************************************************************/

	/**
	 * Class instantiation
	 *
	 * - Checks for the bcmath extension and throws a warning if not found
	 * - Fetches the directory this script resides in for later use
	 * - [Optionally] Sets the calculation precision for the timer class.<br>
	 * 		If the $calc_precision parameter is not set, the {@link $calc_precision default setting}
	 * 		will be used.
	 * - [Optionally] Starts the timer<br>
	 * 		Do not use this feature in combination with the {@link add_markers()} method
	 * 		(which will start the timer automatically) !<br>
	 * 		You can also manually start the timer at a later timer with {@link start_timer()}
	 *
	 * @see 	add_markers()
	 * @see 	start_timer()
	 *
	 * @param	int		$calc_precision	[optional]
	 * @param 	bool	$auto_start		[optional]	whether or not to start the timer at instantiation
	 * 										Defaults to <i>false</i>.
	 * @return	script_timer
	 *
	 * @internal	@uses 	$dir			to store the current directory
	 * @internal	@uses	$calc_precision	to store the required bcmath and float precision
	 */
	function script_timer( $calc_precision = null, $auto_start = false ) {
		if( isset( $calc_precision ) && is_int( $calc_precision ) ) {
			$this->calc_precision = $calc_precision;
		}
		if( ini_get( 'precision' ) < $this->calc_precision ) {
			ini_set( 'precision', $this->calc_precision );
		}

		if( !extension_loaded( 'bcmath' ) ) {
			trigger_error('Your system is compiled without the <strong><em>bcmath</em></strong> extension - all calculations will use float values which by definition are less precise and could lead to unexpected results', E_USER_WARNING );
		}

		$this->dir = dirname(__FILE__);

		if( $auto_start === true ) {
			$this->start_next = microtime();
		}
	}


	/**
	 * Start script timing
	 *
	 * This is optional.<br>
	 * Alternatively the first time you call the {@link set_marker()} method, script timing will
	 * automatically be started.<br>
	 * <i>Take note</i>: Starting the timing via the {@link set_marker()} method will cause the timing
	 * for the first marker to be 0.
	 *
	 * If you use the {@link add_markers()} method to add markers recorded before class instantiation,
	 * the start time will be set through that method. You should not use this method if you use
	 * {@link add_markers()}.
	 *
	 * <b>Important:</b><br>
	 * Starting the timer - independently of which method you use - will destroy all previously
	 * recorded markers as a new timer is presumed.
	 *
	 * @see 	add_markers()
	 * @see 	set_marker()
	 * @see 	reset()
	 *
	 * @internal	@uses 	reset()		to reset the result variables
	 * @internal	@uses 	$start_next		to store the start time for the next marker
	 */
	function start_timer() {
		$this->reset();
		$this->start_next = microtime();
	}


	/**
	 * Add breakpoint microtime markers which were recorded outside of this class
	 *
	 * Typical use: when you start your script, you first validate and normalize some data
	 * before including any classes.
	 * If you want to also time this first part of the script (i.e. before this class was
	 * instantiated), you will need to record some breakpoints yourself.
	 *
	 * Record those breakpoints like this:
	 * <code>$marker_array[] = array( microtime(), 'marker name', 'optional script name' );</code>
	 *
	 * Using this method, you can then later (once the class is instantiated) add those
	 * recorded times to the class object.
	 *
	 * If you use this method, call this (straight) after you instantiate the class and before
	 * recording additional markers using the {@link set_marker()} method.
	 *
	 * This method will start the timer automatically. Do not call the {@link start_timer()} method.
	 *
	 * This method will (optionally) auto-add an extra marker for the script part between
	 * the last marker passed and the call to this method. You can optionally set
	 * your own $marker_name (and - again optionally - $script_name) for this extra marker.<br>
	 * If you choose not to add the extra marker, the timing for the next marker will be corrected for
	 * the processing time taken by this method call.
	 *
	 * @see 	start_timer()
	 * @see 	set_marker()
	 *
	 * @param	array	$marker_array	multi-dimentional array using the following structure
	 * <code>array(
	 * 	[0] =>	array(
	 * 		[0] =>	string	recorded microtime
	 * 		[1] =>	string	breakpoint marker name
	 * 		[2] =>	string	[optional] script name
	 * 	),
	 * 	[1]	=>	array( ....),
	 * 	.....
	 * );</code>
	 * @param	string	$start_time		[optional]	recorded microtime for start of script timing
	 * 										If not passed, the microtime of the first record
	 * 										in the array will be used as start time and the
	 * 										first record will have an elapsed time of 0.
	 * @param	bool	$add_marker_for_interim	[optional]	whether or not to add a marker for the
	 * 										time passed between the last marker in the array and
	 * 										the call to this method. Defaults to <i>true</i>.<br>
	 * 										If set to <i>false</i>, the next marker will measure the
	 * 										time between the last marker and itself including the call
	 * 										to this method, though largely - but not completely -
	 * 										corrected for the time this method takes to run.
	 * @param	string	$marker_name	[optional]	name for the (optional) interim marker,
	 * 										default to the markername in
	 * 										{@link $lang_strings $lang_strings['interim_marker']}
	 * @param	string	$script_name	[optional]	name for the script / group for the
	 * 										(optional) interim marker, defaults to null (i.e. no script
	 * 										/ group name)
	 *
	 * @internal	@uses 	$timer_stopped	to check whether markers can still be set
	 * @internal	@uses 	$start_next		a) to check whether this sets the first marker
	 * 							b) to store the start time for the first marker
	 * 							c) to retrieve the start time for marker(s)
	 * 							d) to store the start time for the next marker(s)
	 * @internal	@uses 	reset()	to reset the result variables
	 * @internal	@uses 	transform_microtime()	to change the microtime string to a more
	 * 							logical format for calculations
	 * @internal	@uses 	bc_sub()		to calculate the difference between end time and
	 * 							start time for a marker
	 * @internal	@uses 	bc_add()		to calculate the current total elapsed time for the timer
	 * @internal	@uses 	$timer_total	a) to retrieve the previous total elapsed time for
	 * 							the timer, b) to store the new total elapsed time for the timer
	 * @internal	@uses 	$scripttiming	to store the information about a marker
	 * @internal	@uses 	$lang_strings	to retrieve the marker name for the interim marker
	 * 							if none was passed
	 */
	function add_markers( $marker_array, $start_time = null, $add_marker_for_interim = true, $marker_name = null, $script_name = null ) {

		$time = microtime();

		if( $this->timer_stopped === true ) {
			trigger_error( 'You tried to add markers after stopping the script timer.<br />No markers were set.<br />To start a new timing sequence, destroy the previous timing first with reset()', E_USER_WARNING );
			return;
		}

		if( isset( $this->start_next ) ) {
			trigger_error( 'You tried to add markers through the add_markers() method after starting the script timer.<br />No markers were set.<br />To start a new timing sequence, destroy the previous timing with reset() before calling this method', E_USER_WARNING );
			return;
		}
		else {
			$this->reset();
			$this->start_next = ( !is_null( $start_time ) ) ? $start_time : $marker_array[0][0];
		}

		foreach( $marker_array as $marker ) {

			$start = $this->transform_microtime( $this->start_next );
			$end = $this->transform_microtime( $marker[0] );

			$elapsed = $this->bc_sub( $end, $start );
			$this->timer_total = $this->bc_add( $this->timer_total, $elapsed );

		 	$this->scripttiming[] = array(
		 		'microtime_end'		=>	$end,
	 			'microtime_elapsed'	=>	$elapsed,
			 	'microtime_start'	=>	$start,
		 		'marker_name'		=>	$marker[1],
			 	'script_name' 		=> 	( ( isset( $marker[2] ) ) ? $marker[2] : null )
			);

		 	$this->start_next = $marker[0];
		}

		if( $add_marker_for_interim === true ) {

			$start = $this->transform_microtime( $this->start_next );
			$end = $this->transform_microtime( $time );

			$elapsed = $this->bc_sub( $end, $start );
			$this->timer_total = $this->bc_add( $this->timer_total, $elapsed );

	 		$this->scripttiming[] = array(
	 			'microtime_end'		=>	$end,
 				'microtime_elapsed'	=>	$elapsed,
		 		'microtime_start'	=>	$start,
	 			'marker_name'		=>	( ( !is_null( $marker_name ) ) ? $marker_name : $this->lang_strings['interim_marker'] ),
		 		'script_name' 		=> 	( ( !is_null( $script_name ) ) ? $script_name : null )
			);

			$this->start_next = microtime();
		}
		else {
			$this->start_next = $this->bc_add( $marker[0], $this->bc_sub( $this->transform_microtime( microtime() ), $time ) );
		}

	}


	/**
	 * Record the time for a breakpoint in your script
	 *
	 * Sets the marker for the breakpoint called $marker_name
	 *
	 * If you use/include a lot of scripts in your page, you may want to set the optional
	 * parameter $script_name to record in which script the breakpoint resides.<br>
	 * Obviously you can also use $script_name to group sequential related parts of your script.<br>
	 * If you use the $script_name variable, you can then receive subtotals per script (group)
	 * in your timer results.
	 *
	 * @see 	start_timer()
	 * @see 	add_markers()
	 *
	 * @param	string	$marker_name	name for your breakpoint so you recognize it later
	 * @param	string	$script_name	[optional]	name of the current script or group
	 * @param 	bool	$pause_timer	[optional]	pause the timer after setting this marker
	 * 									Defaults to <i>false</i>, i.e. don't pause the timer.
	 *
	 * @internal	@uses 	$timer_stopped	to check whether a marker can still be set
	 * @internal 	@uses	$timer_paused	to check whether the script is in pause mode and
	 * 							if a pause is requested to record that the timer is paused
	 * @internal	@uses 	$start_next		a) to check whether this is the first marker and if so,
	 * 							reset the result variables,
	 * 							b) to retrieve the start time for this marker if it is not the first
	 * 							marker,
	 * 							c) to store the start time for the next marker
	 * @internal	@uses 	reset()	to reset the result variables if this is the
	 * 							first marker
	 * @internal	@uses 	transform_microtime()	to change the microtime string to a more
	 * 							logical format for calculations
	 * @internal	@uses 	bc_sub()		to calculate the difference between end time and
	 * 							start time for a marker
	 * @internal	@uses 	bc_add()		to calculate the current total elapsed time for the timer
	 * @internal	@uses 	$timer_total	a) to retrieve the previous total elapsed time for
	 * 							the timer, b) to store the new total elapsed time for the timer
	 * @internal	@uses 	$scripttiming	to store the information about a marker
	 */
	function set_marker( $marker_name, $script_name = null, $pause_timer = false ) {

		$time = microtime();

		if( $this->timer_stopped === true ) {
			trigger_error( 'You tried to set a marker after stopping the script timer. No marker was set.<br />To start a new timing sequence, use the start_timer() method - beware : this will destroy previously recorded results.', E_USER_WARNING );
			return;
		}

		if( $this->timer_paused === true ) {
			trigger_error( 'You tried to set a marker while the timer has been paused. No marker was set.<br />Unpause the timer using the unpause() method before setting a new marker.', E_USER_WARNING );
			return;
		}

		if( !isset( $this->start_next ) ) {
			$this->reset();
		}

		$start = ( !is_null( $this->start_next ) ? $this->transform_microtime( $this->start_next ) : $this->transform_microtime( $time ) );
		$end = $this->transform_microtime( $time );

		$elapsed = $this->bc_sub( $end, $start );
		$this->timer_total = $this->bc_add( $this->timer_total, $elapsed );

	 	$this->scripttiming[] = array(
	 		'microtime_end'		=>	$end,
	 		'microtime_elapsed'	=>	$elapsed,
		 	'microtime_start'	=>	$start,
		 	'marker_name'		=>	$marker_name,
		 	'script_name' 		=> 	( ( !is_null( $script_name ) ) ? $script_name : null )
		);

		if( $pause_timer === true ) {
			$this->timer_paused = true;
		}

		$this->start_next = microtime();
	}


	/**
	 * Unpause the timer
	 *
	 * @see 	set_marker()
	 *
	 * @internal	@uses	$timer_paused	reset this variable
	 * @internal	@uses	$start_next		to record the start time for the next marker
	 */
	function unpause() {
		$this->timer_paused = false;
		$this->start_next = microtime();
	}


	/**
	 * End timer and add additional measurements to the result array
	 *
	 * If the timer is not currently in pause mode, an end marker will be added. You can optionally
	 * specify a marker name (and - again optionally - script name) for the end marker.
	 *
	 * Measurements added are:
	 * - Run time for the marker {@internal set in {@link set_marker()} }}
	 * - Percentage of current marker compared to complete script
	 * - Run time of script up to the marker
	 * - Cumulative percentage of run time up to the marker compared to the complete script
	 *
	 * Additionally (and optionally) subtotals can be added for groups of markers, so indicated
	 * when they where set by script name.
	 *
	 * If you want to display / retrieve as html string the results of your timer as soon
	 * as you end the timer, you can use the {@link get_output()} method which will end
	 * the timer automatically.
	 *
	 * @see 	get_output()
	 *
	 * @param	bool	$add_subtotals	[optional] whether or not to add subtotals for
	 * 										groups of markers by script name.
	 * 										Defaults to <i>false</i>.
	 * @param	string	$marker_name	[optional] marker name for the 'script timing ended' marker,
	 * 										default to the markername in
	 * 										{@link $lang_strings $lang_strings['end_timer']}.
	 * @param	string	$script_name	[optional] script name for this last marker, defaults to null
	 *
	 * @internal	@uses 	$timer_stopped	to check that this method is not run twice
	 * 							on the same dataset and sets this variable at the end of processing
	 * @internal 	@uses	$timer_paused	to check whether the timer is in pause mode and only sets
	 * 							a end timer when the timer is *not* in pause mode
	 * @internal	@uses 	transform_microtime()	to change the microtime string to a more
	 * 							logical format for calculations
	 * @internal	@uses 	$start_next		to retrieve the start time for the end marker
	 * @internal	@uses 	$timer_total	to retrieve the (previous) total elapsed time for the timer
	 * @internal	@uses 	bc_sub()		to calculate the difference between end time
	 * 							and start time for a marker
	 * @internal	@uses 	bc_add()		to calculate the current total elapsed time for the timer
	 * @internal	@uses 	bc_perc()		to calculate percentages
	 * @internal	@uses 	$scripttiming	to store the info about the last marker and to retrieve
	 * 							the information on all markers to perform the measurement calculations
	 * 							on which are then stored back to this array
	 * @internal	@uses 	$lang_strings	to retrieve the marker name for the end marker if none
	 * 							was passed and to retrieve the marker name for the subtotal markers
	 * @internal	@uses 	$subtotals_added	to store the $add_subtotals setting for reference
	 */
	function end_timer( $add_subtotals = false, $marker_name = null, $script_name = null ) {

		$time = microtime();

		if( $this->timer_stopped === true ) {
			trigger_error( 'The script timer was already ended - no action taken', E_USER_WARNING );
			return;
		}
		elseif( $this->timer_paused === false ) {

			// Set the end marker
			$start = ( !is_null( $this->start_next ) ? $this->transform_microtime( $this->start_next ) : $this->transform_microtime( $time ) );
			$end = $this->transform_microtime( $time );

			$elapsed = $this->bc_sub( $end, $start );
			$this->timer_total = $this->bc_add( $this->timer_total, $elapsed );

	 		$this->scripttiming[] = array(
	 			'microtime_end'		=>	$end,
	 			'microtime_elapsed'	=>	$elapsed,
		 		'microtime_start'	=>	$start,
		 		'marker_name'		=>	( !is_null( $marker_name ) ? $marker_name : $this->lang_strings['end_timer'] ),
		 		'script_name' 		=> 	( !is_null( $script_name ) ? $script_name : null )
			);
			unset( $start, $end, $elapsed, $time );
		}


		// Add extra measurements
		sort( $this->scripttiming, SORT_REGULAR );

		$overall_subtotal = 0;
		$script_start = '';
		$script_elapsed = '';
		$count = count( $this->scripttiming );

		for( $i = 0; $i < $count; $i++ ) {
			$array = $this->scripttiming[$i];

			// Calculate measurements and add the to the result array
			$perc_elapsed = $this->bc_perc( $array['microtime_elapsed'], $this->timer_total );
			$overall_subtotal = $this->bc_add( $overall_subtotal, $array['microtime_elapsed'] );
			$cum_perc = $this->bc_perc( $overall_subtotal, $this->timer_total );

			$this->scripttiming[$i]['perc_elapsed'] = $perc_elapsed;
			$this->scripttiming[$i]['subtotal'] = $overall_subtotal;
			$this->scripttiming[$i]['cum_perc'] = $cum_perc;

			// Add subtotal records if so requested
			if( $add_subtotals === true && !is_null( $array['script_name'] ) ) {

				if( $i === 0 || $array['script_name'] !== $this->scripttiming[($i-1)]['script_name'] ) {
					$script_start = $array['microtime_start'];
					$script_elapsed = $array['microtime_elapsed'];
				}
				elseif( $array['script_name'] === $this->scripttiming[($i-1)]['script_name'] ) {
					$script_elapsed = $this->bc_add( $script_elapsed, $array['microtime_elapsed'] );
				}

				if( ( $i + 1 ) === $count || $array['script_name'] !== $this->scripttiming[($i+1)]['script_name'] ) {

					$perc_elapsed = $this->bc_perc( $script_elapsed, $this->timer_total );

					array_splice( $this->scripttiming, ( $i+1 ), 0, 0);
					$this->scripttiming[$i+1] = array(
			 			'microtime_end'		=>	$array['microtime_end'],
 						'microtime_elapsed'	=>	$script_elapsed,
				 		'microtime_start'	=>	$script_start,
	 					'marker_name'		=>	$this->lang_strings['subtotal'] . ' ' .  $array['script_name'],
	 					'script_name' 		=> 	null,
	 					'perc_elapsed'		=>	$perc_elapsed,
	 					'subtotal'			=>	$overall_subtotal,
	 					'cum_perc'			=>	$cum_perc
					);
					$script_start = '';
					$script_elapsed = '';

					$count = count( $this->scripttiming );
					$i++;
				}
			}
		}

		$this->timer_stopped = true;
		$this->subtotals_added = $add_subtotals;
	}


	/**
	 * Retrieve the timer output either as a html string or directly printed to screen
	 *
	 * Please refer to {@link end_timer()} for information on how (and why) to set the optional
	 * $add_subtotals, $marker_name and $script_name parameters
	 *
	 * @see		end_timer()
	 *
	 * @param	bool	$display	[optional] whether to return the output to the screen
	 * 									or as a string<br>
	 * 									<i>false</i> returns a html string<br>
	 * 									<i>true</i> prints the results to screen<br>
	 * 									Defaults to <i>false</i>.
	 * @param	int		$precision	[optional] output precision of microtime values<br>
	 * 									Defaults to {@link $output_precision}
	 * @param	string	$encoding	[optional] character encoding for the html output<br>
	 * 									Defaults to {@link $output_encoding}
	 * @param	bool	$add_subtotals	[optional] only used if the timer was not previously stopped
	 * @param	string	$marker_name	[optional] only used if the timer was not previously stopped
	 * @param	string	$script_name	[optional] only used if the timer was not previously stopped
	 * @return	string|bool		returns the html string or true if output to screen was successful
	 *
	 * @internal	@uses 	$timer_stopped	to check whether the timer has been ended already
	 * @internal	@uses 	end_timer()		to end the timer if this had not been done before
	 * @internal	@uses 	$output_precision	to default to if no $precision was passed
	 * @internal	@uses 	$output_encoding	to default to if no $encoding was passed
	 * @internal	@uses 	$template_class		to retrieve the templating engine
	 * @internal	@uses 	$template			to store the template object
	 * @internal	@uses 	$template_path		as path for the template file
	 * @internal	@uses 	$template_file_timer	as filename for the html template
	 * @internal	@uses 	$scripttiming		to retrieve the information on the markers
	 */
    function get_output( $display = false, $precision = null, $encoding = null, $add_subtotals = null, $marker_name = null, $script_name = null ) {

    	if( $this->timer_stopped !== true ) {
	    	$this->end_timer( $add_subtotals, $marker_name, $script_name );
    	}

    	$encoding = ( !is_null( $encoding ) ? $encoding : $this->output_encoding );
    	$precision = ( !is_null( $precision ) ? $precision : $this->output_precision );

    	if( !class_exists( 'Template') ) {
    		include( $this->dir . $this->template_class );
    	}
    	if( !is_object( $this->template ) ) {
    		$this->template = new Template( $this->dir . $this->template_path );
    	}

    	$this->template->set_filenames( array(
    			'script_timing'	=> $this->template_file_timer
    		)
    	);

		foreach( $this->scripttiming as $marker ) {

			$this->template->assign_block_vars('row', array(
				'SCRIPTNAME'	=>	htmlspecialchars( $marker['script_name'], ENT_QUOTES, $encoding ),
				'MARKERNAME'	=>	htmlspecialchars( $marker['marker_name'], ENT_QUOTES, $encoding ),
				'MARKER_TIME'	=>	htmlspecialchars( sprintf( '%01.' . $precision . 'f', $marker['microtime_elapsed'] ), ENT_QUOTES, $encoding ),
				'MARKER_PERC'	=>	htmlspecialchars( sprintf( '%01.2f', $marker['perc_elapsed'] ), ENT_QUOTES, $encoding ),
				'CUM_TIME'		=>	htmlspecialchars( sprintf( '%01.' . $precision . 'f', $marker['subtotal'] ), ENT_QUOTES, $encoding ),
				'CUM_PERC'		=>	htmlspecialchars( sprintf( '%01.2f', $marker['cum_perc'] ), ENT_QUOTES, $encoding )
				)
			);

			if( is_null( $marker['script_name'] ) ) {
				$this->template->assign_block_vars('row.marker_row', array() );
			}
			else {
				$this->template->assign_block_vars('row.marker_script_row', array() );
			}
		}


		if( $display === false ) {
			ob_start();
			$this->template->pparse('script_timing');
			$table = ob_get_contents();
			ob_end_clean();

			$this->template->destroy();

			return $table;
		}
		else {
			$return = $this->template->pparse('script_timing');

			$this->template->destroy();

			return $return ;
		}
	}


	/**
	 * Reset all result variables
	 *
	 * @internal	@uses 	$start_next		to reset this variable
	 * @internal	@uses 	$timer_total	to reset this variable
	 * @internal	@uses 	$scripttiming	to reset this variable
	 * @internal 	@uses	$timer_paused	to reset this variable
	 * @internal	@uses 	$timer_stopped	to reset this variable
	 * @internal	@uses 	$subtotals_added	to reset this variable
	 */
	function reset() {
		$this->start_next = null;
		$this->timer_total = 0;
		$this->scripttiming = array();
		$this->timer_paused = false;
		$this->timer_stopped = false;
		$this->subtotals_added = null;
	}


    /**
     * Transform a microtime string ('msec sec') to a more logically ordered numerical string
     * i.e.: 'sec.msec'
     *
     * @access	private
     * @param	string	$microtime	microtime string
     * @return	string	transformed microtime string
     */
	function transform_microtime( $microtime ) {
		return substr( $microtime, 11 ) . substr( $microtime, 1, 9 );
	}

    /**
     * Calculate the difference between two arbitrary precision numbers
     *
     * @access	private
	 * @uses 	$calc_precision	for the precision of the bcmath functions
	 *
     * @param	string  $high	string containing arbitrary precision number
     * @param	string  $low	string containing arbitrary precision number
     * @return	string			difference between $high and $low
     */
	function bc_sub( $high, $low ) {
		if( extension_loaded( 'bcmath' ) ) {
			return bcsub( $high, $low, $this->calc_precision );
		}
		else {
			return ( $high - $low );
		}
	}

    /**
     * Calculate the sum of two arbitrary precision numbers
     *
     * @access	private
	 * @uses 	$calc_precision	for the precision of the bcmath functions
	 *
     * @param	string  $str1	string containing arbitrary precision number
     * @param	string  $str2	string containing arbitrary precision number
     * @return	string			sum of $str1 and $str2
     */
	function bc_add( $str1, $str2 ) {
		if( extension_loaded( 'bcmath' ) ) {
			return bcadd( $str1, $str2, $this->calc_precision );
		}
		else {
			return ( $str1 + $str2 );
		}
	}

	/**
	 * Calculate the percentage of $part against $total where
	 * $part and $total are arbitrary precision numbers represented as strings
	 *
     * @access	private
	 * @uses 	$calc_precision	for the precision of the bcmath functions
	 *
	 * @param	string	$part	string containing arbitrary precision number
	 * @param	string	$total	string containing arbitrary precision number
	 * @return	string|float	percentage of $part against $total
	 */
	function bc_perc( $part, $total ) {
		if( extension_loaded( 'bcmath' ) ) {
			return bcdiv( bcmul( $part, '100', $this->calc_precision ), $total, $this->calc_precision );
		}
		else {
			return ( ( $part * 100 ) / $total );
		}
	}

} // END of class

?>
Return current item: Script timer