Commit Graph

84 Commits

Author SHA1 Message Date
Bart de Water
95b6fbd00f Stop building AS::Notifications::Event manually
It's possible since Rails 6 (3ea2857943dc294d7809930b4cc5b318b9c39577) to let the framework create Event objects, but the guides and docs weren't updated to lead with this example.

Manually instantiating an Event doesn't record CPU time and allocations, I've seen it more than once that people copy-pasting the example code get confused about these stats returning 0. The tests here show that - just like the apps I've worked on - the old pattern keeps getting copy-pasted.
2023-09-29 12:34:23 -04:00
Matthew Draper
c285f36366 Never report negative idle time
We calculate idle time by subtracting two separately-obtained values,
and apparently it's possible for the CPU-time clock to tick a
millisecond ahead of the monotonic clock.

I think it's safe to ignore that potential difference in general, but
even at the extreme of CPU activity, I'm reasonably confident time
doesn't move backwards.
2023-01-29 04:56:50 +10:30
John Hawthorn
dbf2edb7f2 Make Notifier::Fanout faster and safer
This commit aims to improve ActiveSupport::Notifications::Fanout. There
are three main goals here: backwards compatibility, safety, and
performance.

* Backwards compatibility

This ActiveSupport::Notifications is an old and well used interface.
Over time it has collected a lot of features and flexibility, much of
which I suspect is not used anywhere by anyone, but it is hard to know
specifics and we would at minimum need a deprecation cycle.

For this reason this aims to fully maintain compatibility. This includes
both the ability to use an alternate notification implementation instead
of Fanout, the signatures received by all types of listeners, and the
interface used on the Instrumenter and Fanout itself (including the
sometimes problematic start/finish).

* Safety

There have been issues (both recent and past) with the "timestacks"
becoming invalid, particularly when subscribing and unsubscribing within
events. This is an issue when topics are subscribed/unsubscribed to
while they are in flight.

The previous implementation would record a separate timestamp or event
object for each listener in a thread local stack. This meant that it was
essential that the listeners to start and finish were identical.

This issue is avoided by passing the listeners used to `start` the event
to `finish` (`finish_with_state` in the Instrumenter), to ensure they
are the same set in `start`/`finish`.

This commit further avoids this issue. Instead of pushing individual
times onto a stack, we now push a single object, `Handle`, onto the
stack for an event. This object holds all the subscribers (recorded at
start time) and all their associated data. This means that as long as
start/stop calls are not interleaved.

This commit also exposes `build_handle` as a public interface. This
returns the Handle object which can have start/stop called at any time
and any order safely. The one reservation I have with making this public
is that existing "evented" listeners (those receiving start/stop) may
not be ready for that (ex. if they maintain an internal thread-local
stack).

* Performance

This aims to be faster and make fewer allocations then the existing
implementation.

For time-based and event-object-based listeners, the previous
implementation created a separate object for each listener, pushing
and popping it on a thread-local stack. This is slower both because we
need to access the thread local repeatedly (hash lookups) and because
we're allocating duplicate objects.

The new implementation works by grouping similar types of listeners
together and shares either the `Event` or start/stop times between all
of them. The grouping was done so that we didn't need to allocate Events
or Times for topics which did have a listener of that type.

This implementation is significantly faster for all cases, except for
evented, which is slower.

For topics with 10 subscriptions:

*main*:

               timed     66.739k (± 2.5%) i/s -    338.800k in   5.079883s
     timed_monotonic    138.265k (± 0.6%) i/s -    699.261k in   5.057575s
        event_object     48.650k (± 0.2%) i/s -    244.250k in   5.020614s
             evented    366.559k (± 1.0%) i/s -      1.851M in   5.049727s
        unsubscribed      3.696M (± 0.5%) i/s -     18.497M in   5.005335s

*This branch*:

               timed    259.031k (± 0.6%) i/s -      1.302M in   5.025612s
     timed_monotonic    327.439k (± 1.7%) i/s -      1.665M in   5.086815s
        event_object    228.991k (± 0.3%) i/s -      1.164M in   5.083539s
             evented    296.057k (± 0.3%) i/s -      1.501M in   5.070315s
        unsubscribed      3.670M (± 0.3%) i/s -     18.376M in   5.007095s

Co-authored-by: John Crepezzi <john.crepezzi@gmail.com>
Co-authored-by: Theo Julienne <theojulienne@github.com>
2022-06-01 18:54:44 -07:00
John Hawthorn
9f0b8eb584 Deprecate Event#{children,parent_of} 2022-02-17 08:20:01 -08:00
John Hawthorn
e1a4061d1d Implement inspect for Notifications::Fanout
A few classes hold a reference to this object (anything with a
subscription), causing the `inspect` or pretty print output to be
unreasonably large and slow.

This implements a simple representation for the Notifications::Fanout
object as users usually don't care about its internals.

Co-authored-by: Daniel Colson <danieljamescolson@gmail.com>
2022-02-16 17:01:30 -08:00
Nikita Vasilevsky
4219696649 [Active Support] Add explicit assertions to tests with no assertions 2022-02-15 23:44:25 +00:00
Jonathan Hefner
763c219539 Fix flakey test in notifications_test.rb
Example failure: https://buildkite.com/rails/rails/builds/82905#80d6c6ec-943d-4ba3-b360-1ef6c4aa5d89/1012-1022

The test designates the event end time as 0.01 seconds (i.e. 10
milliseconds) after the start time.  It then asserts that the event
duration is 10 ± 0.0001 milliseconds.  This sometimes fails due to
floating point precision errors.

This commit changes the assertion to instead check that the duration is
within 1% of the expected value.
2021-11-30 14:03:32 -06:00
Jean Boussier
81d0dc90be Use Process.clock_gettime unit argument to save some floating point multiplications 2021-10-21 10:05:32 +02:00
Jean Boussier
7ddfa93ba5 Stop using Concurrent.monotonic_time
Unless you are on a Ruby older than 2.3 (or some very old JRuby)
`Concurrent.monotonic_time` is just `Process.clock_gettime(Process::CLOCK_MONOTONIC)`.
So might as well skip the extra method call, and more importantly
it allows in many cases to pass the scale as second argument and save some
floating point multiplication.
2021-10-21 09:31:25 +02:00
Rafael França
78bf8f83b2
Merge pull request #39626 from vipulnsward/as-notification-args
Raise error when passing invalid arguments on subscription instead of failing silently
2020-12-28 19:30:02 -05:00
Daniel Colson
4e646bb281
Allow subscribing with a single argument callable
Fixes #39976

Prior to this commit it was possible to pass a single argument block to
`ActiveSupport::Notifications.subscribe`, rather than 5 separate
arguments:

```rb
ActiveSupport::Notifications.subscribe('some_event') do |event|
  puts "Reacting to #{event.name}"
end
```

But it was not possible to do the same with a lambda, since the lambda
parameter is a required (`:req`) parameter, but we were checking only
for an optional (`:opt`) parameter.

```rb
listener = ->(event) do
  puts "Reacting to #{event.name}"
end

ActiveSupport::Notifications.subscribe('some_event', &listener)
```

It was also not possible to do this with a custom callable object, since
the custom callable does not respond directly to `:parameters` (although
it's `:call` method object does).

```rb
class CustomListener
  def call(event)
    puts "Reacting to #{event.name}"
  end
end

ActiveSupport::Notifications.subscribe('some_event', CustomListener.new)
```

Prior to this commit these examples would have raised `ArgumentError:
wrong number of arguments (given 5, expected 1)`.

With this commit the single argument lambda and custom callable work
like the single argument block.
2020-11-09 22:26:21 -05:00
Vipul A M
bcec581160 Currently subscription api on notifications is only supported for String, Regex or nil. If we pass anything different, this causes issues because, it gets added to "other subscribers" silently.
We now raise an argument error instead if something invalid is passed.

Partially accesses original class implementation from 4f2a04cc08 which used the case/when
2020-06-17 08:32:42 +05:30
Michael Grosser
203998c916
allow running each test with pure ruby path/to/test.rb
also:
 - makes test dependencies obvious
 - makes tests runnable from within subfolders
2019-12-18 08:49:19 -06:00
Rafael França
bd5edc2970
Merge pull request #36318 from itsWill/fix_event_object_payload
Merge payload for EventObject subscribers
2019-07-25 16:47:03 -04:00
Ryuta Kamizono
c81af6ae72 Enable Layout/EmptyLinesAroundAccessModifier cop
We sometimes say "✂️ newline after `private`" in a code review (e.g.
https://github.com/rails/rails/pull/18546#discussion_r23188776,
https://github.com/rails/rails/pull/34832#discussion_r244847195).

Now `Layout/EmptyLinesAroundAccessModifier` cop have new enforced style
`EnforcedStyle: only_before` (https://github.com/rubocop-hq/rubocop/pull/7059).

That cop and enforced style will reduce the our code review cost.
2019-06-13 12:00:45 +09:00
Ryuta Kamizono
90d6237762 Fix subscribed with no pattern to subscribe all messages
This is a regression for #36184.

And also, add new `monotonic` argument to the last of the method
signature rather than the first.
2019-06-03 05:47:10 +09:00
Guilherme Mansur
218f0777d6 Merge payload for EventObject subscribers
When instrumenting a block of code like:

```ruby
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_paylaod) do |payload|
 payload[:view_runtime] = render_view
end
```
If we use an evented subscriber like so:

``` ruby
ActiveSupport::Notifications.subscribe("process_action.action_controller", raw_payload) do |event|
 assert event.payload[:view_runtime]
end
```

The code breaks because the underlying EventObject's payload does not have the
`:view_runtime` key added during instrumentation.

This is because the `EventedObject` subscriber calls the `finish` method with the
`payload` of the event at the time it was pushed into the stack, before the
block executes, but we want to call `finish`  with the `payload` after the
instrument block executes this way if the `payload` was modified during the block
we have access to it. This is consistent with the other types of subscribers
who don't have this bug.
2019-05-22 14:11:03 -04:00
Vishal Telangre
93b652affb
Introduce 'ActiveSupport::Notifications::Fanout::Subscribers::MonotonicTimed' and 'ActiveSupport::Notifications::monotonic_subscribe'
Also, change the signature of ‘ActiveSupport::Notifications::Fanout#subscribe’ to accept optional ‘monotonic’ boolean argument. Then initialize either a ‘Timed’ or ‘MonotonicTimed’ subscriber based on the value of ‘monotonic’ parameter.

Introduce ‘ActiveSupport::Notifications::monotonic_subscribe’ method

Also, provision ‘ActiveSupport::Notifications::subscribed’ to optionally accept ‘monotonic’ boolean argument.

Update documentation for ActiveSupport::Notifications

Add tests

Update guides documentation under the 'Active Support Instrumentation' chapter

Incorporate feedback: use optional keyword argument to specify optional 'monotonic' option to 'subscribed' method

Fix a typo
2019-05-10 20:16:17 +05:30
Kevin Solorio
b9a9131fdb revert changes to monotonic times
The change to monotonic times causes failures for applications
where the subscribed block is expecting Time objects as described
in this issue: https://github.com/rails/rails/issues/36145

The original PR (https://github.com/rails/rails/pull/35984) was
concerned with errors on the cpu_time. Test was edited to reflect
changes to initializer using 0 values instead of nil
2019-04-30 14:32:03 -07:00
Vishal Telangre
6379050469
Add test coverage 2019-04-16 05:20:35 +05:30
zvkemp
94f8e8c8f7 use a proxy matcher for AS::N fanout 2019-02-11 16:04:25 -08:00
alkesh26
38472af70e ActiveSupport typo fixes. 2019-02-01 22:17:10 +05:30
Bogdan
fdb2719308 Extend documentation of ActiveSupport::Notifications.subscribe (#34721)
* Extend documentation of `ActiveSupport::Notifications.subscribe`

Add mention that a block with only one argument passed to the method
will yield an event object.

Related to #33451

* Emphasize that `SubscribeEventObjects` is a test class by adding suffix `Test`
2018-12-17 17:25:55 +09:00
Rafael Mendonça França
f679933daa
Change the empty block style to have space inside of the block 2018-09-25 13:19:35 -04:00
Aaron Patterson
4cdedb571c
Always subscribe to event objects via AS::Notifications.subscribe
We don't need to have a special subscribe method for objects.  The
regular `subscribe` method is more expensive than a specialized method,
but `subscribe` should not be called frequently.  If that turns out to
be a hotspot, we can introduce a specialized method.  :)
2018-07-26 12:15:41 -07:00
Aaron Patterson
5e0c423881
Subscribe to event objects via subscribe 2018-07-26 12:05:23 -07:00
Aaron Patterson
b0a16a9776
Subscribe to event objects via subscribe_event
Fanout notifier can send event objects to subscribers now.  Also moved
`end` lower in the `finish!` method to guarantee that CPU time is
shorter than real time.
2018-07-26 12:03:31 -07:00
Daniel Colson
a1ac18671a Replace assert ! with assert_not
This autocorrects the violations after adding a custom cop in
3305c78dcd.
2018-04-19 08:11:33 -04:00
Daniel Colson
fda1863e1a Remove extra whitespace 2018-01-25 23:32:59 -05:00
Koichi ITO
ac717d65a3 [Active Support] rubocop -a --only Layout/EmptyLineAfterMagicComment 2017-07-11 13:12:32 +09:00
Kir Shatrov
72950568dd Use frozen-string-literal in ActiveSupport 2017-07-09 15:08:29 +03:00
Matthew Draper
87b3e226d6 Revert "Merge pull request #29540 from kirs/rubocop-frozen-string"
This reverts commit 3420a14590c0e6915d8b6c242887f74adb4120f9, reversing
changes made to afb66a5a598ce4ac74ad84b125a5abf046dcf5aa.
2017-07-02 02:15:17 +09:30
Kir Shatrov
cfade1ec7e Enforce frozen string in Rubocop 2017-07-01 02:11:03 +03:00
Akira Matsuda
3fe6a5d510 Privatize unneededly protected methods in Active Support tests 2016-12-24 00:22:29 +09:00
Rafael Mendonça França
55f9b8129a
Add three new rubocop rules
Style/SpaceBeforeBlockBraces
Style/SpaceInsideBlockBraces
Style/SpaceInsideHashLiteralBraces

Fix all violations in the repository.
2016-08-16 04:30:11 -03:00
Xavier Noria
5c315a8fa6 modernizes hash syntax in activesupport 2016-08-06 19:38:33 +02:00
Xavier Noria
a731125f12 applies new string literal convention in activesupport/test
The current code base is not uniform. After some discussion,
we have chosen to go with double quotes by default.
2016-08-06 18:10:53 +02:00
Ryan T. Hosford
3e48bfd497 Adds exception object to instrumenter's payload
- Adds new key/value pair to payload when an exception is raised
    e.g. `:exception_object=> #<RuntimeError: FAIL>`
  - Updates relevant test
  - Adds CHANGELOG entry
2015-12-31 01:48:22 -06:00
thedarkone
ab3c4a4083 Subscribing to notifications while inside the said instrumented section.
The issue is that on the exit from Instrumenter#instrument section,
an Evented listener will run into an error because its thread local
(Thread.current[:_timestack]) has not been set up by the #start
method (this obviously happens because the Evented listeners didn't
exist at the time, since no subscribtion to that section was made yet).

Note: support for subscribing to instrumented sections, while being
inside those instrumented sections, might be removed in the future.

Maybe fixes #21873.
2015-11-28 01:40:21 +01:00
Carl Lerche
e539228d08 Bug fix: Evented notification subscribers can handle published events 2013-05-17 16:27:23 -07:00
Prathamesh Sonpatki
b5429eec60 Fix Typo existant -> existent [ci skip] 2013-05-08 09:50:46 +05:30
stopdropandrew
a007800a55 ActiveSupport::Notifications::Instrumenter#instrument should yield
its payload the same way that ActiveSupport::Notifications does.
Fix spelling in test name.
2013-03-02 16:05:05 -08:00
タコ焼き仮面
c6d86a5db4 make events not use date and time to determine parent_of. fixes #5932 2012-06-18 16:34:23 -07:00
Xavier Noria
d287e90870 implements AS::Notifications.subscribed, which provides subscriptions to events while a block runs 2011-11-05 12:02:54 -07:00
Arun Agrawal
83eec4ca4c Requiring delegate. 2011-08-16 01:36:21 +05:30
Jon Leighton
d411c85a65 Replace references to ActiveSupport::SecureRandom with just SecureRandom, and require 'securerandom' from the stdlib when active support is required. 2011-05-23 20:25:44 +01:00
Aaron Patterson
3e02b3702e just use an attr_accessor so we do not pay ||= on every notification call 2011-02-09 14:02:38 -08:00
José Valim
ff0d842454 Revert the previous three commits.
* AS::Notifications#instrument should not measure anything, it is not its responsibility;

* Adding another argument to AS::Notifications#instrument API needs to be properly discussed;
2010-07-25 20:46:42 +02:00
Aaron Patterson
b7e0408ca9 use a hash to collect optional statistics about the instrumentation 2010-07-25 11:11:23 -07:00
José Valim
834bd23a07 Get rid of instrumenter.elapsed. 2010-07-24 10:22:22 +02:00