root / plugins / chrony / chrony_sourcestats @ 4849aa0d
Historique | Voir | Annoter | Télécharger (9,06 ko)
| 1 |
#!/usr/bin/perl -w |
|---|---|
| 2 |
# -*- mode: cperl; cperl-indent-level: 8; -*- |
| 3 |
|
| 4 |
=head1 NAME |
| 5 |
|
| 6 |
chrony_sourcestats - Plugin to monitor Chrony offsets for various hosts. |
| 7 |
|
| 8 |
=head1 CONFIGURATION |
| 9 |
|
| 10 |
Plugin configuration parameters |
| 11 |
|
| 12 |
[chrony_*] |
| 13 |
env.chronycpath /usr/local/bin/chronyc |
| 14 |
|
| 15 |
path to the chronyc executable |
| 16 |
|
| 17 |
env.timesources ntp1.example.org ntp2.example.com ntp1.example.net |
| 18 |
|
| 19 |
timesources - servers and peers that are used by the monitored |
| 20 |
chrony instance run the plugin as with 'suggest' as argument to see |
| 21 |
what timesoures might are available. e.g: |
| 22 |
|
| 23 |
munin-run --servicedir /etc/munin/plugins/ chrony_offsets suggest |
| 24 |
|
| 25 |
will produce a set of servers that can be used in the timesources |
| 26 |
environment variable. |
| 27 |
|
| 28 |
Note: use the same names as in the server and peer directives in |
| 29 |
your chrony configuration. |
| 30 |
|
| 31 |
env.freqlimit 0.7 |
| 32 |
env.freqskewlimit 0.3 |
| 33 |
env.offsetlimit 0.005 |
| 34 |
env.stddevlimit 0.001 |
| 35 |
|
| 36 |
By default the graphs are drawn using automatic scaling with these |
| 37 |
limits set the vertical scale of the graph will be bounded |
| 38 |
(rigidly). Note the vallues above are (reasonable) example vallues, |
| 39 |
not the default. |
| 40 |
|
| 41 |
=head1 VERSION |
| 42 |
|
| 43 |
VERSION 0.1.1 - 2 Nov 2020 |
| 44 |
|
| 45 |
=head1 AUTHOR |
| 46 |
|
| 47 |
Copyright (C) 2020 Olaf Kolkman |
| 48 |
|
| 49 |
=head1 LICENSE |
| 50 |
|
| 51 |
MIT |
| 52 |
|
| 53 |
=head1 MAGIC MARKERS |
| 54 |
Used by munin-node-configure. |
| 55 |
|
| 56 |
#%# family=manual |
| 57 |
#%# capabilities=multigraph |
| 58 |
|
| 59 |
=head1 KNOWN ISSSUES |
| 60 |
|
| 61 |
There may be some issues when IP addresses are used instead of |
| 62 |
hostnames in the timesources environment. Also, the names should match |
| 63 |
those in the chrony config file. |
| 64 |
|
| 65 |
=cut |
| 66 |
|
| 67 |
|
| 68 |
use Munin::Plugin; |
| 69 |
use English qw( -no_match_vars ); |
| 70 |
use strict; |
| 71 |
use warnings; |
| 72 |
|
| 73 |
my $retNetIP; |
| 74 |
my $retNetDNS; |
| 75 |
my $retDataVal; |
| 76 |
|
| 77 |
|
| 78 |
|
| 79 |
BEGIN{
|
| 80 |
# Import the namespaces for symbols used globally below |
| 81 |
if (! eval "require Net::IP;") {
|
| 82 |
$retNetIP = "Net::IP"; |
| 83 |
}else{
|
| 84 |
Net::IP->import(); |
| 85 |
} |
| 86 |
|
| 87 |
if (! eval "require Net::DNS;") {
|
| 88 |
$retNetDNS = "Net::DNS"; |
| 89 |
} |
| 90 |
|
| 91 |
if (! eval "require Data::Validate::IP;") {
|
| 92 |
$retDataVal = "Data::Validate::IP"; |
| 93 |
}else{
|
| 94 |
Data::Validate::IP->import(); |
| 95 |
} |
| 96 |
} |
| 97 |
|
| 98 |
|
| 99 |
|
| 100 |
need_multigraph(); |
| 101 |
|
| 102 |
|
| 103 |
my $chronyc = $ENV{'chrony'} || "/usr/local/bin/chronyc";
|
| 104 |
my $freqskewlimit = $ENV{'freqskewlimit'};
|
| 105 |
my $freqlimit = $ENV{'freqlimit'};
|
| 106 |
my $offsetlimit = $ENV{'offsetlimit'};
|
| 107 |
my $stddevlimit = $ENV{'stddevlimit'};
|
| 108 |
|
| 109 |
my @timesources= split(/\s+/,$ENV{'timesources'});
|
| 110 |
|
| 111 |
|
| 112 |
if ($ARGV[0] and $ARGV[0] eq "autoconf") {
|
| 113 |
`$chronyc help >/dev/null 2>/dev/null`; |
| 114 |
if ($CHILD_ERROR eq "0") {
|
| 115 |
if ($retNetIP || $retNetDNS || $retDataVal){
|
| 116 |
print "no (missing perl libraries: "; |
| 117 |
print $retNetIP . " " if $retNetIP; |
| 118 |
print $retNetDNS . " " if $retNetDNS; |
| 119 |
print $retDataVal . " " if $retDataVal; |
| 120 |
print ")\n"; |
| 121 |
} |
| 122 |
if (`$chronyc -n sources | wc -l` > 0) {
|
| 123 |
print "yes\n"; |
| 124 |
exit 0; |
| 125 |
} else {
|
| 126 |
print "no (chronyc sources returned no sources)\n"; |
| 127 |
exit 0; |
| 128 |
} |
| 129 |
} else {
|
| 130 |
print "no (chronyc not found)\n"; |
| 131 |
exit 0; |
| 132 |
} |
| 133 |
} |
| 134 |
|
| 135 |
if ($ARGV[0] and $ARGV[0] eq "suggest") {
|
| 136 |
print "env.timesources "; |
| 137 |
foreach my $line (`$chronyc -n sources`) {
|
| 138 |
if ($line =~ m/^??\s+\S+\s+\d+/) {
|
| 139 |
my (undef, $peer_addr , undef, undef, undef, undef, undef, undef, undef) = split(/\s+/, $line); |
| 140 |
unless (( $peer_addr eq "0.0.0.0") ){
|
| 141 |
my $hostname; |
| 142 |
if (is_ip($peer_addr) and $hostname = `$chronyc sourcename $peer_addr`){
|
| 143 |
chop $hostname; |
| 144 |
print $hostname . " "; |
| 145 |
}else{
|
| 146 |
# Bit of a last resort, not sure if this path is ever triggered. |
| 147 |
my $resolver = Net::DNS::Resolver->new; |
| 148 |
$resolver->tcp_timeout(5); |
| 149 |
$resolver->udp_timeout(5); |
| 150 |
my $query = $resolver->search($peer_addr, "PTR"); |
| 151 |
if ($query) {
|
| 152 |
foreach my $rr ($query->answer) {
|
| 153 |
if ("PTR" eq $rr->type) {
|
| 154 |
print $hostname=$rr->ptrdname."\n"; |
| 155 |
} |
| 156 |
} |
| 157 |
} |
| 158 |
} |
| 159 |
print $peer_addr."\n" unless $hostname; |
| 160 |
} |
| 161 |
|
| 162 |
} |
| 163 |
} |
| 164 |
print "\n"; |
| 165 |
exit 0; |
| 166 |
} |
| 167 |
|
| 168 |
|
| 169 |
|
| 170 |
$0 =~ /chrony_(.+)*$/; |
| 171 |
my $name = $1; |
| 172 |
|
| 173 |
|
| 174 |
die "You should set env.timesources (try running munin-run --servicedir /etc/munin/plugins/ chrony_offsets suggest)" unless @timesources; |
| 175 |
|
| 176 |
if ($ARGV[0] and $ARGV[0] eq "config") {
|
| 177 |
|
| 178 |
print "multigraph chrony_freq\n"; |
| 179 |
# Using Chrony Sourcestats: <no> title to enforce an order in the presentation. |
| 180 |
print "graph_title CHRONY Sourcestats: 1. Residual Frequency\n"; |
| 181 |
print "graph_args --base 1000 --vertical-label PPM "; |
| 182 |
if ($freqlimit) {
|
| 183 |
print "--lower-limit -$freqlimit --upper-limit $freqlimit --rigid\n"; |
| 184 |
}else{
|
| 185 |
print "\n"; |
| 186 |
} |
| 187 |
|
| 188 |
print "graph_category time\n"; |
| 189 |
foreach my $source (@timesources){
|
| 190 |
my $fldname=clean_fieldname($source)."_"; |
| 191 |
print "$fldname"."freq.label $source \n"; |
| 192 |
print "$fldname"."freq.cdef $fldname"."freq,1,*\n"; |
| 193 |
|
| 194 |
} |
| 195 |
|
| 196 |
|
| 197 |
print "multigraph chrony_freqsk\n"; |
| 198 |
print "graph_title CHRONY Sourcestats: 2. Frequency Skew\n"; |
| 199 |
print "graph_args --base 1000 --vertical-label PPM --lower-limit 0 "; |
| 200 |
if ($freqskewlimit) {
|
| 201 |
print "--upper-limit $freqskewlimit --rigid\n"; |
| 202 |
}else{
|
| 203 |
print "\n"; |
| 204 |
} |
| 205 |
print "graph_category time\n"; |
| 206 |
foreach my $source (@timesources){
|
| 207 |
my $fldname=clean_fieldname($source)."_"; |
| 208 |
print "$fldname"."freqsk.label $source \n"; |
| 209 |
print "$fldname"."freqsk.cdef $fldname"."freqsk,1,*\n"; |
| 210 |
|
| 211 |
} |
| 212 |
|
| 213 |
|
| 214 |
|
| 215 |
print "multigraph chrony_offset\n"; |
| 216 |
print "graph_title CHRONY Sourcestats: 3. Offset\n"; |
| 217 |
print "graph_args --base 1000 --vertical-label seconds "; |
| 218 |
if ($offsetlimit) {
|
| 219 |
print "--lower-limit -$offsetlimit --upper-limit $offsetlimit --rigid\n"; |
| 220 |
}else{
|
| 221 |
print "--lower-limit 0\n"; |
| 222 |
} |
| 223 |
|
| 224 |
print "graph_category time\n"; |
| 225 |
foreach my $source (@timesources){
|
| 226 |
my $fldname=clean_fieldname($source)."_"; |
| 227 |
print "$fldname"."offset.label $source \n"; |
| 228 |
print "$fldname"."offset.cdef $fldname". "offset,1,*\n"; |
| 229 |
|
| 230 |
} |
| 231 |
|
| 232 |
print "multigraph chrony_stdev\n"; |
| 233 |
print "graph_title CHRONY Sourcestats: 4 Estimated Standard Deviation\n"; |
| 234 |
print "graph_args --base 1000 --vertical-label seconds --lower-limit 0 "; |
| 235 |
if ($stddevlimit) {
|
| 236 |
print "--upper-limit $stddevlimit --rigid\n"; |
| 237 |
}else{
|
| 238 |
print "\n"; |
| 239 |
} |
| 240 |
|
| 241 |
print "graph_category time\n"; |
| 242 |
foreach my $source (@timesources){
|
| 243 |
my $fldname=clean_fieldname($source)."_"; |
| 244 |
print "$fldname"."stdev.label $source \n"; |
| 245 |
print "$fldname"."stdev.cdef $fldname"."stdev,1,*\n"; |
| 246 |
|
| 247 |
} |
| 248 |
|
| 249 |
|
| 250 |
|
| 251 |
exit 0; |
| 252 |
|
| 253 |
} |
| 254 |
|
| 255 |
|
| 256 |
|
| 257 |
my $datastore; |
| 258 |
|
| 259 |
my @associations = `$chronyc -n sourcestats`; |
| 260 |
|
| 261 |
foreach my $line (@associations) {
|
| 262 |
if ($line =~ m/^??\s+\S+\s+\d+/) {
|
| 263 |
my ( $srcadr , undef, undef, undef, $freq, $freqsk, $offset, $stddev) = split(/\s+/, $line); |
| 264 |
my $srcname=match_sourcename($srcadr); |
| 265 |
@{$datastore->{$srcname}}=($freq,$freqsk,$offset,$stddev) if $srcname;
|
| 266 |
} |
| 267 |
} |
| 268 |
|
| 269 |
|
| 270 |
|
| 271 |
sub match_sourcename {
|
| 272 |
my $srcadr=shift; |
| 273 |
my $matched = 0; |
| 274 |
my $sourcename=""; |
| 275 |
if ( is_ip($srcadr) ) {
|
| 276 |
$sourcename=`$chronyc sourcename $srcadr`; |
| 277 |
chop $sourcename; |
| 278 |
return $sourcename if $sourcename; |
| 279 |
}; |
| 280 |
# return the src address and deal with it later. |
| 281 |
return $srcadr; |
| 282 |
} |
| 283 |
|
| 284 |
|
| 285 |
print "multigraph chrony_freq\n"; |
| 286 |
foreach (@timesources){
|
| 287 |
if (exists $datastore->{$_}){
|
| 288 |
my $fldname=clean_fieldname($_)."_"; |
| 289 |
my $freq=$datastore->{$_}->[0];
|
| 290 |
print "$fldname"."freq.value $freq \n"; |
| 291 |
} |
| 292 |
} |
| 293 |
|
| 294 |
print "\n\n"; |
| 295 |
|
| 296 |
|
| 297 |
print "multigraph chrony_freqsk\n"; |
| 298 |
foreach (@timesources){
|
| 299 |
if (exists $datastore->{$_}){
|
| 300 |
my $fldname=clean_fieldname($_)."_"; |
| 301 |
my $freqsk=$datastore->{$_}->[1];
|
| 302 |
print "$fldname"."freqsk.value $freqsk \n"; |
| 303 |
} |
| 304 |
|
| 305 |
} |
| 306 |
|
| 307 |
|
| 308 |
print "\n\n"; |
| 309 |
|
| 310 |
|
| 311 |
print "multigraph chrony_offset\n"; |
| 312 |
foreach (@timesources){
|
| 313 |
if (exists $datastore->{$_}){
|
| 314 |
my $fldname=clean_fieldname($_)."_"; |
| 315 |
my $offset=$datastore->{$_}->[2];
|
| 316 |
if ($offset =~ /(.?\d+)(\S+)/){
|
| 317 |
$offset=$1*1e-3 if $2 eq "ms"; |
| 318 |
$offset=$1*1e-6 if $2 eq "us"; |
| 319 |
$offset=$1*1e-9 if $2 eq "ns"; |
| 320 |
} |
| 321 |
print "$fldname"."offset.value $offset\n"; |
| 322 |
} |
| 323 |
|
| 324 |
} |
| 325 |
|
| 326 |
|
| 327 |
print "\n\n"; |
| 328 |
|
| 329 |
|
| 330 |
print "multigraph chrony_stdev\n"; |
| 331 |
foreach (@timesources){
|
| 332 |
if (exists $datastore->{$_}){
|
| 333 |
my $fldname=clean_fieldname($_)."_"; |
| 334 |
my $stdev=$datastore->{$_}->[3];
|
| 335 |
if ($stdev =~ /(.?\d+)(\S+)/){
|
| 336 |
$stdev=$1*1e-3 if $2 eq "ms"; |
| 337 |
$stdev=$1*1e-6 if $2 eq "us"; |
| 338 |
$stdev=$1*1e-9 if $2 eq "ns"; |
| 339 |
} |
| 340 |
print "$fldname"."stdev.value $stdev\n"; |
| 341 |
} |
| 342 |
|
| 343 |
} |
| 344 |
|
| 345 |
|
| 346 |
|
| 347 |
|
| 348 |
|
| 349 |
|
| 350 |
|
| 351 |
|
| 352 |
exit 0; |
| 353 |
|
| 354 |
|
| 355 |
# MIT License |
| 356 |
# |
| 357 |
# Copyright (c) 2020 Olaf M. Kolkman |
| 358 |
# |
| 359 |
# Permission is hereby granted, free of charge, to any person obtaining a copy |
| 360 |
# of this software and associated documentation files (the "Software"), to deal |
| 361 |
# in the Software without restriction, including without limitation the rights |
| 362 |
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 363 |
# copies of the Software, and to permit persons to whom the Software is |
| 364 |
# furnished to do so, subject to the following conditions: |
| 365 |
# |
| 366 |
# The above copyright notice and this permission notice shall be included in all |
| 367 |
# copies or substantial portions of the Software. |
| 368 |
# |
| 369 |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 370 |
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 371 |
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 372 |
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 373 |
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 374 |
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 375 |
# SOFTWARE. |
