Profiling PHP applications with Tideways
Tideways is a commercial application performance monitoring (APM) and profiling service for PHP. It continuously records how long your requests take, where that time is spent, and which errors occur, down to individual function calls and database queries.
Tideways consists of three parts:
- A PHP extension that instruments your application and collects traces. This extension is already bundled with mittwald's PHP builds, so you only need to enable it.
- The Tideways daemon, a small background service that buffers the collected traces and forwards them to the Tideways backend. You run it yourself, as a container in your project.
- The Tideways backend at app.tideways.io, where you analyze the collected data.
This guide walks you through enabling the extension and running the daemon on the mittwald platform.
Prerequisites
To follow this guide, you will need:
- Access to a mittwald mStudio project with a PHP app (or PHP worker)
- A hosting plan that supports containerized workloads, because the Tideways daemon runs as a container
- A Tideways account and the API key of a Tideways project (you will find it in the Tideways UI under the project's settings)
- One of the following PHP versions, which are the first mittwald builds to ship the Tideways extension:
- PHP 8.3.33 or newer
- PHP 8.4.24 or newer
- PHP 8.5.9 or newer
You can look up the PHP version of your app in the mStudio UI, or from the CLI:
user@local $ mw app get <app-id>
If your app runs an older PHP version, update it before you continue:
user@local $ mw app dependency update <app-id> --set php=~8.4
See Managing and deploying PHP applications for more details on PHP version management.
Step 1: Running the Tideways daemon
The PHP extension does not talk to the Tideways backend directly. Instead, it sends its data to the Tideways daemon over a TCP connection, and the daemon takes care of buffering and transmitting it. This means you need exactly one daemon container per project, which all of your PHP apps can share.
We will run this daemon as a container in your project, using the ghcr.io/tideways/daemon:latest image from the GitHub Container Registry. The daemon listens on TCP port 9135 and needs no persistent storage, so no volumes are required.
Using the mStudio UI
In mStudio, go to your project, select "Containers" and click "Create container". A guided dialog will open to assist you with the container setup.
First, enter a description — this is a free text field used to identify the container. For example, enter "Tideways daemon" and click "Next".
Next, you'll be asked for the image name. Enter ghcr.io/tideways/daemon:latest and confirm with "Next".
Entrypoint and Command
- Entrypoint: No changes required
- Command: No changes required for a default setup. To control how the daemon registers itself in the Tideways UI, you can pass
--env=production --hostname=tideways-daemon(see Daemon options below).
Volumes
No volumes are required. The daemon only buffers data in memory and forwards it.
Environment Variables
No environment variables are required.
Once you're through the dialog, you'll be asked for the port. Enter 9135 so that the daemon becomes reachable for the other workloads in your project. Click "Create container" to create and start the container.
Alternative: Using the mw container run command
You can also create and start the daemon container directly from the command line:
user@local $ mw container run \
--name tideways-daemon \
--description "Tideways daemon" \
--publish 9135:9135 \
ghcr.io/tideways/daemon:latest
The --name flag determines the internal DNS name under which the daemon will be reachable from your apps.
To pass options to the daemon itself, add them after the image name. Because these options look like CLI flags, separate them from the mw flags with a --:
user@local $ mw container run \
--name tideways-daemon \
--description "Tideways daemon" \
--publish 9135:9135 \
-- ghcr.io/tideways/daemon:latest --env=production --hostname=tideways-daemon
Alternative: Using the mw stack deploy command
Alternatively, you can use the mw stack deploy command, which is compatible with Docker Compose. Create a docker-compose.yml file with the following content:
services:
tideways-daemon:
image: ghcr.io/tideways/daemon:latest
command: "--env=production --hostname=tideways-daemon"
ports:
- "9135:9135/tcp"
Then deploy it:
user@local $ mw stack deploy
This command will read the docker-compose.yml file from the current directory and deploy it to your default stack.
Daemon options
The daemon runs fine with its defaults, but two options are worth setting explicitly:
--env=<name>sets the environment name that traces are reported under, for exampleproductionorstaging. It defaults toproduction.--hostname=<name>sets the server name that the daemon registers itself with in the Tideways UI. Inside containers, the daemon appends the container ID to the detected hostname by default, which means that every recreation of the container shows up as a new server. Setting a fixed hostname avoids this.
The daemon configuration reference documents all available options.
Step 2: Configuring the PHP extension
The Tideways extension is part of your app's PHP installation, but it is disabled by default. To enable and configure it, add a new configuration file to your project's PHP configuration directory. Connect to your app via SSH:
user@local $ mw app ssh <app-id>
Then create the file ~/.config/php/php.d/20-tideways.ini with the following content:
extension=tideways.so
; The API key of your Tideways project
tideways.api_key=YOUR_API_KEY
; The internal DNS name and port of your daemon container
tideways.connection=tcp://tideways-daemon:9135
; Optional: Override the default service name for this app. Use this
; with caution, as some apps will automatically set the service name
; depending in which interface you're using (e.g. storefront/backend/API)
; tideways.service=...
; Percentage of requests that are recorded with the timeline profiler
tideways.trace_sample_rate=25
Replace YOUR_API_KEY with the API key of your Tideways project, and tideways-daemon with the internal DNS name of the container you created in step 1.
The settings have the following meaning:
tideways.api_key(required) authenticates your traces against your Tideways project.tideways.connection(required in this setup) points the extension at your daemon. Without it, the extension looks for a daemon on a local Unix socket, which does not exist on the mittwald platform.tideways.servicelets you override the default service name that Tideways assigns to your app. See the docs on Configuring Services for more information.tideways.trace_sample_ratecontrols the percentage of requests that are recorded with the full timeline profiler. It defaults to25. All other requests are still covered by performance and error monitoring.
See the Tideways configuration documentation for the complete list of settings.
Step 3: Verifying the setup
First, check that the extension is loaded. In an SSH session on your app, run:
user@ssh $ php -m | grep -i tideways
The output should contain tideways. To also confirm that your settings were picked up, run:
user@ssh $ php -i | grep tideways
The output of this command should contain a section like this:
Can connect to tideways-daemon? => Yes, version 1.15.2
Next, check that the daemon is running and accepting connections:
user@local $ mw container logs tideways-daemon
Finally, send some traffic to your application and open your project in app.tideways.io. The first data points usually appear within a few minutes.
Profiling on demand with the browser extension
Sampling covers your average traffic, but sometimes you want a full trace of one specific page load. The Tideways browser extension lets you trigger this from your browser: it sets a signed TIDEWAYS_SESSION cookie that instructs the PHP extension to record a complete timeline and callgraph trace for your own requests.
After installing it and logging in with your Tideways account, click the Tideways icon and select "Take Profile" to reload the current page with profiling enabled, or "Profile for 15 seconds" to capture a sequence of interactions such as a form submission or a series of AJAX calls.
Troubleshooting
No data appears in Tideways
- Verify that the daemon container is running and that port
9135is published. Without a published port, the container is not reachable from your app. - Check that
tideways.connectionuses the container's internal DNS name, not its display name or container ID. You can look up the DNS name in the mStudio UI or withmw container list. - Inspect the PHP error logs at
/var/log/php_errors.logand the daemon logs withmw container logs tideways-daemonfor connection or authentication errors. - Make sure the API key belongs to the Tideways project you are looking at.
The extension is not loaded
- Confirm that your app runs a PHP version that includes the extension (see Prerequisites).
- Make sure the configuration file is located in
~/.config/php/php.d/and has an.iniextension.extension=tideways.sohas no effect in a per-directory.user.inifile. - Check the PHP error log of your app at
/var/log/php_errors.logfor messages about a failed extension load.
Profiling a containerized PHP application
This guide covers managed PHP apps, where mittwald provides the PHP build including the Tideways extension. If you run PHP in your own container instead, you need to install the extension into your image yourself, following the Tideways Docker installation instructions. The daemon setup described in step 1 stays the same.