| /* |
| * Copyright (C) 2014 Michael Brown <mbrown@fensystems.co.uk>. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation; either version 2 of the |
| * License, or any later version. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| * 02110-1301, USA. |
| * |
| * You can also choose to distribute this program under the terms of |
| * the Unmodified Binary Distribution Licence (as given in the file |
| * COPYING.UBDL), provided that you have satisfied its requirements. |
| */ |
| |
| FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); |
| |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <strings.h> |
| #include <limits.h> |
| #include <assert.h> |
| #include <ipxe/isqrt.h> |
| #include <ipxe/profile.h> |
| |
| /** @file |
| * |
| * Profiling |
| * |
| * The profiler computes basic statistics (mean, variance, and |
| * standard deviation) for the samples which it records. Note that |
| * these statistics need not be completely accurate; it is sufficient |
| * to give a rough approximation. |
| * |
| * The algorithm for updating the mean and variance estimators is from |
| * The Art of Computer Programming (via Wikipedia), with adjustments |
| * to avoid the use of floating-point instructions. |
| */ |
| |
| /** Accumulated time excluded from profiling */ |
| unsigned long profile_excluded; |
| |
| /** |
| * Format a hex fraction (for debugging) |
| * |
| * @v value Value |
| * @v shift Bit shift |
| * @ret string Formatted hex fraction |
| */ |
| static const char * profile_hex_fraction ( signed long long value, |
| unsigned int shift ) { |
| static char buf[23] = "-"; /* -0xXXXXXXXXXXXXXXXX.XX + NUL */ |
| unsigned long long int_part; |
| uint8_t frac_part; |
| char *ptr; |
| |
| if ( value < 0 ) { |
| value = -value; |
| ptr = &buf[0]; |
| } else { |
| ptr = &buf[1]; |
| } |
| int_part = ( value >> shift ); |
| frac_part = ( value >> ( shift - ( 8 * sizeof ( frac_part ) ) ) ); |
| snprintf ( &buf[1], ( sizeof ( buf ) - 1 ), "%#llx.%02x", |
| int_part, frac_part ); |
| return ptr; |
| } |
| |
| /** |
| * Calculate bit shift for mean sample value |
| * |
| * @v profiler Profiler |
| * @ret shift Bit shift |
| */ |
| static inline unsigned int profile_mean_shift ( struct profiler *profiler ) { |
| |
| return ( ( ( 8 * sizeof ( profiler->mean ) ) - 1 ) /* MSB */ |
| - 1 /* Leave sign bit unused */ |
| - profiler->mean_msb ); |
| } |
| |
| /** |
| * Calculate bit shift for accumulated variance value |
| * |
| * @v profiler Profiler |
| * @ret shift Bit shift |
| */ |
| static inline unsigned int profile_accvar_shift ( struct profiler *profiler ) { |
| |
| return ( ( ( 8 * sizeof ( profiler->accvar ) ) - 1 ) /* MSB */ |
| - 1 /* Leave top bit unused */ |
| - profiler->accvar_msb ); |
| } |
| |
| /** |
| * Update profiler with a new sample |
| * |
| * @v profiler Profiler |
| * @v sample Sample value |
| */ |
| void profile_update ( struct profiler *profiler, unsigned long sample ) { |
| unsigned int sample_msb; |
| unsigned int mean_shift; |
| unsigned int delta_shift; |
| signed long pre_delta; |
| signed long post_delta; |
| signed long long accvar_delta; |
| unsigned int accvar_delta_shift; |
| unsigned int accvar_delta_msb; |
| unsigned int accvar_shift; |
| |
| /* Our scaling logic assumes that sample values never overflow |
| * a signed long (i.e. that the high bit is always zero). |
| */ |
| assert ( ( ( signed ) sample ) >= 0 ); |
| |
| /* Update sample count, limiting to avoid signed overflow */ |
| if ( profiler->count < INT_MAX ) |
| profiler->count++; |
| |
| /* Adjust mean sample value scale if necessary. Skip if |
| * sample is zero (in which case flsl(sample)-1 would |
| * underflow): in the case of a zero sample we have no need to |
| * adjust the scale anyway. |
| */ |
| if ( sample ) { |
| sample_msb = ( flsl ( sample ) - 1 ); |
| if ( profiler->mean_msb < sample_msb ) { |
| profiler->mean >>= ( sample_msb - profiler->mean_msb ); |
| profiler->mean_msb = sample_msb; |
| } |
| } |
| |
| /* Scale sample to internal units */ |
| mean_shift = profile_mean_shift ( profiler ); |
| sample <<= mean_shift; |
| |
| /* Update mean */ |
| pre_delta = ( sample - profiler->mean ); |
| profiler->mean += ( pre_delta / ( ( signed ) profiler->count ) ); |
| post_delta = ( sample - profiler->mean ); |
| delta_shift = mean_shift; |
| DBGC ( profiler, "PROFILER %p sample %#lx mean %s", profiler, |
| ( sample >> mean_shift ), |
| profile_hex_fraction ( profiler->mean, mean_shift ) ); |
| DBGC ( profiler, " pre %s", |
| profile_hex_fraction ( pre_delta, delta_shift ) ); |
| DBGC ( profiler, " post %s\n", |
| profile_hex_fraction ( post_delta, delta_shift ) ); |
| |
| /* Scale both deltas to fit in half of an unsigned long long |
| * to avoid potential overflow on multiplication. Note that |
| * shifting a signed quantity is "implementation-defined" |
| * behaviour in the C standard, but gcc documents that it will |
| * always perform sign extension. |
| */ |
| if ( sizeof ( pre_delta ) > ( sizeof ( accvar_delta ) / 2 ) ) { |
| unsigned int shift = ( 8 * ( sizeof ( pre_delta ) - |
| ( sizeof ( accvar_delta ) / 2 ) )); |
| pre_delta >>= shift; |
| post_delta >>= shift; |
| delta_shift -= shift; |
| } |
| |
| /* Update variance, if applicable. Skip if either delta is |
| * zero (in which case flsl(delta)-1 would underflow): in the |
| * case of a zero delta there is no change to the accumulated |
| * variance anyway. |
| */ |
| if ( pre_delta && post_delta ) { |
| |
| /* Calculate variance delta */ |
| accvar_delta = ( ( ( signed long long ) pre_delta ) * |
| ( ( signed long long ) post_delta ) ); |
| accvar_delta_shift = ( 2 * delta_shift ); |
| assert ( accvar_delta > 0 ); |
| |
| /* Calculate variance delta MSB, using flsl() on each |
| * delta individually to provide an upper bound rather |
| * than requiring the existence of flsll(). |
| */ |
| accvar_delta_msb = ( flsll ( accvar_delta ) - 1 ); |
| if ( accvar_delta_msb > accvar_delta_shift ) { |
| accvar_delta_msb -= accvar_delta_shift; |
| } else { |
| accvar_delta_msb = 0; |
| } |
| |
| /* Adjust scales as necessary */ |
| if ( profiler->accvar_msb < accvar_delta_msb ) { |
| /* Rescale accumulated variance */ |
| profiler->accvar >>= ( accvar_delta_msb - |
| profiler->accvar_msb ); |
| profiler->accvar_msb = accvar_delta_msb; |
| } else { |
| /* Rescale variance delta */ |
| accvar_delta >>= ( profiler->accvar_msb - |
| accvar_delta_msb ); |
| accvar_delta_shift -= ( profiler->accvar_msb - |
| accvar_delta_msb ); |
| } |
| |
| /* Scale delta to internal units */ |
| accvar_shift = profile_accvar_shift ( profiler ); |
| accvar_delta <<= ( accvar_shift - accvar_delta_shift ); |
| |
| /* Accumulate variance */ |
| profiler->accvar += accvar_delta; |
| |
| /* Adjust scale if necessary */ |
| if ( profiler->accvar & |
| ( 1ULL << ( ( 8 * sizeof ( profiler->accvar ) ) - 1 ) ) ) { |
| profiler->accvar >>= 1; |
| profiler->accvar_msb++; |
| accvar_delta >>= 1; |
| accvar_shift--; |
| } |
| |
| DBGC ( profiler, "PROFILER %p accvar %s", profiler, |
| profile_hex_fraction ( profiler->accvar, accvar_shift )); |
| DBGC ( profiler, " delta %s\n", |
| profile_hex_fraction ( accvar_delta, accvar_shift ) ); |
| } |
| } |
| |
| /** |
| * Get mean sample value |
| * |
| * @v profiler Profiler |
| * @ret mean Mean sample value |
| */ |
| unsigned long profile_mean ( struct profiler *profiler ) { |
| unsigned int mean_shift = profile_mean_shift ( profiler ); |
| |
| /* Round to nearest and scale down to original units */ |
| return ( ( profiler->mean + ( 1UL << ( mean_shift - 1 ) ) ) |
| >> mean_shift ); |
| } |
| |
| /** |
| * Get sample variance |
| * |
| * @v profiler Profiler |
| * @ret variance Sample variance |
| */ |
| unsigned long profile_variance ( struct profiler *profiler ) { |
| unsigned int accvar_shift = profile_accvar_shift ( profiler ); |
| |
| /* Variance is zero if fewer than two samples exist (avoiding |
| * division by zero error). |
| */ |
| if ( profiler->count < 2 ) |
| return 0; |
| |
| /* Calculate variance, round to nearest, and scale to original units */ |
| return ( ( ( profiler->accvar / ( profiler->count - 1 ) ) |
| + ( 1ULL << ( accvar_shift - 1 ) ) ) >> accvar_shift ); |
| } |
| |
| /** |
| * Get sample standard deviation |
| * |
| * @v profiler Profiler |
| * @ret stddev Sample standard deviation |
| */ |
| unsigned long profile_stddev ( struct profiler *profiler ) { |
| |
| return isqrt ( profile_variance ( profiler ) ); |
| } |