delfos 0.0.1.pre.rc1 → 0.0.1

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.
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