File Coverage

blib/lib/OSLV/Monitor/Backends/cgroups.pm
Criterion Covered Total %
statement 26 351 7.4
branch 0 152 0.0
condition 0 147 0.0
subroutine 9 15 60.0
pod 3 6 50.0
total 38 671 5.6


line stmt bran cond sub pod time code
1             package OSLV::Monitor::Backends::cgroups;
2              
3 1     1   102957 use 5.006;
  1         3  
4 1     1   5 use strict;
  1         1  
  1         22  
5 1     1   3 use warnings;
  1         9  
  1         70  
6 1     1   539 use JSON;
  1         10682  
  1         6  
7 1     1   558 use Clone 'clone';
  1         452  
  1         70  
8 1     1   373 use File::Slurp;
  1         30742  
  1         75  
9 1     1   641 use IO::Interface::Simple;
  1         16140  
  1         36  
10 1     1   1942 use Math::BigInt;
  1         34029  
  1         8  
11 1     1   31508 use Scalar::Util qw(looks_like_number);
  1         2  
  1         6610  
12              
13             =head1 NAME
14              
15             OSLV::Monitor::Backends::cgroups - Backend for Linux cgroups.
16              
17             =head1 VERSION
18              
19             Version 1.0.2
20              
21             =cut
22              
23             our $VERSION = '1.0.2';
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 = `$cgroup_jank_type ps --format json 2> /dev/null`;
418 0 0         if ( $? == 0 ) {
419 0           my $podman_parsed;
420 0           eval { $podman_parsed = decode_json($podman_output); };
  0            
421 0 0 0       if ( defined($podman_parsed) && ref($podman_parsed) eq 'ARRAY' ) {
422 0           foreach my $pod ( @{$podman_parsed} ) {
  0            
423 0 0 0       if ( defined( $pod->{Id} ) && defined( $pod->{Names} ) && defined( $pod->{Names}[0] ) ) {
      0        
424             $self->{ $cgroup_jank_type . '_mapping' }{ $pod->{Id} } = {
425             podname => $pod->{PodName},
426             Networks => $pod->{Networks},
427 0           };
428 0 0         if ( $self->{ $cgroup_jank_type . '_mapping' }{ $pod->{Id} }{podname} ne '' ) {
429             $self->{ $cgroup_jank_type . '_mapping' }{ $pod->{Id} }{name}
430             = $self->{ $cgroup_jank_type . '_mapping' }{ $pod->{Id} }{podname} . '-'
431 0           . $pod->{Names}[0];
432             } else {
433 0           $self->{ $cgroup_jank_type . '_mapping' }{ $pod->{Id} }{name} = $pod->{Names}[0];
434             }
435 0           my $container_id = $pod->{Id};
436 0           my $inspect_output = `$cgroup_jank_type inspect $container_id 2> /dev/null`;
437 0           my $inspect_parsed;
438 0           $self->{ $cgroup_jank_type . '_info' }{$container_id} = { ip => [] };
439 0           eval { $inspect_parsed = decode_json($inspect_output) };
  0            
440 0 0 0       if ( defined($inspect_parsed)
      0        
      0        
      0        
      0        
      0        
      0        
441             && ref($inspect_parsed) eq 'ARRAY'
442             && defined( $inspect_parsed->[0] )
443             && ref( $inspect_parsed->[0] ) eq 'HASH'
444             && defined( $inspect_parsed->[0]{NetworkSettings} )
445             && ref( $inspect_parsed->[0]{NetworkSettings} ) eq 'HASH'
446             && defined( $inspect_parsed->[0]{NetworkSettings}{Networks} )
447             && ref( $inspect_parsed->[0]{NetworkSettings}{Networks} ) eq 'HASH' )
448             {
449 0           my @podman_networks = keys( %{ $inspect_parsed->[0]{NetworkSettings}{Networks} } );
  0            
450 0           foreach my $network_to_process (@podman_networks) {
451             my $current_network
452 0           = $inspect_parsed->[0]{NetworkSettings}{Networks}{$network_to_process};
453 0 0 0       if ( ref($current_network) eq 'HASH'
454             && ref( $current_network->{IPAddress} ) eq '' )
455             {
456             my $net_work_info = {
457             ip => $current_network->{IPAddress},
458 0           gw => undef,
459             gw_if => undef,
460             mac => undef,
461             if => undef,
462             };
463 0 0 0       if ( defined( $current_network->{Gateway} )
464             && ref( $current_network->{Gateway} ) eq '' )
465             {
466 0           $net_work_info->{gw} = $current_network->{Gateway};
467             }
468 0 0 0       if ( defined( $current_network->{MacAddress} )
469             && ref( $current_network->{MacAddress} ) eq '' )
470             {
471 0           $net_work_info->{mac} = $current_network->{MacAddress};
472             }
473 0 0 0       if ( defined( $current_network->{NetworkID} )
474             && ref( $current_network->{NetworkID} ) eq '' )
475             {
476 0           my $network_id = $current_network->{NetworkID};
477 0           my $network_inspect_output
478             = `$cgroup_jank_type network inspect $network_id 2> /dev/null`;
479 0           my $network_inspect_parsed;
480 0           eval { $network_inspect_parsed = decode_json($network_inspect_output) };
  0            
481 0 0 0       if ( defined($network_inspect_parsed)
      0        
      0        
      0        
      0        
482             && ref($network_inspect_parsed) eq 'ARRAY'
483             && defined( $network_inspect_parsed->[0] )
484             && ref( $network_inspect_parsed->[0] ) eq 'HASH'
485             && defined( $network_inspect_parsed->[0]{network_interface} )
486             && ref( $network_inspect_parsed->[0]{network_interface} ) eq '' )
487             {
488 0           $net_work_info->{if} = $network_inspect_parsed->[0]{network_interface};
489             }
490             } ## end if ( defined( $current_network->{NetworkID...}))
491 0 0 0       if ( defined( $net_work_info->{if} )
492             && defined( $net_work_info->{ip} ) )
493             {
494 0           my $ip_r_g_output
495             = `ip r g from $net_work_info->{ip} iif $net_work_info->{if} 8.8.8.8`;
496 0 0         if ( $? == 0 ) {
497 0           my @ip_r_g_output_split = split( /\n/, $ip_r_g_output );
498 0 0         if ( defined( $ip_r_g_output_split[0] ) ) {
499 0           $ip_r_g_output_split[0] =~ s/^.*[\ \t]+dev[\ \t]+//;
500 0           $ip_r_g_output_split[0] =~ s/[\ \t].*$//;
501 0           $net_work_info->{gw_if} = $ip_r_g_output_split[0];
502             }
503             }
504             } ## end if ( defined( $net_work_info->{if} ) && defined...)
505             push(
506 0           @{ $self->{ $cgroup_jank_type . '_info' }{ $pod->{Names}[0] }{ip} },
  0            
507             $net_work_info
508             );
509             } ## end if ( ref($current_network) eq 'HASH' && ref...)
510             } ## end foreach my $network_to_process (@podman_networks)
511             } ## end if ( defined($inspect_parsed) && ref($inspect_parsed...))
512             } ## end if ( defined( $pod->{Id} ) && defined( $pod...))
513             } ## end foreach my $pod ( @{$podman_parsed} )
514             } ## end if ( defined($podman_parsed) && ref($podman_parsed...))
515             } ## end if ( $? == 0 )
516             } ## end foreach my $cgroup_jank_type (@podman_compatible)
517              
518             #
519             # gets of procs for finding a list of containers
520             #
521             # my $ps_output = `ps -haxo pid,uid,gid,cgroupns,%cpu,%mem,rss,vsize,trs,drs,size,cgroup 2> /dev/null`;
522             # if ( $? != 0 ) {
523             # $self->{cgroupns_usable} = 0;
524 0           my $ps_output = `ps -haxo pid,uid,gid,%cpu,%mem,rss,vsize,trs,drs,size,etimes,cgroup 2> /dev/null`;
525             # }
526 0           my @ps_output_split = split( /\n/, $ps_output );
527 0           my %found_cgroups;
528             my %cgroups_percpu;
529 0           my %cgroups_permem;
530 0           my %cgroups_procs;
531 0           my %cgroups_rss;
532 0           my %cgroups_vsize;
533 0           my %cgroups_trs;
534 0           my %cgroups_drs;
535 0           my %cgroups_size;
536 0           my %cgroups_etimes;
537 0           my %cgroups_invvol_ctxt_switches;
538 0           my %cgroups_vol_ctxt_switches;
539              
540 0           foreach my $line (@ps_output_split) {
541 0           $line =~ s/^\s+//;
542 0           my $vol_ctxt_switches = 0;
543 0           my $invol_ctxt_switches = 0;
544 0           my ( $pid, $uid, $gid, $cgroupns, $percpu, $permem, $rss, $vsize, $trs, $drs, $size, $etimes, $cgroup );
545             # if ( $self->{cgroupns_usable} ) {
546             # ( $pid, $uid, $gid, $cgroupns, $percpu, $permem, $rss, $vsize, $trs, $drs, $size, $etimes, $cgroup )#
547             # = split( /\s+/, $line );
548             # } else {
549 0           ( $pid, $uid, $gid, $percpu, $permem, $rss, $vsize, $trs, $drs, $size, $etimes, $cgroup )
550             = split( /\s+/, $line );
551             # }
552 0 0         if ( $cgroup =~ /^0\:\:\// ) {
553              
554 0           my $cache_name = 'proc-' . $pid . '-' . $uid . '-' . $gid . '-' . $cgroup;
555              
556 0           $found_cgroups{$cgroup} = $cgroup;
557 0           $data->{totals}{'percent-cpu'} = $data->{totals}{'percent-cpu'} + $percpu;
558 0           $data->{totals}{'percent-memory'} = $data->{totals}{'percent-memory'} + $permem;
559 0           $data->{totals}{rss} = $data->{totals}{rss} + $rss;
560 0           $data->{totals}{'virtual-size'} = $data->{totals}{'virtual-size'} + $vsize;
561 0           $data->{totals}{'text-size'} = $data->{totals}{'text-size'} + $trs;
562 0           $data->{totals}{'data-size'} = $data->{totals}{'data-size'} + $drs;
563 0           $data->{totals}{'size'} = $data->{totals}{'size'} + $size;
564 0           $data->{totals}{'elapsed-times'} = $data->{totals}{'elapsed-times'} + $etimes;
565              
566 0           eval {
567 0 0         if ( -f '/proc/' . $pid . '/status' ) {
568             my @switches_find
569 0           = grep( /voluntary\_ctxt\_switches\:/, read_file( '/proc/' . $pid . '/status' ) );
570 0           foreach my $found_switch (@switches_find) {
571 0           chomp($found_switch);
572 0           my @switch_split = split( /\:[\ \t]+/, $found_switch );
573 0 0 0       if ( defined( $switch_split[0] ) && defined( $switch_split[1] ) ) {
574 0 0         if ( $switch_split[0] eq 'voluntary_ctxt_switches' ) {
    0          
575 0           $vol_ctxt_switches = $switch_split[1];
576             } elsif ( $switch_split[0] eq 'involuntary_ctxt_switches' ) {
577 0           $invol_ctxt_switches = $switch_split[1];
578             }
579             }
580             } ## end foreach my $found_switch (@switches_find)
581             } ## end if ( -f '/proc/' . $pid . '/status' )
582             };
583 0           $vol_ctxt_switches = $self->cache_process( $cache_name, 'voluntary-context-switches', $vol_ctxt_switches );
584             $data->{totals}{'voluntary-context-switches'}
585 0           = $data->{totals}{'voluntary-context-switches'} + $vol_ctxt_switches;
586 0           $invol_ctxt_switches
587             = $self->cache_process( $cache_name, 'involuntary-context-switches', $invol_ctxt_switches );
588             $data->{totals}{'involuntary-context-switches'}
589 0           = $data->{totals}{'involuntary-context-switches'} + $invol_ctxt_switches;
590              
591 0 0         if ( !defined( $cgroups_permem{$cgroup} ) ) {
592 0           $cgroups_permem{$cgroup} = $permem;
593 0           $cgroups_percpu{$cgroup} = $percpu;
594 0           $cgroups_procs{$cgroup} = 1;
595 0           $cgroups_rss{$cgroup} = $rss;
596 0           $cgroups_vsize{$cgroup} = $vsize;
597 0           $cgroups_trs{$cgroup} = $trs;
598 0           $cgroups_drs{$cgroup} = $drs;
599 0           $cgroups_size{$cgroup} = $size;
600 0           $cgroups_etimes{$cgroup} = $etimes;
601 0           $cgroups_invvol_ctxt_switches{$cgroup} = $invol_ctxt_switches;
602 0           $cgroups_vol_ctxt_switches{$cgroup} = $vol_ctxt_switches;
603             } else {
604 0           $cgroups_permem{$cgroup} = $cgroups_permem{$cgroup} + $permem;
605 0           $cgroups_percpu{$cgroup} = $cgroups_percpu{$cgroup} + $percpu;
606 0           $cgroups_procs{$cgroup}++;
607 0           $cgroups_rss{$cgroup} = $cgroups_rss{$cgroup} + $rss;
608 0           $cgroups_vsize{$cgroup} = $cgroups_vsize{$cgroup} + $vsize;
609 0           $cgroups_trs{$cgroup} = $cgroups_trs{$cgroup} + $trs;
610 0           $cgroups_drs{$cgroup} = $cgroups_drs{$cgroup} + $drs;
611 0           $cgroups_size{$cgroup} = $cgroups_size{$cgroup} + $size;
612 0           $cgroups_etimes{$cgroup} = $cgroups_etimes{$cgroup} + $etimes;
613 0           $cgroups_invvol_ctxt_switches{$cgroup} = $cgroups_invvol_ctxt_switches{$cgroup} + $invol_ctxt_switches;
614 0           $cgroups_vol_ctxt_switches{$cgroup} = $cgroups_vol_ctxt_switches{$cgroup} + $vol_ctxt_switches;
615             } ## end else [ if ( !defined( $cgroups_permem{$cgroup} ) )]
616             } ## end if ( $cgroup =~ /^0\:\:\// )
617             } ## end foreach my $line (@ps_output_split)
618              
619             #
620             # build a list of mappings
621             #
622 0           foreach my $cgroup ( keys(%found_cgroups) ) {
623             #my $cgroupns = $found_cgroups{$cgroup};
624 0           my $map_to = $self->cgroup_mapping($cgroup);
625 0 0         if ( defined($map_to) ) {
626 0           $self->{mappings}{$cgroup} = $map_to;
627             }
628             }
629              
630             #
631             # get the stats
632             #
633 0           foreach my $cgroup ( keys( %{ $self->{mappings} } ) ) {
  0            
634 0           my $name = $self->{mappings}{$cgroup};
635              
636             # only process this cgroup if the include check returns true, otherwise ignore it
637 0 0         if ( $self->{obj}->include($name) ) {
638              
639 0           my $cache_name = 'cgroup-' . $name;
640              
641 0           $data->{oslvms}{$name} = clone($base_stats);
642              
643 0           $data->{oslvms}{$name}{'percent-cpu'} = $cgroups_percpu{$cgroup};
644 0           $data->{oslvms}{$name}{'percent-memory'} = $cgroups_permem{$cgroup};
645 0           $data->{oslvms}{$name}{procs} = $cgroups_procs{$cgroup};
646 0           $data->{totals}{procs} = $data->{totals}{procs} + $cgroups_procs{$cgroup};
647 0           $data->{oslvms}{$name}{rss} = $cgroups_rss{$cgroup};
648 0           $data->{oslvms}{$name}{'virtual-size'} = $cgroups_vsize{$cgroup};
649 0           $data->{oslvms}{$name}{'text-size'} = $cgroups_trs{$cgroup};
650 0           $data->{oslvms}{$name}{'data-size'} = $cgroups_drs{$cgroup};
651 0           $data->{oslvms}{$name}{'size'} = $cgroups_size{$cgroup};
652 0           $data->{oslvms}{$name}{'elapsed-times'} = $cgroups_etimes{$cgroup};
653              
654 0 0 0       if ( $name =~ /^p\_/ || $name =~ /^d\_/ ) {
655 0           my $container_name = $name;
656 0           $container_name =~ s/^[pd]\_//;
657 0 0         if ( $name =~ /^p\_/ ) {
    0          
658 0           $data->{oslvms}{$name}{'ip'} = $self->{podman_info}{$container_name}{ip};
659             } elsif ( $name =~ /^d\_/ ) {
660 0           $data->{oslvms}{$name}{'ip'} = $self->{docker_info}{$container_name}{ip};
661             }
662             }
663              
664 0           my $base_dir = $cgroup;
665 0           $base_dir =~ s/^0\:\://;
666 0           $base_dir = '/sys/fs/cgroup' . $base_dir;
667              
668 0           my $cpu_stats_raw;
669 0 0 0       if ( -f $base_dir . '/cpu.stat' && -r $base_dir . '/cpu.stat' ) {
670 0           eval { $cpu_stats_raw = read_file( $base_dir . '/cpu.stat' ); };
  0            
671 0 0         if ( defined($cpu_stats_raw) ) {
672 0           my @cpu_stats_split = split( /\n/, $cpu_stats_raw );
673 0           foreach my $line (@cpu_stats_split) {
674 0           my ( $stat, $value ) = split( /\s+/, $line, 2 );
675 0 0         if ( defined( $stat_mapping->{$stat} ) ) {
676 0           $stat = $stat_mapping->{$stat};
677             }
678 0 0 0       if ( defined( $data->{oslvms}{$name}{$stat} ) && defined($value) && $value =~ /[0-9\.]+/ ) {
      0        
679 0           $value = $self->cache_process( $cache_name, $stat, $value );
680 0           $data->{oslvms}{$name}{$stat} = $data->{oslvms}{$name}{$stat} + $value;
681 0           $data->{totals}{$stat} = $data->{totals}{$stat} + $value;
682 0 0         if ( $stat eq 'nr_bursts' ) {
683 0           $data->{has}{burst_count} = 1;
684             }
685 0 0         if ( $stat eq 'burst-time' ) {
686 0           $data->{has}{burst_time} = 1;
687             }
688 0 0         if ( $stat eq 'throttled-time' ) {
689 0           $data->{has}{throttled_time} = 1;
690             }
691 0 0         if ( $stat eq 'nr_throttled' ) {
692 0           $data->{has}{throttled_count} = 1;
693             }
694             } ## end if ( defined( $data->{oslvms}{$name}{$stat...}))
695             } ## end foreach my $line (@cpu_stats_split)
696             } ## end if ( defined($cpu_stats_raw) )
697             } ## end if ( -f $base_dir . '/cpu.stat' && -r $base_dir...)
698              
699 0           my $memory_stats_raw;
700 0 0 0       if ( -f $base_dir . '/memory.stat' && -r $base_dir . '/memory.stat' ) {
701 0           eval { $memory_stats_raw = read_file( $base_dir . '/memory.stat' ); };
  0            
702 0 0         if ( defined($memory_stats_raw) ) {
703 0           my @memory_stats_split = split( /\n/, $memory_stats_raw );
704 0           foreach my $line (@memory_stats_split) {
705 0           my ( $stat, $value ) = split( /\s+/, $line, 2 );
706 0 0         if ( defined( $stat_mapping->{$stat} ) ) {
707 0           $stat = $stat_mapping->{$stat};
708             }
709 0 0 0       if ( defined( $data->{oslvms}{$name}{$stat} ) && defined($value) && $value =~ /[0-9\.]+/ ) {
      0        
710 0           $value = $self->cache_process( $cache_name, $stat, $value );
711 0           $data->{oslvms}{$name}{$stat} = $data->{oslvms}{$name}{$stat} + $value;
712 0           $data->{totals}{$stat} = $data->{totals}{$stat} + $value;
713             }
714             } ## end foreach my $line (@memory_stats_split)
715             } ## end if ( defined($memory_stats_raw) )
716             } ## end if ( -f $base_dir . '/memory.stat' && -r $base_dir...)
717              
718 0           my $io_stats_raw;
719 0 0 0       if ( -f $base_dir . '/io.stat' && -r $base_dir . '/io.stat' ) {
720 0           eval { $io_stats_raw = read_file( $base_dir . '/io.stat' ); };
  0            
721 0 0         if ( defined($io_stats_raw) ) {
722 0           $data->{has}{rwdops} = 1;
723 0           $data->{has}{rwdbytes} = 1;
724 0           my @io_stats_split = split( /\n/, $io_stats_raw );
725 0           foreach my $line (@io_stats_split) {
726 0           my @line_split = split( /\s/, $line );
727 0           shift(@line_split);
728 0           foreach my $item (@line_split) {
729 0           my ( $stat, $value ) = split( /\=/, $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 $item (@line_split)
739             } ## end foreach my $line (@io_stats_split)
740             } ## end if ( defined($io_stats_raw) )
741             } ## end if ( -f $base_dir . '/io.stat' && -r $base_dir...)
742             } ## end if ( $self->{obj}->include($name) )
743             } ## end foreach my $cgroup ( keys( %{ $self->{mappings}...}))
744              
745 0           $data->{uid_mapping} = $self->{uid_mapping};
746              
747             # save the proc cache for next run
748 0           eval { write_file( $self->{cache_file}, encode_json( $self->{new_cache} ) ); };
  0            
749 0 0         if ($@) {
750 0           push( @{ $data->{errors} }, 'saving proc cache failed, "' . $self->{proc_cache} . '"... ' . $@ );
  0            
751 0           $data->{cache_failure} = 1;
752             }
753              
754 0 0         if ($cache_is_new) {
755 0           delete( $data->{oslvms} );
756 0           $data->{oslvms} = {};
757 0           my @total_keys = keys( %{ $data->{totals} } );
  0            
758 0           foreach my $total_key (@total_keys) {
759 0 0         if ( ref( $data->{totals}{$total_key} ) eq '' ) {
760 0           $data->{totals}{$total_key} = 0;
761             }
762             }
763             } ## end if ($cache_is_new)
764              
765 0           return $data;
766             } ## end sub run
767              
768             =head2 usable
769              
770             Dies if not usable.
771              
772             eval{ $backend->usable; };
773             if ( $@ ){
774             print 'Not usable because... '.$@."\n";
775             }
776              
777             =cut
778              
779             sub usable {
780 0     0 1   my $self = $_[0];
781              
782             # make sure it is freebsd
783              
784 0 0         if ( $^O !~ 'linux' ) {
785 0           die '$^O is "' . $^O . '" and not "linux"';
786             }
787              
788 0           return 1;
789             } ## end sub usable
790              
791             sub cgroup_mapping {
792 0     0 0   my $self = $_[0];
793 0           my $cgroup_name = $_[1];
794             #my $cgroupns = $_[2];
795              
796 0 0         if ( !defined($cgroup_name) ) {
797 0           return undef;
798             }
799              
800 0 0         if ( $cgroup_name eq '0::/init.scope' ) {
801 0           return 'init';
802             }
803              
804 0 0         if ( $cgroup_name =~ /^0\:\:\/system\.slice\/docker\-[a-zA-Z0-9]+\.scope/ ) {
    0          
    0          
    0          
    0          
    0          
805 0           $cgroup_name =~ s/^0\:\:\/system\.slice\/docker\-//;
806 0           $cgroup_name =~ s/\.scope.*$//;
807 0           return 'd_' . $cgroup_name;
808             } elsif ( $cgroup_name =~ /^0\:\:\/docker\// ) {
809 0           $cgroup_name =~ s/^0\:\:\/docker\///;
810 0           $cgroup_name =~ s/\/.*$//;
811 0           return 'd_' . $cgroup_name;
812             } elsif ( $cgroup_name =~ /^0\:\:\/system\.slice\// ) {
813 0           $cgroup_name =~ s/^.*\///;
814 0           $cgroup_name =~ s/\.service$//;
815 0           return 's_' . $cgroup_name;
816             } elsif ( $cgroup_name =~ /^0\:\:\/user\.slice\// ) {
817 0           $cgroup_name =~ s/^0\:\:\/user\.slice\///;
818 0           $cgroup_name =~ s/\.slice.*$//;
819 0           $cgroup_name =~ s/^user[\-\_]//;
820              
821 0 0         if ( $cgroup_name =~ /^\d+$/ ) {
822 0           my ( $name, $passwd, $uid, $gid, $quota, $comment, $gecos, $dir, $shell, $expire ) = getpwuid($cgroup_name);
823 0 0         if ( defined($name) ) {
824 0           $self->{uid_mapping}{$cgroup_name} = {
825             name => $name,
826             gid => $gid,
827             home => $dir,
828             gecos => $gecos,
829             shell => $shell,
830             };
831             }
832             } ## end if ( $cgroup_name =~ /^\d+$/ )
833              
834 0           return 'u_' . $cgroup_name;
835             } elsif ( $cgroup_name =~ /^0\:\:\/machine\.slice\/libpod\-conmon-/ ) {
836 0           return 'libpod-conmon';
837             } elsif ( $cgroup_name =~ /^0\:\:\/machine\.slice\/libpod\-/ ) {
838 0           $cgroup_name =~ s/^^0\:\:\/machine\.slice\/libpod\-//;
839 0           $cgroup_name =~ s/\.scope.*$//;
840 0 0         if ( defined( $self->{podman_mapping}{$cgroup_name} ) ) {
841 0           return 'p_' . $self->{podman_mapping}{$cgroup_name}{name};
842             }
843 0           return 'libpod';
844             }
845              
846 0           $cgroup_name =~ s/^0\:\:\///;
847 0           $cgroup_name =~ s/\/.*//;
848 0           return $cgroup_name;
849             } ## end sub cgroup_mapping
850              
851             sub ip_to_if {
852 0     0 0   my $self = $_[0];
853 0           my $ip = $_[1];
854              
855 0 0 0       if ( !defined($ip) || ref($ip) ne '' ) {
856 0           return undef;
857             }
858              
859 0           my $if = IO::Interface::Simple->new_from_address($ip);
860              
861 0 0         if ( !defined($if) ) {
862 0           return undef;
863             }
864              
865 0           return $if->name;
866             } ## end sub ip_to_if
867              
868             sub cache_process {
869 0     0 0   my $self = $_[0];
870 0           my $name = $_[1];
871 0           my $var = $_[2];
872 0           my $new_value = $_[3];
873              
874 0 0 0       if ( !defined($name) || !defined($var) || !defined($new_value) ) {
      0        
875 0           warn('name, var, or new_value is undef');
876 0           return 0;
877             }
878              
879             # is a gauge and not a counter
880 0 0         if ( !defined( $self->{counters}{$var} ) ) {
881 0           return $new_value;
882             }
883              
884             # not seen it yet
885 0 0         if ( !defined( $self->{new_cache}{$name} ) ) {
886 0           $self->{new_cache}{$name} = {};
887             }
888 0           $self->{new_cache}{$name}{$var} = $new_value;
889              
890             # not seen it yet
891 0 0         if ( !defined( $self->{cache}{$name}{$var} ) ) {
892 0 0         if ( $new_value != 0 ) {
893 0 0 0       if ( $var eq 'cpu-time'
      0        
      0        
      0        
      0        
894             || $var eq 'system-time'
895             || $var eq 'user-time'
896             || $var eq 'throttled-time'
897             || $var eq 'burst-time'
898             || $var eq 'core_sched.force_idle-time' )
899             {
900 0           $new_value = $new_value / $self->{time_divider};
901             }
902 0           $new_value = $new_value / 300;
903             } ## end if ( $new_value != 0 )
904 0           return $new_value;
905             } ## end if ( !defined( $self->{cache}{$name}{$var}...))
906              
907 0 0         if ( $new_value >= $self->{cache}{$name}{$var} ) {
908 0           $new_value = $new_value - $self->{cache}{$name}{$var};
909 0 0         if ( $new_value != 0 ) {
910 0 0 0       if ( $var eq 'cpu-time'
      0        
      0        
      0        
      0        
911             || $var eq 'system-time'
912             || $var eq 'user-time'
913             || $var eq 'throttled-time'
914             || $var eq 'burst-time'
915             || $var eq 'core_sched.force_idle-time' )
916             {
917 0           $new_value = $new_value / $self->{time_divider};
918             }
919 0           $new_value = $new_value / 300;
920             } ## end if ( $new_value != 0 )
921 0 0         if ( $new_value > 10000000000 ) {
922 0           $self->{new_cache}{$name}{$var} = 0;
923 0           return 0;
924             }
925 0           return $new_value;
926             } ## end if ( $new_value >= $self->{cache}{$name}{$var...})
927              
928 0 0         if ( $new_value != 0 ) {
929 0 0 0       if ( $var eq 'cpu-time'
      0        
      0        
      0        
      0        
930             || $var eq 'system-time'
931             || $var eq 'user-time'
932             || $var eq 'throttled-time'
933             || $var eq 'burst-time'
934             || $var eq 'core_sched.force_idle-time' )
935             {
936 0           $new_value = $new_value / $self->{time_divider};
937             }
938 0           $new_value = $new_value / 300;
939             } ## end if ( $new_value != 0 )
940              
941 0           return $new_value;
942             } ## end sub cache_process
943              
944             =head1 AUTHOR
945              
946             Zane C. Bowers-Hadley, C<< >>
947              
948             =head1 BUGS
949              
950             Please report any bugs or feature requests to C, or through
951             the web interface at L. I will be notified, and then you'll
952             automatically be notified of progress on your bug as I make changes.
953              
954              
955              
956              
957             =head1 SUPPORT
958              
959             You can find documentation for this module with the perldoc command.
960              
961             perldoc OSLV::Monitor
962              
963              
964             You can also look for information at:
965              
966             =over 4
967              
968             =item * RT: CPAN's request tracker (report bugs here)
969              
970             L
971              
972             =item * CPAN Ratings
973              
974             L
975              
976             =item * Search CPAN
977              
978             L
979              
980             =back
981              
982              
983             =head1 ACKNOWLEDGEMENTS
984              
985              
986             =head1 LICENSE AND COPYRIGHT
987              
988             This software is Copyright (c) 2024 by Zane C. Bowers-Hadley.
989              
990             This is free software, licensed under:
991              
992             The Artistic License 2.0 (GPL Compatible)
993              
994              
995             =cut
996              
997             1; # End of OSLV::Monitor