diff --git a/manager.py b/manager.py index b5c030d..7f448a3 100644 --- a/manager.py +++ b/manager.py @@ -1,17 +1,18 @@ import argparse from plotmanager.library.utilities.exceptions import InvalidArgumentException -from plotmanager.library.utilities.commands import start_manager, stop_manager, view, analyze_logs +from plotmanager.library.utilities.commands import start_manager, stop_manager, view, json_output, analyze_logs parser = argparse.ArgumentParser(description='This is the central manager for Swar\'s Chia Plot Manager.') help_description = ''' -There are a few different actions that you can use: "start", "restart", "stop", "view", and "analyze_logs". "start" will -start a manager process. If one already exists, it will display an error message. "restart" will try to kill any -existing manager and start a new one. "stop" will terminate the manager, but all existing plots will be completed. -"view" can be used to display an updating table that will show the progress of your plots. Once a manager has started it -will always be running in the background unless an error occurs. This field is case-sensitive. +There are a few different actions that you can use: "start", "restart", "stop", "view", "status", "json", and +"analyze_logs". "start" will start a manager process. If one already exists, it will display an error message. +"restart" will try to kill any existing manager and start a new one. "stop" will terminate the manager, but all +existing plots will be completed. "view" can be used to display an updating table that will show the progress of your +plots. Once a manager has started it will always be running in the background unless an error occurs. This field is +case-sensitive. "analyze_logs" is a helper command that will scan all the logs in your log_directory to get your custom settings for the progress settings in the YAML file. @@ -34,9 +35,13 @@ stop_manager() elif args.action == 'view': view() +elif args.action == 'json': + json_output() +elif args.action == 'status': + view(loop=False) elif args.action == 'analyze_logs': analyze_logs() else: - error_message = 'Invalid action provided. The valid options are "start", "restart", "stop", "view", and ' \ + error_message = 'Invalid action provided. The valid options are "start", "restart", "stop", "view", "status", "json" and ' \ '"analyze_logs".' raise InvalidArgumentException(error_message) diff --git a/plotmanager/library/utilities/commands.py b/plotmanager/library/utilities/commands.py index b68d055..7551e19 100644 --- a/plotmanager/library/utilities/commands.py +++ b/plotmanager/library/utilities/commands.py @@ -13,8 +13,9 @@ from plotmanager.library.utilities.jobs import load_jobs from plotmanager.library.utilities.log import analyze_log_dates, check_log_progress, analyze_log_times from plotmanager.library.utilities.notifications import send_notifications -from plotmanager.library.utilities.print import print_view -from plotmanager.library.utilities.processes import is_windows, get_manager_processes, get_running_plots, start_process +from plotmanager.library.utilities.print import print_view, print_json +from plotmanager.library.utilities.processes import is_windows, get_manager_processes, get_running_plots, \ + start_process, identify_drive, get_system_drives def start_manager(): @@ -75,7 +76,60 @@ def stop_manager(): print("Successfully stopped manager processes.") -def view(): +def json_output(): + chia_location, log_directory, config_jobs, manager_check_interval, max_concurrent, progress_settings, \ + notification_settings, debug_level, view_settings, instrumentation_settings = get_config_info() + + all_drives = get_system_drives() + + analysis = {'files': {}} + drives = {'temp': [], 'temp2': [], 'dest': []} + jobs = load_jobs(config_jobs) + for job in jobs: + drive = job.temporary_directory.split('\\')[0] + drives['temp'].append(drive) + directories = { + 'dest': job.destination_directory, + 'temp2': job.temporary2_directory, + } + for key, directory_list in directories.items(): + if directory_list is None: + continue + if not isinstance(directory_list, list): + directory_list = [directory_list] + for directory in directory_list: + drive = identify_drive(file_path=directory, drives=all_drives) + if drive in drives[key]: + continue + drives[key].append(drive) + + running_work = {} + + analysis = analyze_log_dates(log_directory=log_directory, analysis=analysis) + jobs = load_jobs(config_jobs) + jobs, running_work = get_running_plots(jobs=jobs, running_work=running_work, + instrumentation_settings=instrumentation_settings) + check_log_progress(jobs=jobs, running_work=running_work, progress_settings=progress_settings, + notification_settings=notification_settings, view_settings=view_settings, + instrumentation_settings=instrumentation_settings) + print_json(jobs=jobs, running_work=running_work, view_settings=view_settings) + + has_file = False + if len(running_work.values()) == 0: + has_file = True + for work in running_work.values(): + if not work.log_file: + continue + has_file = True + break + if not has_file: + print("Restarting view due to psutil going stale...") + system_args = [f'"{sys.executable}"'] + sys.argv + os.execv(sys.executable, system_args) + exit() + + +def view(loop=False): chia_location, log_directory, config_jobs, manager_check_interval, max_concurrent, progress_settings, \ notification_settings, debug_level, view_settings, instrumentation_settings = get_config_info() view_check_interval = view_settings['check_interval'] @@ -114,7 +168,10 @@ def view(): notification_settings=notification_settings, view_settings=view_settings, instrumentation_settings=instrumentation_settings) print_view(jobs=jobs, running_work=running_work, analysis=analysis, drives=drives, - next_log_check=datetime.now() + timedelta(seconds=view_check_interval), view_settings=view_settings) + next_log_check=datetime.now() + timedelta(seconds=view_check_interval), + view_settings=view_settings, loop=loop) + if not loop: + break time.sleep(view_check_interval) has_file = False if len(running_work.values()) == 0: diff --git a/plotmanager/library/utilities/print.py b/plotmanager/library/utilities/print.py index 8368439..bb00fb5 100644 --- a/plotmanager/library/utilities/print.py +++ b/plotmanager/library/utilities/print.py @@ -1,9 +1,35 @@ import os import psutil +import json from datetime import datetime, timedelta -from plotmanager.library.utilities.processes import get_manager_processes, get_chia_drives +from plotmanager.library.utilities.processes import get_manager_processes + + +def _get_json(pid, running_work, view_settings): + work = running_work[pid] + phase_times = work.phase_times + elapsed_time = (datetime.now() - work.datetime_start) + elapsed_time = pretty_print_time(elapsed_time.seconds) + phase_time_log = [] + for i in range(1, 5): + if phase_times.get(i): + phase_time_log.append(phase_times.get(i)) + + row = [ + work.job.name if work.job else '?', + work.k_size, + pid, + work.datetime_start.strftime(view_settings['datetime_format']), + elapsed_time, + work.current_phase, + ' / '.join(phase_time_log), + work.progress, + pretty_print_bytes(work.temp_file_size, 'gb', 0, " GiB"), + ] + + return row def _get_row_info(pid, running_work, view_settings): @@ -66,6 +92,28 @@ def pretty_print_table(rows): return "\n".join(console) +def get_job_json(jobs, running_work, view_settings): + rows = [] + added_pids = [] + for job in jobs: + for pid in job.running_work: + if pid not in running_work: + continue + rows.append(_get_json(pid, running_work, view_settings)) + added_pids.append(pid) + for pid in running_work.keys(): + if pid in added_pids: + continue + rows.append(_get_json(pid, running_work, view_settings)) + added_pids.append(pid) + rows.sort(key=lambda x: (x[4]), reverse=True) + for i in range(len(rows)): + rows[i] = [str(i + 1)] + rows[i] + jobs = dict(jobs=rows) + print(json.dumps(jobs, separators=(',', ':'))) + return rows + + def get_job_data(jobs, running_work, view_settings): rows = [] added_pids = [] @@ -170,7 +218,11 @@ def get_drive_data(drives, running_work, job_data): return pretty_print_table(rows) -def print_view(jobs, running_work, analysis, drives, next_log_check, view_settings): +def print_json(jobs, running_work, view_settings): + get_job_json(jobs=jobs, running_work=running_work, view_settings=view_settings) + + +def print_view(jobs, running_work, analysis, drives, next_log_check, view_settings, loop): # Job Table job_data = get_job_data(jobs=jobs, running_work=running_work, view_settings=view_settings) @@ -202,5 +254,6 @@ def print_view(jobs, running_work, analysis, drives, next_log_check, view_settin print(f'Plots Completed Yesterday: {analysis["summary"].get(datetime.now().date() - timedelta(days=1), 0)}') print(f'Plots Completed Today: {analysis["summary"].get(datetime.now().date(), 0)}') print() - print(f"Next log check at {next_log_check.strftime('%Y-%m-%d %H:%M:%S')}") + if loop: + print(f"Next log check at {next_log_check.strftime('%Y-%m-%d %H:%M:%S')}") print()