mirror of
https://github.com/munin-monitoring/contrib.git
synced 2025-07-21 18:41:03 +00:00
Added two Chrony pluggins and example graphs
Added chrony_ , a wildcard plugin to track sourcestats per indiviual time source Added chrony_sourcestats, a plugin that displays the sourcestats characteristics for several timesources in one graph. Added example graphs for both plugins.
This commit is contained in:
parent
f0479a9a7d
commit
266e82bb81
7 changed files with 675 additions and 0 deletions
375
plugins/chrony/chrony_sourcestats
Executable file
375
plugins/chrony/chrony_sourcestats
Executable file
|
@ -0,0 +1,375 @@
|
|||
#!/usr/bin/perl -w
|
||||
# -*- mode: cperl; cperl-indent-level: 8; -*-
|
||||
|
||||
=head1 NAME
|
||||
|
||||
chrony_sourcestatss - Plugin to monitor Chrony offsets for various hosts.
|
||||
|
||||
=head1 CONFIGURATION
|
||||
|
||||
Plugin configuration parameters
|
||||
|
||||
[chrony_*]
|
||||
env.chronycpath /usr/local/bin/chronyc
|
||||
|
||||
path to the chronyc executable
|
||||
|
||||
env.timesources ntp1.example.org ntp2.example.com ntp1.example.net
|
||||
|
||||
timesources - servers and peers that are used by the monitored
|
||||
chrony instance run the plugin as with 'suggest' as argument to see
|
||||
what timesoures might are available. e.g:
|
||||
|
||||
munin-run --servicedir /etc/munin/plugins/ chrony_offsets suggest
|
||||
|
||||
will produce a set of servers that can be used in the timesources
|
||||
environment variable.
|
||||
|
||||
Note: use the same names as in the server and peer directives in
|
||||
your chrony configuration.
|
||||
|
||||
env.freqlimit 0.7
|
||||
env.freqskewlimit 0.3
|
||||
env.offsetlimit 0.005
|
||||
env.stddevlimit 0.001
|
||||
|
||||
By default the graphs are drawn using automatic scaling with these
|
||||
limits set the vertical scale of the graph will be bounded
|
||||
(rigidly). Note the vallues above are (reasonable) example vallues,
|
||||
not the default.
|
||||
|
||||
=head1 VERSION
|
||||
|
||||
VERSION 0.1.1 - 2 Nov 2020
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Copyright (C) 2020 Olaf Kolkman
|
||||
|
||||
=head1 LICENSE
|
||||
|
||||
MIT
|
||||
|
||||
=head1 MAGIC MARKERS
|
||||
Used by munin-node-configure.
|
||||
|
||||
#%# family=manual
|
||||
#%# capabilities=multigraph
|
||||
|
||||
=head1 KNOWN ISSSUES
|
||||
|
||||
There may be some issues when IP addresses are used instead of
|
||||
hostnames in the timesources environment. Also, the names should match
|
||||
those in the chrony config file.
|
||||
|
||||
=cut
|
||||
|
||||
|
||||
use Munin::Plugin;
|
||||
use English qw( -no_match_vars );
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my $retNetIP;
|
||||
my $retNetDNS;
|
||||
my $retDataVal;
|
||||
|
||||
|
||||
|
||||
BEGIN{
|
||||
# Import the namespaces for symbols used globally below
|
||||
if (! eval "require Net::IP;") {
|
||||
$retNetIP = "Net::IP";
|
||||
}else{
|
||||
Net::IP->import();
|
||||
}
|
||||
|
||||
if (! eval "require Net::DNS;") {
|
||||
$retNetDNS = "Net::DNS";
|
||||
}
|
||||
|
||||
if (! eval "require Data::Validate::IP;") {
|
||||
$retDataVal = "Data::Validate::IP";
|
||||
}else{
|
||||
Data::Validate::IP->import();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
need_multigraph();
|
||||
|
||||
|
||||
my $chronyc = $ENV{'chrony'} || "/usr/local/bin/chronyc";
|
||||
my $freqskewlimit = $ENV{'freqskewlimit'};
|
||||
my $freqlimit = $ENV{'freqlimit'};
|
||||
my $offsetlimit = $ENV{'offsetlimit'};
|
||||
my $stddevlimit = $ENV{'stddevlimit'};
|
||||
|
||||
my @timesources= split(/\s+/,$ENV{'timesources'});
|
||||
|
||||
|
||||
if ($ARGV[0] and $ARGV[0] eq "autoconf") {
|
||||
`$chronyc help >/dev/null 2>/dev/null`;
|
||||
if ($CHILD_ERROR eq "0") {
|
||||
if ($retNetIP || $retNetDNS || $retDataVal){
|
||||
print "no (missing perl libraries: ";
|
||||
print $retNetIP . " " if $retNetIP;
|
||||
print $retNetDNS . " " if $retNetDNS;
|
||||
print $retDataVal . " " if $retDataVal;
|
||||
print ")\n";
|
||||
}
|
||||
if (`$chronyc -n sources | wc -l` > 0) {
|
||||
print "yes\n";
|
||||
exit 0;
|
||||
} else {
|
||||
print "no (chronyc sources returned no sources)\n";
|
||||
exit 0;
|
||||
}
|
||||
} else {
|
||||
print "no (chronyc not found)\n";
|
||||
exit 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ($ARGV[0] and $ARGV[0] eq "suggest") {
|
||||
print "env.timesources ";
|
||||
foreach my $line (`$chronyc -n sources`) {
|
||||
if ($line =~ m/^??\s+\S+\s+\d+/) {
|
||||
my (undef, $peer_addr , undef, undef, undef, undef, undef, undef, undef) = split(/\s+/, $line);
|
||||
unless (( $peer_addr eq "0.0.0.0") ){
|
||||
my $hostname;
|
||||
if (is_ip($peer_addr) and $hostname = `$chronyc sourcename $peer_addr`){
|
||||
chop $hostname;
|
||||
print $hostname . " ";
|
||||
}else{
|
||||
# Bit of a last resort, not sure if this path is ever triggered.
|
||||
my $resolver = Net::DNS::Resolver->new;
|
||||
$resolver->tcp_timeout(5);
|
||||
$resolver->udp_timeout(5);
|
||||
my $query = $resolver->search($peer_addr, "PTR");
|
||||
if ($query) {
|
||||
foreach my $rr ($query->answer) {
|
||||
if ("PTR" eq $rr->type) {
|
||||
print $hostname=$rr->ptrdname."\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
print $peer_addr."\n" unless $hostname;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
print "\n";
|
||||
exit 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
$0 =~ /chrony_(.+)*$/;
|
||||
my $name = $1;
|
||||
|
||||
|
||||
die "You should set env.timesources (try running munin-run --servicedir /etc/munin/plugins/ chrony_offsets suggest)" unless @timesources;
|
||||
|
||||
if ($ARGV[0] and $ARGV[0] eq "config") {
|
||||
|
||||
print "multigraph chrony_freq\n";
|
||||
# Using Chrony Sourcestats: <no> title to enforce an order in the presentation.
|
||||
print "graph_title CHRONY Sourcestats: 1. Residual Frequency\n";
|
||||
print "graph_args --base 1000 --vertical-label PPM ";
|
||||
if ($freqlimit) {
|
||||
print "--lower-limit -$freqlimit --upper-limit $freqlimit --rigid\n";
|
||||
}else{
|
||||
print "\n";
|
||||
}
|
||||
|
||||
print "graph_category time\n";
|
||||
foreach my $source (@timesources){
|
||||
my $fldname=clean_fieldname($source)."_";
|
||||
print "$fldname"."freq.label $source \n";
|
||||
print "$fldname"."freq.cdef $fldname"."freq,1,*\n";
|
||||
|
||||
}
|
||||
|
||||
|
||||
print "multigraph chrony_freqsk\n";
|
||||
print "graph_title CHRONY Sourcestats: 2. Frequency Skew\n";
|
||||
print "graph_args --base 1000 --vertical-label PPM --lower-limit 0 ";
|
||||
if ($freqskewlimit) {
|
||||
print "--upper-limit $freqskewlimit --rigid\n";
|
||||
}else{
|
||||
print "\n";
|
||||
}
|
||||
print "graph_category time\n";
|
||||
foreach my $source (@timesources){
|
||||
my $fldname=clean_fieldname($source)."_";
|
||||
print "$fldname"."freqsk.label $source \n";
|
||||
print "$fldname"."freqsk.cdef $fldname"."freqsk,1,*\n";
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
print "multigraph chrony_offset\n";
|
||||
print "graph_title CHRONY Sourcestats: 3. Offset\n";
|
||||
print "graph_args --base 1000 --vertical-label seconds ";
|
||||
if ($offsetlimit) {
|
||||
print "--lower-limit -$offsetlimit --upper-limit $offsetlimit --rigid\n";
|
||||
}else{
|
||||
print "--lower-limit 0\n";
|
||||
}
|
||||
|
||||
print "graph_category time\n";
|
||||
foreach my $source (@timesources){
|
||||
my $fldname=clean_fieldname($source)."_";
|
||||
print "$fldname"."offset.label $source \n";
|
||||
print "$fldname"."offset.cdef $fldname". "offset,1,*\n";
|
||||
|
||||
}
|
||||
|
||||
print "multigraph chrony_stdev\n";
|
||||
print "graph_title CHRONY Sourcestats: 4 Estimated Standard Deviation\n";
|
||||
print "graph_args --base 1000 --vertical-label seconds --lower-limit 0 ";
|
||||
if ($stddevlimit) {
|
||||
print "--upper-limit $stddevlimit --rigid\n";
|
||||
}else{
|
||||
print "\n";
|
||||
}
|
||||
|
||||
print "graph_category time\n";
|
||||
foreach my $source (@timesources){
|
||||
my $fldname=clean_fieldname($source)."_";
|
||||
print "$fldname"."stdev.label $source \n";
|
||||
print "$fldname"."stdev.cdef $fldname"."stdev,1,*\n";
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
exit 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
my $datastore;
|
||||
|
||||
my @associations = `$chronyc -n sourcestats`;
|
||||
|
||||
foreach my $line (@associations) {
|
||||
if ($line =~ m/^??\s+\S+\s+\d+/) {
|
||||
my ( $srcadr , undef, undef, undef, $freq, $freqsk, $offset, $stddev) = split(/\s+/, $line);
|
||||
my $srcname=match_sourcename($srcadr);
|
||||
@{$datastore->{$srcname}}=($freq,$freqsk,$offset,$stddev) if $srcname;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
sub match_sourcename {
|
||||
my $srcadr=shift;
|
||||
my $matched = 0;
|
||||
my $sourcename="";
|
||||
if ( is_ip($srcadr) ) {
|
||||
$sourcename=`$chronyc sourcename $srcadr`;
|
||||
chop $sourcename;
|
||||
return $sourcename if $sourcename;
|
||||
};
|
||||
# return the src address and deal with it later.
|
||||
return $srcadr;
|
||||
}
|
||||
|
||||
|
||||
print "multigraph chrony_freq\n";
|
||||
foreach (@timesources){
|
||||
if (exists $datastore->{$_}){
|
||||
my $fldname=clean_fieldname($_)."_";
|
||||
my $freq=$datastore->{$_}->[0];
|
||||
print "$fldname"."freq.value $freq \n";
|
||||
}
|
||||
}
|
||||
|
||||
print "\n\n";
|
||||
|
||||
|
||||
print "multigraph chrony_freqsk\n";
|
||||
foreach (@timesources){
|
||||
if (exists $datastore->{$_}){
|
||||
my $fldname=clean_fieldname($_)."_";
|
||||
my $freqsk=$datastore->{$_}->[1];
|
||||
print "$fldname"."freqsk.value $freqsk \n";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
print "\n\n";
|
||||
|
||||
|
||||
print "multigraph chrony_offset\n";
|
||||
foreach (@timesources){
|
||||
if (exists $datastore->{$_}){
|
||||
my $fldname=clean_fieldname($_)."_";
|
||||
my $offset=$datastore->{$_}->[2];
|
||||
if ($offset =~ /(.?\d+)(\S+)/){
|
||||
$offset=$1*1e-3 if $2 eq "ms";
|
||||
$offset=$1*1e-6 if $2 eq "us";
|
||||
$offset=$1*1e-9 if $2 eq "ns";
|
||||
}
|
||||
print "$fldname"."offset.value $offset\n";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
print "\n\n";
|
||||
|
||||
|
||||
print "multigraph chrony_stdev\n";
|
||||
foreach (@timesources){
|
||||
if (exists $datastore->{$_}){
|
||||
my $fldname=clean_fieldname($_)."_";
|
||||
my $stdev=$datastore->{$_}->[3];
|
||||
if ($stdev =~ /(.?\d+)(\S+)/){
|
||||
$stdev=$1*1e-3 if $2 eq "ms";
|
||||
$stdev=$1*1e-6 if $2 eq "us";
|
||||
$stdev=$1*1e-9 if $2 eq "ns";
|
||||
}
|
||||
print "$fldname"."stdev.value $stdev\n";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
exit 0;
|
||||
|
||||
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2020 Olaf M. Kolkman
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
Loading…
Add table
Add a link
Reference in a new issue