DropRateSearch library
[csit.git] / resources / libraries / python / DropRateSearch.py
1 # Copyright (c) 2016 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """Drop rate search algorithms"""
15
16 from abc import ABCMeta, abstractmethod
17 from enum import Enum, unique
18
19 @unique
20 class SearchDirection(Enum):
21     """Direction of linear search"""
22
23     TOP_DOWN = 1
24     BOTTOM_UP = 2
25
26 @unique
27 class SearchResults(Enum):
28     """Result of the drop rate search"""
29
30     SUCCESS = 1
31     FAILURE = 2
32     SUSPICIOUS = 3
33
34 @unique
35 class RateType(Enum):
36     """Type of rate units"""
37
38     PERCENTAGE = 1
39     PACKETS_PER_SECOND = 2
40     BITS_PER_SECOND = 3
41
42 @unique
43 class LossAcceptanceType(Enum):
44     """Type of the loss acceptance criteria"""
45
46     FRAMES = 1
47     PERCENTAGE = 2
48
49 class DropRateSearch(object):
50     """Abstract class with search algorithm implementation"""
51
52     __metaclass__ = ABCMeta
53
54     def __init__(self):
55         #duration of traffic run (binary, linear)
56         self._duration = 60
57         #initial start rate (binary, linear)
58         self._rate_start = 100
59         #step of the linear search, unit: RateType (self._rate_type)
60         self._rate_linear_step = 10
61         #linear search direction, permitted values: SearchDirection
62         self._search_linear_direction = SearchDirection.TOP_DOWN
63         #upper limit of search, unit: RateType (self._rate_type)
64         self._rate_max = 100
65         #lower limit of search, unit: RateType (self._rate_type)
66         self._rate_min = 1
67         #permitted values: RateType
68         self._rate_type = RateType.PERCENTAGE
69         #accepted loss during search, units: LossAcceptanceType
70         self._loss_acceptance = 0
71         #permitted values: LossAcceptanceType
72         self._loss_acceptance_type = LossAcceptanceType.FRAMES
73         #size of frames to send
74         self._frame_size = "64"
75         #binary convergence criterium type is self._rate_type
76         self._binary_convergence_threshhold = "0.01"
77         #numbers of traffic runs during one rate step
78         self._max_attempts = 1
79
80         #result of search
81         self._search_result = None
82         self._search_result_rate = None
83
84     @abstractmethod
85     def measure_loss(self, rate, frame_size, loss_acceptance,
86                      loss_acceptance_type, traffic_type):
87         """Send traffic from TG and measure count of dropped frames
88
89         :param rate: offered traffic load
90         :param frame_size: size of frame
91         :param loss_acceptance: permitted drop ratio or frames count
92         :param loss_acceptance_type: type of permitted loss
93         :param traffic_type: traffic profile ([2,3]-node-L[2,3], ...)
94         :type rate: int
95         :type frame_size: str
96         :type loss_acceptance: float
97         :type loss_acceptance_type: LossAcceptanceType
98         :type traffic_type: str
99         :return: drop threshhold exceeded? (True/False)
100         :rtype bool
101         """
102         pass
103
104     def set_search_rate_boundaries(self, max_rate, min_rate):
105         """Set search boundaries: min,max
106
107         :param max_rate: upper value of search boundaries
108         :param min_rate: lower value of search boundaries
109         :type max_rate: float
110         :type min_rate: float
111         :return: nothing
112         """
113         if float(min_rate) <= 0:
114             raise ValueError("min_rate must be higher than 0")
115         elif float(min_rate) > float(max_rate):
116             raise ValueError("min_rate must be lower than max_rate")
117         else:
118             self._rate_max = float(max_rate)
119             self._rate_min = float(min_rate)
120
121     def set_search_linear_step(self, step_rate):
122         """Set step size for linear search
123
124         :param step_rate: linear search step size
125         :type step_rate: float
126         :return: nothing
127         """
128         self._rate_linear_step = float(step_rate)
129
130     def set_search_rate_type_percentage(self):
131         """Set rate type to percentage of linerate
132
133         :return: nothing
134         """
135         self._set_search_rate_type(RateType.PERCENTAGE)
136
137     def set_search_rate_type_bps(self):
138         """Set rate type to bits per second
139
140         :return: nothing
141         """
142         self._set_search_rate_type(RateType.BITS_PER_SECOND)
143
144     def set_search_rate_type_pps(self):
145         """Set rate type to packets per second
146
147         :return: nothing
148         """
149         self._set_search_rate_type(RateType.PACKETS_PER_SECOND)
150
151     def _set_search_rate_type(self, rate_type):
152         """Set rate type to one of RateType-s
153
154         :param rate_type: type of rate to set
155         :type rate_type: RateType
156         :return: nothing
157         """
158         if rate_type not in RateType:
159             raise Exception("rate_type unknown: {}".format(rate_type))
160         else:
161             self._rate_type = rate_type
162
163     def set_search_frame_size(self, frame_size):
164         """Set size of frames to send
165
166         :param frame_size: size of frames
167         :type frame_size: str
168         :return: nothing
169         """
170         self._frame_size = frame_size
171
172     def set_duration(self, duration):
173         """Set the duration of single traffic run
174
175         :param duration: number of seconds for traffic to run
176         :type duration: int
177         :return: nothing
178         """
179         self._duration = int(duration)
180
181     def get_duration(self):
182         """Return configured duration of single traffic run
183
184         :return: number of seconds for traffic to run
185         :rtype: int
186         """
187         return self._duration
188
189     def get_rate_type_str(self):
190         """Return rate type representation
191
192         :return: string representation of rate type
193         :rtype: str
194         """
195         if self._rate_type == RateType.PERCENTAGE:
196             return "%"
197         elif self._rate_type == RateType.BITS_PER_SECOND:
198             return "bps"
199         elif self._rate_type == RateType.PACKETS_PER_SECOND:
200             return "pps"
201         else:
202             raise ValueError("RateType unknown")
203
204     def linear_search(self, start_rate, traffic_type):
205         """Linear search of rate with loss below acceptance criteria
206
207         :param start_rate: initial rate
208         :param traffic_type: traffic profile
209         :type start_rate: float
210         :param traffic_type: str
211         :return: nothing
212         """
213
214         if not self._rate_min <= float(start_rate) <= self._rate_max:
215             raise ValueError("Start rate is not in min,max range")
216
217         rate = float(start_rate)
218         #the last but one step
219         prev_rate = None
220
221         #linear search
222         while True:
223             res = self.measure_loss(rate, self._frame_size,
224                                     self._loss_acceptance,
225                                     self._loss_acceptance_type,
226                                     traffic_type)
227             if self._search_linear_direction == SearchDirection.BOTTOM_UP:
228                 #loss occured and it was above acceptance criteria
229                 if res == False:
230                     #if this is first run then we didn't find drop rate
231                     if prev_rate == None:
232                         self._search_result = SearchResults.FAILURE
233                         self._search_result_rate = None
234                         return
235                     # else we found the rate, which is value from previous run
236                     else:
237                         self._search_result = SearchResults.SUCCESS
238                         self._search_result_rate = prev_rate
239                         return
240                 #there was no loss / loss below acceptance criteria
241                 elif res == True:
242                     prev_rate = rate
243                     rate += self._rate_linear_step
244                     if rate > self._rate_max:
245                         if prev_rate != self._rate_max:
246                             #one last step with rate set to _rate_max
247                             rate = self._rate_max
248                             continue
249                         else:
250                             self._search_result = SearchResults.SUCCESS
251                             self._search_result_rate = prev_rate
252                             return
253                     else:
254                         continue
255                 else:
256                     raise RuntimeError("Unknown search result")
257
258             elif self._search_linear_direction == SearchDirection.TOP_DOWN:
259                 #loss occured, decrease rate
260                 if res == False:
261                     prev_rate = rate
262                     rate -= self._rate_linear_step
263                     if rate < self._rate_min:
264                         if prev_rate != self._rate_min:
265                             #one last step with rate set to _rate_min
266                             rate = self._rate_min
267                             continue
268                         else:
269                             self._search_result = SearchResults.FAILURE
270                             self._search_result_rate = None
271                             return
272                     else:
273                         continue
274                 #no loss => non/partial drop rate found
275                 elif res == True:
276                     self._search_result = SearchResults.SUCCESS
277                     self._search_result_rate = rate
278                     return
279                 else:
280                     raise RuntimeError("Unknown search result")
281             else:
282                 raise Exception("Unknown search direction")
283
284         raise Exception("Wrong codepath")
285
286     def verify_search_result(self):
287         """Fail if search was not successful
288
289         :return: result rate
290         :rtype: float
291         """
292         if self._search_result == SearchResults.FAILURE:
293             raise Exception('Search FAILED')
294         elif self._search_result in [SearchResults.SUCCESS, SearchResults.SUSPICIOUS]:
295             return self._search_result_rate
296
297     def binary_search(self):
298         raise NotImplementedError
299
300     def combined_search(self):
301         raise NotImplementedError