mirror of
https://github.com/sasjs/core.git
synced 2025-12-11 06:24:35 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 818c0f5eae | |||
| dff9e2f387 | |||
| 6c9256e097 | |||
| 0631a05a78 | |||
| 268bdca4e0 | |||
|
|
e38f331ad5 | ||
| 8d64b30419 | |||
| 4a6c8ffbb3 | |||
| b5c86e7031 | |||
| 9783edd0e3 | |||
| 961728a987 | |||
| 4b34322d94 | |||
| 8bb83deede | |||
| 79c81aa8a4 | |||
| bbbcf7d550 | |||
| 82184bc6be | |||
| efc731cfaa | |||
| da9a74ee14 | |||
| 94762d9381 | |||
|
|
fbd8196230 | ||
|
|
5720caaf86 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
sasjsbuild/
|
||||
|
||||
@@ -1,83 +1,32 @@
|
||||
# Contributing
|
||||
|
||||
Contributions to the macrocore library are warmly welcomed! To avoid any
|
||||
misunderstandings, do please first discuss the change you wish to make via issue,
|
||||
email, or any other method with the owners of this repository before submitting
|
||||
a PR.
|
||||
Contributions are warmly welcomed! To avoid any misunderstandings, do please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before submitting a PR.
|
||||
|
||||
Please note we have a code of conduct, please follow it in all your interactions
|
||||
with the project.
|
||||
Please note we have a [code of conduct](https://www.contributor-covenant.org/version/2/0/code_of_conduct/), please follow it in all your interactions with the project.
|
||||
|
||||
# Environment Setup
|
||||
|
||||
This repository makes use of the [SASjs](https://sasjs.io) framework for code organisation, compilation, documentation, and deployment. The following tools are highly recommended:
|
||||
|
||||
* [NPM](https://sasjs.io/windows/#npm) - the runtime and dependency manager for [SASjs CLI](https://cli.sasjs.io) (batteries included)
|
||||
* [VSCode](https://sasjs.io/windows/#vscode) - feature packed IDE for code editing (warning - highly effective!)
|
||||
* [GIT](https://sasjs.io/windows/#git) - a safety net you cannot (and should not) do without.
|
||||
|
||||
For generating the documentation (`sasjs doc`) it is also necessary to install [doxygen](https://www.doxygen.nl/manual/install.html).
|
||||
|
||||
|
||||
## Code of Conduct
|
||||
To get configured:
|
||||
|
||||
### Our Pledge
|
||||
1. Clone the repository
|
||||
2. Install local dependencies (`npm install`)
|
||||
3. Install the SASjs CLI globally (`npm install -g @sasjs/cli`)
|
||||
4. Add a target, and authentication (`npm add`). See [docs](https://cli.sasjs.io/add/).
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
To contribute:
|
||||
|
||||
### Our Standards
|
||||
1. Create your feature branch (`git checkout -b myfeature`)
|
||||
2. Make your change
|
||||
3. Update the `all.sas` file (`python3 build.py`)
|
||||
4. Commit the change, using the [conventional commit](https://www.conventionalcommits.org/en/v1.0.0) standard
|
||||
5. Push and make a PR
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
### Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
### Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
### Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at support@macropeople.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
### Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
570
all.sas
570
all.sas
@@ -218,9 +218,11 @@ options noquotelenmax;
|
||||
%put Supported features: PROCLUA;
|
||||
%end;
|
||||
%else %if &feature=PROCLUA %then %do;
|
||||
/* https://blogs.sas.com/content/sasdummy/2015/08/03/using-lua-within-your-sas-programs */
|
||||
%if &platform=SASVIYA %then 1;
|
||||
%else %if "&sysver"="9.3" or "&sysver"="9.4" %then 1;
|
||||
%else 0;
|
||||
%else %if "&sysver"="9.2" or "&sysver"="9.3" %then 0;
|
||||
%else %if "&SYSVLONG" < "9.04.01M3" %then 0;
|
||||
%else 1;
|
||||
%end;
|
||||
%else %do;
|
||||
-1
|
||||
@@ -229,6 +231,32 @@ options noquotelenmax;
|
||||
%mend;
|
||||
|
||||
/** @endcond *//**
|
||||
@file
|
||||
@brief Checks whether a fileref exists
|
||||
@details You can probably do without this macro as it is just a one liner.
|
||||
Mainly it is here as a convenient way to remember the syntax!
|
||||
|
||||
@param fref the fileref to detect
|
||||
|
||||
@return output Returns 1 if found and 0 if not found. Note - it is possible
|
||||
that the fileref is found, but the file does not (yet) exist. If you need
|
||||
to test for this, you may as well use the fileref function directly.
|
||||
|
||||
@version 8
|
||||
@author [Allan Bowe](https://www.linkedin.com/in/allanbowe/)
|
||||
**/
|
||||
|
||||
%macro mf_existfileref(fref
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%if %sysfunc(fileref(&fref))=0 %then %do;
|
||||
1
|
||||
%end;
|
||||
%else %do;
|
||||
0
|
||||
%end;
|
||||
|
||||
%mend;/**
|
||||
@file
|
||||
@brief Checks if a variable exists in a data set.
|
||||
@details Returns 0 if the variable does NOT exist, and return the position of
|
||||
@@ -378,7 +406,7 @@ options noquotelenmax;
|
||||
%local dsid rc;
|
||||
%let dsid=%sysfunc(open(&libds,is));
|
||||
%if &dsid = 0 %then %do;
|
||||
%put WARNING: Cannot open %trim(&libds), system message below;
|
||||
%put %str(WARN)ING: Cannot open %trim(&libds), system message below;
|
||||
%put %sysfunc(sysmsg());
|
||||
-1
|
||||
%end;
|
||||
@@ -484,8 +512,8 @@ options noquotelenmax;
|
||||
@brief retrieves a key value pair from a control dataset
|
||||
@details By default, control dataset is work.mp_setkeyvalue. Usage:
|
||||
|
||||
%mp_setkeyvalue(someindex,22,type=N)
|
||||
%put %mf_getkeyvalue(someindex)
|
||||
%mp_setkeyvalue(someindex,22,type=N)
|
||||
%put %mf_getkeyvalue(someindex)
|
||||
|
||||
|
||||
@param key Provide a key on which to perform the lookup
|
||||
@@ -577,11 +605,18 @@ options noquotelenmax;
|
||||
@brief Adds custom quotes / delimiters to a delimited string
|
||||
@details Can be used in open code, eg as follows:
|
||||
|
||||
%put %mf_getquotedstr(blah blah blah);
|
||||
%put %mf_getquotedstr(blah blah blah);
|
||||
|
||||
which returns:
|
||||
> 'blah','blah','blah'
|
||||
|
||||
Alternatively:
|
||||
|
||||
%put %mf_getquotedstr(these words are double quoted,quote=D)
|
||||
|
||||
for:
|
||||
> "these","words","are","double","quoted"
|
||||
|
||||
@param in_str the unquoted, spaced delimited string to transform
|
||||
@param dlm= the delimeter to be applied to the output (default comma)
|
||||
@param indlm= the delimeter used for the input (default is space)
|
||||
@@ -4515,8 +4550,8 @@ proc sql
|
||||
@brief Logs a key value pair a control dataset
|
||||
@details If the dataset does not exist, it is created. Usage:
|
||||
|
||||
%mp_setkeyvalue(someindex,22,type=N)
|
||||
%mp_setkeyvalue(somenewindex,somevalue)
|
||||
%mp_setkeyvalue(someindex,22,type=N)
|
||||
%mp_setkeyvalue(somenewindex,somevalue)
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_existds.sas
|
||||
@@ -4538,7 +4573,7 @@ proc sql
|
||||
|
||||
%if not (%mf_existds(&libds)) %then %do;
|
||||
data &libds (index=(key/unique));
|
||||
length key $32 valc $256 valn 8 type $1;
|
||||
length key $64 valc $2048 valn 8 type $1;
|
||||
call missing(of _all_);
|
||||
stop;
|
||||
run;
|
||||
@@ -4754,6 +4789,97 @@ proc sql
|
||||
%mp_binarycopy(inloc="&inloc",outref=_webout)
|
||||
%end;
|
||||
|
||||
%mend;/**
|
||||
@file
|
||||
@brief Runs arbitrary code for a specified amount of time
|
||||
@details Executes a series of procs and data steps to enable performance
|
||||
testing of arbitrary jobs.
|
||||
|
||||
%mp_testjob(
|
||||
duration=60*5
|
||||
)
|
||||
|
||||
@param [in] duration= the time in seconds which the job should run for. Actual
|
||||
time may vary, as the check is done in between steps. Default = 30 (seconds).
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getuniquelibref.sas
|
||||
@li mf_getuniquename.sas
|
||||
@li mf_mkdir.sas
|
||||
|
||||
@version 9.4
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_testjob(duration=30
|
||||
)/*/STORE SOURCE*/;
|
||||
%local lib dir ds1 ds2 ds3 start_tm i;
|
||||
|
||||
%let start_tm=%sysfunc(datetime());
|
||||
%let duration=%sysevalf(&duration);
|
||||
|
||||
/* create a temporary library in WORK */
|
||||
%let lib=%mf_getuniquelibref();
|
||||
%let dir=%mf_getuniquename();
|
||||
%mf_mkdir(%sysfunc(pathname(work))/&dir)
|
||||
libname &lib "%sysfunc(pathname(work))/&dir";
|
||||
|
||||
/* loop through until time expires */
|
||||
%let ds1=%mf_getuniquename();
|
||||
%let ds2=%mf_getuniquename();
|
||||
%let ds3=%mf_getuniquename();
|
||||
%do i=0 %to 1;
|
||||
|
||||
/* create big dataset */
|
||||
data &lib..&ds1(compress=no );
|
||||
do x=1 to 1000000;
|
||||
randnum0=ranuni(0)*3;
|
||||
randnum1=ranuni(0)*2;
|
||||
bigchar=repeat('A',300);
|
||||
output;
|
||||
end;
|
||||
run;
|
||||
%if %sysevalf( (%sysfunc(datetime())-&start_tm)>&duration ) %then %goto gate;
|
||||
|
||||
proc summary ;
|
||||
class randnum0 randnum1;
|
||||
output out=&lib..&ds2;
|
||||
run;quit;
|
||||
%if %sysevalf( (%sysfunc(datetime())-&start_tm)>&duration ) %then %goto gate;
|
||||
|
||||
/* add more data */
|
||||
proc sql;
|
||||
create table &lib..&ds3 as
|
||||
select *, ranuni(0)*10 as randnum2
|
||||
from &lib..&ds1
|
||||
order by randnum1;
|
||||
quit;
|
||||
%if %sysevalf( (%sysfunc(datetime())-&start_tm)>&duration ) %then %goto gate;
|
||||
|
||||
proc sort data=&lib..&ds3;
|
||||
by descending x;
|
||||
run;
|
||||
%if %sysevalf( (%sysfunc(datetime())-&start_tm)>&duration ) %then %goto gate;
|
||||
|
||||
/* wait 5 seconds */
|
||||
data _null_;
|
||||
call sleep(5,1);
|
||||
run;
|
||||
%if %sysevalf( (%sysfunc(datetime())-&start_tm)>&duration ) %then %goto gate;
|
||||
|
||||
%let i=0;
|
||||
|
||||
%end;
|
||||
|
||||
%gate:
|
||||
%put time is up!;
|
||||
proc datasets lib=&lib kill;
|
||||
run;
|
||||
quit;
|
||||
libname &lib clear;
|
||||
|
||||
|
||||
%mend;/**
|
||||
@file mp_testwritespeedlibrary.sas
|
||||
@brief Tests the write speed of a new table in a SAS library
|
||||
@@ -10535,7 +10661,6 @@ run;
|
||||
@details Expects oauth token in a global macro variable (default
|
||||
ACCESS_TOKEN).
|
||||
|
||||
options mprint;
|
||||
%mv_createfolder(path=/Public)
|
||||
|
||||
|
||||
@@ -11385,7 +11510,7 @@ run;
|
||||
%let oauth_bearer=oauth_bearer=sas_services;
|
||||
%let &access_token_var=;
|
||||
%end;
|
||||
%put &sysmacroname: grant_type=&grant_type;
|
||||
|
||||
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
|
||||
and &grant_type ne sas_services
|
||||
)
|
||||
@@ -11640,7 +11765,6 @@ libname &libref1a clear;
|
||||
@details If not running in Studo 5 +, will expect an oauth token in a global
|
||||
macro variable (default ACCESS_TOKEN).
|
||||
|
||||
options mprint;
|
||||
%mv_createfolder(path=/Public/test/blah)
|
||||
%mv_deleteviyafolder(path=/Public/test)
|
||||
|
||||
@@ -11970,7 +12094,7 @@ libname &libref1 clear;
|
||||
%let oauth_bearer=oauth_bearer=sas_services;
|
||||
%let &access_token_var=;
|
||||
%end;
|
||||
%put &sysmacroname: grant_type=&grant_type;
|
||||
|
||||
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
|
||||
and &grant_type ne sas_services
|
||||
)
|
||||
@@ -12205,7 +12329,7 @@ filename &fname1 clear;
|
||||
%let oauth_bearer=oauth_bearer=sas_services;
|
||||
%let &access_token_var=;
|
||||
%end;
|
||||
%put &sysmacroname: grant_type=&grant_type;
|
||||
|
||||
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
|
||||
and &grant_type ne sas_services
|
||||
)
|
||||
@@ -12298,7 +12422,6 @@ libname &libref1 clear;
|
||||
%let oauth_bearer=oauth_bearer=sas_services;
|
||||
%let &access_token_var=;
|
||||
%end;
|
||||
%put &sysmacroname: grant_type=&grant_type;
|
||||
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
|
||||
and &grant_type ne sas_services
|
||||
)
|
||||
@@ -12363,10 +12486,10 @@ run;
|
||||
%let fname3=%mf_getuniquefileref();
|
||||
%let fpath1=%sysfunc(pathname(&fname1));
|
||||
%let fpath2=%sysfunc(pathname(&fname2));
|
||||
%let fpath3=%sysfunc(pathname(&fname2));
|
||||
%let fpath3=%sysfunc(pathname(&fname3));
|
||||
|
||||
/* compile the lua JSON module */
|
||||
%ml_json()
|
||||
%ml_json()
|
||||
/* read using LUA - this allows the code to be of any length */
|
||||
data _null_;
|
||||
file "&fpath3..lua";
|
||||
@@ -12396,7 +12519,275 @@ data _null_;
|
||||
run;
|
||||
filename &fname1 clear;
|
||||
filename &fname2 clear;
|
||||
filename &fname3 clear;
|
||||
%mend;
|
||||
/**
|
||||
@file
|
||||
@brief Extract the log from a completed SAS Viya Job
|
||||
@details Extracts log from a Viya job and writes it out to a fileref
|
||||
|
||||
To query the job, you need the URI. Sample code for achieving this
|
||||
is provided below.
|
||||
|
||||
## Example
|
||||
|
||||
First, compile the macros:
|
||||
|
||||
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
|
||||
%inc mc;
|
||||
|
||||
Next, create a job (in this case, a web service):
|
||||
|
||||
filename ft15f001 temp;
|
||||
parmcards4;
|
||||
data ;
|
||||
rand=ranuni(0)*1000;
|
||||
do x=1 to rand;
|
||||
y=rand*4;
|
||||
output;
|
||||
end;
|
||||
run;
|
||||
proc sort data=&syslast
|
||||
by descending y;
|
||||
run;
|
||||
;;;;
|
||||
%mv_createwebservice(path=/Public/temp,name=demo)
|
||||
|
||||
Execute it:
|
||||
|
||||
%mv_jobexecute(path=/Public/temp
|
||||
,name=demo
|
||||
,outds=work.info
|
||||
)
|
||||
|
||||
Wait for it to finish, and grab the uri:
|
||||
|
||||
data _null_;
|
||||
set work.info;
|
||||
if method='GET' and rel='self';
|
||||
call symputx('uri',uri);
|
||||
run;
|
||||
|
||||
Finally, fetch the log:
|
||||
|
||||
%mv_getjoblog(uri=&uri,outref=mylog)
|
||||
|
||||
This macro is used by the mv_jobwaitfor.sas macro, which is generally a more
|
||||
convenient way to wait for the job to finish before fetching the log.
|
||||
|
||||
|
||||
@param [in] access_token_var= The global macro variable to contain the access token
|
||||
@param [in] mdebug= set to 1 to enable DEBUG messages
|
||||
@param [in] grant_type= valid values:
|
||||
@li password
|
||||
@li authorization_code
|
||||
@li detect - will check if access_token exists, if not will use sas_services if
|
||||
a SASStudioV session else authorization_code. Default option.
|
||||
@li sas_services - will use oauth_bearer=sas_services.
|
||||
@param [in] uri= The uri of the running job for which to fetch the status,
|
||||
in the format `/jobExecution/jobs/$UUID/state` (unquoted).
|
||||
@param [out] outref= The output fileref to which to APPEND the log (is always
|
||||
appended).
|
||||
|
||||
|
||||
@version VIYA V.03.04
|
||||
@author Allan Bowe, source: https://github.com/sasjs/core
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_abort.sas
|
||||
@li mf_getplatform.sas
|
||||
@li mf_existfileref.sas
|
||||
@li ml_json.sas
|
||||
|
||||
**/
|
||||
|
||||
%macro mv_getjoblog(uri=0,outref=0
|
||||
,contextName=SAS Job Execution compute context
|
||||
,access_token_var=ACCESS_TOKEN
|
||||
,grant_type=sas_services
|
||||
,mdebug=0
|
||||
);
|
||||
%local oauth_bearer;
|
||||
%if &grant_type=detect %then %do;
|
||||
%if %symexist(&access_token_var) %then %let grant_type=authorization_code;
|
||||
%else %let grant_type=sas_services;
|
||||
%end;
|
||||
%if &grant_type=sas_services %then %do;
|
||||
%let oauth_bearer=oauth_bearer=sas_services;
|
||||
%let &access_token_var=;
|
||||
%end;
|
||||
|
||||
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
|
||||
and &grant_type ne sas_services
|
||||
)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Invalid value for grant_type: &grant_type)
|
||||
)
|
||||
|
||||
/* validation in datastep for better character safety */
|
||||
%local errmsg errflg;
|
||||
data _null_;
|
||||
uri=symget('uri');
|
||||
if length(uri)<12 then do;
|
||||
call symputx('errflg',1);
|
||||
call symputx('errmsg',"URI is invalid (too short) - '&uri'",'l');
|
||||
end;
|
||||
if scan(uri,-1)='state' or scan(uri,1) ne 'jobExecution' then do;
|
||||
call symputx('errflg',1);
|
||||
call symputx('errmsg',
|
||||
"URI should be in format /jobExecution/jobs/$$$$UUID$$$$"
|
||||
!!" but is actually like: &uri",'l');
|
||||
end;
|
||||
run;
|
||||
|
||||
%mp_abort(iftrue=(&errflg=1)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(&errmsg)
|
||||
)
|
||||
|
||||
%mp_abort(iftrue=(&outref=0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Output fileref should be provided)
|
||||
)
|
||||
|
||||
%if %mf_existfileref(&outref) ne 1 %then %do;
|
||||
filename &outref temp;
|
||||
%end;
|
||||
|
||||
options noquotelenmax;
|
||||
%local base_uri; /* location of rest apis */
|
||||
%let base_uri=%mf_getplatform(VIYARESTAPI);
|
||||
|
||||
/* prepare request*/
|
||||
%local fname1;
|
||||
%let fname1=%mf_getuniquefileref();
|
||||
proc http method='GET' out=&fname1 &oauth_bearer
|
||||
url="&base_uri&uri";
|
||||
headers
|
||||
%if &grant_type=authorization_code %then %do;
|
||||
"Authorization"="Bearer &&&access_token_var"
|
||||
%end;
|
||||
;
|
||||
run;
|
||||
%if &SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201 %then
|
||||
%do;
|
||||
data _null_;infile &fname1;input;putlog _infile_;run;
|
||||
%mp_abort(mac=&sysmacroname
|
||||
,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
|
||||
)
|
||||
%end;
|
||||
%local fname2 fname3 fpath1 fpath2 fpath3;
|
||||
%let fname2=%mf_getuniquefileref();
|
||||
%let fname3=%mf_getuniquefileref();
|
||||
%let fpath1=%sysfunc(pathname(&fname1));
|
||||
%let fpath2=%sysfunc(pathname(&fname2));
|
||||
%let fpath3=%sysfunc(pathname(&fname3));
|
||||
|
||||
/* compile the lua JSON module */
|
||||
%ml_json()
|
||||
/* read using LUA - this allows the code to be of any length */
|
||||
data _null_;
|
||||
file "&fpath3..lua";
|
||||
put '
|
||||
infile = io.open (sas.symget("fpath1"), "r")
|
||||
outfile = io.open (sas.symget("fpath2"), "w")
|
||||
io.input(infile)
|
||||
local resp=json.decode(io.read())
|
||||
local logloc=resp["logLocation"]
|
||||
outfile:write(logloc)
|
||||
io.close(infile)
|
||||
io.close(outfile)
|
||||
';
|
||||
run;
|
||||
%inc "&fpath3..lua";
|
||||
/* get log path*/
|
||||
%let errflg=1;
|
||||
%let errmsg=No entry in &fname2 fileref;
|
||||
data _null_;
|
||||
infile &fname2;
|
||||
input;
|
||||
uri=_infile_;
|
||||
if length(uri)<12 then do;
|
||||
call symputx('errflg',1);
|
||||
call symputx('errmsg',"URI is invalid (too short) - '&uri'",'l');
|
||||
end;
|
||||
if scan(uri,1) ne 'files' or scan(uri,2) ne 'files' then do;
|
||||
call symputx('errflg',1);
|
||||
call symputx('errmsg',
|
||||
"URI should be in format /files/files/$$$$UUID$$$$"
|
||||
!!" but is actually like: &uri",'l');
|
||||
end;
|
||||
call symputx('errflg',0,'l');
|
||||
call symputx('logloc',uri,'l');
|
||||
run;
|
||||
|
||||
%mp_abort(iftrue=(&errflg=1)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(&errmsg)
|
||||
)
|
||||
|
||||
/* we have a log uri - now fetch the log */
|
||||
proc http method='GET' out=&fname1 &oauth_bearer
|
||||
url="&base_uri&logloc/content";
|
||||
headers
|
||||
%if &grant_type=authorization_code %then %do;
|
||||
"Authorization"="Bearer &&&access_token_var"
|
||||
%end;
|
||||
;
|
||||
run;
|
||||
%if &SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201 %then
|
||||
%do;
|
||||
data _null_;infile &fname1;input;putlog _infile_;run;
|
||||
%mp_abort(mac=&sysmacroname
|
||||
,msg=%str(logfetch: &SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
|
||||
)
|
||||
%end;
|
||||
|
||||
data _null_;
|
||||
file "&fpath3..lua";
|
||||
put '
|
||||
infile = io.open (sas.symget("fpath1"), "r")
|
||||
outfile = io.open (sas.symget("fpath2"), "w")
|
||||
io.input(infile)
|
||||
local resp=json.decode(io.read())
|
||||
for i, v in pairs(resp["items"]) do
|
||||
outfile:write(v.line,"\n")
|
||||
end
|
||||
io.close(infile)
|
||||
io.close(outfile)
|
||||
';
|
||||
run;
|
||||
%inc "&fpath3..lua";
|
||||
|
||||
/* write log out to the specified fileref */
|
||||
data _null_;
|
||||
infile &fname2 end=last;
|
||||
file &outref mod;
|
||||
if _n_=1 then do;
|
||||
put "/** SASJS Viya Job Log Extract start: &uri **/";
|
||||
end;
|
||||
input;
|
||||
put _infile_;
|
||||
%if &mdebug=1 %then %do;
|
||||
putlog _infile_;
|
||||
%end;
|
||||
if last then do;
|
||||
put "/** SASJS Viya Job Log Extract end: &uri **/";
|
||||
end;
|
||||
run;
|
||||
|
||||
%if &mdebug=0 %then %do;
|
||||
filename &fname1 clear;
|
||||
filename &fname2 clear;
|
||||
filename &fname3 clear;
|
||||
%end;
|
||||
%else %do;
|
||||
%put _local_;
|
||||
%end;
|
||||
%mend;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
@file
|
||||
@brief Extract the status from a running SAS Viya job
|
||||
@@ -12500,7 +12891,7 @@ filename &fname2 clear;
|
||||
%let oauth_bearer=oauth_bearer=sas_services;
|
||||
%let &access_token_var=;
|
||||
%end;
|
||||
%put &sysmacroname: grant_type=&grant_type;
|
||||
|
||||
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
|
||||
and &grant_type ne sas_services
|
||||
)
|
||||
@@ -12878,7 +13269,7 @@ libname &libref1 clear;
|
||||
%let oauth_bearer=oauth_bearer=sas_services;
|
||||
%let &access_token_var=;
|
||||
%end;
|
||||
%put &sysmacroname: grant_type=&grant_type;
|
||||
|
||||
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
|
||||
and &grant_type ne sas_services
|
||||
)
|
||||
@@ -12986,21 +13377,34 @@ libname &libref;
|
||||
|
||||
## Input table (minimum variables needed)
|
||||
|
||||
| variable| description |
|
||||
|---|---|---|
|
||||
|FLOW_ID| Numeric value, provides sequential ordering capability|
|
||||
|_CONTEXTNAME|Dictates which context should be used to run the job. If
|
||||
blank, will default to `SAS Job Execution compute context`.|
|
||||
|_PROGRAM|Provides the path to the job itself|
|
||||
@li _PROGRAM - Provides the path to the job itself
|
||||
@li FLOW_ID - Numeric value, provides sequential ordering capability. Is
|
||||
optional, will default to 0 if not provided.
|
||||
@li _CONTEXTNAME - Dictates which context should be used to run the job. If
|
||||
blank (or not provided), will default to `SAS Job Execution compute context`.
|
||||
|
||||
Any additional variables provided in this table are converted into macro
|
||||
variables and passed into the relevant job.
|
||||
|
||||
|_PROGRAM| FLOW_ID (optional)| _CONTEXTNAME (optional) |
|
||||
|---|---|---|
|
||||
|/Public/jobs/somejob1|0|SAS Job Execution compute context|
|
||||
|/Public/jobs/somejob2|0|SAS Job Execution compute context|
|
||||
|
||||
## Output table (minimum variables produced)
|
||||
|
||||
| variable| description |
|
||||
|---|---|---|
|
||||
|FLOW_ID| Numeric value, provides sequential ordering capability|
|
||||
|_CONTEXTNAME|Dictates which context should be used to run the job. If
|
||||
blank, will default to `SAS Job Execution compute context`.|
|
||||
|_PROGRAM|Provides the path to the job itself|
|
||||
@li _PROGRAM - the SAS Drive path of the job
|
||||
@li URI - the URI of the executed job
|
||||
@li STATE - the completed state of the job
|
||||
@li TIMESTAMP - the datetime that the job completed
|
||||
@li JOBPARAMS - the parameters that were passed to the job
|
||||
@li FLOW_ID - the id of the flow in which the job was executed
|
||||
|
||||

|
||||
|
||||
To avoid hammering the box with many hits in rapid succession, a one
|
||||
second pause is made between every request.
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
@@ -13049,7 +13453,16 @@ libname &libref;
|
||||
|
||||
Trigger the flow
|
||||
|
||||
%mv_jobflow(inds=work.inputjobs,outds=work.results,maxconcurrency=4)
|
||||
%mv_jobflow(inds=work.inputjobs
|
||||
,maxconcurrency=4
|
||||
,outds=work.results
|
||||
,outref=myjoblog
|
||||
)
|
||||
|
||||
data _null_;
|
||||
infile myjoblog;
|
||||
input; put _infile_;
|
||||
run;
|
||||
|
||||
|
||||
@param [in] access_token_var= The global macro variable to contain the access token
|
||||
@@ -13061,7 +13474,9 @@ libname &libref;
|
||||
@li sas_services - will use oauth_bearer=sas_services
|
||||
@param [in] inds= The input dataset containing a list of jobs and parameters
|
||||
@param [in] maxconcurrency= The max number of parallel jobs to run. Default=8.
|
||||
@param [in] mdebug= set to 1 to enable DEBUG messages
|
||||
@param [out] outds= The output dataset containing the results
|
||||
@param [out] outref= The output fileref to which to append the log file(s).
|
||||
|
||||
@version VIYA V.03.05
|
||||
@author Allan Bowe, source: https://github.com/sasjs/core
|
||||
@@ -13081,6 +13496,8 @@ libname &libref;
|
||||
,maxconcurrency=8
|
||||
,access_token_var=ACCESS_TOKEN
|
||||
,grant_type=sas_services
|
||||
,outref=0
|
||||
,mdebug=0
|
||||
);
|
||||
%local oauth_bearer;
|
||||
%if &grant_type=detect %then %do;
|
||||
@@ -13103,16 +13520,29 @@ libname &libref;
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Input dataset was not provided)
|
||||
)
|
||||
%mp_abort(iftrue=(%mf_existVarList(&inds,_CONTEXTNAME FLOW_ID _PROGRAM)=0)
|
||||
%mp_abort(iftrue=(%mf_existVarList(&inds,_PROGRAM)=0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(The following columns must exist on input dataset &inds:
|
||||
_CONTEXTNAME FLOW_ID _PROGRAM)
|
||||
,msg=%str(The _PROGRAM column must exist on input dataset &inds)
|
||||
)
|
||||
%mp_abort(iftrue=(&maxconcurrency<1)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(The maxconcurrency variable should be a positive integer)
|
||||
)
|
||||
|
||||
/* set defaults if not provided */
|
||||
%if %mf_existVarList(&inds,_CONTEXTNAME FLOW_ID)=0 %then %do;
|
||||
data &inds;
|
||||
%if %mf_existvarList(&inds,_CONTEXTNAME)=0 %then %do;
|
||||
length _CONTEXTNAME $128;
|
||||
retain _CONTEXTNAME "SAS Job Execution compute context";
|
||||
%end;
|
||||
%if %mf_existvarList(&inds,FLOW_ID)=0 %then %do;
|
||||
retain FLOW_ID 0;
|
||||
%end;
|
||||
set &inds;
|
||||
run;
|
||||
%end;
|
||||
|
||||
%local missings;
|
||||
proc sql noprint;
|
||||
select count(*) into: missings
|
||||
@@ -13190,8 +13620,8 @@ data;run;%let jdswaitfor=&syslast;
|
||||
jparams='jparams'!!left(symget('jid'));
|
||||
call symputx(jparams,substr(_infile_,3,length(_infile_)-4));
|
||||
run;
|
||||
%local joburi&jid;
|
||||
%let joburi&jid=0; /* used in next loop */
|
||||
%local jobuid&jid;
|
||||
%let jobuid&jid=0; /* used in next loop */
|
||||
%end;
|
||||
%local concurrency completed;
|
||||
%let concurrency=0;
|
||||
@@ -13202,8 +13632,21 @@ data;run;%let jdswaitfor=&syslast;
|
||||
* now we can execute the jobs up to the maxconcurrency setting
|
||||
*/
|
||||
%if "&&job&jid" ne "0" %then %do; /* this var is zero if job finished */
|
||||
%if "&&joburi&jid"="0" and &concurrency<&maxconcurrency %then %do;
|
||||
/* job has not been triggered and we have free slots */
|
||||
|
||||
/* check to see if the job finished in the previous round */
|
||||
%if %sysfunc(exist(&outds))=1 %then %do;
|
||||
%local jobcheck; %let jobcheck=0;
|
||||
proc sql noprint;
|
||||
select count(*) into: jobcheck
|
||||
from &outds where uuid="&&jobuid&jid";
|
||||
%if &jobcheck>0 %then %do;
|
||||
%put &&job&jid in flow &fid with uid &&jobuid&jid completed!;
|
||||
%let job&jid=0;
|
||||
%end;
|
||||
%end;
|
||||
|
||||
/* check if job was triggered and if so, if we have enough slots to run */
|
||||
%if "&&jobuid&jid"="0" and &concurrency<&maxconcurrency %then %do;
|
||||
%local jobname jobpath;
|
||||
%let jobname=%scan(&&job&jid,-1,/);
|
||||
%let jobpath=%substr(&&job&jid,1,%length(&&job&jid)-%length(&jobname)-1);
|
||||
@@ -13217,27 +13660,22 @@ data;run;%let jdswaitfor=&syslast;
|
||||
format jobparams $32767.;
|
||||
set &jdsapp(where=(method='GET' and rel='state'));
|
||||
jobparams=symget("jparams&jid");
|
||||
call symputx("joburi&jid",uri,'l');
|
||||
/* uri here has the /state suffix */
|
||||
uuid=scan(uri,-2,'/');
|
||||
call symputx("jobuid&jid",uuid,'l');
|
||||
run;
|
||||
proc append base=&jdsrunning data=&jdsapp;
|
||||
run;
|
||||
%let concurrency=%eval(&concurrency+1);
|
||||
%end;
|
||||
%else %if %sysfunc(exist(&outds))=1 %then %do;
|
||||
/* check to see if the job has finished as was previously executed */
|
||||
%local jobcheck; %let jobcheck=0;
|
||||
proc sql noprint;
|
||||
select count(*) into: jobcheck
|
||||
from &outds where uri="&&joburi&jid";
|
||||
%if &jobcheck>0 %then %do;
|
||||
%let job&jid=0;
|
||||
%put &&job&jid in flow &fid completed!;
|
||||
%end;
|
||||
/* sleep one second after every request to smooth the impact */
|
||||
data _null_;
|
||||
call sleep(1,1);
|
||||
run;
|
||||
%end;
|
||||
%end;
|
||||
%if &jid=&jcnt %then %do;
|
||||
/* we are at the end of the loop - time to see which jobs have finished */
|
||||
%mv_jobwaitfor(ANY,inds=&jdsrunning,outds=&jdswaitfor)
|
||||
%mv_jobwaitfor(ANY,inds=&jdsrunning,outds=&jdswaitfor,outref=&outref)
|
||||
%local done;
|
||||
%let done=%mf_nobs(&jdswaitfor);
|
||||
%if &done>0 %then %do;
|
||||
@@ -13246,20 +13684,30 @@ data;run;%let jdswaitfor=&syslast;
|
||||
data &jdsapp;
|
||||
set &jdswaitfor;
|
||||
flow_id=&&flow&fid;
|
||||
uuid=scan(uri,-1,'/');
|
||||
run;
|
||||
proc append base=&outds data=&jdsapp;
|
||||
run;
|
||||
%end;
|
||||
proc sql;
|
||||
delete from &jdsrunning
|
||||
where uuid in (select uuid from &outds
|
||||
where state in ('canceled','completed','failed')
|
||||
);
|
||||
|
||||
/* loop again if jobs are left */
|
||||
%if &completed < &jcnt %then %do;
|
||||
%let jid=0;
|
||||
%put looping flow &fid again - &completed jobs completed, &concurrency jobs running;
|
||||
%put looping flow &fid again - &completed of &jcnt jobs completed, &concurrency jobs running;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
/* back up and execute the next flow */
|
||||
%end;
|
||||
|
||||
%if &mdebug=1 %then %do;
|
||||
%put _local_;
|
||||
%end;
|
||||
|
||||
%mend;
|
||||
/**
|
||||
@@ -13336,7 +13784,7 @@ data;run;%let jdswaitfor=&syslast;
|
||||
should be in a `_program` variable.
|
||||
@param [out] outds= The output dataset containing the list of states by job
|
||||
(default=work.mv_jobexecute)
|
||||
|
||||
@param [out] outref= A fileref to which the spawned job logs should be appended.
|
||||
|
||||
@version VIYA V.03.04
|
||||
@author Allan Bowe, source: https://github.com/sasjs/core
|
||||
@@ -13347,6 +13795,7 @@ data;run;%let jdswaitfor=&syslast;
|
||||
@li mf_getuniquefileref.sas
|
||||
@li mf_existvar.sas
|
||||
@li mf_nobs.sas
|
||||
@li mv_getjoblog.sas
|
||||
|
||||
**/
|
||||
|
||||
@@ -13355,6 +13804,7 @@ data;run;%let jdswaitfor=&syslast;
|
||||
,grant_type=sas_services
|
||||
,inds=0
|
||||
,outds=work.mv_jobwaitfor
|
||||
,outref=0
|
||||
);
|
||||
%local oauth_bearer;
|
||||
%if &grant_type=detect %then %do;
|
||||
@@ -13365,7 +13815,7 @@ data;run;%let jdswaitfor=&syslast;
|
||||
%let oauth_bearer=oauth_bearer=sas_services;
|
||||
%let &access_token_var=;
|
||||
%end;
|
||||
%put &sysmacroname: grant_type=&grant_type;
|
||||
|
||||
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
|
||||
and &grant_type ne sas_services
|
||||
)
|
||||
@@ -13398,7 +13848,7 @@ options noquotelenmax;
|
||||
data _null_;
|
||||
length jobparams $32767;
|
||||
set &inds end=last;
|
||||
call symputx(cats('joburi',_n_),uri,'l');
|
||||
call symputx(cats('joburi',_n_),substr(uri,1,55)!!'/state','l');
|
||||
call symputx(cats('jobname',_n_),_program,'l');
|
||||
call symputx(cats('jobparams',_n_),jobparams,'l');
|
||||
if last then call symputx('uricnt',_n_,'l');
|
||||
@@ -13442,14 +13892,20 @@ run;
|
||||
run;
|
||||
|
||||
%if &status=completed or &status=failed or &status=canceled %then %do;
|
||||
%local plainuri;
|
||||
%let plainuri=%substr(&&joburi&i,1,55);
|
||||
proc sql;
|
||||
insert into &outds set
|
||||
_program="&&jobname&i",
|
||||
uri="&&joburi&i",
|
||||
uri="&plainuri",
|
||||
state="&status",
|
||||
timestamp=datetime(),
|
||||
jobparams=symget("jobparams&i");
|
||||
%let joburi&i=0; /* do not re-check */
|
||||
/* fetch log */
|
||||
%if %str(&outref) ne 0 %then %do;
|
||||
%mv_getjoblog(uri=&plainuri,outref=&outref)
|
||||
%end;
|
||||
%end;
|
||||
%else %if &status=idle or &status=pending or &status=running %then %do;
|
||||
data _null_;
|
||||
|
||||
@@ -32,9 +32,11 @@
|
||||
%put Supported features: PROCLUA;
|
||||
%end;
|
||||
%else %if &feature=PROCLUA %then %do;
|
||||
/* https://blogs.sas.com/content/sasdummy/2015/08/03/using-lua-within-your-sas-programs */
|
||||
%if &platform=SASVIYA %then 1;
|
||||
%else %if "&sysver"="9.3" or "&sysver"="9.4" %then 1;
|
||||
%else 0;
|
||||
%else %if "&sysver"="9.2" or "&sysver"="9.3" %then 0;
|
||||
%else %if "&SYSVLONG" < "9.04.01M3" %then 0;
|
||||
%else 1;
|
||||
%end;
|
||||
%else %do;
|
||||
-1
|
||||
|
||||
27
base/mf_existfileref.sas
Normal file
27
base/mf_existfileref.sas
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
@file
|
||||
@brief Checks whether a fileref exists
|
||||
@details You can probably do without this macro as it is just a one liner.
|
||||
Mainly it is here as a convenient way to remember the syntax!
|
||||
|
||||
@param fref the fileref to detect
|
||||
|
||||
@return output Returns 1 if found and 0 if not found. Note - it is possible
|
||||
that the fileref is found, but the file does not (yet) exist. If you need
|
||||
to test for this, you may as well use the fileref function directly.
|
||||
|
||||
@version 8
|
||||
@author [Allan Bowe](https://www.linkedin.com/in/allanbowe/)
|
||||
**/
|
||||
|
||||
%macro mf_existfileref(fref
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%if %sysfunc(fileref(&fref))=0 %then %do;
|
||||
1
|
||||
%end;
|
||||
%else %do;
|
||||
0
|
||||
%end;
|
||||
|
||||
%mend;
|
||||
@@ -23,7 +23,7 @@
|
||||
%local dsid rc;
|
||||
%let dsid=%sysfunc(open(&libds,is));
|
||||
%if &dsid = 0 %then %do;
|
||||
%put WARNING: Cannot open %trim(&libds), system message below;
|
||||
%put %str(WARN)ING: Cannot open %trim(&libds), system message below;
|
||||
%put %sysfunc(sysmsg());
|
||||
-1
|
||||
%end;
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
@brief retrieves a key value pair from a control dataset
|
||||
@details By default, control dataset is work.mp_setkeyvalue. Usage:
|
||||
|
||||
%mp_setkeyvalue(someindex,22,type=N)
|
||||
%put %mf_getkeyvalue(someindex)
|
||||
%mp_setkeyvalue(someindex,22,type=N)
|
||||
%put %mf_getkeyvalue(someindex)
|
||||
|
||||
|
||||
@param key Provide a key on which to perform the lookup
|
||||
|
||||
@@ -3,11 +3,18 @@
|
||||
@brief Adds custom quotes / delimiters to a delimited string
|
||||
@details Can be used in open code, eg as follows:
|
||||
|
||||
%put %mf_getquotedstr(blah blah blah);
|
||||
%put %mf_getquotedstr(blah blah blah);
|
||||
|
||||
which returns:
|
||||
> 'blah','blah','blah'
|
||||
|
||||
Alternatively:
|
||||
|
||||
%put %mf_getquotedstr(these words are double quoted,quote=D)
|
||||
|
||||
for:
|
||||
> "these","words","are","double","quoted"
|
||||
|
||||
@param in_str the unquoted, spaced delimited string to transform
|
||||
@param dlm= the delimeter to be applied to the output (default comma)
|
||||
@param indlm= the delimeter used for the input (default is space)
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
@brief Logs a key value pair a control dataset
|
||||
@details If the dataset does not exist, it is created. Usage:
|
||||
|
||||
%mp_setkeyvalue(someindex,22,type=N)
|
||||
%mp_setkeyvalue(somenewindex,somevalue)
|
||||
%mp_setkeyvalue(someindex,22,type=N)
|
||||
%mp_setkeyvalue(somenewindex,somevalue)
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_existds.sas
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
%if not (%mf_existds(&libds)) %then %do;
|
||||
data &libds (index=(key/unique));
|
||||
length key $32 valc $256 valn 8 type $1;
|
||||
length key $64 valc $2048 valn 8 type $1;
|
||||
call missing(of _all_);
|
||||
stop;
|
||||
run;
|
||||
|
||||
92
base/mp_testjob.sas
Normal file
92
base/mp_testjob.sas
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
@file
|
||||
@brief Runs arbitrary code for a specified amount of time
|
||||
@details Executes a series of procs and data steps to enable performance
|
||||
testing of arbitrary jobs.
|
||||
|
||||
%mp_testjob(
|
||||
duration=60*5
|
||||
)
|
||||
|
||||
@param [in] duration= the time in seconds which the job should run for. Actual
|
||||
time may vary, as the check is done in between steps. Default = 30 (seconds).
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getuniquelibref.sas
|
||||
@li mf_getuniquename.sas
|
||||
@li mf_mkdir.sas
|
||||
|
||||
@version 9.4
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_testjob(duration=30
|
||||
)/*/STORE SOURCE*/;
|
||||
%local lib dir ds1 ds2 ds3 start_tm i;
|
||||
|
||||
%let start_tm=%sysfunc(datetime());
|
||||
%let duration=%sysevalf(&duration);
|
||||
|
||||
/* create a temporary library in WORK */
|
||||
%let lib=%mf_getuniquelibref();
|
||||
%let dir=%mf_getuniquename();
|
||||
%mf_mkdir(%sysfunc(pathname(work))/&dir)
|
||||
libname &lib "%sysfunc(pathname(work))/&dir";
|
||||
|
||||
/* loop through until time expires */
|
||||
%let ds1=%mf_getuniquename();
|
||||
%let ds2=%mf_getuniquename();
|
||||
%let ds3=%mf_getuniquename();
|
||||
%do i=0 %to 1;
|
||||
|
||||
/* create big dataset */
|
||||
data &lib..&ds1(compress=no );
|
||||
do x=1 to 1000000;
|
||||
randnum0=ranuni(0)*3;
|
||||
randnum1=ranuni(0)*2;
|
||||
bigchar=repeat('A',300);
|
||||
output;
|
||||
end;
|
||||
run;
|
||||
%if %sysevalf( (%sysfunc(datetime())-&start_tm)>&duration ) %then %goto gate;
|
||||
|
||||
proc summary ;
|
||||
class randnum0 randnum1;
|
||||
output out=&lib..&ds2;
|
||||
run;quit;
|
||||
%if %sysevalf( (%sysfunc(datetime())-&start_tm)>&duration ) %then %goto gate;
|
||||
|
||||
/* add more data */
|
||||
proc sql;
|
||||
create table &lib..&ds3 as
|
||||
select *, ranuni(0)*10 as randnum2
|
||||
from &lib..&ds1
|
||||
order by randnum1;
|
||||
quit;
|
||||
%if %sysevalf( (%sysfunc(datetime())-&start_tm)>&duration ) %then %goto gate;
|
||||
|
||||
proc sort data=&lib..&ds3;
|
||||
by descending x;
|
||||
run;
|
||||
%if %sysevalf( (%sysfunc(datetime())-&start_tm)>&duration ) %then %goto gate;
|
||||
|
||||
/* wait 5 seconds */
|
||||
data _null_;
|
||||
call sleep(5,1);
|
||||
run;
|
||||
%if %sysevalf( (%sysfunc(datetime())-&start_tm)>&duration ) %then %goto gate;
|
||||
|
||||
%let i=0;
|
||||
|
||||
%end;
|
||||
|
||||
%gate:
|
||||
%put time is up!;
|
||||
proc datasets lib=&lib kill;
|
||||
run;
|
||||
quit;
|
||||
libname &lib clear;
|
||||
|
||||
|
||||
%mend;
|
||||
@@ -1,45 +0,0 @@
|
||||
#!/bin/bash
|
||||
####################################################################
|
||||
# PROJECT: Macro Core Docs Build #
|
||||
####################################################################
|
||||
|
||||
BUILD_FOLDER="/tmp/macrocore_docs"
|
||||
|
||||
# move to project root
|
||||
cd ..
|
||||
|
||||
# create build directory
|
||||
rm -rf $BUILD_FOLDER
|
||||
mkdir $BUILD_FOLDER
|
||||
|
||||
# copy relevant files
|
||||
cp -r base $BUILD_FOLDER
|
||||
cp -r meta $BUILD_FOLDER
|
||||
cp -r metax $BUILD_FOLDER
|
||||
cp -r lua $BUILD_FOLDER
|
||||
cp -r viya $BUILD_FOLDER
|
||||
cp -r doxy $BUILD_FOLDER
|
||||
cp main.dox $BUILD_FOLDER
|
||||
cp doxy/Doxyfile $BUILD_FOLDER
|
||||
|
||||
# update Doxyfile and generate
|
||||
cd $BUILD_FOLDER
|
||||
echo "OUTPUT_DIRECTORY=$BUILD_FOLDER/out" >> $BUILD_FOLDER/Doxyfile
|
||||
echo "INPUT+=main.dox" >> $BUILD_FOLDER/Doxyfile
|
||||
doxygen Doxyfile
|
||||
|
||||
# refresh github pages site
|
||||
git clone git@github.com:sasjs/core.github.io.git
|
||||
cd core.github.io
|
||||
rm -r *
|
||||
mv $BUILD_FOLDER/out/doxy/* .
|
||||
echo 'core.sasjs.io' > CNAME
|
||||
git add .
|
||||
git commit -m "build.sh build on $(date +%F:%H:%M:%S)"
|
||||
git push
|
||||
npx sitemap-generator-cli https://core.sasjs.io
|
||||
git add .
|
||||
git commit -m "adding sitemap"
|
||||
git push
|
||||
|
||||
echo "check it out: https://sasjs.github.io/core.github.io/files.html"
|
||||
@@ -1,23 +0,0 @@
|
||||
<!-- HTML footer for doxygen 1.8.17-->
|
||||
<!-- start footer part -->
|
||||
<!--BEGIN GENERATE_TREEVIEW-->
|
||||
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
|
||||
<ul>
|
||||
$navpath
|
||||
<li class="footer">$generatedby
|
||||
<a href="https://www.doxygen.org/index.html">
|
||||
<img class="footer" src="$relpath^doxygen.png" alt="doxygen"/></a> $doxygenversion </li>
|
||||
<i> For more information visit the </i> <a href="https://github.com/sasjs/core">Macro Core library</a>.</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
<!--END GENERATE_TREEVIEW-->
|
||||
<!--BEGIN !GENERATE_TREEVIEW-->
|
||||
<hr class="footer"/><address class="footer"><small>
|
||||
$generatedby  <a href="http://www.doxygen.org/index.html">
|
||||
<img class="footer" src="$relpath^doxygen.png" alt="doxygen"/>
|
||||
</a> $doxygenversion
|
||||
</small></address>
|
||||
<!--END !GENERATE_TREEVIEW-->
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,72 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<!-- HTML header for doxygen 1.8.17-->
|
||||
<html xmlns="https://www.w3.org/1999/xhtml" lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
||||
<meta name="generator" content="Doxygen $doxygenversion"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME-->
|
||||
<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME-->
|
||||
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
|
||||
<script type="text/javascript" src="$relpath^jquery.js"></script>
|
||||
<script type="text/javascript" src="$relpath^dynsections.js"></script>
|
||||
$treeview
|
||||
$search
|
||||
$mathjax
|
||||
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
|
||||
<link REL="icon" HREF="https://sasjs.io/img/runningman.jpg">
|
||||
$extrastylesheet
|
||||
</head>
|
||||
<body>
|
||||
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
||||
|
||||
<!--BEGIN TITLEAREA-->
|
||||
<div id="titlearea">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tbody>
|
||||
<tr style="height: 56px;">
|
||||
<!--BEGIN PROJECT_LOGO-->
|
||||
<td id="projectlogo">
|
||||
<img alt="Logo" src="$relpath^$projectlogo"/></td>
|
||||
<!--END PROJECT_LOGO-->
|
||||
<!--BEGIN PROJECT_NAME-->
|
||||
<td id="projectalign" style="padding-left: 0.5em;">
|
||||
<div id="projectname">
|
||||
<!--BEGIN PROJECT_NUMBER--> <span id="projectnumber">$projectnumber</span><!--END PROJECT_NUMBER-->
|
||||
</div>
|
||||
<!--BEGIN PROJECT_BRIEF--><div id="projectbrief">
|
||||
Production Ready Macros for SAS Application Developers</br>
|
||||
<a href="https://github.com/sasjs/core">
|
||||
https://github.com/sasjs/core
|
||||
</a>
|
||||
|
||||
</div>
|
||||
<meta name="Description" content="$projectbrief">
|
||||
<!--END PROJECT_BRIEF-->
|
||||
</td>
|
||||
<!--END PROJECT_NAME-->
|
||||
<!--BEGIN !PROJECT_NAME-->
|
||||
<!--BEGIN PROJECT_BRIEF-->
|
||||
<td style="padding-left: 0.5em;">
|
||||
<div id="projectbrief">$projectbrief</div>
|
||||
<table style="padding-left: 2em;" cellspacing="0" cellpadding="0">
|
||||
<tr><td> Production Ready Macros for SAS Application Developers</td></tr>
|
||||
<tr><td><a href="https://github.com/sasjs/core">
|
||||
https://github.com/sasjs/core
|
||||
</a></td></tr>
|
||||
</table>
|
||||
</td>
|
||||
<!--END PROJECT_BRIEF-->
|
||||
<!--END !PROJECT_NAME-->
|
||||
<!--BEGIN DISABLE_INDEX-->
|
||||
<!--BEGIN SEARCHENGINE-->
|
||||
<td>$searchbox</td>
|
||||
<!--END SEARCHENGINE-->
|
||||
<!--END DISABLE_INDEX-->
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--END TITLEAREA-->
|
||||
<!-- end header part -->
|
||||
3419
package-lock.json
generated
3419
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
20
package.json
20
package.json
@@ -10,13 +10,27 @@
|
||||
"author": "Allan Bowe <support@macropeople.com>",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sasjs/core"
|
||||
"url": "git+https://github.com/sasjs/core.git"
|
||||
},
|
||||
"release": {
|
||||
"branches": ["main"]
|
||||
"branches": [
|
||||
"main"
|
||||
]
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {}
|
||||
"bugs": {
|
||||
"url": "https://github.com/sasjs/core/issues"
|
||||
},
|
||||
"homepage": "https://github.com/sasjs/core#readme",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"docs": "./sasjs/utils/build.sh"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sasjs/cli": "^2.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
ALPHABETICAL_INDEX = NO
|
||||
DISABLE_INDEX = YES
|
||||
DISABLE_INDEX = NO
|
||||
ENABLE_PREPROCESSING = NO
|
||||
EXTENSION_MAPPING = sas=Java ddl=Java
|
||||
EXTRACT_LOCAL_CLASSES = NO
|
||||
@@ -13,17 +13,20 @@ HIDE_IN_BODY_DOCS = YES
|
||||
HIDE_SCOPE_NAMES = YES
|
||||
HIDE_UNDOC_CLASSES = YES
|
||||
HIDE_UNDOC_MEMBERS = YES
|
||||
HTML_OUTPUT = doxy
|
||||
HTML_HEADER = ./doxy/new_header.html
|
||||
HTML_FOOTER = ./doxy/new_footer.html
|
||||
HTML_EXTRA_STYLESHEET = ./doxy/new_stylesheet.css
|
||||
HTML_OUTPUT = $(DOXY_HTML_OUTPUT)
|
||||
HTML_HEADER = $(DOXY_CONTENT)new_header.html
|
||||
HTML_FOOTER = $(DOXY_CONTENT)new_footer.html
|
||||
HTML_EXTRA_STYLESHEET = $(DOXY_CONTENT)new_stylesheet.css
|
||||
INHERIT_DOCS = NO
|
||||
INLINE_INFO = NO
|
||||
INPUT = base meta metax viya lua
|
||||
LAYOUT_FILE = ./doxy/DoxygenLayout.xml
|
||||
INPUT = $(DOXY_CONTENT)../../README.md \
|
||||
$(DOXY_CONTENT)../../CONTRIBUTING.md \
|
||||
$(DOXY_INPUT)
|
||||
USE_MDFILE_AS_MAINPAGE = README.md
|
||||
LAYOUT_FILE = $(DOXY_CONTENT)DoxygenLayout.xml
|
||||
MAX_INITIALIZER_LINES = 0
|
||||
PROJECT_NAME = Macro Core
|
||||
PROJECT_LOGO = doxy/Macro_core_website_1.png
|
||||
PROJECT_LOGO = $(DOXY_CONTENT)Macro_core_website_1.png
|
||||
PROJECT_BRIEF = "Production Ready Macros for SAS Application Developers"
|
||||
RECURSIVE = YES
|
||||
REPEAT_BRIEF = NO
|
||||
@@ -2,7 +2,8 @@
|
||||
<!-- Generated by doxygen 1.8.14 -->
|
||||
<!-- Navigation index tabs for HTML output -->
|
||||
<navindex>
|
||||
<tab type="mainpage" visible="no" title=""/>
|
||||
<tab type="mainpage" visible="yes" title="Home"/>
|
||||
<tab type="user" url="/contributing" title="Contributing"/>
|
||||
<tab type="pages" visible="no" title="" intro=""/>
|
||||
<tab type="modules" visible="no" title="" intro=""/>
|
||||
<tab type="namespaces" visible="no" title="">
|
||||
@@ -108,4 +109,4 @@
|
||||
<files visible="yes"/>
|
||||
</memberdecl>
|
||||
</directory>
|
||||
</doxygenlayout>
|
||||
</doxygenlayout>
|
||||
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
BIN
sasjs/doxy/favicon.ico
Normal file
BIN
sasjs/doxy/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
32
sasjs/doxy/new_footer.html
Normal file
32
sasjs/doxy/new_footer.html
Normal file
@@ -0,0 +1,32 @@
|
||||
<!-- HTML footer for doxygen 1.8.17-->
|
||||
<!-- start footer part -->
|
||||
<!--BEGIN GENERATE_TREEVIEW-->
|
||||
<div id="nav-path" class="navpath">
|
||||
<!-- id is needed for treeview function! -->
|
||||
<ul>
|
||||
$navpath
|
||||
<li class="footer">
|
||||
$generatedby
|
||||
<a href="https://www.doxygen.org/index.html">
|
||||
<img class="footer" src="$relpath^doxygen.png" alt="doxygen"
|
||||
/></a>
|
||||
$doxygenversion
|
||||
</li>
|
||||
<li>
|
||||
<i> For more information visit the </i>
|
||||
<a href="https://github.com/sasjs/core">Macro Core library</a>.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!--END GENERATE_TREEVIEW-->
|
||||
<!--BEGIN !GENERATE_TREEVIEW-->
|
||||
<hr class="footer" />
|
||||
<address class="footer">
|
||||
<small>
|
||||
$generatedby  <a href="http://www.doxygen.org/index.html">
|
||||
<img class="footer" src="$relpath^doxygen.png" alt="doxygen" />
|
||||
</a>
|
||||
$doxygenversion
|
||||
</small>
|
||||
</address>
|
||||
<!--END !GENERATE_TREEVIEW-->
|
||||
93
sasjs/doxy/new_header.html
Normal file
93
sasjs/doxy/new_header.html
Normal file
@@ -0,0 +1,93 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<!-- HTML header for doxygen 1.8.17-->
|
||||
<html xmlns="https://www.w3.org/1999/xhtml" lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=9" />
|
||||
<meta name="generator" content="Doxygen $doxygenversion" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<!--BEGIN PROJECT_NAME-->
|
||||
<title>$projectname: $title</title>
|
||||
<!--END PROJECT_NAME-->
|
||||
<!--BEGIN !PROJECT_NAME-->
|
||||
<title>$title</title>
|
||||
<!--END !PROJECT_NAME-->
|
||||
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css" />
|
||||
<script type="text/javascript" src="$relpath^jquery.js"></script>
|
||||
<script type="text/javascript" src="$relpath^dynsections.js"></script>
|
||||
$treeview $search $mathjax
|
||||
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
|
||||
<link rel="icon" href="https://sasjs.io/img/runningman.jpg" />
|
||||
$extrastylesheet
|
||||
</head>
|
||||
<body>
|
||||
<div id="top">
|
||||
<!-- do not remove this div, it is closed by doxygen! -->
|
||||
|
||||
<!--BEGIN TITLEAREA-->
|
||||
<div id="titlearea">
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tbody>
|
||||
<tr style="height: 56px">
|
||||
<!--BEGIN PROJECT_LOGO-->
|
||||
<td id="projectlogo">
|
||||
<img alt="Logo" src="$relpath^$projectlogo" />
|
||||
</td>
|
||||
<!--END PROJECT_LOGO-->
|
||||
<!--BEGIN PROJECT_NAME-->
|
||||
<td id="projectalign" style="padding-left: 0.5em">
|
||||
<div id="projectname">
|
||||
<!--BEGIN PROJECT_NUMBER--> <span id="projectnumber"
|
||||
>$projectnumber</span
|
||||
><!--END PROJECT_NUMBER-->
|
||||
</div>
|
||||
<!--BEGIN PROJECT_BRIEF-->
|
||||
<div id="projectbrief">
|
||||
Production Ready Macros for SAS Application Developers<br />
|
||||
<a href="https://github.com/sasjs/core">
|
||||
https://github.com/sasjs/core
|
||||
</a>
|
||||
</div>
|
||||
<meta name="Description" content="$projectbrief" />
|
||||
<!--END PROJECT_BRIEF-->
|
||||
</td>
|
||||
<!--END PROJECT_NAME-->
|
||||
<!--BEGIN !PROJECT_NAME-->
|
||||
<!--BEGIN PROJECT_BRIEF-->
|
||||
<td style="padding-left: 0.5em">
|
||||
<div id="projectbrief">$projectbrief</div>
|
||||
<table
|
||||
style="padding-left: 2em"
|
||||
cellspacing="0"
|
||||
cellpadding="0"
|
||||
>
|
||||
<tr>
|
||||
<td>
|
||||
Production Ready Macros for SAS Application Developers
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://github.com/sasjs/core">
|
||||
https://github.com/sasjs/core
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<!--END PROJECT_BRIEF-->
|
||||
<!--END !PROJECT_NAME-->
|
||||
<!--BEGIN DISABLE_INDEX-->
|
||||
<!--BEGIN SEARCHENGINE-->
|
||||
<td>$searchbox</td>
|
||||
<!--END SEARCHENGINE-->
|
||||
<!--END DISABLE_INDEX-->
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--END TITLEAREA-->
|
||||
<!-- end header part -->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,5 +1,5 @@
|
||||
#projectlogo img
|
||||
{
|
||||
border: 0px none;
|
||||
max-height:70px
|
||||
#projectlogo img
|
||||
{
|
||||
border: 0px none;
|
||||
max-height:70px
|
||||
}
|
||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
7
sasjs/sasjsconfig.json
Normal file
7
sasjs/sasjsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "https://cli.sasjs.io/sasjsconfig-schema.json",
|
||||
"macroFolders": ["../base", "../meta", "../metax", "../viya", "../lua"],
|
||||
"docConfig":{
|
||||
"displayMacroCore": false
|
||||
}
|
||||
}
|
||||
23
sasjs/utils/build.sh
Executable file
23
sasjs/utils/build.sh
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
####################################################################
|
||||
# PROJECT: Macro Core Docs Build #
|
||||
####################################################################
|
||||
|
||||
sasjs doc
|
||||
|
||||
# refresh github pages site
|
||||
rm -rf sasjsbuild/docsite
|
||||
git clone git@github.com:sasjs/core.github.io.git sasjsbuild/docsite
|
||||
rm -rf sasjsbuild/docsite/*
|
||||
mv sasjsbuild/docs/* sasjsbuild/docsite/
|
||||
cd sasjsbuild/docsite/
|
||||
echo 'core.sasjs.io' > CNAME
|
||||
git add .
|
||||
git commit -m "build.sh build on $(date +%F:%H:%M:%S)"
|
||||
git push
|
||||
npx sitemap-generator-cli https://core.sasjs.io
|
||||
git add .
|
||||
git commit -m "adding sitemap"
|
||||
git push
|
||||
|
||||
echo "check it out: https://sasjs.github.io/core.github.io/files.html"
|
||||
@@ -4,7 +4,6 @@
|
||||
@details Expects oauth token in a global macro variable (default
|
||||
ACCESS_TOKEN).
|
||||
|
||||
options mprint;
|
||||
%mv_createfolder(path=/Public)
|
||||
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
%let oauth_bearer=oauth_bearer=sas_services;
|
||||
%let &access_token_var=;
|
||||
%end;
|
||||
%put &sysmacroname: grant_type=&grant_type;
|
||||
|
||||
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
|
||||
and &grant_type ne sas_services
|
||||
)
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
@details If not running in Studo 5 +, will expect an oauth token in a global
|
||||
macro variable (default ACCESS_TOKEN).
|
||||
|
||||
options mprint;
|
||||
%mv_createfolder(path=/Public/test/blah)
|
||||
%mv_deleteviyafolder(path=/Public/test)
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
%let oauth_bearer=oauth_bearer=sas_services;
|
||||
%let &access_token_var=;
|
||||
%end;
|
||||
%put &sysmacroname: grant_type=&grant_type;
|
||||
|
||||
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
|
||||
and &grant_type ne sas_services
|
||||
)
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
%let oauth_bearer=oauth_bearer=sas_services;
|
||||
%let &access_token_var=;
|
||||
%end;
|
||||
%put &sysmacroname: grant_type=&grant_type;
|
||||
|
||||
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
|
||||
and &grant_type ne sas_services
|
||||
)
|
||||
|
||||
@@ -49,7 +49,6 @@
|
||||
%let oauth_bearer=oauth_bearer=sas_services;
|
||||
%let &access_token_var=;
|
||||
%end;
|
||||
%put &sysmacroname: grant_type=&grant_type;
|
||||
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
|
||||
and &grant_type ne sas_services
|
||||
)
|
||||
@@ -114,10 +113,10 @@ run;
|
||||
%let fname3=%mf_getuniquefileref();
|
||||
%let fpath1=%sysfunc(pathname(&fname1));
|
||||
%let fpath2=%sysfunc(pathname(&fname2));
|
||||
%let fpath3=%sysfunc(pathname(&fname2));
|
||||
%let fpath3=%sysfunc(pathname(&fname3));
|
||||
|
||||
/* compile the lua JSON module */
|
||||
%ml_json()
|
||||
%ml_json()
|
||||
/* read using LUA - this allows the code to be of any length */
|
||||
data _null_;
|
||||
file "&fpath3..lua";
|
||||
@@ -147,4 +146,5 @@ data _null_;
|
||||
run;
|
||||
filename &fname1 clear;
|
||||
filename &fname2 clear;
|
||||
filename &fname3 clear;
|
||||
%mend;
|
||||
|
||||
267
viya/mv_getjoblog.sas
Normal file
267
viya/mv_getjoblog.sas
Normal file
@@ -0,0 +1,267 @@
|
||||
/**
|
||||
@file
|
||||
@brief Extract the log from a completed SAS Viya Job
|
||||
@details Extracts log from a Viya job and writes it out to a fileref
|
||||
|
||||
To query the job, you need the URI. Sample code for achieving this
|
||||
is provided below.
|
||||
|
||||
## Example
|
||||
|
||||
First, compile the macros:
|
||||
|
||||
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
|
||||
%inc mc;
|
||||
|
||||
Next, create a job (in this case, a web service):
|
||||
|
||||
filename ft15f001 temp;
|
||||
parmcards4;
|
||||
data ;
|
||||
rand=ranuni(0)*1000;
|
||||
do x=1 to rand;
|
||||
y=rand*4;
|
||||
output;
|
||||
end;
|
||||
run;
|
||||
proc sort data=&syslast
|
||||
by descending y;
|
||||
run;
|
||||
;;;;
|
||||
%mv_createwebservice(path=/Public/temp,name=demo)
|
||||
|
||||
Execute it:
|
||||
|
||||
%mv_jobexecute(path=/Public/temp
|
||||
,name=demo
|
||||
,outds=work.info
|
||||
)
|
||||
|
||||
Wait for it to finish, and grab the uri:
|
||||
|
||||
data _null_;
|
||||
set work.info;
|
||||
if method='GET' and rel='self';
|
||||
call symputx('uri',uri);
|
||||
run;
|
||||
|
||||
Finally, fetch the log:
|
||||
|
||||
%mv_getjoblog(uri=&uri,outref=mylog)
|
||||
|
||||
This macro is used by the mv_jobwaitfor.sas macro, which is generally a more
|
||||
convenient way to wait for the job to finish before fetching the log.
|
||||
|
||||
|
||||
@param [in] access_token_var= The global macro variable to contain the access token
|
||||
@param [in] mdebug= set to 1 to enable DEBUG messages
|
||||
@param [in] grant_type= valid values:
|
||||
@li password
|
||||
@li authorization_code
|
||||
@li detect - will check if access_token exists, if not will use sas_services if
|
||||
a SASStudioV session else authorization_code. Default option.
|
||||
@li sas_services - will use oauth_bearer=sas_services.
|
||||
@param [in] uri= The uri of the running job for which to fetch the status,
|
||||
in the format `/jobExecution/jobs/$UUID/state` (unquoted).
|
||||
@param [out] outref= The output fileref to which to APPEND the log (is always
|
||||
appended).
|
||||
|
||||
|
||||
@version VIYA V.03.04
|
||||
@author Allan Bowe, source: https://github.com/sasjs/core
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_abort.sas
|
||||
@li mf_getplatform.sas
|
||||
@li mf_existfileref.sas
|
||||
@li ml_json.sas
|
||||
|
||||
**/
|
||||
|
||||
%macro mv_getjoblog(uri=0,outref=0
|
||||
,contextName=SAS Job Execution compute context
|
||||
,access_token_var=ACCESS_TOKEN
|
||||
,grant_type=sas_services
|
||||
,mdebug=0
|
||||
);
|
||||
%local oauth_bearer;
|
||||
%if &grant_type=detect %then %do;
|
||||
%if %symexist(&access_token_var) %then %let grant_type=authorization_code;
|
||||
%else %let grant_type=sas_services;
|
||||
%end;
|
||||
%if &grant_type=sas_services %then %do;
|
||||
%let oauth_bearer=oauth_bearer=sas_services;
|
||||
%let &access_token_var=;
|
||||
%end;
|
||||
|
||||
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
|
||||
and &grant_type ne sas_services
|
||||
)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Invalid value for grant_type: &grant_type)
|
||||
)
|
||||
|
||||
/* validation in datastep for better character safety */
|
||||
%local errmsg errflg;
|
||||
data _null_;
|
||||
uri=symget('uri');
|
||||
if length(uri)<12 then do;
|
||||
call symputx('errflg',1);
|
||||
call symputx('errmsg',"URI is invalid (too short) - '&uri'",'l');
|
||||
end;
|
||||
if scan(uri,-1)='state' or scan(uri,1) ne 'jobExecution' then do;
|
||||
call symputx('errflg',1);
|
||||
call symputx('errmsg',
|
||||
"URI should be in format /jobExecution/jobs/$$$$UUID$$$$"
|
||||
!!" but is actually like: &uri",'l');
|
||||
end;
|
||||
run;
|
||||
|
||||
%mp_abort(iftrue=(&errflg=1)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(&errmsg)
|
||||
)
|
||||
|
||||
%mp_abort(iftrue=(&outref=0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Output fileref should be provided)
|
||||
)
|
||||
|
||||
%if %mf_existfileref(&outref) ne 1 %then %do;
|
||||
filename &outref temp;
|
||||
%end;
|
||||
|
||||
options noquotelenmax;
|
||||
%local base_uri; /* location of rest apis */
|
||||
%let base_uri=%mf_getplatform(VIYARESTAPI);
|
||||
|
||||
/* prepare request*/
|
||||
%local fname1;
|
||||
%let fname1=%mf_getuniquefileref();
|
||||
proc http method='GET' out=&fname1 &oauth_bearer
|
||||
url="&base_uri&uri";
|
||||
headers
|
||||
%if &grant_type=authorization_code %then %do;
|
||||
"Authorization"="Bearer &&&access_token_var"
|
||||
%end;
|
||||
;
|
||||
run;
|
||||
%if &SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201 %then
|
||||
%do;
|
||||
data _null_;infile &fname1;input;putlog _infile_;run;
|
||||
%mp_abort(mac=&sysmacroname
|
||||
,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
|
||||
)
|
||||
%end;
|
||||
%local fname2 fname3 fpath1 fpath2 fpath3;
|
||||
%let fname2=%mf_getuniquefileref();
|
||||
%let fname3=%mf_getuniquefileref();
|
||||
%let fpath1=%sysfunc(pathname(&fname1));
|
||||
%let fpath2=%sysfunc(pathname(&fname2));
|
||||
%let fpath3=%sysfunc(pathname(&fname3));
|
||||
|
||||
/* compile the lua JSON module */
|
||||
%ml_json()
|
||||
/* read using LUA - this allows the code to be of any length */
|
||||
data _null_;
|
||||
file "&fpath3..lua";
|
||||
put '
|
||||
infile = io.open (sas.symget("fpath1"), "r")
|
||||
outfile = io.open (sas.symget("fpath2"), "w")
|
||||
io.input(infile)
|
||||
local resp=json.decode(io.read())
|
||||
local logloc=resp["logLocation"]
|
||||
outfile:write(logloc)
|
||||
io.close(infile)
|
||||
io.close(outfile)
|
||||
';
|
||||
run;
|
||||
%inc "&fpath3..lua";
|
||||
/* get log path*/
|
||||
%let errflg=1;
|
||||
%let errmsg=No entry in &fname2 fileref;
|
||||
data _null_;
|
||||
infile &fname2;
|
||||
input;
|
||||
uri=_infile_;
|
||||
if length(uri)<12 then do;
|
||||
call symputx('errflg',1);
|
||||
call symputx('errmsg',"URI is invalid (too short) - '&uri'",'l');
|
||||
end;
|
||||
if scan(uri,1) ne 'files' or scan(uri,2) ne 'files' then do;
|
||||
call symputx('errflg',1);
|
||||
call symputx('errmsg',
|
||||
"URI should be in format /files/files/$$$$UUID$$$$"
|
||||
!!" but is actually like: &uri",'l');
|
||||
end;
|
||||
call symputx('errflg',0,'l');
|
||||
call symputx('logloc',uri,'l');
|
||||
run;
|
||||
|
||||
%mp_abort(iftrue=(&errflg=1)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(&errmsg)
|
||||
)
|
||||
|
||||
/* we have a log uri - now fetch the log */
|
||||
proc http method='GET' out=&fname1 &oauth_bearer
|
||||
url="&base_uri&logloc/content";
|
||||
headers
|
||||
%if &grant_type=authorization_code %then %do;
|
||||
"Authorization"="Bearer &&&access_token_var"
|
||||
%end;
|
||||
;
|
||||
run;
|
||||
%if &SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201 %then
|
||||
%do;
|
||||
data _null_;infile &fname1;input;putlog _infile_;run;
|
||||
%mp_abort(mac=&sysmacroname
|
||||
,msg=%str(logfetch: &SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
|
||||
)
|
||||
%end;
|
||||
|
||||
data _null_;
|
||||
file "&fpath3..lua";
|
||||
put '
|
||||
infile = io.open (sas.symget("fpath1"), "r")
|
||||
outfile = io.open (sas.symget("fpath2"), "w")
|
||||
io.input(infile)
|
||||
local resp=json.decode(io.read())
|
||||
for i, v in pairs(resp["items"]) do
|
||||
outfile:write(v.line,"\n")
|
||||
end
|
||||
io.close(infile)
|
||||
io.close(outfile)
|
||||
';
|
||||
run;
|
||||
%inc "&fpath3..lua";
|
||||
|
||||
/* write log out to the specified fileref */
|
||||
data _null_;
|
||||
infile &fname2 end=last;
|
||||
file &outref mod;
|
||||
if _n_=1 then do;
|
||||
put "/** SASJS Viya Job Log Extract start: &uri **/";
|
||||
end;
|
||||
input;
|
||||
put _infile_;
|
||||
%if &mdebug=1 %then %do;
|
||||
putlog _infile_;
|
||||
%end;
|
||||
if last then do;
|
||||
put "/** SASJS Viya Job Log Extract end: &uri **/";
|
||||
end;
|
||||
run;
|
||||
|
||||
%if &mdebug=0 %then %do;
|
||||
filename &fname1 clear;
|
||||
filename &fname2 clear;
|
||||
filename &fname3 clear;
|
||||
%end;
|
||||
%else %do;
|
||||
%put _local_;
|
||||
%end;
|
||||
%mend;
|
||||
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
%let oauth_bearer=oauth_bearer=sas_services;
|
||||
%let &access_token_var=;
|
||||
%end;
|
||||
%put &sysmacroname: grant_type=&grant_type;
|
||||
|
||||
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
|
||||
and &grant_type ne sas_services
|
||||
)
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
%let oauth_bearer=oauth_bearer=sas_services;
|
||||
%let &access_token_var=;
|
||||
%end;
|
||||
%put &sysmacroname: grant_type=&grant_type;
|
||||
|
||||
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
|
||||
and &grant_type ne sas_services
|
||||
)
|
||||
|
||||
344
viya/mv_jobflow.sas
Normal file
344
viya/mv_jobflow.sas
Normal file
@@ -0,0 +1,344 @@
|
||||
/**
|
||||
@file
|
||||
@brief Execute a series of job flows
|
||||
@details Very (very) simple flow manager. Jobs execute in sequential waves,
|
||||
all previous waves must finish successfully.
|
||||
|
||||
The input table is formed as per below. Each observation represents one job.
|
||||
Each variable is converted into a macro variable with the same name.
|
||||
|
||||
## Input table (minimum variables needed)
|
||||
|
||||
@li _PROGRAM - Provides the path to the job itself
|
||||
@li FLOW_ID - Numeric value, provides sequential ordering capability. Is
|
||||
optional, will default to 0 if not provided.
|
||||
@li _CONTEXTNAME - Dictates which context should be used to run the job. If
|
||||
blank (or not provided), will default to `SAS Job Execution compute context`.
|
||||
|
||||
Any additional variables provided in this table are converted into macro
|
||||
variables and passed into the relevant job.
|
||||
|
||||
|_PROGRAM| FLOW_ID (optional)| _CONTEXTNAME (optional) |
|
||||
|---|---|---|
|
||||
|/Public/jobs/somejob1|0|SAS Job Execution compute context|
|
||||
|/Public/jobs/somejob2|0|SAS Job Execution compute context|
|
||||
|
||||
## Output table (minimum variables produced)
|
||||
|
||||
@li _PROGRAM - the SAS Drive path of the job
|
||||
@li URI - the URI of the executed job
|
||||
@li STATE - the completed state of the job
|
||||
@li TIMESTAMP - the datetime that the job completed
|
||||
@li JOBPARAMS - the parameters that were passed to the job
|
||||
@li FLOW_ID - the id of the flow in which the job was executed
|
||||
|
||||

|
||||
|
||||
To avoid hammering the box with many hits in rapid succession, a one
|
||||
second pause is made between every request.
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
First, compile the macros:
|
||||
|
||||
filename mc url
|
||||
"https://raw.githubusercontent.com/sasjs/core/main/all.sas";
|
||||
%inc mc;
|
||||
|
||||
Next, create some jobs (in this case, as web services):
|
||||
|
||||
filename ft15f001 temp;
|
||||
parmcards4;
|
||||
%put this is job: &_program;
|
||||
%put this was run in flow &flow_id;
|
||||
data ;
|
||||
rand=ranuni(0)*¯ovar1;
|
||||
do x=1 to rand;
|
||||
y=rand*¯ovar2;
|
||||
if y=100 then abort;
|
||||
output;
|
||||
end;
|
||||
run;
|
||||
;;;;
|
||||
%mv_createwebservice(path=/Public/temp,name=demo1)
|
||||
%mv_createwebservice(path=/Public/temp,name=demo2)
|
||||
|
||||
Prepare an input table with 60 executions:
|
||||
|
||||
data work.inputjobs;
|
||||
_contextName='SAS Job Execution compute context';
|
||||
do flow_id=1 to 3;
|
||||
do i=1 to 20;
|
||||
_program='/Public/temp/demo1';
|
||||
macrovar1=10*i;
|
||||
macrovar2=4*i;
|
||||
output;
|
||||
i+1;
|
||||
_program='/Public/temp/demo2';
|
||||
macrovar1=40*i;
|
||||
macrovar2=44*i;
|
||||
output;
|
||||
end;
|
||||
end;
|
||||
run;
|
||||
|
||||
Trigger the flow
|
||||
|
||||
%mv_jobflow(inds=work.inputjobs
|
||||
,maxconcurrency=4
|
||||
,outds=work.results
|
||||
,outref=myjoblog
|
||||
)
|
||||
|
||||
data _null_;
|
||||
infile myjoblog;
|
||||
input; put _infile_;
|
||||
run;
|
||||
|
||||
|
||||
@param [in] access_token_var= The global macro variable to contain the access token
|
||||
@param [in] grant_type= valid values:
|
||||
@li password
|
||||
@li authorization_code
|
||||
@li detect - will check if access_token exists, if not will use sas_services if
|
||||
a SASStudioV session else authorization_code. Default option.
|
||||
@li sas_services - will use oauth_bearer=sas_services
|
||||
@param [in] inds= The input dataset containing a list of jobs and parameters
|
||||
@param [in] maxconcurrency= The max number of parallel jobs to run. Default=8.
|
||||
@param [in] mdebug= set to 1 to enable DEBUG messages
|
||||
@param [out] outds= The output dataset containing the results
|
||||
@param [out] outref= The output fileref to which to append the log file(s).
|
||||
|
||||
@version VIYA V.03.05
|
||||
@author Allan Bowe, source: https://github.com/sasjs/core
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_nobs.sas
|
||||
@li mp_abort.sas
|
||||
@li mf_getplatform.sas
|
||||
@li mf_getuniquefileref.sas
|
||||
@li mf_existvarlist.sas
|
||||
@li mv_jobwaitfor.sas
|
||||
@li mv_jobexecute.sas
|
||||
|
||||
**/
|
||||
|
||||
%macro mv_jobflow(inds=0,outds=work.mv_jobflow
|
||||
,maxconcurrency=8
|
||||
,access_token_var=ACCESS_TOKEN
|
||||
,grant_type=sas_services
|
||||
,outref=0
|
||||
,mdebug=0
|
||||
);
|
||||
%local oauth_bearer;
|
||||
%if &grant_type=detect %then %do;
|
||||
%if %symexist(&access_token_var) %then %let grant_type=authorization_code;
|
||||
%else %let grant_type=sas_services;
|
||||
%end;
|
||||
%if &grant_type=sas_services %then %do;
|
||||
%let oauth_bearer=oauth_bearer=sas_services;
|
||||
%let &access_token_var=;
|
||||
%end;
|
||||
|
||||
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
|
||||
and &grant_type ne sas_services
|
||||
)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Invalid value for grant_type: &grant_type)
|
||||
)
|
||||
|
||||
%mp_abort(iftrue=("&inds"="0")
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Input dataset was not provided)
|
||||
)
|
||||
%mp_abort(iftrue=(%mf_existVarList(&inds,_PROGRAM)=0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(The _PROGRAM column must exist on input dataset &inds)
|
||||
)
|
||||
%mp_abort(iftrue=(&maxconcurrency<1)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(The maxconcurrency variable should be a positive integer)
|
||||
)
|
||||
|
||||
/* set defaults if not provided */
|
||||
%if %mf_existVarList(&inds,_CONTEXTNAME FLOW_ID)=0 %then %do;
|
||||
data &inds;
|
||||
%if %mf_existvarList(&inds,_CONTEXTNAME)=0 %then %do;
|
||||
length _CONTEXTNAME $128;
|
||||
retain _CONTEXTNAME "SAS Job Execution compute context";
|
||||
%end;
|
||||
%if %mf_existvarList(&inds,FLOW_ID)=0 %then %do;
|
||||
retain FLOW_ID 0;
|
||||
%end;
|
||||
set &inds;
|
||||
run;
|
||||
%end;
|
||||
|
||||
%local missings;
|
||||
proc sql noprint;
|
||||
select count(*) into: missings
|
||||
from &inds
|
||||
where flow_id is null or _program is null;
|
||||
%mp_abort(iftrue=(&missings>0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(input dataset contains &missings missing values for FLOW_ID or _PROGRAM)
|
||||
)
|
||||
|
||||
%if %mf_nobs(&inds)=0 %then %do;
|
||||
%put No observations in &inds! Leaving macro &sysmacroname;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
/* ensure output table is available */
|
||||
data &outds;run;
|
||||
proc sql;
|
||||
drop table &outds;
|
||||
|
||||
options noquotelenmax;
|
||||
%local base_uri; /* location of rest apis */
|
||||
%let base_uri=%mf_getplatform(VIYARESTAPI);
|
||||
|
||||
|
||||
/* get flows */
|
||||
proc sort data=&inds;
|
||||
by flow_id;
|
||||
run;
|
||||
data _null_;
|
||||
set &inds (keep=flow_id) end=last;
|
||||
by flow_id;
|
||||
if last.flow_id then do;
|
||||
cnt+1;
|
||||
call symputx(cats('flow',cnt),flow_id,'l');
|
||||
end;
|
||||
if last then call symputx('flowcnt',cnt,'l');
|
||||
run;
|
||||
|
||||
/* prepare temporary datasets and frefs */
|
||||
%local fid jid jds jjson jdsapp jdsrunning jdswaitfor jfref;
|
||||
data;run;%let jds=&syslast;
|
||||
data;run;%let jjson=&syslast;
|
||||
data;run;%let jdsapp=&syslast;
|
||||
data;run;%let jdsrunning=&syslast;
|
||||
data;run;%let jdswaitfor=&syslast;
|
||||
%let jfref=%mf_getuniquefileref();
|
||||
|
||||
/* start loop */
|
||||
%do fid=1 %to &flowcnt;
|
||||
%put preparing job attributes for flow &&flow&fid;
|
||||
%local jds jcnt;
|
||||
data &jds(drop=_contextName _program);
|
||||
set &inds(where=(flow_id=&&flow&fid));
|
||||
if _contextName='' then _contextName="SAS Job Execution compute context";
|
||||
call symputx(cats('job',_n_),_program,'l');
|
||||
call symputx(cats('context',_n_),_contextName,'l');
|
||||
call symputx('jcnt',_n_,'l');
|
||||
run;
|
||||
%put exporting job variables in json format;
|
||||
%do jid=1 %to &jcnt;
|
||||
data &jjson;
|
||||
set &jds;
|
||||
if _n_=&jid then do;
|
||||
output;
|
||||
stop;
|
||||
end;
|
||||
run;
|
||||
proc json out=&jfref;
|
||||
export &jjson / nosastags fmtnumeric;
|
||||
run;
|
||||
data _null_;
|
||||
infile &jfref lrecl=32767;
|
||||
input;
|
||||
jparams='jparams'!!left(symget('jid'));
|
||||
call symputx(jparams,substr(_infile_,3,length(_infile_)-4));
|
||||
run;
|
||||
%local jobuid&jid;
|
||||
%let jobuid&jid=0; /* used in next loop */
|
||||
%end;
|
||||
%local concurrency completed;
|
||||
%let concurrency=0;
|
||||
%let completed=0;
|
||||
proc sql; drop table &jdsrunning;
|
||||
%do jid=1 %to &jcnt;
|
||||
/**
|
||||
* now we can execute the jobs up to the maxconcurrency setting
|
||||
*/
|
||||
%if "&&job&jid" ne "0" %then %do; /* this var is zero if job finished */
|
||||
|
||||
/* check to see if the job finished in the previous round */
|
||||
%if %sysfunc(exist(&outds))=1 %then %do;
|
||||
%local jobcheck; %let jobcheck=0;
|
||||
proc sql noprint;
|
||||
select count(*) into: jobcheck
|
||||
from &outds where uuid="&&jobuid&jid";
|
||||
%if &jobcheck>0 %then %do;
|
||||
%put &&job&jid in flow &fid with uid &&jobuid&jid completed!;
|
||||
%let job&jid=0;
|
||||
%end;
|
||||
%end;
|
||||
|
||||
/* check if job was triggered and if so, if we have enough slots to run */
|
||||
%if "&&jobuid&jid"="0" and &concurrency<&maxconcurrency %then %do;
|
||||
%local jobname jobpath;
|
||||
%let jobname=%scan(&&job&jid,-1,/);
|
||||
%let jobpath=%substr(&&job&jid,1,%length(&&job&jid)-%length(&jobname)-1);
|
||||
%put executing &jobpath/&jobname with paramstring &&jparams&jid;
|
||||
%mv_jobexecute(path=&jobpath
|
||||
,name=&jobname
|
||||
,paramstring=%superq(jparams&jid)
|
||||
,outds=&jdsapp
|
||||
)
|
||||
data &jdsapp;
|
||||
format jobparams $32767.;
|
||||
set &jdsapp(where=(method='GET' and rel='state'));
|
||||
jobparams=symget("jparams&jid");
|
||||
/* uri here has the /state suffix */
|
||||
uuid=scan(uri,-2,'/');
|
||||
call symputx("jobuid&jid",uuid,'l');
|
||||
run;
|
||||
proc append base=&jdsrunning data=&jdsapp;
|
||||
run;
|
||||
%let concurrency=%eval(&concurrency+1);
|
||||
/* sleep one second after every request to smooth the impact */
|
||||
data _null_;
|
||||
call sleep(1,1);
|
||||
run;
|
||||
%end;
|
||||
%end;
|
||||
%if &jid=&jcnt %then %do;
|
||||
/* we are at the end of the loop - time to see which jobs have finished */
|
||||
%mv_jobwaitfor(ANY,inds=&jdsrunning,outds=&jdswaitfor,outref=&outref)
|
||||
%local done;
|
||||
%let done=%mf_nobs(&jdswaitfor);
|
||||
%if &done>0 %then %do;
|
||||
%let completed=%eval(&completed+&done);
|
||||
%let concurrency=%eval(&concurrency-&done);
|
||||
data &jdsapp;
|
||||
set &jdswaitfor;
|
||||
flow_id=&&flow&fid;
|
||||
uuid=scan(uri,-1,'/');
|
||||
run;
|
||||
proc append base=&outds data=&jdsapp;
|
||||
run;
|
||||
%end;
|
||||
proc sql;
|
||||
delete from &jdsrunning
|
||||
where uuid in (select uuid from &outds
|
||||
where state in ('canceled','completed','failed')
|
||||
);
|
||||
|
||||
/* loop again if jobs are left */
|
||||
%if &completed < &jcnt %then %do;
|
||||
%let jid=0;
|
||||
%put looping flow &fid again - &completed of &jcnt jobs completed, &concurrency jobs running;
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
/* back up and execute the next flow */
|
||||
%end;
|
||||
|
||||
%if &mdebug=1 %then %do;
|
||||
%put _local_;
|
||||
%end;
|
||||
|
||||
%mend;
|
||||
@@ -72,7 +72,7 @@
|
||||
should be in a `_program` variable.
|
||||
@param [out] outds= The output dataset containing the list of states by job
|
||||
(default=work.mv_jobexecute)
|
||||
|
||||
@param [out] outref= A fileref to which the spawned job logs should be appended.
|
||||
|
||||
@version VIYA V.03.04
|
||||
@author Allan Bowe, source: https://github.com/sasjs/core
|
||||
@@ -83,6 +83,7 @@
|
||||
@li mf_getuniquefileref.sas
|
||||
@li mf_existvar.sas
|
||||
@li mf_nobs.sas
|
||||
@li mv_getjoblog.sas
|
||||
|
||||
**/
|
||||
|
||||
@@ -91,6 +92,7 @@
|
||||
,grant_type=sas_services
|
||||
,inds=0
|
||||
,outds=work.mv_jobwaitfor
|
||||
,outref=0
|
||||
);
|
||||
%local oauth_bearer;
|
||||
%if &grant_type=detect %then %do;
|
||||
@@ -101,7 +103,7 @@
|
||||
%let oauth_bearer=oauth_bearer=sas_services;
|
||||
%let &access_token_var=;
|
||||
%end;
|
||||
%put &sysmacroname: grant_type=&grant_type;
|
||||
|
||||
%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
|
||||
and &grant_type ne sas_services
|
||||
)
|
||||
@@ -134,7 +136,7 @@ options noquotelenmax;
|
||||
data _null_;
|
||||
length jobparams $32767;
|
||||
set &inds end=last;
|
||||
call symputx(cats('joburi',_n_),uri,'l');
|
||||
call symputx(cats('joburi',_n_),substr(uri,1,55)!!'/state','l');
|
||||
call symputx(cats('jobname',_n_),_program,'l');
|
||||
call symputx(cats('jobparams',_n_),jobparams,'l');
|
||||
if last then call symputx('uricnt',_n_,'l');
|
||||
@@ -178,14 +180,20 @@ run;
|
||||
run;
|
||||
|
||||
%if &status=completed or &status=failed or &status=canceled %then %do;
|
||||
%local plainuri;
|
||||
%let plainuri=%substr(&&joburi&i,1,55);
|
||||
proc sql;
|
||||
insert into &outds set
|
||||
_program="&&jobname&i",
|
||||
uri="&&joburi&i",
|
||||
uri="&plainuri",
|
||||
state="&status",
|
||||
timestamp=datetime(),
|
||||
jobparams=symget("jobparams&i");
|
||||
%let joburi&i=0; /* do not re-check */
|
||||
/* fetch log */
|
||||
%if %str(&outref) ne 0 %then %do;
|
||||
%mv_getjoblog(uri=&plainuri,outref=&outref)
|
||||
%end;
|
||||
%end;
|
||||
%else %if &status=idle or &status=pending or &status=running %then %do;
|
||||
data _null_;
|
||||
|
||||
Reference in New Issue
Block a user