Skip to content

Commit

Permalink
Merge pull request locustio#33 from erlanggakrisnamukti/develop
Browse files Browse the repository at this point in the history
Merge develop to master
  • Loading branch information
pancaprima authored Nov 30, 2017
2 parents cb262ef + bba14e6 commit b0d9cd3
Show file tree
Hide file tree
Showing 11 changed files with 407 additions and 37 deletions.
1 change: 0 additions & 1 deletion locust/clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

absolute_http_url_regexp = re.compile(r"^https?://", re.I)


class LocustResponse(Response):

def raise_for_status(self):
Expand Down
57 changes: 57 additions & 0 deletions locust/configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import os, json, logging

logger = logging.getLogger(__name__)
config_path = '/tests/settings/config.json'

def read_file():
"""
Will read the file and return it as a string with tree view.
"""
try:
with open((os.environ['PYTHONPATH'].split(os.pathsep))[-1] + config_path, "r") as data_file:
data = data_file.read()
data_file.close()
except Exception as err:
logger.info(err)
data = "{}"
return data

def write_file(string_json):
"""
The `string_json` will overwrite existing configuration.
If the previous configuration doesn't exist, then it will create the file.
"""
status, message = None, None
try:
with open((os.environ['PYTHONPATH'].split(os.pathsep))[-1] + config_path, "w") as data_file:
data_file.write(string_json)
status = True
message = 'Configuration has been saved'
except Exception as err:
logger.info(err)
status = False
message = "Can't save the configuration :" + err
return status, message

class ClientConfiguration:
"""
This class is a handler for data configuration with JSON data structure.
"""

config_data = None

def read_json(self):
"""
Will get the data of configuration as JSON.
It reads configuration file once.
"""
if self.config_data is None:
try:
with open((os.environ['PYTHONPATH'].split(os.pathsep))[-1] + config_path, "r") as data_file:
self.config_data = json.load(data_file)
data_file.close()
except Exception as err:
logger.info(err)
self.config_data = json.load({})
return self.config_data

16 changes: 14 additions & 2 deletions locust/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from .clients import HttpSession
from . import events
from .configuration import ClientConfiguration

from .exception import LocustError, InterruptTaskSet, RescheduleTask, RescheduleTaskImmediately, StopLocust

Expand Down Expand Up @@ -223,6 +224,9 @@ class ForumPage(TaskSet):
instantiated. Useful for nested TaskSet classes.
"""

config = None
"""Will refer to the ClientConfiguration class instance when the TaskSet has been instantiated"""

def __init__(self, parent):
self._task_queue = []
self._time_start = time()
Expand All @@ -235,7 +239,8 @@ def __init__(self, parent):
raise LocustError("TaskSet should be called with Locust instance or TaskSet instance as first argument")

self.parent = parent

self.config = ClientConfiguration()

# if this class doesn't have a min_wait or max_wait defined, copy it from Locust
if not self.min_wait:
self.min_wait = self.locust.min_wait
Expand Down Expand Up @@ -350,6 +355,13 @@ def client(self):
"""
Reference to the :py:attr:`client <locust.core.Locust.client>` attribute of the root
Locust instance.
"""
"""
return self.locust.client

@property
def configuration(self):
"""
Reference to configuration.py
"""
return self.config.read_json()

92 changes: 92 additions & 0 deletions locust/csv_to_json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import json
import pandas as pd

class csvToJson:

def __init__(self, file_path):
"""
Initialize object's attribute
:param string file_path: path of csv file
"""
self.file_path = file_path
self.df = pd.read_csv(file_path)

def get_columns_name(self):
"""
Return column's name which exist in the file
:returns: column's name in list object type
"""
return self.df.keys().tolist()

def _get_nested_record(self, grouped_data_key, grouped_data, array_column, regular_column):
"""
Set a record which is consist of regular column and column which has array type value.
:param list grouped_data_key: data key of grouped data
:param list grouped_data: data of grouped data
:param list array_column: column's name which value's data type is list
:param list regular_column: column's name which value's data type is numbers or string
:returns: a data in the shape of dictionary
"""

record = {}
# check whether grouped_data_key tuple or not. if not (only exist one key), turn into tuple
if isinstance(grouped_data_key, int):
grouped_data_key = (grouped_data_key,)

# assign key and value which come from regular column
for i in range(len(grouped_data_key)):
record[regular_column[i]] = grouped_data_key[i]

# assign key and value which come from column which has array type value
for field in array_column:
record[field] = list(grouped_data[field].unique())

return record

def _get_array_record(self, array_column):
"""
Set a record which is consist of column and its value data type is array.
:param list array_column: column's name which value's data type is list
:param dataframe df: raw data which want to be processed
:returns: a data in the shape of dictionary
"""

record = {}
for field in array_column:
record[field] = list(self.df[field].unique())

return record

def convert(self, array_column):
"""
Convert csv data into json data with only 1 depth level.
:param string csv_path: path of csv which want to be converted into json format
:param list array_column: column's name which value's data type is list
:returns: list object
"""

# collect column's name which is not having array as its value
regular_column = []
for column in self.get_columns_name():
if column not in array_column: regular_column.append(column)

records = None
# if csv contains only one column and the column definitely acts as array
if len(self.df.columns)==1:
records= self._get_array_record(self.get_columns_name())
# if csv contains more than one columns and all of it act as array
elif not regular_column:
records= self._get_array_record(array_column)
# group csv data by regular column data
else:
records = []
for grouped_data_key, grouped_data in self.df.groupby(regular_column):
record = self._get_nested_record(grouped_data_key, grouped_data, array_column, regular_column)
records.append(record)

return records
2 changes: 2 additions & 0 deletions locust/ramping.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ def start_ramping(hatch_rate=None, max_locusts=1000, hatch_stride=100,
percent=0.95, response_time_limit=2000, acceptable_fail=0.05,
precision=200, start_count=0, calibration_time=15):

locust_runner.running_type = runners.RAMP

register_listeners()

def ramp_up(clients, hatch_stride, boundery_found=False):
Expand Down
2 changes: 2 additions & 0 deletions locust/runners.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

STATE_INIT, STATE_HATCHING, STATE_RUNNING, STATE_STOPPED = ["ready", "hatching", "running", "stopped"]
SLAVE_REPORT_INTERVAL = 3.0
NORMAL, RAMP = ["Normal", "Auto"]


class LocustRunner(object):
Expand All @@ -40,6 +41,7 @@ def __init__(self, locust_classes, options):
self.hatching_greenlet = None
self.exceptions = {}
self.stats = global_stats
self.running_type = NORMAL

# register listener that resets stats when hatching is complete
def on_hatch_complete(user_count):
Expand Down
53 changes: 51 additions & 2 deletions locust/static/locust.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,17 @@ $(".ramp_test").click(function(event) {
event.preventDefault();
$("#start").hide();
$("#ramp").show();
$("#edit_config").hide();
$(".status").removeClass("none");
});

$("#new_test").click(function(event) {
event.preventDefault();
$("#ramp").hide();
$("#start").show();
$("#ramp").hide();
$("#edit_config").hide();
$("#locust_count").focus().select();
$(".status").removeClass("none");
});

$(".edit_test").click(function(event) {
Expand All @@ -38,6 +42,26 @@ $(".edit_test").click(function(event) {
$("#new_locust_count").focus().select();
});

$(".edit_config_json").click(function(event) {
event.preventDefault();
$("#start").hide();
$("#ramp").hide();
$("#edit_config").show();
$("#config_json").focus().select();
$(".status").addClass("none");
$("ul.tabs_json").tabs("tabs_json").click(0);

});

$(".back_new_test").click(function(event) {
event.preventDefault();
$("#start").show();
$("#ramp").hide();
$("#edit_config").hide();
$("#locust_count").focus().select();
$(".status").removeClass("none");
});

$(".close_link").click(function(event) {
event.preventDefault();
$(this).parent().parent().hide();
Expand All @@ -61,6 +85,21 @@ $('#ramp_form').submit(function(event) {
);
});

$('#edit_config_form').submit(function(event) {
event.preventDefault();
$.post($(this).attr("action"), $(this).serialize(),
function(response) {
if (response.success) {
$("#ramp").hide();
$("#edit_config").hide();
$("#start").show();
$("#locust_count").focus().select();
$(".status").removeClass("none");
}
}
);
});

var alternate = false;

$("ul.tabs").tabs("div.panes > div").on("onClick", function(event) {
Expand All @@ -80,6 +119,8 @@ $("ul.tabs").tabs("div.panes > div").on("onClick", function(event) {
}
});

$("ul.tabs_json").tabs("div.panes_json > div");

var stats_tpl = $('#stats-template');
var errors_tpl = $('#errors-template');
var exceptions_tpl = $('#exceptions-template');
Expand Down Expand Up @@ -160,10 +201,18 @@ function updateStats() {
$("#fail_ratio").html(Math.round(report.fail_ratio*100));
$("#status_text").html(report.state);
$("#userCount").html(report.user_count);
$("#running_type").html(report.running_type);

if (typeof report.slave_count !== "undefined")
$("#slaveCount").html(report.slave_count)

RAMP = "Auto"
if (report.running_type == RAMP) {
$(".edit_test").addClass("none")
} else {
$(".edit_test").removeClass("none")
}

$('#stats tbody').empty();
$('#errors tbody').empty();

Expand Down Expand Up @@ -266,4 +315,4 @@ function initTotalCharts() {
endpointResponseTimeCharts[totalKey] = new LocustLineChart($(".charts-container"), "Average Responses Time", totalKey.toUpperCase(), ["Average Responses Time"], "ms", "33.3%");
endpointRpsCharts[totalKey] = new LocustLineChart($(".charts-container"), "Requests Per Second", totalKey.toUpperCase(), ["RPS"], "request", "33.3%");
endpointFailureCharts[totalKey] = new LocustLineChart($(".charts-container"), "Failure", totalKey.toUpperCase(), ["Failures"], "failure", "33.3%");
}
}
Loading

0 comments on commit b0d9cd3

Please sign in to comment.