We’re pleased to announce that the New Relic PHP agent now reports information for the Guzzle HTTP client library. In this blog post, we’ll walk you through a common experience you might encounter with Guzzle, explore its underlying mechanisms, and end with some thoughts about calculating and visualizing asynchronous behavior.
What is Guzzle?
Guzzle is a popular HTTP client library for PHP. Released in 2011, it has seen widespread adoption by developers (and is now the default client for Drupal 8). We like it because it’s simple to use, allows both synchronous and asynchronous behavior, and abstracts away specific request transport details.
Here’s a hypothetical scenario: Your application uses Guzzle to make several external Web requests during a particular process. Perhaps you make an API call, do some work with the response, make another call, etc. Naturally, you use New Relic to monitor your application, so here’s what it looks like on the APM Overviews page:
The overall response time, which is the most important metric we report, is shown as a dark blue line. Note the time spent in PHP as well as the time spent making all those external Web requests.
As a savvy developer, you know that Guzzle has the ability to make requests concurrently (more on this later). Perhaps consolidating all the external requests to one place at the start of the script would improve response time. Here’s a simple example of how to do this in Guzzle 6 with an array of promises:
$client = new \GuzzleHttp\Client;
$promises = [
So you make the changes and check out your application in APM:
Although the time spent making external Web requests is unchanged, response time has been halved. Because the requests are no longer sequential, the response time is essentially limited only by the longest call we made. All hail the power of concurrency!
Concurrency versus parallelism
Guzzle’s behavior isn’t truly asynchronous because PHP is single-threaded—it cannot do other work while the external calls are made. However, while PHP cannot provide parallelism, it can offer concurrency. Rob Pike gave an excellent talk about the difference between the two, defining concurrency as the composition of independently executing processes. He defines parallelism, on the other hand, as the simultaneous execution of (possibly related) computations. Concurrency is how PHP applications can benefit from asynchronous requests to reduce their overall response time.
This is all managed for your application by Guzzle, which uses file descriptors to delegate the work to the operating system. For each external request, the Guzzle client gives the system a URL and instructs it to do as much work as it can before it has to wait for network activity. When that happens, the system will return to Guzzle and give an update on its progress. The client monitors these connections and does work when it can, switching to another connection if it cannot do anything but wait. It returns the responses—and ownership of the process—to the main application after the last call has completed.
Guzzle handles the complicated task-switching for the application and attempts to minimize time spent waiting. The requests still must be written and read sequentially, so we save time only while we are waiting on the network. However, network activity is a substantial proportion of the work, so while the concurrent model isn’t quite as efficient as the parallel model, it’s still a dramatic improvement over the sequential model.
Note the difference in time elapsed in the figure above. While the concurrent model is certainly faster than making three requests sequentially (9 seconds versus 18 seconds), it loses a little time because the client can write or read in only one connection at a time.
Asynchronous time in the New Relic UI
The New Relic UI represents a transaction’s total time differently depending on which asynchronous model a language is using. For example, in a threaded language like Java that spins off other threads to make external calls, the main thread can keep working while it waits. Therefore the total time we show in the UI includes the work of the main thread as well as the other threads. In the parallel example above, we would calculate total time as 16 seconds: 8 from the main thread and 8 from the three threads shown. In the concurrent example, however, the main process cannot do other actions until all three responses have been read so the total time remains 9 seconds.
When we designed the New Relic UI for asynchronous behavior, we carefully balanced two things: consistency across the New Relic language agents and meaningful reporting for different types of asynchronous behavior. We want you to be able to easily switch between languages, but we don’t make overly broad assumptions that don’t hold for a particular language. Let us know how you think we’ve done at support.newrelic.com.
Background image courtesy of Shutterstock.com.