File Coverage

blib/lib/OSLV/Monitor/Backends/cgroups.pm
Criterion Covered Total %
statement 26 363 7.1
branch 0 166 0.0
condition 0 159 0.0
subroutine 9 15 60.0
pod 3 6 50.0
total 38 709 5.3


line stmt bran cond sub pod time code
1             package OSLV::Monitor::Backends::cgroups;
2              
3 1     1   78233 use 5.006;
  1         3  
4 1     1   4 use strict;
  1         1  
  1         22  
5 1     1   3 use warnings;
  1         9  
  1         39  
6 1     1   489 use JSON;
  1         10113  
  1         5  
7 1     1   453 use Clone 'clone';
  1         339  
  1         58  
8 1     1   408 use File::Slurp;
  1         27919  
  1         69  
9 1     1   488 use IO::Interface::Simple;
  1         14879  
  1         32  
10 1     1   1858 use Math::BigInt;
  1         32018  
  1         4  
11 1     1   22971 use Scalar::Util qw(looks_like_number);
  1         3  
  1         4397  
12              
13             =head1 NAME
14              
15             OSLV::Monitor::Backends::cgroups - Backend for Linux cgroups.
16              
17             =head1 VERSION
18              
19             Version 1.0.3
20              
21             =cut
22              
23             our $VERSION = '1.0.3';
24              
25             =head1 SYNOPSIS
26              
27             use OSLV::Monitor::Backends::cgroups;
28              
29             my $backend = OSLV::Monitor::Backends::cgroups->new;
30              
31             my $usable=$backend->usable;
32             if ( $usable ){
33             $return_hash_ref=$backend->run;
34             }
35              
36             The cgroup to name mapping is done like below.
37              
38             systemd -> s_$name
39             user -> u_$name
40             docker -> d_$name
41             podman -> p_$name
42             anything else -> $name
43              
44             Anything else is formed like below.
45              
46             $cgroup =~ s/^0\:\:\///;
47             $cgroup =~ s/\/.*//;
48              
49             The following ps to stats mapping are as below.
50              
51             %cpu -> percent-cpu
52             %mem -> percent-memory
53             rss -> rss
54             vsize -> virtual-size
55             trs -> text-size
56             drs -> data-size
57             size -> size
58              
59             "procs" is a total number of procs in that cgroup.
60              
61             The rest of the values are pulled from the following files with
62             the names kept as is.
63              
64             cpu.stat
65             io.stat
66             memory.stat
67              
68             The following mappings are done though.
69              
70             pgfault -> minor-faults
71             pgmajfault -> major-faults
72             usage_usec -> cpu-time
73             system_usec -> system-time
74             user_usec -> user-time
75             throttled_usec -> throttled-time
76             burst_usec -> burst-time
77              
78             =head2 METHODS
79              
80             =head2 new
81              
82             Initiates the backend object.
83              
84             my $backend=OSLV::MOnitor::Backend::cgroups->new(obj=>$obj)
85              
86             - base_dir :: Path to use for the base dir, where the proc/cgroup
87             cache, linux_cache.json, is is created.
88             Default :: /var/cache/oslv_monitor
89              
90             - obj :: The OSLVM::Monitor object.
91              
92             - time_divider :: What to use for "usec" to sec conversion. While normally
93             the usec counters are microseconds, sometimes the value is in
94             nanoseconds, despit the name.
95             Default :: 1000000
96              
97             =cut
98              
99             sub new {
100 0     0 1   my ( $blank, %opts ) = @_;
101              
102 0 0         if ( !defined( $opts{base_dir} ) ) {
103 0           $opts{base_dir} = '/var/cache/oslv_monitor';
104             }
105              
106 0 0         if ( !defined( $opts{time_divider} ) ) {
107 0           $opts{time_divider} = 1000000;
108             } else {
109 0 0         if ( !looks_like_number( $opts{time_divider} ) ) {
110 0           die('time_divider is not a number');
111             }
112             }
113              
114 0 0         if ( !defined( $opts{obj} ) ) {
    0          
115 0           die('$opts{obj} is undef');
116             } elsif ( ref( $opts{obj} ) ne 'OSLV::Monitor' ) {
117 0           die('ref $opts{obj} is not OSLV::Monitor');
118             }
119              
120             my $self = {
121             time_divider => $opts{time_divider},
122             version => 1,
123             cgroupns_usable => 1,
124             mappings => {},
125             podman_mapping => {},
126             podman_info => {},
127             docker_mapping => {},
128             docker_info => {},
129             uid_mapping => {},
130             obj => $opts{obj},
131 0           cache_file => $opts{base_dir} . '/linux_cache.json',
132             counters => {
133             'cpu-time' => 1,
134             'system-time' => 1,
135             'user-time' => 1,
136             'throttled-time' => 1,
137             'burst-time' => 1,
138             'core_sched.force_idle-time' => 1,
139             'read-blocks' => 1,
140             'major-faults' => 1,
141             'involuntary-context-switches' => 1,
142             'minor-faults' => 1,
143             'received-messages' => 1,
144             'sent-messages' => 1,
145             'swaps' => 1,
146             'voluntary-context-switches' => 1,
147             'written-blocks' => 1,
148             'copy-on-write-faults' => 1,
149             'signals-taken' => 1,
150             'rbytes' => 1,
151             'wbytes' => 1,
152             'dbytes' => 1,
153             'rios' => 1,
154             'wios' => 1,
155             'dios' => 1,
156             'pgactivate' => 1,
157             'pgdeactivate' => 1,
158             'pglazyfree' => 1,
159             'pglazyfreed' => 1,
160             'pgrefill' => 1,
161             'pgscan' => 1,
162             'pgscan_direct' => 1,
163             'pgscan_khugepaged' => 1,
164             'pgscan_kswapd' => 1,
165             'pgsteal' => 1,
166             'pgsteal_direct' => 1,
167             'pgsteal_khugepaged' => 1,
168             'pgsteal_kswapd' => 1,
169             'thp_fault_alloc' => 1,
170             'thp_collapse_alloc' => 1,
171             'thp_swpout' => 1,
172             'thp_swpout_fallback' => 1,
173             'system_usec' => 1,
174             'usage_usec' => 1,
175             'user_usec' => 1,
176             'zswpin' => 1,
177             'zswpout' => 1,
178             'zswpwb' => 1,
179             },
180             cache => {},
181             new_cache => {},
182             };
183 0           bless $self;
184              
185 0           return $self;
186             } ## end sub new
187              
188             =head2 run
189              
190             $return_hash_ref=$backend->run(obj=>$obj);
191              
192             =cut
193              
194             sub run {
195 0     0 1   my $self = $_[0];
196              
197 0           my $data = {
198             errors => [],
199             oslvms => {},
200             has => {
201             'linux_mem_stats' => 1,
202             'rwdops' => 0,
203             'rwdbytes' => 0,
204             'rwdblocks' => 0,
205             'signals-taken' => 0,
206             'recv_sent_msgs' => 0,
207             'cows' => 0,
208             'stack-size' => 0,
209             'swaps' => 0,
210             'sock' => 1,
211             'burst_time' => 0,
212             'throttled_time' => 0,
213             'burst_count' => 0,
214             'throttled_count' => 0,
215             },
216             totals => {
217             procs => 0,
218             'percent-cpu' => 0,
219             'percent-memory' => 0,
220             'system-time' => 0,
221             'cpu-time' => 0,
222             'user-time' => 0,
223             rbytes => 0,
224             wbytes => 0,
225             rios => 0,
226             wios => 0,
227             dbytes => 0,
228             dios => 0,
229             'core_sched.force_idle_usec' => 0,
230             nr_periods => 0,
231             nr_throttled => 0,
232             throttled_usec => 0,
233             nr_bursts => 0,
234             burst_usec => 0,
235             anon => 0,
236             file => 0,
237             kernel => 0,
238             kernel_stack => 0,
239             pagetables => 0,
240             sec_pagetables => 0,
241             sock => 0,
242             vmalloc => 0,
243             shmem => 0,
244             zswap => 0,
245             zswapped => 0,
246             file_mapped => 0,
247             file_dirty => 0,
248             file_writeback => 0,
249             swapcached => 0,
250             anon_thp => 0,
251             file_thp => 0,
252             shmem_thp => 0,
253             inactive_anon => 0,
254             active_anon => 0,
255             inactive_file => 0,
256             active_file => 0,
257             unevictable => 0,
258             slab_reclaimable => 0,
259             slab_unreclaimable => 0,
260             slab => 0,
261             workingset_refault_anon => 0,
262             workingset_refault_file => 0,
263             workingset_activate_anon => 0,
264             workingset_activate_file => 0,
265             workingset_restore_anon => 0,
266             workingset_restore_file => 0,
267             workingset_nodereclaim => 0,
268             pgscan => 0,
269             pgsteal => 0,
270             pgscan_kswapd => 0,
271             pgscan_direct => 0,
272             pgscan_khugepaged => 0,
273             pgsteal_kswapd => 0,
274             pgsteal_direct => 0,
275             pgsteal_khugepaged => 0,
276             'minor-faults' => 0,
277             'major-faults' => 0,
278             pgrefill => 0,
279             pgactivate => 0,
280             pgdeactivate => 0,
281             pglazyfree => 0,
282             pglazyfreed => 0,
283             zswpin => 0,
284             zswpout => 0,
285             thp_fault_alloc => 0,
286             thp_collapse_alloc => 0,
287             rss => 0,
288             'data-size' => 0,
289             'text-size' => 0,
290             'size' => 0,
291             'virtual-size' => 0,
292             'elapsed-times' => 0,
293             'involuntary-context-switches' => 0,
294             'voluntary-context-switches' => 0,
295             },
296             };
297              
298 0           my $proc_cache;
299 0           my $new_cache = {};
300 0           my $cache_is_new = 0;
301 0 0         if ( -f $self->{cache_file} ) {
302 0           eval {
303 0           my $raw_cache = read_file( $self->{cache_file} );
304 0           $self->{cache} = decode_json($raw_cache);
305             };
306 0 0         if ($@) {
307             push(
308 0           @{ $data->{errors} },
309 0           'reading proc cache "' . $self->{cache_file} . '" failed... using a empty one...' . $@
310             );
311 0           $data->{cache_failure} = 1;
312 0           return $data;
313             }
314             } else {
315 0           $cache_is_new = 1;
316             }
317              
318 0           my $base_stats = {
319             procs => 0,
320             'percent-cpu' => 0,
321             'percent-memory' => 0,
322             'system-time' => 0,
323             'cpu-time' => 0,
324             'user-time' => 0,
325             rbytes => 0,
326             wbytes => 0,
327             rios => 0,
328             wios => 0,
329             dbytes => 0,
330             dios => 0,
331             'core_sched.force_idle_usec' => 0,
332             nr_periods => 0,
333             nr_throttled => 0,
334             throttled_usec => 0,
335             nr_bursts => 0,
336             burst_usec => 0,
337             anon => 0,
338             file => 0,
339             kernel => 0,
340             kernel_stack => 0,
341             pagetables => 0,
342             sec_pagetables => 0,
343             sock => 0,
344             vmalloc => 0,
345             shmem => 0,
346             zswap => 0,
347             zswapped => 0,
348             file_mapped => 0,
349             file_dirty => 0,
350             file_writeback => 0,
351             swapcached => 0,
352             anon_thp => 0,
353             file_thp => 0,
354             shmem_thp => 0,
355             inactive_anon => 0,
356             active_anon => 0,
357             inactive_file => 0,
358             active_file => 0,
359             unevictable => 0,
360             slab_reclaimable => 0,
361             slab_unreclaimable => 0,
362             slab => 0,
363             workingset_refault_anon => 0,
364             workingset_refault_file => 0,
365             workingset_activate_anon => 0,
366             workingset_activate_file => 0,
367             workingset_restore_anon => 0,
368             workingset_restore_file => 0,
369             workingset_nodereclaim => 0,
370             pgscan => 0,
371             pgsteal => 0,
372             pgscan_kswapd => 0,
373             pgscan_direct => 0,
374             pgscan_khugepaged => 0,
375             pgsteal_kswapd => 0,
376             pgsteal_direct => 0,
377             pgsteal_khugepaged => 0,
378             'minor-faults' => 0,
379             'major-faults' => 0,
380             pgrefill => 0,
381             pgactivate => 0,
382             pgdeactivate => 0,
383             pglazyfree => 0,
384             pglazyfreed => 0,
385             zswpin => 0,
386             zswpout => 0,
387             thp_fault_alloc => 0,
388             thp_collapse_alloc => 0,
389             rss => 0,
390             'data-size' => 0,
391             'text-size' => 0,
392             'size' => 0,
393             'virtual-size' => 0,
394             'elapsed-times' => 0,
395             'involuntary-context-switches' => 0,
396             'voluntary-context-switches' => 0,
397             'ip' => [],
398             'path' => [],
399             };
400              
401 0           my $stat_mapping = {
402             'pgmajfault' => 'major-faults',
403             'pgfault' => 'minor-faults',
404             'usage_usec' => 'cpu-time',
405             'user_usec' => 'user-time',
406             'system_usec' => 'system-time',
407             'throttled_usec' => 'throttled-time',
408             'burst_usec' => 'burst-time',
409             'core_sched.force_idle_usec' => 'core_sched.force_idle-time',
410             };
411              
412             #
413             # get podman/docker ID to name mappings
414             #
415 0           my @podman_compatible = ( 'docker', 'podman' );
416 0           foreach my $cgroup_jank_type (@podman_compatible) {
417 0           my $podman_output;
418 0 0         if ( $cgroup_jank_type eq 'podman' ) {
    0          
419 0           $podman_output = `podman ps --format json 2> /dev/null`;
420             } elsif ( $cgroup_jank_type eq 'docker' ) {
421             # --no-trunc is needed as for some unfathonable reason it truncates even when outputting json
422 0           $podman_output = `docker ps --no-trunc --format json 2> /dev/null`;
423             # returns a series of json entries seperated by a newline... the following expects it as
424             # a array like podman outputs
425 0           my @podman_outputA = split( /\n/, $podman_output );
426             # and it is now a array of hashes as expected
427 0           $podman_output = '[' . join( ',', @podman_outputA ) . ']';
428             }
429 0 0         if ( $? == 0 ) {
430 0           my $podman_parsed;
431 0           eval { $podman_parsed = decode_json($podman_output); };
  0            
432 0 0 0       if ( defined($podman_parsed) && ref($podman_parsed) eq 'ARRAY' ) {
433 0           foreach my $pod ( @{$podman_parsed} ) {
  0            
434 0           my $pod_id;
435 0 0         if ( defined( $pod->{'Id'} ) ) {
    0          
436 0           $pod_id = $pod->{'Id'};
437             } elsif ( defined( $pod->{'ID'} ) ) {
438 0           $pod_id = $pod->{'ID'};
439             }
440              
441 0           my $pod_name;
442 0 0 0       if ( defined( $pod->{'PodName'} )
    0 0        
    0 0        
      0        
      0        
443             && ( $pod->{'PodName'} ne '' ) )
444             {
445 0           $pod_name = $pod->{'PodName'};
446             } elsif ( defined( $pod->{'Names'} )
447             && ( ref( $pod->{'Names'} ) eq '' ) )
448             {
449 0           $pod_name = $pod->{'Names'};
450             } elsif ( defined( $pod->{'Names'} )
451             && ( ref( $pod->{'Names'} ) eq 'ARRAY' )
452             && defined( $pod->{'Names'}[0] )
453             && ( ref( $pod->{'Names'}[0] ) eq '' ) )
454             {
455 0           $pod_name = $pod->{'Names'}[0];
456             }
457              
458 0 0 0       if ( defined($pod_id) && defined($pod_name) ) {
459             $self->{ $cgroup_jank_type . '_mapping' }{$pod_id} = {
460             name => $pod_name,
461             Networks => $pod->{Networks},
462 0           };
463 0           my $inspect_output = `$cgroup_jank_type inspect $pod_id 2> /dev/null`;
464 0           my $inspect_parsed;
465 0           $self->{ $cgroup_jank_type . '_info' }{$pod_id} = { ip => [] };
466 0           eval { $inspect_parsed = decode_json($inspect_output) };
  0            
467 0 0 0       if ( defined($inspect_parsed)
      0        
      0        
      0        
      0        
      0        
      0        
468             && ref($inspect_parsed) eq 'ARRAY'
469             && defined( $inspect_parsed->[0] )
470             && ref( $inspect_parsed->[0] ) eq 'HASH'
471             && defined( $inspect_parsed->[0]{NetworkSettings} )
472             && ref( $inspect_parsed->[0]{NetworkSettings} ) eq 'HASH'
473             && defined( $inspect_parsed->[0]{NetworkSettings}{Networks} )
474             && ref( $inspect_parsed->[0]{NetworkSettings}{Networks} ) eq 'HASH' )
475             {
476 0           my @podman_networks = keys( %{ $inspect_parsed->[0]{NetworkSettings}{Networks} } );
  0            
477 0           foreach my $network_to_process (@podman_networks) {
478             my $current_network
479 0           = $inspect_parsed->[0]{NetworkSettings}{Networks}{$network_to_process};
480 0 0 0       if ( ref($current_network) eq 'HASH'
481             && ref( $current_network->{IPAddress} ) eq '' )
482             {
483             my $net_work_info = {
484             ip => $current_network->{IPAddress},
485 0           gw => undef,
486             gw_if => undef,
487             mac => undef,
488             if => undef,
489             };
490 0 0 0       if ( defined( $current_network->{Gateway} )
491             && ref( $current_network->{Gateway} ) eq '' )
492             {
493 0           $net_work_info->{gw} = $current_network->{Gateway};
494             }
495 0 0 0       if ( defined( $current_network->{MacAddress} )
496             && ref( $current_network->{MacAddress} ) eq '' )
497             {
498 0           $net_work_info->{mac} = $current_network->{MacAddress};
499             }
500 0 0 0       if ( defined( $current_network->{NetworkID} )
501             && ref( $current_network->{NetworkID} ) eq '' )
502             {
503 0           my $network_id = $current_network->{NetworkID};
504 0           my $network_inspect_output
505             = `$cgroup_jank_type network inspect $network_id 2> /dev/null`;
506 0           my $network_inspect_parsed;
507 0           eval { $network_inspect_parsed = decode_json($network_inspect_output) };
  0            
508 0 0 0       if ( defined($network_inspect_parsed)
      0        
      0        
      0        
      0        
509             && ref($network_inspect_parsed) eq 'ARRAY'
510             && defined( $network_inspect_parsed->[0] )
511             && ref( $network_inspect_parsed->[0] ) eq 'HASH'
512             && defined( $network_inspect_parsed->[0]{network_interface} )
513             && ref( $network_inspect_parsed->[0]{network_interface} ) eq '' )
514             {
515 0           $net_work_info->{if} = $network_inspect_parsed->[0]{network_interface};
516             }
517             } ## end if ( defined( $current_network->{NetworkID...}))
518 0 0 0       if ( defined( $net_work_info->{if} )
519             && defined( $net_work_info->{ip} ) )
520             {
521 0           my $ip_r_g_output
522             = `ip r g from $net_work_info->{ip} iif $net_work_info->{if} 8.8.8.8`;
523 0 0         if ( $? == 0 ) {
524 0           my @ip_r_g_output_split = split( /\n/, $ip_r_g_output );
525 0 0         if ( defined( $ip_r_g_output_split[0] ) ) {
526 0           $ip_r_g_output_split[0] =~ s/^.*[\ \t]+dev[\ \t]+//;
527 0           $ip_r_g_output_split[0] =~ s/[\ \t].*$//;
528 0           $net_work_info->{gw_if} = $ip_r_g_output_split[0];
529             }
530             }
531             } ## end if ( defined( $net_work_info->{if} ) && defined...)
532 0           push( @{ $self->{ $cgroup_jank_type . '_info' }{$pod_name}{ip} }, $net_work_info );
  0            
533             } ## end if ( ref($current_network) eq 'HASH' && ref...)
534             } ## end foreach my $network_to_process (@podman_networks)
535             } ## end if ( defined($inspect_parsed) && ref($inspect_parsed...))
536             } ## end if ( defined($pod_id) && defined($pod_name...))
537             } ## end foreach my $pod ( @{$podman_parsed} )
538             } ## end if ( defined($podman_parsed) && ref($podman_parsed...))
539             } ## end if ( $? == 0 )
540             } ## end foreach my $cgroup_jank_type (@podman_compatible)
541              
542             #
543             # gets of procs for finding a list of containers
544             #
545             # my $ps_output = `ps -haxo pid,uid,gid,cgroupns,%cpu,%mem,rss,vsize,trs,drs,size,cgroup 2> /dev/null`;
546             # if ( $? != 0 ) {
547             # $self->{cgroupns_usable} = 0;
548 0           my $ps_output = `ps -haxo pid,uid,gid,%cpu,%mem,rss,vsize,trs,drs,size,etimes,cgroup 2> /dev/null`;
549             # }
550 0           my @ps_output_split = split( /\n/, $ps_output );
551 0           my %found_cgroups;
552             my %cgroups_percpu;
553 0           my %cgroups_permem;
554 0           my %cgroups_procs;
555 0           my %cgroups_rss;
556 0           my %cgroups_vsize;
557 0           my %cgroups_trs;
558 0           my %cgroups_drs;
559 0           my %cgroups_size;
560 0           my %cgroups_etimes;
561 0           my %cgroups_invvol_ctxt_switches;
562 0           my %cgroups_vol_ctxt_switches;
563              
564 0           foreach my $line (@ps_output_split) {
565 0           $line =~ s/^\s+//;
566 0           my $vol_ctxt_switches = 0;
567 0           my $invol_ctxt_switches = 0;
568 0           my ( $pid, $uid, $gid, $cgroupns, $percpu, $permem, $rss, $vsize, $trs, $drs, $size, $etimes, $cgroup );
569             # if ( $self->{cgroupns_usable} ) {
570             # ( $pid, $uid, $gid, $cgroupns, $percpu, $permem, $rss, $vsize, $trs, $drs, $size, $etimes, $cgroup )#
571             # = split( /\s+/, $line );
572             # } else {
573 0           ( $pid, $uid, $gid, $percpu, $permem, $rss, $vsize, $trs, $drs, $size, $etimes, $cgroup )
574             = split( /\s+/, $line );
575             # }
576 0 0         if ( $cgroup =~ /^0\:\:\// ) {
577              
578 0           my $cache_name = 'proc-' . $pid . '-' . $uid . '-' . $gid . '-' . $cgroup;
579              
580 0           $found_cgroups{$cgroup} = $cgroup;
581 0           $data->{totals}{'percent-cpu'} = $data->{totals}{'percent-cpu'} + $percpu;
582 0           $data->{totals}{'percent-memory'} = $data->{totals}{'percent-memory'} + $permem;
583 0           $data->{totals}{rss} = $data->{totals}{rss} + $rss;
584 0           $data->{totals}{'virtual-size'} = $data->{totals}{'virtual-size'} + $vsize;
585 0           $data->{totals}{'text-size'} = $data->{totals}{'text-size'} + $trs;
586 0           $data->{totals}{'data-size'} = $data->{totals}{'data-size'} + $drs;
587 0           $data->{totals}{'size'} = $data->{totals}{'size'} + $size;
588 0           $data->{totals}{'elapsed-times'} = $data->{totals}{'elapsed-times'} + $etimes;
589              
590 0           eval {
591 0 0         if ( -f '/proc/' . $pid . '/status' ) {
592             my @switches_find
593 0           = grep( /voluntary\_ctxt\_switches\:/, read_file( '/proc/' . $pid . '/status' ) );
594 0           foreach my $found_switch (@switches_find) {
595 0           chomp($found_switch);
596 0           my @switch_split = split( /\:[\ \t]+/, $found_switch );
597 0 0 0       if ( defined( $switch_split[0] ) && defined( $switch_split[1] ) ) {
598 0 0         if ( $switch_split[0] eq 'voluntary_ctxt_switches' ) {
    0          
599 0           $vol_ctxt_switches = $switch_split[1];
600             } elsif ( $switch_split[0] eq 'involuntary_ctxt_switches' ) {
601 0           $invol_ctxt_switches = $switch_split[1];
602             }
603             }
604             } ## end foreach my $found_switch (@switches_find)
605             } ## end if ( -f '/proc/' . $pid . '/status' )
606             };
607 0           $vol_ctxt_switches = $self->cache_process( $cache_name, 'voluntary-context-switches', $vol_ctxt_switches );
608             $data->{totals}{'voluntary-context-switches'}
609 0           = $data->{totals}{'voluntary-context-switches'} + $vol_ctxt_switches;
610 0           $invol_ctxt_switches
611             = $self->cache_process( $cache_name, 'involuntary-context-switches', $invol_ctxt_switches );
612             $data->{totals}{'involuntary-context-switches'}
613 0           = $data->{totals}{'involuntary-context-switches'} + $invol_ctxt_switches;
614              
615 0 0         if ( !defined( $cgroups_permem{$cgroup} ) ) {
616 0           $cgroups_permem{$cgroup} = $permem;
617 0           $cgroups_percpu{$cgroup} = $percpu;
618 0           $cgroups_procs{$cgroup} = 1;
619 0           $cgroups_rss{$cgroup} = $rss;
620 0           $cgroups_vsize{$cgroup} = $vsize;
621 0           $cgroups_trs{$cgroup} = $trs;
622 0           $cgroups_drs{$cgroup} = $drs;
623 0           $cgroups_size{$cgroup} = $size;
624 0           $cgroups_etimes{$cgroup} = $etimes;
625 0           $cgroups_invvol_ctxt_switches{$cgroup} = $invol_ctxt_switches;
626 0           $cgroups_vol_ctxt_switches{$cgroup} = $vol_ctxt_switches;
627             } else {
628 0           $cgroups_permem{$cgroup} = $cgroups_permem{$cgroup} + $permem;
629 0           $cgroups_percpu{$cgroup} = $cgroups_percpu{$cgroup} + $percpu;
630 0           $cgroups_procs{$cgroup}++;
631 0           $cgroups_rss{$cgroup} = $cgroups_rss{$cgroup} + $rss;
632 0           $cgroups_vsize{$cgroup} = $cgroups_vsize{$cgroup} + $vsize;
633 0           $cgroups_trs{$cgroup} = $cgroups_trs{$cgroup} + $trs;
634 0           $cgroups_drs{$cgroup} = $cgroups_drs{$cgroup} + $drs;
635 0           $cgroups_size{$cgroup} = $cgroups_size{$cgroup} + $size;
636 0           $cgroups_etimes{$cgroup} = $cgroups_etimes{$cgroup} + $etimes;
637 0           $cgroups_invvol_ctxt_switches{$cgroup} = $cgroups_invvol_ctxt_switches{$cgroup} + $invol_ctxt_switches;
638 0           $cgroups_vol_ctxt_switches{$cgroup} = $cgroups_vol_ctxt_switches{$cgroup} + $vol_ctxt_switches;
639             } ## end else [ if ( !defined( $cgroups_permem{$cgroup} ) )]
640             } ## end if ( $cgroup =~ /^0\:\:\// )
641             } ## end foreach my $line (@ps_output_split)
642              
643             #
644             # build a list of mappings
645             #
646 0           foreach my $cgroup ( keys(%found_cgroups) ) {
647             #my $cgroupns = $found_cgroups{$cgroup};
648 0           my $map_to = $self->cgroup_mapping($cgroup);
649 0 0         if ( defined($map_to) ) {
650 0           $self->{mappings}{$cgroup} = $map_to;
651             }
652             }
653              
654             #
655             # get the stats
656             #
657 0           foreach my $cgroup ( keys( %{ $self->{mappings} } ) ) {
  0            
658 0           my $name = $self->{mappings}{$cgroup};
659              
660             # only process this cgroup if the include check returns true, otherwise ignore it
661 0 0         if ( $self->{obj}->include($name) ) {
662              
663 0           my $cache_name = 'cgroup-' . $name;
664              
665 0           $data->{oslvms}{$name} = clone($base_stats);
666              
667 0           $data->{oslvms}{$name}{'percent-cpu'} = $cgroups_percpu{$cgroup};
668 0           $data->{oslvms}{$name}{'percent-memory'} = $cgroups_permem{$cgroup};
669 0           $data->{oslvms}{$name}{procs} = $cgroups_procs{$cgroup};
670 0           $data->{totals}{procs} = $data->{totals}{procs} + $cgroups_procs{$cgroup};
671 0           $data->{oslvms}{$name}{rss} = $cgroups_rss{$cgroup};
672 0           $data->{oslvms}{$name}{'virtual-size'} = $cgroups_vsize{$cgroup};
673 0           $data->{oslvms}{$name}{'text-size'} = $cgroups_trs{$cgroup};
674 0           $data->{oslvms}{$name}{'data-size'} = $cgroups_drs{$cgroup};
675 0           $data->{oslvms}{$name}{'size'} = $cgroups_size{$cgroup};
676 0           $data->{oslvms}{$name}{'elapsed-times'} = $cgroups_etimes{$cgroup};
677              
678 0 0 0       if ( $name =~ /^p\_/ || $name =~ /^d\_/ ) {
679 0           my $container_name = $name;
680 0           $container_name =~ s/^[pd]\_//;
681 0 0         if ( $name =~ /^p\_/ ) {
    0          
682 0           $data->{oslvms}{$name}{'ip'} = $self->{podman_info}{$container_name}{ip};
683             } elsif ( $name =~ /^d\_/ ) {
684 0           $data->{oslvms}{$name}{'ip'} = $self->{docker_info}{$container_name}{ip};
685             }
686             }
687              
688 0           my $base_dir = $cgroup;
689 0           $base_dir =~ s/^0\:\://;
690 0           $base_dir = '/sys/fs/cgroup' . $base_dir;
691              
692 0           my $cpu_stats_raw;
693 0 0 0       if ( -f $base_dir . '/cpu.stat' && -r $base_dir . '/cpu.stat' ) {
694 0           eval { $cpu_stats_raw = read_file( $base_dir . '/cpu.stat' ); };
  0            
695 0 0         if ( defined($cpu_stats_raw) ) {
696 0           my @cpu_stats_split = split( /\n/, $cpu_stats_raw );
697 0           foreach my $line (@cpu_stats_split) {
698 0           my ( $stat, $value ) = split( /\s+/, $line, 2 );
699 0 0         if ( defined( $stat_mapping->{$stat} ) ) {
700 0           $stat = $stat_mapping->{$stat};
701             }
702 0 0 0       if ( defined( $data->{oslvms}{$name}{$stat} ) && defined($value) && $value =~ /[0-9\.]+/ ) {
      0        
703 0           $value = $self->cache_process( $cache_name, $stat, $value );
704 0           $data->{oslvms}{$name}{$stat} = $data->{oslvms}{$name}{$stat} + $value;
705 0           $data->{totals}{$stat} = $data->{totals}{$stat} + $value;
706 0 0         if ( $stat eq 'nr_bursts' ) {
707 0           $data->{has}{burst_count} = 1;
708             }
709 0 0         if ( $stat eq 'burst-time' ) {
710 0           $data->{has}{burst_time} = 1;
711             }
712 0 0         if ( $stat eq 'throttled-time' ) {
713 0           $data->{has}{throttled_time} = 1;
714             }
715 0 0         if ( $stat eq 'nr_throttled' ) {
716 0           $data->{has}{throttled_count} = 1;
717             }
718             } ## end if ( defined( $data->{oslvms}{$name}{$stat...}))
719             } ## end foreach my $line (@cpu_stats_split)
720             } ## end if ( defined($cpu_stats_raw) )
721             } ## end if ( -f $base_dir . '/cpu.stat' && -r $base_dir...)
722              
723 0           my $memory_stats_raw;
724 0 0 0       if ( -f $base_dir . '/memory.stat' && -r $base_dir . '/memory.stat' ) {
725 0           eval { $memory_stats_raw = read_file( $base_dir . '/memory.stat' ); };
  0            
726 0 0         if ( defined($memory_stats_raw) ) {
727 0           my @memory_stats_split = split( /\n/, $memory_stats_raw );
728 0           foreach my $line (@memory_stats_split) {
729 0           my ( $stat, $value ) = split( /\s+/, $line, 2 );
730 0 0         if ( defined( $stat_mapping->{$stat} ) ) {
731 0           $stat = $stat_mapping->{$stat};
732             }
733 0 0 0       if ( defined( $data->{oslvms}{$name}{$stat} ) && defined($value) && $value =~ /[0-9\.]+/ ) {
      0        
734 0           $value = $self->cache_process( $cache_name, $stat, $value );
735 0           $data->{oslvms}{$name}{$stat} = $data->{oslvms}{$name}{$stat} + $value;
736 0           $data->{totals}{$stat} = $data->{totals}{$stat} + $value;
737             }
738             } ## end foreach my $line (@memory_stats_split)
739             } ## end if ( defined($memory_stats_raw) )
740             } ## end if ( -f $base_dir . '/memory.stat' && -r $base_dir...)
741              
742 0           my $io_stats_raw;
743 0 0 0       if ( -f $base_dir . '/io.stat' && -r $base_dir . '/io.stat' ) {
744 0           eval { $io_stats_raw = read_file( $base_dir . '/io.stat' ); };
  0            
745 0 0         if ( defined($io_stats_raw) ) {
746 0           $data->{has}{rwdops} = 1;
747 0           $data->{has}{rwdbytes} = 1;
748 0           my @io_stats_split = split( /\n/, $io_stats_raw );
749 0           foreach my $line (@io_stats_split) {
750 0           my @line_split = split( /\s/, $line );
751 0           shift(@line_split);
752 0           foreach my $item (@line_split) {
753 0           my ( $stat, $value ) = split( /\=/, $line, 2 );
754 0 0         if ( defined( $stat_mapping->{$stat} ) ) {
755 0           $stat = $stat_mapping->{$stat};
756             }
757 0 0 0       if ( defined( $data->{oslvms}{$name}{$stat} ) && defined($value) && $value =~ /[0-9]+/ ) {
      0        
758 0           $value = $self->cache_process( $cache_name, $stat, $value );
759 0           $data->{oslvms}{$name}{$stat} = $data->{oslvms}{$name}{$stat} + $value;
760 0           $data->{totals}{$stat} = $data->{totals}{$stat} + $value;
761             }
762             } ## end foreach my $item (@line_split)
763             } ## end foreach my $line (@io_stats_split)
764             } ## end if ( defined($io_stats_raw) )
765             } ## end if ( -f $base_dir . '/io.stat' && -r $base_dir...)
766             } ## end if ( $self->{obj}->include($name) )
767             } ## end foreach my $cgroup ( keys( %{ $self->{mappings}...}))
768              
769 0           $data->{uid_mapping} = $self->{uid_mapping};
770              
771             # save the proc cache for next run
772 0           eval { write_file( $self->{cache_file}, encode_json( $self->{new_cache} ) ); };
  0            
773 0 0         if ($@) {
774 0           push( @{ $data->{errors} }, 'saving proc cache failed, "' . $self->{proc_cache} . '"... ' . $@ );
  0            
775 0           $data->{cache_failure} = 1;
776             }
777              
778 0 0         if ($cache_is_new) {
779 0           delete( $data->{oslvms} );
780 0           $data->{oslvms} = {};
781 0           my @total_keys = keys( %{ $data->{totals} } );
  0            
782 0           foreach my $total_key (@total_keys) {
783 0 0         if ( ref( $data->{totals}{$total_key} ) eq '' ) {
784 0           $data->{totals}{$total_key} = 0;
785             }
786             }
787             } ## end if ($cache_is_new)
788              
789 0           return $data;
790             } ## end sub run
791              
792             =head2 usable
793              
794             Dies if not usable.
795              
796             eval{ $backend->usable; };
797             if ( $@ ){
798             print 'Not usable because... '.$@."\n";
799             }
800              
801             =cut
802              
803             sub usable {
804 0     0 1   my $self = $_[0];
805              
806             # make sure it is freebsd
807              
808 0 0         if ( $^O !~ 'linux' ) {
809 0           die '$^O is "' . $^O . '" and not "linux"';
810             }
811              
812 0           return 1;
813             } ## end sub usable
814              
815             sub cgroup_mapping {
816 0     0 0   my $self = $_[0];
817 0           my $cgroup_name = $_[1];
818             #my $cgroupns = $_[2];
819              
820 0 0         if ( !defined($cgroup_name) ) {
821 0           return undef;
822             }
823              
824 0 0         if ( $cgroup_name eq '0::/init.scope' ) {
825 0           return 'init';
826             }
827              
828 0 0         if ( $cgroup_name =~ /^0\:\:\/system\.slice\/docker\-[a-zA-Z0-9]+\.scope/ ) {
    0          
    0          
    0          
    0          
    0          
829 0           $cgroup_name =~ s/^0\:\:\/system\.slice\/docker\-//;
830 0           $cgroup_name =~ s/\.scope.*$//;
831 0 0         if ( defined( $self->{docker_mapping}{$cgroup_name} ) ) {
832 0           return 'd_' . $self->{docker_mapping}{$cgroup_name}{name};
833             }
834 0           return 'd_' . $cgroup_name;
835             } elsif ( $cgroup_name =~ /^0\:\:\/docker\// ) {
836 0           $cgroup_name =~ s/^0\:\:\/docker\///;
837 0           $cgroup_name =~ s/\/.*$//;
838 0           return 'd_' . $cgroup_name;
839             } elsif ( $cgroup_name =~ /^0\:\:\/system\.slice\// ) {
840 0           $cgroup_name =~ s/^.*\///;
841 0           $cgroup_name =~ s/\.service$//;
842 0           return 's_' . $cgroup_name;
843             } elsif ( $cgroup_name =~ /^0\:\:\/user\.slice\// ) {
844 0           $cgroup_name =~ s/^0\:\:\/user\.slice\///;
845 0           $cgroup_name =~ s/\.slice.*$//;
846 0           $cgroup_name =~ s/^user[\-\_]//;
847              
848 0 0         if ( $cgroup_name =~ /^\d+$/ ) {
849 0           my ( $name, $passwd, $uid, $gid, $quota, $comment, $gecos, $dir, $shell, $expire ) = getpwuid($cgroup_name);
850 0 0         if ( defined($name) ) {
851 0           $self->{uid_mapping}{$cgroup_name} = {
852             name => $name,
853             gid => $gid,
854             home => $dir,
855             gecos => $gecos,
856             shell => $shell,
857             };
858             }
859             } ## end if ( $cgroup_name =~ /^\d+$/ )
860              
861 0           return 'u_' . $cgroup_name;
862             } elsif ( $cgroup_name =~ /^0\:\:\/machine\.slice\/libpod\-conmon-/ ) {
863 0           return 'libpod-conmon';
864             } elsif ( $cgroup_name =~ /^0\:\:\/machine\.slice\/libpod\-/ ) {
865 0           $cgroup_name =~ s/^^0\:\:\/machine\.slice\/libpod\-//;
866 0           $cgroup_name =~ s/\.scope.*$//;
867 0 0         if ( defined( $self->{podman_mapping}{$cgroup_name} ) ) {
868 0           return 'p_' . $self->{podman_mapping}{$cgroup_name}{name};
869             }
870 0           return 'libpod';
871             }
872              
873 0           $cgroup_name =~ s/^0\:\:\///;
874 0           $cgroup_name =~ s/\/.*//;
875 0           return $cgroup_name;
876             } ## end sub cgroup_mapping
877              
878             sub ip_to_if {
879 0     0 0   my $self = $_[0];
880 0           my $ip = $_[1];
881              
882 0 0 0       if ( !defined($ip) || ref($ip) ne '' ) {
883 0           return undef;
884             }
885              
886 0           my $if = IO::Interface::Simple->new_from_address($ip);
887              
888 0 0         if ( !defined($if) ) {
889 0           return undef;
890             }
891              
892 0           return $if->name;
893             } ## end sub ip_to_if
894              
895             sub cache_process {
896 0     0 0   my $self = $_[0];
897 0           my $name = $_[1];
898 0           my $var = $_[2];
899 0           my $new_value = $_[3];
900              
901 0 0 0       if ( !defined($name) || !defined($var) || !defined($new_value) ) {
      0        
902 0           warn('name, var, or new_value is undef');
903 0           return 0;
904             }
905              
906             # is a gauge and not a counter
907 0 0         if ( !defined( $self->{counters}{$var} ) ) {
908 0           return $new_value;
909             }
910              
911             # not seen it yet
912 0 0         if ( !defined( $self->{new_cache}{$name} ) ) {
913 0           $self->{new_cache}{$name} = {};
914             }
915 0           $self->{new_cache}{$name}{$var} = $new_value;
916              
917             # not seen it yet
918 0 0         if ( !defined( $self->{cache}{$name}{$var} ) ) {
919 0 0         if ( $new_value != 0 ) {
920 0 0 0       if ( $var eq 'cpu-time'
      0        
      0        
      0        
      0        
921             || $var eq 'system-time'
922             || $var eq 'user-time'
923             || $var eq 'throttled-time'
924             || $var eq 'burst-time'
925             || $var eq 'core_sched.force_idle-time' )
926             {
927 0           $new_value = $new_value / $self->{time_divider};
928             }
929 0           $new_value = $new_value / 300;
930             } ## end if ( $new_value != 0 )
931 0           return $new_value;
932             } ## end if ( !defined( $self->{cache}{$name}{$var}...))
933              
934 0 0         if ( $new_value >= $self->{cache}{$name}{$var} ) {
935 0           $new_value = $new_value - $self->{cache}{$name}{$var};
936 0 0         if ( $new_value != 0 ) {
937 0 0 0       if ( $var eq 'cpu-time'
      0        
      0        
      0        
      0        
938             || $var eq 'system-time'
939             || $var eq 'user-time'
940             || $var eq 'throttled-time'
941             || $var eq 'burst-time'
942             || $var eq 'core_sched.force_idle-time' )
943             {
944 0           $new_value = $new_value / $self->{time_divider};
945             }
946 0           $new_value = $new_value / 300;
947             } ## end if ( $new_value != 0 )
948 0 0         if ( $new_value > 10000000000 ) {
949 0           $self->{new_cache}{$name}{$var} = 0;
950 0           return 0;
951             }
952 0           return $new_value;
953             } ## end if ( $new_value >= $self->{cache}{$name}{$var...})
954              
955 0 0         if ( $new_value != 0 ) {
956 0 0 0       if ( $var eq 'cpu-time'
      0        
      0        
      0        
      0        
957             || $var eq 'system-time'
958             || $var eq 'user-time'
959             || $var eq 'throttled-time'
960             || $var eq 'burst-time'
961             || $var eq 'core_sched.force_idle-time' )
962             {
963 0           $new_value = $new_value / $self->{time_divider};
964             }
965 0           $new_value = $new_value / 300;
966             } ## end if ( $new_value != 0 )
967              
968 0           return $new_value;
969             } ## end sub cache_process
970              
971             =head1 AUTHOR
972              
973             Zane C. Bowers-Hadley, C<< >>
974              
975             =head1 BUGS
976              
977             Please report any bugs or feature requests to C, or through
978             the web interface at L. I will be notified, and then you'll
979             automatically be notified of progress on your bug as I make changes.
980              
981              
982              
983              
984             =head1 SUPPORT
985              
986             You can find documentation for this module with the perldoc command.
987              
988             perldoc OSLV::Monitor
989              
990              
991             You can also look for information at:
992              
993             =over 4
994              
995             =item * RT: CPAN's request tracker (report bugs here)
996              
997             L
998              
999             =item * CPAN Ratings
1000              
1001             L
1002              
1003             =item * Search CPAN
1004              
1005             L
1006              
1007             =back
1008              
1009              
1010             =head1 ACKNOWLEDGEMENTS
1011              
1012              
1013             =head1 LICENSE AND COPYRIGHT
1014              
1015             This software is Copyright (c) 2024 by Zane C. Bowers-Hadley.
1016              
1017             This is free software, licensed under:
1018              
1019             The Artistic License 2.0 (GPL Compatible)
1020              
1021              
1022             =cut
1023              
1024             1; # End of OSLV::Monitor