Projet

Général

Profil

Paste
Télécharger au format
Statistiques
| Branche: | Révision:

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.