techarch-newrelic_rpm 2.10.2

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 +335 -0
  2. data/LICENSE +37 -0
  3. data/Manifest +159 -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/init.rb +38 -0
  10. data/install.rb +45 -0
  11. data/lib/new_relic/agent.rb +281 -0
  12. data/lib/new_relic/agent/agent.rb +636 -0
  13. data/lib/new_relic/agent/chained_call.rb +13 -0
  14. data/lib/new_relic/agent/collection_helper.rb +66 -0
  15. data/lib/new_relic/agent/error_collector.rb +121 -0
  16. data/lib/new_relic/agent/instrumentation/active_merchant.rb +18 -0
  17. data/lib/new_relic/agent/instrumentation/active_record_instrumentation.rb +88 -0
  18. data/lib/new_relic/agent/instrumentation/authlogic.rb +8 -0
  19. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +437 -0
  20. data/lib/new_relic/agent/instrumentation/data_mapper.rb +90 -0
  21. data/lib/new_relic/agent/instrumentation/dispatcher_instrumentation.rb +167 -0
  22. data/lib/new_relic/agent/instrumentation/memcache.rb +24 -0
  23. data/lib/new_relic/agent/instrumentation/merb/controller.rb +26 -0
  24. data/lib/new_relic/agent/instrumentation/merb/dispatcher.rb +13 -0
  25. data/lib/new_relic/agent/instrumentation/merb/errors.rb +8 -0
  26. data/lib/new_relic/agent/instrumentation/net.rb +23 -0
  27. data/lib/new_relic/agent/instrumentation/passenger_instrumentation.rb +20 -0
  28. data/lib/new_relic/agent/instrumentation/rack.rb +108 -0
  29. data/lib/new_relic/agent/instrumentation/rails/action_controller.rb +59 -0
  30. data/lib/new_relic/agent/instrumentation/rails/action_web_service.rb +27 -0
  31. data/lib/new_relic/agent/instrumentation/rails/dispatcher.rb +41 -0
  32. data/lib/new_relic/agent/instrumentation/rails/errors.rb +27 -0
  33. data/lib/new_relic/agent/instrumentation/sinatra.rb +39 -0
  34. data/lib/new_relic/agent/method_tracer.rb +349 -0
  35. data/lib/new_relic/agent/patch_const_missing.rb +125 -0
  36. data/lib/new_relic/agent/sampler.rb +12 -0
  37. data/lib/new_relic/agent/samplers/cpu_sampler.rb +49 -0
  38. data/lib/new_relic/agent/samplers/memory_sampler.rb +138 -0
  39. data/lib/new_relic/agent/samplers/mongrel_sampler.rb +22 -0
  40. data/lib/new_relic/agent/shim_agent.rb +21 -0
  41. data/lib/new_relic/agent/stats_engine.rb +22 -0
  42. data/lib/new_relic/agent/stats_engine/metric_stats.rb +111 -0
  43. data/lib/new_relic/agent/stats_engine/samplers.rb +71 -0
  44. data/lib/new_relic/agent/stats_engine/transactions.rb +152 -0
  45. data/lib/new_relic/agent/transaction_sampler.rb +310 -0
  46. data/lib/new_relic/agent/worker_loop.rb +118 -0
  47. data/lib/new_relic/commands/deployments.rb +145 -0
  48. data/lib/new_relic/commands/new_relic_commands.rb +30 -0
  49. data/lib/new_relic/control.rb +473 -0
  50. data/lib/new_relic/control/external.rb +13 -0
  51. data/lib/new_relic/control/merb.rb +22 -0
  52. data/lib/new_relic/control/rails.rb +145 -0
  53. data/lib/new_relic/control/ruby.rb +36 -0
  54. data/lib/new_relic/control/sinatra.rb +14 -0
  55. data/lib/new_relic/histogram.rb +89 -0
  56. data/lib/new_relic/local_environment.rb +328 -0
  57. data/lib/new_relic/merbtasks.rb +6 -0
  58. data/lib/new_relic/metric_data.rb +42 -0
  59. data/lib/new_relic/metric_parser.rb +124 -0
  60. data/lib/new_relic/metric_parser/action_mailer.rb +9 -0
  61. data/lib/new_relic/metric_parser/active_merchant.rb +26 -0
  62. data/lib/new_relic/metric_parser/active_record.rb +25 -0
  63. data/lib/new_relic/metric_parser/controller.rb +54 -0
  64. data/lib/new_relic/metric_parser/controller_cpu.rb +38 -0
  65. data/lib/new_relic/metric_parser/errors.rb +6 -0
  66. data/lib/new_relic/metric_parser/external.rb +50 -0
  67. data/lib/new_relic/metric_parser/mem_cache.rb +12 -0
  68. data/lib/new_relic/metric_parser/view.rb +61 -0
  69. data/lib/new_relic/metric_parser/web_frontend.rb +14 -0
  70. data/lib/new_relic/metric_parser/web_service.rb +9 -0
  71. data/lib/new_relic/metric_spec.rb +52 -0
  72. data/lib/new_relic/metrics.rb +7 -0
  73. data/lib/new_relic/noticed_error.rb +23 -0
  74. data/lib/new_relic/rack/metric_app.rb +56 -0
  75. data/lib/new_relic/rack/newrelic.ru +25 -0
  76. data/lib/new_relic/rack/newrelic.yml +25 -0
  77. data/lib/new_relic/rack_app.rb +5 -0
  78. data/lib/new_relic/recipes.rb +82 -0
  79. data/lib/new_relic/stats.rb +361 -0
  80. data/lib/new_relic/transaction_analysis.rb +121 -0
  81. data/lib/new_relic/transaction_sample.rb +666 -0
  82. data/lib/new_relic/version.rb +54 -0
  83. data/lib/new_relic_api.rb +313 -0
  84. data/lib/newrelic_rpm.rb +40 -0
  85. data/lib/tasks/all.rb +4 -0
  86. data/lib/tasks/install.rake +7 -0
  87. data/lib/tasks/tests.rake +13 -0
  88. data/newrelic.yml +227 -0
  89. data/recipes/newrelic.rb +6 -0
  90. data/techarch-newrelic_rpm.gemspec +32 -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 +264 -0
  95. data/test/new_relic/agent/agent_controller_test.rb +107 -0
  96. data/test/new_relic/agent/agent_test.rb +119 -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 +125 -0
  100. data/test/new_relic/agent/dispatcher_instrumentation_test.rb +76 -0
  101. data/test/new_relic/agent/error_collector_test.rb +172 -0
  102. data/test/new_relic/agent/method_tracer_test.rb +340 -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 +63 -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 +81 -0
  109. data/test/new_relic/agent/stats_engine/stats_engine_test.rb +184 -0
  110. data/test/new_relic/agent/task_instrumentation_test.rb +126 -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 +186 -0
  114. data/test/new_relic/agent/transaction_sampler_test.rb +385 -0
  115. data/test/new_relic/agent/worker_loop_test.rb +103 -0
  116. data/test/new_relic/control_test.rb +113 -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 +220 -0
  128. data/ui/helpers/google_pie_chart.rb +55 -0
  129. data/ui/helpers/newrelic_helper.rb +317 -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 +19 -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 +57 -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 +80 -0
  155. data/ui/views/newrelic/show_source.rhtml +3 -0
  156. data/ui/views/newrelic/stylesheets/style.css +484 -0
  157. data/ui/views/newrelic/threads.rhtml +52 -0
  158. metadata +330 -0
@@ -0,0 +1,220 @@
1
+ class NewrelicController < ActionController::Base
2
+ include NewrelicHelper
3
+ helper NewrelicHelper
4
+
5
+ # See http://wiki.rubyonrails.org/rails/pages/Safe+ERB:
6
+ # We don't need to worry about checking taintedness
7
+ def initialize(*args)
8
+ @skip_checking_tainted = true
9
+ super *args
10
+ end
11
+
12
+ # do not include any filters inside the application since there might be a conflict
13
+ if respond_to? :filter_chain
14
+ filters = filter_chain.collect do |f|
15
+ if f.respond_to? :filter
16
+ # rails 2.0
17
+ f.filter
18
+ elsif f.respond_to? :method
19
+ # rails 2.1
20
+ f.method
21
+ else
22
+ fail "Unknown filter class. Please send this exception to support@newrelic.com"
23
+ end
24
+ end
25
+ skip_filter filters
26
+ end
27
+
28
+ # for this controller, the views are located in a different directory from
29
+ # the application's views.
30
+ view_path = File.join(File.dirname(__FILE__), '..', 'views')
31
+ if respond_to? :append_view_path # rails 2.1+
32
+ self.append_view_path view_path
33
+ elsif respond_to? :view_paths # rails 2.0+
34
+ self.view_paths << view_path
35
+ else # rails <2.0
36
+ self.template_root = view_path
37
+ end
38
+
39
+ layout "newrelic_default"
40
+
41
+ write_inheritable_attribute('do_not_trace', true)
42
+
43
+ def profile
44
+ NewRelic::Control.instance.profiling = params['start'] == 'true'
45
+ get_samples
46
+ redirect_to :action => 'index'
47
+ end
48
+
49
+ def file
50
+ file_name=params[:file].to_s
51
+ file_name=~/^.*[.]([^.]*)$/
52
+ ext=$1
53
+ case ext
54
+ when 'css' then
55
+ forward_to_file '/newrelic/stylesheets/', 'text/css'
56
+ when 'gif','jpg','png' then
57
+ forward_to_file '/newrelic/images/', "image/#{ext}"
58
+ when 'js' then
59
+ forward_to_file '/newrelic/javascript/', 'text/javascript'
60
+ else
61
+ raise "Unknown type '#{ext}' (#{file_name})"
62
+ end
63
+ end
64
+
65
+ def index
66
+ get_samples
67
+ end
68
+
69
+ def threads
70
+
71
+ end
72
+
73
+ def reset
74
+ NewRelic::Agent.instance.transaction_sampler.reset!
75
+ redirect_to :action => 'index'
76
+ end
77
+
78
+ def show_sample_detail
79
+ show_sample_data
80
+ end
81
+
82
+ def show_sample_summary
83
+ show_sample_data
84
+ end
85
+
86
+ def show_sample_sql
87
+ show_sample_data
88
+ end
89
+
90
+
91
+ def explain_sql
92
+ get_segment
93
+
94
+ render :action => "sample_not_found" and return unless @sample
95
+
96
+ @sql = @segment[:sql]
97
+ @trace = @segment[:backtrace]
98
+
99
+ if NewRelic::Agent.agent.record_sql == :obfuscated
100
+ @obfuscated_sql = @segment.obfuscated_sql
101
+ end
102
+
103
+ explanations = @segment.explain_sql
104
+ if explanations
105
+ @explanation = explanations.first
106
+ if !@explanation.blank?
107
+ first_row = @explanation.first
108
+ # Show the standard headers if it looks like a mysql explain plan
109
+ # Otherwise show blank headers
110
+ if first_row.length < NewRelic::MYSQL_EXPLAIN_COLUMNS.length
111
+ @row_headers = nil
112
+ else
113
+ @row_headers = NewRelic::MYSQL_EXPLAIN_COLUMNS
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ # show the selected source file with the highlighted selected line
120
+ def show_source
121
+ @filename = params[:file]
122
+ line_number = params[:line].to_i
123
+
124
+ if !File.readable?(@filename)
125
+ @source="<p>Unable to read #{@filename}.</p>"
126
+ return
127
+ end
128
+ begin
129
+ file = File.new(@filename, 'r')
130
+ rescue => e
131
+ @source="<p>Unable to access the source file #{@filename} (#{e.message}).</p>"
132
+ return
133
+ end
134
+ @source = ""
135
+
136
+ @source << "<pre>"
137
+ file.each_line do |line|
138
+ # place an anchor 6 lines above the selected line (if the line # < 6)
139
+ if file.lineno == line_number - 6
140
+ @source << "</pre><pre id = 'selected_line'>"
141
+ @source << line.rstrip
142
+ @source << "</pre><pre>"
143
+
144
+ # highlight the selected line
145
+ elsif file.lineno == line_number
146
+ @source << "</pre><pre class = 'selected_source_line'>"
147
+ @source << line.rstrip
148
+ @source << "</pre><pre>"
149
+ else
150
+ @source << line
151
+ end
152
+ end
153
+ end
154
+
155
+ private
156
+
157
+ # root path is relative to plugin newrelic_rpm/ui/views directory.
158
+ def forward_to_file(root_path, content_type='ignored anyway')
159
+ file = File.expand_path(File.join(__FILE__,"../../views", root_path, params[:file]))
160
+ last_modified = File.mtime(file)
161
+ date_check = request.respond_to?(:headers) ? request.headers['if-modified-since'] : request.env['HTTP_IF_MODIFIED_SINCE']
162
+ if date_check && Time.parse(date_check) >= last_modified
163
+ expires_in 24.hours
164
+ head :not_modified,
165
+ :last_modified => last_modified,
166
+ :type => 'text/plain'
167
+ else
168
+ response.headers['Last-Modified'] = last_modified.to_formatted_s(:rfc822)
169
+ expires_in 24.hours
170
+ send_file file, :content_type => mime_type_from_extension(file), :disposition => 'inline' #, :filename => File.basename(file)
171
+ end
172
+ end
173
+
174
+ def show_sample_data
175
+ get_sample
176
+
177
+ render :action => "sample_not_found" and return unless @sample
178
+
179
+ @request_params = @sample.params[:request_params] || {}
180
+ @custom_params = @sample.params[:custom_params] || {}
181
+
182
+ controller_metric = @sample.root_segment.called_segments.first.metric_name
183
+
184
+ controller_segments = controller_metric.split('/')
185
+ @sample_controller_name = controller_segments[1..-2].join('/').camelize+"Controller"
186
+ @sample_action_name = controller_segments[-1].underscore
187
+
188
+ render :action => :show_sample
189
+ end
190
+
191
+ def get_samples
192
+ @samples = NewRelic::Agent.instance.transaction_sampler.samples.select do |sample|
193
+ sample.params[:path] != nil
194
+ end
195
+
196
+ return @samples = @samples.sort{|x,y| y.omit_segments_with('(Rails/Application Code Loading)|(Database/.*/.+ Columns)').duration <=>
197
+ x.omit_segments_with('(Rails/Application Code Loading)|(Database/.*/.+ Columns)').duration} if params[:h]
198
+ return @samples = @samples.sort{|x,y| x.params[:uri] <=> y.params[:uri]} if params[:u]
199
+ @samples = @samples.reverse
200
+ end
201
+
202
+ def get_sample
203
+ get_samples
204
+ sample_id = params[:id].to_i
205
+ @samples.each do |s|
206
+ if s.sample_id == sample_id
207
+ @sample = stripped_sample(s)
208
+ return
209
+ end
210
+ end
211
+ end
212
+
213
+ def get_segment
214
+ get_sample
215
+ return unless @sample
216
+
217
+ segment_id = params[:segment].to_i
218
+ @segment = @sample.find_segment(segment_id)
219
+ end
220
+ end
@@ -0,0 +1,55 @@
1
+
2
+ # A wrapper around the google charts service.
3
+ # TODO consider making generic and open sourcing
4
+ class GooglePieChart
5
+ attr_accessor :width, :height, :color
6
+
7
+ def initialize
8
+ # an array of [label, value]
9
+ @data = []
10
+
11
+ self.width = 300
12
+ self.height = 200
13
+ end
14
+
15
+ def add_data_point(label, value)
16
+ @data << [label, value]
17
+ @max = (@max.nil? || @max < value ? value : @max)
18
+ end
19
+
20
+ # render the chart to html by creating an image object and
21
+ # placing the correct URL to the google charts api
22
+ def render
23
+ labels = ''
24
+ values = ''
25
+ @data.each do |label, value|
26
+ labels << CGI::escape(label) + '|'
27
+ values << (value * 100 / @max).round.to_s + ","
28
+ end
29
+
30
+ # strip of the last separator char for labels and values
31
+ labels = labels[0..-2]
32
+ values = values[0..-2]
33
+
34
+ params = {:cht => 'p', :chs => "#{width}x#{height}", :chd => "t:#{values}", :chl => labels }
35
+ params['chco'] = color if color
36
+
37
+ url = "http://chart.apis.google.com/chart?#{to_query(params)}"
38
+
39
+ alt_msg = "This pie chart is generated by Google Charts. You must be connected to the Internet to view this chart."
40
+ html = "<img id=\"pie_chart_image\" src=\"#{url}\" alt=\"#{alt_msg}\"/>"
41
+ return html
42
+ end
43
+
44
+ private
45
+ # Hash#to_query is not present in all supported rails platforms, so implement
46
+ # its equivalent here.
47
+ def to_query(params)
48
+ p = []
49
+ params.each do |k,v|
50
+ p << "#{k}=#{v}"
51
+ end
52
+
53
+ p.join "&"
54
+ end
55
+ end
@@ -0,0 +1,317 @@
1
+ require 'pathname'
2
+ require 'new_relic/agent/collection_helper'
3
+ module NewrelicHelper
4
+ include NewRelic::Agent::CollectionHelper
5
+
6
+ # return the host that serves static content (css, metric documentation, images, etc)
7
+ # that supports the desktop edition.
8
+ def server
9
+ NewRelic::Control.instance['desktop_server'] || "http://rpm.newrelic.com"
10
+ end
11
+
12
+ # limit of how many detail/SQL rows we display - very large data sets (~10000+) crash browsers
13
+ def trace_row_display_limit
14
+ 2000
15
+ end
16
+
17
+ def trace_row_display_limit_reached
18
+ (!@detail_segment_count.nil? && @detail_segment_count > trace_row_display_limit) || @sample.sql_segments.length > trace_row_display_limit
19
+ end
20
+
21
+ # return the sample but post processed to strip out segments that normally don't show
22
+ # up in production (after the first execution, at least) such as application code loading
23
+ def stripped_sample(sample = @sample)
24
+ sample.omit_segments_with('(Rails/Application Code Loading)|(Database/.*/.+ Columns)')
25
+ end
26
+
27
+ # return the highest level in the call stack for the trace that is not rails or
28
+ # newrelic agent code
29
+ def application_caller(trace)
30
+ trace = strip_nr_from_backtrace(trace) unless params[:show_nr]
31
+ trace.each do |trace_line|
32
+ file = file_and_line(trace_line).first
33
+ unless exclude_file_from_stack_trace?(file, false)
34
+ return trace_line
35
+ end
36
+ end
37
+ trace.last
38
+ end
39
+
40
+ def application_stack_trace(trace, include_rails = false)
41
+ trace = strip_nr_from_backtrace(trace) unless params[:show_nr]
42
+ trace.reject do |trace_line|
43
+ file = file_and_line(trace_line).first
44
+ exclude_file_from_stack_trace?(file, include_rails)
45
+ end
46
+ end
47
+
48
+ def render_backtrace
49
+ if @segment[:backtrace]
50
+ content_tag('h3', 'Application Stack Trace') +
51
+ render(:partial => agent_views_path('stack_trace'), :locals => {:segment => @segment})
52
+ end
53
+ end
54
+
55
+ def agent_views_path(path)
56
+ path
57
+ end
58
+
59
+ def url_for_metric_doc(metric_name)
60
+ "#{server}/metric_doc?metric=#{CGI::escape(metric_name)}"
61
+ end
62
+
63
+ def url_for_source(trace_line)
64
+ file, line = file_and_line(trace_line)
65
+
66
+ begin
67
+ file = Pathname.new(file).realpath
68
+ rescue Errno::ENOENT
69
+ # we hit this exception when Pathame.realpath fails for some reason; attempt a link to
70
+ # the file without a real path. It may also fail, only when the user clicks on this specific
71
+ # entry in the stack trace
72
+ rescue
73
+ # catch all other exceptions. We're going to create an invalid link below, but that's okay.
74
+ end
75
+
76
+ if using_textmate?
77
+ "txmt://open?url=file://#{file}&line=#{line}"
78
+ else
79
+ url_for :action => 'show_source', :file => file, :line => line, :anchor => 'selected_line'
80
+ end
81
+ end
82
+
83
+ def dev_name(metric_name)
84
+ NewRelic::MetricParser.parse(metric_name).developer_name
85
+ end
86
+
87
+ # write the metric label for a segment metric in the detail view
88
+ def write_segment_label(segment)
89
+ if source_available && segment[:backtrace] && (source_url = url_for_source(application_caller(segment[:backtrace])))
90
+ link_to dev_name(segment.metric_name), source_url
91
+ else
92
+ h(dev_name(segment.metric_name))
93
+ end
94
+ end
95
+
96
+ def source_available
97
+ true
98
+ end
99
+
100
+ # write the metric label for a segment metric in the summary table of metrics
101
+ def write_summary_segment_label(segment)
102
+ dev_name(segment.metric_name)
103
+ end
104
+
105
+ def write_stack_trace_line(trace_line)
106
+ link_to h(trace_line), url_for_source(trace_line)
107
+ end
108
+
109
+ # write a link to the source for a trace
110
+ def link_to_source(trace)
111
+ image_url = url_for(:controller => :newrelic, :action => :file, :file => (using_textmate? ? "textmate.png" : "file_icon.png"))
112
+
113
+ link_to image_tag(image_url, :alt => (title = 'View Source'), :title => title), url_for_source(application_caller(trace))
114
+ end
115
+
116
+ # print the formatted timestamp for a segment
117
+ def timestamp(segment)
118
+ sprintf("%1.3f", segment.entry_timestamp)
119
+ end
120
+
121
+ def format_timestamp(time)
122
+ time.strftime("%H:%M:%S")
123
+ end
124
+
125
+ def colorize(value, yellow_threshold = 0.05, red_threshold = 0.15, s=to_ms(value))
126
+ if value > yellow_threshold
127
+ color = (value > red_threshold ? 'red' : 'orange')
128
+ "<font color=#{color}>#{s}</font>"
129
+ else
130
+ "#{s}"
131
+ end
132
+ end
133
+
134
+ def expanded_image_path()
135
+ url_for(:controller => :newrelic, :action => :file, :file => 'arrow-open.png')
136
+ end
137
+
138
+ def collapsed_image_path()
139
+ url_for(:controller => :newrelic, :action => :file, :file => 'arrow-close.png')
140
+ end
141
+
142
+ def explain_sql_url(segment)
143
+ url_for(:action => :explain_sql,
144
+ :id => @sample.sample_id,
145
+ :segment => segment.segment_id)
146
+ end
147
+
148
+ def segment_duration_value(segment)
149
+ link_to colorize(segment.duration, 0.05, 0.15, "#{with_delimiter(to_ms(segment.duration))} ms"), explain_sql_url(segment)
150
+ end
151
+
152
+ def line_wrap_sql(sql)
153
+ h(sql.gsub(/\,/,', ').squeeze(' ')) if sql
154
+ end
155
+
156
+ def render_sample_details(sample)
157
+ @indentation_depth=0
158
+ # skip past the root segments to the first child, which is always the controller
159
+ first_segment = sample.root_segment.called_segments.first
160
+
161
+ # render the segments, then the css classes to indent them
162
+ render_segment_details(first_segment) + render_indentation_classes(@indentation_depth)
163
+ end
164
+
165
+ # the rows logger plugin disables the sql tracing functionality of the NewRelic agent -
166
+ # notify the user about this
167
+ def rows_logger_present?
168
+ File.exist?(File.join(File.dirname(__FILE__), "../../../rows_logger/init.rb"))
169
+ end
170
+
171
+ def expand_segment_image(segment, depth)
172
+ if depth > 0
173
+ if !segment.called_segments.empty?
174
+ row_class =segment_child_row_class(segment)
175
+ link_to_function(tag('img', :src => collapsed_image_path, :id => "image_#{row_class}",
176
+ :class_for_children => row_class,
177
+ :class => (!segment.called_segments.empty?) ? 'parent_segment_image' : 'child_segment_image'),
178
+ "toggle_row_class(this)")
179
+ end
180
+ end
181
+ end
182
+
183
+ def segment_child_row_class(segment)
184
+ "segment#{segment.segment_id}"
185
+ end
186
+
187
+ def summary_pie_chart(sample, width, height)
188
+ pie_chart = GooglePieChart.new
189
+ pie_chart.color, pie_chart.width, pie_chart.height = '6688AA', width, height
190
+
191
+ chart_data = sample.breakdown_data(6)
192
+ chart_data.each { |s| pie_chart.add_data_point dev_name(s.metric_name), to_ms(s.exclusive_time) }
193
+
194
+ pie_chart.render
195
+ end
196
+
197
+ def segment_row_classes(segment, depth)
198
+ classes = []
199
+
200
+ classes << "segment#{segment.parent_segment.segment_id}" if depth > 1
201
+
202
+ classes << "view_segment" if segment.metric_name.starts_with?('View')
203
+ classes << "summary_segment" if segment.is_a?(NewRelic::TransactionSample::CompositeSegment)
204
+
205
+ classes.join(' ')
206
+ end
207
+
208
+ # render_segment_details should be called before calling this method
209
+ def render_indentation_classes(depth)
210
+ styles = []
211
+ (1..depth).each do |d|
212
+ styles << ".segment_indent_level#{d} { display: inline-block; margin-left: #{(d-1)*20}px }"
213
+ end
214
+ content_tag("style", styles.join(' '))
215
+ end
216
+
217
+ def sql_link_mouseover_options(segment)
218
+ { :onmouseover => "sql_mouse_over(#{segment.segment_id})", :onmouseout => "sql_mouse_out(#{segment.segment_id})"}
219
+ end
220
+
221
+ def explain_sql_link(segment, child_sql = false)
222
+ link_to 'SQL', explain_sql_url(segment), sql_link_mouseover_options(segment)
223
+ end
224
+
225
+ def explain_sql_links(segment)
226
+ if segment[:sql_obfuscated] || segment[:sql]
227
+ explain_sql_link segment
228
+ else
229
+ links = []
230
+ segment.called_segments.each do |child|
231
+ if child[:sql_obfuscated] || child[:sql]
232
+ links << explain_sql_link(child, true)
233
+ end
234
+ end
235
+ links[0..1].join(', ') + (links.length > 2?', ...':'')
236
+ end
237
+ end
238
+
239
+ private
240
+ def file_and_line(stack_trace_line)
241
+ stack_trace_line.match(/(.*):(\d+)/)[1..2]
242
+ end
243
+
244
+ def using_textmate?
245
+ NewRelic::Control.instance.use_textmate?
246
+ end
247
+
248
+
249
+ def render_segment_details(segment, depth=0)
250
+ @detail_segment_count ||= 0
251
+ @detail_segment_count += 1
252
+
253
+ return '' if @detail_segment_count > trace_row_display_limit
254
+
255
+ @indentation_depth = depth if depth > @indentation_depth
256
+ repeat = nil
257
+ if segment.is_a?(NewRelic::TransactionSample::CompositeSegment)
258
+ html = ''
259
+ else
260
+ repeat = segment.parent_segment.detail_segments.length if segment.parent_segment.is_a?(NewRelic::TransactionSample::CompositeSegment)
261
+ html = render(:partial => agent_views_path('segment'), :object => segment, :locals => {:indent => depth, :repeat => repeat})
262
+ depth += 1
263
+ end
264
+
265
+ segment.called_segments.each do |child|
266
+ html << render_segment_details(child, depth)
267
+ end
268
+
269
+ html
270
+ end
271
+
272
+ def exclude_file_from_stack_trace?(file, include_rails)
273
+ !include_rails && (
274
+ file =~ /\/active(_)*record\// ||
275
+ file =~ /\/action(_)*controller\// ||
276
+ file =~ /\/activesupport\// ||
277
+ file =~ /\/lib\/mongrel/ ||
278
+ file =~ /\/actionpack\// ||
279
+ file =~ /\/passenger\// ||
280
+ file =~ /\/benchmark.rb/ ||
281
+ file !~ /\.rb/) # must be a .rb file, otherwise it's a script of something else...we could have gotten trickier and tried to see if this file exists...
282
+ end
283
+
284
+ def show_view_link(title, page_name)
285
+ link_to_function("[#{title}]", "show_view('#{page_name}')");
286
+ end
287
+ def mime_type_from_extension(extension)
288
+ extension = extension[/[^.]*$/].downcase
289
+ case extension
290
+ when 'png'; 'image/png'
291
+ when 'gif'; 'image/gif'
292
+ when 'jpg'; 'image/jpg'
293
+ when 'css'; 'text/css'
294
+ when 'js'; 'text/javascript'
295
+ else 'text/plain'
296
+ end
297
+ end
298
+ def to_ms(number)
299
+ (number*1000).round
300
+ end
301
+ def to_percentage(value)
302
+ (value * 100).round if value
303
+ end
304
+ def with_delimiter(val)
305
+ return '0' if val.nil?
306
+ parts = val.to_s.split('.')
307
+ parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1,")
308
+ parts.join '.'
309
+ end
310
+
311
+ def profile_table(profile)
312
+ out = StringIO.new
313
+ printer = RubyProf::GraphHtmlPrinter.new(profile)
314
+ printer.print(out, :min_percent=>0.1)
315
+ out.string[/<body>(.*)<\/body>/im, 0].gsub('<table>', '<table class=profile>')
316
+ end
317
+ end