Skip to content

Commit

Permalink
Add transitions listener
Browse files Browse the repository at this point in the history
  • Loading branch information
serradura committed Jan 29, 2024
1 parent da3b8de commit 9b50a57
Show file tree
Hide file tree
Showing 12 changed files with 188 additions and 30 deletions.
4 changes: 4 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ Layout/MultilineMethodCallIndentation:
Lint/UnderscorePrefixedVariableName:
Enabled: false

Lint/UnusedMethodArgument:
Exclude:
- lib/bcdd/result/transitions/listener.rb

Style/AccessModifierDeclarations:
Enabled: false

Expand Down
7 changes: 6 additions & 1 deletion lib/bcdd/result/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

class BCDD::Result
class Config
include Singleton
include ::Singleton

attr_reader :addon, :feature, :constant_alias, :pattern_matching

Expand All @@ -21,6 +21,10 @@ def initialize
@and_then_ = CallableAndThen::Config.new
end

def transitions
Transitions::Config.instance
end

def and_then!
@and_then_
end
Expand All @@ -31,6 +35,7 @@ def freeze
constant_alias.freeze
pattern_matching.freeze
and_then!.freeze
transitions.freeze

super
end
Expand Down
5 changes: 2 additions & 3 deletions lib/bcdd/result/transitions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class BCDD::Result
module Transitions
require_relative 'transitions/tree'
require_relative 'transitions/tracking'
require_relative 'transitions/config'

THREAD_VAR_NAME = :bcdd_result_transitions_tracking

Expand All @@ -21,8 +22,6 @@ def self.tracking
def self.transitions(name: nil, desc: nil, &block)
Transitions.tracking.exec(name, desc, &block)
rescue ::Exception => e
Transitions.tracking.reset!

raise e
Transitions.tracking.err!(e)
end
end
30 changes: 30 additions & 0 deletions lib/bcdd/result/transitions/config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

module BCDD::Result::Transitions
require_relative 'listener'

class Config
include ::Singleton

attr_reader :listener, :trace_id

def initialize
@trace_id = -> {}
@listener = Listener::Null.new
end

def listener=(arg)
unless (arg.is_a?(::Class) && arg < Listener) || arg.is_a?(Listener)
raise ::ArgumentError, "#{arg.inspect} must be a #{Listener}"
end

@listener = arg
end

def trace_id=(arg)
raise ::ArgumentError, 'must be a lambda with arity 0' unless arg.is_a?(::Proc) && arg.lambda? && arg.arity.zero?

@trace_id = arg
end
end
end
27 changes: 27 additions & 0 deletions lib/bcdd/result/transitions/listener.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

module BCDD::Result::Transitions
module Listener
def around_execution(scope:)
yield
end

def around_and_then(scope:, and_then:)
yield
end

def after_record(record:); end

def after_finish(transitions:); end

def before_interruption(exception:, transitions:); end
end

module Listener::Null
extend Listener

def self.new
self
end
end
end
11 changes: 8 additions & 3 deletions lib/bcdd/result/transitions/tracking.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@ module Tracking
require_relative 'tracking/enabled'
require_relative 'tracking/disabled'

VERSION = 1

EMPTY_ARRAY = [].freeze
EMPTY_HASH = {}.freeze
EMPTY_TREE = Tree.new(nil).freeze
VERSION = 1
EMPTY = { version: VERSION, records: EMPTY_ARRAY, metadata: { duration: 0, tree_map: EMPTY_ARRAY } }.freeze
EMPTY = {
version: VERSION,
records: EMPTY_ARRAY,
metadata: { duration: 0, tree_map: EMPTY_ARRAY, trace_id: nil }.freeze
}.freeze

def self.instance
Config.instance.feature.enabled?(:transitions) ? Tracking::Enabled.new : Tracking::Disabled
::BCDD::Result::Config.instance.feature.enabled?(:transitions) ? Tracking::Enabled.new : Tracking::Disabled
end
end
end
Expand Down
4 changes: 4 additions & 0 deletions lib/bcdd/result/transitions/tracking/disabled.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ def self.exec(_name, _desc)
EnsureResult[yield]
end

def self.err!(err)
raise err
end

def self.reset!; end

def self.record(result); end
Expand Down
70 changes: 53 additions & 17 deletions lib/bcdd/result/transitions/tracking/enabled.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,36 @@

module BCDD::Result::Transitions
class Tracking::Enabled
attr_accessor :tree, :records, :root_started_at
attr_accessor :tree, :records, :root_started_at, :listener

private :tree, :tree=, :records, :records=, :root_started_at, :root_started_at=
private :tree, :tree=, :records, :records=, :root_started_at, :root_started_at=, :listener, :listener=

def exec(name, desc)
start(name, desc)

transition_node = tree.current
result = nil

result = EnsureResult[yield]
listener.around_execution(scope: tree.current_value[0]) do
transition_node = tree.current

tree.move_to_root! if transition_node.root?
result = EnsureResult[yield]

finish(result)
tree.move_to_root! if transition_node.root?

finish(result)
end

result
end

def err!(exception)
listener.before_interruption(exception: exception, transitions: map_transitions)

reset!

raise exception
end

def reset!
self.tree = Tracking::EMPTY_TREE
end
Expand All @@ -30,17 +42,19 @@ def record(result)
track(result, time: ::Time.now.getutc)
end

def record_and_then(type_arg, arg)
def record_and_then(type_arg, arg, &block)
return yield if tree.frozen?

type = type_arg.instance_of?(::Method) ? :method : type_arg

unless tree.frozen?
current_and_then = { type: type, arg: arg }
current_and_then[:method_name] = type_arg.name if type == :method
current_and_then = { type: type, arg: arg }
current_and_then[:method_name] = type_arg.name if type == :method

tree.current.value[1] = current_and_then
end
tree.current.value[1] = current_and_then

yield
scope, and_then = tree.current_value

listener.around_and_then(scope: scope, and_then: and_then, &block)
end

def reset_and_then!
Expand All @@ -64,11 +78,11 @@ def finish(result)

return unless node.root?

duration = (now_in_milliseconds - root_started_at)
transitions = map_transitions

metadata = { duration: duration, tree_map: tree.nested_ids }
result.send(:transitions=, transitions)

result.send(:transitions=, version: Tracking::VERSION, records: records, metadata: metadata)
listener.after_finish(transitions: transitions)

reset!
end
Expand All @@ -78,24 +92,46 @@ def finish(result)
def root_start(name_and_desc)
self.root_started_at = now_in_milliseconds

self.listener = Config.instance.listener.new

self.records = []

self.tree = Tree.new(name_and_desc, normalizer: TreeNodeValueNormalizer)
end

def track(result, time:)
record = track_record(result, time)

records << record

listener.after_record(record: record)

record
end

def track_record(result, time)
result_data = result.data.to_h
result_data[:source] = result.send(:source)

root, = tree.root_value
parent, = tree.parent_value
current, and_then = tree.current_value

records << { root: root, parent: parent, current: current, result: result_data, and_then: and_then, time: time }
{ root: root, parent: parent, current: current, result: result_data, and_then: and_then, time: time }
end

def now_in_milliseconds
::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :millisecond)
end

def map_transitions
duration = (now_in_milliseconds - root_started_at)

trace_id = Config.instance.trace_id.call

metadata = { duration: duration, tree_map: tree.nested_ids, trace_id: trace_id }

{ version: Tracking::VERSION, records: records, metadata: metadata }
end
end
end
2 changes: 1 addition & 1 deletion sig/bcdd/result.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class BCDD::Result

def self.config: -> BCDD::Result::Config
def self.configuration: { (BCDD::Result::Config) -> void } -> BCDD::Result::Config
def self.transitions: { () -> untyped } -> BCDD::Result
def self.transitions: (name: untyped, desc: untyped) { () -> untyped } -> BCDD::Result

def initialize: (
type: Symbol,
Expand Down
1 change: 1 addition & 0 deletions sig/bcdd/result/config.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class BCDD::Result::Config
def initialize: -> void

def and_then!: () -> BCDD::Result::CallableAndThen::Config
def transitions: () -> BCDD::Result::Transitions::Config

def freeze: -> BCDD::Result::Config
def options: -> Hash[Symbol, BCDD::Result::Config::Switcher]
Expand Down
54 changes: 50 additions & 4 deletions sig/bcdd/result/transitions.rbs
Original file line number Diff line number Diff line change
@@ -1,5 +1,45 @@
class BCDD::Result
module Transitions
module Listener
def around_execution: (
scope: Hash[Symbol, untyped]
) { () -> untyped } -> untyped

def around_and_then: (
scope: Hash[Symbol, untyped],
and_then: Hash[Symbol, untyped],
) { () -> untyped } -> untyped

def after_record: (record: Hash[Symbol, untyped] ) -> untyped

def after_finish: (transitions: Hash[Symbol, untyped] ) -> untyped

def before_interruption: (
exception: ::Exception,
transitions: Hash[Symbol, untyped]
) -> untyped
end

module Listener::Null
extend Listener

def self.new: () -> untyped
end

class Config
include ::Singleton

attr_reader listener: untyped
attr_reader trace_id: ::Proc

def self.instance: -> Config

def initialize: -> void

def listener=: (Listener) -> void
def trace_id=: (::Proc) -> void
end

class Tree
class Node
attr_reader id: Integer
Expand Down Expand Up @@ -53,9 +93,11 @@ class BCDD::Result
class Enabled
private attr_accessor tree: Transitions::Tree
private attr_accessor records: Array[Hash[Symbol, untyped]]
private attr_accessor listener: untyped
private attr_accessor root_started_at: Integer

def exec: (String, String) { () -> untyped } -> BCDD::Result
def exec: (untyped, untyped) { () -> untyped } -> untyped
def err!: (::Exception) -> void
def reset!: () -> void
def record: (BCDD::Result) -> void
def record_and_then: ((untyped), untyped) { () -> BCDD::Result } -> BCDD::Result
Expand All @@ -64,19 +106,23 @@ class BCDD::Result
private

def start: (String, String) -> void
def finish: (BCDD::Result) -> void
def finish: (untyped) -> untyped

TreeNodeValueNormalizer: ^(Integer, Array[untyped]) -> untyped

def root_start: (Array[untyped]) -> void

def track: (BCDD::Result, time: Time) -> void
def track_record: (BCDD::Result, Time) -> Hash[Symbol, untyped]

def now_in_milliseconds: () -> Integer

def map_transitions: () -> Hash[Symbol, untyped]
end

module Disabled
def self.exec: (String, String) { () -> untyped } -> BCDD::Result
def self.exec: (untyped, untyped) { () -> untyped } -> untyped
def self.err!: (::Exception) -> void
def self.reset!: () -> void
def self.record: (BCDD::Result) -> void
def self.record_and_then: ((untyped), untyped) { () -> BCDD::Result } -> BCDD::Result
Expand All @@ -85,7 +131,7 @@ class BCDD::Result
private

def self.start: (String, String) -> void
def self.finish: (BCDD::Result) -> void
def self.finish: (untyped) -> untyped
end

def self.instance: () -> (Enabled | singleton(Disabled))
Expand Down
Loading

0 comments on commit 9b50a57

Please sign in to comment.