Commit b673d384 authored by Ibrahim Ahmed's avatar Ibrahim Ahmed
Browse files

monitor & controller: Monitor args in same ini. Optional formatter in make_logger()

parent cd11e6ac
Loading
Loading
Loading
Loading
+12 −9
Changes for src/controller.py: 12 added lines, 9 removed lines.
Original line number Diff line number Diff line
@@ -29,11 +29,12 @@ from utils.logging import EmailHandler, RemoteHandler, get_logger, make_logger

SOURCECODE_DIR = os.path.dirname(os.path.abspath(__file__))
WORKING_DIR = os.path.abspath(os.getcwd())
# Settings that are mandatory for the script. If not provided in command line
# or ini, these defaults are used.
DEFAULTS = dict(
    settings=os.path.join(SOURCECODE_DIR, 'settings.ini'),
    logs=os.path.join(WORKING_DIR, 'log.txt'),
    output=os.path.join(WORKING_DIR, 'output.txt'),
    verbosity='INFO'
    output=os.path.join(WORKING_DIR, 'output.txt')
)


@@ -74,7 +75,7 @@ def make_arguments() -> ArgumentParser:



def get_settings(parsed_args) -> dict:
def get_settings(parsed_args, section: str='DEFAULT', write_settings=False) -> dict:
    # Combines command line flags with settings parsed from settings ini file.
    # Command line takes precedence. Values set in command line are not over-
    # written by ini file.
@@ -85,12 +86,13 @@ def get_settings(parsed_args) -> dict:
    if parsed_args.settings is None:
        raise ValueError('No settings file provided.')
    cfg.read(parsed_args.settings)
    for setting, value in cfg['DEFAULT'].items():
        # Only update settings which were not specified in the command line
    for setting, value in cfg[section].items():
        # Only update settings which were not specified in the command line,
        # and which had non empty values
        if (setting not in settings) or (settings.get(setting) is None):
            if setting in ('stepsize', 'window', 'interval'):
                settings[setting] = float(value)
            elif setting in ('logs_email_batchsize',):
            elif setting in ('logs_email_batchsize', 'port'):
                settings[setting] = int(value)
            elif setting=='bounds':
                settings[setting] = np.asarray([tuple(map(float, value.split(',')))])
@@ -98,11 +100,13 @@ def get_settings(parsed_args) -> dict:
                settings[setting] = value.lower()
            else:
                settings[setting] = value
    # Add default settings if they did not have a value in the ini or command line.
    # These are settings that must be set in any case.
    for setting in DEFAULTS:
        if settings.get(setting) is None:
            settings[setting] = DEFAULTS[setting]

    if settings.get('output_settings') not in ('', None):
    if settings.get('output_settings') not in ('', None) and write_settings:
        with open(settings['output_settings'], 'w', newline='') as f:
            # Only these settings are written to the output settings csv
            keys = ['interval', 'stepsize', 'target', 'window', 'bounds']
@@ -186,7 +190,6 @@ def put_control_action(action: np.ndarray, **settings):




if __name__ == '__main__':
    try:
        parser = make_arguments()
@@ -206,7 +209,7 @@ if __name__ == '__main__':
        while not ev_halt.isSet():
            try:
                start = datetime.now(pytz.utc)
                settings = get_settings(args)
                settings = get_settings(args, write_settings=True)
                update_controller(ctrl, **settings)
                prev_end = start - 2*timedelta(seconds=int(settings['interval']))
                if settings['no_network']:
+34 −4
Changes for src/monitor.py: 34 added lines, 4 removed lines.
Original line number Diff line number Diff line
@@ -3,6 +3,8 @@ Run a companion server to `controller.py` on a separate machine to receive log
messages sent via HTTP/POST requests.
"""

from argparse import ArgumentParser, Namespace

from flask import request
from dash import Dash
import dash_core_components as dcc
@@ -12,6 +14,8 @@ import plotly.express as px
from utils.logging import get_logger, make_logger
from controller import get_settings, DEFAULTS

DEFAULTS['logs_server'] = 'http://127.0.0.1:5000/log'


dapp = Dash(__name__)   # the Dash application wrapper
app = dapp.server       # The flask app used
@@ -25,13 +29,39 @@ dapp.layout = html.Div([
@app.route('/log', methods=('POST',))   # endpoint for POST requests
def log():
    logger = get_logger('monitor')
    logger.info(request.form['msg'])
    rdict = request.form
    logger.log(int(rdict['levelno']), rdict['message'])
    return 'OK'



def make_arguments() -> ArgumentParser:
    # If a setting can be overridden by the settings ini file, then the default
    # should be None. This is because get_settings() assumes a non-None value
    # means that the setting was explicitly provided as a flag in the command
    # line and should not be changed.
    # Actual default values should be stored as variables (DEFAULTS), or put in
    # the settings ini file.
    parser = ArgumentParser(description='Monitor for condenser set-point optimization script.',
        epilog='Additional settings can be changed from the specified settings ini file.')
    parser.add_argument('-s', '--settings', type=str, required=False,
                        help='Location of settings file.', default=DEFAULTS['settings'])
    parser.add_argument('-l', '--logs', type=str, required=False, default=None,
                        help='Location of file to write logs to.')
    parser.add_argument('-r', '--logs-server', type=str, required=False, default=None,
                        help='http://host[:port][/path] of remote server to POST logs to.')
    parser.add_argument('-v', '--verbosity', type=str, required=False, default=None,
                        help='Verbosity level.',
                        choices=('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG'))
    return parser



if __name__ == '__main__':
    parser = make_arguments()
    args = parser.parse_args()
    settings = get_settings(args, section='MONITOR')
    logger = get_logger('monitor')
    logger = make_logger(enable=('stream',), logger=logger,
        verbosity='INFO', logs_stream_verbosity='INFO')
    dapp.run_server(host='0.0.0.0', port=5000, debug=True)
    logger = make_logger(enable=('stream', 'file', 'email'), logger=logger, **settings)
    logger.info('Started monitoring...')
    dapp.run_server(host=settings['host'], port=settings['port'], debug=True)
+15 −2
Changes for src/settings.ini: 15 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -28,13 +28,13 @@ logs_server_verbosity = DEBUG
logs_email_verbosity = DEBUG
# Logging via POST requests to a server.
logs_server = 
# Email logging settings
# Email logging settings. Will skip if username/password is absent.
logs_email_to = ibrahim.ahmed@vanderbilt.edu
logs_email_from =
logs_email_smtp_server = smtpauth.vanderbilt.edu
logs_email_username =
logs_email_password =
logs_email_batchsize = 288
logs_email_batchsize = 1008

## CONTROLLER
## ==========
@@ -49,3 +49,16 @@ stepsize = 1.
window = 1.
# min,max setpoint values. Setpoint is further limited by the wetbulb temperature.
bounds = 55.,75.

[MONITOR]
# Settings here overwrite the default settings in case a different configuration
# is needed for the monitor script for e.g logging levels
host = 0.0.0.0
port = 5000
# Location of local log file where to record actions/errors
logs = ./server-logs.txt
# global level. No less severe messages will be processed. Can only be higher
# than default verbosity.
verbosity = DEBUG
logs_stream_verbosity = INFO
logs_file_verbosity = INFO
+2 −1
Changes for src/utils/logging.py: 2 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -20,10 +20,11 @@ def get_logger(name: str=None) -> logging.Logger:



def make_logger(enable='all', logger=None, **settings) -> logging.Logger:
def make_logger(enable='all', logger=None, formatter=None, **settings) -> logging.Logger:
    logging.captureWarnings(True)
    logger = get_logger() if logger is None else logger
    logger.setLevel(settings['verbosity'])
    if not isinstance(formatter, logging.Formatter):
        formatter = logging.Formatter('%(asctime)s, %(levelname)s, %(message)s',
                                      datefmt='%Y-%m-%d %H:%M:%S')