Repository
Munin (contrib)
Last change
2021-12-13
Graph Categories
Family
auto
Capabilities
Keywords
Language
Bash
License
GPL-3.0-or-later
Authors

mikrotik_system

Name

mikrotik_system - This plugin fetches multiple Values from a mikrotik device.

Configuration

tested with a RB493G, RB750GR3, RB5009 and CRS309. Dependencies: - sshpass

A User is needed for this plugin to work:

/user add name=munin group=read password=hallowelt address=<munin-server-ip>

plugin config: [mt_system_<name>] user root env.ssh_user munin env.ssh_password hallowelt env.ssh_host 192.168.2.1

Author

Michael Grote

License

GPLv3 or later

SPDX-License-Identifier: GPL-3.0-or-later

Magic Markers

#%# family=auto
#!/bin/bash
#%# family=auto

: << EOF
=head1 NAME

mikrotik_system - This plugin fetches multiple Values from a mikrotik device.

=head1 CONFIGURATION

tested with a RB493G, RB750GR3, RB5009 and CRS309.
Dependencies:
- sshpass

A User is needed for this plugin to work:

  /user add name=munin group=read password=hallowelt address=<munin-server-ip>

plugin config:
  [mt_system_<name>]
  user root
  env.ssh_user munin
  env.ssh_password hallowelt
  env.ssh_host 192.168.2.1

=head1 AUTHOR

Michael Grote

=head1 LICENSE

GPLv3 or later

SPDX-License-Identifier: GPL-3.0-or-later

=head1 MAGIC MARKERS

 #%# family=auto

=cut

EOF

# set variables with default values
ssh_user=${ssh_user:-user}
ssh_password=${ssh_password:-password}
ssh_host=${ssh_host:-192.168.2.1}
c=0 # counter, it is used in a few loops

# Function to get stderr from command
# USAGE: catch STDOUT STDERR cmd args..
# Source: https://stackoverflow.com/a/41069638
catch()
{
eval "$({
__2="$(
  { __1="$("${@:3}")"; } 2>&1;
  ret=$?;
  printf '%q=%q\n' "$1" "$__1" >&2;
  exit $ret
  )";
ret="$?";
printf '%s=%q\n' "$2" "$__2" >&2;
printf '( exit %q )' "$ret" >&2;
} 2>&1 )";
}

# functions
function get_name {
  local fname
  IFS=$'\n'; for line in $data; do
    if echo "$line" | grep -q 'name:'; then
      fname="$(echo "$line" | cut -d: -f2 | xargs | sed 's/[\s\n\r]*$//g' | sed 's/[^A-Za-z0-9]/_/g')"
      if [ -n "$fname" ]; then
        name="$fname"
      fi
    fi
  done
}
function get_ros_version {
  while read -r line; do
    if echo "$line" | grep -q 'version: '; then
      if echo "$line" | awk '/version:/{print $2}' | grep -q  "^[0-6]\."; then
        ros_version="6"
      else
        ros_version="7"
      fi
    fi
  done <<< "$data"
}
function get_cpu_count {
  while read -r line; do
    if echo "$line" | grep -q 'cpu-count'; then
      anzahl_cpu=$(echo "$line" | grep cpu-count: | sed -r 's/(cpu-count: )([0-9]+)/\2/g' | tr -dc '0-9')
    fi
  done <<< "$data"
}
function get_data {
  # fetch data per ssh
  catch data stderr sshpass -p  "$ssh_password" ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "$ssh_user"@"$ssh_host" ':delay 6s; /system health print; /system resource print; /system resource cpu print; /system identity print'
  data_ret=$?
}
function validate_data {
  if [ $data_ret -ne 0 ]; then
    echo "SSH returned errorcode = $data_ret:"
    echo "$stderr"
    exit $data_ret
  fi
}
function get_mem_total {
  mem_total=$(
    while read -r line; do
      echo "$line" | awk '/total-memory:/{ gsub(/MiB/,"",$2); print $2 }' | tr -dc '0-9.'
    done <<< "$data")
}
function get_mem_free {
  mem_free=$(
    while read -r line; do
      echo "$line" | awk '/free-memory:/{ gsub(/MiB/,"",$2); print $2 }' | tr -dc '0-9.'
    done <<< "$data")
}

function get_voltage_label {
  while read -r line; do # for every line
    # output line
    # search with awk for "voltage:"
    # if found
    # print line
    # external/bash-variables with "'"<var>"'"
    echo "$line" | awk '/voltage:/{
      print "multigraph voltage_graph_""'"$name"'";
      print "graph_title voltage " "'"$name"'";
      print "graph_vlabel volt";
      print "graph_category mikrotik";
      print "graph_args -l 0";
      print "voltage.label voltage";
      print "graph_info Input Voltage."
      }'
  done <<< "$data" # der variable data
}
function get_voltage_value {
  while read -r line; do
    # like the label functions
    # print title if dirtyconfig is not set
    # because the call does not come in "config"
    if [ "$MUNIN_CAP_DIRTYCONFIG" = "0" ] || [ -z "$MUNIN_CAP_DIRTYCONFIG" ]; then
      echo "$line" | awk '/voltage:/{
        print "multigraph voltage_graph_""'"$name"'";
        }'
    fi
    # remove with gsub the char % in argument $2
    # print $2
    echo "$line" | awk '/voltage:/{
      gsub(/V/,"",$2);
      print "voltage.value " $2
      }'
  done <<< "$data"
}

function get_bad_blocks_label {
  while read -r line; do
    echo "$line" | awk '/bad-blocks:/{
      print "multigraph bad_blocks_graph_""'"$name"'";
      print "graph_title bad blocks " "'"$name"'";
      print "graph_vlabel %";
      print "graph_category mikrotik";
      print "graph_args -l 0 --upper-limit 5";
      print "bad_blocks.label bad_blocks";
      print "bad_blocks.warning 3";
      print "bad_blocks.critical 4";
      print "graph_info Percentage of Bad Blocks."
      }'
  done <<< "$data"
}
function get_bad_blocks_value {
  while read -r line; do
    if [ "$MUNIN_CAP_DIRTYCONFIG" = "0" ] || [ -z "$MUNIN_CAP_DIRTYCONFIG" ]; then
    echo "$line" | awk '/bad-blocks:/{
      print "multigraph bad_blocks_graph_""'"$name"'";
      }'
    fi
    echo "$line" | awk '/bad-blocks:/{
      gsub(/%/,"",$2);
      print "bad_blocks.value " $2
      }'
  done <<< "$data"
}

function get_write_sect_total_label {
  while read -r line; do
    echo "$line" | awk '/write-sect-total:/{
      print "multigraph write_sect_total_graph_""'"$name"'";
      print "graph_title Total sector writes " "'"$name"'";
      print "graph_vlabel count";
      print "graph_category mikrotik";
      print "graph_args -l 0";
      print "write_sect_total.label write_sect_total";
      print "graph_info Total sector writes."
      }'
  done <<< "$data"
}
function get_write_sect_total_value {
  while read -r line; do
    if [ "$MUNIN_CAP_DIRTYCONFIG" = "0" ] || [ -z "$MUNIN_CAP_DIRTYCONFIG" ]; then
      echo "$line" | awk '/write-sect-total:/{
        print "multigraph write_sect_total_graph_'"$name"'";
        }'
    fi
    echo "$line" | awk '/write-sect-total:/{
      print "write_sect_total.value " $2
      }'
  done <<< "$data"
}

function get_write_sect_since_reboot_label {
  while read -r line; do
    echo "$line" | awk '/write-sect-since-reboot:/{
      print "multigraph write_sect_since_reboot_graph_'"$name"'";
      print "graph_title Sector writes since reboot " "'"$name"'";
      print "graph_vlabel count";
      print "graph_category mikrotik";
      print "graph_args -l 0";
      print "write_sect_since_reboot.label write_sect_since_reboot";
      print "graph_info Total Sector writes since last reboot."
      }'
  done <<< "$data"
}
function get_write_sect_since_reboot_value {
  while read -r line; do
    if [ "$MUNIN_CAP_DIRTYCONFIG" = "0" ] || [ -z "$MUNIN_CAP_DIRTYCONFIG" ]; then
      echo "$line" | awk '/write-sect-since-reboot:/{
        print "multigraph write_sect_since_reboot_graph_""'"$name"'";
      }'
    fi
    echo "$line" | awk '/write-sect-since-reboot:/{
      print "write_sect_since_reboot.value " $2
      }'
  done <<< "$data"
}

function get_temperature_label {
  while read -r line; do
    echo "$line" | awk '/temperature/{
      print "multigraph temperature_graph_""'"$name"'";
      print "graph_title temperature " "'"$name"'";
      print "graph_vlabel °C";
      print "graph_category mikrotik";
      print "graph_args -l 0";
      print "temperature.label cpu temperature";
      print "temperature.warning 75";
      print "temperature.critical 90"
      }'
  done <<< "$data"
}
function get_temperature_value {
  get_ros_version
  while read -r line; do
    if [ "$MUNIN_CAP_DIRTYCONFIG" = "0" ] || [ -z "$MUNIN_CAP_DIRTYCONFIG" ]; then
      echo "$line" | awk '/temperature/{
        print "multigraph temperature_graph_""'"$name"'";
        }'
    fi
    if [ "$ros_version" == "6" ]; then
      echo "$line" | awk '/temperature:/{
        gsub(/C/,"",$2);
        print "temperature.value " $2
        }'
    fi
    if [ "$ros_version" == "7" ]; then
      echo "$line" | awk '/cpu-temperature/{
        print "temperature.value " $3
        }'
    fi
  done <<< "$data"
}

function get_cpu_label {
  echo multigraph cpu_load_graph_"$name"
  echo graph_title cpu load "$name"
  echo graph_vlabel %
  echo graph_category mikrotik
  echo graph_args -l 0 --upper-limit 100
  echo cpu_total.label total load
  echo cpu_total.warning 75
  echo cpu_total.critical 90
  echo graph_info Total CPU Load and Load, IRQ and Disk per Core.

  while [ "$c" -lt "$anzahl_cpu" ]; do
    while read -r line; do
      echo "$line" | grep cpu$c | awk '{
        print "cpu"'"$c"'"_load.label " "cpu" "'"$c"'" " load"
        print "cpu"'"$c"'"_irq.label " "cpu" "'"$c"'" " irq"
        print "cpu"'"$c"'"_disk.label " "cpu" "'"$c"'" " disk"
        }'
    done <<< "$data"
    c=$(( c + 1 ))
  done
}
function get_cpu_value {
  if [ "$MUNIN_CAP_DIRTYCONFIG" = "0" ] || [ -z "$MUNIN_CAP_DIRTYCONFIG" ]; then
    echo multigraph cpu_load_graph_"$name"
  fi
  while read -r line; do
    echo "$line" | awk '/cpu-load:/{
      gsub(/%/,"",$2);
      print "cpu_total.value " $2
      }'
  done <<< "$data"
  c=0
  while [ "$c" -lt "$anzahl_cpu" ]; do
    while read -r line; do
      echo "$line" | grep cpu$c | awk '{
        gsub(/%/,"",$3);
        gsub(/%/,"",$4);
        gsub(/%/,"",$5);
        print "cpu"'"$c"'"_load.value " $3
        print "cpu"'"$c"'"_irq.value " $4
        print "cpu"'"$c"'"_disk.value " $5
        }'
    done <<< "$data"
    c=$(( c + 1 ))
  done
}

function get_memory_label {
  get_mem_total
  get_mem_free
  while read -r line; do
    echo "$line" | awk -v name=$name '/free-memory:/{
      printf "multigraph memory_graph_%s\n", name;
      printf "graph_title memory %s\n", name;
      print "graph_vlabel MiB";
      print "graph_category mikrotik";
      print "graph_args -l 0";
      print "total_memory.label total memory";
      print "used_memory.label used memory";
      print "free_memory.label free memory";
      print "graph_info Total, Used & free RAM.";
      gsub(/MiB/,"",$2);
      printf "used_memory.critical %.0f\n", $2*0.9;
      printf "used_memory.warning %.0f\n", $2*0.7 }'
  done <<< "$data"
}
function get_memory_value {
  get_mem_total
  get_mem_free
  if [ "$MUNIN_CAP_DIRTYCONFIG" = "0" ] || [ -z "$MUNIN_CAP_DIRTYCONFIG" ]; then
    echo multigraph memory_graph_"$name"
  fi
  while read -r line; do
    echo "$line" | awk '/total-memory:/{
        gsub(/MiB/,"",$2);
        printf "total_memory.value %.0f\n", $2
      }'
  done <<< "$data"
  while read -r line; do
    echo "$line" | awk '/free-memory:/{
        gsub(/MiB/,"",$2);
        printf "free_memory.value %.0f\n", $2
      }'
  done <<< "$data"
  # calculate used-memory
  # total - free = used
  printf "used_memory.value %.0f\n" "$(echo $mem_total $mem_free | awk '{print ($1 - $2)}')"
}

function get_disk_label {
  while read -r line; do
    echo "$line" | awk -v name=$name '/free-hdd-space:/{
      printf "multigraph disk_graph_%s\n", name;
      printf "graph_title disk %s\n", name;
      print "graph_vlabel Bytes";
      print "graph_category mikrotik";
      print "graph_args -l 0";
      print "total_disk.label total disk space";
      print "free_disk.label free disk space";
      print "graph_info Total & free disk space."}'
  done <<< "$data"
}
function get_disk_value {
  if [ "$MUNIN_CAP_DIRTYCONFIG" = "0" ] || [ -z "$MUNIN_CAP_DIRTYCONFIG" ]; then
    echo multigraph disk_graph_"$name"
  fi
  while read -r line; do
    echo "$line" | grep KiB | awk '/free-hdd-space:/ {
      gsub(/KiB/,"",$2)
      printf "free_disk.value %d\n", $2*1024 }'
    echo "$line" |  grep MiB | awk '/free-hdd-space:/ {
      gsub(/MiB/,"",$2)
      printf "free_disk.value %d\n", $2*1048576}'
    echo "$line" | grep KiB | awk '/total-hdd-space:/ {
      gsub(/KiB/,"",$2)
      printf "total_disk.value %d\n", $2*1024 }'
    echo "$line" |  grep MiB | awk '/total-hdd-space:/ {
      gsub(/MiB/,"",$2)
      printf "total_disk.value %d\n", $2*1048576 }'
  done <<< "$data"
}

# call functions, the order is important
get_data
validate_data
get_name
get_cpu_count
# munin-Logic
# id $1 = X; then
if [ "$1" = "autoconf" ]; then
    if ! command -v sshpass &> /dev/null; then
        echo "[ERROR] sshpass could not be found!"
    else
        echo "yes"
    fi
    exit 0
fi
if [ "$1" = "config" ]; then
  # print label
  # if dirtyconfig is set print the value
  get_voltage_label
  if [ "$MUNIN_CAP_DIRTYCONFIG" = "1" ]; then
    get_voltage_value
  fi
  get_bad_blocks_label
  if [ "$MUNIN_CAP_DIRTYCONFIG" = "1" ]; then
    get_bad_blocks_value
  fi
  get_write_sect_total_label
  if [ "$MUNIN_CAP_DIRTYCONFIG" = "1" ]; then
    get_write_sect_total_value
  fi
  get_write_sect_since_reboot_label
  if [ "$MUNIN_CAP_DIRTYCONFIG" = "1" ]; then
    get_write_sect_since_reboot_value
  fi
  get_temperature_label
  if [ "$MUNIN_CAP_DIRTYCONFIG" = "1" ]; then
    get_temperature_value
  fi
  get_cpu_label
  if [ "$MUNIN_CAP_DIRTYCONFIG" = "1" ]; then
    get_cpu_value
  fi
  get_memory_label
  if [ "$MUNIN_CAP_DIRTYCONFIG" = "1" ]; then
    get_memory_value
  fi
  get_disk_label
  if [ "$MUNIN_CAP_DIRTYCONFIG" = "1" ]; then
    get_disk_value
  fi
  exit 0
fi
# does not get called if dirtyconfig is set
get_voltage_value
get_bad_blocks_value
get_write_sect_total_value
get_write_sect_since_reboot_value
get_temperature_value
get_cpu_value
get_memory_value
get_disk_value
exit 0

# Example Output ROS 6.4*
#  voltage: 24.5V
#   cpu-temperature: 31C
#                    uptime: 4w3d21h28m58s
#                   version: 6.48.4 (stable)
#                build-time: Aug/18/2021 06:43:27
#          factory-software: 6.44.6
#               free-memory: 475.8MiB
#              total-memory: 512.0MiB
#                       cpu: ARMv7
#                 cpu-count: 2
#             cpu-frequency: 800MHz
#                  cpu-load: 9%
#            free-hdd-space: 2124.0KiB
#           total-hdd-space: 15.9MiB
#   write-sect-since-reboot: 339810
#          write-sect-total: 350544
#                bad-blocks: 0%
#         architecture-name: arm
#                board-name: CRS309-1G-8S+
#                  platform: MikroTik
#  # CPU                                                                                                                                         LOAD         IRQ        DISK
#  0 cpu0                                                                                                                                          0%          0%          0%
#  1 cpu1                                                                                                                                         19%          0%          0%
#   name: crs309

# Example Output ROS 7*
# Columns: NAME, VALUE, TYPE
#   #  NAME             VA  T
#   0  cpu-temperature  44  C
#                    uptime: 3d1h30m
#                   version: 7.0.5 (stable)
#                build-time: Aug/06/2021 11:33:39
#          factory-software: 7.0.4
#               free-memory: 818.8MiB
#              total-memory: 1024.0MiB
#                 cpu-count: 4
#             cpu-frequency: 1400MHz
#                  cpu-load: 0%
#            free-hdd-space: 993.8MiB
#           total-hdd-space: 1025.0MiB
#   write-sect-since-reboot: 4802
#          write-sect-total: 4802
#                bad-blocks: 0%
#         architecture-name: arm64
#                board-name: RB5009UG+S+
#                  platform: MikroTik
# Columns: CPU, LOAD, IRQ, DISK
#   #  CPU   LO  IR  DI
#   0  cpu0  0%  0%  0%
#   1  cpu1  0%  0%  0%
#   2  cpu2  0%  0%  0%
#   3  cpu3  0%  0%  0%
#   name: rb5009
#