Z-Ray

The Z-Ray feature of ZendHQ provides application-level insight into requests to your application or API. Unlike monitoring, which is passive, Z-Ray requires that you enable tracking, and then make one or more requests that include a token. These requests are then captured in ZendHQ for you to inspect.

The primary use cases for Z-Ray include:

  • Identifying root causes of problems: Every Z-Ray request includes a full stack trace of function calls made in the application. Additionally, it includes timing and memory usage information for those calls, allowing you to drill down to those that are the most expensive.
  • Profiling your application: Z-Ray requests provide you with full timing and memory usage information, which you can use to pro-actively identify where you need to make adjustments to the application in order to improve performance.
  • Understanding application flow and more: Z-Ray is extensible, and a number of plugins are included by default. These plugins generally trigger under specific conditions, such as identifying that a specific PHP framework or application is running. Next, they collect more specific information, such as events or hooks that were triggered, what listeners were invoked, what database calls were made, and more. This information can help DevOps teams identify performance issues and/or work flows that lead to errors, as well as their root causes.

Using Z-Ray

To use Z-Ray, navigate to the Z-Ray > Live navigation heading. Z-Ray does not collect data by default, you must first select Start tracking. The Z-Ray session ends after a time period of one day, when you close the ZendHQ User Interface, or when you select Stop Tracking.

In order to track requests, they must include a Z-Ray session token. The token is available in a box immediately to the left of the tracking toggle button. You may either manually highlight and copy the token, use the button to copy to your system clipboard, or use the button that opens your application in a browser. In the case where you copy the token, you need to include the token in a request to your application via either:

  • a query string argument named "zraytok"
  • a cookie named "zraytok"

In a browser, this may be accomplished by appending ?zraytok={TOKEN} to any URL.

If you prefer to pass the token via the command line, you can include it via a query string argument. Examples with popular HTTP request tools include:

  • cURL: curl "https://example.org/some/page?zraktok={TOKEN}"
  • wget: wget "https://example.org/some/page?zraktok={TOKEN}"
  • HTTPie: https GET example.org/some/page zraytok=={TOKEN}

In a browser, you only need to include the token in the query string on the first request, as a cookie is set thereafter. For CLI tools, you need to include it in each request.

Viewing Z-Ray events

Once you have started tracking and made one or more requests with the Z-Ray token, events appear in the Z-Ray > Live window. Clicking on a request provides a details pane that includes the HTTP method used, the URL, and the response status. Additionally, it includes a number of tabs. By default, the following tabs are always present:

  • Info: this provides statistics on CPU and memory usage, as well as the host that generated the event.
  • Request: this provides information from the HTTP request, including headers and body content, if any.
  • Variable: this tab displays the values of the various PHP superglobals.
  • Response: this tab provides a list of all HTTP headers in the response, as well as the body content.
  • Functions: this tab details each function called, how many times it was called, inclusive time (time spent executing the function and all functions it called internally), exclusive time (amount of processing time exclusive to the function itself), and where it was defined (file and line number).

Depending on the Z-Ray plugins you have installed, you may see additional tabs with more application information.

Deleting Z-Ray events from the live log

If you wish to remove events from the live log (for example, you've started a new session, or you want to remove ones you've already examined), you can click the check box next to the request, and then select Delete selected at the top left of the live log.

Note that deleting events from the live log does not remove them from the historical log.

Historical Z-Ray events

You may run several different Z-Ray sessions, and want to compare results between them. For example, you may want to see if timing has changed from a previous request.

The Z-Ray History menu item contains the list of all captured Z-Ray events, and has the exact same views as the live log contains. You cannot delete events from this list.

Note: The ZendHQ daemon does clean these up periodically; this is controlled by the zendhqd.zray_db.history_time and zendhqd.zray_db.history_requests settings.

Extending Z-Ray

ZendHQ allows you to extend the capabilities of Z-Ray through a plugin system. Z-Ray plugins consist of:

  • a directory named after the plugin, containing:
  • the file zray.php

Internally, the zray.php file will create an instance of the class ZRayPlugin, and configure it to trace one or more functions. The directory is then placed in /opt/zend/zendphp/plugins/enabled. A restart of Apache (if using mod_php) or your PHP-FPM pool is necessary after adding a new plugin.

The ZRayPlugin class

The ZRayPlugin class API is as follows:

final class ZRayPlugin
{
	public function __construct(string $namespace, bool $enabled = false) {}
	public function traceFunction(string $funcName, ?callable $enterFunc, ?callable $leaveFunc): bool {}
	public function untraceFunction(string $funcName): bool {}
	public function hasTraceFunction(string $funcName): bool {}
	public function setEnabledAfter(string $funcName): void {}
	public function setEnabled(bool $enable = true): void {}
	public function isEnabled(): bool {}
}		

A typical plugin file generally instantiates the ZRayPlugin instance with the name of the plugin, and either enables it universally (via the $enabled flag to the constructor, or by calling setEnabled()) or via a setEnabledAfter() call. In order to do anything of value, it will need to also call traceFunction().

In all functions where a $funcName argument is defined, the value should be a string with the function name, or a method name in the form {Class Name}::{Method Name} (For example. Doctrine\ORM\EntityManager::create, Laminas\ServiceManager\ServiceManager::get). When calling setEnabledAfter(), the plugin does not execute unless the function specified is called. You can use this to ensure a plugin is specific to a given framework or application, and does not add overhead unless a Z-Ray session is active for a matching application.

Note: You cannot call setEnabledAfter() using the same function you provide to traceFunction(). If you do, the function provided to traceFunction()is not traced unless it is invoked again later in the request. However, you can call setEnabledAfter() multiple times, if there are multiple application events that should cause it to activate.

The callables $enterFunc and $leaveFunc should have the following signature:

function (array $context, array &$storage): void {}

The $context array is populated with the following keys:

  • string "functionName"
  • array "functionArgs"
  • object "this" (if the function is an instance method, this is the instance itself)
  • mixed "returnValue" (if the function returned successfully)
  • array "locals" (a key/value pair of local variables defined in the function, with the values as set by the end of function execution)
  • bool "exceptionThrown" (whether or not an exception is thrown)
  • object "exception" (the exception instance, if an exception is thrown)
  • int "timesCalled (the iteration of this particular call to the function)
  • int "durationInclusive" (in microseconds)
  • int "durationExclusive" (in microseconds)

The $storage argument is an associative array, and must be passed by reference. The value is carried over from "enter" callbacks to "leave" callbacks for the same function call, but not preserved between function calls. It is used to provide data to display in the ZendHQ Z-Ray request listings. The data passed must be an array of rows, while each row can consist of an array of data, an associative array, an associative multi-level array or object. For example:

$storage['myParams'][] = [
	'col1' => 'foo', 
	'col2' => 'bar', 
	'col3' => 'test'
];

$storage['myArray'][] = [
	'name'   => 'foo', 
	'values' => ['col2' => 'bar', 'col3' => 'test']
];

$objectOrClass = new ClassName();
$storage['myObject']['firstObject'] = $objectOrClass;		

If an array or associative array is passed, a grid table is created with columns as the names of the keys. You can sort this table lexicographically by default and you can free-text search it.

If a multi-level array or an object is passed, a tree table is created.

Selectively enabling plugins

The structure of the /opt/zend/zendphp/plugins directory is:

  • available/
  • enabled/

We recommend placing custom plugins in the available/ subdirectory, and then symlinking them into the enabled/ subdirectory. This allows you to quickly enable and disable plugins without needing to delete directories

Sample plugin

<?php
namespace ZRay;

class MyPlugin
{
	private $zrp = null;
	public function __construct($zrp)
	{
		$this->zrp = $zrp;
	}

	public function leave(array $context, array &$storage)
	{
		static $cnt = 0;
		$name = $context['functionArgs'][0];
		$storage['Params'][] = [
			'#'    => ++$cnt,
			'Name' => $name,
		[;
		$storage['Info'][] = [
			'#'                       => $cnt,
			'Duration inclusive (ms)' => $context['durationInclusive'] / 1000,
			'Duration exclusive (ms)' => $context['durationExclusive'] / 1000,
			];
	}
}

$zrp = new \ZRayPlugin("MyClass");
$my  = new MyPlugin($zrp);

$zrp->traceFunction('MyClass::hello', null, [$my, 'leave']);
$zrp->setEnabledAfter('MyClass::__construct');		

The above plugin triggers as calls to MyClass::hello() complete, but only after an instance of MyClass is constructed. When it executes, it generates a tab named Params with a table showing the count of calls to the method and the method name, and another tab named Info with a tabel showing the iteration and the amount of exclusive and inclusive time it took to execute the method.