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.
@@ -7,24 +7,7 @@ module Delfos
7
7
  module Neo4j
8
8
  class DistanceUpdate
9
9
  def perform
10
- query = <<-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 |klass, call_site, call_site_id, meth, called, called_id, called_klass|
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(#{call_site_id}),
56
- called = node(#{called_id})
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) - #{rel_for(calc)} -> (called)
58
+ MERGE (call_site) - #{rel} -> (called)
59
59
  QUERY
60
60
  end
61
61
 
62
- def rel_for(calc)
62
+ def rel
63
63
  <<-REL
64
64
  [:EFFERENT_COUPLING{
65
- distance: #{calc.sum_traversals},
66
- possible_distance: #{calc.sum_possible_traversals}
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!(other)
7
- new(other.call_sites, other.execution_count).save!
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 query
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
- call_sites.compact.map.with_index do |c, i|
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
- <<-QUERY
32
- #{method_query(cs, i)}
48
+ <<-QUERY
49
+ MERGE
33
50
 
34
- MERGE
35
- (m#{i})
51
+ (
52
+ k#{i}:Class { name: {klass#{i}} }
53
+ )
36
54
 
37
- -[:CONTAINS]->
55
+ - [:OWNS] ->
38
56
 
39
- (cs#{i}:CallSite {file: "#{cs.file}", line_number: #{cs.line_number}})
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
- #{execution_chain_query(cs, i)}
42
- QUERY
43
- end
65
+ MERGE
44
66
 
45
- def execution_chain_query(cs, i)
46
- <<-QUERY
47
- MERGE (e#{i}:ExecutionChain{number: #{execution_count}})
67
+ (m#{i})
48
68
 
49
- MERGE e#{i}-[:STEP{number: #{i + 1}}]-> (cs#{i})
50
- QUERY
51
- end
69
+ -[:CONTAINS]->
52
70
 
53
- def method_query(cs, i)
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
- (m#{i} :#{cs.method_type} {name: "#{cs.method_name}", file: "#{cs.method_definition_file}", line_number: #{cs.method_definition_line}})
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}:#{klass})"
23
+ "MERGE (#{name}:Class {name: #{klass.inspect}})"
23
24
  end.join("\n")
24
25
 
25
26
  <<-QUERY
26
27
  #{klasses_query}
27
28
 
28
- #{merge_query(call_site, called_code)}
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 > 0 && code.method_definition_line > 0
51
+ if code.method_definition_file.length.positive? && code.method_definition_line.positive?
62
52
  <<-NODE
63
- (#{id}:#{code.method_type}{name: "#{code.method_name}", file: #{code.method_definition_file.inspect}, line_number: #{code.method_definition_line}})
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}:#{code.method_type}{name: "#{code.method_name}"})
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.method_chain
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, url=nil)
10
- result = JSON.parse response_for(query)
11
- result["results"].first["data"].map{|r| r["row"]}
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
- command = "curl #{headers} --data-ascii '#{body}' #{url} 2> /dev/null"
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
- `#{command}`
69
+ request.body = yield
70
+ end
25
71
  end
26
72
 
27
- def headers
28
- <<-HEADERS.chomp
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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class BasicObject
4
+ def self.method_added(*_args); end
5
+
6
+ def self.singleton_method_added(*_args); end
7
+ 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