codebeacon-tracer 0.1.0
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 +7 -0
- data/lib/codebeacon/tracer/src/configuration.rb +199 -0
- data/lib/codebeacon/tracer/src/data/database.rb +54 -0
- data/lib/codebeacon/tracer/src/data/metadata_mapper.rb +26 -0
- data/lib/codebeacon/tracer/src/data/node_source_mapper.rb +29 -0
- data/lib/codebeacon/tracer/src/data/persistence_manager.rb +107 -0
- data/lib/codebeacon/tracer/src/data/tree_node_mapper.rb +60 -0
- data/lib/codebeacon/tracer/src/logger.rb +58 -0
- data/lib/codebeacon/tracer/src/models/call_tree.rb +54 -0
- data/lib/codebeacon/tracer/src/models/node_builder.rb +60 -0
- data/lib/codebeacon/tracer/src/models/node_source.rb +32 -0
- data/lib/codebeacon/tracer/src/models/thread_local_call_tree_manager.rb +35 -0
- data/lib/codebeacon/tracer/src/models/tpklass.rb +53 -0
- data/lib/codebeacon/tracer/src/models/tree_node.rb +113 -0
- data/lib/codebeacon/tracer/src/rails/middleware.rb +47 -0
- data/lib/codebeacon/tracer/src/rails/railtie.rb +10 -0
- data/lib/codebeacon/tracer/src/tracer.rb +113 -0
- data/lib/codebeacon/tracer/version.rb +7 -0
- data/lib/codebeacon-tracer.rb +132 -0
- metadata +193 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: cc0306f363cf17c02cf4e0de4c37df04d52b22f7b992b047767a47e243341a9a
|
4
|
+
data.tar.gz: ba785f9723112678105d34030f1ea3f9960630d6701a3f14041e5d82d554c6c6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c59c1bd83110e7e4214988ca69fa6f08d9533c096b98e4e555c28d2700ef35d05e3c1fd154804d156bc9b022656dbf3b3a6c17e2592df1cc0f67abe73f3aef15
|
7
|
+
data.tar.gz: 7d2ae9552ea537767cb5ef93409565243674a75b65f3b9810ca5f3c894845743aa775def5dd519c4ac99687b057ab157e7b4e2e5b57ea234afb7aaaa84f9c96c
|
@@ -0,0 +1,199 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Codebeacon
|
5
|
+
module Tracer
|
6
|
+
class Configuration
|
7
|
+
MAX_DB_FILES = 100
|
8
|
+
RETURN_VAL_MAX_LENGTH = 1000
|
9
|
+
MAX_CALL_COUNT = 100000000
|
10
|
+
MAX_DEPTH = 99999
|
11
|
+
|
12
|
+
def initialize()
|
13
|
+
@query = ""
|
14
|
+
@exclude_paths = []
|
15
|
+
ensure_db_path
|
16
|
+
end
|
17
|
+
|
18
|
+
def setup
|
19
|
+
exclude_paths << lib_root
|
20
|
+
reload_paths_to_record
|
21
|
+
load_main_config
|
22
|
+
end
|
23
|
+
|
24
|
+
def load_main_config
|
25
|
+
if File.exist?(config_path)
|
26
|
+
config_data = YAML.load_file(config_path)
|
27
|
+
load_exclude_paths(config_data['exclude'])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def load_exclude_paths(excludes)
|
32
|
+
return if excludes.nil?
|
33
|
+
(excludes['paths'] || []).each { |path| exclude_paths << path }
|
34
|
+
(excludes['gems'] || []).each do |gem_name|
|
35
|
+
Gem::Specification.find_all_by_name(gem_name).each do |gem_spec|
|
36
|
+
exclude_paths << gem_spec.gem_dir
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def set_query_config(query)
|
42
|
+
@query = query || ""
|
43
|
+
# self.trace_enabled = @query.include?('rf__trace_enabled=true')
|
44
|
+
self.debug = @query.include?('rf__debug=true')
|
45
|
+
self.dry_run = @query.include?('rf__dry_run=true')
|
46
|
+
# self.local_methods_only = @query.include?('rf__local_methods_only=true')
|
47
|
+
# self.local_lines_only = @query.include?('rf__local_lines_only=true')
|
48
|
+
end
|
49
|
+
|
50
|
+
def ensure_db_path
|
51
|
+
FileUtils.mkpath(db_path)
|
52
|
+
end
|
53
|
+
|
54
|
+
# def load_ruby_flow_config
|
55
|
+
# if File.exist?('.code-beacon.yml')
|
56
|
+
# @config = YAML.load_file('.code-beacon.yml')
|
57
|
+
# end
|
58
|
+
# end
|
59
|
+
|
60
|
+
def lib_root
|
61
|
+
File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..'))
|
62
|
+
end
|
63
|
+
|
64
|
+
def data_dir
|
65
|
+
".code-beacon"
|
66
|
+
end
|
67
|
+
|
68
|
+
def db_path
|
69
|
+
File.join(data_dir, "db")
|
70
|
+
end
|
71
|
+
|
72
|
+
def tmp_dir
|
73
|
+
File.join(data_dir, "tmp")
|
74
|
+
end
|
75
|
+
|
76
|
+
def refresh_path
|
77
|
+
File.join(data_dir, "tmp", "refresh")
|
78
|
+
end
|
79
|
+
|
80
|
+
def paths_path
|
81
|
+
File.join(data_dir, "paths.yml")
|
82
|
+
end
|
83
|
+
|
84
|
+
def config_path
|
85
|
+
File.expand_path(File.join(lib_root, 'config.yml'))
|
86
|
+
end
|
87
|
+
|
88
|
+
def read_paths
|
89
|
+
if File.exist?(paths_path)
|
90
|
+
YAML.load_file(paths_path)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def db_name
|
95
|
+
"codebeacon_tracer"
|
96
|
+
end
|
97
|
+
|
98
|
+
def max_db_files
|
99
|
+
MAX_DB_FILES
|
100
|
+
end
|
101
|
+
|
102
|
+
def gem_path
|
103
|
+
@gem_path ||= ENV['GEM_HOME'] || Gem.paths.home
|
104
|
+
end
|
105
|
+
|
106
|
+
def root_path
|
107
|
+
@root_path ||= defined?(Rails) ? Rails.root.to_s : Dir.pwd
|
108
|
+
end
|
109
|
+
|
110
|
+
def rubylib_path
|
111
|
+
@rubylib_path ||= RbConfig::CONFIG['rubylibdir']
|
112
|
+
end
|
113
|
+
|
114
|
+
def paths_to_record
|
115
|
+
@paths_to_record ||= [Codebeacon::Tracer.config.root_path, *Codebeacon::Tracer.config.read_paths]
|
116
|
+
end
|
117
|
+
|
118
|
+
def reload_paths_to_record
|
119
|
+
@paths_to_record = nil
|
120
|
+
paths_to_record
|
121
|
+
end
|
122
|
+
|
123
|
+
def exclude_paths(*paths)
|
124
|
+
@exclude_paths += paths
|
125
|
+
end
|
126
|
+
|
127
|
+
def trace_enabled?
|
128
|
+
# @trace_enabled
|
129
|
+
true
|
130
|
+
end
|
131
|
+
|
132
|
+
def trace_enabled=(value)
|
133
|
+
@trace_enabled = value
|
134
|
+
end
|
135
|
+
|
136
|
+
def debug?
|
137
|
+
@debug
|
138
|
+
end
|
139
|
+
|
140
|
+
def debug=(value)
|
141
|
+
Codebeacon::Tracer.logger.level = value ? ::Logger::DEBUG : ::Logger::INFO
|
142
|
+
@debug = value
|
143
|
+
end
|
144
|
+
|
145
|
+
def dry_run?
|
146
|
+
@dry_run
|
147
|
+
end
|
148
|
+
|
149
|
+
def dry_run=(value)
|
150
|
+
@dry_run = value
|
151
|
+
end
|
152
|
+
|
153
|
+
def local_methods_only?
|
154
|
+
# @local_methods_only
|
155
|
+
true
|
156
|
+
end
|
157
|
+
|
158
|
+
def local_methods_only=(value)
|
159
|
+
@local_methods_only = value
|
160
|
+
end
|
161
|
+
|
162
|
+
def local_lines_only?
|
163
|
+
# @local_lines_only
|
164
|
+
true
|
165
|
+
end
|
166
|
+
|
167
|
+
def local_lines_only=(value)
|
168
|
+
@local_lines_only = value
|
169
|
+
end
|
170
|
+
|
171
|
+
def skip_internal?
|
172
|
+
true
|
173
|
+
end
|
174
|
+
|
175
|
+
def max_value_length
|
176
|
+
RETURN_VAL_MAX_LENGTH
|
177
|
+
end
|
178
|
+
|
179
|
+
def max_call_count
|
180
|
+
MAX_CALL_COUNT
|
181
|
+
end
|
182
|
+
|
183
|
+
def max_depth
|
184
|
+
MAX_DEPTH
|
185
|
+
end
|
186
|
+
|
187
|
+
def logger
|
188
|
+
@logger ||= Codebeacon::Tracer::Logger.new()
|
189
|
+
end
|
190
|
+
|
191
|
+
def debug_something
|
192
|
+
#### debug return
|
193
|
+
# @return_count += 1
|
194
|
+
# Codebeacon::Tracer.logger.info("Call count: #{@return_count}")
|
195
|
+
# Codebeacon::Tracer.logger.info("Return @depth: #{@depth}, method: #{tp.method_id}, line: #{tp.lineno}, path: #{tp.path}")
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'sqlite3'
|
2
|
+
require 'fileutils'
|
3
|
+
require_relative 'tree_node_mapper'
|
4
|
+
require_relative 'node_source_mapper'
|
5
|
+
|
6
|
+
module Codebeacon
|
7
|
+
module Tracer
|
8
|
+
class DatabaseSchema
|
9
|
+
def initialize
|
10
|
+
@db = initialize_db
|
11
|
+
end
|
12
|
+
|
13
|
+
def db
|
14
|
+
@db
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize_db
|
18
|
+
db_path = Codebeacon::Tracer.config.db_path
|
19
|
+
timestamp = Time.now.strftime("%Y%m%d%H%M%S")
|
20
|
+
db_name = "#{Codebeacon::Tracer.config.db_name}_#{timestamp}.db"
|
21
|
+
db_symlink = File.join(db_path, "#{Codebeacon::Tracer.config.db_name}.db")
|
22
|
+
|
23
|
+
File.delete(db_symlink) if File.exist?(db_symlink)
|
24
|
+
FileUtils.ln_sf(db_name, db_symlink)
|
25
|
+
SQLite3::Database.new(File.join(db_path, db_name))
|
26
|
+
end
|
27
|
+
|
28
|
+
def create_tables
|
29
|
+
MetadataMapper.create_table(db)
|
30
|
+
TreeNodeMapper.create_table(db)
|
31
|
+
NodeSourceMapper.create_table(db)
|
32
|
+
end
|
33
|
+
|
34
|
+
def create_indexes
|
35
|
+
MetadataMapper.create_indexes(db)
|
36
|
+
TreeNodeMapper.create_indexes(db)
|
37
|
+
NodeSourceMapper.create_indexes(db)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.trim_db_files
|
41
|
+
db_path = Codebeacon::Tracer.config.db_path
|
42
|
+
db_files = Dir.glob(File.join(db_path, "*.db"))
|
43
|
+
db_files.reject! { |file| File.symlink?(file) }
|
44
|
+
db_files.sort_by! { |db_file| File.mtime(db_file) }
|
45
|
+
db_files.reverse!
|
46
|
+
|
47
|
+
db_files.each_with_index do |db_file, index|
|
48
|
+
next if index < Codebeacon::Tracer.config.max_db_files
|
49
|
+
File.delete(db_file)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Codebeacon
|
2
|
+
module Tracer
|
3
|
+
class MetadataMapper
|
4
|
+
def initialize(database)
|
5
|
+
@db = database
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.create_table(database)
|
9
|
+
database.execute <<-SQL
|
10
|
+
CREATE TABLE IF NOT EXISTS metadata (
|
11
|
+
id INTEGER PRIMARY KEY,
|
12
|
+
name TEXT,
|
13
|
+
description TEXT
|
14
|
+
);
|
15
|
+
SQL
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.create_indexes(database)
|
19
|
+
end
|
20
|
+
|
21
|
+
def insert(name, description)
|
22
|
+
@db.execute("INSERT INTO metadata (name, description) VALUES (?, ?)", [name, description])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative '../models/node_source'
|
2
|
+
|
3
|
+
module Codebeacon
|
4
|
+
module Tracer
|
5
|
+
class NodeSourceMapper
|
6
|
+
def initialize(database)
|
7
|
+
@db = database
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.create_table(database)
|
11
|
+
database.execute <<-SQL
|
12
|
+
CREATE TABLE IF NOT EXISTS node_sources (
|
13
|
+
id INTEGER PRIMARY KEY,
|
14
|
+
name TEXT NOT NULL,
|
15
|
+
root_path TEXT NOT NULL
|
16
|
+
);
|
17
|
+
SQL
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.create_indexes(database)
|
21
|
+
end
|
22
|
+
|
23
|
+
def insert(name, root_path)
|
24
|
+
@db.execute("INSERT INTO node_sources (name, root_path) VALUES (?, CAST(? AS TEXT))", [name, root_path])
|
25
|
+
@db.last_insert_row_id
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require_relative 'tree_node_mapper'
|
2
|
+
require_relative 'node_source_mapper'
|
3
|
+
require_relative 'metadata_mapper'
|
4
|
+
|
5
|
+
module Codebeacon
|
6
|
+
module Tracer
|
7
|
+
class PersistenceManager
|
8
|
+
|
9
|
+
def self.marshal(name, value, tree_node)
|
10
|
+
begin
|
11
|
+
return value.inspect[0..Codebeacon::Tracer.config.max_value_length]
|
12
|
+
rescue => e
|
13
|
+
begin
|
14
|
+
if Codebeacon::Tracer.config.debug?
|
15
|
+
Codebeacon::Tracer.logger.warn "Marshal inspect failure - attempting to_s fallback for: \"#{name}\", located at: \"#{tree_node.file}:#{tree_node.line}\"\nerror message: \"#{e.message}\", error_location: \"#{e.backtrace[0]}\""
|
16
|
+
end
|
17
|
+
return value.to_s[0..Codebeacon::Tracer.config.max_value_length]
|
18
|
+
rescue => e
|
19
|
+
Codebeacon::Tracer.logger.error "Marshal failure for: \"#{name}\", located at: \"#{tree_node.file}:#{tree_node.line}\"\nerror message: \"#{e.message}\", error_location: \"#{e.backtrace[0]}\""
|
20
|
+
return "--Codebeacon::Tracer ERROR-- could not marshall value. See logs."
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(database)
|
26
|
+
@database = database
|
27
|
+
@tree_node_mapper = TreeNodeMapper.new(database)
|
28
|
+
@node_source_mapper = NodeSourceMapper.new(database)
|
29
|
+
@metadata_mapper = MetadataMapper.new(database)
|
30
|
+
@progress_logger = Codebeacon::Tracer.logger.newProgressLogger("nodes persisted")
|
31
|
+
end
|
32
|
+
|
33
|
+
def save_metadata(name, description)
|
34
|
+
@metadata_mapper.insert(name, description)
|
35
|
+
end
|
36
|
+
|
37
|
+
def save_node_sources(node_sources)
|
38
|
+
node_sources.each do |node_source|
|
39
|
+
next if node_source.nil?
|
40
|
+
node_source.id = @node_source_mapper.insert(node_source.name, node_source.root_path)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def save_trees(trees)
|
45
|
+
Codebeacon::Tracer.logger.info("BEGIN db persistence")
|
46
|
+
begin
|
47
|
+
trees.each do |tree|
|
48
|
+
save_tree(tree.root)
|
49
|
+
end
|
50
|
+
rescue => e
|
51
|
+
Codebeacon::Tracer.logger.error("Error during tree persistence: #{e.message}")
|
52
|
+
Codebeacon::Tracer.logger.error(e.backtrace.join("\n")) if Codebeacon::Tracer.config.debug?
|
53
|
+
# Continue execution without crashing the application
|
54
|
+
ensure
|
55
|
+
@progress_logger.finish()
|
56
|
+
Codebeacon::Tracer.logger.info("END db persistence")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def save_tree(tree_node, parent_id = nil)
|
61
|
+
_save_tree(tree_node, parent_id)
|
62
|
+
end
|
63
|
+
|
64
|
+
def _save_tree(tree_node, parent_id = nil)
|
65
|
+
@progress_logger.increment
|
66
|
+
return if tree_node.nil?
|
67
|
+
|
68
|
+
begin
|
69
|
+
node_id = @tree_node_mapper.insert(
|
70
|
+
tree_node.file,
|
71
|
+
tree_node.line,
|
72
|
+
tree_node.method.to_s,
|
73
|
+
tree_node.tp_class.to_s,
|
74
|
+
tree_node.tp_defined_class.to_s,
|
75
|
+
tree_node.tp_class_name.to_s,
|
76
|
+
tree_node.self_type.to_s,
|
77
|
+
tree_node.depth,
|
78
|
+
tree_node.caller,
|
79
|
+
tree_node.gem_entry,
|
80
|
+
parent_id,
|
81
|
+
tree_node.block,
|
82
|
+
tree_node.node_source&.id,
|
83
|
+
_return_value(tree_node)
|
84
|
+
)
|
85
|
+
|
86
|
+
unless tree_node.depth_truncated?
|
87
|
+
tree_node.children.each do |child|
|
88
|
+
_save_tree(child, node_id)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
rescue => e
|
92
|
+
Codebeacon::Tracer.logger.error("Error saving tree node: #{e.message}")
|
93
|
+
Codebeacon::Tracer.logger.error("Node details: file=#{tree_node.file}, line=#{tree_node.line}, method=#{tree_node.method}") if Codebeacon::Tracer.config.debug?
|
94
|
+
# Continue with siblings and other nodes without crashing
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def _return_value(node)
|
99
|
+
if node.method == :initialize
|
100
|
+
return nil
|
101
|
+
else
|
102
|
+
PersistenceManager.marshal(node.method, node.return_value, node)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'sqlite3'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Codebeacon
|
5
|
+
module Tracer
|
6
|
+
class TreeNodeMapper
|
7
|
+
def initialize(database)
|
8
|
+
@db = database
|
9
|
+
end
|
10
|
+
|
11
|
+
def insert(file, line, method, tp_class, tp_defined_class, tp_class_name, self_type, depth, caller, gem_entry, parent_id, block, node_source_id, return_value)
|
12
|
+
@db.execute(<<-SQL,
|
13
|
+
INSERT INTO treenodes
|
14
|
+
(
|
15
|
+
file, line, method, tp_class, tp_defined_class, tp_class_name, self_type, depth, caller,
|
16
|
+
gemEntry, parent_id, block, node_source_id, return_value
|
17
|
+
)
|
18
|
+
VALUES
|
19
|
+
(
|
20
|
+
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
21
|
+
)
|
22
|
+
SQL
|
23
|
+
file, line, method, tp_class, tp_defined_class, tp_class_name, self_type, depth, caller,
|
24
|
+
gem_entry ? 1 : 0, parent_id, block ? 1 : 0, node_source_id, return_value)
|
25
|
+
|
26
|
+
@db.last_insert_row_id
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.create_table(database)
|
30
|
+
database.execute <<-SQL
|
31
|
+
CREATE TABLE IF NOT EXISTS treenodes (
|
32
|
+
id INTEGER PRIMARY KEY,
|
33
|
+
file TEXT,
|
34
|
+
line INTEGER,
|
35
|
+
method TEXT,
|
36
|
+
tp_class TEXT,
|
37
|
+
tp_defined_class TEXT,
|
38
|
+
tp_class_name TEXT,
|
39
|
+
self_type TEXT,
|
40
|
+
depth INTEGER,
|
41
|
+
caller TEXT,
|
42
|
+
gemEntry INTEGER,
|
43
|
+
parent_id INTEGER,
|
44
|
+
block INTEGER,
|
45
|
+
node_source_id INTEGER,
|
46
|
+
return_value TEXT,
|
47
|
+
FOREIGN KEY (parent_id) REFERENCES treenodes(id),
|
48
|
+
FOREIGN KEY (node_source_id) REFERENCES node_sources(id)
|
49
|
+
)
|
50
|
+
SQL
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.create_indexes(database)
|
54
|
+
database.execute("CREATE INDEX IF NOT EXISTS IDX_treenode_parent_id ON treenodes(parent_id)")
|
55
|
+
database.execute("CREATE INDEX IF NOT EXISTS IDX_treenode_node_source_id ON treenodes(node_source_id)")
|
56
|
+
database.execute("CREATE INDEX IF NOT EXISTS IDX_treenode_file ON treenodes(file)")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Codebeacon
|
4
|
+
module Tracer
|
5
|
+
class Logger
|
6
|
+
FILENAME = "codebeacon_tracer.log"
|
7
|
+
attr_reader :logger
|
8
|
+
|
9
|
+
def initialize(level = nil)
|
10
|
+
level ||= Codebeacon::Tracer.config.debug? ? ::Logger::DEBUG : ::Logger::INFO
|
11
|
+
@logger ||= ::Logger.new(File.join(Codebeacon::Tracer.config.data_dir, FILENAME), 3, 104857600, level: level)
|
12
|
+
end
|
13
|
+
|
14
|
+
def newProgressLogger(*args)
|
15
|
+
ProgressLogger.new(@logger, *args)
|
16
|
+
end
|
17
|
+
|
18
|
+
def debug(message, *args, &block)
|
19
|
+
return unless Codebeacon::Tracer.config.debug?
|
20
|
+
@logger.debug(message, *args, &block)
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_missing(method, *args, &block)
|
24
|
+
if @logger.respond_to?(method)
|
25
|
+
@logger.send(method, *args, &block)
|
26
|
+
else
|
27
|
+
super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def respond_to_missing?(method, include_private = false)
|
32
|
+
@logger.respond_to?(method, include_private) || super
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class ProgressLogger
|
37
|
+
PROGRESS_LOG_INTERVAL = 1000
|
38
|
+
|
39
|
+
def initialize(logger, msg, interval = PROGRESS_LOG_INTERVAL)
|
40
|
+
@logger = logger
|
41
|
+
@msg = msg
|
42
|
+
@interval = interval
|
43
|
+
@count = 0
|
44
|
+
end
|
45
|
+
|
46
|
+
def increment()
|
47
|
+
@count += 1
|
48
|
+
if @count % @interval == 0
|
49
|
+
@logger.info(@count.to_s + " " + @msg)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def finish()
|
54
|
+
@logger.info("Finished: " + @count.to_s + " " + @msg)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Codebeacon
|
2
|
+
module Tracer
|
3
|
+
class CallTree
|
4
|
+
@thread_id_mutex = Mutex.new
|
5
|
+
@thread_id = 0
|
6
|
+
|
7
|
+
attr_reader :thread, :root, :current_node, :depth, :call_count, :block_call_count
|
8
|
+
|
9
|
+
def self.next_thread_id
|
10
|
+
@thread_id_mutex.synchronize do
|
11
|
+
@thread_id += 1
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(thread)
|
16
|
+
@thread = thread
|
17
|
+
root_name = (thread.name || "thread") + " (#{CallTree.next_thread_id})"
|
18
|
+
@root = TreeNode.new(method: root_name)
|
19
|
+
@root.file, @root.line = __FILE__, __LINE__
|
20
|
+
@current_node = @root
|
21
|
+
@depth = 0
|
22
|
+
@call_count = 0
|
23
|
+
@block_call_count = 0
|
24
|
+
end
|
25
|
+
|
26
|
+
def total_call_count
|
27
|
+
@call_count + @block_call_count
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_call()
|
31
|
+
@call_count += 1
|
32
|
+
add_node()
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_block_call()
|
36
|
+
@block_call_count += 1
|
37
|
+
add_node()
|
38
|
+
end
|
39
|
+
|
40
|
+
def add_node()
|
41
|
+
new_node = TreeNode.new()
|
42
|
+
@current_node.children << new_node
|
43
|
+
new_node.parent = @current_node
|
44
|
+
@depth += 1
|
45
|
+
@current_node = new_node
|
46
|
+
end
|
47
|
+
|
48
|
+
def add_return()
|
49
|
+
@depth -= 1
|
50
|
+
@current_node = @current_node.parent if @current_node
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Codebeacon
|
2
|
+
module Tracer
|
3
|
+
class NodeBuilder
|
4
|
+
class << self
|
5
|
+
def backtrace_location_eql(loc1, loc2)
|
6
|
+
loc1.absolute_path == loc2.absolute_path && loc1.lineno == loc2.lineno && loc1.label == loc2.label
|
7
|
+
end
|
8
|
+
|
9
|
+
def trace_method_call(call_tree, tp, tp_caller)
|
10
|
+
call_tree.add_call
|
11
|
+
trace_call(call_tree, tp, tp_caller, :get_method_ast)
|
12
|
+
end
|
13
|
+
|
14
|
+
def trace_block_call(call_tree, tp, tp_caller)
|
15
|
+
current_context = call_tree.add_block_call
|
16
|
+
current_context.block = true
|
17
|
+
trace_call(call_tree, tp, tp_caller, :get_block_ast)
|
18
|
+
end
|
19
|
+
|
20
|
+
def trace_return(call_tree, tp)
|
21
|
+
begin
|
22
|
+
current_context = call_tree.current_node
|
23
|
+
variable_values = {}
|
24
|
+
current_context.return_value = "--Codebeacon::Tracer ERROR-- could not capture return value"
|
25
|
+
previous_line = current_context.trace_status.previous_line
|
26
|
+
current_context.return_value = tp.return_value
|
27
|
+
ensure
|
28
|
+
call_tree.add_return()
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private def trace_call(call_tree, tp, tp_caller, ast_get_method)
|
33
|
+
current_context = call_tree.current_node
|
34
|
+
|
35
|
+
current_context.file = File.absolute_path(tp.path)
|
36
|
+
current_context.node_source = NodeSource.find(tp.path)
|
37
|
+
current_context.line = tp.lineno
|
38
|
+
current_context.object_id = tp.self.object_id
|
39
|
+
current_context.method = tp.method_id
|
40
|
+
klass = TPKlass.new(tp)
|
41
|
+
|
42
|
+
current_context.tp_class = klass.tp_class.to_s
|
43
|
+
current_context.tp_defined_class = klass.defined_class
|
44
|
+
current_context.tp_class_name = klass.tp_class_name
|
45
|
+
current_context.self_type = klass.type
|
46
|
+
current_context.depth = call_tree.depth
|
47
|
+
|
48
|
+
gem_entry = false
|
49
|
+
if Codebeacon::Tracer.config.gem_path \
|
50
|
+
&& !Codebeacon::Tracer.config.gem_path.empty? \
|
51
|
+
&& tp.path.start_with?(Codebeacon::Tracer.config.gem_path) # && caller[1].start_with?(Codebeacon::Tracer.config.root_path)
|
52
|
+
gem_entry = true
|
53
|
+
end
|
54
|
+
current_context.gem_entry = gem_entry
|
55
|
+
current_context.caller = ""
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Codebeacon
|
4
|
+
module Tracer
|
5
|
+
class NodeSource
|
6
|
+
attr_accessor :id, :name, :root_path
|
7
|
+
@instances = []
|
8
|
+
|
9
|
+
class << self
|
10
|
+
attr_accessor :instances
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(name, root_path)
|
14
|
+
@name = name
|
15
|
+
@root_path = root_path
|
16
|
+
self.class.instances << self
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.find(path)
|
20
|
+
return nil if path.nil?
|
21
|
+
Pathname.new(path).ascend do |dir|
|
22
|
+
source = self.instances.find { |ns| dir.to_s == ns.root_path }
|
23
|
+
return source if source
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.clear
|
28
|
+
self.instances = []
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|