Skip to content

Commit

Permalink
Added support for additional HTTP methods - WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
Joakim Hamren committed Jan 16, 2012
1 parent fb0ef12 commit 6db337d
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 94 deletions.
44 changes: 27 additions & 17 deletions locust/clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ def __exit__(self, exc, value, traceback):

def log_request(f):
def _wrapper(*args, **kwargs):
name = kwargs.get('name', args[1]) or args[1]
request_method = args[1]
name = kwargs.get('name', args[2]) or args[2]
if "catch_response" in kwargs:
catch_response = kwargs["catch_response"]
del kwargs["catch_response"]
Expand All @@ -49,26 +50,26 @@ def _wrapper(*args, **kwargs):
retval.allow_http_error = allow_http_error
response_time = int((time.time() - start) * 1000)
if catch_response:
retval._trigger_success = lambda : events.request_success.fire(name, response_time, retval)
retval._trigger_failure = lambda e : events.request_failure.fire(name, response_time, e, None)
retval._trigger_success = lambda : events.request_success.fire(request_method, name, response_time, retval)
retval._trigger_failure = lambda e : events.request_failure.fire(request_method, name, response_time, e, None)
else:
events.request_success.fire(name, response_time, retval)
events.request_success.fire(request_method, name, response_time, retval)
return retval
except Exception, e:
response_time = int((time.time() - start) * 1000)
extra = {}
response = None

if isinstance(e, HTTPError):
e.msg += " (" + name + ")"
extra = {"response": e.locust_http_response}
e.msg += " (" + request_method + " " + name + ")"
response = e.locust_http_response
elif isinstance(e, URLError) or isinstance(e, BadStatusLine):
e.args = tuple(list(e.args) + [name])
e.args = tuple(list(e.args) + [request_method, name])
elif isinstance(e, socket.error):
pass
else:
raise

events.request_failure.fire(name, response_time, e, **extra)
events.request_failure.fire(request_method, name, response_time, e, response)

if catch_response:
return NoneContext()
Expand Down Expand Up @@ -106,8 +107,8 @@ class HttpResponse(object):
_trigger_success = None
_trigger_failure = None


def __init__(self, url, name, code, data, info, gzip):
def __init__(self, method, url, name, code, data, info, gzip):
self.method = method
self.url = url
self._name = name
self.code = code
Expand All @@ -132,7 +133,6 @@ def _get_data(self):
def _set_data(self, data):
self._data = data


def __enter__(self):
if not self.catch_response:
raise LocustError("If using response in a with() statement you must use catch_response=True")
Expand Down Expand Up @@ -211,7 +211,7 @@ def get(self, path, headers={}, name=None, **kwargs):
if response.data == "fail":
raise ResponseError("Request failed")
"""
return self._request(path, None, headers=headers, name=name, **kwargs)
return self._request('GET', path, None, headers=headers, name=name, **kwargs)

def post(self, path, data, headers={}, name=None, **kwargs):
"""
Expand Down Expand Up @@ -245,10 +245,19 @@ def post(self, path, data, headers={}, name=None, **kwargs):
if response.data == "fail":
raise ResponseError("Posting of inbox message failed")
"""
return self._request(path, data, headers=headers, name=name, **kwargs)
return self._request('POST', path, data, headers=headers, name=name, **kwargs)

def put(self, path, data, headers={}, name=None, **kwargs):
return self._request('PUT', path, data, headers=headers, name=name, **kwargs)

def delete(self, path, headers={}, name=None, **kwargs):
return self._request('DELETE', path, None, headers=headers, name=name, **kwargs)

def head(self, path, headers={}, name=None, **kwargs):
return self._request('HEAD', path, None, headers=headers, name=name, **kwargs)

@log_request
def _request(self, path, data=None, headers={}, name=None):
def _request(self, method, path, data=None, headers={}, name=None):
if self.gzip:
headers["Accept-Encoding"] = "gzip"

Expand All @@ -260,14 +269,15 @@ def _request(self, path, data=None, headers={}, name=None):

url = self.base_url + path
request = urllib2.Request(url, data, headers)
request.get_method = lambda: method
try:
f = self.opener.open(request)
data = f.read()
f.close()
except HTTPError, e:
data = e.read()
e.locust_http_response = HttpResponse(url, name, e.code, data, e.info, self.gzip)
e.locust_http_response = HttpResponse(method, url, name, e.code, data, e.info, self.gzip)
e.close()
raise e

return HttpResponse(url, name, f.code, data, f.info, self.gzip)
return HttpResponse(method, url, name, f.code, data, f.info, self.gzip)
21 changes: 11 additions & 10 deletions locust/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ class RequestStats(object):
global_start_time = None
errors = {}

def __init__(self, name):
def __init__(self, method, name):
self.method = method
self.name = name
self.num_reqs_per_sec = {}
self.last_request_timestamp = 0
Expand Down Expand Up @@ -231,16 +232,16 @@ def percentile(self, tpl=" %-" + str(STATS_NAME_WIDTH) + "s %8d %6d %6d %6d %6d
)

@classmethod
def get(cls, name):
request = cls.requests.get(name, None)
def get(cls, method, name):
request = cls.requests.get((method, name), None)
if not request:
request = RequestStats(name)
cls.requests[name] = request
request = RequestStats(method, name)
cls.requests[(method, name)] = request
return request

@classmethod
def sum_stats(cls, name="Total", full_request_history=False):
stats = RequestStats(name)
stats = RequestStats(None, name)
for s in cls.requests.itervalues():
stats.iadd_stats(s, full_request_history)
return stats
Expand Down Expand Up @@ -281,15 +282,15 @@ def percentile(N, percent, key=lambda x:x):
d1 = key(N[int(c)]) * (k-f)
return d0+d1

def on_request_success(name, response_time, response):
def on_request_success(method, name, response_time, response):
if RequestStats.global_max_requests is not None and RequestStats.total_num_requests >= RequestStats.global_max_requests:
raise InterruptLocust("Maximum number of requests reached")

content_length = int(response.info.getheader("Content-Length") or 0)
RequestStats.get(name).log(response_time, content_length)
RequestStats.get(method, name).log(response_time, content_length)

def on_request_failure(name, response_time, error, response=None):
RequestStats.get(name).log_error(error)
def on_request_failure(method, name, response_time, error, response=None):
RequestStats.get(method, name).log_error(error)

def on_report_to_master(client_id, data):
data["stats"] = [RequestStats.requests[name].get_stripped_report() for name in RequestStats.requests if not (RequestStats.requests[name].num_reqs == 0 and RequestStats.requests[name].num_failures == 0)]
Expand Down
2 changes: 2 additions & 0 deletions locust/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ <h2>Ramping</h2>
<table id="stats" class="stats">
<thead>
<tr>
<th class="stats_label" href="#" data-sortkey="method">Method</th>
<th class="stats_label" href="#" data-sortkey="name">Name</th>
<th class="stats_label" href="#" data-sortkey="num_reqs"># requests</th>
<th class="stats_label" href="#" data-sortkey="num_failures"># fails</th>
Expand Down Expand Up @@ -212,6 +213,7 @@ <h1>Version</h1>
<script type="text/x-jqote-template" id="stats-template">
<![CDATA[
<tr class="<%=(alternate ? "dark" : "")%>">
<td><%= (this.method ? this.method : "") %></td>
<td><%= this.name %></td>
<td><%= this.num_reqs %></td>
<td><%= this.num_failures %></td>
Expand Down
40 changes: 32 additions & 8 deletions locust/test/locust_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,30 @@ class MyLocust(Locust):
self.assertEqual("POST", locust.client.post("/request_method", {"arg":"hello world"}).data)
self.assertEqual("hello world", locust.client.post("/post", {"arg":"hello world"}).data)

def test_client_put(self):
class MyLocust(Locust):
host = "http://127.0.0.1:%i" % self.port

locust = MyLocust()
self.assertEqual("PUT", locust.client.put("/request_method", {"arg":"hello world"}).data)
self.assertEqual("hello world", locust.client.put("/put", {"arg":"hello world"}).data)

def test_client_delete(self):
class MyLocust(Locust):
host = "http://127.0.0.1:%i" % self.port

locust = MyLocust()
self.assertEqual("DELETE", locust.client.delete("/request_method").method)
self.assertEqual(200, locust.client.delete("/request_method").code)

def test_client_head(self):
class MyLocust(Locust):
host = "http://127.0.0.1:%i" % self.port

locust = MyLocust()
self.assertEqual("HEAD", locust.client.head("/request_method").method)
self.assertEqual(200, locust.client.head("/request_method").code)

def test_client_basic_auth(self):
class MyLocust(Locust):
host = "http://127.0.0.1:%i" % self.port
Expand Down Expand Up @@ -217,8 +241,8 @@ def t1(l):
my_locust = MyLocust()
my_locust.t1()

self.assertEqual(1, RequestStats.get("new name!").num_reqs)
self.assertEqual(0, RequestStats.get("/ultra_fast").num_reqs)
self.assertEqual(1, RequestStats.get("GET", "new name!").num_reqs)
self.assertEqual(0, RequestStats.get("GET", "/ultra_fast").num_reqs)

def test_catch_response(self):
class MyLocust(Locust):
Expand All @@ -227,8 +251,8 @@ class MyLocust(Locust):
locust = MyLocust()

num = {'failures': 0, 'success': 0}
def on_failure(path, response_time, exception, response): num['failures'] += 1
def on_success(a, b, c): num['success'] += 1
def on_failure(method, path, response_time, exception, response): num['failures'] += 1
def on_success(a, b, c, d): num['success'] += 1

events.request_failure += on_failure
events.request_success += on_success
Expand All @@ -253,8 +277,8 @@ class MyLocust(Locust):
l = MyLocust()

num = {'failures': 0, 'success': 0}
def on_failure(path, response_time, exception, response): num['failures'] += 1
def on_success(a, b, c): num['success'] += 1
def on_failure(method, path, response_time, exception, response): num['failures'] += 1
def on_success(a, b, c, d): num['success'] += 1
events.request_failure += on_failure
events.request_success += on_success

Expand All @@ -274,8 +298,8 @@ def interrupted_task(self):
raise InterruptLocust()

num = {'failures': 0, 'success': 0}
def on_failure(path, response_time, exception, response): num['failures'] += 1
def on_success(a, b, c): num['success'] += 1
def on_failure(method, path, response_time, exception, response): num['failures'] += 1
def on_success(a, b, c, d): num['success'] += 1
events.request_failure += on_failure
events.request_success += on_success

Expand Down
12 changes: 6 additions & 6 deletions locust/test/test_stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
class TestRequestStats(unittest.TestCase):
def setUp(self):
RequestStats.global_start_time = time.time()
self.s = RequestStats("test_entry")
self.s = RequestStats("GET", "test_entry")
self.s.log(45, 0)
self.s.log(135, 0)
self.s.log(44, 0)
Expand Down Expand Up @@ -53,13 +53,13 @@ def test_reset(self):
self.assertEqual(self.s.median_response_time, 85)

def test_aggregation(self):
s1 = RequestStats("aggregate me!")
s1 = RequestStats("GET", "aggregate me!")
s1.log(12, 0)
s1.log(12, 0)
s1.log(38, 0)
s1.log_error("Dummy exzeption")

s2 = RequestStats("aggregate me!")
s2 = RequestStats("GET", "aggregate me!")
s2.log_error("Dummy exzeption")
s2.log_error("Dummy exzeption")
s2.log(12, 0)
Expand All @@ -70,7 +70,7 @@ def test_aggregation(self):
s2.log(55, 0)
s2.log(97, 0)

s = RequestStats("")
s = RequestStats("GET", "")
s.iadd_stats(s1, full_request_history=True)
s.iadd_stats(s2, full_request_history=True)

Expand All @@ -86,6 +86,6 @@ class MyLocust(Locust):

locust = MyLocust()
locust.client.get("/ultra_fast")
self.assertEqual(RequestStats.get("/ultra_fast").avg_content_length, len("This is an ultra fast response"))
self.assertEqual(RequestStats.get("GET", "/ultra_fast").avg_content_length, len("This is an ultra fast response"))
locust.client.get("/ultra_fast")
self.assertEqual(RequestStats.get("/ultra_fast").avg_content_length, len("This is an ultra fast response"))
self.assertEqual(RequestStats.get("GET", "/ultra_fast").avg_content_length, len("This is an ultra fast response"))

0 comments on commit 6db337d

Please sign in to comment.