FOCUS AREAS

ARM - Gathering Performance Data Using ARM Macros

This section provides examples that best demonstrate how the ARM API works. The following code demonstrates how to use all of the ARM macros in the same DATA step:
data _null_;

/* note the start of the application */
%arminit(appname='Sales App', appuser='userxyz');

/* define the transaction classes */
%armgtid(txnname='Sales Order', txndet='Sales Order Transaction'); 

/* more arm_getid calls go here for different transaction classes */

/* note the start of the transaction */
%armstrt; 

/* place the actual transaction code here */

/* update the status of the transaction as it is running */
%armupdt(data='Sales transaction still running...');

/* place the actual transaction stop here */

/* note that the transaction has stopped */
%armstop(status=0);
 
/* note the end of the application */
%armend;
run;

All ID management is performed by the macros without requiring the calling program to track IDs. Each macro in the previous example uses the most recently generated ID values from previous macros. The following example is identical, but the comments explain the passing of IDs in more detail:

data _null_;
      /*
      * The %arminit macro will generate both a SAS
      * variable and a global macro variable by the name
      * of _armapid and set it to the ID.
      */
   %arminit(appname='Sales App', appuser='userxyz');

      /*
      * The %armgtid macro uses the _armapid SAS variable.
      * It also generates both a SAS variable and global macro 
      * variable by the name of _armtxid and sets them to the 
      * ID.
      */
   %armgtid(txnname='Sales Order', txndet='Sales Order Transaction'); 

      /*
      * Because we are still on the same DATA step, the %armstrt
      * macro below will use the _armtxid SAS variable that is
      * generated from the previous %armgtid macro. It 
      * also generates an _armshdl variable.
      */
   %armstrt;

      /*
      * The %armupdt call below uses the _armshdl SAS variable
      * that is generated from the previous %armstrt macro.
      */
   %armupdt(data='Sales transaction still running...');

      /*
      * The %armstop call also uses the same _armshdl SAS
      * variable from the %armstrt.
      */
   %armstop(status=0);

      /*
      * The %armend call uses the _armapid SAS variable
      * generated by the %arminit macro earlier to end
      * the application.
      */
   %armend; 

run;

In the previous example, all ARM macro calls were on the same DATA step; therefore, DATA step variables were used to communicate ID values rather than macro variables. Any variable that is generated as a SAS variable is not included in any output data set, because the ARM macro generates a DROP statement for all macro-generated SAS variables.

You can code the macros across different DATA steps as follows and achieve the same results:

data _null_;
   /* note the start of the application */
   %arminit(appname='Sales App', appuser='userxyz');
run;

data _null_; 
   %armgtid(txnname='Sales Order', txndet='Sales Order Transaction'); 
   /* more %armgtid calls go here for different transaction classes */
run;

data _null_;
   /* note the start of the transaction */
   %armstrt;

   /* place the actual transaction here */
run; data _null_; /* update the status of the transaction as it is running */ %armupdt(data='Sales transaction still running...'); run; data _null_; /* place the actual transaction stop here */ /* note that the transaction has stopped */ %armstop(status=0); run; data _null_; /* note the end of the application */ %armend; run;

The end result is the same as in the first example, except that the macros are using the generated macro variables rather than the SAS variables for passing IDs. Allowing the macros to automatically use the global variables in basic scenarios simplifies coding. However, macros that use global variables can lead to misleading results in more complicated scenarios when you attempt to monitor concurrent applications or transactions as follows:

data _null_;
   %arminit(appname='App 1',getid=YES,txnname='txn 1');
run;

/* Start a transaction instance. */
data _null_;
   %armstrt;
run;

/* Start another transaction instance. */
data _null_;
   %armstrt;
run;

/*
* WRONG! This assumes that this %armupdt is updating
* the first transaction. However, it is actually updating the
* second transaction instance because _armshdl contains the value 
* from the last macro call that was executed, which is the second 
* transaction.
 */
data _null_;
   %armupdt(data='txn instance 1 still running...');
run;

One solution is to save the start handles using the internal _armshdl global variable and use the shandle= parameter on subsequent macro calls. Here is an example that uses macro variables to store the start handles:

data _null_;
   %arminit(appname='xyz',getid=YES,txnname='txn 1');
run;

/* Start transaction instance 1 and save the ID. */ 
data _null_;
   %armstrt();
   call symput('savhdl1', put(_armshdl,z8.));
run;

/* Start transaction instance 2 and save the ID. */
data _null_;
   %armstrt();
   call symput('savhdl2', put(_armshdl,z8.));
run;

/* Now use the shandle= parameter after you retrieve the first ID. */
data _null_;
   savhdl1 = input(left(symget('savhdl1')), 8.);
   %armupdt(data='updating txn 1', shandle=savhdl1);
run;

/* Use the same technique to stop the transactions */
/* in the order that they were started. */
data _null_;
   savhdl1 = input(symget(left('savhdl1')), 8.);
   savhdl2 = input(symget(left('savhdl2')), 8.);
   %armstop(shandle=savhdl1);
   %armstop(shandle=savhdl2);
   %armend();
run;

The previous technique is not recommended because it makes the code more complicated. A better method of saving the IDs is to use the *var parameters (appidvar=, txnidvar=, and shdlvar=) to pass or return the IDs in your own named variables. This process does not require you to code the symputs and symgets. Here is an example like the previous that uses the shdlvar= parameter to save start handles:

data _null_;
   %arminit(appname='xyz',getid=YES,txnname='txn 1');
run;

/* Start transaction instance 1 and save the ID using shdlvar=. */
data _null_;
   %armstrt( shdlvar=savhdl1 );
run;

/* Start transaction instance 2 and save the ID using shdlvar=. */
data _null_;
   %armstrt( shdlvar=savhdl2 );
run;

/* Now use the shandle= parameter after you retrieve the first ID. */
data _null_;
   %armupdt(data='updating txn 1', shdlvar=savhdl1);
run;

/* Use the same technique to stop the transactions */
/* in the order that they were started. */
data _null_;
   %armstop(shdlvar=savhdl1);
   %armstop(shdlvar=savhdl2);
   %armend();
run;

As the previous example shows, using the *var parameters simplifies the code. The previous technique is recommended for use on all ARM macro calls. The following table summarizes how the *var parameters are used.

parm ARMINIT ARMGTID ARMSTRT ARMUPDT ARMSTOP ARMEND
appidvar output input input when GETID=YES n/a n/a input
txnidvar output when GETID=YES output input when GETID=NO and
output when GETID=YES
n/a n/a n/a
shdldvar n/a n/a output input input n/a


The following example demonstrates how to use all of the *var parameters to automatically manage IDs for concurrent applications, transaction classes, and transaction instances:


data _null_;
    %arminit(appname='Appl 1', appuser='snotll', appidvar=app1);
    %arminit(appname='Appl 2', appuser='snotll', appidvar=app2);
    %arminit(appname='Appl 3', appuser='snotll', appidvar=app3);
run;
data _null_;
    %armgtid(txnname='Txn 1A', txndet='Txn Class 1A',
             appidvar=app1,txnidvar=txn1a);
    %armgtid(txnname='Txn 1B', txndet='Txn Class 1B',
             appidvar=app1,txnidvar=txn1b);
    %armgtid(txnname='Txn 2A', txndet='Txn Class 2A',
             appidvar=app2,txnidvar=txn2a);
   %armgtid(txnname='Txn 2B', txndet='Txn Class 2B',
            appidvar=app2,txnidvar=txn2b);
    %armgtid(txnname='Txn 3A', txndet='Txn Class 3A',
             appidvar=app3,txnidvar=txn3a);
    %armgtid(txnname='Txn 3B', txndet='Txn Class 3B',
             appidvar=app3,txnidvar=txn3b);
run;
data _null_;
    %armstrt(txnidvar=txn1a,shdlvar=sh1a);
    %armstrt(txnidvar=txn1b,shdlvar=sh1b);
    %armstrt(txnidvar=txn2a,shdlvar=sh2a);
    %armstrt(txnidvar=txn2b,shdlvar=sh2b);
    %armstrt(txnidvar=txn3a,shdlvar=sh3a);
    %armstrt(txnidvar=txn3b,shdlvar=sh3b);
run;
data _null_;
    %armupdt(data='Updating txn instance 1a...', shdlvar=sh1a);
    %armupdt(data='Updating txn instance 1b...', shdlvar=sh1b);
    %armupdt(data='Updating txn instance 2a...', shdlvar=sh2a);
    %armupdt(data='Updating txn instance 2b...', shdlvar=sh2b);
    %armupdt(data='Updating txn instance 3a...', shdlvar=sh3a);
    %armupdt(data='Updating txn instance 3b...', shdlvar=sh3b);
run;
data _null_;
    %armstop(status=0, shdlvar=sh1a);
    %armstop(status=1, shdlvar=sh1b);
    %armstop(status=0, shdlvar=sh2a);
    %armstop(status=1, shdlvar=sh2b);
    %armstop(status=0, shdlvar=sh3a);
    %armstop(status=1, shdlvar=sh3b);
run;
data _null_;
    %armend(appidvar=app1);
    %armend(appidvar=app2);
    %armend(appidvar=app3);
run;


As the previous example demonstrates, you can establish your own naming conventions to uniquely identify applications, transaction classes, and transaction instances across different DATA steps or SCL programs.

 
NOTE: By default, all ARM macros are disabled. This default prevents unwanted logging when ARM macros are inserted within instrumented code. To enable the ARM macros, you must explicitly set the global macro variable to _armexec=1. This globally enables all ARM macros. Any other value for _armexec disables the ARM macros.


There are two methods for setting the _armexec macro variable. The first method sets the variable during DATA step or SCL program compilation using %let:


    %let _armexec = 1;


Setting _armexec to a value other than 1 will result in no DATA step or SCL code being generated. A message like the following is written to the log:


NOTE: ARMSTRT macro bypassed by _armexec.


The second method sets the variable during DATA step or SCL program execution:


call symput('_armexec', '1');


With this technique, the macro checks the _armexec variable during program execution and the ARM function call is executed or bypassed as appropriate.


When you use the compilation technique within SCL, if _armexec is not set to 1, then compiling an SCL program that contains ARM macros will result in the macros being nulled out. Therefore, the ARM interface will be permanently unavailable until the SCL program is recompiled with _armexec=1. The recommended technique when you use ARM macros within an SCL environment is to compile the SCL with _armexec=1 using the compilation technique (%let), which you can code in an autoexec at SAS initialization. Then set _armexec to the desired value within your application code at initialization using the call symput technique. You could also code a pull-down menu option or other method within the application to turn _armexec on and off dynamically using the call symput technique.