def stats_command(args): """Implements the 'stats' command for the 'wult' and 'ndl' tools.""" if args.list_funcs: for name, descr in RORawResult.get_stat_funcs(): _LOG.info("%s: %s", name, descr) return if args.funcs: funcnames = Trivial.split_csv_line(args.funcs) all_funcs = True else: funcnames = None all_funcs = False res = RORawResult.RORawResult(args.respath) apply_filters(args, res) non_numeric = res.get_non_numeric_colnames() if non_numeric and (args.csel or args.cfilt): non_numeric = ", ".join(non_numeric) _LOG.warning("skipping non-numeric column(s): %s", non_numeric) res.calc_stats(funcnames=funcnames, all_funcs=all_funcs) _LOG.info("Datapoints count: %d", len(res.df)) YAML.dump(res.cstats, sys.stdout, float_format="%.2f")
def report_command_open_raw_results(args): """ Opens the input raw test results for the 'report' command of the 'wult' and 'ndl' tools. Returns the list of 'RORawResult' objects. At the same time, implements the '--list-columns' option by printing the column names for each input raw result. """ if args.reportids: reportids = Trivial.split_csv_line(args.reportids) else: reportids = [] if len(reportids) > len(args.respaths): raise Error(f"there are {len(reportids)} report IDs to assign to {len(args.respaths)} " f"input test results. Please, provide {len(args.respaths)} or less report IDs.") # Append the required amount of 'None's to make the 'reportids' list be of the same length as # the 'args.respaths' list. reportids += [None] * (len(args.respaths) - len(reportids)) rsts = [] for respath, reportid in zip(args.respaths, reportids): if reportid: additional_chars = getattr(args, "reportid_additional_chars", None) ReportID.validate_reportid(reportid, additional_chars=additional_chars) res = RORawResult.RORawResult(respath, reportid=reportid) rsts.append(res) if args.list_columns: _LOG.info("Column names in '%s':", respath) for colname in res.colnames: _LOG.info(" * %s: %s", colname, res.defs.info[colname]["title"]) return rsts
def validate_ldist(ldist): """ Validate and modify launch distanse. 'ldist' argument is string of single or two comma-separated integers, and tells launch distance in microseconds. Return value is launch distance in nanoseconds, as list of one or two integers. """ ldst = Trivial.split_csv_line(ldist) for idx, val in enumerate(ldst): ldst[idx] = Trivial.str_to_num(val, default=None) if ldst[idx] is None: raise Error(f"bad launch distance '{ldist}', should be an integer") if ldst[idx] <= 0: raise Error( f"bad launch distance value '{ldst[idx]}', should be greater than zero" ) ldst_str = ", ".join([str(val) for val in ldst]) if len(ldst) > 2: raise Error( f"bad launch distance range '{ldst_str}', it should include 2 numbers" ) if len(ldst) == 2 and ldst[1] - ldst[0] < 0: raise Error( f"bad launch distance range '{ldst_str}', first number cannot be " f"greater than the second number") # Return launch distance as nanoseconds. for idx, val in enumerate(ldst): ldst[idx] = val * 1000 return ldst
def _parse_ip_address_show(raw): """ Parse output of the 'ip address show <IFNAME>' command and return the resulting dictionary. """ info = {} for line in raw.splitlines(): line = line.strip() elts = Trivial.split_csv_line(line, sep=" ") if re.match(r"^\d+:$", elts[0]): info["ifname"] = elts[1][:-1] elif elts[0] == "inet": ipnet = ipaddress.IPv4Network(elts[1], strict=False) info["ipv4"] = {} info["ipv4"]["ip"] = ipnet.network_address info["ipv4"]["mask"] = ipnet.netmask info["ipv4"]["bcast"] = ipnet.broadcast_address info["ipv4"]["ip_cidr"] = elts[1] info["ipv4"]["cidr"] = str(ipnet) elif elts[0] == "link/ether": info["ether"] = {} info["ether"]["mac"] = elts[1] info["ether"]["bcast"] = elts[3] return info
def do_filter(res, ops): """Apply filter operations in 'ops' to wult test result 'res'.""" res.clear_filts() for name, expr in ops.items(): if name.startswith("c"): expr = Trivial.split_csv_line(expr) getattr(res, f"set_{name}")(expr) res.load_df()
def parse_int_list(nums, ints=False): """ Turn a string contaning a comma-separated list of numbers and ranges into a list of numbers and return it. For example, a string like "0,1-3,7" would become ["0", "1", "2", "3", "7"]. The 'ints' argument controls whether the resulting list should contain strings or integers. """ if nums is None: return None if isinstance(nums, int): nums = str(nums) if isinstance(nums, str): nums = Trivial.split_csv_line(nums) nums_set = set() for elts in nums: elts = str(elts) if "-" in elts: elts = Trivial.split_csv_line(elts, sep="-") if len(elts) != 2: raise Error("bad range '%s', should be two integers separated by '-'" % elts) else: elts = [elts] for elt in elts: if not Trivial.is_int(elt): raise Error("bad number '%s', should be an integer" % elt) if len(elts) > 1: if int(elts[0]) > int(elts[1]): raise Error("bad range %s-%s, the first number should be smaller than thesecond" % (elts[0], elts[1])) nums_set.update([str(elt) for elt in range(int(elts[0]), int(elts[1]) + 1)]) else: nums_set.add(elts[0]) result = sorted([int(num) for num in nums_set]) if not ints: result = [str(num) for num in result] return result
def _get_deployables(srcpath, proc): """ Returns the list of "deployables" (driver names or helper tool names) provided by tools or drivers source code directory 'srcpath' on a host defined by 'proc'. """ cmd = f"make --silent -C '{srcpath}' list_deployables" deployables, _ = proc.run_verify(cmd) if deployables: deployables = Trivial.split_csv_line(deployables, sep=" ") return deployables
def _build_colmap(self): """Build and return the turbostat -> CSV column names map.""" self._colmap = {} # Build the turbostat -> CSV column names map. cmd = f"{self._ts_bin} -l" stdout, _ = self._proc.run_verify(cmd) for key in Trivial.split_csv_line(stdout): if key == "Busy%": self._colmap[key] = "CC0%" elif key.startswith("CPU%c"): self._colmap[key] = f"CC{key[5:]}%" elif key.startswith("Pkg%pc"): self._colmap[key] = f"PC{key[6:]}%"
def set_post_trigger(self, path, trange=None): """ Configure the post-trigger - a program that has to be executed after a datapoint is collected. The arguments are as follows. * path - path to the executable program to run. The program will be executed with the '--latency <value>' option, where '<value>' is the observed wake latency value in nanoseconds. * trange - the post-trigger range. By default, the trigger program is executed on every datapoint. But if the trigger range is provided, the trigger program will be executed only when wake latency is in trigger range. """ if not FSHelpers.isexe(path, proc=self._proc): raise Error( f"post-trigger program '{path}' does not exist{self._proc.hostmsg} or it " f"is not an executable file") self._post_trigger = path if trange is not None: vals = Trivial.split_csv_line(trange) for idx, val in enumerate(vals): if not Trivial.is_int(val): raise Error( f"bad post-trigger range value '{val}', should be an integer " f"amount of nanoseconds") vals[idx] = Trivial.str_to_num(val, default=None) if vals[idx] < 0: raise Error( f"bad post trigger range value '{vals[idx]}', should be greater or " f"equal to zero") if len(vals) != 2: raise Error( f"bad post trigger range '{trange}', it should include 2 numbers" ) if vals[1] - vals[0] < 0: raise Error( f"bad post trigger range '{trange}', first number cannot be greater " f"than the second number") self._post_trigger_range = vals
def _normalize_cstates(self, cstates): """ Some methods accept the C-states to operate on as a string or a list. There may be C-state names or indexes in the list. This method turns the user input into a list of C-state indexes and returns this list. """ if isinstance(cstates, int): cstates = str(cstates) if cstates is not None: if isinstance(cstates, str): cstates = Trivial.split_csv_line(cstates, dedup=True) indexes = [] for cstate in cstates: if not Trivial.is_int(cstate): cstate = self._name2idx(cstate) indexes.append(int(cstate)) cstates = indexes return cstates
def _get_latency(self, dp): """ Read the next latency data line from the 'ndlrunner' helper, parse it, and save the result in the 'dp' dictionary. """ line = self._get_line(prefix="datapoint") line = Trivial.split_csv_line(line) if len(line) != 2: msg = self._unexpected_line_error_prefix(line) raise Error( f"{msg}\nExpected 2 comma-separated integers, got {len(line)}") for val in line: if not Trivial.is_int(val): msg = self._unexpected_line_error_prefix(line) raise Error( f"{msg}\n: Expected 2 comma-separated integers, got a non-integer " f"'{val}'") dp["RTD"] = int(line[0]) dp["LDist"] = int(line[1])