installing XHGui via Ansible
I’m still using Ansible to provision Vagrant VMs. This is how I added the XHGui profiler to my standard setup.
Theres a number steps we need to do:
- Install Composer
- Install the uprofiler PHP extension
- Install XHGui
- Set up for profiling
- Set up host for XHGui website
Install Composer
Installing Composer requires these tasks:
- name: Install Composer shell: curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin creates=/usr/local/bin/composer - name: Rename composer.phar to composer shell: mv /usr/local/bin/composer.phar /usr/local/bin/composer creates=/usr/local/bin/composer - name: Make composer executable file: path=/usr/local/bin/composer mode=a+x state=file - name: Create global composer directory file: path=/usr/local/composer state=directory mode=0775
Firstly we download the Composer installer and run it to create composer.phar. We then rename to composer, make executable and then create a global directory for storing the packages that we download.
Install the uprofiler PHP extension
We install uprofiler via composer:
- name: Install uprofiler shell: export COMPOSER_HOME=/usr/local/composer && composer global require 'friendsofphp/uprofiler=dev-master' creates=/usr/local/composer/vendor/friendsofphp/uprofiler/composer.json - name: Compile uprofiler shell: cd /usr/local/composer/vendor/friendsofphp/uprofiler/extension && phpize && ./configure && make && make install creates=/usr/lib/php5/20121212/uprofiler.so - name: Configure PHP (cli) copy: src=uprofiler.ini dest=/etc/php5/cli/conf.d/21-uprofiler.ini mode=644 - name: Configure PHP (apache2) copy: src=uprofiler.ini dest=/etc/php5/apache2/conf.d/21-uprofiler.ini mode=644
The last two tasks copy uprofiler.ini to the relevant configuration directories. uprofiler.ini file is really simple:
[uprofiler] extension=uprofiler.so
Install XHGui
Similarly, we install XHGui using composer:
- name: Install MongoDB
apt: pkg={{ item }} state=latest
with_items:
- mongodb
- php5-mongo
- name: Install XHGui
shell: export COMPOSER_HOME=/usr/local/composer && composer global require --ignore-platform-reqs 'perftools/xhgui=dev-master' creates=/usr/local/composer/vendor/perftools/xhgui/composer.json
- name: Set XHGui permisssions
file: path=/usr/local/composer/vendor/perftools/xhgui/cache group=www-data mode=775
- name: Configure XHGui
template: src=xhgui_config.php dest=/usr/local/composer/vendor/perftools/xhgui/config/config.php owner=vagrant group=www-data mode=644
- name: Index mongo for XHGui
script: xhgui_indexes.sh --some-arguments 1234 creates=/root/indexed_xhgui.txt
XHGi uses MongoDB for storage, so we install that install that first and then install XHGui via composer which pulls in all the dependencies. Note that XHGui has a extension dependency on xhprof, but we’re using uprofiler, so we use the --ignore-platform-reqs flag to ignore.
XHGui requires a configuration file in it’s config directory. I copied the default one and then changed it to profile every run. The minimum xhgui_config.php that you need is:
<?php
return [
// Profile every request
'profiler.enable' => function() {
return true;
},
]
This is the place where you could put in additional checks to decide whether to profile or not, such as checking for a GET variable of “profile”, for instance.
Lastly, the XHGui README recommends that you add some indexes to MongoDB. I also wanted to automatically delete old records, which is also done via a MongoDB directive. This is done via the xhgui_indexes.sh shell script:
#!/bin/bash
# auto-remove records older than 2592000 seconds (30 days)
mongo xhprof --eval 'db.collection.ensureIndex( { "meta.request_ts" : 1 }, { expireAfterSeconds : 2592000 } )'
# indexes
mongo xhprof --eval "db.collection.ensureIndex( { 'meta.SERVER.REQUEST_TIME' : -1 } )"
mongo xhprof --eval "db.collection.ensureIndex( { 'profile.main().wt' : -1 } )"
mongo xhprof --eval "db.collection.ensureIndex( { 'profile.main().mu' : -1 } )"
mongo xhprof --eval "db.collection.ensureIndex( { 'profile.main().cpu' : -1 } )"
mongo xhprof --eval "db.collection.ensureIndex( { 'meta.url' : 1 } )"
touch /root/indexed_xhgui.txt
Note that we create an empty file that is tested in the task as we only need to run this task once.
Set up for profiling
To profile a website, we just need to include /usr/local/composer/vendor/perftools/xhgui/external/header.php. This can be done by setting the auto_prepend_file PHP ini setting. As I use Apache, I can just add:
php_admin_value auto_prepend_file "/usr/local/composer/vendor/perftools/xhgui/external/header.php"
To my VirtualHost configuration.
Set up host for XHGui website
Finally, we need a VirtualHost for the XHGui website where we can view our profiles. I decided to use a separate subdomain, “profile“, so my vhost looks like this:
<VirtualHost *:80>
ServerName profiler.{{ server_name }}
DocumentRoot /usr/local/composer/vendor/perftools/xhgui/webroot
<Directory /usr/local/composer/vendor/perftools/xhgui/webroot>
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Order allow,deny
Allow from all
Require all granted
</Directory>
</VirtualHost>
Where {{server_name}} is an Ansible variable that is the domain name of the site.
All done
That’s it. Once I had worked out which pieces were required, putting them into Ansible tasks was remarkably obvious and now I can profile my website in development.



Why didn't you use Ansible's builtin composer module? http://docs.ansible.com/composer_module.html
Mxx,
My understanding of the Ansible composer module is that it is designed for installation of dependencies of a project that has a composer.json file and isn't built around global installation of dev tools which is how I'm using it.
I could of course be wrong, but as I only call composer once in order to globally install uprofiler, it didn't seem worth spending my time investigating.