Projet

Général

Profil

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

root / plugins / varnish4 / varnish4_ @ ed8a615e

Historique | Voir | Annoter | Télécharger (25,6 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 Lyngstol <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
     env.varnishstat varnishstat
38
     env.name
39

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

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

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

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

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

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

    
59
=head1 INTERPRETATION
60

    
61
Each graph uses data from varnishstat.
62

    
63
=head1 MAGIC MARKERS
64

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

    
68
=head1 VERSION
69

    
70
 $Id$
71

    
72
=head1 BUGS
73

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

    
77
=head1 PATCHES-TO
78

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

    
83
=head1 AUTHOR
84

    
85
Kristian Lyngstol <kristian@bohemians.org>
86

    
87
=head1 MODIFICATIONS
88

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

    
91
=head1 LICENSE
92

    
93
GPLv2
94

    
95
=cut
96

    
97

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

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

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

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

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

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

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

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

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

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

    
769
################################
770
# Various helper functions     #
771
################################
772

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

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

    
807
# Create a output-friendly name
808
sub normalize_name
809
{
810
	my $name = $_[0];
811
	$name =~ s/[^a-zA-Z0-9]/_/g;
812
	return $name;
813
}
814

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

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

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

    
883

    
884
# Internal state for the XML parsing
885
my %state = (
886
	'stat' => 0,     # inside <stat> or not
887
	'field' => 'none', # <name>, <value>, <stat>, etc.
888
);
889

    
890
# Reset the state of XML, mainly used for end-elements.
891
sub xml_reset_state() {
892
	$state{'stat'} = '0';
893
	$state{'field'} = 'none';
894
	$state{'values'} = ();
895
}
896

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

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

    
921
	foreach my $key (keys %{$state{'values'}}) {
922
		my $data = $state{'values'}{$key};
923
		if ($key eq 'flag') {
924
			$data = translate_type($data);
925
		}
926
		if (defined($type) and $type ne "" and defined($ident) and $ident ne "") {
927
			$data{$type}{$ident}{$name}{$key} = $data;
928
		} else {
929
			$data{$name}{$key} = $data
930
		}
931
	}
932
}
933

    
934
# Callback for end tag. E.g: </stat>
935
sub xml_end_elem {
936
	my $element = $_[1];
937
	if ($element ne "stat") {
938
		return;
939
	}
940

    
941
	xml_commit_state();
942
	xml_reset_state();
943
}
944

    
945
# Callback for opening tag. E.g: <stat>
946
sub xml_start_elem {
947
	$state{'field'} = $_[1];
948
	if ($state{'field'} eq "stat") {
949
		$state{'stat'} = 1;
950
	}
951
}
952

    
953
################################
954
# Internal API                 #
955
################################
956

    
957

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

    
971
	open (XMLDATA, "$varnishstatexec $arg|") or die "meh";
972
	$parser->parse(*XMLDATA, ProtocolEncoding => 'ISO-8859-1');
973
	close(XMLDATA);
974
}
975

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

    
1024
# Read and verify the aspect ($self).
1025
sub set_aspect
1026
{
1027
	$self = $0;
1028
	$self =~ s/^.*\/varnish[0-9]?_//;
1029
	if (!defined($ASPECTS{$self}) && @ARGV == 0) {
1030
		usage "No such aspect";
1031
	}
1032
}
1033

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

    
1049
# Suggest relevant aspects/values of $self.
1050
# 'DEBUG'-graphs are excluded.
1051
sub suggest
1052
{
1053
	foreach my $key (keys %ASPECTS) {
1054
		if (defined($ASPECTS{$key}{'DEBUG'}) && $FULL_SUGGEST != 1) {
1055
			next;
1056
		}
1057
		print "$key\n";
1058
	}
1059
}
1060

    
1061
# Walk through the relevant aspect and print all top-level configuration
1062
# values and value-definitions.
1063
sub get_config
1064
{
1065
	my $graph = $_[0];
1066

    
1067
	# Need to double-check since set_aspect only checks this if there
1068
	# is no argument (suggest/autoconf doesn't require a valid aspect)
1069
	if (!defined($ASPECTS{$graph})) {
1070
		usage "No such aspect";
1071
	}
1072
	my %values = %{$ASPECTS{$graph}{'values'}};
1073

    
1074
	print "graph_category Varnish\n";
1075
	foreach my $field (@graph_parameters) {
1076
		print_if_exist(\%ASPECTS,$graph,$field,"graph_$field");
1077
	}
1078

    
1079
	foreach my $value (keys %values) {
1080
		# Just print the description/type if it's a dynamic
1081
		# counter. It'll be silent if it isn't.
1082
		if(print_dynamic($value,'.label',('description','type','ident'))) {
1083
			print_dynamic($value,'.type',('flag'));
1084
			next;
1085
		}
1086

    
1087
		# Need either RPN definition or a varnishstat value.
1088
		if (!defined($data{$value}{'value'}) &&
1089
		    !defined($values{$value}{'rpn'})) {
1090
			if ($DEBUG) {
1091
				print "ERROR: $value not part of varnishstat.\n"
1092
			}
1093
			next;
1094
		}
1095
		
1096
		if (!print_if_exist(\%values,$value,'label')) {
1097
			print "$value.label $data{$value}{'description'}\n";
1098
		}
1099
		foreach my $field (@field_parameters) {
1100
			print_if_exist(\%values,$value,$field);
1101
		}
1102
	}
1103
}
1104

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

    
1129
################################
1130
# Execution starts here        #
1131
################################
1132

    
1133
set_aspect;
1134
check_args;
1135
populate_stats;
1136

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

    
1152
		if (!defined($data{$value}{'value'})) {
1153
			if ($DEBUG) {
1154
				print STDERR "Error: $value not part of "
1155
					   . "varnishstat.\n";
1156
			}
1157
			next;
1158
		}
1159
		print "$value.value ";
1160
		print "$data{$value}{'value'}\n";
1161
	}
1162
}
1163