-
Notifications
You must be signed in to change notification settings - Fork 375
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
Instrument ActiveRecord instantiations #334
Instrument ActiveRecord instantiations #334
Conversation
1909df6
to
58a73c9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❤️
span_type: span_type | ||
) | ||
|
||
span.span_type = Datadog::Ext::SQL::TYPE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can remove this line (or span_type: span_type
) since it's a duplicate.
span_type: span_type | ||
) | ||
|
||
span.span_type = Datadog::Ext::SQL::TYPE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here
span = get_option(:tracer).trace( | ||
'active_record.instantiation', | ||
resource: payload.fetch(:class_name), | ||
service: database_service, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should be the main service. If we set the database_service
, it will be considered as a latency of your database service (let's say a MySQL Cluster), while what slows down is not the query itself, but ActiveRecord allocating objects.
Makes sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. I thought the same thing (that there wasn't a neat category for this sort of thing.) I'll make that change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I don't think this is possible, since this ActiveRecord implementation doesn't know what a "main service" is. It might be Sinatra, but it could be a generic Ruby application, too. There's no good way to tell, short of adding an option for specifically configuring service name just for active_record.instantiation
, e.g. something like c.use :active_record, ruby_service_name: 'active_record'
.
Another consideration for this pull request... for stand alone implementations of ActiveRecord (e.g. a simple Ruby app that only implements ActiveRecord), we might see disjoint traces for This probably won't be an issue for Rails/Sinatra apps with respective tracing enabled though, since they have an implicit parent trace, and thus will simply append themselves to that parent. |
2f2eac7
to
8770b9d
Compare
module Datadog | ||
module Contrib | ||
module ActiveRecord | ||
# Patcher enables patching of 'active_record' module. | ||
module Patcher | ||
include Base | ||
register_as :active_record, auto_patch: false | ||
option :service_name | ||
option :service_name, default: 'active_record', depends_on: [:tracer] do |value| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a dangerous breaking change; if people update the gem, metrics will be different and so possible monitors. It means that:
- you will see
postgresql-cluster
what is in real the ActiveRecord instantiation - you will see a new
active_record
service that is the real query for postgresql.
I think service_name
must remain the same as before, and we may find a new option name for the new functionality.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also not sure the AR part should be called active_record
by default. Suppose you create a parent span with name my-app
, probably you want to have it my-app
instead of active_record
(so you don't create another service in your list but you only have my-app
that includes the AR latency.
This should handle only the case where you use a standalone AR without a parent span, but in that case maybe we should introduce more the concept of default service for a Tracer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rolling back service_name
.
span = tracer.trace( | ||
'active_record.instantiation', | ||
resource: payload.fetch(:class_name), | ||
service: rails_service, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is fine 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have to change this... I think it should belong to the parent service (if available), to be consistent with the behavior in standalone ActiveRecord. It will fallback to this is the parent name isn't available.
span = get_option(:tracer).trace( | ||
'active_record.instantiation', | ||
resource: payload.fetch(:class_name), | ||
service: get_option(:service_name), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the standalone AR, I would prefer to:
- create the span
- check if the service name is our default
- if not, set the service
- if yes, override the span service only if not already set
In this way: ar_service_name
> parent service > default value (active_record
let's say).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing this to be ruby_service_name
(not default) > parent service > ruby_service
(default)
span.start_time = start | ||
span.finish(finish) | ||
rescue StandardError => e | ||
Datadog::Tracer.log.error(e.message) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure.... these rescue
are in a critical path and can spam users' logs. What about making all of them log.debug
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is what Rails was already doing for sql
. If we change it here, we should probably change it there, too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I meant all occurrences in this integration (Rails included). I'm worried that for an internal problem we may cause issues like it happened before.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, I can do that then.
test/contrib/rails/database_test.rb
Outdated
|
||
instantiation_span = spans.first | ||
assert_equal(instantiation_span.name, 'active_record.instantiation') | ||
assert_equal(instantiation_span.span_type, 'ruby') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The span_type
should not be ruby
since it's not recognized by our backend. Probably it should be db
since it's part of a database transaction? web
applies as well, unless you're using AR standalone (that could be just a script).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh, didn't know we can't specify our own types here, which is why I gave it the name I thought more suitable.
Does either web
or db
apply? I think the answer would be no: instantiating objects is not a web
kind of action, nor is it strictly a db
thing either. Although it's probably closer to db
than to web in the sense that its for the purpose of aiding a DB query, I'd think this could cause us some headaches in the future if, for the purposes of doing statistics or other useful product implementation, we were trying to compare spans of type db
to one other, and one isn't actually a DB query at all.
This might be a little out of scope of what this PR is for, but I'd suggest maybe we should add a ruby
or custom span type, because there are many spans that aren't web
or db
. This came up once already while trying to implement GraphQL (which is also neither of those things), so I think this example is adding to a compelling case to examine what span types we have at our disposal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say to remove it then; we can change it to custom
instead of ruby
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay.
56f433c
to
88361a6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this integration is ready in this state. We just need to address a couple of comments and we're ready to go!
test/contrib/rails/database_test.rb
Outdated
|
||
instantiation_span = spans.first | ||
assert_equal(instantiation_span.name, 'active_record.instantiation') | ||
assert_equal(instantiation_span.span_type, 'ruby') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say to remove it then; we can change it to custom
instead of ruby
.
@@ -1,11 +1,19 @@ | |||
require 'ddtrace/contrib/rails/utils' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need to require these modules if the patch()
is not called?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm perhaps not. I think I'll move this into patch
.
# which isn't a good idea at initialization time. | ||
def self.database_service_name | ||
@database_service_name ||= (get_option(:service_name) || adapter_name).tap do |name| | ||
get_option(:tracer).set_service_info(name, 'active_record', Ext::AppTypes::DB) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
set_service_info
is called only once, correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's what this does, unless you change the service_name
, in which case this would run again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
totally legit then
span_type: Datadog::Ext::Ruby::TYPE | ||
) | ||
|
||
# Inherit service name from parent, if available. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 I think this is a better default. In general we expect this to be child of something else, though the corner case is well covered with active_record
default. Furthermore, if someone wants to change and split the service, they can. So we provide a reasonable default that is easy to configure.
option :service_name do |value| | ||
value.tap { @database_service_name = nil } | ||
end | ||
option :ruby_service_name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not entirely sure to keep ruby_
as a prefix, since it's too generic. If we check what this integration is, we can state that ActiveRecord
is an architectural pattern that stores in-memory object data in relational databases (ref). In the case of Rails, this pattern is used to implement the ORM. I think we can assume that the most common case is to use it to store data in-memory (or somewhere), so we may use different names such as:
active_record_name
orm_service_name
(I prefer this one because it keeps theservice_name
suffix that is clear in our terminology +orm_
has a specific meaning that is generic enough to cover instantiations and any other operation we may have in the future)
What do you think @delner ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like orm_service_name
. Ill change that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
one thing I forgot in the review is that we need to update also the documentation with the new option
b1e0f1a
to
e5fe4cd
Compare
2748235
to
e3e4da1
Compare
e3e4da1
to
8a217be
Compare
lib/ddtrace/ext/ruby.rb
Outdated
module Datadog | ||
module Ext | ||
module Ruby | ||
TYPE = 'ruby'.freeze |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this one is not required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will remove.
end | ||
end | ||
|
||
def instantiation_tracing_supported? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we want to use that also to avoid ActiveSupport::Notifications
calls?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, will use this to prevent unnecessary instrument calls.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good to go! thank you very much!
For #311 , sometimes ActiveRecord can take significant time instantiating records.
This pull request traces the
instantiation.active_record
event, for versions of ActiveRecord 4.2+. The additional span has two tags associated with it:active_record.instantiation.class_name
andactive_record.instantiation.record_count
.Sample output: