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.