def read(self, path, load_files=True): """Read a YAML file into ``self.config_all``. Raises: ConfigurationError """ check_arg(load_files, bool) self.config_all = None if not path.lower().endswith(".yaml"): path += ".yaml" path = os.path.abspath(path) if not os.path.isfile(path): raise ConfigurationError("File not found: {}".format(path)) self.path = path self.root_folder = os.path.dirname(path) self.name = os.path.splitext(os.path.basename(path))[0] with open(path, "rt") as f: try: res = yaml.safe_load(f) except yaml.parser.ParserError as e: raise ConfigurationError("Could not parse YAML: {}".format(e)) from None if not isinstance(res, dict) or not res.get("file_version", "").startswith( "stressor#" ): raise ConfigurationError( "Not a `stressor` file (missing 'stressor#VERSION' tag)." ) self._compile(res) self.file_version = self.validate_config(res) if self.results["warning"]: logger.error("Compiler warnings:") for m in self.results["warning"]: logger.warning(" - {}: {}".format(m["path"], m["msg"])) if self.results["error"]: logger.error("Compiler errors:") for m in self.results["error"]: logger.error(" - {}: {}".format(m["path"], m["msg"])) raise ConfigurationError("Config file had compile errors.") self.config_all = res # Copy values from `config.*` to `context.*` if self.config_all.get("context") is None: self.config_all["context"] = {} for k, v in self.config.items(): self.context.setdefault(k, v) return self.config_all
def _push(self, context): """ Raises: RuntimeError if queue length exceeds ContextStack.MAX_DEPTH """ check_arg(context, RunContext) if len(self.ctx_stack) >= self.MAX_DEPTH: raise RuntimeError("Max depth exceeded ({})".format( self.MAX_DEPTH)) self.ctx_stack.append(context) return context
def match_value(pattern, value, info): """Return .""" check_arg(pattern, str) value = str(value) if "." in pattern or "*" in pattern: match = re.match(pattern, value) # , re.MULTILINE) else: match = pattern == value if not match: msg = "`{}` value {!r} does not match pattern {!r}".format( info, value, pattern) return False, msg return True, None
def push(self, name, attributes=None, copy_data=False): """Push `name` to the stack and optionally update or copy context. Args: name (str): Raises: RuntimeError if queue length exceeds ContextStack.MAX_DEPTH """ check_arg(name, str) check_arg(attributes, dict, or_none=True) try: parent = self.peek() except IndexError: parent = None ctx = RunContext(parent, name, attributes, copy_data) return self._push(ctx)
def report_error(self, msg, level="error", exc=None, stack=None): """Called by activity and macro constructors to signal errors or warnings. The compiler also calls this when a constructor raises an exception. """ check_arg(level, str, level in ("error", "warning")) path = stack if stack else str(self.stack) hint = "{}: {}".format(path, msg) if exc: logger.exception(hint) # No need to log, since self.results are also summarized later # elif level == "warning": # logger.warning(hint) # else: # logger.error(hint) self.results[level].append({"msg": msg, "path": path})
def update_config(self, extra_config, context_only=False): """Override self.config (and self.context) with new items. self.config was already copied to self.context, so normally we want to update both. Args: extra_config (dict): new values context_only (bool): pass true to only set the shadow-copy (i.e. context) """ check_arg(extra_config, dict, or_none=True) if not extra_config: return config = self.config context = self.context for k, v in extra_config.items(): if not context_only: logger.info("Set config.{}: {!r} -> {!r}".format(k, config.get(k), v)) config[k] = v else: logger.info("Set context.{}: {!r} -> {!r}".format(k, context.get(k), v)) context[k] = v return
def __init__(self, parent, name, update_attributes=None, copy_data=False): """ Args: parent (:class:`RunContext`): The stack frame parent or None if this is the root. name (str): A short name for this context. The context manager uses it to concatenate a path string for the current scope. update_attributes (dict): A dict of attributes that are explicitly defined by this instance. copy_data (bool): """ check_arg(parent, RunContext, or_none=True) check_arg(name, str, name != "") check_arg(update_attributes, dict, or_none=True) check_arg(copy_data, bool) self.parent = parent self.name = name if update_attributes is None: self.own_attributes = {} else: self.own_attributes = deepcopy(update_attributes) if parent is None: self.all_attributes = self.own_attributes elif copy_data: # Create a copy of the parent's context, so the original state is # restored on pull self.all_attributes = deepcopy(parent.all_attributes) if self.own_attributes: self.all_attributes.update(self.own_attributes) else: # We only reference the parent context instance, so change will persist # after a popping from this stack self.all_attributes = parent.all_attributes return
def __init__(self, config_manager, **activity_args): """""" super().__init__(config_manager, **activity_args) path = activity_args.get("path") script = activity_args.get("script") # Allow to pass `export: null` to define 'no export wanted' # (omitting the argumet is considered 'undefined' and will emit a # warning if the script produces variables) export = activity_args.get("export", NO_DEFAULT) if export in (None, False): export = tuple() elif export is NO_DEFAULT: export = None check_arg(path, str, or_none=True) check_arg(script, str, or_none=True) check_arg(export, (str, list, tuple), or_none=True) if path: if script: raise ActivityCompileError( "`path` and `script` args are mutually exclusive" ) path = config_manager.resolve_path(path) with open(path, "rt") as f: script = f.read() #: self.script = compile(script, path, "exec") elif script: # script = dedent(self.script) self.script = compile(script, "<string>", "exec") else: raise ActivityCompileError("Either `path` or `script` expected") #: Store a shortened code snippet for debug output self.source = shorten_string(dedent(script), 500, 100) # print(self.source) if export is None: self.export = None elif isinstance(export, str): self.export = set((export,)) else: self.export = set(export) return
def run(self, options, extra_context=None): """Run the current Args: options (dict): see RunManager.DEFAULT_OPTS extra_context (dict, optional): Returns: (int) Exit code 0 if no errors occurred """ check_arg(options, dict) check_arg(extra_context, dict, or_none=True) self.options.update(options) if extra_context: self.config_manager.update_config(extra_context) context = self.config_manager.context sessions = self.config_manager.sessions count = int(sessions.get("count", 1)) if count > 1 and self.config_manager.config.get("force_single"): logger.info("force_single: restricting sessions count to one.") count = 1 # Construct a `User` with at least 'name', 'password', and optional # custom attributes user_list = [] for user_dict in sessions["users"]: user = User(**user_dict) user_list.append(user) # We have N users and want `count` sessions: re-use round-robin user_list = itertools.islice(itertools.cycle(user_list), 0, count) user_list = list(user_list) monitor = None if self.options.get("monitor"): monitor = MonitorServer(self) monitor.start() time.sleep(0.5) monitor.open_browser() self.start_stamp = time.monotonic() self.start_dt = datetime.now() self.end_dt = None self.end_stamp = None try: try: res = False res = self.run_in_threads(user_list, context) except KeyboardInterrupt: # if not self.stop_request.is_set(): logger.warning("Caught Ctrl-C: terminating...") self.stop() finally: self.end_dt = datetime.now() self.end_stamp = time.monotonic() if self.options.get("log_summary", True): logger.important(self.get_cli_summary()) if monitor: self.set_stage("waiting") logger.important("Waiting for monitor... Press Ctrl+C to quit.") self.stop_request.wait() finally: if monitor: monitor.shutdown() # print("RES", res, self.has_errors(), self.stats.format_result()) self.set_stage("stopped") return res
def set_stage(self, stage): check_arg(stage, str, stage in self.STAGES) logger.info("Enter stage '{}'".format(stage.upper())) self.stage = stage
def __init__(self, config_manager, **activity_args): super().__init__(config_manager, **activity_args) check_arg(activity_args.get("method"), str) check_arg(activity_args.get("url"), str) check_arg(activity_args.get("params"), dict, or_none=True)
def __init__(self, run_manager, context, session_id, user): # check_arg(run_manager, RunManager) check_arg(context, dict) check_arg(session_id, str) check_arg(user, User, or_none=True) #: The :class:`RunManager` object that holds global settings and definitions self.run_manager = run_manager config = run_manager.config_manager.config # (dict) Global variables for this session. Initialized from the # run configuration, but not shared between sessions. # (`self.context` is accessible by the respective property below.) context = context.copy() #: (str) Unique ID string for this session self.session_id = session_id #: The :class:`User` object that is assigned to this session self.user = user or User("anonymous", "") #: (dict) Copy of `run_config.sessions` configuration self.sessions = run_manager.config_manager.sessions.copy() #: (dict) Activities can store per-session data here. #: Note that the activity objects are instintiated only once and shared #: by all sessions. self.data = {} #: (bool) True: only simulate activities self.dry_run = bool(context.get("dry_run")) #: (int) Verbosity 0..5 self.verbose = context.get("verbose", 3) #: (:class:`threading.Event`) self.stop_request = run_manager.stop_request # Set some default entries in context dict # context.setdefault("timeout", self.DEFAULT_REQUEST_TIMEOUT) context.setdefault("session_id", self.session_id) context.setdefault("user", self.user) #: The :class:`~stressor.context_stack.ContextStack` object that reflects the current execution path self.context_stack = ContextStack(run_manager.host_id, context) self.context_stack.push(run_manager.process_id) self.context_stack.push(session_id) #: :class:`~stressor.statistic_manager.StatisticManager` object that containscurrent execution path self.stats = run_manager.stats # Lazy initialization using a property self._browser_session = None #: (int) Stop session if global error count > X #: Passing `--max-errors` will override this. self.max_errors = int(config.get("max_errors", 0)) #: (float) Stop session if total run time > X seconds. #: Passing `--max-time` will override this. self.max_time = float(config.get("max_time", 0.0)) self._cancelled_seq = None # Used by StatisticsManager self.pending_sequence = None self.sequence_start = None self.pending_activity = None self.activity_start = None self.stats.register_session(self)
def __init__(self, config_manager, **activity_args): check_arg(activity_args.get("duration"), (str, int, float)) check_arg(activity_args.get("duration_2"), (str, int, float), or_none=True) super().__init__(config_manager, **activity_args)
def get_attr(self, key_path, context=None): check_arg(key_path, str) check_arg(context, RunContext, or_none=True) if context is None: context = self.peek() return get_dict_attr(self.as_dict(), key_path)
def foo(name, amount, options=None): check_arg(name, str) check_arg(amount, (int, float), amount > 0) check_arg(options, dict, or_none=True) return name