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,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
         | 
| @@ -0,0 +1,80 @@ | |
| 1 | 
            +
            module Flows
         | 
| 2 | 
            +
              module Plugin
         | 
| 3 | 
            +
                module Profiler
         | 
| 4 | 
            +
                  class Report
         | 
| 5 | 
            +
                    class Flat < Tree
         | 
| 6 | 
            +
                      # @api private
         | 
| 7 | 
            +
                      class MethodReport
         | 
| 8 | 
            +
                        attr_reader :root_node, :calculated_nodes
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                        def initialize(root_node, *calculated_nodes)
         | 
| 11 | 
            +
                          @root_node = root_node
         | 
| 12 | 
            +
                          @calculated_nodes = calculated_nodes
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                          raise 'no single node provided' if calculated_nodes.empty?
         | 
| 15 | 
            +
                          raise 'calculated_nodes must be about the same subject' unless nodes_have_same_subject
         | 
| 16 | 
            +
                        end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                        def subject
         | 
| 19 | 
            +
                          @subject ||= calculated_nodes.first.subject
         | 
| 20 | 
            +
                        end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                        def count
         | 
| 23 | 
            +
                          @count ||= calculated_nodes.map(&:count).sum
         | 
| 24 | 
            +
                        end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                        def total_self_ms
         | 
| 27 | 
            +
                          @total_self_ms ||= calculated_nodes.map(&:total_self_ms).sort.sum
         | 
| 28 | 
            +
                        end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                        def total_self_percentage
         | 
| 31 | 
            +
                          @total_self_percentage ||= calculated_nodes
         | 
| 32 | 
            +
                                                     .map { |node| node.total_self_percentage(root_node) }
         | 
| 33 | 
            +
                                                     .sort
         | 
| 34 | 
            +
                                                     .sum
         | 
| 35 | 
            +
                        end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                        def avg_self_ms
         | 
| 38 | 
            +
                          @avg_self_ms ||= total_self_ms / count
         | 
| 39 | 
            +
                        end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                        def direct_subcalls
         | 
| 42 | 
            +
                          @direct_subcalls ||= calculated_nodes
         | 
| 43 | 
            +
                                               .flat_map { |node| node.children.map(&:subject) }
         | 
| 44 | 
            +
                                               .uniq
         | 
| 45 | 
            +
                        end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                        def to_h
         | 
| 48 | 
            +
                          @to_h ||= {
         | 
| 49 | 
            +
                            subject: subject,
         | 
| 50 | 
            +
                            count: count,
         | 
| 51 | 
            +
                            total_self_ms: total_self_ms,
         | 
| 52 | 
            +
                            total_self_percentage: total_self_percentage,
         | 
| 53 | 
            +
                            avg_self_ms: avg_self_ms,
         | 
| 54 | 
            +
                            direct_subcalls: direct_subcalls
         | 
| 55 | 
            +
                          }
         | 
| 56 | 
            +
                        end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                        def to_s
         | 
| 59 | 
            +
                          [
         | 
| 60 | 
            +
                            '',
         | 
| 61 | 
            +
                            "- #{subject} -",
         | 
| 62 | 
            +
                            "called:                      #{count} time(s)",
         | 
| 63 | 
            +
                            "total self execution time:   #{total_self_ms.truncate(2)}ms",
         | 
| 64 | 
            +
                            "total self percentage:       #{total_self_percentage.truncate(2)}%",
         | 
| 65 | 
            +
                            "average self execution time: #{avg_self_ms.truncate(2)}ms",
         | 
| 66 | 
            +
                            "direct subcalls:             #{direct_subcalls.join(', ')}"
         | 
| 67 | 
            +
                          ]
         | 
| 68 | 
            +
                        end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                        private
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                        def nodes_have_same_subject
         | 
| 73 | 
            +
                          calculated_nodes.all? { |node| node.subject == subject }
         | 
| 74 | 
            +
                        end
         | 
| 75 | 
            +
                      end
         | 
| 76 | 
            +
                    end
         | 
| 77 | 
            +
                  end
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
              end
         | 
| 80 | 
            +
            end
         | 
| @@ -0,0 +1,98 @@ | |
| 1 | 
            +
            require_relative 'tree/node'
         | 
| 2 | 
            +
            require_relative 'tree/calculated_node'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Flows
         | 
| 5 | 
            +
              module Plugin
         | 
| 6 | 
            +
                module Profiler
         | 
| 7 | 
            +
                  class Report
         | 
| 8 | 
            +
                    # Tree report. Merges similar calls, saves execution structure (who called whom).
         | 
| 9 | 
            +
                    #
         | 
| 10 | 
            +
                    # @example
         | 
| 11 | 
            +
                    #     Flows::Plugin::Profiler.profile(:tree) do
         | 
| 12 | 
            +
                    #       # some code here
         | 
| 13 | 
            +
                    #     end
         | 
| 14 | 
            +
                    #
         | 
| 15 | 
            +
                    #     puts Flows::Plugin::Profiler.last_report
         | 
| 16 | 
            +
                    class Tree < Report
         | 
| 17 | 
            +
                      # Returns tree report as Ruby data structs.
         | 
| 18 | 
            +
                      #
         | 
| 19 | 
            +
                      # @return [Array<Hash>] tree report.
         | 
| 20 | 
            +
                      #
         | 
| 21 | 
            +
                      # @example
         | 
| 22 | 
            +
                      #   [
         | 
| 23 | 
            +
                      #     {
         | 
| 24 | 
            +
                      #       subject: 'MyClass#call',
         | 
| 25 | 
            +
                      #       count: 2,
         | 
| 26 | 
            +
                      #       total_ms: 100.0,
         | 
| 27 | 
            +
                      #       total_self_ms: 80.0,
         | 
| 28 | 
            +
                      #       total_self_percentage: 80.0,
         | 
| 29 | 
            +
                      #       avg_ms: 50.0,
         | 
| 30 | 
            +
                      #       avg_self_ms: 40.0,
         | 
| 31 | 
            +
                      #       nested: [
         | 
| 32 | 
            +
                      #         {
         | 
| 33 | 
            +
                      #           subject: 'MyClass#another_method',
         | 
| 34 | 
            +
                      #           count: 1,
         | 
| 35 | 
            +
                      #           total_ms: 20.0,
         | 
| 36 | 
            +
                      #           total_self_ms: 20.0,
         | 
| 37 | 
            +
                      #           total_self_percentage: 20.0,
         | 
| 38 | 
            +
                      #           avg_ms: 20.0,
         | 
| 39 | 
            +
                      #           avg_self_ms: 20.0,
         | 
| 40 | 
            +
                      #           nested: []
         | 
| 41 | 
            +
                      #         }
         | 
| 42 | 
            +
                      #       ]
         | 
| 43 | 
            +
                      #     }
         | 
| 44 | 
            +
                      #   ]
         | 
| 45 | 
            +
                      def to_a
         | 
| 46 | 
            +
                        root_calculated_node.children.map { |node| node.to_h(root_calculated_node) }
         | 
| 47 | 
            +
                      end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                      def add(*)
         | 
| 50 | 
            +
                        forget_memoized_values
         | 
| 51 | 
            +
                        super
         | 
| 52 | 
            +
                      end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                      def to_s
         | 
| 55 | 
            +
                        root_calculated_node.children.map { |node| node.to_s(root_calculated_node) }.join
         | 
| 56 | 
            +
                      end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                      private
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                      def forget_memoized_values
         | 
| 61 | 
            +
                        @root_node = nil
         | 
| 62 | 
            +
                        @root_calculated_node = nil
         | 
| 63 | 
            +
                      end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                      def root_calculated_node
         | 
| 66 | 
            +
                        @root_calculated_node ||= CalculatedNode.new(root_node)
         | 
| 67 | 
            +
                      end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                      def root_node
         | 
| 70 | 
            +
                        @root_node ||= Node.new(subject: :ROOT).tap do |root_node|
         | 
| 71 | 
            +
                          events.each_with_object([root_node]) do |event, node_path|
         | 
| 72 | 
            +
                            current_node = node_path.last
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                            case event
         | 
| 75 | 
            +
                            when StartEvent then process_start_event(node_path, current_node, event)
         | 
| 76 | 
            +
                            when FinishEvent then process_finish_event(node_path, current_node, event)
         | 
| 77 | 
            +
                            end
         | 
| 78 | 
            +
                          end
         | 
| 79 | 
            +
                        end
         | 
| 80 | 
            +
                      end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                      # :reek:UtilityFunction
         | 
| 83 | 
            +
                      def process_start_event(node_path, current_node, event)
         | 
| 84 | 
            +
                        node_path << current_node[event.subject]
         | 
| 85 | 
            +
                      end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                      # :reek:UtilityFunction :reek:FeatureEnvy
         | 
| 88 | 
            +
                      def process_finish_event(node_path, current_node, event)
         | 
| 89 | 
            +
                        raise 'Invalid profiling events detected' if event.subject != current_node.subject
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                        current_node.register_execution(event.data)
         | 
| 92 | 
            +
                        node_path.pop
         | 
| 93 | 
            +
                      end
         | 
| 94 | 
            +
                    end
         | 
| 95 | 
            +
                  end
         | 
| 96 | 
            +
                end
         | 
| 97 | 
            +
              end
         | 
| 98 | 
            +
            end
         | 
| @@ -0,0 +1,116 @@ | |
| 1 | 
            +
            module Flows
         | 
| 2 | 
            +
              module Plugin
         | 
| 3 | 
            +
                module Profiler
         | 
| 4 | 
            +
                  class Report
         | 
| 5 | 
            +
                    class Tree < Report
         | 
| 6 | 
            +
                      # @api private
         | 
| 7 | 
            +
                      class CalculatedNode
         | 
| 8 | 
            +
                        MICROSECONDS_IN_MILLISECOND = 1000.0
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                        attr_reader :children
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                        def initialize(node)
         | 
| 13 | 
            +
                          @node = node
         | 
| 14 | 
            +
                          @children = node.children
         | 
| 15 | 
            +
                                          .map { |child| self.class.new(child) }
         | 
| 16 | 
            +
                                          .sort_by(&:total_ms)
         | 
| 17 | 
            +
                                          .reverse
         | 
| 18 | 
            +
                        end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                        def subject
         | 
| 21 | 
            +
                          @node.subject
         | 
| 22 | 
            +
                        end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                        def count
         | 
| 25 | 
            +
                          @count ||= @node.executions.count
         | 
| 26 | 
            +
                        end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                        def total_ms
         | 
| 29 | 
            +
                          @total_ms ||= @node.executions.sort.sum / MICROSECONDS_IN_MILLISECOND
         | 
| 30 | 
            +
                        end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                        def avg_ms
         | 
| 33 | 
            +
                          @avg_ms ||= total_ms / count
         | 
| 34 | 
            +
                        end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                        def children_ms
         | 
| 37 | 
            +
                          @children_ms ||= children.map(&:total_ms).sort.sum
         | 
| 38 | 
            +
                        end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                        def total_self_ms
         | 
| 41 | 
            +
                          @total_self_ms ||= total_ms - children_ms
         | 
| 42 | 
            +
                        end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                        def total_self_percentage(root_node = self)
         | 
| 45 | 
            +
                          @total_self_percentage ||= total_self_ms / root_node.children_ms * 100.0
         | 
| 46 | 
            +
                        end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                        def total_percentage(root_node = self)
         | 
| 49 | 
            +
                          @total_percentage ||= total_ms / root_node.children_ms * 100.0
         | 
| 50 | 
            +
                        end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                        def avg_self_ms
         | 
| 53 | 
            +
                          @avg_self_ms ||= total_self_ms / count
         | 
| 54 | 
            +
                        end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                        def to_h(root_node = self) # rubocop:disable Metrics/MethodLength
         | 
| 57 | 
            +
                          @to_h ||= {
         | 
| 58 | 
            +
                            subject: subject,
         | 
| 59 | 
            +
                            count: count,
         | 
| 60 | 
            +
                            total_ms: total_ms,
         | 
| 61 | 
            +
                            total_percentage: total_percentage(root_node),
         | 
| 62 | 
            +
                            total_self_ms: total_self_ms,
         | 
| 63 | 
            +
                            total_self_percentage: total_self_percentage(root_node),
         | 
| 64 | 
            +
                            avg_ms: avg_ms,
         | 
| 65 | 
            +
                            avg_self_ms: avg_self_ms,
         | 
| 66 | 
            +
                            nested: children.map { |node| node.to_h(root_node) }
         | 
| 67 | 
            +
                          }
         | 
| 68 | 
            +
                        end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                        def to_s(root_node = self)
         | 
| 71 | 
            +
                          @to_s ||= (base_text_list(root_node) + childeren_text_list(root_node)).join("\n")
         | 
| 72 | 
            +
                        end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                        # :reek:DuplicateMethodCall
         | 
| 75 | 
            +
                        # :reek:NestedIterators
         | 
| 76 | 
            +
                        def group_by_subject
         | 
| 77 | 
            +
                          @group_by_subject ||= (
         | 
| 78 | 
            +
                            [children.group_by(&:subject)] + children.map(&:group_by_subject)
         | 
| 79 | 
            +
                          ).each_with_object({}) do |group, result|
         | 
| 80 | 
            +
                            group.each do |subject, nodes|
         | 
| 81 | 
            +
                              result[subject] ||= []
         | 
| 82 | 
            +
                              result[subject] += nodes
         | 
| 83 | 
            +
                            end
         | 
| 84 | 
            +
                          end
         | 
| 85 | 
            +
                        end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                        private
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                        def base_text_list(root_node) # rubocop:disable Metrics/MethodLength
         | 
| 90 | 
            +
                          [
         | 
| 91 | 
            +
                            '',
         | 
| 92 | 
            +
                            "- #{subject} -",
         | 
| 93 | 
            +
                            "called:                      #{count} time(s)",
         | 
| 94 | 
            +
                            "total execution time:        #{total_ms.truncate(2)}ms",
         | 
| 95 | 
            +
                            "total percentage:            #{total_percentage(root_node).truncate(2)}%",
         | 
| 96 | 
            +
                            "total self execution time:   #{total_self_ms.truncate(2)}ms",
         | 
| 97 | 
            +
                            "total self percentage:       #{total_self_percentage(root_node).truncate(2)}%",
         | 
| 98 | 
            +
                            "average execution time:      #{avg_ms.truncate(2)}ms",
         | 
| 99 | 
            +
                            "average self execution time: #{avg_self_ms.truncate(2)}ms"
         | 
| 100 | 
            +
                          ]
         | 
| 101 | 
            +
                        end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                        def childeren_text_list(root_node)
         | 
| 104 | 
            +
                          return [] if @children.empty?
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                          children.map { |node| node.to_s(root_node) }
         | 
| 107 | 
            +
                                  .join("\n")
         | 
| 108 | 
            +
                                  .split("\n")
         | 
| 109 | 
            +
                                  .map { |str| "|    #{str}" }
         | 
| 110 | 
            +
                        end
         | 
| 111 | 
            +
                      end
         | 
| 112 | 
            +
                    end
         | 
| 113 | 
            +
                  end
         | 
| 114 | 
            +
                end
         | 
| 115 | 
            +
              end
         | 
| 116 | 
            +
            end
         | 
| @@ -0,0 +1,34 @@ | |
| 1 | 
            +
            module Flows
         | 
| 2 | 
            +
              module Plugin
         | 
| 3 | 
            +
                module Profiler
         | 
| 4 | 
            +
                  class Report
         | 
| 5 | 
            +
                    class Tree < Report
         | 
| 6 | 
            +
                      # @api private
         | 
| 7 | 
            +
                      class Node
         | 
| 8 | 
            +
                        attr_reader :subject, :executions
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                        def initialize(subject:)
         | 
| 11 | 
            +
                          @subject = subject
         | 
| 12 | 
            +
                          @children = {}
         | 
| 13 | 
            +
                          @cache = {}
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                          @executions = []
         | 
| 16 | 
            +
                        end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                        def [](subject)
         | 
| 19 | 
            +
                          @children[subject] ||= Node.new(subject: subject)
         | 
| 20 | 
            +
                        end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                        def children
         | 
| 23 | 
            +
                          @children.values
         | 
| 24 | 
            +
                        end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                        def register_execution(microseconds)
         | 
| 27 | 
            +
                          @executions << microseconds
         | 
| 28 | 
            +
                        end
         | 
| 29 | 
            +
                      end
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
            end
         | 
| @@ -0,0 +1,53 @@ | |
| 1 | 
            +
            module Flows
         | 
| 2 | 
            +
              module Plugin
         | 
| 3 | 
            +
                module Profiler
         | 
| 4 | 
            +
                  # @api private
         | 
| 5 | 
            +
                  module Wrapper
         | 
| 6 | 
            +
                    class << self
         | 
| 7 | 
            +
                      def make_instance_wrapper(method_name) # rubocop:disable Metrics/MethodLength
         | 
| 8 | 
            +
                        Module.new.tap do |mod|
         | 
| 9 | 
            +
                          mod.define_method(method_name) do |*args, &block| # rubocop:disable Metrics/MethodLength
         | 
| 10 | 
            +
                            thread = Thread.current
         | 
| 11 | 
            +
                            klass = self.class
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                            return super(*args, &block) unless thread[THREAD_VAR_FLAG]
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                            report = thread[THREAD_VAR_REPORT]
         | 
| 16 | 
            +
                            report.add(:started, klass, :instance, method_name, nil)
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                            before = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_microsecond)
         | 
| 19 | 
            +
                            super(*args, &block)
         | 
| 20 | 
            +
                          ensure
         | 
| 21 | 
            +
                            if before
         | 
| 22 | 
            +
                              after = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_microsecond)
         | 
| 23 | 
            +
                              report.add(:finished, klass, :instance, method_name, after - before)
         | 
| 24 | 
            +
                            end
         | 
| 25 | 
            +
                          end
         | 
| 26 | 
            +
                        end
         | 
| 27 | 
            +
                      end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                      def make_singleton_wrapper(method_name) # rubocop:disable Metrics/MethodLength
         | 
| 30 | 
            +
                        Module.new.tap do |mod|
         | 
| 31 | 
            +
                          mod.define_method(method_name) do |*args, &block| # rubocop:disable Metrics/MethodLength
         | 
| 32 | 
            +
                            thread = Thread.current
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                            return super(*args, &block) unless thread[THREAD_VAR_FLAG]
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                            report = thread[THREAD_VAR_REPORT]
         | 
| 37 | 
            +
                            report.add(:started, self, :singleton, method_name, nil)
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                            before = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_microsecond)
         | 
| 40 | 
            +
                            super(*args, &block)
         | 
| 41 | 
            +
                          ensure
         | 
| 42 | 
            +
                            if before
         | 
| 43 | 
            +
                              after = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_microsecond)
         | 
| 44 | 
            +
                              report.add(:finished, self, :singleton, method_name, after - before)
         | 
| 45 | 
            +
                            end
         | 
| 46 | 
            +
                          end
         | 
| 47 | 
            +
                        end
         | 
| 48 | 
            +
                      end
         | 
| 49 | 
            +
                    end
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
            end
         |