From ebb5499630262de54c770292bd2b91b6aaa02231 Mon Sep 17 00:00:00 2001 From: Kailash Date: Wed, 7 Sep 2022 12:17:01 +0530 Subject: [PATCH 01/16] Version update --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f9a47bd..d937066 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name="ravpy", - version="0.6", + version="0.7", license='MIT', author="Raven Protocol", author_email='kailash@ravenprotocol.com', From d75ca312137bf4a2ccc98954efb10b127543f0ce Mon Sep 17 00:00:00 2001 From: Kailash Date: Fri, 9 Sep 2022 18:40:03 +0530 Subject: [PATCH 02/16] Code restructure, logging and docker-compose file --- .env | 2 +- Dockerfile | 13 + docker-compose.yml | 4 + ravpy/config.py | 8 +- ravpy/distributed/benchmarking.py | 74 +--- ravpy/distributed/compute.py | 709 ++++++++++++++++-------------- ravpy/distributed/evaluate.py | 32 +- ravpy/distributed/participate.py | 22 +- ravpy/ftp/__init__.py | 18 +- ravpy/globals.py | 48 +- ravpy/initialize.py | 16 +- ravpy/utils.py | 95 +++- 12 files changed, 559 insertions(+), 482 deletions(-) create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.env b/.env index b99628a..d277f0f 100644 --- a/.env +++ b/.env @@ -1,3 +1,3 @@ TOKEN= -RAVENVERSE_URL=http://0.0.0.0:9999 +RAVENVERSE_URL=http://0.0.0.0:8081 RAVENVERSE_FTP_URL=0.0.0.0 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..85ebeb8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.7.11 + +ARG DEBIAN_FRONTEND=noninteractive + +COPY . / + +RUN python -m ensurepip --upgrade +RUN python -m pip install --upgrade pip + +RUN pip install -r requirements.txt + +CMD ["run_distributed_client.py"] +ENTRYPOINT ["python"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..eff9764 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,4 @@ +version: '3.3' +services: + ravsock: + build: . diff --git a/ravpy/config.py b/ravpy/config.py index 2529b8b..b0e49ef 100644 --- a/ravpy/config.py +++ b/ravpy/config.py @@ -3,7 +3,7 @@ from pathlib import Path BASE_DIR = os.path.join(str(Path.home()), "ravenverse/ravpy") - +PROJECT_DIR = pathlib.Path(__file__).parent.parent.resolve() CONTEXT_FOLDER = os.path.join(BASE_DIR, "contexts") PARAMS_DIR = os.path.join(BASE_DIR, "params") @@ -21,4 +21,10 @@ FTP_TEMP_FILES_FOLDER = os.path.join(os.getcwd(), "ravpy/distributed/temp_files") FTP_DOWNLOAD_FILES_FOLDER = os.path.join(os.getcwd(), "ravpy/distributed/downloads") +os.makedirs(FTP_TEMP_FILES_FOLDER, exist_ok=True) +os.makedirs(FTP_DOWNLOAD_FILES_FOLDER, exist_ok=True) + RAVPY_LOG_FILE = os.path.join(pathlib.Path(__file__).parent.parent.resolve(), "debug.log") + +BENCHMARK_DOWNLOAD_PATH = os.path.join(PROJECT_DIR, "ravpy/distributed/downloads/") +TEMP_FILES_PATH = os.path.join(PROJECT_DIR, "ravpy/distributed/temp_files/") diff --git a/ravpy/distributed/benchmarking.py b/ravpy/distributed/benchmarking.py index 664bc70..5d6a928 100644 --- a/ravpy/distributed/benchmarking.py +++ b/ravpy/distributed/benchmarking.py @@ -1,73 +1,18 @@ -import ast import json import time -import speedtest -from ..strings import functions - from .compute import compute_locally_bm from .evaluate import waitInterval -from ..config import RAVENVERSE_URL, BENCHMARK_FILE_NAME, RAVENVERSE_FTP_URL -from ..ftp import check_credentials -from ..ftp import get_client as get_ftp_client +from ..config import RAVENVERSE_URL, BENCHMARK_FILE_NAME from ..globals import g -from ..utils import download_file, get_key, setTimeout, get_ftp_credentials - - -def initialize(): - credentials = get_ftp_credentials() - - if credentials is None: - g.logger.debug("Unable to fetch credentials") - return - - creds = ast.literal_eval(credentials['ftp_credentials']) - g.logger.debug("Ftp credentials: {}".format(creds)) - time.sleep(2) - - try: - if RAVENVERSE_FTP_URL != 'localhost' and RAVENVERSE_FTP_URL != '0.0.0.0': - wifi = speedtest.Speedtest() - upload_speed = int(wifi.upload()) - download_speed = int(wifi.download()) - upload_speed = upload_speed / 8 - download_speed = download_speed / 8 - if upload_speed <= 3000000: - upload_multiplier = 1 - elif upload_speed < 80000000: - upload_multiplier = int((upload_speed / 80000000) * 1000) - else: - upload_multiplier = 1000 - - if download_speed <= 3000000: - download_multiplier = 1 - elif download_speed < 80000000: - download_multiplier = int((download_speed / 80000000) * 1000) - else: - download_multiplier = 1000 - - g.ftp_upload_blocksize = 8192 * upload_multiplier - g.ftp_download_blocksize = 8192 * download_multiplier - - else: - g.ftp_upload_blocksize = 8192 * 1000 - g.ftp_download_blocksize = 8192 * 1000 - - except Exception as e: - g.ftp_upload_blocksize = 8192 * 1000 - g.ftp_download_blocksize = 8192 * 1000 - - - g.logger.debug("FTP Upload Blocksize:{} ---- FTP Download Blocksize: {}".format(g.ftp_upload_blocksize, - g.ftp_download_blocksize)) - g.ftp_client = get_ftp_client(creds['username'], creds['password']) - g.logger.debug("Check creds:{}".format(check_credentials(creds['username'], creds['password']))) - g.ftp_client.list_server_files() +from ..strings import functions +from ..utils import download_file, get_key, setTimeout def benchmark(): - g.logger.debug("benchmarking") - initialize() + g.logger.debug("") + g.logger.debug("Starting benchmarking...") + client = g.client initialTimeout = g.initialTimeout @@ -80,15 +25,14 @@ def benchmark(): benchmark_results = {} for benchmark_op in benchmark_ops: - # print("BM OP inside enumerate: ",benchmark_op) operator = get_key(benchmark_op['operator'], functions) t1 = time.time() - g.logger.debug(compute_locally_bm(*benchmark_op['values'], op_type=benchmark_op['op_type'], operator=operator)) + compute_locally_bm(*benchmark_op['values'], op_type=benchmark_op['op_type'], operator=operator) t2 = time.time() benchmark_results[benchmark_op["operator"]] = t2 - t1 - g.logger.debug("\nEmitting Benchmark Results...") + g.logger.debug("Benchmarking completed successfully, emitting results...") client.emit("benchmark_callback", data=json.dumps(benchmark_results), namespace="/client") client.sleep(1) + setTimeout(waitInterval, initialTimeout) - return benchmark_results diff --git a/ravpy/distributed/compute.py b/ravpy/distributed/compute.py index 5ad59c1..584f45d 100644 --- a/ravpy/distributed/compute.py +++ b/ravpy/distributed/compute.py @@ -1,253 +1,246 @@ -import os -import numpy as np import json -import sys +import numpy as np +import os import time from scipy import stats - -from ..globals import g -from ..utils import get_key, dump_data, load_data -from ..ftp import check_credentials as check_credentials from ..config import FTP_DOWNLOAD_FILES_FOLDER +from ..globals import g from ..strings import functions - +from ..utils import get_key, dump_data, load_data numpy_functions = { - "neg": "np.negative", - "pos": "np.positive", - "add": "np.add", - "sub": "np.subtract", - "exp": "np.exp", - "natlog": "np.log", - "square":"np.square", - "pow":"np.power", - "square_root":"np.sqrt", - "cube_root":"np.cbrt", - "abs":"np.abs", - "sum":"sum", - "sort":"np.sort", - "reverse":"np.flip", - "min":"np.min", - "max":"max", - "argmax":"np.argmax", - "argmin":"np.argmin", - "transpose":"np.transpose", - "div":"np.divide", - 'mul': 'np.multiply', - 'matmul': 'np.matmul', - 'multiply':'np.multiply', - 'dot': 'np.dot', - 'split': 'np.split', - 'reshape':'reshape', - 'unique': 'np.unique', - 'expand_dims':'expand_dims', - 'inv': 'np.linalg.inv', - 'gather': 'gather', - 'stack': 'np.stack', - 'tile': 'np.tile', - 'slice': 'ravslice', - - 'find_indices': 'find_indices', - 'shape':'shape', - 'squeeze':'np.squeeze', - 'pad':'pad', - 'index':'index', - - #Comparision ops - 'greater': 'np.greater', - 'greater_equal':'np.greater_equal' , - 'less': 'np.less', - 'less_equal':'np.less_equal' , - 'equal':'np.equal' , - 'not_equal': 'np.not_equal', - 'logical_and':'np.logical_and' , - 'logical_or': 'np.logical_or', - 'logical_not': 'np.logical_not', - 'logical_xor': 'np.logical_xor', - - #statistics - 'mean': 'mean', - 'average': 'np.average', - 'mode': 'mode', - 'variance': 'variance', - 'std': 'np.std', - 'percentile': 'np.percentile', - 'random': 'np.random', - 'bincount': 'np.bincount', - 'where': 'where', - #'sign': Operators.SIGN, - 'foreach': 'foreach', - 'set_value': 'set_value', - 'clip': 'clip', - 'random_uniform': 'np.random.uniform', - 'prod': 'np.prod', - 'flatten': 'flatten', - 'ravel': 'np.ravel', - - 'concat': 'concatenate', - 'cube': 'np.cbrt', - 'arange':'np.arange', - 'repeat':'repeat', - 'join_to_list': 'join_to_list', - 'combine_to_list': 'combine_to_list', - 'zeros':'np.zeros', - 'ravint':'ravint', - 'cnn_index':'cnn_index', - 'cnn_add_at':'cnn_add_at', - 'cnn_index_2':'cnn_index_2', - 'size': 'size', - - # Machine Learning Ops - 'linear_regression': 'linear_regression', - 'logistic_regression': 'logistic_regression', - 'knn_classifier': 'knn_classifier', - 'knn_regressor': 'knn_regressor', - 'naive_bayes': 'naive_bayes', - 'kmeans': 'kmeans', - 'svm_svc': 'svm_svc', - 'svm_svr': 'svm_svr', - 'decision_tree_classifier': 'decision_tree_classifier', - 'decision_tree_regressor': 'decision_tree_regressor', - 'random_forest_classifier': 'random_forest_classifier', - 'random_forest_regressor': 'random_forest_regressor' - } + "neg": "np.negative", + "pos": "np.positive", + "add": "np.add", + "sub": "np.subtract", + "exp": "np.exp", + "natlog": "np.log", + "square": "np.square", + "pow": "np.power", + "square_root": "np.sqrt", + "cube_root": "np.cbrt", + "abs": "np.abs", + "sum": "sum", + "sort": "np.sort", + "reverse": "np.flip", + "min": "np.min", + "max": "max", + "argmax": "np.argmax", + "argmin": "np.argmin", + "transpose": "np.transpose", + "div": "np.divide", + 'mul': 'np.multiply', + 'matmul': 'np.matmul', + 'multiply': 'np.multiply', + 'dot': 'np.dot', + 'split': 'np.split', + 'reshape': 'reshape', + 'unique': 'np.unique', + 'expand_dims': 'expand_dims', + 'inv': 'np.linalg.inv', + 'gather': 'gather', + 'stack': 'np.stack', + 'tile': 'np.tile', + 'slice': 'ravslice', + + 'find_indices': 'find_indices', + 'shape': 'shape', + 'squeeze': 'np.squeeze', + 'pad': 'pad', + 'index': 'index', + + # Comparision ops + 'greater': 'np.greater', + 'greater_equal': 'np.greater_equal', + 'less': 'np.less', + 'less_equal': 'np.less_equal', + 'equal': 'np.equal', + 'not_equal': 'np.not_equal', + 'logical_and': 'np.logical_and', + 'logical_or': 'np.logical_or', + 'logical_not': 'np.logical_not', + 'logical_xor': 'np.logical_xor', + + # statistics + 'mean': 'mean', + 'average': 'np.average', + 'mode': 'mode', + 'variance': 'variance', + 'std': 'np.std', + 'percentile': 'np.percentile', + 'random': 'np.random', + 'bincount': 'np.bincount', + 'where': 'where', + # 'sign': Operators.SIGN, + 'foreach': 'foreach', + 'set_value': 'set_value', + 'clip': 'clip', + 'random_uniform': 'np.random.uniform', + 'prod': 'np.prod', + 'flatten': 'flatten', + 'ravel': 'np.ravel', + + 'concat': 'concatenate', + 'cube': 'np.cbrt', + 'arange': 'np.arange', + 'repeat': 'repeat', + 'join_to_list': 'join_to_list', + 'combine_to_list': 'combine_to_list', + 'zeros': 'np.zeros', + 'ravint': 'ravint', + 'cnn_index': 'cnn_index', + 'cnn_add_at': 'cnn_add_at', + 'cnn_index_2': 'cnn_index_2', + 'size': 'size', + + # Machine Learning Ops + 'linear_regression': 'linear_regression', + 'logistic_regression': 'logistic_regression', + 'knn_classifier': 'knn_classifier', + 'knn_regressor': 'knn_regressor', + 'naive_bayes': 'naive_bayes', + 'kmeans': 'kmeans', + 'svm_svc': 'svm_svc', + 'svm_svr': 'svm_svr', + 'decision_tree_classifier': 'decision_tree_classifier', + 'decision_tree_regressor': 'decision_tree_regressor', + 'random_forest_classifier': 'random_forest_classifier', + 'random_forest_regressor': 'random_forest_regressor' +} def compute_locally_bm(*args, **kwargs): operator = kwargs.get("operator", None) op_type = kwargs.get("op_type", None) - param_args =kwargs.get("params",None) + param_args = kwargs.get("params", None) # print("Operator", operator,"Op Type:",op_type) if op_type == "unary": value1 = args[0] - t1=time.time() - params="" - if param_args is not None: - params=[op_param_mapping[operator][str(_)] for _ in param_args ] + t1 = time.time() + params = "" + # if param_args is not None: + # params=[op_param_mapping[operator][str(_)] for _ in param_args ] # print(params) - expression="{}({})".format(numpy_functions[operator], value1['value']) + expression = "{}({})".format(numpy_functions[operator], value1['value']) eval(expression) - t2=time.time() - return t2-t1 + t2 = time.time() + return t2 - t1 elif op_type == "binary": value1 = args[0]['value'] value2 = args[1]['value'] - t1=time.time() + t1 = time.time() eval("{}({},{})".format(numpy_functions[operator], value1, value2)) - t2=time.time() - return t2-t1 + t2 = time.time() + return t2 - t1 -# async -def compute_locally(payload, subgraph_id, graph_id): - try: - # print("Computing ",payload["operator"]) - # print('\n\nPAYLOAD: ',payload) - values = [] - - - for i in range(len(payload["values"])): - if "value" in payload["values"][i].keys(): - # print("From server") - if "path" not in payload["values"][i].keys(): - values.append(payload["values"][i]["value"]) - - else: - server_file_path = payload["values"][i]["path"] - - download_path = os.path.join(FTP_DOWNLOAD_FILES_FOLDER,os.path.basename(payload["values"][i]["path"])) +# async +def compute_locally(payload, subgraph_id, graph_id): + # try: + # print("Computing ",payload["operator"]) + # print('\n\nPAYLOAD: ',payload) - # try: - g.ftp_client.download(download_path, os.path.basename(server_file_path)) - value = load_data(download_path).tolist() - # print('Loaded Data Value: ',value) - values.append(value) + values = [] - # except Exception as error: - # print('Error: ', error) - # emit_error(payload, error, subgraph_id, graph_id) + for i in range(len(payload["values"])): + if "value" in payload["values"][i].keys(): + # print("From server") + if "path" not in payload["values"][i].keys(): + values.append(payload["values"][i]["value"]) - if os.path.basename(server_file_path) not in g.delete_files_list and payload["values"][i]["to_delete"] == 'True': - g.delete_files_list.append(os.path.basename(server_file_path)) + else: + server_file_path = payload["values"][i]["path"] - if os.path.exists(download_path): - os.remove(download_path) + download_path = os.path.join(FTP_DOWNLOAD_FILES_FOLDER, os.path.basename(payload["values"][i]["path"])) - elif "op_id" in payload["values"][i].keys(): - # print("From client") # try: - values.append(g.outputs[payload['values'][i]['op_id']]) - # except Exception as e: - # emit_error(payload,e, subgraph_id, graph_id) - - payload["values"] = values - - # print("Payload Values: ", payload["values"]) - - op_type = payload["op_type"] - operator = payload["operator"] - params=payload['params'] - param_string="" - for i in params.keys(): - if type(params[i]) == str: - param_string+=","+i+"=\'"+str(params[i])+"\'" - elif type(params[i]) == dict: - op_id = params[i]["op_id"] - param_value = g.outputs[op_id] - if type(param_value) == str: - param_string+=","+i+"=\'"+str(param_value)+"\'" - else: - param_string+=","+i+"="+str(param_value) + g.ftp_client.download(download_path, os.path.basename(server_file_path)) + value = load_data(download_path).tolist() + # print('Loaded Data Value: ',value) + values.append(value) + + # except Exception as error: + # print('Error: ', error) + # emit_error(payload, error, subgraph_id, graph_id) + + if os.path.basename(server_file_path) not in g.delete_files_list and payload["values"][i][ + "to_delete"] == 'True': + g.delete_files_list.append(os.path.basename(server_file_path)) + + if os.path.exists(download_path): + os.remove(download_path) + + elif "op_id" in payload["values"][i].keys(): + # print("From client") + # try: + values.append(g.outputs[payload['values'][i]['op_id']]) + # except Exception as e: + # emit_error(payload,e, subgraph_id, graph_id) + + payload["values"] = values + + # print("Payload Values: ", payload["values"]) + + op_type = payload["op_type"] + operator = payload["operator"] + params = payload['params'] + param_string = "" + for i in params.keys(): + if type(params[i]) == str: + param_string += "," + i + "=\'" + str(params[i]) + "\'" + elif type(params[i]) == dict: + op_id = params[i]["op_id"] + param_value = g.outputs[op_id] + if type(param_value) == str: + param_string += "," + i + "=\'" + str(param_value) + "\'" else: - param_string+=","+i+"="+str(params[i]) - - - # try: - if op_type == "unary": - value1 = payload["values"][0] - short_name = get_key(operator,functions) - result = eval("{}({}{})".format(numpy_functions[short_name], value1,param_string)) - - elif op_type == "binary": - value1 = payload["values"][0] - value2 = payload["values"][1] - short_name = get_key(operator,functions) - expression="{}({}, {}{})".format(numpy_functions[short_name], value1, value2,param_string) - + param_string += "," + i + "=" + str(param_value) + else: + param_string += "," + i + "=" + str(params[i]) + # try: + if op_type == "unary": + value1 = payload["values"][0] + short_name = get_key(operator, functions) + result = eval("{}({}{})".format(numpy_functions[short_name], value1, param_string)) - result = eval(expression) + elif op_type == "binary": + value1 = payload["values"][0] + value2 = payload["values"][1] + short_name = get_key(operator, functions) + expression = "{}({}, {}{})".format(numpy_functions[short_name], value1, value2, param_string) - if 'sklearn' in str(type(result)): - file_path = upload_result(payload, result) + result = eval(expression) - return json.dumps({ - 'op_type': payload["op_type"], - 'file_name': os.path.basename(file_path), - # 'username': g.cid, - # 'token': g.ravenverse_token, - 'operator': payload["operator"], - "op_id": payload["op_id"], - "status": "success" - }) + if 'sklearn' in str(type(result)): + file_path = upload_result(payload, result) + return json.dumps({ + 'op_type': payload["op_type"], + 'file_name': os.path.basename(file_path), + # 'username': g.cid, + # 'token': g.ravenverse_token, + 'operator': payload["operator"], + "op_id": payload["op_id"], + "status": "success" + }) - if not isinstance(result, np.ndarray): - result = np.array(result) + if not isinstance(result, np.ndarray): + result = np.array(result) - result_byte_size = result.size * result.itemsize + result_byte_size = result.size * result.itemsize - if result_byte_size < (30 * 1000000)//10000: - try: - result = result.tolist() - except: - result = result + if result_byte_size < (30 * 1000000) // 10000: + try: + result = result.tolist() + except: + result = result - g.outputs[payload["op_id"]] = result + g.outputs[payload["op_id"]] = result - return json.dumps({ + return json.dumps({ 'op_type': payload["op_type"], 'result': result, # 'username': g.cid, @@ -255,36 +248,36 @@ def compute_locally(payload, subgraph_id, graph_id): 'operator': payload["operator"], "op_id": payload["op_id"], "status": "success" - }) + }) - else: - - file_path = upload_result(payload, result) + else: - g.outputs[payload["op_id"]] = result.tolist() + file_path = upload_result(payload, result) - # op = g.ops[payload["op_id"]] - # op["status"] = "success" - # op["endTime"] = int(time.time() * 1000) - # g.ops[payload["op_id"]] = op + g.outputs[payload["op_id"]] = result.tolist() - return json.dumps({ - 'op_type': payload["op_type"], - 'file_name': os.path.basename(file_path), - # 'username': g.cid, - # 'token': g.ravenverse_token, - 'operator': payload["operator"], - "op_id": payload["op_id"], - "status": "success" - }) + # op = g.ops[payload["op_id"]] + # op["status"] = "success" + # op["endTime"] = int(time.time() * 1000) + # g.ops[payload["op_id"]] = op - except Exception as error: - print('Error: ', error) - if 'broken pipe' in str(error).lower() or '421' in str(error).lower(): - print('\n\nYou have encountered an IO based Broken Pipe Error. \nRestart terminal and try connecting again') - sys.exit() + return json.dumps({ + 'op_type': payload["op_type"], + 'file_name': os.path.basename(file_path), + # 'username': g.cid, + # 'token': g.ravenverse_token, + 'operator': payload["operator"], + "op_id": payload["op_id"], + "status": "success" + }) - emit_error(payload, error, subgraph_id, graph_id) + # except Exception as error: + # print('Error: ', error) + # if 'broken pipe' in str(error).lower() or '421' in str(error).lower(): + # print('\n\nYou have encountered an IO based Broken Pipe Error. \nRestart terminal and try connecting again') + # sys.exit() + # + # emit_error(payload, error, subgraph_id, graph_id) def upload_result(payload, result): @@ -292,35 +285,35 @@ def upload_result(payload, result): try: result = result.tolist() except: - result=result - + result = result + # print("Emit Success") - file_path = dump_data(payload['op_id'],result) + file_path = dump_data(payload['op_id'], result) g.ftp_client.upload(file_path, os.path.basename(file_path)) - - print("\nFile uploaded!", file_path)#, ' Size: ', result_size) + + # g.logger.debug("\nFile uploaded!:{", file_path) # , ' Size: ', result_size) os.remove(file_path) - + return file_path - + def emit_error(payload, error, subgraph_id, graph_id): print("Emit Error") # print(payload) # print(error) g.error = True - error=str(error) + error = str(error) client = g.client - print(error,payload) + print(error, payload) client.emit("op_completed", json.dumps({ - 'op_type': payload["op_type"], - 'error': error, - 'operator': payload["operator"], - "op_id": payload["op_id"], - "status": "failure", - "subgraph_id": subgraph_id, - "graph_id": graph_id + 'op_type': payload["op_type"], + 'error': error, + 'operator': payload["operator"], + "op_id": payload["op_id"], + "status": "failure", + "subgraph_id": subgraph_id, + "graph_id": graph_id }), namespace="/client") # op = g.ops[payload["op_id"]] @@ -342,118 +335,127 @@ def emit_error(payload, error, subgraph_id, graph_id): g.has_subgraph = False -#ops: -def ravslice(tensor,begin=None,size=None): - result=tensor[begin:begin+size] +# ops: +def ravslice(tensor, begin=None, size=None): + result = tensor[begin:begin + size] return result -def gather(tensor,indices): - result=[] + +def gather(tensor, indices): + result = [] for i in indices: result.append(tensor[i]) return result - -def where(a,b,condition=None): + + +def where(a, b, condition=None): if condition is None: raise Exception("condition is missing") else: - result=np.where(condition,a,b) + result = np.where(condition, a, b) return result -def clip(a,lower_limit=None,upper_limit=None): + +def clip(a, lower_limit=None, upper_limit=None): if lower_limit is None: raise Exception("lower limit is missing") elif upper_limit is None: raise Exception("upper limit is missing") else: - result = np.clip(a,lower_limit,upper_limit) + result = np.clip(a, lower_limit, upper_limit) return result -def max(a,axis=None,keepdims=False): + +def max(a, axis=None, keepdims=False): if str(keepdims) == 'True': keepdims = True else: keepdims = False - if isinstance(axis,list): - axis= tuple(axis) - result=np.max(a,axis=axis,keepdims=keepdims) + if isinstance(axis, list): + axis = tuple(axis) + result = np.max(a, axis=axis, keepdims=keepdims) return result -def mean(a,axis=None,keepdims=False): + +def mean(a, axis=None, keepdims=False): if str(keepdims) == 'True': keepdims = True else: keepdims = False - if isinstance(axis,list): - axis= tuple(axis) - result=np.mean(a,axis=axis,keepdims=keepdims) + if isinstance(axis, list): + axis = tuple(axis) + result = np.mean(a, axis=axis, keepdims=keepdims) return result -def variance(a,axis=None,keepdims=False): + +def variance(a, axis=None, keepdims=False): if str(keepdims) == 'True': keepdims = True else: keepdims = False - if isinstance(axis,list): - axis= tuple(axis) - result=np.var(a,axis=axis,keepdims=keepdims) + if isinstance(axis, list): + axis = tuple(axis) + result = np.var(a, axis=axis, keepdims=keepdims) return result -def sum(a,axis=None,keepdims=False): + +def sum(a, axis=None, keepdims=False): if str(keepdims) == 'True': keepdims = True else: keepdims = False - if isinstance(axis,list): - axis= tuple(axis) - result=np.sum(a,axis=axis,keepdims=keepdims) + if isinstance(axis, list): + axis = tuple(axis) + result = np.sum(a, axis=axis, keepdims=keepdims) return result + def flatten(a): a = np.array(a) return a.flatten() -def split(arr,numOrSizeSplits=None,axis=None): - result=np.split(arr,numOrSizeSplits,axis=axis) + +def split(arr, numOrSizeSplits=None, axis=None): + result = np.split(arr, numOrSizeSplits, axis=axis) return result -def expand_dims(arr,**kwargs): - axis=kwargs.get('axis') + +def expand_dims(arr, **kwargs): + axis = kwargs.get('axis') if axis is not None: - result=np.expand_dims(arr,axis=axis) + result = np.expand_dims(arr, axis=axis) else: - result= np.expand_dims(arr,axis=0) + result = np.expand_dims(arr, axis=0) return result - -def tile(arr,reps): +def tile(arr, reps): + print("Function:tile") if reps is not None: - result=np.tile(arr,reps) + result = np.tile(arr, reps) else: emit_error() return result - -def one_hot_encoding(arr,depth): +def one_hot_encoding(arr, depth): return np.squeeze(np.eye(depth)[arr.reshape(-1)]) -def foreach(val=None,**kwargs): - operator=kwargs.get("operation") - result=[] - paramstr="" +def foreach(val=None, **kwargs): + operator = kwargs.get("operation") + result = [] + paramstr = "" del kwargs['operation'] print(kwargs) for _ in kwargs.keys(): - paramstr+=","+_+"="+str(kwargs.get(_)) + paramstr += "," + _ + "=" + str(kwargs.get(_)) for i in val: - evalexp="{}({}{})".format(numpy_functions[operator],i,paramstr) - print("\n\nevaluating:",evalexp) - res=eval(evalexp) + evalexp = "{}({}{})".format(numpy_functions[operator], i, paramstr) + print("\n\nevaluating:", evalexp) + res = eval(evalexp) if type(res) is np.ndarray: result.append(res.tolist()) else: @@ -461,9 +463,9 @@ def foreach(val=None,**kwargs): return result -def find_indices(arr,val): - result=[] - +def find_indices(arr, val): + result = [] + for i in val: indices = [_ for _, arr in enumerate(arr) if arr == i] result.append(indices) @@ -472,91 +474,104 @@ def find_indices(arr,val): else: return result -def reshape(tens,shape=None): + +def reshape(tens, shape=None): if shape is None: return None else: - return np.reshape(tens,newshape=shape) + return np.reshape(tens, newshape=shape) + -def mode(arr,axis=0): - result=stats.mode(arr,axis=axis) +def mode(arr, axis=0): + result = stats.mode(arr, axis=axis) return result.mode -def concatenate(*args,**kwargs): - param_string="" + +def concatenate(*args, **kwargs): + param_string = "" for i in kwargs.keys(): if type(params[i]) == str: - param_string+=","+i+"=\'"+str(params[i])+"\'" + param_string += "," + i + "=\'" + str(params[i]) + "\'" else: - param_string+=","+i+"="+str(params[i]) - result=eval("np.concatenate(args"+param_string+")") + param_string += "," + i + "=" + str(params[i]) + result = eval("np.concatenate(args" + param_string + ")") return result -def shape(arr,index=None): + +def shape(arr, index=None): arr = np.array(arr) if index is None: return arr.shape else: return arr.shape[int(index)] -def pad(arr,sequence=None,mode=None): + +def pad(arr, sequence=None, mode=None): if sequence is None: raise Exception("sequence param is missing") elif mode is None: raise Exception("mode is missing") arr = np.array(arr) - result = np.pad(arr,sequence,mode=mode) + result = np.pad(arr, sequence, mode=mode) return result -def repeat(arr,repeats=None, axis=None): + +def repeat(arr, repeats=None, axis=None): if repeats is None: raise Exception("repeats param is missing") arr = np.array(arr) - result = np.repeat(arr,repeats=repeats,axis=axis) + result = np.repeat(arr, repeats=repeats, axis=axis) return result -def index(arr,indices=None): + +def index(arr, indices=None): if indices is None: raise Exception("indices param is missing") if isinstance(indices, str): # arr = np.array(arr) - result = eval("np.array(arr)"+indices) + result = eval("np.array(arr)" + indices) else: result = eval("np.array(arr)[{}]".format(tuple(indices))) return result -def join_to_list(a,b): + +def join_to_list(a, b): a = np.array(a) - result = np.append(a,b) + result = np.append(a, b) return result -def combine_to_list(a,b): - result = np.array([a,b]) + +def combine_to_list(a, b): + result = np.array([a, b]) return result + def ravint(a): return int(a) -def cnn_index(arr,index1=None,index2=None,index3=None): + +def cnn_index(arr, index1=None, index2=None, index3=None): if index1 is None or index2 is None or index3 is None: raise Exception("index1, index2 or index3 param is missing") - - result = eval("np.array(arr)"+"[:,{},{},{}]".format(index1,index2,index3)) + + result = eval("np.array(arr)" + "[:,{},{},{}]".format(index1, index2, index3)) return result + def cnn_index_2(a, pad_h=None, height=None, pad_w=None, width=None): if pad_h is None or height is None or pad_w is None or width is None: raise Exception("index1, index2 or index3 param is missing") a = np.array(a) - result = a[:, :, pad_h:height+pad_h, pad_w:width+pad_w] + result = a[:, :, pad_h:height + pad_h, pad_w:width + pad_w] return result -def cnn_add_at(a, b, index1=None,index2=None,index3=None): + +def cnn_add_at(a, b, index1=None, index2=None, index3=None): if index1 is None or index2 is None or index3 is None: raise Exception("index1, index2 or index3 param is missing") - + a = np.array(a) b = np.array(b) index1 = np.array(index1) @@ -566,83 +581,99 @@ def cnn_add_at(a, b, index1=None,index2=None,index3=None): np.add.at(a, (slice(None), index1, index2, index3), b) return a -def set_value(a,b,indices): + +def set_value(a, b, indices): if indices is None: raise Exception("indices param is missing") if isinstance(indices, str): - exec("a"+indices+'='+'b') + exec("a" + indices + '=' + 'b') else: print("\n\n Indices in set value: ", indices) a = np.array(a) a[tuple(indices)] = b return a + def size(a): a = np.array(a) return a.size -def linear_regression(x,y): + +def linear_regression(x, y): from sklearn.linear_model import LinearRegression - model = LinearRegression().fit(x, y) + model = LinearRegression().fit(x, y) return model -def knn_classifier(x,y,k=None): + +def knn_classifier(x, y, k=None): if k is None: raise Exception("k param is missing") from sklearn.neighbors import KNeighborsClassifier - model = KNeighborsClassifier(n_neighbors=k).fit(x, y) + model = KNeighborsClassifier(n_neighbors=k).fit(x, y) return model -def knn_regressor(x,y,k=None): + +def knn_regressor(x, y, k=None): if k is None: raise Exception("k param is missing") from sklearn.neighbors import KNeighborsRegressor - model = KNeighborsRegressor(n_neighbors=k).fit(x, y) + model = KNeighborsRegressor(n_neighbors=k).fit(x, y) return model + def logistic_regression(x, y, random_state=0): from sklearn.linear_model import LogisticRegression - model = LogisticRegression(random_state=random_state).fit(x, y) + model = LogisticRegression(random_state=random_state).fit(x, y) return model + def naive_bayes(x, y): from sklearn.naive_bayes import GaussianNB - model = GaussianNB().fit(x, y) + model = GaussianNB().fit(x, y) return model + def kmeans(x, n_clusters=None): if n_clusters is None: raise Exception("n_clusters param is missing") from sklearn.cluster import KMeans - model = KMeans(n_clusters=n_clusters).fit(x) + model = KMeans(n_clusters=n_clusters).fit(x) return model + def svm_svc(x, y, kernel='linear'): from sklearn.svm import SVC - model = SVC(kernel=kernel).fit(x, y) + model = SVC(kernel=kernel).fit(x, y) return model + def svm_svr(x, y, kernel='linear'): from sklearn.svm import SVR - model = SVR(kernel=kernel).fit(x, y) + model = SVR(kernel=kernel).fit(x, y) return model + def decision_tree_classifier(x, y): from sklearn.tree import DecisionTreeClassifier - model = DecisionTreeClassifier().fit(x, y) + model = DecisionTreeClassifier().fit(x, y) return model + def decision_tree_regressor(x, y): from sklearn.tree import DecisionTreeRegressor - model = DecisionTreeRegressor().fit(x, y) + model = DecisionTreeRegressor().fit(x, y) return model + def random_forest_classifier(x, y, n_estimators=100, criterion='gini', max_depth=None, min_samples_split=2): from sklearn.ensemble import RandomForestClassifier - model = RandomForestClassifier(n_estimators=n_estimators, criterion=criterion, max_depth=max_depth, min_samples_split=min_samples_split).fit(x, y) + model = RandomForestClassifier(n_estimators=n_estimators, criterion=criterion, max_depth=max_depth, + min_samples_split=min_samples_split).fit(x, y) return model + def random_forest_regressor(x, y, n_estimators=100, criterion='squared_error', max_depth=None, min_samples_split=2): from sklearn.ensemble import RandomForestRegressor - model = RandomForestRegressor(n_estimators=n_estimators, criterion=criterion, max_depth=max_depth, min_samples_split=min_samples_split).fit(x, y) + model = RandomForestRegressor(n_estimators=n_estimators, criterion=criterion, max_depth=max_depth, + min_samples_split=min_samples_split).fit(x, y) return model diff --git a/ravpy/distributed/evaluate.py b/ravpy/distributed/evaluate.py index 45821cf..33d25c8 100644 --- a/ravpy/distributed/evaluate.py +++ b/ravpy/distributed/evaluate.py @@ -1,21 +1,20 @@ -import time import json -import os -from .compute import compute_locally, emit_error - -from ..utils import setTimeout, stopTimer +from .compute import compute_locally from ..globals import g +from ..utils import setTimeout, stopTimer timeoutId = g.timeoutId opTimeout = g.opTimeout initialTimeout = g.initialTimeout client = g.client + @g.client.on('subgraph', namespace="/client") def compute_subgraph(d): global client, timeoutId - print("Received Subgraph : ",d["subgraph_id"]," of Graph : ",d["graph_id"]) + g.logger.debug("Received subgraph: {} of graph: {}".format(d["subgraph_id"], d["graph_id"])) + g.logger.debug("Computing subgraphs(ops)...") g.has_subgraph = True subgraph_id = d["subgraph_id"] graph_id = d["graph_id"] @@ -25,8 +24,7 @@ def compute_subgraph(d): g.error = False for index in data: - - #Perform + # Perform operation_type = index["op_type"] operator = index["operator"] if operation_type is not None and operator is not None: @@ -40,15 +38,15 @@ def compute_subgraph(d): # stopTimer(timeoutId) # timeoutId = setTimeout(waitInterval,opTimeout) if not g.error: - emit_result_data = {"subgraph_id": d["subgraph_id"],"graph_id":d["graph_id"],"token": g.ravenverse_token, "results":results} + emit_result_data = {"subgraph_id": d["subgraph_id"], "graph_id": d["graph_id"], "token": g.ravenverse_token, + "results": results} client.emit("subgraph_completed", json.dumps(emit_result_data), namespace="/client") - print('Emitted subgraph_completed') + g.logger.debug('Subgraph results emitted successfully') g.has_subgraph = False - - stopTimer(timeoutId) - timeoutId = setTimeout(waitInterval,opTimeout) + stopTimer(timeoutId) + timeoutId = setTimeout(waitInterval, opTimeout) for ftp_file in g.delete_files_list: g.ftp_client.delete_file(ftp_file) @@ -57,6 +55,9 @@ def compute_subgraph(d): g.outputs = {} # g.ops = {} + g.logger.debug("") + g.logger.debug("Waiting for more ops and subgraphs...") + # # Check if the client is connected # @g.client.on('check_status', namespace="/client") @@ -71,6 +72,7 @@ def ping(d): # g.logger.debug("\n\n\nPing: {}".format(d)) client.emit('pong', d, namespace='/client') + def waitInterval(): # g.logger.debug("waitInterval") global client, timeoutId, opTimeout, initialTimeout @@ -81,8 +83,8 @@ def waitInterval(): if g.client.connected: if not g.has_subgraph: client.emit("get_op", json.dumps({ - "message": "Send me an aop" + "message": "Send me an aop" }), namespace="/client") stopTimer(timeoutId) - timeoutId = setTimeout(waitInterval, opTimeout) \ No newline at end of file + timeoutId = setTimeout(waitInterval, opTimeout) diff --git a/ravpy/distributed/participate.py b/ravpy/distributed/participate.py index b46ad44..2a15bf4 100644 --- a/ravpy/distributed/participate.py +++ b/ravpy/distributed/participate.py @@ -1,15 +1,19 @@ import os +from ..globals import g +from ..utils import initialize_ftp_client + def participate(): - # Connect - download_path = "./ravpy/distributed/downloads/" - temp_files_path = "./ravpy/distributed/temp_files/" - if not os.path.exists(download_path): - os.makedirs(download_path) - if not os.path.exists(temp_files_path): - os.makedirs(temp_files_path) + # Initialize and create FTP client + res = initialize_ftp_client() + if res is None: + os._exit(1) from .benchmarking import benchmark - bm_results = benchmark() - print("Benchmark Results: ", bm_results) + benchmark() + + g.logger.debug("") + g.logger.debug("Ravpy is waiting for ops and subgraphs...") + g.logger.debug("Warning: Do not close this terminal if you like to " + "keep participating and keep earning Raven tokens\n") diff --git a/ravpy/ftp/__init__.py b/ravpy/ftp/__init__.py index 5f5e276..d05f772 100644 --- a/ravpy/ftp/__init__.py +++ b/ravpy/ftp/__init__.py @@ -1,3 +1,5 @@ +import os + from ftplib import FTP from ..config import RAVENVERSE_FTP_URL @@ -30,8 +32,20 @@ def close(self): def get_client(username, password): - print("FTP User credentials:", RAVENVERSE_FTP_URL, username, password) - return FTPClient(host=RAVENVERSE_FTP_URL, user=username, passwd=password) + """ + Create FTP client + :param username: FTP username + :param password: FTP password + :return: FTP client + """ + try: + g.logger.debug("Creating FTP client...") + client = FTPClient(host=RAVENVERSE_FTP_URL, user=username, passwd=password) + g.logger.debug("FTP client created successfully") + return client + except Exception as e: + g.logger.debug("Unable to create FTP client") + os._exit(1) def check_credentials(username, password): diff --git a/ravpy/globals.py b/ravpy/globals.py index ecc6b7b..ad7208c 100644 --- a/ravpy/globals.py +++ b/ravpy/globals.py @@ -1,41 +1,41 @@ -import socketio import os -from .config import RAVENVERSE_URL, TYPE +import socketio + +from .config import RAVENVERSE_URL, TYPE, RAVENVERSE_FTP_URL from .logger import get_logger from .singleton_utils import Singleton def get_client(ravenverse_token): - g.logger.debug("get_client") + """ + Connect to Ravebverse and return socket client instance + :param ravenverse_token: authentication token + :return: socket client + """ + g.logger.debug("Connecting to Ravenverse...") auth_headers = {"token": ravenverse_token} client = socketio.Client(logger=False, request_timeout=100, engineio_logger=False) @client.on('error', namespace='/client') def check_error(d): - g.logger.debug("\n======= Error: {} =======".format(d)) + g.logger.error("Connection error:a{}".format(d)) client.disconnect() os._exit(1) - class MyCustomNamespace(socketio.ClientNamespace): - def on_connect(self): - g.logger.debug("on_connect") - pass - - def on_disconnect(self): - g.logger.debug("on_disconnect") - - client.register_namespace(MyCustomNamespace('/client')) - - # try: - g.logger.debug("{}?type={}".format(RAVENVERSE_URL, TYPE)) - client.connect(url="{}?type={}".format(RAVENVERSE_URL, TYPE), - auth=auth_headers, - transports=['websocket'], - namespaces=['/client'], wait_timeout=100) - return client - # except Exception as e: - # print("Exception:{}, Unable to connect to ravsock. Make sure you are using the right hostname and port".format(e)) - # exit() + try: + g.logger.debug("Ravenverse url: {}?type={}".format(RAVENVERSE_URL, TYPE)) + g.logger.debug("Ravenverse FTP host: {}".format(RAVENVERSE_FTP_URL)) + client.connect(url="{}?type={}".format(RAVENVERSE_URL, TYPE), + auth=auth_headers, + transports=['websocket'], + namespaces=['/client'], wait_timeout=100) + g.logger.debug("Successfully connected to Ravenverse") + return client + except Exception as e: + g.logger.error("Error: Unable to connect to Ravenverse. " + "Make sure you are using the right hostname and port. \n{}".format(e)) + client.disconnect() + os._exit(1) @Singleton diff --git a/ravpy/initialize.py b/ravpy/initialize.py index 8d178af..38de157 100644 --- a/ravpy/initialize.py +++ b/ravpy/initialize.py @@ -1,29 +1,29 @@ import atexit +import os from .globals import g def exit_handler(): - g.logger.debug('My application is ending!') + g.logger.debug('Ravpy is exiting!') if g.client is not None: g.logger.debug("disconnecting") if g.client.connected: g.client.emit("disconnect", namespace="/client") - else: - g.logger.debug("client is none") atexit.register(exit_handler) def initialize(ravenverse_token): - + g.logger.debug("Initializing...") g.ravenverse_token = ravenverse_token - '''Add Token Authorization code here.''' client = g.client if client is None: g.client.disconnect() - raise Exception("Unable to connect to ravsock. Make sure you are using the right hostname and port") - - return client + g.logger.error("Unable to connect to ravsock. Make sure you are using the right hostname and port") + os._exit(1) + else: + g.logger.debug("Initializied successfully\n") + return client diff --git a/ravpy/utils.py b/ravpy/utils.py index b32462a..7ccd9b5 100644 --- a/ravpy/utils.py +++ b/ravpy/utils.py @@ -1,31 +1,30 @@ +import ast +import numpy as np import os import pickle as pkl -import shutil - -import numpy as np import requests - +import shutil +import speedtest +import time from terminaltables import AsciiTable -from .config import ENCRYPTION +from .config import ENCRYPTION, RAVENVERSE_FTP_URL, BASE_DIR, CONTEXT_FOLDER, RAVENVERSE_URL, FTP_TEMP_FILES_FOLDER if ENCRYPTION: import tenseal as ts -from .config import BASE_DIR, CONTEXT_FOLDER, RAVENVERSE_URL, FTP_TEMP_FILES_FOLDER - from threading import Timer - +from .ftp import get_client as get_ftp_client from .globals import g def download_file(url, file_name): - g.logger.debug("download_file:{}".format(url)) + g.logger.debug("Downloading benchmark data") headers = {"token": g.ravenverse_token} with requests.get(url, stream=True, headers=headers) as r: with open(file_name, 'wb') as f: shutil.copyfileobj(r.raw, f) - g.logger.debug("file downloaded") + g.logger.debug("Benchmark data downloaded") def get_key(val, dict): @@ -67,14 +66,20 @@ def fetch_and_load_context(client, context_filename): def get_ftp_credentials(): - # Get - g.logger.debug("Fetching credentials:{}".format(RAVENVERSE_URL)) + """ + Fetch ftp credentials + :return: json response + """ + g.logger.debug("Fetching ftp credentials...") headers = {"token": g.ravenverse_token} r = requests.get(url="{}/client/ftp_credentials/".format(RAVENVERSE_URL), headers=headers) - g.logger.debug("Response:{}".format(r.text)) if r.status_code == 200: + g.logger.debug("Credentials fetched successfully") return r.json() - return None + else: + g.logger.debug("Unable to fetch ftp credentials. Try again after some time or " + "contact our team at team@ravenprotocol.com") + return None def get_graph(graph_id): @@ -100,7 +105,7 @@ def get_federated_graph(graph_id): def list_graphs(approach=None): # Get graphs headers = {"token": g.ravenverse_token} - r = requests.get(url="{}/graph/get/all/?approach={}".format(RAVENVERSE_URL,approach), headers=headers) + r = requests.get(url="{}/graph/get/all/?approach={}".format(RAVENVERSE_URL, approach), headers=headers) if r.status_code != 200: return None @@ -120,9 +125,9 @@ def print_graphs(graphs): g.logger.debug("\nGraphs") for graph in graphs: g.logger.debug("\nGraph id:{}\n" - "Name:{}\n" - "Approach:{}\n" - "Rules:{}".format(graph['id'], graph['name'], graph['approach'], graph['rules'])) + "Name:{}\n" + "Approach:{}\n" + "Rules:{}".format(graph['id'], graph['name'], graph['approach'], graph['rules'])) def get_subgraph_ops(graph_id): @@ -193,3 +198,57 @@ def load_data(path): with open(path, 'rb') as f: data = pkl.load(f) return np.array(data) + + +def initialize_ftp_client(): + credentials = get_ftp_credentials() + + if credentials is None: + return None + + creds = ast.literal_eval(credentials['ftp_credentials']) + time.sleep(2) + + try: + g.logger.debug("") + g.logger.debug("Testing network speed...") + if RAVENVERSE_FTP_URL != 'localhost' and RAVENVERSE_FTP_URL != '0.0.0.0': + wifi = speedtest.Speedtest() + upload_speed = int(wifi.upload()) + download_speed = int(wifi.download()) + upload_speed = upload_speed / 8 + download_speed = download_speed / 8 + if upload_speed <= 3000000: + upload_multiplier = 1 + elif upload_speed < 80000000: + upload_multiplier = int((upload_speed / 80000000) * 1000) + else: + upload_multiplier = 1000 + + if download_speed <= 3000000: + download_multiplier = 1 + elif download_speed < 80000000: + download_multiplier = int((download_speed / 80000000) * 1000) + else: + download_multiplier = 1000 + + g.ftp_upload_blocksize = 8192 * upload_multiplier + g.ftp_download_blocksize = 8192 * download_multiplier + + else: + g.ftp_upload_blocksize = 8192 * 1000 + g.ftp_download_blocksize = 8192 * 1000 + + except Exception as e: + g.ftp_upload_blocksize = 8192 * 1000 + g.ftp_download_blocksize = 8192 * 1000 + + g.logger.debug("FTP Upload Blocksize:{}".format(g.ftp_upload_blocksize)) + g.logger.debug("FTP Download Blocksize: {}\n".format(g.ftp_download_blocksize)) + + """ + Create ftp client + """ + g.ftp_client = get_ftp_client(creds['username'], creds['password']) + + return g.ftp_client From 6ceba53834019714caf94ed16688cb7b520a01c1 Mon Sep 17 00:00:00 2001 From: Kailash Date: Mon, 12 Sep 2022 12:52:27 +0530 Subject: [PATCH 03/16] Version upgrade --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d937066..d7aed04 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name="ravpy", - version="0.7", + version="0.8", license='MIT', author="Raven Protocol", author_email='kailash@ravenprotocol.com', From 6c5daa54ef89f656fa8a78b0ebe2b2847b1b61c9 Mon Sep 17 00:00:00 2001 From: Kailash Date: Thu, 15 Sep 2022 18:10:07 +0530 Subject: [PATCH 04/16] Pyinstalled and Pkinter sample ui added --- requirements.txt | 3 ++- setup.py | 3 ++- ui.py | 58 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 ui.py diff --git a/requirements.txt b/requirements.txt index 751ccf2..611fc4a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,5 @@ python-dotenv scipy speedtest-cli terminaltables==3.1.10 -websocket-client \ No newline at end of file +websocket-client +pyinstaller diff --git a/setup.py b/setup.py index d7aed04..945efad 100644 --- a/setup.py +++ b/setup.py @@ -27,6 +27,7 @@ "scipy", "speedtest-cli", "terminaltables==3.1.10", - "websocket-client" + "websocket-client", + "pyinstaller" ] ) diff --git a/ui.py b/ui.py new file mode 100644 index 0000000..b228476 --- /dev/null +++ b/ui.py @@ -0,0 +1,58 @@ +import os +import tkinter as tk +from dotenv import load_dotenv + +load_dotenv() + +from ravpy.distributed.participate import participate +from ravpy.initialize import initialize + + +def signin(): + token = entryToken.get() + if token == "": + label3 = tk.Label(root, text='Enter token to signin', fg='red', font=('Arial', 14)) + canvas1.create_window(250, 375, window=label3) + else: + entryToken.pack_forget() + label1.pack_forget() + label2.pack_forget() + + label3 = tk.Label(root, text='Log\n', fg='black', font=('Arial', 14)) + label3.pack() + # canvas1.create_window(250, 375, window=label3) + label3['text'] += "Initializing...\n" + initialize(token) + label3['text'] += "Initialized\n" + participate() + label3['text'] += "Participated\n" + + + +class RavpyUI(): + def __init__(self): + pass + + +if __name__ == '__main__': + root = tk.Tk() + root.title('Ravpy') + canvas1 = tk.Canvas(root, width=500, height=500) + canvas1.pack() + + label1 = tk.Label(root, text='Sign in', fg='black', font=('helvetica', 16, 'bold')) + canvas1.create_window(250, 150, window=label1) + + label2 = tk.Label(root, text='Enter Token:', fg='black', font=('helvetica', 14)) + canvas1.create_window(250, 200, window=label2) + + tokenString = tk.StringVar() + entryToken = tk.Entry(root, width=30, textvariable=tokenString, font=("helvetica", 20)) + entryToken.pack(padx=10, pady=10) + + canvas1.create_window(250, 250, window=entryToken) + + button1 = tk.Button(text='Submit', command=signin, bg='brown', fg='black') + canvas1.create_window(250, 300, window=button1) + + root.mainloop() From b6f4c4f376b6ad10b729c717cb3b4cf5bd1409de Mon Sep 17 00:00:00 2001 From: Kailash Date: Thu, 15 Sep 2022 19:15:26 +0530 Subject: [PATCH 05/16] Version updated, import related issue fixed --- ravpy/distributed/compute.py | 2 +- setup.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ravpy/distributed/compute.py b/ravpy/distributed/compute.py index a5c869f..6bb2444 100644 --- a/ravpy/distributed/compute.py +++ b/ravpy/distributed/compute.py @@ -4,11 +4,11 @@ import sys import time -from .op_functions import * from ..config import FTP_DOWNLOAD_FILES_FOLDER from ..globals import g from ..strings import functions from ..utils import get_key, dump_data, load_data +from .op_functions import * def compute_locally_bm(*args, **kwargs): diff --git a/setup.py b/setup.py index 6ca10dc..fee0da1 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name="ravpy", - version="0.9", + version="0.9.2", license='MIT', author="Raven Protocol", author_email='kailash@ravenprotocol.com', @@ -28,7 +28,7 @@ "speedtest-cli", "terminaltables==3.1.10", "websocket-client", - "pyinstaller" + "pyinstaller", "scikit-learn" ] ) From 02e77437266771459339a08e0d15e295d4839625 Mon Sep 17 00:00:00 2001 From: Kailash Date: Thu, 15 Sep 2022 19:29:22 +0530 Subject: [PATCH 06/16] Version updated --- ravpy/distributed/deep_learning/__init__.py | 5 +++++ setup.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 ravpy/distributed/deep_learning/__init__.py diff --git a/ravpy/distributed/deep_learning/__init__.py b/ravpy/distributed/deep_learning/__init__.py new file mode 100644 index 0000000..695698b --- /dev/null +++ b/ravpy/distributed/deep_learning/__init__.py @@ -0,0 +1,5 @@ +from .activation_functions import * +from .utils import * +from .layers import * +from .loss_functions import * +from .optimizers import * diff --git a/setup.py b/setup.py index fee0da1..a3e6abf 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name="ravpy", - version="0.9.2", + version="0.9.3", license='MIT', author="Raven Protocol", author_email='kailash@ravenprotocol.com', From e313f1f8ebdc774953c04abed448a2ed845ac6f8 Mon Sep 17 00:00:00 2001 From: Kailash Date: Thu, 22 Sep 2022 23:08:56 +0800 Subject: [PATCH 07/16] Verify token function added;ui updated --- .env.example | 4 +++ .gitignore | 1 + ravpy/config.py | 1 + ravpy/utils.py | 16 ++++++++- ui.py | 88 ++++++++++++++++++++++++++++--------------------- 5 files changed, 71 insertions(+), 39 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..0501126 --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +TOKEN= +RAVENVERSE_URL=http://0.0.0.0:8081 +RAVENVERSE_FTP_URL=0.0.0.0 +RAVENAUTH_URL=http://0.0.0.0:8000 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 090a1f0..857d7de 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea .DS_Store +.env \ No newline at end of file diff --git a/ravpy/config.py b/ravpy/config.py index b0e49ef..316f91d 100644 --- a/ravpy/config.py +++ b/ravpy/config.py @@ -28,3 +28,4 @@ BENCHMARK_DOWNLOAD_PATH = os.path.join(PROJECT_DIR, "ravpy/distributed/downloads/") TEMP_FILES_PATH = os.path.join(PROJECT_DIR, "ravpy/distributed/temp_files/") +RAVENAUTH_TOKEN_VERIFY_URL = "{}{}".format(os.environ.get("RAVENAUTH_URL"), "/api/token/verify/") diff --git a/ravpy/utils.py b/ravpy/utils.py index 9d71989..be8fe0d 100644 --- a/ravpy/utils.py +++ b/ravpy/utils.py @@ -4,7 +4,7 @@ import speedtest import time -from .config import ENCRYPTION, RAVENVERSE_FTP_URL +from .config import ENCRYPTION, RAVENVERSE_FTP_URL, RAVENAUTH_TOKEN_VERIFY_URL if ENCRYPTION: import tenseal as ts @@ -257,3 +257,17 @@ def initialize_ftp_client(): g.ftp_client = get_ftp_client(creds['username'], creds['password']) return g.ftp_client + + +def verify_token(token): + """ + Verify user token + :param token: token + :return: valid or not + """ + r = requests.post(RAVENAUTH_TOKEN_VERIFY_URL, data={"token": token}) + if r.status_code != 200: + g.logger.debug("Error:{}".format(r.text)) + return False + else: + return True diff --git a/ui.py b/ui.py index b228476..353e436 100644 --- a/ui.py +++ b/ui.py @@ -1,58 +1,70 @@ -import os -import tkinter as tk from dotenv import load_dotenv load_dotenv() +import tkinter as tk +from ravpy.utils import verify_token from ravpy.distributed.participate import participate from ravpy.initialize import initialize -def signin(): - token = entryToken.get() - if token == "": - label3 = tk.Label(root, text='Enter token to signin', fg='red', font=('Arial', 14)) - canvas1.create_window(250, 375, window=label3) - else: - entryToken.pack_forget() - label1.pack_forget() - label2.pack_forget() +class RavpyUI(object): + def __init__(self): + self.root = tk.Tk() + self.root.title('Ravpy') - label3 = tk.Label(root, text='Log\n', fg='black', font=('Arial', 14)) - label3.pack() - # canvas1.create_window(250, 375, window=label3) - label3['text'] += "Initializing...\n" - initialize(token) - label3['text'] += "Initialized\n" - participate() - label3['text'] += "Participated\n" + self.signin_canvas = None + self.entry_token = None + self.dashboard_canvas = None + self.create_signin_canvas() + def create_signin_canvas(self): + self.signin_canvas = tk.Canvas(self.root, width=500, height=500) + self.signin_canvas.pack() -class RavpyUI(): - def __init__(self): - pass + signin_label = tk.Label(self.root, text='Sign in', fg='black', font=('helvetica', 16, 'bold')) + enter_token_label = tk.Label(self.root, text='Enter Token:', fg='black', font=('helvetica', 14)) + token_string = tk.StringVar() + self.entry_token = tk.Entry(self.root, width=30, textvariable=token_string, font=("helvetica", 20)) + self.entry_token.pack(padx=10, pady=10) -if __name__ == '__main__': - root = tk.Tk() - root.title('Ravpy') - canvas1 = tk.Canvas(root, width=500, height=500) - canvas1.pack() + signin_button = tk.Button(text='Submit', command=self.signin, bg='brown', fg='black') + + self.signin_canvas.create_window(250, 150, window=signin_label) + self.signin_canvas.create_window(250, 200, window=enter_token_label) + self.signin_canvas.create_window(250, 250, window=self.entry_token) + self.signin_canvas.create_window(250, 300, window=signin_button) + + def create_dashboard_canvas(self, token): + self.dashboard_canvas = tk.Canvas(self.root, width=500, height=500) + self.dashboard_canvas.pack() - label1 = tk.Label(root, text='Sign in', fg='black', font=('helvetica', 16, 'bold')) - canvas1.create_window(250, 150, window=label1) + label3 = tk.Label(self.root, text='Log\n', fg='black', font=('Arial', 14)) + self.dashboard_canvas.create_window(250, 150, window=label3) - label2 = tk.Label(root, text='Enter Token:', fg='black', font=('helvetica', 14)) - canvas1.create_window(250, 200, window=label2) + # canvas1.create_window(250, 375, window=label3) + label3['text'] += "Initializing...\n" + initialize(token) + label3['text'] += "Initialized\n" + participate() + label3['text'] += "Participated\n" - tokenString = tk.StringVar() - entryToken = tk.Entry(root, width=30, textvariable=tokenString, font=("helvetica", 20)) - entryToken.pack(padx=10, pady=10) + def start(self): + self.root.mainloop() - canvas1.create_window(250, 250, window=entryToken) + def signin(self): + token = self.entry_token.get() + if token == "": + label3 = tk.Label(self.root, text='Enter token to signin', fg='red', font=('Arial', 14)) + self.signin_canvas.create_window(250, 375, window=label3) + else: + if verify_token(token=token): + self.signin_canvas.pack_forget() + self.create_dashboard_canvas(token) - button1 = tk.Button(text='Submit', command=signin, bg='brown', fg='black') - canvas1.create_window(250, 300, window=button1) - root.mainloop() +if __name__ == '__main__': + ui = RavpyUI() + ui.start() From 4eecee97f75f6e52ad845de8aa150b40d2536caf Mon Sep 17 00:00:00 2001 From: Kailash Date: Fri, 14 Oct 2022 14:04:24 +0530 Subject: [PATCH 08/16] Version updated --- .env | 3 --- setup.py | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 .env diff --git a/.env b/.env deleted file mode 100644 index d277f0f..0000000 --- a/.env +++ /dev/null @@ -1,3 +0,0 @@ -TOKEN= -RAVENVERSE_URL=http://0.0.0.0:8081 -RAVENVERSE_FTP_URL=0.0.0.0 diff --git a/setup.py b/setup.py index a3e6abf..e472311 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name="ravpy", - version="0.9.3", + version="0.10", license='MIT', author="Raven Protocol", author_email='kailash@ravenprotocol.com', From b0b43b150dc1e110a99e60668683e1e7c0d818ac Mon Sep 17 00:00:00 2001 From: Anirudh Menon Date: Mon, 7 Nov 2022 15:02:16 +0530 Subject: [PATCH 09/16] FTP for params, Latest version check, FTP Noop to prevent broken conn Co-authored-by: Unnikrishnan --- ravpy/distributed/compute.py | 14 ++++++++++---- ravpy/distributed/evaluate.py | 8 ++++++++ ravpy/ftp/__init__.py | 4 ++++ ravpy/globals.py | 29 ++++++++++++++++++++++++++++- ravpy/initialize.py | 7 +++++++ ravpy/utils.py | 18 ++++++++++++++++++ 6 files changed, 75 insertions(+), 5 deletions(-) diff --git a/ravpy/distributed/compute.py b/ravpy/distributed/compute.py index 6bb2444..b0ec882 100644 --- a/ravpy/distributed/compute.py +++ b/ravpy/distributed/compute.py @@ -69,9 +69,15 @@ def compute_locally(payload, subgraph_id, graph_id): temp = ast.literal_eval(params[i]) if type(temp) == dict: params[i] = temp - elif type(params[i]) == dict and 'op_id' in params[i].keys(): - op_id = params[i]["op_id"] - param_value = g.outputs[op_id] + elif type(params[i]) == dict: + if 'op_id' in params[i].keys(): + op_id = params[i]["op_id"] + param_value = g.outputs[op_id] + elif 'value' in params[i].keys(): + download_path = os.path.join(FTP_DOWNLOAD_FILES_FOLDER, + os.path.basename(params[i]["path"])) + param_value = load_data(download_path).tolist() + params[i] = param_value if op_type == "unary": @@ -330,7 +336,7 @@ def get_binary_result(value1, value2, params, operator): result = one_hot_encoding(value1, value2, params=params) elif operator == 'find_indices': result = find_indices(value1, value2, params=params) - elif operator == 'concatenate': + elif operator == 'concat': result = concatenate(value1, value2, params=params) elif operator == 'join_to_list': result = join_to_list(value1, value2, params=params) diff --git a/ravpy/distributed/evaluate.py b/ravpy/distributed/evaluate.py index c65add6..df73d3f 100644 --- a/ravpy/distributed/evaluate.py +++ b/ravpy/distributed/evaluate.py @@ -134,3 +134,11 @@ def waitInterval(): stopTimer(timeoutId) timeoutId = setTimeout(waitInterval, opTimeout) + + if not g.is_downloading: + if not g.is_uploading: + if g.noop_counter % 17 == 0: + g.ftp_client.ftp.voidcmd('NOOP') + print("Nooped") + + g.noop_counter += 1 \ No newline at end of file diff --git a/ravpy/ftp/__init__.py b/ravpy/ftp/__init__.py index d05f772..4a4a708 100644 --- a/ravpy/ftp/__init__.py +++ b/ravpy/ftp/__init__.py @@ -14,12 +14,16 @@ def __init__(self, host, user, passwd): self.ftp.set_pasv(True) def download(self, filename, path): + g.is_downloading = True with open(filename, 'wb') as f: self.ftp.retrbinary('RETR ' + path, f.write, blocksize=g.ftp_download_blocksize) + g.is_downloading = False def upload(self, filename, path): + g.is_uploading = True with open(filename, 'rb') as f: self.ftp.storbinary('STOR ' + path, f, blocksize=g.ftp_upload_blocksize) + g.is_uploading = False def list_server_files(self): self.ftp.retrlines('LIST') diff --git a/ravpy/globals.py b/ravpy/globals.py index b2fd6fa..353ab8b 100644 --- a/ravpy/globals.py +++ b/ravpy/globals.py @@ -45,12 +45,15 @@ def __init__(self): self._client = None self._timeoutId = None self._ops = {} - self._opTimeout = 50 + self._opTimeout = 300 #5000 self._initialTimeout = 100 self._outputs = {} self._ftp_client = None self._delete_files_list = [] self._has_subgraph = False + self._is_downloading = False + self._is_uploading = False + self._noop_counter = 0 self._ftp_upload_blocksize = 8192 self._ftp_download_blocksize = 8192 self._error = False @@ -131,6 +134,30 @@ def has_subgraph(self): def has_subgraph(self, has_subgraph): self._has_subgraph = has_subgraph + @property + def is_downloading(self): + return self._is_downloading + + @is_downloading.setter + def is_downloading(self, is_downloading): + self._is_downloading = is_downloading + + @property + def is_uploading(self): + return self._is_uploading + + @is_uploading.setter + def is_uploading(self, is_uploading): + self._is_uploading = is_uploading + + @property + def noop_counter(self): + return self._noop_counter + + @noop_counter.setter + def noop_counter(self, noop_counter): + self._noop_counter = noop_counter + @outputs.setter def outputs(self, outputs): self._outputs = outputs diff --git a/ravpy/initialize.py b/ravpy/initialize.py index bc72677..8054d55 100644 --- a/ravpy/initialize.py +++ b/ravpy/initialize.py @@ -3,6 +3,7 @@ from .globals import g +from .utils import isLatestVersion def exit_handler(): g.logger.debug('Application is Closing!') @@ -16,6 +17,12 @@ def exit_handler(): def initialize(ravenverse_token): + g.logger.debug("Checking Version of Ravpy...") + + if not isLatestVersion('ravpy'): + g.logger.debug("Please update ravpy to latest version...") + os._exit(1) + g.logger.debug("Initializing...") g.ravenverse_token = ravenverse_token diff --git a/ravpy/utils.py b/ravpy/utils.py index be8fe0d..b75391c 100644 --- a/ravpy/utils.py +++ b/ravpy/utils.py @@ -22,6 +22,24 @@ from threading import Timer from .globals import g +import json +import urllib.request +from pip._internal.operations.freeze import freeze + +def isLatestVersion(pkgName): + # Get the currently installed version + current_version = '' + for requirement in freeze(local_only=False): + pkg = requirement.split('==') + if pkg[0] == pkgName: + current_version = pkg[1] + # Check pypi for the latest version number + contents = urllib.request.urlopen('https://pypi.org/pypi/'+pkgName+'/json').read() + data = json.loads(contents) + latest_version = data['info']['version'] + # print(‘Current version of ‘+pkgName+’ is ’+current_version) + # print(‘Latest version of ‘+pkgName+’ is ’+latest_version) + return latest_version == current_version def download_file(url, file_name): g.logger.debug("Downloading benchmark data") From 2a92a03ba579918996666218007d096c5c0ccacc Mon Sep 17 00:00:00 2001 From: Kailash Date: Thu, 10 Nov 2022 11:57:33 +0530 Subject: [PATCH 10/16] Library added;basic ui created --- ravpy/__init__.py | 2 + ravpy/globals.py | 20 +-- ravpy/initialize.py | 8 +- ravpy/socket/__init__.py | 1 + ravpy/socket/socket_client.py | 54 +++++++ requirements.txt | 3 + run_distributed_client.py | 7 +- ui2.py | 95 ++++++++++++ web/css/main.css | 0 web/img/ravenverse-logo.png | Bin 0 -> 6767 bytes web/main.html | 263 ++++++++++++++++++++++++++++++++++ web/main.js | 0 12 files changed, 437 insertions(+), 16 deletions(-) create mode 100644 ravpy/socket/__init__.py create mode 100644 ravpy/socket/socket_client.py create mode 100644 ui2.py create mode 100644 web/css/main.css create mode 100644 web/img/ravenverse-logo.png create mode 100644 web/main.html create mode 100644 web/main.js diff --git a/ravpy/__init__.py b/ravpy/__init__.py index e69de29..2721361 100644 --- a/ravpy/__init__.py +++ b/ravpy/__init__.py @@ -0,0 +1,2 @@ +from .initialize import initialize +from .distributed.participate import participate diff --git a/ravpy/globals.py b/ravpy/globals.py index b2fd6fa..183cf23 100644 --- a/ravpy/globals.py +++ b/ravpy/globals.py @@ -17,12 +17,6 @@ def get_client(ravenverse_token): auth_headers = {"token": ravenverse_token} client = socketio.Client(logger=False, request_timeout=100, engineio_logger=False) - @client.on('error', namespace='/client') - def check_error(d): - g.logger.error("Connection error:a{}".format(d)) - client.disconnect() - os._exit(1) - try: g.logger.debug("Ravenverse url: {}?type={}".format(RAVENVERSE_URL, TYPE)) g.logger.debug("Ravenverse FTP host: {}".format(RAVENVERSE_FTP_URL)) @@ -30,7 +24,6 @@ def check_error(d): auth=auth_headers, transports=['websocket'], namespaces=['/client'], wait_timeout=100) - g.logger.debug("Successfully connected to Ravenverse") return client except Exception as e: g.logger.error("Error: Unable to connect to Ravenverse. " @@ -45,8 +38,8 @@ def __init__(self): self._client = None self._timeoutId = None self._ops = {} - self._opTimeout = 50 - self._initialTimeout = 100 + self._opTimeout = 5000 + self._initialTimeout = 5000 self._outputs = {} self._ftp_client = None self._delete_files_list = [] @@ -104,9 +97,16 @@ def client(self): return self._client if self._client is None: - self._client = get_client(self._ravenverse_token) + self._client = self.get_socket_client() return self._client + def get_socket_client(self): + from .socket import SocketClient + socket_client = SocketClient() + socket_client.connect(self._ravenverse_token) + self._client = socket_client.client + return self._client + @property def ftp_client(self): return self._ftp_client diff --git a/ravpy/initialize.py b/ravpy/initialize.py index bc72677..82ba642 100644 --- a/ravpy/initialize.py +++ b/ravpy/initialize.py @@ -19,11 +19,11 @@ def initialize(ravenverse_token): g.logger.debug("Initializing...") g.ravenverse_token = ravenverse_token - client = g.client - if client is None: + client = g.get_socket_client() + if not client.connected: g.client.disconnect() - g.logger.error("Unable to connect to ravsock. Make sure you are using the right hostname and port") - os._exit(1) + g.logger.error("Unable to connect to ravenverse. Make sure you are using the right hostname and port") + return None else: g.logger.debug("Initialized successfully\n") return client diff --git a/ravpy/socket/__init__.py b/ravpy/socket/__init__.py new file mode 100644 index 0000000..9753504 --- /dev/null +++ b/ravpy/socket/__init__.py @@ -0,0 +1 @@ +from .socket_client import SocketClient diff --git a/ravpy/socket/socket_client.py b/ravpy/socket/socket_client.py new file mode 100644 index 0000000..cf62641 --- /dev/null +++ b/ravpy/socket/socket_client.py @@ -0,0 +1,54 @@ +import socketio +from socketio.exceptions import ConnectionError +from ravpy.config import RAVENVERSE_URL, TYPE, RAVENVERSE_FTP_URL +from ravpy.globals import g + + +class SocketNamespace(socketio.ClientNamespace): + def on_connect(self): + g.logger.debug('Connected to Ravenverse successfully!') + + def on_disconnect(self): + g.logger.debug('Disconnected from the server') + + def on_message(self, data): + g.logger.debug('Message received:', data) + + def on_result(self, data): + g.logger.debug(data) + + def on_connect_error(self, e): + g.logger.debug("Error:{}".format(str(e))) + + +class SocketClient(object): + def __init__(self): + self._client = socketio.Client(logger=False, request_timeout=100, engineio_logger=False) + self._client.register_namespace(SocketNamespace('/client')) + + def connect(self, token): + g.logger.debug("Connecting to Ravenverse...") + g.logger.debug("Ravenverse url: {}?type={}".format(RAVENVERSE_URL, TYPE)) + g.logger.debug("Ravenverse FTP host: {}".format(RAVENVERSE_FTP_URL)) + auth_headers = {"token": token} + + try: + self._client.connect(url="{}?type={}".format(RAVENVERSE_URL, TYPE), + auth=auth_headers, + transports=['websocket'], + namespaces=['/client'], wait_timeout=100) + except ConnectionError as e: + g.logger.error("Error: Unable to connect to Ravenverse. " + "Make sure you are using the right hostname and port. \n{}".format(e)) + self._client.disconnect() + except Exception as e: + g.logger.error("Error: Unable to connect to Ravenverse. " + "Make sure you are using the right hostname and port. \n{}".format(e)) + self._client.disconnect() + + @property + def client(self): + return self._client + + def disconnect(self): + self._client.disconnect() diff --git a/requirements.txt b/requirements.txt index 57c0169..cd0381f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,6 @@ terminaltables==3.1.10 websocket-client pyinstaller scikit-learn +eel +psutil +hurry.filesize diff --git a/run_distributed_client.py b/run_distributed_client.py index 640cb13..19997c3 100644 --- a/run_distributed_client.py +++ b/run_distributed_client.py @@ -4,9 +4,12 @@ load_dotenv() -from ravpy.distributed.participate import participate -from ravpy.initialize import initialize +from ravpy import participate +from ravpy import initialize if __name__ == '__main__': client = initialize(os.environ.get("TOKEN")) + if not client.connected: + os._exit(1) + participate() diff --git a/ui2.py b/ui2.py new file mode 100644 index 0000000..316c0ac --- /dev/null +++ b/ui2.py @@ -0,0 +1,95 @@ +import logging + +import eel +import psutil +import shutil +from dotenv import load_dotenv +from hurry.filesize import size + +load_dotenv() + +from ravpy.utils import verify_token +from ravpy.distributed.participate import participate +from ravpy.initialize import initialize +from ravpy.globals import g + +eel.init('web') + + +class CustomHandler(logging.Handler): + def __init__(self): + logging.Handler.__init__(self) + + def emit(self, record): + print("Custom", record) + eel.getLog("{} [{}] {}".format(record.asctime, record.levelname, record.message)) + + +log_formatter = logging.Formatter("%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s] %(message)s") +my_handler = CustomHandler() +my_handler.setLevel(logging.DEBUG) +my_handler.setFormatter(log_formatter) +g.logger.addHandler(my_handler) + + +@eel.expose +def verify_access_token(access_token): + if verify_token(access_token): + return [access_token, "success", ""] + else: + return [access_token, "failure", "Invalid access token!"] + + +@eel.expose +def get_system_config(): + ram_total = str(size(psutil.virtual_memory().total)) + ram_available = str(size(psutil.virtual_memory().available)) + cpu_count = psutil.cpu_count(logical=False) + cpu_percent = psutil.cpu_percent() + total, used, free = shutil.disk_usage("/") + storage_total = total // (2 ** 30) + storage_available = free // (2 ** 30) + + return {"ram_total": ram_total, "ram_available": ram_available, + "cpu_count": cpu_count, "cpu_percent": cpu_percent, "storage_total": storage_total, + "storage_available": storage_available} + + +@eel.expose +def initialize_exposed(token): + client = initialize(token) + if client is None: + return False + else: + return True + + +@eel.expose +def participate_exposed(): + participate() + + +@eel.expose +def get_logs(skip, limit): + with open("debug.log", "r") as f: + logs = f.readlines()[skip:] + print(logs) + + +@eel.expose +def disconnect(): + if g.client.connected: + g.logger.debug("Disconnecting...") + if g.client.connected: + g.client.emit("disconnect", namespace="/client") + g.logger.debug("Disconnected") + + return True + + +def close_callback(a, b): + print("closing", a, b) + disconnect() + + +eel.start('main.html', close_callback=close_callback) diff --git a/web/css/main.css b/web/css/main.css new file mode 100644 index 0000000..e69de29 diff --git a/web/img/ravenverse-logo.png b/web/img/ravenverse-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..1deaa6bf970fc7ae3c155292d0f8a780c2a1a84a GIT binary patch literal 6767 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
)md9r+$v{h z{%J$4?1usqZQ(5XecWB@a4#wZGdg61IqAHBrbl2xSJkjPjdc>$dbiX*ZhPa)j-=cm zReK8e1o3*{va{SZ`?c?T8^*}t(fdb7aO%LGsW&m25T1EmCJ7 r4#N07ulSkBYHlM>R#r&y?=x+;zly!fpUryz{p>N;w?NkDxkvsV0&;%! literal 0 HcmV?d00001 diff --git a/web/main.html b/web/main.html new file mode 100644 index 0000000..571b762 --- /dev/null +++ b/web/main.html @@ -0,0 +1,263 @@ + + + + + + + + Ravpy + + + + + + + + + + + +
+ + +
+ + + +
+
+ + + + + + + \ No newline at end of file diff --git a/web/main.js b/web/main.js new file mode 100644 index 0000000..e69de29 From 12452dd34f00a31211e1a27dc07a5e004d097272 Mon Sep 17 00:00:00 2001 From: Anirudh Menon Date: Thu, 10 Nov 2022 15:49:57 +0530 Subject: [PATCH 11/16] Connection issues resolved, Reconnection handled, FTP try catch Co-authored-by: Unnikrishnan --- ravpy/distributed/evaluate.py | 43 +++++++++++++++++-- ravpy/ftp/__init__.py | 78 ++++++++++++++++++++++++++++++++--- ravpy/globals.py | 9 ++++ 3 files changed, 121 insertions(+), 9 deletions(-) diff --git a/ravpy/distributed/evaluate.py b/ravpy/distributed/evaluate.py index df73d3f..20f4882 100644 --- a/ravpy/distributed/evaluate.py +++ b/ravpy/distributed/evaluate.py @@ -1,6 +1,7 @@ import json import os import sys +import socket from terminaltables import AsciiTable from zipfile import ZipFile @@ -39,6 +40,7 @@ def compute_subgraph(d): download_path = os.path.join(FTP_DOWNLOAD_FILES_FOLDER, server_file_path) try: + g.ftp_client.ftp.voidcmd('NOOP') g.ftp_client.download(download_path, os.path.basename(server_file_path)) except Exception as error: @@ -119,13 +121,31 @@ def compute_subgraph(d): @g.client.on('ping', namespace="/client") def ping(d): global client + g.ping_timeout_counter = 0 client.emit('pong', d, namespace='/client') - +@g.client.on('redundant_subgraph', namespace="/client") +def redundant_subgraph(d): + subgraph_id = d['subgraph_id'] + graph_id = d['graph_id'] + for i in range(len(g.dashboard_data)): + if g.dashboard_data[i][0] == subgraph_id and g.dashboard_data[i][1] == graph_id: + g.dashboard_data[i][2] = "redundant_computation" + os.system('clear') + print(AsciiTable([['Provider Dashboard']]).table) + print(AsciiTable(g.dashboard_data).table) + def waitInterval(): global client, timeoutId, opTimeout, initialTimeout client = g.client + try: + sock = socket.create_connection(('8.8.8.8',53)) + sock.close() + except Exception as e: + print('\n ----------- Device offline -----------') + os._exit(1) + if g.client.connected: if not g.has_subgraph: client.emit("get_op", json.dumps({ @@ -138,7 +158,22 @@ def waitInterval(): if not g.is_downloading: if not g.is_uploading: if g.noop_counter % 17 == 0: - g.ftp_client.ftp.voidcmd('NOOP') - print("Nooped") + try: + g.ftp_client.ftp.voidcmd('NOOP') + g.ping_timeout_counter += 1 + except Exception as e: + exit_handler() + os._exit(1) + + if g.ping_timeout_counter > 10: + exit_handler() + os._exit(1) - g.noop_counter += 1 \ No newline at end of file + g.noop_counter += 1 + +def exit_handler(): + g.logger.debug('Application is Closing!') + if g.client is not None: + g.logger.debug("Disconnecting...") + if g.client.connected: + g.client.emit("disconnect", namespace="/client") \ No newline at end of file diff --git a/ravpy/ftp/__init__.py b/ravpy/ftp/__init__.py index 4a4a708..1679232 100644 --- a/ravpy/ftp/__init__.py +++ b/ravpy/ftp/__init__.py @@ -1,10 +1,16 @@ import os - +import socket from ftplib import FTP from ..config import RAVENVERSE_FTP_URL from ..globals import g +try: + import ssl +except ImportError: + _SSLSocket = None +else: + _SSLSocket = ssl.SSLSocket class FTPClient: def __init__(self, host, user, passwd): @@ -14,15 +20,40 @@ def __init__(self, host, user, passwd): self.ftp.set_pasv(True) def download(self, filename, path): + try: + sock = socket.create_connection(('8.8.8.8',53)) + sock.close() + except Exception as e: + print('\n ----------- Device offline -----------') + os._exit(1) g.is_downloading = True - with open(filename, 'wb') as f: - self.ftp.retrbinary('RETR ' + path, f.write, blocksize=g.ftp_download_blocksize) + + try: + with open(filename, 'wb') as f: + self.ftp.retrbinary('RETR ' + path, f.write, blocksize=g.ftp_download_blocksize) + except Exception as e: + exit_handler() + os._exit(1) + g.is_downloading = False def upload(self, filename, path): + try: + sock = socket.create_connection(('8.8.8.8',53)) + sock.close() + except Exception as e: + print('\n ----------- Device offline -----------') + os._exit(1) g.is_uploading = True - with open(filename, 'rb') as f: - self.ftp.storbinary('STOR ' + path, f, blocksize=g.ftp_upload_blocksize) + + try: + with open(filename, 'rb') as f: + # self.ftp.storbinary('STOR ' + path, f, blocksize=g.ftp_upload_blocksize) + self.storbinary('STOR ' + path, f, blocksize=g.ftp_upload_blocksize) + except Exception as e: + exit_handler() + os._exit(1) + g.is_uploading = False def list_server_files(self): @@ -34,6 +65,36 @@ def delete_file(self, path): def close(self): self.ftp.quit() + def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None): + """Store a file in binary mode. A new port is created for you. + + Args: + cmd: A STOR command. + fp: A file-like object with a read(num_bytes) method. + blocksize: The maximum data size to read from fp and send over + the connection at once. [default: 8192] + callback: An optional single parameter callable that is called on + each block of data after it is sent. [default: None] + rest: Passed to transfercmd(). [default: None] + + Returns: + The response code. + """ + self.ftp.voidcmd('TYPE I') + with self.ftp.transfercmd(cmd, rest) as conn: + while 1: + buf = fp.read(blocksize) + if not buf: + break + conn.sendall(buf) + if callback: + callback(buf) + # shutdown ssl layer + if _SSLSocket is not None and isinstance(conn, _SSLSocket): + # conn.unwrap() + pass + return self.ftp.voidresp() + def get_client(username, password): """ @@ -59,3 +120,10 @@ def check_credentials(username, password): except Exception as e: print("Error:{}".format(str(e))) return False + +def exit_handler(): + g.logger.debug('Application is Closing!') + if g.client is not None: + g.logger.debug("Disconnecting...") + if g.client.connected: + g.client.emit("disconnect", namespace="/client") \ No newline at end of file diff --git a/ravpy/globals.py b/ravpy/globals.py index 353ab8b..6ba6a08 100644 --- a/ravpy/globals.py +++ b/ravpy/globals.py @@ -56,6 +56,7 @@ def __init__(self): self._noop_counter = 0 self._ftp_upload_blocksize = 8192 self._ftp_download_blocksize = 8192 + self._ping_timeout_counter = 0 self._error = False self._ravenverse_token = None self._logger = get_logger() @@ -158,6 +159,14 @@ def noop_counter(self): def noop_counter(self, noop_counter): self._noop_counter = noop_counter + @property + def ping_timeout_counter(self): + return self._ping_timeout_counter + + @ping_timeout_counter.setter + def ping_timeout_counter(self, ping_timeout_counter): + self._ping_timeout_counter = ping_timeout_counter + @outputs.setter def outputs(self, outputs): self._outputs = outputs From 838723cdbe886579c200cd49b5f458a655d4e160 Mon Sep 17 00:00:00 2001 From: Kailash Date: Thu, 10 Nov 2022 18:44:50 +0530 Subject: [PATCH 12/16] Version update --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e472311..bcff064 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name="ravpy", - version="0.10", + version="0.11", license='MIT', author="Raven Protocol", author_email='kailash@ravenprotocol.com', From 4b4f6559c3fb39116fc167ea436343560d87559e Mon Sep 17 00:00:00 2001 From: Kailash Date: Fri, 2 Dec 2022 15:50:38 +0530 Subject: [PATCH 13/16] App ui completed with major improvements --- ravpy/config.py | 2 + ravpy/db/__init__.py | 2 + ravpy/db/manager.py | 122 ++++++++++++++ ravpy/db/models.py | 29 ++++ ravpy/distributed/benchmark.json | 2 +- ravpy/distributed/benchmarking.py | 2 +- ravpy/distributed/compute.py | 3 +- ravpy/distributed/evaluate.py | 36 +++- ravpy/distributed/participate.py | 20 ++- ravpy/globals.py | 26 +-- ravpy/initialize.py | 5 +- ravpy/socket/socket_client.py | 33 ++-- ravpy/utils.py | 11 ++ requirements.txt | 7 +- settings.py | 266 ++++++++++++++++++++++++++++++ setup.py | 11 +- ui2.py | 103 ++++++++---- web/img/ravpy-icon.png | Bin 0 -> 37269 bytes web/img/ravpy-icon.svg | 13 ++ web/img/reload.svg | 1 + web/main.html | 164 +++++++++++++++--- 21 files changed, 758 insertions(+), 100 deletions(-) create mode 100644 ravpy/db/__init__.py create mode 100644 ravpy/db/manager.py create mode 100644 ravpy/db/models.py create mode 100644 settings.py create mode 100644 web/img/ravpy-icon.png create mode 100644 web/img/ravpy-icon.svg create mode 100644 web/img/reload.svg diff --git a/ravpy/config.py b/ravpy/config.py index 316f91d..b893104 100644 --- a/ravpy/config.py +++ b/ravpy/config.py @@ -29,3 +29,5 @@ BENCHMARK_DOWNLOAD_PATH = os.path.join(PROJECT_DIR, "ravpy/distributed/downloads/") TEMP_FILES_PATH = os.path.join(PROJECT_DIR, "ravpy/distributed/temp_files/") RAVENAUTH_TOKEN_VERIFY_URL = "{}{}".format(os.environ.get("RAVENAUTH_URL"), "/api/token/verify/") + +DATABASE_URI = "sqlite:///{}/{}".format(PROJECT_DIR, "database.db") diff --git a/ravpy/db/__init__.py b/ravpy/db/__init__.py new file mode 100644 index 0000000..5cc4e19 --- /dev/null +++ b/ravpy/db/__init__.py @@ -0,0 +1,2 @@ +from .models import Subgraph +from .manager import DBManager diff --git a/ravpy/db/manager.py b/ravpy/db/manager.py new file mode 100644 index 0000000..11861e6 --- /dev/null +++ b/ravpy/db/manager.py @@ -0,0 +1,122 @@ +import sqlalchemy +import sqlalchemy as db +from sqlalchemy.orm import sessionmaker +from sqlalchemy_utils import create_database as cd +from sqlalchemy_utils import database_exists, get_tables +from sqlalchemy_utils import drop_database as dba + +from .models import Base, Subgraph +from ..config import DATABASE_URI + + +class DBManager: + def __init__(self): + self.create_database() + self.engine = self.connect() + self.logger = None + + def set_logger(self, logger): + self.logger = logger + + def get_session(self): + Session = sessionmaker(bind=self.engine, expire_on_commit=False) + return Session + + def connect(self): + engine = db.create_engine(DATABASE_URI, isolation_level='READ UNCOMMITTED') + Base.metadata.bind = engine + return engine + + def create_database(self): + if not database_exists(DATABASE_URI): + cd(DATABASE_URI) + print('Database created') + + def drop_database(self): + if database_exists(DATABASE_URI): + dba(DATABASE_URI) + print('Database dropped') + + def create_tables(self): + """ + Create tables + """ + Base.metadata.create_all(self.engine, checkfirst=True) + + def add_subgraph(self, **kwargs): + """ + Create a subgraph and add values + :param kwargs: subgraph details + """ + Session = self.get_session() + with Session.begin() as session: + + subgraph = self.find_subgraph(graph_id=kwargs['graph_id'], subgraph_id=kwargs['subgraph_id']) + if subgraph is None: + # create new subgraph + subgraph = Subgraph() + for key, value in kwargs.items(): + setattr(subgraph, key, value) + session.add(subgraph) + self.logger.debug("Subgraph created") + else: + self.logger.debug("Subgraph available") + return subgraph + + def find_subgraph(self, graph_id, subgraph_id): + """ + Find a subgraph + :param graph_id: Graph id + :param subgraph_id: subgraph id + :return: subgraph object + """ + Session = self.get_session() + with Session.begin() as session: + subgraph = ( + session.query(Subgraph).filter( + Subgraph.graph_id == graph_id, Subgraph.subgraph_id == subgraph_id, + ).first() + ) + return subgraph + + def update_subgraph(self, subgraph, **kwargs): + """ + Update a subgraph + :param subgraph: subgraph object + :param kwargs: details + :return: updated subgraph object + """ + Session = self.get_session() + with Session.begin() as session: + for key, value in kwargs.items(): + setattr(subgraph, key, value) + session.add(subgraph) + return subgraph + + def delete_subgraph(self, obj): + """ + Delete subgraph object + :param obj: subgraph object + :return: None + """ + Session = self.get_session() + with Session.begin() as session: + session.delete(obj) + + def get_subgraphs(self): + """ + Fetch all subgraphs + :return: list of subgraphs + """ + Session = self.get_session() + with Session.begin() as session: + return session.query(Subgraph).order_by(Subgraph.created_at.desc()).all() + + def delete_subgraphs(self): + """ + Delete all subgraphs + :return: None + """ + Session = self.get_session() + with Session.begin() as session: + session.query(Subgraph).delete() diff --git a/ravpy/db/models.py b/ravpy/db/models.py new file mode 100644 index 0000000..77d9143 --- /dev/null +++ b/ravpy/db/models.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +import datetime +import sqlalchemy as sa +from sqlalchemy import Column +from sqlalchemy import DateTime +from sqlalchemy import Float +from sqlalchemy import Integer +from sqlalchemy import String +from sqlalchemy import orm + +metadata = sa.MetaData() +Base = orm.declarative_base(metadata=metadata) + + +class Subgraph(Base): + __tablename__ = 'subgraphs' + id = Column(Integer, primary_key=True) + graph_id = Column(Integer, nullable=False) + subgraph_id = Column(Integer, nullable=False) + status = Column(String(50), nullable=True, default=None) + progress = Column(Float, nullable=True, default=None) + tokens = Column(Float, nullable=True, default=None) + created_at = Column(DateTime, default=datetime.datetime.utcnow) + + def as_dict(self): + values = {c.name: getattr(self, c.name) for c in self.__table__.columns} + values['created_at'] = values['created_at'].strftime("%Y-%m-%d %H:%M:%S") + return values diff --git a/ravpy/distributed/benchmark.json b/ravpy/distributed/benchmark.json index d318313..21339ca 100644 --- a/ravpy/distributed/benchmark.json +++ b/ravpy/distributed/benchmark.json @@ -1 +1 @@ -[{"op_id": 2, "values": [{"value": [[0.3631658436056542, 0.3771815454382733, 0.8594016439073865, 0.05726714637859631, 0.4275623426080757], [0.8375701874521871, 0.4592154167090502, 0.682271933358012, 0.5646052761737979, 0.26399901595145336], [0.46822458979223125, 0.18634697752694784, 0.5732316786217657, 0.6056714628174507, 0.5282834154533805], [0.7201091813882491, 0.7241274529709905, 0.4056821924851388, 0.07909783412481852, 0.9617915703759579], [0.30318477338898864, 0.1818823691577709, 0.6103249343557879, 0.39524829285981566, 0.5800398402795025]]}], "op_type": "unary", "operator": "square", "params": {}}, {"op_id": 4, "values": [{"value": [[0.3631658436056542, 0.3771815454382733, 0.8594016439073865, 0.05726714637859631, 0.4275623426080757], [0.8375701874521871, 0.4592154167090502, 0.682271933358012, 0.5646052761737979, 0.26399901595145336], [0.46822458979223125, 0.18634697752694784, 0.5732316786217657, 0.6056714628174507, 0.5282834154533805], [0.7201091813882491, 0.7241274529709905, 0.4056821924851388, 0.07909783412481852, 0.9617915703759579], [0.30318477338898864, 0.1818823691577709, 0.6103249343557879, 0.39524829285981566, 0.5800398402795025]]}], "op_type": "unary", "operator": "cube_root", "params": {}}, {"op_id": 6, "values": [{"value": [[0.3631658436056542, 0.3771815454382733, 0.8594016439073865, 0.05726714637859631, 0.4275623426080757], [0.8375701874521871, 0.4592154167090502, 0.682271933358012, 0.5646052761737979, 0.26399901595145336], [0.46822458979223125, 0.18634697752694784, 0.5732316786217657, 0.6056714628174507, 0.5282834154533805], [0.7201091813882491, 0.7241274529709905, 0.4056821924851388, 0.07909783412481852, 0.9617915703759579], [0.30318477338898864, 0.1818823691577709, 0.6103249343557879, 0.39524829285981566, 0.5800398402795025]]}], "op_type": "unary", "operator": "absolute", "params": {}}, {"op_id": 8, "values": [{"value": [[0.3631658436056542, 0.3771815454382733, 0.8594016439073865, 0.05726714637859631, 0.4275623426080757], [0.8375701874521871, 0.4592154167090502, 0.682271933358012, 0.5646052761737979, 0.26399901595145336], [0.46822458979223125, 0.18634697752694784, 0.5732316786217657, 0.6056714628174507, 0.5282834154533805], [0.7201091813882491, 0.7241274529709905, 0.4056821924851388, 0.07909783412481852, 0.9617915703759579], [0.30318477338898864, 0.1818823691577709, 0.6103249343557879, 0.39524829285981566, 0.5800398402795025]]}], "op_type": "unary", "operator": "matrix_sum", "params": {}}, {"op_id": 10, "values": [{"value": [[0.3631658436056542, 0.3771815454382733, 0.8594016439073865, 0.05726714637859631, 0.4275623426080757], [0.8375701874521871, 0.4592154167090502, 0.682271933358012, 0.5646052761737979, 0.26399901595145336], [0.46822458979223125, 0.18634697752694784, 0.5732316786217657, 0.6056714628174507, 0.5282834154533805], [0.7201091813882491, 0.7241274529709905, 0.4056821924851388, 0.07909783412481852, 0.9617915703759579], [0.30318477338898864, 0.1818823691577709, 0.6103249343557879, 0.39524829285981566, 0.5800398402795025]]}], "op_type": "unary", "operator": "min", "params": {}}, {"op_id": 12, "values": [{"value": [[0.3631658436056542, 0.3771815454382733, 0.8594016439073865, 0.05726714637859631, 0.4275623426080757], [0.8375701874521871, 0.4592154167090502, 0.682271933358012, 0.5646052761737979, 0.26399901595145336], [0.46822458979223125, 0.18634697752694784, 0.5732316786217657, 0.6056714628174507, 0.5282834154533805], [0.7201091813882491, 0.7241274529709905, 0.4056821924851388, 0.07909783412481852, 0.9617915703759579], [0.30318477338898864, 0.1818823691577709, 0.6103249343557879, 0.39524829285981566, 0.5800398402795025]]}], "op_type": "unary", "operator": "max", "params": {}}, {"op_id": 14, "values": [{"value": [[0.3631658436056542, 0.3771815454382733, 0.8594016439073865, 0.05726714637859631, 0.4275623426080757], [0.8375701874521871, 0.4592154167090502, 0.682271933358012, 0.5646052761737979, 0.26399901595145336], [0.46822458979223125, 0.18634697752694784, 0.5732316786217657, 0.6056714628174507, 0.5282834154533805], [0.7201091813882491, 0.7241274529709905, 0.4056821924851388, 0.07909783412481852, 0.9617915703759579], [0.30318477338898864, 0.1818823691577709, 0.6103249343557879, 0.39524829285981566, 0.5800398402795025]]}], "op_type": "unary", "operator": "argmax", "params": {}}, {"op_id": 16, "values": [{"value": [[0.3631658436056542, 0.3771815454382733, 0.8594016439073865, 0.05726714637859631, 0.4275623426080757], [0.8375701874521871, 0.4592154167090502, 0.682271933358012, 0.5646052761737979, 0.26399901595145336], [0.46822458979223125, 0.18634697752694784, 0.5732316786217657, 0.6056714628174507, 0.5282834154533805], [0.7201091813882491, 0.7241274529709905, 0.4056821924851388, 0.07909783412481852, 0.9617915703759579], [0.30318477338898864, 0.1818823691577709, 0.6103249343557879, 0.39524829285981566, 0.5800398402795025]]}], "op_type": "unary", "operator": "argmin", "params": {}}, {"op_id": 18, "values": [{"value": [[0.3631658436056542, 0.3771815454382733, 0.8594016439073865, 0.05726714637859631, 0.4275623426080757], [0.8375701874521871, 0.4592154167090502, 0.682271933358012, 0.5646052761737979, 0.26399901595145336], [0.46822458979223125, 0.18634697752694784, 0.5732316786217657, 0.6056714628174507, 0.5282834154533805], [0.7201091813882491, 0.7241274529709905, 0.4056821924851388, 0.07909783412481852, 0.9617915703759579], [0.30318477338898864, 0.1818823691577709, 0.6103249343557879, 0.39524829285981566, 0.5800398402795025]]}], "op_type": "unary", "operator": "transpose", "params": {}}, {"op_id": 20, "values": [{"value": [[0.3631658436056542, 0.3771815454382733, 0.8594016439073865, 0.05726714637859631, 0.4275623426080757], [0.8375701874521871, 0.4592154167090502, 0.682271933358012, 0.5646052761737979, 0.26399901595145336], [0.46822458979223125, 0.18634697752694784, 0.5732316786217657, 0.6056714628174507, 0.5282834154533805], [0.7201091813882491, 0.7241274529709905, 0.4056821924851388, 0.07909783412481852, 0.9617915703759579], [0.30318477338898864, 0.1818823691577709, 0.6103249343557879, 0.39524829285981566, 0.5800398402795025]]}], "op_type": "unary", "operator": "exponential", "params": {}}, {"op_id": 22, "values": [{"value": [[0.3631658436056542, 0.3771815454382733, 0.8594016439073865, 0.05726714637859631, 0.4275623426080757], [0.8375701874521871, 0.4592154167090502, 0.682271933358012, 0.5646052761737979, 0.26399901595145336], [0.46822458979223125, 0.18634697752694784, 0.5732316786217657, 0.6056714628174507, 0.5282834154533805], [0.7201091813882491, 0.7241274529709905, 0.4056821924851388, 0.07909783412481852, 0.9617915703759579], [0.30318477338898864, 0.1818823691577709, 0.6103249343557879, 0.39524829285981566, 0.5800398402795025]]}], "op_type": "unary", "operator": "sort", "params": {}}, {"op_id": 25, "values": [{"value": [[0.2354680068490863, 0.32564373263284785, 0.5046043720985676, 0.22498776205647286, 0.5087178637009997], [0.08929225852698419, 0.10393547471624143, 0.3034992265525378, 0.6318728499269698, 0.09378128963350707], [0.9715402429374916, 0.4821253826417833, 0.34844373662083095, 0.2528055071171341, 0.2573120529971662], [0.7824634922839924, 0.9311343807629027, 0.968404179646788, 0.4992724266843359, 0.8375160726585381], [0.021473677988800843, 0.44050088265597964, 0.283720364881099, 0.37163149181647237, 0.6998221781668889]]}, {"value": [[0.2354680068490863, 0.32564373263284785, 0.5046043720985676, 0.22498776205647286, 0.5087178637009997], [0.08929225852698419, 0.10393547471624143, 0.3034992265525378, 0.6318728499269698, 0.09378128963350707], [0.9715402429374916, 0.4821253826417833, 0.34844373662083095, 0.2528055071171341, 0.2573120529971662], [0.7824634922839924, 0.9311343807629027, 0.968404179646788, 0.4992724266843359, 0.8375160726585381], [0.021473677988800843, 0.44050088265597964, 0.283720364881099, 0.37163149181647237, 0.6998221781668889]]}], "op_type": "binary", "operator": "addition", "params": {}}, {"op_id": 28, "values": [{"value": [[0.2354680068490863, 0.32564373263284785, 0.5046043720985676, 0.22498776205647286, 0.5087178637009997], [0.08929225852698419, 0.10393547471624143, 0.3034992265525378, 0.6318728499269698, 0.09378128963350707], [0.9715402429374916, 0.4821253826417833, 0.34844373662083095, 0.2528055071171341, 0.2573120529971662], [0.7824634922839924, 0.9311343807629027, 0.968404179646788, 0.4992724266843359, 0.8375160726585381], [0.021473677988800843, 0.44050088265597964, 0.283720364881099, 0.37163149181647237, 0.6998221781668889]]}, {"value": [[0.2354680068490863, 0.32564373263284785, 0.5046043720985676, 0.22498776205647286, 0.5087178637009997], [0.08929225852698419, 0.10393547471624143, 0.3034992265525378, 0.6318728499269698, 0.09378128963350707], [0.9715402429374916, 0.4821253826417833, 0.34844373662083095, 0.2528055071171341, 0.2573120529971662], [0.7824634922839924, 0.9311343807629027, 0.968404179646788, 0.4992724266843359, 0.8375160726585381], [0.021473677988800843, 0.44050088265597964, 0.283720364881099, 0.37163149181647237, 0.6998221781668889]]}], "op_type": "binary", "operator": "subtraction", "params": {}}, {"op_id": 31, "values": [{"value": [[0.2354680068490863, 0.32564373263284785, 0.5046043720985676, 0.22498776205647286, 0.5087178637009997], [0.08929225852698419, 0.10393547471624143, 0.3034992265525378, 0.6318728499269698, 0.09378128963350707], [0.9715402429374916, 0.4821253826417833, 0.34844373662083095, 0.2528055071171341, 0.2573120529971662], [0.7824634922839924, 0.9311343807629027, 0.968404179646788, 0.4992724266843359, 0.8375160726585381], [0.021473677988800843, 0.44050088265597964, 0.283720364881099, 0.37163149181647237, 0.6998221781668889]]}, {"value": [[0.2354680068490863, 0.32564373263284785, 0.5046043720985676, 0.22498776205647286, 0.5087178637009997], [0.08929225852698419, 0.10393547471624143, 0.3034992265525378, 0.6318728499269698, 0.09378128963350707], [0.9715402429374916, 0.4821253826417833, 0.34844373662083095, 0.2528055071171341, 0.2573120529971662], [0.7824634922839924, 0.9311343807629027, 0.968404179646788, 0.4992724266843359, 0.8375160726585381], [0.021473677988800843, 0.44050088265597964, 0.283720364881099, 0.37163149181647237, 0.6998221781668889]]}], "op_type": "binary", "operator": "multiplication", "params": {}}, {"op_id": 34, "values": [{"value": [[0.2354680068490863, 0.32564373263284785, 0.5046043720985676, 0.22498776205647286, 0.5087178637009997], [0.08929225852698419, 0.10393547471624143, 0.3034992265525378, 0.6318728499269698, 0.09378128963350707], [0.9715402429374916, 0.4821253826417833, 0.34844373662083095, 0.2528055071171341, 0.2573120529971662], [0.7824634922839924, 0.9311343807629027, 0.968404179646788, 0.4992724266843359, 0.8375160726585381], [0.021473677988800843, 0.44050088265597964, 0.283720364881099, 0.37163149181647237, 0.6998221781668889]]}, {"value": [[0.2354680068490863, 0.32564373263284785, 0.5046043720985676, 0.22498776205647286, 0.5087178637009997], [0.08929225852698419, 0.10393547471624143, 0.3034992265525378, 0.6318728499269698, 0.09378128963350707], [0.9715402429374916, 0.4821253826417833, 0.34844373662083095, 0.2528055071171341, 0.2573120529971662], [0.7824634922839924, 0.9311343807629027, 0.968404179646788, 0.4992724266843359, 0.8375160726585381], [0.021473677988800843, 0.44050088265597964, 0.283720364881099, 0.37163149181647237, 0.6998221781668889]]}], "op_type": "binary", "operator": "power", "params": {}}] \ No newline at end of file +[{"op_id": 2, "values": [{"value": [[0.3631658436056542, 0.3771815454382733, 0.8594016439073865, 0.05726714637859631, 0.4275623426080757], [0.8375701874521871, 0.4592154167090502, 0.682271933358012, 0.5646052761737979, 0.26399901595145336], [0.46822458979223125, 0.18634697752694784, 0.5732316786217657, 0.6056714628174507, 0.5282834154533805], [0.7201091813882491, 0.7241274529709905, 0.4056821924851388, 0.07909783412481852, 0.9617915703759579], [0.30318477338898864, 0.1818823691577709, 0.6103249343557879, 0.39524829285981566, 0.5800398402795025]]}], "op_type": "unary", "operator": "square", "params": {}}, {"op_id": 4, "values": [{"value": [[0.3631658436056542, 0.3771815454382733, 0.8594016439073865, 0.05726714637859631, 0.4275623426080757], [0.8375701874521871, 0.4592154167090502, 0.682271933358012, 0.5646052761737979, 0.26399901595145336], [0.46822458979223125, 0.18634697752694784, 0.5732316786217657, 0.6056714628174507, 0.5282834154533805], [0.7201091813882491, 0.7241274529709905, 0.4056821924851388, 0.07909783412481852, 0.9617915703759579], [0.30318477338898864, 0.1818823691577709, 0.6103249343557879, 0.39524829285981566, 0.5800398402795025]]}], "op_type": "unary", "operator": "cube_root", "params": {}}, {"op_id": 6, "values": [{"value": [[0.3631658436056542, 0.3771815454382733, 0.8594016439073865, 0.05726714637859631, 0.4275623426080757], [0.8375701874521871, 0.4592154167090502, 0.682271933358012, 0.5646052761737979, 0.26399901595145336], [0.46822458979223125, 0.18634697752694784, 0.5732316786217657, 0.6056714628174507, 0.5282834154533805], [0.7201091813882491, 0.7241274529709905, 0.4056821924851388, 0.07909783412481852, 0.9617915703759579], [0.30318477338898864, 0.1818823691577709, 0.6103249343557879, 0.39524829285981566, 0.5800398402795025]]}], "op_type": "unary", "operator": "absolute", "params": {}}, {"op_id": 8, "values": [{"value": [[0.3631658436056542, 0.3771815454382733, 0.8594016439073865, 0.05726714637859631, 0.4275623426080757], [0.8375701874521871, 0.4592154167090502, 0.682271933358012, 0.5646052761737979, 0.26399901595145336], [0.46822458979223125, 0.18634697752694784, 0.5732316786217657, 0.6056714628174507, 0.5282834154533805], [0.7201091813882491, 0.7241274529709905, 0.4056821924851388, 0.07909783412481852, 0.9617915703759579], [0.30318477338898864, 0.1818823691577709, 0.6103249343557879, 0.39524829285981566, 0.5800398402795025]]}], "op_type": "unary", "operator": "matrix_sum", "params": {}}, {"op_id": 10, "values": [{"value": [[0.3631658436056542, 0.3771815454382733, 0.8594016439073865, 0.05726714637859631, 0.4275623426080757], [0.8375701874521871, 0.4592154167090502, 0.682271933358012, 0.5646052761737979, 0.26399901595145336], [0.46822458979223125, 0.18634697752694784, 0.5732316786217657, 0.6056714628174507, 0.5282834154533805], [0.7201091813882491, 0.7241274529709905, 0.4056821924851388, 0.07909783412481852, 0.9617915703759579], [0.30318477338898864, 0.1818823691577709, 0.6103249343557879, 0.39524829285981566, 0.5800398402795025]]}], "op_type": "unary", "operator": "min", "params": {}}, {"op_id": 12, "values": [{"value": [[0.3631658436056542, 0.3771815454382733, 0.8594016439073865, 0.05726714637859631, 0.4275623426080757], [0.8375701874521871, 0.4592154167090502, 0.682271933358012, 0.5646052761737979, 0.26399901595145336], [0.46822458979223125, 0.18634697752694784, 0.5732316786217657, 0.6056714628174507, 0.5282834154533805], [0.7201091813882491, 0.7241274529709905, 0.4056821924851388, 0.07909783412481852, 0.9617915703759579], [0.30318477338898864, 0.1818823691577709, 0.6103249343557879, 0.39524829285981566, 0.5800398402795025]]}], "op_type": "unary", "operator": "max", "params": {}}, {"op_id": 14, "values": [{"value": [[0.3631658436056542, 0.3771815454382733, 0.8594016439073865, 0.05726714637859631, 0.4275623426080757], [0.8375701874521871, 0.4592154167090502, 0.682271933358012, 0.5646052761737979, 0.26399901595145336], [0.46822458979223125, 0.18634697752694784, 0.5732316786217657, 0.6056714628174507, 0.5282834154533805], [0.7201091813882491, 0.7241274529709905, 0.4056821924851388, 0.07909783412481852, 0.9617915703759579], [0.30318477338898864, 0.1818823691577709, 0.6103249343557879, 0.39524829285981566, 0.5800398402795025]]}], "op_type": "unary", "operator": "argmax", "params": {}}, {"op_id": 16, "values": [{"value": [[0.3631658436056542, 0.3771815454382733, 0.8594016439073865, 0.05726714637859631, 0.4275623426080757], [0.8375701874521871, 0.4592154167090502, 0.682271933358012, 0.5646052761737979, 0.26399901595145336], [0.46822458979223125, 0.18634697752694784, 0.5732316786217657, 0.6056714628174507, 0.5282834154533805], [0.7201091813882491, 0.7241274529709905, 0.4056821924851388, 0.07909783412481852, 0.9617915703759579], [0.30318477338898864, 0.1818823691577709, 0.6103249343557879, 0.39524829285981566, 0.5800398402795025]]}], "op_type": "unary", "operator": "argmin", "params": {}}, {"op_id": 18, "values": [{"value": [[0.3631658436056542, 0.3771815454382733, 0.8594016439073865, 0.05726714637859631, 0.4275623426080757], [0.8375701874521871, 0.4592154167090502, 0.682271933358012, 0.5646052761737979, 0.26399901595145336], [0.46822458979223125, 0.18634697752694784, 0.5732316786217657, 0.6056714628174507, 0.5282834154533805], [0.7201091813882491, 0.7241274529709905, 0.4056821924851388, 0.07909783412481852, 0.9617915703759579], [0.30318477338898864, 0.1818823691577709, 0.6103249343557879, 0.39524829285981566, 0.5800398402795025]]}], "op_type": "unary", "operator": "transpose", "params": {}}, {"op_id": 20, "values": [{"value": [[0.3631658436056542, 0.3771815454382733, 0.8594016439073865, 0.05726714637859631, 0.4275623426080757], [0.8375701874521871, 0.4592154167090502, 0.682271933358012, 0.5646052761737979, 0.26399901595145336], [0.46822458979223125, 0.18634697752694784, 0.5732316786217657, 0.6056714628174507, 0.5282834154533805], [0.7201091813882491, 0.7241274529709905, 0.4056821924851388, 0.07909783412481852, 0.9617915703759579], [0.30318477338898864, 0.1818823691577709, 0.6103249343557879, 0.39524829285981566, 0.5800398402795025]]}], "op_type": "unary", "operator": "exponential", "params": {}}, {"op_id": 22, "values": [{"value": [[0.3631658436056542, 0.3771815454382733, 0.8594016439073865, 0.05726714637859631, 0.4275623426080757], [0.8375701874521871, 0.4592154167090502, 0.682271933358012, 0.5646052761737979, 0.26399901595145336], [0.46822458979223125, 0.18634697752694784, 0.5732316786217657, 0.6056714628174507, 0.5282834154533805], [0.7201091813882491, 0.7241274529709905, 0.4056821924851388, 0.07909783412481852, 0.9617915703759579], [0.30318477338898864, 0.1818823691577709, 0.6103249343557879, 0.39524829285981566, 0.5800398402795025]]}], "op_type": "unary", "operator": "sort", "params": {}}, {"op_id": 25, "values": [{"value": [[0.2354680068490863, 0.32564373263284785, 0.5046043720985676, 0.22498776205647286, 0.5087178637009997], [0.08929225852698419, 0.10393547471624143, 0.3034992265525378, 0.6318728499269698, 0.09378128963350707], [0.9715402429374916, 0.4821253826417833, 0.34844373662083095, 0.2528055071171341, 0.2573120529971662], [0.7824634922839924, 0.9311343807629027, 0.968404179646788, 0.4992724266843359, 0.8375160726585381], [0.021473677988800843, 0.44050088265597964, 0.283720364881099, 0.37163149181647237, 0.6998221781668889]]}, {"value": [[0.2354680068490863, 0.32564373263284785, 0.5046043720985676, 0.22498776205647286, 0.5087178637009997], [0.08929225852698419, 0.10393547471624143, 0.3034992265525378, 0.6318728499269698, 0.09378128963350707], [0.9715402429374916, 0.4821253826417833, 0.34844373662083095, 0.2528055071171341, 0.2573120529971662], [0.7824634922839924, 0.9311343807629027, 0.968404179646788, 0.4992724266843359, 0.8375160726585381], [0.021473677988800843, 0.44050088265597964, 0.283720364881099, 0.37163149181647237, 0.6998221781668889]]}], "op_type": "binary", "operator": "addition", "params": {}}, {"op_id": 28, "values": [{"value": [[0.2354680068490863, 0.32564373263284785, 0.5046043720985676, 0.22498776205647286, 0.5087178637009997], [0.08929225852698419, 0.10393547471624143, 0.3034992265525378, 0.6318728499269698, 0.09378128963350707], [0.9715402429374916, 0.4821253826417833, 0.34844373662083095, 0.2528055071171341, 0.2573120529971662], [0.7824634922839924, 0.9311343807629027, 0.968404179646788, 0.4992724266843359, 0.8375160726585381], [0.021473677988800843, 0.44050088265597964, 0.283720364881099, 0.37163149181647237, 0.6998221781668889]]}, {"value": [[0.2354680068490863, 0.32564373263284785, 0.5046043720985676, 0.22498776205647286, 0.5087178637009997], [0.08929225852698419, 0.10393547471624143, 0.3034992265525378, 0.6318728499269698, 0.09378128963350707], [0.9715402429374916, 0.4821253826417833, 0.34844373662083095, 0.2528055071171341, 0.2573120529971662], [0.7824634922839924, 0.9311343807629027, 0.968404179646788, 0.4992724266843359, 0.8375160726585381], [0.021473677988800843, 0.44050088265597964, 0.283720364881099, 0.37163149181647237, 0.6998221781668889]]}], "op_type": "binary", "operator": "subtraction", "params": {}}, {"op_id": 31, "values": [{"value": [[0.2354680068490863, 0.32564373263284785, 0.5046043720985676, 0.22498776205647286, 0.5087178637009997], [0.08929225852698419, 0.10393547471624143, 0.3034992265525378, 0.6318728499269698, 0.09378128963350707], [0.9715402429374916, 0.4821253826417833, 0.34844373662083095, 0.2528055071171341, 0.2573120529971662], [0.7824634922839924, 0.9311343807629027, 0.968404179646788, 0.4992724266843359, 0.8375160726585381], [0.021473677988800843, 0.44050088265597964, 0.283720364881099, 0.37163149181647237, 0.6998221781668889]]}, {"value": [[0.2354680068490863, 0.32564373263284785, 0.5046043720985676, 0.22498776205647286, 0.5087178637009997], [0.08929225852698419, 0.10393547471624143, 0.3034992265525378, 0.6318728499269698, 0.09378128963350707], [0.9715402429374916, 0.4821253826417833, 0.34844373662083095, 0.2528055071171341, 0.2573120529971662], [0.7824634922839924, 0.9311343807629027, 0.968404179646788, 0.4992724266843359, 0.8375160726585381], [0.021473677988800843, 0.44050088265597964, 0.283720364881099, 0.37163149181647237, 0.6998221781668889]]}], "op_type": "binary", "operator": "multiplication", "params": {}}, {"op_id": 34, "values": [{"value": [[0.2354680068490863, 0.32564373263284785, 0.5046043720985676, 0.22498776205647286, 0.5087178637009997], [0.08929225852698419, 0.10393547471624143, 0.3034992265525378, 0.6318728499269698, 0.09378128963350707], [0.9715402429374916, 0.4821253826417833, 0.34844373662083095, 0.2528055071171341, 0.2573120529971662], [0.7824634922839924, 0.9311343807629027, 0.968404179646788, 0.4992724266843359, 0.8375160726585381], [0.021473677988800843, 0.44050088265597964, 0.283720364881099, 0.37163149181647237, 0.6998221781668889]]}, {"value": [[0.2354680068490863, 0.32564373263284785, 0.5046043720985676, 0.22498776205647286, 0.5087178637009997], [0.08929225852698419, 0.10393547471624143, 0.3034992265525378, 0.6318728499269698, 0.09378128963350707], [0.9715402429374916, 0.4821253826417833, 0.34844373662083095, 0.2528055071171341, 0.2573120529971662], [0.7824634922839924, 0.9311343807629027, 0.968404179646788, 0.4992724266843359, 0.8375160726585381], [0.021473677988800843, 0.44050088265597964, 0.283720364881099, 0.37163149181647237, 0.6998221781668889]]}], "op_type": "binary", "operator": "power", "params": {}}] diff --git a/ravpy/distributed/benchmarking.py b/ravpy/distributed/benchmarking.py index 57344bc..d492263 100644 --- a/ravpy/distributed/benchmarking.py +++ b/ravpy/distributed/benchmarking.py @@ -36,7 +36,7 @@ def benchmark(): if file.endswith(".zip"): os.remove(file) - g.logger.debug("Benchmarking completed successfully, emitting results...") + g.logger.debug("Benchmarking completed successfully!") g.logger.debug("Emitting Benchmark Results...") client.emit("benchmark_callback", data=json.dumps(benchmark_results), namespace="/client") client.sleep(1) diff --git a/ravpy/distributed/compute.py b/ravpy/distributed/compute.py index 6bb2444..1567644 100644 --- a/ravpy/distributed/compute.py +++ b/ravpy/distributed/compute.py @@ -401,11 +401,10 @@ def upload_result(payload, result, subgraph_id=None, graph_id=None): def emit_error(payload, error, subgraph_id, graph_id): - print("Emit Error") g.error = True error = str(error) client = g.client - print(error, payload) + print("Emmit error:{}".format(str(error))) client.emit("op_completed", json.dumps({ 'op_type': payload["op_type"], 'error': error, diff --git a/ravpy/distributed/evaluate.py b/ravpy/distributed/evaluate.py index c65add6..7c30e31 100644 --- a/ravpy/distributed/evaluate.py +++ b/ravpy/distributed/evaluate.py @@ -18,13 +18,19 @@ @g.client.on('subgraph', namespace="/client") def compute_subgraph(d): global client, timeoutId - + g.logger.debug("") + g.logger.debug("Subgraph received!") + g.logger.debug("Graph id: {}, subgraph id: {}".format(d['graph_id'], d["subgraph_id"])) os.system('clear') # print("Received Subgraph : ",d["subgraph_id"]," of Graph : ",d["graph_id"]) print(AsciiTable([['Provider Dashboard']]).table) g.dashboard_data.append([d["subgraph_id"], d["graph_id"], "Computing"]) print(AsciiTable(g.dashboard_data).table) + # create a subgraph row in database + subgraph_obj = g.ravdb.add_subgraph(graph_id=d["graph_id"], subgraph_id=d["subgraph_id"], status="Computing") + g.ravdb.update_subgraph(subgraph=subgraph_obj, status="Computing") + g.has_subgraph = True subgraph_id = d["subgraph_id"] graph_id = d["graph_id"] @@ -46,6 +52,13 @@ def compute_subgraph(d): g.dashboard_data[-1][2] = "Failed" print(AsciiTable([['Provider Dashboard']]).table) print(AsciiTable(g.dashboard_data).table) + + # update subgraph + g.ravdb.update_subgraph(subgraph=subgraph_obj, + status="Failed") + + g.logger.debug("Error: {}".format(str(error))) + g.has_subgraph = False stopTimer(timeoutId) timeoutId = setTimeout(waitInterval, opTimeout) @@ -57,10 +70,9 @@ def compute_subgraph(d): g.delete_files_list = [] g.outputs = {} - print('Error: ', error) emit_error(data[0], error, subgraph_id, graph_id) if 'broken pipe' in str(error).lower() or '421' in str(error).lower(): - print( + g.logger.debug( '\n\nYou have encountered an IO based Broken Pipe Error. \nRestart terminal and try connecting again') sys.exit() @@ -72,16 +84,20 @@ def compute_subgraph(d): os.remove(download_path) g.ftp_client.delete_file(server_file_path) - for index in data: + for index, op_obj in enumerate(data): # Perform - operation_type = index["op_type"] - operator = index["operator"] + operation_type = op_obj["op_type"] + operator = op_obj["operator"] if operation_type is not None and operator is not None: - result_payload = compute_locally(index, subgraph_id, graph_id) + result_payload = compute_locally(op_obj, subgraph_id, graph_id) if not g.error: results.append(result_payload) + + # update subgraph + g.ravdb.update_subgraph(subgraph=subgraph_obj, + progress=((index+1)/len(data))*100) else: break @@ -103,6 +119,12 @@ def compute_subgraph(d): print(AsciiTable([['Provider Dashboard']]).table) print(AsciiTable(g.dashboard_data).table) + # update subgraph + g.ravdb.update_subgraph(subgraph=subgraph_obj, + status="Computed") + + g.logger.debug("Subgraph computed successfully") + g.has_subgraph = False stopTimer(timeoutId) diff --git a/ravpy/distributed/participate.py b/ravpy/distributed/participate.py index 2a15bf4..5fb1801 100644 --- a/ravpy/distributed/participate.py +++ b/ravpy/distributed/participate.py @@ -1,19 +1,21 @@ import os +import eel from ..globals import g -from ..utils import initialize_ftp_client +from ..utils import initialize_ftp_client, disconnect def participate(): # Initialize and create FTP client res = initialize_ftp_client() if res is None: - os._exit(1) + g.logger.error("quitting") + disconnect() + else: + from .benchmarking import benchmark + benchmark() - from .benchmarking import benchmark - benchmark() - - g.logger.debug("") - g.logger.debug("Ravpy is waiting for ops and subgraphs...") - g.logger.debug("Warning: Do not close this terminal if you like to " - "keep participating and keep earning Raven tokens\n") + g.logger.debug("") + g.logger.debug("Ravpy is waiting for ops and subgraphs...") + g.logger.debug("Warning: Do not close this terminal if you like to " + "keep participating and keep earning Raven tokens\n") diff --git a/ravpy/globals.py b/ravpy/globals.py index 183cf23..1e52df7 100644 --- a/ravpy/globals.py +++ b/ravpy/globals.py @@ -2,6 +2,7 @@ import socketio from .config import RAVENVERSE_URL, TYPE, RAVENVERSE_FTP_URL +from .db import DBManager from .logger import get_logger from .singleton_utils import Singleton @@ -50,6 +51,9 @@ def __init__(self): self._ravenverse_token = None self._logger = get_logger() self._dashboard_data = [['Subgraph ID', 'Graph ID', 'Status']] + self._ravdb = DBManager() + self._ravdb.logger = self._logger + self._socket_client = self.get_socket_client() @property def timeoutId(self): @@ -93,19 +97,17 @@ def ops(self, ops): @property def client(self): - if self._client is not None: - return self._client - - if self._client is None: - self._client = self.get_socket_client() - return self._client + return self._client def get_socket_client(self): from .socket import SocketClient - socket_client = SocketClient() - socket_client.connect(self._ravenverse_token) - self._client = socket_client.client - return self._client + self._socket_client = SocketClient(self.logger) + self._client = self._socket_client.client + return self._socket_client + + def connect_socket_client(self): + self._socket_client.connect(self._ravenverse_token) + self._client = self._socket_client.client @property def ftp_client(self): @@ -163,5 +165,9 @@ def dashboard_data(self): def dashboard_data(self, dashboard_data): self._dashboard_data = dashboard_data + @property + def ravdb(self): + return self._ravdb + g = Globals.Instance() diff --git a/ravpy/initialize.py b/ravpy/initialize.py index 82ba642..6a68020 100644 --- a/ravpy/initialize.py +++ b/ravpy/initialize.py @@ -5,7 +5,7 @@ def exit_handler(): - g.logger.debug('Application is Closing!') + g.logger.debug('Application is closing!') if g.client is not None: g.logger.debug("Disconnecting...") if g.client.connected: @@ -19,7 +19,8 @@ def initialize(ravenverse_token): g.logger.debug("Initializing...") g.ravenverse_token = ravenverse_token - client = g.get_socket_client() + g.connect_socket_client() + client = g.client if not client.connected: g.client.disconnect() g.logger.error("Unable to connect to ravenverse. Make sure you are using the right hostname and port") diff --git a/ravpy/socket/socket_client.py b/ravpy/socket/socket_client.py index cf62641..8c2432f 100644 --- a/ravpy/socket/socket_client.py +++ b/ravpy/socket/socket_client.py @@ -1,35 +1,42 @@ import socketio from socketio.exceptions import ConnectionError from ravpy.config import RAVENVERSE_URL, TYPE, RAVENVERSE_FTP_URL -from ravpy.globals import g class SocketNamespace(socketio.ClientNamespace): + def __init__(self, namespace, logger): + super().__init__(namespace) + self.logger = logger + def on_connect(self): - g.logger.debug('Connected to Ravenverse successfully!') + self.logger.debug('Connected to Ravenverse successfully!') def on_disconnect(self): - g.logger.debug('Disconnected from the server') + self.logger.debug('Disconnected from the server') def on_message(self, data): - g.logger.debug('Message received:', data) + self.logger.debug('Message received:', data) def on_result(self, data): - g.logger.debug(data) + self.logger.debug(data) def on_connect_error(self, e): - g.logger.debug("Error:{}".format(str(e))) + self.logger.debug("Error:{}".format(str(e))) class SocketClient(object): - def __init__(self): + def __init__(self, logger): self._client = socketio.Client(logger=False, request_timeout=100, engineio_logger=False) - self._client.register_namespace(SocketNamespace('/client')) + self._client.register_namespace(SocketNamespace('/client', logger)) + self.logger = logger + + def set_logger(self, logger): + self.logger = logger def connect(self, token): - g.logger.debug("Connecting to Ravenverse...") - g.logger.debug("Ravenverse url: {}?type={}".format(RAVENVERSE_URL, TYPE)) - g.logger.debug("Ravenverse FTP host: {}".format(RAVENVERSE_FTP_URL)) + self.logger.debug("Connecting to Ravenverse...") + self.logger.debug("Ravenverse url: {}?type={}".format(RAVENVERSE_URL, TYPE)) + self.logger.debug("Ravenverse FTP host: {}".format(RAVENVERSE_FTP_URL)) auth_headers = {"token": token} try: @@ -38,11 +45,11 @@ def connect(self, token): transports=['websocket'], namespaces=['/client'], wait_timeout=100) except ConnectionError as e: - g.logger.error("Error: Unable to connect to Ravenverse. " + self.logger.error("Error: Unable to connect to Ravenverse. " "Make sure you are using the right hostname and port. \n{}".format(e)) self._client.disconnect() except Exception as e: - g.logger.error("Error: Unable to connect to Ravenverse. " + self.logger.error("Error: Unable to connect to Ravenverse. " "Make sure you are using the right hostname and port. \n{}".format(e)) self._client.disconnect() diff --git a/ravpy/utils.py b/ravpy/utils.py index be8fe0d..6c23e28 100644 --- a/ravpy/utils.py +++ b/ravpy/utils.py @@ -84,6 +84,7 @@ def get_ftp_credentials(): else: g.logger.debug("Unable to fetch ftp credentials. Try again after some time or " "contact our team at team@ravenprotocol.com") + g.logger.debug(r.text) return None @@ -271,3 +272,13 @@ def verify_token(token): return False else: return True + + +def disconnect(): + if g.client.connected: + g.logger.debug("Disconnecting...") + if g.client.connected: + g.client.emit("disconnect", namespace="/client") + g.logger.debug("Disconnected") + + return True diff --git a/requirements.txt b/requirements.txt index cd0381f..4ac95ea 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,8 +9,13 @@ scipy speedtest-cli terminaltables==3.1.10 websocket-client -pyinstaller scikit-learn eel psutil hurry.filesize +sqlalchemy +sqlalchemy-utils +pillow +tinyaes +pyinstaller[encryption] +dmgbuild diff --git a/settings.py b/settings.py new file mode 100644 index 0000000..fd0b08c --- /dev/null +++ b/settings.py @@ -0,0 +1,266 @@ +import os.path + +# +# Example settings file for dmgbuild +# + +# Use like this: dmgbuild -s settings.py "Test Volume" test.dmg + +# You can actually use this file for your own application (not just TextEdit) +# by doing e.g. +# +# dmgbuild -s settings.py -D app=/path/to/My.app "My Application" MyApp.dmg + +# .. Useful stuff .............................................................. +import plistlib + +application = defines.get("app", "dist/ui2.app") # noqa: F821 +appname = os.path.basename(application) + + +def icon_from_app(app_path): + plist_path = os.path.join(app_path, "Contents", "Info.plist") + with open(plist_path, "rb") as f: + plist = plistlib.load(f) + icon_name = plist["CFBundleIconFile"] + icon_root, icon_ext = os.path.splitext(icon_name) + if not icon_ext: + icon_ext = ".icns" + icon_name = icon_root + icon_ext + return os.path.join(app_path, "Contents", "Resources", icon_name) + + +# .. Basics .................................................................... + +# Uncomment to override the output filename +# filename = 'test.dmg' + +# Uncomment to override the output volume name +# volume_name = 'Test' + +# Volume format (see hdiutil create -help) +format = defines.get("format", "UDBZ") # noqa: F821 + +# Compression level (if relevant) +# compression_level = 9 + +# Volume size +size = defines.get("size", None) # noqa: F821 + +# Files to include +files = [application] + +# Symlinks to create +symlinks = {"Applications": "/Applications"} + +# Files to hide +# hide = [ 'Secret.data' ] + +# Files to hide the extension of +# hide_extension = [ 'README.rst' ] + +# Volume icon +# +# You can either define icon, in which case that icon file will be copied to the +# image, *or* you can define badge_icon, in which case the icon file you specify +# will be used to badge the system's Removable Disk icon. Badge icons require +# pyobjc-framework-Quartz. +# +# icon = '/path/to/icon.icns' +badge_icon = icon_from_app(application) + +# Where to put the icons +icon_locations = {appname: (140, 120), "Applications": (500, 120)} + +# .. Window configuration ...................................................... + +# Background +# +# This is a STRING containing any of the following: +# +# #3344ff - web-style RGB color +# #34f - web-style RGB color, short form (#34f == #3344ff) +# rgb(1,0,0) - RGB color, each value is between 0 and 1 +# hsl(120,1,.5) - HSL (hue saturation lightness) color +# hwb(300,0,0) - HWB (hue whiteness blackness) color +# cmyk(0,1,0,0) - CMYK color +# goldenrod - X11/SVG named color +# builtin-arrow - A simple built-in background with a blue arrow +# /foo/bar/baz.png - The path to an image file +# +# The hue component in hsl() and hwb() may include a unit; it defaults to +# degrees ('deg'), but also supports radians ('rad') and gradians ('grad' +# or 'gon'). +# +# Other color components may be expressed either in the range 0 to 1, or +# as percentages (e.g. 60% is equivalent to 0.6). +background = "builtin-arrow" + +show_status_bar = False +show_tab_view = False +show_toolbar = False +show_pathbar = False +show_sidebar = False +sidebar_width = 180 + +# Window position in ((x, y), (w, h)) format +window_rect = ((100, 100), (640, 280)) + +# Select the default view; must be one of +# +# 'icon-view' +# 'list-view' +# 'column-view' +# 'coverflow' +# +default_view = "icon-view" + +# General view configuration +show_icon_preview = False + +# Set these to True to force inclusion of icon/list view settings (otherwise +# we only include settings for the default view) +include_icon_view_settings = "auto" +include_list_view_settings = "auto" + +# .. Icon view configuration ................................................... + +arrange_by = None +grid_offset = (0, 0) +grid_spacing = 100 +scroll_position = (0, 0) +label_pos = "bottom" # or 'right' +text_size = 16 +icon_size = 128 + +# .. List view configuration ................................................... + +# Column names are as follows: +# +# name +# date-modified +# date-created +# date-added +# date-last-opened +# size +# kind +# label +# version +# comments +# +list_icon_size = 16 +list_text_size = 12 +list_scroll_position = (0, 0) +list_sort_by = "name" +list_use_relative_dates = True +list_calculate_all_sizes = (False,) +list_columns = ("name", "date-modified", "size", "kind", "date-added") +list_column_widths = { + "name": 300, + "date-modified": 181, + "date-created": 181, + "date-added": 181, + "date-last-opened": 181, + "size": 97, + "kind": 115, + "label": 100, + "version": 75, + "comments": 300, +} +list_column_sort_directions = { + "name": "ascending", + "date-modified": "descending", + "date-created": "descending", + "date-added": "descending", + "date-last-opened": "descending", + "size": "descending", + "kind": "ascending", + "label": "ascending", + "version": "ascending", + "comments": "ascending", +} + +# .. License configuration ..................................................... + +# Text in the license configuration is stored in the resources, which means +# it gets stored in a legacy Mac encoding according to the language. dmgbuild +# will *try* to convert Unicode strings to the appropriate encoding, *but* +# you should be aware that Python doesn't support all of the necessary encodings; +# in many cases you will need to encode the text yourself and use byte strings +# instead here. + +# Recognized language names are: +# +# af_ZA, ar, be_BY, bg_BG, bn, bo, br, ca_ES, cs_CZ, cy, da_DK, de_AT, de_CH, +# de_DE, dz_BT, el_CY, el_GR, en_AU, en_CA, en_GB, en_IE, en_SG, en_US, eo, +# es_419, es_ES, et_EE, fa_IR, fi_FI, fo_FO, fr_001, fr_BE, fr_CA, fr_CH, +# fr_FR, ga-Latg_IE, ga_IE, gd, grc, gu_IN, gv, he_IL, hi_IN, hr_HR, hu_HU, +# hy_AM, is_IS, it_CH, it_IT, iu_CA, ja_JP, ka_GE, kl, ko_KR, lt_LT, lv_LV, +# mk_MK, mr_IN, mt_MT, nb_NO, ne_NP, nl_BE, nl_NL, nn_NO, pa, pl_PL, pt_BR, +# pt_PT, ro_RO, ru_RU, se, sk_SK, sl_SI, sr_RS, sv_SE, th_TH, to_TO, tr_TR, +# uk_UA, ur_IN, ur_PK, uz_UZ, vi_VN, zh_CN, zh_TW + +license = { + "default-language": "en_US", + "licenses": { + # For each language, the text of the license. This can be plain text, + # RTF (in which case it must start "{\rtf1"), or a path to a file + # containing the license text. If you're using RTF, + # watch out for Python escaping (or read it from a file). + "en_GB": b"""{\\rtf1\\ansi\\ansicpg1252\\cocoartf1504\\cocoasubrtf820 + {\\fonttbl\\f0\\fnil\\fcharset0 Helvetica-Bold;\\f1\\fnil\\fcharset0 Helvetica;} + {\\colortbl;\\red255\\green255\\blue255;\\red0\\green0\\blue0;} + {\\*\\expandedcolortbl;;\\cssrgb\\c0\\c0\\c0;} + \\paperw11905\\paperh16837\\margl1133\\margr1133\\margb1133\\margt1133 + \\deftab720 + \\pard\\pardeftab720\\sa160\\partightenfactor0 + + \\f0\\b\\fs60 \\cf2 \\expnd0\\expndtw0\\kerning0 + \\up0 \\nosupersub \\ulnone \\outl0\\strokewidth0 \\strokec2 Test License\\ + \\pard\\pardeftab720\\sa160\\partightenfactor0 + + \\fs36 \\cf2 \\strokec2 What is this?\\ + \\pard\\pardeftab720\\sa160\\partightenfactor0 + + \\f1\\b0\\fs22 \\cf2 \\strokec2 This is the English license. It says what you are allowed to do with this software.\\ + \\ + }""", + "de_DE": "Ich bin ein Berliner. Bielefeld gibt's doch gar nicht.", + }, + "buttons": { + # For each language, text for the buttons on the licensing window. + # + # Default buttons and text are built-in for the following languages: + # + # da_DK: Danish + # de_DE: German + # en_AU: English (Australian) + # en_GB: English (UK) + # en_NZ: English (New Zealand) + # en_US: English (US) + # es_ES: Spanish + # fr_CA: French (Canadian) + # fr_FR: French + # it_IT: Italian + # ja_JP: Japanese + # nb_NO: Norsk + # nl_BE: Flemish + # nl_NL: Dutch + # pt_BR: Brazilian Portuguese + # pt_PT: Portugese + # sv_SE: Swedish + # zh_CN: Simplified Chinese + # zh_TW: Traditional Chinese + # + # You don't need to specify them for those languages; if you fail to + # specify them for some other language, English will be used instead. + "en_US": ( + b"English", + b"Agree!", + b"Disagree!", + b"Print!", + b"Save!", + b'Do you agree or not? Press "Agree" or "Disagree".', + ), + }, +} diff --git a/setup.py b/setup.py index e472311..366ed3d 100644 --- a/setup.py +++ b/setup.py @@ -24,11 +24,16 @@ "python-socketio==5.4.1", "requests==2.27.1", "python-dotenv", - "scipy", "speedtest-cli", "terminaltables==3.1.10", "websocket-client", "pyinstaller", - "scikit-learn" - ] + "scikit-learn", + "psutil", + "hurry.filesize", + "sqlalchemy", + "sqlalchemy-utils", + ], + app=["ui2.py"], + setup_requires=["py2app"], ) diff --git a/ui2.py b/ui2.py index 316c0ac..a6a0a33 100644 --- a/ui2.py +++ b/ui2.py @@ -1,28 +1,46 @@ -import logging - import eel +import logging +import os import psutil import shutil -from dotenv import load_dotenv from hurry.filesize import size -load_dotenv() - -from ravpy.utils import verify_token -from ravpy.distributed.participate import participate -from ravpy.initialize import initialize -from ravpy.globals import g +os.environ['RAVENVERSE_URL'] = "http://server.ravenverse.ai" +os.environ['RAVENVERSE_FTP_HOST'] = "server.ravenverse.ai" +os.environ['RAVENVERSE_FTP_URL'] = "server.ravenverse.ai" +os.environ['RAVENAUTH_URL'] = "https://auth.ravenverse.ai" eel.init('web') +@eel.expose +def disconnect(): + if g.client.connected: + g.logger.debug("Disconnecting...") + if g.client.connected: + g.client.emit("disconnect", namespace="/client") + g.logger.debug("Disconnected") + g.logger.debug("") + + return True + + +def close_callback(a, b): + disconnect() + + +from ravpy.globals import g + + class CustomHandler(logging.Handler): def __init__(self): logging.Handler.__init__(self) def emit(self, record): print("Custom", record) - eel.getLog("{} [{}] {}".format(record.asctime, record.levelname, record.message)) + eel.getLog({"asctime": record.asctime, "threadName": record.threadName, "levelname": record.levelname, + "message": record.message}) + return record log_formatter = logging.Formatter("%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s] %(message)s") @@ -34,6 +52,7 @@ def emit(self, record): @eel.expose def verify_access_token(access_token): + from ravpy.utils import verify_token if verify_token(access_token): return [access_token, "success", ""] else: @@ -56,40 +75,62 @@ def get_system_config(): @eel.expose -def initialize_exposed(token): - client = initialize(token) - if client is None: +def get_logs(skip, limit): + with open("debug.log", "r") as f: + logs = f.readlines()[skip:] + print(logs) + + +@eel.expose +def participate(token): + from ravpy.distributed.benchmarking import benchmark + from ravpy.utils import initialize_ftp_client + from ravpy.initialize import initialize + + # Initialize + socket_client = initialize(ravenverse_token=token) + + if socket_client is None: + disconnect() + eel.clientDisconnected() return False else: - return True + eel.clientConnected() + # get ftp client + ftp_client = initialize_ftp_client() -@eel.expose -def participate_exposed(): - participate() + if ftp_client is None: + disconnect() + eel.clientDisconnected() + return False + # do benchmark + benchmark() -@eel.expose -def get_logs(skip, limit): - with open("debug.log", "r") as f: - logs = f.readlines()[skip:] - print(logs) + g.logger.debug("") + g.logger.debug("Ravpy is waiting for graphs/subgraphs/ops...") + g.logger.debug("Warning: Do not close Ravpy if you like to " + "keep participating and keep earning Raven tokens\n") + + return True @eel.expose -def disconnect(): - if g.client.connected: - g.logger.debug("Disconnecting...") - if g.client.connected: - g.client.emit("disconnect", namespace="/client") - g.logger.debug("Disconnected") +def get_subgraphs(): + subgraphs = g.ravdb.get_subgraphs() + subgraphs = [sg.as_dict() for sg in subgraphs] + return subgraphs + +@eel.expose +def delete_subgraphs(): + g.ravdb.delete_subgraphs() return True -def close_callback(a, b): - print("closing", a, b) - disconnect() +g.ravdb.create_database() +g.ravdb.create_tables() eel.start('main.html', close_callback=close_callback) diff --git a/web/img/ravpy-icon.png b/web/img/ravpy-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f3180a4b41be6f9c2fdb10d5a50206bd612f2957 GIT binary patch literal 37269 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 + + + + + + + + + + + + diff --git a/web/img/reload.svg b/web/img/reload.svg new file mode 100644 index 0000000..a281b0b --- /dev/null +++ b/web/img/reload.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/main.html b/web/main.html index 571b762..4eab7d0 100644 --- a/web/main.html +++ b/web/main.html @@ -107,7 +107,7 @@

-

+

Welcome, Ravenverse Provider!

Participate

-

Logs

-
+
+
+

Graphs received

+ reload +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Graph id + + Subgraph id + + Status + + Progress(%) + + Reward(Tokens) +
+ + + + + +
+
+ +
+
+
+

Logs

+
+
@@ -171,6 +233,7 @@

Logs

$("#dashboard_container").show() $("#disconnectButton").show() getSystemConfig() + getSubgraphs() } else if (status === "failure") { $("#access_token_container").show() $("#dashboard_container").hide() @@ -182,6 +245,7 @@

Logs

$("#disconnectButton").on("click", function () { localStorage.removeItem("access_token") window.location.reload() + eel.delete_subgraphs() }) $("#enterButton").on("click", function () { @@ -205,6 +269,9 @@

Logs

$("#disconnectButton").show() localStorage.setItem("access_token", access_token) getSystemConfig() + getSubgraphs() + $("#subgraphs-body").empty() + $("#logsContainer").empty() } else if (status === "failure") { console.log(message) Swal.fire({ @@ -221,10 +288,12 @@

Logs

function getSystemConfig() { eel.get_system_config()().then((result) => { console.log(result) - $("#ramConfig").append("Total: " + result.ram_total + "
Available: " + result.ram_available + "") - $("#cpuConfig").append("Total: " + result.cpu_count + "
Available(%): " + result.cpu_percent + "") - $("#storageConfig").append("Total: " + result.storage_total + "
Available: " + result.storage_available + "") + $("#ramConfig").html("Total: " + result.ram_total + "
Available: " + result.ram_available + "") + $("#cpuConfig").html("Total: " + result.cpu_count + "
Available(%): " + result.cpu_percent + "") + $("#storageConfig").html("Total: " + result.storage_total + "
Available: " + result.storage_available + "") }) + + setTimeout(getSystemConfig, 10000) } // Three steps @@ -232,31 +301,86 @@

Logs

// 2. participate $("#participateButton").on("click", function () { - eel.initialize_exposed(localStorage.getItem("access_token"))().then((initialized) => { - if(initialized){ - $("#participateTitle").text("Participating...") - $("#participateButton").hide() - $("#stopParticipation").show() - eel.participate_exposed()().then(() => { - }) + $("#participateButton span").text("Starting...") + eel.participate(localStorage.getItem("access_token"))().then((initialized) => { + if (initialized) { + $("#participateButton span").text("Start") + clientConnected() + }else{ + $("#participateButton span").text("Start") + clientDisconnected() } }) }) - $("#stopParticipation").on("click", function (){ - eel.disconnect()().then((result)=>{ - $("#participateTitle").text("Participate") - $("#participateButton").show() - $("#stopParticipation").hide() + $("#stopParticipation").on("click", function () { + eel.disconnect()().then((result) => { + clientDisconnected() }) }) eel.expose(getLog) + function getLog(log) { - $("#logsContainer").append(log) - $("#logsContainer").append("
") + console.log(log.message, log.message.length) + let log_ = "" + if (log.message.length === 0) { + log_ = "
 
" + } else { + log_ = "
" + log.message + "    [" + log.levelname + "] " + log.asctime + "
" + } + + $("#logsContainer").append(log_) $('#logsContainer').scrollTop($('#logsContainer')[0].scrollHeight); } + + eel.expose(clientDisconnected) + + function clientDisconnected() { + $("#participateTitle").text("Participate") + $("#participateButton").show() + $("#stopParticipation").hide() + } + + eel.expose(clientConnected) + + function clientConnected() { + $("#participateTitle").text("Participating...") + $("#participateButton").hide() + $("#stopParticipation").show() + } + + function getSubgraphs() { + eel.get_subgraphs()().then((subgraphs) => { + console.log("subgraphs", subgraphs) + $("#subgraphs-body").empty() + subgraphs.forEach((subgraph) => { + let row = "" + row += `${subgraph.graph_id}` + row += `${subgraph.subgraph_id}` + row += `${subgraph.status}` + + if (subgraph.progress === null) { + row += `NA` + } else { + row += `${subgraph.progress}` + } + + if (subgraph.tokens === null) { + row += `NA` + } else { + row += `${subgraph.tokens}` + } + row += "" + $("#subgraphs-body").append(row) + }) + }) + setTimeout(getSubgraphs, 2000) + } + + $("#reloadSubgraphsBt").on("click", function () { + getSubgraphs() + }) }); From 8a558bc22d41a8c2b7aefb4e64252891670dc11a Mon Sep 17 00:00:00 2001 From: Kailash Date: Mon, 5 Dec 2022 15:50:12 +0530 Subject: [PATCH 14/16] Version update --- ui2.py => gui.py | 0 setup.py | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename ui2.py => gui.py (100%) diff --git a/ui2.py b/gui.py similarity index 100% rename from ui2.py rename to gui.py diff --git a/setup.py b/setup.py index 3b9ced2..9a971ee 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name="ravpy", - version="0.11", + version="0.12", license='MIT', author="Raven Protocol", author_email='kailash@ravenprotocol.com', @@ -34,6 +34,6 @@ "sqlalchemy", "sqlalchemy-utils", ], - app=["ui2.py"], + app=["gui.py"], setup_requires=["py2app"], ) From 561c4724bad64155def6ba9eeff01cf69260bd85 Mon Sep 17 00:00:00 2001 From: Kailash Date: Mon, 5 Dec 2022 17:11:40 +0530 Subject: [PATCH 15/16] Path configuration for download files corrected --- ravpy/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ravpy/config.py b/ravpy/config.py index b893104..0fd1a42 100644 --- a/ravpy/config.py +++ b/ravpy/config.py @@ -18,8 +18,8 @@ ENCRYPTION = False -FTP_TEMP_FILES_FOLDER = os.path.join(os.getcwd(), "ravpy/distributed/temp_files") -FTP_DOWNLOAD_FILES_FOLDER = os.path.join(os.getcwd(), "ravpy/distributed/downloads") +FTP_TEMP_FILES_FOLDER = os.path.join(PROJECT_DIR, "ravpy/distributed/temp_files") +FTP_DOWNLOAD_FILES_FOLDER = os.path.join(PROJECT_DIR, "ravpy/distributed/downloads") os.makedirs(FTP_TEMP_FILES_FOLDER, exist_ok=True) os.makedirs(FTP_DOWNLOAD_FILES_FOLDER, exist_ok=True) From a3a2dd139321aece21632a75a75471ca117326c7 Mon Sep 17 00:00:00 2001 From: Kailash Date: Mon, 5 Dec 2022 20:23:46 +0530 Subject: [PATCH 16/16] Config paths change and check version removed for now --- ravpy/config.py | 4 ++-- ravpy/initialize.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ravpy/config.py b/ravpy/config.py index 0fd1a42..43d0af3 100644 --- a/ravpy/config.py +++ b/ravpy/config.py @@ -13,7 +13,7 @@ RAVENVERSE_URL = os.environ.get("RAVENVERSE_URL") RAVENVERSE_FTP_URL = os.environ.get("RAVENVERSE_FTP_URL") -BENCHMARK_FILE_NAME = "ravpy/distributed/benchmark.json" +BENCHMARK_FILE_NAME = os.path.join(PROJECT_DIR, "ravpy/distributed/benchmark.json") TYPE = "client" ENCRYPTION = False @@ -24,7 +24,7 @@ os.makedirs(FTP_TEMP_FILES_FOLDER, exist_ok=True) os.makedirs(FTP_DOWNLOAD_FILES_FOLDER, exist_ok=True) -RAVPY_LOG_FILE = os.path.join(pathlib.Path(__file__).parent.parent.resolve(), "debug.log") +RAVPY_LOG_FILE = os.path.join(PROJECT_DIR, "debug.log") BENCHMARK_DOWNLOAD_PATH = os.path.join(PROJECT_DIR, "ravpy/distributed/downloads/") TEMP_FILES_PATH = os.path.join(PROJECT_DIR, "ravpy/distributed/temp_files/") diff --git a/ravpy/initialize.py b/ravpy/initialize.py index 72cda30..56876fd 100644 --- a/ravpy/initialize.py +++ b/ravpy/initialize.py @@ -17,11 +17,11 @@ def exit_handler(): def initialize(ravenverse_token): - g.logger.debug("Checking Version of Ravpy...") - - if not isLatestVersion('ravpy'): - g.logger.debug("Please update ravpy to latest version...") - os._exit(1) + # g.logger.debug("Checking Version of Ravpy...") + # + # if not isLatestVersion('ravpy'): + # g.logger.debug("Please update ravpy to latest version...") + # os._exit(1) g.logger.debug("Initializing...") g.ravenverse_token = ravenverse_token