memgraph workload simulation server - first implementation
This commit is contained in:
parent
3eb84e8f1c
commit
e93687b1db
1
demo/.gitignore
vendored
Normal file
1
demo/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
ve/
|
15
demo/demo_server.docker
Normal file
15
demo/demo_server.docker
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
FROM ubuntu:16.04
|
||||||
|
|
||||||
|
RUN apt-get update
|
||||||
|
RUN apt-get install -y python3.5 python3-pip
|
||||||
|
RUN apt-get install -y python3-setuptools
|
||||||
|
# RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
|
|
||||||
|
RUN pip3 install flask requests
|
||||||
|
|
||||||
|
COPY simulation /app/simulation
|
||||||
|
COPY demo_server_run.py /app/demo_server_run.py
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
CMD ["python3", "demo_server_run.py"]
|
31
demo/demo_server_run.py
Normal file
31
demo/demo_server_run.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from simulation.web_server import SimulationWebServer
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
'''
|
||||||
|
The frontend run script. Environment could be configured
|
||||||
|
via the MEMGRAPH_DEMO environtment variable. Available environments
|
||||||
|
are: debug, prod.
|
||||||
|
'''
|
||||||
|
if 'MEMGRAPH_DEMO' in os.environ:
|
||||||
|
environment = os.environ['MEMGRAPH_DEMO']
|
||||||
|
else:
|
||||||
|
environment = "debug"
|
||||||
|
|
||||||
|
frontend_server = SimulationWebServer()
|
||||||
|
|
||||||
|
if environment == 'prod':
|
||||||
|
logging.basicConfig(level=logging.WARNING)
|
||||||
|
frontend_server.run("0.0.0.0", 8080, False)
|
||||||
|
elif environment == 'debug':
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
frontend_server.run("0.0.0.0", 8080, True)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
@ -1,7 +1,8 @@
|
|||||||
FROM ubuntu:16.04
|
FROM ubuntu:16.04
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y python3.5 python3-pip \
|
RUN apt-get update
|
||||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
RUN apt-get install -y python3.5 python3-pip
|
||||||
|
RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
|
|
||||||
RUN pip3 install flask requests
|
RUN pip3 install flask requests
|
||||||
|
|
||||||
|
@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
docker stop neo4j_demo
|
docker stop neo4j_demo
|
||||||
docker rm neo4j_demo
|
docker rm neo4j_demo
|
||||||
docker run -d --name neo4j_demo -p 7474:7474 neo4j
|
docker run -d --name neo4j_demo --net=host -p 7474:7474 neo4j
|
||||||
|
0
demo/simulation/__init__.py
Normal file
0
demo/simulation/__init__.py
Normal file
20
demo/simulation/epoch_result.py
Normal file
20
demo/simulation/epoch_result.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
class SimulationEpochResult(object):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, per_query, for_all):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
self.per_query = per_query
|
||||||
|
self.for_all = for_all
|
||||||
|
|
||||||
|
def json_data(self):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
return {
|
||||||
|
"per_query": [item.json_data() for item in self.per_query],
|
||||||
|
"for_all": self.for_all
|
||||||
|
}
|
97
demo/simulation/executor.py
Normal file
97
demo/simulation/executor.py
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
import itertools
|
||||||
|
import requests
|
||||||
|
from concurrent.futures import ProcessPoolExecutor
|
||||||
|
|
||||||
|
from .epoch_result import SimulationEpochResult
|
||||||
|
from .group_result import SimulationGrupeResult
|
||||||
|
from .iteration_result import SimulationIterationResult
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_qps(results):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
max_delta_t = max([result.delta_t for result in results])
|
||||||
|
qps = sum([result.count * (max_delta_t / result.delta_t)
|
||||||
|
for result in results]) / max_delta_t
|
||||||
|
return qps
|
||||||
|
|
||||||
|
|
||||||
|
def send(query):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
requests.post('http://localhost:7474/db/data/transaction/commit',
|
||||||
|
json={
|
||||||
|
"statements": [
|
||||||
|
{"statement": query}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
headers={'Authorization': 'Basic bmVvNGo6cGFzcw=='})
|
||||||
|
|
||||||
|
|
||||||
|
class SimulationExecutor(object):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
pass
|
||||||
|
|
||||||
|
def setup(self, params):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
self.params = params
|
||||||
|
return self
|
||||||
|
|
||||||
|
def iteration(self, task):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
count = 0
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
for i in range(self.params.queries_per_period):
|
||||||
|
send(task.query)
|
||||||
|
|
||||||
|
end_time = time.time()
|
||||||
|
count = count + 1
|
||||||
|
delta_t = end_time - start_time
|
||||||
|
|
||||||
|
if delta_t > self.params.period_time:
|
||||||
|
break
|
||||||
|
|
||||||
|
return SimulationIterationResult(task.id, count, delta_t)
|
||||||
|
|
||||||
|
def epoch(self):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
log.info('epoch')
|
||||||
|
|
||||||
|
pool = ProcessPoolExecutor
|
||||||
|
with pool(max_workers=self.params.workers_number) as executor:
|
||||||
|
|
||||||
|
# execute all tasks
|
||||||
|
futures = [executor.submit(self.iteration, task)
|
||||||
|
for task in self.params.tasks
|
||||||
|
for i in range(self.params.workers_per_query)]
|
||||||
|
results = [future.result() for future in futures]
|
||||||
|
|
||||||
|
# per query calculation
|
||||||
|
grouped = itertools.groupby(results, lambda x: x.id)
|
||||||
|
per_query = [SimulationGrupeResult(id, calculate_qps(list(tasks)))
|
||||||
|
for id, tasks in grouped]
|
||||||
|
|
||||||
|
# for all calculation
|
||||||
|
for_all = calculate_qps(results)
|
||||||
|
|
||||||
|
return SimulationEpochResult(per_query, for_all)
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
return self.epoch()
|
20
demo/simulation/group_result.py
Normal file
20
demo/simulation/group_result.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
class SimulationGrupeResult(object):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, id, queries_per_period):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
self.id = id
|
||||||
|
self.queries_per_second = queries_per_period
|
||||||
|
|
||||||
|
def json_data(self):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
return {
|
||||||
|
'id': self.id,
|
||||||
|
'queries_per_second': self.queries_per_second
|
||||||
|
}
|
22
demo/simulation/iteration_result.py
Normal file
22
demo/simulation/iteration_result.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
class SimulationIterationResult(object):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, id, count, delta_t):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
self.id = id
|
||||||
|
self.count = count
|
||||||
|
self.delta_t = delta_t
|
||||||
|
self.queries_per_second = self.count / self.delta_t
|
||||||
|
|
||||||
|
def json_data(self):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
return {
|
||||||
|
"id": self.id,
|
||||||
|
"queries_per_second": self.queries_per_second
|
||||||
|
}
|
116
demo/simulation/params.py
Normal file
116
demo/simulation/params.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
class SimulationParams(object):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
self.protocol = 'http'
|
||||||
|
self.host = 'localhost'
|
||||||
|
self.port = 7474
|
||||||
|
|
||||||
|
self.workers_number = 16
|
||||||
|
self.period_time = 0.5
|
||||||
|
self.workers_per_query = 1
|
||||||
|
self.queries_per_second = 15000
|
||||||
|
self.recalculate_qps()
|
||||||
|
|
||||||
|
self.tasks = []
|
||||||
|
|
||||||
|
def json_data(self):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
return {
|
||||||
|
"protocol": self.protocol,
|
||||||
|
"host": self.host,
|
||||||
|
"port": self.port,
|
||||||
|
"workers_number": self.workers_number,
|
||||||
|
"period_time": self.period_time,
|
||||||
|
"workers_per_query": self.workers_per_query,
|
||||||
|
"queries_per_second": self.queries_per_second,
|
||||||
|
"queries_per_period": self.queries_per_period
|
||||||
|
}
|
||||||
|
|
||||||
|
# protocol
|
||||||
|
@property
|
||||||
|
def protocol(self):
|
||||||
|
return self._protocol
|
||||||
|
|
||||||
|
@protocol.setter
|
||||||
|
def protocol(self, value):
|
||||||
|
self._protocol = value
|
||||||
|
|
||||||
|
# host
|
||||||
|
@property
|
||||||
|
def host(self):
|
||||||
|
return self._host
|
||||||
|
|
||||||
|
@host.setter
|
||||||
|
def host(self, value):
|
||||||
|
self._host = value
|
||||||
|
|
||||||
|
# port
|
||||||
|
@property
|
||||||
|
def port(self):
|
||||||
|
return self._port
|
||||||
|
|
||||||
|
@port.setter
|
||||||
|
def port(self, value):
|
||||||
|
self._port = value
|
||||||
|
|
||||||
|
# workers number
|
||||||
|
@property
|
||||||
|
def workers_number(self):
|
||||||
|
return self._workers_number
|
||||||
|
|
||||||
|
@workers_number.setter
|
||||||
|
def workers_number(self, value):
|
||||||
|
self._workers_number = value
|
||||||
|
|
||||||
|
# workers per query
|
||||||
|
@property
|
||||||
|
def workers_per_query(self):
|
||||||
|
return self._workers_per_query
|
||||||
|
|
||||||
|
@workers_per_query.setter
|
||||||
|
def workers_per_query(self, value):
|
||||||
|
self._workers_per_query = value
|
||||||
|
|
||||||
|
# queries per second
|
||||||
|
@property
|
||||||
|
def queries_per_second(self):
|
||||||
|
return self._queries_per_second
|
||||||
|
|
||||||
|
@queries_per_second.setter
|
||||||
|
def queries_per_second(self, value):
|
||||||
|
self._queries_per_second = value
|
||||||
|
|
||||||
|
def recalculate_qps(self):
|
||||||
|
try:
|
||||||
|
self.queries_per_period = \
|
||||||
|
int(self.queries_per_second * self.period_time)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# queries per period
|
||||||
|
@property
|
||||||
|
def queries_per_period(self):
|
||||||
|
return self._queries_per_period
|
||||||
|
|
||||||
|
@queries_per_period.setter
|
||||||
|
def queries_per_period(self, value):
|
||||||
|
self._queries_per_period = value
|
||||||
|
self.recalculate_qps()
|
||||||
|
|
||||||
|
# period time
|
||||||
|
@property
|
||||||
|
def period_time(self):
|
||||||
|
return self._period_time
|
||||||
|
|
||||||
|
@period_time.setter
|
||||||
|
def period_time(self, value):
|
||||||
|
self._period_time = value
|
||||||
|
self.recalculate_qps()
|
12
demo/simulation/task.py
Normal file
12
demo/simulation/task.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
class SimulationTask(object):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, id, query):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
self.id = id
|
||||||
|
self.query = query
|
118
demo/simulation/web_server.py
Normal file
118
demo/simulation/web_server.py
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
from flask import Flask, request, jsonify
|
||||||
|
|
||||||
|
from .executor import SimulationExecutor
|
||||||
|
from .params import SimulationParams
|
||||||
|
from .task import SimulationTask
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class SimulationWebServer():
|
||||||
|
'''
|
||||||
|
Memgraph demo fontend server. For now it wraps the flask server.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
'''
|
||||||
|
Instantiates the flask web server.
|
||||||
|
'''
|
||||||
|
self.server = Flask(__name__)
|
||||||
|
self.is_simulation_running = False
|
||||||
|
self.simulation_stats = None
|
||||||
|
self.simulation_params = SimulationParams()
|
||||||
|
self.simulation_executor = \
|
||||||
|
SimulationExecutor().setup(self.simulation_params)
|
||||||
|
|
||||||
|
def setup_routes(self):
|
||||||
|
'''
|
||||||
|
Setup all available rutes:
|
||||||
|
/ping
|
||||||
|
'''
|
||||||
|
self.server.add_url_rule('/ping', 'ping', self.ping)
|
||||||
|
self.server.add_url_rule('/tasks', 'tasks', self.tasks,
|
||||||
|
methods=['POST'])
|
||||||
|
self.server.add_url_rule('/start', 'start', self.start,
|
||||||
|
methods=['POST'])
|
||||||
|
self.server.add_url_rule('/stop', 'stop', self.stop,
|
||||||
|
methods=['POST'])
|
||||||
|
self.server.add_url_rule('/stats', 'stats', self.stats,
|
||||||
|
methods=['GET'])
|
||||||
|
self.server.add_url_rule('/params', 'params_get', self.params_get,
|
||||||
|
methods=['GET'])
|
||||||
|
self.server.add_url_rule('/params', 'params_set', self.params_set,
|
||||||
|
methods=['POST'])
|
||||||
|
|
||||||
|
def run(self, host="127.0.0.1", port=8080, debug=False):
|
||||||
|
'''
|
||||||
|
Runs the server. Before run, routes are initialized.
|
||||||
|
'''
|
||||||
|
self.setup_routes()
|
||||||
|
self.server.run(host=host, port=port, debug=debug)
|
||||||
|
|
||||||
|
def ping(self):
|
||||||
|
'''
|
||||||
|
Ping endpoint. Returns 204 HTTP status code.
|
||||||
|
'''
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
def tasks(self):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
data = request.get_json()['data']
|
||||||
|
self.simulation_params.tasks = \
|
||||||
|
[SimulationTask(item['id'], item['query'])
|
||||||
|
for item in data]
|
||||||
|
return ('', 200)
|
||||||
|
|
||||||
|
def run_simulation(self):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
log.info('new simulation run')
|
||||||
|
|
||||||
|
while self.is_simulation_running:
|
||||||
|
self.simulation_stats = self.simulation_executor.execute()
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
self.is_simulation_running = True
|
||||||
|
t = threading.Thread(target=self.run_simulation, daemon=True)
|
||||||
|
t.start()
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
self.is_simulation_running = False
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
def stats(self):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
if not self.simulation_stats:
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
return jsonify(self.simulation_stats.json_data())
|
||||||
|
|
||||||
|
def params_get(self):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
return jsonify(self.simulation_params.json_data())
|
||||||
|
|
||||||
|
def params_set(self):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
param_names = ['protocol', 'port', 'workers_number', 'period_time',
|
||||||
|
'queries_per_second', 'workers_per_query']
|
||||||
|
|
||||||
|
for param in param_names:
|
||||||
|
if param in data:
|
||||||
|
setattr(self.simulation_params, param, data[param])
|
||||||
|
|
||||||
|
return self.params_get()
|
Loading…
Reference in New Issue
Block a user