Article
· 16 hr ago 9m read

codemonitor.MonLBL - Line-by-Line ObjectScript Code Monitoring

Introduction

MonLBL is a tool for analyzing the performance of ObjectScript code execution line by line. codemonitor.MonLBL is a wrapper based on the %Monitor.System.LineByLine package from InterSystems IRIS, designed to collect precise metrics on the execution of routines, classes, or CSP pages.

The wrapper and all examples presented in this article are available in the following GitHub repository: iris-monlbl-example

Features

The utility allows the collection of several types of metrics:

  • RtnLine: Number of executions of the line
  • GloRef: Number of global references generated by the line
  • Time: Execution time of the line
  • TotalTime: Total execution time, including called subroutines

All metrics are exported to CSV files.

In addition to line-by-line metrics, dc.codemonitor.MonLBL collects global statistics:

  • Total execution time
  • Total number of executed lines
  • Total number of global references
  • System and user CPU time:
    • User CPU time corresponds to the time spent by the processor executing application code
    • System CPU time corresponds to the time spent by the processor executing operating system operations (system calls, memory management, I/O)
  • Disk read time

Prerequisites

To monitor code with MonLBL:
1. Obtain the dc.codemonitor.MonLBL class (available here)
2. The routines or classes to analyze must be compiled with the "ck" flags

⚠️ Important Warning

Using line-by-line monitoring impacts the server performance. It is important to follow these recommendations:

  • Use this tool only on a limited set of code and processes (ideally for one-off execution in a terminal)
  • Avoid using it on a production server (but sometime we need it)
  • Prefer using this tool in a development or test environment

These precautions are essential to avoid performance issues that could affect users or production systems. Note that monitored code runs approximately 15-20% slower than unmonitored code.

Usage

Basic Example

// Create an instance of MonLBL
Set mon = ##class(dc.codemonitor.MonLBL).%New()

// Define the routines to monitor
Set mon.routines = $ListBuild("User.MyClass.1")

// Start monitoring
Do mon.startMonitoring()

// Code to analyze...
// ...

// Stop monitoring and generate results
Do mon.stopMonitoring()

Note: Monitoring started here is only valid for the current process. Other processes executing the same code will not be included in the measurements.

Configuration Options

The wrapper offers several configurable options:

  • directory: Directory where CSV files will be exported (default is the IRIS Temp directory)
  • autoCompile: Automatically recompiles routines with the "ck" flags if necessary
  • metrics: Customizable list of metrics to collect
  • decimalPointIsComma: Uses a comma as the decimal separator for better compatibility with Excel (depending your local environment)
  • metricsEnabled: Enables or disables line-by-line metric collection

Advanced Usage Example

Here is a more complete example (available in the dc.codemonitor.Example class):

ClassMethod MonitorGenerateNumber(parameters As %DynamicObject) As %Status
{
    Set sc = $$$OK
    Try {
        // Display received parameters
        Write "* Parameters:", !
        Set formatter = ##class(%JSON.Formatter).%New()
        Do formatter.Format(parameters)
        Write !

        // Create and configure the monitor
        Set monitor = ##class(dc.codemonitor.MonLBL).%New()

        // WARNING: In production, set autoCompile to $$$NO
        // and manually compile the code to monitor
        Set monitor.autoCompile = $$$YES
        Set monitor.metricsEnabled = $$$YES
        Set monitor.directory = ##class(%File).NormalizeDirectory(##class(%SYS.System).TempDirectory())
        Set monitor.decimalPointIsComma = $$$YES

        // Configure the routine to monitor ("int" form of the class)
        // To find the exact routine name, use the command:
        // Do $SYSTEM.OBJ.Compile("dc.codemonitor.DoSomething","ck")
        // The line "Compiling routine XXX" will give you the routine name
        Set monitor.routines = $ListBuild("dc.codemonitor.DoSomething.1")

        // Start monitoring
        $$$TOE(sc, monitor.startMonitoring())

        // Execute the code to monitor with error handling
        Try {
            Do ##class(dc.codemonitor.DoSomething).GenerateNumber(parameters.Number)

            // Important: Always stop monitoring
            Do monitor.stopMonitoring()
        }
        Catch ex {
            // Stop monitoring even in case of error
            Do monitor.stopMonitoring()
            Throw ex
        }
    }
    Catch ex {
        Set sc = ex.AsStatus()
        Do $SYSTEM.Status.DisplayError(sc)
    }

    Return sc
}

This example demonstrates several important best practices:

  • Using a Try/Catch block for error handling
  • Systematically stopping monitoring, even in case of error
  • Complete monitor configuration

Example with CSP Pages

MonLBL also allows monitoring CSP pages. Here is an example (also available in the dc.codemonitor.ExampleCsp class):

ClassMethod MonitorCSP(parameters As %DynamicObject = {{}}) As %Status
{
    Set sc = $$$OK
    Try {
        // Display received parameters
        Write "* Parameters:", !
        Set formatter = ##class(%JSON.Formatter).%New()
        Do formatter.Format(parameters)
        Write !

        // Create and configure the monitor
        Set monitor = ##class(dc.codemonitor.MonLBL).%New()
        Set monitor.autoCompile = $$$YES
        Set monitor.metricsEnabled = $$$YES
        Set monitor.directory = ##class(%File).NormalizeDirectory(##class(%SYS.System).TempDirectory())
        Set monitor.decimalPointIsComma = $$$YES

        // To monitor a CSP page, use the generated routine
        // Example: /csp/user/menu.csp --> class: csp.menu --> routine: csp.menu.1
        Set monitor.routines = $ListBuild("csp.menu.1")

        // CSP pages require %session, %request, and %response objects
        // Create these objects with the necessary parameters
        Set %request = ##class(%CSP.Request).%New()
        // Configure request parameters if necessary
        // Set %request.Data("<param_name>", 1) = <value>
        Set %request.CgiEnvs("SERVER_NAME") = "localhost"
        Set %request.URL = "/csp/user/menu.csp"

        Set %session = ##class(%CSP.Session).%New(1234)
        // Configure session data if necessary
        // Set %session.Data("<data_name>", 1) = <value>

        Set %response = ##class(%CSP.Response).%New()

        // Start monitoring
        $$$TOE(sc, monitor.startMonitoring())

        Try {
            // To avoid displaying the CSP page content in the terminal,
            // use the IORedirect class to redirect output to null
            // (requires installation via zpm "install io-redirect")
            Do ##class(IORedirect.Redirect).ToNull() 

            // Call the CSP page via its OnPage method
            Do ##class(csp.menu).OnPage()

            // Restore standard output
            Do ##class(IORedirect.Redirect).RestoreIO()

            // Stop monitoring
            Do monitor.stopMonitoring()
        }
        Catch ex {
            // Always restore output and stop monitoring in case of error
            Do ##class(IORedirect.Redirect).RestoreIO()
            Do monitor.stopMonitoring()

            Throw ex
        }
    }
    Catch ex {
        Set sc = ex.AsStatus()
        Do $SYSTEM.Status.DisplayError(sc)
    }

    Return sc
}

Key points for monitoring CSP pages:

  1. Routine Identification: A CSP page is compiled into a class and a routine. For example, /csp/user/menu.csp generates the class csp.menu and the routine csp.menu.1.

  2. CSP Environment: It is necessary to create CSP context objects (%request, %session, %response) for the page to execute correctly.

  3. Output Redirection: To avoid displaying HTML content in the terminal, you can use the IORedirect utility (available on OpenExchange via zpm "install io-redirect").

  4. Page Call: Execution is done via the OnPage() method of the generated class.

Example Output

Here is an example of the output obtained when executing the MonitorGenerateNumber method:

USER>d ##class(dc.codemonitor.Example).MonitorGenerateNumber({"number":"100"})
* Parameters:
{
  "number":"100"
}

* Metrics are exported to /usr/irissys/mgr/Temp/dc.codemonitor.DoSomething.1.csv
* Perf results:
{
  "startDateTime":"2025-05-07 18:45:42",
  "systemCPUTime":0,
  "userCPUTime":0,
  "timing":0.000205,
  "lines":19,
  "gloRefs":14,
  "diskReadInMs":"0"
}

In this output, we can observe:

  1. Display of input parameters
  2. Confirmation that metrics were exported to a CSV file
  3. A summary of global performance in JSON format, including :
    • Start date and time
    • System and user CPU time
    • Total execution time
    • Number of executed lines
    • Number of global references
    • Disk read time

Interpreting CSV Results

After execution, CSV files (one per routine in the $ListBuild routines) are generated in the configured directory. These files contain:
- Line number
- Metrics collected for each line
- Source code of the line (note: if you did not compile with the "k" flag, the source code will not be available in the CSV file)

Here is an example of the content of an exported CSV file (dc.codemonitor.DoSomething.1.csv):

Line RtnLine GloRef Time TotalTime Code
1 0 0 0 0 ;dc.codemonitor.DoSomething.1
2 0 0 0 0 ;Generated for class dc.codemonitor.DoSomething...
3 0 0 0 0 ;;59595738;dc.codemonitor.DoSomething
4 0 0 0 0 ;
5 0 0 0 0 GenerateNumber(n=1000000) methodimpl {
6 1 0 0.000005 0.000005 For i=1:1:n {
7 100 0 0.000019 0.000019 Set number = $Random(100000)
8 100 0 0.000015 0.000015 Set isOdd = number # 2
9 100 0 0.000013 0.000013 }
10 1 0 0.000003 0.000003 Return }

In this table, we can analyze:

  • RtnLine: Indicates how many times each line was executed (here, lines 6 and 10 were executed once)
  • GloRef: Shows the global references generated by each line
  • Time: Presents the execution time specific to each line
  • TotalTime: Displays the total time, including calls to other routines

These data can be easily imported into a spreadsheet for in-depth analysis. The most resource-intensive lines in terms of time or data access can thus be easily identified.

Note on Cache Efficiency

The efficiency of the database cache (global buffer) can mask real performance issues. During analysis, data access may appear fast due to this cache but could be much slower under certain real-world usage conditions.

On development systems, you can clear the cache between measurements with the following command:

Do ClearBuffers^|"%SYS"|GLOBUFF()

⚠️ WARNING: Be cautious with this command, as it applies to the entire system. Never use it in a production environment, as it could impact the performance of all running applications.

Conclusion

Line-by-line monitoring is a valuable tool for analyzing ObjectScript code performance. By precisely identifying the lines of code that consume the most resources, it allows developers to save significant time in analyzing performance bottlenecks.

Discussion (0)1
Log in or sign up to continue