-
Notifications
You must be signed in to change notification settings - Fork 173
[Prototype] Object tracking and observability #451
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,70 @@ | ||||||||||||||||||||||||||
# frozen_string_literal: true | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
module IdentityCache | ||||||||||||||||||||||||||
module Tracking | ||||||||||||||||||||||||||
TrackedObject = Struct.new(:object, :includes, :caller, :accessed_associations) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
extend self | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def tracked_objects | ||||||||||||||||||||||||||
Thread.current[:idc_tracked_objects] ||= {} | ||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def reset_tracked_objects | ||||||||||||||||||||||||||
Thread.current[:idc_tracked_objects] = {} | ||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def track_object(object, includes) | ||||||||||||||||||||||||||
return unless object_tracking_enabled | ||||||||||||||||||||||||||
locations = caller(1, 20) | ||||||||||||||||||||||||||
tracked_objects[object] = TrackedObject.new(object, Array(includes), locations, Set.new) | ||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def track_association_accessed(object, association_name) | ||||||||||||||||||||||||||
return unless object_tracking_enabled | ||||||||||||||||||||||||||
obj = self.tracked_objects[object] | ||||||||||||||||||||||||||
obj.accessed_associations << association_name if obj | ||||||||||||||||||||||||||
dylanahsmith marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def instrument_and_reset_tracked_objects | ||||||||||||||||||||||||||
tracked_objects.each do |_, to| | ||||||||||||||||||||||||||
ActiveSupport::Notifications.instrument('object_track.identity_cache', { | ||||||||||||||||||||||||||
object: to.object, | ||||||||||||||||||||||||||
accessed_associations: to.accessed_associations, | ||||||||||||||||||||||||||
caller: to.caller | ||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||
reset_tracked_objects | ||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def with_object_tracking_and_instrumentation | ||||||||||||||||||||||||||
begin | ||||||||||||||||||||||||||
with_object_tracking { yield } | ||||||||||||||||||||||||||
ensure | ||||||||||||||||||||||||||
instrument_and_reset_tracked_objects | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If an exception causes associations to not be used in the block, then that doesn't mean that the block shouldn't load those associations. So we might want to just call |
||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||
Comment on lines
+40
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def with_object_tracking(enabled: true) | ||||||||||||||||||||||||||
begin | ||||||||||||||||||||||||||
orig = object_tracking_enabled | ||||||||||||||||||||||||||
Comment on lines
+49
to
+50
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If
Suggested change
|
||||||||||||||||||||||||||
self.object_tracking_enabled = enabled | ||||||||||||||||||||||||||
yield | ||||||||||||||||||||||||||
ensure | ||||||||||||||||||||||||||
self.object_tracking_enabled = orig | ||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def skip_object_tracking | ||||||||||||||||||||||||||
with_object_tracking(enabled: false) { yield } | ||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def object_tracking_enabled | ||||||||||||||||||||||||||
Thread.current[:object_tracking_enabled] | ||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def object_tracking_enabled=(value) | ||||||||||||||||||||||||||
Thread.current[:object_tracking_enabled] = value | ||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||
end | ||||||||||||||||||||||||||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
# frozen_string_literal: true | ||
require "test_helper" | ||
|
||
class SaveTest < IdentityCache::TestCase | ||
def setup | ||
super | ||
Item.cache_index(:title, unique: true) | ||
Item.cache_has_many(:normalized_associated_records, embed: true) | ||
|
||
@record = Item.create(title: 'bob') | ||
@record.normalized_associated_records.create! | ||
end | ||
|
||
def test_fetch_index_tracks_object_no_accessed_associations | ||
fill_cache | ||
|
||
captured = 0 | ||
subscriber = ActiveSupport::Notifications.subscribe('object_track.identity_cache') do |_, _, _, _, payload| | ||
captured += 1 | ||
assert_same_record(@record, payload[:object]) | ||
assert_equal [].to_set, payload[:accessed_associations] | ||
assert_kind_of Array, payload[:caller] | ||
end | ||
|
||
IdentityCache::Tracking.with_object_tracking_and_instrumentation do | ||
Item.fetch_by_title('bob') | ||
end | ||
assert_equal 1, captured | ||
ensure | ||
ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber | ||
end | ||
|
||
def test_fetch_index_tracks_object_with_accessed_associations | ||
fill_cache | ||
|
||
captured = 0 | ||
subscriber = ActiveSupport::Notifications.subscribe('object_track.identity_cache') do |_, _, _, _, payload| | ||
captured += 1 | ||
assert_same_record(@record, payload[:object]) | ||
assert_equal [:normalized_associated_records].to_set, payload[:accessed_associations] | ||
assert_kind_of Array, payload[:caller] | ||
end | ||
|
||
IdentityCache::Tracking.with_object_tracking_and_instrumentation do | ||
item = Item.fetch_by_title('bob') | ||
item.fetch_normalized_associated_records | ||
end | ||
assert_equal 1, captured | ||
ensure | ||
ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber | ||
end | ||
|
||
|
||
def test_fetch_tracks_object_no_accessed_associations | ||
fill_cache | ||
|
||
captured = 0 | ||
subscriber = ActiveSupport::Notifications.subscribe('object_track.identity_cache') do |_, _, _, _, payload| | ||
captured += 1 | ||
assert_same_record(@record, payload[:object]) | ||
assert_equal [].to_set, payload[:accessed_associations] | ||
assert_kind_of Array, payload[:caller] | ||
end | ||
|
||
IdentityCache::Tracking.with_object_tracking_and_instrumentation do | ||
item = Item.fetch(@record.id) | ||
end | ||
assert_equal 1, captured | ||
ensure | ||
ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber | ||
end | ||
|
||
def test_fetch_tracks_object_with_accessed_associations | ||
fill_cache | ||
|
||
captured = 0 | ||
subscriber = ActiveSupport::Notifications.subscribe('object_track.identity_cache') do |_, _, _, _, payload| | ||
captured += 1 | ||
assert_same_record(@record, payload[:object]) | ||
assert_equal [:normalized_associated_records].to_set, payload[:accessed_associations] | ||
assert_kind_of Array, payload[:caller] | ||
end | ||
|
||
IdentityCache::Tracking.with_object_tracking_and_instrumentation do | ||
item = Item.fetch(@record.id) | ||
item.fetch_normalized_associated_records | ||
end | ||
assert_equal 1, captured | ||
ensure | ||
ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber | ||
end | ||
|
||
def test_does_not_track_objects_unless_enabled | ||
fill_cache | ||
|
||
subscriber = ActiveSupport::Notifications.subscribe('object_track.identity_cache') do |_, _, _, _, payload| | ||
refute | ||
end | ||
|
||
item = Item.fetch_by_title('bob') | ||
ensure | ||
ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber | ||
end | ||
|
||
private | ||
|
||
def fill_cache | ||
item = Item.fetch_by_title('bob') | ||
assert item | ||
item.fetch_normalized_associated_records | ||
end | ||
|
||
def assert_same_record(expected, actual) | ||
assert_equal expected.id, actual.id | ||
end | ||
end |
Uh oh!
There was an error while loading. Please reload this page.