Projet

Général

Profil

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

root / plugins / network / if @ 42fd0b1a

Historique | Voir | Annoter | Télécharger (44,7 ko)

1
#!/usr/bin/perl -w
2
# -*- perl -*-
3

    
4
=head1 NAME
5

    
6
if - Multigraph plugin to monitor network wired and wireless interfaces
7

    
8
=head1 INTERPRETATION
9

    
10
In the general graphs made key fields for each interface, a subsidiary graphs for each interface there are detailed
11

    
12
This plugin displays the following charts:
13
Traffic, bit
14
Traffic, packets
15
Average packet size
16
Interface errors
17
WiFi interface errors
18
WiFi interface signal and noise
19
WiFi interface link quality
20
Interface utilisation
21

    
22
Virtual interface names prefixes with '~'
23

    
24
=head1 CONFIGURATION
25

    
26
This plugin is configurable environment variables.
27
env.exclude         - Removing interfaces from graphs, default empty
28
env.include         - Includes interfaces into graphs, default empty
29
env.if_max_bps      - Maximum interface bps. Avialable suffixes: k, M, G, default empty
30
env.protexct_peaks  - Protect graph peaks, default 'no'
31
env.min_packet_size - Minimal network packet size, default 20
32
Example:
33
 [if]
34
    env.exclude lo
35
    env.include wlan0 tun0
36
    env.wlan0_max_bps 54M
37
    env.eth0_max_bps 1G
38
    env.protect_peaks yes
39

    
40
Protect peak:
41
1. Protect wifi signal and noise values, all values > 0 print as NaN
42
2. protect all percent values. All values > 100 print as NaN
43
3. Protect bps values. env.if_max_bps must be set. All values > max_bps prints as 0
44
4. protect pps values. env.if_max_bps must be set. All values > max_bps/minimal packet size, prints as 0
45

    
46
=head1 AUTHOR
47

    
48
Gorlow Maxim aka Sheridan <sheridan@sheridan-home.ru> (email and jabber)
49

    
50
=head1 LICENSE
51

    
52
GPLv2
53

    
54
=head1 MAGIC MARKERS
55

    
56
  #%# family=auto
57
  #%# capabilities=autoconf
58

    
59
=cut
60

    
61
use strict;
62
use warnings;
63
use IO::Dir;
64
use Munin::Plugin;
65
use Data::Dumper;
66

    
67
# ------------------------------------------------------------- constants ---------------------
68
my $exclude         = $ENV{exclude}         || '';
69
my $include         = $ENV{include}         || '-';
70
my $protect_peacks  = $ENV{protect_peaks}   || 'no';
71
my $min_packet_size = $ENV{min_packet_size} || 20;
72
my $ifpath          = '/sys/class/net';
73
# ----------------------------------- global -----------------
74
my $interfaces = {};
75

    
76
# ------------------------ avialable graphs -------------------------
77
my $graphs =
78
{
79
  'if_bit' =>
80
  {
81
    'munin' =>
82
    {
83
      'category' => 'network',
84
      'args'     => '--base 1000',
85
      'title'    => ':if: traffic, bit',
86
      'vlabel'   => 'Bit in (-) / out (+), avg. per second',
87
      'info'     => 'This graph shows the traffic in bit of the :if:, averaged value per second from last update'
88
    },
89
    'per_if_fields'  => [qw(rx_bytes tx_bytes)],
90
    'general_fields' => [qw(rx_bytes tx_bytes)]
91
  },
92
  'if_packets' => 
93
  {
94
    'munin' =>
95
    {
96
      'category' => 'network',
97
      'args'     => '--base 1000',
98
      'title'    => ':if: traffic, packets', 
99
      'vlabel'   => 'Packets in (-) / out (+), avg. per second',
100
      'info'     => 'This graph shows the traffic in packets of the :if:, averaged value per second from last update'
101
    },
102
    'per_if_fields'  => [qw(rx_packets tx_packets rx_compressed tx_compressed rx_dropped tx_dropped multicast)],
103
    'general_fields' => [qw(rx_packets tx_packets)]
104
  },
105
  'if_errors' => 
106
  {
107
    'munin' =>
108
    {
109
      'category' => 'network',
110
      'args'     => '--base 1000',
111
      'title'    => ':if: errors',
112
      'vlabel'   => 'Errors RX (-) / TX (+)',
113
      'info'     => 'This graph shows the errors of the :if: from last update',
114
      'scale'    => 'no'
115
    },
116
    'per_if_fields'  => [qw(rx_errors tx_errors rx_fifo_errors tx_fifo_errors rx_crc_errors rx_frame_errors rx_length_errors rx_missed_errors rx_over_errors collisions tx_aborted_errors tx_carrier_errors tx_heartbeat_errors tx_window_errors)],
117
    'general_fields' => [qw(rx_errors tx_errors)]
118
  },
119
  'if_wifi_sino' =>
120
  {
121
    'munin' =>
122
    {
123
      'category' => 'wifi',
124
      'args'     => '--base 1000 -u 0',
125
      'title'    => ':if: signal and noise levels',
126
      'vlabel'   => 'dB',
127
      'info'     => 'This graph shows the WiFi signal and noise levels of the :if:',
128
      'scale'    => 'no'
129
    },
130
    'per_if_fields'  => [qw(signal noise)],
131
    'general_fields' => [qw(signal)]
132
  },
133
  'if_wifi_link_quality' =>
134
  {
135
    'munin' =>
136
    {
137
      'category' => 'wifi',
138
      'args'     => '--base 1000',
139
      'title'    => ':if: link quality',
140
      'vlabel'   => '%',
141
      'info'     => 'This graph shows the WiFi link quality of the :if:',
142
      'scale'    => 'no'
143
    },
144
    'per_if_fields'  => [qw(link)],
145
    'general_fields' => [qw(link)]
146
  },
147
  'if_wifi_errors' =>
148
  {
149
    'munin' =>
150
    {
151
      'category' => 'wifi',
152
      'args'     => '--base 1000',
153
      'title'    => ':if: errors',
154
      'vlabel'   => 'Errors RX (-) / TX (+)',
155
      'info'     => 'This graph shows the WiFi errors of the :if: from last update',
156
      'scale'    => 'no'
157
    },
158
    'per_if_fields'  => [qw(nwid fragment crypt beacon retries misc)],
159
    'general_fields' => [qw(rx_wifierr tx_wifierr)]
160
  },
161
  'if_utilisation' =>
162
  {
163
    'munin' =>
164
    {
165
      'category' => 'network',
166
      'args'     => '--base 1000',
167
      'title'    => ':if: utilisation',
168
      'vlabel'   => '%',
169
      'info'     => 'This graph shows utilisation of the :if:',
170
      'scale'    => 'no'
171
    },
172
    'per_if_fields'  => [qw(rx_percent tx_percent)],
173
    'general_fields' => [qw(rx_percent tx_percent)]
174
  },
175
  'if_avgpacketsize' =>
176
  {
177
    'munin' =>
178
    {
179
      'category' => 'network',
180
      'args'     => '--base 1024',
181
      'title'    => ':if: avgerage packet size',
182
      'vlabel'   => 'bytes',
183
      'info'     => 'This graph shows average packet size of the :if:'
184
    },
185
    'per_if_fields'  => [qw(rx_size tx_size)],
186
    'general_fields' => [qw(rx_size tx_size)]
187
  }
188
};
189

    
190
#-------------------------- avialable fields -------------------------
191
# info:
192
# 'munin' => {} - just copy fields to munin config
193
# 'source' => - field data source
194
#    {
195
#      'type' =>  types:
196
#         'file' - data just cat from file
197
#           'location' => file location
198
#         'calculated' => calculated data
199
#         {
200
#            'type' - types:
201
#               'percent',
202
#                 'full' => 
203
#                 {
204
#                    'source' => 'interface',
205
#                    'name' => 'bps'
206
#                 },
207
#                 'part' => 
208
#                 {
209
#                   'source' => 'field',
210
#                   'name' => 'tx_bytes'
211
#                 }
212
#              'division',
213
#                'dividend' =>
214
#                {
215
#                  'source' => 'field',
216
#                  'name' => 'rx_bytes'
217
#                },
218
#                'divider' =>
219
#                {
220
#                  'source' => 'field',
221
#                  'name' => 'rx_packets'
222
#                }
223
#              'sum',
224
#                'sum' => [qw(nwid fragment crypt)]
225
#
226
#          }
227
#    }
228
# 'difference'    => difference types:
229
#     count      - just count from last update
230
#     per_secund - count from last update / time difference per last update
231
# 'negative' => - if field under zero line
232
#    {
233
#      'type' => types:
234
#        'dummy' - dummy field, not draw
235
#          'value' => '' - value for dummy field in update
236
#        'field' - exists field, must be included in graph
237
#          'name' => '' - field name
238
#    }
239
# 'peack_protect' => protect peaks. Using munin field.max and field.min and truncate data to NaN
240
#       protect types
241
#        'max_interface_bps'     - maximum: max interface bps (if configured), minimum - zero
242
#        'packet_size_range'     - maximum: mtu, minimum: minimal packet size (may be configured)
243
#        'max_interface_pps'     - maximum: max interface bps/minimum packt size (if configured), minimum - zero
244
#        'percents'              - maximum: 100, minimum: 0
245
#        'min_number:max_number' - no comments :)
246
my $fields =
247
{
248
  'collisions' =>
249
  {
250
    'munin' =>
251
    {
252
      'label' => 'Collisions'         ,
253
      'info'  => 'Transmit collisions',
254
      'type'  => 'GAUGE',
255
      'draw'  => 'LINE1'
256
    },
257
    'source' =>
258
    {
259
      'type'     => 'file',
260
      'location' => $ifpath.'/:if:/statistics/collisions'
261
    },
262
    'difference' => 'count'
263
  },
264
  # --------------------------------------------------------------------------
265
  'multicast' => 
266
  {
267
    'munin' =>
268
    {
269
     'type'  => 'GAUGE',
270
     'draw'  => 'LINE1',
271
     'label' => 'Multicast packets',
272
     'info'  => 'Multicast packets received'
273
    },
274
    'source' =>
275
    {
276
      'type'     => 'file',
277
      'location' => $ifpath.'/:if:/statistics/multicast'
278
    },
279
    'negative' =>
280
    {
281
      'type'  => 'dummy',
282
      'value' => 'NaN'
283
    },
284
    'difference'    => 'per_secund'
285
  },
286
  # --------------------------------------------------------------------------
287
  'rx_bytes' => 
288
  { 
289
    'munin' =>
290
    {
291
     'type'  => 'GAUGE',
292
     'draw'  => 'LINE1',
293
     'label' => 'RX bit',
294
     'info'  => 'RX bit'
295
    },
296
    'source' =>
297
    {
298
      'type'     => 'file',
299
      'location' => $ifpath.'/:if:/statistics/rx_bytes'
300
    },
301
    'cdef' => '8,*',
302
    'peack_protect' => 'max_interface_bps',
303
    'negative' =>
304
    {
305
      'type' => 'field',
306
      'name' => 'tx_bytes'
307
    },
308
    'difference' => 'per_secund'
309
  },
310
  # --------------------------------------------------------------------------
311
  'rx_compressed' =>
312
  {
313
    'munin' =>
314
    {
315
      'type'  => 'GAUGE',
316
      'draw'  => 'LINE1',
317
      'label' => 'RX compressed packets',
318
      'info'  => 'Compressed packets',
319
    },
320
    'source' =>
321
    {
322
      'type'     => 'file',
323
      'location' => $ifpath.'/:if:/statistics/rx_compressed'
324
    },
325
    'peack_protect' => 'max_interface_pps',
326
    'negative' =>
327
    {
328
      'type' => 'field',
329
      'name' => 'tx_compressed'
330
    },
331
    'difference' => 'per_secund'
332
  },
333
  # --------------------------------------------------------------------------
334
  'rx_crc_errors' =>
335
  {
336
    'munin' =>
337
    {
338
      'type'  => 'GAUGE', 
339
      'draw'  => 'LINE1', 
340
      'label' => 'CRC errors' , 
341
      'info'  => 'CRC errors'
342
    },
343
    'source' =>
344
    {
345
      'type'     => 'file',
346
      'location' => $ifpath.'/:if:/statistics/rx_crc_errors'
347
    },
348
    'negative' =>
349
    {
350
      'type'  => 'dummy',
351
      'value' => 'NaN'
352
    },
353
    'difference' => 'count'
354
  },
355
  # --------------------------------------------------------------------------
356
  'rx_dropped' =>
357
  {
358
    'munin' =>
359
    {
360
      'type'  => 'GAUGE',
361
      'draw'  => 'LINE1',
362
      'label' => 'RX dropped packets',
363
      'info'  => 'Dropped frames'
364
    },
365
    'source' =>
366
    {
367
      'type'     => 'file',
368
      'location' => $ifpath.'/:if:/statistics/rx_dropped'
369
    },
370
    'negative' =>
371
    {
372
      'type' => 'field',
373
      'name' => 'tx_dropped'
374
    },
375
    'difference' => 'per_secund'
376
  },
377
  # --------------------------------------------------------------------------
378
  'rx_errors' =>
379
  {
380
    'munin' =>
381
    {
382
      'type'  => 'GAUGE',
383
      'draw'  => 'LINE1',
384
      'label' => 'RX errors',
385
      'info'  => 'Bad packets'
386
    },
387
    'source' =>
388
    {
389
      'type'     => 'file',
390
      'location' => $ifpath.'/:if:/statistics/rx_errors'
391
    },
392
    'negative' =>
393
    {
394
      'type' => 'field',
395
      'name' => 'tx_errors'
396
    },
397
    'difference' => 'count'
398
  },
399
  # --------------------------------------------------------------------------
400
  'rx_fifo_errors' =>
401
  {
402
    'munin' =>
403
    {
404
      'type'  => 'GAUGE',
405
      'draw'  => 'LINE1',
406
      'label' => 'RX FIFO errors',
407
      'info'  => 'FIFO overrun errors'
408
    },
409
    'source' =>
410
    {
411
      'type'     => 'file',
412
      'location' => $ifpath.'/:if:/statistics/rx_fifo_errors'
413
    },
414
    'negative' =>
415
    {
416
      'type' => 'field',
417
      'name' => 'tx_fifo_errors'
418
    },
419
    'difference' => 'count'
420
  },
421
  # --------------------------------------------------------------------------
422
  'rx_frame_errors' =>
423
  {
424
    'munin' =>
425
    {
426
      'type'  => 'GAUGE',
427
      'draw'  => 'LINE1',
428
      'label' => 'Frame format errors',
429
      'info'  => 'Frame format errors'
430
    },
431
    'source' =>
432
    {
433
      'type'     => 'file',
434
      'location' => $ifpath.'/:if:/statistics/rx_frame_errors'
435
    },
436
    'negative' =>
437
    {
438
      'type'  => 'dummy',
439
      'value' => 'NaN'
440
    },
441
    'difference' => 'count'
442
  },
443
  # --------------------------------------------------------------------------
444
  'rx_length_errors' =>
445
  {
446
    'munin' =>
447
    {
448
      'type'  => 'GAUGE',
449
      'draw'  => 'LINE1',
450
      'label' => 'Length errors',
451
      'info'  => 'Length errors'
452
    },
453
    'source' =>
454
    {
455
      'type'     => 'file',
456
      'location' => $ifpath.'/:if:/statistics/rx_length_errors'
457
    },
458
    'negative' =>
459
    {
460
      'type'  => 'dummy',
461
      'value' => 'NaN'
462
    },
463
    'difference' => 'count'
464
  },
465
  # --------------------------------------------------------------------------
466
  'rx_missed_errors' =>
467
  {
468
    'munin' =>
469
    {
470
      'type'  => 'GAUGE',
471
      'draw'  => 'LINE1',
472
      'label' => 'Missed packetss',
473
      'info'  => 'Missed packets'
474
    },
475
    'source' =>
476
    {
477
      'type'     => 'file',
478
      'location' => $ifpath.'/:if:/statistics/rx_missed_errors'
479
    },
480
    'negative' =>
481
    {
482
      'type'  => 'dummy',
483
      'value' => 'NaN'
484
    },
485
    'difference' => 'count'
486
  },
487
  # --------------------------------------------------------------------------
488
  'rx_over_errors' =>
489
  {
490
    'munin' =>
491
    {
492
      'type'  => 'GAUGE',
493
      'draw'  => 'LINE1',
494
      'label' => 'Overrun errors',
495
      'info'  => 'Overrun errors'
496
    },
497
    'source' =>
498
    {
499
      'type'     => 'file',
500
      'location' => $ifpath.'/:if:/statistics/rx_over_errors'
501
    },
502
    'negative' =>
503
    {
504
      'type'  => 'dummy',
505
      'value' => 'NaN'
506
    },
507
    'difference' => 'count'
508
  },
509
  # --------------------------------------------------------------------------
510
  'rx_packets' =>
511
  {
512
    'munin' =>
513
    {
514
      'type'  => 'GAUGE',
515
      'draw'  => 'LINE1',
516
      'label' => 'RX packets',
517
      'info'  => 'RX packets'
518
    },
519
    'source' =>
520
    {
521
      'type'     => 'file',
522
      'location' => $ifpath.'/:if:/statistics/rx_packets'
523
    },
524
    'peack_protect' => 'max_interface_pps',
525
    'negative' =>
526
    {
527
      'type' => 'field',
528
      'name' => 'tx_packets'
529
    },
530
    'difference' => 'per_secund'
531
  },
532
  # --------------------------------------------------------------------------
533
  'tx_aborted_errors' =>
534
  {
535
    'munin' =>
536
    {
537
      'type'  => 'GAUGE',
538
      'draw'  => 'LINE1',
539
      'label' => 'Aborted frames',
540
      'info'  => 'Aborted frames'
541
    },
542
    'source' =>
543
    {
544
      'type'     => 'file',
545
      'location' => $ifpath.'/:if:/statistics/tx_aborted_errors'
546
    },
547
    'difference' => 'count'
548
  },
549
  # --------------------------------------------------------------------------
550
  'tx_bytes' =>
551
  {
552
    'munin' =>
553
    {
554
      'type'  => 'GAUGE',
555
      'draw'  => 'LINE1',
556
      'label' => 'TX bit',
557
      'info'  => 'TX bit'
558
    },
559
    'source' =>
560
    {
561
      'type' => 'file',
562
      'location' => $ifpath.'/:if:/statistics/tx_bytes'
563
    },
564
    'cdef' => '8,*',
565
    'peack_protect'  => 'max_interface_bps',
566
    'difference'     => 'per_secund'
567
  },
568
  # --------------------------------------------------------------------------
569
  'tx_carrier_errors' =>
570
  {
571
    'munin' =>
572
    {
573
      'type'  => 'GAUGE',
574
      'draw'  => 'LINE1',
575
      'label' => 'Carrier errors',
576
      'info'  => 'Carrier errors'
577
    },
578
    'source' =>
579
    {
580
      'type'     => 'file',
581
      'location' => $ifpath.'/:if:/statistics/tx_carrier_errors'
582
    },
583
    'difference' => 'count'
584
  },
585
  # --------------------------------------------------------------------------
586
  'tx_compressed' =>
587
  {
588
    'munin' =>
589
    {
590
      'type'  => 'GAUGE',
591
      'draw'  => 'LINE1',
592
      'label' => 'TX compressed packets',
593
      'info'  => 'Compressed packets'
594
    },
595
    'source' =>
596
    {
597
      'type'     => 'file',
598
      'location' => $ifpath.'/:if:/statistics/tx_compressed'
599
    },
600
    'peack_protect' => 'max_interface_pps',
601
    'difference'    => 'per_secund'
602
  },
603
  # --------------------------------------------------------------------------
604
  'tx_dropped' =>
605
  {
606
    'munin' =>
607
    {
608
      'type'  => 'GAUGE',
609
      'draw'  => 'LINE1',
610
      'label' => 'TX dropped packets',
611
      'info'  => 'Dropped frames'
612
    },
613
    'source' =>
614
    {
615
      'type'     => 'file',
616
      'location' => $ifpath.'/:if:/statistics/tx_dropped'
617
    },
618
    'difference' => 'per_secund'
619
  },
620
  # --------------------------------------------------------------------------
621
  'tx_errors' =>
622
  {
623
    'munin' =>
624
    {
625
      'type'  => 'GAUGE',
626
      'draw'  => 'LINE1',
627
      'label' => 'TX errors',
628
      'info'  => 'Transmit problems'
629
    },
630
    'source' =>
631
    {
632
      'type'     => 'file',
633
      'location' => $ifpath.'/:if:/statistics/tx_errors'
634
    },
635
    'difference' => 'count'
636
  },
637
  # --------------------------------------------------------------------------
638
  'tx_fifo_errors' =>
639
  {
640
    'munin' =>
641
    {
642
      'type'  => 'GAUGE',
643
      'draw'  => 'LINE1',
644
      'label' => 'TX FIFO errors',
645
      'info'  => 'FIFO errors'
646
    },
647
    'source' =>
648
    {
649
      'type'     => 'file',
650
      'location' => $ifpath.'/:if:/statistics/tx_fifo_errors'
651
    },
652
    'difference' => 'count'
653
  },
654
  # --------------------------------------------------------------------------
655
  'tx_heartbeat_errors' =>
656
  {
657
    'munin' =>
658
    {
659
      'type'  => 'GAUGE',
660
      'draw'  => 'LINE1',
661
      'label' => 'Heartbeat errors',
662
      'info'  => 'Heartbeat errors'
663
    },
664
    'source' =>
665
    {
666
      'type'     => 'file',
667
      'location' => $ifpath.'/:if:/statistics/tx_heartbeat_errors'
668
    },
669
    'difference' => 'count'
670
  },
671
  # --------------------------------------------------------------------------
672
  'tx_packets' =>
673
  {
674
    'munin' =>
675
    {
676
      'type'  => 'GAUGE',
677
      'draw'  => 'LINE1',
678
      'label' => 'TX packets',
679
      'info'  => 'TX packets'
680
    },
681
    'source' =>
682
    {
683
      'type'     => 'file',
684
      'location' => $ifpath.'/:if:/statistics/tx_packets'
685
    },
686
    'peack_protect' => 'max_interface_pps',
687
    'difference'    => 'per_secund'
688
  },
689
  # --------------------------------------------------------------------------
690
  'tx_window_errors' =>
691
  {
692
    'munin' =>
693
    {
694
      'type'  => 'GAUGE',
695
      'draw'  => 'LINE1',
696
      'label' => 'Window errors',
697
      'info'  => 'Window errors'
698
    },
699
    'source' =>
700
    {
701
      'type'     => 'file',
702
      'location' => $ifpath.'/:if:/statistics/tx_window_errors'
703
    },
704
    'difference' => 'count'
705
  },
706
  # --------------------------------------------------------------------------
707
  'signal' =>
708
  {
709
    'munin' =>
710
    {
711
      'type'  => 'GAUGE',
712
      'draw'  => 'LINE1',
713
      'label' => 'Signal level',
714
      'info'  => 'WiFi signal level'
715
    },
716
    'source' =>
717
    {
718
      'type'     => 'file',
719
      'location' => $ifpath.'/:if:/wireless/level',
720
      'prepare'  => ':data:=:data:-256'
721
    },
722
#    'cdef'  => '-256,+',
723
    'peack_protect'  => '-256:0'
724
  },
725
  # --------------------------------------------------------------------------
726
  'noise' =>
727
  {
728
    'munin' =>
729
    {
730
      'type'  => 'GAUGE',
731
      'draw'  => 'LINE1',
732
      'label' => 'Noise level',
733
      'info'  => 'WiFi noise level'
734
    },
735
    'source' =>
736
    {
737
      'type'     => 'file',
738
      'location' => $ifpath.'/:if:/wireless/noise',
739
      'prepare'  => ':data:=:data:-256'
740
    },
741
#    'cdef'  => '-256,+',
742
    'peack_protect' => '-256:0'
743
  },
744
  # --------------------------------------------------------------------------
745
  'link' =>
746
  {
747
    'munin' =>
748
    {
749
      'type'  => 'GAUGE',
750
      'draw'  => 'LINE1',
751
      'label' => 'Signal quality',
752
      'info'  => 'WiFi signal quality'
753
    },
754
    'source' =>
755
    {
756
      'type'     => 'file',
757
      'location' => $ifpath.'/:if:/wireless/link'
758
    },
759
    'peack_protect' => 'percent'
760
  },
761
  # --------------------------------------------------------------------------
762
  'rx_percent' =>
763
  {
764
    'munin' =>
765
    {
766
      'type'  => 'GAUGE',
767
      'draw'  => 'LINE1',
768
      'label' => 'RX Utilisation',
769
      'info'  => 'RX utilisation'
770
    },
771
    'source' =>
772
    {
773
      'type' => 'calculated',
774
      'calculated' =>
775
      {
776
        'type' => 'percent',
777
        'full' => 
778
        {
779
          'source' => 'interface',
780
          'name'   => 'bps'
781
        },
782
        'part' => 
783
        {
784
          'source' => 'field',
785
          'name'   => 'rx_bytes'
786
        }
787
      }
788
    },
789
    'peack_protect' => 'percent',
790
    'negative' =>
791
    {
792
      'type' => 'field',
793
      'name' => 'tx_percent'
794
    }
795
  },
796
  # --------------------------------------------------------------------------
797
  'tx_percent' =>
798
  {
799
    'munin' =>
800
    {
801
      'type'  => 'GAUGE',
802
      'draw'  => 'LINE1',
803
      'label' => 'TX Utilisation',
804
      'info'  => 'TX utilisation'
805
    },
806
    'source' =>
807
    {
808
      'type' => 'calculated',
809
      'calculated' =>
810
      {
811
        'type' => 'percent',
812
        'full' => 
813
        {
814
          'source' => 'interface',
815
          'name'   => 'bps'
816
        },
817
        'part' => 
818
        {
819
          'source' => 'field',
820
          'name'   => 'tx_bytes'
821
        }
822
      }
823
    },
824
    'peack_protect' => 'percent'
825
  },
826
  # --------------------------------------------------------------------------
827
  'rx_size' =>
828
  {
829
    'munin' =>
830
    {
831
      'type'  => 'GAUGE',
832
      'draw'  => 'LINE1',
833
      'label' => 'RX packet size',
834
      'info'  => 'Average RX packet size'
835
    },
836
    'source' =>
837
    {
838
      'type' => 'calculated',
839
      'calculated' =>
840
      {
841
        'type' => 'division',
842
        'dividend' =>
843
        {
844
          'source' => 'field',
845
          'name'   => 'rx_bytes'
846
        },
847
        'divider' =>
848
        {
849
          'source' => 'field',
850
          'name'   => 'rx_packets'
851
        }
852
      }
853
    },
854
    'peack_protect' => 'packet_size_range',
855
    'negative' =>
856
    {
857
      'type' => 'field',
858
      'name' => 'tx_size'
859
    }
860
  },
861
  # --------------------------------------------------------------------------
862
  'tx_size' =>
863
  {
864
    'munin' =>
865
    {
866
      'type'  => 'GAUGE',
867
      'draw'  => 'LINE1',
868
      'label' => 'TX packet size',
869
      'info'  => 'Average TX packet size'
870
    },
871
    'source' =>
872
    {
873
      'type' => 'calculated',
874
      'calculated' =>
875
      {
876
        'type' => 'division',
877
        'dividend' =>
878
        {
879
          'source' => 'field',
880
          'name'   => 'tx_bytes'
881
        },
882
        'divider' =>
883
        {
884
          'source' => 'field',
885
          'name'   => 'tx_packets'
886
        }
887
      }
888
    },
889
    'peack_protect' => 'packet_size_range'
890
  },
891
  # --------------------------------------------------------------------------
892
  'retries' => 
893
  {
894
    'munin' =>
895
    {
896
      'type'  => 'GAUGE',
897
      'draw'  => 'LINE1',
898
      'label' => 'Max. retries reached',
899
      'info'  => 'Max MAC retries num reached'
900
    },
901
    'source' =>
902
    {
903
      'type'     => 'file',
904
      'location' => $ifpath.'/:if:/wireless/retries'
905
    },
906
    'difference' => 'count'
907
  },
908
  # --------------------------------------------------------------------------
909
  'nwid' => 
910
  {
911
    'munin' =>
912
    {
913
      'type'  => 'GAUGE',
914
      'draw'  => 'LINE1',
915
      'label' => 'Wrong nwid/essid',
916
      'info'  => 'Wrong nwid/essid'
917
    },
918
    'source' =>
919
    {
920
      'type'     => 'file',
921
      'location' => $ifpath.'/:if:/wireless/nwid'
922
    },
923
    'negative' =>
924
    {
925
      'type'  => 'dummy',
926
      'value' => 'NaN'
927
    },
928
    'difference' => 'count'
929
  },
930
  # --------------------------------------------------------------------------
931
  'misc' => 
932
  {
933
    'munin' =>
934
    {
935
      'type'  => 'GAUGE',
936
      'draw'  => 'LINE1',
937
      'label' => 'Other',
938
      'info'  => 'Others cases'
939
    },
940
    'source' =>
941
    {
942
      'type'     => 'file',
943
      'location' => $ifpath.'/:if:/wireless/misc'
944
    },
945
    'difference' => 'count'
946
  },
947
  # --------------------------------------------------------------------------
948
  'fragment' => 
949
  {
950
    'munin' =>
951
    {
952
      'type'  => 'GAUGE',
953
      'draw'  => 'LINE1',
954
      'label' => 'MAC reassemby',
955
      'info'  => 'Can\'t perform MAC reassembly'
956
    },
957
    'source' =>
958
    {
959
      'type'     => 'file',
960
      'location' => $ifpath.'/:if:/wireless/fragment'
961
    },
962
    'negative' =>
963
    {
964
      'type'  => 'dummy',
965
      'value' => 'NaN'
966
    },
967
    'difference' => 'count'
968
  },
969
  # --------------------------------------------------------------------------
970
  'beacon' => 
971
  {
972
    'munin' =>
973
    {
974
      'type'  => 'GAUGE',
975
      'draw'  => 'LINE1',
976
      'label' => 'Missed beacons',
977
      'info'  => 'Missed beacons/superframe'
978
    },
979
    'source' =>
980
    {
981
      'type'     => 'file',
982
      'location' => $ifpath.'/:if:/wireless/beacon'
983
    },
984
    'difference' => 'count'
985
  },
986
  # --------------------------------------------------------------------------
987
  'crypt' => 
988
  {
989
    'munin' =>
990
    {
991
      'type'  => 'GAUGE',
992
      'draw'  => 'LINE1',
993
      'label' => 'Code/decode',
994
      'info'  => 'Unable to code/decode (WEP)'
995
    },
996
    'source' =>
997
    {
998
      'type'     => 'file',
999
      'location' => $ifpath.'/:if:/wireless/crypt'
1000
    },
1001
    'negative' =>
1002
    {
1003
      'type'  => 'dummy',
1004
      'value' => 'NaN'
1005
    },
1006
    'difference' => 'count'
1007
  },
1008
  # --------------------------------------------------------------------------
1009
  'rx_wifierr' => 
1010
  {
1011
    'munin' =>
1012
    {
1013
      'type'  => 'GAUGE',
1014
      'draw'  => 'LINE1',
1015
      'label' => 'RX errors',
1016
      'info'  => 'Total RX Wifi Errors'
1017
    },
1018
    'source' =>
1019
    {
1020
      'type' => 'calculated',
1021
      'calculated' =>
1022
      {
1023
        'type' => 'sum',
1024
        'sum'  => [qw(nwid fragment crypt)]
1025
      }
1026
    },
1027
    'negative' =>
1028
    {
1029
      'type' => 'field',
1030
      'name' => 'tx_wifierr'
1031
    }
1032
  },
1033
  # --------------------------------------------------------------------------
1034
  'tx_wifierr' => 
1035
  {
1036
    'munin' =>
1037
    {
1038
      'type'  => 'GAUGE',
1039
      'draw'  => 'LINE1',
1040
      'label' => 'TX errors',
1041
      'info'  => 'Total TX Wifi errors'
1042
    },
1043
    'source' =>
1044
    {
1045
      'type' => 'calculated',
1046
      'calculated' =>
1047
      {
1048
        'type' => 'sum',
1049
        'sum'  => [qw(misc beacon retries)]
1050
      }
1051
    }
1052
  }
1053
};
1054

    
1055
# ----------------- main ----------------
1056

    
1057
need_multigraph();
1058

    
1059
if (defined($ARGV[0]) and ($ARGV[0] eq 'autoconf')) 
1060
{
1061
  printf("%s\n", -e $ifpath ? "yes" : "no ($ifpath not exists)");
1062
  exit (0);
1063
}
1064
$interfaces = get_interfaces();
1065
if (defined($ARGV[0]) and ($ARGV[0] eq 'config')) 
1066
{
1067
  print_config();
1068
  exit (0);
1069
}
1070
print_values();
1071
exit(0);
1072

    
1073

    
1074
# ====================================== both config and values ===========================
1075
# ---------------  read sysfs file (one file - one value) --------------
1076
sub get_file_content
1077
{
1078
  my $file = $_[0];
1079
  return 'NaN' if (-z $file);
1080
  open (FH, '<', $file) or die "$! $file \n";
1081
  my $content = <FH>;
1082
  close (FH);
1083
  chomp $content;
1084
  #print "$content\n";
1085
  return trim($content);
1086
}
1087

    
1088
# ------------------ build interface list and his options -------------------------
1089
sub get_interfaces
1090
{
1091
  my $interfaces;
1092
  my $ifdir = IO::Dir->new($ifpath);
1093
  if(defined $ifdir)
1094
  {
1095
    my $if;
1096
    while (defined ($if = $ifdir->read))
1097
    {
1098
      next if $if =~ m/\./;
1099
      unless($if =~ m/$include/)
1100
      {
1101
       next unless get_file_content(sprintf("%s/%s/operstate", $ifpath, $if)) =~ m/(up|unknown)/;
1102
       next if $exclude =~ m/$if/;
1103
      }
1104
      my $mtufile = sprintf("%s/%s/mtu", $ifpath, $if);
1105
      if(-e $mtufile)
1106
      {
1107
        $interfaces->{$if}{'mtu'} = get_file_content($mtufile);
1108
      }
1109
      my $bps = $ENV{"${if}_max_bps"} || undef;
1110
      if(defined($bps))
1111
      {
1112
        my ($num, $suff) = $bps =~ /(\d+)(\w)/;
1113
        if   ($suff eq 'k') { $bps = $num * 1000 / 8; }
1114
        elsif($suff eq 'M') { $bps = $num * 1000 * 1000 / 8; }
1115
        elsif($suff eq 'G') { $bps = $num * 1000 * 1000 * 1000 / 8; }
1116
        $interfaces->{$if}{'bps'} = $bps;
1117
      }
1118
      if (-e sprintf("/sys/devices/virtual/net/%s", $if)) { $interfaces->{$if}{'virtual'} = 1; }
1119
      $interfaces->{$if}{'name'} = exists($interfaces->{$if}{'virtual'}) ? sprintf("~%s", $if) : $if;
1120
    }
1121
    my ($maxlen, $tl) = (0, 0);
1122
    for (keys %{$interfaces}) { $tl = length($interfaces->{$_}{'name'}); $maxlen = $tl if $tl > $maxlen;  }
1123
    for (keys %{$interfaces}) { $interfaces->{$_}{'name'} = sprintf("[%${maxlen}s]", $interfaces->{$_}{'name'}); }
1124
  }
1125
  else { die "$ifpath not exists\n"; }
1126
  
1127
  return $interfaces;
1128
}
1129

    
1130
# ----------------------- trim whitespace at begin and end of string ------------
1131
sub trim
1132
{
1133
  my($string)=@_;
1134
  for ($string) { s/^\s+//; s/\s+$//; }
1135
  return $string;
1136
}
1137

    
1138
# ------------------------ replacing :if: from strings to need value ----------------------
1139
sub replace_if_template
1140
{
1141
  my ($string, $replacement) = @_[0..1];
1142
  $string =~ s/:if:/$replacement/g;
1143
  return $string;
1144
}
1145

    
1146
# --------------------------- calculating range values for peack_protect --------------------------
1147
sub get_peak_range
1148
{
1149
  my ($field, $if) = @_[0..1];
1150
  my $range = {};
1151
  return $range unless defined($fields->{$field}{'peack_protect'});
1152
  # percent
1153
  if ($fields->{$field}{'peack_protect'} eq 'percent')
1154
  {
1155
    $range->{'max'} = 100;
1156
    $range->{'min'} = 0;
1157
  }
1158
  # numbers
1159
  elsif ($fields->{$field}{'peack_protect'} =~ m/[-\d]+:[-\d]+/)
1160
  {
1161
    my @r = split(/:/, $fields->{$field}{'peack_protect'});
1162
    $range->{'max'} = $r[1];
1163
    $range->{'min'} = $r[0];
1164
  }
1165
  # bytes per sec
1166
  elsif($fields->{$field}{'peack_protect'} eq 'max_interface_bps' and defined ($interfaces->{$if}{'bps'}))
1167
  {
1168
    $range->{'max'} = $interfaces->{$if}{'bps'};
1169
    $range->{'min'} = 0;
1170
  }
1171
  # packets per sec 
1172
  elsif($fields->{$field}{'peack_protect'} eq 'max_interface_pps' and defined ($interfaces->{$if}{'bps'}))
1173
  {
1174
    $range->{'max'} = $interfaces->{$if}{'bps'}/$min_packet_size;
1175
    $range->{'min'} = 0;
1176
  }
1177
  # packets size range
1178
  elsif($fields->{$field}{'peack_protect'} eq 'packet_size_range' and defined ($interfaces->{$if}{'mtu'}))
1179
  {
1180
    $range->{'max'} = $interfaces->{$if}{'mtu'};
1181
    $range->{'min'} = $min_packet_size;
1182
  }
1183
  return $range;
1184
}
1185

    
1186

    
1187
# ----------------------------- checking avialability of fields -------------------------
1188
sub check_field_avialability
1189
{
1190
  my ($if, $field) = @_[0..1];
1191
  unless(exists($fields->{$field}{'avialable'}{$if}))
1192
  {
1193
    # -------------------- file ----------------
1194
    if($fields->{$field}{'source'}{'type'} eq 'file')
1195
    {
1196
      my $file = $fields->{$field}{'source'}{'location'};
1197
      $file =~ s/:if:/$if/g;
1198
      $fields->{$field}{'avialable'}{$if} = 1 if (-e $file);
1199
    }
1200
    #---------------------------- calculated ----------------
1201
    elsif ($fields->{$field}{'source'}{'type'} eq 'calculated')
1202
    {
1203
      #------------------------------ percent ------------------------
1204
      if($fields->{$field}{'source'}{'calculated'}{'type'} eq 'percent')
1205
      {
1206
        my %avialable;
1207
        for my $a ('full', 'part')
1208
        {
1209
          if($fields->{$field}{'source'}{'calculated'}{$a}{'source'} eq 'interface')
1210
          {
1211
            $avialable{$a} = exists($interfaces->{$if}{$fields->{$field}{'source'}{'calculated'}{$a}{'name'}});
1212
          }
1213
          elsif($fields->{$field}{'source'}{'calculated'}{$a}{'source'} eq 'field')
1214
          {
1215
            $avialable{$a} = check_field_avialability($if, $fields->{$field}{'source'}{'calculated'}{$a}{'name'});
1216
          }
1217
        }
1218
        $fields->{$field}{'avialable'}{$if} = ($avialable{'full'} and $avialable{'part'});
1219
      }
1220
      #------------------------------ division ------------------------
1221
      elsif($fields->{$field}{'source'}{'calculated'}{'type'} eq 'division')
1222
      {
1223
        my %avialable;
1224
        for my $a ('dividend', 'divider')
1225
        {
1226
          if($fields->{$field}{'source'}{'calculated'}{$a}{'source'} eq 'interface')
1227
          {
1228
            $avialable{$a} = exists($interfaces->{$if}{$fields->{$field}{'source'}{'calculated'}{$a}{'name'}});
1229
          }
1230
          elsif($fields->{$field}{'source'}{'calculated'}{$a}{'source'} eq 'field')
1231
          {
1232
            $avialable{$a} = check_field_avialability($if, $fields->{$field}{'source'}{'calculated'}{$a}{'name'});
1233
          }
1234
        }
1235
        $fields->{$field}{'avialable'}{$if} = ($avialable{'dividend'} and $avialable{'divider'});
1236
      }
1237
      #------------------------------ sum ------------------------
1238
      elsif($fields->{$field}{'source'}{'calculated'}{'type'} eq 'sum')
1239
      {
1240
        my $count = 0;
1241
        for my $a (@{$fields->{$field}{'source'}{'calculated'}{'sum'}})
1242
        {
1243
          $count++ if (check_field_avialability($if, $a));
1244
        }
1245
        $fields->{$field}{'avialable'}{$if} = ($count == scalar(@{$fields->{$field}{'source'}{'calculated'}{'sum'}}));
1246
      }
1247
    }
1248
  }
1249
  return $fields->{$field}{'avialable'}{$if};
1250
}
1251

    
1252

    
1253
# ==================================  config-only ==============================
1254
# --------------- concatenate field names ------------------
1255
sub concat_names
1256
{
1257
  my ($f1, $f2, $if) = @_[0..2];
1258
  my $name = $f1;
1259
  if ($f1 ne $f2)
1260
  {
1261
    my @a = split(' ', $f1);
1262
    my @b = split(' ', $f2);
1263
    my ($t, $ra, $rb) = ('','','');
1264
    for (my $i = scalar(@a) - 1; $i >= 0; $i--)
1265
    {
1266
      #printf("%s %s\n", $a[$i], $b[$i]);
1267
      if   ($a[$i] eq $b[$i]) { $t = sprintf("%s %s", $a[$i], $t); }
1268
      else { $ra = sprintf("%s %s", $a[$i], $ra); $rb = sprintf("%s %s", $b[$i], $rb); }
1269
    }
1270
    $name = trim(sprintf("%s/%s %s", trim($ra), trim($rb), trim($t)));
1271
  }
1272
  if (exists($interfaces->{$if}))
1273
  {
1274
    $name = sprintf ("%s %s", $interfaces->{$if}{'name'}, $name);
1275
  }
1276
  return $name;
1277
}
1278

    
1279
# --------------------------- generating graph field ----------------------
1280
sub generate_field
1281
{
1282
  my ($config, $graph_name, $field, $if, $is_general_graph) = @_[0..4];
1283
  return '' unless(check_field_avialability($if, $field));
1284
  my $field_graph_name = $is_general_graph ? sprintf("%s_%s", $if, $field) : $field;
1285
  for my $option (keys %{$fields->{$field}{'munin'}}) 
1286
  {
1287
    next if exists($config->{$graph_name}{'fields'}{$field_graph_name}{$option});
1288
    $config->{$graph_name}{'fields'}{$field_graph_name}{$option} = replace_if_template($fields->{$field}{'munin'}{$option}, $interfaces->{$if}{'name'}); 
1289
  }
1290
  if(exists($fields->{$field}{'cdef'}))
1291
  {
1292
    $config->{$graph_name}{'fields'}{$field_graph_name}{'cdef'} = sprintf("%s,%s", $field_graph_name, $fields->{$field}{'cdef'});
1293
  }
1294
  if(exists($fields->{$field}{'negative'}))
1295
  {
1296
    my ($up_field_name, $down_field_name) = ('', $field_graph_name);
1297
    my ($up_field, $down_field) = ('', $field);
1298
    if($fields->{$down_field}{'negative'}{'type'} eq 'field')
1299
    {
1300
      $up_field = $fields->{$down_field}{'negative'}{'name'};
1301
      $up_field_name = $is_general_graph ? sprintf("%s_%s", $if, $up_field) : $up_field;
1302
      $config->{$graph_name}{'fields'}{$up_field_name}{'label'} = 
1303
        concat_names($fields->{$down_field}{'munin'}{'label'}, $fields->{$up_field}{'munin'}{'label'}, $is_general_graph ? $if : '');
1304
    }
1305
    elsif($fields->{$down_field}{'negative'}{'type'} eq 'dummy')
1306
    {
1307
      $up_field_name = $is_general_graph ? sprintf("%s_%s_dummy", $if, $down_field) : sprintf("%s_dummy", $down_field);
1308
      $config->{$graph_name}{'fields'}{$up_field_name}{'label'} = 
1309
        concat_names($fields->{$down_field}{'munin'}{'label'}, $fields->{$down_field}{'munin'}{'label'}, $is_general_graph ? $if : '');
1310
      $config->{$graph_name}{'fields'}{$up_field_name}{'info'} = $fields->{$down_field}{'munin'}{'info'};
1311
    }
1312
    $config->{$graph_name}{'fields'}{$up_field_name}{'negative'} = $down_field_name;
1313
    $config->{$graph_name}{'fields'}{$down_field_name}{'graph'} = 'no';
1314
    $config->{$graph_name}{'fields'}{$down_field_name}{'label'} = 'none';
1315
  }
1316
  # Fix field label on general graphs
1317
  if ($is_general_graph and not $config->{$graph_name}{'fields'}{$field_graph_name}{'label'} =~ m/$if/)
1318
  {
1319
    $config->{$graph_name}{'fields'}{$field_graph_name}{'label'} = sprintf ("%s %s", $interfaces->{$if}{'name'}, $fields->{$field}{'munin'}{'label'});
1320
  }
1321
  # do peaks protect
1322
  if($protect_peacks ne 'no' and exists($fields->{$field}{'peack_protect'}))
1323
  {
1324
    my $range = get_peak_range($field, $if);
1325
    for my $a (qw(min max))
1326
    {
1327
      if (exists($range->{$a}))
1328
      {
1329
        $config->{$graph_name}{'fields'}{$field_graph_name}{$a} = $range->{$a};
1330
      }
1331
    }
1332
  }
1333
  return $field_graph_name;
1334
}
1335

    
1336
# ------------------------------- generating graph ----------------------------
1337
sub generate_graph
1338
{
1339
  my ($config, $graph, $if, $is_general_graph) = @_[0..4];
1340
  my @order = ();
1341
  my $graph_name = $is_general_graph ? $graph : sprintf("%s.%s", $graph, $if);
1342
  if($is_general_graph)
1343
  {
1344
    for my $field (@{$graphs->{$graph}{'general_fields'}})
1345
    {
1346
      for my $general_if (keys %{$interfaces}) 
1347
      {
1348
        my $res_field = generate_field($config, $graph_name, $field, $general_if, 1);
1349
        push(@order, $res_field) if $res_field ne '';
1350
      }
1351
    }
1352
  }
1353
  else
1354
  {
1355
    for my $field (@{$graphs->{$graph}{'per_if_fields'}})
1356
    {
1357
      my $res_field = generate_field($config, $graph_name, $field, $if, 0);
1358
      push(@order, $res_field) if $res_field ne '';
1359
    }
1360
  }
1361
  if(scalar(@order) > 0)
1362
  {
1363
    for my $option (keys %{$graphs->{$graph}{'munin'}}) 
1364
    {
1365
      $config->{$graph_name}{'graph'}{$option} = replace_if_template($graphs->{$graph}{'munin'}{$option}, $is_general_graph ? 'All interfaces' : $interfaces->{$if}{'name'}); 
1366
    }
1367
    $config->{$graph_name}{'graph'}{'order'} = join(' ', @order); # if scalar(@order) > 1;
1368
    unless($is_general_graph)
1369
    {
1370
      $config->{$graph_name}{'graph'}{'category'} = $if;
1371
    }
1372
  }
1373
}
1374

    
1375
# ------------------------ generate general and per-interface graphs ------------------------------
1376
sub generate_graphs
1377
{
1378
  my ($config, $graph) = @_[0..1];
1379
  generate_graph($config, $graph, '', 1);
1380
  for my $if (keys %{$interfaces}) 
1381
  {
1382
    generate_graph($config, $graph, $if, 0);
1383
  }
1384
}
1385

    
1386
# ---------------------------------------------------------- config ------------------------------------------------------
1387
sub print_config
1388
{
1389
  my $config = {};
1390
  my $graph;
1391
  for $graph (keys %{$graphs}) { generate_graphs($config, $graph); }
1392
  #-------------------- print ---------------
1393
  for $graph (sort  keys %{$config})
1394
  {
1395
    printf ("multigraph %s\n", $graph);
1396
    for my $option (sort keys %{$config->{$graph}{'graph'}})
1397
    {
1398
      printf ("graph_%s %s\n", $option, $config->{$graph}{'graph'}{$option});
1399
    }
1400
    for my $field (sort keys %{$config->{$graph}{'fields'}})
1401
    {
1402
      for my $type (sort keys %{$config->{$graph}{'fields'}{$field}})
1403
      {
1404
        printf ("%s.%s %s\n", $field, $type, $config->{$graph}{'fields'}{$field}{$type});
1405
      }
1406
    }
1407
    print "\n";
1408
  }
1409
}
1410

    
1411
# ===========================================  values ==========================================================
1412
# ------------------------------- calculate percent --------------------------
1413
sub percent
1414
{
1415
  my ($full, $current) = @_[0..1];
1416
  return $current/($full/100);
1417
}
1418

    
1419
# ----------------------------------- saving state data using munin --------------------
1420
sub save_state_data
1421
{
1422
  my $data = $_[0];
1423
  my $d = Data::Dumper->new([$data]);
1424
  $d->Indent(0);
1425
  save_state($d->Dump);
1426
}
1427

    
1428
# -------------------------------- loading previous state data using munin -------------------
1429
sub restore_state_data
1430
{
1431
  my $VAR1;
1432
  my $states = (restore_state())[0];
1433
  eval $states if defined $states;
1434
  return $VAR1;
1435
}
1436

    
1437
# -------------------- protect field data from under zero value (for example prev tx_bytes = 10000, interface reset, current tx_bytes = 100, 100-1000=-900)
1438
sub underzero_protect
1439
{
1440
  my ($a, $b) = @_[0..1];
1441
  return $a > $b ? $b : $b - $a;
1442
}
1443

    
1444
# ------------------- calculating difference from last stored data ---------------------------------
1445
sub calc_diff
1446
{
1447
  my ($raw_data, $raw_prev_data, $if, $field) = @_[0..4];
1448
  return $raw_data->{$if}{$field} unless (exists($fields->{$field}{'difference'}) and defined($raw_prev_data));
1449
  if    ($fields->{$field}{'difference'} eq 'count'     ) { return underzero_protect($raw_prev_data->{$if}{$field}, $raw_data->{$if}{$field}); }
1450
  elsif ($fields->{$field}{'difference'} eq 'per_secund') { return underzero_protect($raw_prev_data->{$if}{$field}, $raw_data->{$if}{$field}) / ($raw_data->{'timestamp'} - $raw_prev_data->{'timestamp'}); }
1451
}
1452

    
1453
# ---------------------- protecting values from peaks ------------------------
1454
sub protect_data_peak
1455
{
1456
  my ($field, $if, $value) = @_[0..2];
1457
  my $range = get_peak_range($field, $if);
1458
  return $value if (
1459
                     $protect_peacks ne 'no'    or
1460
                     (
1461
                      $value ne 'NaN'           and
1462
                      exists($range->{'max'})   and
1463
                      $value <= $range->{'max'} and
1464
                      $value >= $range->{'min'}
1465
                     )
1466
                   );
1467
  return 'NaN';
1468
}
1469

    
1470
# --------------------------------- loading or calculating fields values ----------------------------
1471
sub get_field_data
1472
{
1473
  my ($data, $raw_data, $raw_prev_data, $if, $field) = @_[0..4];
1474
  unless (exists($data->{$if}{$field}))
1475
  {
1476
    # ----------------------------  file source ------------------------------------------------------------
1477
    if($fields->{$field}{'source'}{'type'} eq 'file' and not exists($raw_data->{$if}{$field}))
1478
    {
1479
      $raw_data->{$if}{$field} = get_file_content(replace_if_template($fields->{$field}{'source'}{'location'}, $if));
1480
      $data->{$if}{$field} = calc_diff($raw_data, $raw_prev_data, $if, $field);
1481
    }
1482
    # ----------------------------  calculated source ------------------------------------------------------------
1483
    elsif($fields->{$field}{'source'}{'type'} eq 'calculated')
1484
    {
1485
      # -------------------------------- percent ---------------------------
1486
      if($fields->{$field}{'source'}{'calculated'}{'type'} eq 'percent')
1487
      {
1488
        my $percents = {};
1489
        for my $pf (qw(full part))
1490
        {
1491
          if ($fields->{$field}{'source'}{'calculated'}{$pf}{'source'} eq 'interface')
1492
          {
1493
            $percents->{$pf} = $interfaces->{$if}{$fields->{$field}{'source'}{'calculated'}{$pf}{'name'}};
1494
          }
1495
          elsif ($fields->{$field}{'source'}{'calculated'}{$pf}{'source'} eq 'field')
1496
          {
1497
            $percents->{$pf} = get_field_data($data, $raw_data, $raw_prev_data, $if, $fields->{$field}{'source'}{'calculated'}{$pf}{'name'});
1498
          }
1499
        }
1500
        $data->{$if}{$field} = percent($percents->{'full'}, $percents->{'part'});
1501
      }
1502
      # -------------------------------- division ---------------------------
1503
      if($fields->{$field}{'source'}{'calculated'}{'type'} eq 'division')
1504
      {
1505
        my $division = {};
1506
        for my $df (qw(dividend divider))
1507
        {
1508
          if ($fields->{$field}{'source'}{'calculated'}{$df}{'source'} eq 'interface')
1509
          {
1510
            $division->{$df} = $interfaces->{$if}{$fields->{$field}{'source'}{'calculated'}{$df}{'name'}};
1511
          }
1512
          elsif ($fields->{$field}{'source'}{'calculated'}{$df}{'source'} eq 'field')
1513
          {
1514
            $division->{$df} = get_field_data($data, $raw_data, $raw_prev_data, $if, $fields->{$field}{'source'}{'calculated'}{$df}{'name'});
1515
          }
1516
        }
1517
        $data->{$if}{$field} = $division->{'divider'} != 0 ? $division->{'dividend'}/$division->{'divider'} : 'NaN';
1518
      }
1519
      # -------------------------------- sum ---------------------------
1520
      if($fields->{$field}{'source'}{'calculated'}{'type'} eq 'sum')
1521
      {
1522
        my $sum = 0;
1523
        for my $s (@{$fields->{$field}{'source'}{'calculated'}{'sum'}})
1524
        {
1525
          $sum += get_field_data($data, $raw_data, $raw_prev_data, $if, $s);
1526
        }
1527
        $data->{$if}{$field} = $sum;
1528
      }
1529
    }
1530
    if(exists($fields->{$field}{'source'}{'prepare'}))
1531
    {
1532
      my $eval = $fields->{$field}{'source'}{'prepare'};
1533
      $eval =~ s/:data:/\$data->{\$if}{\$field}/g;
1534
      eval $eval;
1535
    }
1536
    $data->{$if}{$field} = protect_data_peak($field, $if, $data->{$if}{$field});
1537
  }
1538
  return $data->{$if}{$field};
1539
}
1540

    
1541
# ------------------------- preparing value for print ----------------------------
1542
sub prepare_value
1543
{
1544
  my ($values, $field, $field_name, $graph_name, $if, $data, $raw_data, $raw_prev_data) = @_[0..7];
1545
  if(check_field_avialability($if, $field))
1546
  {
1547
    $values->{$graph_name}{$field_name} = get_field_data($data, $raw_data, $raw_prev_data, $if, $field);
1548
    if(exists($fields->{$field}{'negative'}) and $fields->{$field}{'negative'}{'type'} eq 'dummy')
1549
    {
1550
      $values->{$graph_name}{$field_name.'_dummy'} = $fields->{$field}{'negative'}{'value'};
1551
    }
1552
  }
1553
}
1554

    
1555
# --------------------------------- print field.value value for every graph ----------------------
1556
sub print_values
1557
{
1558
  my $data     = {};
1559
  my $raw_data = {};
1560
  my $raw_prev_data = restore_state_data();
1561
  my $values   = {};
1562
  $raw_data->{'timestamp'} = time();
1563
  for my $graph (keys %{$graphs})                                                                                     {
1564
    for my $field (@{$graphs->{$graph}{'general_fields'}})                                                          {
1565
      for my $if (keys %{$interfaces})                                                                            {
1566
        prepare_value($values, $field, sprintf("%s_%s", $if, $field), $graph, $if, $data, $raw_data, $raw_prev_data); } } }
1567
  for my $if (keys %{$interfaces}) 
1568
  {
1569
    for my $graph (keys %{$graphs}) 
1570
    {
1571
        my $graph_name = sprintf("%s.%s", $graph, $if);
1572
        for my $field (@{$graphs->{$graph}{'per_if_fields'}})
1573
        {
1574
          prepare_value($values, $field, $field, $graph_name, $if, $data, $raw_data, $raw_prev_data);
1575
        }
1576
    }
1577
  }
1578
  save_state_data($raw_data);
1579
  exit (0) unless defined ($raw_prev_data); # first update need just for collect and save data
1580
  # ------------------------ print ------------------------
1581
  for my $graph (sort (keys %{$values}))
1582
  {
1583
    printf ("multigraph %s\n", $graph);
1584
    for my $field (sort keys %{$values->{$graph}})
1585
    {
1586
      printf("%s.value %s\n", $field, $values->{$graph}{$field});
1587
    }
1588
    print "\n";
1589
  }
1590
}