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