| #!/usr/bin/python3 |
| |
| # Copyright (C) 2019 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| import argparse |
| import logging |
| import os |
| import random |
| import time |
| |
| import numpy as np |
| |
| import actions |
| import config |
| |
| LOG_PATH = "/var/logs/loadtester.log" |
| |
| |
| class LoadTestInstance: |
| def __init__(self, test_config): |
| self.config = test_config |
| self.log = logging.getLogger("ActionLogger") |
| |
| self.url = self.config["gerrit"]["url"] |
| self.user = self.config["gerrit"]["user"] |
| self.pwd = self.config["gerrit"]["password"] |
| |
| self.timeout = ( |
| time.time() + self.config["testrun"]["duration"] |
| if self.config["testrun"]["duration"] |
| else None |
| ) |
| |
| self.action_config = self.config["actions"] |
| |
| self.owned_projects = set() |
| if self.config["testrun"]["initialization"]["knownProjects"]: |
| self.owned_projects = set( |
| self.config["testrun"]["initialization"]["knownProjects"] |
| ) |
| |
| self.cloned_projects = set() |
| |
| def prerun(self): |
| if self.config["testrun"]["initialization"]["delay"]["enabled"]: |
| self._wait_random_seconds( |
| self.config["testrun"]["initialization"]["delay"]["min"], |
| self.config["testrun"]["initialization"]["delay"]["max"], |
| ) |
| |
| if self.config["testrun"]["initialization"]["createProjects"]["enabled"]: |
| self._create_initial_projects( |
| self.config["testrun"]["initialization"]["createProjects"]["number"] |
| ) |
| |
| def run(self): |
| while True: |
| if self.timeout and time.time() >= self.timeout: |
| break |
| |
| if self.config["testrun"]["waitBetweenCycles"]["enabled"]: |
| self._wait_random_seconds( |
| self.config["testrun"]["waitBetweenCycles"]["min"], |
| self.config["testrun"]["waitBetweenCycles"]["max"], |
| ) |
| |
| self._exec_create_project_action() |
| self._exec_list_projects_action() |
| |
| if self.owned_projects: |
| self._exec_clone_project_action() |
| |
| if self.cloned_projects: |
| self._exec_fetch_project_action() |
| self._exec_push_head_to_master_action() |
| self._exec_push_change_action() |
| |
| self._exec_query_hundred_open_changes_action() |
| self._exec_review_change_action() |
| |
| def _create_initial_projects(self, num_init_projects): |
| for _ in range(num_init_projects): |
| self.owned_projects.add( |
| actions.CreateProjectAction( |
| self.url, self.user, self.pwd, 1.0 |
| ).execute() |
| ) |
| |
| def _wait_random_seconds(self, min_wait, max_wait): |
| wait_duration = random.randint(min_wait, max_wait) |
| self.log.info("Waiting for %d seconds.", wait_duration) |
| time.sleep(wait_duration) |
| |
| @staticmethod |
| def _choose_from_list_poisson(input_list): |
| probabilities = np.random.poisson(20, len(input_list)) |
| probabilities = probabilities / np.sum(probabilities) |
| return np.random.choice(input_list, 1, p=probabilities).tolist()[0] |
| |
| def _exec_create_project_action(self): |
| action = actions.CreateProjectAction( |
| self.url, |
| self.user, |
| self.pwd, |
| self.action_config["create_project"]["probability"], |
| ) |
| project_name = action.execute() |
| if not action.failed and project_name: |
| self.owned_projects.add(project_name) |
| |
| def _exec_list_projects_action(self): |
| action = actions.QueryProjectsAction( |
| self.url, |
| self.user, |
| self.pwd, |
| self.action_config["query_projects"]["probability"], |
| ) |
| project_name = action.execute() |
| if not action.failed and project_name: |
| self.owned_projects.add(project_name) |
| |
| def _exec_clone_project_action(self): |
| action = actions.CloneProjectAction( |
| self.url, |
| self.user, |
| self.pwd, |
| self._choose_from_list_poisson(list(self.owned_projects)), |
| self.action_config["clone_project"]["probability"], |
| ) |
| action.execute() |
| if not action.failed and action.was_executed: |
| self.cloned_projects.add(action.project_name) |
| |
| def _exec_fetch_project_action(self): |
| action = actions.FetchProjectAction( |
| self._choose_from_list_poisson(list(self.cloned_projects)), |
| self.action_config["fetch_project"]["probability"], |
| ) |
| action.execute() |
| |
| def _exec_push_head_to_master_action(self): |
| action = actions.PushHeadToMasterAction( |
| self._choose_from_list_poisson(list(self.cloned_projects)), |
| self.action_config["push_head_to_master"]["probability"], |
| ) |
| action.execute() |
| |
| def _exec_push_change_action(self): |
| action = actions.PushForReviewAction( |
| self._choose_from_list_poisson(list(self.cloned_projects)), |
| self.action_config["push_for_review"]["probability"], |
| ) |
| action.execute() |
| |
| def _exec_query_hundred_open_changes_action(self): |
| action = actions.QueryHundredOpenChanges( |
| self.url, |
| self.user, |
| self.pwd, |
| self.action_config["query_hundred_open_changes"]["probability"], |
| ) |
| action.execute() |
| |
| def _exec_review_change_action(self): |
| action = actions.ReviewChangeAction( |
| self.url, |
| self.user, |
| self.pwd, |
| self.action_config["review_change"]["probability"], |
| ) |
| action.execute() |
| |
| |
| # pylint: disable=C0103 |
| if __name__ == "__main__": |
| |
| os.makedirs(os.path.dirname(LOG_PATH), exist_ok=True) |
| |
| log_format = "%(asctime)s %(message)s" |
| |
| logging.basicConfig( |
| level=logging.DEBUG, format=log_format, filename=LOG_PATH, filemode="w" |
| ) |
| |
| handler = logging.StreamHandler() |
| handler.setLevel(logging.DEBUG) |
| handler.setFormatter(logging.Formatter(log_format)) |
| logging.getLogger("ActionLogger").addHandler(handler) |
| |
| parser = argparse.ArgumentParser() |
| parser.add_argument( |
| "-U", "--url", help="Gerrit base url", dest="url", action="store" |
| ) |
| |
| parser.add_argument("-u", "--user", help="Gerrit user", dest="user", action="store") |
| |
| parser.add_argument( |
| "-p", "--password", help="Gerrit password", dest="password", action="store" |
| ) |
| |
| parser.add_argument( |
| "-d", |
| "--duration", |
| help="Test duration in seconds", |
| dest="duration", |
| action="store", |
| type=int, |
| ) |
| |
| parser.add_argument( |
| "-c", "--config", help="Configuration file", dest="config_file", action="store" |
| ) |
| |
| args = parser.parse_args() |
| |
| test = LoadTestInstance(config.Parser(args).parse()) |
| test.prerun() |
| test.run() |