2015/05/21

Playing with the PIPE command

If you want to do something with het output of e.g. the SEARCH command, without creating intermediate files, you can use the PIPE command. One way to do this, is using 2 scripts, but it requires the second script to check the existance of SYS$PIPE.

The scripts below do nothing useful, but are just for demonstration.

DO_SOMETHING.COM

$ IF P1 .EQS. ""
$ THEN
$     WRITE SYS$OUTPUT "NO PARAMETER"
$ ELSE
$     PIPE DIR /SIZE/DATE/OWNER | -
           SEARCH SYS$PIPE 'P1' | -
           @DO_SOME_MORE | -
           TYPE SYS$PIPE > SOME_OUTPUT.TXT
$ ENDIF
$ EXIT

DO_SOME_MORE.COM

$ IF F$TRNLNM( "SYS$PIPE" ) .EQS. "" 
$ THEN
$     WRITE SYS$OUTPUT "NO PIPE"
$ ELSE
$read_loop:
$     READ /END=end_loop /ERROR=end_loop SYS$PIPE input_line
$     WRITE SYS$OUTPUT F$ELEMENT( 0, ";", input_line )
$     GOTO read_loop
$end_loop:
$ ENDIF
$ EXIT

You can do this with just one DCL script, which contains a PIPE command, but runs itself again within the PIPE command to process the output of a previous command.


DO_SOMETHING.COM

$ IF P1 .EQS. ""
$ THEN
$     WRITE SYS$OUTPUT "NO PARAMETER"
$     EXIT
$ ENDIF
$ IF P1 .EQS. "CALL$SELF"
$ THEN
$     CALL do_some_more
$ ELSE
$     do_some_more := @'F$ENVIRONMENT( "PROCEDURE" )' "CALL$SELF"
$     PIPE DIR /SIZE/DATE/OWNER | -
           SEARCH SYS$PIPE 'P1' | -
           do_some_more | -
           TYPE SYS$PIPE > SOME_OUTPUT.TXT
$ ENDIF
$ EXIT
$do_some_more: SUBROUTINE
$read_loop:
$     READ /END=end_loop /ERROR=end_loop SYS$PIPE input_line
$     WRITE SYS$OUTPUT F$ELEMENT( 0, ";", input_line )
$     GOTO read_loop
$end_loop:
$ ENDSUBROUTINE

2015/03/23

Tracing using SDA

The problem

About one or two weeks ago, I needed to find a problem in virtual memory allocation/de-allocation, since a process was running out of virtual memory. In our system we already had some tracing in a library in place, using conditional compilation. However, when building the entire system the trace just gave too much data.

The solution

I stumbled upon an article at http://labs.hoffmanlabs.com about the C-macro tr_print. That was just what I was looking for.

The definition of the macro can be found by issueing the following command

$ libr/text sys$library:sys$lib_c.tlb /extract=vms_macros /output=tt:

Note: I used output to tt:, which is effectively my terminal, so I do not extract to a file, but straight to the screen. The last part of the file shows the definition of C-macro tr_print, which was the only thing I was interested in.

Since we code in HP Pascal, at first I tried to write code like is included in the tr_print macro, but that did not quite work out. Some issues with parameter passing, I guess. Not spending too much time on getting it to work, I decided to create a small C file with a function taking only one string parameter, and to call the C function from a Pascal module.

The source code

The created C file looks like this (stripped from all mandatory style elements):

writetrc_c.c

#include <vms_macros.h>
#include <stdlib.h>

void _write_trc_c( const char *trace_string )
{

    /* NOTE: THE DOUBLE PARENTHESES ARE REQUIRED IN THIS MACRO BY DESIGN */

    tr_print(( trace_string ));

}

and the pascal module looks like this:

writetrc.pas

[
    ENVIRONMENT
    (
        'writetrc.pen'
    )
]

MODULE writetrc;

[GLOBAL,ASYNCHRONOUS]
PROCEDURE write_trc(
    p_trace_string : VARYING [$m1] OF CHAR
);

{----- External Routine Declarations ------}

[EXTERNAL]
PROCEDURE _write_trc_c(
    p_trace_str : [IMMEDIATE] C_STR_T
); EXTERNAL;

{---------- Variable Declarations ---------}
VAR
    lv_trace_string     : [VOLATILE] STRING($m1 + 1); 
                          { volatile to get rid of compiler warning }
    lv_trace_str_ptr    : C_STR_T := NIL;

BEGIN

    { Calling a C routine, so zero terminated string required }

    lv_trace_string := p_trace_string + CHR(0);
    lv_trace_str_ptr := C_STR( lv_trace_string );

    { C-function checks if the trace is activated   }
    { If not, it immediately returns                }

    _write_trc_c( lv_trace_str_ptr );

END; { of PROCEDURE write_trc }

END.

Compilation

Compilation of the C source is done as follows:

$ define DECC$TEXT_LIBRARY SYS$LIBRARY:SYS$LIB_C.TLB
$ cc writetrc_c.c /debug /noopt

Logical DECC$TEXT_LIBRARY needs to be set to let the C compiler find vms_macros.h.
Compilation of the Pascal source is done as follows:

$ pas writetrc /debug /noopt

Note: for development and test, we standard compile with /debug /noopt

Linking

When linking a program that uses the functions above, you must include /SYSEXE. The link command will then look like:

$ link example writetrc.opt/opt /sysexe

Where the file writetrc.opt contains the following 2 lines:

writetrc_c.obj
writetrc.obj

Example program

Just to demonstrate, here is a small example program, that writes "Hello, World! 2015" to screen and in the trace buffer.

[
    INHERIT
    (
        'writetrc.pen'
    )
]
PROGRAM example( INPUT, OUTPUT );

VAR
    i : INTEGER;
    s : STRING(40);
BEGIN
    i := 2015;
    s := 'Hello, World!';
    writetrc( s + DEC(i) );
    WRITELN( s, ' ', i );
END.

Compile as usual and link as described earlier.

Running the example

Open a second terminal to start SDA with the correct privileges. You need at least privilege ALTPRI to start the trace, and another privilege to increase the buffer size. Since I don't know yet which one, I use priv=all for now.

$ set proc /priv=all
$ analyze/system
SDA> TR LOAD
SDA> TR START TRACE ! optionally with /BUFFER=<nr of pages>
SDA>

Go back to the other terminal and run the example:

$ run example
Hello, World! 2015
$

Go back to the system analyzer session.

SDA> TR STOP TRACE
SDA> TR SHOW TRACE

You should get a screen with a header, and one trace line with timestamp and text "Hello, World!  00002015"  (slightly different formatted than the output of the WRITELN statement).

Finally, exit the system analyzer with:

SDA> TR UNLOAD
SDA> EXIT

That's all, folks!