delfos 0.0.1.pre.beta → 0.0.1.pre.rc1

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