genki-newrelic_rpm 2.10.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (158) hide show
  1. data/CHANGELOG +316 -0
  2. data/LICENSE +37 -0
  3. data/Manifest +156 -0
  4. data/README.md +138 -0
  5. data/Rakefile +22 -0
  6. data/bin/mongrel_rpm +33 -0
  7. data/bin/newrelic_cmd +4 -0
  8. data/cert/cacert.pem +34 -0
  9. data/genki-newrelic_rpm.gemspec +32 -0
  10. data/init.rb +38 -0
  11. data/install.rb +37 -0
  12. data/lib/new_relic/agent.rb +280 -0
  13. data/lib/new_relic/agent/agent.rb +627 -0
  14. data/lib/new_relic/agent/chained_call.rb +13 -0
  15. data/lib/new_relic/agent/collection_helper.rb +61 -0
  16. data/lib/new_relic/agent/error_collector.rb +125 -0
  17. data/lib/new_relic/agent/instrumentation/active_merchant.rb +18 -0
  18. data/lib/new_relic/agent/instrumentation/active_record_instrumentation.rb +83 -0
  19. data/lib/new_relic/agent/instrumentation/authlogic.rb +8 -0
  20. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +368 -0
  21. data/lib/new_relic/agent/instrumentation/data_mapper.rb +90 -0
  22. data/lib/new_relic/agent/instrumentation/dispatcher_instrumentation.rb +132 -0
  23. data/lib/new_relic/agent/instrumentation/memcache.rb +21 -0
  24. data/lib/new_relic/agent/instrumentation/merb/controller.rb +26 -0
  25. data/lib/new_relic/agent/instrumentation/merb/dispatcher.rb +13 -0
  26. data/lib/new_relic/agent/instrumentation/merb/errors.rb +8 -0
  27. data/lib/new_relic/agent/instrumentation/net.rb +12 -0
  28. data/lib/new_relic/agent/instrumentation/passenger_instrumentation.rb +20 -0
  29. data/lib/new_relic/agent/instrumentation/rack.rb +77 -0
  30. data/lib/new_relic/agent/instrumentation/rails/action_controller.rb +59 -0
  31. data/lib/new_relic/agent/instrumentation/rails/action_web_service.rb +27 -0
  32. data/lib/new_relic/agent/instrumentation/rails/dispatcher.rb +38 -0
  33. data/lib/new_relic/agent/instrumentation/rails/errors.rb +27 -0
  34. data/lib/new_relic/agent/instrumentation/sinatra.rb +39 -0
  35. data/lib/new_relic/agent/method_tracer.rb +277 -0
  36. data/lib/new_relic/agent/patch_const_missing.rb +125 -0
  37. data/lib/new_relic/agent/sampler.rb +12 -0
  38. data/lib/new_relic/agent/samplers/cpu_sampler.rb +49 -0
  39. data/lib/new_relic/agent/samplers/memory_sampler.rb +137 -0
  40. data/lib/new_relic/agent/samplers/mongrel_sampler.rb +22 -0
  41. data/lib/new_relic/agent/shim_agent.rb +21 -0
  42. data/lib/new_relic/agent/stats_engine.rb +24 -0
  43. data/lib/new_relic/agent/stats_engine/metric_stats.rb +111 -0
  44. data/lib/new_relic/agent/stats_engine/samplers.rb +71 -0
  45. data/lib/new_relic/agent/stats_engine/transactions.rb +155 -0
  46. data/lib/new_relic/agent/transaction_sampler.rb +319 -0
  47. data/lib/new_relic/agent/worker_loop.rb +118 -0
  48. data/lib/new_relic/commands/deployments.rb +145 -0
  49. data/lib/new_relic/commands/new_relic_commands.rb +30 -0
  50. data/lib/new_relic/control.rb +436 -0
  51. data/lib/new_relic/control/external.rb +13 -0
  52. data/lib/new_relic/control/merb.rb +22 -0
  53. data/lib/new_relic/control/rails.rb +143 -0
  54. data/lib/new_relic/control/ruby.rb +34 -0
  55. data/lib/new_relic/control/sinatra.rb +14 -0
  56. data/lib/new_relic/histogram.rb +89 -0
  57. data/lib/new_relic/local_environment.rb +285 -0
  58. data/lib/new_relic/merbtasks.rb +6 -0
  59. data/lib/new_relic/metric_data.rb +44 -0
  60. data/lib/new_relic/metric_parser.rb +120 -0
  61. data/lib/new_relic/metric_parser/action_mailer.rb +9 -0
  62. data/lib/new_relic/metric_parser/active_merchant.rb +26 -0
  63. data/lib/new_relic/metric_parser/active_record.rb +25 -0
  64. data/lib/new_relic/metric_parser/controller.rb +54 -0
  65. data/lib/new_relic/metric_parser/controller_cpu.rb +38 -0
  66. data/lib/new_relic/metric_parser/errors.rb +6 -0
  67. data/lib/new_relic/metric_parser/external.rb +50 -0
  68. data/lib/new_relic/metric_parser/mem_cache.rb +12 -0
  69. data/lib/new_relic/metric_parser/view.rb +61 -0
  70. data/lib/new_relic/metric_parser/web_frontend.rb +14 -0
  71. data/lib/new_relic/metric_parser/web_service.rb +9 -0
  72. data/lib/new_relic/metric_spec.rb +52 -0
  73. data/lib/new_relic/metrics.rb +7 -0
  74. data/lib/new_relic/noticed_error.rb +25 -0
  75. data/lib/new_relic/rack/metric_app.rb +56 -0
  76. data/lib/new_relic/rack/newrelic.ru +25 -0
  77. data/lib/new_relic/rack/newrelic.yml +25 -0
  78. data/lib/new_relic/rack_app.rb +5 -0
  79. data/lib/new_relic/recipes.rb +82 -0
  80. data/lib/new_relic/stats.rb +360 -0
  81. data/lib/new_relic/transaction_analysis.rb +121 -0
  82. data/lib/new_relic/transaction_sample.rb +583 -0
  83. data/lib/new_relic/version.rb +54 -0
  84. data/lib/new_relic_api.rb +315 -0
  85. data/lib/newrelic_rpm.rb +40 -0
  86. data/lib/tasks/all.rb +4 -0
  87. data/lib/tasks/install.rake +7 -0
  88. data/lib/tasks/tests.rake +13 -0
  89. data/newrelic.yml +214 -0
  90. data/recipes/newrelic.rb +6 -0
  91. data/test/active_record_fixtures.rb +55 -0
  92. data/test/config/newrelic.yml +46 -0
  93. data/test/config/test_control.rb +39 -0
  94. data/test/new_relic/agent/active_record_instrumentation_test.rb +234 -0
  95. data/test/new_relic/agent/agent_controller_test.rb +107 -0
  96. data/test/new_relic/agent/agent_test.rb +117 -0
  97. data/test/new_relic/agent/agent_test_controller.rb +44 -0
  98. data/test/new_relic/agent/classloader_patch_test.rb +56 -0
  99. data/test/new_relic/agent/collection_helper_test.rb +118 -0
  100. data/test/new_relic/agent/dispatcher_instrumentation_test.rb +76 -0
  101. data/test/new_relic/agent/error_collector_test.rb +155 -0
  102. data/test/new_relic/agent/method_tracer_test.rb +335 -0
  103. data/test/new_relic/agent/metric_data_test.rb +56 -0
  104. data/test/new_relic/agent/mock_ar_connection.rb +40 -0
  105. data/test/new_relic/agent/mock_scope_listener.rb +23 -0
  106. data/test/new_relic/agent/net_instrumentation_test.rb +51 -0
  107. data/test/new_relic/agent/stats_engine/metric_stats_test.rb +79 -0
  108. data/test/new_relic/agent/stats_engine/samplers_test.rb +78 -0
  109. data/test/new_relic/agent/stats_engine/stats_engine_test.rb +177 -0
  110. data/test/new_relic/agent/task_instrumentation_test.rb +67 -0
  111. data/test/new_relic/agent/testable_agent.rb +13 -0
  112. data/test/new_relic/agent/transaction_sample_builder_test.rb +195 -0
  113. data/test/new_relic/agent/transaction_sample_test.rb +146 -0
  114. data/test/new_relic/agent/transaction_sampler_test.rb +387 -0
  115. data/test/new_relic/agent/worker_loop_test.rb +103 -0
  116. data/test/new_relic/control_test.rb +94 -0
  117. data/test/new_relic/deployments_api_test.rb +68 -0
  118. data/test/new_relic/environment_test.rb +75 -0
  119. data/test/new_relic/metric_parser_test.rb +172 -0
  120. data/test/new_relic/metric_spec_test.rb +177 -0
  121. data/test/new_relic/shim_agent_test.rb +9 -0
  122. data/test/new_relic/stats_test.rb +291 -0
  123. data/test/new_relic/version_number_test.rb +74 -0
  124. data/test/test_helper.rb +38 -0
  125. data/test/ui/newrelic_controller_test.rb +14 -0
  126. data/test/ui/newrelic_helper_test.rb +53 -0
  127. data/ui/controllers/newrelic_controller.rb +214 -0
  128. data/ui/helpers/google_pie_chart.rb +55 -0
  129. data/ui/helpers/newrelic_helper.rb +314 -0
  130. data/ui/views/layouts/newrelic_default.rhtml +47 -0
  131. data/ui/views/newrelic/_explain_plans.rhtml +27 -0
  132. data/ui/views/newrelic/_sample.rhtml +15 -0
  133. data/ui/views/newrelic/_segment.rhtml +28 -0
  134. data/ui/views/newrelic/_segment_limit_message.rhtml +1 -0
  135. data/ui/views/newrelic/_segment_row.rhtml +14 -0
  136. data/ui/views/newrelic/_show_sample_detail.rhtml +24 -0
  137. data/ui/views/newrelic/_show_sample_sql.rhtml +20 -0
  138. data/ui/views/newrelic/_show_sample_summary.rhtml +3 -0
  139. data/ui/views/newrelic/_sql_row.rhtml +11 -0
  140. data/ui/views/newrelic/_stack_trace.rhtml +30 -0
  141. data/ui/views/newrelic/_table.rhtml +12 -0
  142. data/ui/views/newrelic/explain_sql.rhtml +42 -0
  143. data/ui/views/newrelic/images/arrow-close.png +0 -0
  144. data/ui/views/newrelic/images/arrow-open.png +0 -0
  145. data/ui/views/newrelic/images/blue_bar.gif +0 -0
  146. data/ui/views/newrelic/images/file_icon.png +0 -0
  147. data/ui/views/newrelic/images/gray_bar.gif +0 -0
  148. data/ui/views/newrelic/images/new_relic_rpm_desktop.gif +0 -0
  149. data/ui/views/newrelic/images/textmate.png +0 -0
  150. data/ui/views/newrelic/index.rhtml +45 -0
  151. data/ui/views/newrelic/javascript/prototype-scriptaculous.js +7288 -0
  152. data/ui/views/newrelic/javascript/transaction_sample.js +107 -0
  153. data/ui/views/newrelic/sample_not_found.rhtml +2 -0
  154. data/ui/views/newrelic/show_sample.rhtml +77 -0
  155. data/ui/views/newrelic/show_source.rhtml +3 -0
  156. data/ui/views/newrelic/stylesheets/style.css +433 -0
  157. data/ui/views/newrelic/threads.rhtml +52 -0
  158. metadata +327 -0
@@ -0,0 +1,121 @@
1
+ # Add these methods to TransactionSample that enable performance analysis in the user interface.
2
+ module NewRelic::TransactionAnalysis
3
+ def database_time
4
+ time_percentage(/^Database\/.*/)
5
+ end
6
+
7
+ def render_time
8
+ time_percentage(/^View\/.*/)
9
+ end
10
+
11
+ # summarizes performance data for all calls to segments
12
+ # with the same metric_name
13
+ class SegmentSummary
14
+ attr_accessor :metric_name, :total_time, :exclusive_time, :call_count
15
+ def initialize(metric_name, sample)
16
+ @metric_name = metric_name
17
+ @total_time, @exclusive_time, @call_count = 0,0,0
18
+ @sample = sample
19
+ end
20
+
21
+ def <<(segment)
22
+ if metric_name != segment.metric_name
23
+ raise ArgumentError, "Metric Name Mismatch: #{segment.metric_name} != #{metric_name}"
24
+ end
25
+
26
+ @total_time += segment.duration
27
+ @exclusive_time += segment.exclusive_duration
28
+ @call_count += 1
29
+ end
30
+
31
+ def average_time
32
+ @total_time / @call_count
33
+ end
34
+
35
+ def average_exclusive_time
36
+ @exclusive_time / @call_count
37
+ end
38
+
39
+ def exclusive_time_percentage
40
+ return 0 unless @exclusive_time && @sample.duration && @sample.duration > 0
41
+ @exclusive_time / @sample.duration
42
+ end
43
+
44
+ def total_time_percentage
45
+ return 0 unless @total_time && @sample.duration && @sample.duration > 0
46
+ @total_time / @sample.duration
47
+ end
48
+
49
+ def developer_name
50
+ return @metric_name if @metric_name == 'Remainder'
51
+ NewRelic::MetricParser.parse(@metric_name).developer_name
52
+ end
53
+ end
54
+
55
+ # return the data that breaks down the performance of the transaction
56
+ # as an array of SegmentSummary objects. If a limit is specified, then
57
+ # limit the data set to the top n
58
+ def breakdown_data(limit = nil)
59
+ metric_hash = {}
60
+ each_segment do |segment|
61
+ unless segment == root_segment
62
+ metric_name = segment.metric_name
63
+ metric_hash[metric_name] ||= SegmentSummary.new(metric_name, self)
64
+ metric_hash[metric_name] << segment
65
+ end
66
+ end
67
+
68
+ data = metric_hash.values
69
+
70
+ data.sort! do |x,y|
71
+ y.exclusive_time <=> x.exclusive_time
72
+ end
73
+
74
+ if limit && data.length > limit
75
+ data = data[0..limit - 1]
76
+ end
77
+
78
+ # add one last segment for the remaining time if any
79
+ remainder = duration
80
+ data.each do |segment|
81
+ remainder -= segment.exclusive_time
82
+ end
83
+
84
+ if (remainder*1000).round > 0
85
+ remainder_summary = SegmentSummary.new('Remainder', self)
86
+ remainder_summary.total_time = remainder_summary.exclusive_time = remainder
87
+ remainder_summary.call_count = 1
88
+ data << remainder_summary
89
+ end
90
+
91
+ data
92
+ end
93
+
94
+ # return an array of sql statements executed by this transaction
95
+ # each element in the array contains [sql, parent_segment_metric_name, duration]
96
+ def sql_segments
97
+ segments = []
98
+ each_segment do |segment|
99
+ if segment[:sql]
100
+ segments << segment
101
+ end
102
+ if segment[:sql_obfuscated]
103
+ segments << segment
104
+ end
105
+ end
106
+ segments
107
+ end
108
+
109
+ private
110
+ def time_percentage(regex)
111
+ total = 0
112
+ each_segment do |segment|
113
+ if regex =~ segment.metric_name
114
+ total += segment.duration
115
+ end
116
+ end
117
+ fraction = 100.0 * total / duration
118
+ # percent value rounded to two digits:
119
+ return (100 * fraction).round / 100.0
120
+ end
121
+ end
@@ -0,0 +1,583 @@
1
+
2
+ module NewRelic
3
+ COLLAPSE_SEGMENTS_THRESHOLD = 2
4
+
5
+ MYSQL_EXPLAIN_COLUMNS = [
6
+ "Id",
7
+ "Select Type",
8
+ "Table",
9
+ "Type",
10
+ "Possible Keys",
11
+ "Key",
12
+ "Key Length",
13
+ "Ref",
14
+ "Rows",
15
+ "Extra"
16
+ ].freeze
17
+
18
+ class TransactionSample
19
+ EMPTY_ARRAY = [].freeze
20
+
21
+ @@start_time = Time.now
22
+
23
+ include TransactionAnalysis
24
+ class Segment
25
+
26
+
27
+ attr_reader :entry_timestamp
28
+ attr_reader :exit_timestamp
29
+ attr_reader :parent_segment
30
+ attr_reader :metric_name
31
+ attr_reader :segment_id
32
+
33
+ def initialize(timestamp, metric_name, segment_id)
34
+ @entry_timestamp = timestamp
35
+ @metric_name = metric_name || '<unknown>'
36
+ @segment_id = segment_id || object_id
37
+ end
38
+
39
+ def end_trace(timestamp)
40
+ @exit_timestamp = timestamp
41
+ end
42
+
43
+ def add_called_segment(s)
44
+ @called_segments ||= []
45
+ @called_segments << s
46
+ s.parent_segment = self
47
+ end
48
+
49
+ def to_s
50
+ to_debug_str(0)
51
+ end
52
+
53
+ def to_json
54
+ map = {:entry_timestamp => @entry_timestamp,
55
+ :exit_timestamp => @exit_timestamp,
56
+ :metric_name => @metric_name,
57
+ :segment_id => @segment_id}
58
+ if @called_segments && !@called_segments.empty?
59
+ map[:called_segments] = @called_segments
60
+ end
61
+ if @params && !@params.empty?
62
+ map[:params] = @params
63
+ end
64
+ map.to_json
65
+ end
66
+
67
+ def self.from_json(json)
68
+ json = ActiveSupport::JSON.decode(json) if json.is_a?(String)
69
+ segment = Segment.new(json["entry_timestamp"].to_f, json["metric_name"], json["segment_id"])
70
+ segment.end_trace json["exit_timestamp"].to_f
71
+ params = json["params"]
72
+ if params
73
+ segment.send :params=, HashWithIndifferentAccess.new(params)
74
+ end
75
+ called_segments = json["called_segments"]
76
+ if called_segments
77
+ called_segments.each do |child|
78
+ segment.add_called_segment(self.from_json(child))
79
+ end
80
+ end
81
+ segment
82
+ end
83
+
84
+ def path_string
85
+ "#{metric_name}[#{called_segments.collect {|segment| segment.path_string }.join('')}]"
86
+ end
87
+ def to_s_compact
88
+ str = ""
89
+ str << metric_name
90
+ if called_segments.any?
91
+ str << "{#{called_segments.map { | cs | cs.to_s_compact }.join(",")}}"
92
+ end
93
+ str
94
+ end
95
+ def to_debug_str(depth)
96
+ tab = ""
97
+ depth.times {tab << " "}
98
+
99
+ s = tab.clone
100
+ s << ">> #{metric_name}: #{(@entry_timestamp*1000).round}\n"
101
+ unless params.empty?
102
+ s << "#{tab}#{tab}{\n"
103
+ params.each do |k,v|
104
+ s << "#{tab}#{tab}#{k}: #{v}\n"
105
+ end
106
+ s << "#{tab}#{tab}}\n"
107
+ end
108
+ called_segments.each do |cs|
109
+ s << cs.to_debug_str(depth + 1)
110
+ end
111
+ s << tab
112
+ s << "<< #{metric_name}: #{@exit_timestamp ? (@exit_timestamp*1000).round : 'n/a'}\n"
113
+ s
114
+ end
115
+
116
+ def called_segments
117
+ @called_segments || EMPTY_ARRAY
118
+ end
119
+
120
+ def freeze
121
+ params.freeze
122
+ if @called_segments
123
+ @called_segments.each do |s|
124
+ s.freeze
125
+ end
126
+ end
127
+ super
128
+ end
129
+
130
+ # return the total duration of this segment
131
+ def duration
132
+ @exit_timestamp - @entry_timestamp
133
+ end
134
+
135
+ # return the duration of this segment without
136
+ # including the time in the called segments
137
+ def exclusive_duration
138
+ d = duration
139
+
140
+ if @called_segments
141
+ @called_segments.each do |segment|
142
+ d -= segment.duration
143
+ end
144
+ end
145
+ d
146
+ end
147
+
148
+ def []=(key, value)
149
+ # only create a parameters field if a parameter is set; this will save
150
+ # bandwidth etc as most segments have no parameters
151
+ params[key] = value
152
+ end
153
+
154
+ def [](key)
155
+ params[key]
156
+ end
157
+
158
+ def params
159
+ @params ||= {}
160
+ end
161
+
162
+ # call the provided block for this segment and each
163
+ # of the called segments
164
+ def each_segment(&block)
165
+ block.call self
166
+
167
+ if @called_segments
168
+ @called_segments.each do |segment|
169
+ segment.each_segment(&block)
170
+ end
171
+ end
172
+ end
173
+
174
+ def find_segment(id)
175
+ return self if @segment_id == id
176
+ called_segments.each do |segment|
177
+ found = segment.find_segment(id)
178
+ return found if found
179
+ end
180
+ nil
181
+ end
182
+
183
+ # perform this in the runtime environment of a managed application, to explain the sql
184
+ # statement(s) executed within a segment of a transaction sample.
185
+ # returns an array of explanations (which is an array rows consisting of
186
+ # an array of strings for each column returned by the the explain query)
187
+ # Note this happens only for statements whose execution time exceeds a threshold (e.g. 500ms)
188
+ # and only within the slowest transaction in a report period, selected for shipment to RPM
189
+ def explain_sql
190
+ sql = params[:sql]
191
+ return nil unless sql && params[:connection_config]
192
+ statements = sql.split(";\n")
193
+ explanations = []
194
+ statements.each do |statement|
195
+ if statement.split($;, 2)[0].upcase == 'SELECT'
196
+ explain_resultset = []
197
+ begin
198
+ connection = NewRelic::TransactionSample.get_connection(params[:connection_config])
199
+ if connection
200
+ # The resultset type varies for different drivers. Only thing you can count on is
201
+ # that it implements each. Also: can't use select_rows because the native postgres
202
+ # driver doesn't know that method.
203
+ explain_resultset = connection.execute("EXPLAIN #{statement}") if connection
204
+ rows = []
205
+ # Note: we can't use map.
206
+ # Note: have to convert from native column element types to string so we can
207
+ # serialize. Esp. for postgresql.
208
+ # Can't use map. Suck it up.
209
+ if explain_resultset.respond_to?(:each)
210
+ explain_resultset.each { | row | rows << row.map(&:to_s) }
211
+ else
212
+ rows << [ explain_resultset ]
213
+ end
214
+ explanations << rows
215
+ # sleep for a very short period of time in order to yield to the main thread
216
+ # this is because a remote database call will likely hang the VM
217
+ sleep 0.05
218
+ end
219
+ rescue => e
220
+ handle_exception_in_explain(e)
221
+ end
222
+ end
223
+ end
224
+
225
+ explanations
226
+ end
227
+
228
+ def handle_exception_in_explain(e)
229
+ x = 1 # this is here so that code coverage knows we've entered this block
230
+ # swallow failed attempts to run an explain. One example of a failure is the
231
+ # connection for the sql statement is to a different db than the default connection
232
+ # specified in AR::Base
233
+ end
234
+ def obfuscated_sql
235
+ TransactionSample.obfuscate_sql(params[:sql])
236
+ end
237
+
238
+ def called_segments=(segments)
239
+ @called_segments = segments
240
+ end
241
+
242
+ protected
243
+ def parent_segment=(s)
244
+ @parent_segment = s
245
+ end
246
+ def params=(p)
247
+ @params = p
248
+ end
249
+ end
250
+
251
+ class SummarySegment < Segment
252
+
253
+
254
+ def initialize(segment)
255
+ super segment.entry_timestamp, segment.metric_name, nil
256
+
257
+ add_segments segment.called_segments
258
+
259
+ end_trace segment.exit_timestamp
260
+ end
261
+
262
+ def add_segments(segments)
263
+ segments.collect do |segment|
264
+ SummarySegment.new(segment)
265
+ end.each {|segment| add_called_segment(segment)}
266
+ end
267
+
268
+ end
269
+
270
+ class CompositeSegment < Segment
271
+ attr_reader :detail_segments
272
+
273
+ def initialize(segments)
274
+ summary = SummarySegment.new(segments.first)
275
+ super summary.entry_timestamp, "Repeating pattern (#{segments.length} repeats)", nil
276
+
277
+ summary.end_trace(segments.last.exit_timestamp)
278
+
279
+ @detail_segments = segments.clone
280
+
281
+ add_called_segment(summary)
282
+ end_trace summary.exit_timestamp
283
+ end
284
+
285
+ def detail_segments=(segments)
286
+ @detail_segments = segments
287
+ end
288
+
289
+ end
290
+
291
+ class << self
292
+ def obfuscate_sql(sql)
293
+ NewRelic::Agent.instance.obfuscator.call(sql)
294
+ end
295
+
296
+
297
+ def get_connection(config)
298
+ @@connections ||= {}
299
+
300
+ connection = @@connections[config]
301
+
302
+ return connection if connection
303
+
304
+ begin
305
+ connection = ActiveRecord::Base.send("#{config[:adapter]}_connection", config)
306
+ @@connections[config] = connection
307
+ rescue => e
308
+ NewRelic::Agent.agent.log.error("Caught exception #{e} trying to get connection to DB for explain. Control: #{config}")
309
+ NewRelic::Agent.agent.log.error(e.backtrace.join("\n"))
310
+ nil
311
+ end
312
+ end
313
+
314
+ def close_connections
315
+ @@connections ||= {}
316
+ @@connections.values.each do |connection|
317
+ begin
318
+ connection.disconnect!
319
+ rescue
320
+ end
321
+ end
322
+
323
+ @@connections = {}
324
+ end
325
+
326
+ end
327
+
328
+ attr_reader :root_segment
329
+ attr_reader :params
330
+ attr_reader :sample_id
331
+
332
+ def initialize(time = Time.now.to_f, sample_id = nil)
333
+ @sample_id = sample_id || object_id
334
+ @start_time = time
335
+ @root_segment = create_segment 0.0, "ROOT"
336
+ @params = {}
337
+ @params[:request_params] = {}
338
+ end
339
+
340
+ # offset from start of app
341
+ def timestamp
342
+ @start_time - @@start_time.to_f
343
+ end
344
+
345
+ def to_json(options = {})
346
+ map = {:sample_id => @sample_id,
347
+ :start_time => @start_time,
348
+ :root_segment => @root_segment}
349
+ if @params && !@params.empty?
350
+ map[:params] = @params
351
+ end
352
+ map.to_json
353
+ end
354
+
355
+ def self.from_json(json)
356
+ json = ActiveSupport::JSON.decode(json) if json.is_a?(String)
357
+ sample = TransactionSample.new(json["start_time"].to_f, json["sample_id"].to_i)
358
+ params = json["params"]
359
+ if params
360
+ sample.send :params=, HashWithIndifferentAccess.new(params)
361
+ end
362
+ root = json["root_segment"]
363
+ if root
364
+ sample.send :root_segment=, Segment.from_json(root)
365
+ end
366
+ sample
367
+ end
368
+
369
+ def start_time
370
+ Time.at(@start_time)
371
+ end
372
+
373
+ def path_string
374
+ @root_segment.path_string
375
+ end
376
+
377
+ def create_segment (relative_timestamp, metric_name, segment_id = nil)
378
+ raise TypeError.new("Frozen Transaction Sample") if frozen?
379
+ NewRelic::TransactionSample::Segment.new(relative_timestamp, metric_name, segment_id)
380
+ end
381
+
382
+ def freeze
383
+ @root_segment.freeze
384
+ params.freeze
385
+ super
386
+ end
387
+
388
+ def duration
389
+ root_segment.duration
390
+ end
391
+
392
+ def each_segment(&block)
393
+ @root_segment.each_segment(&block)
394
+ end
395
+
396
+ def to_s_compact
397
+ @root_segment.to_s_compact
398
+ end
399
+
400
+ def find_segment(id)
401
+ @root_segment.find_segment(id)
402
+ end
403
+
404
+ def to_s
405
+ s = "Transaction Sample collected at #{start_time}\n"
406
+ s << " {\n"
407
+ s << " Path: #{params[:path]} \n"
408
+
409
+ params.each do |k,v|
410
+ next if k == :path
411
+ s << " #{k}: " <<
412
+ case v
413
+ when Enumerable; v.map(&:to_s).sort.join("; ")
414
+ else
415
+ v
416
+ end << "\n"
417
+ end
418
+ s << " }\n\n"
419
+ s << @root_segment.to_debug_str(0)
420
+ end
421
+
422
+ # return a new transaction sample that treats segments
423
+ # with the given regular expression in their name as if they
424
+ # were never called at all. This allows us to strip out segments
425
+ # from traces captured in development environment that would not
426
+ # normally show up in production (like Rails/Application Code Loading)
427
+ def omit_segments_with(regex)
428
+ regex = Regexp.new(regex)
429
+
430
+ sample = TransactionSample.new(@start_time, sample_id)
431
+
432
+ params.each {|k,v| sample.params[k] = v}
433
+
434
+ delta = build_segment_with_omissions(sample, 0.0, @root_segment, sample.root_segment, regex)
435
+ sample.root_segment.end_trace(@root_segment.exit_timestamp - delta)
436
+ sample.freeze
437
+ end
438
+
439
+ # return a new transaction sample that can be sent to the RPM service.
440
+ # this involves potentially one or more of the following options
441
+ # :explain_sql : run EXPLAIN on all queries whose response times equal the value for this key
442
+ # (for example :explain_sql => 2.0 would explain everything over 2 seconds. 0.0 would explain everything.)
443
+ # :keep_backtraces : keep backtraces, significantly increasing size of trace (off by default)
444
+ # :obfuscate_sql : clear sql fields of potentially sensitive values (higher overhead, better security)
445
+ def prepare_to_send(options={})
446
+ sample = TransactionSample.new(@start_time, sample_id)
447
+
448
+ sample.params.merge! self.params
449
+
450
+ begin
451
+ build_segment_for_transfer(sample, @root_segment, sample.root_segment, options)
452
+ ensure
453
+ self.class.close_connections
454
+ end
455
+
456
+ sample.root_segment.end_trace(@root_segment.exit_timestamp)
457
+ sample.freeze
458
+ end
459
+
460
+ def analyze
461
+ sample = self
462
+ original_path_string = nil
463
+ loop do
464
+ original_path_string = sample.path_string.to_s
465
+ new_sample = sample.dup
466
+ new_sample.root_segment = sample.root_segment.dup
467
+ new_sample.root_segment.called_segments = analyze_called_segments(root_segment.called_segments)
468
+ sample = new_sample
469
+ return sample if sample.path_string.to_s == original_path_string
470
+ end
471
+
472
+ end
473
+
474
+ protected
475
+ def root_segment=(segment)
476
+ @root_segment = segment
477
+ end
478
+ def params=(params)
479
+ @params = params
480
+ end
481
+
482
+ private
483
+
484
+ def analyze_called_segments(called_segments)
485
+ path = nil
486
+ like_segments = []
487
+
488
+ segments = []
489
+
490
+ called_segments.each do |segment|
491
+ segment = segment.dup
492
+ segment.called_segments = analyze_called_segments(segment.called_segments)
493
+
494
+ current_path = segment.path_string
495
+ if path == current_path
496
+ like_segments << segment
497
+ else
498
+ segments += summarize_segments(like_segments)
499
+
500
+ like_segments.clear
501
+ like_segments << segment
502
+ path = current_path
503
+ end
504
+ end
505
+ segments += summarize_segments(like_segments)
506
+
507
+ segments
508
+ end
509
+
510
+ def summarize_segments(like_segments)
511
+ if like_segments.length > COLLAPSE_SEGMENTS_THRESHOLD
512
+ puts "#{like_segments.first.path_string} #{like_segments.length}"
513
+ [CompositeSegment.new(like_segments)]
514
+ else
515
+ like_segments
516
+ end
517
+ end
518
+
519
+ def build_segment_with_omissions(new_sample, time_delta, source_segment, target_segment, regex)
520
+ source_segment.called_segments.each do |source_called_segment|
521
+ # if this segment's metric name matches the given regular expression, bail
522
+ # here and increase the amount of time that we reduce the target sample with
523
+ # by this omitted segment's duration.
524
+ do_omit = regex =~ source_called_segment.metric_name
525
+
526
+ if do_omit
527
+ time_delta += source_called_segment.duration
528
+ else
529
+ target_called_segment = new_sample.create_segment(
530
+ source_called_segment.entry_timestamp - time_delta,
531
+ source_called_segment.metric_name,
532
+ source_called_segment.segment_id)
533
+
534
+ target_segment.add_called_segment target_called_segment
535
+ source_called_segment.params.each do |k,v|
536
+ target_called_segment[k]=v
537
+ end
538
+
539
+ time_delta = build_segment_with_omissions(
540
+ new_sample, time_delta, source_called_segment, target_called_segment, regex)
541
+ target_called_segment.end_trace(source_called_segment.exit_timestamp - time_delta)
542
+ end
543
+ end
544
+
545
+ return time_delta
546
+ end
547
+
548
+ # see prepare_to_send for what we do with options
549
+ def build_segment_for_transfer(new_sample, source_segment, target_segment, options)
550
+ source_segment.called_segments.each do |source_called_segment|
551
+ target_called_segment = new_sample.create_segment(
552
+ source_called_segment.entry_timestamp,
553
+ source_called_segment.metric_name,
554
+ source_called_segment.segment_id)
555
+
556
+ target_segment.add_called_segment target_called_segment
557
+ source_called_segment.params.each do |k,v|
558
+ case k
559
+ when :backtrace
560
+ target_called_segment[k]=v if options[:keep_backtraces]
561
+ when :sql
562
+ sql = v
563
+
564
+ # run an EXPLAIN on this sql if specified.
565
+ if options[:explain_enabled] && options[:explain_sql] && source_called_segment.duration > options[:explain_sql].to_f
566
+ target_called_segment[:explanation] = source_called_segment.explain_sql
567
+ end
568
+
569
+ target_called_segment[:sql]=sql if options[:record_sql] == :raw
570
+ target_called_segment[:sql_obfuscated] = TransactionSample.obfuscate_sql(sql) if options[:record_sql] == :obfuscated
571
+ when :connection_config
572
+ # don't copy it
573
+ else
574
+ target_called_segment[k]=v
575
+ end
576
+ end
577
+
578
+ build_segment_for_transfer(new_sample, source_called_segment, target_called_segment, options)
579
+ target_called_segment.end_trace(source_called_segment.exit_timestamp)
580
+ end
581
+ end
582
+ end
583
+ end