Skip to content
Browse files

Initial import.

  • Loading branch information...
0 parents commit f2c57e187b18baee03d475ccf38d6a226420dfd1 @jsuchal committed Oct 2, 2012
Showing with 175 additions and 0 deletions.
  1. +18 −0 .gitignore
  2. +4 −0 Gemfile
  3. +22 −0 LICENSE
  4. +60 −0 README.md
  5. +2 −0 Rakefile
  6. +17 −0 garelic.gemspec
  7. +31 −0 lib/garelic.rb
  8. +18 −0 lib/garelic/dispatcher.rb
  9. +3 −0 lib/garelic/version.rb
18 .gitignore
@@ -0,0 +1,18 @@
+*.gem
+*.rbc
+.bundle
+.config
+.yardoc
+Gemfile.lock
+InstalledFiles
+_yardoc
+coverage
+doc/
+lib/bundler/man
+pkg
+rdoc
+spec/reports
+test/tmp
+test/version_tmp
+tmp
+.idea
4 Gemfile
@@ -0,0 +1,4 @@
+source 'https://rubygems.org'
+
+# Specify your gem's dependencies in garelic.gemspec
+gemspec
22 LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2012 Jan Suchal
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
60 README.md
@@ -0,0 +1,60 @@
+# Garelic: Google Analytics Reports as "New Relic"-like performance monitoring for your Rails app
+
+This is a proof of concept and will probably break things. Use it at your own risk.
+
+
+## Installation
+
+1. Add this line to your application's Gemfile:
+
+ gem 'garelic'
+
+2. Add Garelic::Timing instrumentation to your GA code in application layout template like this:
+
+ <script type="text/javascript">
+ var _gaq = _gaq || [];
+ _gaq.push(['_setAccount', 'UA-XXXXXXXX-X']);
+ _gaq.push(['_setSiteSpeedSampleRate', 100]);
+ _gaq.push(['_trackPageview']);
+
+ <%= Garelic::Timing %>
+
+ (function() {
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+ })();
+ </script>
+
+3. Go to Google Analytics > Content > Site speed > User Timings
+
+4. Enjoy!
+
+
+## Know advantages
+
+- it's free
+- shows slow performing pages (not only actions)
+- show response times histogram for any action (response time averages tend to lie, since distribution of response times is multimodal)
+- segment/slice/dice response data across any dimensions available in your GA account
+
+## Known drawbacks
+
+- you can only track actions that return a response body (redirects, ajax-requests & async jobs are not supported)
+- all timings are visible in page source code (if you are concerned about this look elsewere)
+- caching GA code (e.g. page caching) & not modified response will probably break/skew reported statistics
+- adding user timing table widgets to GA dashboards does not preserve sorting order (wtf?)
+- it's kind of a hack
+
+## TODO
+
+- add more fine-grained ActiveRecord instrumentation
+- add support for adding custom user tracers (e.g. for external services)
+
+## Contributing
+
+1. Fork it
+2. Create your feature branch (`git checkout -b my-new-feature`)
+3. Commit your changes (`git commit -am 'Added some feature'`)
+4. Push to the branch (`git push origin my-new-feature`)
+5. Create new Pull Request
2 Rakefile
@@ -0,0 +1,2 @@
+#!/usr/bin/env rake
+require "bundler/gem_tasks"
17 garelic.gemspec
@@ -0,0 +1,17 @@
+# -*- encoding: utf-8 -*-
+require File.expand_path('../lib/garelic/version', __FILE__)
+
+Gem::Specification.new do |gem|
+ gem.authors = ["Jan Suchal"]
+ gem.email = ["johno@jsmf.net"]
+ gem.description = %q{Google Analytics Reports as "New Relic"-like performance monitoring for your Rails app}
+ gem.summary = %q{}
+ gem.homepage = ""
+
+ gem.files = `git ls-files`.split($\)
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
+ gem.name = "garelic"
+ gem.require_paths = ["lib"]
+ gem.version = Garelic::VERSION
+end
31 lib/garelic.rb
@@ -0,0 +1,31 @@
+require "garelic/version"
+require "garelic/dispatcher"
+
+class Garelic
+ Timing = '<!-- /* GARELIC DATA */ -->'.html_safe
+
+ def self.build_user_timing_from_payload(payload)
+ action_identifier = "#{payload[:controller]}##{payload[:action]}"
+ [
+ track_timing('Garelic', 'Response (Total)', payload[:total_runtime], action_identifier),
+ track_timing('Garelic', 'Response (Views)', payload[:view_runtime], action_identifier),
+ track_timing('Garelic', 'Response (ActiveRecord)', payload[:db_runtime], action_identifier),
+ ].join("\n")
+ end
+
+ def self.track_timing(category, variable, time, opt_label = nil, opt_sample = nil)
+ parameters = ["'#{category}'", "'#{variable}'", time.to_i]
+ parameters << "'#{opt_label}'" if opt_label
+ parameters << opt_sample if opt_sample
+ "_gaq.push(['_trackTiming', #{parameters.join(', ')}]);"
+ end
+
+ class Railtie < Rails::Railtie
+ initializer 'garelic.install_instrumentation' do
+ ActiveSupport::Notifications.subscribe('process_action.action_controller') do |_, start, finish, _, payload|
+ payload[:total_runtime] = (finish - start) * 1000
+ Thread.current[:garelic_payload] = payload
+ end
+ end
+ end
+end
18 lib/garelic/dispatcher.rb
@@ -0,0 +1,18 @@
+module ActionController
+ class Metal < AbstractController::Base
+ alias :dispatch_without_garelic :dispatch
+
+ def dispatch(*args)
+ response = dispatch_without_garelic(*args)
+
+ timing_data = Garelic.build_user_timing_from_payload(Thread.current[:garelic_payload])
+
+ _, _, chunks = response
+ chunks.each do |chunk|
+ chunk.gsub!(Garelic::Timing, timing_data)
+ end
+
+ response
+ end
+ end
+end
3 lib/garelic/version.rb
@@ -0,0 +1,3 @@
+class Garelic
+ VERSION = "0.0.1"
+end

0 comments on commit f2c57e1

Please sign in to comment.
Something went wrong with that request. Please try again.