Projet

Général

Profil

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

root / plugins / varnish / varnish4_ @ 5562eec2

Historique | Voir | Annoter | Télécharger (25,8 ko)

1
#!/usr/bin/perl
2
# -*- perl -*-
3
#
4
# varnish4_ - Munin plugin to for Varnish 4.x
5
# Copyright (C) 2009  Redpill Linpro AS
6
#
7
# Author: Kristian Lyngstøl <kristian@bohemians.org>
8
#
9
# This program is free software; you can redistribute it and/or modify
10
# it under the terms of the GNU General Public License as published by
11
# the Free Software Foundation; either version 2 of the License, or
12
# (at your option) any later version.
13
#
14
# This program is distributed in the hope that it will be useful,
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
# GNU General Public License for more details.
18
#
19
# You should have received a copy of the GNU General Public License along
20
# with this program; if not, write to the Free Software Foundation, Inc.,
21
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22

    
23
=head1 NAME
24

    
25
varnish4_ - Munin plugin to monitor various aspects of varnish
26

    
27
=head1 APPLICABLE SYSTEMS
28

    
29
Varnish 4.x with varnishstat
30

    
31
=head1 CONFIGURATION
32

    
33
The plugin needs to be able to execute varnishstat.
34

    
35
The configuration section shows the defaults
36
  [varnish4_*]
37
     group varnish
38
     env.varnishstat varnishstat
39
     env.name
40

    
41
env.varnishstat can be a full path to varnishstat if it's
42
not in the path already.
43

    
44
env.name is blank (undefined) by default and can be used to specify a -n
45
name argument to varnish if multiple instances are running on the same
46
server.
47

    
48
A few aspects are not linked by default. They are marked as
49
'DEBUG' => 'yes' (or any other value). They are:
50

    
51
vcl, bans, bans_lurker, lru, objects_per_objhead,
52
losthdr, esi, hcb, shm, shm_writes, overflow,
53
session, session_herd, gzip
54

    
55
You can link them yourself with something like this:
56

    
57
  ln -s @@LIBDIR@@/plugins/varnish4_ \
58
    @@CONFDIR@@/plugins/varnish4_data_structures
59

    
60
=head1 INTERPRETATION
61

    
62
Each graph uses data from varnishstat.
63

    
64
=head1 MAGIC MARKERS
65

    
66
 #%# family=auto
67
 #%# capabilities=autoconf suggest
68

    
69
=head1 VERSION
70

    
71
 $Id$
72

    
73
=head1 BUGS
74

    
75
The hit_rate graph requires munin r2040 or newer to display
76
correctly.
77

    
78
=head1 PATCHES-TO
79

    
80
Please send patches to Kristian Lyngstøl <kristian@bohemians.org>
81
and/or varnish-misc@varnish-cache.org for significant changes. Munin SVN
82
is the authoritative repository for this plugin.
83

    
84
=head1 AUTHOR
85

    
86
Kristian Lyngstøl <kristian@bohemians.org>
87

    
88
=head1 MODIFICATIONS
89

    
90
Ingo Oppermann <ingo.oppermann@gmail.com>
91

    
92
=head1 LICENSE
93

    
94
GPLv2
95

    
96
=cut
97

    
98

    
99
use XML::Parser;
100
use strict;
101

    
102
# Set to 1 to enable output when a variable is defined in a graph but
103
# omitted because it doesn't exist in varnishstat.
104
my $DEBUG = 0;
105

    
106
# Set to 1 to ignore 'DEBUG' and suggest all available aspects.
107
my $FULL_SUGGEST = 0;
108

    
109
# Varnishstat executable. Include full path if it's not in your path.
110
my $varnishstatexec = exists $ENV{'varnishstat'} ? $ENV{'varnishstat'} : "varnishstat";
111

    
112
# For multiple instances
113
my $varnishname = exists $ENV{'name'} ? $ENV{'name'} : undef;
114

    
115
my $self; # Haha, myself, what a clever pun.
116

    
117
# Parameters that can be defined on top level of a graph. Config will print
118
# them as "graph_$foo $value\n"
119
my @graph_parameters = ('title','total','order','scale','vlabel','args');
120

    
121
# Parameters that can be defined on a value-to-value basis and will be
122
# blindly passed to config. Printed as "$fieldname.$param $value\n".
123
#
124
# 'label' is hardcoded as it defaults to a varnishstat-description if not
125
# set.
126
my @field_parameters = ('graph', 'min', 'max', 'draw', 'cdef', 'warning',
127
			'colour', 'info', 'type');
128

    
129
# Varnishstat data is stored here. Example
130
# ('n_vbe' => { 'value' => '124', 'description'=>...,'flag'=>... }, SMA =>
131
# { s0 => { 'value' => '...', 'flag'=> '...' },'Transient' => ...})
132
# Both dynamic and static counters are kept here.
133
#
134
# Notes:
135
#  - The 'flag' field for a counter is in RRD-dialect, not varnishstat
136
my %data;
137

    
138
# Data structure that defines all possible graphs (aspects) and how they
139
# are to be plotted. Every top-level entry is a graph/aspect. Each
140
# top-level graph MUST have title set and 'values'.
141
#
142
# The 'values' hash must have at least one value definition. The actual
143
# value used is either fetched from varnishstat based on the value-name, or
144
# if 'rpn' is defined: calculated. 'type' SHOULD be set.
145
#
146
# Graphs with 'DEBUG' set to anything is omitted from 'suggest'.
147
#
148
# 'rpn' on values allows easy access to graphs consisting of multiple
149
# values from varnishstat. (Reverse polish notation). The RPN
150
# implementation only accepts +-*/ and varnishstat-values.
151
#
152
# With the exception of 'label', which is filled with the
153
# varnishstat-description if left undefined, any value left undefined will
154
# be left up to Munin to define/ignore/yell about.
155
#
156
# For dynamic counters, the values listed need to specify a counter and
157
# family. This will plot the specified counter for each identity within
158
# that family. Example: family of SMA, counter c_fail. This will create a
159
# c_fail-counter for each of the SMA-identities (e.g: Transient, s0, etc).
160
# For dynamic graphs, the value-name is only used to identify the data
161
# point, and does not relate to any varnishstat data as that is set by
162
# family/counter.
163
#
164
# Note that dynamic counters fetch the type from the XML and things like
165
# min/max are currently not supported (and silently ignored).
166
#
167
# See munin documentation or rrdgraph/rrdtool for more information.
168
my %ASPECTS = (
169
	'request_rate' => {
170
		'title' => 'Request rates',
171
		'order' => 'cache_hit cache_hitpass cache_miss '
172
			 . 'backend_conn backend_unhealthy '
173
			 . 'client_req client_conn' ,
174
		'values' => {
175
			'sess_conn' => {
176
				'type' => 'DERIVE',
177
				'min' => '0',
178
				'colour' => '444444',
179
				'graph' => 'ON'
180
			},
181
			'client_req' => {
182
				'type' => 'DERIVE',
183
				'colour' => '111111',
184
				'min' => '0'
185
			},
186
			'cache_hit' => {
187
				'type' => 'DERIVE',
188
				'draw' => 'AREA',
189
				'colour' => '00FF00',
190
				'min' => '0'
191
			},
192
			'cache_hitpass' => {
193
				'info' => 'Hitpass are cached passes: An '
194
					. 'entry in the cache instructing '
195
					. 'Varnish to pass. Typically '
196
					. 'achieved after a pass in '
197
					. 'vcl_fetch.',
198
				'type' => 'DERIVE',
199
				'draw' => 'STACK',
200
				'colour' => 'FFFF00',
201
				'min' => '0'
202
			},
203
			'cache_miss' => {
204
				'type' => 'DERIVE',
205
				'colour' => 'FF0000',
206
				'draw' => 'STACK',
207
				'min' => '0'
208
			},
209
			'backend_conn' => {
210
				'type' => 'DERIVE',
211
				'colour' => '995599',
212
				'min' => '0'
213
			},
214
			'backend_unhealthy' => {
215
				'type' => 'DERIVE',
216
				'min' => '0',
217
				'colour' => 'FF55FF'
218
			},
219
			's_pipe' => {
220
				'type' => 'DERIVE',
221
				'min' => '0',
222
				'colour' => '1d2bdf'
223
			},
224
			's_pass' => {
225
				'type' => 'DERIVE',
226
				'min' => '0',
227
				'colour' => '785d0d'
228
			}
229
		}
230
	},
231
	'hit_rate' => {
232
		'title' => 'Hit rates',
233
		'order' => 'client_req cache_hit cache_miss '
234
			 . 'cache_hitpass' ,
235
		'vlabel' => '%',
236
		'args' => '-l 0 -u 100 --rigid',
237
		'scale' => 'no',
238
		'values' => {
239
			'client_req' => {
240
				'type' => 'DERIVE',
241
				'min' => '0',
242
				'graph' => 'off',
243
				'rpn' => [ 'cache_hit' , 'cache_miss' , 'cache_hitpass' , '+' , '+' ]
244
			},
245
			'cache_hit' => {
246
				'type' => 'DERIVE',
247
				'min' => '0',
248
				'draw' => 'AREA',
249
				'cdef' => 'cache_hit,client_req,/,100,*'
250
			},
251
			'cache_miss' => {
252
				'type' => 'DERIVE',
253
				'draw' => 'STACK',
254
				'min' => '0',
255
				'cdef' => 'cache_miss,client_req,/,100,*'
256
			},
257
			'cache_hitpass' => {
258
				'type' => 'DERIVE',
259
				'draw' => 'STACK',
260
				'min' => '0',
261
				'cdef' => 'cache_hitpass,client_req,/,100,*'
262
			},
263
		}
264
	},
265
	'backend_traffic' => {
266
		'title' => 'Backend traffic',
267
		'values' => {
268
			'backend_conn' => {
269
				'type' => 'DERIVE',
270
				'min' => '0'
271
			},
272
			'backend_unhealthy' => {
273
				'type' => 'DERIVE',
274
				'min' => '0',
275
				'warning' => ':1'
276
			},
277
			'backend_busy' => {
278
				'type' => 'DERIVE',
279
				'min' => '0'
280
			},
281
			'backend_fail' => {
282
				'type' => 'DERIVE',
283
				'min' => '0'
284
			},
285
			'backend_reuse' => {
286
				'type' => 'DERIVE',
287
				'min' => 0
288
			},
289
			'backend_recycle' => {
290
				'type' => 'DERIVE',
291
				'min' => 0
292
			},
293
			'backend_toolate' => {
294
				'type' => 'DERIVE',
295
				'min' => '0'
296
			},
297
			'backend_retry' => {
298
				'type' => 'DERIVE',
299
				'min' => '0'
300
			},
301
			'backend_req' => {
302
				'type' => 'DERIVE',
303
				'min' => '0'
304
			}
305
		}
306
	},
307
	'objects' => {
308
		'title' => 'Number of objects',
309
		'order' => 'n_object n_objectcore n_vampireobject n_objecthead',
310
		'values' => {
311
			'n_object' => {
312
				'type' => 'GAUGE',
313
				'label' => 'Number of objects'
314
			},
315
			'n_objectcore' => {
316
				'type' => 'GAUGE',
317
				'label' => 'Number of object cores'
318
			},
319
			'n_vampireobject' => {
320
				'type' => 'GAUGE',
321
				'label' => 'Number of unresurrected objects'
322
			},
323
			'n_objecthead' => {
324
				'type' => 'GAUGE',
325
				'label' => 'Number of object heads',
326
				'info' => 'Each object head can have one '
327
					. 'or more object attached, '
328
					. 'typically based on the Vary: header'
329
			}
330
		}
331
	},
332
	'transfer_rates' => {
333
		'title' => 'Transfer rates',
334
		'order' => 's_resp_bodybytes s_resp_hdrbytes',
335
		'args' => '-l 0',
336
		'vlabel' => 'bit/s',
337
		'values' => {
338
			's_resp_hdrbytes' => {
339
				'type' => 'DERIVE',
340
				'label' => 'Header traffic',
341
				'draw' => 'STACK',
342
				'min' => '0',
343
				'info' => 'HTTP Header traffic. TCP/IP '
344
					. 'overhead is not included.',
345
				'cdef' => 's_resp_hdrbytes,8,*'
346
			},
347
			's_resp_bodybytes' => {
348
				'type' => 'DERIVE',
349
				'draw' => 'AREA',
350
				'label' => 'Body traffic',
351
				'min' => '0',
352
				'cdef' => 's_resp_bodybytes,8,*'
353
			}
354
		}
355
	},
356
	'threads' => {
357
		'title' => 'Thread status',
358
		'values' => {
359
			'threads' => {
360
				'type' => 'GAUGE',
361
				'min' => '0',
362
				'warning' => '1:'
363
			},
364
			'threads_created' => {
365
				'type' => 'DERIVE',
366
				'min' => '0'
367
			},
368
			'threads_failed' => {
369
				'type' => 'DERIVE',
370
				'min' => '0',
371
				'warning' => ':1'
372
			},
373
			'threads_limited' => {
374
				'type' => 'DERIVE',
375
				'min' => '0'
376
			},
377
			'threads_destroyed' => {
378
				'type' => 'DERIVE',
379
				'min' => '0',
380
				'warning' => ':1'
381
			}
382
		}
383
	},
384
	'memory_usage' => {
385
		'title' => 'Memory usage',
386
		'args' => '--base 1024',
387
		'vlabel' => 'bytes',
388
		'values' => {
389
			'sms_balloc' => {
390
				'type' => 'GAUGE',
391
			},
392
			'sms_nbytes' => {
393
				'type' => 'GAUGE',
394
			},
395
			'SMA_1' => {
396
				'counter' => 'g_bytes',
397
				'family' => 'SMA',
398
			},
399
			'SMA_2' => {
400
				'counter' => 'g_space',
401
				'family' => 'SMA',
402
			},
403
			'SMA_3' => {
404
				'counter' => 'c_bytes',
405
				'family' => 'SMA'
406
			},
407
			'SMF_1' => {
408
				'counter' => 'g_bytes',
409
				'family' => 'SMF',
410
			},
411
			'SMF_2' => {
412
				'counter' => 'g_space',
413
				'family' => 'SMF',
414
			}
415
		}
416
	},
417
	'uptime' => {
418
		'title' => 'Varnish uptime',
419
		'vlabel' => 'days',
420
		'scale' => 'no',
421
		'values' => {
422
			'uptime' => {
423
				'type' => 'GAUGE',
424
				'cdef' => 'uptime,86400,/'
425
			}
426
		}
427
	},
428
	'objects_per_objhead' => {
429
		'title' => 'Objects per objecthead',
430
		'DEBUG' => 'yes',
431
		'values' => {
432
			'obj_per_objhead' => {
433
				'type' => 'GAUGE',
434
				'label' => 'Objects per object heads',
435
				'rpn' => [ 'n_object','n_objecthead','/' ]
436
			}
437
		}
438
	},
439
	'losthdr' => {
440
		'title' => 'HTTP Header overflows',
441
		'DEBUG' => 'yes',
442
		'values' => {
443
			'losthdr' => {
444
				'type' => 'DERIVE',
445
				'min' => '0'
446
			}
447
		}
448
	},
449
	'hcb' => {
450
		'title' => 'Critbit data',
451
		'DEBUG' => 'yes',
452
		'values' => {
453
			'hcb_nolock' => {
454
				'type' => 'DERIVE',
455
				'min' => '0'
456
			},
457
			'hcb_lock' => {
458
				'type' => 'DERIVE',
459
				'min' => '0'
460
			},
461
			'hcb_insert' => {
462
				'type' => 'DERIVE',
463
				'min' => '0'
464
			}
465
		}
466
	},
467
	'esi' => {
468
		'title' => 'ESI',
469
		'DEBUG' => 'yes',
470
		'values' => {
471
			'esi_parse' => {
472
				'type' => 'DERIVE',
473
				'min' => '0'
474
			},
475
			'esi_errors' => {
476
				'type' => 'DERIVE',
477
				'min' => '0'
478
			},
479
			'esi_warnings' => {
480
				'type' => 'DERIVE',
481
				'min' => '0'
482
			}
483
		}
484
	},
485
	'session' => {
486
		'title' => 'Sessions',
487
		'DEBUG' => 'yes',
488
		'values' => {
489
			'sess_conn' => {
490
				'type' => 'DERIVE',
491
				'min' => '0'
492
			},
493
			'sess_drop' => {
494
				'type' => 'DERIVE',
495
				'min' => '0'
496
			},
497
			'sess_fail' => {
498
				'type' => 'DERIVE',
499
				'min' => '0'
500
			},
501
			'sess_pipe_overflow' => {
502
				'type' => 'DERIVE',
503
				'min' => '0'
504
			},
505
			'sess_queued' => {
506
				'type' => 'DERIVE',
507
				'min' => '0'
508
			},
509
			'sess_dropped' => {
510
				'type' => 'DERIVE',
511
				'min' => '0'
512
			},
513
			'sess_closed' => {
514
				'type' => 'DERIVE',
515
				'min' => '0'
516
			},
517
			'sess_pipeline' => {
518
				'type' => 'DERIVE',
519
				'min' => '0'
520
			},
521
			'sess_readahead' => {
522
				'type' => 'DERIVE',
523
				'min' => '0'
524
			}
525
		}
526
	},
527
	'session_herd' => {
528
		'title' => 'Session herd',
529
		'DEBUG' => 'yes',
530
		'values' => {
531
			'sess_herd' => {
532
				'type' => 'DERIVE',
533
				'min' => '0'
534
			}
535
		}
536
	},
537
	'shm_writes' => {
538
		'title' => 'SHM writes and records',
539
		'DEBUG' => 'yes',
540
		'values' => {
541
			'shm_records' => {
542
				'type' => 'DERIVE',
543
				'min' => '0'
544
			},
545
			'shm_writes' => {
546
				'type' => 'DERIVE',
547
				'min' => '0'
548
			}
549
		}
550
	},
551
	'shm' => {
552
		'title' => 'Shared memory activity',
553
		'DEBUG' => 'yes',
554
		'values' => {
555
			'shm_flushes' => {
556
				'type' => 'DERIVE',
557
				'min' => '0'
558
			},
559
			'shm_cont' => {
560
				'type' => 'DERIVE',
561
				'min' => '0'
562
			},
563
			'shm_cycles' => {
564
				'type' => 'DERIVE',
565
				'min' => '0'
566
			}
567
		}
568
	},
569
	'allocations' => {
570
		'title' => 'Memory allocation requests',
571
		'DEBUG' => 'yes',
572
		'values' => {
573
			'sm_nreq' => {
574
				'type' => 'DERIVE',
575
				'min' => '0'
576
			},
577
			'sma_nreq' => {
578
				'type' => 'DERIVE',
579
				'min' => '0'
580
			},
581
			'sms_nreq' => {
582
				'type' => 'DERIVE',
583
				'min' => '0'
584
			}
585
		}
586
	},
587
	'vcl' => {
588
		'title' => 'VCL',
589
		'DEBUG' => 'yes',
590
		'values' => {
591
			'n_backend' => {
592
				'type' => 'GAUGE'
593
			},
594
			'n_vcl' => {
595
				'type' => 'DERIVE',
596
				'min' => '0'
597
			},
598
			'n_vcl_avail' => {
599
				'type' => 'DERIVE',
600
				'min' => '0'
601
			},
602
			'n_vcl_discard' => {
603
				'type' => 'DERIVE',
604
				'min' => '0'
605
			}
606
		}
607
	},
608
	'bans' => {
609
		'title' => 'Bans',
610
		'DEBUG' => 'yes',
611
		'values' => {
612
			'bans' => {
613
				'type' => 'GAUGE'
614
			},
615
			'bans_added' => {
616
				'type' => 'DERIVE',
617
				'min' => '0'
618
			},
619
			'bans_deleted' => {
620
				'type' => 'DERIVE',
621
				'min' => '0'
622
			},
623
			'bans_completed' => {
624
				'type' => 'GAUGE'
625
			},
626
			'bans_obj' => {
627
				'type' => 'GAUGE'
628
			},
629
			'bans_req' => {
630
				'type' => 'GAUGE'
631
			},
632
			'bans_tested' => {
633
				'type' => 'DERIVE',
634
				'min' => '0'
635
			},
636
			'bans_obj_killed' => {
637
				'type' => 'DERIVE',
638
				'min' => '0'
639
			},
640
			'bans_tests_tested' => {
641
				'type' => 'DERIVE',
642
				'min' => '0'
643
			},
644
			'bans_dups' => {
645
				'type' => 'GAUGE'
646
			},
647
			'bans_persisted_bytes' => {
648
				'type' => 'GAUGE'
649
			},
650
			'bans_persisted_fragmentation' => {
651
				'type' => 'GAUGE'
652
			}
653
		}
654
	},
655
	'bans_lurker' => {
656
		'title' => 'Ban Lurker',
657
		'DEBUG' => 'yes',
658
		'values' => {
659
			'bans_lurker_tested' => {
660
				'type' => 'DERIVE',
661
				'min' => '0'
662
			},
663
			'bans_lurker_tests_tested' => {
664
				'type' => 'DERIVE',
665
				'min' => '0'
666
			},
667
			'bans_lurker_obj_killed' => {
668
				'type' => 'DERIVE',
669
				'min' => '0'
670
			},
671
			'bans_lurker_contention' => {
672
				'type' => 'DERIVE',
673
				'min' => '0'
674
			}
675
		}
676
	},
677
	'expunge' => {
678
		'title' => 'Object expunging',
679
		'order' => 'n_expired n_lru_nuked',
680
		'values' => {
681
			'n_expired' => {
682
				'type' => 'DERIVE',
683
				'min' => '0'
684
			},
685
			'n_lru_nuked' => {
686
				'type' => 'DERIVE',
687
				'min' => '0'
688
			}
689
		}
690
	},
691
	'lru' => {
692
		'title' => 'LRU activity',
693
		'DEBUG' => 'yes',
694
		'values' => {
695
			'n_lru_nuked' => {
696
				'type' => 'DERIVE',
697
				'min' => '0'
698
			},
699
			'n_lru_moved' => {
700
				'type' => 'DERIVE',
701
				'min' => '0'
702
			}
703
		}
704
	},
705
	'bad' => {
706
		'title' => 'Misbehavior',
707
		'values' => {
708
			'SMA_1' => {
709
				'counter' => 'c_fail',
710
				'family' => 'SMA',
711
			},
712
			'SMF_1' => {
713
				'counter' => 'c_fail',
714
				'family' => 'SMF',
715
			},
716
			'sess_drop' => {
717
				'type' => 'DERIVE'
718
			},
719
			'backend_unhealthy' => {
720
				'type' => 'DERIVE'
721
			},
722
			'fetch_failed' => {
723
				'type' => 'DERIVE'
724
			},
725
			'backend_busy' => {
726
				'type' => 'DERIVE'
727
			},
728
			'threads_failed' => {
729
				'type' => 'DERIVE'
730
			},
731
			'threads_limited' => {
732
				'type' => 'DERIVE'
733
			},
734
			'threads_destroyed' => {
735
				'type' => 'DERIVE'
736
			},
737
			'thread_queue_len' => {
738
				'type' => 'GAUGE'
739
			},
740
			'losthdr' => {
741
				'type' => 'DERIVE'
742
			},
743
			'esi_errors' => {
744
				'type' => 'DERIVE'
745
			},
746
			'esi_warnings' => {
747
				'type' => 'DERIVE'
748
			},
749
			'sess_fail' => {
750
				'type' => 'DERIVE'
751
			},
752
			'sess_pipe_overflow' => {
753
				'type' => 'DERIVE'
754

    
755
			}
756
		}
757
	},
758
	'gzip' => {
759
		'title' => 'GZIP activity',
760
		'DEBUG' => 'yes',
761
		'values' => {
762
			'n_gzip' => {
763
				'type' => 'DERIVE',
764
				'min' => '0'
765
			},
766
			'n_gunzip' => {
767
				'type' => 'DERIVE',
768
				'min' => '0'
769
			}
770
		}
771
	}
772
);
773

    
774
################################
775
# Various helper functions     #
776
################################
777

    
778
# Translate $_[0] from varnish' internal types (flags) to munin/rrd
779
# variants (e.g: from 'i' to GAUGE). Returns the result.
780
sub translate_type
781
{
782
	my $d = $_[0];
783
	if ($d eq "i" or $d eq "g") {
784
		$d = "GAUGE";
785
	} elsif ($d eq "a" or $d eq "c") {
786
		$d = "DERIVE";
787
	}
788
	return $d;
789
}
790

    
791
# Print the value of a two-dimensional hash if it exist.
792
# Returns false if non-existent.
793
#
794
# Output is formatted for plugins if arg4 is blank, otherwise arg4 is used
795
# as the title/name of the field (ie: arg4=graph_title).
796
sub print_if_exist
797
{
798
	my %values = %{$_[0]};
799
	my $value = $_[1];
800
	my $field = $_[2];
801
	my $title = "$value.$field";
802
	if (defined($_[3])) {
803
		$title = $_[3];
804
	}
805
	if (defined($values{$value}{$field})) {
806
		print "$title $values{$value}{$field}\n";
807
	} else {
808
		return 0;
809
	}
810
}
811

    
812
# Create a output-friendly name
813
sub normalize_name
814
{
815
	my $name = $_[0];
816
	$name =~ s/[^a-zA-Z0-9]/_/g;
817
	return $name;
818
}
819

    
820
# Braindead RPN: +,-,/,* will pop two items from @stack, and perform
821
# the relevant operation on the items. If the item in the array isn't one
822
# of the 4 basic math operations, a value from varnishstat is pushed on to
823
# the stack. IE: 'client_req','client_conn','/' will leave the value of
824
# "client_req/client_conn" on the stack.
825
#
826
# If only one item is left on the stack, it is printed. Otherwise, an error
827
# message is printed.
828
sub rpn
829
{
830
	my @stack;
831
	my $left;
832
	my $right;
833
	foreach my $item (@{$_[0]}) {
834
		if ($item eq "+") {
835
			$right = pop(@stack);
836
			$left = pop(@stack);
837
			push(@stack,$left+$right);
838
		} elsif ($item eq "-") {
839
			$right = pop(@stack);
840
			$left = pop(@stack);
841
			push(@stack,$left-$right);
842
		} elsif ($item eq "/") {
843
			$right = pop(@stack);
844
			$left = pop(@stack);
845
			push(@stack,$left/$right);
846
		} elsif ($item eq "*") {
847
			$right = pop(@stack);
848
			$left = pop(@stack);
849
			push(@stack,$left*$right);
850
		} else {
851
			push(@stack,int($data{$item}{'value'}));
852
		}
853
	}
854
	if (@stack > 1)
855
	{
856
		print STDERR "RPN error: Stack has more than one item left.\n";
857
		print STDERR "@stack\n";
858
		exit 255;
859
	}
860
	print "@stack";
861
	print "\n";
862
}
863

    
864
# Bail-function.
865
sub usage
866
{
867
	if (@_) {
868
		print STDERR "@_" . "\n\n";
869
	}
870
	print STDERR "Known arguments: suggest, config, autoconf.\n";
871
	print STDERR "Run with suggest to get a list of known aspects.\n";
872
	exit 1;
873
}
874

    
875
################################
876
# XML Parsing                  #
877
################################
878
# The following code is for parsing varnishstat -x. While %data should be
879
# stable, the following bits can easily be replaced with anything (json, an
880
# other xml-parser, magic, etc)
881
#
882
# The basic concept is simple enough. Only worry about stuff inside
883
# <state>. Updating %state on each new data field, and commit it to %data
884
# when </state> is seen.
885
#
886
# We do use translate_type() on the 'flag' field.
887

    
888

    
889
# Internal state for the XML parsing
890
my %state = (
891
	'stat' => 0,     # inside <stat> or not
892
	'field' => 'none', # <name>, <value>, <stat>, etc.
893
);
894

    
895
# Reset the state of XML, mainly used for end-elements.
896
sub xml_reset_state() {
897
	$state{'stat'} = '0';
898
	$state{'field'} = 'none';
899
	$state{'values'} = ();
900
}
901

    
902
# Callback for data entry. Cleans leading whitespace and updates state.
903
sub xml_characters {
904
	my $d = $_[1];
905
	if ($state{'stat'} == 0) {
906
		return;
907
	}
908
	if ($state{'field'} eq "type" && $d eq "MAIN") {
909
		return;
910
	}
911
	$d =~ s/^\s*$//g;
912
	if ($d eq "") {
913
		return;
914
	}
915
	$state{'values'}{$state{'field'}} = $d;
916
}
917

    
918
# Store the current state in %data. Issued at </stat>
919
# Note that 'flag' is translated to RRD-equivalents here.
920
sub xml_commit_state
921
{
922
	my $name = $state{'values'}{'name'};
923
	my $type = $state{'values'}{'type'};
924
	my $ident = $state{'values'}{'ident'};
925

    
926
	foreach my $key (keys %{$state{'values'}}) {
927
		my $data = $state{'values'}{$key};
928
		if ($key eq 'flag') {
929
			$data = translate_type($data);
930
		}
931
		if (defined($type) and $type ne '' and defined($ident) and $ident ne '') {
932
			$data{$type}{$ident}{$name}{$key} = $data;
933
		} else {
934
			$data{$name}{$key} = $data
935
		}
936
	}
937
}
938

    
939
# Callback for end tag. E.g: </stat>
940
sub xml_end_elem {
941
	my $element = $_[1];
942
	if ($element ne "stat") {
943
		return;
944
	}
945

    
946
	xml_commit_state();
947
	xml_reset_state();
948
}
949

    
950
# Callback for opening tag. E.g: <stat>
951
sub xml_start_elem {
952
	$state{'field'} = $_[1];
953
	if ($state{'field'} eq "stat") {
954
		$state{'stat'} = 1;
955
	}
956
}
957

    
958
################################
959
# Internal API                 #
960
################################
961

    
962

    
963
# Populate %data, includes both values and descriptions and more.
964
# Currently driven by XML, but that could change.
965
sub populate_stats
966
{
967
	my $arg = "-x";
968
	my $parser = new XML::Parser(Handlers => {Start => \&xml_start_elem,
969
						End => \&xml_end_elem,
970
						Char => \&xml_characters} );
971

    
972
	if ($varnishname) {
973
		$arg .= " -n $varnishname";
974
	}
975

    
976
	open (XMLDATA, "$varnishstatexec $arg|") or die "meh";
977
	$parser->parse(*XMLDATA, ProtocolEncoding => 'ISO-8859-1');
978
	close(XMLDATA);
979
}
980

    
981
# Prints the fields in the list in $_[2] (e.g: 'value'/'description') for
982
# each identity of the varnish counter/family combination as defined by
983
# the $_[0]-counter on the aspect definition. Err, that's jibberish, so
984
# an example:
985
#
986
# e.g: dynamic_print('SMA_1','',('value'))
987
# e.g: dynamic_print('SMA_2','.label',('ident','description'))
988
# SMA_1 is the counter-value. If it is a dynamic counter, it has a counter
989
# and family-member (e.g: counter: c_req, family: SMA) and print_dynamic
990
# will print c_req for each SMA-identity.
991
#
992
# Note that the variables to print is a list. This is to allow printing a
993
# single item with multiple fields. Typically for identity+description so
994
# you can distinguish between different data points.
995
#
996
# Returns true if it was a dynamic counter.
997
sub print_dynamic
998
{
999
	my $name = $_[0];
1000
	shift;
1001
	my $suffix = $_[0];
1002
	shift;
1003
	my @field = @_;
1004
	if (!defined($ASPECTS{$self}{'values'}{$name}{'counter'})) {
1005
		return 0;
1006
	}
1007
	if (!defined($ASPECTS{$self}{'values'}{$name}{'family'})) {
1008
		return 0;
1009
	}
1010
	my $counter = $ASPECTS{$self}{'values'}{$name}{'counter'};
1011
	my $type = $ASPECTS{$self}{'values'}{$name}{'family'};
1012

    
1013
	foreach my $key (keys %{$data{$type}}) {
1014
		my $pname = normalize_name($type . "_" . $key . "_" . $counter);
1015
		print $pname . $suffix . " ";
1016
		my $i = 0;
1017
		foreach my $f (@field) {
1018
			if ($i != 0) {
1019
				print " ";
1020
			}
1021
			$i += 1;
1022
			print $data{$type}{$key}{$counter}{$f};
1023
		}
1024
		print "\n";
1025
	}
1026
	return 1;
1027
}
1028

    
1029
# Read and verify the aspect ($self).
1030
sub set_aspect
1031
{
1032
	$self = $0;
1033
	$self =~ s/^.*\/varnish[0-9]?_//;
1034
	return if defined($ASPECTS{$self});
1035
	# remove instance name and try again
1036
	$self =~ s/^.*?_//;
1037
	if (!defined($ASPECTS{$self}) && @ARGV == 0) {
1038
		usage "No such aspect";
1039
	}
1040
}
1041

    
1042
# Print 'yes' if it's reasonable to use this plugin, or 'no' with a
1043
# human-readable error message. Always exit true, even if the response
1044
# is 'no'.
1045
sub autoconf
1046
{
1047
	# XXX: Solaris outputs errors to stderr and always returns true.
1048
	# XXX: See #873
1049
	if (`which $varnishstatexec 2>/dev/null` =~ m{^/}) {
1050
		print "yes\n";
1051
	} else {
1052
		print "no ($varnishstatexec could not be found)\n";
1053
	}
1054
	exit 0;
1055
}
1056

    
1057
# Suggest relevant aspects/values of $self.
1058
# 'DEBUG'-graphs are excluded.
1059
sub suggest
1060
{
1061
	foreach my $key (keys %ASPECTS) {
1062
		if (defined($ASPECTS{$key}{'DEBUG'}) && $FULL_SUGGEST != 1) {
1063
			next;
1064
		}
1065
		print "$key\n";
1066
	}
1067
}
1068

    
1069
# Walk through the relevant aspect and print all top-level configuration
1070
# values and value-definitions.
1071
sub get_config
1072
{
1073
	my $graph = $_[0];
1074

    
1075
	# Need to double-check since set_aspect only checks this if there
1076
	# is no argument (suggest/autoconf doesn't require a valid aspect)
1077
	if (!defined($ASPECTS{$graph})) {
1078
		usage "No such aspect";
1079
	}
1080
	my %values = %{$ASPECTS{$graph}{'values'}};
1081

    
1082
	print "graph_category webserver\n";
1083
	foreach my $field (@graph_parameters) {
1084
		print_if_exist(\%ASPECTS,$graph,$field,"graph_$field");
1085
	}
1086

    
1087
	foreach my $value (sort keys %values) {
1088
		# Just print the description/type if it's a dynamic
1089
		# counter. It'll be silent if it isn't.
1090
		if(print_dynamic($value,'.label',('description','type','ident'))) {
1091
			print_dynamic($value,'.type',('flag'));
1092
			next;
1093
		}
1094

    
1095
		# Need either RPN definition or a varnishstat value.
1096
		if (!defined($data{$value}{'value'}) &&
1097
		    !defined($values{$value}{'rpn'})) {
1098
			if ($DEBUG) {
1099
				print "ERROR: $value not part of varnishstat.\n"
1100
			}
1101
			next;
1102
		}
1103

    
1104
		if (!print_if_exist(\%values,$value,'label')) {
1105
			print "$value.label $data{$value}{'description'}\n";
1106
		}
1107
		foreach my $field (@field_parameters) {
1108
			print_if_exist(\%values,$value,$field);
1109
		}
1110
	}
1111
}
1112

    
1113
# Handle arguments (config, autoconf, suggest)
1114
# Populate stats for config is necessary, but we want to avoid it for
1115
# autoconf as it would generate a nasty error.
1116
sub check_args
1117
{
1118
	if (@ARGV && $ARGV[0] eq '') {
1119
		shift @ARGV;
1120
	}
1121
	if (@ARGV == 1) {
1122
		if ($ARGV[0] eq "config") {
1123
			populate_stats;
1124
			get_config($self);
1125
			exit 0;
1126
		} elsif ($ARGV[0] eq "autoconf") {
1127
			autoconf($self);
1128
			exit 0;
1129
		} elsif ($ARGV[0] eq "suggest") {
1130
			suggest;
1131
			exit 0;
1132
		}
1133
		usage "Unknown argument";
1134
	}
1135
}
1136

    
1137
################################
1138
# Execution starts here        #
1139
################################
1140

    
1141
set_aspect;
1142
check_args;
1143
populate_stats;
1144

    
1145
# We only get here if we're supposed to.
1146
# Walks through the relevant values and either prints the varnishstat, or
1147
# if the 'rpn' variable is set, calls rpn() to execute ... the rpn.
1148
#
1149
# NOTE: Due to differences in varnish-versions, this checks if the value
1150
# actually exist before using it.
1151
foreach my $value (keys %{$ASPECTS{$self}{'values'}}) {
1152
	if (defined($ASPECTS{$self}{'values'}{$value}{'rpn'})) {
1153
		print "$value.value ";
1154
		rpn($ASPECTS{$self}{'values'}{$value}{'rpn'});
1155
	} else {
1156
		if (print_dynamic($value,'.value',('value'))) {
1157
			next;
1158
		}
1159

    
1160
		if (!defined($data{$value}{'value'})) {
1161
			if ($DEBUG) {
1162
				print STDERR "Error: $value not part of "
1163
					   . "varnishstat.\n";
1164
			}
1165
			next;
1166
		}
1167
		print "$value.value ";
1168
		print "$data{$value}{'value'}\n";
1169
	}
1170
}
1171