Tracing procedures

This article is a follow up of tracing. Two procedures are discussed that show how to trace entering and leaving procedures, such that the reader knows:

  1. When the procedure was entered

  2. When the procedure was finished

  3. How long this took

  4. How much memory was used or freed during its execution.

These latter two purposes are achieved by comparing the state at the beginning and end of the procedure.

Running example

The running example is based on a transportation problem: Pellets of bottled water are to be shipped from bottling locations to distribution centers. This example is extended with some logging and can be downloaded here

How to use

Suppose you have a procedure to do some work, for instance as follows:

1
2
3
4
5
Procedure pr_doSolve {
    Body: {
        solve mp_transport ;
    }
}

By adding a few lines, and declarations, the execution of the procedure is traced. The additions are marked in yellow below.

1
2
3
4
5
6
7
8
9
Procedure pr_doSolve {
    Body: {
        pr_enter(sp_enterTime,p_enterMemoryInUse);
        solve mp_transport ;
        pr_leave(sp_enterTime,p_enterMemoryInUse);
    }
    StringParameter sp_enterTime;
    Parameter p_enterMemoryInUse;
}

Remarks:

  1. Line 3: At the start of a procedure, trace the entering of that procedure. The arguments are used to store the part of the state (memory in use, moment of execution) in local parameters.

  2. Line 5: At the end of the procedure, trace the leaving of that procedure. In addition, trace how much memory and time has been used.

  3. Line 7,8: Declaration of local parameters.

Tip

As you know, lines of text can be copied. But the AIMMS Developer lets you also copy declarations; just right click on a declaration, keep the right mouse pressed, and move to the target location and select Copy Identifier(s).

How it is implemented

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Procedure pr_enter {
    Arguments: (sp_procEnterTimestamp,p_procEnterMemoryInUse,sp_procEnterContextMessage);
    Body: {
        sp_procEnterTimestamp := CurrentToString("%c%y-%m-%d %H:%M:%S:%t%TZ('UTC')");
        p_procEnterMemoryInUse := MemoryInUse();
        sp_node := formatString("%e",CallerNode(1));
        sp_fullMsg := formatString("Enter %s(%s) at %s [%.3n Mb] in use",
            sp_node, sp_procEnterContextMessage,
            MomentToString(
                Format        :  "%c%y-%m-%d %H:%M:%S:%t%TZ(ep_traceTimezone)",
                unit          :  [s],
                ReferenceDate :  sp_procEnterTimestamp,
                Elapsed       :  0[s]),
            p_procEnterMemoryInUse );
        pr_logMsg( sp_fullMsg );
    }
    StringParameter sp_procEnterTimestamp {
        Property: InOut;
    }
    Parameter p_procEnterMemoryInUse {
        Property: InOut;
    }
    StringParameter sp_procEnterContextMessage {
        Property: Optional;
        Comment: {
            "If the traced procedure contains arguments,
            you may want to summarize these arguments here."
         
    }
    StringParameter sp_node;
    StringParameter sp_fullMsg;
}

Remarks:

  1. Line 4: Timestamps are recorded in timezone UTC. This eases the measurement of duration, as the reference time argument in StringToMoment is assumed to be in timezone UTC, well, at least since AIMMS 4.76.

  2. Line 6: CallerNode(1) returns the procedure that called the currently running procedure.

  3. Line 9-13: Only when communicating the timestamps to the log file, the timezone of the timestamp is converted to the local timezone. See also the initialization of element parameter ep_traceTimezone

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Procedure pr_leave {
    Arguments: (sp_procEnterTimestamp,p_procEnterMemoryInUse,sp_msg);
    Body: {
        sp_leavingTime := CurrentToString("%c%y-%m-%d %H:%M:%S:%t%TZ('UTC')");
        p_duration := StringToMoment(
            Format        :  "%c%y-%m-%d %H:%M:%S:%t%TZ('UTC')",
            Unit          :  [s],
            ReferenceDate :  sp_procEnterTimestamp,
            Timeslot      :  sp_leavingTime);
        sp_node := formatString("%e",CallerNode(1));
        p_leaveMemoryInUse := MemoryInUse();
        sp_fullMsg :=
            formatString( "Leave %s(%s) at %s [%.3n Mb] in use. ",
                sp_node, sp_msg,
                MomentToString(
                    Format        :  "%c%y-%m-%d %H:%M:%S:%t%TZ(ep_traceTimezone)",
                    unit          :  [s],
                    ReferenceDate :  sp_leavingTime,
                    Elapsed       :  0[s]),
                p_leaveMemoryInUse ) +
            formatString( "Duration is %.3n [seconds] and memory %s is %n Mb.",
                p_duration,
                if p_leaveMemoryInUse >= p_procEnterMemoryInUse then "increase" else "decrease" endif,
                abs( p_leaveMemoryInUse - p_procEnterMemoryInUse ) );
        pr_logMsg( sp_fullMsg );
    }
    Parameter p_procEnterMemoryInUse {
        Property: Input;
    }
    StringParameter sp_procEnterTimestamp {
        Property: Input;
    }
    StringParameter sp_leavingTime;
    Parameter p_duration {
        Unit: s;
    }
    Parameter p_leaveMemoryInUse;
    StringParameter sp_msg {
        Property: Optional;
    }
    StringParameter sp_node;
    StringParameter sp_fullMsg;
}

Remarks:

  1. Line 5-9: Computing the duration. Note that UTC timestamps are compared.

  2. Line 21: The format string to trace how much time and memory was used to execute the procedure.

How to integrate

The code explained and demoed above is contained in a separate section named: Logging. See Export code to another project for copying the code to your project.