Introducing Better Statistics and the Statistics API
Several weeks ago, I released the Better Statistics module. Its initial release was intended to simply keep track of cache hits and misses, allowing analysis of the effectiveness of this patch (and this related module) on increasing cache hit rate in Drupal's default cache implementation.
Yesterday, I released into the 7.x-1.x-dev branch a major feature: a Statistics API, allowing any module to declare fields of its own and track their values on each page request. Jump to see how it looks for administrators and site builders, or dive right into the API!
Core Statistics and the limitations of the accesslog
The core Statistics module provides two basic features:
- If an administrator so chooses, each time a node is displayed as a full page, a counter is incremented on the node, providing basic "popular content" functionality.
- If an administrator so chooses, for every page request, an entry is added to the accesslog table with information like the path of the requested page, referring URL, user ID, and a timer reading for how long it took PHP to render the request.
Both features are integrated with Views, and there are some basic status reports provided.
While the information gathered in the accesslog can be useful, it's an incredibly inflexible system. It's either on or it's off, and there's no way for you to hook into it to alter values, add data, etc. Views can be used to generate some basic dashboards and reports, but even then, you're still limited to the data that core provides.
What if you wanted to analyze performance, but segment by content type? Or how about if you wanted to analyze performance based on memory consumption instead of a basic timer? Perhaps you have interesting data coming from CRMs or real-time identification services you can use for deeper insight. Or maybe you want to set up and track goals that are more difficult to push into services like Google Analytics.
The Statistics API is a great match for those use-cases.
Better Statistics for Administrators and Site Builders
The module requires that Core Statistics be enabled and that "Enable accesslog" is checked (node statistics are unrelated). Once the module (currently just the dev version) is enabled and the requirements above are met, a new set of checkboxes appears on the Statistics configuration page:
In addition to the statistics fields provided by Core, you'll see that Better Statistics provides three custom fields out of the box. Administrators can simply check and uncheck desired fields; upon saving the configuration, data collection begins on the fields immediately.
All of the fields provided by Better Statistics also have built in Views integration, so site builders familiar with Views can easily build reports and dashboards with the new data now coming in to the accesslog. Simply create a view of type "Access log," and see the new fields that were enabled on the statistics configuration page:
Those looking to capitalize further might look into data visualization modules like Views Data Viz or Views Chart Tools. (Though, truth be told, it doesn't get much better than pointing Tableau at Drupal's accesslog.)
Better Statistics for Developers
If you're a developer and/or maintain a contrib module and want to take advantage of the Statistics API, it's extremely simple. Conceptually, all that's required is to define a field schema and provide a callback that returns the data for the field; Better Statistics takes care of the rest. Basic views integration is provided automatically based on the schema type you provide, but advanced Views handlers and relationships can optionally be provided.
More detailed notes are available in the API documentation, but I'll provide a basic example here from the module for illustrative purposes.
<span style="color: #000000"><span style="color: #0000BB"><?php<br></span><span style="color: #FF8000">/**<br> * Implements hook_better_statistics_fields().<br> */<br></span><span style="color: #007700">function </span><span style="color: #0000BB">better_statistics_better_statistics_fields</span><span style="color: #007700">() {<br> </span><span style="color: #0000BB">$fields </span><span style="color: #007700">= array();<br> </span><span style="color: #0000BB">$fields</span><span style="color: #007700">[</span><span style="color: #DD0000">'cache'</span><span style="color: #007700">] = array(<br> </span><span style="color: #DD0000">'schema' </span><span style="color: #007700">=> array(<br> </span><span style="color: #DD0000">'type' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'varchar'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'length' </span><span style="color: #007700">=> </span><span style="color: #0000BB">128</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'not null' </span><span style="color: #007700">=> </span><span style="color: #0000BB">FALSE</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'description' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'Cache hit, miss, or not applicable.'</span><span style="color: #007700">,<br> ),<br> </span><span style="color: #DD0000">'callback' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'better_statistics_get_field_value'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'views_field' </span><span style="color: #007700">=> array(<br> </span><span style="color: #DD0000">'title' </span><span style="color: #007700">=> </span><span style="color: #0000BB">t</span><span style="color: #007700">(</span><span style="color: #DD0000">'Cache status'</span><span style="color: #007700">, array(), array(</span><span style="color: #DD0000">'langcode' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'en'</span><span style="color: #007700">)),<br> </span><span style="color: #DD0000">'help' </span><span style="color: #007700">=> </span><span style="color: #0000BB">t</span><span style="color: #007700">(</span><span style="color: #DD0000">'Help text...'</span><span style="color: #007700">, array(), array(</span><span style="color: #DD0000">'langcode' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'en'</span><span style="color: #007700">)),<br> ),<br> );<br> return </span><span style="color: #0000BB">$fields</span><span style="color: #007700">;<br>}<p></p></span><span style="color: #FF8000">/**<br> * Callback for the Better Statistics API.<br> */<br></span><span style="color: #007700">function </span><span style="color: #0000BB">better_statistics_get_field_value</span><span style="color: #007700">(</span><span style="color: #0000BB">$field</span><span style="color: #007700">) {<br> switch (</span><span style="color: #0000BB">$field</span><span style="color: #007700">) {<br> case </span><span style="color: #DD0000">'cache'</span><span style="color: #007700">:<br> </span><span style="color: #0000BB">$cached </span><span style="color: #007700">= </span><span style="color: #0000BB">NULL</span><span style="color: #007700">;<br> </span><span style="color: #0000BB">$headers </span><span style="color: #007700">= </span><span style="color: #0000BB">headers_list</span><span style="color: #007700">();<br> foreach (</span><span style="color: #0000BB">$headers </span><span style="color: #007700">as </span><span style="color: #0000BB">$header</span><span style="color: #007700">) {<br> if (</span><span style="color: #0000BB">strpos</span><span style="color: #007700">(</span><span style="color: #0000BB">$header</span><span style="color: #007700">, </span><span style="color: #DD0000">'X-Drupal-Cache:'</span><span style="color: #007700">) !== </span><span style="color: #0000BB">FALSE</span><span style="color: #007700">) {<br> </span><span style="color: #0000BB">$cached </span><span style="color: #007700">= </span><span style="color: #0000BB">trim</span><span style="color: #007700">(</span><span style="color: #0000BB">substr</span><span style="color: #007700">(</span><span style="color: #0000BB">$header</span><span style="color: #007700">, </span><span style="color: #0000BB">15</span><span style="color: #007700">));<br> break;<br> }<br> }<br> return </span><span style="color: #0000BB">$cached<br> </span><span style="color: #007700">}<br>}<br></span><span style="color: #0000BB">?></span></span>
If you're a developer and have time, I could sure use a hand testing and reviewing this API and ironing out any kinks that come up before a general, stable release.
If you're interested, I'd also love to see someone start working on a backport of the API to the 6.x version of the module. Drush integration could be very useful too.
Most importantly, I'd love to see other contrib module maintainers start exposing their data to the API!