File Coverage

blib/lib/Net/Prometheus/ProcessCollector/linux.pm
Criterion Covered Total %
statement 50 50 100.0
branch 11 18 61.1
condition 2 5 40.0
subroutine 9 9 100.0
pod 1 2 50.0
total 73 84 86.9


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2016 -- leonerd@leonerd.org.uk
5              
6             package Net::Prometheus::ProcessCollector::linux;
7              
8 6     6   76118 use strict;
  6         22  
  6         181  
9 6     6   45 use warnings;
  6         10  
  6         220  
10 6     6   33 use base qw( Net::Prometheus::ProcessCollector );
  6         28  
  6         1170  
11              
12             our $VERSION = '0.11';
13              
14             use constant {
15 6         5052 TICKS_PER_SEC => 100,
16             BYTES_PER_PAGE => 4096,
17 6     6   43 };
  6         11  
18              
19             =head1 NAME
20              
21             C - Process Collector for F OS
22              
23             =head1 SYNOPSIS
24              
25             use Net::Prometheus;
26             use Net::Prometheus::ProcessCollector::linux;
27              
28             my $prometheus = Net::Prometheus->new;
29              
30             $prometheus->register( Net::Prometheus::ProcessCollector::linux->new );
31              
32             =head1 DESCRIPTION
33              
34             This class provides a L collector instance to provide
35             process-wide metrics for a process running on the F operating system.
36              
37             At collection time, if the requested process does not exist, no metrics are
38             returned.
39              
40             =head2 Other Process Collection
41              
42             The C argument allows the collector to collect from processes other than
43             the one actually running the code.
44              
45             Note also that scraping processes owned by other users may not be possible for
46             non-root users. In particular, most systems do not let non-root users see the
47             L directory of processes they don't own. In this case, the
48             C metric will not be returned.
49              
50             =cut
51              
52             =head1 CONSTRUCTOR
53              
54             =head2 new
55              
56             $collector = Net::Prometheus::ProcessCollector::linux->new( %args )
57              
58             As well as the default arguments supported by
59             L, the following extra named arguments are
60             recognised:
61              
62             =over
63              
64             =item pid => STR
65              
66             The numerical PID to collect information about; defaults to the string
67             C<"self"> allowing the exporter to collect information about itself, even over
68             fork calls.
69              
70             If the collector is collecting from C<"self"> or from a numerical PID that
71             matches its own PID, then it will subtract 1 from the count of open file
72             handles, to account for the C handle being used to collect that
73             count. If it is collecting a different process, it will not.
74              
75             =back
76              
77             =cut
78              
79             my $BOOTTIME;
80              
81             sub new
82             {
83 6     6 1 2994 my $class = shift;
84 6         18 my %args = @_;
85              
86             # To report process_start_time_seconds correctly, we need the machine boot
87             # time
88 6 100       22 if( !defined $BOOTTIME ) {
89 5         15 foreach my $line ( do { open my $fh, "<", "/proc/stat"; <$fh> } ) {
  5         304  
  5         600  
90 100 100       243 next unless $line =~ m/^btime /;
91 5         36 $BOOTTIME = +( split m/\s+/, $line )[1];
92 5         28 last;
93             }
94             }
95              
96 6         80 my $self = $class->__new( %args );
97              
98 6   50     59 $self->{pid} = $args{pid} || "self";
99              
100 6         39 return $self;
101             }
102              
103             sub _read_procfile
104             {
105 18     18   29 my $self = shift;
106 18         45 my ( $path ) = @_;
107              
108 18 50       907 open my $fh, "<", "/proc/$self->{pid}/$path" or return;
109 18         1017 return <$fh>;
110             }
111              
112             sub _open_fds
113             {
114 9     9   21 my $self = shift;
115              
116 9         23 my $pid = $self->{pid};
117              
118 9 50       404 opendir my $dirh, "/proc/$pid/fd" or return undef;
119 9         287 my $count = ( () = readdir $dirh );
120              
121 9 50 33     64 $count -= 1 if $pid eq "self" or $pid == $$; # subtract 1 for $dirh itself
122              
123 9         123 return $count;
124             }
125              
126             sub _limit_fds
127             {
128 9     9   19 my $self = shift;
129 9         33 my $line = ( grep m/^Max open files/, $self->_read_procfile( "limits" ) )[0];
130 9 50       64 defined $line or return undef;
131              
132             # Max open files $SOFT $HARD
133 9         81 return +( split m/\s+/, $line )[3];
134             }
135              
136             sub collect
137             {
138 9     9 0 1153 my $self = shift;
139              
140 9         30 my $statline = $self->_read_procfile( "stat" );
141 9 50       55 defined $statline or return; # process missing
142              
143             # /proc/PID/stat contains PID (COMM) more fields here
144 9         270 my @statfields = split( m/\s+/,
145             ( $statline =~ m/\)\s+(.*)/ )[0]
146             );
147              
148 9         65 my $utime = $statfields[11] / TICKS_PER_SEC;
149 9         26 my $stime = $statfields[12] / TICKS_PER_SEC;
150 9         25 my $starttime = $statfields[19] / TICKS_PER_SEC;
151 9         19 my $vsize = $statfields[20];
152 9         25 my $rss = $statfields[21] * BYTES_PER_PAGE;
153              
154 9         33 my $open_fds = $self->_open_fds;
155              
156 9         39 my $limit_fds = $self->_limit_fds;
157              
158             return
159 9 50       77 $self->_make_metric( cpu_user_seconds_total => $utime,
    50          
160             "counter", "Total user CPU time spent in seconds" ),
161             $self->_make_metric( cpu_system_seconds_total => $stime,
162             "counter", "Total system CPU time spent in seconds" ),
163             $self->_make_metric( cpu_seconds_total => $utime + $stime,
164             "counter", "Total user and system CPU time spent in seconds" ),
165              
166             $self->_make_metric( virtual_memory_bytes => $vsize,
167             "gauge", "Virtual memory size in bytes" ),
168             $self->_make_metric( resident_memory_bytes => $rss,
169             "gauge", "Resident memory size in bytes" ),
170              
171             ( defined $open_fds ?
172             $self->_make_metric( open_fds => $open_fds,
173             "gauge", "Number of open file handles" ) :
174             () ),
175             ( defined $limit_fds ?
176             $self->_make_metric( max_fds => $limit_fds,
177             "gauge", "Maximum number of allowed file handles" ) :
178             () ),
179              
180             $self->_make_metric( start_time_seconds => $BOOTTIME + $starttime,
181             "gauge", "Unix epoch time the process started at" ),
182              
183             # TODO: consider some stats out of /proc/PID/io
184             }
185              
186             =head1 AUTHOR
187              
188             Paul Evans
189              
190             =cut
191              
192             0x55AA;