<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Joel Solon</title>
    <description>The latest articles on DEV Community by Joel Solon (@iscsolon).</description>
    <link>https://dev.to/iscsolon</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F576082%2Fbf550ce7-ead8-41f0-acb1-2458ccc67e33.jpeg</url>
      <title>DEV Community: Joel Solon</title>
      <link>https://dev.to/iscsolon</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/iscsolon"/>
    <language>en</language>
    <item>
      <title>Collecting Performance Data While Running Unit Tests</title>
      <dc:creator>Joel Solon</dc:creator>
      <pubDate>Tue, 09 Feb 2021 14:17:14 +0000</pubDate>
      <link>https://dev.to/intersystems/collecting-performance-data-while-running-unit-tests-pd6</link>
      <guid>https://dev.to/intersystems/collecting-performance-data-while-running-unit-tests-pd6</guid>
      <description>&lt;p&gt;A few years ago, I was teaching the basics of our %UnitTest framework during our &lt;a href="https://www.intersystems.com/support-learning/learning-services/classroom-learning/course/developing-with-intersystems-objects-and-sql/"&gt;Developing Using InterSystems Objects and SQL&lt;/a&gt; course. A student asked if it was possible to collect performance statistics while running unit tests. Here's some code that can be used with the &lt;code&gt;%UnitTest&lt;/code&gt; classes to answer this question.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;$zhorolog&lt;/code&gt; you can calculate the duration of the test. Beyond that, the &lt;code&gt;%SYSTEM.Process&lt;/code&gt; class provides several metrics that you can collect for a process.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lines Executed&lt;/li&gt;
&lt;li&gt;Global References&lt;/li&gt;
&lt;li&gt;System CPU Time&lt;/li&gt;
&lt;li&gt;User CPU Time&lt;/li&gt;
&lt;li&gt;Disk Read Time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To enable any unit test to collect these stats, create a subclass of &lt;code&gt;%UnitTest.TestCase&lt;/code&gt;, and add properties.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Class Performance.TestCase Extends %UnitTest.TestCase
{
Property Duration As %Time;
Property Lines As %Integer;
Property Globals As %Integer;
Property SystemCPUTime As %Integer;
Property UserCPUTime As %Integer;
Property DiskReadTime As %Integer;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any specific unit test class you create should inherit from your new subclass instead of &lt;code&gt;%UnitTest.TestCase&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the subclass, use &lt;code&gt;OnBeforeOneTest()&lt;/code&gt; to initialize the stats collection for each unit test. For everything except &lt;code&gt;DiskReadTime&lt;/code&gt;, the code initializes the property with the current value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/// initialize performance stats
Method OnBeforeOneTest(testname As %String) As %Status
{
    // initialize with current values
    set ..Duration = $zh
    set ..Lines = $system.Process.LinesExecuted()
    set ..Globals = $system.Process.GlobalReferences()
    set ..SystemCPUTime = $piece(CPUTime, ",", 1)
    set ..UserCPUTime = $piece(CPUTime, ",", 2)
    // reset disk read time to 0 and start counting
    do $system.Process.ResetDiskReadTiming()
    do $system.Process.EnableDiskReadTiming()
    return $$$OK
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;OnAfterOneTest()&lt;/code&gt; to finalize the stats collection for each unit test. For everything except &lt;code&gt;DiskReadTime&lt;/code&gt;, the code subtracts the initial value from the current value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/// Finalize performance stats 
/// This is where you could also add code to save the counters to a separate table for analysis.
Method OnAfterOneTest(testname As %String) As %Status
{
    set ..Duration = $zh - ..Duration
    set ..Lines = $system.Process.LinesExecuted() - ..Lines
    set ..Globals = $system.Process.GlobalReferences() - ..Globals
    set CPUTime = $system.Process.GetCPUTime()
    set ..SystemCPUTime = $piece(CPUTime, ",", 1) - ..SystemCPUTime
    set ..UserCPUTime = $piece(CPUTime, ",", 2) - ..UserCPUTime
    // get disk read time and stop counting
    set ..DiskReadTime = $system.Process.DiskReadMilliseconds()
    do $system.Process.DisableDiskReadTiming()
    // add message to unit test log
    set msg = "Performance: " _
              "Duration: " _           ..Duration _
              ", Lines: " _            ..Lines _
              ", Globals: " _          ..Globals _
              ", System CPU Time: " _ (..SystemCPUTime / 1000) _
              ", User CPU Time: " _   (..UserCPUTime / 1000) _
              ", Disk Read Time: " _  (..DiskReadTime / 1000)
    do $$$LogMessage(msg)
    return $$$OK
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There’s one more little trick. You may want to run your unit tests &lt;em&gt;with or without&lt;/em&gt; collecting statistics. So, the code where you are invoking your unit tests must take an argument (could be a %Boolean 1 or 0) and somehow pass that in. The methods that actually run the tests (such as &lt;code&gt;RunTest()&lt;/code&gt; or one of the other &lt;code&gt;Run*()&lt;/code&gt; methods) take an array as the 3rd argument, passed by reference (preceded by a &lt;code&gt;.&lt;/code&gt;). Here’s an example snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    // create an array to hold the logging argument (1 or 0) and pass it by reference
    set p("logging") = logging
    do ##class(%UnitTest.Manager).RunTest(test, qualifiers, .p)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The value you pass in the array can be accessed in &lt;code&gt;OnBeforeOneTest()&lt;/code&gt; and &lt;code&gt;OnAfterOneTest()&lt;/code&gt;. Add this as the first line in both methods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   if (..Manager.UserFields.GetAt("logging") = 0) { return $$$OK }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it! Looking forward to your questions, comments, and additional ideas.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
