Source code for stosim.sim.commands

'''
commands
=============

This module provides all commands you can use in StoSim:
run, resume, status, kill, snapshot, run_more, make_plots, run_ttests, list_data
'''


import sys
import os
import os.path as osp
import subprocess
try:
    import configparser
except ImportError:
    import ConfigParser as configparser  # for py2
from shutil import rmtree


from shutil import copy

import fjd

from stosim.sim import utils
from stosim.analysis import plotter, tester
from stosim.sim.job_creator import create_jobs


if sys.version < '3':
    input = raw_input


we_exited = [False]  # this helps us to stop using FJD if the user quit

[docs]def run(simfolder): ''' The main function to start running simulations :param string simfolder: relative path to simfolder :returns: True if successful, False otherwise ''' print('*' * 80) sim_name = utils.get_simulation_name(simfolder, "{}/stosim.conf".format(simfolder)) print("Running simulation {}".format(sim_name)) print('*' * 80) print('') if not osp.exists("%s/stosim.conf" % simfolder): print("[StoSim] %s/stosim.conf does not exist!" % simfolder) utils.usage() return False # prepare all jobs to be run by FJD fjd_dir = fjd.utils.ensure_wdir(sim_name) fjd.utils.empty_queues(sim_name) for job in [j for j in os.listdir("{}/jobs".format(simfolder))\ if j.endswith('.conf')]: copy("{}/jobs/{}".format(simfolder, job), "{}/jobqueue".format(fjd_dir)) dispatch_cmd = 'fjd-dispatcher --project {} --end_when_jobs_are_done '\ ' --callback "stosim --folder {} --kill" --interval {}'\ .format(sim_name, simfolder, utils.get_interval(simfolder)) # now decide if recruiting is done in a local network or on a PBS cluster scheduler = utils.get_scheduler(simfolder) if scheduler == 'fjd': # let FJD handle it in local network (default: only local PC) if os.path.exists('{}/remote.conf'.format(simfolder)): copy('{}/remote.conf'.format(simfolder), fjd_dir) subprocess.call('fjd-recruiter --project {} hire'.format(sim_name), shell=True) if not we_exited[0]: subprocess.call(dispatch_cmd, shell=True) # when recruiter got remote.conf, clean up in fjd dir if os.path.exists('{}/remote.conf'.format(simfolder)): os.remove('{}/remote.conf'.format(fjd_dir)) elif scheduler == 'pbs': # queue the PBS jobs we created on a PBS job scheduler (e.g. clusters # running Torque or PBS Pro). These simply start FJD workers. for job in [j for j in os.listdir('{}/jobs'.format(simfolder)) if j.endswith('.pbs')]: subprocess.call('qsub {}'.format('{}/jobs/{}'.format(simfolder, job)), shell=True) # Now we start dispatching subprocess.call(dispatch_cmd, shell=True) return True
[docs]def resume(simfolder): """ (Re)start dispatching jobs :param string simfolder: relative path to simfolder :returns: True if successful, False otherwise """ sim_name = utils.get_simulation_name(simfolder, "{}/stosim.conf".format(simfolder)) subprocess.call('fjd-dispatcher --project {} --interval {}'\ .format(sim_name, utils.get_interval(simfolder)), shell=True) return True
[docs]def status(simfolder): """ Check status of simulation. If on PBS scheduling, show status of nodes. Then, ask the fjd-dispatcher about status of jobs. :param string simfolder: relative path to simfolder :returns: True if successful, False otherwise """ scheduler = utils.get_scheduler(simfolder) sim_name = utils.get_simulation_name(simfolder, "{}/stosim.conf".format(simfolder)) if scheduler == 'pbs': num_nodes = len([n for n in os.listdir('{}/jobs'.format(simfolder))\ if n.endswith('.pbs')]) if num_nodes > 0: print("[StoSim] State of our {} PBS computing nodes:".format(num_nodes)) subprocess.call('echo "Waiting: $(qselect -u $USER -s W | wc -l)"', shell=True) subprocess.call('echo "Queued: $(qselect -u $USER -s Q | wc -l)"', shell=True) subprocess.call('echo "Running: $(qselect -u $USER -s R | wc -l)"', shell=True) else: print("[StoSim] No PBS computing nodes seem to be configured...") print("[StoSim] State of workers and jobs:") subprocess.call('fjd-dispatcher --project {} --status_only --interval {}'\ .format(sim_name, utils.get_interval(simfolder)), shell=True) return True
[docs]def snapshot(simfolder, identifier=None): """ Make a snapshot of the current state, using rsync with an example from http://schlutech.com/2011/11/rsync-full-incremental-differential-snapshots/ :param string simfolder: relative path to simfolder :param string identifier: custom identifier for this snapshot :returns: True if successful, False otherwise """ snapfolder = 'stosim-snapshots' date_format = "+%Y.%m.%d_%H:%M:%S" if not identifier: print('[StoSim] If wanted, enter a custom identifier:') identifier = input().replace(' ', '_') # TODO: replace other characters? if not os.path.exists("{}/{}".format(simfolder, snapfolder)): os.mkdir("{}/{}".format(simfolder, snapfolder)) rsync = 'rsync' else: rsync = 'link_dest=`find {abs_sf}/{ss} -maxdepth 1 -type d | sort | tail -n 1`;'\ ' rsync --link-dest=${{link_dest}}'\ .format(abs_sf=os.path.abspath(simfolder), ss=snapfolder) subprocess.call('cd {sf}; {rsync} -a --exclude {ss} --exclude \.svn --exclude \.git --exclude \.hg'\ ' . {ss}/`date {df}`_{ident}; cd {curdir}'\ .format(rsync=rsync, sf=simfolder, ss=snapfolder, df=date_format, ident=identifier, curdir=os.path.abspath(os.curdir)), shell=True) print('[StoSim] Made a new snapshot in {}/{}.'.format(simfolder, snapfolder)) return True
[docs]def kill(simfolder): """ Kill simulation Warning: On pbs, kills all your jobs (ignores --project)! """ scheduler = utils.get_scheduler(simfolder) sim_name = utils.get_simulation_name(simfolder, "{}/stosim.conf".format(simfolder)) if scheduler == 'fjd': subprocess.call('fjd-recruiter --project {} fire'.format(sim_name), shell=True) elif scheduler == 'pbs': subprocess.call('qselect -u $USER | xargs qdel', shell=True) return True
[docs]def run_more(simfolder): """ let the user make more runs on current config, in addition to the given data :param string simfolder: relative path to simfolder :returns: True if successful, False otherwise """ simfolder = simfolder.strip('/') conf = utils.get_combined_conf(simfolder) print(''' [StoSim] Let's make {} more run(s)! Please tell me on which configurations.\n Enter any parameter values you want to narrow down to, nothing otherwise." '''.format(conf.getint('control', 'runs'))) sel_params = {} for o in conf.options('params'): selected = False params = [p.strip() for p in conf.get('params', o).split(',')] if len(params) == 1: print("<{}> has only one value:".format(o)) print(params[0]) continue while not selected: choice = [] print("<{}> ? (out of [{}])".format(o, conf.get('params', o))) for selection in input().split(','): selected = True if selection == "": pass elif selection in params: choice.append(selection) else: print("Sorry, {} is not a valid value.".format(selection)) selected = False if len(choice) > 0: sel_params[o] = choice else: print("No restriction chosen.") print("You selected: {}. Do this? [Y|n]\n"\ "(Remember that configuration and code should still be the same!)"\ .format(str(sel_params))) if input().lower() in ["", "y"]: prepare_folders_and_jobs(simfolder, limit_to=sel_params, more=True) return run(simfolder) return False
[docs]def make_plots(simfolder, plot_nrs=[]): """ generate plots as specified in the simulation conf :param string simfolder: relative path to simfolder :param list plot_nrs: a list with plot indices. If empty, plot all """ simfolder = simfolder.strip('/') #if osp.exists("%s/plots" % simfolder): # rmtree('%s/plots' % simfolder) if not osp.exists("%s/plots" % simfolder): os.mkdir('%s/plots' % simfolder) # tell about what we'll do if we have at least one plot relevant_confs = utils.get_relevant_confs(simfolder) for c in relevant_confs: if c.has_section("figure1"): print('') print('*' * 80) print("[StoSim] creating plots ...") print('*' * 80) print('') break else: print("[StoSim] No plots specified.") # Describe all options first. # These might be set in plot-settings (in each simulation config) # and also per-figure general_options = {'use-colors': bool, 'use-tex': bool, 'line-width': int, 'font-size': int, 'infobox-pos': str, 'use-y-errorbars': bool, 'errorbar-every': int } figure_specific_options = { 'name': str, 'xcol': int, 'x-range': str, 'y-range': str, 'x-label': str, 'y-label': str, 'custom-script': str } figure_specific_options.update(general_options) def get_opt_val(conf, d, section, option, t): if conf.has_option(section, option): val = c.get(section, option).strip() if t is int: val = c.getint(section, option) if t is bool: val = c.getboolean(section, option) if t is float: val = c.getfloat(section, option) # config-options with '-' are nice, but not good parameter names d[option.replace('-', '_')] = val general_settings = {} c = configparser.ConfigParser() c.read('{}/stosim.conf'.format((simfolder))) delim = utils.get_delimiter(c) for o, t in general_options.items(): get_opt_val(c, general_settings, 'plot-settings', o, t) general_params = [] if c.has_option('plot-settings', 'params'): general_params = c.get('plot-settings', 'params').split(',') for c in relevant_confs: i = 1 settings = general_settings.copy() # overwrite with plot-settings for this subsimulation for o, t in general_options.items(): get_opt_val(c, settings, 'plot-settings', o, t) if c.has_option('plot-settings', 'params'): general_params.extend(c.get('plot-settings', 'params').split(',')) while c.has_section("figure%i" % i): if i in plot_nrs or len(plot_nrs) == 0: fig_settings = settings.copy() for o, t in figure_specific_options.items(): get_opt_val(c, fig_settings, 'figure%i' % i, o, t) plot_confs = [] j = 1 while c.has_option("figure%i" % i, "plot%i" % j): # make plot settings from conf string d = utils.decode_search_from_confstr( c.get('figure%i' % i, 'plot%i' % j), sim=c.get('meta', 'name') ) # then add general param settings to each plot, if # not explicitly in there for param in general_params: if ":" in param: param = param.split(':') key = param[0].strip() if not key in list(d.keys()): d[key] = param[1].strip() # add simulation file name, then we can select accordingly if c.has_option('meta', '_subconf-filename_'): scfn = c.get('meta', '_subconf-filename_') if scfn != '' and not 'sim' in d: d['sim'] = scfn # making sure all necessary plot attributes are there if ('_name' in list(d.keys())and '_ycol' in list(d.keys()) and '_type' in list(d.keys())): plot_confs.append(d) else: print(''' [StoSim] Warning: Incomplete graph specification in Experiment %s - for plot {} in figure {}. \n Specify at least _name and _ycol.'''.format((c.get('meta', 'name'), j, i))) j += 1 plotter.plot(filepath='%s/data' % simfolder, delim=delim, outfile_name='%s/plots/%s.pdf' \ % (simfolder, fig_settings['name']),\ plots=plot_confs,\ **fig_settings) i += 1
[docs]def run_ttests(simfolder): ''' Make statistical t tests :param string simfolder: relative path to simfolder ''' c = configparser.ConfigParser() c.read('{}/stosim.conf'.format((simfolder))) delim = utils.get_delimiter(c) relevant_confs = utils.get_relevant_confs(simfolder) # tell about what we'll do if we have at least one test for c in relevant_confs: if c.has_section("ttest1"): print('') print('*' * 80) print("[StoSim] Running T-tests ...") print('*' * 80) print('') break else: print("[StoSim] No T-tests specified.") for c in relevant_confs: i = 1 while c.has_section("ttest%i" % i): print("Test {}:".format(c.get('ttest%i' % i, 'name').strip('"'))) if not (c.has_option("ttest%i" % i, "set1") and c.has_option("ttest%i" % i, "set2")): print("[StoSim] T-test {} is missing one or both"\ " data set descriptions.".format(i)) break tester.ttest(simfolder, c, i, delim) i += 1
[docs]def list_data(simfolder): """ List the number of runs that have been made per configuration. :param string simfolder: relative path to simfolder :returns: True if successful, False otherwise """ print("[StoSim] The configurations and number of runs made so far:\n") for sim in utils.get_subsimulation_names(utils.get_main_conf(simfolder)): print("Simulation: {}".format(sim)) # get a list w/ relevant params from first-found config file # they should be the same data_dirs = os.listdir("%s/data" % simfolder) sim_dirs = [d for d in data_dirs if d.startswith("sim{}".format(sim))] if len(sim_dirs) == 0: print("No runs found for simulation {}\n".format(sim)) continue conf = utils.get_combined_conf(simfolder) params = conf.options('params') charlen = 1 + sum([len(p) + 6 for p in params]) + 9 print('-' * charlen) print("|"), for p in params: print(" {} |".format(p)), print("| runs |") print('-' * charlen) # now show how much we have in each relevant dir for d in sim_dirs: print("|"), for p in params: v = d.split("_{}".format(p))[1].split("_")[0] print(" {}|".format(v.ljust(len(p) + 2))), print("| {} |".format(str(utils.runs_in_folder(simfolder, d)).rjust(4))) print('-' * charlen) return True
[docs]def prepare_folders_and_jobs(simfolder, limit_to={}, more=False): """ ensure that data and job directories exist, create jobs. limit_to can contain parameter settings we want to limit ourselves to (this is in case we add more data) :param string simfolder: relative path to simfolder :param dict limit_to: key-value pairs that narrow down the dataset, when empty (default) all possible configs are run :param boolean more: when True, new data will simply be added """ if not osp.exists("%s/data" % simfolder): os.mkdir('%s/data' % simfolder) if osp.exists("%s/jobs" % simfolder): rmtree('%s/jobs' % simfolder) os.mkdir('%s/jobs' % simfolder) conf = utils.get_main_conf(simfolder) create_jobs(conf, simfolder, limit_to=limit_to, more=more)