honkster-newrelic_rpm 2.13.1

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