Source code for perfsim.environment.perfsim_server
import json
import logging
from pathlib import Path
from time import perf_counter
from typing import Callable, Union
from dash import Dash, html
from flask import Flask, request, Response, make_response, jsonify
from single_source import get_version
from perfsim import SimulationScenarioManager, ResponseException
[docs]
class PerfSimServer:
"""
The PerfSimServer class is a wrapper for the Flask server that handles the simulation requests.
It is responsible for the following:
- Initializing the Flask server
- Handling the simulation requests
- Handling the errors via populating appropriate responses
"""
#: The Flask server
server: Flask
#: The Dash app
app: Dash
#: The host address
_host: str
#: The port number of the server
_port: int
#: Enable/disable the performance logging
perf_logs: bool
#: The SimulationScenarioManager object, created from the scenario/config
sm: Union[SimulationScenarioManager, None]
def __init__(self,
host: str = '0.0.0.0',
port: int = 8081,
perf_logs: bool = True,
log_filename: str = None,
log_format: str = None,
log_level: int = logging.INFO,
debug: bool = False):
"""
Initialize the server.
:param host: The host address for the server. Default is '0.0.0.0'
:param port: The port for the server. Default is 8081.
:param perf_logs: Enable/disable the logs related to the execution time of math functions. Default is True.
:param log_filename: The filename for the log file. If None, the log will be written to the console.
:param log_format: The format for the log file. If None, the default format will be used.
:param log_level: The log level based on the logging module. If None, the logging.INFO will be used.
:param debug: Enable/disable the debug mode. Default is False.
"""
self._set_connection_params(host, port)
self.perf_logs = perf_logs
self.app = Dash(name=__name__, url_base_pathname='/dash/')
self.server = self.app.server
self.app.layout = html.Div(id='dash-container')
self.app.debug = debug
logging.basicConfig(level=log_level, filename=log_filename, format=log_format)
self.configure_routes()
self.sm = None
# self.sm = SimulationScenarioManager()
[docs]
def run(self) -> None:
"""
Run the server.
:return: None
"""
self.app.run_server(host=self.host, port=self.port)
def _set_connection_params(self, host_address: str, port: int) -> None:
"""
Set the host address and port for the server.
:param host_address: The host address for the server.
:param port: The port for the server.
:return: None
"""
self._host = host_address
self._port = port
[docs]
def create_scenario_manager(self):
"""
Create a scenario manager from the configuration.
:return:
"""
config = json.loads(request.form.get("config"))
self.sm = SimulationScenarioManager.from_config(conf=config, existing_scenario_manager=self.sm)
return "ok"
def _validate_sim(self):
"""
Validate the simulation scenario manager.
:return:
"""
if self.sm is None:
return self.make_error_response(error_code=500,
error_message="No scenario created, please setup a scenario "
"first via /perfsim/api/v1/scenario/setupAll.")
[docs]
def run_scenario(self):
"""
Run the scenario.
:return:
"""
self._validate_sim()
try:
load_generator = self.sm.simulations_dict[request.args.get("id")].load_generator
except KeyError:
return self.make_error_response(error_code=500,
error_message="Scenario " + str(request.args.get("id")) + " not found.")
load_generator.execute_traffic()
result = self.sm.get_all_latencies()
return json.dumps(result)
[docs]
def save_all(self):
"""
Save all the results.
:return:
"""
self._validate_sim()
self.sm.save_all()
return "ok"
[docs]
@staticmethod
def make_error_response(error_code: int, error_message: str) -> Response:
"""
Make an error response.
:param error_code: The error code.
:param error_message: The error message.
:return: The error response.
"""
raise ResponseException(str(error_code) + ": " + error_message)
[docs]
def make_function_response(self, func: Callable, *args) -> Response:
"""
Make a response for a function call.
:param func: The function to call.
:param args: The arguments to pass to the function.
:return: The response of type Response.
"""
try:
if self.perf_logs:
start_time = perf_counter()
result = func(*args)
end_time = perf_counter()
duration = end_time - start_time
self.app.logger.info("Function call of {} took {} seconds".format(func.__name__, duration))
else:
result = func(*args)
return make_response(jsonify({'result': result}), 200)
except ValueError as e:
return make_response(jsonify({"error": str(e)}), 400)
except ResponseException as e:
return make_response(jsonify({"error": str(e)}), 400)
@property
def host(self):
"""
Get the host address.
:return:
"""
return self._host
@property
def port(self):
"""
Get the port number.
:return:
"""
return self._port
@host.setter
def host(self, host_address: str):
"""
Set the host address.
:param host_address:
:return:
"""
raise NotImplementedError("The host address cannot be changed.")
@port.setter
def port(self, port: int):
"""
Set the port number.
:param port:
:return:
"""
raise NotImplementedError("The port cannot be changed.")