cloudstore-developers

Merge branch 'distributed-jmeter'

4/20/2015 5:18:23 AM

Details

.gitignore 7(+7 -0)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7b84229
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+*.pyc
+.idea
+bin/config.ini
+bin/distributed_jmeter.log
+build
+cloudscale_distributed_jmeter.egg-info
+dist
\ No newline at end of file

bin/config.aws.ini 27(+27 -0)

diff --git a/bin/config.aws.ini b/bin/config.aws.ini
new file mode 100644
index 0000000..44c0f2d
--- /dev/null
+++ b/bin/config.aws.ini
@@ -0,0 +1,27 @@
+[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>
+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
+
+
diff --git a/bin/config.openstack.ini b/bin/config.openstack.ini
new file mode 100644
index 0000000..bcecb18
--- /dev/null
+++ b/bin/config.openstack.ini
@@ -0,0 +1,19 @@
+[SHOWCASE]
+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
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'
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
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
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.md 103(+103 -0)

diff --git a/README.md b/README.md
new file mode 100644
index 0000000..785160a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,103 @@
+## Distributed JMeter
+Distributed JMeter application is a load generator application which was developed for CloudScale project, but it can be
+ used independently from CloudScale project. For generating the load it uses the opensource software Apache JMeter.
+Distributed JMeter can be deployed on AWS or OpenStack. For more information how to do it, see below.
+
+You can read more about CloudScale project on: http://www.cloudscale-project.eu
+
+## Configs
+
+Settings in config files are separated into sections for easier understanding.
+
+### Amazon Web Services
+
+**[SHOWCASE]**
+
+```autoscalable``` - It's value ```yes``` or ```no``` tells application if showcase is deployed in autoscalable mode. This is important for getting the data from AWS.
+```host``` - The host name where showcase is deployed. Showcase must be deployed on ```/showcase-1-a``` path
+```frontend_instances_id``` - The name of frontend instances of showcase. It is used for getting data from showcase instances.
+
+**[SCENARIO]**
+
+```num_threads``` - The number of threads that we want to simulate. One JMeter instance can handle 2000 VU.
+```ips``` - IP addresses of instances to deploy JMeter on. Leave empty to not use this setting.
+```jmeter_url``` - URL to JMeter distribution. You can download JMeter and modify it, upload it somewhere and replace existing URL with yours. Otherwise leave as it is.
+
+**[AWS]**
+
+```region``` - The region name where to deploy application.
+```aws_access_key_id``` - Your AWS access key.
+```aws_secret_access_key``` - Your AWS secret key.
+```availability_zones``` - Availability zones for region.
+
+**[EC2]**
+
+```instance_type``` - EC2 instance type for distributed JMeter
+```remote_user``` - Virtual Machine user name for SSH access
+```ami_id``` - Amazon Machine Image ID to provision VM from.
+```key_name``` - Only the name of SSH key for connecting to VM.
+```key_pair``` - Path to SSH key for connecting to VM. It is auto-generated.
+
+**[RDS]**
+
+```identifiers``` - Name of VM for RDS database.
+
+### OpenStack
+
+**[SHOWCASE]**
+
+```host``` - The host name where showcase is deployed. Showcase must be deployed on ```/showcase-1-a``` path
+```frontend_instances_id``` - The name of frontend instances of showcase. It is used for getting data from showcase instances.
+
+**[SCENARIO]**
+
+```num_threads``` - The number of threads that we want to simulate. One JMeter instance can handle 2000 VU.
+```instance_names``` - Name of instances on OpenStack to deploy distributed JMeter on.
+```jmeter_url``` - URL to JMeter distribution. You can download JMeter and modify it, upload it somewhere and replace existing URL with yours. Otherwise leave as it is.
+
+**[OPENSTACK]**
+
+```user``` - User for authentication to OpenStack.
+```pwd``` - Password for user for authentication to OpenStack.
+```tenant``` - Tenant name.
+```url``` - URL to your OpenStack authentication.
+```image``` - Image name to use for VM.
+```instance_type``` - Flavor name to use with VM.
+```key_name``` - The name of SSH key on OpenStack.
+```key_pair_path``` - Path to SSH key.
+```remote_user``` - Username to use for SSH on VM.
+
+## Installation
+
+Before you can use distributed JMeter scripts you need to install them. You can do this by downloading the ZIP archive and then run:
+
+```
+$ python setup.py install 
+```
+
+You can also install the scripts using ```pip``` tool:
+
+```
+$ pip install -e https://github.com/CloudScale-project/Showcase/distributed-jmeter/zipball/distributed-jmeter
+```
+
+## Usage
+
+### Amazon Web Services
+To run distributed JMeter on AWS edit ```bin/config.aws.ini``` file and run:
+
+```
+$ python run.py aws config.aws.ini scenarios/cloudscale-max.jmx
+```
+
+from ```bin/``` directory.
+
+### OpenStack
+
+To run distributed JMeter on OpenStack edit ```bin/config.openstack.ini``` file and run:
+
+```
+$ python run.py openstack config.openstack.ini scenarios/cloudscale-max.jmx
+```
+
+from ```bin/``` directory.

README.txt 95(+95 -0)

diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..86a49cd
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,95 @@
+==================
+Distributed JMeter
+==================
+
+Distributed JMeter application is a load generator application which was developed for CloudScale project, but it can be
+ used independently from CloudScale project. For generating the load it uses the opensource software Apache JMeter.
+Distributed JMeter can be deployed on AWS or OpenStack. For more information how to do it, see below.
+
+You can read more about CloudScale project on: http://www.cloudscale-project.eu
+
+Configs
+=======
+
+Settings in config files are separated into sections for easier understanding.
+
+Amazon Web Services
+-------------------
+[SHOWCASE]
+
+```autoscalable``` - It's value ```yes``` or ```no``` tells application if showcase is deployed in autoscalable mode. This is important for getting the data from AWS.
+```host``` - The host name where showcase is deployed. Showcase must be deployed on ```/showcase-1-a``` path
+```frontend_instances_id``` - The name of frontend instances of showcase. It is used for getting data from showcase instances.
+
+[SCENARIO]
+
+```num_threads``` - The number of threads that we want to simulate. One JMeter instance can handle 2000 VU.
+```ips``` - IP addresses of instances to deploy JMeter on. Leave empty to not use this setting.
+```jmeter_url``` - URL to JMeter distribution. You can download JMeter and modify it, upload it somewhere and replace existing URL with yours. Otherwise leave as it is.
+
+[AWS]
+
+```region``` - The region name where to deploy application.
+```aws_access_key_id``` - Your AWS access key.
+```aws_secret_access_key``` - Your AWS secret key.
+```availability_zones``` - Availability zones for region.
+
+[EC2]
+
+```instance_type``` - EC2 instance type for distributed JMeter
+```remote_user``` - Virtual Machine user name for SSH access
+```ami_id``` - Amazon Machine Image ID to provision VM from.
+```key_name``` - Only the name of SSH key for connecting to VM.
+```key_pair``` - Path to SSH key for connecting to VM. It is auto-generated.
+
+[RDS]
+```identifiers``` - Name of VM for RDS database.
+
+OpenStack
+---------
+[SHOWCASE]
+
+```host``` - The host name where showcase is deployed. Showcase must be deployed on ```/showcase-1-a``` path
+```frontend_instances_id``` - The name of frontend instances of showcase. It is used for getting data from showcase instances.
+
+[SCENARIO]
+
+```num_threads``` - The number of threads that we want to simulate. One JMeter instance can handle 2000 VU.
+```instance_names``` - Name of instances on OpenStack to deploy distributed JMeter on.
+```jmeter_url``` - URL to JMeter distribution. You can download JMeter and modify it, upload it somewhere and replace existing URL with yours. Otherwise leave as it is.
+
+[OPENSTACK]
+
+```user``` - User for authentication to OpenStack.
+```pwd``` - Password for user for authentication to OpenStack.
+```tenant``` - Tenant name.
+```url``` - URL to your OpenStack authentication.
+```image``` - Image name to use for VM.
+```instance_type``` - Flavor name to use with VM.
+```key_name``` - The name of SSH key on OpenStack.
+```key_pair_path``` - Path to SSH key.
+```remote_user``` - Username to use for SSH on VM.
+
+Usage
+=========
+
+Amazon Web Services
+-------------------
+To run distributed JMeter on AWS edit ```bin/config.aws.ini``` file and run:
+
+```
+$ python run.py aws config.aws.ini scenarios/cloudscale-max.jmx
+```
+
+from ```bin/``` directory.
+
+OpenStack
+---------
+
+To run distributed JMeter on OpenStack edit ```bin/config.openstack.ini``` file and run:
+
+```
+$ python run.py openstack config.openstack.ini scenarios/cloudscale-max.jmx
+```
+
+from ```bin/``` directory.

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"
+    ],
+)