#  You may distribute under the terms of either the GNU General Public License
#  or the Artistic License (the same terms as Perl itself)
#
#  (C) Paul Evans, 2016 -- leonerd@leonerd.org.uk

package Net::Prometheus::ProcessCollector::linux;

use strict;
use warnings;

our $VERSION = '0.04';

use Net::Prometheus::Types qw( MetricSamples Sample );

use constant {
   TICKS_PER_SEC  => 100,
   BYTES_PER_PAGE => 4096,
};

=head1 NAME

C<Net::Prometheus::ProcessCollector::linux> - Process Collector for F<linux> OS

=head1 SYNOPSIS

   use Net::Prometheus;
   use Net::Prometheus::ProcessCollector::linux;

   my $prometheus = Net::Prometheus->new;

   $prometheus->register( Net::Prometheus::ProcessCollector::linux->new );

=head1 DESCRIPTION

This class provides a L<Net::Prometheus> collector instance to provide
process-wide metrics for a process running on the F<linux> operating system.

=cut

my $BOOTTIME;

sub new
{
   my $class = shift;
   # TODO: configurable PID

   # To report process_start_time_seconds correctly, we need the machine boot
   # time
   if( !defined $BOOTTIME ) {
      foreach my $line ( do { open my $fh, "<", "/proc/stat"; <$fh> } ) {
         next unless $line =~ m/^btime /;
         $BOOTTIME = +( split m/\s+/, $line )[1];
         last;
      }
   }

   return bless {}, $class;
}

sub _read_procfile
{
   my $self = shift;
   my ( $path ) = @_;

   open my $fh, "<", "/proc/self/$path" or return;
   return <$fh>;
}

sub _open_fds
{
   my $self = shift;

   opendir my $dirh, "/proc/self/fd" or return -1;
   return ( () = readdir $dirh ) - 1; # subtract 1 for $dirh itself
}

sub _limit_fds
{
   my $self = shift;
   my $line = ( grep m/^Max open files/, $self->_read_procfile( "limits" ) )[0];
   # Max open files  $SOFT  $HARD
   return +( split m/\s+/, $line )[3];
}

sub collect
{
   my $self = shift;

   my $statline = $self->_read_procfile( "stat" );
   # /proc/PID/stat contains PID (COMM) more fields here
   my @statfields = split( m/\s+/,
      ( $statline =~ m/\)\s+(.*)/ )[0]
   );

   my $utime     = $statfields[11] / TICKS_PER_SEC;
   my $stime     = $statfields[12] / TICKS_PER_SEC;
   my $starttime = $statfields[19] / TICKS_PER_SEC;
   my $vsize     = $statfields[20];
   my $rss       = $statfields[21] * BYTES_PER_PAGE;

   return
      MetricSamples( "process_cpu_user_seconds_total",
         counter => "Total user CPU time spent in seconds",
         [ Sample( "process_cpu_user_seconds_total", [], $utime ) ] ),
      MetricSamples( "process_cpu_system_seconds_total",
         counter => "Total system CPU time spent in seconds",
         [ Sample( "process_cpu_system_seconds_total", [], $stime ) ] ),
      MetricSamples( "process_cpu_seconds_total",
         counter => "Total user and system CPU time spent in seconds",
         [ Sample( "process_cpu_seconds_total", [], $utime + $stime ) ] ),

      MetricSamples( "process_virtual_memory_bytes",
         gauge => "Virtual memory size in bytes",
         [ Sample( "process_virtual_memory_bytes", [], $vsize ) ] ),
      MetricSamples( "process_resident_memory_bytes",
         gauge => "Resident memory size in bytes",
         [ Sample( "process_resident_memory_bytes", [], $rss ) ] ),

      MetricSamples( "process_open_fds",
         gauge => "Number of open file handles",
         [ Sample( "process_open_fds", [], $self->_open_fds ) ] ),
      MetricSamples( "process_max_fds",
         gauge => "Maximum number of allowed file handles",
         [ Sample( "process_max_fds", [], $self->_limit_fds ) ] ),

      MetricSamples( "process_start_time_seconds",
         gauge => "Unix epoch time the process started at",
         [ Sample( "process_start_time_seconds", [], $BOOTTIME + $starttime ) ] ),

      # TODO: consider some stats out of /proc/PID/io
}

=head1 AUTHOR

Paul Evans <leonerd@leonerd.org.uk>

=cut

0x55AA;
