Repository
Munin (contrib)
Last change
2020-10-24
Graph Categories
Family
auto
Capabilities
Keywords
Language
Perl
License
GPL-2.0-only

irq

Name

irq - Plugin to monitor interrupts.

Applicable Systems

All Linux systems

Configuration

None need

Warning and Critical Settings

You can set warning and critical levels for each of the data series the plugin reports. ‘General’ graph support cpu-irqtype limits and irqtype limits

Examples:

[irq]
env.warning_cpu1_sirq_total 550
env.critical_cpu0_irq_total 600
env.warning_irq_total 700
env.critical_sirq_total 700

‘Child’ graphs support cpu-irqtype-irqname and irqtype-irqname limits

Examples:

[irq]
env.warning_cpu0_irq_7 100
env.critical_cpu1_sirq_HI 100
env.warning_irq_LOC 100
env.critical_irq_12 200
env.warning_sirq_BLOCK 1000

Note: irqtype: sirq, irq; sirq - Software IRQ; irq name you can see in [] on graph

Interpretation

The plugin shows each cpu interrupts: summary and IRQ/Software IRQ per CPU

Magic Markers

#%# family=auto
#%# capabilities=autoconf

Version

1.0

Bugs

I can not understand, how set limit to mirrored fields, so they may not display correctly

Author

Gorlow Maxim aka Sheridan sheridan@sheridan-home.ru (email and jabber)

License

GPLv2

#!/usr/bin/perl -w
# -*- perl -*-

=head1 NAME

irq - Plugin to monitor interrupts.

=head1 APPLICABLE SYSTEMS

All Linux systems

=head1 CONFIGURATION

None need

=head2 WARNING AND CRITICAL SETTINGS

You can set warning and critical levels for each of the data
series the plugin reports.
'General' graph support cpu-irqtype limits and irqtype limits

Examples:

 [irq]
 env.warning_cpu1_sirq_total 550
 env.critical_cpu0_irq_total 600
 env.warning_irq_total 700
 env.critical_sirq_total 700

'Child' graphs support cpu-irqtype-irqname and irqtype-irqname limits

Examples:

 [irq]
 env.warning_cpu0_irq_7 100
 env.critical_cpu1_sirq_HI 100
 env.warning_irq_LOC 100
 env.critical_irq_12 200
 env.warning_sirq_BLOCK 1000

Note: irqtype: sirq, irq; sirq - Software IRQ; irq name you can see in [] on graph

=head1 INTERPRETATION

The plugin shows each cpu interrupts: summary and IRQ/Software IRQ per CPU

=head1 MAGIC MARKERS

  #%# family=auto
  #%# capabilities=autoconf

=head1 VERSION

  1.0

=head1 BUGS

I can not understand, how set limit to mirrored fields, so they may not display correctly

=head1 AUTHOR

Gorlow Maxim aka Sheridan <sheridan@sheridan-home.ru> (email and jabber)

=head1 LICENSE

GPLv2

=cut

use strict;
use warnings;
use Munin::Plugin;
use Data::Dumper;
my $IRQi = {};


my $graphs =
{
  'irq_total' =>
  {
    'title'    => 'Interrupts per cpu',
    'args'     => '--base 1000',
    'vlabel'   => 'count of IRQ (+) / sIRQ (-) per second',
    'info'     => 'This graph shows interrupts per second from last update',
    'category' => 'system'
  },
  'irq_cpu' =>
  {
    'title'    => 'CPU:cpu: :irq_type:',
    'args'     => '--base 1000',
    'vlabel'   => 'count per second',
    'info'     => 'This graph shows :irq_type: for CPU:cpu: per second from last update',
    'category' => 'cpu :cpu:'
  }
};

my $fields =
{
  'irq' =>
  {
    'label' => '[:irq:] :irqinfo:',
    'info'  => ':irq:: :irqinfo:',
    'type'  => 'GAUGE',
    'draw'  => 'LINE1'
  },
  'irq_sirq' =>
  {
    'label' => 'CPU:cpu: sIRQ/IRQ',
    'info'  => 'Total sIRQ/IRQ for CPU:cpu:',
    'type'  => 'GAUGE',
    'draw'  => 'LINE1'
  }
};

my $irq_types =
{
  'irq'  => 'Interrupts',
  'sirq' => 'Software interrupts'
};

my $irq_descriptions =
{
  'HI'      => 'High priority tasklets',
  'TIMER'   => 'Timer bottom half',
  'NET_TX'  => 'Transmit network packets',
  'NET_RX'  => 'Receive network packets',
  'SCSI'    => 'SCSI bottom half',
  'TASKLET' => 'Handles regular tasklets'
};

# ----------------- main ----------------
need_multigraph();
# -- autoconf --
if (defined($ARGV[0]) and ($ARGV[0] eq 'autoconf'))
{
  printf("%s\n", (-e "/proc/interrupts" and -e "/proc/softirqs") ? "yes" : "no (stats not exists)");
  exit (0);
}
# -- config --
load_irq_info();
if (defined($ARGV[0]) and ($ARGV[0] eq 'config')) { print_config(); exit (0); }
# -- values --
print_values(); exit(0);

# ----------------- sub's ----------------

# ----------------------- trim whitespace at begin and end of string ------------
sub trim
{
  my    ($string) = @_;
  for   ($string) { s/^\s+//; s/\s+$//; }
  return $string;
}


# -------------------------- loading irq stats ---------------------------------
sub load_irq_info_file
{
  my $file      = $_[0];
  my $info      = {};
  my $cpu_count = 0;
  my $summ      = 0;
  open (FH, '<', $file) or die "$! $file \n";
  for my $line (<FH>)
  {
    chomp $line;
    if($line =~ m/:/)
    {
      my ($name, $stat) = split(/:/, trim($line));
      my @data = split(/\s+/, trim($stat));
      for (my $i=0; $i<$cpu_count; $i++)
      {
        if (defined $data[$i])
        {
          $info->{'info'}{$i}{$name} = $data[$i];
          $info->{'summ'}{$i}       += $data[$i];
        }
      }
      if(scalar(@data) > $cpu_count)
      {
        my $iname = '';
        ($iname = $line) =~ s/^.*:(\s+\d+)+\s+(.*)$/$2/;
        if ($iname ne '')
        {
          if ($iname =~ m/.*-.*-.*/)
          {
            my @parts = ($iname =~ /^((\w+-)*\w+)\s+(.*)\s*$/g);
            $iname = sprintf("%s (%s)", $parts[0], $parts[2]);
          }
          $info->{'description'}{$name} = $iname;
        }
      }
      elsif(exists ($irq_descriptions->{$name}))
      {
        $info->{'description'}{$name} = $irq_descriptions->{$name};
      }
    }
    else
    {
      my @cpus = split(/\s+/, trim($line));
      $cpu_count = scalar(@cpus);
      for (my $i=0; $i<$cpu_count; $i++)
      {
        $info->{'summ'}{$i} = 0;
      }
    }
  }
  close (FH);
  $info->{'cpu_count'} = $cpu_count;
  return $info;
}

# -------------- loading all IRQ statistics ---------------------------
sub load_irq_info
{
  my ($irq, $sirq) = (load_irq_info_file("/proc/interrupts"), load_irq_info_file("/proc/softirqs"));
  $IRQi->{'stat'}{'irq' } = $irq ->{'info'};
  $IRQi->{'stat'}{'sirq'} = $sirq->{'info'};
  $IRQi->{'summ'}{'irq' } = $irq ->{'summ'};
  $IRQi->{'summ'}{'sirq'} = $sirq->{'summ'};
  $IRQi->{'description'}{'irq' } = $irq ->{'description'} if exists($irq ->{'description'});
  $IRQi->{'description'}{'sirq'} = $sirq->{'description'} if exists($sirq->{'description'});
  $IRQi->{'cpu_count'} = $irq ->{'cpu_count'};
}

# ------------------ loading limits ---------------------
sub load_limits
{
  my $flags = {};
  my $limits = {};
  my $name = '';
  for my $irq_type (qw(irq sirq))
  {
    for my $t (qw(warning critical))
    {
      $name = sprintf("%s_%s_total", $t, $irq_type); # env.warning_irq_total 22
      $limits->{'irq_total'}{$irq_type}{$t} = $ENV{$name} || undef;
      for (my $i=0; $i < $IRQi->{'cpu_count'}; $i++)
      {
        $name = sprintf("%s_cpu%s_%s_total", $t, $i, $irq_type); # env.warning_cpu1_sirq_total 1112
        $limits->{'irq_total_percpu'}{$irq_type}{$t}{$i} = $ENV{$name} || undef;
        for my $irq_name (keys %{$IRQi->{'stat'}{$irq_type}{$i}})
        {
          $name = sprintf("%s_cpu%s_%s_%s", $t, $i, $irq_type, $irq_name); # env.warning_cpu0_irq_7 25
          $limits->{'percpu_perirq'}{$irq_type}{$t}{$i}{$irq_name} = $ENV{$name} || undef;
          $name = sprintf("%s_%s_%s", $t, $irq_type, $irq_name);
          unless (exists($flags->{$name}))
          {
            $limits->{'perirq'}{$irq_type}{$t}{$irq_name} = $ENV{$name} || undef; # env.critical_sirq_RCU 14
            $flags->{$name} = 1;
          }
        }
      }
    }
  }
  return $limits;
}

# -------------------------------- replacing strings ------------------------
sub replace
{
  my ($string, $needle, $replacement) = @_[0..2];
  $string =~ s/$needle/$replacement/g;
  return $string;
}

# ----------------- append limit values to general graph fields-----------------------------
sub append_total_limit
{
  my ($limits, $gr, $irq_type, $field_name, $cpu_num) = @_[0..4];
  for my $t (qw(warning critical))
  {
    my $limit = defined($limits->{'irq_total_percpu'}{$irq_type}{$t}{$cpu_num}) ? $limits->{'irq_total_percpu'}{$irq_type}{$t}{$cpu_num} :
                ($limits->{'irq_total'}{$irq_type}{$t} || undef);
    if (defined($limit))
    {
      $gr->{'irq'}{'fields'}{$field_name}{$t} = $limit;
    }
  }
}

# ----------------- append limit values to chields graphs fields-----------------------------
sub append_cpu_limit
{
  my ($limits, $gr, $irq_type, $field_name, $graph_name, $cpu_num, $irq_name) = @_[0..6];
  for my $t (qw(warning critical))
  {
    my $limit = defined($limits->{'percpu_perirq'}{$irq_type}{$t}{$cpu_num}{$irq_name}) ? $limits->{'percpu_perirq'}{$irq_type}{$t}{$cpu_num}{$irq_name} :
                ($limits->{'perirq'}{$irq_type}{$t}{$irq_name} || undef);
    if (defined($limit))
    {
      $gr->{$graph_name}{'fields'}{$field_name}{$t} = $limit;
    }
  }
}

# ------------------------------ preparing graphs configurations ------------------------------
sub prepare_graphs
{
  my $gr = {};
  my $limits = load_limits();
  # --- general graph ---
  $gr->{'irq'}{'graph'} = $graphs->{'irq_total'};
  $gr->{'irq'}{'graph'}{'order'} = "";
  for (my $i=0; $i < $IRQi->{'cpu_count'}; $i++)
  {
    # --- general fields ---
    my ($up_field_name, $down_field_name) = (sprintf("i%s", $i), sprintf("si%s", $i));
    append_total_limit($limits, $gr, 'irq',  $up_field_name,   $i);
    append_total_limit($limits, $gr, 'sirq', $down_field_name, $i);
    $gr->{'irq'}{'graph'}{'order'} .= sprintf(" %s %s", $down_field_name, $up_field_name);
    $gr->{'irq'}{'fields'}{$up_field_name}{'type'}   = $fields->{'irq_sirq'}{'type'};
    $gr->{'irq'}{'fields'}{$down_field_name}{'type'} = $fields->{'irq_sirq'}{'type'};
    $gr->{'irq'}{'fields'}{$up_field_name}{'draw'}   = $fields->{'irq_sirq'}{'draw'};
    $gr->{'irq'}{'fields'}{$down_field_name}{'draw'} = $fields->{'irq_sirq'}{'draw'};

    $gr->{'irq'}{'fields'}{$up_field_name}{'label'} = replace($fields->{'irq_sirq'}{'label'}, ':cpu:', $i);
    $gr->{'irq'}{'fields'}{$up_field_name}{'info'}  = replace($fields->{'irq_sirq'}{'info'} , ':cpu:', $i);
    $gr->{'irq'}{'fields'}{$down_field_name}{'label'} = 'NaN';
    $gr->{'irq'}{'fields'}{$down_field_name}{'info'}  = 'NaN';

    $gr->{'irq'}{'fields'}{$up_field_name}{'negative'} = $down_field_name;
    $gr->{'irq'}{'fields'}{$down_field_name}{'graph'}  = 'no';

    # --- child graphs ---
    for my $irq_type (qw(irq sirq))
    {
      my $graph_name = sprintf("irq.%s_cpu%s", $irq_type, $i);
      $gr->{$graph_name}{'graph'}{'order'} = "";
      $gr->{$graph_name}{'graph'}{'args'} = $graphs->{'irq_cpu'}{'args'};
      $gr->{$graph_name}{'graph'}{'vlabel'} = $graphs->{'irq_cpu'}{'vlabel'};
      for my $go (qw(title info))
      {
        $gr->{$graph_name}{'graph'}{$go} = replace($graphs->{'irq_cpu'}{$go}, ':irq_type:', $irq_types->{$irq_type});
        $gr->{$graph_name}{'graph'}{$go} = replace($gr->{$graph_name}{'graph'}{$go}, ':cpu:', $i);
      }
      $gr->{$graph_name}{'graph'}{'category'} = replace($graphs->{'irq_cpu'}{'category'}, ':cpu:', $i);
      # -- child fields --
      my @irq_names = keys %{$IRQi->{'stat'}{$irq_type}{$i}};
      # names split for better sorting
      for my $irq_name ((
                          (sort {int $a <=> int $b} grep{/^\d/}            @irq_names),
                          (sort                     grep{!/(^\d|ERR|MIS)/} @irq_names),
                          (sort                     grep{/(ERR|MIS)/     } @irq_names)
                        ))
      {
        my $field_name = clean_fieldname(sprintf("irq_%s", $irq_name));
        append_cpu_limit($limits, $gr, $irq_type, $field_name, $graph_name, $i, $irq_name);
        $gr->{$graph_name}{'graph'}{'order'} .= ' '.$field_name;
        for my $this_field (qw(label info))
        {
          $gr->{$graph_name}{'fields'}{$field_name}{$this_field} = replace($fields->{'irq'}{$this_field}, ':irq:', $irq_name);
          $gr->{$graph_name}{'fields'}{$field_name}{$this_field} = replace($gr->{$graph_name}{'fields'}{$field_name}{$this_field},
                                                                   ':irqinfo:',
                                                                   exists($IRQi->{'description'}{$irq_type}{$irq_name}) ?
                                                                    $IRQi->{'description'}{$irq_type}{$irq_name} :
                                                                    '');
        }
        $gr->{$graph_name}{'fields'}{$field_name}{'type'} = $fields->{'irq'}{'type'};
        $gr->{$graph_name}{'fields'}{$field_name}{'draw'} = $fields->{'irq'}{'draw'};
      }
    }
  }
  return $gr;
}

# --------------------------------- graph configs ----------------------------
sub print_config
{
  my $config = prepare_graphs();
  for my $g (sort keys %{$config})
  {
    printf("multigraph %s\n", $g);
    for my $go (sort keys %{$config->{$g}{'graph'}}) { printf("graph_%s %s\n", $go, $config->{$g}{'graph'}{$go}); }
    for my $f (sort keys %{$config->{$g}{'fields'}}) { for my $this_field (sort keys %{$config->{$g}{'fields'}{$f}}) { printf("%s.%s %s\n", $f, $this_field, $config->{$g}{'fields'}{$f}{$this_field}); } }
    print "\n";
  }
}

# ----------------------------------- saving state data using munin --------------------
sub save_state_data
{
  my $data = $_[0];
  my $d = Data::Dumper->new([$data]);
  $d->Indent(0);
  save_state($d->Dump);
}

# -------------------------------- loading previous state data using munin -------------------
sub restore_state_data
{
  my $VAR1;
  my $states = (restore_state())[0];
  eval $states if defined $states;
  return $VAR1;
}

# ----------------------------- loading statistic and save it for feature use--------------
sub load_stats
{
  delete ($IRQi->{'description'});
  $IRQi->{'timestamp'} = time();
  save_state_data($IRQi);
  return $IRQi;
}

# ----------- calculate current and previous values difference -----------------------
sub diff_value
{
  my ($pvalue, $cvalue, $timediff) = @_[0..2];
  return 'NaN' if $timediff <= 0 or $pvalue > $cvalue;
  return ($cvalue - $pvalue)/$timediff;
}

# -----------------  calculating values ---------------------
sub calculate
{
  my ($pstats, $cstats) = @_[0..1];
  my $data = {};
  my $timediff = $cstats->{'timestamp'} - $pstats->{'timestamp'};
  for my $irq_type (qw(irq sirq))
  {
    for (my $i=0; $i < $IRQi->{'cpu_count'}; $i++)
    {
      $data->{'summ'}{$irq_type}{$i} = diff_value($pstats->{'summ'}{$irq_type}{$i}, $cstats->{'summ'}{$irq_type}{$i}, $timediff);
      for my $irq_name (keys %{$cstats->{'stat'}{$irq_type}{$i}})
      {
        $data->{'stat'}{$irq_type}{$i}{$irq_name} = diff_value($pstats->{'stat'}{$irq_type}{$i}{$irq_name}, $cstats->{'stat'}{$irq_type}{$i}{$irq_name}, $timediff);
      }
    }
  }
  return $data;
}

# --------------------- preparing graphs values config ------------------------
sub prepare_graphs_values
{
  my $data = $_[0];
  my $values = {};
  for (my $i=0; $i < $IRQi->{'cpu_count'}; $i++)
  {
    $values->{'irq'}{sprintf("i%s",  $i)} = $data->{'summ'}{'irq'} {$i};
    $values->{'irq'}{sprintf("si%s", $i)} = $data->{'summ'}{'sirq'}{$i};
    for my $irq_type (qw(irq sirq))
    {
      my $graph_name = sprintf("irq.%s_cpu%s", $irq_type, $i);
      for my $irq_name (keys %{$data->{'stat'}{$irq_type}{$i}})
      {
        my $field_name = clean_fieldname(sprintf("irq_%s", $irq_name));
        $values->{$graph_name}{$field_name} = $data->{'stat'}{$irq_type}{$i}{$irq_name};
      }
    }
  }
  return $values;
}

# -------------------------------- printing values -----------------------------------
sub print_values
{
  my $pstats = restore_state_data();
  my $cstats = load_stats();
  if (exists ($pstats->{'timestamp'}))
  {
    my $values = prepare_graphs_values(calculate($pstats, $cstats));
    for my $g (sort keys %{$values})
    {
      printf("multigraph %s\n", $g);
      for my $f (sort keys %{$values->{$g}}) { printf("%s.value %s\n", $f, $values->{$g}{$f}); }
      print "\n";
    }
  }
}