cloudstore-developers
Changes
bin/config.aws.ini 28(+28 -0)
bin/config.openstack.ini 20(+20 -0)
bin/run.py 25(+25 -0)
CHANGES.txt 1(+1 -0)
cloudscale/__init__.py 1(+1 -0)
cloudscale/distributed_jmeter/aws.py 471(+471 -0)
cloudscale/distributed_jmeter/openstack.py 119(+119 -0)
cloudscale/distributed_jmeter/run_test.py 77(+77 -0)
LICENSE.txt 0(+0 -0)
MANIFEST 22(+22 -0)
MANIFEST.in 1(+1 -0)
README.txt 43(+43 -0)
requirements.txt 28(+28 -0)
setup.py 21(+21 -0)
Details
bin/config.aws.ini 28(+28 -0)
diff --git a/bin/config.aws.ini b/bin/config.aws.ini
new file mode 100644
index 0000000..d730cc9
--- /dev/null
+++ b/bin/config.aws.ini
@@ -0,0 +1,28 @@
+[SHOWCASE]
+autoscalable = no
+host = localhost
+frontend_instances_id = cloudscale
+
+[SCENARIO]
+num_threads = 5000
+ips = <ip adresses of instances to deploy JMeter on>
+jmeter_url = http://cloudscale.xlab.si/jmeter/jmeter_master.tar.gz
+
+[AWS]
+region = eu-west-1
+aws_access_key_id = <your access key>
+aws_secret_access_key = <your secret access key>
+region = eu-west-1
+availability_zones = eu-west-1a
+
+[EC2]
+instance_type = t2.medium
+remote_user = ubuntu
+ami_id = ami-4d5bd93a
+key_name = distributed_jmeter
+key_pair = <auto-generated>
+
+[RDS]
+identifiers = cloudscale-master
+
+
bin/config.openstack.ini 20(+20 -0)
diff --git a/bin/config.openstack.ini b/bin/config.openstack.ini
new file mode 100644
index 0000000..57c541b
--- /dev/null
+++ b/bin/config.openstack.ini
@@ -0,0 +1,20 @@
+[SHOWCASE]
+autoscalable = no
+host = localhost
+frontend_instances_id = cloudscale
+
+[SCENARIO]
+num_threads = 2000
+instance_names = <names of instances to deploy JMeter on>
+jmeter_url = http://cloudscale.xlab.si/jmeter/jmeter_master.tar.gz
+
+[OPENSTACK]
+user = dummy_user
+pwd = dummy_pass
+tenant = tenant
+url = http://localhost:5000/v2.0
+image = distributed-jmeter-slave1
+instance_type = 4GB-2CPU-10GB
+key_name = distributed-jmeter
+key_pair_path = /path/to/key_pair
+remote_user = ubuntu
bin/run.py 25(+25 -0)
diff --git a/bin/run.py b/bin/run.py
new file mode 100644
index 0000000..26a00c1
--- /dev/null
+++ b/bin/run.py
@@ -0,0 +1,25 @@
+#!/usr/bin/python
+
+import sys
+import os
+from cloudscale.distributed_jmeter import run_test
+from cloudscale.distributed_jmeter.logger import Logger
+from cloudscale.distributed_jmeter.scripts import meet_sla_req
+
+if __name__ == "__main__":
+
+ if len(sys.argv) > 1:
+ infrastructure = sys.argv[1]
+ config_path = sys.argv[2]
+ scenario_path = sys.argv[3]
+ logger = Logger("distributed_jmeter.log")
+
+ results_path = run_test.run_test(infrastructure, config_path, scenario_path, "%s/results" % os.path.abspath(os.path.dirname(__file__)), logger)
+
+ with open("%s/SLO_violations" % results_path, "w") as fp:
+ output = meet_sla_req.check("%s/response-times-over-time.csv" % results_path)
+ fp.write(output)
+
+ print "See results in %s" % results_path
+ else:
+ print """Usage: python run.py <aws|openstack> <path_to_config> <path_to_scenario>"""
CHANGES.txt 1(+1 -0)
diff --git a/CHANGES.txt b/CHANGES.txt
new file mode 100644
index 0000000..27b2995
--- /dev/null
+++ b/CHANGES.txt
@@ -0,0 +1 @@
+v1, 2015-02-24 -- Initial release
cloudscale/__init__.py 1(+1 -0)
diff --git a/cloudscale/__init__.py b/cloudscale/__init__.py
new file mode 100644
index 0000000..8f76346
--- /dev/null
+++ b/cloudscale/__init__.py
@@ -0,0 +1 @@
+__author__ = 'ivansek'
diff --git a/cloudscale/distributed_jmeter/__init__.py b/cloudscale/distributed_jmeter/__init__.py
new file mode 100644
index 0000000..8f76346
--- /dev/null
+++ b/cloudscale/distributed_jmeter/__init__.py
@@ -0,0 +1 @@
+__author__ = 'ivansek'
cloudscale/distributed_jmeter/aws.py 471(+471 -0)
diff --git a/cloudscale/distributed_jmeter/aws.py b/cloudscale/distributed_jmeter/aws.py
new file mode 100644
index 0000000..a11b661
--- /dev/null
+++ b/cloudscale/distributed_jmeter/aws.py
@@ -0,0 +1,471 @@
+import re
+import os
+import time
+import datetime
+import subprocess
+import select
+from threading import Thread
+import shutil
+
+import boto.rds
+import boto
+import boto.ec2
+import boto.ec2.cloudwatch
+import boto.ec2.autoscale
+from boto.exception import EC2ResponseError
+import paramiko
+
+
+from cloudscale.distributed_jmeter.scripts.meet_sla_req import check
+from cloudscale.distributed_jmeter.scripts.visualization.visualize import Visualize
+
+
+class AWS:
+ def __init__(self, cfg, scenario_path, r_path, output_path, logger, test=False):
+ self.cfg = cfg
+ self.r_path = r_path
+ self.logger = logger
+ self.scenario_path = scenario_path
+ self.output_directory = output_path
+ if not test:
+ self.init()
+ self.start()
+
+ def init(self):
+ self.key_name = self.cfg.get('EC2', 'key_name')
+ self.startup_threads = self.cfg.get('TEST', 'startup_threads')
+ self.rest_threads = self.cfg.get('TEST', 'rest_threads')
+ self.host = self.cfg.get('SHOWCASE', 'host')
+ self.user = self.cfg.get('EC2', 'remote_user')
+ self.jmeter_url = self.cfg.get('SCENARIO', 'jmeter_url')
+ self.region = self.cfg.get('AWS', 'region')
+ self.access_key = self.cfg.get('AWS', 'aws_access_key_id')
+ self.secret_key = self.cfg.get('AWS', 'aws_secret_access_key')
+ self.num_jmeter_slaves = int(self.cfg.get('TEST', 'num_jmeter_slaves'))
+ self.frontend_instances_identifier = self.cfg.get('SHOWCASE', 'frontend_instances_id')
+ self.rds_identifiers = self.cfg.get('RDS', 'identifiers').split(',')
+ self.is_autoscalable = True if self.cfg.get('SHOWCASE', 'autoscalable') == 'yes' else False
+ self.num_threads = int(self.cfg.get('SCENARIO', 'num_threads'))
+ self.instance_type = self.cfg.get('EC2', 'instance_type')
+ self.ami_id = self.cfg.get('EC2', 'ami_id')
+ self.scenario_duration = self.cfg.get('SCENARIO', 'duration_in_minutes')
+
+ self.conn = boto.ec2.connect_to_region(
+ self.region,
+ aws_access_key_id=self.access_key,
+ aws_secret_access_key=self.secret_key
+ )
+
+ self.key_pair = self.create_keypair()
+
+ self.create_security_groups()
+
+ def start(self):
+ ips = []
+ for i in xrange(self.num_jmeter_slaves):
+ instance = self.create_instance("Creating master instance {0} ...".format(i + 1))
+ time.sleep(15)
+ self.logger.log(instance.ip_address)
+ self.setup_master(instance.ip_address)
+ ips.append(instance.ip_address)
+
+ self.run_masters(ips)
+
+ def setup_master(self, ip_addr):
+ ssh = self.ssh_to_instance(ip_addr)
+
+ scp = paramiko.SFTPClient.from_transport(ssh.get_transport())
+ dirname = os.path.abspath(os.path.dirname(__file__))
+ _, stdout, _ = ssh.exec_command('rm -rf /home/' + self.user + '/*')
+ stdout.readlines()
+
+ self.logger.log("Transfering jmeter_master.tar.gz ")
+ _, stdout, _ = ssh.exec_command("wget -q -T90 %s -O jmeter.tar.gz" % self.jmeter_url)
+ self.wait_for_command(stdout)
+
+ self.logger.log("Transfering JMeter scenario ...")
+ scp.put(self.scenario_path, 'scenario.jmx')
+
+ self.logger.log("Unpacking JMeter ...")
+ _, stdout, _ = ssh.exec_command("tar xvf jmeter.tar.gz")
+ self.wait_for_command(stdout)
+
+ _, stdout, _ = ssh.exec_command("find . -iname '._*' -exec rm -rf {} \;")
+ self.wait_for_command(stdout)
+
+ def wait_for_command(self, stdout, verbose=False):
+ # Wait for the command to terminate
+ while not stdout.channel.exit_status_ready():
+ # Only print data if there is data to read in the channel
+ if stdout.channel.recv_ready():
+ rl, wl, xl = select.select([stdout.channel], [], [], 0.0)
+ if len(rl) > 0:
+ response = stdout.channel.recv(1024)
+ if verbose:
+ print response
+
+
+ def ssh_to_instance(self, ip_addr, i=0):
+ try:
+ if i < 3:
+ ssh = paramiko.SSHClient()
+ ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+ if self.key_pair:
+ ssh.connect(ip_addr, username=self.user, key_filename=os.path.abspath(self.key_pair))
+ else:
+ ssh.connect(ip_addr, username=self.user, password="")
+ return ssh
+ raise Exception('Failed 3 times to SSH to %s' % ip_addr)
+ except Exception as e:
+ self.logger.log('%s\nTrying to reconnect ...' % e.message)
+ time.sleep(30)
+ return self.ssh_to_instance(ip_addr, i=i + 1)
+
+
+ def run_masters(self, ips):
+ start_time = datetime.datetime.utcnow()
+
+ tmp_userpath = "/tmp/{0}".format(os.path.basename(self.scenario_path)[:-4])
+
+ if not os.path.exists(tmp_userpath):
+ os.makedirs(tmp_userpath, 0777)
+
+ self.logger.log(self.output_directory)
+
+ for ip in ips:
+ self.logger.log("Running JMeter on instance %s" % ip)
+ ssh = self.ssh_to_instance(ip)
+ cmd = "(~/jmeter/bin/jmeter -n -t ~/scenario.jmx -l scenario.jtl -j scenario.log -Jall_threads=%s -Jstartup_threads=%s -Jrest_threads=%s -Jhost=%s;touch finish)" % (
+ int(self.startup_threads)+int(self.rest_threads),
+ self.startup_threads,
+ self.rest_threads,
+ self.host
+ )
+ self.logger.log(cmd)
+ self.logger.log("Executing your JMeter scenario. This can take a while. Please wait ...")
+ ssh.exec_command(cmd)
+ ssh.close()
+
+ i = 1
+ threads = []
+ for ip in ips:
+ self.logger.log("Starting thread for %s" % ip)
+ t = Thread(target=self.check_instance, args=(i, tmp_userpath, self.output_directory, ip))
+ t.start()
+ threads.append(t)
+ i += 1
+
+ for t in threads:
+ t.join()
+
+ self.terminate_instances(ips)
+ end_time = datetime.datetime.utcnow()
+
+ instances = self.get_instances_by_tag('Name', self.frontend_instances_identifier);
+ instance_ids = [instance.id for instance in instances]
+ rds_instance_ids = self.rds_identifiers
+ ec2_data = self.get_cloudwatch_ec2_data(start_time, end_time, instance_ids)
+ rds_data = self.get_cloudwatch_rds_data(start_time, end_time, rds_instance_ids)
+
+ resultspath = self.output_directory
+
+ cmd = "cp -r {0}/./ {1}/".format(tmp_userpath, resultspath)
+ self.logger.log(cmd)
+ p = subprocess.check_output(cmd.split())
+
+ shutil.rmtree(tmp_userpath, True)
+
+ filenames = ["{0}/scenario{1}.log".format(resultspath, j) for j in xrange(1, i)]
+ self.logger.log(filenames)
+ with open("{0}/scenario.log".format(resultspath), 'w') as outfile:
+ for fname in filenames:
+ with open(fname) as infile:
+ for line in infile:
+ outfile.write(line)
+ cmd = "rm -rf %s" % " ".join(filenames)
+ subprocess.call(cmd.split())
+ cmd = "rm -rf %s/*.jtl" % resultspath
+ subprocess.call(cmd.split())
+
+ filenames = ["{0}/response-times-over-time{1}.csv".format(resultspath, j) for j in xrange(1, i)]
+ self.logger.log(filenames)
+ with open("{0}/response-times-over-time.csv".format(resultspath), 'w') as outfile:
+ for fname in filenames:
+ with open(fname) as infile:
+ for line in infile:
+ outfile.write(line)
+
+ cmd = "rm -rf %s" % " ".join(filenames)
+ subprocess.call(cmd.split())
+
+ filename = "{0}/ec2-cpu.csv".format(resultspath)
+ with open(filename, 'w') as fp:
+ fp.write("instance_id,timestamp,average\n")
+ for row in ec2_data:
+ for data in row.get('data'):
+ fp.write("%s,%s,%s\n" % (row.get('instance_id'), self.unix_time(data['Timestamp']), data['Average']))
+
+ filename = "{0}/rds-cpu.csv".format(resultspath)
+ with open(filename, 'w') as fp:
+ fp.write("instance_id,timestamp,average\n")
+ for row in rds_data:
+ for data in row.get('data'):
+ fp.write("%s,%s,%s\n" % (row.get('instance_id'), self.unix_time(data['Timestamp']), data['Average']))
+
+ if self.is_autoscalable:
+ activites = self.get_autoscalability_data(start_time, end_time)
+ self.write_autoscalability_data(resultspath, activites)
+ else:
+ self.write_autoscalability_data(resultspath, [])
+
+ slo_output = check("{0}/response-times-over-time.csv".format(resultspath))
+ self.logger.log("<br>".join(slo_output).split('\n'))
+ self.logger.log("Visualizing....")
+ v = Visualize(self.num_threads, self.scenario_duration, self.r_path,
+ "{0}/response-times-over-time.csv".format(resultspath),
+ "{0}/autoscalability.log".format(resultspath))
+ v.save()
+
+ self.logger.log("finished!", fin=True)
+ with open("{0}/finish".format(resultspath), "w") as fp:
+ fp.write("finish")
+
+ def unix_time(self, dt):
+
+ epoch = datetime.datetime.fromtimestamp(0)
+ delta = dt - epoch
+ return delta.total_seconds()
+
+ def unix_time_millis(self,dt):
+ return self.unix_time(dt) * 1000.0
+
+ def get_autoscalability_data(self, start_time, end_time):
+ def get_action(activity):
+ if activity.description.lower().find('terminating') > -1:
+ return 'terminate'
+ return 'launch'
+
+ def filter_activites(activites):
+ filtered_activites = []
+ for activity in activites:
+ instance_id = re.search('(i-.*)', activity.description.lower()).group(1)
+
+ a = {
+ 'instance_id': instance_id,
+ 'start_time': activity.start_time + datetime.timedelta(hours=1),
+ 'end_time': activity.end_time + datetime.timedelta(hours=1),
+ 'action': get_action(activity)
+ }
+ filtered_activites.append(a)
+ return filtered_activites
+
+ def closest_activity(closest_activity, activites):
+ for i in xrange(1, len(activites)):
+ activity = activites[i]
+ if activity['end_time'] > closest_activity['end_time'] and activity['end_time'] < start_time and \
+ activity['action'] == 'launch' and activity['instance_id'] not in terminating_ids:
+ closest_activity = activity
+ return closest_activity
+
+ autoscale = boto.ec2.autoscale.connect_to_region(
+ self.region,
+ aws_access_key_id=self.access_key,
+ aws_secret_access_key=self.secret_key
+ )
+ activites = autoscale.get_all_activities('distributed_jmeter-as')
+ launching_ids = []
+ terminating_ids = []
+ new_activites = filter_activites(activites)
+ filtered_activites = []
+ for activity in new_activites:
+ if activity['end_time'] > start_time:
+ filtered_activites.append(activity)
+
+ if activity['action'] == 'terminate':
+ terminating_ids.append(activity['instance_id'])
+
+ # closest_activity_id = set(launching_ids) - set(terminating_ids) # get activites that were not terminated
+
+ f_a = []
+ for a in filtered_activites:
+ for a2 in filtered_activites:
+ if (a['action'] == 'terminate' and a2['instance_id'] == a['instance_id'] and a2[
+ 'action'] == 'launch') or (a['action'] == 'launch' and a2['instance_id'] == a['instance_id']):
+ f_a.append(a)
+
+ return f_a
+ # return [closest_activity] + f_a
+
+ def terminate_instances(self, ips):
+ reservations = self.conn.get_all_instances()
+ for res in reservations:
+ for instance in res.instances:
+ if instance.ip_address in ips:
+ self.conn.terminate_instances(instance_ids=[instance.id])
+
+ def check_instance(self, i, tmp_userpath, resultspath, ip):
+ cmd = "cat finish"
+
+ ssh = self.ssh_to_instance(ip)
+ _, _, stderr = ssh.exec_command(cmd)
+
+ while len(stderr.readlines()) > 0:
+ time.sleep(10)
+ ssh.close()
+ ssh = self.ssh_to_instance(ip)
+ _, _, stderr = ssh.exec_command(cmd)
+
+ self.logger.log("finishing thread " + str(i))
+ ssh.close()
+ ssh = self.ssh_to_instance(ip)
+ scp = paramiko.SFTPClient.from_transport(ssh.get_transport())
+ self.logger.log("jmeter scenario finished. collecting results")
+ scp.get("/home/{0}/scenario.log".format(self.user),
+ "{0}/{1}".format(tmp_userpath, "scenario" + str(i) + ".log"))
+ # scp.get("/home/{0}/scenario.jtl".format(self.user),
+ # "{0}/{1}".format(tmp_userpath, "scenario" + str(i) + ".jtl"))
+ scp.get("/home/{0}/response-times-over-time.csv".format(self.user),
+ "{0}/{1}".format(tmp_userpath, "response-times-over-time" + str(i) + ".csv", self.user))
+ # scp.get("/home/{0}/results-tree.xml".format(self.user), "{0}/{1}".format(tmp_userpath, "results-tree" + str(i) + ".xml", self.user))
+ scp.close()
+ ssh.close()
+
+ def create_security_groups(self):
+ self.logger.log("creating security groups ...")
+ self.create_security_group('cs-jmeter', 'security group for jmeter', '8557', '0.0.0.0/0')
+ self.add_security_group_rule('cs-jmeter', 'tcp', '1099', '0.0.0.0/0')
+ # self.create_security_group('http', 'security group for http protocol', '80', '0.0.0.0/0')
+ self.create_security_group('ssh', 'security group for http protocol', '22', '0.0.0.0/0')
+
+ def create_security_group(self, name, description, port, cidr):
+ try:
+ self.conn.create_security_group(name, description)
+ self.conn.authorize_security_group(group_name=name, ip_protocol='tcp', from_port=port, to_port=port,
+ cidr_ip=cidr)
+ except EC2ResponseError as e:
+ if str(e.error_code) != 'InvalidGroup.Duplicate':
+ raise
+
+ def add_security_group_rule(self, group_name, protocol, port, cidr):
+ try:
+ group = self.conn.get_all_security_groups(groupnames=[group_name])[0]
+ group.authorize(protocol, port, port, cidr)
+ except EC2ResponseError as e:
+ if str(e.error_code) != 'InvalidPermission.Duplicate':
+ raise
+
+
+ def create_instance(self, msg="creating ec2 instance"):
+ self.logger.log(msg)
+ res = self.conn.run_instances(self.ami_id, key_name=self.key_name,
+ instance_type=self.instance_type,
+ security_groups=['cs-jmeter', 'ssh', 'flask'])
+ time.sleep(30)
+ self.wait_available(res.instances[0])
+ instance = self.conn.get_all_instances([res.instances[0].id])[0].instances[0]
+ return instance
+
+ def wait_available(self, instance):
+ self.logger.log("waiting for instance to become available")
+ self.logger.log("please wait ...")
+ status = self.conn.get_all_instances(instance_ids=[instance.id])[0].instances[0].state
+ i = 1
+ while status != 'running':
+ if i % 10 == 0:
+ self.logger.log("please wait ...")
+ status = self.conn.get_all_instances(instance_ids=[instance.id])[0].instances[0].state
+ time.sleep(10)
+ i = i + 1
+ self.logger.log("instance is up and running")
+
+
+ def write_config(self, config_path, instance):
+ self.cfg.save_option(config_path, 'infrastructure', 'remote_user', 'ubuntu')
+ self.cfg.save_option(config_path, 'infrastructure', 'ip_address', instance.ip_address)
+
+ def write_autoscalability_data(self, resultspath, activites):
+ with open("{0}/autoscalability.log".format(resultspath), "w") as fp:
+ fp.write('"instance_id","start_time","end_time","action"\n')
+ for activity in activites:
+ fp.write('%s,%s,%s,%s\n' % (
+ activity['instance_id'], str(activity['start_time']).split(".")[0], activity['end_time'],
+ activity['action']))
+
+ def create_keypair(self):
+ try:
+ keypair = self.conn.create_key_pair(self.key_name)
+ except EC2ResponseError as e:
+ if e.error_code == 'InvalidKeyPair.Duplicate':
+ self.conn.delete_key_pair(key_name=self.key_name)
+ keypair = self.conn.create_key_pair(self.key_name)
+ else:
+ raise e
+
+ keypair.save(self.output_directory)
+ return "%s/%s.pem" % (self.output_directory, self.key_name)
+
+ def get_cloudwatch_ec2_data(self, start_time, end_time, instance_ids):
+ conn = boto.ec2.cloudwatch.connect_to_region(
+ self.region,
+ aws_access_key_id=self.access_key,
+ aws_secret_access_key=self.secret_key
+ )
+ data = []
+ for instance_id in instance_ids:
+ data.append({
+ 'instance_id': instance_id,
+ 'data': conn.get_metric_statistics(
+ 60,
+ start_time,
+ end_time,
+ 'CPUUtilization',
+ 'AWS/EC2',
+ 'Average',
+ dimensions={'InstanceId': [instance_id]}
+ )
+ })
+
+ return data
+
+ def get_cloudwatch_rds_data(self, start_time, end_time, instance_ids):
+ conn = boto.ec2.cloudwatch.connect_to_region(
+ self.region,
+ aws_access_key_id=self.access_key,
+ aws_secret_access_key=self.secret_key
+ )
+ data = []
+ for instance_id in instance_ids:
+ data.append({
+ 'instance_id': instance_id,
+ 'data': conn.get_metric_statistics(
+ 60,
+ start_time,
+ end_time,
+ 'CPUUtilization',
+ 'AWS/RDS',
+ 'Average',
+ dimensions={'DBInstanceIdentifier': [instance_id]}
+ )
+ })
+
+ return data
+
+ def get_instance_ids_from_ip(self, ips):
+ instance_ids = []
+ for ip in ips:
+ instances = self.conn.get_only_instances()
+ for instance in instances:
+ if instance.ip_address == ip and instance:
+ instance_ids.append(instance.id)
+ break
+ return instance_ids
+
+
+ def get_instances_by_tag(self, tag, value):
+ reservations = self.conn.get_all_instances()
+ my_instances = []
+ for res in reservations:
+ for instance in res.instances:
+ if tag in instance.tags and instance.tags[tag] == value:
+ my_instances.append(instance)
+ return my_instances
\ No newline at end of file
diff --git a/cloudscale/distributed_jmeter/helpers.py b/cloudscale/distributed_jmeter/helpers.py
new file mode 100644
index 0000000..4ed188f
--- /dev/null
+++ b/cloudscale/distributed_jmeter/helpers.py
@@ -0,0 +1,8 @@
+import os
+import boto
+
+def read_config(config_file):
+ cfg = boto.Config()
+ cfg.load_from_path(os.path.abspath(config_file))
+
+ return cfg
\ No newline at end of file
diff --git a/cloudscale/distributed_jmeter/logger.py b/cloudscale/distributed_jmeter/logger.py
new file mode 100644
index 0000000..acad9ae
--- /dev/null
+++ b/cloudscale/distributed_jmeter/logger.py
@@ -0,0 +1,9 @@
+import logging
+
+class Logger:
+
+ def __init__(self, filename):
+ logging.basicConfig(filename=filename,level=logging.DEBUG)
+
+ def log(self, msg, level=logging.DEBUG, append_to_last=False, fin=False):
+ logging.log(level, msg)
\ No newline at end of file
cloudscale/distributed_jmeter/openstack.py 119(+119 -0)
diff --git a/cloudscale/distributed_jmeter/openstack.py b/cloudscale/distributed_jmeter/openstack.py
new file mode 100644
index 0000000..805312b
--- /dev/null
+++ b/cloudscale/distributed_jmeter/openstack.py
@@ -0,0 +1,119 @@
+import time
+
+import novaclient.v2 as novaclient
+
+from cloudscale.distributed_jmeter.aws import AWS
+
+
+class OpenStack(AWS):
+
+ def __init__(self, r_path, scenario_path, output_directory, cfg, logger):
+ super(OpenStack, self).__init__(cfg, scenario_path, r_path, output_directory, logger)
+
+ def init(self):
+ self.host = self.cfg.get('OPENSTACK', 'host')
+ self.startup_threads = self.cfg.get('TEST', 'startup_threads')
+ self.rest_threads = self.cfg.get('TEST', 'rest_threads')
+ self.host = self.cfg.get('SHOWCASE', 'host')
+ self.num_jmeter_slaves = int(self.cfg.get('TEST', 'num_jmeter_slaves'))
+ self.key_pair = self.cfg.get('OPENSTACK', 'key_pair_path')
+ self.key_name = self.cfg.get('OPENSTACK', 'key_name')
+ self.jmeter_url = self.cfg.get('SCENARIO', 'jmeter_url')
+ self.user = self.cfg.get('OPENSTACK', 'remote_user')
+ self.ips = self.cfg.get('SCENARIO', 'instance_names')
+ self.image = self.cfg.get('OPENSTACK', 'image')
+ self.flavor = self.cfg.get('OPENSTACK', 'instance_type')
+ self.nc = novaclient.Client(
+ self.cfg.get('OPENSTACK', 'user'),
+ self.cfg.get('OPENSTACK', 'pwd'),
+ self.cfg.get('OPENSTACK', 'tenant'),
+ auth_url=self.cfg.get('OPENSTACK', 'url')
+ )
+
+ def start(self):
+ if self.ips != "":
+ self.server_ids = [self.nc.servers.find(name=x).id for x in self.ips.split(",")]
+ else:
+ self.server_ids = [self.create_instance('jmeter-%s' % i) for i in range(self.num_jmeter_slaves) ]
+
+ ips = []
+ for server_id in self.server_ids:
+ time.sleep(30)
+ ip = self.add_floating_ip(server_id)
+ ips.append(ip)
+
+ time.sleep(60)
+ for ip in ips:
+ super(OpenStack, self).setup_master(ip)
+
+ super(OpenStack, self).run_masters(ips)
+
+ def create_instance(self, name):
+ self.logger.log("Creating JMeter instance %s" % name)
+
+ image = self.get_image(self.image)
+ flavor = self.get_flavor(self.flavor)
+
+ try:
+ server = self.nc.servers.create(name, image, flavor, key_name=self.key_name)
+ time.sleep(10)
+ self.wait_active(server.id)
+ except Exception as e:
+ raise e
+
+
+ for server in self.nc.servers.list():
+ if server._info['name'] == name:
+ return server.id
+
+ def wait_active(self, server_id):
+ self.logger.log("Waiting for instance to be built . . .")
+ status = self.wait_for_instance_status(server_id, u'BUILD', u'ACTIVE')
+ if not status:
+ self.logger.log("Can not start instance %s!" % self.instance_name)
+ return False
+ return True
+
+ def wait_for_instance_status(self, server_id, current_status, wait_for_status):
+ while True:
+ server = self.nc.servers.get(server_id)
+ if server.status != current_status:
+ if server.status == wait_for_status:
+ return True
+ return False
+ time.sleep(10)
+
+ def get_image(self, name):
+ for image in self.nc.images.list():
+ if image.name == name:
+ return image
+
+ def get_flavor(self, name):
+ for flavor in self.nc.flavors.list():
+ if flavor.name == name:
+ return flavor
+
+ def add_floating_ip(self, server_id):
+
+ server = self.nc.servers.get(server_id)
+ if len(server._info['addresses']['distributed_jmeter']) > 1:
+ return server._info['addresses']['distributed_jmeter'][1]['addr']
+
+ unallocated_floating_ips = self.nc.floating_ips.findall(fixed_ip=None)
+ if len(unallocated_floating_ips) < 1:
+ unallocated_floating_ips.append(self.nc.floating_ips.create())
+
+ i=0
+ floating_ip = unallocated_floating_ips[i]
+ i+=1
+ while floating_ip.ip == '10.10.43.74' and i < len(unallocated_floating_ips):
+ floating_ip = unallocated_floating_ips[i]
+ server.add_floating_ip(floating_ip)
+ return floating_ip.ip
+
+ def terminate_instances(self, ips):
+ #for server_id in self.server_ids:
+ # for server in self.nc.servers.list():
+ # if server.id == server_id:
+ # server.delete()
+ return
cloudscale/distributed_jmeter/run_test.py 77(+77 -0)
diff --git a/cloudscale/distributed_jmeter/run_test.py b/cloudscale/distributed_jmeter/run_test.py
new file mode 100644
index 0000000..45d5ead
--- /dev/null
+++ b/cloudscale/distributed_jmeter/run_test.py
@@ -0,0 +1,77 @@
+import os
+import uuid
+import shutil
+from math import ceil
+from cloudscale.distributed_jmeter.aws import AWS
+
+from cloudscale.distributed_jmeter.helpers import read_config
+from cloudscale.distributed_jmeter.openstack import OpenStack
+
+def run_test(infrastructure, config_path, scenario_path, results_directory, logger):
+ if infrastructure != 'aws':
+ raise Exception("Not supported!")
+
+
+ new_scenario_name = uuid.uuid4()
+ userpath = "{0}/{1}".format(results_directory, new_scenario_name)
+
+ try:
+ os.makedirs(userpath)
+ except OSError as e:
+ if e.errno != 17:
+ raise
+ pass
+
+ shutil.copy2('%s/scripts/visualization/r_visualization.R' % os.path.abspath(os.path.dirname(__file__)), userpath)
+
+ r_path = "%s/r_visualization.R" % userpath
+ run_tests(config_path, userpath, r_path, scenario_path, infrastructure, logger)
+ return userpath
+
+def run_tests(config_path, user_path, r_path, scenario_path, infrastructure, logger):
+
+ cfg = read_config(config_path)
+ num_threads = int(cfg.get('SCENARIO', 'num_threads'))
+ num_jmeter_slaves, startup_threads, rest_threads = calculate(num_threads)
+
+ try:
+ if infrastructure == 'aws':
+ cfg = write_config(config_path, user_path, 'TEST', num_threads, num_jmeter_slaves, startup_threads, rest_threads)
+ run_aws_test(r_path, user_path, scenario_path, cfg, logger)
+ elif infrastructure == 'openstack':
+ cfg = write_config(config_path, user_path, 'TEST', num_threads, num_jmeter_slaves, startup_threads, rest_threads)
+ run_openstack_test(r_path, user_path, scenario_path, cfg, logger)
+ except Exception as e:
+ import traceback
+ logger.log(traceback.format_exc())
+ raise Exception(e)
+
+def calculate(num_threads):
+ num_users_per_jmeter_instance = 2000
+ num_threads = int(num_threads)
+ num_jmeter_slaves = int(ceil(num_threads/(num_users_per_jmeter_instance*1.0)))
+
+ startup_threads = int((num_threads/10)/num_jmeter_slaves)
+ threads_per_slave = int(num_threads/num_jmeter_slaves)
+
+ if (num_jmeter_slaves*num_users_per_jmeter_instance) - num_threads > 0:
+ rest_threads = int(threads_per_slave - startup_threads)
+ else:
+ rest_threads = int(num_users_per_jmeter_instance-startup_threads)
+ return num_jmeter_slaves, startup_threads, rest_threads
+
+def write_config(config_path, user_path, section, num_threads, num_jmeter_slaves, startup_threads, rest_threads):
+ cfg = read_config(config_path)
+
+ cfg.save_option(config_path, section, 'num_threads', str(num_threads))
+ cfg.save_option(config_path, section, 'num_jmeter_slaves', str(num_jmeter_slaves))
+ cfg.save_option(config_path, section, 'startup_threads', str(startup_threads))
+ cfg.save_option(config_path, section, 'rest_threads', str(rest_threads))
+
+ return cfg
+
+def run_openstack_test(r_path, user_path, scenario_path, cfg, logger):
+ OpenStack(r_path, scenario_path, user_path, cfg, logger)
+
+def run_aws_test(r_path, output_path, scenario_path, cfg, logger):
+ AWS(cfg, scenario_path, r_path, output_path, logger)
\ No newline at end of file
diff --git a/cloudscale/distributed_jmeter/scripts/__init__.py b/cloudscale/distributed_jmeter/scripts/__init__.py
new file mode 100644
index 0000000..8f76346
--- /dev/null
+++ b/cloudscale/distributed_jmeter/scripts/__init__.py
@@ -0,0 +1 @@
+__author__ = 'ivansek'
diff --git a/cloudscale/distributed_jmeter/scripts/meet_sla_req.py b/cloudscale/distributed_jmeter/scripts/meet_sla_req.py
new file mode 100644
index 0000000..7bade44
--- /dev/null
+++ b/cloudscale/distributed_jmeter/scripts/meet_sla_req.py
@@ -0,0 +1,59 @@
+import sys
+from cloudscale.distributed_jmeter.scripts.visualization.SLO import SLO
+from cloudscale.distributed_jmeter.scripts.visualization.converters import Converters
+
+max_time = SLO
+
+def check(file_path):
+ output = ""
+ urls = {}
+ unsuccessfull = 0
+ all_requests = 0
+ fp = open(file_path)
+ for line in fp:
+ converters = Converters()
+ try:
+
+ timestamp, estimated_time, url, response_code, _, _, _ = line.split(",")
+ url = converters.url_converter(url)
+
+ if max_time.has_key(url):
+ all_requests+=1
+ if not urls.has_key(url):
+ urls[url] = {}
+ urls[url]['times'] = []
+
+ urls[url]['times'].append([estimated_time, response_code])
+
+ if response_code != "200":
+ unsuccessfull += 1
+
+ except Exception as e:
+ output += "Exception occured\n"
+ output += e.message + "\n"
+ pass
+
+ for k in urls:
+ count_succ = 0
+ all = len(urls[k]['times'])
+
+ for time, response_code in urls[k]['times']:
+ if int(time) <= max_time[k] and response_code == "200":
+ count_succ += 1
+
+ dist = (all*100.0)/all_requests
+ if count_succ >= (all * 90) / 100:
+ output += "%-50s %-50s prob = %.2f%%\n" % (k, "OK [no. requests = %s]" % all, dist)
+ else:
+ p = (count_succ*100)/all
+ output += "%-50s %-50s prob = %.2f%%\n" % (k, "NOT OK [all = %s, succ = %s (%s%%) ]" % (all, count_succ, p), dist)
+
+ fp.close()
+ output += "--------------------------------------------------\n"
+ output += "ALL = %s, UNSUCCESSFULL = %s\n" % (all_requests, unsuccessfull)
+
+ return output
+
+if __name__ == "__main__":
+ print check(sys.argv[1])
+
diff --git a/cloudscale/distributed_jmeter/scripts/visualization/__init__.py b/cloudscale/distributed_jmeter/scripts/visualization/__init__.py
new file mode 100644
index 0000000..8f76346
--- /dev/null
+++ b/cloudscale/distributed_jmeter/scripts/visualization/__init__.py
@@ -0,0 +1 @@
+__author__ = 'ivansek'
diff --git a/cloudscale/distributed_jmeter/scripts/visualization/converters.py b/cloudscale/distributed_jmeter/scripts/visualization/converters.py
new file mode 100644
index 0000000..939b591
--- /dev/null
+++ b/cloudscale/distributed_jmeter/scripts/visualization/converters.py
@@ -0,0 +1,56 @@
+import datetime
+
+
+class Converters:
+
+ def __init__(self):
+ self.launch_count = 1
+
+ def response_code_converter(self, c):
+ try:
+ return int(c)
+ except Exception as e:
+ return 500
+
+ def timestamp_converter(self, t):
+ try:
+ return datetime.datetime.fromtimestamp(float(t)/1000.0)
+ except Exception as e:
+ raise e
+
+ def datetime_to_timestamp(self, t):
+ d = datetime.datetime.strptime(t, "%Y-%m-%d %H:%M:%S")
+ return d
+
+ def _totimestamp(self, dt, epoch=datetime.datetime(1970,1,1)):
+ td = dt - epoch
+ return ((td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 1e6) * 1000
+
+ def action_to_number(self, a):
+ if a == 'launch':
+ self.launch_count += 1
+ return self.launch_count-1
+ elif a == 'terminate':
+ self.launch_count -= 1
+ return self.launch_count+1
+ else:
+ return -10
+
+ def url_converter(self, url):
+ if url == '/search?C_ID':
+ return '/search'
+ if url == '/?SHOPPING_ID':
+ return '/'
+ if url == '/shopping-cart?ADD_FLAG=N':
+ return '/shopping-cart'
+ if url == '/shopping-cart?I_ID=&QTY=1&ADD_FLAG=Y':
+ return '/shopping-cart'
+ if url == '/customer-registration?SHOPPING_ID=':
+ return '/customer-registration'
+ if url == '/buy?RETURNING_FLAG=Y':
+ return '/buy'
+ if url == '/buy?RETURNING_FLAG=N':
+ return '/buy'
+ if url == '[BeanShell] probability':
+ return None
+ return url
\ No newline at end of file
diff --git a/cloudscale/distributed_jmeter/scripts/visualization/parser.py b/cloudscale/distributed_jmeter/scripts/visualization/parser.py
new file mode 100644
index 0000000..5cab671
--- /dev/null
+++ b/cloudscale/distributed_jmeter/scripts/visualization/parser.py
@@ -0,0 +1,256 @@
+import datetime
+import os
+import pandas as pd
+import csv
+import sys
+from cloudscale.distributed_jmeter.scripts.visualization.SLO import SLO
+from cloudscale.distributed_jmeter.scripts.visualization.converters import Converters
+
+
+class Parse:
+
+ def __init__(self):
+ self.max_time = SLO
+
+ def does_violate_slo(self, url, estimated_time, response_code):
+ try:
+ return not (int(estimated_time) < self.max_time[url] and response_code == 200)
+ except KeyError as e:
+ print "There's no SLO for %s" % url
+
+
+ def get_instances_lifetime(self, as_file):
+ instance_ids = []
+ instances = []
+
+ with open(as_file) as fp:
+ next(fp) # skip the header
+ for line in fp:
+ line = line[:-1]
+ instance_id, start_time, end_time, action = line.split(',')
+
+ if action == 'launch' and instance_id not in instance_ids:
+ instance = {}
+ instance['id'] = instance_id
+ instance['start_time'] = datetime.datetime.strptime(end_time, "%Y-%m-%d %H:%M:%S")
+ instances.append(instance)
+ instance_ids.append(instance_id)
+
+ if action == 'terminate' and instance_id in instance_ids:
+ i=0
+ for instance in instances:
+ if instance['id'] == instance_id and not instance.has_key('end_time'):
+ instances[i]['end_time'] = datetime.datetime.strptime(start_time, "%Y-%m-%d %H:%M:%S")
+ break
+ i+=1
+ for instance in instances:
+ min_date = self.data['date'].min().to_datetime()
+ if instance['start_time'] < min_date:
+ instance['start_time'] = min_date
+
+ if not instance.has_key('end_time'):
+ instance['end_time'] = self.data['date'].max().to_datetime()
+ return instances
+
+ def delete_records_that_violates_slo(self, output_file, input_file):
+ converters = Converters()
+ with open(output_file, 'w') as slo_fp:
+ slo_fp.write('"date","response_time","url","response_code","status","autoscalable","instance_id"\n')
+ with open(input_file) as fp:
+ next(fp) # skip header
+ for line in fp:
+ timestamp, estimated_time, url, response_code, status, attr1, attr2 = line.split(",")
+ response_code = converters.response_code_converter(response_code)
+ if self.does_violate_slo(url, estimated_time, response_code):
+ slo_fp.write('%s,%s,%s,%s,%s,%s,%s' % (timestamp, estimated_time, url, response_code, status, attr1, attr2))
+
+
+ def slo_agg_1minute(self, output_file, file):
+ converters = Converters()
+ with open(output_file, 'w') as slo_fp:
+ with open(file) as fp:
+ next(fp) # skip header
+ timestamps = {}
+ for line in fp:
+ timestamp, estimated_time, url, response_code, status, attr1, attr2 = line.split(",")
+ #df = datetime.datetime.strptime(datetime.datetime.fromtimestamp(float(timestamp)/1000).strftime("%Y-%m-%d %H:%M"), "%Y-%m-%d %H:%M") # remove microseconds from time
+ response_code = converters.response_code_converter(response_code)
+ new_timestamp = int(float(timestamp)/60000)*60000
+ if not timestamps.has_key(new_timestamp):
+ timestamps[new_timestamp] = {'num' : 0, 'num_all_requests': 0}
+
+ timestamps[new_timestamp]['num_all_requests'] += 1
+
+ i=0
+ if self.does_violate_slo(url, estimated_time, response_code):
+ i = 1
+
+ timestamps[new_timestamp]['num'] += i
+
+
+ slo_fp.write('"date","num","num_all_requests"\n')
+ for timestamp in timestamps.keys():
+ slo_fp.write('%s,%s,%s\n' % (timestamp, timestamps[timestamp]['num'],timestamps[timestamp]['num_all_requests']))
+
+ def slo_agg_seconds(self, parsed_file, output_file, seconds):
+ print "Seconds: %s" % seconds
+ converters = Converters()
+ with open(output_file, 'w') as slo_fp:
+ with open(parsed_file) as fp:
+ next(fp) # skip header
+ parsed_file_data = csv.reader(fp)
+ sorted_data = sorted(parsed_file_data, key = lambda row: int(row[0]))
+
+ timestamps = {}
+ ref_timestamp, _, _, _, _, _, _ = sorted_data[0]
+ ref_timestamp = (int(ref_timestamp)/1000)
+ timestamps[ref_timestamp] = {'num' : 0, 'num_all_requests': 0}
+
+ min_date = sys.maxint
+ for line in sorted_data:
+ timestamp, estimated_time, url, response_code, status, attr1, attr2 = line
+ timestamp = (int(timestamp)/1000)
+ if timestamp < min_date:
+ min_date = timestamp
+ response_code = converters.response_code_converter(response_code)
+
+ time_delta = datetime.datetime.fromtimestamp(timestamp) - datetime.datetime.fromtimestamp(ref_timestamp)
+ # print "time_delta: %s" % time_delta.seconds
+ # print "time_delta: %s" % datetime.datetime.fromtimestamp(timestamp)
+ if time_delta.seconds >= seconds:
+ # print "new ref timestamp: %s" % datetime.datetime.fromtimestamp(timestamp)
+ ref_timestamp = timestamp
+ if not timestamps.has_key(ref_timestamp):
+ timestamps[ref_timestamp] = {'num' : 0, 'num_all_requests': 0}
+
+ timestamps[ref_timestamp]['num_all_requests'] += 1
+
+ i=0
+ if self.does_violate_slo(url, estimated_time, response_code):
+ i = 1
+
+ timestamps[ref_timestamp]['num'] += i
+
+ print min_date
+ slo_fp.write('"date","num","num_all_requests"\n')
+ slo_fp.write('%s,%s,%s\n' % (0, 0, 0))
+ for timestamp in timestamps.keys():
+ timestamp_subtract = (timestamp - min_date)+seconds
+ slo_fp.write('%s,%s,%s\n' % (timestamp_subtract*1000, timestamps[timestamp]['num'],timestamps[timestamp]['num_all_requests']))
+
+ def _find_min_date(self, data):
+ min_date = sys.maxint
+ for row in data:
+ if int(row[0]) < min_date:
+ min_date = min_date
+
+ return min_date
+
+ def merge(self, output_file, as_file, file):
+
+ response_time_stack = []
+ epoch = datetime.datetime(1970,1,1)
+ with open(output_file, 'w') as m_fp:
+ m_fp.write('"date","response_time","url","response_code","status","attr1","attr2","instance_id","y"\n')
+ if os.path.exists(as_file):
+ instances = self.get_instances_lifetime(as_file)
+ i = -5
+ for instance in instances:
+ instance_id = instance['id']
+ with open(file) as fp:
+ next(fp) # skip the header
+ already_got_max_response_time = False
+ for line in fp:
+ # if already_got_max_response_time is False: # do this only the first time in loop
+ # start_time = instance['start_time']
+ # end_time = instance['end_time'] if instance.has_key('end_time') and instance['end_time'] < self.data['date'].max().to_datetime() else self.get_end_time(instance_id, instances)
+ # max_response_time_to_as_dt = self.data_indexed.between_time(start_time, end_time).max()['response_time']
+ # already_got_max_response_time = True
+
+ timestamp, estimated_time, url, response_code, status, attr1, attr2 = line.split(",")
+ dt = datetime.datetime.fromtimestamp(int(timestamp)/1000.0)
+ if dt >= instance['start_time'] and dt <= instance['end_time']:
+ m_fp.write('%s,%s,%s,%s,%s,%s,%s,%s,%s\n' % (timestamp, estimated_time, url, response_code, status, attr1, attr2[:-1], instance_id, i))
+ i-=5
+
+ def timestamp_to_datetime_file(self):
+ with open('files/response-times-over-time.trans.tab', 'w') as fp_out:
+ fp_out.write('timestamp\tdate\tresponse_time\turl\tresponse_code\tstatus\n')
+ fp_out.write('c\td\tc\ts\td\td\n')
+ with open(self.file) as fp:
+ next(fp) # skip the header
+ for line in fp:
+ ts, curr_dt, response_time, url, response_code, status = self.parse_line(line)
+ fp_out.write('%s\t%s\t%s\t%s\t%s\t%s\n' % (ts, curr_dt.strftime('%H:%M'), response_time, url, response_code, status))
+
+ def get_end_time(self, instance_id, instances):
+ i = 0
+ for instance in instances:
+ if instance['id'] == instance_id:
+ break
+ i+=1
+ try:
+ end_time = instances[i+1]['start_time']
+ return end_time
+ except IndexError as e:
+ return self.data['date'].max().to_datetime()
+
+ def parse_line(self, line):
+ ts, response_time, url, response_code, status, _, _ = line.split(",")
+ dt = datetime.datetime.fromtimestamp(int(ts)/1000.0)
+ rc = self.parse_response_code(response_code)
+
+ return ts, dt, int(response_time), str(url), rc, str(status)
+
+ def parse_response_code(self, rc):
+ try:
+ return int(rc)
+ except:
+ return 500
+
+ def parse(self, output_file, file):
+ with open(file) as fp:
+ next(fp) # skip the header
+ with open(output_file, "w") as parsed_fp:
+ parsed_fp.write("date,response_time,url,response_code,status,attr1,attr2\n")
+ converters = Converters()
+ for line in fp:
+ timestamp, estimated_time, url, response_code, status, attr1, attr2 = line.split(",")
+ response_code = converters.response_code_converter(response_code)
+ url = converters.url_converter(url)
+ if url is not None:
+ parsed_fp.write('%s,%s,%s,%s,%s,%s,%s' % (timestamp, estimated_time, url, response_code, status, attr1, attr2))
+
+ def to_dataframe(self, file, as_file):
+
+
+ print "Parsing " + file
+ converters = Converters()
+ self.data_indexed = pd.read_csv(file, index_col='date', converters={
+ 'date' : converters.timestamp_converter,
+ 'response_code' : converters.response_code_converter,
+ 'url' : converters.url_converter
+ })
+
+ self.data = pd.read_csv(file, converters={
+ 'date' : converters.timestamp_converter,
+ 'response_code' : converters.response_code_converter,
+ 'url' : converters.url_converter
+ })
+
+
+ # print "Parsing " + as_file
+ # self.autoscalability_data = pd.read_csv(as_file, converters={
+ # 'start_time' : converters.datetime_to_timestamp,
+ # 'end_time' : converters.datetime_to_timestamp,
+ # 'action' : converters.action_to_number
+ # })
+ return
+
+ def merge_autoscaling(self, file1, file2):
+ pass
+
+
+
+
+
diff --git a/cloudscale/distributed_jmeter/scripts/visualization/plotter.py b/cloudscale/distributed_jmeter/scripts/visualization/plotter.py
new file mode 100644
index 0000000..1ddead0
--- /dev/null
+++ b/cloudscale/distributed_jmeter/scripts/visualization/plotter.py
@@ -0,0 +1,31 @@
+import datetime
+import os
+import subprocess
+
+
+class Plot:
+
+ def __init__(self, num_threads, duration, r_file, main_file, parsed_file, merged_file, slo_violations_file, slo_violations_file_non_agg, autoscaling_file, slo_agg_1second, slo_agg_5seconds, slo_agg_10seconds, ec2_file, rds_cpu_file):
+ r_file_new = "%s/%s_new.R" % (os.path.dirname(main_file), os.path.basename(r_file)[:-2])
+ with open(r_file) as fp :
+ with open(r_file_new, "w") as fp_new:
+ fp_new.write('num_threads <-%s\n' % num_threads)
+ fp_new.write('scenario_duration_in_min <- %s\n' % duration)
+ fp_new.write('slo_f <-"%s"\n' % slo_violations_file)
+ fp_new.write('slo_f_non_aggregated <- "%s"\n' % slo_violations_file_non_agg)
+ fp_new.write('as_f<-"%s"\n' % autoscaling_file)
+ fp_new.write('m_f<-"%s"\n' % merged_file)
+ fp_new.write('f <- "%s"\n' % parsed_file)
+ fp_new.write('output_file <- "%s/graphs.png"\n' % os.path.dirname(main_file))
+ fp_new.write('slo_agg_1second <- "%s"\n' % slo_agg_1second)
+ fp_new.write('slo_agg_5seconds <- "%s"\n' % slo_agg_5seconds)
+ fp_new.write('slo_agg_10seconds <- "%s"\n' % slo_agg_10seconds)
+ fp_new.write('ec2_file <- "%s"\n' % ec2_file)
+ fp_new.write('rds_cpu_file <- "%s"\n' % rds_cpu_file)
+
+ for line in fp:
+ fp_new.write(line)
+
+ escaped_r_filepath = r_file_new.replace(" ", "\\ ")
+ print escaped_r_filepath
+ subprocess.call(['Rscript', r_file_new])
diff --git a/cloudscale/distributed_jmeter/scripts/visualization/r_visualization.R b/cloudscale/distributed_jmeter/scripts/visualization/r_visualization.R
new file mode 100644
index 0000000..f4a3e66
--- /dev/null
+++ b/cloudscale/distributed_jmeter/scripts/visualization/r_visualization.R
@@ -0,0 +1,539 @@
+###########################
+# load required libraries #
+###########################
+library(ggplot2)
+library(scales)
+
+##################
+# SLO violations #
+##################
+SLO <- data.frame()
+SLO[1, '/'] <- 3000
+SLO[1, '/best-sellers'] <- 5000
+SLO[1, '/new-products'] <- 5000
+SLO[1, '/product-detail'] <- 3000
+SLO[1, '/search?searchField=&keyword=&C_ID='] <- 10000
+SLO[1, '/search'] <- 3000
+SLO[1, '/shopping-cart'] <- 3000
+SLO[1, '/customer-registration'] <- 3000
+SLO[1, '/buy-confirm'] <- 5000
+SLO[1, '/buy'] <- 3000
+SLO[1, '/order-inquiry'] <- 3000
+SLO[1, '/admin-confirm'] <- 5000
+SLO[1, '/admin'] <- 3000
+SLO[1, '/payment'] <- 10000
+
+#################################
+# read csv files to data frames #
+#################################
+df<-read.csv(f,header=TRUE)
+slo_df<-read.csv(slo_f, header=TRUE)
+
+if(file.exists(as_f))
+{
+as_df<-read.csv(as_f, header=TRUE)
+}
+
+mdf<-read.csv(m_f, header=TRUE, row.names=NULL)
+slo_df_non_agg <- read.csv(slo_f_non_aggregated, header=TRUE)
+slo_agg_1second_df <- read.csv(slo_agg_1second, header=TRUE)
+slo_agg_5seconds_df <- read.csv(slo_agg_5seconds, header=TRUE)
+slo_agg_10seconds_df <- read.csv(slo_agg_10seconds, header=TRUE)
+ec2_cpu_df <- read.csv(ec2_file, header=TRUE)
+rds_cpu_df <- read.csv(rds_cpu_file, header=TRUE)
+
+####################
+# define functions #
+####################
+get_vline <- function(df)
+{
+ index <- which(is.character(df$violates) & df$violates != "" & !is.na(df$violates))
+ return(as.numeric(df[index+1, 'date']))
+}
+
+normalized_response_time <- function(df, scale=1)
+{
+ if( nrow(df) == 0)
+ {
+ df[1, 'response_time_normalized'] <- 0
+ df <- df[-c(1), ]
+ return(df)
+ }
+ my_df <- df
+ for(i in 1:nrow(df))
+ {
+ normalized_value <- scale/SLO[1, df[i, 'url']]
+ my_df[i, 'response_time_normalized'] <- df[i, 'response_time']*normalized_value
+ }
+
+ return(my_df)
+}
+
+cut_scenario <- function(df, duration)
+{
+ steps <- (scenario_duration_in_min*60)/duration
+ if (nrow(df) > steps+1)
+ {
+
+ c <- seq.int(nrow(df) + (steps - nrow(df)) + 2, nrow(df), 1)
+ return(df[-c,])
+ }
+ return(df)
+}
+
+when_violates <- function(df, start=1)
+{
+ stop <- FALSE
+ for(i in start:nrow(df))
+ {
+ if(df[i, 'num_threads'] > 10 & !stop)
+ {
+ df[i-1, 'violates'] <- sprintf("req. = %s (%s) / VU = (%s)", round(df[i-1, "num_requests_theory"]), df[i-1, 'num_all_requests'], round(as.numeric(df[i-1, 'vus'])))
+ stop <- TRUE
+ }
+ else
+ {
+ df[i, 'violates'] <- ""
+ }
+ }
+
+ return(df)
+}
+
+transform_date <- function(df, field="date")
+{
+ df[,field] <- as.POSIXct(df[,field]/1000, origin='1970-01-01')
+ return(df)
+}
+
+order_by_date <- function(df, field="date"){
+ my_df<-df[order(df[,field]),]
+ return(my_df)
+}
+
+
+create_vus <- function(df)
+{
+ df<-order_by_date(df)
+ threads_per_minute <- num_threads/ (nrow(df)-1)
+ for(i in 1:nrow(df)){
+ df[i, "vus"] <- round((i-1)*threads_per_minute)
+ }
+ return(df)
+}
+
+add_scale_x <- function(gg, df){
+ my_breaks <- seq.int(0, scenario_duration*60, 60)
+ return(gg + scale_x_continuous(breaks=my_breaks, labels=format(as.POSIXct(my_breaks, origin="1970-01-01"), format="%M:%S")))
+}
+
+
+date2scenario_time <- function(df, field="date")
+{
+ min_d <- as.numeric(min(df[,field]))
+ df$scenario_date <- as.POSIXct(as.numeric(df[,field])-min_d, origin="1970-01-01")
+ return(df)
+}
+
+add_requests_per_second <- function(df, duration){
+ my_df <- df
+
+ scenario_duration_in_sec <- scenario_duration*60
+
+ requests_per_second <- (num_threads/7)
+ requests_per_scenario <- requests_per_second * scenario_duration_in_sec
+ requests_per_duration <- requests_per_scenario/(scenario_duration_in_sec/duration)
+ inc <- requests_per_duration/nrow(my_df)
+
+ my_df[1, "drek"] <- 0
+ for(i in 2:nrow(my_df)){
+ my_df[i, "drek"] <- as.numeric((i-1)*inc)
+ }
+ return(my_df)
+}
+
+add_theorethical_requests <- function(df, duration)
+{
+ scenario_duration_in_sec <- scenario_duration*60
+ requests_per_second <- (num_threads/7)
+ num_intervals <- scenario_duration_in_sec/duration
+
+ requests_per_scenario <- requests_per_second * scenario_duration_in_sec
+ requests_per_duration <- requests_per_scenario/num_intervals
+
+ requests_per_interval <- requests_per_duration/num_intervals
+ df[1, "num_requests_theory"] <- 0
+ for(i in 1:(nrow(df)-1))
+ {
+ df[i+1, "num_requests_theory"] <- (((i-1) * requests_per_interval) + (i * requests_per_interval))/2
+ }
+
+ return(df)
+}
+
+insertrow <- function(existingdf, newrow, r)
+{
+ existingdf[seq(r+1,nrow(existingdf)+1),] <- existingdf[seq(r,nrow(existingdf)),]
+ existingdf[r,] <- newrow
+ existingdf
+}
+
+multiplot <- function(..., plotlist=NULL, file, cols=1, layout=NULL) {
+ require(grid)
+
+ # Make a list from the ... arguments and plotlist
+ plots <- c(list(...), plotlist)
+
+ numPlots = length(plots)
+
+ # If layout is NULL, then use 'cols' to determine layout
+ if (is.null(layout)) {
+ # Make the panel
+ # ncol: Number of columns of plots
+ # nrow: Number of rows needed, calculated from # of cols
+ layout <- matrix(seq(1, cols * ceiling(numPlots/cols)),
+ ncol = cols, nrow = ceiling(numPlots/cols))
+ }
+
+ if (numPlots==1) {
+ print(plots[[1]])
+
+ } else {
+ # Set up the page
+ grid.newpage()
+ pushViewport(viewport(layout = grid.layout(nrow(layout), ncol(layout))))
+
+ # Make each plot, in the correct location
+ for (i in 1:numPlots) {
+ # Get the i,j matrix positions of the regions that contain this subplot
+ matchidx <- as.data.frame(which(layout == i, arr.ind = TRUE))
+
+ print(plots[[i]], vp = viewport(layout.pos.row = matchidx$row,
+ layout.pos.col = matchidx$col))
+ }
+ }
+}
+
+########################################
+# transform timestamps to date objects #
+########################################
+slo_df <- transform_date(slo_df)
+mdf <- transform_date(mdf)
+df <- transform_date(df)
+slo_df_non_agg <- transform_date(slo_df_non_agg)
+slo_agg_1second_df <- transform_date(slo_agg_1second_df)
+slo_agg_5seconds_df <- transform_date(slo_agg_5seconds_df)
+slo_agg_10seconds_df <- transform_date(slo_agg_10seconds_df)
+
+############################
+# order data frame by date #
+############################
+slo_df<-order_by_date(slo_df)
+mdf <- order_by_date(mdf)
+df <- order_by_date(df)
+slo_df_non_agg <- order_by_date(slo_df_non_agg)
+slo_agg_1second_df <- order_by_date(slo_agg_1second_df)
+slo_agg_5seconds_df <- order_by_date(slo_agg_5seconds_df)
+slo_agg_10seconds_df <- order_by_date(slo_agg_10seconds_df)
+rds_cpu_df <- order_by_date(rds_cpu_df, "timestamp")
+
+################
+# Cut scenario #
+################
+slo_df <- cut_scenario(slo_df, 60)
+
+slo_agg_1second_df <- cut_scenario(slo_agg_1second_df, 1)
+
+slo_agg_5seconds_df <- cut_scenario(slo_agg_5seconds_df, 5)
+
+slo_agg_10seconds_df <- cut_scenario(slo_agg_10seconds_df, 10)
+
+
+##################
+# transform data #
+##################
+slo_df_non_agg$response_code <- factor(slo_df_non_agg$response_code)
+
+scenario_duration <- c(max(slo_df$date) - min(slo_df$date))
+
+num_ec2_instances <- length(levels(ec2_cpu_df$instance_id))
+
+slo_df <- create_vus(slo_df)
+slo_agg_1second_df <- create_vus(slo_agg_1second_df)
+slo_agg_5seconds_df <- create_vus(slo_agg_5seconds_df)
+slo_agg_10seconds_df <- create_vus(slo_agg_10seconds_df)
+
+specify_decimal <- function(x, k) format(round(x, k), nsmall=k)
+
+# change time to match scenario time
+slo_df <- add_requests_per_second(slo_df, 60)
+slo_agg_1second_df <- add_requests_per_second(slo_agg_1second_df, 1)
+slo_agg_5seconds_df <- add_requests_per_second(slo_agg_5seconds_df, 5)
+slo_agg_10seconds_df <- add_requests_per_second(slo_agg_10seconds_df, 10)
+
+ec2_cpu_avg <- aggregate(average ~ timestamp, ec2_cpu_df, mean)
+ec2_cpu_avg$timestamp <- seq.int(60, nrow(ec2_cpu_avg)*60, 60)
+ec2_cpu_avg <- insertrow(ec2_cpu_avg, c(0,0), 1)
+
+rds_cpu_df <- insertrow(rds_cpu_df, c(as.character(rds_cpu_df[1,"instance_id"]),0,0), 1)
+rds_cpu_df$timestamp <- seq.int(0, (nrow(rds_cpu_df)-1)*60, 60)
+
+#############################################
+# calculate theorethical number of requests #
+#############################################
+
+slo_df <- add_theorethical_requests(slo_df, 60)
+slo_agg_1second_df <- add_theorethical_requests(slo_agg_1second_df, 1)
+slo_agg_5seconds_df <- add_theorethical_requests(slo_agg_5seconds_df, 5)
+slo_agg_10seconds_df <- add_theorethical_requests(slo_agg_10seconds_df, 10)
+
+##########################################
+# calculate percentage of slo violations #
+##########################################
+
+slo_df$num_threads <- ifelse(slo_df$num > 0, specify_decimal((100*slo_df$num)/slo_df$num_all_requests, 2), "")
+slo_agg_5seconds_df$num_threads <- ifelse(slo_agg_5seconds_df$num > 0, specify_decimal((100*slo_agg_5seconds_df$num)/slo_agg_5seconds_df$num_all_requests, 2), "")
+slo_agg_10seconds_df$num_threads <- ifelse(slo_agg_10seconds_df$num > 0, specify_decimal((100*slo_agg_10seconds_df$num)/slo_agg_10seconds_df$num_all_requests, 2), "")
+
+##################################
+# add text when starts violating #
+##################################
+
+slo_df <- when_violates(slo_df)
+slo_agg_5seconds_df <- when_violates(slo_agg_5seconds_df, start=10)
+slo_agg_10seconds_df <- when_violates(slo_agg_10seconds_df, start=5)
+
+####################################
+# transform times to scenario time #
+####################################
+slo_df <- date2scenario_time(slo_df)
+
+slo_agg_1second_df <- date2scenario_time(slo_agg_1second_df)
+
+slo_agg_5seconds_df <- date2scenario_time(slo_agg_5seconds_df)
+
+slo_agg_10seconds_df <-date2scenario_time(slo_agg_10seconds_df)
+
+df <- date2scenario_time(df)
+
+slo_df_non_agg <- date2scenario_time(slo_df_non_agg)
+
+mdf <- date2scenario_time(mdf)
+
+slo_df_non_agg <- normalized_response_time(slo_df_non_agg)
+
+#################
+# define graphs #
+#################
+common_1minute_gg <- ggplot(slo_df, aes(x=as.numeric(scenario_date), y=num_all_requests)) +
+ geom_line() +
+ geom_vline(xintercept=get_vline(slo_df), colour="red") +
+ geom_line(data=slo_df, aes(x=as.numeric(scenario_date), y=drek)) +
+ geom_bar(stat="identity", data=slo_df, aes(x=as.numeric(scenario_date), y=num)) +
+ geom_text(data=slo_df, size=5, vjust=-1.5, aes(label=violates))
+
+common_5seconds_gg <- ggplot(slo_agg_5seconds_df, aes(x=as.numeric(scenario_date), y=num_all_requests)) +
+ geom_line() +
+ geom_vline(xintercept=get_vline(slo_agg_5seconds_df), colour="red") +
+ geom_line(data=slo_agg_5seconds_df, aes(x=as.numeric(scenario_date), y=drek)) +
+ geom_bar(stat="identity", data=slo_agg_5seconds_df, aes(x=as.numeric(scenario_date), y=num)) +
+ geom_text(data=slo_agg_5seconds_df, size=5, vjust=-1.5, aes(label=violates))
+
+common_10seconds_gg <- ggplot(slo_agg_10seconds_df, aes(x=as.numeric(scenario_date), y=num_all_requests)) +
+ geom_line() +
+ geom_vline(xintercept=get_vline(slo_agg_10seconds_df), colour="red") +
+ geom_line(data=slo_agg_10seconds_df, aes(x=as.numeric(scenario_date), y=drek)) +
+ geom_bar(stat="identity", data=slo_agg_10seconds_df, aes(x=as.numeric(scenario_date), y=num)) +
+ geom_text(data=slo_agg_10seconds_df, size=5, vjust=-1.5, aes(label=violates))
+
+scenario_gg <- ggplot(slo_df, aes(x=as.numeric(scenario_date), y=num_all_requests)) +
+ geom_line(stat="identity") +
+ ylab(label="no requests") +
+ xlab(label="time")
+
+slo_gg2 <- ggplot(slo_df, aes(x=as.numeric(scenario_date), y=num)) +
+ geom_bar(stat="identity") +
+ ylab(label="no of slo violations") +
+ xlab(label="time")
+
+slo_non_agg_gg <- ggplot(slo_df_non_agg, aes(x=as.numeric(scenario_date), y=response_time, colour=response_code)) +
+ geom_point() +
+ ylab(label="response time") +
+ xlab(label="time")
+
+slo_non_agg_gg_urls <- ggplot(slo_df_non_agg, aes(x=as.numeric(scenario_date), y=response_time, colour=url)) +
+ geom_point() +
+ ylab(label="response time") +
+ xlab(label="time")
+
+slo_non_agg_gg_urls_normalized <- ggplot(slo_df_non_agg, aes(x=as.numeric(scenario_date), y=response_time_normalized, colour=url)) +
+ geom_point() +
+ ylab(label="response time") +
+ xlab(label="time") +
+ ggtitle("slo violations by url - normalized")
+
+
+slo_non_agg_gg_normalized <- ggplot(slo_df_non_agg, aes(x=as.numeric(scenario_date), y=response_time_normalized, colour=response_code)) +
+ geom_point() +
+ ylab(label="response time") +
+ xlab(label="time") +
+ ggtitle("slo_violations by response code - normalized")
+
+gg <- ggplot(df, aes(x=as.numeric(scenario_date), y=response_time, colour=url)) +
+ geom_point() +
+ xlab(label="time")
+
+gg2 <- ggplot(slo_df, aes(x=vus, y=num_all_requests)) +
+ geom_point(stat="identity") +
+ scale_x_continuous(breaks=seq(0, max(slo_df$vus), num_threads/10))
+ #geom_line(data=slo_df, aes(x=as.numeric(scenario_date), y=drek)) +
+
+slo_agg_1second_gg <- ggplot(slo_agg_1second_df, aes(x=as.numeric(scenario_date), y=num_all_requests)) +
+ geom_line(stat="identity") +
+ ylab(label="no requests") +
+ xlab(label="time")
+
+slo_agg_5seconds_gg <- ggplot(slo_agg_5seconds_df, aes(x=as.numeric(scenario_date), y=num_all_requests)) +
+ geom_line(stat="identity") +
+ ylab(label="no requests") +
+ xlab(label="time")
+
+slo_agg_10seconds_gg <- ggplot(slo_agg_10seconds_df, aes(x=as.numeric(scenario_date), y=num_all_requests)) +
+ geom_line(stat="identity") +
+ ylab(label="no requests") +
+ xlab(label="time")
+
+ec2_cpu_gg <- ggplot(ec2_cpu_avg, aes(x=as.numeric(timestamp), y=average)) +
+ geom_line() +
+ geom_point() +
+ ylab("avg. cpu utilization") +
+ xlab("time") +
+ geom_text(vjust=2, aes(label=round(as.numeric(average),digits=2)))
+
+rds_cpu_gg <- ggplot(rds_cpu_df, aes(x=as.numeric(timestamp), y=as.double(average))) +
+ geom_line() +
+ geom_point() +
+ ylab("avg. cpu utilization") +
+ xlab("time") +
+ geom_text(vjust=2, aes(label=round(as.numeric(average), digits=2)))
+
+
+##################
+# scale x-origin #
+##################
+
+common_1minute_gg <- add_scale_x(common_1minute_gg, slo_df)
+
+common_5seconds_gg <- add_scale_x(common_5seconds_gg, slo_agg_5seconds_df)
+
+common_10seconds_gg <- add_scale_x(common_10seconds_gg, slo_agg_10seconds_df)
+
+scenario_gg <- add_scale_x(scenario_gg, slo_df)
+
+slo_gg2 <- add_scale_x(slo_gg2, slo_df)
+
+slo_non_agg_gg <- add_scale_x(slo_non_agg_gg, slo_df_non_agg)
+
+slo_non_agg_gg_urls <- add_scale_x(slo_non_agg_gg_urls, slo_df_non_agg)
+
+slo_non_agg_gg_urls_normalized <- add_scale_x(slo_non_agg_gg_urls_normalized, slo_df_non_agg)
+
+slo_non_agg_gg_normalized <- add_scale_x(slo_non_agg_gg_normalized, slo_df_non_agg)
+
+gg <- add_scale_x(gg, df)
+
+slo_agg_1second_gg <- add_scale_x(slo_agg_1second_gg, slo_agg_1second_df)
+
+slo_agg_5seconds_gg <- add_scale_x(slo_agg_5seconds_gg,slo_agg_5seconds_df)
+
+slo_agg_10seconds_gg <- add_scale_x(slo_agg_10seconds_gg,slo_agg_10seconds_df)
+
+ec2_cpu_gg <- add_scale_x(ec2_cpu_gg, ec_cpu_avg)
+
+ec2_cpu_gg <- ec2_cpu_gg + scale_y_continuous(breaks=seq.int(0, 100, 10))
+
+rds_cpu_gg <- add_scale_x(rds_cpu_gg, rds_cpu_df)
+
+rds_cpu_gg <- rds_cpu_gg + scale_y_continuous(breaks=seq.int(0, 100, 10))
+
+max_date <- max(slo_df$date)
+
+if(exists("as_df"))
+{
+filtered_as <- as_df[as.numeric(as.POSIXct(as_df$end_time)) < as.numeric(max_date),]
+}
+
+########################
+# add layers to graphs #
+########################
+
+common_1minute_gg <- common_1minute_gg + xlab(label='time') + ylab(label='requests') + ggtitle("slo violations - 1 minute")
+
+common_5seconds_gg <- common_5seconds_gg + xlab(label='time') + ylab(label='requests') + ggtitle("slo violations - 5 second")
+
+common_10seconds_gg <- common_10seconds_gg + xlab(label='time') + ylab(label='requests') + ggtitle("slo violations - 10 seconds")
+
+scenario_gg <- scenario_gg + geom_line(data=mdf, aes(x=date,y=y*1000, colour=instance_id), size=2) + ggtitle("requests aggregated by 1 minute")
+
+slo_agg_1second_gg <- slo_agg_1second_gg + geom_line(data=mdf, aes(x=date,y=y*1000, colour=instance_id), size=2) + ggtitle("requests aggregated by 1 second")
+
+slo_agg_5seconds_gg <- slo_agg_5seconds_gg + geom_line(data=mdf, aes(x=date,y=y*1000, colour=instance_id), size=2) + ggtitle("requests aggregated by 5 seconds")
+
+slo_agg_10seconds_gg <- slo_agg_10seconds_gg + geom_line(data=mdf, aes(x=date,y=y*1000, colour=instance_id), size=2) + ggtitle("requests aggregated by 10 seconds")
+
+my_gg <- slo_agg_10seconds_gg + geom_line(data=slo_df, aes(x=as.numeric(scenario_date), y=vus))
+
+gg2 <- gg2 + xlab(label='virtual users') + ylab(label='requests')
+
+slo_non_agg_gg_urls <- slo_non_agg_gg_urls + xlab(label='time') + ylab(label='response time') + ggtitle("slo violations by url")
+
+ec2_cpu_gg <- ec2_cpu_gg + ggtitle(paste("average cpu utilization of", num_ec2_instances, "instances - by minute", sep=" "))
+
+rds_cpu_gg <- rds_cpu_gg + ggtitle(paste("average cpu utilization of rds - by minute"))
+
+################################
+# add vm provisioning to graph #
+################################
+if(nrow(mdf) > 0)
+{
+slo_gg2 <- slo_gg2 + geom_line(data=mdf, aes(x=date,y=y*10, colour=instance_id), size=2)
+
+slo_non_agg_gg <- slo_non_agg_gg + geom_line(data=mdf, aes(x=date,y=y*1000, colour=instance_id), size=2)
+
+gg <- gg + geom_line(data=mdf, aes(x=date,y=y*1000, colour=instance_id), size=2)
+}
+
+slo_gg2 <- slo_gg2 + ggtitle("slo violations - 1 minute")
+
+slo_non_agg_gg <- slo_non_agg_gg + ggtitle("slo violations by response code")
+
+gg <- gg + xlab(label='time') + ylab(label='response time') + ggtitle("all responses")
+
+min_y <- ifelse(nrow(mdf) > 0, min(mdf$y), 0)
+slo_gg2 <- slo_gg2 +
+ geom_text(data=slo_df, size=3, vjust=-0.5, aes(label=num_threads)) +
+ ylim(min_y * 10, max(slo_df$num) + 50)
+ # xlim(min(df$date), max(df$date))
+
+#######################
+# save graphs to file #
+#######################
+png(output_file, width=2000, height=6000, res=100)
+multiplot(
+ec2_cpu_gg,
+rds_cpu_gg,
+slo_gg2,
+ slo_non_agg_gg,
+ slo_non_agg_gg_urls,
+ slo_non_agg_gg_urls_normalized,
+ slo_non_agg_gg_normalized,
+ gg,
+# scenario_gg,
+# slo_agg_1second_gg,
+# slo_agg_5seconds_gg,
+# slo_agg_10seconds_gg,
+# my_gg,
+ gg2,
+ common_1minute_gg,
+ common_5seconds_gg,
+ common_10seconds_gg)
diff --git a/cloudscale/distributed_jmeter/scripts/visualization/SLO.py b/cloudscale/distributed_jmeter/scripts/visualization/SLO.py
new file mode 100644
index 0000000..96e664a
--- /dev/null
+++ b/cloudscale/distributed_jmeter/scripts/visualization/SLO.py
@@ -0,0 +1,16 @@
+SLO = {}
+SLO['/'] = 3000
+SLO['/best-sellers'] = 5000
+SLO['/new-products'] = 5000
+SLO['/product-detail'] = 3000
+SLO['/search?searchField=&keyword=&C_ID='] = 10000
+SLO['/search'] = 3000
+SLO['/shopping-cart'] = 3000
+SLO['/customer-registration'] = 3000
+SLO['/buy-confirm'] = 5000
+SLO['/buy'] = 3000
+SLO['/order-inquiry'] = 3000
+SLO['/admin-confirm'] = 5000
+SLO['/admin'] = 3000
+SLO['/payment'] = 10000
+
diff --git a/cloudscale/distributed_jmeter/scripts/visualization/visualize.py b/cloudscale/distributed_jmeter/scripts/visualization/visualize.py
new file mode 100644
index 0000000..0692671
--- /dev/null
+++ b/cloudscale/distributed_jmeter/scripts/visualization/visualize.py
@@ -0,0 +1,50 @@
+import os
+import shutil
+
+from plotter import Plot
+from cloudscale.distributed_jmeter.scripts.visualization.parser import Parse
+
+
+class Visualize:
+
+ def __init__(self, num_threads, duration, r_file, main_file, autoscaling_file):
+ base_filename = main_file[:-4]
+ path = os.path.dirname(main_file)
+
+ self.main_file = main_file
+ self.parsed_file = base_filename + ".parsed.csv"
+ self.merged_file = base_filename + ".merged.csv"
+ self.slo_violations_non_agg_file = base_filename + ".slo_non_agg.csv"
+ self.slo_violations_agg = base_filename + ".slo_agg.csv"
+ self.slo_violations_agg_1second = base_filename + ".slo_agg_1second.csv"
+ self.slo_violations_agg_5seconds = base_filename + ".slo_agg_5seconds.csv"
+ self.slo_violations_agg_10seconds = base_filename + ".slo_agg_10seconds.csv"
+ self.ec2_file = path + "/ec2-cpu.csv"
+ self.rds_cpu_file = path + "/rds-cpu.csv"
+
+ data = Parse()
+ data.parse(self.parsed_file, self.main_file)
+ data.to_dataframe(self.parsed_file, autoscaling_file)
+ data.merge(self.merged_file, autoscaling_file, self.parsed_file)
+ data.delete_records_that_violates_slo(self.slo_violations_non_agg_file, self.parsed_file)
+ data.slo_agg_seconds(self.parsed_file, self.slo_violations_agg, 60)
+ data.slo_agg_seconds(self.parsed_file, self.slo_violations_agg_1second, 1)
+ data.slo_agg_seconds(self.parsed_file, self.slo_violations_agg_5seconds, 5)
+ data.slo_agg_seconds(self.parsed_file, self.slo_violations_agg_10seconds, 10)
+
+ plotter = Plot(num_threads, duration, r_file,
+ self.main_file,
+ self.parsed_file,
+ self.merged_file,
+ self.slo_violations_agg,
+ self.slo_violations_non_agg_file,
+ autoscaling_file,
+ self.slo_violations_agg_1second,
+ self.slo_violations_agg_5seconds,
+ self.slo_violations_agg_10seconds,
+ self.ec2_file,
+ self.rds_cpu_file)
+
+ def save(self):
+ return ""
+
diff --git a/cloudscale/distributed_jmeter/test/__init__.py b/cloudscale/distributed_jmeter/test/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cloudscale/distributed_jmeter/test/__init__.py
LICENSE.txt 0(+0 -0)
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/LICENSE.txt
MANIFEST 22(+22 -0)
diff --git a/MANIFEST b/MANIFEST
new file mode 100644
index 0000000..185049d
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,22 @@
+# file GENERATED by distutils, do NOT edit
+CHANGES.txt
+LICENSE.txt
+README.txt
+setup.py
+cloudscale/__init__.py
+cloudscale/distributed_jmeter/__init__.py
+cloudscale/distributed_jmeter/aws.py
+cloudscale/distributed_jmeter/helpers.py
+cloudscale/distributed_jmeter/logger.py
+cloudscale/distributed_jmeter/openstack.py
+cloudscale/distributed_jmeter/run_test.py
+cloudscale/distributed_jmeter/scripts/__init__.py
+cloudscale/distributed_jmeter/scripts/meet_sla_req.py
+cloudscale/distributed_jmeter/scripts/visualization/SLO.py
+cloudscale/distributed_jmeter/scripts/visualization/__init__.py
+cloudscale/distributed_jmeter/scripts/visualization/converters.py
+cloudscale/distributed_jmeter/scripts/visualization/parser.py
+cloudscale/distributed_jmeter/scripts/visualization/plotter.py
+cloudscale/distributed_jmeter/scripts/visualization/r_visualization.R
+cloudscale/distributed_jmeter/scripts/visualization/visualize.py
+cloudscale/distributed_jmeter/test/__init__.py
MANIFEST.in 1(+1 -0)
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..e29da8a
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1 @@
+recursive-include cloudscale *
README.txt 43(+43 -0)
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..2bc961f
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,43 @@
+==================
+Distributed JMeter
+==================
+
+Distributed JMeter provides such and such and so and so. You might find
+it most useful for tasks involving <x> and also <y>. Typical usage
+often looks like this::
+
+ #!/usr/bin/env python
+
+ from towelstuff import location
+ from towelstuff import utils
+
+ if utils.has_towel():
+ print "Your towel is located:", location.where_is_my_towel()
+
+(Note the double-colon and 4-space indent formatting above.)
+
+Paragraphs are separated by blank lines. *Italics*, **bold**,
+and ``monospace`` look like this.
+
+
+A Section
+=========
+
+Lists look like this:
+
+* First
+
+* Second. Can be multiple lines
+but must be indented properly.
+
+A Sub-Section
+-------------
+
+Numbered lists look like you'd expect:
+
+1. hi there
+
+2. must be going
+
+Urls are http://like.this and links can be
+written `like this <http://www.example.com/foo/bar>`_.
requirements.txt 28(+28 -0)
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..c8ddbe5
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,28 @@
+Babel==1.3
+argparse==1.3.0
+boto==2.36.0
+cloudscale==0.1.0
+ecdsa==0.13
+iso8601==0.1.10
+msgpack-python==0.4.5
+netaddr==0.7.13
+netifaces==0.10.4
+numpy==1.9.1
+oslo.config==1.7.0
+oslo.i18n==1.4.0
+oslo.serialization==1.3.0
+oslo.utils==1.3.0
+pandas==0.15.2
+paramiko==1.15.2
+pbr==0.10.7
+prettytable==0.7.2
+pycrypto==2.6.1
+python-dateutil==2.4.0
+python-keystoneclient==1.1.0
+python-novaclient==2.21.0
+pytz==2014.10
+requests==2.5.3
+simplejson==3.6.5
+six==1.9.0
+stevedore==1.2.0
+wsgiref==0.1.2
setup.py 21(+21 -0)
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..9758920
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,21 @@
+from distutils.core import setup
+from setuptools import find_packages
+setup(
+ name='cloudscale-distributed-jmeter',
+ version='0.1.0',
+ author='Simon Ivansek',
+ author_email='simon.ivansek@xlab.si',
+ packages=find_packages(),
+ package_data={'cloudscale.distributed_jmeter.scripts.visualization' : ['r_visualization.R']},
+ url='http://pypi.python.org/pypi/TowelStuff/',
+ license='LICENSE.txt',
+ description='Distributed JMeter for CloudScale project',
+ long_description=open('README.txt').read(),
+ include_package_data=True,
+ install_requires=[
+ "boto==2.36.0",
+ "python-novaclient==2.21.0",
+ "paramiko==1.15.2",
+ "pandas==0.15.2"
+ ],
+)