From cc8e109065be0bbbaf32e80757268488d5239231 Mon Sep 17 00:00:00 2001
From: Arnaud Morin <arnaud.morin@corp.ovh.com>
Date: Tue, 29 Jan 2019 17:22:25 +0100
Subject: [PATCH] Implement some new meters for vnic delta

Add two new meters called network.incoming.bytes.delta and
network.outgoing.bytes.delta that give the delta Bytes that were send or
received by an vNic.

Change-Id: Icf45a8d185cdb4a7b00a83586c98f998cbc0e928
Signed-off-by: Arnaud Morin <arnaud.morin@gmail.com>
---
 ceilometer/compute/pollsters/net.py           | 14 +++++++
 ceilometer/compute/virt/hyperv/inspector.py   |  4 +-
 ceilometer/compute/virt/inspector.py          |  3 +-
 ceilometer/compute/virt/libvirt/inspector.py  | 28 +++++++++++++-
 ceilometer/compute/virt/xenapi/inspector.py   |  3 +-
 .../tests/unit/compute/pollsters/test_net.py  | 37 ++++++++++++++++---
 setup.cfg                                     |  2 +
 7 files changed, 82 insertions(+), 9 deletions(-)

diff --git a/ceilometer/compute/pollsters/net.py b/ceilometer/compute/pollsters/net.py
index 3dd948990d..a452e802e0 100644
--- a/ceilometer/compute/pollsters/net.py
+++ b/ceilometer/compute/pollsters/net.py
@@ -109,3 +109,17 @@ class OutgoingErrorsPollster(NetworkPollster):
     sample_type = sample.TYPE_CUMULATIVE
     sample_unit = 'packet'
     sample_stats_key = 'tx_errors'
+
+
+class IncomingBytesDeltaPollster(NetworkPollster):
+    sample_name = 'network.incoming.bytes.delta'
+    sample_type = sample.TYPE_DELTA
+    sample_unit = 'B'
+    sample_stats_key = 'rx_bytes_delta'
+
+
+class OutgoingBytesDeltaPollster(NetworkPollster):
+    sample_name = 'network.outgoing.packets.delta'
+    sample_type = sample.TYPE_DELTA
+    sample_unit = 'B'
+    sample_stats_key = 'tx_bytes_delta'
diff --git a/ceilometer/compute/virt/hyperv/inspector.py b/ceilometer/compute/virt/hyperv/inspector.py
index 3d4fcb42dc..9fdd0de634 100644
--- a/ceilometer/compute/virt/hyperv/inspector.py
+++ b/ceilometer/compute/virt/hyperv/inspector.py
@@ -127,7 +127,9 @@ class HyperVInspector(virt_inspector.Inspector):
                 tx_bytes=vnic_metrics['tx_mb'] * units.Mi,
                 tx_packets=0,
                 tx_drop=0,
-                tx_errors=0)
+                tx_errors=0,
+                rx_bytes_delta=0,
+                tx_bytes_delta=0)
 
     @convert_exceptions(exception_conversion_map)
     def inspect_disks(self, instance, duration):
diff --git a/ceilometer/compute/virt/inspector.py b/ceilometer/compute/virt/inspector.py
index eb705168fa..0ec9bc7317 100644
--- a/ceilometer/compute/virt/inspector.py
+++ b/ceilometer/compute/virt/inspector.py
@@ -82,7 +82,8 @@ InterfaceStats = collections.namedtuple('InterfaceStats',
                                          'rx_bytes', 'tx_bytes',
                                          'rx_packets', 'tx_packets',
                                          'rx_drop', 'tx_drop',
-                                         'rx_errors', 'tx_errors'])
+                                         'rx_errors', 'tx_errors',
+                                         'rx_bytes_delta', 'tx_bytes_delta'])
 
 
 # Named tuple representing vNIC rate statistics.
diff --git a/ceilometer/compute/virt/libvirt/inspector.py b/ceilometer/compute/virt/libvirt/inspector.py
index b1e2c1aa29..b57107230c 100755
--- a/ceilometer/compute/virt/libvirt/inspector.py
+++ b/ceilometer/compute/virt/libvirt/inspector.py
@@ -38,6 +38,7 @@ class LibvirtInspector(virt_inspector.Inspector):
         super(LibvirtInspector, self).__init__(conf)
         # NOTE(sileht): create a connection on startup
         self.connection
+        self.cache = {}
 
     @property
     def connection(self):
@@ -115,6 +116,29 @@ class LibvirtInspector(virt_inspector.Inspector):
             params['bridge'] = bridge
 
             dom_stats = domain.interfaceStats(name)
+
+            # Retrieve previous values
+            prev = self.cache.get(name)
+
+            # Store values for next call
+            self.cache[name] = dom_stats
+
+            if prev:
+                # Compute stats
+                rx_delta = dom_stats[0] - prev[0]
+                tx_delta = dom_stats[4] - prev[4]
+
+                # Avoid negative values
+                if rx_delta < 0:
+                    rx_delta = dom_stats[0]
+                if tx_delta < 0:
+                    tx_delta = dom_stats[4]
+            else:
+                LOG.debug('No delta meter predecessor for %s / %s' %
+                          (instance.id, name))
+                rx_delta = 0
+                tx_delta = 0
+
             yield virt_inspector.InterfaceStats(name=name,
                                                 mac=mac_address,
                                                 fref=fref,
@@ -123,10 +147,12 @@ class LibvirtInspector(virt_inspector.Inspector):
                                                 rx_packets=dom_stats[1],
                                                 rx_errors=dom_stats[2],
                                                 rx_drop=dom_stats[3],
+                                                rx_bytes_delta=rx_delta,
                                                 tx_bytes=dom_stats[4],
                                                 tx_packets=dom_stats[5],
                                                 tx_errors=dom_stats[6],
-                                                tx_drop=dom_stats[7])
+                                                tx_drop=dom_stats[7],
+                                                tx_bytes_delta=tx_delta)
 
     @staticmethod
     def _get_disk_devices(domain):
diff --git a/ceilometer/compute/virt/xenapi/inspector.py b/ceilometer/compute/virt/xenapi/inspector.py
index 6fa6f53ea6..31adaacc73 100644
--- a/ceilometer/compute/virt/xenapi/inspector.py
+++ b/ceilometer/compute/virt/xenapi/inspector.py
@@ -141,7 +141,8 @@ class XenapiInspector(virt_inspector.Inspector):
                 parameters=None,
                 rx_bytes=bw_vif['bw_in'], rx_packets=-1, rx_drop=-1,
                 rx_errors=-1, tx_bytes=bw_vif['bw_out'], tx_packets=-1,
-                tx_drop=-1, tx_errors=-1)
+                tx_drop=-1, tx_errors=-1, rx_bytes_delta=-1,
+                tx_bytes_delta=-1)
 
     def inspect_vnic_rates(self, instance, duration):
         instance_name = util.instance_name(instance)
diff --git a/ceilometer/tests/unit/compute/pollsters/test_net.py b/ceilometer/tests/unit/compute/pollsters/test_net.py
index 63bbe6263f..190c93df41 100644
--- a/ceilometer/tests/unit/compute/pollsters/test_net.py
+++ b/ceilometer/tests/unit/compute/pollsters/test_net.py
@@ -50,7 +50,8 @@ class TestNetPollster(base.TestPollsterBase):
             rx_bytes=1, rx_packets=2,
             rx_drop=20, rx_errors=21,
             tx_bytes=3, tx_packets=4,
-            tx_drop=22, tx_errors=23)
+            tx_drop=22, tx_errors=23,
+            rx_bytes_delta=42, tx_bytes_delta=43)
 
         self.vnic1 = virt_inspector.InterfaceStats(
             name='vnet1',
@@ -63,7 +64,8 @@ class TestNetPollster(base.TestPollsterBase):
             rx_bytes=5, rx_packets=6,
             rx_drop=24, rx_errors=25,
             tx_bytes=7, tx_packets=8,
-            tx_drop=26, tx_errors=27)
+            tx_drop=26, tx_errors=27,
+            rx_bytes_delta=44, tx_bytes_delta=45)
 
         self.vnic2 = virt_inspector.InterfaceStats(
             name='vnet2',
@@ -76,7 +78,8 @@ class TestNetPollster(base.TestPollsterBase):
             rx_bytes=9, rx_packets=10,
             rx_drop=28, rx_errors=29,
             tx_bytes=11, tx_packets=12,
-            tx_drop=30, tx_errors=31)
+            tx_drop=30, tx_errors=31,
+            rx_bytes_delta=46, tx_bytes_delta=47)
 
         vnics = [
             self.vnic0,
@@ -117,7 +120,7 @@ class TestNetPollster(base.TestPollsterBase):
 
         self.faux_instance = FauxInstance(**self.INSTANCE_PROPERTIES)
 
-    def _check_get_samples(self, factory, expected):
+    def _check_get_samples(self, factory, expected, kind='cumulative'):
         mgr = manager.AgentManager(0, self.CONF)
         pollster = factory(self.CONF)
         samples = list(pollster.get_samples(mgr, {}, [self.instance]))
@@ -131,7 +134,7 @@ class TestNetPollster(base.TestPollsterBase):
                      ]
             self.assertEqual(len(match), 1, 'missing ip %s' % ip)
             self.assertEqual(expected_volume, match[0].volume)
-            self.assertEqual('cumulative', match[0].type)
+            self.assertEqual(kind, match[0].type)
             self.assertEqual(expected_rid, match[0].resource_id)
 
         for ip, volume, rid in expected:
@@ -159,6 +162,30 @@ class TestNetPollster(base.TestPollsterBase):
              ],
         )
 
+    def test_incoming_bytes_delta(self):
+        instance_name_id = "%s-%s" % (self.instance.name, self.instance.id)
+        self._check_get_samples(
+            net.IncomingBytesDeltaPollster,
+            [('10.0.0.2', 42, self.vnic0.fref),
+             ('192.168.0.3', 44, self.vnic1.fref),
+             ('192.168.0.4', 46,
+              "%s-%s" % (instance_name_id, self.vnic2.name)),
+             ],
+            'delta',
+        )
+
+    def test_outgoing_bytes_delta(self):
+        instance_name_id = "%s-%s" % (self.instance.name, self.instance.id)
+        self._check_get_samples(
+            net.OutgoingBytesDeltaPollster,
+            [('10.0.0.2', 43, self.vnic0.fref),
+             ('192.168.0.3', 45, self.vnic1.fref),
+             ('192.168.0.4', 47,
+              "%s-%s" % (instance_name_id, self.vnic2.name)),
+             ],
+            'delta',
+        )
+
     def test_incoming_packets(self):
         instance_name_id = "%s-%s" % (self.instance.name, self.instance.id)
         self._check_get_samples(
diff --git a/setup.cfg b/setup.cfg
index 004541a89c..d71985223d 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -89,6 +89,8 @@ ceilometer.poll.compute =
     network.outgoing.packets = ceilometer.compute.pollsters.net:OutgoingPacketsPollster
     network.incoming.bytes.rate = ceilometer.compute.pollsters.net:IncomingBytesRatePollster
     network.outgoing.bytes.rate = ceilometer.compute.pollsters.net:OutgoingBytesRatePollster
+    network.incoming.bytes.delta = ceilometer.compute.pollsters.net:IncomingBytesDeltaPollster
+    network.outgoing.bytes.delta = ceilometer.compute.pollsters.net:OutgoingBytesDeltaPollster
     network.incoming.packets.drop = ceilometer.compute.pollsters.net:IncomingDropPollster
     network.outgoing.packets.drop = ceilometer.compute.pollsters.net:OutgoingDropPollster
     network.incoming.packets.error = ceilometer.compute.pollsters.net:IncomingErrorsPollster