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.
- checksums.yaml +4 -4
- data/lib/delfos.rb +22 -78
- data/lib/delfos/call_stack.rb +39 -0
- data/lib/delfos/call_stack/stack.rb +59 -0
- data/lib/delfos/file_system/common_path.rb +62 -0
- data/lib/delfos/{distance/calculation.rb → file_system/distance_calculation.rb} +4 -15
- data/lib/delfos/file_system/path_determination.rb +37 -0
- data/lib/delfos/{distance → file_system}/relation.rb +1 -1
- data/lib/delfos/method_logging.rb +32 -49
- data/lib/delfos/method_logging/call_site_parsing.rb +74 -0
- data/lib/delfos/method_logging/code_location.rb +34 -98
- data/lib/delfos/method_logging/{args.rb → method_parameters.rb} +17 -18
- data/lib/delfos/neo4j.rb +58 -0
- data/lib/delfos/neo4j/batch/execution.rb +149 -0
- data/lib/delfos/neo4j/{execution_persistence.rb → call_stack_query.rb} +12 -22
- data/lib/delfos/neo4j/distance/call_site_fetcher.rb +19 -0
- data/lib/delfos/neo4j/distance/update.rb +50 -0
- data/lib/delfos/neo4j/informer.rb +62 -34
- data/lib/delfos/neo4j/query_execution/errors.rb +30 -0
- data/lib/delfos/neo4j/query_execution/http.rb +61 -0
- data/lib/delfos/neo4j/query_execution/http_query.rb +67 -0
- data/lib/delfos/neo4j/query_execution/sync.rb +35 -0
- data/lib/delfos/neo4j/query_execution/transactional.rb +68 -0
- data/lib/delfos/neo4j/schema.rb +73 -0
- data/lib/delfos/patching/basic_object.rb +1 -5
- data/lib/delfos/patching/method_cache.rb +83 -0
- data/lib/delfos/patching/method_override.rb +76 -66
- data/lib/delfos/patching/module_defining_methods.rb +105 -0
- data/lib/delfos/patching/unstubber.rb +34 -0
- data/lib/delfos/setup.rb +88 -0
- metadata +42 -15
- data/lib/delfos/common_path.rb +0 -58
- data/lib/delfos/execution_chain.rb +0 -75
- data/lib/delfos/method_logging/added_methods.rb +0 -67
- data/lib/delfos/neo4j/distance_update.rb +0 -73
- data/lib/delfos/neo4j/query_execution.rb +0 -79
- 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,
|
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, :
|
22
|
+
attr_reader :object, :method_name, :class_method, :line_number
|
24
23
|
|
25
|
-
def initialize(object
|
26
|
-
@object
|
27
|
-
@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
|
-
@
|
30
|
-
@
|
31
|
-
@file = file
|
28
|
+
@line_number = line_number.to_i
|
29
|
+
@file = file
|
32
30
|
end
|
33
31
|
|
34
32
|
def file
|
35
|
-
|
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
|
-
|
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
|
-
|
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
|
91
|
-
|
48
|
+
def method_type
|
49
|
+
class_method ? "ClassMethod" : "InstanceMethod"
|
92
50
|
end
|
93
|
-
end
|
94
51
|
|
95
|
-
|
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
|
-
|
54
|
+
def relative_filename(f)
|
55
|
+
return unless f
|
56
|
+
file = f.to_s
|
102
57
|
|
103
|
-
|
104
|
-
|
105
|
-
|
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
|
-
|
62
|
+
file
|
114
63
|
end
|
115
64
|
|
116
|
-
|
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
|
-
|
123
|
-
|
124
|
-
|
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
|
-
|
130
|
-
@object ||= call_site_binding.of_caller(stack_index + stack_offset).receiver
|
73
|
+
file
|
131
74
|
end
|
132
75
|
|
133
|
-
def
|
134
|
-
@
|
76
|
+
def fallback_method_definition_file
|
77
|
+
@file
|
135
78
|
end
|
136
79
|
|
137
|
-
def
|
138
|
-
|
80
|
+
def fallback_method_definition_line_number
|
81
|
+
0
|
139
82
|
end
|
140
83
|
|
141
|
-
def
|
142
|
-
|
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
|
-
|
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
|
6
|
+
class MethodParameters
|
9
7
|
attr_reader :block
|
10
8
|
|
11
|
-
def initialize(
|
12
|
-
@raw_args
|
13
|
-
@raw_keyword_args =
|
14
|
-
@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
|
-
|
41
|
+
any? { |f| record?(f) }
|
36
42
|
end
|
37
43
|
|
38
44
|
def files_for(klass)
|
39
|
-
|
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.
|
49
|
+
Delfos::MethodLogging.include_file?(f)
|
51
50
|
end
|
52
51
|
end
|
53
52
|
end
|
data/lib/delfos/neo4j.rb
ADDED
@@ -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
|