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
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative 'call_tree'
|
2
|
+
|
3
|
+
module Codebeacon
|
4
|
+
module Tracer
|
5
|
+
class ThreadLocalCallTreeManager
|
6
|
+
attr_reader :trees, :trace_id
|
7
|
+
|
8
|
+
def initialize(trace_id)
|
9
|
+
@trees = []
|
10
|
+
@trace_id = trace_id
|
11
|
+
end
|
12
|
+
|
13
|
+
def current()
|
14
|
+
Thread.current[thread_key] ||= begin
|
15
|
+
CallTree.new(Thread.current).tap do |tree|
|
16
|
+
Codebeacon::Tracer.logger.debug("Creating new call tree for thread: #{Thread.current} with trace_id: #{trace_id}")
|
17
|
+
@trees << tree
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def cleanup()
|
23
|
+
Thread.list.each do |thread|
|
24
|
+
if thread[thread_key]
|
25
|
+
thread[thread_key] = nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private def thread_key()
|
31
|
+
"call_tree_#{trace_id}".to_sym
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Codebeacon
|
2
|
+
module Tracer
|
3
|
+
class TPKlass
|
4
|
+
def initialize(tp)
|
5
|
+
@tp = tp
|
6
|
+
end
|
7
|
+
|
8
|
+
def tp_class
|
9
|
+
klass = @tp.self.class
|
10
|
+
while klass && klass.to_s =~ /Class:0x/
|
11
|
+
klass = klass.superclass
|
12
|
+
end
|
13
|
+
klass
|
14
|
+
end
|
15
|
+
|
16
|
+
def defined_class
|
17
|
+
klass = @tp.defined_class.to_s.sub("#<", "").sub(">", "")
|
18
|
+
if klass.match(/^(Class|Module):/)
|
19
|
+
klass = klass.split(":")[1..].join(":")
|
20
|
+
elsif klass.match(/:0x[0-9a-f]+$/)
|
21
|
+
klass = klass.split(":")[0..-2].join(":")
|
22
|
+
klass += " Singleton"
|
23
|
+
end
|
24
|
+
klass
|
25
|
+
end
|
26
|
+
|
27
|
+
def tp_class_name
|
28
|
+
if @tp.self.is_a?(Module)
|
29
|
+
@tp.self.name
|
30
|
+
else
|
31
|
+
klass = @tp.self.class
|
32
|
+
while klass && klass.to_s =~ /Class:0x/
|
33
|
+
klass = klass.superclass
|
34
|
+
end
|
35
|
+
klass.name
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def type
|
40
|
+
case @tp.self
|
41
|
+
when Class
|
42
|
+
:Class
|
43
|
+
when Module
|
44
|
+
:Module
|
45
|
+
when Object
|
46
|
+
:Object
|
47
|
+
else
|
48
|
+
:Unknown
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
# store node memory in a format that can be directly loaded 1:1 into sqlite3
|
4
|
+
# could use rocksdb or something else
|
5
|
+
# could use lmdb
|
6
|
+
# could use leveldb
|
7
|
+
# actually log if this speeds up developer time - would be really cool to show!
|
8
|
+
module Codebeacon
|
9
|
+
module Tracer
|
10
|
+
class TreeNode
|
11
|
+
class TraceStatus < Struct.new(:previous_line)
|
12
|
+
def any_lines_traced?
|
13
|
+
!previous_line.nil?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
# attr_accessor :file, :line, :method, :depth, :caller, :gem_entry, :children, :parent, :block, :locals, :ast, :return_value, :linevars
|
17
|
+
|
18
|
+
# def initialize(file: nil, line: nil, method: nil, depth: 0, caller: "", gem_entry: false, parent: nil, block: false, locals: [], ast: nil, return_value: "")
|
19
|
+
# @file = file
|
20
|
+
# @line = line
|
21
|
+
# @method = method
|
22
|
+
# @depth = depth
|
23
|
+
# @caller = caller
|
24
|
+
# @gem_entry = gem_entry
|
25
|
+
# @parent = parent
|
26
|
+
# @block = block
|
27
|
+
# @locals = locals
|
28
|
+
# @ast = ast
|
29
|
+
# @return_value = return_value
|
30
|
+
# @linevars = {}
|
31
|
+
# @children = []
|
32
|
+
# end
|
33
|
+
|
34
|
+
attr_accessor :file, :line, :method, :object_id, :tp_class, :tp_defined_class, :tp_class_name, :self_type, :depth, :caller, :gem_entry, :children, :parent, :block, :locals, :return_value, :linevars, :node_source, :trace_status, :script, :backtrace_count, :backtrace_location, :script_binding, :script_self
|
35
|
+
|
36
|
+
def initialize(file: nil, line: nil, object_id: nil, method: nil, tp_class: nil, tp_defined_class: nil, tp_class_name: nil, self_type: nil, depth: 0, caller: "", gem_entry: false, parent: nil, block: false, locals: [], return_value: nil, node_source: nil, script: false)
|
37
|
+
@file = file
|
38
|
+
@line = line
|
39
|
+
@method = method
|
40
|
+
@object_id = object_id
|
41
|
+
@tp_class = tp_class
|
42
|
+
@tp_defined_class = tp_defined_class
|
43
|
+
@tp_class_name = tp_class_name
|
44
|
+
@self_type = self_type
|
45
|
+
@children = []
|
46
|
+
@depth = depth
|
47
|
+
@gem_entry = gem_entry
|
48
|
+
@caller = caller
|
49
|
+
@parent = parent
|
50
|
+
@block = block
|
51
|
+
@locals = locals
|
52
|
+
@return_value = return_value
|
53
|
+
@linevars = Hash.new { |h, k| h[k] = {} }
|
54
|
+
@node_source = node_source
|
55
|
+
@trace_status = TraceStatus.new(nil)
|
56
|
+
@script = script
|
57
|
+
@backtrace_count = 0
|
58
|
+
@backtrace_location = nil
|
59
|
+
@script_binding = nil
|
60
|
+
@script_self = nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def add_line(lineno, variables)
|
64
|
+
@linevars[lineno] = @linevars[lineno].merge(variables)
|
65
|
+
end
|
66
|
+
|
67
|
+
def set_args(lineno, variables)
|
68
|
+
@linevars[lineno] = @linevars[lineno].merge(variables)
|
69
|
+
end
|
70
|
+
|
71
|
+
def inspect
|
72
|
+
ivar_inspect = instance_variables.reject { |ivar| [:@children].include?(ivar) }.map do |ivar|
|
73
|
+
"#{ivar.to_s}=#{instance_variable_get(ivar).inspect}"
|
74
|
+
end
|
75
|
+
ivar_inspect << "@children=#<#{@children.map(&:to_s)}>"
|
76
|
+
"#<#{self.class.name}:0x#{self.object_id.to_s} #{ivar_inspect.join(', ')}>"
|
77
|
+
end
|
78
|
+
|
79
|
+
def inspect_tree(attrs = [], depth = 0)
|
80
|
+
str = file.split("/").last + ":#{script ? "script" : method}"
|
81
|
+
if !attrs.empty?
|
82
|
+
attr_values = attrs.map { |k, v| "#{k}=#{v.inspect}" }.join(', ')
|
83
|
+
str += " " + attrs
|
84
|
+
end
|
85
|
+
str += children.map { |c| "\n" + " " * (depth + 1) * 2 + c.inspect_tree(attrs, depth + 1) }.join()
|
86
|
+
return str
|
87
|
+
end
|
88
|
+
|
89
|
+
def to_h
|
90
|
+
children = depth > Codebeacon::Tracer.config.max_depth ? nil : @children.map(&:to_h)
|
91
|
+
is_truncated = depth > Codebeacon::Tracer.config.max_depth ? true : false
|
92
|
+
{
|
93
|
+
file: @file,
|
94
|
+
line: @line,
|
95
|
+
method: @method,
|
96
|
+
class: @tp_class,
|
97
|
+
tp_defined_class: @tp_defined_class,
|
98
|
+
tp_class_name: @tp_class_name,
|
99
|
+
class_name: @class_name,
|
100
|
+
self_type: @self_type,
|
101
|
+
gemEntry: @gem_entry,
|
102
|
+
caller: @caller,
|
103
|
+
isDepthTruncated: is_truncated,
|
104
|
+
children: children
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
def depth_truncated?
|
109
|
+
@depth > Codebeacon::Tracer.config.max_depth && @children.count > 0
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Codebeacon
|
2
|
+
module Tracer
|
3
|
+
class Middleware
|
4
|
+
def initialize(app)
|
5
|
+
@app = app
|
6
|
+
@first_run = true
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
response = nil
|
11
|
+
# tracer = Tracer.news
|
12
|
+
begin
|
13
|
+
Codebeacon::Tracer.config.set_query_config(env['QUERY_STRING'])
|
14
|
+
if !@first_run #Codebeacon::Tracer.config.trace_enabled? && !@first_run
|
15
|
+
Codebeacon::Tracer.trace do |tracer|
|
16
|
+
dry_run_log = Codebeacon::Tracer.config.dry_run? ? "--DRY RUN-- " : ""
|
17
|
+
Codebeacon::Tracer.logger.info(dry_run_log + "Tracing enabled for URI=#{env['REQUEST_URI']}")
|
18
|
+
response = @app.call(env).tap do |_|
|
19
|
+
Codebeacon::Tracer.logger.info("Tracing disabled for URI=#{env['REQUEST_URI']}")
|
20
|
+
end
|
21
|
+
begin
|
22
|
+
params = env['action_dispatch.request.parameters'].dup
|
23
|
+
tracer.name = "#{params.delete('controller')}##{params.delete('action')}"
|
24
|
+
tracer.description = params.to_json
|
25
|
+
rescue => e
|
26
|
+
Codebeacon::Tracer.logger.error("Error setting tracer metadata: #{e.message}")
|
27
|
+
end
|
28
|
+
response
|
29
|
+
end
|
30
|
+
else
|
31
|
+
if Codebeacon::Tracer.config.trace_enabled? && @first_run
|
32
|
+
Codebeacon::Tracer.logger.info("Bypassing first request for performance.")
|
33
|
+
end
|
34
|
+
@first_run = false if @first_run
|
35
|
+
response = @app.call(env)
|
36
|
+
end
|
37
|
+
rescue => e
|
38
|
+
Codebeacon::Tracer.logger.error("Error in middleware: #{e.message}")
|
39
|
+
Codebeacon::Tracer.logger.error(e.backtrace.join("\n")) if Codebeacon::Tracer.config.debug?
|
40
|
+
# Ensure the request is processed even if tracing fails
|
41
|
+
response = @app.call(env) if response.nil?
|
42
|
+
end
|
43
|
+
response
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require_relative 'models/node_builder'
|
2
|
+
require_relative 'models/thread_local_call_tree_manager'
|
3
|
+
|
4
|
+
module Codebeacon
|
5
|
+
module Tracer
|
6
|
+
class Tracer
|
7
|
+
attr_reader :id, :tree_manager
|
8
|
+
attr_accessor :name, :description
|
9
|
+
|
10
|
+
def initialize(name = nil, description = nil)
|
11
|
+
@progress_logger = Codebeacon::Tracer.logger.newProgressLogger("calls traced")
|
12
|
+
@traces = [trace_call, trace_b_call, trace_return, trace_b_return]
|
13
|
+
@name = name
|
14
|
+
@description = description
|
15
|
+
@trace_id = SecureRandom.uuid
|
16
|
+
@tree_manager = ThreadLocalCallTreeManager.new(@trace_id)
|
17
|
+
end
|
18
|
+
|
19
|
+
def id()
|
20
|
+
@trace_id
|
21
|
+
end
|
22
|
+
|
23
|
+
def call_tree()
|
24
|
+
@tree_manager.current()
|
25
|
+
end
|
26
|
+
|
27
|
+
def start()
|
28
|
+
@progress_logger = Codebeacon::Tracer.logger.newProgressLogger("calls traced")
|
29
|
+
start_traces
|
30
|
+
end
|
31
|
+
|
32
|
+
def stop()
|
33
|
+
stop_traces
|
34
|
+
@progress_logger.finish()
|
35
|
+
end
|
36
|
+
|
37
|
+
def cleanup()
|
38
|
+
@tree_manager.cleanup
|
39
|
+
end
|
40
|
+
|
41
|
+
def start_traces
|
42
|
+
dry_run_log = Codebeacon::Tracer.config.dry_run? ? "--DRY RUN-- " : ""
|
43
|
+
Codebeacon::Tracer.logger.info("#{dry_run_log}Starting trace: #{id}")
|
44
|
+
@traces.each do |trace|
|
45
|
+
trace.enable
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def stop_traces
|
50
|
+
@traces.each do |trace|
|
51
|
+
trace.disable
|
52
|
+
end
|
53
|
+
Codebeacon::Tracer.logger.info("END tracing")
|
54
|
+
end
|
55
|
+
|
56
|
+
def enable_traces
|
57
|
+
start
|
58
|
+
return yield
|
59
|
+
ensure
|
60
|
+
stop
|
61
|
+
end
|
62
|
+
|
63
|
+
def trace_call
|
64
|
+
trace(:call) do |tp|
|
65
|
+
NodeBuilder.trace_method_call(call_tree, tp, Kernel.caller[2..])
|
66
|
+
ensure
|
67
|
+
@progress_logger.increment()
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def trace_b_call
|
72
|
+
trace(:b_call) do |tp|
|
73
|
+
NodeBuilder.trace_block_call(call_tree, tp, Kernel.caller[2..])
|
74
|
+
ensure
|
75
|
+
@progress_logger.increment()
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def trace_return
|
80
|
+
trace(:return) do |tp|
|
81
|
+
NodeBuilder.trace_return(call_tree, tp)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def trace_b_return
|
86
|
+
trace(:b_return) do |tp|
|
87
|
+
NodeBuilder.trace_return(call_tree, tp)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def trace(type)
|
92
|
+
TracePoint.new(type) do |tp|
|
93
|
+
paths = [tp.path]
|
94
|
+
# capture calls and returns to skipped paths from non skipped paths. All I need is the return value to display in recorded files, but the code doesn't yet support this without tracing the entire call and return.
|
95
|
+
if [:call, :b_call, :return, :b_return].include?(type)
|
96
|
+
paths << Kernel.caller(1..1)[0]
|
97
|
+
end
|
98
|
+
next if skip_methods?(paths)
|
99
|
+
yield tp
|
100
|
+
rescue => e
|
101
|
+
Codebeacon::Tracer.logger.error("TracePoint(#{type}) #{tp.path} #{e.message}")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def skip_methods?(paths)
|
106
|
+
paths.all? do |path|
|
107
|
+
path.nil? || Codebeacon::Tracer.config.exclude_paths.any?{ |exclude_path| path.start_with?(exclude_path) } ||
|
108
|
+
Codebeacon::Tracer.config.local_methods_only? && !path.start_with?(Codebeacon::Tracer.config.root_path)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'parser/current'
|
4
|
+
require_relative "codebeacon/tracer/version"
|
5
|
+
require 'securerandom'
|
6
|
+
|
7
|
+
# Load all the source files
|
8
|
+
Dir[File.join(File.dirname(__FILE__), 'codebeacon', 'tracer', 'src', '*.rb')].each { |file| require file }
|
9
|
+
Dir[File.join(File.dirname(__FILE__), 'codebeacon', 'tracer', 'src', 'models', '*.rb')].each { |file| require file }
|
10
|
+
Dir[File.join(File.dirname(__FILE__), 'codebeacon', 'tracer', 'src', 'data', '*.rb')].each { |file| require file }
|
11
|
+
Dir[File.join(File.dirname(__FILE__), 'codebeacon', 'tracer', 'src', 'rails', '*.rb')].each { |file| require file } if defined?(Rails::Railtie)
|
12
|
+
|
13
|
+
module Codebeacon
|
14
|
+
# The Tracer module provides tools to trace and analyze the runtime performance
|
15
|
+
# of your Ruby applications. It captures method calls, execution times, and generates
|
16
|
+
# reports to help identify bottlenecks.
|
17
|
+
#
|
18
|
+
# @example Tracing a block of code
|
19
|
+
# Codebeacon::Tracer.trace("My Trace", "Description of what I'm tracing") do |tracer|
|
20
|
+
# # Your code to analyze goes here
|
21
|
+
# some_method_to_analyze
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
module Tracer
|
25
|
+
class << self
|
26
|
+
# @return [ThreadLocalCallTreeManager] The current tree manager
|
27
|
+
attr_reader :tree_manager
|
28
|
+
|
29
|
+
# Returns the configuration object for Codebeacon::Tracer
|
30
|
+
# @return [Configuration] The configuration object
|
31
|
+
def config
|
32
|
+
@config ||= Configuration.new
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns the current call tree
|
36
|
+
# @return [CallTree] The current call tree
|
37
|
+
def current_tree
|
38
|
+
@tracer&.tree_manager&.current()
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns the logger instance
|
42
|
+
# @return [Logger] The logger instance
|
43
|
+
def logger
|
44
|
+
config.logger
|
45
|
+
end
|
46
|
+
|
47
|
+
# Traces a block of code and collects runtime information
|
48
|
+
#
|
49
|
+
# @param name [String, nil] Optional name for the trace
|
50
|
+
# @param description [String, nil] Optional description for the trace
|
51
|
+
# @yield [tracer] Yields the tracer object to the block
|
52
|
+
# @yieldparam tracer [Tracer] The tracer object
|
53
|
+
# @return [Object] The result of the block
|
54
|
+
def trace(name = nil, description = nil)
|
55
|
+
begin
|
56
|
+
setup
|
57
|
+
@tracer = Tracer.new(name, description)
|
58
|
+
result = @tracer.enable_traces do
|
59
|
+
yield @tracer
|
60
|
+
end
|
61
|
+
persist(@tracer.name, @tracer.description)
|
62
|
+
cleanup
|
63
|
+
result
|
64
|
+
rescue => e
|
65
|
+
Codebeacon::Tracer.logger.error("Error during tracing: #{e.message}")
|
66
|
+
Codebeacon::Tracer.logger.error(e.backtrace.join("\n")) if Codebeacon::Tracer.config.debug?
|
67
|
+
# Continue execution without crashing the application
|
68
|
+
yield nil if block_given?
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Starts tracing without a block
|
73
|
+
# @return [void]
|
74
|
+
def start
|
75
|
+
setup
|
76
|
+
@tracer = Tracer.new()
|
77
|
+
@tracer.start
|
78
|
+
end
|
79
|
+
|
80
|
+
# Stops tracing and persists the results
|
81
|
+
# @return [void]
|
82
|
+
def stop
|
83
|
+
@tracer.stop
|
84
|
+
persist
|
85
|
+
cleanup
|
86
|
+
end
|
87
|
+
|
88
|
+
private def setup
|
89
|
+
Codebeacon::Tracer.config.setup
|
90
|
+
@app_node = NodeSource.new('app', Codebeacon::Tracer.config.root_path)
|
91
|
+
@gem_node = NodeSource.new('gem', Codebeacon::Tracer.config.gem_path)
|
92
|
+
@rubylib_node = NodeSource.new('rubylib', Codebeacon::Tracer.config.rubylib_path)
|
93
|
+
end
|
94
|
+
|
95
|
+
private def persist(name = "", description = "")
|
96
|
+
unless Codebeacon::Tracer.config.dry_run?
|
97
|
+
begin
|
98
|
+
schema = DatabaseSchema.new
|
99
|
+
schema.create_tables
|
100
|
+
DatabaseSchema.trim_db_files
|
101
|
+
pm = PersistenceManager.new(schema.db)
|
102
|
+
ordered_sources = [ @app_node, @gem_node, @rubylib_node ]
|
103
|
+
pm.save_metadata(name, description)
|
104
|
+
pm.save_node_sources(ordered_sources)
|
105
|
+
pm.save_trees(@tracer.tree_manager.trees)
|
106
|
+
schema.create_indexes
|
107
|
+
schema.db.close
|
108
|
+
touch_refresh
|
109
|
+
rescue => e
|
110
|
+
Codebeacon::Tracer.logger.error("Error during persistence: #{e.message}")
|
111
|
+
Codebeacon::Tracer.logger.error(e.backtrace.join("\n")) if Codebeacon::Tracer.config.debug?
|
112
|
+
# Continue execution without crashing the application
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
private def cleanup
|
118
|
+
NodeSource.clear
|
119
|
+
@tracer.cleanup
|
120
|
+
end
|
121
|
+
|
122
|
+
private def touch_refresh
|
123
|
+
FileUtils.mkdir_p(Codebeacon::Tracer.config.tmp_dir) unless File.exist?(Codebeacon::Tracer.config.tmp_dir)
|
124
|
+
if File.exist?(Codebeacon::Tracer.config.refresh_path)
|
125
|
+
File.utime(Time.now, Time.now, Codebeacon::Tracer.config.refresh_path)
|
126
|
+
else
|
127
|
+
File.open(Codebeacon::Tracer.config.refresh_path, 'w') {}
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|