/
data_serialization.rb
153 lines (135 loc) · 4.45 KB
/
data_serialization.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
require 'fileutils'
module NewRelic
# Handles serialization of data to disk, to save on contacting the
# server. Lowers both server and client overhead, if the disk is not overloaded
class DataSerialization
module ClassMethods
# Check whether the store is too large, too old, or the
# semaphore file is too old. If so, we should send the data
# right away. If not, we presumably store it for later sending
# (handled elsewhere)
def should_send_data?
NewRelic::Control.instance.disable_serialization? || store_too_large? || store_too_old? || semaphore_too_old?
rescue (ENV['CATCH_EXCEPTION'] ? Exception : Class.new) => e
# This is not what we really should do here, but the fail-safe
# behavior is to do what the older agent did: send data every
# time we think we might want to send data.
true
end
# A combined locked read/write from the store file - reduces
# contention by not acquiring the lock and file handle twice
def read_and_write_to_file
with_locked_store do |f|
result = (yield get_data_from_file(f))
f.rewind
f.truncate(0)
write_contents_nonblockingly(f, dump(result)) if result
end
end
# touches the age file that determines whether we should send
# data now or not
def update_last_sent!
FileUtils.touch(semaphore_path)
end
private
def store_too_large?
size = File.size(file_path) > max_size
NewRelic::Control.instance.log.debug("Store was oversize, sending data") if size
size
rescue Errno::ENOENT
FileUtils.touch(file_path)
retry
end
def store_too_old?
age = (Time.now.to_i - File.mtime(file_path).to_i)
NewRelic::Control.instance.log.debug("Store was #{age} seconds old, sending data") if age > 60
age > 50
rescue Errno::ENOENT
FileUtils.touch(file_path)
retry
end
def semaphore_too_old?
age = (Time.now.to_i - File.mtime(semaphore_path).to_i)
NewRelic::Control.instance.log.debug("Pid was #{age} seconds old, sending data") if age > 60
age > 60
rescue Errno::ENOENT
FileUtils.touch(semaphore_path)
retry
end
def open_arguments
if defined?(Encoding)
[file_path, File::RDWR | File::CREAT, {:internal_encoding => nil}]
else
[file_path, File::RDWR | File::CREAT]
end
end
def with_locked_store
File.open(*open_arguments) do |f|
f.flock(File::LOCK_EX)
begin
yield(f)
ensure
f.flock(File::LOCK_UN)
end
end
rescue Exception => e
NewRelic::Control.instance.log.error("Error serializing data to disk: #{e.inspect}")
NewRelic::Control.instance.log.debug(e.backtrace.split("\n"))
end
def get_data_from_file(f)
data = read_until_eof_error(f)
result = load(data)
f.truncate(0)
result
end
def write_contents_nonblockingly(f, string)
result = 0
while(result < string.length)
result += f.write_nonblock(string)
end
rescue Errno::EAGAIN, Errno::EINTR
IO.select(nil, [f])
retry
end
def read_until_eof_error(f)
accumulator = ""
while(true)
accumulator << f.read_nonblock(10_000)
end
rescue Errno::EAGAIN, Errno::EINTR
IO.select([f])
retry
rescue EOFError
accumulator
end
def max_size
10_000
end
def dump(object)
Marshal.dump(object)
end
def load(dump)
if dump.size == 0
NewRelic::Control.instance.log.debug("Spool file empty.")
return nil
end
Marshal.load(dump)
rescue ArgumentError, TypeError => e
NewRelic::Control.instance.log.error("Error loading data from newrelic_agent_store.db: #{e.inspect}")
NewRelic::Control.instance.log.debug(e.backtrace.inspect)
nil
end
def truncate_file
FileUtils.touch(file_path)
File.truncate(file_path, 0)
end
def file_path
"#{NewRelic::Control.instance.log_file_path}/newrelic_agent_store.db"
end
def semaphore_path
"#{NewRelic::Control.instance.log_file_path}/newrelic_agent_store.age"
end
end
extend ClassMethods
end
end