flows 0.1.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +38 -0
- data/.gitignore +9 -1
- data/.mdlrc +1 -0
- data/.reek.yml +54 -0
- data/.rubocop.yml +44 -2
- data/.ruby-version +1 -1
- data/.yardopts +1 -0
- data/CHANGELOG.md +65 -0
- data/README.md +186 -256
- data/Rakefile +35 -1
- data/bin/.rubocop.yml +5 -0
- data/bin/all_the_errors +55 -0
- data/bin/benchmark +69 -78
- data/bin/benchmark_cli/compare.rb +118 -0
- data/bin/benchmark_cli/compare/a_plus_b.rb +22 -0
- data/bin/benchmark_cli/compare/base.rb +45 -0
- data/bin/benchmark_cli/compare/command.rb +47 -0
- data/bin/benchmark_cli/compare/ten_steps.rb +22 -0
- data/bin/benchmark_cli/examples.rb +23 -0
- data/bin/benchmark_cli/examples/.rubocop.yml +19 -0
- data/bin/benchmark_cli/examples/a_plus_b/dry_do.rb +23 -0
- data/bin/benchmark_cli/examples/a_plus_b/dry_transaction.rb +17 -0
- data/bin/benchmark_cli/examples/a_plus_b/flows_do.rb +22 -0
- data/bin/benchmark_cli/examples/a_plus_b/flows_railway.rb +13 -0
- data/bin/benchmark_cli/examples/a_plus_b/flows_scp.rb +13 -0
- data/bin/benchmark_cli/examples/a_plus_b/flows_scp_mut.rb +13 -0
- data/bin/benchmark_cli/examples/a_plus_b/flows_scp_oc.rb +21 -0
- data/bin/benchmark_cli/examples/a_plus_b/trailblazer.rb +15 -0
- data/bin/benchmark_cli/examples/ten_steps/dry_do.rb +70 -0
- data/bin/benchmark_cli/examples/ten_steps/dry_transaction.rb +64 -0
- data/bin/benchmark_cli/examples/ten_steps/flows_do.rb +69 -0
- data/bin/benchmark_cli/examples/ten_steps/flows_railway.rb +58 -0
- data/bin/benchmark_cli/examples/ten_steps/flows_scp.rb +58 -0
- data/bin/benchmark_cli/examples/ten_steps/flows_scp_mut.rb +58 -0
- data/bin/benchmark_cli/examples/ten_steps/flows_scp_oc.rb +66 -0
- data/bin/benchmark_cli/examples/ten_steps/trailblazer.rb +60 -0
- data/bin/benchmark_cli/helpers.rb +12 -0
- data/bin/benchmark_cli/ruby.rb +15 -0
- data/bin/benchmark_cli/ruby/command.rb +38 -0
- data/bin/benchmark_cli/ruby/method_exec.rb +71 -0
- data/bin/benchmark_cli/ruby/self_class.rb +69 -0
- data/bin/benchmark_cli/ruby/structs.rb +90 -0
- data/bin/console +1 -0
- data/bin/docserver +7 -0
- data/bin/errors +130 -0
- data/bin/errors_cli/contract_error_demo.rb +49 -0
- data/bin/errors_cli/di_error_demo.rb +38 -0
- data/bin/errors_cli/flow_error_demo.rb +22 -0
- data/bin/errors_cli/flows_router_error_demo.rb +15 -0
- data/bin/errors_cli/oc_error_demo.rb +40 -0
- data/bin/errors_cli/railway_error_demo.rb +10 -0
- data/bin/errors_cli/result_error_demo.rb +13 -0
- data/bin/errors_cli/scp_error_demo.rb +17 -0
- data/docs/.nojekyll +0 -0
- data/docs/README.md +13 -0
- data/docs/_sidebar.md +2 -0
- data/docs/index.html +30 -0
- data/flows.gemspec +27 -2
- data/forspell.dict +17 -0
- data/lefthook.yml +21 -0
- data/lib/flows.rb +13 -5
- data/lib/flows/contract.rb +402 -0
- data/lib/flows/contract/array.rb +55 -0
- data/lib/flows/contract/case_eq.rb +43 -0
- data/lib/flows/contract/compose.rb +77 -0
- data/lib/flows/contract/either.rb +53 -0
- data/lib/flows/contract/error.rb +25 -0
- data/lib/flows/contract/hash.rb +75 -0
- data/lib/flows/contract/hash_of.rb +70 -0
- data/lib/flows/contract/helpers.rb +22 -0
- data/lib/flows/contract/predicate.rb +34 -0
- data/lib/flows/contract/transformer.rb +50 -0
- data/lib/flows/contract/tuple.rb +70 -0
- data/lib/flows/flow.rb +96 -7
- data/lib/flows/flow/errors.rb +29 -0
- data/lib/flows/flow/node.rb +132 -0
- data/lib/flows/flow/router.rb +29 -0
- data/lib/flows/flow/router/custom.rb +59 -0
- data/lib/flows/flow/router/errors.rb +11 -0
- data/lib/flows/flow/router/simple.rb +25 -0
- data/lib/flows/plugin.rb +14 -0
- data/lib/flows/plugin/dependency_injector.rb +159 -0
- data/lib/flows/plugin/dependency_injector/dependency.rb +24 -0
- data/lib/flows/plugin/dependency_injector/dependency_definition.rb +16 -0
- data/lib/flows/plugin/dependency_injector/dependency_list.rb +57 -0
- data/lib/flows/plugin/dependency_injector/errors.rb +58 -0
- data/lib/flows/plugin/implicit_init.rb +45 -0
- data/lib/flows/plugin/output_contract.rb +85 -0
- data/lib/flows/plugin/output_contract/dsl.rb +48 -0
- data/lib/flows/plugin/output_contract/errors.rb +74 -0
- data/lib/flows/plugin/output_contract/wrapper.rb +55 -0
- data/lib/flows/plugin/profiler.rb +114 -0
- data/lib/flows/plugin/profiler/injector.rb +35 -0
- data/lib/flows/plugin/profiler/report.rb +48 -0
- data/lib/flows/plugin/profiler/report/events.rb +43 -0
- data/lib/flows/plugin/profiler/report/flat.rb +41 -0
- data/lib/flows/plugin/profiler/report/flat/method_report.rb +81 -0
- data/lib/flows/plugin/profiler/report/raw.rb +15 -0
- data/lib/flows/plugin/profiler/report/tree.rb +98 -0
- data/lib/flows/plugin/profiler/report/tree/calculated_node.rb +116 -0
- data/lib/flows/plugin/profiler/report/tree/node.rb +35 -0
- data/lib/flows/plugin/profiler/wrapper.rb +53 -0
- data/lib/flows/railway.rb +154 -0
- data/lib/flows/railway/dsl.rb +18 -0
- data/lib/flows/railway/errors.rb +17 -0
- data/lib/flows/railway/step.rb +24 -0
- data/lib/flows/railway/step_list.rb +38 -0
- data/lib/flows/result.rb +189 -2
- data/lib/flows/result/do.rb +172 -0
- data/lib/flows/result/err.rb +12 -6
- data/lib/flows/result/errors.rb +29 -17
- data/lib/flows/result/helpers.rb +25 -3
- data/lib/flows/result/ok.rb +12 -6
- data/lib/flows/shared_context_pipeline.rb +299 -0
- data/lib/flows/shared_context_pipeline/dsl.rb +12 -0
- data/lib/flows/shared_context_pipeline/dsl/callbacks.rb +38 -0
- data/lib/flows/shared_context_pipeline/dsl/tracks.rb +52 -0
- data/lib/flows/shared_context_pipeline/errors.rb +17 -0
- data/lib/flows/shared_context_pipeline/mutation_step.rb +29 -0
- data/lib/flows/shared_context_pipeline/router_definition.rb +21 -0
- data/lib/flows/shared_context_pipeline/step.rb +44 -0
- data/lib/flows/shared_context_pipeline/track.rb +54 -0
- data/lib/flows/shared_context_pipeline/track_list.rb +51 -0
- data/lib/flows/shared_context_pipeline/wrap.rb +74 -0
- data/lib/flows/util.rb +17 -0
- data/lib/flows/util/inheritable_singleton_vars.rb +86 -0
- data/lib/flows/util/inheritable_singleton_vars/dup_strategy.rb +98 -0
- data/lib/flows/util/inheritable_singleton_vars/isolation_strategy.rb +91 -0
- data/lib/flows/util/prepend_to_class.rb +179 -0
- data/lib/flows/version.rb +1 -1
- metadata +288 -20
- data/.travis.yml +0 -8
- data/Gemfile.lock +0 -119
- data/bin/demo +0 -66
- data/bin/examples.rb +0 -159
- data/bin/profile_10steps +0 -64
- data/bin/ruby_benchmarks +0 -26
- data/lib/flows/node.rb +0 -27
- data/lib/flows/operation.rb +0 -54
- data/lib/flows/operation/builder.rb +0 -130
- data/lib/flows/operation/builder/build_router.rb +0 -37
- data/lib/flows/operation/dsl.rb +0 -72
- data/lib/flows/operation/errors.rb +0 -75
- data/lib/flows/operation/executor.rb +0 -78
- data/lib/flows/result_router.rb +0 -14
- data/lib/flows/router.rb +0 -22
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module Flows
|
|
2
|
+
module Plugin
|
|
3
|
+
module OutputContract
|
|
4
|
+
# DSL for OutputContract plugin.
|
|
5
|
+
module DSL
|
|
6
|
+
# Hash of contracts for successful results.
|
|
7
|
+
attr_reader :success_contracts
|
|
8
|
+
|
|
9
|
+
# Hash of contracts for failure results.
|
|
10
|
+
attr_reader :failure_contracts
|
|
11
|
+
|
|
12
|
+
# Is contract check and transformation disabled
|
|
13
|
+
attr_reader :skip_output_contract_flag
|
|
14
|
+
|
|
15
|
+
SingletonVarsSetup = Flows::Util::InheritableSingletonVars::DupStrategy.make_module(
|
|
16
|
+
'@success_contracts' => {},
|
|
17
|
+
'@failure_contracts' => {},
|
|
18
|
+
'@skip_output_contract_flag' => false
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
include SingletonVarsSetup
|
|
22
|
+
|
|
23
|
+
# Defines a contract for a successful result with specific status.
|
|
24
|
+
#
|
|
25
|
+
# @param status [Symbol] Corresponding result status.
|
|
26
|
+
# @param contract_block [Proc] This block will be passed to {Contract.make} to get a contract.
|
|
27
|
+
def success_with(status, &contract_block)
|
|
28
|
+
success_contracts[status] = Flows::Contract.make(&contract_block)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Defines a contract for a failure result with specific status.
|
|
32
|
+
#
|
|
33
|
+
# @param status [Symbol] Corresponding result status.
|
|
34
|
+
# @param contract_block [Proc] This block will be passed to {Contract.make} to get a contract.
|
|
35
|
+
def failure_with(status, &contract_block)
|
|
36
|
+
failure_contracts[status] = Flows::Contract.make(&contract_block)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Disables contract check and transformation for current class and children.
|
|
40
|
+
#
|
|
41
|
+
# @param enable [Boolean] if true - contracts are disabled
|
|
42
|
+
def skip_output_contract(enable = true)
|
|
43
|
+
@skip_output_contract_flag = enable
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
module Flows
|
|
2
|
+
module Plugin
|
|
3
|
+
module OutputContract
|
|
4
|
+
# Base error class for output contract errors.
|
|
5
|
+
class Error < StandardError; end
|
|
6
|
+
|
|
7
|
+
# Raised when no single contract for successful results is defined
|
|
8
|
+
class NoContractError < Error
|
|
9
|
+
def initialize(klass)
|
|
10
|
+
@klass = klass
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def message
|
|
14
|
+
"No single success contract defined for #{@klass}"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Raised when result's data violates contract
|
|
19
|
+
class ContractError < Error
|
|
20
|
+
def initialize(klass, result, error)
|
|
21
|
+
@klass = klass
|
|
22
|
+
@result = result
|
|
23
|
+
@error = error
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def message
|
|
27
|
+
shifted_error = @error.split("\n").map { |str| ' ' + str }.join("\n")
|
|
28
|
+
|
|
29
|
+
"Output contract for #{@klass} is violated.\n" \
|
|
30
|
+
"Result:\n" \
|
|
31
|
+
" `#{@result.inspect}`\n" \
|
|
32
|
+
"Contract Error:\n" \
|
|
33
|
+
"#{shifted_error}"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Raised when no contract found for result
|
|
38
|
+
class StatusError < Error
|
|
39
|
+
def initialize(klass, result, allowed_statuses)
|
|
40
|
+
@klass = klass
|
|
41
|
+
@result = result
|
|
42
|
+
@allowed_statuses = allowed_statuses
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def message
|
|
46
|
+
allowed_statuses_str = @allowed_statuses.map { |st| "`#{st.inspect}`" }.join(', ')
|
|
47
|
+
|
|
48
|
+
"Output contract for #{@klass} is violated.\n" \
|
|
49
|
+
"Result:\n" \
|
|
50
|
+
" `#{@result.inspect}`\n" \
|
|
51
|
+
"Contract Error:\n" \
|
|
52
|
+
" has unexpected status `#{@result.status.inspect}`\n" \
|
|
53
|
+
" allowed statuses for `#{@result.class}` are: #{allowed_statuses_str}"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Raised when not a result object returned
|
|
58
|
+
class ResultTypeError < Error
|
|
59
|
+
def initialize(klass, result)
|
|
60
|
+
@klass = klass
|
|
61
|
+
@result = result
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def message
|
|
65
|
+
"Output contract for #{@klass} is violated.\n" \
|
|
66
|
+
"Result:\n" \
|
|
67
|
+
" `#{@result.inspect}`\n" \
|
|
68
|
+
"Contract Error:\n" \
|
|
69
|
+
' result must be instance of `Flows::Result`'
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module Flows
|
|
2
|
+
module Plugin
|
|
3
|
+
module OutputContract
|
|
4
|
+
# Contains wrappers for initializer and `#call` methods.
|
|
5
|
+
#
|
|
6
|
+
# @api private
|
|
7
|
+
module Wrapper
|
|
8
|
+
def initialize(*args, &block)
|
|
9
|
+
super(*args, &block)
|
|
10
|
+
klass = self.class
|
|
11
|
+
raise NoContractError, klass if klass.success_contracts.empty? && !klass.skip_output_contract_flag
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call(*args, &block)
|
|
15
|
+
result = super(*args, &block)
|
|
16
|
+
klass = self.class
|
|
17
|
+
|
|
18
|
+
return result if klass.skip_output_contract_flag
|
|
19
|
+
|
|
20
|
+
Util.transform_result(klass, result)
|
|
21
|
+
|
|
22
|
+
result
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Helper methods for {Wrapper} are extracted to this
|
|
26
|
+
# module as singleton methods to not pollute user classes.
|
|
27
|
+
#
|
|
28
|
+
# @api private
|
|
29
|
+
module Util
|
|
30
|
+
class << self
|
|
31
|
+
def transform_result(klass, result)
|
|
32
|
+
contract = Util.contract_for(klass, result)
|
|
33
|
+
|
|
34
|
+
data = result.send(:data)
|
|
35
|
+
|
|
36
|
+
transformed_result = contract.transform(data)
|
|
37
|
+
raise ContractError.new(klass, result, transformed_result.error) if transformed_result.err?
|
|
38
|
+
|
|
39
|
+
result.send(:'data=', transformed_result.unwrap)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def contract_for(klass, result)
|
|
43
|
+
raise ResultTypeError.new(klass, result) unless result.is_a?(Flows::Result)
|
|
44
|
+
|
|
45
|
+
status = result.status
|
|
46
|
+
contracts = result.ok? ? klass.success_contracts : klass.failure_contracts
|
|
47
|
+
|
|
48
|
+
contracts[status] || raise(StatusError.new(klass, result, contracts.keys))
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
require_relative 'profiler/report'
|
|
2
|
+
require_relative 'profiler/injector'
|
|
3
|
+
require_relative 'profiler/wrapper'
|
|
4
|
+
|
|
5
|
+
module Flows
|
|
6
|
+
module Plugin
|
|
7
|
+
# Allows to record execution count and time of particular method on class or singleton class.
|
|
8
|
+
#
|
|
9
|
+
# Recorded data can be displayed in a different ways.
|
|
10
|
+
# See {Profiler::Report} implementations for possible options.
|
|
11
|
+
#
|
|
12
|
+
# To do a measurement you have call your classes inside {.profile} block.
|
|
13
|
+
#
|
|
14
|
+
# @note even without calling {.profile} using this module has some performance
|
|
15
|
+
# impact. Don't left this module used in production environments.
|
|
16
|
+
#
|
|
17
|
+
# @example
|
|
18
|
+
# class MyClass
|
|
19
|
+
# CallProfiler = Flows::Plugin::Profiler.for_method(:call)
|
|
20
|
+
# include CallProfiler
|
|
21
|
+
#
|
|
22
|
+
# def call(a, b)
|
|
23
|
+
# # some work here
|
|
24
|
+
# end
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# class AnotherClass
|
|
28
|
+
# CallProfiler = Flows::Plugin::Profiler.for_method(:perform)
|
|
29
|
+
# extend CallProfiler
|
|
30
|
+
#
|
|
31
|
+
# def self.perform(x)
|
|
32
|
+
# MyClass.new.call(x, x)
|
|
33
|
+
# end
|
|
34
|
+
# end
|
|
35
|
+
#
|
|
36
|
+
# last_result = Flows::Plugin::Profiler.profile do
|
|
37
|
+
# AnotherClass.perform(2)
|
|
38
|
+
# AnotherClass.perform(6)
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
# Profiler.last_report.to_a
|
|
42
|
+
# # => [
|
|
43
|
+
# # [:started, AnotherClass, :singleton, :perform, nil],
|
|
44
|
+
# # [:started, MyClass, :instance, :call, nil],
|
|
45
|
+
# # [:finished, MyClass, :instance, :call, 7.3],
|
|
46
|
+
# # [:finished, AnotherClass, :singleton, :perform, 10.5],
|
|
47
|
+
# # [:started, AnotherClass, :singleton, :perform, nil],
|
|
48
|
+
# # [:started, MyClass, :instance, :call, nil],
|
|
49
|
+
# # [:finished, MyClass, :instance, :call, 8.8],
|
|
50
|
+
# # [:finished, AnotherClass, :singleton, :perform, 14.2]
|
|
51
|
+
# # ]
|
|
52
|
+
module Profiler
|
|
53
|
+
THREAD_VAR_FLAG = :flows_profiler_flag
|
|
54
|
+
THREAD_VAR_REPORT = :flows_profiler_report
|
|
55
|
+
|
|
56
|
+
class << self
|
|
57
|
+
# Generates profiler module for a particular method.
|
|
58
|
+
#
|
|
59
|
+
# Use `include` for instance methods and `extend` for singleton ones.
|
|
60
|
+
#
|
|
61
|
+
# @param method_name [Symbol] method to wrap with profiling.
|
|
62
|
+
# @return [Module] module to include or extend.
|
|
63
|
+
def for_method(method_name)
|
|
64
|
+
Module.new.tap do |mod|
|
|
65
|
+
injector_mod = Injector.make_module(method_name)
|
|
66
|
+
mod.const_set(:Injector, injector_mod)
|
|
67
|
+
mod.extend injector_mod
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Profiles a block execution.
|
|
72
|
+
#
|
|
73
|
+
# @param report [Report, Symbol]
|
|
74
|
+
# desired {Report} to be used.
|
|
75
|
+
# In case of symbol `:some_name` the `Flows::Plugin::Profiler::Report::SomeName.new` will be used.
|
|
76
|
+
# @yield code to profile
|
|
77
|
+
# @return block result
|
|
78
|
+
def profile(report = :raw)
|
|
79
|
+
thread = Thread.current
|
|
80
|
+
|
|
81
|
+
thread[THREAD_VAR_FLAG] = true
|
|
82
|
+
thread[THREAD_VAR_REPORT] = make_report(report)
|
|
83
|
+
|
|
84
|
+
yield
|
|
85
|
+
ensure
|
|
86
|
+
thread[THREAD_VAR_FLAG] = false
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Resets thread-local variables used for reporting.
|
|
90
|
+
def reset
|
|
91
|
+
thread = Thread.current
|
|
92
|
+
thread[THREAD_VAR_FLAG] = false
|
|
93
|
+
thread[THREAD_VAR_REPORT] = nil
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# @return [Report, nil] last generated report if some.
|
|
97
|
+
def last_report
|
|
98
|
+
Thread.current[THREAD_VAR_REPORT]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
def make_report(report_or_sym)
|
|
104
|
+
case report_or_sym
|
|
105
|
+
when Report then report_or_sym
|
|
106
|
+
when Symbol
|
|
107
|
+
const_name = report_or_sym.to_s.split('_').map(&:capitalize).join
|
|
108
|
+
Report.const_get(const_name).new
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Flows
|
|
2
|
+
module Plugin
|
|
3
|
+
module Profiler
|
|
4
|
+
# @api private
|
|
5
|
+
module Injector
|
|
6
|
+
class << self
|
|
7
|
+
def make_module(method_name)
|
|
8
|
+
Module.new.tap do |mod|
|
|
9
|
+
add_included(mod, method_name)
|
|
10
|
+
add_extended(mod, method_name)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def add_included(mod, method_name)
|
|
17
|
+
mod.define_method(:included) do |target|
|
|
18
|
+
raise 'must be included into class' unless target.is_a?(Class)
|
|
19
|
+
|
|
20
|
+
target.prepend Wrapper.make_instance_wrapper(method_name)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def add_extended(mod, method_name)
|
|
25
|
+
mod.define_method(:extended) do |target|
|
|
26
|
+
raise 'must be extended into class' unless target.is_a?(Class)
|
|
27
|
+
|
|
28
|
+
target.singleton_class.prepend(Wrapper.make_singleton_wrapper(method_name))
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require_relative 'report/events'
|
|
2
|
+
require_relative 'report/raw'
|
|
3
|
+
require_relative 'report/tree'
|
|
4
|
+
require_relative 'report/flat'
|
|
5
|
+
|
|
6
|
+
module Flows
|
|
7
|
+
module Plugin
|
|
8
|
+
module Profiler
|
|
9
|
+
# Base class for {Profiler} reports.
|
|
10
|
+
#
|
|
11
|
+
# @!method to_s
|
|
12
|
+
# @abstract
|
|
13
|
+
# @return [String] human-readable representation.
|
|
14
|
+
class Report
|
|
15
|
+
# @return [Array<Array>] raw profiler events
|
|
16
|
+
attr_reader :raw_data
|
|
17
|
+
|
|
18
|
+
def initialize
|
|
19
|
+
@raw_data = []
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Add event to profile report.
|
|
23
|
+
#
|
|
24
|
+
# @param event_type [:started, :finished] event type
|
|
25
|
+
# @param klass [Class] class where called method is placed
|
|
26
|
+
# @param method_type [:instance, :singleton] method type
|
|
27
|
+
# @param method_name [Symbol] name of the called method
|
|
28
|
+
# @param data [nil, Float] event data, time represented as
|
|
29
|
+
# a Float microseconds value.
|
|
30
|
+
def add(*args)
|
|
31
|
+
raw_data << args
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @return [Array<Event>] array of events
|
|
35
|
+
def events
|
|
36
|
+
raw_data.map do |raw_event|
|
|
37
|
+
klass = case raw_event.first
|
|
38
|
+
when :started then StartEvent
|
|
39
|
+
when :finished then FinishEvent
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
klass.new(*raw_event[1..-1])
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Flows
|
|
2
|
+
module Plugin
|
|
3
|
+
module Profiler
|
|
4
|
+
class Report
|
|
5
|
+
# @api private
|
|
6
|
+
Event = Struct.new(:method_class, :method_type, :method_name, :data) do
|
|
7
|
+
def subject
|
|
8
|
+
"#{method_class}#{delimeter}#{method_name}"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def delimeter
|
|
14
|
+
case method_type
|
|
15
|
+
when :instance then '#'
|
|
16
|
+
when :singleton then '.'
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @api private
|
|
22
|
+
#
|
|
23
|
+
# Method execution start event.
|
|
24
|
+
class StartEvent < Event
|
|
25
|
+
def to_s
|
|
26
|
+
"start: #{subject}"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @api private
|
|
31
|
+
#
|
|
32
|
+
# Method execution finish event.
|
|
33
|
+
#
|
|
34
|
+
# Data is an execution time in microseconds.
|
|
35
|
+
class FinishEvent < Event
|
|
36
|
+
def to_s
|
|
37
|
+
"finish(#{data} microseconds): #{subject}"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require_relative 'flat/method_report'
|
|
2
|
+
|
|
3
|
+
module Flows
|
|
4
|
+
module Plugin
|
|
5
|
+
module Profiler
|
|
6
|
+
class Report
|
|
7
|
+
# Flat report. Merges similar calls, hides execution structure.
|
|
8
|
+
#
|
|
9
|
+
# It's a variation of a {Rport::Tree} where all calls of the same method
|
|
10
|
+
# are combined into a one first-level entry.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# Flows::Plugin::Profiler.profile(:flat) do
|
|
14
|
+
# # some code here
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# puts Flows::Plugin::Profiler.last_report
|
|
18
|
+
class Flat < Tree
|
|
19
|
+
def to_a
|
|
20
|
+
method_reports.map(&:to_h)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def to_s
|
|
24
|
+
method_reports.map(&:to_s).join("\n")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def method_reports
|
|
30
|
+
@method_reports ||= root_calculated_node
|
|
31
|
+
.group_by_subject
|
|
32
|
+
.values
|
|
33
|
+
.map { |nodes| MethodReport.new(root_calculated_node, *nodes) }
|
|
34
|
+
.sort_by(&:total_self_ms)
|
|
35
|
+
.reverse
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|