NewRelic + HHVM: Automatic Segments

Posted by Jared Kipe on | 0 Comments

Tags: , ,

I've been messing around with building a HHVM extension using NewRelics AgentSDK that will allow for segmenting database and external calls. This is a brief summary of the things I've learned about overriding HHVM's core functionality to hook in NewRelic segments.

Extending HHVM Core Functionality

fb_rename_function(string $oldName, string $newName)

The easiest to understand method for hooking into the core of HHVM is by renaming function names at runtime. Basically, you start with a function that you want to track the performance of, probably a function that relies on external resources like curl, and replace it with your own implementation. Ideally, you provide a thin wrapper around the original function.

The big con here is that the runtime has this disabled by default and needs either CLI flags or other configuration variables set.

  • CLI: `-v Eval.JitEnableRenameFunction=true`
  • hdf: Eval { JitEnableRenameFunction = true }

Here is an example of how you could use this to wrap a core function:

function my_curl_exec( resource $ch ) {
    $time_start = microtime(true);
    $resp = @obs_curl_exec($ch);
    $time_end = microtime(true);
    $time = $time_end - $time_start;
    error_log('curl_exec: ' . $time);
    return $resp;
}

fb_rename_function('curl_exec', 'obs_curl_exec');
fb_rename_function('my_curl_exec', 'curl_exec');

The need to opt-in with runtime options is an obvious downside, but the security implications should not be underestimated if you cannot trust all extensions and code running on the server. A malicious user could certainly wrap library or any userspace functions and have access to the program flow and arguements. However, an attack like this could probably be more direct given the control over the system the attacker would need.

fb_intercept('Class::method', function $callback)

This interesting function allows you to intercept a Class method, but does not give you access to the classes private data or methods. It also does not wrap around the original classes method, so you cannot directly use it to time the execution of the the original method. Here is how my current PDO implementation works:

fb_intercept('PDOStatement::execute', function ($name, $obj, $args, $data, &$done) {
    $query = $obj->queryString;
    $a = _newrelic_parse_query($query);
        
    $obj->_newrelic_segment = newrelic_get_scoped_database_segment($a[1], $a[0]);
    $done=false;
});

If you set the $done arguement to true, the original method will never get called. In this example, newrelic_get_scoped_database_segment returns a sweepable object whose lifecycle depends on the current scope. To make this useful, I extend the life of that object by saving it on the current object as an instance variable. When this object is destroyed it will take my segment object with it, causing the timing of the code to come to completion.

Using fb_intercept() is much harder for timing the original methods runtime because you never have access to the scope of the original method. I couldn't find a way to capture the result of the original method and return it (with $done = true;) and had to rely on ... more creative ways to accomplish the same outcome.

I managed a similar result for mysqli (and SilverStripe) by noting that the mysqli class's implementation makes use of the mysqli::hh_real_query($query) method in HHVM for practically all querying.

Working Extension Code

I have my current working data up on GitHub. Big thankyou to Github user chregu for starting the extension and making it so easy to compile and work with.


Post your comment

Comments

No one has commented on this page yet.

RSS feed for comments on this page | RSS feed for all comments