Skip to content
This repository has been archived by the owner on Nov 3, 2024. It is now read-only.

Added xhgui service. #128

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ General information on how to do additional services and some additional example
* [redis](docker-compose-services/redis)
* [redis-commander](docker-compose-services/redis-commander)
* [Varnish](docker-compose-services/varnish)
* [XHGui](docker-compose-services/xhgui)

## .ddev/web-build/Dockerfile examples to customize web container

Expand Down
96 changes: 96 additions & 0 deletions docker-compose-services/xhgui/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# XHGui

This container provides a [XHGui](https://github.com/perftools/xhgui) container for your project so you can collect performance information
provided by `xhprof`.

![A screenshoot of XHGui](assets/xhgui-screenshot.png)


## Warning

This recipe has a dev environment on mind.
Profiling production environments with this recipe is probably not a good idea.

## Installation

### Your DDEV config
You need the `xhprof` php module. The easiest way is adding
```
webimage_extra_packages: [php7.4-xhprof]
```
to your `.ddev/config.yaml` file.

### The containers

* Copy the `docker-compose.xhgui.yml` file to your `.ddev` folder.
* Copy the `xhgui` folder to your `.ddev` folder.
* Copy the `xhgui-mongodb` folder to your `.ddev` folder.
* (Optional) Copy the `commands/xhprof` file to your `.ddev/web/commands` folder, so you can easily start/stop `xhprof`. This probably won't be needed with next ddev release, where you can just do `ddev xhprof on` as you are already used to with xdebug.

### Your application

Your application needs to have a profiler set up.

If your application uses composer, you can install it with

```
ddev composer require perftools/php-profiler (maybe you want to use --dev)
```

For other usecases, see the WordPress section as an example.

You need to place some code for initializing the profiling as soon as possible in the
bootstrap of your application.

In the `examples` folder you will find the collector initialization
and the config for that collector.

#### Drupal 8+ based projects

An easy way of doing this in Drupal, is copying those two files in the `examples` folder to your
`sites/default` folder, and append to your `settings.ddev.php`.
```
require_once __DIR__ . '/xhgui.collector.php';
```

If you want to stop profiling, you can just comment/remove that line.
Take into account that with the default configuration, every time you
`ddev start`, DDEV will recreate this file. You can remove the #ddev-generated at the top of the file if you want to avoid that.

#### WordPress projects

Download latest version of `perftools/php-profiler` (this has been validated with the current latest release, 0.18.0).
If you use [bedrock](https://roots.io/bedrock/), just use the composer command from the previous section.

If you use vanilla WordPress:

```
wget https://github.com/perftools/php-profiler/archive/refs/tags/0.18.0.tar.gz
tar -xvf 0.18.0.tar.gz
```

Copy those two files in the `examples` folder of **this** repo (not the `php-profiler` you just downloaded) to your
WordPress folder, and append to your `wp-config-ddev.php`:

```
require_once __DIR__ . '/php-profiler-0.18.0/autoload.php';
require_once __DIR__ . '/xhgui.collector.php';
```

If you want to stop profiling, you can just comment/remove those lines.
Take into account that with the default configuration, every time you
`ddev start`, DDEV will recreate this file. You can remove the #ddev-generated at the top of the file if you want to avoid that.

### Service initialization

Start (or restart) DDEV to have the service initialized when you are ready: `ddev start`
Remember, `settings.ddev.php` or `wp-config-ddev.php` might be rewritten and you need to do changes there.

### Accessing the service

By default, xhgui will be available at http://`<your site>`:8282. Note that it's http only.


**Contributed by [@penyaskito](https://github.com/penyaskito)**

**Help and feedback from** [@randyfay](https://github.com/randyfay), [@e0ipso](https://github.com/e0ipso), [@andypost](https://github.com/andypost)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 43 additions & 0 deletions docker-compose-services/xhgui/commands/xhprof
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/bin/bash

## Description: Enable or disable xhprof
## Usage: xhprof on|off|enable|disable|true|false|status
## Example: "ddev xhprof" (default is "on"), "ddev xhprof off", "ddev xhprof on", "ddev xhprof status"

enable_xhprof() {
phpenmod -v $DDEV_PHP_VERSION xhprof;
killall -HUP php-fpm 2>/dev/null || true;
echo "Enabled xhprof";
}

disable_xhprof() {
phpdismod -v $DDEV_PHP_VERSION xhprof;
killall -HUP php-fpm 2>/dev/null || true;
echo "Disabled xhprof";
}

if [ $# -eq 0 ]; then
enable_xhprof
exit
fi

case $1 in
on | true | enable)
enable_xhprof
;;
off | false | disable)
disable_xhprof
;;
status)
status=$(php -m | grep 'xhprof')
if [ "${status}" = "xhprof" ]; then
result="xhprof is enabled"
else
result="xhprof is disabled"
fi
echo $result
;;
*)
echo "Invalid argument: $1"
;;
esac
41 changes: 41 additions & 0 deletions docker-compose-services/xhgui/docker-compose.xhgui.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
version: '3.7'

services:
xhgui:
container_name: ddev-${DDEV_SITENAME}-xhgui
hostname: ${DDEV_SITENAME}-xhgui
image: xhgui/xhgui
ports:
- 8282:80
links:
- xhgui-mongo
environment:
- XHGUI_MONGO_HOSTNAME=xhgui-mongo
- XHGUI_MONGO_DATABASE=xhprof
labels:
com.ddev.site-name: ${DDEV_SITENAME}
com.ddev.approot: $DDEV_APPROOT
volumes:
- ./xhgui/nginx.default.conf:/etc/nginx/conf.d/default.conf
- ./xhgui/xhgui.config.php:/var/www/xhgui/config/config.php
xhgui-mongo:
container_name: ddev-${DDEV_SITENAME}-xhgui-mongo
# xhgui doesn't work with mongodb >= 3.6
image: percona/percona-server-mongodb:3.4
# (case sensitive) engine: mmapv1, rocksdb, wiredTiger, inMemory
command: --storageEngine=wiredTiger
environment:
- MONGO_INITDB_DATABASE=xhprof
volumes:
- ./xhgui-mongo/mongo.init.d:/docker-entrypoint-initdb.d
- xhgui-mongo:/data/db
ports:
- "27017:27017"
web:
links:
- xhgui
depends_on:
- xhgui
volumes:
xhgui-source:
xhgui-mongo:
45 changes: 45 additions & 0 deletions docker-compose-services/xhgui/examples/xhgui.collector.config.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php
/**
* Default configuration for Xhgui
*/

use Xhgui\Profiler\Profiler;

return [
'debug' => true,
'mode' => 'development',

'save.handler' => Profiler::SAVER_STACK,
'save.handler.stack' => [
'savers' => [
Profiler::SAVER_UPLOAD,
Profiler::SAVER_FILE,
],
// if saveAll=false, break the chain on successful save
'saveAll' => false,
],
'save.handler.file' => [
// Appends jsonlines formatted data to this path
'filename' => '/tmp/xhgui.data.jsonl',
],
'save.handler.upload' => [
'url' => 'http://xhgui/run/import',
// The timeout option is in seconds and defaults to 3 if unspecified.
'timeout' => 3,
// the token must match 'upload.token' config in XHGui
'token' => getenv('XHGUI_UPLOAD_TOKEN', 'token'),
],
// Profile all requests.
'profiler.enable' => function() {
return true;
},

'profiler.simple_url' => function($url) {
return preg_replace('/\=\d+/', '', $url);
},

'profiler.simple_url' => function($url) {
return str_replace(getenv('DDEV_HOSTNAME'), '', $url);
}

];
19 changes: 19 additions & 0 deletions docker-compose-services/xhgui/examples/xhgui.collector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

use Xhgui\Profiler\Profiler;

// Add this block inside some bootstrapper or other "early central point in execution"
try {
$config = require_once __DIR__ . '/xhgui.collector.config.php';
/**
* The constructor will throw an exception if the environment
* isn't fit for profiling (extensions missing, other problems)
*/
$profiler = new Profiler($config);

// The profiler itself checks whether it should be enabled
// for request (executes lambda function from config)
$profiler->start();
} catch (Exception $e) {
// throw away or log error about profiling instantiation failure
}
5 changes: 5 additions & 0 deletions docker-compose-services/xhgui/xhgui-mongo/mongo.init.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
db.results.ensureIndex( { 'meta.SERVER.REQUEST_TIME' : -1 } );
db.results.ensureIndex( { 'profile.main().wt' : -1 } );
db.results.ensureIndex( { 'profile.main().mu' : -1 } );
db.results.ensureIndex( { 'profile.main().cpu' : -1 } );
db.results.ensureIndex( { 'meta.url' : 1 } );
20 changes: 20 additions & 0 deletions docker-compose-services/xhgui/xhgui/nginx.default.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name xhgui;
# root directive should be global
root /var/www/xhgui/webroot/;
index index.php;

location / {
try_files $uri $uri/ /index.php?$args;
}

location ~ \.php$ {
try_files $uri =404;
include /etc/nginx/fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
63 changes: 63 additions & 0 deletions docker-compose-services/xhgui/xhgui/xhgui.config.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php
/**
* Default configuration for Xhgui
*/
return [
'mode' => 'production',

// Can be either mongodb or file.
/*
'save.handler' => 'file',
'save.handler.filename' => dirname(__DIR__) . '/cache/' . 'xhgui.data.' . microtime(true) . '_' . substr(md5($url), 0, 6),
*/
'save.handler' => 'mongodb',

// Needed for file save handler. Beware of file locking. You can adjust this file path
// to reduce locking problems (eg uniqid, time ...)
//'save.handler.filename' => __DIR__.'/../data/xhgui_'.date('Ymd').'.dat',
// Database options for MongoDB.
'mongodb' => [
// 'hostname' and 'port' are used to build DSN for MongoClient
'hostname' => getenv('XHGUI_MONGO_HOSTNAME') ?: 'xhgui-mongo',
'port' => getenv('XHGUI_MONGO_PORT') ?: 27017,
// The database name
'database' => getenv('XHGUI_MONGO_DATABASE') ?: 'xhprof',
// Additional options for the MongoClient constructor,
// for example 'username', 'password', or 'replicaSet'.
// See <https://www.php.net/mongoclient_construct#options>.
'options' => [],
// An array of options for the MongoDB driver.
// Options include setting connection context options for SSL or logging callbacks.
// See <https://www.php.net/mongoclient_construct#options>.
'driverOptions' => [],
],

'run.view.filter.names' => [
'Zend*',
'Composer*',
],
// If defined, add imports via upload (/run/import) must pass token parameter with this value
'upload.token' => getenv('XHGUI_UPLOAD_TOKEN') ?: '',

// Add this path prefix to all links and resources
// If this is not defined, auto-detection will try to find it itself
'path.prefix' => null,

// Setup timezone for date formatting
// Example: 'UTC', 'Europe/Tallinn'
// If left empty, php default will be used (php.ini or compiled in default)
'timezone' => '',

// Date format used when browsing XHGui pages.
//
// Must be a format supported by the PHP date() function.
// See <https://secure.php.net/date>.
'date.format' => 'M jS H:i:s',

// The number of items to show in "Top lists" with functions
// using the most time or memory resources, on XHGui Run pages.
'detail.count' => 6,

// The number of items to show per page, on XHGui list pages.
'page.limit' => 25,
];