We’re smarter together. Learn from this collection of community knowledge and add your expertise.

A single-threaded scheduler

by Super Contributor on ‎12-14-2016 10:05 PM - edited on ‎01-03-2017 01:52 PM by Community Manager (1,034 Views)

A few years ago I worked for a large organisation which wasn't prepared, then, to invest in SAS/DI nor a scheduler, at least one that I had access to. So I had to manually run all the hand-written SAS jobs in the suite, some sixty or so, and check the results before I fired off the next one. This annoyed me more than I could say, so I quickly dashed off a scheduler to allow me to make things fire off in the middle of the night and do rudimentary error checking. As you can see, it's quite small, and very efficient. It doesn't do much, because it was never planned to, but what it does it does very well.

 

The only event it is sensitive to is time: it runs the schedule immediately, or delays it until a timestamp. It's possible, by touching a file called 'schedule.stop', to make it stop after the current schedule entry.

 

The schedule dataset contains two variables: job_name and run_flag. The former is suffixed by '.sas', this being the file containing the SAS code for the schedule entry; the latter is Y or N, to allow skipping over an entry.

 

If you find this useful, buy me a beer sometime. It's free for use and redistribution, but please notify me of problems or modifications and improvements.

 

 

%macro schedule(schedule=, start=, control=stagctrl) / des='Single-threaded scheduler';
/*
	Macro name:							schedule.sas
	Author:								Laurie Fleming
	Date-written:						October 2012
	Macros required:					putmsg
										timer
										set_error
	External references:				source (filename)
										
										
	Remarks:
	Go through the (&)schedule dataset, executing job names serially.
	
	The schedule dataset has two variables: job_name (length 32) and run_flag.
	The former must have a SAS file containing the code, suffixed of course with .sas,
	stored in the directory referenced to by 'source'.
	Run_flag is either Y or N; if it's N, the job is skipped over.
	
	The start parameter is a SAS datetime value (in readable format, ddmmmyyyy:hh:mm:ss);
	denoting when the schedule will be kicked off.
	
	Control is the directory/libname where the schedule dataset and flat file schedule.stop
    is stored.
	
	If at any time a 'schedule.stop' file is found, the schedule is stopped.
	
	Modification history:				(in reverse chronological order)
	Laurie Fleming						Documentation updated
	15 December 2016
	
	Laurie Fleming						First written
	2 October 2012
*/
%global iu_rc _start_time;
%local rc dsid job_name run_flag;
%let _start_time =;
%timer;
%if &iu_rc = %then
    %let iu_rc = 0;
%if %eval(&iu_rc > 0) %then %do;
    %putmsg(type=ERROR, msg=Error code non-zero - cannot continue. Check log.);
    %goto EndMac;
    %end;
/*
    Check start time, and delay until then if it's populated.    
*/
%let start = %quote(&start);
%if &start ne %then %do;
    data _null_;
    calculated_start = "&start"dt - datetime();
    select;
       when(missing(calculated_start)) do;
          put 'ERROR: Invalid start time.';
          call symput('iu_rc', '4');
          end;
       when(calculated_start le 0)
          put "WARNING: start time &start has already passed.";
       otherwise do;
          put "INFORMATION: Suspended until &start.";
          call sleep(calculated_start, 1);
          end;
       end;
    stop;
    run;
    %end;
%if %eval(&iu_rc > 0) %then %do;
    %putmsg(type=ERROR, msg=Invalid start time &start.);
    %goto EndMac;
    %end;
%if %eval(not %sysfunc(exist(&control..&schedule))) %then %do;
    %putmsg(type=ERROR, msg=Control dataset &schedule does not exist in &control..);
    %let iu_rc = 16;
    %goto EndMac;
    %end;
/*
    Lock the schedule dataset from modification.
*/
lock &control..&schedule;
%if &syslckrc ne 0 %then %do;
    %let iu_rc = &syslckrc;
    %putmsg(type=ERROR, msg=Cannot get a lock on &control..&schedule);
    %goto EndMac;
    %end;
/*
    Open the schedule, and read through it, submitting code until it's finished.
*/
%let dsid = %sysfunc(open(&control..&schedule));
%do %while(%sysfunc(fetch(&dsid)) = 0 and &iu_rc = 0);
    %let job_name = %sysfunc(strip(%sysfunc(getvarc(&dsid, %sysfunc(varnum(&dsid, job_name))))));
/*
    Create an empty dataset in the work library, to show how far through things have got
    (easier than reading the log).
*/
    data _&job_name(compress=no);
    stop;
    run;
/*
    Check if a flat file called schedule.stop exists. If it does, well, stop. 
*/
    %if %eval(%sysfunc(fileexist(%sysfunc(pathname(&control))\schedule.stop)) = 1) %then %do;
        %putmsg(type=WARNING, msg=Schedule interrupted);
        %let iu_rc = 4;
        %goto EndLoop;
        %end;
    %let run_flag = %sysfunc(upcase(%sysfunc(getvarc(&dsid, %sysfunc(varnum(&dsid, run_flag))))));
    %if &run_flag = Y %then %do;
        %putmsg(type=INFO, msg=Running &job_name..sas);
        %inc source(&job_name..sas);
        %put Return_code: &iu_rc..;
        %set_error;
        %end;
    %EndLoop:
    proc delete data=_&job_name;
    run;
    %end;
%if %eval(&iu_rc ne 0) %then %do;
    %putmsg(type=ERROR, msg=An error occurred processing &job_name.. Check log before continuing.);
    %end;
%let rc = %sysfunc(close(&dsid));
lock &control..&schedule clear;
%EndMac:
%timer;
%mend schedule;

%macro putmsg(msg=, type=ERROR) / des='Display macro error messages in the log';
%if &type =
    %then %let type = ERROR;
    %else %let type = %upcase(&type);
%put;
%let msg = %quote(&msg);
%if &msg =
    %then %put &type: (No further information);
    %else %put &type: &msg;
%mend putmsg;

%macro timer / des='Calculate execution times.';
%global _start_time _end_time _elapsed;
%if &_start_time =
    %then %do;
          %let _start_time = %sysfunc(datetime());
          %put INFORMATION: Process started at %sysfunc(left(%sysfunc(putn(&_start_time, datetime20.3)))).;
          %end;
    %else %do;
          %let _end_time = %sysfunc(datetime());
          %let _elapsed = %sysevalf(&_end_time - &_start_time);
          %put INFORMATION: Process ended at %sysfunc(left(%sysfunc(putn(&_end_time, datetime20.3)))).;
          %put INFORMATION: Elapsed time: %sysfunc(left(%sysfunc(putn(&_elapsed, time15.3)))).;
          %let _start_time =;
          %end;
%mend timer;

%macro set_error;
%global iu_rc;
%if &iu_rc = %then
    %let iu_rc = 0;
%if &sqlrc = %then
    %let sqlrc = 0;
%let iu_rc = %sysfunc(max(&iu_rc, &syserr, &sysrc, &sqlrc));
%mend set_error;

 

Your turn
Sign In!

Want to write an article? Sign in with your profile.


Looking for the Ask the Expert series? Find it in its new home: communities.sas.com/askexpert.