From be1d9cad29132cb138c9d4432b8e1eebff766711 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 18 Nov 2024 11:55:38 -0800 Subject: [PATCH 1/7] adding signal handlers on backend --- backend/restapi_server.py | 119 +++++++++++++++++++++----------------- 1 file changed, 66 insertions(+), 53 deletions(-) diff --git a/backend/restapi_server.py b/backend/restapi_server.py index c608c290..6c3e395a 100644 --- a/backend/restapi_server.py +++ b/backend/restapi_server.py @@ -1,10 +1,7 @@ -# -# Copyright (C) 2024 RapidSilicon -# Authorized use only -# import argparse import os import sys +import signal from flask import Flask, request, jsonify from flasgger import Swagger from submodule.rs_device_manager import RsDeviceManager @@ -19,25 +16,77 @@ from api.utils import attrs_api from api.project import project_api -# -# Main entry point -# +# Create Flask app +app = Flask(__name__) +app.url_map.strict_slashes = False + +# Set up Swagger for API documentation +swagger_template = { + "swagger": "2.0", + "info": { + "title": "RPE Backend API", + "description": "The RPE Backend APIs consumed by the RPE frontend for power and thermal estimation of Rapid Silicon devices.", + "version": "0.1.0" + } +} +swagger = Swagger(app, template=swagger_template) + +# Register API blueprints with the Flask app +app.register_blueprint(device_api) +app.register_blueprint(clock_api) +app.register_blueprint(dsp_api) +app.register_blueprint(fabric_le_api) +app.register_blueprint(bram_api) +app.register_blueprint(io_api) +app.register_blueprint(peripherals_api) +app.register_blueprint(attrs_api) +app.register_blueprint(project_api) + +# Hook up request signal to log requests from the UI +@app.before_request +def before_request(): + log(f"{request.method} {request.url}") + +@app.after_request +def after_request(response): + log(f"{request.method} {request.url} {response.status_code} - DONE") + return response + +# Graceful shutdown function +def shutdown_server(): + log("Shutting down server...") + func = request.environ.get('werkzeug.server.shutdown') + if func is not None: + func() + else: + log("Server shutdown function not found.", RsLogLevel.ERROR) + +# Signal handler for smooth shutdown +def signal_handler(signal_received, frame): + log(f"Signal {signal_received} received, initiating shutdown...") + shutdown_server() + sys.exit(0) + +# Register the signal handler for SIGINT (Ctrl+C) and SIGTERM +signal.signal(signal.SIGINT, signal_handler) +signal.signal(signal.SIGTERM, signal_handler) + def main(): - # create and parse command line args - parser = argparse.ArgumentParser(description='Rapid Power Estimator Rest API Server command-line arguments.') - parser.add_argument('device_file', type=str, help='Path to the input device xml file') + # Parse command-line arguments + parser = argparse.ArgumentParser(description='Rapid Power Estimator REST API Server command-line arguments.') + parser.add_argument('device_file', type=str, help='Path to the input device XML file') parser.add_argument('--port', type=int, default=5000, help='Specify TCP Port to use for REST server') parser.add_argument('--debug', default=False, action='store_true', help='Enable/Disable debug mode') parser.add_argument('--logfile', type=str, default="rpe.log", help='Specify log file name') - parser.add_argument('--maxbytes', type=int, default=2048, help='Specify maximun log file size in kilobytes before rollover') - parser.add_argument('--backupcount', type=int, default=20, help='Specify no. of backup log files') + parser.add_argument('--maxbytes', type=int, default=2048, help='Specify maximum log file size in kilobytes before rollover') + parser.add_argument('--backupcount', type=int, default=20, help='Specify number of backup log files') args = parser.parse_args() - # setup app logger - log_setup(filename=args.logfile, max_bytes=args.maxbytes*1024, backup_count=args.backupcount) + # Set up logger + log_setup(filename=args.logfile, max_bytes=args.maxbytes * 1024, backup_count=args.backupcount) # Check if the device_file exists - if os.path.exists(args.device_file) == False: + if not os.path.exists(args.device_file): log(f"Device file '{args.device_file}' does not exist.", RsLogLevel.ERROR) sys.exit(1) @@ -45,46 +94,10 @@ def main(): devicemanager = RsDeviceManager.get_instance() devicemanager.load_xml(args.device_file) - # create flask app object - app = Flask(__name__) - app.url_map.strict_slashes = False - - # auto generate restapi documentation - template = { - "swagger": "2.0", - "info": { - "title": "RPE Backend API", - "description": "The RPE Backend APIs which consumed by the RPE frontend for power and thermal estimation of the Rapid Silicon devices.", - "version": "0.1.0" - } - } - swagger = Swagger(app, template=template) - - # bind device api objects onto the flask app - app.register_blueprint(device_api) - app.register_blueprint(clock_api) - app.register_blueprint(dsp_api) - app.register_blueprint(fabric_le_api) - app.register_blueprint(bram_api) - app.register_blueprint(io_api) - app.register_blueprint(peripherals_api) - app.register_blueprint(attrs_api) - app.register_blueprint(project_api) - - # hook up request signal to log request by UI - @app.before_request - def before_request(): - log(f"{request.method} {request.url}") - - @app.after_request - def after_request(response): - log(f"{request.method} {request.url} {response.status_code} - DONE") - return response - - # log app server started + # Log server start message log("App server is running...") - # Start Rest API server + # Start the Flask app app.run(debug=args.debug, port=args.port) if __name__ == "__main__": From fe61195360040d6eca0d811a973cc023bfee0a0d Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 18 Nov 2024 11:59:51 -0800 Subject: [PATCH 2/7] adding restart.test.js --- tests/e2e/restart.test.js | 51 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 tests/e2e/restart.test.js diff --git a/tests/e2e/restart.test.js b/tests/e2e/restart.test.js new file mode 100644 index 00000000..d77f9863 --- /dev/null +++ b/tests/e2e/restart.test.js @@ -0,0 +1,51 @@ +const { _electron: electron } = require('playwright'); +const { test, expect } = require('@playwright/test'); +const { execSync } = require('child_process'); +const os = require('os'); + +function isElectronRunning(pid) { + try { + const platform = os.platform(); + let output; + + if (platform === 'win32') { + output = execSync(`tasklist /FI "PID eq ${pid}"`).toString(); + return output.includes('electron.exe'); + } else if (platform === 'darwin' || platform === 'linux') { + output = execSync(`ps -p ${pid}`).toString(); + return output.includes('Electron'); + } + } catch (error) { + console.error('Error checking for Electron process:', error); + return false; + } +} + +test('Launch and close Electron app 10 times', async () => { + for (let i = 0; i < 10; i++) { + console.log(`Iteration ${i + 1}: Launching and closing Electron app.`); + + // Launch the Electron app + const app = await electron.launch({ args: ['main.js'] }); + const pid = app.process().pid; + const window = await app.firstWindow(); + + // Selecting the device (MPW1 Gemini) + const deviceDropdown = await window.waitForSelector('#deviceId'); + await deviceDropdown.selectOption('MPW1'); + + + // Close the app + await app.close(); + + // Waiting for a moment to allow for process termination + await new Promise((resolve) => setTimeout(resolve, 3000)); + + // Check if the Electron app is still running + let running = isElectronRunning(pid); + if (running) { + console.error(`Iteration ${i + 1}: Electron app could not be terminated.`); + break; // Stop further iterations if the app cannot be killed + } + } +}); \ No newline at end of file From 96be71e24ac556a554b8ce4ae70b584af9026735 Mon Sep 17 00:00:00 2001 From: Shiva Ahir Date: Mon, 18 Nov 2024 13:29:46 -0800 Subject: [PATCH 3/7] Update restapi_server.py --- backend/restapi_server.py | 136 ++++++++++++++++++++------------------ 1 file changed, 72 insertions(+), 64 deletions(-) diff --git a/backend/restapi_server.py b/backend/restapi_server.py index 6c3e395a..45193332 100644 --- a/backend/restapi_server.py +++ b/backend/restapi_server.py @@ -1,7 +1,11 @@ +# +# Copyright (C) 2024 RapidSilicon +# Authorized use only +# import argparse import os -import sys import signal +import sys from flask import Flask, request, jsonify from flasgger import Swagger from submodule.rs_device_manager import RsDeviceManager @@ -16,73 +20,21 @@ from api.utils import attrs_api from api.project import project_api -# Create Flask app -app = Flask(__name__) -app.url_map.strict_slashes = False - -# Set up Swagger for API documentation -swagger_template = { - "swagger": "2.0", - "info": { - "title": "RPE Backend API", - "description": "The RPE Backend APIs consumed by the RPE frontend for power and thermal estimation of Rapid Silicon devices.", - "version": "0.1.0" - } -} -swagger = Swagger(app, template=swagger_template) - -# Register API blueprints with the Flask app -app.register_blueprint(device_api) -app.register_blueprint(clock_api) -app.register_blueprint(dsp_api) -app.register_blueprint(fabric_le_api) -app.register_blueprint(bram_api) -app.register_blueprint(io_api) -app.register_blueprint(peripherals_api) -app.register_blueprint(attrs_api) -app.register_blueprint(project_api) - -# Hook up request signal to log requests from the UI -@app.before_request -def before_request(): - log(f"{request.method} {request.url}") - -@app.after_request -def after_request(response): - log(f"{request.method} {request.url} {response.status_code} - DONE") - return response - -# Graceful shutdown function -def shutdown_server(): - log("Shutting down server...") - func = request.environ.get('werkzeug.server.shutdown') - if func is not None: - func() - else: - log("Server shutdown function not found.", RsLogLevel.ERROR) - -# Signal handler for smooth shutdown -def signal_handler(signal_received, frame): - log(f"Signal {signal_received} received, initiating shutdown...") - shutdown_server() - sys.exit(0) - -# Register the signal handler for SIGINT (Ctrl+C) and SIGTERM -signal.signal(signal.SIGINT, signal_handler) -signal.signal(signal.SIGTERM, signal_handler) - +# +# Main entry point +# def main(): - # Parse command-line arguments - parser = argparse.ArgumentParser(description='Rapid Power Estimator REST API Server command-line arguments.') - parser.add_argument('device_file', type=str, help='Path to the input device XML file') + # create and parse command line args + parser = argparse.ArgumentParser(description='Rapid Power Estimator Rest API Server command-line arguments.') + parser.add_argument('device_file', type=str, help='Path to the input device xml file') parser.add_argument('--port', type=int, default=5000, help='Specify TCP Port to use for REST server') parser.add_argument('--debug', default=False, action='store_true', help='Enable/Disable debug mode') parser.add_argument('--logfile', type=str, default="rpe.log", help='Specify log file name') - parser.add_argument('--maxbytes', type=int, default=2048, help='Specify maximum log file size in kilobytes before rollover') - parser.add_argument('--backupcount', type=int, default=20, help='Specify number of backup log files') + parser.add_argument('--maxbytes', type=int, default=2048, help='Specify maximun log file size in kilobytes before rollover') + parser.add_argument('--backupcount', type=int, default=20, help='Specify no. of backup log files') args = parser.parse_args() - # Set up logger + # setup app logger log_setup(filename=args.logfile, max_bytes=args.maxbytes * 1024, backup_count=args.backupcount) # Check if the device_file exists @@ -94,11 +46,67 @@ def main(): devicemanager = RsDeviceManager.get_instance() devicemanager.load_xml(args.device_file) - # Log server start message + # create flask app object + app = Flask(__name__) + app.url_map.strict_slashes = False + + # auto generate restapi documentation + template = { + "swagger": "2.0", + "info": { + "title": "RPE Backend API", + "description": "The RPE Backend APIs which are consumed by the RPE frontend for power and thermal estimation of the Rapid Silicon devices.", + "version": "0.1.0" + } + } + swagger = Swagger(app, template=template) + + # bind device api objects onto the flask app + app.register_blueprint(device_api) + app.register_blueprint(clock_api) + app.register_blueprint(dsp_api) + app.register_blueprint(fabric_le_api) + app.register_blueprint(bram_api) + app.register_blueprint(io_api) + app.register_blueprint(peripherals_api) + app.register_blueprint(attrs_api) + app.register_blueprint(project_api) + + # hook up request signal to log request by UI + @app.before_request + def before_request(): + log(f"{request.method} {request.url}") + + @app.after_request + def after_request(response): + log(f"{request.method} {request.url} {response.status_code} - DONE") + return response + + # Graceful shutdown function + def shutdown_server(): + log("Shutting down server...") + func = request.environ.get('werkzeug.server.shutdown') + if func is not None: + func() + else: + log("Server shutdown function not found.", RsLogLevel.ERROR) + + # Signal handler for smooth shutdown + def signal_handler(signal_received, frame): + log(f"Signal {signal_received} received, initiating shutdown...") + shutdown_server() + sys.exit(0) + + # Register the signal handler for SIGINT (Ctrl+C) and SIGTERM + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + # log app server started log("App server is running...") - # Start the Flask app + # Start Rest API server app.run(debug=args.debug, port=args.port) + if __name__ == "__main__": main() From 1db277ea730de337cef7c9117a5a752d674687fb Mon Sep 17 00:00:00 2001 From: Shiva Ahir Date: Mon, 18 Nov 2024 13:31:29 -0800 Subject: [PATCH 4/7] Update restapi_server.py --- backend/restapi_server.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/restapi_server.py b/backend/restapi_server.py index 45193332..b25d8961 100644 --- a/backend/restapi_server.py +++ b/backend/restapi_server.py @@ -38,7 +38,7 @@ def main(): log_setup(filename=args.logfile, max_bytes=args.maxbytes * 1024, backup_count=args.backupcount) # Check if the device_file exists - if not os.path.exists(args.device_file): + if os.path.exists(args.device_file) == False: log(f"Device file '{args.device_file}' does not exist.", RsLogLevel.ERROR) sys.exit(1) @@ -107,6 +107,5 @@ def signal_handler(signal_received, frame): # Start Rest API server app.run(debug=args.debug, port=args.port) - if __name__ == "__main__": main() From 23122d3434f4a845d20324df27ab39c8f933dd84 Mon Sep 17 00:00:00 2001 From: Shiva Ahir Date: Mon, 18 Nov 2024 19:27:13 -0800 Subject: [PATCH 5/7] Update cleanup.js --- cleanup.js | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/cleanup.js b/cleanup.js index da4d242f..d72afc79 100644 --- a/cleanup.js +++ b/cleanup.js @@ -1,12 +1,26 @@ const isWindows = process.platform === 'win32'; const kill = (process) => { - if (isWindows) { - const kill = require('tree-kill'); - kill(process.pid); - } else { - process.kill('SIGINT'); - } + console.log(`Attempting to kill process with PID: ${process.pid}`); + if (isWindows) { + const { spawn } = require('child_process'); + // used taskkill for Windows to terminate the process tree + const taskKill = spawn('taskkill', ['/PID', process.pid, '/T', '/F']); + taskKill.on('close', (code) => { + if (code === 0) { + console.log('Process killed successfully on Windows.'); + } else { + console.error(`taskkill failed with exit code: ${code}`); + } + }); + } else { + try { + process.kill('SIGINT'); + console.log('SIGINT sent to process'); + } catch (error) { + console.error(`Failed to send SIGINT: ${error.message}`); + } + } }; module.exports = { kill }; From 8e7b8597bb7e6c18434b141c15a78cd7caf883df Mon Sep 17 00:00:00 2001 From: NadeemYaseen Date: Tue, 19 Nov 2024 21:00:11 +0500 Subject: [PATCH 6/7] kill backend explicitly with kill -9 command on Unix either on clicking cross or via quit --- cleanup.js | 16 +++++++++++++--- main.js | 5 ++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/cleanup.js b/cleanup.js index d72afc79..4f70d9da 100644 --- a/cleanup.js +++ b/cleanup.js @@ -1,9 +1,10 @@ -const isWindows = process.platform === 'win32'; +const os = require('os'); +const platform = os.platform(); const kill = (process) => { console.log(`Attempting to kill process with PID: ${process.pid}`); - if (isWindows) { - const { spawn } = require('child_process'); + const { spawn } = require('child_process'); + if (platform === 'win32') { // used taskkill for Windows to terminate the process tree const taskKill = spawn('taskkill', ['/PID', process.pid, '/T', '/F']); taskKill.on('close', (code) => { @@ -13,6 +14,15 @@ const kill = (process) => { console.error(`taskkill failed with exit code: ${code}`); } }); + } else if (platform === 'darwin' || platform === 'linux') { + const taskKill = spawn('kill', ['-9', process.pid]); + taskKill.on('close', (code) => { + if (code === 0) { + console.log('Process killed successfully on Unix.'); + } else { + console.error(`taskkill failed with exit code: ${code}`); + } + }); } else { try { process.kill('SIGINT'); diff --git a/main.js b/main.js index 2fdf879f..b8edcbda 100644 --- a/main.js +++ b/main.js @@ -317,7 +317,10 @@ app.whenReady().then(() => { }); }); -app.on('window-all-closed', () => { +app.on("before-quit", function () { kill(serverProcess); +}) + +app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); }); From 297c631fb0e1c220282b0b2ed589a0b25c2944e5 Mon Sep 17 00:00:00 2001 From: Shiva Ahir Date: Tue, 19 Nov 2024 14:09:53 -0800 Subject: [PATCH 7/7] Update restart.test.js --- tests/e2e/restart.test.js | 91 ++++++++++++++++++++++++++++++--------- 1 file changed, 71 insertions(+), 20 deletions(-) diff --git a/tests/e2e/restart.test.js b/tests/e2e/restart.test.js index d77f9863..743b547a 100644 --- a/tests/e2e/restart.test.js +++ b/tests/e2e/restart.test.js @@ -3,25 +3,56 @@ const { test, expect } = require('@playwright/test'); const { execSync } = require('child_process'); const os = require('os'); -function isElectronRunning(pid) { +// Helper function to find a process ID by name +function getProcessIdByName(processName,pid) { + try { + const platform = os.platform(); + let output; + + if (platform === 'win32') { + // Fetch all processes + output = execSync(`tasklist /FO CSV`).toString(); + const lines = output.split('\n'); + for (const line of lines) { + if (line.includes(processName)) { + const parts = line.split(','); + const pid = parseInt(parts[1].replace(/"/g, '').trim(), 10); + console.log(`Found process: ${line}`); + return pid; // Return the PID of the process + } + } + } else if (platform === 'darwin' || platform === 'linux') { + // Fetch the PID for Unix-based systems + output = execSync(`pgrep "${processName}" -P ${pid}`).toString(); + console.log(`Found backend PID: ${output.trim()}`); + return parseInt(output.trim(), 10); // Return the PID + } + } catch (error) { + console.error(`Error fetching process ID for ${processName}:`, error.message); + return null; + } +} + +// Helper function to check if a process is running by PID +function isProcessRunning(pid) { try { const platform = os.platform(); let output; if (platform === 'win32') { output = execSync(`tasklist /FI "PID eq ${pid}"`).toString(); - return output.includes('electron.exe'); + return output.includes(`${pid}`); } else if (platform === 'darwin' || platform === 'linux') { - output = execSync(`ps -p ${pid}`).toString(); - return output.includes('Electron'); + output = execSync(`ps -aux | grep ${pid}`).toString(); + return output.includes(`main.js`); } } catch (error) { - console.error('Error checking for Electron process:', error); + console.error(`Error checking for process ${pid}:`, error.message); return false; } } -test('Launch and close Electron app 10 times', async () => { +test('Launch and close Electron app 10 times, ensuring backend termination', async () => { for (let i = 0; i < 10; i++) { console.log(`Iteration ${i + 1}: Launching and closing Electron app.`); @@ -29,23 +60,43 @@ test('Launch and close Electron app 10 times', async () => { const app = await electron.launch({ args: ['main.js'] }); const pid = app.process().pid; const window = await app.firstWindow(); + console.log(`Frontend PID: ${pid}`) + // Selecting the device (MPW1 Gemini) + const deviceDropdown = await window.waitForSelector('#deviceId'); + await deviceDropdown.selectOption('MPW1'); - // Selecting the device (MPW1 Gemini) - const deviceDropdown = await window.waitForSelector('#deviceId'); - await deviceDropdown.selectOption('MPW1'); - - - // Close the app + let backendProcessName = ''; + if (os.platform() === 'win32') { + backendProcessName = 'python.exe'; + } else if (os.platform() === 'darwin' || os.platform() === 'linux') { + backendProcessName = 'python'; + } + console.log(`The backend process name is: ${backendProcessName}`); + const backendPid = getProcessIdByName(backendProcessName,pid); + if (!backendPid) { + console.error('Failed to fetch backend PID.'); + break; + } + console.log(`Backend PID: ${backendPid}`); + // Close the Electron app await app.close(); - - // Waiting for a moment to allow for process termination - await new Promise((resolve) => setTimeout(resolve, 3000)); - + // Wait for a moment to allow processes to terminate + await new Promise((resolve) => setTimeout(resolve, 3000)); // Check if the Electron app is still running - let running = isElectronRunning(pid); - if (running) { + let frontendRunning = isProcessRunning(pid); + if (frontendRunning) { console.error(`Iteration ${i + 1}: Electron app could not be terminated.`); - break; // Stop further iterations if the app cannot be killed + break; + } + // Check if the backend process is still running + let backendRunning = isProcessRunning(backendPid); + if (backendRunning) { + console.error( + `Iteration ${i + 1}: Backend process ${backendPid} could not be terminated.` + ); + break; + } else { + console.log(`Iteration ${i + 1}: Backend process terminated successfully.`); } } -}); \ No newline at end of file +});