Beispiel #1
0
    def run_all_times(self):
        """! Build up the command to invoke the MET tool tc_pairs.
        """

        # pylint:disable=protected-access
        # sys._getframe is a legitimate way to access the current
        # filename and method.
        # Used for logging information
        cur_filename = sys._getframe().f_code.co_filename
        cur_function = sys._getframe().f_code.co_name

        missing_values = \
            (self.config.getstr('config', 'MISSING_VAL_TO_REPLACE'),
             self.config.getstr('config', 'MISSING_VAL'))

        # Get the desired YYYYMMDD_HH init increment list
        # convert the increment INIT_INC from seconds to hours
        init_list = util.gen_init_list(
            self.config.getstr('config', 'INIT_BEG'),
            self.config.getstr('config', 'INIT_END'),
            int(self.config.getint('config', 'INIT_INC') / 3600),
            self.config.getstr('config', 'INIT_HOUR_END'))

        # get a list of YYYYMM values
        year_month_list = []
        for init in init_list:
            if init[0:6] not in year_month_list:
                year_month_list.append(init[0:6])

        # Set up the environment variable to be used in the TCPairs Config
        # file (TC_PAIRS_CONFIG_FILE)
        # Used to set init_inc in "TC_PAIRS_CONFIG_FILE"
        # Need to do some pre-processing so that Python will use " and not '
        # because currently MET
        # doesn't support single-quotes
        tmp_init_string = str(init_list)
        tmp_init_string = tmp_init_string.replace("\'", "\"")
        os.environ['INIT_INC'] = tmp_init_string

        # Get a directory path listing of the dated subdirectories
        # (YYYYMM format) in the track_data directory
        dir_list = []
        # Get a list of all the year_month directories in the
        # track data directory specified in the config file.
        for year_month in os.listdir(self.config.getdir('TRACK_DATA_DIR')):
            # if the full directory path isn't an empty directory,
            # check if the current year_month is the requested time.
            if os.path.isdir(os.path.join(self.config.getdir('TRACK_DATA_DIR'),
                                          year_month)) \
                    and year_month in year_month_list:
                dir_list.append(
                    os.path.join(self.config.getdir('TRACK_DATA_DIR'),
                                 year_month))

        if not dir_list:
            self.logger.warning("ERROR | [" + cur_filename + ":" +
                                cur_function + "] | There are no dated"
                                "sub-directories (YYYYMM) " +
                                "with input data as expected in: " +
                                self.config.getdir('TRACK_DATA_DIR'))
            exit(0)

        # Get a list of files in the dated subdirectories
        for mydir in dir_list:
            myfiles = os.listdir(mydir)
            # Need to do extra processing if track_type is extra_tropical
            # cyclone
            if self.config.getstr('config',
                                  'TRACK_TYPE') == "extra_tropical_cyclone":
                # Create an atcf output directory for writing the modified
                # files
                adeck_mod = os.path.join(
                    self.config.getdir('TRACK_DATA_SUBDIR_MOD'),
                    os.path.basename(mydir))
                bdeck_mod = os.path.join(
                    self.config.getdir('TRACK_DATA_SUBDIR_MOD'),
                    os.path.basename(mydir))
                produtil.fileop.makedirs(adeck_mod, logger=self.logger)

            # Iterate over the files, modifying them and writing new output
            # files if necessary ("extra_tropical_cyclone" track type), and
            # run tc_pairs
            for myfile in myfiles:
                # Check to see if the files have the ADeck prefix
                if myfile.startswith(
                        self.config.getstr('config', 'ADECK_FILE_PREFIX')):
                    # Create the output directory for the pairs, if
                    # it doesn't already exist
                    pairs_out_dir = \
                        os.path.join(self.config.getdir('TC_PAIRS_DIR'),
                                     os.path.basename(mydir))
                    produtil.fileop.makedirs(pairs_out_dir, logger=self.logger)

                    # Need to do extra processing if track_type is
                    # extra_tropical_cyclone
                    if self.config.getstr('config',
                                          'TRACK_TYPE') == \
                            "extra_tropical_cyclone":

                        # Form the adeck and bdeck input filename paths
                        adeck_in_file_path = os.path.join(mydir, myfile)
                        bdeck_in_file_path = re.sub(
                            self.config.getstr('config', 'ADECK_FILE_PREFIX'),
                            self.config.getstr('config', 'BDECK_FILE_PREFIX'),
                            adeck_in_file_path)
                        adeck_file_path = os.path.join(adeck_mod, myfile)
                        bdeck_file_path = os.path.join(
                            bdeck_mod,
                            re.sub(
                                self.config.getstr('config',
                                                   'ADECK_FILE_PREFIX'),
                                self.config.getstr('config',
                                                   'BDECK_FILE_PREFIX'),
                                myfile))

                        # Get the storm number e.g. 0004 in
                        # amlq2012033118.gfso.0004
                        # split_basename = myfile.split(".")

                        # Get the YYYYMM e.g 201203 in amlq2012033118.gfso.0004
                        year_month = myfile[4:10]

                        # Get the MM from the YYYYMM
                        storm_month = year_month[-2:]

                        # Set up the adeck and bdeck track file paths for the
                        # extra tropical cyclone data.
                        self.setup_tropical_track_dirs(adeck_in_file_path,
                                                       adeck_file_path,
                                                       storm_month,
                                                       missing_values)

                        # Read in the bdeck file, modify it,
                        # and write a new bdeck file
                        # Check for existence of data and overwrite if desired
                        self.setup_tropical_track_dirs(bdeck_in_file_path,
                                                       bdeck_file_path,
                                                       storm_month,
                                                       missing_values)
                    else:
                        # Set up the adeck and bdeck file paths
                        adeck_file_path = os.path.join(mydir, myfile)
                        bdeck_file_path = re.sub(
                            self.config.getstr('config', 'ADECK_FILE_PREFIX'),
                            self.config.getstr('config', 'BDECK_FILE_PREFIX'),
                            adeck_file_path)

                    # Run tc_pairs
                    cmd = self.build_tc_pairs(pairs_out_dir, myfile,
                                              adeck_file_path, bdeck_file_path)
                    cmd = batchexe('sh')['-c', cmd].err2out()
                    ret = run(cmd, sleeptime=.00001)
                    if ret != 0:
                        self.logger.error("ERROR | [" + cur_filename + ":" +
                                          cur_function + "] | " +
                                          "Problem executing: " +
                                          cmd.to_shell())
                        exit(0)
Beispiel #2
0
def retrieve_and_regrid(tmp_filename, cur_init, cur_storm, out_dir, logger,
                        config):
    """! Retrieves the data from the MODEL_DATA_DIR (defined in metplus.conf)
         that corresponds to the storms defined in the tmp_filename:
        1) create the analysis tile and forecast file names from the
           tmp_filename file.
        2) perform regridding via MET tool (regrid_data_plane) and store
           results (netCDF files) in the out_dir or via
           Regridding via  regrid_data_plane on the forecast and analysis
           files via a latlon string with the following format:
                latlon Nx Ny lat_ll lon_ll delta_lat delta_lon
                NOTE:  these values are defined in the extract_tiles_parm
                parameter/config file as NLAT, NLON.
        Args:
        @param tmp_filename:   Filename of the temporary filter file in
                               the /tmp directory. Contains rows
                               of data corresponding to a storm id of varying
                               times.
        @param cur_init:       The current init time
        @param cur_storm:      The current storm
        @param out_dir:  The directory where regridded netCDF or grib2 output
                         is saved depending on which regridding methodology is
                         requested.  If the MET tool regrid_data_plane is
                         requested, then netCDF data is produced.  If wgrib2
                         is requested, then grib2 data is produced.
        @param logger:  The name of the logger used in logging.
        @param config:  config instance
        Returns:
           None
    """

    # pylint: disable=protected-access
    # Need to call sys._getframe() to get current function and file for
    # logging information.
    # pylint: disable=too-many-arguments
    # all input is needed to perform task

    # For logging
    cur_filename = sys._getframe().f_code.co_filename
    cur_function = sys._getframe().f_code.co_name

    # Get variables, etc. from param/config file.
    model_data_dir = config.getdir('MODEL_DATA_DIR')
    metplus_base = config.getdir('MET_BUILD_BASE')
    regrid_data_plane_exe = os.path.join(metplus_base, 'bin/regrid_data_plane')
    # regrid_data_plane_exe = config.getexe('REGRID_DATA_PLANE_EXE')
    wgrib2_exe = config.getexe('WGRIB2')
    egrep_exe = config.getexe('EGREP_EXE')
    regrid_with_met_tool = config.getbool('config', 'REGRID_USING_MET_TOOL')
    overwrite_flag = config.getbool('config', 'OVERWRITE_TRACK')

    # Extract the columns of interest: init time, lead time,
    # valid time lat and lon of both tropical cyclone tracks, etc.
    # Then calculate the forecast hour and other things.
    with open(tmp_filename, "r") as tf:
        # read header
        header = tf.readline().split()
        # get column number for columns on interest
        # print('header{}:'.format(header))
        header_colnum_init, header_colnum_lead, header_colnum_valid = \
            header.index('INIT'), header.index('LEAD'), header.index('VALID')
        header_colnum_alat, header_colnum_alon =\
            header.index('ALAT'), header.index('ALON')
        header_colnum_blat, header_colnum_blon = \
            header.index('BLAT'), header.index('BLON')
        for line in tf:
            col = line.split()
            init, lead, valid, alat, alon, blat, blon = \
                col[header_colnum_init], col[header_colnum_lead], \
                col[header_colnum_valid], col[header_colnum_alat], \
                col[header_colnum_alon], col[header_colnum_blat], \
                col[header_colnum_blon]

            # integer division for both Python 2 and 3
            lead_time = int(lead)
            fcst_hr = lead_time // 10000

            init_ymd_match = re.match(r'[0-9]{8}', init)
            if init_ymd_match:
                init_ymd = init_ymd_match.group(0)
            else:
                logger.WARN("RuntimeError raised")
                raise RuntimeError('init time has unexpected format for YMD')

            init_ymdh_match = re.match(r'[0-9|_]{11}', init)
            if init_ymdh_match:
                init_ymdh = init_ymdh_match.group(0)
            else:
                logger.WARN("RuntimeError raised")

            valid_ymd_match = re.match(r'[0-9]{8}', valid)
            if valid_ymd_match:
                valid_ymd = valid_ymd_match.group(0)
            else:
                logger.WARN("RuntimeError raised")

            valid_ymdh_match = re.match(r'[0-9|_]{11}', valid)
            if valid_ymdh_match:
                valid_ymdh = valid_ymdh_match.group(0)
            else:
                logger.WARN("RuntimeError raised")

            lead_str = str(fcst_hr).zfill(3)
            fcst_dir = os.path.join(model_data_dir, init_ymd)
            init_ymdh_split = init_ymdh.split("_")
            init_yyyymmddhh = "".join(init_ymdh_split)
            anly_dir = os.path.join(model_data_dir, valid_ymd)
            valid_ymdh_split = valid_ymdh.split("_")
            valid_yyyymmddhh = "".join(valid_ymdh_split)

            # Create output filenames for regridding
            # wgrib2 used to regrid.
            # Create the filename for the regridded file, which is a
            # grib2 file.
            fcst_sts = \
                StringSub(logger, config.getraw('filename_templates',
                                                'GFS_FCST_FILE_TMPL'),
                          init=init_yyyymmddhh, lead=lead_str)

            anly_sts = \
                StringSub(logger, config.getraw('filename_templates',
                                                'GFS_ANLY_FILE_TMPL'),
                          valid=valid_yyyymmddhh, lead=lead_str)

            fcst_file = fcst_sts.doStringSub()
            fcst_filename = os.path.join(fcst_dir, fcst_file)
            anly_file = anly_sts.doStringSub()
            anly_filename = os.path.join(anly_dir, anly_file)

            # Check if the forecast input file exists. If it doesn't
            # exist, just log it
            if file_exists(fcst_filename):
                msg = ("INFO| [" + cur_filename + ":" + cur_function +
                       " ] | Forecast file: " + fcst_filename)
                logger.debug(msg)
            else:
                msg = ("WARNING| [" + cur_filename + ":" + cur_function +
                       " ] | " +
                       "Can't find forecast file, continuing anyway: " +
                       fcst_filename)
                logger.debug(msg)
                continue

            # Check if the analysis input file exists. If it doesn't
            # exist, just log it.
            if file_exists(anly_filename):
                msg = ("INFO| [" + cur_filename + ":" + cur_function +
                       " ] | Analysis file: " + anly_filename)
                logger.debug(msg)

            else:
                msg = ("WARNING| [" + cur_filename + ":" + cur_function +
                       " ] | " +
                       "Can't find analysis file, continuing anyway: " +
                       anly_filename)
                logger.debug(msg)
                continue

            # Create the arguments used to perform regridding.
            # NOTE: the base name
            # is the same for both the fcst and anly filenames,
            # so use either one to derive the base name that will be used to
            # create the fcst_regridded_filename and anly_regridded_filename.
            fcst_anly_base = os.path.basename(fcst_filename)

            fcst_grid_spec = create_grid_specification_string(
                alat, alon, logger, config)
            anly_grid_spec = create_grid_specification_string(
                blat, blon, logger, config)
            if regrid_with_met_tool:
                nc_fcst_anly_base = re.sub("grb2", "nc", fcst_anly_base)
                fcst_anly_base = nc_fcst_anly_base

            tile_dir = os.path.join(out_dir, cur_init, cur_storm)
            fcst_hr_str = str(fcst_hr).zfill(3)

            fcst_regridded_filename = \
                config.getstr('regex_pattern', 'FCST_TILE_PREFIX') + \
                fcst_hr_str + "_" + fcst_anly_base
            fcst_regridded_file = os.path.join(tile_dir,
                                               fcst_regridded_filename)
            anly_regridded_filename = \
                config.getstr('regex_pattern', 'ANLY_TILE_PREFIX') +\
                fcst_hr_str + "_" + fcst_anly_base
            anly_regridded_file = os.path.join(tile_dir,
                                               anly_regridded_filename)

            # Regrid the fcst file only if a fcst tile
            # file does NOT already exist or if the overwrite flag is True.
            # Create new gridded file for fcst tile
            if file_exists(fcst_regridded_file) and not overwrite_flag:
                msg = ("INFO| [" + cur_filename + ":" + cur_function +
                       " ] | Forecast tile file " + fcst_regridded_file +
                       " exists, skip regridding")
                logger.debug(msg)
            else:
                # Perform fcst regridding on the records of interest
                var_level_string = retrieve_var_info(config, logger)
                if regrid_with_met_tool:
                    # Perform regridding using MET Tool regrid_data_plane
                    fcst_cmd_list = [
                        regrid_data_plane_exe, ' ', fcst_filename, ' ',
                        fcst_grid_spec, ' ', fcst_regridded_file, ' ',
                        var_level_string, ' -method NEAREST '
                    ]
                    regrid_cmd_fcst = ''.join(fcst_cmd_list)
                    regrid_cmd_fcst = \
                        batchexe('sh')['-c', regrid_cmd_fcst].err2out()
                    msg = ("INFO|[regrid]| regrid_data_plane regrid command:" +
                           regrid_cmd_fcst.to_shell())
                    logger.debug(msg)
                    run(regrid_cmd_fcst)

                else:
                    # Perform regridding via wgrib2
                    requested_records = retrieve_var_info(config, logger)
                    fcst_cmd_list = [
                        wgrib2_exe, ' ', fcst_filename, ' | ', egrep_exe, ' ',
                        requested_records, '|', wgrib2_exe, ' -i ',
                        fcst_filename, ' -new_grid ', fcst_grid_spec, ' ',
                        fcst_regridded_file
                    ]
                    wgrb_cmd_fcst = ''.join(fcst_cmd_list)
                    wgrb_cmd_fcst = \
                        batchexe('sh')['-c', wgrb_cmd_fcst].err2out()
                    msg = ("INFO|[wgrib2]| wgrib2 regrid command:" +
                           wgrb_cmd_fcst.to_shell())
                    logger.debug(msg)
                    run(wgrb_cmd_fcst)

            # Create new gridded file for anly tile
            if file_exists(anly_regridded_file) and not overwrite_flag:
                logger.debug("INFO| [" + cur_filename + ":" + cur_function +
                             " ] |" + " Analysis tile file: " +
                             anly_regridded_file + " exists, skip regridding")
            else:
                # Perform anly regridding on the records of interest
                var_level_string = retrieve_var_info(config, logger)
                if regrid_with_met_tool:
                    anly_cmd_list = [
                        regrid_data_plane_exe, ' ', anly_filename, ' ',
                        anly_grid_spec, ' ', anly_regridded_file, ' ',
                        var_level_string, ' ', ' -method NEAREST '
                    ]
                    regrid_cmd_anly = ''.join(anly_cmd_list)
                    regrid_cmd_anly = \
                        batchexe('sh')['-c', regrid_cmd_anly].err2out()
                    run(regrid_cmd_anly)
                    msg = ("INFO|[regrid]| on anly file:" +
                           anly_regridded_file)
                    logger.debug(msg)
                else:
                    # Regridding via wgrib2.
                    requested_records = retrieve_var_info(config, logger)
                    anly_cmd_list = [
                        wgrib2_exe, ' ', anly_filename, ' | ', egrep_exe, ' ',
                        requested_records, '|', wgrib2_exe, ' -i ',
                        anly_filename, ' -new_grid ', anly_grid_spec, ' ',
                        anly_regridded_file
                    ]
                    wgrb_cmd_anly = ''.join(anly_cmd_list)
                    wgrb_cmd_anly = \
                        batchexe('sh')['-c', wgrb_cmd_anly].err2out()
                    msg = ("INFO|[wgrib2]| Regridding via wgrib2:" +
                           wgrb_cmd_anly.to_shell())
                    run(wgrb_cmd_anly)
                    logger.debug(msg)
Beispiel #3
0
    def run_cmd(self,
                cmd,
                env=None,
                log_name=None,
                copyable_env=None,
                **kwargs):
        """!The command cmd is a string which is converted to a produtil
        exe Runner object and than run. Output of the command may also
        be redirected to either METplus log, MET log, or TTY.

        Some subclasses of CommandBuilder ie. series_by_init_wrapper, run
        non MET commands ie. convert, in addition to MET binary commands,
        ie. regrid_data_plane.

        Args:
            @param cmd: A string, Command used in the produtil exe Runner object.
            @param env: Default None, environment for run to pass in, uses
            os.environ if not set.
            @param log_name: Used only when ismetcmd=True, The name of the exectable
            being run.
            @param kwargs Other options sent to the produtil Run constructor
        """

        if cmd is None:
            return cmd

        # if env not set, use os.environ
        if env is None:
            env = os.environ

        self.logger.info("COMMAND: %s" % cmd)

        # don't run app if DO_NOT_RUN_EXE is set to True
        if self.skip_run:
            self.logger.info("Not running command (DO_NOT_RUN_EXE = True)")
            return 0, cmd

        # self.log_name MUST be defined in the subclass' constructor,
        # this code block is a safety net in case that was not done.
        # self.log_name is used to generate the MET log output name,
        # if output is directed there, based on the conf settings.
        #
        # cmd.split()[0] 'should' be the /path/to/<some_met_binary>
        if not log_name:
            log_name = os.path.basename(cmd.split()[0])
            self.logger.warning('MISSING self.log_name, '
                                'setting name to: %s' % repr(log_name))
            self.logger.warning('Fix the code and edit the following objects'
                                ' contructor: %s, ' % repr(self))

        # Determine where to send the output from the MET command.
        log_dest = self.cmdlog_destination(cmdlog=log_name + '.log')

        # determine if command must be run in a shell
        run_inshell = False
        if '*' in cmd or ';' in cmd or '<' in cmd or '>' in cmd:
            run_inshell = True

        # KEEP This comment as a reference note.
        # Run the executable in a new process instead of through a shell.
        # FYI. We were originally running the command through a shell
        # which also works just fine. IF we go back to running through
        # a shell, The string ,cmd, is formatted exactly as is needed.
        # By formatted, it is as it would be when typed at the shell prompt.
        # This includes, for example, quoting or backslash escaping filenames
        # with spaces in them.

        # Run the executable and pass the arguments as a sequence.
        # Split the command in to a sequence using shell syntax.
        the_exe = shlex.split(cmd)[0]
        the_args = shlex.split(cmd)[1:]
        if log_dest:
            self.logger.debug("log_name is: %s, output sent to: %s" %
                              (log_name, log_dest))

            self.log_header_info(log_dest, copyable_env, cmd)

            if run_inshell:
                cmd_exe = exe('sh')['-c', cmd].env(**env).err2out() >> log_dest
            else:
                cmd_exe = exe(the_exe)[the_args].env(
                    **env).err2out() >> log_dest
        else:
            if run_inshell:
                cmd_exe = exe('sh')['-c', cmd].env(**env)
            else:
                cmd_exe = exe(the_exe)[the_args].env(**env).err2out()

        # get current time to calculate total time to run command
        start_cmd_time = datetime.now()

        # run command
        try:
            ret = run(cmd_exe, **kwargs)
        except:
            ret = -1
        else:
            # calculate time to run
            end_cmd_time = datetime.now()
            total_cmd_time = end_cmd_time - start_cmd_time
            self.logger.debug(f'Finished running {the_exe} '
                              f'in {total_cmd_time}')

        return ret, cmd
Beispiel #4
0
def main():
    """!Main program.

    Master MET+ script that invokes the necessary Python scripts
    to perform various activities, such as series analysis."""

    # Job Logger
    produtil.log.jlogger.info('Top of master_metplus')

    # Setup Task logger, Until Conf object is created, Task logger is
    # only logging to tty, not a file.
    logger = logging.getLogger('master_metplus')
    logger.info('logger Top of master_metplus.')

    # Used for logging and usage statment
    cur_filename = sys._getframe().f_code.co_filename
    cur_function = sys._getframe().f_code.co_name

    short_opts = "c:r:h"
    long_opts = ["config=", "help", "runtime="]
    # All command line input, get options and arguments
    try:
        opts, args = getopt.gnu_getopt(sys.argv[1:], short_opts, long_opts)
    except getopt.GetoptError as err:
        print(str(err))
        usage('SCRIPT IS EXITING DUE TO UNRECOGNIZED COMMAND LINE OPTION')
    for k, v in opts:
        if k in ('-c', '--config'):
            # adds the conf file to the list of arguments.
            print("ADDED CONF FILE: " + v)
            args.append(config_launcher.set_conf_file_path(v))
        elif k in ('-h', '--help'):
            usage()
            exit()
        elif k in ('-r', '--runtime'):
            start_time = v
            end_time = v
        else:
            assert False, "UNHANDLED OPTION"
    if not args:
        args = None
    (parm, infiles,
     moreopt) = config_launcher.parse_launch_args(args, usage, None, logger)
    p = config_launcher.launch(infiles, moreopt)

    # NOW I have a conf object p, I can now setup the handler
    # to write to the LOG_FILENAME.
    logger = util.get_logger(p)

    # This is available in each subprocess from os.system BUT
    # we also set it in each process since they may be called stand alone.
    os.environ['MET_BASE'] = p.getdir('MET_BASE')

    # Use config object to get the list of processes to call
    process_list = util.getlist(p.getstr('config', 'PROCESS_LIST'))

    # Keep this comment.
    # When running commands in the process_list, reprocess the
    # original command line using (item))[sys.argv[1:]].
    #
    # You could call each task (ie. run_tc_pairs.py) without any args since
    # the final METPLUS_CONF file was just created from config_metplus.setup,
    # and each task, also calls setup, which use an existing final conf
    # file over command line args.
    #
    # both work ...
    # Note: Using (item))sys.argv[1:], is preferable since
    # it doesn't depend on the conf file existing.
    processes = []
    for item in process_list:
        try:
            command_builder = getattr(sys.modules[__name__],
                                      item + "Wrapper")(p, logger)
        except AttributeError:
            raise NameError("Process %s doesn't exist" % item)
            exit()

        processes.append(command_builder)

    if p.getstr('config', 'LOOP_METHOD') == "processes":
        for process in processes:
            process.run_all_times()

    elif p.getstr('config', 'LOOP_METHOD') == "times":
        time_format = p.getstr('config', 'INIT_TIME_FMT')
        start_t = p.getstr('config', 'INIT_BEG')
        end_t = p.getstr('config', 'INIT_END')
        time_interval = p.getint('config', 'INIT_INC')
        if time_interval < 60:
            print(
                "ERROR: time_interval parameter must be greater than 60 seconds"
            )
            exit(1)

        init_time = calendar.timegm(time.strptime(start_t, time_format))
        end_time = calendar.timegm(time.strptime(end_t, time_format))
        while init_time <= end_time:
            run_time = time.strftime("%Y%m%d%H%M", time.gmtime(init_time))
            print("")
            print("****************************************")
            print("* RUNNING MET+")
            print("* at init time: " + run_time)
            print("****************************************")
            logger.info("****************************************")
            logger.info("* RUNNING MET+")
            logger.info("*  at init time: " + run_time)
            logger.info("****************************************")
            for process in processes:
                process.run_at_time(run_time)
                process.clear()

            init_time += time_interval

    else:
        print("ERROR: Invalid LOOP_METHOD defined. " + \
              "Options are processes, times")
        exit()
    exit()
    for item in process_list:

        cmd_shell = cmd.to_shell()
        logger.info("INFO | [" + cur_filename + ":" + cur_function + "] | " +
                    "Running: " + cmd_shell)
        ret = run(cmd)
        if ret != 0:
            logger.error("ERROR | [" + cur_filename + ":" + cur_function +
                         "] | " + "Problem executing: " + cmd_shell)
            exit(0)
Beispiel #5
0
    def run_cmd(self,
                cmd,
                env=None,
                ismetcmd=True,
                app_name=None,
                run_inshell=False,
                log_theoutput=False,
                copyable_env=None,
                **kwargs):
        """!The command cmd is a string which is converted to a produtil
        exe Runner object and than run. Output of the command may also
        be redirected to either METplus log, MET log, or TTY.

        Some subclasses of CommandBuilder ie. series_by_init_wrapper, run
        non MET commands ie. convert, in addition to MET binary commands,
        ie. regrid_data_plane.

        Args:
            @param cmd: A string, Command used in the produtil exe Runner object.
            @param env: Default None, environment for run to pass in, uses
            os.environ if not set.
            @param ismetcmd: Default True, Will direct output to METplus log,
            Metlog , or TTY. Set to False and use the other keywords as needed.
            @param app_name: Used only when ismetcmd=True, The name of the exectable
            being run.
            @param run_inshell: Used only when ismetcmd=False, will Create a
            runner object with the cmd being run through a shell, exe('sh')['-c', cmd]
            This is required by commands, such as ncdump that are redirecting
            output to a file, and other commands such as the convert command
            when creating animated gifs.
            @param log_theoutput: Used only when ismetcmd=False, will redirect
            the stderr and stdout to a the METplus log file or tty.
            DO Not set to True if the command is redirecting output to a file.
            @param kwargs Other options sent to the produtil Run constructor
        """

        if cmd is None:
            return cmd

        # if env not set, use os.environ
        if env is None:
            env = os.environ

        self.logger.info("COMMAND: %s" % cmd)

        if ismetcmd:

            # self.app_name MUST be defined in the subclass' constructor,
            # this code block is a safety net in case that was not done.
            # self.app_name is used to generate the MET log output name,
            # if output is directed there, based on the conf settings.
            #
            # cmd.split()[0] 'should' be the /path/to/<some_met_binary>
            if not app_name:
                app_name = os.path.basename(cmd.split()[0])
                self.logger.warning('MISSING self.app_name, '
                                    'setting name to: %s' % repr(app_name))
                self.logger.warning(
                    'Fix the code and edit the following objects'
                    ' contructor: %s, ' % repr(self))

            # Determine where to send the output from the MET command.
            log_dest = self.cmdlog_destination(cmdlog=app_name + '.log')

            # KEEP This comment as a reference note.
            # Run the executable in a new process instead of through a shell.
            # FYI. We were originally running the command through a shell
            # which also works just fine. IF we go back to running through
            # a shell, The string ,cmd, is formatted exactly as is needed.
            # By formatted, it is as it would be when typed at the shell prompt.
            # This includes, for example, quoting or backslash escaping filenames
            # with spaces in them.

            # Run the executable and pass the arguments as a sequence.
            # Split the command in to a sequence using shell syntax.
            the_exe = shlex.split(cmd)[0]
            the_args = shlex.split(cmd)[1:]
            if log_dest:
                self.logger.debug("app_name is: %s, output sent to: %s" %
                                  (app_name, log_dest))

                with open(log_dest, 'a+') as log_file_handle:
                    # if logging MET command to its own log file, add command that was run to that log
                    if self.log_command_to_met_log:
                        # if environment variables were set and available, write them to MET tool log
                        if copyable_env:
                            log_file_handle.write(
                                "\nCOPYABLE ENVIRONMENT FOR NEXT COMMAND:\n")
                            log_file_handle.write(f"{copyable_env}\n\n")
                        else:
                            log_file_handle.write('\n')

                        log_file_handle.write(f"COMMAND:\n{cmd}\n\n")

                    # write line to designate where MET tool output starts
                    log_file_handle.write("MET OUTPUT:\n")

                cmd_exe = exe(the_exe)[the_args].env(
                    **env).err2out() >> log_dest
            else:
                cmd_exe = exe(the_exe)[the_args].env(**env).err2out()

        else:
            # This block is for all the Non-MET commands
            # Some commands still need to be run in  a shell in order to work.
            #
            # There are currently 3 cases of non met commnds that need to be handled.
            # case 1. Redirecting to a file in cmd string, which must be through a shell
            #         ie. cmd = ncdump ...series_F006/min.nc > .../min.txt
            #             cmd = exe('sh')['-c', cmd]
            # case 2. Running the executable directly, w/ arguments, NO redirection
            #         ie. ncap2,  cmd = exe(the_exe)[the_args]
            # case 3. Runnng the command and logging the output to
            #         log_dest
            if run_inshell:
                # set the_exe to log command has finished running
                the_exe = shlex.split(cmd)[0]

                if log_theoutput:
                    log_dest = self.cmdlog_destination()
                    cmd_exe = exe('sh')['-c',
                                        cmd].env(**env).err2out() >> log_dest
                else:
                    cmd_exe = exe('sh')['-c', cmd].env(**env)

            else:
                the_exe = shlex.split(cmd)[0]
                the_args = shlex.split(cmd)[1:]
                if log_theoutput:
                    log_dest = self.cmdlog_destination()
                    cmd_exe = exe(the_exe)[the_args].env(
                        **env).err2out() >> log_dest
                else:
                    cmd_exe = exe(the_exe)[the_args].env(**env)

        ret = 0
        # run app unless DO_NOT_RUN_EXE is set to True
        if not self.config.getbool('config', 'DO_NOT_RUN_EXE', False):
            # get current time to calculate total time to run command
            start_cmd_time = datetime.now()

            # run command
            try:
                ret = run(cmd_exe, **kwargs)
            except:
                ret = -1
            else:
                # calculate time to run
                end_cmd_time = datetime.now()
                total_cmd_time = end_cmd_time - start_cmd_time
                self.logger.debug(
                    f'Finished running {the_exe} in {total_cmd_time}')

        return (ret, cmd)
Beispiel #6
0
    def generate_plots(self, sorted_filter_init, tile_dir):
        """! Generate the plots from the series_analysis output.
           Args:
               @param sorted_filter_init:  A list of the sorted directories
                                        corresponding to the init times (that
                                        are the result of filtering).  If
                                        filtering produced no results, this
                                        is the list of files created from
                                        running extract_tiles.

               @param tile_dir:  The directory where input data resides.
           Returns:
        """
        convert_exe = self.p.getexe('CONVERT_EXE')
        background_map = self.p.getbool('config', 'BACKGROUND_MAP')
        plot_data_plane_exe = os.path.join(self.p.getdir('MET_BUILD_BASE'),
                                           'bin/plot_data_plane')
        for cur_var in self.var_list:
            name, level = util.get_name_level(cur_var, self.logger)
            for cur_init in sorted_filter_init:
                storm_list = self.get_storms_for_init(cur_init, tile_dir)
                for cur_storm in storm_list:
                    # create the output directory where the finished
                    # plots will reside
                    output_dir = os.path.join(self.series_out_dir, cur_init,
                                              cur_storm)
                    util.mkdir_p(output_dir)

                    # Now we need to invoke the MET tool
                    # plot_data_plane to generate plots that are
                    # recognized by the MET viewer.
                    # Get the number of forecast tile files,
                    # the name of the first and last in the list
                    # to be used by the -title option.
                    if tile_dir == self.extract_tiles_dir:
                        # Since filtering was not requested, or
                        # the additional filtering doesn't yield results,
                        # search the series_out_dir
                        num, beg, end = \
                            self.get_fcst_file_info(self.series_out_dir,
                                                    cur_init, cur_storm)
                    else:
                        # Search the series_filtered_out_dir for
                        # the filtered files.
                        num, beg, end = self.get_fcst_file_info(
                            self.series_filtered_out_dir, cur_init, cur_storm)

                    # Assemble the input file, output file, field string,
                    # and title
                    plot_data_plane_input_fname = self.sbi_plotting_out_dir
                    for cur_stat in self.stat_list:
                        plot_data_plane_output = [
                            output_dir, '/series_', name, '_', level, '_',
                            cur_stat, '.ps'
                        ]
                        plot_data_plane_output_fname = ''.join(
                            plot_data_plane_output)
                        os.environ['CUR_STAT'] = cur_stat
                        self.add_env_var('CUR_STAT', cur_stat)

                        # Create versions of the arg based on
                        # whether the background map is requested
                        # in param file.
                        map_data = ' map_data={ source=[];}'

                        if background_map:
                            # Flag set to True, draw background map.
                            field_string_parts = [
                                "'name=", '"series_cnt_', cur_stat, '";',
                                'level="', level, '";', "'"
                            ]
                        else:
                            field_string_parts = [
                                "'name=", '"series_cnt_', cur_stat, '";',
                                'level="', level, '";', map_data, "'"
                            ]

                        field_string = ''.join(field_string_parts)
                        title_parts = [
                            ' -title "GFS Init ', cur_init, ' Storm ',
                            cur_storm, ' ',
                            str(num), ' Forecasts (',
                            str(beg), ' to ',
                            str(end), '),', cur_stat, ' for ', cur_var, '"'
                        ]
                        title = ''.join(title_parts)

                        # Now assemble the entire plot data plane command
                        data_plane_command_parts = \
                            [plot_data_plane_exe, ' ',
                             plot_data_plane_input_fname, ' ',
                             plot_data_plane_output_fname, ' ',
                             field_string, ' ', title]

                        data_plane_command = ''.join(data_plane_command_parts)
                        data_plane_command = \
                            batchexe('sh')[
                                '-c', data_plane_command].err2out()
                        run(data_plane_command)

                        # Now assemble the command to convert the
                        # postscript file to png
                        png_fname = plot_data_plane_output_fname.replace(
                            '.ps', '.png')
                        convert_parts = [
                            convert_exe, ' -rotate 90',
                            ' -background white -flatten ',
                            plot_data_plane_output_fname, ' ', png_fname
                        ]
                        convert_command = ''.join(convert_parts)
                        convert_command = \
                            batchexe('sh')['-c', convert_command].err2out()
                        run(convert_command)
Beispiel #7
0
    def run_cmd(self, cmd, ismetcmd = True, app_name=None, run_inshell=False,
                log_theoutput=False, **kwargs):
        """!The command cmd is a string which is converted to a produtil
        exe Runner object and than run. Output of the command may also
        be redirected to either METplus log, MET log, or TTY.

        Some subclasses of CommandBuilder ie. series_by_init_wrapper, run
        non MET commands ie. convert, in addition to MET binary commands,
        ie. regrid_data_plane.

        Args:
            @param cmd: A string, Command used in the produtil exe Runner object.
            @param ismetcmd: Default True, Will direct output to METplus log,
            Metlog , or TTY. Set to False and use the other keywords as needed.
            @param app_name: Used only when ismetcmd=True, The name of the exectable
            being run.
            @param run_inshell: Used only when ismetcmd=False, will Create a
            runner object with the cmd being run through a shell, exe('sh')['-c', cmd]
            This is required by commands, such as ncdump that are redirecting
            output to a file, and other commands such as the convert command
            when creating animated gifs.
            @param log_theoutput: Used only when ismetcmd=False, will redirect
            the stderr and stdout to a the METplus log file or tty.
            DO Not set to True if the command is redirecting output to a file.
            @param kwargs Other options sent to the produtil Run constructor
        """

        if cmd is None:
            return cmd

        if ismetcmd:

            # self.app_name MUST be defined in the subclass' constructor,
            # this code block is a safety net in case that was not done.
            # self.app_name is used to generate the MET log output name,
            # if output is directed there, based on the conf settings.
            #
            # cmd.split()[0] 'should' be the /path/to/<some_met_binary>
            if not app_name:
                app_name = os.path.basename(cmd.split()[0])
                self.logger.warning('MISSING self.app_name, '
                                    'setting name to: %s' % repr(app_name))
                self.logger.warning('Fix the code and edit the following objects'
                                    ' contructor: %s, ' % repr(self))

            # Determine where to send the output from the MET command.
            log_dest = self.cmdlog_destination(cmdlog=app_name+'.log')

            # KEEP This comment as a reference note.
            # Run the executable in a new process instead of through a shell.
            # FYI. We were originally running the command through a shell
            # which also works just fine. IF we go back to running through
            # a shell, The string ,cmd, is formatted exactly as is needed.
            # By formatted, it is as it would be when typed at the shell prompt.
            # This includes, for example, quoting or backslash escaping filenames
            # with spaces in them.

            # Run the executable and pass the arguments as a sequence.
            # Split the command in to a sequence using shell syntax.
            the_exe = shlex.split(cmd)[0]
            the_args = shlex.split(cmd)[1:]
            if log_dest:
                self.logger.info("app_name is: %s, output sent to: %s" % (app_name, log_dest))
                #cmd = exe('sh')['-c', cmd].err2out() >> log_dest
                cmd = exe(the_exe)[the_args].err2out() >> log_dest
            else:
                #cmd = exe('sh')['-c', cmd].err2out()
                cmd = exe(the_exe)[the_args].err2out()

        else:
            # This block is for all the Non-MET commands
            # Some commands still need to be run in  a shell in order to work.
            #
            # There are currently 3 cases of non met commnds that need to be handled.
            # case 1. Redirecting to a file in cmd string, which must be through a shell
            #         ie. cmd = ncdump ...series_F006/min.nc > .../min.txt
            #             cmd = exe('sh')['-c', cmd]
            # case 2. Running the executable directly, w/ arguments, NO redirection
            #         ie. ncap2,  cmd = exe(the_exe)[the_args]
            # case 3. Runnng the command and logging the output to
            #         log_dest
            if run_inshell:
                if log_theoutput:
                    log_dest = self.cmdlog_destination()
                    cmd = exe('sh')['-c', cmd].err2out() >> log_dest
                else:
                    cmd = exe('sh')['-c', cmd]

            else:
                the_exe = shlex.split(cmd)[0]
                the_args = shlex.split(cmd)[1:]
                if log_theoutput:
                    log_dest = self.cmdlog_destination()
                    cmd = exe(the_exe)[the_args].err2out() >> log_dest
                else:
                    cmd = exe(the_exe)[the_args]


        self.logger.info("RUNNING: %s" % cmd.to_shell())
        self.logger.debug("RUNNING %s" % repr(cmd))

        ret = run(cmd, **kwargs)

        return (ret, cmd)