Projet

Général

Profil

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

root / plugins / network / if @ dd4afac8

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

    
1053
# ----------------- main ----------------
1054

    
1055
need_multigraph();
1056

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

    
1071

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

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

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

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

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

    
1184

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

    
1250

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

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

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

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

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

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

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

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

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

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

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

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

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

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