NFVBENCH-84 Report results with requested L2 frame size
[nfvbench.git] / nfvbench / stats_collector.py
1 # Copyright 2016 Cisco Systems, Inc.  All rights reserved.
2 #
3 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
4 #    not use this file except in compliance with the License. You may obtain
5 #    a copy of the License at
6 #
7 #         http://www.apache.org/licenses/LICENSE-2.0
8 #
9 #    Unless required by applicable law or agreed to in writing, software
10 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 #    License for the specific language governing permissions and limitations
13 #    under the License.
14
15
16 import time
17
18
19 class StatsCollector(object):
20     """Base class for all stats collector classes."""
21
22     def __init__(self, start_time):
23         self.start_time = start_time
24         self.stats = []
25
26     def get(self):
27         return self.stats
28
29     def peek(self):
30         return self.stats[-1]
31
32     @staticmethod
33     def _get_drop_percentage(drop_pkts, total_pkts):
34         return float(drop_pkts * 100) / total_pkts
35
36     @staticmethod
37     def _get_rx_pps(tx_pps, drop_percentage):
38         return (tx_pps * (100 - drop_percentage)) / 100
39
40     def _get_current_time_diff(self):
41         return int((time.time() - self.start_time) * 1000)
42
43
44 class IntervalCollector(StatsCollector):
45     """Collects stats while traffic is running. Frequency is specified by 'interval_sec' setting."""
46
47     last_tx_pkts = 0
48     last_rx_pkts = 0
49     last_time = 0
50
51     def __init__(self, start_time):
52         StatsCollector.__init__(self, start_time)
53         self.notifier = None
54
55     def attach_notifier(self, notifier):
56         self.notifier = notifier
57
58     def add(self, stats):
59         if self.notifier:
60             current_stats = self.__compute_tx_rx_diff(stats)
61             self.notifier.send_interval_stats(**current_stats)
62
63     def reset(self):
64         # don't reset time!
65         self.last_rx_pkts = 0
66         self.last_tx_pkts = 0
67
68     def add_ndr_pdr(self, tag, stats):
69         if self.notifier:
70
71             current_time = self._get_current_time_diff()
72             rx_pps = self._get_rx_pps(stats['tx_pps'], stats['drop_percentage'])
73
74             self.last_tx_pkts = stats['tx_pps'] / 1000 * (current_time - self.last_time)
75             self.last_rx_pkts = rx_pps / 1000 * (current_time - self.last_time)
76             self.last_time = current_time
77
78             # 'drop_pct' key is an unfortunate name, since in iteration stats it means
79             # number of the packets. More suitable would be 'drop_percentage'.
80             # FDS frontend depends on this key
81             current_stats = {
82                 '{}_pps'.format(tag): stats['tx_pps'],
83                 'tx_pps': stats['tx_pps'],
84                 'rx_pps': rx_pps,
85                 'drop_pct': stats['drop_percentage'],
86                 'time_ms': current_time
87             }
88
89             self.notifier.send_interval_stats(time_ms=current_stats['time_ms'],
90                                               tx_pps=current_stats['tx_pps'],
91                                               rx_pps=current_stats['rx_pps'],
92                                               drop_pct=current_stats['drop_pct'])
93             if tag == 'ndr':
94                 self.notifier.send_ndr_found(stats['tx_pps'])
95             else:
96                 self.notifier.send_pdr_found(stats['tx_pps'])
97
98     def __compute_tx_rx_diff(self, stats):
99         current_time = self._get_current_time_diff()
100         tx_diff = stats['overall']['tx']['total_pkts'] - self.last_tx_pkts
101         tx_pps = (tx_diff * 1000) / (current_time - self.last_time)
102         rx_diff = stats['overall']['rx']['total_pkts'] - self.last_rx_pkts
103         rx_pps = (rx_diff * 1000) / (current_time - self.last_time)
104
105         self.last_rx_pkts = stats['overall']['rx']['total_pkts']
106         self.last_tx_pkts = stats['overall']['tx']['total_pkts']
107         self.last_time = current_time
108
109         return {
110             'tx_pps': tx_pps,
111             'rx_pps': rx_pps,
112             'drop_pct': max(0.0, (1 - (float(rx_pps) / tx_pps)) * 100),
113             'time_ms': current_time
114         }
115
116
117 class IterationCollector(StatsCollector):
118     """Collects stats after traffic is stopped. Frequency is specified by 'duration_sec' setting."""
119
120     def __init__(self, start_time):
121         StatsCollector.__init__(self, start_time)
122
123     def add(self, stats, tx_pps):
124         drop_percentage = self._get_drop_percentage(stats['overall']['rx']['dropped_pkts'],
125                                                     stats['overall']['tx']['total_pkts'])
126
127         record = {
128             'total_tx_pps': int(stats['total_tx_rate']),
129             'tx_pps': tx_pps,
130             'tx_pkts': stats['overall']['tx']['total_pkts'],
131             'rx_pps': self._get_rx_pps(tx_pps, drop_percentage),
132             'rx_pkts': stats['overall']['rx']['total_pkts'],
133             'drop_pct': stats['overall']['rx']['dropped_pkts'],
134             'drop_percentage': drop_percentage,
135             'time_ms': int(time.time() * 1000)
136         }
137
138         if 'warning' in stats:
139             record['warning'] = stats['warning']
140
141         self.stats.append(record)
142
143     def add_ndr_pdr(self, tag, rate):
144         last_stats = self.peek()
145         last_stats['{}_pps'.format(tag)] = rate