flows 0.1.0 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|