Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental - DO NOT MERGE! Auto-instrumentation of Controller Time #198

Closed
wants to merge 31 commits into from

Conversation

itsderek23
Copy link
Contributor

@itsderek23 itsderek23 commented Jun 1, 2018

Install instructions:

  • add rubocop to the Gemfile
  • add require 'scout_apm/auto_instrument' to config/boot.rb

@itsderek23
Copy link
Contributor Author

No rush, but I tried adding this to an app, but I'm not seeing any auto-instrumentation.

Relevant diffs below:

diff --git a/Gemfile b/Gemfile
index f467c15..15bf591 100644
--- a/Gemfile
+++ b/Gemfile
@@ -31,9 +31,13 @@
-gem 'scout_apm', git: 'https://github.com/scoutapp/scout_apm_ruby.git', branch: 'scoutprof_statsd_context'
+
+gem 'rubocop' # for auto instrument
+gem 'scout_apm', git: 'https://github.com/scoutapp/scout_apm_ruby.git', branch: 'auto_instruments'
+# gem 'scout_apm', git: 'https://github.com/scoutapp/scout_apm_ruby.git', branch: 'scoutprof_statsd_context'
diff --git a/Gemfile.lock b/Gemfile.lock
index 353ce16..a4f3768 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,9 +1,9 @@
 GIT
   remote: https://github.com/scoutapp/scout_apm_ruby.git
-  revision: 634484b7673baf1083b8b3076118c100ab2e022e
-  branch: scoutprof_statsd_context
+  revision: 3f469a854bf57d23fd778e9860628eceabd64196
+  branch: auto_instruments
   specs:
-    scout_apm (3.0.0.pre23)
+    scout_apm (2.4.11)
 
 GEM
   remote: https://rubygems.org/
@@ -51,6 +51,7 @@ GEM
     airbrussh (1.1.0)
       sshkit (>= 1.6.1, != 1.7.0)
     arel (6.0.3)
+    ast (2.4.0)
     autoprefixer-rails (6.5.0.1)
       execjs
     aws-sdk (2.0.28)
@@ -268,6 +269,9 @@ GEM
     oj (3.1.3)
     orm_adapter (0.5.0)
     os (0.9.6)
+    parallel (1.12.1)
+    parser (2.5.1.0)
+      ast (~> 2.4.0)
     pg (0.18.3)
     pg-eyeballs (1.1.0)
       activerecord (>= 4.0, < 6.0)
@@ -280,6 +284,7 @@ GEM
       capybara (~> 2.1)
       cliver (~> 0.3.1)
       websocket-driver (>= 0.2.0)
+    powerpack (0.1.1)
     premailer (1.8.7)
       css_parser (>= 1.4.5)
       htmlentities (>= 4.0.0)
@@ -334,6 +339,7 @@ GEM
       activesupport (= 4.2.7.1)
       rake (>= 0.8.7)
       thor (>= 0.18.1, < 2.0)
+    rainbow (3.0.0)
     raindrops (0.17.0)
     rake (11.3.0)
     rbnacl (3.4.0)
@@ -373,6 +379,14 @@ GEM
     rollbar (2.15.5)
       multi_json
     rouge (1.10.1)
+    rubocop (0.56.0)
+      parallel (~> 1.10)
+      parser (>= 2.5)
+      powerpack (~> 0.1)
+      rainbow (>= 2.2.2, < 4.0)
+      ruby-progressbar (~> 1.7)
+      unicode-display_width (~> 1.0, >= 1.0.1)
+    ruby-progressbar (1.9.0)
     rugged (0.23.2)
     safe_yaml (1.0.4)
     sass (3.4.22)
@@ -476,6 +490,7 @@ GEM
       unf_ext
     unf_ext (0.0.7.1)
     unicode (0.4.4.2)
+    unicode-display_width (1.3.3)
     unicorn (5.2.0)
       kgio (~> 2.6)
       raindrops (~> 0.7)
@@ -558,6 +573,7 @@ DEPENDENCIES
   retriable (~> 2.1)
   rollbar
   rouge
+  rubocop
   rugged
   sass-rails
   scout_apm!
diff --git a/config/boot.rb b/config/boot.rb
index 6b750f0..961b641 100644
--- a/config/boot.rb
+++ b/config/boot.rb
@@ -1,3 +1,3 @@
 ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
-
+require 'scout_apm/auto_instrument'
 require 'bundler/setup' # Set up gems listed in the Gemfile.

@itsderek23
Copy link
Contributor Author

Also:

ruby -v
ruby 2.1.2p95 (2014-05-08 revision 45877) [x86_64-darwin13.0]

I noticed that ScoutApm::InstructionSequence#load_iseq isn't called. I wonder if this is due to my older Ruby version.

@itsderek23
Copy link
Contributor Author

Works with Ruby 2.3.1, so a Ruby version issue.

Pretty cool - this breaks down a lot of the time!

image

@itsderek23
Copy link
Contributor Author

This helps address scoutapp/roadmap#68

@dlanderson
Copy link
Contributor

Correct - needs Ruby 2.3.1+ currently.

@ioquatix
Copy link
Contributor

Okay, I've made a first stab at improving this.

I've added some tests.

I suggest that the next step is to ensure we are generating the correct instrumentation and add a handful more tests.

@ioquatix
Copy link
Contributor

The code right now is very much Rails specific, but I've left it open for the future, we can probably modify it to suit different frameworks e.g. Sinatra.

@ioquatix
Copy link
Contributor

Do you have any prescribed method for doing integration tests? i.e. create a default rails site, insert the scout_apm gem, run it, and check that it's instrumented/collecting data?

I feel like this would be a nice addition.

lib/scout_apm/layer_children_set.rb Outdated Show resolved Hide resolved
lib/scout_apm/auto_instrument.rb Outdated Show resolved Hide resolved
Gemfile Outdated
@@ -10,3 +10,7 @@ if RUBY_VERSION <= "1.8.7"
gem "pry", "~> 0.9.12"
gem "rake", "~> 10.5"
end

group :development do
gem "parser"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be a dev-time gemspec dependency instead?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, no problem.

Copy link
Contributor

@dlanderson dlanderson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ioquatix awesome! A few things I want to go over and that need fixing. I'll ping you so we can chat about them

if path =~ CONTROLLER_PATH_PATTERN
new_code = Rails.rewrite(path)

return compile(new_code, path, filepath)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

filepath is undefined. Also, I think the arguments to compile are wrong - https://ruby-doc.org/core-2.5.0/RubyVM/InstructionSequence.html#method-c-compile

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, my bad, we should probably add some tests for this.

module ScoutApm
module AutoInstrument
module InstructionSequence
CONTROLLER_PATH_PATTERN = /\/apm\/app\/controllers\/.*_controller.rb$/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to make sure we remove the \/apm portion here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

lib/scout_apm/auto_instrument/instruction_sequence.rb Outdated Show resolved Hide resolved
@@ -0,0 +1,73 @@

# In order for this to work, you must add `gem 'parser'` to your Gemfile.
require 'parser/current'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking we'll be more defensive here and use conditionals around our definitions and code in order to prevent things blowing up if someone doesn't include parser or we're running in an older ruby version, non MRI, etc.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay I wrapped it and rescue LoadError. Let's see how this works on travis.

lib/scout_apm/auto_instrument/rails.rb Show resolved Hide resolved
column = node.location.column || 'column?'
method_name = node.children[1] || '*unknown*'

wrap(node.location.expression, "::ScoutApm::Instruments::AutoInstruments.dynamic_layer('#{method_name}:l#{line}:c#{column}'){", "}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

::ScoutApm::Instruments::AutoInstruments.dynamic_layer si so... ugly 😜 Thinking about how we can make that better. We mentioned about the invisible space or hot dog. Need to decide on options here

Copy link
Contributor

@ioquatix ioquatix Jan 29, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use ::ScoutApm::AutoInstrument{...}.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I implemented this change in recent commit.

layer = ScoutApm::Layer.new('AutoInstrument', name)
req.start_layer(layer)
started_layer = true
puts "================ STARTED AUTO INSTRUMENT LAYER #{name} ============="
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reminder to remove this debug code before releasing to customers

def test_rails_controller_rewrite
assert_equal instrumented_source("controller"), ::ScoutApm::AutoInstrument::Rails.rewrite(source_path("controller"))
end
end if RUBY_VERSION >= "2.3.1"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can test the rewriter on all versions. It's the load_iseq feature that's version dependent

Copy link
Contributor

@dlanderson dlanderson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot a crucial bug in super for load_iseq


return compile(new_code, path, filepath)
else
return super
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot this crucial one: we must assume super (load_iseq) does not exist (but it might exist).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I reworked it to be compile_file. It's simple solution for now.

Copy link
Contributor

@ioquatix ioquatix Jan 29, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could probably write super rescue compile_file(path) but that might be a bad idea, because it could eat up other exceptions.

lib/scout_apm/auto_instrument.rb Show resolved Hide resolved
end

def instrument(method_name, line, column)
["::ScoutApm::AutoInstrument(\"\#{self.class}\\\##{method_name}:L#{line}:C#{column}\"){", "}"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The desc field of a layer may make sense to put an exact line & column number in.

So the name can be Foo/length and desc is line 3, column 8 or whatever format you want.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest version of this puts the class and controller method with line number into the name field and the first line of code/expression into the description field. How do you feel about that? Can you try it and see if it makes sense?

lib/scout_apm/auto_instrument/layer.rb Outdated Show resolved Hide resolved
When instrumenting specific lines of code in the controller, we aren't
failing the entire request if that line fails, because the controller may
catch the error and do something with it (e.g. error page).
@jsierles
Copy link

Thanks for the work on this!

Just one finding. On Heroku, the Rails console is not run using bundler, so requiring in boot.rb would fail. To remedy this, I added the gem to a pre_boot bundler group:

require "bundler"
Bundler.setup(:pre_boot)

require 'scout_apm/auto_instrument'

@itsderek23
Copy link
Contributor Author

Should we set the version to something like 2.4.21.pre.auto? Without that, I'm unsure how to check which apps are running this branch.

@dlanderson dlanderson closed this Feb 20, 2019
@dlanderson
Copy link
Contributor

Replaced by #247

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants