oneapm_rpm 1.1.3 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/lib/one_apm/agent.rb +4 -4
  3. data/lib/one_apm/agent/agent/connect.rb +3 -49
  4. data/lib/one_apm/agent/agent/container_data_manager.rb +2 -10
  5. data/lib/one_apm/agent/agent/forkable_dispatcher_functions.rb +4 -2
  6. data/lib/one_apm/agent/agent/helpers.rb +2 -0
  7. data/lib/one_apm/agent/agent/start.rb +3 -3
  8. data/lib/one_apm/agent/agent/start_worker_thread.rb +2 -0
  9. data/lib/one_apm/agent/database/active_record_helper.rb +1 -0
  10. data/lib/one_apm/agent/{datastores.rb → datastore.rb} +2 -2
  11. data/lib/one_apm/agent/{datastores → datastore}/metric_helper.rb +1 -1
  12. data/lib/one_apm/agent/{datastores → datastore}/mongo.rb +1 -1
  13. data/lib/one_apm/agent/{datastores → datastore}/mongo/metric_translator.rb +4 -4
  14. data/lib/one_apm/agent/{datastores → datastore}/mongo/obfuscator.rb +1 -1
  15. data/lib/one_apm/agent/{datastores → datastore}/mongo/statement_formatter.rb +2 -2
  16. data/lib/one_apm/agent/threading/agent_thread.rb +50 -50
  17. data/lib/one_apm/agent/threading/thread_profile.rb +1 -4
  18. data/lib/one_apm/collector/commands/thread_profiler_session.rb +1 -1
  19. data/lib/one_apm/collector/containers/agent_command_router.rb +1 -1
  20. data/lib/one_apm/collector/containers/transaction_sampler.rb +7 -1
  21. data/lib/one_apm/collector/containers/utilization_data.rb +3 -4
  22. data/lib/one_apm/{agent → collector}/sampler.rb +1 -1
  23. data/lib/one_apm/{agent → collector}/samplers/cpu_sampler.rb +4 -4
  24. data/lib/one_apm/{agent → collector}/samplers/delayed_job_sampler.rb +3 -14
  25. data/lib/one_apm/{agent → collector}/samplers/memory_sampler.rb +13 -15
  26. data/lib/one_apm/{agent → collector}/samplers/object_sampler.rb +3 -3
  27. data/lib/one_apm/{agent → collector}/samplers/vm_sampler.rb +2 -2
  28. data/lib/one_apm/collector/{forked_process_service.rb → support/forked_process_service.rb} +1 -1
  29. data/lib/one_apm/{agent → collector/support}/sampler_collection.rb +2 -2
  30. data/lib/one_apm/configuration/default_source.rb +7 -2
  31. data/lib/one_apm/frameworks/rails.rb +18 -0
  32. data/lib/one_apm/inst/nosql/memcache.rb +2 -2
  33. data/lib/one_apm/inst/nosql/mongo.rb +6 -6
  34. data/lib/one_apm/inst/nosql/mongo_moped.rb +3 -3
  35. data/lib/one_apm/inst/nosql/redis.rb +4 -4
  36. data/lib/one_apm/manager.rb +38 -6
  37. data/lib/one_apm/metrics/metric_spec.rb +1 -4
  38. data/lib/one_apm/probe/instrumentation.rb +2 -5
  39. data/lib/one_apm/rack/developer_mode.rb +221 -0
  40. data/lib/one_apm/rack/developer_mode/helper.rb +299 -0
  41. data/lib/one_apm/rack/developer_mode/views/oneapm/_explain_plans.html.erb +25 -0
  42. data/lib/one_apm/rack/developer_mode/views/oneapm/_home_right.html.erb +18 -0
  43. data/lib/one_apm/rack/developer_mode/views/oneapm/_sample.html.erb +20 -0
  44. data/lib/one_apm/rack/developer_mode/views/oneapm/_segment.html.erb +24 -0
  45. data/lib/one_apm/rack/developer_mode/views/oneapm/_segment_limit_message.html.erb +1 -0
  46. data/lib/one_apm/rack/developer_mode/views/oneapm/_segment_row.html.erb +11 -0
  47. data/lib/one_apm/rack/developer_mode/views/oneapm/_show_sample_detail.html.erb +30 -0
  48. data/lib/one_apm/rack/developer_mode/views/oneapm/_show_sample_sql.html.erb +19 -0
  49. data/lib/one_apm/rack/developer_mode/views/oneapm/_show_sample_summary.html.erb +25 -0
  50. data/lib/one_apm/rack/developer_mode/views/oneapm/_sql_row.html.erb +15 -0
  51. data/lib/one_apm/rack/developer_mode/views/oneapm/_stack_trace.html.erb +14 -0
  52. data/lib/one_apm/rack/developer_mode/views/oneapm/_summary_table.html.erb +12 -0
  53. data/lib/one_apm/rack/developer_mode/views/oneapm/assets/images/arrow-close.png +0 -0
  54. data/lib/one_apm/rack/developer_mode/views/oneapm/assets/images/arrow-open.png +0 -0
  55. data/lib/one_apm/rack/developer_mode/views/oneapm/assets/images/oneapm_logo.png +0 -0
  56. data/lib/one_apm/rack/developer_mode/views/oneapm/assets/javascript/c3.min.js +5 -0
  57. data/lib/one_apm/rack/developer_mode/views/oneapm/assets/javascript/d3.min.js +5 -0
  58. data/lib/one_apm/rack/developer_mode/views/oneapm/assets/javascript/jquery.min.js +4 -0
  59. data/lib/one_apm/rack/developer_mode/views/oneapm/assets/javascript/transaction_sample.js +120 -0
  60. data/lib/one_apm/rack/developer_mode/views/oneapm/assets/stylesheets/bootstrap.min.css +5 -0
  61. data/lib/one_apm/rack/developer_mode/views/oneapm/assets/stylesheets/c3.css +158 -0
  62. data/lib/one_apm/rack/developer_mode/views/oneapm/assets/stylesheets/style.css +149 -0
  63. data/lib/one_apm/rack/developer_mode/views/oneapm/explain_sql.html.erb +53 -0
  64. data/lib/one_apm/rack/developer_mode/views/oneapm/index.html.erb +33 -0
  65. data/lib/one_apm/rack/developer_mode/views/oneapm/layout.html.erb +46 -0
  66. data/lib/one_apm/rack/developer_mode/views/oneapm/sample_not_found.html.erb +2 -0
  67. data/lib/one_apm/rack/developer_mode/views/oneapm/show_sample.html.erb +61 -0
  68. data/lib/one_apm/rack/developer_mode/views/oneapm/threads.html.erb +53 -0
  69. data/lib/one_apm/{agent/threading → support/backtrace}/backtrace_node.rb +0 -0
  70. data/lib/one_apm/{agent/threading → support/backtrace}/backtrace_service.rb +0 -0
  71. data/lib/one_apm/support/environment_report.rb +6 -38
  72. data/lib/one_apm/support/system_info.rb +1 -6
  73. data/lib/one_apm/transaction/composite_segment.rb +30 -0
  74. data/lib/one_apm/transaction/sample_buffer/developer_mode_sample_buffer.rb +58 -0
  75. data/lib/one_apm/transaction/segment_summary.rb +0 -5
  76. data/lib/one_apm/transaction/summary_segment.rb +24 -0
  77. data/lib/one_apm/transaction/transaction_sample.rb +2 -0
  78. data/lib/one_apm/version.rb +2 -2
  79. metadata +51 -19
  80. data/lib/one_apm/configuration/autostart.rb +0 -41
@@ -14,7 +14,7 @@ LibraryDetection.defer do
14
14
  end
15
15
 
16
16
  executes do
17
- require 'one_apm/agent/datastores'
17
+ require 'one_apm/agent/datastore'
18
18
 
19
19
  ::Redis::Client.class_eval do
20
20
  # Support older versions of Redis::Client that used the method
@@ -28,7 +28,7 @@ LibraryDetection.defer do
28
28
  _send_to_one_apm(args, elapsed)
29
29
  end
30
30
 
31
- OneApm::Agent::Datastores.wrap("Redis", method_name, nil, callback) do
31
+ OneApm::Agent::Datastore.wrap("Redis", method_name, nil, callback) do
32
32
  call_without_oneapm_trace(*args, &blk)
33
33
  end
34
34
  end
@@ -57,7 +57,7 @@ LibraryDetection.defer do
57
57
  end
58
58
  end
59
59
 
60
- OneApm::Agent::Datastores.wrap("Redis", "pipelined", nil, callback) do
60
+ OneApm::Agent::Datastore.wrap("Redis", "pipelined", nil, callback) do
61
61
  call_pipelined_without_oneapm_trace commands, *rest
62
62
  end
63
63
  end
@@ -76,7 +76,7 @@ LibraryDetection.defer do
76
76
  end
77
77
  end
78
78
  end
79
- OneApm::Agent::Datastores.notice_statement(args.inspect, elapsed)
79
+ OneApm::Agent::Datastore.notice_statement(args.inspect, elapsed)
80
80
  end
81
81
  end
82
82
  end
@@ -35,12 +35,12 @@ require 'one_apm/support/forked_process_channel'
35
35
 
36
36
  require 'one_apm/configuration'
37
37
 
38
- require 'one_apm/agent/sampler'
39
- require 'one_apm/agent/samplers/cpu_sampler'
40
- require 'one_apm/agent/samplers/memory_sampler'
41
- require 'one_apm/agent/samplers/object_sampler'
42
- require 'one_apm/agent/samplers/delayed_job_sampler'
43
- require 'one_apm/agent/samplers/vm_sampler'
38
+ require 'one_apm/collector/sampler'
39
+ require 'one_apm/collector/samplers/cpu_sampler'
40
+ require 'one_apm/collector/samplers/memory_sampler'
41
+ require 'one_apm/collector/samplers/object_sampler'
42
+ require 'one_apm/collector/samplers/delayed_job_sampler'
43
+ require 'one_apm/collector/samplers/vm_sampler'
44
44
 
45
45
  module OneApm
46
46
  module Manager
@@ -245,5 +245,37 @@ module OneApm
245
245
  require File.expand_path(path)
246
246
  end
247
247
 
248
+ def agent_should_start?
249
+ !blacklisted_constants? &&
250
+ !blacklisted_executables? &&
251
+ !in_blacklisted_rake_task?
252
+ end
253
+
254
+ def blacklisted_constants?
255
+ blacklisted?(OneApm::Manager.config[:'autostart.blacklisted_constants']) do |name|
256
+ OneApm::LanguageSupport.constant_is_defined?(name)
257
+ end
258
+ end
259
+
260
+ def blacklisted_executables?
261
+ blacklisted?(OneApm::Manager.config[:'autostart.blacklisted_executables']) do |bin|
262
+ File.basename($0) == bin
263
+ end
264
+ end
265
+
266
+ def blacklisted?(value, &block)
267
+ value.split(/\s*,\s*/).any?(&block)
268
+ end
269
+
270
+ def in_blacklisted_rake_task?
271
+ tasks = begin
272
+ ::Rake.application.top_level_tasks
273
+ rescue => e
274
+ OneApm::Manager.logger.debug("Not in Rake environment so skipping blacklisted_rake_tasks check: #{e}")
275
+ []
276
+ end
277
+ !(tasks & OneApm::Manager.config[:'autostart.blacklisted_rake_tasks'].split(/\s*,\s*/)).empty?
278
+ end
279
+
248
280
  end
249
281
  end
@@ -1,8 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
- # this struct uniquely defines a metric, optionally inside
4
- # the call scope of another metric
5
- # the call scope of another metric
6
3
  module OneApm
7
4
  class MetricSpec
8
5
  attr_reader :name, :scope
@@ -79,4 +76,4 @@ module OneApm
79
76
  return (self.scope || '') <=> (o.scope || '')
80
77
  end
81
78
  end
82
- end
79
+ end
@@ -6,14 +6,11 @@ module OneApm
6
6
  class Probe
7
7
  module Instrumentation
8
8
 
9
- # install instrumentations for the current framework
10
9
  def install_instrumentation
11
10
  return if @instrumented
12
11
 
13
12
  @instrumented = true
14
13
 
15
- # Instrumentation for the key code points inside rails for monitoring by OneApm.
16
- # note this file is loaded only if the oneapm agent is enabled (through config/oneapm.yml)
17
14
  instrumentation_path = File.expand_path(File.join(File.dirname(__FILE__), '..', 'inst'))
18
15
  Dir.glob("#{instrumentation_path}/**/*.rb").each do |inst_file|
19
16
  load_instrumentation_files inst_file
@@ -30,7 +27,6 @@ module OneApm
30
27
  LibraryDetection.detect!
31
28
  end
32
29
 
33
- # require specified instrumentation
34
30
  def require_instrumentation file
35
31
  require file
36
32
  rescue => e
@@ -38,7 +34,6 @@ module OneApm
38
34
  end
39
35
 
40
36
  def install_shim
41
- # Once we install instrumentation, you can't undo that by installing the shim.
42
37
  if @instrumented
43
38
  OneApm::Manager.logger.error "Cannot install the Agent shim after instrumentation has already been installed!"
44
39
  OneApm::Manager.logger.error caller.join("\n")
@@ -54,7 +49,9 @@ module OneApm
54
49
  @instrumentation_files << pattern
55
50
  end
56
51
  end
52
+
57
53
  end
54
+
58
55
  include Instrumentation
59
56
  end
60
57
  end
@@ -0,0 +1,221 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rack'
4
+ require 'rack/request'
5
+ require 'rack/response'
6
+ require 'rack/file'
7
+
8
+ require 'one_apm/support/collection_helper'
9
+ require 'one_apm/rack/middleware_base'
10
+ require 'one_apm/rack/middleware_wrapper'
11
+
12
+ require 'one_apm/transaction/transaction_sample'
13
+ require 'one_apm/transaction/transaction_analysis'
14
+
15
+ require 'one_apm/rack/developer_mode/helper'
16
+
17
+ module OneApm
18
+ module Rack
19
+ class DeveloperMode < MiddlewareBase
20
+
21
+ VIEW_PATH = File.expand_path('../developer_mode/views/', __FILE__)
22
+
23
+ include OneApm::DeveloperModeHelper
24
+
25
+ def traced_call(env)
26
+ return @app.call(env) unless /^\/oneapm/ =~ ::Rack::Request.new(env).path_info
27
+ dup._call(env)
28
+ end
29
+
30
+ protected
31
+
32
+ def _call(env)
33
+ OneApm::Manager.ignore_transaction
34
+
35
+ @req = ::Rack::Request.new(env)
36
+ @rendered = false
37
+ case @req.path_info
38
+ when /assets/
39
+ ::Rack::File.new(VIEW_PATH).call(env)
40
+ when /index/
41
+ index
42
+ when /threads/
43
+ threads
44
+ when /reset/
45
+ reset
46
+ when /show_sample_summary/
47
+ show_sample_data
48
+ when /show_sample_detail/
49
+ show_sample_data
50
+ when /show_sample_sql/
51
+ show_sample_data
52
+ when /explain_sql/
53
+ explain_sql
54
+ when /^\/oneapm\/?$/
55
+ index
56
+ else
57
+ @app.call(env)
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def index
64
+ get_samples
65
+ render(:index)
66
+ end
67
+
68
+ def reset
69
+ OneApm::Manager.agent.transaction_sampler.reset!
70
+ OneApm::Manager.agent.sql_sampler.reset!
71
+ ::Rack::Response.new{|r| r.redirect('/oneapm/')}.finish
72
+ end
73
+
74
+ def explain_sql
75
+ get_segment
76
+
77
+ return render(:sample_not_found) unless @sample
78
+
79
+ @sql = @segment[:sql]
80
+ @trace = @segment[:backtrace]
81
+
82
+ if OneApm::Manager.agent.record_sql == :obfuscated
83
+ @obfuscated_sql = @segment.obfuscated_sql
84
+ end
85
+
86
+ _headers, explanations = @segment.explain_sql
87
+ if explanations
88
+ @explanation = explanations
89
+ if !@explanation.blank?
90
+ if @explanation.first.length < OneApm::MYSQL_EXPLAIN_COLUMNS.length
91
+ @row_headers = nil
92
+ else
93
+ @row_headers = OneApm::MYSQL_EXPLAIN_COLUMNS
94
+ end
95
+ end
96
+ end
97
+
98
+ render(:explain_sql)
99
+ end
100
+
101
+ def threads
102
+ render(:threads)
103
+ end
104
+
105
+ def render(view, layout=true)
106
+ add_rack_array = true
107
+ if view.is_a? Hash
108
+ layout = false
109
+ if view[:object]
110
+ # object *is* used here, as it is capture in the binding below
111
+ object = view[:object]
112
+ end
113
+
114
+ if view[:collection]
115
+ return view[:collection].map do |obj|
116
+ render({:partial => view[:partial], :object => obj})
117
+ end.join(' ')
118
+ end
119
+
120
+ if view[:partial]
121
+ add_rack_array = false
122
+ view = "_#{view[:partial]}"
123
+ end
124
+ end
125
+ binding = Proc.new {}.binding
126
+ if layout
127
+ body = render_with_layout(view) do
128
+ render_without_layout(view, binding)
129
+ end
130
+ else
131
+ body = render_without_layout(view, binding)
132
+ end
133
+ if add_rack_array
134
+ ::Rack::Response.new(body, 200, {'Content-Type' => 'text/html'}).finish
135
+ else
136
+ body
137
+ end
138
+ end
139
+
140
+ # You have to call this with a block - the contents returned from
141
+ # that block are interpolated into the layout
142
+ def render_with_layout(view)
143
+ body = ERB.new(File.read(File.join(VIEW_PATH, 'oneapm/layout.html.erb')))
144
+ body.result(Proc.new {}.binding)
145
+ end
146
+
147
+ # you have to pass a binding to this (a proc) so that ERB can have
148
+ # access to helper functions and local variables
149
+ def render_without_layout(view, binding)
150
+ ERB.new(File.read(File.join(VIEW_PATH, 'oneapm', view.to_s + '.html.erb')), nil, nil, 'frobnitz').result(binding)
151
+ end
152
+
153
+ def content_tag(tag, contents, opts={})
154
+ opt_values = opts.map {|k, v| "#{k}=\"#{v}\"" }.join(' ')
155
+ "<#{tag} #{opt_values}>#{contents}</#{tag}>"
156
+ end
157
+
158
+ def sample
159
+ @sample || @samples[0]
160
+ end
161
+
162
+ def params
163
+ @req.params
164
+ end
165
+
166
+ def segment
167
+ @segment
168
+ end
169
+
170
+ def show_sample_data
171
+ get_sample
172
+
173
+ return render(:sample_not_found) unless @sample
174
+
175
+ controller_metric = @sample.transaction_name
176
+
177
+ @sample_controller_name = controller_metric
178
+
179
+ @sql_segments = @sample.sql_segments
180
+ if params['d']
181
+ @sql_segments.sort!{|a,b| b.duration <=> a.duration }
182
+ end
183
+
184
+ sort_method = params['sort'] || :total_time
185
+ @profile_options = {:min_percent => 0.5, :sort_method => sort_method.to_sym}
186
+
187
+ render(:show_sample)
188
+ end
189
+
190
+ def get_samples
191
+ @samples = OneApm::Manager.agent.transaction_sampler.dev_mode_sample_buffer.samples.select do |sample|
192
+ sample.params[:path] != nil
193
+ end
194
+
195
+ return @samples = @samples.sort_by(&:duration).reverse if params['h']
196
+ return @samples = @samples.sort{|x,y| x.params[:uri] <=> y.params[:uri]} if params['u']
197
+ @samples = @samples.reverse
198
+ end
199
+
200
+ def get_sample
201
+ get_samples
202
+ id = params['id']
203
+ sample_id = id.to_i
204
+ @samples.each do |s|
205
+ if s.sample_id == sample_id
206
+ @sample = s
207
+ return
208
+ end
209
+ end
210
+ end
211
+
212
+ def get_segment
213
+ get_sample
214
+ return unless @sample
215
+
216
+ segment_id = params['segment'].to_i
217
+ @segment = @sample.find_segment(segment_id)
218
+ end
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,299 @@
1
+ # encoding: utf-8
2
+
3
+ require 'pathname'
4
+ require 'one_apm/support/collection_helper'
5
+
6
+ module OneApm::DeveloperModeHelper
7
+ include OneApm::CollectionHelper
8
+
9
+ private
10
+
11
+ # limit of how many detail/SQL rows we display - very large data sets (~10000+) crash browsers
12
+ def trace_row_display_limit
13
+ 2000
14
+ end
15
+
16
+ def trace_row_display_limit_reached
17
+ (!@detail_segment_count.nil? && @detail_segment_count > trace_row_display_limit) || @sample.sql_segments.length > trace_row_display_limit
18
+ end
19
+
20
+ # return the highest level in the call stack for the trace that is not rails or
21
+ # oneapm agent code
22
+ def application_caller(trace)
23
+ trace = strip_oa_from_backtrace(trace) unless params[:show_nr]
24
+ trace.each do |trace_line|
25
+ file, _line, gem = file_and_line(trace_line)
26
+ unless file && exclude_file_from_stack_trace?(file, false, gem)
27
+ return trace_line
28
+ end
29
+ end
30
+ trace.last
31
+ end
32
+
33
+ def application_stack_trace(trace, include_rails = false)
34
+ trace = strip_oa_from_backtrace(trace) unless params[:show_nr]
35
+ trace.reject do |trace_line|
36
+ file, _line, gem = file_and_line(trace_line)
37
+ file && exclude_file_from_stack_trace?(file, include_rails, gem)
38
+ end
39
+ end
40
+
41
+ def render_backtrace
42
+ if @segment[:backtrace]
43
+ content_tag('h3', '调用堆栈') +
44
+ render(:partial => 'stack_trace')
45
+ end
46
+ end
47
+
48
+ def h(text)
49
+ text
50
+ end
51
+
52
+ def agent_views_path(path)
53
+ path
54
+ end
55
+
56
+ # write the metric label for a segment metric in the detail view
57
+ def write_segment_label(segment)
58
+ link_to_function(segment.metric_name, "toggle_row_class($(this).closest('td').find('a')[0])")
59
+ end
60
+
61
+ # write the metric label for a segment metric in the summary table of metrics
62
+ def write_summary_segment_label(segment)
63
+ segment.metric_name
64
+ end
65
+
66
+ def write_stack_trace_line(trace_line)
67
+ trace_line
68
+ end
69
+
70
+ # print the formatted timestamp for a segment
71
+ def timestamp(segment)
72
+ sprintf("%1.3f", segment.entry_timestamp)
73
+ end
74
+
75
+ def format_timestamp(time)
76
+ time.strftime("%H:%M:%S")
77
+ end
78
+
79
+ def colorize(value, yellow_threshold = 0.05, red_threshold = 0.15, s=to_ms(value))
80
+ if value > yellow_threshold
81
+ color = (value > red_threshold ? 'red' : 'orange')
82
+ "<font color=#{color}>#{s}</font>"
83
+ else
84
+ "#{s}"
85
+ end
86
+ end
87
+
88
+ def expanded_image_path()
89
+ '/oneapm/assets/images/arrow-open.png'
90
+ end
91
+
92
+ def collapsed_image_path()
93
+ '/oneapm/assets/images/arrow-close.png'
94
+ end
95
+
96
+ def explain_sql_url(segment)
97
+ "explain_sql?id=#{@sample.sample_id}&amp;segment=#{segment.segment_id}"
98
+ end
99
+
100
+ def segment_duration_value(segment)
101
+ link_to colorize(segment.duration, 0.05, 0.15, "#{with_delimiter(to_ms(segment.duration))} ms"), explain_sql_url(segment)
102
+ end
103
+
104
+ def line_wrap_sql(sql)
105
+ sql.gsub(/\,/,', ').squeeze(' ') if sql
106
+ end
107
+
108
+ def render_sample_details(sample)
109
+ @indentation_depth=0
110
+ # skip past the root segments to the first child, which is always the controller
111
+ first_segment = sample.root_segment.called_segments.first
112
+
113
+ # render the segments, then the css classes to indent them
114
+ render_segment_details(first_segment).to_s + render_indentation_classes(@indentation_depth).to_s
115
+ end
116
+
117
+ def expand_segment_image(segment, depth)
118
+ if depth > 0
119
+ if !segment.called_segments.empty?
120
+ row_class =segment_child_row_class(segment)
121
+ link_to_function("<img src=\"#{collapsed_image_path}\" id=\"image_#{row_class}\" class_for_children=\"#{row_class}\" class=\"#{(!segment.called_segments.empty?) ? 'parent_segment_image' : 'child_segment_image'}\" />", "toggle_row_class(this)")
122
+ end
123
+ end
124
+ end
125
+
126
+ def segment_child_row_class(segment)
127
+ "segment#{segment.segment_id}"
128
+ end
129
+
130
+ def segment_row_classes(segment, depth)
131
+ classes = []
132
+
133
+ classes << "segment#{segment.parent_segment.segment_id}" if depth > 1
134
+
135
+ classes << "view_segment" if segment.metric_name.index('View') == 0
136
+ classes << "summary_segment" if segment.is_a?(OneApm::TransactionSample::CompositeSegment)
137
+
138
+ classes.join(' ')
139
+ end
140
+
141
+ # render_segment_details should be called before calling this method
142
+ def render_indentation_classes(depth)
143
+ styles = []
144
+ (1..depth).each do |d|
145
+ styles << ".segment_indent_level#{d} { display: inline-block; margin-left: #{(d-1)*10}px }"
146
+ end
147
+ content_tag("style", styles.join(' '))
148
+ end
149
+
150
+ def explain_sql_link(segment, child_sql = false)
151
+ link_to 'SQL', explain_sql_url(segment)
152
+ end
153
+
154
+ def explain_sql_links(segment)
155
+ if segment[:sql]
156
+ explain_sql_link segment
157
+ else
158
+ links = []
159
+ segment.called_segments.each do |child|
160
+ if child[:sql]
161
+ links << explain_sql_link(child, true)
162
+ end
163
+ end
164
+ links[0..1].join(', ') + (links.length > 2?', ...':'')
165
+ end
166
+ end
167
+
168
+ private
169
+ # return three objects, the file path, the line in the file, and the gem the file belongs to
170
+ # if found
171
+ def file_and_line(stack_trace_line)
172
+ if stack_trace_line =~ /^(?:(\w+) \([\d.]*\) )?(.*):(\d+)/
173
+ return $2, $3, $1
174
+ else
175
+ return nil
176
+ end
177
+ end
178
+
179
+ def render_segment_details(segment, depth=0)
180
+ @detail_segment_count ||= 0
181
+ @detail_segment_count += 1
182
+
183
+ return '' if @detail_segment_count > trace_row_display_limit
184
+
185
+ @indentation_depth = depth if depth > @indentation_depth
186
+ repeat = nil
187
+ if segment.is_a?(OneApm::TransactionSample::CompositeSegment)
188
+ html = ''
189
+ else
190
+ repeat = segment.parent_segment.detail_segments.length if segment.parent_segment.is_a?(OneApm::TransactionSample::CompositeSegment)
191
+ html = render(:partial => 'segment', :object => [segment, depth, repeat])
192
+ depth += 1
193
+ end
194
+
195
+ segment.called_segments.each do |child|
196
+ html << render_segment_details(child, depth)
197
+ end
198
+
199
+ html
200
+ end
201
+
202
+ def exclude_file_from_stack_trace?(file, include_rails, gem=nil)
203
+ return false if include_rails
204
+ return true if file !~ /\.(rb|java)/
205
+ return true if %w[rack activerecord activeresource activesupport actionpack railties].include? gem
206
+ %w[/actionmailer/
207
+ /activerecord
208
+ /activeresource
209
+ /activesupport
210
+ /lib/mongrel
211
+ /actionpack
212
+ /passenger/
213
+ /railties
214
+ benchmark.rb].each { |s| return true if file.include? s }
215
+ false
216
+ end
217
+
218
+ def link_to(name, location)
219
+ location = "/oneapm/#{location}" unless /:\/\// =~ location
220
+ "<a href=\"#{location}\">#{name}</a>"
221
+ end
222
+
223
+ def link_to_if(predicate, text, location="")
224
+ if predicate
225
+ link_to(text, location)
226
+ else
227
+ text
228
+ end
229
+ end
230
+
231
+ def link_to_unless_current(text, hash)
232
+ unless params[hash.keys[0].to_s]
233
+ link_to(text,"?#{hash.keys[0]}=#{hash.values[0]}")
234
+ else
235
+ text
236
+ end
237
+ end
238
+
239
+ def cycle(even, odd)
240
+ @cycle ||= 'a'
241
+ if @cycle == 'a'
242
+ @cycle = 'b'
243
+ even
244
+ else
245
+ @cycle = 'a'
246
+ odd
247
+ end
248
+ end
249
+
250
+ def link_to_function(title, javascript)
251
+ "<a href=\"#\" onclick=\"#{javascript}; return false;\">#{title}</a>"
252
+ end
253
+
254
+ def mime_type_from_extension(extension)
255
+ extension = extension[/[^.]*$/].dncase
256
+ case extension
257
+ when 'png'; 'image/png'
258
+ when 'gif'; 'image/gif'
259
+ when 'jpg'; 'image/jpg'
260
+ when 'css'; 'text/css'
261
+ when 'js'; 'text/javascript'
262
+ else 'text/plain'
263
+ end
264
+ end
265
+ def to_ms(number)
266
+ (number*1000).round
267
+ end
268
+ def to_percentage(value)
269
+ (value * 100).round if value
270
+ end
271
+ def with_delimiter(val)
272
+ return '0' if val.nil?
273
+ parts = val.to_s.split('.')
274
+ parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1,")
275
+ parts.join '.'
276
+ end
277
+
278
+ SORT_REPLACEMENTS = {
279
+ "Total" => :total_time,
280
+ "Self" => :self_time,
281
+ "Child" => :children_time,
282
+ "Wait" => :wait_time
283
+ }
284
+
285
+ def profile_table(sample, options)
286
+ out = StringIO.new
287
+ printer = RubyProf::GraphHtmlPrinter.new(sample.profile)
288
+ printer.print(out, options)
289
+ out = out.string[/<body>(.*)<\/body>/im, 0].gsub('<table>', '<table class=profile>')
290
+ SORT_REPLACEMENTS.each do |text, param|
291
+ replacement = (options[:sort_method] == param) ?
292
+ "<th> #{text}&nbsp;&darr;</th>" :
293
+ "<th>#{link_to text, "show_sample_summary?id=#{sample.sample_id}&sort=#{param}"}</th>"
294
+
295
+ out.gsub!(/<th> +#{text}<\/th>/, replacement)
296
+ end
297
+ out
298
+ end
299
+ end