1# Copyright 2016 Google Inc. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain 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, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import io 16import json 17import logging 18import os 19 20from mobly import utils 21 22MOBLY_CONTROLLER_CONFIG_NAME = 'IPerfServer' 23 24 25def create(configs): 26 results = [] 27 for c in configs: 28 try: 29 results.append(IPerfServer(c, logging.log_path)) 30 except: 31 pass 32 return results 33 34 35def destroy(objs): 36 for ipf in objs: 37 try: 38 ipf.stop() 39 except: 40 pass 41 42 43class IPerfResult: 44 45 def __init__(self, result_path): 46 try: 47 with io.open(result_path, 'r', encoding='utf-8') as f: 48 self.result = json.load(f) 49 except ValueError: 50 with io.open(result_path, 'r', encoding='utf-8') as f: 51 # Possibly a result from interrupted iperf run, skip first line 52 # and try again. 53 lines = f.readlines()[1:] 54 self.result = json.loads(''.join(lines)) 55 56 def _has_data(self): 57 """Checks if the iperf result has valid throughput data. 58 59 Returns: 60 True if the result contains throughput data. False otherwise. 61 """ 62 return ('end' in self.result) and ('sum' in self.result['end']) 63 64 def get_json(self): 65 """ 66 Returns: 67 The raw json output from iPerf. 68 """ 69 return self.result 70 71 @property 72 def error(self): 73 if 'error' not in self.result: 74 return None 75 return self.result['error'] 76 77 @property 78 def avg_rate(self): 79 """Average receiving rate in MB/s over the entire run. 80 81 If the result is not from a success run, this property is None. 82 """ 83 if not self._has_data or 'sum' not in self.result['end']: 84 return None 85 bps = self.result['end']['sum']['bits_per_second'] 86 return bps / 8 / 1024 / 1024 87 88 @property 89 def avg_receive_rate(self): 90 """Average receiving rate in MB/s over the entire run. This data may 91 not exist if iperf was interrupted. 92 93 If the result is not from a success run, this property is None. 94 """ 95 if not self._has_data or 'sum_received' not in self.result['end']: 96 return None 97 bps = self.result['end']['sum_received']['bits_per_second'] 98 return bps / 8 / 1024 / 1024 99 100 @property 101 def avg_send_rate(self): 102 """Average sending rate in MB/s over the entire run. This data may 103 not exist if iperf was interrupted. 104 105 If the result is not from a success run, this property is None. 106 """ 107 if not self._has_data or 'sum_sent' not in self.result['end']: 108 return None 109 bps = self.result['end']['sum_sent']['bits_per_second'] 110 return bps / 8 / 1024 / 1024 111 112 113class IPerfServer: 114 """Class that handles iperf3 operations.""" 115 116 def __init__(self, port, log_path): 117 self.port = port 118 self.log_path = os.path.join(log_path, 'iPerf{}'.format(self.port)) 119 self.iperf_str = 'iperf3 -s -J -p {}'.format(port) 120 self.iperf_process = None 121 self.log_files = [] 122 self.started = False 123 124 def start(self, extra_args='', tag=''): 125 """Starts iperf server on specified port. 126 127 Args: 128 extra_args: A string representing extra arguments to start iperf 129 server with. 130 tag: Appended to log file name to identify logs from different 131 iperf runs. 132 """ 133 if self.started: 134 return 135 utils.create_dir(self.log_path) 136 if tag: 137 tag = tag + ',' 138 out_file_name = 'IPerfServer,{},{}{}.log'.format( 139 self.port, tag, len(self.log_files) 140 ) 141 full_out_path = os.path.join(self.log_path, out_file_name) 142 cmd = '%s %s > %s' % (self.iperf_str, extra_args, full_out_path) 143 self.iperf_process = utils.start_standing_subprocess(cmd, shell=True) 144 self.log_files.append(full_out_path) 145 self.started = True 146 147 def stop(self): 148 if self.started: 149 utils.stop_standing_subprocess(self.iperf_process) 150 self.started = False 151