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:
- Obtain the
dc.codemonitor.MonLBLclass (available here) - 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:
Routine Identification: A CSP page is compiled into a class and a routine. For example,
/csp/user/menu.cspgenerates the classcsp.menuand the routinecsp.menu.1.CSP Environment: It is necessary to create CSP context objects (%request, %session, %response) for the page to execute correctly.
Output Redirection: To avoid displaying HTML content in the terminal, you can use the IORedirect utility (available on OpenExchange via
zpm "install io-redirect").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:
- Display of input parameters
- Confirmation that metrics were exported to a CSV file
- 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.
Comments
Enlightening article.