delfos 0.0.1.pre.beta → 0.0.1.pre.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/delfos.rb +63 -21
- data/lib/delfos/common_path.rb +1 -3
- data/lib/delfos/execution_chain.rb +15 -14
- data/lib/delfos/method_logging.rb +18 -20
- data/lib/delfos/method_logging/added_methods.rb +67 -0
- data/lib/delfos/method_logging/args.rb +24 -23
- data/lib/delfos/method_logging/code_location.rb +25 -28
- data/lib/delfos/neo4j/distance_update.rb +26 -26
- data/lib/delfos/neo4j/execution_persistence.rb +59 -24
- data/lib/delfos/neo4j/informer.rb +16 -26
- data/lib/delfos/neo4j/query_execution.rb +58 -17
- data/lib/delfos/patching/basic_object.rb +18 -0
- data/lib/delfos/patching/basic_object_remove.rb +7 -0
- data/lib/delfos/patching/method_override.rb +138 -0
- data/lib/delfos/patching/unstubbing_spec_helper.rb +58 -0
- metadata +40 -58
- data/lib/delfos/method_logging/klass_determination.rb +0 -16
- data/lib/delfos/patching.rb +0 -105
- data/lib/delfos/patching_unstubbing_spec_helper.rb +0 -44
- data/lib/delfos/perform_patching.rb +0 -16
- data/lib/delfos/remove_patching.rb +0 -10
- data/lib/delfos/version.rb +0 -4
@@ -7,24 +7,7 @@ module Delfos
|
|
7
7
|
module Neo4j
|
8
8
|
class DistanceUpdate
|
9
9
|
def perform
|
10
|
-
query
|
11
|
-
MATCH
|
12
|
-
(klass) - [:OWNS] -> (method),
|
13
|
-
(method) - [:CONTAINS] -> (call_site),
|
14
|
-
(call_site) - [:CALLS] -> (called),
|
15
|
-
(called_klass) - [:OWNS] -> (called)
|
16
|
-
|
17
|
-
RETURN
|
18
|
-
head(labels(klass)),
|
19
|
-
call_site, id(call_site),
|
20
|
-
method,
|
21
|
-
called, id(called),
|
22
|
-
head(labels(called_klass))
|
23
|
-
QUERY
|
24
|
-
|
25
|
-
results = Delfos::Neo4j::QueryExecution.execute(query)
|
26
|
-
|
27
|
-
update(results)
|
10
|
+
update Delfos::Neo4j::QueryExecution.execute(query)
|
28
11
|
end
|
29
12
|
|
30
13
|
def determine_full_path(f)
|
@@ -39,8 +22,25 @@ module Delfos
|
|
39
22
|
|
40
23
|
private
|
41
24
|
|
25
|
+
def query
|
26
|
+
<<-QUERY
|
27
|
+
MATCH
|
28
|
+
(klass:Class) - [:OWNS] -> (method:Method),
|
29
|
+
(method) - [:CONTAINS] -> (call_site:CallSite),
|
30
|
+
(call_site) - [:CALLS] -> (called:Method),
|
31
|
+
(called_klass:Class) - [:OWNS] -> (called:Method)
|
32
|
+
|
33
|
+
RETURN
|
34
|
+
head(labels(klass)),
|
35
|
+
call_site, id(call_site),
|
36
|
+
method,
|
37
|
+
called, id(called),
|
38
|
+
head(labels(called_klass))
|
39
|
+
QUERY
|
40
|
+
end
|
41
|
+
|
42
42
|
def update(results)
|
43
|
-
results.map do |
|
43
|
+
Array(results).compact.map do |_klass, call_site, call_site_id, _meth, called, called_id, _called_klass|
|
44
44
|
start = determine_full_path call_site["file"]
|
45
45
|
finish = determine_full_path called["file"]
|
46
46
|
|
@@ -51,19 +51,19 @@ module Delfos
|
|
51
51
|
end
|
52
52
|
|
53
53
|
def perform_query(calc, call_site_id, called_id)
|
54
|
-
Delfos::Neo4j::QueryExecution.execute <<-QUERY
|
55
|
-
START call_site = node(
|
56
|
-
called = node(
|
54
|
+
Delfos::Neo4j::QueryExecution.execute <<-QUERY, {call_site_id: call_site_id, called_id: called_id, sum_traversals: calc.sum_traversals, sum_possible_traversals: calc.sum_possible_traversals}
|
55
|
+
START call_site = node({call_site_id}),
|
56
|
+
called = node({called_id})
|
57
57
|
|
58
|
-
MERGE (call_site) - #{
|
58
|
+
MERGE (call_site) - #{rel} -> (called)
|
59
59
|
QUERY
|
60
60
|
end
|
61
61
|
|
62
|
-
def
|
62
|
+
def rel
|
63
63
|
<<-REL
|
64
64
|
[:EFFERENT_COUPLING{
|
65
|
-
distance:
|
66
|
-
possible_distance:
|
65
|
+
distance: {sum_traversals},
|
66
|
+
possible_distance: {sum_possible_traversals}
|
67
67
|
}
|
68
68
|
]
|
69
69
|
REL
|
@@ -1,14 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require_relative "query_execution"
|
2
3
|
|
3
4
|
module Delfos
|
4
5
|
module Neo4j
|
5
6
|
class ExecutionPersistence
|
6
|
-
def self.save!(
|
7
|
-
new(
|
7
|
+
def self.save!(chain)
|
8
|
+
new(chain.call_sites, chain.execution_count).save!
|
8
9
|
end
|
9
10
|
|
10
11
|
def save!
|
11
|
-
Neo4j::QueryExecution.execute
|
12
|
+
Neo4j::QueryExecution.execute(query, params)
|
12
13
|
end
|
13
14
|
|
14
15
|
private
|
@@ -22,42 +23,76 @@ module Delfos
|
|
22
23
|
attr_reader :call_sites, :execution_count
|
23
24
|
|
24
25
|
def query
|
25
|
-
|
26
|
+
map_call_sites do |c,i|
|
26
27
|
call_site_query(c, i)
|
27
28
|
end.join("\n")
|
28
29
|
end
|
29
30
|
|
31
|
+
def params
|
32
|
+
params = {}
|
33
|
+
|
34
|
+
map_call_sites do |c,i|
|
35
|
+
params.merge!(call_site_params(c, i))
|
36
|
+
end
|
37
|
+
|
38
|
+
params
|
39
|
+
end
|
40
|
+
|
41
|
+
def map_call_sites
|
42
|
+
call_sites.compact.map.with_index do |c, i|
|
43
|
+
yield c, i
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
30
47
|
def call_site_query(cs, i)
|
31
|
-
|
32
|
-
|
48
|
+
<<-QUERY
|
49
|
+
MERGE
|
33
50
|
|
34
|
-
|
35
|
-
|
51
|
+
(
|
52
|
+
k#{i}:Class { name: {klass#{i}} }
|
53
|
+
)
|
36
54
|
|
37
|
-
|
55
|
+
- [:OWNS] ->
|
38
56
|
|
39
|
-
|
57
|
+
(
|
58
|
+
m#{i} :Method {
|
59
|
+
type: {method_type#{i}},
|
60
|
+
name: {method_name#{i}},
|
61
|
+
file: {method_definition_file#{i}},
|
62
|
+
line_number: {method_definition_line#{i}}}
|
63
|
+
)
|
40
64
|
|
41
|
-
|
42
|
-
QUERY
|
43
|
-
end
|
65
|
+
MERGE
|
44
66
|
|
45
|
-
|
46
|
-
<<-QUERY
|
47
|
-
MERGE (e#{i}:ExecutionChain{number: #{execution_count}})
|
67
|
+
(m#{i})
|
48
68
|
|
49
|
-
|
50
|
-
QUERY
|
51
|
-
end
|
69
|
+
-[:CONTAINS]->
|
52
70
|
|
53
|
-
|
54
|
-
<<-QUERY
|
55
|
-
MERGE (k#{i}:#{cs.klass})
|
56
|
-
- [:OWNS] ->
|
71
|
+
(cs#{i}:CallSite {file: {file#{i}}, line_number: {line_number#{i}}})
|
57
72
|
|
58
|
-
|
73
|
+
MERGE (e#{i}:ExecutionChain{number: {execution_count#{i}}})
|
74
|
+
|
75
|
+
MERGE (e#{i})
|
76
|
+
-
|
77
|
+
[:STEP {number: {step_number#{i}}}]
|
78
|
+
->
|
79
|
+
(cs#{i})
|
59
80
|
QUERY
|
60
81
|
end
|
82
|
+
|
83
|
+
def call_site_params(cs, i)
|
84
|
+
{
|
85
|
+
"klass#{i}" => cs.klass,
|
86
|
+
"method_name#{i}" => cs.method_name,
|
87
|
+
"method_type#{i}" => cs.method_type,
|
88
|
+
"method_definition_file#{i}" => cs.method_definition_file,
|
89
|
+
"execution_count#{i}" => execution_count,
|
90
|
+
"method_definition_line#{i}" => cs.method_definition_line,
|
91
|
+
"file#{i}" => cs.file,
|
92
|
+
"line_number#{i}" => cs.line_number,
|
93
|
+
"step_number#{i}" => i + 1
|
94
|
+
}
|
95
|
+
end
|
61
96
|
end
|
62
97
|
end
|
63
98
|
end
|
@@ -11,21 +11,29 @@ module Delfos
|
|
11
11
|
|
12
12
|
def execute_query(*args)
|
13
13
|
query = query_for(*args)
|
14
|
+
params = {}
|
14
15
|
|
15
|
-
QueryExecution.execute(query)
|
16
|
+
QueryExecution.execute(query, params)
|
16
17
|
end
|
17
18
|
|
18
19
|
def query_for(args, call_site, called_code)
|
19
20
|
assign_query_variables(args, call_site, called_code)
|
20
21
|
|
21
22
|
klasses_query = query_variables.map do |klass, name|
|
22
|
-
"MERGE (#{name}
|
23
|
+
"MERGE (#{name}:Class {name: #{klass.inspect}})"
|
23
24
|
end.join("\n")
|
24
25
|
|
25
26
|
<<-QUERY
|
26
27
|
#{klasses_query}
|
27
28
|
|
28
|
-
#{
|
29
|
+
MERGE (#{query_variable(call_site.klass)}) - [:OWNS] -> #{method_node(call_site, "m1")}
|
30
|
+
|
31
|
+
MERGE (m1) - [:CONTAINS] -> (cs:CallSite{file: "#{call_site.file}", line_number: #{call_site.line_number}})
|
32
|
+
|
33
|
+
MERGE (#{query_variable(called_code.klass)}) - [:OWNS] -> #{method_node(called_code, "m2")}
|
34
|
+
|
35
|
+
MERGE (cs) - [:CALLS] -> m2
|
36
|
+
|
29
37
|
#{args_query args}
|
30
38
|
QUERY
|
31
39
|
end
|
@@ -39,32 +47,14 @@ module Delfos
|
|
39
47
|
end
|
40
48
|
end
|
41
49
|
|
42
|
-
def merge_query(call_site, called_code)
|
43
|
-
<<-MERGE_QUERY
|
44
|
-
#{method_definition call_site, "m1"}
|
45
|
-
|
46
|
-
MERGE (m1) - [:CONTAINS] -> (cs:CallSite{file: "#{call_site.file}", line_number: #{call_site.line_number}})
|
47
|
-
|
48
|
-
#{method_definition called_code, "m2"}
|
49
|
-
|
50
|
-
MERGE (cs) - [:CALLS] -> m2
|
51
|
-
MERGE_QUERY
|
52
|
-
end
|
53
|
-
|
54
|
-
def method_definition(code, id)
|
55
|
-
<<-METHOD
|
56
|
-
MERGE (#{query_variable(code.klass)}) - [:OWNS] -> #{method_node(code, id)}
|
57
|
-
METHOD
|
58
|
-
end
|
59
|
-
|
60
50
|
def method_node(code, id)
|
61
|
-
if code.method_definition_file.length
|
51
|
+
if code.method_definition_file.length.positive? && code.method_definition_line.positive?
|
62
52
|
<<-NODE
|
63
|
-
(#{id}
|
53
|
+
(#{id}:Method{type: "#{code.method_type}", name: "#{code.method_name}", file: #{code.method_definition_file.inspect}, line_number: #{code.method_definition_line}})
|
64
54
|
NODE
|
65
55
|
else
|
66
56
|
<<-NODE
|
67
|
-
(#{id}
|
57
|
+
(#{id}:Method{type: "#{code.method_type}", name: "#{code.method_name}"})
|
68
58
|
NODE
|
69
59
|
end
|
70
60
|
end
|
@@ -85,7 +75,7 @@ module Delfos
|
|
85
75
|
end
|
86
76
|
|
87
77
|
def code_execution_query
|
88
|
-
Delfos::Patching.
|
78
|
+
Delfos::Patching.execution_chain
|
89
79
|
end
|
90
80
|
|
91
81
|
class QueryVariables < Hash
|
@@ -95,7 +85,7 @@ module Delfos
|
|
95
85
|
end
|
96
86
|
|
97
87
|
def assign(klass, prefix)
|
98
|
-
klass = klass.to_s
|
88
|
+
klass = klass.to_s.tr(":", "_")
|
99
89
|
val = self[klass]
|
100
90
|
return val if val
|
101
91
|
|
@@ -1,38 +1,79 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require "delfos"
|
3
3
|
require "json"
|
4
|
+
require "net/http"
|
5
|
+
require "uri"
|
4
6
|
|
5
7
|
module Delfos
|
6
8
|
module Neo4j
|
7
9
|
module QueryExecution
|
10
|
+
class ExecutionError < IOError
|
11
|
+
def initialize(response, query, params)
|
12
|
+
message = response.inspect
|
13
|
+
|
14
|
+
super [message, query, params.to_json].join("\n\n ")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
8
18
|
class << self
|
9
|
-
def execute(query,
|
10
|
-
|
11
|
-
|
19
|
+
def execute(query, params={})
|
20
|
+
return unless query.length.positive?
|
21
|
+
|
22
|
+
parse_response(*response_for(query, params))
|
12
23
|
end
|
13
24
|
|
14
25
|
private
|
15
26
|
|
16
|
-
def response_for(query)
|
17
|
-
body = <<-BODY.gsub(/^\s+/, "").chomp
|
18
|
-
{"statements": [ { "statement": #{query.inspect} }]}
|
19
|
-
BODY
|
20
|
-
url = "http://neo4j:password@localhost:7474/db/data/transaction/commit"
|
21
27
|
|
22
|
-
|
28
|
+
def parse_response(result, query, params)
|
29
|
+
result = JSON.parse result.body
|
30
|
+
|
31
|
+
if result["errors"].length.positive?
|
32
|
+
raise ExecutionError.new(result["errors"], query, params)
|
33
|
+
end
|
34
|
+
|
35
|
+
strip_out_meta_data(result)
|
36
|
+
end
|
37
|
+
|
38
|
+
def strip_out_meta_data(result)
|
39
|
+
results = result["results"]
|
40
|
+
|
41
|
+
if results
|
42
|
+
result = results.first
|
43
|
+
if result
|
44
|
+
data = result["data"]
|
45
|
+
data &.map { |r| r["row"] }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def response_for(query, params)
|
51
|
+
request = request_for(query, params)
|
52
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
53
|
+
|
54
|
+
response = http.request(request)
|
55
|
+
[response, query, params]
|
56
|
+
end
|
57
|
+
|
58
|
+
def request_for(query, params)
|
59
|
+
build_request do
|
60
|
+
"{\"statements\": [{\"statement\": #{query.inspect} , \"parameters\": #{params.to_json}}]}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def build_request
|
65
|
+
Net::HTTP::Post.new(uri.request_uri).tap do |request|
|
66
|
+
request.basic_auth(Delfos.neo4j.username, Delfos.neo4j.password)
|
67
|
+
request.content_type = "application/json"
|
23
68
|
|
24
|
-
|
69
|
+
request.body = yield
|
70
|
+
end
|
25
71
|
end
|
26
72
|
|
27
|
-
def
|
28
|
-
|
29
|
-
-H "User-Agent: delfos" -H "Accept: application/json; charset=UTF-8" -H "Content-Type: application/json"
|
30
|
-
HEADERS
|
73
|
+
def uri
|
74
|
+
@uri ||= URI.parse "#{Delfos.neo4j.url}/db/data/transaction/commit"
|
31
75
|
end
|
32
76
|
end
|
33
77
|
end
|
34
78
|
end
|
35
79
|
end
|
36
|
-
|
37
|
-
|
38
|
-
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "method_override"
|
3
|
+
|
4
|
+
class BasicObject
|
5
|
+
def self.method_added(name)
|
6
|
+
return if %i(to_s singleton_method_added method_added).include?(name)
|
7
|
+
|
8
|
+
::Delfos::Patching::MethodOverride.setup(self, name, private_instance_methods, class_method: false)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.singleton_method_added(name)
|
12
|
+
# can't currently support to_s and name as those are used inside the method logging code
|
13
|
+
return if %i(to_s name extended included inherited method_added singleton_method_added).include?(name)
|
14
|
+
return if ::Delfos::MethodLogging.exclude?(method(name))
|
15
|
+
|
16
|
+
::Delfos::Patching::MethodOverride.setup(self, name, private_methods, class_method: true)
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../method_logging"
|
4
|
+
|
5
|
+
module Delfos
|
6
|
+
module Patching
|
7
|
+
class MethodOverride
|
8
|
+
class << self
|
9
|
+
def setup(klass, name, private_methods, class_method:)
|
10
|
+
instance = new(klass, name, private_methods, class_method)
|
11
|
+
|
12
|
+
instance.ensure_method_recorded_once!
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :klass, :name, :private_methods, :class_method
|
17
|
+
|
18
|
+
def initialize(klass, name, private_methods, class_method)
|
19
|
+
@klass = klass
|
20
|
+
@name = name
|
21
|
+
@private_methods = private_methods
|
22
|
+
@class_method = class_method
|
23
|
+
original_method # ensure memoized method is the original not the overridden one
|
24
|
+
end
|
25
|
+
|
26
|
+
def ensure_method_recorded_once!
|
27
|
+
record_method! { setup }
|
28
|
+
end
|
29
|
+
|
30
|
+
# Redefine the method (only once) at runtime to enabling logging to Neo4j
|
31
|
+
def setup
|
32
|
+
processor = method(:process)
|
33
|
+
cm = class_method
|
34
|
+
|
35
|
+
method_defining_method.call(name) do |*args, **kw_args, &block|
|
36
|
+
arguments = MethodArguments.new(args, kw_args, block, cm)
|
37
|
+
|
38
|
+
processor.call(self, caller.dup, binding.dup, arguments)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def process(instance, stack, caller_binding, arguments)
|
43
|
+
method_to_call = method_selector(instance)
|
44
|
+
|
45
|
+
call_site = Delfos::MethodLogging::CodeLocation.from_call_site(stack, caller_binding)
|
46
|
+
|
47
|
+
with_logging(call_site, instance, method_to_call, class_method, arguments) do
|
48
|
+
arguments.apply_to(method_to_call)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def with_logging(call_site, instance, method_to_call, class_method, arguments)
|
53
|
+
Delfos.method_logging.log(call_site, instance, method_to_call, class_method, arguments) if call_site
|
54
|
+
|
55
|
+
with_stack(call_site) do
|
56
|
+
yield
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
def with_stack(call_site)
|
62
|
+
return yield unless call_site
|
63
|
+
|
64
|
+
begin
|
65
|
+
ExecutionChain.push(call_site)
|
66
|
+
yield
|
67
|
+
ensure
|
68
|
+
ExecutionChain.pop
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
MethodArguments = Struct.new(:args, :keyword_args, :block, :class_method) do
|
73
|
+
def apply_to(method)
|
74
|
+
if keyword_args.empty?
|
75
|
+
method.call(*args, &block)
|
76
|
+
else
|
77
|
+
method.call(*args, **keyword_args, &block)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
def original_method
|
84
|
+
@original_method ||= if class_method
|
85
|
+
klass.method(name)
|
86
|
+
else
|
87
|
+
klass.instance_method(name)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def record_method!
|
94
|
+
return true if bail?
|
95
|
+
yield
|
96
|
+
|
97
|
+
Delfos::MethodLogging::AddedMethods.append(klass, key, original_method)
|
98
|
+
end
|
99
|
+
|
100
|
+
def method_selector(instance)
|
101
|
+
if class_method
|
102
|
+
m = Delfos::MethodLogging::AddedMethods.find(instance, "ClassMethod_#{name}")
|
103
|
+
m.receiver == instance ? m : m.unbind.bind(instance)
|
104
|
+
else
|
105
|
+
original_method.bind(instance)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def method_defining_method
|
110
|
+
class_method ? klass.method(:define_singleton_method) : klass.method(:define_method)
|
111
|
+
end
|
112
|
+
|
113
|
+
def bail?
|
114
|
+
method_has_been_added? || private_method? || exclude?
|
115
|
+
end
|
116
|
+
|
117
|
+
def method_has_been_added?
|
118
|
+
Delfos::MethodLogging::AddedMethods.find(klass, key)
|
119
|
+
end
|
120
|
+
|
121
|
+
def private_method?
|
122
|
+
private_methods.include?(name.to_sym)
|
123
|
+
end
|
124
|
+
|
125
|
+
def exclude?
|
126
|
+
::Delfos.method_logging.exclude?(original_method)
|
127
|
+
end
|
128
|
+
|
129
|
+
def key
|
130
|
+
"#{type}_#{name}"
|
131
|
+
end
|
132
|
+
|
133
|
+
def type
|
134
|
+
class_method ? "ClassMethod" : "InstanceMethod"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|