Nir Soffer has uploaded a new change for review.
Change subject: sampling: Scalable vm sampling
......................................................................
sampling: Scalable vm sampling
Previously each vm was running a sampling thread, which is not very
efficient in Python when running 100's of vms. This introduces the
Sampler class, managing sampling for one vm, replacing the
AdvancedStatsThread class.
Each VM keeps one Sampler object, running sampling tasks serially using
a thread pool, ensuring that each vm has only single sampling task
running at any time.
If a VM sampling task get stuck, sampling for this vm stops until the
stuck task is finished. This keep the same behavior of the current code
using one sampling thread per vm.
For simplicity, this uses the storage thread pool for running sampling
tasks. In the final version, this should use a more robust thread pool
handling stuck worker threads, see:
http://gerrit.ovirt.org/29191
Threads usage:
- One scheduler thread serving all vm samplers
- 20 worker threads. The final count should be determined by testing,
ensuring that we don't overload libvirt. Configurable using new
vars:vm_sampling_threads option.
Change-Id: I5539a728f3a1b3075712331440eff1749da6c530
Signed-off-by: Nir Soffer <nsoffer(a)redhat.com>
---
M lib/vdsm/config.py.in
M tests/numaUtilsTests.py
M tests/samplingTests.py
M tests/vmApiTests.py
M tests/vmTests.py
M vdsm/vdsm
M vdsm/virt/sampling.py
M vdsm/virt/vm.py
8 files changed, 420 insertions(+), 121 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/80/29980/1
diff --git a/lib/vdsm/config.py.in b/lib/vdsm/config.py.in
index 52e0cd9..c411e4e 100644
--- a/lib/vdsm/config.py.in
+++ b/lib/vdsm/config.py.in
@@ -190,6 +190,14 @@
('vm_sample_cpu_tune_interval', '15', None),
+ ('vm_sampling_threads', '20',
+ 'Number of vm sampling threads. Should match libvirt thread '
+ 'pool size (default 20)'),
+
+ ('vm_sampling_tasks', '500',
+ 'Number of vm sampling tasks. Should be bigger then number of '
+ 'running vms (default 500)'),
+
('trust_store_path', '@TRUSTSTORE@',
'Where the certificates and keys are situated.'),
diff --git a/tests/numaUtilsTests.py b/tests/numaUtilsTests.py
index e5dd293..93e30a9 100644
--- a/tests/numaUtilsTests.py
+++ b/tests/numaUtilsTests.py
@@ -65,7 +65,7 @@
(3, 1, 19590000000L, 2)], 15
-class FakeVmStatsThread:
+class FakeVmStats:
def __init__(self, vm):
self._vm = vm
self.sampleVcpuPinning = FakeAdvancedStatsFunction()
@@ -98,7 +98,7 @@
'memory': '1024',
'nodeIndex': 1}]}
with vmTests.FakeVM(VM_PARAMS) as fake:
- fake._vmStats = FakeVmStatsThread(fake)
+ fake._vmStats = FakeVmStats(fake)
expectedResult = {'0': [0, 1], '1': [0, 1]}
vmNumaNodeRuntimeMap = numaUtils.getVmNumaNodeRuntimeInfo(fake)
self.assertEqual(expectedResult, vmNumaNodeRuntimeMap)
diff --git a/tests/samplingTests.py b/tests/samplingTests.py
index 2ff703e..1089960 100644
--- a/tests/samplingTests.py
+++ b/tests/samplingTests.py
@@ -111,3 +111,228 @@
s1 = sampling.InterfaceSample(lo)
s1.operstate = 'x'
self.assertEquals('operstate:x', s1.connlog_diff(s0))
+
+
+class Task(object):
+
+ def __init__(self, interval, name, error=None):
+ self.interval = interval
+ self.name = name
+ self.error = error
+ self.executed = 0
+
+ def __call__(self):
+ self.executed += 1
+ if self.error:
+ raise self.error
+
+
+class SamplingIterationTests(TestCaseBase):
+
+ task1 = Task(2, "task1")
+ task2 = Task(2, "task2")
+ task3 = Task(3, "task3")
+ samplings = [task1, task2, task3]
+
+ virtual_times = [
+ # cycle 1
+ (2, task1),
+ (2, task2),
+ (3, task3),
+ (4, task1),
+ (4, task2),
+ (6, task1),
+ (6, task2),
+ (6, task3),
+ # cycle 2
+ (8, task1),
+ (8, task2),
+ (9, task3),
+ (10, task1),
+ (10, task2),
+ (12, task1),
+ (12, task2),
+ (12, task3),
+ ]
+
+ delays = [
+ (2, task1),
+ (0, task2),
+ (1, task3),
+ (1, task1),
+ (0, task2),
+ (2, task1),
+ (0, task2),
+ (0, task3),
+ ]
+
+ def test_virtual_time(self):
+ iterator = sampling.cycle(self.samplings)
+ for pair in self.virtual_times:
+ self.assertEquals(next(iterator), pair)
+
+ def test_delays(self):
+ iterator = sampling.delays(self.virtual_times)
+ for i in range(2):
+ for pair in self.delays:
+ self.assertEquals(next(iterator), pair)
+
+ def test_chain(self):
+ iterator = sampling.delays(sampling.cycle(self.samplings))
+ for i in range(3):
+ for pair in self.delays:
+ self.assertEquals(next(iterator), pair)
+
+
+class SamplerTests(TestCaseBase):
+
+ def setUp(self):
+ self.scheduler = FakeScheduler()
+ self.threadpool = FakeThreadPool()
+ self.handler = FakeHandler(True)
+ self.task1 = Task(2, "task1")
+ self.task2 = Task(3, "task2")
+ self.task3 = Task(4, "task3")
+ samplings = [self.task1, self.task2, self.task3]
+ self.sampler = sampling.Sampler("vm-id", samplings, self.handler,
+ scheduler=self.scheduler,
+ threadpool=self.threadpool)
+
+ def test_start(self):
+ self.sampler.start()
+ self.assertEquals(self.scheduler.call.delay, 2)
+ self.assertEquals(self.scheduler.call.func, self.sampler._dispatch)
+ self.assertEquals(self.threadpool.tasks, [])
+
+ def test_stop(self):
+ self.sampler.start()
+ self.sampler.stop()
+ self.assertEquals(self.scheduler.call.func, INVALID)
+ self.assertEquals(self.threadpool.tasks, [])
+
+ def test_dispatch(self):
+ self.sampler.start()
+ self.scheduler.fire()
+ self.assertEquals(self.threadpool.tasks,
+ [("vm-id_task1", self.sampler)])
+
+ def test_run_samplings(self):
+ self.sampler.start()
+
+ # time = 2
+ self.scheduler.fire()
+ self.threadpool.run()
+ self.assertEquals(self.scheduler.call.delay, 1)
+ self.assertEquals(self.task1.executed, 1)
+
+ # time = 3
+ self.scheduler.fire()
+ self.threadpool.run()
+ self.assertEquals(self.task2.executed, 1)
+ self.assertEquals(self.scheduler.call.delay, 1)
+
+ # time = 4
+ self.scheduler.fire()
+ self.threadpool.run()
+ self.assertEquals(self.task1.executed, 2)
+ self.assertEquals(self.scheduler.call.delay, 0)
+
+ # time = 4
+ self.scheduler.fire()
+ self.threadpool.run()
+ self.assertEquals(self.task3.executed, 1)
+ self.assertEquals(self.scheduler.call.delay, 2)
+
+ # time = 6
+ self.scheduler.fire()
+ self.threadpool.run()
+ self.assertEquals(self.task1.executed, 3)
+ self.assertEquals(self.scheduler.call.delay, 0)
+
+ # time = 6
+ self.scheduler.fire()
+ self.threadpool.run()
+ self.assertEquals(self.task2.executed, 2)
+ self.assertEquals(self.scheduler.call.delay, 2)
+
+
+class SamplerHandlerTests(TestCaseBase):
+
+ def test_does_handle_error(self):
+ self.check(FakeHandler(True))
+
+ def test_does_not_handle_error(self):
+ self.check(FakeHandler(False))
+
+ def check(self, handler):
+ scheduler = FakeScheduler()
+ threadpool = FakeThreadPool()
+ error = Exception("Sampling task failed")
+ task = Task(2, "task", error=error)
+ sampler = sampling.Sampler("vm-id", [task], handler,
+ scheduler=scheduler, threadpool=threadpool)
+ sampler.start()
+ scheduler.fire()
+ threadpool.run()
+ self.assertEquals(handler.error, error)
+
+
+# Helpers
+
+
+class FakeHandler(object):
+
+ def __init__(self, result):
+ self.error = None
+ self.result = result
+
+ def handleStatsException(self, e):
+ self.error = e
+ return self.result
+
+
+class FakeScheduler(object):
+
+ def __init__(self):
+ # Schedule only single call to simplify the tests
+ self.call = None
+
+ def schedule(self, delay, func):
+ assert self.call is None
+ self.call = Call(delay, func)
+ return self.call
+
+ def fire(self):
+ call = self.call
+ self.call = None
+ call.func()
+
+
+class Call(object):
+
+ def __init__(self, delay, func):
+ self.delay = delay
+ self.func = func
+
+ def cancel(self):
+ self.func = INVALID
+
+
+def INVALID():
+ pass
+
+
+class FakeThreadPool(object):
+
+ def __init__(self):
+ self.tasks = []
+
+ def queueTask(self, id, task):
+ # Executes task on a worker thread in the real object. Does nothing
+ # here to simplify the tests.
+ self.tasks.append((id, task))
+
+ def run(self):
+ # Run next task manually to simplify the tests
+ task_id, task = self.tasks.pop()
+ task()
diff --git a/tests/vmApiTests.py b/tests/vmApiTests.py
index bc68c6d..ae2f98f 100644
--- a/tests/vmApiTests.py
+++ b/tests/vmApiTests.py
@@ -22,6 +22,7 @@
import os
import os.path
+from virt import sampling
from virt import vmexitreason
from vdsm import define
from testrunner import VdsmTestCase as TestCaseBase
@@ -56,11 +57,15 @@
@contextmanager
def ensureVmStats(vm):
- vm._initVmStats()
+ sampling.start()
try:
- yield vm
+ vm._initVmStats()
+ try:
+ yield vm
+ finally:
+ vm._vmStats.stop()
finally:
- vm._vmStats.stop()
+ sampling.stop()
class TestVmStats(TestSchemaCompliancyBase):
diff --git a/tests/vmTests.py b/tests/vmTests.py
index fda71f5..3ef04f9 100644
--- a/tests/vmTests.py
+++ b/tests/vmTests.py
@@ -1338,7 +1338,7 @@
self.assertEqual(stats['exitMessage'], msg)
-class TestVmStatsThread(TestCaseBase):
+class TestVmStats(TestCaseBase):
VM_PARAMS = {'displayPort': -1, 'displaySecurePort': -1,
'display': 'qxl', 'displayIp':
'127.0.0.1',
'vmType': 'kvm', 'memSize': 1024}
@@ -1349,8 +1349,8 @@
GBPS = 10 ** 9 / 8
MAC = '52:54:00:59:F5:3F'
with FakeVM() as fake:
- mock_stats_thread = vm.VmStatsThread(fake)
- res = mock_stats_thread._getNicStats(
+ mock_stats = vm.VmStats(fake)
+ res = mock_stats._getNicStats(
name='vnettest', model='virtio', mac=MAC,
start_sample=(2 ** 64 - 15 * GBPS, 1, 2, 3, 0, 4, 5, 6),
end_sample=(0, 7, 8, 9, 5 * GBPS, 10, 11, 12),
@@ -1366,9 +1366,9 @@
# bz1073478 - main case
with FakeVM(self.VM_PARAMS, self.DEV_BALLOON) as fake:
self.assertEqual(fake._dom, None)
- mock_stats_thread = vm.VmStatsThread(fake)
+ mock_stats = vm.VmStats(fake)
res = {}
- mock_stats_thread._getBalloonStats(res)
+ mock_stats._getBalloonStats(res)
self.assertIn('balloonInfo', res)
self.assertIn('balloon_cur', res['balloonInfo'])
@@ -1376,9 +1376,9 @@
# bz1073478 - extra case
with FakeVM(self.VM_PARAMS, self.DEV_BALLOON) as fake:
fake._dom = FakeDomain()
- mock_stats_thread = vm.VmStatsThread(fake)
+ mock_stats = vm.VmStats(fake)
res = {}
- mock_stats_thread._getBalloonStats(res)
+ mock_stats._getBalloonStats(res)
self.assertIn('balloonInfo', res)
self.assertIn('balloon_cur', res['balloonInfo'])
diff --git a/vdsm/vdsm b/vdsm/vdsm
index cbc16ff..2343606 100755
--- a/vdsm/vdsm
+++ b/vdsm/vdsm
@@ -38,6 +38,8 @@
from storage.dispatcher import Dispatcher
from storage.hsm import HSM
+from virt import sampling
+
import zombiereaper
import dsaversion
@@ -71,6 +73,7 @@
profile.start()
libvirtconnection.start_event_loop()
+ sampling.start()
if config.getboolean('irs', 'irs_enable'):
try:
@@ -88,6 +91,7 @@
profile.stop()
finally:
cif.prepareForShutdown()
+ sampling.stop()
def run(pidfile=None):
diff --git a/vdsm/virt/sampling.py b/vdsm/virt/sampling.py
index 3479eee..0cd823a 100644
--- a/vdsm/virt/sampling.py
+++ b/vdsm/virt/sampling.py
@@ -26,24 +26,50 @@
Contains a reverse dictionary pointing from error string to its error code.
"""
-import threading
-import os
-import time
-import logging
import errno
import ethtool
+import itertools
+import logging
+import operator
+import os
import re
+import threading
+import time
+from vdsm.config import config
from vdsm import utils
from vdsm import netinfo
+from vdsm import schedule
from vdsm.ipwrapper import getLinks
from vdsm.constants import P_VDSM_RUN
import caps
+from storage import threadPool
_THP_STATE_PATH = '/sys/kernel/mm/transparent_hugepage/enabled'
if not os.path.exists(_THP_STATE_PATH):
_THP_STATE_PATH = '/sys/kernel/mm/redhat_transparent_hugepage/enabled'
+
+
+_scheduler = None
+_threadpool = None
+
+
+def start():
+ """ Called during application startup """
+ global _scheduler, _threadpool
+ threads = config.getint('vars', 'vm_sampling_threads')
+ tasks = config.getint('vars', 'vm_sampling_tasks')
+ _threadpool = threadPool.ThreadPool(threads, maxTasks=tasks)
+ _scheduler = schedule.Scheduler("sampling.scheduler")
+
+
+def stop():
+ """ Called during application shutdown """
+ if _threadpool:
+ _threadpool.joinAll(waitForTasks=False, waitForThreads=False)
+ if _scheduler:
+ _scheduler.cancel()
class InterfaceSample:
@@ -318,9 +344,13 @@
def interval(self):
return self._interval
+ @property
+ def name(self):
+ return self._function.__name__
+
def __repr__(self):
return "<AdvancedStatsFunction %s at 0x%x>" % (
- self._function.__name__, id(self._function.__name__))
+ self.name, id(self))
def __call__(self, *args, **kwargs):
retValue = self._function(*args, **kwargs)
@@ -347,115 +377,136 @@
return bgn_sample, end_sample, (end_time - bgn_time)
-class AdvancedStatsThread(threading.Thread):
+class Sampler(object):
"""
- A thread that runs the registered AdvancedStatsFunction objects
- for statistic and monitoring purpose.
+ Execute sampling tasks on the threadpool according to sampling schedule.
+
+ The sepcified samplings tasks are executed on threadpool one at a time, as
+ if there was a thread running them one after another in a loop.
+
+ If one of the sampling task get stuck, the rest of the sampling tasks are
+ delayed. This is important to prevent flooding of the thread pool with
+ possibly blocking sampling task, when the underlying libvirt thread is
+ stuck. When a stuck sampling tasks finish, the sampling tasks continue
+ normally.
+
+ When a sampling task is stuck, no other task can run on the blocked worker
+ thread. The threadpool is responsible for detecting and handling this.
+
+ Each vm should create one sampler, to ensure that there is only one
+ sampling task per vm excuting on the threadpool.
"""
- DEFAULT_LOG = logging.getLogger("AdvancedStatsThread")
- def __init__(self, log=DEFAULT_LOG, daemon=False):
- """
- Initialize an AdvancedStatsThread object
- """
- threading.Thread.__init__(self)
- self.daemon = daemon
+ _log = logging.getLogger("Sampler")
- self._log = log
- self._stopEvent = threading.Event()
- self._contEvent = threading.Event()
-
- self._statsTime = None
- self._statsFunctions = []
-
- def addStatsFunction(self, *args):
- """
- Register the functions listed as arguments
- """
- if self.isAlive():
- raise RuntimeError("AdvancedStatsThread is started")
-
- for statsFunction in args:
- self._statsFunctions.append(statsFunction)
+ def __init__(self, vm_id, samplings, handler, scheduler=None,
+ threadpool=None):
+ self._vm_id = vm_id
+ self._samplings = samplings
+ self._handler = handler
+ self._scheduler = scheduler or _scheduler
+ self._threadpool = threadpool or _threadpool
+ self._lock = threading.Lock()
+ self._running = False
+ self._iterator = None
+ self._task = _INVALID
+ self._call = None
+ self._last_sample_time = 0 # Not sample taken yet
def start(self):
- """
- Start the execution of the thread and exit
- """
- self._log.debug("Start statistics collection")
- threading.Thread.start(self)
+ with self._lock:
+ if self._running:
+ raise AssertionError("Sampler is running")
+ self._log.debug("Starting sampler for vm %s", self._vm_id)
+ self._running = True
+ self._iterator = delays(cycle(self._samplings))
+ self._schedule_next_task()
def stop(self):
+ with self._lock:
+ if self._running:
+ self._log.debug("Stopping sampler for vm %s", self._vm_id)
+ self._running = False
+ self._call.cancel()
+ self._call = None
+ self._task = _INVALID
+ self._iterator = None
+
+ @property
+ def last_sample_time(self):
+ return self._last_sample_time
+
+ # Task interface
+
+ def __call__(self):
+ try:
+ self._last_sample_time = time.time()
+ self._task()
+ except Exception as e:
+ if not self._handler.handleStatsException(e):
+ self._log.exception("Stats function failed: %s", self._task)
+ finally:
+ with self._lock:
+ if self._running:
+ self._schedule_next_task()
+
+ # Private
+
+ def _dispatch(self):
"""
- Stop the execution of the thread and exit
+ Called from the scheduler thread when its time to ran the current
+ sampling task.
"""
- self._log.debug("Stop statistics collection")
- self._stopEvent.set()
- self._contEvent.set()
+ with self._lock:
+ if self._running:
+ task_id = self._vm_id + '_' + self._task.name
+ self._threadpool.queueTask(task_id, self)
- def pause(self):
- """
- Pause the execution of the registered functions
- """
- self._log.debug("Pause statistics collection")
- self._contEvent.clear()
+ def _schedule_next_task(self):
+ delay, self._task = next(self._iterator)
+ self._call = self._scheduler.schedule(delay, self._dispatch)
- def cont(self):
- """
- Resume the execution of the registered functions
- """
- self._log.debug("Resume statistics collection")
- self._contEvent.set()
- def getLastSampleTime(self):
- return self._statsTime
+# Sentinel used to mark a task as invalid, allowing running sampling task
+# without holding a lock.
+def _INVALID():
+ pass
- def run(self):
- self._log.debug("Stats thread started")
- self._contEvent.set()
- while not self._stopEvent.isSet():
- try:
- self.collect()
- except:
- self._log.debug("Stats thread failed", exc_info=True)
+def delays(virtual_times):
+ """
+ Accept stream of tuples (virtual_time, task) and return stream of tuples
+ (delay, task).
+ """
+ last_virtual_time = 0
+ for virtual_time, task in virtual_times:
+ delay = virtual_time - last_virtual_time
+ last_virtual_time = virtual_time
+ yield delay, task
- self._log.debug("Stats thread finished")
- def handleStatsException(self, ex):
- """
- Handle the registered function exceptions and eventually stop the
- sampling if a fatal error occurred.
- """
- return False
+def cycle(samplings):
+ """
+ Returns endless stream of tuples (virtual_time, task)
+ """
+ samplings = group_by_interval(samplings)
+ virtual_time = 1
+ while True:
+ for interval, tasks in samplings:
+ if virtual_time % interval == 0:
+ for task in tasks:
+ yield virtual_time, task
+ virtual_time += 1
- def collect(self):
- # TODO: improve this with lcm
- _mInt = map(lambda x: x.interval, self._statsFunctions)
- maxInterval = reduce(lambda x, y: x * y, set(_mInt), 1)
- intervalAccum = 0
- while not self._stopEvent.isSet():
- self._contEvent.wait()
-
- self._statsTime = time.time()
- waitInterval = maxInterval
-
- for statsFunction in self._statsFunctions:
- thisInterval = statsFunction.interval - (
- intervalAccum % statsFunction.interval)
- waitInterval = min(waitInterval, thisInterval)
-
- if intervalAccum % statsFunction.interval == 0:
- try:
- statsFunction()
- except Exception as e:
- if not self.handleStatsException(e):
- self._log.error("Stats function failed: %s",
- statsFunction, exc_info=True)
-
- self._stopEvent.wait(waitInterval)
- intervalAccum = (intervalAccum + waitInterval) % maxInterval
+def group_by_interval(samplings):
+ """
+ Groups samplings by interval.
+ """
+ keyfn = operator.attrgetter('interval')
+ samplings = sorted(samplings, key=keyfn)
+ return [(interval, tuple(tasks))
+ for interval, tasks in itertools.groupby(samplings, keyfn)]
class HostStatsThread(threading.Thread):
diff --git a/vdsm/virt/vm.py b/vdsm/virt/vm.py
index 7835e99..f1d8983 100644
--- a/vdsm/virt/vm.py
+++ b/vdsm/virt/vm.py
@@ -65,7 +65,7 @@
from . import vmstatus
from .vmtune import updateIoTuneDom
-from .sampling import AdvancedStatsFunction, AdvancedStatsThread
+from .sampling import AdvancedStatsFunction, Sampler
from .utils import isVdsmImage, XMLElement
from vmpowerdown import VmShutdown, VmReboot
@@ -167,7 +167,7 @@
pass
-class VmStatsThread(AdvancedStatsThread):
+class VmStats(object):
MBPS_TO_BPS = 10 ** 6 / 8
# CPU tune sampling window
@@ -184,7 +184,6 @@
_libvirt_metadata_supported = True
def __init__(self, vm):
- AdvancedStatsThread.__init__(self, log=vm.log, daemon=True)
self._vm = vm
self.highWrite = (
@@ -237,11 +236,18 @@
config.getint('vars', 'vm_sample_cpu_tune_interval'),
self.CPU_TUNE_SAMPLING_WINDOW))
- self.addStatsFunction(
- self.highWrite, self.updateVolumes, self.sampleCpu,
- self.sampleDisk, self.sampleDiskLatency, self.sampleNet,
- self.sampleBalloon, self.sampleVmJobs, self.sampleVcpuPinning,
- self.sampleCpuTune)
+ samplings = (self.highWrite, self.updateVolumes, self.sampleCpu,
+ self.sampleDisk, self.sampleDiskLatency, self.sampleNet,
+ self.sampleBalloon, self.sampleVmJobs,
+ self.sampleVcpuPinning, self.sampleCpuTune)
+
+ self._sampler = Sampler(self._vm.id, samplings, self)
+
+ def start(self):
+ self._sampler.start()
+
+ def stop(self):
+ self._sampler.stop()
def _highWrite(self):
if not self._vm.isDisksStatsCollectionEnabled():
@@ -311,13 +317,13 @@
metadataCpuLimit = None
try:
- if VmStatsThread._libvirt_metadata_supported:
+ if VmStats._libvirt_metadata_supported:
metadataCpuLimit = self._vm._dom.metadata(
libvirt.VIR_DOMAIN_METADATA_ELEMENT,
METADATA_VM_TUNE_URI, 0)
except libvirt.libvirtError as e:
if e.get_error_code() == libvirt.VIR_ERR_ARGUMENT_UNSUPPORTED:
- VmStatsThread._libvirt_metadata_supported = False
+ VmStats._libvirt_metadata_supported = False
self._log.error("libvirt does not support metadata")
elif (e.get_error_code()
@@ -581,7 +587,7 @@
stats = {}
try:
- stats['statsAge'] = time.time() - self.getLastSampleTime()
+ stats['statsAge'] = time.time() - self._sampler.last_sample_time
except TypeError:
self._log.debug("Stats age not available")
stats['statsAge'] = -1.0
@@ -2778,7 +2784,7 @@
WARNING: this method should only gather statistics by copying data.
Especially avoid costly and dangerous ditrect calls to the _dom
- attribute. Use the VmStatsThread instead!
+ attribute. Use the VmStats instead!
"""
if self.lastStatus == vmstatus.DOWN:
@@ -3059,7 +3065,7 @@
return domxml.toxml()
def _initVmStats(self):
- self._vmStats = VmStatsThread(self)
+ self._vmStats = VmStats(self)
self._vmStats.start()
self._guestEventTime = self._startTime
@@ -3174,7 +3180,7 @@
if drive['device'] == 'disk' and isVdsmImage(drive):
self._syncVolumeChain(drive)
- # VmStatsThread may use block devices info from libvirt.
+ # VmStats may use block devices info from libvirt.
# So, run it after you have this info
self._initVmStats()
self.guestAgent = guestagent.GuestAgent(
--
To view, visit
http://gerrit.ovirt.org/29980
To unsubscribe, visit
http://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I5539a728f3a1b3075712331440eff1749da6c530
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Nir Soffer <nsoffer(a)redhat.com>