flows 0.2.0 → 0.6.0
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/{build.yml → test.yml} +5 -10
- data/.gitignore +9 -1
- data/.mdlrc +1 -1
- data/.reek.yml +54 -0
- data/.rubocop.yml +26 -7
- data/.rubocop_todo.yml +27 -0
- data/.ruby-version +1 -1
- data/.yardopts +1 -0
- data/CHANGELOG.md +81 -0
- data/Gemfile +0 -6
- data/README.md +167 -363
- data/Rakefile +35 -1
- data/bin/.rubocop.yml +5 -0
- data/bin/all_the_errors +55 -0
- data/bin/benchmark +73 -105
- 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 +22 -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 +138 -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/interface_error_demo.rb +17 -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/README.md +3 -187
- data/docs/_sidebar.md +0 -24
- data/docs/index.html +1 -1
- data/flows.gemspec +27 -2
- data/forspell.dict +9 -0
- data/lefthook.yml +9 -0
- data/lib/flows.rb +11 -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 +24 -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 +15 -0
- data/lib/flows/plugin/dependency_injector.rb +170 -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 +55 -0
- data/lib/flows/plugin/dependency_injector/errors.rb +58 -0
- data/lib/flows/plugin/implicit_init.rb +45 -0
- data/lib/flows/plugin/interface.rb +84 -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 +80 -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 +34 -0
- data/lib/flows/plugin/profiler/wrapper.rb +53 -0
- data/lib/flows/railway.rb +140 -34
- data/lib/flows/railway/dsl.rb +8 -18
- data/lib/flows/railway/errors.rb +8 -12
- data/lib/flows/railway/step.rb +24 -0
- data/lib/flows/railway/step_list.rb +38 -0
- data/lib/flows/result.rb +188 -2
- data/lib/flows/result/do.rb +158 -16
- 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 +342 -0
- data/lib/flows/shared_context_pipeline/dsl.rb +12 -0
- data/lib/flows/shared_context_pipeline/dsl/callbacks.rb +35 -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 +30 -0
- data/lib/flows/shared_context_pipeline/router_definition.rb +21 -0
- data/lib/flows/shared_context_pipeline/step.rb +55 -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 +73 -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 +100 -0
- data/lib/flows/util/inheritable_singleton_vars/isolation_strategy.rb +91 -0
- data/lib/flows/util/prepend_to_class.rb +191 -0
- data/lib/flows/version.rb +1 -1
- metadata +253 -38
- data/Gemfile.lock +0 -174
- data/bin/demo +0 -66
- data/bin/examples.rb +0 -195
- data/bin/profile_10steps +0 -106
- data/bin/ruby_benchmarks +0 -26
- data/docs/CNAME +0 -1
- data/docs/contributing/benchmarks_profiling.md +0 -3
- data/docs/contributing/local_development.md +0 -3
- data/docs/flow/direct_usage.md +0 -3
- data/docs/flow/general_idea.md +0 -3
- data/docs/operation/basic_usage.md +0 -1
- data/docs/operation/inject_steps.md +0 -3
- data/docs/operation/lambda_steps.md +0 -3
- data/docs/operation/result_shapes.md +0 -3
- data/docs/operation/routing_tracks.md +0 -3
- data/docs/operation/wrapping_steps.md +0 -3
- data/docs/overview/performance.md +0 -336
- data/docs/railway/basic_usage.md +0 -232
- data/docs/result_objects/basic_usage.md +0 -196
- data/docs/result_objects/do_notation.md +0 -139
- data/lib/flows/node.rb +0 -27
- data/lib/flows/operation.rb +0 -52
- 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 -93
- data/lib/flows/operation/errors.rb +0 -75
- data/lib/flows/operation/executor.rb +0 -78
- data/lib/flows/railway/builder.rb +0 -68
- data/lib/flows/railway/executor.rb +0 -23
- data/lib/flows/result_router.rb +0 -14
- data/lib/flows/router.rb +0 -22
@@ -0,0 +1,85 @@
|
|
1
|
+
require_relative 'output_contract/errors'
|
2
|
+
require_relative 'output_contract/dsl'
|
3
|
+
require_relative 'output_contract/wrapper'
|
4
|
+
|
5
|
+
module Flows
|
6
|
+
module Plugin
|
7
|
+
# Allows to make a contract check and transformation for `#call` method execution in any class.
|
8
|
+
#
|
9
|
+
# Plugin applies a wrapper to a `#call` instance method.
|
10
|
+
# This wrapper will do the following:
|
11
|
+
#
|
12
|
+
# * check that {Result} instance is returned by `#call`
|
13
|
+
# * check that returned {Result#status} is expected
|
14
|
+
# * check that returned result data conforms {Contract} assigned
|
15
|
+
# to a particular result type and status
|
16
|
+
# * applies contract transform to the returned data
|
17
|
+
# * returns {Result} with the same status and type,
|
18
|
+
# wraps transformed data inside.
|
19
|
+
#
|
20
|
+
# Plugin provides DSL to express expected result statuses and assigned contracts.
|
21
|
+
# Contracts definition reuses {Contract.make} to execute block and get a contract.
|
22
|
+
#
|
23
|
+
# * `success_with(status, &block)` - defines contract for a successful result with status `status`.
|
24
|
+
# * `failure_with(status, &block)` - defines contract for a failure result with status `status`.
|
25
|
+
# * `skip_output_contract` - disables contract check and transformation for current class and children.
|
26
|
+
#
|
27
|
+
# @example with one possible output contract
|
28
|
+
# class DoJob
|
29
|
+
# include Flows::Result::Helpers
|
30
|
+
# include Flows::Plugin::OutputContract
|
31
|
+
#
|
32
|
+
# success_with :ok do
|
33
|
+
# Integer
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# def call(a, b)
|
37
|
+
# ok_data(a + b)
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# DoJob.new.call(1, 2).unwrap
|
42
|
+
# # => 3
|
43
|
+
#
|
44
|
+
# DoJob.new.call('a', 'b')
|
45
|
+
# # Flows::Contract::Error exception raised
|
46
|
+
#
|
47
|
+
# @example with multiple contracts
|
48
|
+
# class DoJob
|
49
|
+
# include Flows::Result::Helpers
|
50
|
+
# include Flows::Plugin::OutputContract
|
51
|
+
#
|
52
|
+
# success_with :int_sum do
|
53
|
+
# Integer
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# success_with :float_sum do
|
57
|
+
# Float
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# failure_with :err do
|
61
|
+
# hash_of(
|
62
|
+
# key: Symbol,
|
63
|
+
# msg: String
|
64
|
+
# )
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# def call(a, b)
|
68
|
+
# if a.is_a?(Float) || b.is_a?(Float)
|
69
|
+
# ok_data(a + b, status: :float_sum)
|
70
|
+
# elsif a.is_a?(Integer) && b.is_a?(Integer)
|
71
|
+
# ok_data(a + b, status: :int_sum)
|
72
|
+
# else
|
73
|
+
# err(key: :unexpected_type, msg: "Unexpected argument types")
|
74
|
+
# end
|
75
|
+
# end
|
76
|
+
# end
|
77
|
+
module OutputContract
|
78
|
+
# @api private
|
79
|
+
def self.included(mod)
|
80
|
+
mod.extend(DSL)
|
81
|
+
mod.prepend(Wrapper)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -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
|