|
1
|
|
|
# -*- coding: utf-8 -*- |
|
2
|
|
|
"""Network interface metrics reader for /proc on Linux.""" |
|
3
|
|
|
import re |
|
4
|
|
|
import time |
|
5
|
|
|
from collections import deque |
|
6
|
|
|
|
|
7
|
|
|
import plumd |
|
8
|
|
|
from plumd.util import Differential |
|
9
|
|
|
from plumd.util import get_file |
|
10
|
|
|
|
|
11
|
|
|
__author__ = 'Kenny Freeman' |
|
12
|
|
|
__email__ = '[email protected]' |
|
13
|
|
|
__license__ = "ISCL" |
|
14
|
|
|
__docformat__ = 'reStructuredText' |
|
15
|
|
|
|
|
16
|
|
|
|
|
17
|
|
|
class NetDev(plumd.Reader): |
|
18
|
|
|
"""Plugin to measure various kernel metrics from /proc.""" |
|
19
|
|
|
|
|
20
|
|
|
rename = { |
|
21
|
|
|
'packets': 'pkt', |
|
22
|
|
|
'compressed': 'compr', |
|
23
|
|
|
'multicast': 'mcast', |
|
24
|
|
|
} |
|
25
|
|
|
|
|
26
|
|
|
defaults = { |
|
27
|
|
|
'poll.interval': 10, |
|
28
|
|
|
'proc_path': '/proc', |
|
29
|
|
|
'proc_netdev_re': "(virbr\d+)|(vnet\d+)" |
|
|
|
|
|
|
30
|
|
|
} |
|
31
|
|
|
|
|
32
|
|
|
def __init__(self, log, config): |
|
33
|
|
|
"""Plugin to measure network interface metrics from proc file net/dev. |
|
34
|
|
|
|
|
35
|
|
|
:param log: A logger |
|
36
|
|
|
:type log: logging.RootLogger |
|
37
|
|
|
:param config: a plumd.config.Conf configuration helper instance. |
|
38
|
|
|
:type config: plumd.config.Conf |
|
39
|
|
|
""" |
|
40
|
|
|
super(NetDev, self).__init__(log, config) |
|
41
|
|
|
config.defaults(NetDev.defaults) |
|
42
|
|
|
self.proc_file = "{0}/net/dev".format(config.get('proc_path')) |
|
43
|
|
|
self.dev_re = re.compile(config.get('proc_netdev_re')) |
|
44
|
|
|
self.calc = Differential() |
|
45
|
|
|
self.enabled = True |
|
46
|
|
|
self.dev_cols = self.get_columns() |
|
47
|
|
|
|
|
48
|
|
|
def get_columns(self): |
|
49
|
|
|
"""Return the /proc file column names. |
|
50
|
|
|
|
|
51
|
|
|
:rtype: list |
|
52
|
|
|
""" |
|
53
|
|
|
cols = [] |
|
54
|
|
|
dat = get_file(self.proc_file) |
|
55
|
|
|
lines = deque(dat.split("\n")) |
|
56
|
|
|
nlines = len(lines) |
|
57
|
|
|
# expect at least one network interface + 2 header lines |
|
58
|
|
|
if nlines < 2: |
|
59
|
|
|
msg = "NetDev: cannot parse {0}" |
|
60
|
|
|
self.log.error(msg.format(self.proc_file)) |
|
61
|
|
|
self.enabled = False |
|
62
|
|
|
return cols |
|
63
|
|
|
|
|
64
|
|
|
# skip first line eg. Inter-| Receive |
|
65
|
|
|
lines.popleft() |
|
66
|
|
|
# next line has the header values |
|
67
|
|
|
hdr_vals = deque(lines.popleft().split("|")) |
|
68
|
|
|
# remove eg. face |
|
69
|
|
|
hdr_vals.popleft() |
|
70
|
|
|
if len(hdr_vals) != 2: |
|
71
|
|
|
msg = "NetDev: cannot parse {0}" |
|
72
|
|
|
self.log.error(msg.format(self.proc_file)) |
|
73
|
|
|
self.enabled = False |
|
74
|
|
|
return cols |
|
75
|
|
|
|
|
76
|
|
|
# values are receive then transmit |
|
77
|
|
|
hdrs = dict(zip(["rx", "tx"], hdr_vals)) |
|
78
|
|
|
for pfx, metrics in hdrs.items(): |
|
79
|
|
|
for metric in metrics.split(): |
|
80
|
|
|
if metric in NetDev.rename: |
|
81
|
|
|
metric = NetDev.rename[metric] |
|
82
|
|
|
cols.append("{0}_{1}".format(pfx, metric)) |
|
83
|
|
|
return cols |
|
84
|
|
|
|
|
85
|
|
|
def poll(self): |
|
86
|
|
|
"""Return network interface metrics from proc file net/dev. |
|
87
|
|
|
|
|
88
|
|
|
:rtype: ResultSet |
|
89
|
|
|
""" |
|
90
|
|
|
return plumd.ResultSet(self.check()) |
|
91
|
|
|
|
|
92
|
|
|
def check(self): |
|
93
|
|
|
"""Return network interface metrics from proc file net/dev. |
|
94
|
|
|
|
|
95
|
|
|
:rtype: list |
|
96
|
|
|
""" |
|
97
|
|
|
result = plumd.Result("netdev") |
|
98
|
|
|
if not self.enabled: |
|
99
|
|
|
return [result] |
|
100
|
|
|
|
|
101
|
|
|
dat = {} |
|
102
|
|
|
cols = self.dev_cols |
|
103
|
|
|
regexp = self.dev_re |
|
104
|
|
|
|
|
105
|
|
|
# read and process /proc/net/dev |
|
106
|
|
|
with open(self.proc_file, 'r') as pfd: |
|
107
|
|
|
lines = deque(pfd.read().strip().split("\n")) |
|
108
|
|
|
# remove the two header lines - format fail :| |
|
109
|
|
|
lines.popleft() |
|
110
|
|
|
lines.popleft() |
|
111
|
|
|
|
|
112
|
|
|
# for Differential |
|
113
|
|
|
times = time.time() |
|
114
|
|
|
|
|
115
|
|
|
# now it's dev, metrics |
|
116
|
|
|
for line in lines: |
|
117
|
|
|
dat = line.split() |
|
118
|
|
|
dev = dat[0].split(":")[0] |
|
119
|
|
|
vals = dat[1:] |
|
120
|
|
|
if regexp.match(dev): |
|
121
|
|
|
continue |
|
122
|
|
|
for key, val in dict(zip(cols, vals)).items(): |
|
123
|
|
|
mstr = "{0}.{1}".format(dev, key) |
|
124
|
|
|
dval = self.calc.per_second(mstr, int(val), times) |
|
125
|
|
|
result.add(plumd.Float(mstr, dval)) |
|
126
|
|
|
|
|
127
|
|
|
return [result] |
|
128
|
|
|
|
Escape sequences in Python are generally interpreted according to rules similar to standard C. Only if strings are prefixed with
rorRare they interpreted as regular expressions.The escape sequence that was used indicates that you might have intended to write a regular expression.
Learn more about the available escape sequences. in the Python documentation.