Perl Profiling API

By Stephen Kellett
6 August, 2011

For some time we have had tools for use with Perl in the beta stage. The reason the tools never left beta is because the instrumentation part of the tools was tool brittle. It relied up scanning the Perl binary for suitable insertion points then modifying the binary to insert a suitable profiling callback. The callback would then ask the interpreter for filename, line number and function name information.

This worked fine so long as the version of Perl being used had data stored in the same format as the version we had inspected when we wrote the tools. As soon as the internal design of the Perl runtime changed our tools stopped working.

The Perl interpreter has some rudimentary profiling capabilities built in. The are only suitable for gathering timing statistics and cannot be used to provide code coverage flow tracing or call graph timing results. The only was to get statistics for these uses is to build your own profiling API. That is what we have done with the Perl Profiling API.

The Perl Profiling API is inspired by similar functionality in the Python interpreter, the Ruby interpreter and the Mozilla JavaScript interpreter. The API consists of four functions:

  • void Perl_set_do_profiling(int enable);
  • int Perl_get_do_profiling();
  • void Perl_set_coverage_callback(PERL_PROFILER_CALLBACK callback, void *userData);
  • void Perl_set_profiling_callback(PERL_PROFILER_CALLBACK callback, void *userData);
PERL_PROFILER_CALLBACK

PERL_PROFILER_CALLBACK is defined as

void (*PERL_PROFILER_CALLBACK)(PERL_PROFILER_EVENT event,
                               const char          *fileName,
                               const int            lineNumber,
                               const char          *functionName,
                               const char          *packageName,
                               void                *userData);

The PERL_PROFILER_EVENT event can receive values of PPE_LINE, PPE_CALL, PPE_RETURN. PPE_LINE is used for line visits, PPE_CALL is used when a function is called, PPE_RETURN is used when a function returns. The fileName and lineNumber specify which part of which fileName the code is executing, the functionName specifies the current function and the packageName specifies which package/namespace the function is part of (if any). The userData value is the value passed when setting the callback using Perl_set_coverage_callback() and/or Perl_set_profiling_callback.

void Perl_set_do_profiling(int enable);

Perl_set_do_profiling() is used to enable or disable profiling with either of the callbacks. Pass TRUE to enable and FALSE to disable.

int Perl_get_do_profiling();

Perl_get_do_profiling() is used to determine if profiling is enabled or not. The function returns TRUE for enabled and FALSE for disabled.

void Perl_set_coverage_callback(PERL_PROFILER_CALLBACK callback, void *userData);

Perl_set_coverage_callback() is used to set the callback that will be called for line visit events. The callback function will be called for all lines the Perl program visits. The userData value specified will be passed to the callback.

void Perl_set_profiling_callback(PERL_PROFILER_CALLBACK callback, void *userData);

Perl_set_profiling_callback() is used to set the callback that will be called for function call and function return events. The callback function will be called for all functions the Perl program visits. The userData value specified will be passed to the callback.

Using the Perl Profiling API

By default the Perl Profiling API is not enabled and no callback values are set.

To use the Perl Profiling API we need to define a callback to do the work.

void callback(PERL_PROFILER_EVENT event,
              const char          *fileName,
              const int            lineNumber,
              const char          *functionName,
              const char          *packageName,
              void                *userData)
{
    switch(event)
    {
    case PPE_LINE:
        storeLineVisitInfo(fileName, lineNumber, userData);
        break;

    case PPE_CALL:
        startFunctionCallTiming(fileName, lineNumber, functionName, packageName, userData);
        break;

    case PPE_RETURN:
        endFunctionCallTiming(fileName, lineNumber, functionName, packageName, userData);
        break;
    }
}

The implementation of storeLineVisitInfo(), startFunctionCallTiming() and endFunctionCallTiming() are down to the profiler writer. You can use these to implement a timing profiler, code coverage, flow tracing or some other tool.

We also need to tell the Perl to use the Perl Profiling API.

	Perl_set_coverage_callback(callback, userData);
	Perl_set_profiling_callback(callback, userData);
	Perl_set_dp_profiling(TRUE);

Conclusion

As you can see using the Perl Profiling API is easy.

Adding the Perl Profiling API to the version of Perl you are using is also straightforward – there are two files that implement the API and two simple modifications to the runtime loop in dump.c and run.c. The overhead of the API is trivial when not in use and when in use the overhead is defined by what the callback writer chooses to do.

Learn more about the sources and binaries for the Perl Profiling API.

Fully functional, free for 30 days