422 lines
14 KiB
Markdown
422 lines
14 KiB
Markdown
|
# SNMP plugin tutorial
|
||
|
|
||
|
All files showed in this section can be found on the centreon-plugins GitHub in
|
||
|
the [tutorial](https://github.com/centreon/centreon-plugins/tree/develop/src/contrib/tutorial) **contrib**
|
||
|
section.
|
||
|
|
||
|
> You have to move the contents of `contrib/tutorial/apps/` to `apps/` if you want to run it for testing purposes.
|
||
|
>
|
||
|
> `cp -R src/contrib/tutorial/network/* src/network/`
|
||
|
|
||
|
You also need to be able to use linux standard snmpwalk in your development environment.
|
||
|
If you can't, you can use [this snmpwalk](https://github.com/centreon/centreon-plugins/blob/develop/tests/resources/snmp/os_linux_snmp_plugin.snmpwalk) coupled with snmpsim (in Docker for example)
|
||
|
|
||
|
**Description**
|
||
|
|
||
|
This example explains how to check a single SNMP oid value to check system CPUs.
|
||
|
|
||
|
## 1. Understand the data
|
||
|
|
||
|
Understanding the data is very important as it will drive the way you will design
|
||
|
the **mode** internals. This is the **first thing to do**, no matter what protocol you
|
||
|
are using.
|
||
|
|
||
|
There are several important properties for a piece of data:
|
||
|
|
||
|
- Type of the data to process: string, int... There is no limitation in the kind of data you can process
|
||
|
- Dimensions of the data, is it **global** or linked to an **instance**?
|
||
|
- Data layout, in other words anticipate the kind of **data structure** to manipulate.
|
||
|
|
||
|
Here we use a very simple example with only one oid value : `hrProcessorLoad` = `.1.3.6.1.2.1.25.3.3.1.2`
|
||
|
If you use [this snmpwalk](https://github.com/centreon/centreon-plugins/blob/develop/tests/resources/snmp/os_linux_snmp_plugin.snmpwalk) you have this values :
|
||
|
```
|
||
|
.1.3.6.1.2.1.25.3.3.1.2.768 = INTEGER: 6
|
||
|
.1.3.6.1.2.1.25.3.3.1.2.769 = INTEGER: 16
|
||
|
```
|
||
|
- the `cpu` node contains integer values (`6`, `16`) referring to specific **instances** (`768`, `769`). The structure is an array of hashes
|
||
|
|
||
|
## 2. Create directories for a new plugin
|
||
|
|
||
|
Create directories and files required for your **plugin** and **modes**.
|
||
|
|
||
|
Go to your centreon-plugins local git and create the appropriate directories and files:
|
||
|
|
||
|
```shell
|
||
|
# path to the main directory and the subdirectory containing modes
|
||
|
mkdir -p src/network/mysnmpplugin/snmp/mode
|
||
|
# path to the main plugin file
|
||
|
touch network/mysnmpplugin/snmp/plugin.pm
|
||
|
# path to the specific mode(s) file(s) => for example appsmetrics.pm
|
||
|
touch network/mysnmpplugin/snmp/mode/cpu.pm
|
||
|
```
|
||
|
|
||
|
## 3. Create the plugin file : plugin.pm
|
||
|
|
||
|
Edit **plugin.pm** and add the following lines:
|
||
|
|
||
|
```perl
|
||
|
#
|
||
|
# Copyright 2023 Centreon (http://www.centreon.com/)
|
||
|
#
|
||
|
# Centreon is a full-fledged industry-strength solution that meets
|
||
|
# the needs in IT infrastructure and application monitoring for
|
||
|
# service performance.
|
||
|
#
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
# you may not use this file except in compliance with the License.
|
||
|
# You may obtain a copy of the License at
|
||
|
#
|
||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
# See the License for the specific language governing permissions and
|
||
|
# limitations under the License.
|
||
|
#
|
||
|
|
||
|
# Path to the plugin
|
||
|
package network::mysnmpplugin::snmp::plugin;
|
||
|
|
||
|
# Needed libraries
|
||
|
use strict;
|
||
|
use warnings;
|
||
|
# Use this library to check using SNMP protocol
|
||
|
use base qw(centreon::plugins::script_snmp);
|
||
|
```
|
||
|
> **TIP** : Don't forget to edit 'Authors' line.
|
||
|
|
||
|
Add ```new``` method to instantiate the plugin:
|
||
|
|
||
|
```perl
|
||
|
sub new {
|
||
|
my ( $class, %options ) = @_;
|
||
|
my $self = $class->SUPER::new(package => __PACKAGE__, %options);
|
||
|
bless $self, $class;
|
||
|
# $options->{options} = options object
|
||
|
|
||
|
# Modes association
|
||
|
$self->{modes} = {
|
||
|
# Mode name => path to the mode
|
||
|
'cpu' => 'network::mysnmpplugin::snmp::mode::cpu'
|
||
|
};
|
||
|
|
||
|
return $self;
|
||
|
}
|
||
|
```
|
||
|
Declare this plugin as a perl module:
|
||
|
|
||
|
```perl
|
||
|
1;
|
||
|
```
|
||
|
Add a description to the plugin:
|
||
|
|
||
|
```perl
|
||
|
__END__
|
||
|
|
||
|
=head1 PLUGIN DESCRIPTION
|
||
|
|
||
|
Check my-plugin-snmp CPU through SNMP.
|
||
|
|
||
|
=cut
|
||
|
```
|
||
|
> **TIP** : This description is printed with '--help' option.
|
||
|
|
||
|
To test if this plugin file works you can run this command:
|
||
|
|
||
|
`perl centreon_plugins.pl --plugin=apps::mysnmpplugin:::snmp::plugin --list-mode`
|
||
|
|
||
|
It already outputs a lot of things. Ellipsized lines are basically all standard capabilities
|
||
|
inherited from the **script_custom** base.
|
||
|
|
||
|
```perl
|
||
|
Plugin Description:
|
||
|
Check CPU through SNMP.
|
||
|
|
||
|
Global Options:
|
||
|
--mode Choose a mode.
|
||
|
[...]
|
||
|
--version
|
||
|
Display plugin version.
|
||
|
[...]
|
||
|
|
||
|
Modes Available:
|
||
|
cpu
|
||
|
|
||
|
```
|
||
|
|
||
|
## 4. Create the mode file : cpu.pm
|
||
|
|
||
|
### 4.1 Common declarations and new constructor
|
||
|
|
||
|
Edit **cpu.pm** and add the following lines:
|
||
|
|
||
|
```perl
|
||
|
#
|
||
|
# Copyright 2023 Centreon (http://www.centreon.com/)
|
||
|
#
|
||
|
# Centreon is a full-fledged industry-strength solution that meets
|
||
|
# the needs in IT infrastructure and application monitoring for
|
||
|
# service performance.
|
||
|
#
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
# you may not use this file except in compliance with the License.
|
||
|
# You may obtain a copy of the License at
|
||
|
#
|
||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
# See the License for the specific language governing permissions and
|
||
|
# limitations under the License.
|
||
|
#
|
||
|
|
||
|
# Path to the plugin
|
||
|
package network::mysnmpplugin::snmp::mode::cpu;
|
||
|
|
||
|
# Consider this as mandatory when writing a new mode.
|
||
|
use base qw(centreon::plugins::templates::counter);
|
||
|
|
||
|
# Needed libraries
|
||
|
use strict;
|
||
|
use warnings;
|
||
|
|
||
|
```
|
||
|
|
||
|
Add a `new` function (sub) to initialize the mode:
|
||
|
|
||
|
```perl
|
||
|
sub new {
|
||
|
my ($class, %options) = @_;
|
||
|
# All options/properties of this mode, always add the force_new_perfdata => 1 to enable new metric/performance data naming.
|
||
|
# It also where you can specify that the plugin uses a cache file for example
|
||
|
my $self = $class->SUPER::new(package => __PACKAGE__, %options, force_new_perfdata => 1);
|
||
|
bless $self, $class;
|
||
|
|
||
|
# Declare options
|
||
|
$options{options}->add_options(arguments => {
|
||
|
# One the left it's the option name that will be used in the command line. The ':s' at the end is to
|
||
|
# define that this options takes a value.
|
||
|
# On the right, it's the code name for this option, optionnaly you can define a default value so the user
|
||
|
# doesn't have to set it.
|
||
|
# option name => variable name
|
||
|
'filter-id:s' => { name => 'filter_id' }
|
||
|
});
|
||
|
|
||
|
return $self;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### 4.2 Declare your counters
|
||
|
|
||
|
This part essentially maps the data you want to get from the SNMP with the internal
|
||
|
counter mode structure.
|
||
|
|
||
|
Remember how we categorized the data in the previous section understand-the-data.
|
||
|
|
||
|
The `$self->{maps_counters_type}` data structure describes these data while the `$self->{maps_counters}->{global}` one defines
|
||
|
their properties like thresholds and how they will be displayed to the users.
|
||
|
|
||
|
```perl
|
||
|
sub set_counters {
|
||
|
my ($self, %options) = @_;
|
||
|
|
||
|
$self->{maps_counters_type} = [
|
||
|
# cpu will receive value for both instances (768 and 769) : the type => 1 explicits that
|
||
|
# You can define a callback (cb) function to manage the output prefix. This function is called
|
||
|
# each time a value is passed to the counter and can be shared across multiple counters.
|
||
|
{ name => 'cpu', type => 1, cb_prefix_output => 'prefix_cpu_output', message_multiple => 'All CPUs are ok' }
|
||
|
];
|
||
|
|
||
|
$self->{maps_counters}->{cpu} = [
|
||
|
{ label => 'cpu-usage-prct', nlabel => 'cpu.usage.percentage', set => {
|
||
|
key_values => [ { name => 'cpu_usage' }, { name => 'name' } ],
|
||
|
output_template => '%.2f %%',
|
||
|
perfdatas => [
|
||
|
# we add the label_extra_instance option to have one perfdata per instance
|
||
|
{ label => 'cpu', template => '%.2f', min => 0, max => 100, unit => '%', label_extra_instance => 1, instance_use => 'name' }
|
||
|
]
|
||
|
}
|
||
|
}
|
||
|
];
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### 4.3 Create prefix callback functions
|
||
|
|
||
|
These functions are not mandatory but help to make the output more readable for a human. We will create
|
||
|
it now but as you have noticed the mode compiles so you can choose to keep those for the polishing moment.
|
||
|
|
||
|
During counters definitions, we associated a callback function like this :
|
||
|
- `cb_prefix_output => 'prefix_cpu_output'`
|
||
|
|
||
|
Define those function by adding it to our `cpu.pm` file. It is self-explanatory.
|
||
|
|
||
|
```perl
|
||
|
sub prefix_cpu_output {
|
||
|
my ($self, %options) = @_;
|
||
|
|
||
|
return "CPU '" . $options{instance_value}->{name} . "' usage: ";
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### 4.4 Get raw data from SNMP and understand the data structure
|
||
|
|
||
|
It's the moment to write the main sub (`manage_selection`) - the most complex, but also the one that
|
||
|
will transform your mode to something useful and alive.
|
||
|
|
||
|
Think about the logic, what we have to do is:
|
||
|
|
||
|
- Query a specific path corresponding to a SNMP oid
|
||
|
- Store and process the result
|
||
|
- Spread this result across counters definitions
|
||
|
|
||
|
```perl
|
||
|
sub manage_selection {
|
||
|
my ($self, %options) = @_;
|
||
|
###################################################
|
||
|
##### Load SNMP informations to a result hash #####
|
||
|
###################################################
|
||
|
|
||
|
# Select relevant oids for CPU monitoring
|
||
|
my $mapping = {
|
||
|
# hashKey => { oid => 'oid_number_path'}
|
||
|
hrProcessorID => { oid => '.1.3.6.1.2.1.25.3.3.1.1' },
|
||
|
hrProcessorLoad => { oid => '.1.3.6.1.2.1.25.3.3.1.2' }
|
||
|
#
|
||
|
};
|
||
|
|
||
|
# Point at the begining of the SNMP table
|
||
|
# Oid to point the table ahead all the oids given in mapping
|
||
|
my $oid_hrProcessorTable = '.1.3.6.1.2.1.25.3.3.1';
|
||
|
|
||
|
# Use SNMP Centreon plugins tools to push SNMP result in hash to handle with.
|
||
|
my $cpu_result = $options{snmp}->get_table(
|
||
|
oid => $oid_hrProcessorTable,
|
||
|
nothing_quit => 1
|
||
|
);
|
||
|
|
||
|
###################################################
|
||
|
##### SNMP Result table to browse #####
|
||
|
###################################################
|
||
|
foreach my $oid (keys %{$cpu_result}) {
|
||
|
next if ($oid !~ /^$mapping->{hrProcessorID}->{oid}\.(.*)$/);
|
||
|
|
||
|
# Catch table instance if exist :
|
||
|
# Instance is a number availible for a same oid refering to different target
|
||
|
my $instance = $1;
|
||
|
# Uncomment the lines below to see what instance looks like :
|
||
|
|
||
|
# use Data::Dumper;
|
||
|
# print Dumper($oid);
|
||
|
# print Dumper($instance);
|
||
|
|
||
|
# Data Dumper returns : with oid = hrProcessorID.instance
|
||
|
# $VAR1 = '.1.3.6.1.2.1.25.3.3.1.1.769';
|
||
|
# $VAR1 = '769';
|
||
|
# $VAR1 = '.1.3.6.1.2.1.25.3.3.1.1.768';
|
||
|
# $VAR1 = '768';
|
||
|
|
||
|
my $result = $options{snmp}->map_instance(mapping => $mapping, results => $cpu_result, instance => $instance);
|
||
|
|
||
|
# Here is the way to handle with basic name/id filter.
|
||
|
# This filter is compare with hrProcessorID and in case of no match the oid is skipped
|
||
|
if (defined($self->{option_results}->{filter_id}) && $self->{option_results}->{filter_id} ne '' &&
|
||
|
$result->{hrProcessorID} !~ /$self->{option_results}->{filter_id}/) {
|
||
|
$self->{output}->output_add(long_msg => "skipping '" . $result->{hrProcessorID} . "': no matching filter.", debug => 1);
|
||
|
next;
|
||
|
}
|
||
|
|
||
|
# If the oid is not skipped above, here is convert the target values in result hash.
|
||
|
# Here is where the counter magic happens.
|
||
|
# $self->{cpu} is your counter definition (see $self->{maps_counters}->{<name>})
|
||
|
# Here, we map the obtained string $result->{hrProcessorLoad} with the cpu_usage key_value in the counter.
|
||
|
$self->{cpu}->{$instance} = {
|
||
|
name => $result->{hrProcessorID},
|
||
|
cpu_usage => $result->{hrProcessorLoad}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
# IMPORTANT !
|
||
|
# If you use a way to filter the values set in result hash,
|
||
|
# check if at the end of parsing the result table isn't empty.
|
||
|
# If it's the case, add a message for user to explain the filter doesn't match.
|
||
|
if (scalar(keys %{$self->{cpu}}) <= 0) {
|
||
|
$self->{output}->add_option_msg(short_msg => "No processor ID matching with filter found.");
|
||
|
$self->{output}->option_exit();
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Declare this plugin as a perl module:
|
||
|
|
||
|
```perl
|
||
|
1;
|
||
|
```
|
||
|
|
||
|
Execute this command (`--verbose` will display the long output and the details for each `type => 1` counters).
|
||
|
This command is based on use Docker SNMPSIM to simulate snmpwalk behavior (hostname, snmp-community and snmp-port).
|
||
|
|
||
|
```shell
|
||
|
perl centreon_plugins.pl --plugin=network::mysnmpplugin::snmp::plugin --mode=cpu --hostname=localhost --snmp-community=local/os_linux_snmp_plugin --snmp-port=2024 --verbose
|
||
|
```
|
||
|
|
||
|
Here is the expected output:
|
||
|
|
||
|
```shell
|
||
|
OK: All CPUs are ok | '.0.0#cpu.usage.percentage'=6.00%;;;0;100 '.0.0#cpu.usage.percentage'=16.00%;;;0;100
|
||
|
CPU '.0.0' usage: 6.00 %
|
||
|
CPU '.0.0' usage: 16.00 %
|
||
|
```
|
||
|
|
||
|
### 4.5 Help section and assistant to build your centreon objects
|
||
|
|
||
|
Last but not least, you need to write a help section to explain users what your mode is
|
||
|
doing and what options they can use.
|
||
|
|
||
|
The centreon-plugins framework has a built-in assistant to help you with the list of counters
|
||
|
and options.
|
||
|
|
||
|
Run this command to obtain a summary that will simplify the work of creating Centreon commands and write
|
||
|
the mode's help:
|
||
|
|
||
|
```shell
|
||
|
perl centreon_plugins.pl --plugin=network::mysnmpplugin::snmp::plugin --mode=cpu --hostname='anyvalue' --list-counters --verbose
|
||
|
```
|
||
|
|
||
|
Get information from its output (shown below) to start building your mode's help:
|
||
|
|
||
|
```shell
|
||
|
counter list: cpu-usage-prct
|
||
|
configuration: --warning-cpu-usage-prct='$_SERVICEWARNINGCPUUSAGEPRCT$' --critical-cpu-usage-prct='$_SERVICECRITICALCPUUSAGEPRCT$'
|
||
|
```
|
||
|
|
||
|
Here is how you can write the help, note that this time you will add the content after the `1;` and add the same
|
||
|
`__END__` instruction like you did in the `plugin.pm` file.
|
||
|
|
||
|
```perl
|
||
|
__END__
|
||
|
|
||
|
=head1 MODE
|
||
|
|
||
|
Check system CPUs.
|
||
|
|
||
|
=over 8
|
||
|
|
||
|
=item B<--filter-id>
|
||
|
|
||
|
Filter on one ID name.
|
||
|
|
||
|
=item B<--warning>
|
||
|
|
||
|
Warning threshold for CPU.
|
||
|
|
||
|
=item B<--critical>
|
||
|
|
||
|
Critical threshold for CPU.
|
||
|
|
||
|
=back
|
||
|
|
||
|
=cut
|
||
|
```
|