summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMonsyne Dragon <mdragon@rackspace.com>2012-09-24 19:38:09 +0000
committerMonsyne Dragon <mdragon@rackspace.com>2012-09-25 20:36:55 +0000
commit8887f10c66bca248f289db8f834ae8f36f9a03a1 (patch)
tree3347a2976f0fcc005e5fcf65cd3fa38701e0f4d6
parent6e89cd0b8cec0c233e55ea8f4e2bf4f2855073af (diff)
Collect more accurate bandwidth data for XenServer
This changes the method used to poll xenserver for bandwidth data. The reccomended way of collecting such data from xenserver (namely the RRD files provided by the hosts) do not seem to be reliable, they will sometimes be correct, often will be signifigantly under (> 10%), and occasionally will show artifacts, such as phantom 4gb bandwidth 'spikes'. This patch changes that to use the much simpler method of simply polling the byte counters on the VIF network devices on the host. (We have old non-nova code that does that on xenserver, and that method is known to work). This should also make it much easier for other hypervisors other than xenserver to implement bandwidth polling, as polling the counters is a rather more universal method. Fixes bug 1055737 Change-Id: I6a280d8bbfcc74914f888b11bc09349a270a5f58
-rw-r--r--nova/compute/manager.py56
-rw-r--r--nova/db/api.py9
-rw-r--r--nova/db/sqlalchemy/api.py16
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/121_add_indexes_to_bw_usage_cache.py19
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/134_add_counters_to_bw_usage_cache.py60
-rw-r--r--nova/db/sqlalchemy/models.py2
-rw-r--r--nova/tests/test_db_api.py15
-rw-r--r--nova/tests/test_xenapi.py88
-rw-r--r--nova/virt/driver.py4
-rw-r--r--nova/virt/fake.py8
-rw-r--r--nova/virt/xenapi/driver.py28
-rw-r--r--nova/virt/xenapi/vm_utils.py9
-rw-r--r--nova/virt/xenapi/vmops.py41
-rwxr-xr-xplugins/xenserver/xenapi/etc/xapi.d/plugins/bandwidth51
14 files changed, 330 insertions, 76 deletions
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 4d88e2772..0f6653c66 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -2525,9 +2525,8 @@ class ComputeManager(manager.SchedulerDependentManager):
time.time() - start_time))
@manager.periodic_task
- def _poll_bandwidth_usage(self, context, start_time=None, stop_time=None):
- if not start_time:
- start_time = utils.last_completed_audit_period()[1]
+ def _poll_bandwidth_usage(self, context):
+ prev_time, start_time = utils.last_completed_audit_period()
curr_time = time.time()
if (curr_time - self._last_bw_usage_poll >
@@ -2537,8 +2536,7 @@ class ComputeManager(manager.SchedulerDependentManager):
instances = self.db.instance_get_all_by_host(context, self.host)
try:
- bw_usage = self.driver.get_all_bw_usage(instances, start_time,
- stop_time)
+ bw_counters = self.driver.get_all_bw_counters(instances)
except NotImplementedError:
# NOTE(mdragon): Not all hypervisors have bandwidth polling
# implemented yet. If they don't it doesn't break anything,
@@ -2546,12 +2544,52 @@ class ComputeManager(manager.SchedulerDependentManager):
return
refreshed = timeutils.utcnow()
- for usage in bw_usage:
+ for bw_ctr in bw_counters:
+ # Allow switching of greenthreads between queries.
+ greenthread.sleep(0)
+ bw_in = 0
+ bw_out = 0
+ last_ctr_in = None
+ last_ctr_out = None
+ usage = self.db.bw_usage_get(context,
+ bw_ctr['uuid'],
+ start_time,
+ bw_ctr['mac_address'])
+ if usage:
+ bw_in = usage['bw_in']
+ bw_out = usage['bw_out']
+ last_ctr_in = usage['last_ctr_in']
+ last_ctr_out = usage['last_ctr_out']
+ else:
+ usage = self.db.bw_usage_get(context,
+ bw_ctr['uuid'],
+ prev_time,
+ bw_ctr['mac_address'])
+ last_ctr_in = usage['last_ctr_in']
+ last_ctr_out = usage['last_ctr_out']
+
+ if last_ctr_in is not None:
+ if bw_ctr['bw_in'] < last_ctr_in:
+ # counter rollover
+ bw_in += bw_ctr['bw_in']
+ else:
+ bw_in += (bw_ctr['bw_in'] - last_ctr_in)
+
+ if last_ctr_out is not None:
+ if bw_ctr['bw_out'] < last_ctr_out:
+ # counter rollover
+ bw_out += bw_ctr['bw_out']
+ else:
+ bw_out += (bw_ctr['bw_out'] - last_ctr_out)
+
self.db.bw_usage_update(context,
- usage['uuid'],
- usage['mac_address'],
+ bw_ctr['uuid'],
+ bw_ctr['mac_address'],
start_time,
- usage['bw_in'], usage['bw_out'],
+ bw_in,
+ bw_out,
+ bw_ctr['bw_in'],
+ bw_ctr['bw_out'],
last_refreshed=refreshed)
@manager.periodic_task
diff --git a/nova/db/api.py b/nova/db/api.py
index cb651793e..4d91c5db9 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -1543,18 +1543,23 @@ def agent_build_update(context, agent_build_id, values):
####################
+def bw_usage_get(context, uuid, start_period, mac):
+ """Return bw usage for instance and mac in a given audit period."""
+ return IMPL.bw_usage_get(context, uuid, start_period, mac)
+
+
def bw_usage_get_by_uuids(context, uuids, start_period):
"""Return bw usages for instance(s) in a given audit period."""
return IMPL.bw_usage_get_by_uuids(context, uuids, start_period)
def bw_usage_update(context, uuid, mac, start_period, bw_in, bw_out,
- last_refreshed=None):
+ last_ctr_in, last_ctr_out, last_refreshed=None):
"""Update cached bandwidth usage for an instance's network based on mac
address. Creates new record if needed.
"""
return IMPL.bw_usage_update(context, uuid, mac, start_period, bw_in,
- bw_out, last_refreshed=last_refreshed)
+ bw_out, last_ctr_in, last_ctr_out, last_refreshed=last_refreshed)
####################
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 3798cade8..10619c9ed 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -4303,6 +4303,15 @@ def agent_build_update(context, agent_build_id, values):
####################
@require_context
+def bw_usage_get(context, uuid, start_period, mac):
+ return model_query(context, models.BandwidthUsage, read_deleted="yes").\
+ filter_by(start_period=start_period).\
+ filter_by(uuid=uuid).\
+ filter_by(mac=mac).\
+ first()
+
+
+@require_context
def bw_usage_get_by_uuids(context, uuids, start_period):
return model_query(context, models.BandwidthUsage, read_deleted="yes").\
filter(models.BandwidthUsage.uuid.in_(uuids)).\
@@ -4312,7 +4321,8 @@ def bw_usage_get_by_uuids(context, uuids, start_period):
@require_context
def bw_usage_update(context, uuid, mac, start_period, bw_in, bw_out,
- last_refreshed=None, session=None):
+ last_ctr_in, last_ctr_out, last_refreshed=None,
+ session=None):
if not session:
session = get_session()
@@ -4324,6 +4334,8 @@ def bw_usage_update(context, uuid, mac, start_period, bw_in, bw_out,
# records. Fall back to creation when no rows are updated.
with session.begin():
values = {'last_refreshed': last_refreshed,
+ 'last_ctr_in': last_ctr_in,
+ 'last_ctr_out': last_ctr_out,
'bw_in': bw_in,
'bw_out': bw_out}
rows = model_query(context, models.BandwidthUsage,
@@ -4342,6 +4354,8 @@ def bw_usage_update(context, uuid, mac, start_period, bw_in, bw_out,
bwusage.last_refreshed = last_refreshed
bwusage.bw_in = bw_in
bwusage.bw_out = bw_out
+ bwusage.last_ctr_in = last_ctr_in
+ bwusage.last_ctr_out = last_ctr_out
bwusage.save(session=session)
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/121_add_indexes_to_bw_usage_cache.py b/nova/db/sqlalchemy/migrate_repo/versions/121_add_indexes_to_bw_usage_cache.py
index fcbe49061..1345e5396 100644
--- a/nova/db/sqlalchemy/migrate_repo/versions/121_add_indexes_to_bw_usage_cache.py
+++ b/nova/db/sqlalchemy/migrate_repo/versions/121_add_indexes_to_bw_usage_cache.py
@@ -16,7 +16,7 @@
# under the License.
from sqlalchemy import Index, MetaData, Table
-from sqlalchemy.exc import IntegrityError
+from sqlalchemy.exc import IntegrityError, OperationalError
def upgrade(migrate_engine):
@@ -41,4 +41,19 @@ def downgrade(migrate_engine):
t = Table('bw_usage_cache', meta, autoload=True)
i = Index('bw_usage_cache_uuid_start_period_idx',
t.c.uuid, t.c.start_period)
- i.drop(migrate_engine)
+ if migrate_engine.url.get_dialect().name.startswith('sqlite'):
+ try:
+ i.drop(migrate_engine)
+ except OperationalError:
+ # Sqlite is very broken for any kind of table modification.
+ # adding columns creates a new table, then copies the data,
+ # and looses the indexes.
+ # Thus later migrations that add columns will cause the
+ # earlier migration's downgrade unittests to fail on
+ # dropping indexes.
+ # Honestly testing migrations on sqlite is not really a very
+ # valid test (because of above facts), but that is for
+ # another day. (mdragon)
+ pass
+ else:
+ i.drop(migrate_engine)
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/134_add_counters_to_bw_usage_cache.py b/nova/db/sqlalchemy/migrate_repo/versions/134_add_counters_to_bw_usage_cache.py
new file mode 100644
index 000000000..985149e91
--- /dev/null
+++ b/nova/db/sqlalchemy/migrate_repo/versions/134_add_counters_to_bw_usage_cache.py
@@ -0,0 +1,60 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from sqlalchemy import Boolean, Column, DateTime, BigInteger
+from sqlalchemy import MetaData, Integer, String, Table
+
+from nova.openstack.common import log as logging
+
+LOG = logging.getLogger(__name__)
+
+
+def upgrade(migrate_engine):
+ meta = MetaData()
+ meta.bind = migrate_engine
+
+ # add column:
+ bw_usage_cache = Table('bw_usage_cache', meta, autoload=True)
+ last_ctr_in = Column('last_ctr_in', BigInteger())
+ last_ctr_out = Column('last_ctr_out', BigInteger())
+
+ bw_usage_cache.create_column(last_ctr_in)
+ bw_usage_cache.create_column(last_ctr_out)
+
+
+def downgrade(migrate_engine):
+ meta = MetaData()
+ meta.bind = migrate_engine
+
+ # drop column:
+ bw_usage_cache = Table('bw_usage_cache', meta,
+ Column('created_at', DateTime(timezone=False)),
+ Column('updated_at', DateTime(timezone=False)),
+ Column('deleted_at', DateTime(timezone=False)),
+ Column('deleted', Boolean(create_constraint=True, name=None)),
+ Column('id', Integer(), primary_key=True, nullable=False),
+ Column('mac', String(255)),
+ Column('uuid', String(36)),
+ Column('start_period', DateTime(timezone=False), nullable=False),
+ Column('last_refreshed', DateTime(timezone=False)),
+ Column('bw_in', BigInteger()),
+ Column('bw_out', BigInteger()),
+ Column('last_ctr_in', BigInteger()),
+ Column('last_ctr_out', BigInteger()),
+ extend_existing=True)
+
+ bw_usage_cache.drop_column('last_ctr_in')
+ bw_usage_cache.drop_column('last_ctr_out')
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index 26981114b..0e960940d 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -937,6 +937,8 @@ class BandwidthUsage(BASE, NovaBase):
last_refreshed = Column(DateTime)
bw_in = Column(BigInteger)
bw_out = Column(BigInteger)
+ last_ctr_in = Column(BigInteger)
+ last_ctr_out = Column(BigInteger)
class S3Image(BASE, NovaBase):
diff --git a/nova/tests/test_db_api.py b/nova/tests/test_db_api.py
index e434faaad..eb0b8bed8 100644
--- a/nova/tests/test_db_api.py
+++ b/nova/tests/test_db_api.py
@@ -478,18 +478,24 @@ class DbApiTestCase(test.TestCase):
'start_period': start_period,
'bw_in': 100,
'bw_out': 200,
+ 'last_ctr_in': 12345,
+ 'last_ctr_out': 67890,
'last_refreshed': now},
{'uuid': 'fake_uuid2',
'mac': 'fake_mac2',
'start_period': start_period,
'bw_in': 200,
'bw_out': 300,
+ 'last_ctr_in': 22345,
+ 'last_ctr_out': 77890,
'last_refreshed': now},
{'uuid': 'fake_uuid3',
'mac': 'fake_mac3',
'start_period': start_period,
'bw_in': 400,
'bw_out': 500,
+ 'last_ctr_in': 32345,
+ 'last_ctr_out': 87890,
'last_refreshed': uuid3_refreshed}]
def _compare(bw_usage, expected):
@@ -504,18 +510,19 @@ class DbApiTestCase(test.TestCase):
# Add 3 entries
db.bw_usage_update(ctxt, 'fake_uuid1',
'fake_mac1', start_period,
- 100, 200)
+ 100, 200, 12345, 67890)
db.bw_usage_update(ctxt, 'fake_uuid2',
'fake_mac2', start_period,
- 100, 200)
+ 100, 200, 42, 42)
# Test explicit refreshed time
db.bw_usage_update(ctxt, 'fake_uuid3',
'fake_mac3', start_period,
- 400, 500, last_refreshed=uuid3_refreshed)
+ 400, 500, 32345, 87890,
+ last_refreshed=uuid3_refreshed)
# Update 2nd entry
db.bw_usage_update(ctxt, 'fake_uuid2',
'fake_mac2', start_period,
- 200, 300)
+ 200, 300, 22345, 77890)
bw_usages = db.bw_usage_get_by_uuids(ctxt,
['fake_uuid1', 'fake_uuid2', 'fake_uuid3'], start_period)
diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py
index b543f3e0f..18f8f2ea7 100644
--- a/nova/tests/test_xenapi.py
+++ b/nova/tests/test_xenapi.py
@@ -1442,11 +1442,23 @@ class XenAPIGenerateLocal(stubs.XenAPITestBase):
self.assertCalled(instance)
-class XenAPIBWUsageTestCase(stubs.XenAPITestBase):
+class XenAPIBWCountersTestCase(stubs.XenAPITestBase):
+ FAKE_VMS = {'test1:ref': dict(name_label='test1',
+ other_config=dict(nova_uuid='hash'),
+ domid='12',
+ _vifmap={'0': "a:b:c:d...",
+ '1': "e:f:12:q..."}),
+ 'test2:ref': dict(name_label='test2',
+ other_config=dict(nova_uuid='hash'),
+ domid='42',
+ _vifmap={'0': "a:3:c:d...",
+ '1': "e:f:42:q..."}),
+ }
+
def setUp(self):
- super(XenAPIBWUsageTestCase, self).setUp()
- self.stubs.Set(vm_utils, 'compile_metrics',
- XenAPIBWUsageTestCase._fake_compile_metrics)
+ super(XenAPIBWCountersTestCase, self).setUp()
+ self.stubs.Set(vm_utils, 'list_vms',
+ XenAPIBWCountersTestCase._fake_list_vms)
self.flags(xenapi_connection_url='test_url',
xenapi_connection_password='test_pass',
firewall_driver='nova.virt.xenapi.firewall.'
@@ -1454,21 +1466,75 @@ class XenAPIBWUsageTestCase(stubs.XenAPITestBase):
stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests)
self.conn = xenapi_conn.XenAPIDriver(False)
+ def _fake_get_vif_device_map(vm_rec):
+ return vm_rec['_vifmap']
+
+ self.stubs.Set(self.conn._vmops, "_get_vif_device_map",
+ _fake_get_vif_device_map)
+
@classmethod
- def _fake_compile_metrics(cls, start_time, stop_time=None):
- raise exception.CouldNotFetchMetrics()
+ def _fake_list_vms(cls, session):
+ return cls.FAKE_VMS.iteritems()
- def test_get_all_bw_usage_in_failure_case(self):
- """Test that get_all_bw_usage returns an empty list when metrics
- compilation failed. c.f. bug #910045.
+ @classmethod
+ def _fake_fetch_bandwidth_mt(cls, session):
+ return {}
+
+ @classmethod
+ def _fake_fetch_bandwidth(cls, session):
+ return {'42':
+ {'0': {'bw_in': 21024, 'bw_out': 22048},
+ '1': {'bw_in': 231337, 'bw_out': 221212121}},
+ '12':
+ {'0': {'bw_in': 1024, 'bw_out': 2048},
+ '1': {'bw_in': 31337, 'bw_out': 21212121}},
+ }
+
+ def test_get_all_bw_counters(self):
+ class testinstance(object):
+ def __init__(self, name, uuid):
+ self.name = name
+ self.uuid = uuid
+
+ self.stubs.Set(vm_utils, 'fetch_bandwidth',
+ XenAPIBWCountersTestCase._fake_fetch_bandwidth)
+ result = self.conn.get_all_bw_counters([testinstance(
+ name='test1',
+ uuid='1-2-3'),
+ testinstance(
+ name='test2',
+ uuid='4-5-6')])
+ self.assertEqual(len(result), 4)
+ self.assertIn(dict(uuid='1-2-3',
+ mac_address="a:b:c:d...",
+ bw_in=1024,
+ bw_out=2048), result)
+ self.assertIn(dict(uuid='1-2-3',
+ mac_address="e:f:12:q...",
+ bw_in=31337,
+ bw_out=21212121), result)
+
+ self.assertIn(dict(uuid='4-5-6',
+ mac_address="a:3:c:d...",
+ bw_in=21024,
+ bw_out=22048), result)
+ self.assertIn(dict(uuid='4-5-6',
+ mac_address="e:f:42:q...",
+ bw_in=231337,
+ bw_out=221212121), result)
+
+ def test_get_all_bw_counters_in_failure_case(self):
+ """Test that get_all_bw_conters returns an empty list when
+ no data returned from Xenserver. c.f. bug #910045.
"""
class testinstance(object):
def __init__(self):
self.name = "instance-0001"
self.uuid = "1-2-3-4-5"
- result = self.conn.get_all_bw_usage([testinstance()],
- timeutils.utcnow())
+ self.stubs.Set(vm_utils, 'fetch_bandwidth',
+ XenAPIBWCountersTestCase._fake_fetch_bandwidth_mt)
+ result = self.conn.get_all_bw_counters([testinstance()])
self.assertEqual(result, [])
diff --git a/nova/virt/driver.py b/nova/virt/driver.py
index 41df132fc..d741524b0 100644
--- a/nova/virt/driver.py
+++ b/nova/virt/driver.py
@@ -221,8 +221,8 @@ class ComputeDriver(object):
# TODO(Vek): Need to pass context in for access to auth_token
raise NotImplementedError()
- def get_all_bw_usage(self, instances, start_time, stop_time=None):
- """Return bandwidth usage info for each interface on each
+ def get_all_bw_counters(self, instances):
+ """Return bandwidth usage counters for each interface on each
running VM"""
raise NotImplementedError()
diff --git a/nova/virt/fake.py b/nova/virt/fake.py
index a6476f9d9..959ab174c 100644
--- a/nova/virt/fake.py
+++ b/nova/virt/fake.py
@@ -186,11 +186,11 @@ class FakeDriver(driver.ComputeDriver):
def get_diagnostics(self, instance_name):
return 'FAKE_DIAGNOSTICS'
- def get_all_bw_usage(self, instances, start_time, stop_time=None):
- """Return bandwidth usage info for each interface on each
+ def get_all_bw_counters(self, instances):
+ """Return bandwidth usage counters for each interface on each
running VM"""
- bwusage = []
- return bwusage
+ bw = []
+ return bw
def block_stats(self, instance_name, disk_id):
return [0L, 0L, 0L, 0L, None]
diff --git a/nova/virt/xenapi/driver.py b/nova/virt/xenapi/driver.py
index 3425c64f8..ad2d64a38 100644
--- a/nova/virt/xenapi/driver.py
+++ b/nova/virt/xenapi/driver.py
@@ -298,35 +298,27 @@ class XenAPIDriver(driver.ComputeDriver):
"""Return data about VM diagnostics"""
return self._vmops.get_diagnostics(instance)
- def get_all_bw_usage(self, instances, start_time, stop_time=None):
- """Return bandwidth usage info for each interface on each
+ def get_all_bw_counters(self, instances):
+ """Return bandwidth usage counters for each interface on each
running VM"""
# we only care about VMs that correspond to a nova-managed
# instance:
imap = dict([(inst.name, inst.uuid) for inst in instances])
-
- bwusage = []
- start_time = time.mktime(start_time.timetuple())
- if stop_time:
- stop_time = time.mktime(stop_time.timetuple())
+ bwcounters = []
# get a dictionary of instance names. values are dictionaries
- # of mac addresses with values that are the bw stats:
+ # of mac addresses with values that are the bw counters:
# e.g. {'instance-001' : { 12:34:56:78:90:12 : {'bw_in': 0, ....}}
- iusages = self._vmops.get_all_bw_usage(start_time, stop_time)
- for instance_name in iusages:
+ all_counters = self._vmops.get_all_bw_counters()
+ for instance_name, counters in all_counters.iteritems():
if instance_name in imap:
# yes these are stats for a nova-managed vm
# correlate the stats with the nova instance uuid:
- iusage = iusages[instance_name]
-
- for macaddr, usage in iusage.iteritems():
- bwusage.append(dict(mac_address=macaddr,
- uuid=imap[instance_name],
- bw_in=usage['bw_in'],
- bw_out=usage['bw_out']))
- return bwusage
+ for vif_counter in counters.values():
+ vif_counter['uuid'] = imap[instance_name]
+ bwcounters.append(vif_counter)
+ return bwcounters
def get_console_output(self, instance):
"""Return snapshot of console"""
diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py
index a9adb4575..2dc358f0f 100644
--- a/nova/virt/xenapi/vm_utils.py
+++ b/nova/virt/xenapi/vm_utils.py
@@ -1351,9 +1351,16 @@ def compile_diagnostics(record):
return {"Unable to retrieve diagnostics": e}
+def fetch_bandwidth(session):
+ bw = session.call_plugin_serialized('bandwidth', 'fetch_all_bandwidth')
+ return bw
+
+
def compile_metrics(start_time, stop_time=None):
"""Compile bandwidth usage, cpu, and disk metrics for all VMs on
- this host"""
+ this host.
+ Note that some stats, like bandwith, do not seem to be very
+ accurate in some of the data from XenServer (mdragon). """
start_time = int(start_time)
xml = _get_rrd_updates(_get_rrd_server(), start_time)
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index 52cb9b17b..ad6f0d38c 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -1208,34 +1208,31 @@ class VMOps(object):
vm_rec = self._session.call_xenapi("VM.get_record", vm_ref)
return vm_utils.compile_diagnostics(vm_rec)
- def get_all_bw_usage(self, start_time, stop_time=None):
- """Return bandwidth usage info for each interface on each
+ def _get_vif_device_map(self, vm_rec):
+ vif_map = {}
+ for vif in [self._session.call_xenapi("VIF.get_record", vrec)
+ for vrec in vm_rec['VIFs']]:
+ vif_map[vif['device']] = vif['MAC']
+ return vif_map
+
+ def get_all_bw_counters(self):
+ """Return running bandwidth counter for each interface on each
running VM"""
- try:
- metrics = vm_utils.compile_metrics(start_time, stop_time)
- except exception.CouldNotFetchMetrics:
- LOG.exception(_("Could not get bandwidth info."))
- return {}
+ counters = vm_utils.fetch_bandwidth(self._session)
bw = {}
- for uuid, data in metrics.iteritems():
- vm_ref = self._session.call_xenapi("VM.get_by_uuid", uuid)
- vm_rec = self._session.call_xenapi("VM.get_record", vm_ref)
- vif_map = {}
- for vif in [self._session.call_xenapi("VIF.get_record", vrec)
- for vrec in vm_rec['VIFs']]:
- vif_map[vif['device']] = vif['MAC']
+ for vm_ref, vm_rec in vm_utils.list_vms(self._session):
+ vif_map = self._get_vif_device_map(vm_rec)
name = vm_rec['name_label']
if 'nova_uuid' not in vm_rec['other_config']:
continue
+ dom = vm_rec.get('domid')
+ if dom is None or dom not in counters:
+ continue
vifs_bw = bw.setdefault(name, {})
- for key, val in data.iteritems():
- if key.startswith('vif_'):
- vname = key.split('_')[1]
- vif_bw = vifs_bw.setdefault(vif_map[vname], {})
- if key.endswith('tx'):
- vif_bw['bw_out'] = int(val)
- if key.endswith('rx'):
- vif_bw['bw_in'] = int(val)
+ for vif_num, vif_data in counters[dom].iteritems():
+ mac = vif_map[vif_num]
+ vif_data['mac_address'] = mac
+ vifs_bw[mac] = vif_data
return bw
def get_console_output(self, instance):
diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/bandwidth b/plugins/xenserver/xenapi/etc/xapi.d/plugins/bandwidth
new file mode 100755
index 000000000..171011a06
--- /dev/null
+++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/bandwidth
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2012 OpenStack, LLC
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Fetch Bandwidth data from VIF network devices."""
+
+import os
+import shutil
+
+import utils
+
+from pluginlib_nova import *
+configure_logging('bandwidth')
+
+
+def _read_proc_net():
+ devs = [l.strip() for l in open('/proc/net/dev', 'r').readlines()]
+ #ignore headers
+ devs = devs[2:]
+ dlist = [d.split(':', 1) for d in devs if d.startswith('vif')]
+ devmap = dict()
+ for name, stats in dlist:
+ slist = stats.split()
+ dom, vifnum = name[3:].split('.', 1)
+ dev = devmap.get(dom, {})
+ # Note, we deliberately swap in and out, as instance traffic
+ # shows up inverted due to going though the bridge. (mdragon)
+ dev[vifnum] = dict(bw_in=int(slist[0]), bw_out=int(slist[8]))
+ devmap[dom] = dev
+ return devmap
+
+
+def fetch_all_bandwidth(session):
+ return _read_proc_net()
+
+
+if __name__ == '__main__':
+ utils.register_plugin_calls(fetch_all_bandwidth)