how to load test a grpc server with locust - python

i have a simple grpc server that has two services:
signin, ping, encapsulated in the following class that also has a private method to authenticate the requests:
class Listener(pingpong_pb2_grpc.PingPongServiceServicer):
def __init__(self):
self.counter = counter_g
self.last_print_time = time.time()
def __str__(self):
return self.__class__.__name__
def auth_request(self, request, context):
metadata_dict = dict(context.invocation_metadata())
if metadata_dict.get("authorization").split(" ")[1] == "jf90845h5gfip345t8":
pass
else:
print("Auth Failed")
context.abort(grpc.StatusCode.UNAUTHENTICATED, "Auth Failed")
def signin(self, request, context):
"""The signin function is the rpc call that is called by the client"""
if request.username == "test" and request.password == "test":
print('Signin Success')
return pingpong_pb2.SignInResponse(token="jf90845h5gfip345t8", success=True)
else:
print('Signin Failed')
return pingpong_pb2.SignInResponse(token="bad token", success=False)
def ping(self, request, context):
"""The ping function is the rpc call that is called by the client"""#
self.auth_request(request, context)
self.counter += 1
if self.counter > 1000:
print("1000 calls in %3f seconds" % (time.time() - self.last_print_time))
self.last_print_time = time.time()
self.counter = 0
response = pingpong_pb2.Pong(count=request.count + 1)
return response
in order to make the grpc tasks report back execution time and success/failure events, i wrote this decorator:
def grpctask(func):
def wrapper(*args, **kwargs):
# get task's function name
task_name = func.__name__
start = time.time()
result = None
try:
result = func(*args, **kwargs)
except grpc.RpcError as e:
total = int((time.time() - start) * 1000)
events.request_failure.fire(request_type="grpc",
name=task_name,
response_time=total,
response_length=0,
exception=e)
else:
total = int((time.time() - start) * 1000)
events.request_success.fire(request_type="grpc",
name=task_name,
response_time=total,
response_length=5)
return result
return wrapper
my user behaviour is as follows:
every 31 seconds the user should execute:\ (behaviour 1)
ping_server_1
ping_server_2
ping_server_3
(note that each funtion is diffrent that have similar names only)
every 43 seconds the user should excute:\ (behaviour 2)
hello_server_1
hello_server_2
the two user actions should be independent, meaning that the user may execute both at the same time (not really parallel, just wait time between behaviour 1 and 2 should be zero ) \
i wrote the following script, nesting ping_server_1, ping_server_2, ping_server_3 inside a task, made locust not able to show data for each of those sub tasks"
from locust import TaskSet, between, task, User, events, HttpUser, constant, SequentialTaskSet
import random
import grpc
from google.protobuf import json_format
from client import PingClient
import time
from tools import grpctask
class TaskOne(SequentialTaskSet):
#task
class PingTest(SequentialTaskSet):
host = "localhost:9999"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.stub = None
self.vacancy_id = None
self.token = None
self.ping_client = PingClient(host="localhost:9999")
def on_start(self):
self.connect_to_server()
self.login()
def connect_to_server(self):
# use the ping client to connect to the server
self.ping_client.connect_to_server()
def login(self):
# use the ping client to login
self.ping_client.set_token()
#task
#grpctask
def ping_server(self):
self.ping_client.ping()
#task
#grpctask
def ping_server_2(self):
self.ping_client.ping()
#task
#grpctask
def ping_server_3(self):
self.ping_client.ping()
self.interrupt()
#task
def empty(self):
print("PingTest is empty")
self.interrupt()
class TaskTwo(SequentialTaskSet):
#task
class HelloServer(TaskSet):
host = "localhost:9999"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.stub = None
self.vacancy_id = None
self.token = None
self.ping_client = PingClient(host="localhost:9999")
def on_start(self):
self.connect_to_server()
self.login()
def connect_to_server(self):
# use the ping client to connect to the server
self.ping_client.connect_to_server()
def login(self):
# use the ping client to login
self.ping_client.set_token()
#task
#grpctask
def hello_server(self):
self.ping_client.ping()
#task
#grpctask
def hello_server_2(self):
self.ping_client.ping()
self.interrupt()
#task
def empty(self):
print("TaskTwo is empty")
self.interrupt()
class PingUser(User):
# force TaskOne to be executed every 31 seconds,
# and TaskTwo to be executed every 43 seconds
tasks = [TaskOne, TaskTwo]
is there a way to define a wait time for TaskOne and TaskTwo independetly from each other?
if not, what can be done to achieve the user behaviour described above while still treating each function as a task to get metrics for each function (task) (write each action as one function wont give metrics on each function)

Related

Can't seem to set the tasks for Locust with Clarifai API

aI am trying to use Locust to do stress testing for a model I made with Clarifai's API. I seem to have most of the stuff working but I can't seem to get the syntax of task assignment to work properly. The UI is working, the locust swarm works, I can get the stats in a csv but it isn't executing the task defined here which is the prediction being made to a custom model.
This is my function that is making the actual call.
import gevent
from gevent import monkey
monkey.patch_all()
from clarifai.rest import ClarifaiApp, Workflow
import random
from locust import task, TaskSet, between, User
import app_settings. #contains some hardcoded values as a script.
from locust_utils import ClarifaiLocust, locust_call
class ApiUser(ClarifaiLocust):
min_wait = 1000
max_wait = 3000
wait_time = between(2,5)
def on_start(self):
# self.small_model = self.client.get_app('model-specialization-demo-pt2').workflows.get('locust-vehicle-det')
self.small_model = self.client.get_app('app_id').workflows.get('locust-vehicle-det')
#task
def predict_small_model(self):
locust_call(
self.small_model.predict_by_url,
'predict to Public Vehicle Detector',
url=random.choice(app_settings.small_app_urls)
)
The functions being referred as ClarifaiLocust and locust_call are below
def locust_call(func, name, *args, **kwargs):
start_time = time.time()
try:
func(*args, **kwargs) # Don't really care about results for stress test
except ApiError as e:
total_time = int((time.time() - start_time) * 1000)
events.request_failure.fire(
request_type='Client', name=name, response_time=total_time, exception=e)
else:
total_time = int((time.time() - start_time) * 1000)
events.request_success.fire(
request_type='Client', name=name, response_time=total_time, response_length=0)
class ClarifaiLocust(HttpUser):
test_app = None
search_app = None
wait_time = between(2,5)
def __init__(self, *args, **kwargs):
# Locust.__init__(self, *args, **kwargs)
User.__init__(self, *args, **kwargs)
#super(ClarifaiLocust, self).__init__(*args, **kwargs)
self.client = ClarifaiUser(self.host)
but I keep getting this error.
raise Exception("No tasks defined. use the #task decorator or set the tasks property of the User")
Exception: No tasks defined. use the #task decorator or set the tasks property of the User
What am I doing wrong here?
Add abstract = True on ClarifaiLocust, otherwise Locust will try to run it as well.
So:
class ClarifaiLocust(HttpUser):
abstract = True
....

Tornado gen.sleep add delay

I'm trying to add a delay between requests in an asynchronous way.
When I use Tornado gen.sleep(x) my function (launch) doesn't get executed.
If I remove yield from yield gen.sleep(1.0), function is called, but no delay is added.
How to add delay between requests in my for loop? I need to control Request per second to external API.
If I use time.sleep the response is delayed after all requests are completed.
Tried to add #gen.engine decorator to launch function and no results.
Code:
import collections
import tornado.httpclient
class BacklogClient(object):
MAX_CONCURRENT_REQUESTS = 20
def __init__(self, ioloop):
self.ioloop = ioloop
self.client = tornado.httpclient.AsyncHTTPClient(max_clients=self.MAX_CONCURRENT_REQUESTS)
self.client.configure(None, defaults=dict(connect_timeout=20, request_timeout=30))
self.backlog = collections.deque()
self.concurrent_requests = 0
def __get_callback(self, function):
def wrapped(*args, **kwargs):
self.concurrent_requests -= 1
self.try_run_request()
return function(*args, **kwargs)
return wrapped
def try_run_request(self):
while self.backlog and self.concurrent_requests < self.MAX_CONCURRENT_REQUESTS:
request, callback = self.backlog.popleft()
self.client.fetch(request, callback=callback)
self.concurrent_requests += 1
def fetch(self, request, callback=None):
wrapped = self.__get_callback(callback)
self.backlog.append((request, wrapped))
self.try_run_request()
import time
from tornado import ioloop, httpclient, gen
class TornadoBacklog:
def __init__(self):
self.queue = 0
self.debug = 1
self.toProcess = [
'http://google.com',
'http://yahoo.com',
'http://nytimes.com',
'http://msn.com',
'http://cnn.com',
'http://twitter.com',
'http://facebook.com',
]
def handle_request(self, response):
print response.code
if not self.backlog.backlog and self.backlog.concurrent_requests == 0:
ioloop.IOLoop.instance().stop()
def launch(self):
self.ioloop = ioloop.IOLoop.current()
self.backlog = BacklogClient(self.ioloop)
for item in self.toProcess:
yield gen.sleep(1.0)
print item
self.backlog.fetch(
httpclient.HTTPRequest(
item,
method='GET',
headers=None,
),
self.handle_request
)
self.ioloop.start()
def main():
start_time = time.time()
scraper = TornadoBacklog()
scraper.launch()
elapsed_time = time.time() - start_time
print('Process took %f seconds processed %d items.' % (elapsed_time, len(scraper.toProcess)))
if __name__ == "__main__":
main()
Reference: https://github.com/tornadoweb/tornado/issues/1400
Tornado coroutines have two components:
They contain "yield" statements
They are decorated with "gen.coroutine"
Use the "coroutine" decorator on your "launch" function:
#gen.coroutine
def launch(self):
Run a Tornado coroutine from start to finish like this:
tornado.ioloop.IOLoop.current().run_sync(launch)
Remove the call to "ioloop.start" from your "launch" function: the loop runs the "launch" function, not vice-versa.

python time instance inside class not updating on call

I have a small script that polls a database to look for status of certain jobs. I decided to use APScheduler to handle the looping call. I created a decorator to timeout a function if taking too long. The issue I am having here is that the decorator is inside a class and even though I create two instances of the class, inside two different functions, they always have the same start_time. I thought maybe if I move the decorator inside of my class and initialize the start_time in the init call it would update the start_time per instance of the class. When I moved the decorator insdie of the class and assigned self.start_time = datetime.now() the start time updates on each call of the class and thus will never time out. The example of the decorator inside of the class is also below.
def timeout(start, min_to_wait):
def decorator(func):
def _handle_timeout():
scheduler.shutdown(wait=False)
#wraps(func)
def wrapper(*args, **kwargs):
expire = start + timedelta(minutes = min_to_wait)
now = datetime.now()
if now > expire:
_handle_timeout()
return func(*args, **kwargs)
return wrapper
return decorator
class Job(object):
def __init__(self, name, run_id, results):
self.name = name
self.run_id = object_id
self.results = results
self.parcel_id= None
self.status = None
start_time = datetime.now()
#timeout(start_time, config.WAIT_TIME)
def wait_for_results(self):
if self.results:
self.pack_id = self.results[0].get('parcel_id')
self.status = self.results[0].get('status')
return self.results[0]
else:
return False
#timeout(start_time, config.WORK_TIME)
def is_done(self):
status = self.results[0].get('status')
status_map = {'done': True,
'failed': FailedError,
'lost': LostError}
def _get_or_throw(s, map_obj):
value = map_obj.get(s)
if s in ['failed', 'lost']:
raise value(s)
else:
self.status = s
return s
return _get_or_throw(status, status_map)
def job_1(mssql, postgres, runid):
res = get_results(mssql, config.MSSQL, first_query_to_call)
first_job= Job('first_job', runid, res)
step_two = pack_job.wait_for_results()
if step_two:
try:
logger.info(first_job)
if first_job.is_done() == 'done':
scheduler.remove_job('first_job')
scheduler.add_job(lambda: job_two(mssql,
postgres, first_job.object_id, runid), 'interval', seconds=config.POLL_RATE, id='second_job')
except LostError as e:
logger.error(e, exc_info=True)
scheduler.shutdown(wait=False)
except FailedError as e:
logger.error(e, exc_info=True)
scheduler.shutdown(wait=False)
def job_two(mssql, postgres, object_id, runid):
res = get_results(mssql, config.MSSQL, some_other_query_to_run, object_id)
second_job= Job('second_job', runid, res)
step_two = second_job.wait_for_results()
if step_two:
try:
logger.info(second_job)
if second_job.is_done() == 'done':
scheduler.remove_job('second_job')
except LostError as e:
logger.error(e, exc_info=True)
scheduler.shutdown(wait=False)
except FailedError as e:
logger.error(e, exc_info=True)
scheduler.shutdown(wait=False)
if __name__ == '__main__':
runid = sys.argv[1:]
if runid:
runid = runid[0]
scheduler = BlockingScheduler()
run_job = scheduler.add_job(lambda: job_one(pymssql, psycopg2, runid), 'interval', seconds=config.POLL_RATE, id='first_job')
attempt to move decorator inside class:
class Job(object):
def __init__(self, name, run_id, results):
self.name = name
self.run_id = run_id
self.results = results
self.pack_id = None
self.status = None
self.start_time = datetime.now()
def timeout(min_to_wait):
def decorator(func):
def _handle_timeout():
scheduler.shutdown(wait=False)
#wraps(func)
def wrapper(self, *args, **kwargs):
print '**'
print self.start_time
print ''
expire = self.start_time + timedelta(minutes = min_to_wait)
now = datetime.now()
if now > expire:
_handle_timeout()
return func(self, *args, **kwargs)
return wrapper
return decorator
here is an example output from when I use the above decorator.
**
self start time: 2014-10-28 08:57:11.947026
**
self start time: 2014-10-28 08:57:16.976828
**
self start time: 2014-10-28 08:57:21.989064
the start_time needs to stay the same or else I can't timeout the function.
In the first exemple, your start time is initialised when the class statement is executed, which in your case is when the module is first imported in the interpreter.
In the second exemple, the start time is initialized when the class is instanciated. It should not change from one method call to another for a same Job instance. Of course if you keep on creating new instances, the start time will be different for each instance.
Now you didn't post the code using your Job class, so it's hard to tell what the right solution would be.

Python, Call a class function from another class

Can you anyone please help me (noob) call the broadcast function from class BroadcastServerFactory in class process, as per attached code
I have tried so many methods of call a function from another class, but no solution
import time, sys
from apscheduler.scheduler import Scheduler
import threading
import socket
from twisted.internet import reactor
from twisted.python import log
from twisted.web.server import Site
from twisted.web.static import File
from autobahn.websocket import WebSocketServerFactory, \
WebSocketServerProtocol, \
listenWS
class process(threading.Thread):
def __init__(self, buffer3):
threading.Thread.__init__(self)
self.setDaemon(True)
self.buffer3 = buffer3
def run(self):
factory.broadcast("I don't know what I'm doing!")
class BroadcastServerProtocol(WebSocketServerProtocol):
def onOpen(self):
self.factory.register(self)
def onMessage(self, msg, binary):
if not binary:
self.factory.broadcast("'%s' from %s" % (msg, self.peerstr))
def connectionLost(self, reason):
WebSocketServerProtocol.connectionLost(self, reason)
self.factory.unregister(self)
class BroadcastServerFactory(WebSocketServerFactory):
"""
Simple broadcast server broadcasting any message it receives to all
currently connected clients.
"""
def __init__(self, url, debug = False, debugCodePaths = False):
WebSocketServerFactory.__init__(self, url, debug = debug, debugCodePaths = debugCodePaths)
self.clients = []
self.tickcount = 0
self.tick()
def tick(self):
self.tickcount += 1
self.broadcast("'tick %d' from server" % self.tickcount)
reactor.callLater(1, self.tick)
def register(self, client):
if not client in self.clients:
print "registered client " + client.peerstr
self.clients.append(client)
def unregister(self, client):
if client in self.clients:
print "unregistered client " + client.peerstr
self.clients.remove(client)
def broadcast(self, msg):
print "broadcasting message '%s' .." % msg
for c in self.clients:
c.sendMessage(msg)
print "message sent to " + c.peerstr
class BroadcastPreparedServerFactory(BroadcastServerFactory):
"""
Functionally same as above, but optimized broadcast using
prepareMessage and sendPreparedMessage.
"""
def broadcast(self, msg):
print "broadcasting prepared message '%s' .." % msg
preparedMsg = self.prepareMessage(msg)
for c in self.clients:
c.sendPreparedMessage(preparedMsg)
print "prepared message sent to " + c.peerstr
def testing():
buffer2 - "hello"
myDisplay = process(buffer2)
myDisplay.start()
if __name__ == '__main__':
if len(sys.argv) > 1 and sys.argv[1] == 'debug':
log.startLogging(sys.stdout)
debug = True
else:
debug = False
level_scheduler = Scheduler()
level_scheduler.add_interval_job(testing, seconds=5)
level_scheduler.start()
#ServerFactory = BroadcastServerFactory
ServerFactory = BroadcastPreparedServerFactory
factory = ServerFactory("ws://localhost:9000",
debug = debug,
debugCodePaths = debug)
factory.protocol = BroadcastServerProtocol
factory.setProtocolOptions(allowHixie76 = True)
listenWS(factory)
webdir = File(".")
web = Site(webdir)
reactor.listenTCP(8080, web)
reactor.run()
Thanks
Pass the class instance of BroadcastServerFactory to be called to the class instance that calls it process on creation
class process(threading.Thread):
def __init__(self, buffer3m, broadcast_server_factory):
threading.Thread.__init__(self)
self.setDaemon(True)
self.buffer3 = buffer3
self.factory = broadcast_server_factory
def run(self):
self.factory.broadcast("I don't know what I'm doing!")
and then call it (it's assigned as self.factory in the run statement. I can't see where you create a process class in your __main__ but it will be created with something like
p = process(buffer, factory)
Aside: Using capital letters for class names is considered good form in python process -> Process

Twisted task.loop and pb auth

Learn Twisted. I decided to write a server and client that once a second to share data.
Wrote one implementation, but it seems to me that it is not correct.
# -*- coding: utf-8 -*-
from twisted.spread import pb
from twisted.internet import reactor, task
from twisted.cred import credentials
from win32com.server import factory
class login_send:
def __init__(self):
self.count=0
self.timeout = 1.0
self.factory = pb.PBClientFactory()
reactor.connectTCP("localhost", 8800, self.factory)
def testTimeout(self):
self.count+=1
print self.count
def1 = self.factory.login(credentials.UsernamePassword("test1","bb1b"))
def1.addCallbacks(self.good_connected, self.bad_connected)
def1.addCallback(self.send_data)
def1.addErrback(self.disconnect)
if self.count>10:def1.addBoth(self.disconnect)
def start(self):
l = task.LoopingCall(self.testTimeout)
l.start(self.timeout)
reactor.run()
def good_connected(self, perspective):
print 'good login and password', perspective
return perspective
def bad_connected(self, perspective):
print 'bad login or password', perspective
return perspective
def send_data(self, perspective):
print 'send'
return perspective.callRemote("foo", self.count)
def disconnect(self, perspective):
print 'disconnect'
reactor.stop()
if __name__ == "__main__":
st=login_send()
st.start()
Code: if login and password True -> send self.count, if login or password False -> disconnect, if self.count>10 -> disconnect
The first mistake, in my opinion is that I have to login every time.
def1 = self.factory.login(credentials.UsernamePassword("test1", "bb1b"))
How to make one authorization, and continue to send data every second?
simple test server code:
from zope.interface import implements
from twisted.spread import pb
from twisted.cred import checkers, portal
from twisted.internet import reactor
class MyPerspective(pb.Avatar):
def __init__(self, name):
self.name = name
def perspective_foo(self, arg):
print "I am", self.name, "perspective_foo(",arg,") called on", self
return arg
class MyRealm:
implements(portal.IRealm)
def requestAvatar(self, avatarId, mind, *interfaces):
if pb.IPerspective not in interfaces:
print 'qqqq'
raise NotImplementedError
return pb.IPerspective, MyPerspective(avatarId), lambda:None
p = portal.Portal(MyRealm())
c = checkers.InMemoryUsernamePasswordDatabaseDontUse(test1="bbb",
user2="pass2")
p.registerChecker(c)
reactor.listenTCP(8800, pb.PBServerFactory(p))
reactor.run()
I believe this should do the trick.
# Upper case first letter of class name is good policy.
class Login_send:
def __init__(self):
# initialize the state variable to False.
self.connection = False
self.count=0
self.timeout = 1.0
self.factory = pb.PBClientFactory()
reactor.connectTCP("localhost", 8800, self.factory)
def testTimeout(self):
self.count+=1
print self.count
# no connection -- create one.
if not self.connection:
self.assign_connection()
# cached connection exists, call send_data manually.
elif self.count > 10:
self.disconnect(self.connection)
else:
#you probably want to send data only if it it should be valid.
self.send_data(self.connection)
def assign_connection(self):
''' Creates and stores a Deffered which represents the connection to
the server. '''
# cache the connection.
self.connection = self.factory.login(
credentials.UsernamePassword("test1","bb1b"))
# add connection callbacks as normal.
self.connection.addCallbacks(
self.good_connected, self.bad_connected)
self.connection.addCallback(self.send_data)
self.connection.addErrback(self.disconnect)
def disconnect(self, perspective):
# be sure to cleanup after yourself!
self.connection = False
print 'disconnect'
reactor.stop()
# the rest of your class goes here.

Categories

Resources