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:
6 # http://www.apache.org/licenses/LICENSE-2.0
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.
14 """Drop rate search algorithms"""
16 from abc import ABCMeta, abstractmethod
17 from enum import Enum, unique
20 class SearchDirection(Enum):
21 """Direction of linear search."""
27 class SearchResults(Enum):
28 """Result of the drop rate search."""
36 """Type of rate units."""
39 PACKETS_PER_SECOND = 2
43 class LossAcceptanceType(Enum):
44 """Type of the loss acceptance criteria."""
50 class SearchResultType(Enum):
51 """Type of search result evaluation."""
56 class DropRateSearch(object):
57 """Abstract class with search algorithm implementation."""
59 __metaclass__ = ABCMeta
62 #duration of traffic run (binary, linear)
64 #initial start rate (binary, linear)
65 self._rate_start = 100
66 #step of the linear search, unit: RateType (self._rate_type)
67 self._rate_linear_step = 10
68 #last rate of the binary search, unit: RateType (self._rate_type)
69 self._last_binary_rate = 0
70 #linear search direction, permitted values: SearchDirection
71 self._search_linear_direction = SearchDirection.TOP_DOWN
72 #upper limit of search, unit: RateType (self._rate_type)
74 #lower limit of search, unit: RateType (self._rate_type)
76 #permitted values: RateType
77 self._rate_type = RateType.PERCENTAGE
78 #accepted loss during search, units: LossAcceptanceType
79 self._loss_acceptance = 0
80 #permitted values: LossAcceptanceType
81 self._loss_acceptance_type = LossAcceptanceType.FRAMES
82 #size of frames to send
83 self._frame_size = "64"
84 #binary convergence criterium type is self._rate_type
85 self._binary_convergence_threshold = 100000
86 #numbers of traffic runs during one rate step
87 self._max_attempts = 1
88 #type of search result evaluation, unit: SearchResultType
89 self._search_result_type = SearchResultType.BEST_OF_N
92 self._search_result = None
93 self._search_result_rate = None
96 def measure_loss(self, rate, frame_size, loss_acceptance,
97 loss_acceptance_type, traffic_type):
98 """Send traffic from TG and measure count of dropped frames.
100 :param rate: offered traffic load
101 :param frame_size: size of frame
102 :param loss_acceptance: permitted drop ratio or frames count
103 :param loss_acceptance_type: type of permitted loss
104 :param traffic_type: traffic profile ([2,3]-node-L[2,3], ...)
106 :type frame_size: str
107 :type loss_acceptance: float
108 :type loss_acceptance_type: LossAcceptanceType
109 :type traffic_type: str
110 :return: drop threshold exceeded? (True/False)
115 def set_search_rate_boundaries(self, max_rate, min_rate):
116 """Set search boundaries: min,max.
118 :param max_rate: upper value of search boundaries
119 :param min_rate: lower value of search boundaries
120 :type max_rate: float
121 :type min_rate: float
124 if float(min_rate) <= 0:
125 raise ValueError("min_rate must be higher than 0")
126 elif float(min_rate) > float(max_rate):
127 raise ValueError("min_rate must be lower than max_rate")
129 self._rate_max = float(max_rate)
130 self._rate_min = float(min_rate)
132 def set_search_linear_step(self, step_rate):
133 """Set step size for linear search.
135 :param step_rate: linear search step size
136 :type step_rate: float
139 self._rate_linear_step = float(step_rate)
141 def set_search_rate_type_percentage(self):
142 """Set rate type to percentage of linerate.
146 self._set_search_rate_type(RateType.PERCENTAGE)
148 def set_search_rate_type_bps(self):
149 """Set rate type to bits per second.
153 self._set_search_rate_type(RateType.BITS_PER_SECOND)
155 def set_search_rate_type_pps(self):
156 """Set rate type to packets per second.
160 self._set_search_rate_type(RateType.PACKETS_PER_SECOND)
162 def _set_search_rate_type(self, rate_type):
163 """Set rate type to one of RateType-s.
165 :param rate_type: type of rate to set
166 :type rate_type: RateType
169 if rate_type not in RateType:
170 raise Exception("rate_type unknown: {}".format(rate_type))
172 self._rate_type = rate_type
174 def set_search_frame_size(self, frame_size):
175 """Set size of frames to send.
177 :param frame_size: size of frames
178 :type frame_size: str
181 self._frame_size = frame_size
183 def set_duration(self, duration):
184 """Set the duration of single traffic run.
186 :param duration: number of seconds for traffic to run
190 self._duration = int(duration)
192 def get_duration(self):
193 """Return configured duration of single traffic run.
195 :return: number of seconds for traffic to run
198 return self._duration
200 def set_binary_convergence_threshold(self, convergence):
201 """Set convergence for binary search.
203 :param convergence: treshold value number
204 :type convergence: float
207 self._binary_convergence_threshold = float(convergence)
209 def get_binary_convergence_threshold(self):
210 """Get convergence for binary search.
212 :return: treshold value number
215 return self._binary_convergence_threshold
217 def get_rate_type_str(self):
218 """Return rate type representation.
220 :return: string representation of rate type
223 if self._rate_type == RateType.PERCENTAGE:
225 elif self._rate_type == RateType.BITS_PER_SECOND:
227 elif self._rate_type == RateType.PACKETS_PER_SECOND:
230 raise ValueError("RateType unknown")
232 def set_max_attempts(self, max_attempts):
233 """Set maximum number of traffic runs during one rate step.
235 :param max_attempts: number of traffic runs
236 :type max_attempts: int
239 if int(max_attempts) > 0:
240 self._max_attempts = int(max_attempts)
242 raise ValueError("Max attempt must by greater then zero")
244 def get_max_attempts(self):
245 """Return maximum number of traffic runs during one rate step.
247 :return: number of traffic runs
250 return self._max_attempts
252 def set_search_result_type_best_of_n(self):
253 """Set type of search result evaluation to Best of N.
257 self._set_search_result_type(SearchResultType.BEST_OF_N)
259 def set_search_result_type_worst_of_n(self):
260 """Set type of search result evaluation to Worst of N.
264 self._set_search_result_type(SearchResultType.WORST_OF_N)
266 def _set_search_result_type(self, search_type):
267 """Set type of search result evaluation to one of SearchResultType.
269 :param search_type: type of search result evaluation to set
270 :type search_type: SearchResultType
273 if search_type not in SearchResultType:
274 raise ValueError("search_type unknown: {}".format(search_type))
276 self._search_result_type = search_type
278 def _get_best_of_n(self, res_list):
279 """Return best result of N traffic runs.
281 :param res_list: list of return values from all runs at one rate step
283 :return: True if at least one run is True, False otherwise
286 #Return True if any element of the iterable is True.
289 def _get_worst_of_n(self, res_list):
290 """Return worst result of N traffic runs.
292 :param res_list: list of return values from all runs at one rate step
294 :return: False if at least one run is False, True otherwise
297 #Return False if not all elements of the iterable are True.
298 return not all(res_list)
300 def _get_result_based_on_search_type(self, res_list):
301 """Return result of search based on search evaluation type.
303 :param res_list: list of return values from all runs at one rate step
305 :return: Boolean based on search result type
308 if self._search_result_type == SearchResultType.BEST_OF_N:
309 return self._get_best_of_n(res_list)
310 elif self._search_result_type == SearchResultType.WORST_OF_N:
311 return self._get_worst_of_n(res_list)
313 raise ValueError("Unknown search result type")
316 def linear_search(self, start_rate, traffic_type):
317 """Linear search of rate with loss below acceptance criteria.
319 :param start_rate: initial rate
320 :param traffic_type: traffic profile
321 :type start_rate: float
322 :param traffic_type: str
326 if not self._rate_min <= float(start_rate) <= self._rate_max:
327 raise ValueError("Start rate is not in min,max range")
329 rate = float(start_rate)
330 #the last but one step
336 for n in range(self._max_attempts):
337 res.append(self.measure_loss(rate, self._frame_size,
338 self._loss_acceptance,
339 self._loss_acceptance_type,
342 res = self._get_result_based_on_search_type(res)
344 if self._search_linear_direction == SearchDirection.BOTTOM_UP:
345 #loss occured and it was above acceptance criteria
347 #if this is first run then we didn't find drop rate
348 if prev_rate == None:
349 self._search_result = SearchResults.FAILURE
350 self._search_result_rate = None
352 # else we found the rate, which is value from previous run
354 self._search_result = SearchResults.SUCCESS
355 self._search_result_rate = prev_rate
357 #there was no loss / loss below acceptance criteria
360 rate += self._rate_linear_step
361 if rate > self._rate_max:
362 if prev_rate != self._rate_max:
363 #one last step with rate set to _rate_max
364 rate = self._rate_max
367 self._search_result = SearchResults.SUCCESS
368 self._search_result_rate = prev_rate
373 raise RuntimeError("Unknown search result")
375 elif self._search_linear_direction == SearchDirection.TOP_DOWN:
376 #loss occured, decrease rate
379 rate -= self._rate_linear_step
380 if rate < self._rate_min:
381 if prev_rate != self._rate_min:
382 #one last step with rate set to _rate_min
383 rate = self._rate_min
386 self._search_result = SearchResults.FAILURE
387 self._search_result_rate = None
391 #no loss => non/partial drop rate found
393 self._search_result = SearchResults.SUCCESS
394 self._search_result_rate = rate
397 raise RuntimeError("Unknown search result")
399 raise Exception("Unknown search direction")
401 raise Exception("Wrong codepath")
403 def verify_search_result(self):
404 """Fail if search was not successful.
409 if self._search_result == SearchResults.FAILURE:
410 raise Exception('Search FAILED')
411 elif self._search_result in [SearchResults.SUCCESS, SearchResults.SUSPICIOUS]:
412 return self._search_result_rate
414 def binary_search(self, b_min, b_max, traffic_type):
415 """Binary search of rate with loss below acceptance criteria.
417 :param b_min: min range rate
418 :param b_max: max range rate
419 :param traffic_type: traffic profile
422 :type traffic_type: str
426 if not self._rate_min <= float(b_min) <= self._rate_max:
427 raise ValueError("Min rate is not in min,max range")
428 if not self._rate_min <= float(b_max) <= self._rate_max:
429 raise ValueError("Max rate is not in min,max range")
430 if float(b_max) < float(b_min):
431 raise ValueError("Min rate is greater then max rate")
434 #rate is half of interval + start of interval
435 rate = ((float(b_max) - float(b_min)) / 2) + float(b_min)
436 #rate diff with previous run
437 rate_diff = abs(self._last_binary_rate - rate)
439 #convergence criterium
440 if float(rate_diff) < float(self._binary_convergence_threshold):
441 if not self._search_result_rate:
442 self._search_result = SearchResults.FAILURE
444 self._search_result = SearchResults.SUCCESS
447 self._last_binary_rate = rate
450 for n in range(self._max_attempts):
451 res.append(self.measure_loss(rate, self._frame_size,
452 self._loss_acceptance,
453 self._loss_acceptance_type,
456 res = self._get_result_based_on_search_type(res)
458 #loss occured and it was above acceptance criteria
460 self.binary_search(b_min, rate, traffic_type)
461 #there was no loss / loss below acceptance criteria
463 self._search_result_rate = rate
464 self.binary_search(rate, b_max, traffic_type)
466 raise RuntimeError("Unknown search result")
468 def combined_search(self):
469 raise NotImplementedError