#!/usr/bin/perl -w # -*- perl -*- =head1 NAME batteries Munin plugin to monitor the battery states through procfs and sysfs =head1 APPLICABLE SYSTEMS Systems with available /proc/acpi/battery/BATx or /sys/class/power_supply/BATx =head1 CONFIGURATION none =head1 INTERPRETATION The plugin shows: Design capacity -> available if available /proc/acpi/battery/BATx or /sys/class/power_supply/BATx Last full capacity -> available if available /proc/acpi/battery/BATx or /sys/class/power_supply/BATx Design capacity low -> available only if available /proc/acpi/battery/BATx Design capacity warning -> available only if available /proc/acpi/battery/BATx Capacity granularity 1 -> available only if available /proc/acpi/battery/BATx Capacity granularity 2 -> available only if available /proc/acpi/battery/BATx Remaining capacity -> available if available /proc/acpi/battery/BATx or /sys/class/power_supply/BATx Present rate -> available if available /proc/acpi/battery/BATx or /sys/class/power_supply/BATx Percentage Current/design voltage -> available if available /proc/acpi/battery/BATx or /sys/class/power_supply/BATx Percentage Current/full capacity -> available if available /proc/acpi/battery/BATx or /sys/class/power_supply/BATx Percentage Full/design capacity -> available if available /proc/acpi/battery/BATx or /sys/class/power_supply/BATx Design voltage -> available if available /proc/acpi/battery/BATx or /sys/class/power_supply/BATx Present voltage -> available if available /proc/acpi/battery/BATx or /sys/class/power_supply/BATx =head1 MAGIC MARKERS #%# family=power #%# capabilities=autoconf =head1 VERSION 1.0 =head1 BUGS None known. =head1 AUTHOR Gorlow Maxim aka Sheridan - email and jabber =head1 LICENSE GPLv2 =cut use strict; use warnings; use IO::Dir; use Munin::Plugin; need_multigraph(); my $proc_path = '/proc/acpi/battery'; my $sys_path = '/sys/class/power_supply'; my $proc_data_exists; my $sys_data_exists; my $batteryes_count; sub trim { my($string)=@_; for ($string) { s/^\s+//; s/\s+$//; } return $string; } sub get_batteryes_count { my $path = $_[0]; return 0 unless (-e $path); my $count = 0; my $dir = IO::Dir->new($path); if(defined $dir) { my $d; while (defined ($d = $dir->read)) { next unless $d =~ m/BAT\d+/; $count++; } } else { return 0; } return $count; } sub init { my $proc_batt_count = get_batteryes_count($proc_path); my $sys_batt_count = get_batteryes_count($sys_path ); #print "$proc_batt_count $sys_batt_count\n"; $proc_data_exists = $proc_batt_count > 0; $sys_data_exists = $sys_batt_count > 0; if ($proc_data_exists and $sys_data_exists and ($proc_batt_count != $sys_batt_count)) { die "Something wrong, batteryes count from $proc_path and $sys_path not equal (proc: $proc_batt_count, sys: $sys_batt_count)!" } if ($proc_data_exists) { $batteryes_count = $proc_batt_count; } elsif ($sys_data_exists) { $batteryes_count = $sys_batt_count; } unless ($batteryes_count) { die "Batteryes not found." } } sub read_proc_data { my ($batt_num, $file) = @_[0..1]; my ($var, $val, $result); open(FH, '<', "${proc_path}/BAT${batt_num}/${file}") or die $!; foreach my $line () { chomp ($line); ($var, $val) = split(':', $line); if ($val =~ m/^\s*$/ ) { $val = "nan"; } elsif ($val =~ m/\w\s+\w/) { $val = (split(" " ,$val))[0]; } $result->{$var} = trim($val); #print "var $var - val $val\n"; } close(FH); return $result; } sub read_sys_data { my ($batt_num, $file) = @_[0..1]; my $file_content = "nan"; open(FH, '<', "${sys_path}/BAT${batt_num}/${file}") or die $!; $file_content = ; close(FH); chomp ($file_content); if($file_content =~ m/^\s*$/) { return 'nan'; } return $file_content; } sub percent { my ($full, $current) = @_[0..1]; return $current/($full/100); } sub read_info { my $info; for (my $i = 0; $i < $batteryes_count; $i++) { if($sys_data_exists) { $info->{$i}{'manufacturer'} = read_sys_data($i, 'manufacturer'); $info->{$i}{'battery_type'} = read_sys_data($i, 'technology' ); $info->{$i}{'model_name'} = read_sys_data($i, 'model_name' ); $info->{$i}{'serial_number'} = read_sys_data($i, 'serial_number'); } else { my $proc_info = read_proc_data($i, 'info'); $info->{$i}{'manufacturer'} = $proc_info->{'OEM info' }; $info->{$i}{'battery_type'} = $proc_info->{'battery type' }; $info->{$i}{'model_name'} = $proc_info->{'model number' }; $info->{$i}{'serial_number'} = $proc_info->{'serial number'}; } } return $info; } sub read_data { my $data; for (my $i = 0; $i < $batteryes_count; $i++) { if($sys_data_exists) { my $divider = 1000000; # need for equvivalent sys and proc data $data->{$i}{'design_capacity'} = read_sys_data($i, 'charge_full_design')/$divider; $data->{$i}{'last_full_capacity'} = read_sys_data($i, 'charge_full') /$divider; $data->{$i}{'remaining_capacity'} = read_sys_data($i, 'charge_now') /$divider; $data->{$i}{'design_voltage'} = read_sys_data($i, 'voltage_min_design')/$divider; $data->{$i}{'present_voltage'} = read_sys_data($i, 'voltage_now') /$divider; $data->{$i}{'present_rate'} = read_sys_data($i, 'current_now') /$divider; } if($proc_data_exists) { my $divider = 1000; # need for equvivalent sys and proc data my $proc_info = read_proc_data($i, 'info'); unless($sys_data_exists) { my $proc_state = read_proc_data($i, 'state'); $data->{$i}{'design_capacity'} = $proc_info ->{'design capacity'} /$divider; $data->{$i}{'last_full_capacity'} = $proc_info ->{'last full capacity'}/$divider; $data->{$i}{'remaining_capacity'} = $proc_state->{'remaining capacity'}/$divider; $data->{$i}{'design_voltage'} = $proc_info ->{'design voltage'} /$divider; $data->{$i}{'present_voltage'} = $proc_state->{'present voltage'} /$divider; $data->{$i}{'present_rate'} = $proc_state->{'present rate'} /$divider; } $data->{$i}{'design_capacity_low'} = $proc_info ->{'design capacity low'} /$divider; $data->{$i}{'design_capacity_warning'} = $proc_info ->{'design capacity warning'}/$divider; $data->{$i}{'capacity_granularity_1'} = $proc_info ->{'capacity granularity 1'} /$divider; $data->{$i}{'capacity_granularity_2'} = $proc_info ->{'capacity granularity 2'} /$divider; } $data->{$i}{'current_voltage_percent'} = percent($data->{$i}{'design_voltage'} , $data->{$i}{'present_voltage'}); $data->{$i}{'current_capacity_percent'} = percent($data->{$i}{'last_full_capacity'}, $data->{$i}{'remaining_capacity'}); $data->{$i}{'full_capacity_percent'} = percent($data->{$i}{'design_capacity'} , $data->{$i}{'last_full_capacity'}); } return $data; } my $graphs = { 'batteryes_capacity' => { 'vlabel' => 'Capacity, Ah', 'title' => '%s capacity', 'args' => '--base 1000', 'fields' => [qw/design_capacity last_full_capacity design_capacity_low design_capacity_warning capacity_granularity_1 capacity_granularity_2 remaining_capacity/] }, 'batteryes_voltage' => { 'vlabel' => 'Voltage, V' , 'title' => '%s voltage' , 'args' => '--base 1000', 'fields' => [qw/design_voltage present_voltage/] }, 'batteryes_percents' => { 'vlabel' => '%' , 'title' => '%s percents', 'args' => '--base 1000 --upper-limit 100 -l 0', 'fields' => [qw/current_voltage_percent current_capacity_percent full_capacity_percent/] }, 'batteryes_current' => { 'vlabel' => 'Current, A' , 'title' => '%s current' , 'args' => '--base 1000', 'fields' => [qw/present_rate/] } }; my $fields = { 'design_capacity' => { 'source' => 'both', 'draw' => 'AREA' , 'label' => 'Design capacity' , 'info' => 'Battery design capacity' }, 'last_full_capacity' => { 'source' => 'both', 'draw' => 'AREA' , 'label' => 'Last full capacity' , 'info' => 'Battery full charge capacity' }, 'design_capacity_low' => { 'source' => 'proc', 'draw' => 'LINE2', 'label' => 'Design capacity low' , 'info' => 'Low battery level' }, 'design_capacity_warning' => { 'source' => 'proc', 'draw' => 'LINE2', 'label' => 'Design capacity warning', 'info' => 'Warning battery level' }, 'capacity_granularity_1' => { 'source' => 'proc', 'draw' => 'LINE2', 'label' => 'Capacity granularity 1' , 'info' => 'Capacity granularity 1' }, 'capacity_granularity_2' => { 'source' => 'proc', 'draw' => 'LINE2', 'label' => 'Capacity granularity 2' , 'info' => 'Capacity granularity 2' }, 'remaining_capacity' => { 'source' => 'both', 'draw' => 'LINE2', 'label' => 'Remaining capacity' , 'info' => 'Current battery charge' }, 'present_rate' => { 'source' => 'both', 'draw' => 'LINE2', 'label' => 'Present rate' , 'info' => 'Current battery rate' }, 'design_voltage' => { 'source' => 'both', 'draw' => 'AREA' , 'label' => 'Design voltage' , 'info' => 'Battery design voltage' }, 'present_voltage' => { 'source' => 'both', 'draw' => 'AREA' , 'label' => 'Present voltage' , 'info' => 'Current battery voltage' }, 'current_voltage_percent' => { 'source' => 'both', 'draw' => 'LINE2', 'label' => 'Current/design voltage' , 'info' => 'Current battery voltage / ( Battery design voltage / 100 )' }, 'current_capacity_percent' => { 'source' => 'both', 'draw' => 'LINE2', 'label' => 'Current/full capacity' , 'info' => 'Current battery charge / ( Battery full charge capacity / 100 )' }, 'full_capacity_percent' => { 'source' => 'both', 'draw' => 'LINE2', 'label' => 'Full/design capacity' , 'info' => 'Battery full charge capacity / ( Battery design capacity / 100 )' }, }; # ------------------------------------ start here ----------------------------------- if (defined($ARGV[0]) and ($ARGV[0] eq 'autoconf')) { printf("%s\n", (-e $proc_path or -e $sys_path) ? "yes" : "no ($proc_path and $sys_path not exists)"); exit (0); } init(); if ($ARGV[0] and $ARGV[0] eq "config") { my %config; my $info = read_info(); foreach my $graph (keys %{$graphs}) { my @order; $config{$graph}{'graph'}{'title'} = sprintf($graphs->{$graph}{'title'}, 'Mean batteryes'); $config{$graph}{'graph'}{'args'} = $graphs->{$graph}{'args'}; $config{$graph}{'graph'}{'vlabel'} = $graphs->{$graph}{'vlabel'}; $config{$graph}{'graph'}{'category'} = 'sensors'; foreach my $field (@{$graphs->{$graph}{'fields'}}) { if(($proc_data_exists and $fields->{$field}{'source'} eq 'proc') or $fields->{$field}{'source'} eq 'both') { $config{$graph}{'fields'}{$field}{'label'} = $fields->{$field}{'label'}; $config{$graph}{'fields'}{$field}{'info'} = $fields->{$field}{'info'}; $config{$graph}{'fields'}{$field}{'draw'} = $fields->{$field}{'draw'}; $config{$graph}{'fields'}{$field}{'type'} = 'GAUGE'; push(@order, $field); } } $config{$graph}{'graph'}{'order'} = join(' ', @order); for (my $i = 0; $i < $batteryes_count; $i++) { my @b_order; my $battery_name = sprintf("BAT%s", $i); my $graph_name = sprintf("%s.%s", $graph, $battery_name); $config{$graph_name}{'graph'}{'title'} = sprintf($graphs->{$graph}{'title'}, $battery_name); $config{$graph_name}{'graph'}{'info'} = sprintf("%s battery %s %s (sn: %s)", $info->{$i}{'battery_type'}, $info->{$i}{'manufacturer'}, $info->{$i}{'model_name'}, $info->{$i}{'serial_number'}); $config{$graph_name}{'graph'}{'args'} = '--base 1000'; $config{$graph_name}{'graph'}{'vlabel'} = $graphs->{$graph}{'vlabel'}; $config{$graph_name}{'graph'}{'category'} = 'sensors'; foreach my $field (@{$graphs->{$graph}{'fields'}}) { if(($proc_data_exists and $fields->{$field}{'source'} eq 'proc') or $fields->{$field}{'source'} eq 'both') { $config{$graph_name}{'fields'}{$field}{'label'} = $fields->{$field}{'label'}; $config{$graph_name}{'fields'}{$field}{'info'} = $fields->{$field}{'info'}; $config{$graph_name}{'fields'}{$field}{'draw'} = $fields->{$field}{'draw'}; $config{$graph_name}{'fields'}{$field}{'type'} = 'GAUGE'; push(@b_order, $field); } } $config{$graph_name}{'graph'}{'order'} = join(' ', @b_order); } } # ---------------- print ------------------ foreach my $graph (sort keys %config) { printf("multigraph %s\n", $graph); foreach my $g_option(sort keys %{$config{$graph}{'graph'}}) { printf("graph_%s %s\n", $g_option, $config{$graph}{'graph'}{$g_option}); } foreach my $field (sort keys %{$config{$graph}{'fields'}}) { foreach my $f_option (sort keys %{$config{$graph}{'fields'}{$field}}) { printf("%s.%s %s\n", $field, $f_option, $config{$graph}{'fields'}{$field}{$f_option}); } } print "\n"; } exit (0); } # ----------------------------- values --------------------------------------------- my $data = read_data(); foreach my $graph (sort keys %{$graphs}) { printf ("multigraph %s\n", $graph); foreach my $field (sort @{$graphs->{$graph}{'fields'}}) { my $field_summ = 0; if(($proc_data_exists and $fields->{$field}{'source'} eq 'proc') or $fields->{$field}{'source'} eq 'both') { for (my $i = 0; $i < $batteryes_count; $i++) { $field_summ += $data->{$i}{$field}; } printf("%s.value %s\n", $field, $field_summ/$batteryes_count); } } print "\n"; for (my $i = 0; $i < $batteryes_count; $i++) { my $graph_name = sprintf("%s.BAT%s", $graph, $i); printf ("multigraph %s\n", $graph_name); foreach my $field (sort @{$graphs->{$graph}{'fields'}}) { if(($proc_data_exists and $fields->{$field}{'source'} eq 'proc') or $fields->{$field}{'source'} eq 'both') { printf("%s.value %s\n", $field, $data->{$i}{$field}); } } print "\n"; } }