trex_client.py revision af52e17f
1#!/router/bin/python
2
3# internal libs
4import sys
5import os
6import socket
7import errno
8import time
9import re
10import copy
11import binascii
12from distutils.util import strtobool
13from collections import deque, OrderedDict
14from json import JSONDecoder
15import traceback
16import signal
17
18try:
19    from . import outer_packages
20    from .trex_status_e import TRexStatus
21    from .trex_exceptions import *
22    from .trex_exceptions import exception_handler
23    from .general_utils import *
24except Exception as e: # is __main__
25    import outer_packages
26    from trex_status_e import TRexStatus
27    from trex_exceptions import *
28    from trex_exceptions import exception_handler
29    from general_utils import *
30
31# external libs
32import jsonrpclib
33from jsonrpclib import ProtocolError, AppError
34from enum import Enum
35
36
37
38class CTRexClient(object):
39    """
40    This class defines the client side of the RESTfull interaction with TRex
41    """
42
43    def __init__(self, trex_host, max_history_size = 100, filtered_latency_amount = 0.001, trex_daemon_port = 8090, master_daemon_port = 8091, trex_zmq_port = 4500, verbose = False, debug_image = False, trex_args = ''):
44        """
45        Instantiate a TRex client object, and connecting it to listening daemon-server
46
47        :parameters:
48             trex_host : str
49                a string of the TRex ip address or hostname.
50             max_history_size : int
51                a number to set the maximum history size of a single TRex run. Each sampling adds a new item to history.
52
53                default value : **100**
54
55             filtered_latency_amount : float
56                Ignore high latency for this ammount of packets. (by default take value of 99.9% measurements)
57
58                default value : **0.001**
59
60             trex_daemon_port : int
61                the port number on which the trex-daemon server can be reached
62
63                default value: **8090**
64             master_daemon_port : int
65                the port number on which the master-daemon server can be reached
66
67                default value: **8091**
68             trex_zmq_port : int
69                the port number on which trex's zmq module will interact with daemon server
70
71                default value: **4500**
72             verbose : bool
73                sets a verbose output on supported class method.
74
75                default value : **False**
76             trex_args : string
77                additional arguments passed to TRex. For example, "-w 3 --no-watchdog"
78
79        :raises:
80            socket errors, in case server could not be reached.
81
82        """
83        try:
84            self.trex_host          = socket.gethostbyname(trex_host)
85        except: # give it another try
86            self.trex_host          = socket.gethostbyname(trex_host)
87        self.trex_daemon_port       = trex_daemon_port
88        self.master_daemon_port     = master_daemon_port
89        self.trex_zmq_port          = trex_zmq_port
90        self.seq                    = None
91        self._last_sample           = time.time()
92        self.__default_user         = get_current_user()
93        self.verbose                = verbose
94        self.result_obj             = CTRexResult(max_history_size, filtered_latency_amount)
95        self.decoder                = JSONDecoder()
96        self.history                = jsonrpclib.history.History()
97        self.master_daemon_path     = "http://{hostname}:{port}/".format( hostname = self.trex_host, port = master_daemon_port )
98        self.master_daemon          = jsonrpclib.Server(self.master_daemon_path, history = self.history)
99        self.trex_server_path       = "http://{hostname}:{port}/".format( hostname = self.trex_host, port = trex_daemon_port )
100        self.server                 = jsonrpclib.Server(self.trex_server_path, history = self.history)
101        self.debug_image            = debug_image
102        self.trex_args              = trex_args
103        self.sample_to_run_finish   = self.sample_until_finish # alias for legacy
104
105
106    def add (self, x, y):
107        try:
108            return self.server.add(x,y)
109        except AppError as err:
110            self._handle_AppError_exception(err.args[0])
111        except ProtocolError:
112            raise
113        finally:
114            self.prompt_verbose_data()
115
116    def start_trex (self, f, d, block_to_success = True, timeout = 40, user = None, trex_development = False, **trex_cmd_options):
117        """
118        Request to start a TRex run on server in stateful mode.
119
120        :parameters:
121            f : str
122                a path (on server) for the injected traffic data (.yaml file)
123            d : int
124                the desired duration of the test. must be at least 30 seconds long.
125            block_to_success : bool
126                determine if this method blocks until TRex changes state from 'Starting' to either 'Idle' or 'Running'
127
128                default value : **True**
129            timeout : int
130                maximum time (in seconds) to wait in blocking state until TRex changes state from 'Starting' to either 'Idle' or 'Running'
131
132                default value: **40**
133            user : str
134                the identity of the the run issuer.
135            trex_cmd_options : key, val
136                sets desired TRex options using key=val syntax, separated by comma.
137                for keys with no value, state key=True
138
139        :return:
140            **True** on success
141
142        :raises:
143            + :exc:`ValueError`, in case 'd' parameter inserted with wrong value.
144            + :exc:`trex_exceptions.TRexError`, in case one of the trex_cmd_options raised an exception at server.
145            + :exc:`trex_exceptions.TRexInUseError`, in case TRex is already taken.
146            + :exc:`trex_exceptions.TRexRequestDenied`, in case TRex is reserved for another user than the one trying start TRex.
147            + ProtocolError, in case of error in JSON-RPC protocol.
148
149        """
150        user = user or self.__default_user
151        try:
152            d = int(d)
153        except ValueError:
154            raise ValueError('d parameter must be integer, specifying how long TRex run.')
155
156        trex_cmd_options.update( {'f' : f, 'd' : d} )
157        self.result_obj.latency_checked = 'l' in trex_cmd_options
158        if 'k' in trex_cmd_options:
159            timeout += int(trex_cmd_options['k']) # during 'k' seconds TRex stays in 'Starting' state
160
161        self.result_obj.clear_results()
162        try:
163            issue_time = time.time()
164            retval = self.server.start_trex(trex_cmd_options, user, block_to_success, timeout, False, self.debug_image, self.trex_args)
165        except AppError as err:
166            self._handle_AppError_exception(err.args[0])
167        except ProtocolError:
168            raise
169        finally:
170            self.prompt_verbose_data()
171
172        if retval!=0:
173            self.seq = retval   # update seq num only on successful submission
174            return True
175        else:   # TRex is has been started by another user
176            raise TRexInUseError('TRex is already being used by another user or process. Try again once TRex is back in IDLE state.')
177
178
179    def start_stateless(self, block_to_success = True, timeout = 40, user = None, **trex_cmd_options):
180        """
181        Request to start a TRex run on server in stateless mode.
182
183        :parameters:
184            block_to_success : bool
185                determine if this method blocks until TRex changes state from 'Starting' to either 'Idle' or 'Running'
186
187                default value : **True**
188            timeout : int
189                maximum time (in seconds) to wait in blocking state until TRex changes state from 'Starting' to either 'Idle' or 'Running'
190
191                default value: **40**
192            user : str
193                the identity of the the run issuer.
194            trex_cmd_options : key, val
195                sets desired TRex options using key=val syntax, separated by comma.
196                for keys with no value, state key=True
197
198        :return:
199            **True** on success
200
201        :raises:
202            + :exc:`trex_exceptions.TRexError`, in case one of the trex_cmd_options raised an exception at server.
203            + :exc:`trex_exceptions.TRexInUseError`, in case TRex is already taken.
204            + :exc:`trex_exceptions.TRexRequestDenied`, in case TRex is reserved for another user than the one trying start TRex.
205            + ProtocolError, in case of error in JSON-RPC protocol.
206
207        """
208        try:
209            user = user or self.__default_user
210            retval = self.server.start_trex(trex_cmd_options, user, block_to_success, timeout, True, self.debug_image, self.trex_args)
211        except AppError as err:
212            self._handle_AppError_exception(err.args[0])
213        except ProtocolError:
214            raise
215        finally:
216            self.prompt_verbose_data()
217
218        if retval!=0:
219            self.seq = retval   # update seq num only on successful submission
220            return True
221        else:   # TRex is has been started by another user
222            raise TRexInUseError('TRex is already being used by another user or process. Try again once TRex is back in IDLE state.')
223
224
225    def stop_trex (self):
226        """
227        Request to stop a TRex run on server.
228
229        The request is only valid if the stop initiator is the same client as the TRex run initiator.
230
231        :parameters:
232            None
233
234        :return:
235            + **True** on successful termination
236            + **False** if request issued but TRex wasn't running.
237
238        :raises:
239            + :exc:`trex_exceptions.TRexRequestDenied`, in case TRex ir running but started by another user.
240            + :exc:`trex_exceptions.TRexIncompleteRunError`, in case one of failed TRex run (unexpected termination).
241            + ProtocolError, in case of error in JSON-RPC protocol.
242
243        """
244        try:
245            return self.server.stop_trex(self.seq)
246        except AppError as err:
247            self._handle_AppError_exception(err.args[0])
248        except ProtocolError:
249            raise
250        finally:
251            self.prompt_verbose_data()
252
253    def force_kill (self, confirm = True):
254        """
255        Force killing of running TRex process (if exists) on the server.
256
257        .. tip:: This method is a safety method and **overrides any running or reserved resources**, and as such isn't designed to be used on a regular basis.
258                 Always consider using :func:`trex_client.CTRexClient.stop_trex` instead.
259
260        In the end of this method, TRex will return to IDLE state with no reservation.
261
262        :parameters:
263            confirm : bool
264                Prompt a user confirmation before continue terminating TRex session
265
266        :return:
267            + **True** on successful termination
268            + **False** otherwise.
269
270        :raises:
271            + ProtocolError, in case of error in JSON-RPC protocol.
272
273        """
274        if confirm:
275            prompt = "WARNING: This will terminate active TRex session indiscriminately.\nAre you sure? "
276            sys.stdout.write('%s [y/n]\n' % prompt)
277            while True:
278                try:
279                    if strtobool(user_input().lower()):
280                        break
281                    else:
282                        return
283                except ValueError:
284                    sys.stdout.write('Please respond with \'y\' or \'n\'.\n')
285        try:
286            return self.server.force_trex_kill()
287        except AppError as err:
288            # Silence any kind of application errors- by design
289            return False
290        except ProtocolError:
291            raise
292        finally:
293            self.prompt_verbose_data()
294
295    def kill_all_trexes(self, timeout = 15):
296        """
297        Kills running TRex processes (if exists) on the server, not only owned by current daemon.
298        Raises exception upon error killing.
299
300        :return:
301            + **True** if processes killed/not running
302            + **False** otherwise.
303
304        """
305        try:
306            poll_rate = 0.1
307            # try Ctrl+C, usual kill, -9
308            for signal_name in [signal.SIGINT, signal.SIGTERM, signal.SIGKILL]:
309                self.server.kill_all_trexes(signal_name)
310                for i in range(int(timeout / poll_rate)):
311                    if not self.get_trex_cmds():
312                        return True
313                    time.sleep(poll_rate)
314            if self.get_trex_cmds():
315                return False
316            return True
317        except AppError as err:
318            self._handle_AppError_exception(err.args[0])
319        finally:
320            self.prompt_verbose_data()
321
322
323    def get_trex_cmds(self):
324        """
325        Gets list of running TRex pids and command lines.
326        Can be used to verify if any TRex is running.
327
328        :return:
329            List of tuples (pid, command) of running TRexes
330        """
331        try:
332            return self.server.get_trex_cmds()
333        except AppError as err:
334            self._handle_AppError_exception(err.args[0])
335        finally:
336            self.prompt_verbose_data()
337
338
339    def get_trex_path(self):
340        '''
341        Returns TRex path on server
342        '''
343        try:
344            return str(self.master_daemon.get_trex_path())
345        except AppError as err:
346            self._handle_AppError_exception(err.args[0])
347        finally:
348            self.prompt_verbose_data()
349
350
351    def wait_until_kickoff_finish(self, timeout = 40):
352        """
353        Block the client application until TRex changes state from 'Starting' to either 'Idle' or 'Running'
354
355        The request is only valid if the stop initiator is the same client as the TRex run initiator.
356
357        :parameters:
358            timeout : int
359                maximum time (in seconds) to wait in blocking state until TRex changes state from 'Starting' to either 'Idle' or 'Running'
360
361        :return:
362            + **True** on successful termination
363            + **False** if request issued but TRex wasn't running.
364
365        :raises:
366            + :exc:`trex_exceptions.TRexIncompleteRunError`, in case one of failed TRex run (unexpected termination).
367            + ProtocolError, in case of error in JSON-RPC protocol.
368
369            .. note::  Exceptions are throws only when start_trex did not block in the first place, i.e. `block_to_success` parameter was set to `False`
370
371        """
372
373        try:
374            return self.server.wait_until_kickoff_finish(timeout)
375        except AppError as err:
376            self._handle_AppError_exception(err.args[0])
377        except ProtocolError:
378            raise
379        finally:
380            self.prompt_verbose_data()
381
382    def is_running (self, dump_out = False):
383        """
384        Poll for TRex running status.
385
386        If TRex is running, a history item will be added into result_obj and processed.
387
388        .. tip:: This method is especially useful for iterating until TRex run is finished.
389
390        :parameters:
391            dump_out : dict
392                if passed, the pointer object is cleared and the latest dump stored in it.
393
394        :return:
395            + **True** if TRex is running.
396            + **False** if TRex is not running.
397
398        :raises:
399            + :exc:`trex_exceptions.TRexIncompleteRunError`, in case one of failed TRex run (unexpected termination).
400            + :exc:`TypeError`, in case JSON stream decoding error.
401            + ProtocolError, in case of error in JSON-RPC protocol.
402
403        """
404        try:
405            res = self.get_running_info()
406            if res == {}:
407                return False
408            if (dump_out != False) and (isinstance(dump_out, dict)):        # save received dump to given 'dump_out' pointer
409                dump_out.clear()
410                dump_out.update(res)
411            return True
412        except TRexWarning as err:
413            if err.code == -12:      # TRex is either still at 'Starting' state or in Idle state, however NO error occured
414                return False
415        except TRexException:
416            raise
417        except ProtocolError as err:
418            raise
419        #is printed by self.get_running_info()
420        #finally:
421        #    self.prompt_verbose_data()
422
423    def is_idle (self):
424        """
425        Poll for TRex running status, check if TRex is in Idle state.
426
427        :parameters:
428            None
429
430        :return:
431            + **True** if TRex is idle.
432            + **False** if TRex is starting or running.
433
434        :raises:
435            + :exc:`trex_exceptions.TRexIncompleteRunError`, in case one of failed TRex run (unexpected termination).
436            + :exc:`TypeError`, in case JSON stream decoding error.
437            + ProtocolError, in case of error in JSON-RPC protocol.
438
439        """
440        try:
441            if self.get_running_status()['state'] == TRexStatus.Idle:
442                return True
443            return False
444        except TRexException:
445            raise
446        except ProtocolError as err:
447            raise
448        finally:
449            self.prompt_verbose_data()
450
451    def get_trex_files_path (self):
452        """
453        Fetches the local path in which files are stored when pushed to TRex server from client.
454
455        :parameters:
456            None
457
458        :return:
459            string representation of the desired path
460
461            .. note::  The returned path represents a path on the TRex server **local machine**
462
463        :raises:
464            ProtocolError, in case of error in JSON-RPC protocol.
465
466        """
467        try:
468            return (self.server.get_files_path() + '/')
469        except AppError as err:
470            self._handle_AppError_exception(err.args[0])
471        except ProtocolError:
472            raise
473        finally:
474            self.prompt_verbose_data()
475
476    def get_running_status (self):
477        """
478        Fetches the current TRex status.
479
480        If available, a verbose data will accompany the state itself.
481
482        :parameters:
483            None
484
485        :return:
486            dictionary with 'state' and 'verbose' keys.
487
488        :raises:
489            ProtocolError, in case of error in JSON-RPC protocol.
490
491        """
492        try:
493            res = self.server.get_running_status()
494            res['state'] = TRexStatus(res['state'])
495            return res
496        except AppError as err:
497            self._handle_AppError_exception(err.args[0])
498        except ProtocolError:
499            raise
500        finally:
501            self.prompt_verbose_data()
502
503    def get_running_info (self):
504        """
505        Performs single poll of TRex running data and process it into the result object (named `result_obj`).
506
507        .. tip:: This method will throw an exception if TRex isn't running. Always consider using :func:`trex_client.CTRexClient.is_running` which handles a single poll operation in safer manner.
508
509        :parameters:
510            None
511
512        :return:
513            dictionary containing the most updated data dump from TRex.
514
515        :raises:
516            + :exc:`trex_exceptions.TRexIncompleteRunError`, in case one of failed TRex run (unexpected termination).
517            + :exc:`TypeError`, in case JSON stream decoding error.
518            + ProtocolError, in case of error in JSON-RPC protocol.
519
520        """
521        if not self.is_query_relevance():
522            # if requested in timeframe smaller than the original sample rate, return the last known data without interacting with server
523            return self.result_obj.get_latest_dump()
524        else:
525            try:
526                latest_dump = self.decoder.decode( self.server.get_running_info() ) # latest dump is not a dict, but json string. decode it.
527                self.result_obj.update_result_data(latest_dump)
528                return latest_dump
529            except TypeError as inst:
530                raise TypeError('JSON-RPC data decoding failed. Check out incoming JSON stream.')
531            except AppError as err:
532                self._handle_AppError_exception(err.args[0])
533            except ProtocolError:
534                raise
535            finally:
536                self.prompt_verbose_data()
537
538    def sample_until_condition (self, condition_func, time_between_samples = 1):
539        """
540        Automatically sets ongoing sampling of TRex data, with sampling rate described by time_between_samples.
541
542        On each fetched dump, the condition_func is applied on the result objects, and if returns True, the sampling will stop.
543
544        :parameters:
545            condition_func : function
546                function that operates on result_obj and checks if a condition has been met
547
548                .. note:: `condition_finc` is applied on `CTRexResult` object. Make sure to design a relevant method.
549            time_between_samples : int
550                determines the time between each sample of the server
551
552                default value : **1**
553
554        :return:
555            the first result object (see :class:`CTRexResult` for further details) of the TRex run on which the condition has been met.
556
557        :raises:
558            + :exc:`UserWarning`, in case the condition_func method condition hasn't been met
559            + :exc:`trex_exceptions.TRexIncompleteRunError`, in case one of failed TRex run (unexpected termination).
560            + :exc:`TypeError`, in case JSON stream decoding error.
561            + ProtocolError, in case of error in JSON-RPC protocol.
562            + :exc:`Exception`, in case the condition_func suffered from any kind of exception
563
564        """
565        # make sure TRex is running. raise exceptions here if any
566        self.wait_until_kickoff_finish()
567        try:
568            while self.is_running():
569                results = self.get_result_obj()
570                if condition_func(results):
571                    # if condition satisfied, stop TRex and return result object
572                    self.stop_trex()
573                    return results
574                time.sleep(time_between_samples)
575        except TRexWarning:
576            # means we're back to Idle state, and didn't meet our condition
577            raise UserWarning("TRex results condition wasn't met during TRex run.")
578        except Exception:
579            # this could come from provided method 'condition_func'
580            raise
581
582    def sample_until_finish (self, time_between_samples = 1):
583        """
584        Automatically samples TRex data with sampling rate described by time_between_samples until TRex run finishes.
585
586        :parameters:
587            time_between_samples : int
588                determines the time between each sample of the server
589
590                default value : **1**
591
592        :return:
593            the latest result object (see :class:`CTRexResult` for further details) with sampled data.
594
595        :raises:
596            + :exc:`UserWarning`, in case the condition_func method condition hasn't been met
597            + :exc:`trex_exceptions.TRexIncompleteRunError`, in case one of failed TRex run (unexpected termination).
598            + :exc:`TypeError`, in case JSON stream decoding error.
599            + ProtocolError, in case of error in JSON-RPC protocol.
600
601        """
602        self.wait_until_kickoff_finish()
603
604        try:
605            while self.is_running():
606                time.sleep(time_between_samples)
607        except TRexWarning:
608            pass
609        results = self.get_result_obj()
610        return results
611
612    def sample_x_seconds (self, sample_time, time_between_samples = 1):
613        """
614        Automatically sets ongoing sampling of TRex data for sample_time seconds, with sampling rate described by time_between_samples.
615        Does not stop the TRex afterwards!
616
617        .. tip:: Useful for changing the device (Router, ASA etc.) configuration after given time.
618
619        :parameters:
620            sample_time : int
621                sample the TRex this number of seconds
622
623            time_between_samples : int
624                determines the time between each sample of the server
625
626                default value : **1**
627
628        :return:
629            the first result object (see :class:`CTRexResult` for further details) of the TRex run after given sample_time.
630
631        :raises:
632            + :exc:`UserWarning`, in case the TRex run ended before sample_time duration
633            + :exc:`trex_exceptions.TRexIncompleteRunError`, in case one of failed TRex run (unexpected termination).
634            + :exc:`TypeError`, in case JSON stream decoding error.
635            + ProtocolError, in case of error in JSON-RPC protocol.
636
637        """
638        # make sure TRex is running. raise exceptions here if any
639        self.wait_until_kickoff_finish()
640        end_time = time.time() + sample_time
641        while self.is_running():
642            if time.time() < end_time:
643                time.sleep(time_between_samples)
644            else:
645                return self.get_result_obj()
646        raise UserWarning("TRex has stopped at %s seconds (before expected %s seconds)\nTry increasing test duration or decreasing sample_time" % (elapsed_time, sample_time))
647
648    def get_result_obj (self, copy_obj = True):
649        """
650        Returns the result object of the trex_client's instance.
651
652        By default, returns a **copy** of the objects (so that changes to the original object are masked).
653
654        :parameters:
655            copy_obj : bool
656                False means that a reference to the original (possibly changing) object are passed
657
658                defaul value : **True**
659
660        :return:
661            the latest result object (see :class:`CTRexResult` for further details) with sampled data.
662
663        """
664        if copy_obj:
665            return copy.deepcopy(self.result_obj)
666        else:
667            return self.result_obj
668
669    def is_reserved (self):
670        """
671        Checks if TRex is currently reserved to any user or not.
672
673        :parameters:
674            None
675
676        :return:
677            + **True** if TRex is reserved.
678            + **False** otherwise.
679
680        :raises:
681            ProtocolError, in case of error in JSON-RPC protocol.
682
683        """
684        try:
685            return self.server.is_reserved()
686        except AppError as err:
687            self._handle_AppError_exception(err.args[0])
688        except ProtocolError:
689            raise
690        finally:
691            self.prompt_verbose_data()
692
693    def get_trex_daemon_log (self):
694        """
695        Get Trex daemon log.
696
697        :return:
698            String representation of TRex daemon log
699
700        :raises:
701            + :exc:`trex_exceptions.TRexRequestDenied`, in case file could not be read.
702            + ProtocolError, in case of error in JSON-RPC protocol.
703
704        """
705        try:
706            res = binascii.a2b_base64(self.server.get_trex_daemon_log())
707            if type(res) is bytes:
708                return res.decode()
709            return res
710        except AppError as err:
711            self._handle_AppError_exception(err.args[0])
712        except ProtocolError:
713            raise
714        finally:
715            self.prompt_verbose_data()
716
717    def get_trex_log (self):
718        """
719        Get TRex CLI output log
720
721        :return:
722            String representation of TRex log
723
724        :raises:
725            + :exc:`trex_exceptions.TRexRequestDenied`, in case file could not be fetched at server side.
726            + ProtocolError, in case of error in JSON-RPC protocol.
727
728        """
729        try:
730            res = binascii.a2b_base64(self.server.get_trex_log())
731            if type(res) is bytes:
732                return res.decode()
733            return res
734        except AppError as err:
735            self._handle_AppError_exception(err.args[0])
736        except ProtocolError:
737            raise
738        finally:
739            self.prompt_verbose_data()
740
741    def get_trex_version (self):
742        """
743        Get TRex version details.
744
745        :return:
746            Trex details (Version, User, Date, Uuid, Git SHA) as ordered dictionary
747
748        :raises:
749            + :exc:`trex_exceptions.TRexRequestDenied`, in case TRex version could not be determined.
750            + ProtocolError, in case of error in JSON-RPC protocol.
751            + General Exception is case one of the keys is missing in response
752        """
753
754        try:
755            version_dict = OrderedDict()
756            res = binascii.a2b_base64(self.server.get_trex_version())
757            if type(res) is bytes:
758                res = res.decode()
759            result_lines = res.split('\n')
760            for line in result_lines:
761                if not line:
762                    continue
763                key, value = line.strip().split(':', 1)
764                version_dict[key.strip()] = value.strip()
765            for key in ('Version', 'User', 'Date', 'Uuid', 'Git SHA'):
766                if key not in version_dict:
767                    raise Exception('get_trex_version: got server response without key: {0}'.format(key))
768            return version_dict
769        except AppError as err:
770            self._handle_AppError_exception(err.args[0])
771        except ProtocolError:
772            raise
773        finally:
774            self.prompt_verbose_data()
775
776    def reserve_trex (self, user = None):
777        """
778        Reserves the usage of TRex to a certain user.
779
780        When TRex is reserved, it can't be reserved.
781
782        :parameters:
783            user : str
784                a username of the desired owner of TRex
785
786                default: current logged user
787
788        :return:
789            **True** if reservation made successfully
790
791        :raises:
792            + :exc:`trex_exceptions.TRexRequestDenied`, in case TRex is reserved for another user than the one trying to make the reservation.
793            + :exc:`trex_exceptions.TRexInUseError`, in case TRex is currently running.
794            + ProtocolError, in case of error in JSON-RPC protocol.
795
796        """
797        username = user or self.__default_user
798        try:
799            return self.server.reserve_trex(user = username)
800        except AppError as err:
801            self._handle_AppError_exception(err.args[0])
802        except ProtocolError:
803            raise
804        finally:
805            self.prompt_verbose_data()
806
807    def cancel_reservation (self, user = None):
808        """
809        Cancels a current reservation of TRex to a certain user.
810
811        When TRex is reserved, no other user can start new TRex runs.
812
813
814        :parameters:
815            user : str
816                a username of the desired owner of TRex
817
818                default: current logged user
819
820        :return:
821            + **True** if reservation canceled successfully,
822            + **False** if there was no reservation at all.
823
824        :raises:
825            + :exc:`trex_exceptions.TRexRequestDenied`, in case TRex is reserved for another user than the one trying to cancel the reservation.
826            + ProtocolError, in case of error in JSON-RPC protocol.
827
828        """
829
830        username = user or self.__default_user
831        try:
832            return self.server.cancel_reservation(user = username)
833        except AppError as err:
834            self._handle_AppError_exception(err.args[0])
835        except ProtocolError:
836            raise
837        finally:
838            self.prompt_verbose_data()
839
840    def get_files_list (self, path):
841        """
842        Gets a list of dirs and files either from /tmp/trex_files or path relative to TRex server.
843
844        :parameters:
845            path : str
846                a path to directory to read.
847
848        :return:
849            Tuple: list of dirs and list of files in given path
850
851        :raises:
852            + :exc:`trex_exceptions.TRexRequestDenied`, in case TRex is reserved for another user than the one trying to cancel the reservation.
853            + ProtocolError, in case of error in JSON-RPC protocol.
854
855        """
856
857        try:
858            return self.server.get_files_list(path)
859        except AppError as err:
860            self._handle_AppError_exception(err.args[0])
861        except ProtocolError:
862            raise
863        finally:
864            self.prompt_verbose_data()
865
866    def get_file(self, filepath):
867        """
868        Gets content of file as bytes string from /tmp/trex_files or TRex server directory.
869
870        :parameters:
871            filepath : str
872                a path to a file at server.
873                it can be either relative to TRex server or absolute path starting with /tmp/trex_files
874
875        :return:
876            Content of the file
877
878        :raises:
879            + :exc:`trex_exceptions.TRexRequestDenied`, in case TRex is reserved for another user than the one trying to cancel the reservation.
880            + ProtocolError, in case of error in JSON-RPC protocol.
881        """
882
883        try:
884            return binascii.a2b_base64(self.server.get_file(filepath))
885        except AppError as err:
886            self._handle_AppError_exception(err.args[0])
887        except ProtocolError:
888            raise
889        finally:
890            self.prompt_verbose_data()
891
892    def get_trex_config(self):
893        """
894        Get Trex config file (/etc/trex_cfg.yaml).
895
896        :return:
897            String representation of TRex config file
898
899        :raises:
900            + :exc:`trex_exceptions.TRexRequestDenied`, in case file could not be read.
901            + ProtocolError, in case of error in JSON-RPC protocol.
902
903        """
904        try:
905            res = binascii.a2b_base64(self.server.get_trex_config())
906            if type(res) is bytes:
907                return res.decode()
908            return res
909        except AppError as err:
910            self._handle_AppError_exception(err.args[0])
911        except ProtocolError:
912            raise
913        finally:
914            self.prompt_verbose_data()
915
916
917    def push_files (self, filepaths):
918        """
919        Pushes a file (or a list of files) to store locally on server.
920
921        :parameters:
922            filepaths : str or list
923                a path to a file to be pushed to server.
924                if a list of paths is passed, all of those will be pushed to server
925
926        :return:
927            + **True** if file(s) copied successfully.
928            + **False** otherwise.
929
930        :raises:
931            + :exc:`IOError`, in case specified file wasn't found or could not be accessed.
932            + ProtocolError, in case of error in JSON-RPC protocol.
933
934        """
935        paths_list = None
936        if isinstance(filepaths, str):
937            paths_list = [filepaths]
938        elif isinstance(filepaths, list):
939            paths_list = filepaths
940        else:
941            raise TypeError("filepaths argument must be of type str or list")
942
943        for filepath in paths_list:
944            try:
945                if not os.path.exists(filepath):
946                    raise IOError(errno.ENOENT, "The requested `{fname}` file wasn't found. Operation aborted.".format(
947                        fname = filepath) )
948                else:
949                    filename = os.path.basename(filepath)
950                    with open(filepath, 'rb') as f:
951                        file_content = f.read()
952                        self.server.push_file(filename, binascii.b2a_base64(file_content).decode())
953            finally:
954                self.prompt_verbose_data()
955        return True
956
957    def is_query_relevance(self):
958        """
959        Checks if time between any two consecutive server queries (asking for live running data) passed.
960
961        .. note:: The allowed minimum time between each two consecutive samples is 0.5 seconds.
962
963        :parameters:
964            None
965
966        :return:
967            + **True** if more than 0.5 seconds has been past from last server query.
968            + **False** otherwise.
969
970        """
971        cur_time = time.time()
972        if cur_time-self._last_sample < 0.5:
973            return False
974        else:
975            self._last_sample = cur_time
976            return True
977
978    def call_server_mathod_safely (self, method_to_call):
979        try:
980            return method_to_call()
981        except socket.error as e:
982            if e.errno == errno.ECONNREFUSED:
983                raise SocketError(errno.ECONNREFUSED, "Connection to TRex daemon server was refused. Please make sure the server is up.")
984
985    def check_server_connectivity (self):
986        """
987        Checks TRex daemon server for connectivity.
988        """
989        try:
990            socket.gethostbyname(self.trex_host)
991            return self.server.connectivity_check()
992        except socket.gaierror as e:
993            raise socket.gaierror(e.errno, "Could not resolve server hostname. Please make sure hostname entered correctly.")
994        except socket.error as e:
995            if e.errno == errno.ECONNREFUSED:
996                raise socket.error(errno.ECONNREFUSED, "Connection to TRex daemon server was refused. Please make sure the server is up.")
997        finally:
998            self.prompt_verbose_data()
999
1000
1001    def master_add(self, x, y):
1002        ''' Sanity check for Master daemon '''
1003        try:
1004            return self.master_daemon.add(x,y)
1005        except AppError as err:
1006            self._handle_AppError_exception(err.args[0])
1007        finally:
1008            self.prompt_verbose_data()
1009
1010
1011    def check_master_connectivity (self):
1012        '''
1013        Check Master daemon for connectivity.
1014        Return True upon success
1015        '''
1016        try:
1017            socket.gethostbyname(self.trex_host)
1018            return self.master_daemon.check_connectivity()
1019        except socket.gaierror as e:
1020            raise socket.gaierror(e.errno, "Could not resolve server hostname. Please make sure hostname entered correctly.")
1021        except socket.error as e:
1022            if e.errno == errno.ECONNREFUSED:
1023                raise socket.error(errno.ECONNREFUSED, "Connection to Master daemon was refused. Please make sure the server is up.")
1024        finally:
1025            self.prompt_verbose_data()
1026
1027    def is_trex_daemon_running(self):
1028        '''
1029        Check if TRex server daemon is running.
1030        Returns True/False
1031        '''
1032        try:
1033            return self.master_daemon.is_trex_daemon_running()
1034        except AppError as err:
1035            self._handle_AppError_exception(err.args[0])
1036        finally:
1037            self.prompt_verbose_data()
1038
1039    def restart_trex_daemon(self):
1040        '''
1041        Restart TRex server daemon. Useful after update.
1042        Will not fail if daemon is initially stopped.
1043        '''
1044        try:
1045            return self.master_daemon.restart_trex_daemon()
1046        except AppError as err:
1047            self._handle_AppError_exception(err.args[0])
1048        finally:
1049            self.prompt_verbose_data()
1050
1051    def start_trex_daemon(self):
1052        '''
1053        Start TRex server daemon.
1054
1055        :return:
1056            + **True** if success.
1057            + **False** if TRex server daemon already running.
1058        '''
1059        try:
1060            return self.master_daemon.start_trex_daemon()
1061        except AppError as err:
1062            self._handle_AppError_exception(err.args[0])
1063        finally:
1064            self.prompt_verbose_data()
1065
1066    def stop_trex_daemon(self):
1067        '''
1068        Stop TRex server daemon.
1069
1070        :return:
1071            + **True** if success.
1072            + **False** if TRex server daemon already running.
1073        '''
1074        try:
1075            return self.master_daemon.stop_trex_daemon()
1076        except AppError as err:
1077            self._handle_AppError_exception(err.args[0])
1078        finally:
1079            self.prompt_verbose_data()
1080
1081    def prompt_verbose_data(self):
1082        """
1083        This method prompts any verbose data available, only if `verbose` option has been turned on.
1084        """
1085        if self.verbose:
1086            print ('\n')
1087            print ("(*) JSON-RPC request:", self.history.request)
1088            print ("(*) JSON-RPC response:", self.history.response)
1089
1090    def __verbose_print(self, print_str):
1091        """
1092        This private method prints the `print_str` string only in case self.verbose flag is turned on.
1093
1094        :parameters:
1095            print_str : str
1096                a string to be printed
1097
1098        :returns:
1099            None
1100        """
1101        if self.verbose:
1102            print (print_str)
1103
1104
1105
1106    def _handle_AppError_exception(self, err):
1107        """
1108        This private method triggres the TRex dedicated exception generation in case a general ProtocolError has been raised.
1109        """
1110        # handle known exceptions based on known error codes.
1111        # if error code is not known, raise ProtocolError
1112        exc = exception_handler.gen_exception(err)
1113        exc.__cause__ = None # remove "During handling of the above exception, another exception occurred:" in Python3.3+
1114        raise exc
1115
1116
1117class CTRexResult(object):
1118    """
1119    A class containing all results received from TRex.
1120
1121    Ontop to containing the results, this class offers easier data access and extended results processing options
1122    """
1123    def __init__(self, max_history_size, filtered_latency_amount = 0.001):
1124        """
1125        Instatiate a TRex result object
1126
1127        :parameters:
1128             max_history_size : int
1129                A number to set the maximum history size of a single TRex run. Each sampling adds a new item to history.
1130             filtered_latency_amount : float
1131                Ignore high latency for this ammount of packets. (by default take into account 99.9%)
1132
1133        """
1134        self._history = deque(maxlen = max_history_size)
1135        self.clear_results()
1136        self.latency_checked = True
1137        self.filtered_latency_amount = filtered_latency_amount
1138        self.set_warmup_default()
1139
1140    def set_warmup_default (self):
1141        self.set_warmup(0.96)
1142
1143    def set_warmup (self,new_warmup_max):
1144        self.warmup_max = new_warmup_max
1145
1146    def __repr__(self):
1147        return ("Is valid history?       {arg}\n".format( arg = self.is_valid_hist() ) +
1148                "Done warmup?            {arg}\n".format( arg = self.is_done_warmup() ) +
1149                "Expected tx rate:       {arg}\n".format( arg = self.get_expected_tx_rate() ) +
1150                "Current tx rate:        {arg}\n".format( arg = self.get_current_tx_rate() ) +
1151                "Minimum latency:        {arg}\n".format( arg = self.get_min_latency() ) +
1152                "Maximum latency:        {arg}\n".format( arg = self.get_max_latency() ) +
1153                "Average latency:        {arg}\n".format( arg = self.get_avg_latency() ) +
1154                "Average window latency: {arg}\n".format( arg = self.get_avg_window_latency() ) +
1155                "Total drops:            {arg}\n".format( arg = self.get_total_drops() ) +
1156                "Drop rate:              {arg}\n".format( arg = self.get_drop_rate() ) +
1157                "History size so far:    {arg}\n".format( arg = len(self._history) ) )
1158
1159    def get_expected_tx_rate (self):
1160        """
1161        Fetches the expected TX rate in various units representation
1162
1163        :parameters:
1164            None
1165
1166        :return:
1167            dictionary containing the expected TX rate, where the key is the measurement units, and the value is the measurement value.
1168
1169        """
1170        return self._expected_tx_rate
1171
1172    def get_current_tx_rate (self):
1173        """
1174        Fetches the current TX rate in various units representation
1175
1176        :parameters:
1177            None
1178
1179        :return:
1180            dictionary containing the current TX rate, where the key is the measurement units, and the value is the measurement value.
1181
1182        """
1183        return self._current_tx_rate
1184
1185    def get_max_latency (self):
1186        """
1187        Fetches the maximum latency measured on each of the interfaces
1188
1189        :parameters:
1190            None
1191
1192        :return:
1193            dictionary containing the maximum latency, where the key is the measurement interface (`c` indicates client), and the value is the measurement value.
1194
1195        """
1196        return self._max_latency
1197
1198    def get_min_latency (self):
1199        """
1200        Fetches the minimum latency measured on each of the interfaces
1201
1202        :parameters:
1203            None
1204
1205        :return:
1206            dictionary containing the maximum latency, where the key is the measurement interface (`c` indicates client), and the value is the measurement value.
1207
1208        """
1209        return self._min_latency
1210
1211
1212
1213    def get_jitter_latency (self):
1214        """
1215        Fetches the jitter latency measured on each of the interfaces from the start of TRex run
1216
1217        :parameters:
1218            None
1219
1220        :return:
1221            dictionary containing the average latency, where the key is the measurement interface (`c` indicates client), and the value is the measurement value.
1222
1223            The `all` key represents the average of all interfaces' average
1224
1225        """
1226        return self._jitter_latency
1227
1228    def get_avg_latency (self):
1229        """
1230        Fetches the average latency measured on each of the interfaces from the start of TRex run
1231
1232        :parameters:
1233            None
1234
1235        :return:
1236            dictionary containing the average latency, where the key is the measurement interface (`c` indicates client), and the value is the measurement value.
1237
1238            The `all` key represents the average of all interfaces' average
1239
1240        """
1241        return self._avg_latency
1242
1243    def get_avg_window_latency (self):
1244        """
1245        Fetches the average latency measured on each of the interfaces from all the sampled currently stored in window.
1246
1247        :parameters:
1248            None
1249
1250        :return:
1251            dictionary containing the average latency, where the key is the measurement interface (`c` indicates client), and the value is the measurement value.
1252
1253            The `all` key represents the average of all interfaces' average
1254
1255        """
1256        return self._avg_window_latency
1257
1258    def get_total_drops (self):
1259        """
1260        Fetches the total number of drops identified from the moment TRex run began.
1261
1262        :parameters:
1263            None
1264
1265        :return:
1266            total drops count (as int)
1267
1268        """
1269        return self._total_drops
1270
1271    def get_drop_rate (self):
1272        """
1273        Fetches the most recent drop rate in pkts/sec units.
1274
1275        :parameters:
1276            None
1277
1278        :return:
1279            current drop rate (as float)
1280
1281        """
1282        return self._drop_rate
1283
1284    def is_valid_hist (self):
1285        """
1286        Checks if result obejct contains valid data.
1287
1288        :parameters:
1289            None
1290
1291        :return:
1292            + **True** if history is valid.
1293            + **False** otherwise.
1294
1295        """
1296        return self.valid
1297
1298    def set_valid_hist (self, valid_stat = True):
1299        """
1300        Sets result obejct validity status.
1301
1302        :parameters:
1303            valid_stat : bool
1304                defines the validity status
1305
1306                dafault value : **True**
1307
1308        :return:
1309            None
1310
1311        """
1312        self.valid = valid_stat
1313
1314    def is_done_warmup (self):
1315        """
1316        Checks if TRex latest results TX-rate indicates that TRex has reached its expected TX-rate.
1317
1318        :parameters:
1319            None
1320
1321        :return:
1322            + **True** if expected TX-rate has been reached.
1323            + **False** otherwise.
1324
1325        """
1326        return self._done_warmup
1327
1328    def get_last_value (self, tree_path_to_key, regex = None):
1329        """
1330        A dynamic getter from the latest sampled data item stored in the result object.
1331
1332        :parameters:
1333            tree_path_to_key : str
1334                defines a path to desired data.
1335
1336                .. tip:: | Use '.' to enter one level deeper in dictionary hierarchy.
1337                         | Use '[i]' to access the i'th indexed object of an array.
1338
1339            regex : regex
1340                apply a regex to filter results out from a multiple results set.
1341
1342                Filter applies only on keys of dictionary type.
1343
1344                dafault value : **None**
1345
1346        :return:
1347            + a list of values relevant to the specified path
1348            + None if no results were fetched or the history isn't valid.
1349
1350        """
1351        if not self.is_valid_hist():
1352            return None
1353        else:
1354            return CTRexResult.__get_value_by_path(self._history[-1], tree_path_to_key, regex)
1355
1356    def get_value_list (self, tree_path_to_key, regex = None, filter_none = True):
1357        """
1358        A dynamic getter from all sampled data items stored in the result object.
1359
1360        :parameters:
1361            tree_path_to_key : str
1362                defines a path to desired data.
1363
1364                .. tip:: | Use '.' to enter one level deeper in dictionary hierarchy.
1365                         | Use '[i]' to access the i'th indexed object of an array.
1366
1367            regex : regex
1368                apply a regex to filter results out from a multiple results set.
1369
1370                Filter applies only on keys of dictionary type.
1371
1372                dafault value : **None**
1373
1374            filter_none : bool
1375                specify if None results should be filtered out or not.
1376
1377                dafault value : **True**
1378
1379        :return:
1380            + a list of values relevant to the specified path. Each item on the list refers to a single server sample.
1381            + None if no results were fetched or the history isn't valid.
1382        """
1383
1384        if not self.is_valid_hist():
1385            return None
1386        else:
1387            raw_list = list( map(lambda x: CTRexResult.__get_value_by_path(x, tree_path_to_key, regex), self._history) )
1388            if filter_none:
1389                return list (filter(lambda x: x!=None, raw_list) )
1390            else:
1391                return raw_list
1392
1393    def get_latest_dump(self):
1394        """
1395        A  getter to the latest sampled data item stored in the result object.
1396
1397        :parameters:
1398            None
1399
1400        :return:
1401            + a dictionary of the latest data item
1402            + an empty dictionary if history is empty.
1403
1404        """
1405        if len(self._history):
1406            return self._history[-1]
1407        return {}
1408
1409    def get_ports_count(self):
1410        """
1411        Returns number of ports based on TRex result
1412
1413        :return:
1414            + number of ports in TRex result
1415            + -1 if history is empty.
1416        """
1417
1418        if not len(self._history):
1419            return -1
1420        return len(self.get_last_value('trex-global.data', 'opackets-\d+'))
1421
1422
1423    def update_result_data (self, latest_dump):
1424        """
1425        Integrates a `latest_dump` dictionary into the CTRexResult object.
1426
1427        :parameters:
1428            latest_dump : dict
1429                a dictionary with the items desired to be integrated into the object history and stats
1430
1431        :return:
1432            None
1433
1434        """
1435        # add latest dump to history
1436        if latest_dump:
1437            self._history.append(latest_dump)
1438            if not self.valid:
1439                self.valid = True
1440
1441            # parse important fields and calculate averages and others
1442            if self._expected_tx_rate is None:
1443                # get the expected data only once since it doesn't change
1444                self._expected_tx_rate = CTRexResult.__get_value_by_path(latest_dump, "trex-global.data", "m_tx_expected_\w+")
1445
1446            self._current_tx_rate = CTRexResult.__get_value_by_path(latest_dump, "trex-global.data", "m_tx_(?!expected_)\w+")
1447            if not self._done_warmup and self._expected_tx_rate is not None:
1448                # check for up to 4% change between expected and actual
1449                if (self._current_tx_rate['m_tx_bps'] > self.warmup_max * self._expected_tx_rate['m_tx_expected_bps']):
1450                    self._done_warmup = True
1451                    latest_dump['warmup_barrier'] = True
1452
1453            # handle latency data
1454            if self.latency_checked:
1455                # fix typos, by "pointer"
1456                if 'trex-latecny-v2' in latest_dump and 'trex-latency-v2' not in latest_dump:
1457                    latest_dump['trex-latency-v2'] = latest_dump['trex-latecny-v2']
1458                if 'trex-latecny' in latest_dump and 'trex-latency' not in latest_dump:
1459                    latest_dump['trex-latency'] = latest_dump['trex-latecny']
1460
1461                latency_per_port         = self.get_last_value("trex-latency-v2.data", "port-")
1462                self._max_latency        = self.__get_filtered_max_latency(latency_per_port, self.filtered_latency_amount)
1463                self._min_latency        = self.__get_filtered_min_latency(latency_per_port)
1464                avg_latency              = self.get_last_value("trex-latency.data", "avg-")
1465                self._avg_latency        = CTRexResult.__avg_all_and_rename_keys(avg_latency)
1466                jitter_latency           = self.get_last_value("trex-latency.data", "jitter-")
1467                self._jitter_latency        = CTRexResult.__avg_all_and_rename_keys(jitter_latency)
1468                avg_win_latency_list     = self.get_value_list("trex-latency.data", "avg-")
1469                self._avg_window_latency = CTRexResult.__calc_latency_win_stats(avg_win_latency_list)
1470
1471            tx_pkts = CTRexResult.__get_value_by_path(latest_dump, "trex-global.data.m_total_tx_pkts")
1472            rx_pkts = CTRexResult.__get_value_by_path(latest_dump, "trex-global.data.m_total_rx_pkts")
1473            if tx_pkts is not None and rx_pkts is not None:
1474                self._total_drops = tx_pkts - rx_pkts
1475            self._drop_rate   = CTRexResult.__get_value_by_path(latest_dump, "trex-global.data.m_rx_drop_bps")
1476
1477    def clear_results (self):
1478        """
1479        Clears all results and sets the history's validity to `False`
1480
1481        :parameters:
1482            None
1483
1484        :return:
1485            None
1486
1487        """
1488        self.valid               = False
1489        self._done_warmup        = False
1490        self._expected_tx_rate   = None
1491        self._current_tx_rate    = None
1492        self._max_latency        = None
1493        self._min_latency        = None
1494        self._avg_latency        = None
1495        self._jitter_latency     = None
1496        self._avg_window_latency = None
1497        self._total_drops        = None
1498        self._drop_rate          = None
1499        self._history.clear()
1500
1501    @staticmethod
1502    def __get_value_by_path (dct, tree_path, regex = None):
1503        try:
1504            for i, p in re.findall(r'(\d+)|([\w|-]+)', tree_path):
1505                dct = dct[p or int(i)]
1506            if regex is not None and isinstance(dct, dict):
1507                res = {}
1508                for key,val in dct.items():
1509                    match = re.match(regex, key)
1510                    if match:
1511                        res[key]=val
1512                return res
1513            else:
1514               return dct
1515        except (KeyError, TypeError):
1516            return None
1517
1518    @staticmethod
1519    def __calc_latency_win_stats (latency_win_list):
1520        res = {'all' : None }
1521        port_dict = {'all' : []}
1522        list( map(lambda x: CTRexResult.__update_port_dict(x, port_dict), latency_win_list) )
1523
1524        # finally, calculate everages for each list
1525        res['all'] = float("%.3f" % (sum(port_dict['all'])/float(len(port_dict['all']))) )
1526        port_dict.pop('all')
1527        for port, avg_list in port_dict.items():
1528            res[port] = float("%.3f" % (sum(avg_list)/float(len(avg_list))) )
1529
1530        return res
1531
1532    @staticmethod
1533    def __update_port_dict (src_avg_dict, dest_port_dict):
1534        all_list = src_avg_dict.values()
1535        dest_port_dict['all'].extend(all_list)
1536        for key, val in src_avg_dict.items():
1537            reg_res = re.match("avg-(\d+)", key)
1538            if reg_res:
1539                tmp_key = "port"+reg_res.group(1)
1540                if tmp_key in dest_port_dict:
1541                    dest_port_dict[tmp_key].append(val)
1542                else:
1543                    dest_port_dict[tmp_key] = [val]
1544
1545    @staticmethod
1546    def __avg_all_and_rename_keys (src_dict):
1547        res       = {}
1548        all_list  = src_dict.values()
1549        res['all'] = float("%.3f" % (sum(all_list)/float(len(all_list))) )
1550        for key, val in src_dict.items():
1551            reg_res = re.match("avg-(\d+)", key)
1552            if reg_res:
1553                tmp_key = "port"+reg_res.group(1)
1554                res[tmp_key] = val  # don't touch original fields values
1555        return res
1556
1557    @staticmethod
1558    def __get_filtered_min_latency(src_dict):
1559        result = {}
1560        if src_dict:
1561            for port, data in src_dict.items():
1562                if not port.startswith('port-'):
1563                    continue
1564                res = data['hist']['min_usec']
1565                min_port = 'min-%s' % port[5:]
1566                result[min_port] = int(res)
1567
1568        return(result);
1569
1570
1571
1572    @staticmethod
1573    def __get_filtered_max_latency (src_dict, filtered_latency_amount = 0.001):
1574        result = {}
1575        if src_dict:
1576            for port, data in src_dict.items():
1577                if not port.startswith('port-'):
1578                    continue
1579                max_port = 'max-%s' % port[5:]
1580                res = data['hist']
1581                if not len(res['histogram']):
1582                    result[max_port] = 0
1583                    continue
1584                result[max_port] = 5 # if sum below will not get to filtered amount, use this value
1585                sum_high = 0.0
1586                for elem in reversed(res['histogram']):
1587                    sum_high += elem['val']
1588                    if sum_high >= filtered_latency_amount * res['cnt']:
1589                        result[max_port] = elem['key'] + int('5' + repr(elem['key'])[2:])
1590                        break
1591        return result
1592
1593
1594    # history iterator after warmup period
1595    def _get_steady_state_history_iterator(self):
1596        if not self.is_done_warmup():
1597            raise Exception('Warm-up period not finished')
1598        for index, res in enumerate(self._history):
1599            if 'warmup_barrier' in res:
1600                for steady_state_index in range(index, max(index, len(self._history) - 1)):
1601                    yield self._history[steady_state_index]
1602                return
1603        for index in range(len(self._history) - 1):
1604            yield self._history[index]
1605
1606
1607    def get_avg_steady_state_value(self, tree_path_to_key):
1608        '''
1609        Gets average value after warmup period.
1610        For example: <result object>.get_avg_steady_state_value('trex-global.data.m_tx_bps')
1611        Usually more accurate than latest history value.
1612
1613        :parameters:
1614            tree_path_to_key : str
1615                defines a path to desired data.
1616
1617        :return:
1618            average value at steady state
1619
1620        :raises:
1621            Exception in case steady state period was not reached or tree_path_to_key was not found in result.
1622        '''
1623        values_arr = [self.__get_value_by_path(res, tree_path_to_key) for res in self._get_steady_state_history_iterator()]
1624        values_arr = list(filter(lambda x: x is not None, values_arr))
1625        if not values_arr:
1626            raise Exception('All the keys are None, probably wrong tree_path_to_key: %s' % tree_path_to_key)
1627        return sum(values_arr) / float(len(values_arr))
1628
1629
1630if __name__ == "__main__":
1631    c = CTRexClient('127.0.0.1')
1632    print('restarting daemon')
1633    c.restart_trex_daemon()
1634    print('kill any running')
1635    c.kill_all_trexes()
1636    print('start')
1637    c.start_stateless()
1638    print('sleep')
1639    time.sleep(5)
1640    print('done')
1641
1642