File Coverage

blib/lib/Net/Prometheus/ProcessCollector/linux.pm
Criterion Covered Total %
statement 68 68 100.0
branch 16 28 57.1
condition 2 5 40.0
subroutine 11 11 100.0
pod 1 2 50.0
total 98 114 85.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-2026 -- leonerd@leonerd.org.uk
5              
6             package Net::Prometheus::ProcessCollector::linux 0.16;
7              
8 6     6   183914 use v5.20;
  6         27  
9 6     6   75 use warnings;
  6         12  
  6         409  
10 6     6   57 use base qw( Net::Prometheus::ProcessCollector );
  6         13  
  6         1331  
11              
12 6     6   57 use feature qw( signatures );
  6         12  
  6         1041  
13 6     6   43 no warnings qw( experimental::signatures );
  6         41  
  6         338  
14              
15             use constant {
16 6         8343 TICKS_PER_SEC => 100,
17             BYTES_PER_PAGE => 4096,
18 6     6   42 };
  6         15  
19              
20             =head1 NAME
21              
22             C - Process Collector for F OS
23              
24             =head1 SYNOPSIS
25              
26             =for highlighter language=perl
27              
28             use Net::Prometheus;
29             use Net::Prometheus::ProcessCollector::linux;
30              
31             my $prometheus = Net::Prometheus->new;
32              
33             $prometheus->register( Net::Prometheus::ProcessCollector::linux->new );
34              
35             =head1 DESCRIPTION
36              
37             This class provides a L collector instance to provide
38             process-wide metrics for a process running on the F operating system.
39              
40             At collection time, if the requested process does not exist, no metrics are
41             returned.
42              
43             =head2 Other Process Collection
44              
45             The C argument allows the collector to collect from processes other than
46             the one actually running the code.
47              
48             Note also that scraping processes owned by other users may not be possible for
49             non-root users. In particular, most systems do not let non-root users see the
50             F directory of processes they don't own. In this case, the
51             C metric will not be returned.
52              
53             =cut
54              
55             =head1 CONSTRUCTOR
56              
57             =head2 new
58              
59             $collector = Net::Prometheus::ProcessCollector::linux->new( %args )
60              
61             As well as the default arguments supported by
62             L, the following extra named arguments are
63             recognised:
64              
65             =over
66              
67             =item pid => STR
68              
69             The numerical PID to collect information about; defaults to the string
70             C<"self"> allowing the exporter to collect information about itself, even over
71             fork calls.
72              
73             If the collector is collecting from C<"self"> or from a numerical PID that
74             matches its own PID, then it will subtract 1 from the count of open file
75             handles, to account for the C handle being used to collect that
76             count. If it is collecting a different process, it will not.
77              
78             =back
79              
80             =cut
81              
82             my $BOOTTIME;
83              
84 6         43 sub new ( $class, %args )
85 6     6 1 189742 {
  6         18  
  6         14  
86             # To report process_start_time_seconds correctly, we need the machine boot
87             # time
88 6 100       26 if( !defined $BOOTTIME ) {
89 5         13 foreach my $line ( do { open my $fh, "<", "/proc/stat"; <$fh> } ) {
  5         411  
  5         733  
90 85 100       226 next unless $line =~ m/^btime /;
91 5         57 $BOOTTIME = +( split m/\s+/, $line )[1];
92 5         16 last;
93             }
94             }
95              
96 6         153 my $self = $class->__new( %args );
97              
98 6   50     87 $self->{pid} = $args{pid} || "self";
99              
100 6         47 return $self;
101             }
102              
103 27         77 sub _read_procfile ( $self, $path )
104 27     27   50 {
  27         55  
  27         47  
105 27 50       1729 open my $fh, "<", "/proc/$self->{pid}/$path" or return;
106 27         1623 return <$fh>;
107             }
108              
109             sub _open_fds ( $self )
110 9     9   22 {
  9         18  
  9         20  
111 9         45 my $pid = $self->{pid};
112              
113 9 50       560 opendir my $dirh, "/proc/$pid/fd" or return undef;
114 9         737 my $count = ( () = readdir $dirh );
115              
116 9 50 33     71 $count -= 1 if $pid eq "self" or $pid == $$; # subtract 1 for $dirh itself
117              
118 9         102 return $count;
119             }
120              
121             sub _limit_fds ( $self )
122 9     9   21 {
  9         18  
  9         15  
123 9         36 my $line = ( grep m/^Max open files/, $self->_read_procfile( "limits" ) )[0];
124 9 50       64 defined $line or return undef;
125              
126             # Max open files $SOFT $HARD
127 9         57 return +( split m/\s+/, $line )[3];
128             }
129              
130 9         20 sub collect ( $self, $opts = undef )
131 9     9 0 8324 {
  9         21  
  9         19  
132 9         33 my $statline = $self->_read_procfile( "stat" );
133 9 50       54 defined $statline or return; # process missing
134              
135             # /proc/PID/stat contains PID (COMM) more fields here
136 9         194 my @statfields = split( m/\s+/,
137             ( $statline =~ m/\)\s+(.*)/ )[0]
138             );
139              
140 9         57 my $utime = $statfields[11] / TICKS_PER_SEC;
141 9         24 my $stime = $statfields[12] / TICKS_PER_SEC;
142 9         26 my $starttime = $statfields[19] / TICKS_PER_SEC;
143 9         38 my $vsize = $statfields[20];
144 9         46 my $rss = $statfields[21] * BYTES_PER_PAGE;
145              
146 9         36 my $open_fds = $self->_open_fds;
147              
148 9         42 my $limit_fds = $self->_limit_fds;
149              
150 9 50       35 my %io_stats = map { m/^(\S+): (\d+)$/ ? ( $1, $2 ) : () } $self->_read_procfile( "io" );
  63         433  
151              
152             return
153             $self->_make_metric( cpu_user_seconds_total => $utime,
154             "counter", "Total user CPU time spent in seconds" ),
155             $self->_make_metric( cpu_system_seconds_total => $stime,
156             "counter", "Total system CPU time spent in seconds" ),
157             $self->_make_metric( cpu_seconds_total => $utime + $stime,
158             "counter", "Total user and system CPU time spent in seconds" ),
159              
160             $self->_make_metric( virtual_memory_bytes => $vsize,
161             "gauge", "Virtual memory size in bytes" ),
162             $self->_make_metric( resident_memory_bytes => $rss,
163             "gauge", "Resident memory size in bytes" ),
164              
165             ( defined $open_fds ?
166             $self->_make_metric( open_fds => $open_fds,
167             "gauge", "Number of open file handles" ) :
168             () ),
169             ( defined $limit_fds ?
170             $self->_make_metric( max_fds => $limit_fds,
171             "gauge", "Maximum number of allowed file handles" ) :
172             () ),
173              
174             $self->_make_metric( start_time_seconds => $BOOTTIME + $starttime,
175             "gauge", "Unix epoch time the process started at" ),
176              
177             # The following at not standard Prometheus ones, I just made up the names
178             ( defined $io_stats{rchar} ?
179             $self->_make_metric( read_bytes_total => $io_stats{rchar},
180             "counter", "Total bytes read" ) :
181             () ),
182             ( defined $io_stats{wchar} ?
183             $self->_make_metric( write_bytes_total => $io_stats{wchar},
184             "counter", "Total bytes written" ) :
185             () ),
186             ( defined $io_stats{syscr} ?
187             $self->_make_metric( read_calls_total => $io_stats{syscr},
188             "counter", "Total system calls for reading" ) :
189             () ),
190             ( defined $io_stats{syscw} ?
191             $self->_make_metric( write_calls_total => $io_stats{syscw},
192 9 50       115 "counter", "Total system calls for writing" ) :
    50          
    50          
    50          
    50          
    50          
193             () ),
194             }
195              
196             =head1 AUTHOR
197              
198             Paul Evans
199              
200             =cut
201              
202             0x55AA;