Skip to content
Browse files

Merge pull request #13 from jenisys/master

FIXED issue #12, windows compatibility, new script loading
  • Loading branch information...
2 parents c39e335 + 4eb08e3 commit 8057014acbc8368b14ca4da7e9a3cd1facc6ffdd Corey Goldberg committed Jun 13, 2012
Showing with 182 additions and 58 deletions.
  1. +1 −1 multimechanize/__init__.py
  2. +32 −26 multimechanize/core.py
  3. +110 −0 multimechanize/script_loader.py
  4. +39 −31 multimechanize/utilities/run.py
View
2 multimechanize/__init__.py
@@ -6,4 +6,4 @@
# This file is part of Multi-Mechanize | Performance Test Framework
#
-__version__ = '1.2.1dev'
+__version__ = '1.2.0.1'
View
58 multimechanize/core.py
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
#
# Copyright (c) 2010-2012 Corey Goldberg (corey@goldb.org)
# License: GNU LGPLv3
@@ -7,29 +8,40 @@
#
-import glob
import multiprocessing
import os
import sys
import threading
import time
-
+from multimechanize.script_loader import ScriptLoader
+import os.path
def init(projects_dir, project_name):
+ """
+ Sanity check that all test scripts can be loaded.
+ """
scripts_path = '%s/%s/test_scripts' % (projects_dir, project_name)
if not os.path.exists(scripts_path):
sys.stderr.write('\nERROR: can not find project: %s\n\n' % project_name)
sys.exit(1)
- sys.path.append(scripts_path)
- for f in glob.glob( '%s/*.py' % scripts_path): # import all test scripts as modules
- f = f.replace(scripts_path, '').replace(os.sep, '').replace('.py', '')
- exec('import %s' % f) in globals()
+ # -- NORMAL-CASE: Ensure that all scripts can be loaded (at program start).
+ ScriptLoader.load_all(scripts_path, validate=True)
+def load_script(script_file):
+ """
+ Load a test scripts as Python module.
+ :returns: Imported script as python module.
+ """
+ module = ScriptLoader.load(script_file)
+ # -- SKIP-HERE: ScriptValidator.ensure_module_valid(module)
+ # NOTE: Performed above in ScriptLoader.load_all() at process start.
+ return module
class UserGroup(multiprocessing.Process):
- def __init__(self, queue, process_num, user_group_name, num_threads, script_file, run_time, rampup):
+ def __init__(self, queue, process_num, user_group_name, num_threads,
+ script_file, run_time, rampup):
multiprocessing.Process.__init__(self)
self.queue = queue
self.process_num = process_num
@@ -41,12 +53,17 @@ def __init__(self, queue, process_num, user_group_name, num_threads, script_file
self.start_time = time.time()
def run(self):
+ # -- ENSURE: (Re-)Import script_module in forked Process
+ script_module = load_script(self.script_file)
threads = []
for i in range(self.num_threads):
spacing = float(self.rampup) / float(self.num_threads)
if i > 0:
time.sleep(spacing)
- agent_thread = Agent(self.queue, self.process_num, i, self.start_time, self.run_time, self.user_group_name, self.script_file)
+ agent_thread = Agent(self.queue, self.process_num, i,
+ self.start_time, self.run_time,
+ self.user_group_name,
+ script_module, self.script_file)
agent_thread.daemon = True
threads.append(agent_thread)
agent_thread.start()
@@ -56,17 +73,20 @@ def run(self):
class Agent(threading.Thread):
- def __init__(self, queue, process_num, thread_num, start_time, run_time, user_group_name, script_file):
+ def __init__(self, queue, process_num, thread_num, start_time, run_time,
+ user_group_name, script_module, script_file):
threading.Thread.__init__(self)
self.queue = queue
self.process_num = process_num
self.thread_num = thread_num
self.start_time = start_time
self.run_time = run_time
self.user_group_name = user_group_name
- self.script_file = script_file
+ self.script_module = script_module
+ self.script_file = script_file
- # choose most accurate timer to use (time.clock has finer granularity than time.time on windows, but shouldn't be used on other systems)
+ # choose most accurate timer to use (time.clock has finer granularity
+ # than time.time on windows, but shouldn't be used on other systems).
if sys.platform.startswith('win'):
self.default_timer = time.clock
else:
@@ -75,21 +95,7 @@ def __init__(self, queue, process_num, thread_num, start_time, run_time, user_gr
def run(self):
elapsed = 0
-
- if self.script_file.lower().endswith('.py'):
- module_name = self.script_file.replace('.py', '')
- else:
- sys.stderr.write('ERROR: scripts must have .py extension. can not run test script: %s. aborting user group: %s\n' % (self.script_file, self.user_group_name))
- return
- try:
- trans = eval(module_name + '.Transaction()')
- except NameError, e:
- sys.stderr.write('ERROR: can not find test script: %s. aborting user group: %s\n' % (self.script_file, self.user_group_name))
- return
- #except Exception, e:
- # sys.stderr.write('ERROR: failed initializing Transaction: %s. aborting user group: %s\n' % (self.script_file, self.user_group_name))
- # return
-
+ trans = self.script_module.Transaction()
trans.custom_timers = {}
# scripts have access to these vars, which can be useful for loading unique data
View
110 multimechanize/script_loader.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+"""
+REPLACE: multi-mechanize exec()/eval() magic with real imports.
+"""
+
+import glob
+import inspect
+import os.path
+import sys
+
+class InvalidScriptError(StandardError):
+ """
+ Should be raised when a Script does not confirm to required interface.
+
+ SCRIPT INTERFACE:
+ - Transaction class exists.
+ - Transaction.run() method exists.
+ """
+
+class ScriptValidator(object):
+ """
+ Utility class to ensure that scripts are valid and conforms to conventions.
+ """
+
+ @staticmethod
+ def check_module_invalid(module):
+ """
+ Check if a script module is invalid and does not comply w/ conventions
+ :returns: Problem as string, if any is found.
+ :returns: None, if no problems are detected.
+ """
+ transaction_class = getattr(module, "Transaction", None)
+ if not transaction_class:
+ return "{module}.Transaction class missing".format(
+ module=module.__name__)
+ run_method = getattr(transaction_class, "run", None)
+ if not run_method:
+ return "{module}.Transaction.run() method is missing".format(
+ module=module.__name__)
+ if not callable(run_method):
+ return "{module}.Transaction.run() method is not callable".format(
+ module=module.__name__)
+ # -- EVERYTHING CHECKED: No problems detected.
+ return None
+
+ @classmethod
+ def ensure_module_valid(cls, module):
+ """
+ Ensures that a script module is valid.
+ :raises: InvalidScriptError, if any convention is violated.
+ """
+ problem = cls.check_module_invalid(module)
+ if problem:
+ raise InvalidScriptError, problem
+
+class ScriptLoader(object):
+ """Utility class to load scripts as python modules."""
+
+ @staticmethod
+ def load(path):
+ """
+ Load a script by using a path.
+ :returns: Loaded script module-
+ :raise: ImportError, when script module cannot be loaded.
+ """
+ module_name = inspect.getmodulename(path).replace("-", "_")
+ module_dirname = os.path.dirname(path)
+ if not module_dirname:
+ module_dirname = os.curdir
+ if not os.path.exists(path):
+ raise ImportError("File not found: %s" % path)
+ module_dirnamea = os.path.abspath(module_dirname)
+ if not sys.path or module_dirnamea != sys.path[0]:
+ sys.path.insert(0, module_dirnamea)
+
+ module = None
+ try:
+ module = __import__(module_name)
+ # module.__name__ = module_name
+ # module.__file__ = path
+ except ImportError, e:
+ print "IMPORT-ERROR: %s (file=%s, curdir=%s)" % \
+ (module_name, path, os.getcwd())
+ sys.stderr.write("Cannot import: %s\n" % e)
+ for index, searchpath in enumerate(sys.path):
+ print " %2s. %s" % (index, searchpath)
+ raise
+ return module
+
+ @classmethod
+ def load_all(cls, scripts_path, validate=False):
+ """
+ Load all python scripts in a path.
+ :returns: Loaded script modules as dictionary.
+ """
+ if not os.path.isdir(scripts_path):
+ return None
+ pattern = "%s/*.py" % scripts_path
+ modules = dict()
+ for script in glob.glob(pattern): #< import all scripts as modules
+ basename = os.path.basename(script)
+ if basename.startswith("_"):
+ continue #< SKIP: __init__.py, ...
+ module = cls.load(os.path.normpath(script))
+ modules[module.__name__] = module
+ if validate:
+ ScriptValidator.ensure_module_valid(module)
+ return modules
+
View
70 multimechanize/utilities/run.py
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
#
# Copyright (c) 2010-2012 Corey Goldberg (corey@goldb.org)
# License: GNU LGPLv3
@@ -12,7 +13,7 @@
import multiprocessing
import optparse
import os
-import Queue
+# -- NOT-NEEDED: import Queue
import shutil
import subprocess
import sys
@@ -31,47 +32,49 @@
import multimechanize.results as results
import multimechanize.resultswriter as resultswriter
import multimechanize.progressbar as progressbar
-
-
-usage = 'Usage: %prog <project name> [options]'
-parser = optparse.OptionParser(usage=usage)
-parser.add_option('-p', '--port', dest='port', type='int', help='rpc listener port')
-parser.add_option('-r', '--results', dest='results_dir', help='results directory to reprocess')
-parser.add_option('-b', '--bind-addr', dest='bind_addr', help='rpc bind address', default='localhost')
-parser.add_option('-d', '--directory', dest='projects_dir', help='directory containing project folder', default='.')
-cmd_opts, args = parser.parse_args()
-
-try:
- project_name = args[0]
-except IndexError:
- sys.stderr.write('\nERROR: no project specified\n\n')
- sys.stderr.write('%s\n' % usage)
- sys.stderr.write('Example: multimech-run my_project\n\n')
- sys.exit(1)
-
-core.init(cmd_opts.projects_dir, project_name)
-
-
-
+from multimechanize import __version__ as VERSION
def main():
+ """
+ Main function to run multimechanize benchmark/performance test.
+ """
+
+ usage = 'Usage: %prog <project name> [options]'
+ parser = optparse.OptionParser(usage=usage, version=VERSION)
+ parser.add_option('-p', '--port', dest='port', type='int', help='rpc listener port')
+ parser.add_option('-r', '--results', dest='results_dir', help='results directory to reprocess')
+ parser.add_option('-b', '--bind-addr', dest='bind_addr', help='rpc bind address', default='localhost')
+ parser.add_option('-d', '--directory', dest='projects_dir', help='directory containing project folder', default='.')
+ cmd_opts, args = parser.parse_args()
+
+ try:
+ project_name = args[0]
+ except IndexError:
+ sys.stderr.write('\nERROR: no project specified\n\n')
+ sys.stderr.write('%s\n' % usage)
+ sys.stderr.write('Example: multimech-run my_project\n\n')
+ sys.exit(1)
+
+ core.init(cmd_opts.projects_dir, project_name)
+
+ # -- ORIGINAL-MAIN:
if cmd_opts.results_dir: # don't run a test, just re-process results
- rerun_results(cmd_opts.results_dir)
+ rerun_results(project_name, cmd_opts, cmd_opts.results_dir)
elif cmd_opts.port:
import multimechanize.rpcserver
multimechanize.rpcserver.launch_rpc_server(cmd_opts.bind_addr, cmd_opts.port, project_name, run_test)
else:
- run_test()
+ run_test(project_name, cmd_opts)
return
-def run_test(remote_starter=None):
+def run_test(project_name, cmd_opts, remote_starter=None):
if remote_starter is not None:
remote_starter.test_running = True
remote_starter.output_dir = None
- run_time, rampup, results_ts_interval, console_logging, progress_bar, results_database, post_run_script, xml_report, user_group_configs = configure(project_name)
+ run_time, rampup, results_ts_interval, console_logging, progress_bar, results_database, post_run_script, xml_report, user_group_configs = configure(project_name, cmd_opts)
run_localtime = time.localtime()
output_dir = '%s/%s/results/results_%s' % (cmd_opts.projects_dir, project_name, time.strftime('%Y.%m.%d_%H.%M.%S/', run_localtime))
@@ -82,9 +85,14 @@ def run_test(remote_starter=None):
rw.daemon = True
rw.start()
+ script_prefix = os.path.join(cmd_opts.projects_dir, project_name, "test_scripts")
+ script_prefix = os.path.normpath(script_prefix)
+
user_groups = []
for i, ug_config in enumerate(user_group_configs):
- ug = core.UserGroup(queue, i, ug_config.name, ug_config.num_threads, ug_config.script_file, run_time, rampup)
+ script_file = os.path.join(script_prefix, ug_config.script_file)
+ ug = core.UserGroup(queue, i, ug_config.name, ug_config.num_threads,
+ script_file, run_time, rampup)
user_groups.append(ug)
for user_group in user_groups:
user_group.start()
@@ -159,10 +167,10 @@ def run_test(remote_starter=None):
-def rerun_results(results_dir):
+def rerun_results(project_name, cmd_opts, results_dir):
output_dir = '%s/%s/results/%s/' % (cmd_opts.projects_dir, project_name, results_dir)
saved_config = '%s/config.cfg' % output_dir
- run_time, rampup, results_ts_interval, console_logging, progress_bar, results_database, post_run_script, xml_report, user_group_configs = configure(project_name, config_file=saved_config)
+ run_time, rampup, results_ts_interval, console_logging, progress_bar, results_database, post_run_script, xml_report, user_group_configs = configure(project_name, cmd_opts, config_file=saved_config)
print '\n\nanalyzing results...\n'
results.output_results(output_dir, 'results.csv', run_time, rampup, results_ts_interval, user_group_configs, xml_report)
print 'created: %sresults.html\n' % output_dir
@@ -172,7 +180,7 @@ def rerun_results(results_dir):
-def configure(project_name, config_file=None):
+def configure(project_name, cmd_opts, config_file=None):
user_group_configs = []
config = ConfigParser.ConfigParser()
if config_file is None:

0 comments on commit 8057014

Please sign in to comment.
Something went wrong with that request. Please try again.