=head1 NAME

Sys::Statistics::Linux::CpuStats - Collect linux cpu statistics.

=head1 SYNOPSIS

   use Sys::Statistics::Linux::CpuStats;

   my $lxs = new Sys::Statistics::Linux::CpuStats;
   $lxs->init;
   sleep 1;
   my $stats = $lxs->get;

=head1 DESCRIPTION

This module collects cpu statistics from the F</proc> filesystem. It is tested on x86 hardware
with the distributions SuSE (SuSE on s390 and s390x architecture as well), Red Hat, Debian
and Mandrake on kernel versions 2.4 and 2.6 but should also running on other linux distributions
with the same kernel release number. To run this module it is necessary to start it as root or
another user with the authorization to read the F</proc> filesystem.

=head1 DELTAS

It's necessary to initialize the statistics by calling C<init()>, because the statistics are deltas between
the call of C<init()> and C<get()>. By calling C<get()> the deltas be generated and the initial values
be updated automatically. This way making it possible that the call of C<init()> is only necessary
after the call of C<new()>. Further it's recommended to sleep for a while - at least one second - between
the call of C<init()> and/or C<get()> if you want to get useful statistics.

=head1 CPU STATISTICS

Generated by F</proc/stat> for each cpu (cpu0, cpu1 ...). F<cpu> without a number is the summary.

   user    -  Percentage of CPU utilization at the user level.
   nice    -  Percentage of CPU utilization at the user level with nice priority.
   system  -  Percentage of CPU utilization at the system level.
   idle    -  Percentage of time the CPU is in idle state.
   iowait  -  Percentage of time the CPU is in idle state because an i/o operation is waiting for a disk.
   total   -  Total percentage of CPU utilization at user and system level.

=head1 METHODS

=head2 All methods

   C<new()>
   C<init()>
   C<get()>

=head2 new()

Call C<new()> to create a new object.

   my $lxs = new Sys::Statistics::Linux::CpuStats;

=head2 init()

Call C<init()> to initialize the statistics.

   $lxs->init;

=head2 get()

Call C<get()> to get the statistics. C<get()> returns the statistics as a hash reference.

   my $stats = $lxs->get;

=head1 EXPORTS

No exports.

=head1 SEE ALSO

B<proc(5)>

=head1 REPORTING BUGS

Please report all bugs to <jschulz.cpan(at)bloonix.de>.

=head1 AUTHOR

Jonny Schulz <jschulz.cpan(at)bloonix.de>.

=head1 COPYRIGHT

Copyright (c) 2006, 2007 by Jonny Schulz. All rights reserved.

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

=cut

package Sys::Statistics::Linux::CpuStats;
our $VERSION = '0.01';

use strict;
use warnings;
use IO::File;
use Carp qw(croak);

sub new {
   return bless {
      files => {
         stat => '/proc/stat',
      },
      init  => {},
      stats => {},
   }, shift;
}

sub init {
   my $self = shift;
   $self->{init} = $self->_load;
}

sub get {
   my $self  = shift;
   my $class = ref $self;

   croak "$class: there are no initial statistics defined"
      unless %{$self->{init}};

   $self->{stats} = $self->_load;
   $self->_deltas;
   return $self->{stats};
}

#
# private stuff
#

sub _load {
   my $self  = shift;
   my $class = ref $self;
   my $file  = $self->{files};
   my $fh    = new IO::File;
   my %stats;

   $fh->open($file->{stat}, 'r') or croak "$class: unable to open $file->{stat} ($!)";

   while (my $line = <$fh>) {
      if ($line =~ /^(cpu.*?)\s+(.*)$/) {
         my $cpu = \%{$stats{$1}};
         @{$cpu}{qw(user nice system idle iowait)} = split /\s+/, $2;
         # iowait is only set as fifth parameter
         # by kernel versions higher than 2.4
         $cpu->{iowait} = 0 unless defined $cpu->{iowait};
      }
   }

   $fh->close;
   return \%stats;
}

sub _deltas {
   my $self  = shift;
   my $class = ref $self;
   my $istat = $self->{init};
   my $lstat = $self->{stats};

   foreach my $cpu (keys %{$lstat}) {
      my $icpu = $istat->{$cpu};
      my $dcpu = $lstat->{$cpu};
      my $uptime;

      while (my ($k, $v) = each %{$dcpu}) {
         croak "$class: different keys in statistics"
            unless defined $icpu->{$k};
         croak "$class: value of '$k' is not a number"
            unless $v =~ /^\d+$/ && $dcpu->{$k} =~ /^\d+$/;
         $dcpu->{$k} -= $icpu->{$k};
         $icpu->{$k}  = $v;
         $uptime += $dcpu->{$k};
      }

      foreach my $k (keys %{$dcpu}) {
         $dcpu->{$k} = sprintf('%.2f', 100 * $dcpu->{$k} / $uptime)
            if $dcpu->{$k} > 0;
      }
   
      $dcpu->{total} = $dcpu->{user} + $dcpu->{nice} + $dcpu->{system};
   }
}

1;
