1
#============================================================================
2
# This library is free software; you can redistribute it and/or
3
# modify it under the terms of version 2.1 of the GNU Lesser General Public
4
# License as published by the Free Software Foundation.
6
# This library is distributed in the hope that it will be useful,
7
# but WITHOUT ANY WARRANTY; without even the implied warranty of
8
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
9
# Lesser General Public License for more details.
11
# You should have received a copy of the GNU Lesser General Public
12
# License along with this library; if not, write to the Free Software
13
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
14
#============================================================================
15
# Copyright (C) 2007 XenSource Ltd.
16
#============================================================================
18
from xen.lowlevel.xc import xc
24
"""Monitoring thread to keep track of Xend statistics. """
26
VBD_SYSFS_PATH = '/sys/devices/xen-backend/'
27
VBD_WR_PATH = VBD_SYSFS_PATH + '%s/statistics/wr_sect'
28
VBD_RD_PATH = VBD_SYSFS_PATH + '%s/statistics/rd_sect'
29
VBD_DOMAIN_RE = r'vbd-(?P<domid>\d+)-(?P<devid>\d+)$'
31
NET_PROCFS_PATH = '/proc/net/dev'
32
PROC_NET_DEV_RE = r'(?P<rx_bytes>\d+)\s+' \
33
r'(?P<rx_packets>\d+)\s+' \
34
r'(?P<rx_errs>\d+)\s+' \
35
r'(?P<rx_drop>\d+)\s+' \
36
r'(?P<rx_fifo>\d+)\s+' \
37
r'(?P<rx_frame>\d+)\s+' \
38
r'(?P<rx_compressed>\d+)\s+' \
39
r'(?P<rx_multicast>\d+)\s+' \
40
r'(?P<tx_bytes>\d+)\s+' \
41
r'(?P<tx_packets>\d+)\s+' \
42
r'(?P<tx_errs>\d+)\s+' \
43
r'(?P<tx_drop>\d+)\s+' \
44
r'(?P<tx_fifo>\d+)\s+' \
45
r'(?P<tx_collisions>\d+)\s+' \
46
r'(?P<tx_carrier>\d+)\s+' \
47
r'(?P<tx_compressed>\d+)\s*$'
50
VIF_DOMAIN_RE = re.compile(r'vif(?P<domid>\d+)\.(?P<iface>\d+):\s*' +
52
PIF_RE = re.compile(r'^\s*(?P<iface>peth\d+):\s*' + PROC_NET_DEV_RE)
54
# Interval to poll xc, sysfs and proc
57
class XendMonitor(threading.Thread):
58
"""Monitors VCPU, VBD, VIF and PIF statistics for Xen API.
60
Polls sysfs and procfs for statistics on VBDs and VIFs respectively.
62
@ivar domain_vcpus_util: Utilisation for VCPUs indexed by domain
63
@type domain_vcpus_util: {domid: {vcpuid: float, vcpuid: float}}
64
@ivar domain_vifs_util: Bytes per second for VIFs indexed by domain
65
@type domain_vifs_util: {domid: {vifid: (rx_bps, tx_bps)}}
66
@ivar domain_vifs_stat: Total amount of bytes used for VIFs indexed by domain
67
@type domain_vifs_stat: {domid: {vbdid: (rx, tx)}}
68
@ivar domain_vbds_util: Blocks per second for VBDs index by domain.
69
@type domain_vbds_util: {domid: {vbdid: (rd_reqps, wr_reqps)}}
73
threading.Thread.__init__(self)
77
self.lock = threading.Lock()
79
# tracks the last polled statistics
80
self._domain_vcpus = {}
81
self._domain_vifs = {}
82
self._domain_vbds = {}
85
# instantaneous statistics
86
self._domain_vcpus_util = {}
87
self._domain_vifs_util = {}
88
self._domain_vifs_stat = {}
89
self._domain_vbds_util = {}
92
def get_domain_vcpus_util(self):
95
return self._domain_vcpus_util
99
def get_domain_vbds_util(self):
102
return self._domain_vbds_util
106
def get_domain_vifs_util(self):
109
return self._domain_vifs_util
113
def get_domain_vifs_stat(self):
116
return self._domain_vifs_stat
120
def get_pifs_util(self):
123
return self.pifs_util
127
def _get_vif_stats(self):
130
if not os.path.exists(NET_PROCFS_PATH):
133
usage_at = time.time()
134
for line in open(NET_PROCFS_PATH):
135
is_vif = re.search(VIF_DOMAIN_RE, line.strip())
139
domid = int(is_vif.group('domid'))
140
vifid = int(is_vif.group('iface'))
141
rx_bytes = int(is_vif.group('rx_bytes'))
142
tx_bytes = int(is_vif.group('tx_bytes'))
143
if not domid in stats:
146
stats[domid][vifid] = (usage_at, rx_bytes, tx_bytes)
150
def _get_pif_stats(self):
153
if not os.path.exists(NET_PROCFS_PATH):
156
usage_at = time.time()
157
for line in open(NET_PROCFS_PATH):
158
is_pif = re.search(PIF_RE, line.strip())
162
pifname = is_pif.group('iface')
163
rx_bytes = int(is_pif.group('rx_bytes'))
164
tx_bytes = int(is_pif.group('tx_bytes'))
165
stats[pifname] = (usage_at, rx_bytes, tx_bytes)
169
def _get_vbd_stats(self):
172
if not os.path.exists(VBD_SYSFS_PATH):
175
for vbd_path in os.listdir(VBD_SYSFS_PATH):
176
is_vbd = re.search(VBD_DOMAIN_RE, vbd_path)
180
domid = int(is_vbd.group('domid'))
181
vbdid = int(is_vbd.group('devid'))
182
rd_stat_path = VBD_RD_PATH % vbd_path
183
wr_stat_path = VBD_WR_PATH % vbd_path
185
if not os.path.exists(rd_stat_path) or \
186
not os.path.exists(wr_stat_path):
191
usage_at = time.time()
192
rd_stat = int(open(rd_stat_path).readline().strip())
193
wr_stat = int(open(wr_stat_path).readline().strip())
194
rd_stat *= SECTOR_SIZE
195
wr_stat *= SECTOR_SIZE
196
if domid not in stats:
199
stats[domid][vbdid] = (usage_at, rd_stat, wr_stat)
201
except (IOError, ValueError):
206
def _get_cpu_stats(self):
208
for domain in self.xc.domain_getinfo():
209
domid = domain['domid']
210
vcpu_count = domain['online_vcpus']
212
for i in range(vcpu_count):
213
vcpu_info = self.xc.vcpu_getinfo(domid, i)
214
usage = vcpu_info['cpu_time']
215
usage_at = time.time()
216
stats[domid][i] = (usage_at, usage)
223
# loop every second for stats
228
# Calculate utilisation for VCPUs
230
for domid, cputimes in self._get_cpu_stats().items():
231
active_domids.append(domid)
232
if domid not in self._domain_vcpus:
233
# if not initialised, save current stats
234
# and skip utilisation calculation
235
self._domain_vcpus[domid] = cputimes
236
self._domain_vcpus_util[domid] = {}
239
for vcpu, (usage_at, usage) in cputimes.items():
240
if vcpu not in self._domain_vcpus[domid]:
243
prv_usage_at, prv_usage = \
244
self._domain_vcpus[domid][vcpu]
245
interval_s = (usage_at - prv_usage_at) * 1000000000
247
util = (usage - prv_usage) / interval_s
248
self._domain_vcpus_util[domid][vcpu] = util
250
self._domain_vcpus[domid] = cputimes
252
# Calculate utilisation for VBDs
254
for domid, vbds in self._get_vbd_stats().items():
255
if domid not in self._domain_vbds:
256
self._domain_vbds[domid] = vbds
257
self._domain_vbds_util[domid] = {}
260
for devid, (usage_at, rd, wr) in vbds.items():
261
if devid not in self._domain_vbds[domid]:
264
prv_at, prv_rd, prv_wr = \
265
self._domain_vbds[domid][devid]
266
interval = usage_at - prv_at
267
rd_util = (rd - prv_rd)/interval
268
wr_util = (wr - prv_wr)/interval
269
self._domain_vbds_util[domid][devid] = \
272
self._domain_vbds[domid] = vbds
275
# Calculate utilisation for VIFs
277
for domid, vifs in self._get_vif_stats().items():
279
if domid not in self._domain_vifs:
280
self._domain_vifs[domid] = vifs
281
self._domain_vifs_util[domid] = {}
282
self._domain_vifs_stat[domid] = {}
285
for devid, (usage_at, rx, tx) in vifs.items():
286
if devid not in self._domain_vifs[domid]:
289
prv_at, prv_rx, prv_tx = \
290
self._domain_vifs[domid][devid]
291
interval = usage_at - prv_at
292
rx_util = (rx - prv_rx)/interval
293
tx_util = (tx - prv_tx)/interval
295
# note these are flipped around because
296
# we are measuring the host interface,
297
# not the guest interface
298
self._domain_vifs_util[domid][devid] = \
300
self._domain_vifs_stat[domid][devid] = \
301
(float(tx), float(rx))
303
self._domain_vifs[domid] = vifs
305
# Calculate utilisation for PIFs
307
for pifname, stats in self._get_pif_stats().items():
308
if pifname not in self.pifs:
309
self.pifs[pifname] = stats
312
usage_at, rx, tx = stats
313
prv_at, prv_rx, prv_tx = self.pifs[pifname]
314
interval = usage_at - prv_at
315
rx_util = (rx - prv_rx)/interval
316
tx_util = (tx - prv_tx)/interval
318
self.pifs_util[pifname] = (rx_util, tx_util)
319
self.pifs[pifname] = stats
321
for domid in self._domain_vcpus_util.keys():
322
if domid not in active_domids:
323
del self._domain_vcpus_util[domid]
324
del self._domain_vcpus[domid]
325
for domid in self._domain_vifs_util.keys():
326
if domid not in active_domids:
327
del self._domain_vifs_util[domid]
328
del self._domain_vifs[domid]
329
del self._domain_vifs_stat[domid]
330
for domid in self._domain_vbds_util.keys():
331
if domid not in active_domids:
332
del self._domain_vbds_util[domid]
333
del self._domain_vbds[domid]
338
# Sleep a while before next poll
339
time.sleep(POLL_INTERVAL)