Saturday, January 28, 2017

Profiling Apex, Part 2 (Display The Data)

LimitsProfiler is on GitHub

(Install Version 1.1)

So now that I have a mechanism to take snapshots of governor limit usage, I need a way to display it. This display mechanism should be easy to connect to any org, extend, and run multiple times. I'm starting out with a Visualforce Page for the front end interface. Perhaps down the road I will develop a Lightning UI, but that's definitely a bigger investment of my time.

Anyway, after thrashing about scrapping a few rough drafts, I eventually settled on an approach I'm pretty happy with. I want to be able to specify which limits to display, select a profiler type and number of iterations, and then measure it as many times as needed. From a technical and a convenience standpoint, it seems simplest to persist the data in a Hierarchy Custom Setting. While it's not strictly necessary, I added a small interface to manage this setting so you can switch back and forth between config and profiling in one consistent UI.

The configuration page isn't very interesting, so I won't cover it here (you can see it on GitHub). But the display page was a breeze to write, since all of the limits data points are already set up as properties on the LimitsSnapshot class. It's a bit tedious to write out, but mostly just copy pasta. The basic idea:

Controller
public Boolean isTypeDefined { get; private set; }
public List diffs { get; private set; }

@TestVisible final LimitsProfiler profiler;
public LimitsProfilerController()
{
    diffs = new List();
    summary = getProfilerType();
    try
    {
        profiler = (LimitsProfiler)Type.forName(getProfilerType()).newInstance();
        isTypeDefined = true;
    }
    catch (NullPointerException n) { isTypeDefined = false; }
    catch (TypeException t) { isTypeDefined = false; }
}
public PageReference configure() { return Page.LimitsProfilerConfig; }
public void measure()
{
    diffs.add(profiler.measure(getIterations()));
}
public String getProfilerType()
{
    return LimitsProfilerConfig__c.getInstance().ProfilerType__c;
}
public Integer getIterations()
{
    return (Integer)LimitsProfilerConfig__c.getInstance().Iterations__c;
}
Page
<apex:pageBlock>
    <apex:pageBlockButtons>
        <apex:form>
            <apex:commandButton value="Measure" action="{!measure}" rendered="{!isTypeDefined}" />
            <apex:commandButton value="Configure" action="{!configure}" />
        </apex:form>
    </apex:pageBlockButtons>
    <apex:pageBlockTable value="{!diffs}" var="snapshot" rendered="{!isTypeDefined}">
        <apex:column rendered="{!$Setup.LimitsProfilerConfig__c.DisplayAggregateQueries__c}"
                     headerValue="Aggregate Queries"
                     value="{!snapshot.aggregateQueries}" />
        <apex:column rendered="{!$Setup.LimitsProfilerConfig__c.DisplayAsyncCalls__c}"
                     headerValue="Async Calls"
                     value="{!snapshot.asyncCalls}" />
        <!--etc-->
    </apex:pageBlockTable>
</apex:pageBlock>

The code changes are small, but the impact they have on what can be measured is immense. When set up this way, each trial runs in its own transaction. It becomes possible to profile code that takes over ten seconds across trials without changing context. It also means you should be able to profile your triggers more consistently, as code like lazy loads will have to re-initialize each time it is profiled.

// when profiled multiple times from execute anonymous,
// this code will only consume a query on the first run
// but from Visualforce it will consume one on each run
public static List users
{
    get
    {
        if (users == null)
            users = [SELECT Id FROM User];
        return users;
    }
    private set;
}
Previous Post (Gather Usage Data)

No comments:

Post a Comment