delfos 0.0.1.pre.rc1 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/lib/delfos.rb +22 -78
  3. data/lib/delfos/call_stack.rb +39 -0
  4. data/lib/delfos/call_stack/stack.rb +59 -0
  5. data/lib/delfos/file_system/common_path.rb +62 -0
  6. data/lib/delfos/{distance/calculation.rb → file_system/distance_calculation.rb} +4 -15
  7. data/lib/delfos/file_system/path_determination.rb +37 -0
  8. data/lib/delfos/{distance → file_system}/relation.rb +1 -1
  9. data/lib/delfos/method_logging.rb +32 -49
  10. data/lib/delfos/method_logging/call_site_parsing.rb +74 -0
  11. data/lib/delfos/method_logging/code_location.rb +34 -98
  12. data/lib/delfos/method_logging/{args.rb → method_parameters.rb} +17 -18
  13. data/lib/delfos/neo4j.rb +58 -0
  14. data/lib/delfos/neo4j/batch/execution.rb +149 -0
  15. data/lib/delfos/neo4j/{execution_persistence.rb → call_stack_query.rb} +12 -22
  16. data/lib/delfos/neo4j/distance/call_site_fetcher.rb +19 -0
  17. data/lib/delfos/neo4j/distance/update.rb +50 -0
  18. data/lib/delfos/neo4j/informer.rb +62 -34
  19. data/lib/delfos/neo4j/query_execution/errors.rb +30 -0
  20. data/lib/delfos/neo4j/query_execution/http.rb +61 -0
  21. data/lib/delfos/neo4j/query_execution/http_query.rb +67 -0
  22. data/lib/delfos/neo4j/query_execution/sync.rb +35 -0
  23. data/lib/delfos/neo4j/query_execution/transactional.rb +68 -0
  24. data/lib/delfos/neo4j/schema.rb +73 -0
  25. data/lib/delfos/patching/basic_object.rb +1 -5
  26. data/lib/delfos/patching/method_cache.rb +83 -0
  27. data/lib/delfos/patching/method_override.rb +76 -66
  28. data/lib/delfos/patching/module_defining_methods.rb +105 -0
  29. data/lib/delfos/patching/unstubber.rb +34 -0
  30. data/lib/delfos/setup.rb +88 -0
  31. metadata +42 -15
  32. data/lib/delfos/common_path.rb +0 -58
  33. data/lib/delfos/execution_chain.rb +0 -75
  34. data/lib/delfos/method_logging/added_methods.rb +0 -67
  35. data/lib/delfos/neo4j/distance_update.rb +0 -73
  36. data/lib/delfos/neo4j/query_execution.rb +0 -79
  37. data/lib/delfos/patching/unstubbing_spec_helper.rb +0 -58
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+ require "binding_of_caller"
3
+
4
+ module Delfos
5
+ module MethodLogging
6
+ class CallSiteParsing
7
+ # This magic number is based on the implementation within this file.
8
+ # If the line with `call_site_binding.of_caller(stack_index + STACK_OFFSET).receiver`
9
+ # is moved up or down the call stack a test fails and we have to change `STACK_OFFSET`
10
+ STACK_OFFSET = 5
11
+
12
+ attr_reader :stack, :call_site_binding
13
+
14
+ def initialize(stack, call_site_binding, stack_offset: nil)
15
+ @stack = stack
16
+ @call_site_binding = call_site_binding
17
+ @stack_offset = stack_offset
18
+ end
19
+
20
+ def perform
21
+ file, line_number, method_name = method_details
22
+ return unless current && file && line_number && method_name
23
+
24
+ CodeLocation.new(object: object,
25
+ method_name: method_name.to_s,
26
+ class_method: class_method,
27
+ file: file,
28
+ line_number: line_number)
29
+ end
30
+
31
+ private
32
+
33
+ def class_method
34
+ object.is_a? Module
35
+ end
36
+
37
+ def current
38
+ stack.detect do |s|
39
+ file = s.split(":")[0]
40
+ Delfos::MethodLogging.include_file?(file)
41
+ end
42
+ end
43
+
44
+ def object
45
+ @object ||= call_site_binding.of_caller(stack_index + stack_offset).receiver
46
+ end
47
+
48
+ def stack_offset
49
+ @stack_offset ||= STACK_OFFSET
50
+ end
51
+
52
+ def stack_index
53
+ stack.index { |c| c == current }
54
+ end
55
+
56
+ METHOD_NAME_REGEX = /`(.*)'$/
57
+
58
+ def method_details
59
+ return unless current
60
+ file, line_number, rest, more = current.split(":")
61
+
62
+ rest = more.nil? ? rest : "#{rest}:#{more}"
63
+ method_name = rest.match(METHOD_NAME_REGEX)&.[](1)
64
+
65
+ return unless method_name && file && line_number
66
+
67
+ method_name = method_name.delete("`")
68
+ method_name = method_name.delete("'")
69
+
70
+ [file, line_number.to_i, method_name]
71
+ end
72
+ end
73
+ end
74
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require_relative "call_site_parsing"
2
3
 
3
4
  module Delfos
4
5
  module MethodLogging
@@ -12,141 +13,76 @@ module Delfos
12
13
  file, line_number = called_method.source_location
13
14
  return unless file && line_number
14
15
 
15
- new(object, called_method.name.to_s, class_method, file, line_number)
16
+ new(object: object, method_name: called_method.name.to_s,
17
+ class_method: class_method, file: file, line_number: line_number)
16
18
  end
17
19
 
18
- def method_type_from(class_method)
19
- class_method ? "ClassMethod" : "InstanceMethod"
20
- end
21
20
  end
22
21
 
23
- attr_reader :object, :method_name, :class_method, :method_type, :file, :line_number
22
+ attr_reader :object, :method_name, :class_method, :line_number
24
23
 
25
- def initialize(object, method_name, class_method, file, line_number)
26
- @object = object
27
- @method_name = method_name
24
+ def initialize(object:, method_name:, class_method:, file:, line_number:)
25
+ @object = object
26
+ @method_name = method_name
28
27
  @class_method = class_method
29
- @method_type = self.class.method_type_from class_method
30
- @line_number = line_number.to_i
31
- @file = file
28
+ @line_number = line_number.to_i
29
+ @file = file
32
30
  end
33
31
 
34
32
  def file
35
- file = @file.to_s
36
-
37
- if file
38
- Delfos.application_directories.map do |d|
39
- file = relative_path(file, d)
40
- end
41
- end
42
-
43
- file
44
- end
45
-
46
- def relative_path(file, dir)
47
- match = dir.to_s.split("/")[0..-2].join("/")
48
-
49
- if file[match]
50
- file = file.gsub(match, "").
51
- gsub(%r{^/}, "")
52
- end
53
-
54
- file
33
+ relative_filename @file
55
34
  end
56
35
 
57
36
  def klass
58
37
  object.is_a?(Class) ? object : object.class
59
38
  end
60
39
 
61
- def klass_name
62
- name = klass.name || "__AnonymousClass"
63
- name.tr ":", "_"
64
- end
65
-
66
40
  def method_definition_file
67
- if method_definition
68
- method_definition[0].to_s
69
- else
70
- #TODO fix edge case when block
71
- "#{@file} in block"
72
- end
41
+ relative_filename(method_definition&.first || fallback_method_definition_file)
73
42
  end
74
43
 
75
44
  def method_definition_line
76
- if method_definition
77
- method_definition[1].to_i
78
- else
79
- #TODO fix edge case when block
80
- 0
81
- end
82
- end
83
-
84
- private
85
-
86
- def method_key
87
- "#{method_type}_#{method_name}"
45
+ method_definition&.last&.to_i || fallback_method_definition_line_number
88
46
  end
89
47
 
90
- def method_definition
91
- @method_definition ||= ::Delfos::MethodLogging::AddedMethods.method_source_for(klass, method_key)
48
+ def method_type
49
+ class_method ? "ClassMethod" : "InstanceMethod"
92
50
  end
93
- end
94
51
 
95
- class CallSiteParsing
96
- # This magic number is based on the implementation within this file.
97
- # If the line with `call_site_binding.of_caller(stack_index + STACK_OFFSET).receiver`
98
- # is moved up or down the call stack a test fails and we have to change `STACK_OFFSET`
99
- STACK_OFFSET = 5
52
+ private
100
53
 
101
- attr_reader :stack, :call_site_binding
54
+ def relative_filename(f)
55
+ return unless f
56
+ file = f.to_s
102
57
 
103
- def initialize(stack, call_site_binding, stack_offset: nil)
104
- @stack = stack
105
- @call_site_binding = call_site_binding
106
- @stack_offset = stack_offset
107
- end
108
-
109
- def perform
110
- file, line_number, method_name = method_details
111
- return unless current && file && line_number && method_name
58
+ Delfos.application_directories.map do |d|
59
+ file = relative_path(file, d)
60
+ end
112
61
 
113
- CodeLocation.new(object, method_name.to_s, class_method, file, line_number)
62
+ file
114
63
  end
115
64
 
116
- private
117
-
118
- def class_method
119
- object.is_a? Module
120
- end
65
+ def relative_path(file, dir)
66
+ match = dir.to_s.split("/")[0..-2].join("/")
121
67
 
122
- def current
123
- stack.detect do |s|
124
- file = s.split(":")[0]
125
- Delfos.method_logging.include_file_in_logging?(file)
68
+ if file[match]
69
+ file = file.gsub(match, "").
70
+ gsub(%r{^/}, "")
126
71
  end
127
- end
128
72
 
129
- def object
130
- @object ||= call_site_binding.of_caller(stack_index + stack_offset).receiver
73
+ file
131
74
  end
132
75
 
133
- def stack_offset
134
- @stack_offset ||= STACK_OFFSET
76
+ def fallback_method_definition_file
77
+ @file
135
78
  end
136
79
 
137
- def stack_index
138
- stack.index { |c| c == current }
80
+ def fallback_method_definition_line_number
81
+ 0
139
82
  end
140
83
 
141
- def method_details
142
- return unless current
143
- file, line_number, rest = current.split(":")
144
- method_name = rest[/`.*'$/]
145
- return unless method_name && file && line_number
146
-
147
- method_name.delete!("`").delete!("'")
148
-
149
- [file, line_number.to_i, method_name]
84
+ def method_definition
85
+ @method_definition ||= Patching::MethodCache.find(klass: klass, method_name: method_name, class_method: class_method)&.source_location
150
86
  end
151
87
  end
152
88
  end
@@ -1,27 +1,33 @@
1
1
  # frozen_string_literal: true
2
- require_relative "../../delfos"
3
- require_relative "../common_path"
4
- require_relative "./added_methods"
2
+ require "delfos/patching/method_cache"
5
3
 
6
4
  module Delfos
7
5
  module MethodLogging
8
- class Args
6
+ class MethodParameters
9
7
  attr_reader :block
10
8
 
11
- def initialize(arguments)
12
- @raw_args = arguments.args
13
- @raw_keyword_args = arguments.keyword_args
14
- @block = arguments.block
9
+ def initialize(args=[], keyword_args=nil, block=nil)
10
+ @raw_args = args
11
+ @raw_keyword_args = keyword_args
12
+ @block = block
15
13
  end
16
14
 
17
15
  def args
18
16
  @args ||= calculate_args(@raw_args)
19
17
  end
20
18
 
19
+ def argument_classes
20
+ (args + keyword_args).uniq
21
+ end
22
+
21
23
  def keyword_args
22
24
  @keyword_args ||= calculate_args(@raw_keyword_args.values)
23
25
  end
24
26
 
27
+ def as_json(*params)
28
+ {arguments: args, keyword_arguments: keyword_args}
29
+ end
30
+
25
31
  private
26
32
 
27
33
  def calculate_args(arguments)
@@ -32,22 +38,15 @@ module Delfos
32
38
 
33
39
  def keep?(klass)
34
40
  files_for(klass).
35
- select { |f| record?(f) }.length.positive?
41
+ any? { |f| record?(f) }
36
42
  end
37
43
 
38
44
  def files_for(klass)
39
- source_files(klass).
40
- flatten.
41
- compact.
42
- uniq
43
- end
44
-
45
- def source_files(klass)
46
- AddedMethods.all_method_sources_for(klass).map(&:first)
45
+ Patching::MethodCache.files_for(klass)
47
46
  end
48
47
 
49
48
  def record?(f)
50
- Delfos.method_logging.include_any_path_in_logging?(f)
49
+ Delfos::MethodLogging.include_file?(f)
51
50
  end
52
51
  end
53
52
  end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+ require_relative "neo4j/informer"
3
+ require_relative "neo4j/query_execution/sync"
4
+ require_relative "neo4j/batch/execution"
5
+ require_relative "neo4j/schema"
6
+ require_relative "neo4j/distance/update"
7
+
8
+ module Delfos
9
+ module Neo4j
10
+ extend self
11
+
12
+ def execute_sync(query, params = {})
13
+ QueryExecution::Sync.new(query, params).perform
14
+ end
15
+
16
+ def execute(query, params = {})
17
+ Batch::Execution.execute!(query, params: params)
18
+ end
19
+
20
+ def flush!
21
+ Batch::Execution.flush!
22
+ end
23
+
24
+ def ensure_schema!
25
+ Schema.ensure_constraints!(
26
+ "Class" => "name",
27
+ "CallStack" => "number",
28
+ )
29
+ end
30
+
31
+ def update_distance!
32
+ Distance::Update.new.perform
33
+ end
34
+
35
+ def config
36
+ host ||= ENV["NEO4J_HOST"] || "http://localhost"
37
+ port ||= ENV["NEO4J_PORT"] || "7476"
38
+ username ||= ENV["NEO4J_USERNAME"] || "neo4j"
39
+ password ||= ENV["NEO4J_PASSWORD"] || "password"
40
+
41
+ Neo4jConfig.new(host, port, username, password)
42
+ end
43
+
44
+ Neo4jConfig = Struct.new(:host, :port, :username, :password) do
45
+ def url
46
+ "#{host}:#{port}"
47
+ end
48
+
49
+ def uri_for(path)
50
+ URI.parse("#{url}#{path}")
51
+ end
52
+
53
+ def to_s
54
+ " host: #{host}\n port: #{port}\n username: #{username}\n password: #{password}"
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+ require "delfos/neo4j/query_execution/transactional"
3
+
4
+ module Delfos
5
+ module Neo4j
6
+ module Batch
7
+ class Execution
8
+ BATCH_MUTEX = Mutex.new
9
+
10
+ class << self
11
+ def execute!(query, params: {}, size: nil)
12
+ batch = @batch || new_batch(size || 1_000)
13
+
14
+ batch.execute!(query, params: params)
15
+ end
16
+
17
+ def new_batch(size)
18
+ @batch = new(size: size)
19
+ end
20
+
21
+ def flush!
22
+ @batch&.flush!
23
+ rescue
24
+ reset!
25
+ end
26
+
27
+ def reset!
28
+ @batch = nil
29
+ end
30
+
31
+ attr_writer :batch
32
+ end
33
+
34
+ def initialize(size:, clock: Time)
35
+ @size = size
36
+ @clock = clock
37
+ @queries = []
38
+ @expires = nil
39
+ @commit_url = nil
40
+ @current_transaction_url = nil
41
+ end
42
+
43
+ attr_reader :size, :current_transaction_url, :commit_url, :expires, :queries
44
+
45
+ def execute!(query, params: {}, retrying: false)
46
+ queries.push([query, params]) unless retrying
47
+
48
+ with_retry(retrying) do
49
+ BATCH_MUTEX.synchronize do
50
+ check_for_expiry!
51
+
52
+ perform_query(query, params)
53
+ flush_if_required!
54
+ end
55
+ end
56
+ end
57
+
58
+ def flush!
59
+ return unless query_count.positive?
60
+ return unless @commit_url
61
+ QueryExecution::Transactional.flush!(@commit_url)
62
+
63
+ reset!
64
+ end
65
+
66
+ def query_count
67
+ queries.length
68
+ end
69
+
70
+ private
71
+
72
+ def perform_query(query, params)
73
+ transactional_query = QueryExecution::Transactional.new(query, params, url)
74
+ transaction_url, @commit_url, @expires = transactional_query.perform
75
+ @current_transaction_url ||= transaction_url # the transaction_url is only returned with the first request
76
+ end
77
+
78
+ def with_retry(retrying)
79
+ yield
80
+ rescue QueryExecution::ExpiredTransaction
81
+ @retry_count ||= 0
82
+
83
+ if retrying
84
+ @retry_count += 1
85
+
86
+ if @retry_count > 5
87
+ @retry_count = 0
88
+ raise
89
+ end
90
+ end
91
+
92
+ Delfos.logger.error "Transaction expired - retrying batch. #{query_count} queries retry_count: #{@retry_count}"
93
+ reset_transaction!
94
+ retry_batch!
95
+ end
96
+
97
+ def retry_batch!
98
+ queries.each do |q, p|
99
+ execute!(q, params: p, retrying: true)
100
+ end
101
+ end
102
+
103
+ def url
104
+ return @commit_url if @commit_url && batch_full? || expires_soon?
105
+
106
+ current_transaction_url || new_transaction_url
107
+ end
108
+
109
+ def new_transaction_url
110
+ Delfos.neo4j.uri_for("/db/data/transaction")
111
+ end
112
+
113
+ def check_for_expiry!
114
+ return unless @expires
115
+
116
+ if @clock.now > @expires
117
+ self.class.batch = nil
118
+ raise QueryExecution::ExpiredTransaction.new(@comit_url, "")
119
+ end
120
+ end
121
+
122
+ def flush_if_required!
123
+ check_for_expiry!
124
+
125
+ flush! if batch_full? || expires_soon?
126
+ end
127
+
128
+ def batch_full?
129
+ query_count >= size
130
+ end
131
+
132
+ def expires_soon?
133
+ @expires && (@clock.now + 2 > @expires)
134
+ end
135
+
136
+ def reset!
137
+ @queries = []
138
+ reset_transaction!
139
+ end
140
+
141
+ def reset_transaction!
142
+ @current_transaction_url = nil
143
+ @commit_url = nil
144
+ @expires = nil
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end