Projet

Général

Profil

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

root / plugins / network / if @ 17f78427

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' => 'wireless',
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' => 'wireless',
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' => 'wireless',
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 unless -d "$ifpath/$if";
1099
      next if $if =~ m/\./;
1100
      unless($if =~ m/$include/)
1101
      {
1102
       next unless get_file_content(sprintf("%s/%s/operstate", $ifpath, $if)) =~ m/(up|unknown)/;
1103
       next if $exclude =~ m/$if/;
1104
      }
1105
      my $mtufile = sprintf("%s/%s/mtu", $ifpath, $if);
1106
      if(-e $mtufile)
1107
      {
1108
        $interfaces->{$if}{'mtu'} = get_file_content($mtufile);
1109
      }
1110
      my $bps = $ENV{"${if}_max_bps"} || undef;
1111
      if(defined($bps))
1112
      {
1113
        my ($num, $suff) = $bps =~ /(\d+)(\w)/;
1114
        if   ($suff eq 'k') { $bps = $num * 1000 / 8; }
1115
        elsif($suff eq 'M') { $bps = $num * 1000 * 1000 / 8; }
1116
        elsif($suff eq 'G') { $bps = $num * 1000 * 1000 * 1000 / 8; }
1117
        $interfaces->{$if}{'bps'} = $bps;
1118
      }
1119
      if (-e sprintf("/sys/devices/virtual/net/%s", $if)) { $interfaces->{$if}{'virtual'} = 1; }
1120
      $interfaces->{$if}{'name'} = exists($interfaces->{$if}{'virtual'}) ? sprintf("~%s", $if) : $if;
1121
    }
1122
    my ($maxlen, $tl) = (0, 0);
1123
    for (keys %{$interfaces}) { $tl = length($interfaces->{$_}{'name'}); $maxlen = $tl if $tl > $maxlen;  }
1124
    for (keys %{$interfaces}) { $interfaces->{$_}{'name'} = sprintf("[%${maxlen}s]", $interfaces->{$_}{'name'}); }
1125
  }
1126
  else { die "$ifpath not exists\n"; }
1127

    
1128
  return $interfaces;
1129
}
1130

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

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

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

    
1187

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

    
1253

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

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

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

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

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

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

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

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

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

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

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

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

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

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