# This program is copyright 2010-2011 Percona Inc.
# Feedback and improvements are welcome.
#
# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
#
# 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, version 2; OR the Perl Artistic License.  On UNIX and similar
# systems, you can issue `man perlgpl' or `man perlartistic' to read these
# licenses.
#
# 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., 59 Temple
# Place, Suite 330, Boston, MA  02111-1307  USA.
# ###########################################################################
# TimeSeriesTrender package $Revision: 7096 $
# ###########################################################################

# Package: TimeSeriesTrender
# TimeSeriesTrender calculates trends in time.
{
package TimeSeriesTrender;

use strict;
use warnings FATAL => 'all';
use English qw(-no_match_vars);
use constant MKDEBUG => $ENV{MKDEBUG} || 0;

# Arguments:
#  *  callback    Subroutine to call when the time is set to the next larger
#                 increment.  Receives a hashref of the current timestamp's
#                 stats (see compute_stats()).
sub new {
   my ( $class, %args ) = @_;
   foreach my $arg ( qw(callback) ) {
      die "I need a $arg argument" unless defined $args{$arg};
   }
   my $self = {
      %args,
      ts      => '',
      numbers => [],
   };
   return bless $self, $class;
}

# Set the current timestamp to be applied to all subsequent values received
# through add_number().  If the timestamp changes to the "next larger
# increment," then fire the callback.  It *is* possible for a timestamp to be
# less than one previously seen.  In such cases, we simply lump those
# time-series data points into the current timestamp's bucket.
sub set_time {
   my ( $self, $ts ) = @_;
   my $cur_ts = $self->{ts};
   if ( !$cur_ts ) {
      $self->{ts} = $ts;
   }
   elsif ( $ts gt $cur_ts ) {
      my $statistics = $self->compute_stats($cur_ts, $self->{numbers});
      $self->{callback}->($statistics);
      $self->{numbers} = [];
      $self->{ts}      = $ts;
   }
   # If $cur_ts > $ts, then we do nothing -- we do not want $self->{ts} to ever
   # decrease!
}

# Add a number to the current batch defined by the current timestamp, which is
# set by set_time().
sub add_number {
   my ( $self, $number ) = @_;
   push @{$self->{numbers}}, $number;
}

# Compute the desired statistics over the set of numbers, which is passed in as
# an arrayref.  Returns a hashref.
sub compute_stats {
   my ( $self, $ts, $numbers ) = @_;
   my $cnt = scalar @$numbers;
   my $result = {
      ts    => $ts,
      cnt   => 0,
      sum   => 0,
      min   => 0,
      max   => 0,
      avg   => 0,
      stdev => 0,
   };
   return $result unless $cnt;
   my ( $sum, $min, $max, $sumsq ) = (0, 2 ** 32, 0, 0);
   foreach my $num ( @$numbers ) {
      $sum   += $num;
      $min    = $num < $min ? $num : $min;
      $max    = $num > $max ? $num : $max;
      $sumsq += $num * $num;
   }
   my $avg   = $sum / $cnt;
   my $var   = $sumsq / $cnt - ( $avg * $avg );
   my $stdev = $var > 0 ? sqrt($var) : 0;
   # TODO: must compute the significant digits of the input, and use that to
   # round the output appropriately.
   @{$result}{qw(cnt sum min max avg stdev)}
      = ($cnt, $sum, $min, $max, $avg, $stdev);
   return $result;
}

sub _d {
   my ($package, undef, $line) = caller 0;
   @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
        map { defined $_ ? $_ : 'undef' }
        @_;
   print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
}

1;
}
# ###########################################################################
# End TimeSeriesTrender package
# ###########################################################################
