/var/lib/sorcery/modules/libtime

     1	#!/bin/bash
     2	#---------------------------------------------------------------------
     3	##
     4	## @Synopsis Set of functions used by gaze for time calculations
     5	## @Copyright (C) 2008 The Source Mage Team <http://www.sourcemage.org>
     6	##
     7	## This file holds various statistical functions and an interface to
     8	## the activity log for getting the input data.
     9	#---------------------------------------------------------------------
    10	
    11	#---------------------------------------------------------------------
    12	##
    13	## Computes all the casting times of the passed spell by inspecting
    14	## the activity log.
    15	##
    16	## @param spell
    17	## @param version (optional)
    18	##
    19	## @Stdout casting time(s)
    20	#---------------------------------------------------------------------
    21	function compute_cast_times() {
    22	  gawk -v spell=$1 -v version=$2 '
    23	    # log timestamps are in the "%Y%m%d:%H%M\(%z\)" format (20080625:0853(+0000))
    24	    # we need them in "%Y %m %d %H %M %S" and %s (epoch time). We ignore the
    25	    # timezone, since the time is stored in UTC and %z is always +0000
    26	    function since_epoch(time,      date) {
    27	      # The date:
    28	      date = substr(time,1,4) " " substr(time,5,2) " " substr(time,7,2)
    29	      # The time (use 00 for seconds):
    30	      date = date " " substr(time,10,2) " " substr(time,12,2) " 00"
    31	      return mktime(date)
    32	    }
    33	
    34	    /^.*\tcast\t.*\t.*\t.*\t.*$/ {
    35	      # check the spell and version manually - literally
    36	      if ($3 != spell) next
    37	      if (version != "" && $4 != version) next
    38	
    39	      # check all valid start/succes pairs
    40	      if ($5 == "start") {
    41	        start_time = $1
    42	        start_version = $4
    43	      }
    44	      if ($5 == "success" && start_time != 0 && start_version == $4) {
    45	        succes_time = $1
    46	        print since_epoch(succes_time)-since_epoch(start_time)
    47	        start_time = 0
    48	      }
    49	  }' $ACTIVITY_LOG
    50	}
    51	
    52	#---------------------------------------------------------------------
    53	##
    54	## Display the time in seconds a spell took to compile and install.
    55	## @param spell
    56	## @Stdout cast time of spell in seconds
    57	#---------------------------------------------------------------------
    58	function compute_cast_time() {
    59	  # FIXME: make the default configurable
    60	  local spell=$1
    61	  local type=${2:---last}
    62	
    63	  compute_cast_times $spell |
    64	  case $type in
    65	    --last)
    66	      tail -n 1 ;;
    67	    --median)
    68	      compute_median ;;
    69	    --mean)
    70	      compute_mean ;;
    71	    --weigh-last)
    72	      compute_weighted_mean last-cast $(private_installed_version $spell) ;;
    73	  esac
    74	}
    75	
    76	#---------------------------------------------------------------------
    77	##
    78	## Display the time in seconds a spell took to compile and install.
    79	## All known algorithms are used (see compute_cast_time). Mean estimates
    80	## also show the estimation error.
    81	##
    82	## @param spell
    83	## @param verbosity 0-machine readable/quiet
    84	## @Stdout pretty-printed cast times of spell
    85	## @return 1 if there are no valid times available
    86	## @return 0 otherwise
    87	#---------------------------------------------------------------------
    88	function compute_all_cast_times() {
    89	  local spell=$1 verbosity=$2
    90	  local times time
    91	
    92	  times=$(compute_cast_times $spell)
    93	  [[ -z $times ]] && return 1
    94	
    95	  if [[ $verbosity == false ]]; then
    96	    echo -n $spell:
    97	    echo "$times" | tail -n 1
    98	    echo -n $spell:
    99	    echo "$times" | compute_median
   100	    echo -n $spell:
   101	    echo "$times" | compute_mean
   102	    echo -n $spell:
   103	    echo "$times" | compute_weighted_mean last-cast $(private_installed_version $spell)
   104	    echo -n $spell:n $(wc -l <<< "$times")
   105	    echo
   106	  else
   107	    message -n "Last cast time of ${SPELL_COLOR}$spell$DEFAULT_COLOR: "
   108	    time=$(echo "$times" | tail -n 1)
   109	    pretty_print_time $time
   110	
   111	    message -n "Median cast time of ${SPELL_COLOR}$spell$DEFAULT_COLOR: "
   112	    time=$(echo "$times" | compute_median)
   113	    pretty_print_time $time
   114	
   115	    message -n "Mean cast time of ${SPELL_COLOR}$spell$DEFAULT_COLOR: "
   116	    time=$(echo "$times" | compute_mean)
   117	    pretty_print_time $time
   118	
   119	    message -n "Weighted mean cast time of ${SPELL_COLOR}$spell$DEFAULT_COLOR: "
   120	    time=$(echo "$times" | compute_weighted_mean last-cast $(private_installed_version $spell))
   121	    pretty_print_time $time
   122	
   123	    message "Number of samples: $(wc -l <<< "$times")"
   124	    echo
   125	  fi
   126	
   127	  return 0
   128	}
   129	#---------------------------------------------------------------------
   130	##
   131	## Computes the mean of the passed arguments and its standard error
   132	##
   133	## @Stdin  numbers separated by newlines
   134	## @Stdout mean and its error
   135	#---------------------------------------------------------------------
   136	function compute_mean() {
   137	  gawk '
   138	    BEGIN { mean = 0; variance = 0; }
   139	
   140	    { sum += $0; numbers[NR] = $0; }
   141	
   142	    END {
   143	      if (NR == 0) exit
   144	      mean = sum/NR;
   145	      if (NR == 1) {
   146	        error = 0;
   147	      } else {
   148	        for (i in numbers) {
   149	          variance += (numbers[i] - mean)^2;
   150	        }
   151	        error = int(sqrt(variance/(NR-1)/NR)*1.96 + 0.5);
   152	      }
   153	
   154	      print int(mean+0.5), error
   155	    }'
   156	}
   157	
   158	#---------------------------------------------------------------------
   159	##
   160	## Computes the weighted mean of the passed and some more arguments.
   161	## WARNING: only usable for cast times!
   162	##
   163	## @param type - weigh either just the times of the latest version
   164	##
   165	## @Stdin  numbers separated by newlines
   166	## @Stdout mean value
   167	#---------------------------------------------------------------------
   168	function compute_weighted_mean() {
   169	  local type=$1
   170	  local version=$2
   171	
   172	  if [[ $type == last-cast ]]; then
   173	    local weight=10 #make this relative someday?
   174	    local i command
   175	    for ((i=1; i < $weight; i++)); do
   176	      command="p; $command"
   177	    done
   178	    {
   179	      # weigh the latest version by adding it another weight-1 times
   180	      # to the piped list of casting times of all versions
   181	      cat -
   182	      compute_cast_times $spell $version | sed -n "$command"
   183	    } | compute_mean
   184	  fi
   185	}
   186	
   187	#---------------------------------------------------------------------
   188	##
   189	## Computes the median of the passed arguments
   190	##
   191	## @Stdin  numbers separated by newlines
   192	## @Stdout median value
   193	#---------------------------------------------------------------------
   194	function compute_median() {
   195	  gawk '
   196	    { numbers[NR] = $0; }
   197	
   198	    END {
   199	      if (NR == 0) exit
   200	      if (NR == 1) {
   201	        print numbers[0]
   202	      } else {
   203	        asort(numbers)
   204	        print (NR % 2) ? numbers[int(NR/2+1)] : (numbers[NR/2] + numbers[NR/2+1])/2
   205	      }
   206	    }'
   207	}
   208	
   209	#---------------------------------------------------------------------
   210	##
   211	## Computes the combined error of independent variables
   212	##
   213	## @param error for each variable
   214	## @param ...
   215	## @Stdout total error
   216	#---------------------------------------------------------------------
   217	function compute_total_error() {
   218	  tr ' ' '\n' <<< "$@" |
   219	  gawk '
   220	    { total += $0^2 }
   221	
   222	    END {
   223	      print int(sqrt(total))
   224	    }'
   225	}
   226	
   227	#---------------------------------------------------------------------
   228	##
   229	## Converts a time in seconds and the estimation error (if available)
   230	## into a human readable format
   231	##
   232	## @param time in seconds
   233	## @param estimation error in seconds (optional)
   234	## @Stdout time (DD:HH:MM or HH:MM or MM or "less than a minute")
   235	#---------------------------------------------------------------------
   236	function pretty_print_time() {
   237	  if [[ -z $2 ]]; then
   238	    pretty_print_time_sub "$@"
   239	  else
   240	    local time=$(pretty_print_time_sub $1)
   241	    echo -n "$time "
   242	    pretty_print_time_error $2
   243	  fi
   244	}
   245	
   246	#---------------------------------------------------------------------
   247	##
   248	## Converts a time in seconds into a human readable format.
   249	##
   250	## @param time in seconds
   251	## @Stdout time (DD:HH:MM or HH:MM or MM or "less than a minute")
   252	#---------------------------------------------------------------------
   253	function pretty_print_time_sub() {
   254	  # date can only print day of year starting with 1, so we can't use it,
   255	  # yet we have to display the number of days, since the total can be big
   256	  awk -v sum=$1 'END {
   257	    days = int(sum/3600/24)
   258	    sum -= days * 3600 * 24
   259	    hours = int(sum/3600)
   260	    sum -= hours * 3600
   261	    minutes = int(sum/60)
   262	    # sum is now < 3600
   263	
   264	    if (sum < 60 && days == 0 && hours == 0) {
   265	      print "less than a minute."
   266	      exit
   267	    }
   268	    if (days > 0) {
   269	      print days "d " hours "h " minutes "m"
   270	    } else if (hours > 0) {
   271	      print hours "h " minutes "m"
   272	    } else {
   273	      print minutes "m"
   274	    }
   275	  }' /dev/null
   276	}
   277	
   278	#---------------------------------------------------------------------
   279	##
   280	## Converts a time estimation error into a human readable format.
   281	## If the error is very small or 0, it isn't shown.
   282	##
   283	## @param estimation error in seconds
   284	## @Stdout time +/- error (see pretty_print_time)
   285	#---------------------------------------------------------------------
   286	function pretty_print_time_error() {
   287	  local error=$1
   288	
   289	  if (( $error < 60 )); then
   290	    error=0
   291	  else
   292	    error=$(pretty_print_time_sub $error)
   293	  fi
   294	
   295	  if [[ $error == 0 ]]; then
   296	    echo
   297	  else
   298	    echo "(+/- $error)"
   299	  fi
   300	}
   301	
   302	#---------------------------------------------------------------------
   303	## @License
   304	##
   305	## This software is free software; you can redistribute it and/or modify
   306	## it under the terms of the GNU General Public License as published by
   307	## the Free Software Foundation; either version 2 of the License, or
   308	## (at your option) any later version.
   309	##
   310	## This software is distributed in the hope that it will be useful,
   311	## but WITHOUT ANY WARRANTY; without even the implied warranty of
   312	## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   313	## GNU General Public License for more details.
   314	##
   315	## You should have received a copy of the GNU General Public License
   316	## along with this software; if not, write to the Free Software
   317	## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   318	##
   319	#---------------------------------------------------------------------