tingyun_rpm 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.gitignore +14 -0
  4. data/.travis.yml +4 -0
  5. data/CODE_OF_CONDUCT.md +13 -0
  6. data/Gemfile +3 -0
  7. data/Guardfile +25 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +41 -0
  10. data/cert/cacert.pem +0 -0
  11. data/lib/ting_yun/agent/agent.rb +128 -0
  12. data/lib/ting_yun/agent/class_methods.rb +21 -0
  13. data/lib/ting_yun/agent/collector/base_sampler.rb +2 -0
  14. data/lib/ting_yun/agent/collector/error_collector/error_trace_array.rb +88 -0
  15. data/lib/ting_yun/agent/collector/error_collector/noticed_error.rb +129 -0
  16. data/lib/ting_yun/agent/collector/error_collector.rb +165 -0
  17. data/lib/ting_yun/agent/collector/middle_ware_collector/cpu_sampler.rb +68 -0
  18. data/lib/ting_yun/agent/collector/middle_ware_collector/memory_sampler.rb +139 -0
  19. data/lib/ting_yun/agent/collector/middle_ware_collector/middle_ware.rb +13 -0
  20. data/lib/ting_yun/agent/collector/middle_ware_collector/sampler.rb +59 -0
  21. data/lib/ting_yun/agent/collector/middle_ware_collector.rb +80 -0
  22. data/lib/ting_yun/agent/collector/sql_sampler.rb +299 -0
  23. data/lib/ting_yun/agent/collector/stats_engine/metric_stats.rb +170 -0
  24. data/lib/ting_yun/agent/collector/stats_engine/stats_hash.rb +172 -0
  25. data/lib/ting_yun/agent/collector/stats_engine.rb +28 -0
  26. data/lib/ting_yun/agent/collector/transaction_sampler/slowest_sample_buffer.rb +25 -0
  27. data/lib/ting_yun/agent/collector/transaction_sampler/transaction_sample_buffer_base.rb +96 -0
  28. data/lib/ting_yun/agent/collector/transaction_sampler.rb +226 -0
  29. data/lib/ting_yun/agent/container_data_manager.rb +94 -0
  30. data/lib/ting_yun/agent/cross_app/cross_app_monitor.rb +131 -0
  31. data/lib/ting_yun/agent/cross_app/cross_app_tracing.rb +202 -0
  32. data/lib/ting_yun/agent/cross_app/inbound_request_monitor.rb +22 -0
  33. data/lib/ting_yun/agent/database.rb +410 -0
  34. data/lib/ting_yun/agent/datastore/metric_helper.rb +82 -0
  35. data/lib/ting_yun/agent/datastore/mongo.rb +44 -0
  36. data/lib/ting_yun/agent/datastore.rb +33 -0
  37. data/lib/ting_yun/agent/dispatcher.rb +39 -0
  38. data/lib/ting_yun/agent/event/event_listener.rb +47 -0
  39. data/lib/ting_yun/agent/event/event_loop.rb +194 -0
  40. data/lib/ting_yun/agent/instance_methods/connect.rb +164 -0
  41. data/lib/ting_yun/agent/instance_methods/container_data_manager.rb +137 -0
  42. data/lib/ting_yun/agent/instance_methods/handle_errors.rb +71 -0
  43. data/lib/ting_yun/agent/instance_methods/start.rb +219 -0
  44. data/lib/ting_yun/agent/instance_methods/start_worker_thread.rb +51 -0
  45. data/lib/ting_yun/agent/instance_methods.rb +39 -0
  46. data/lib/ting_yun/agent/method_tracer.rb +256 -0
  47. data/lib/ting_yun/agent/method_tracer_helpers.rb +85 -0
  48. data/lib/ting_yun/agent/threading/agent_thread.rb +49 -0
  49. data/lib/ting_yun/agent/transaction/attributes.rb +22 -0
  50. data/lib/ting_yun/agent/transaction/request_attributes.rb +126 -0
  51. data/lib/ting_yun/agent/transaction/trace.rb +125 -0
  52. data/lib/ting_yun/agent/transaction/trace_node.rb +110 -0
  53. data/lib/ting_yun/agent/transaction/traced_method_stack.rb +80 -0
  54. data/lib/ting_yun/agent/transaction/transaction_metrics.rb +51 -0
  55. data/lib/ting_yun/agent/transaction/transaction_sample_builder.rb +63 -0
  56. data/lib/ting_yun/agent/transaction/transaction_state.rb +112 -0
  57. data/lib/ting_yun/agent/transaction.rb +522 -0
  58. data/lib/ting_yun/agent.rb +207 -0
  59. data/lib/ting_yun/configuration/default_source.rb +638 -0
  60. data/lib/ting_yun/configuration/dotted_hash.rb +46 -0
  61. data/lib/ting_yun/configuration/environment_source.rb +116 -0
  62. data/lib/ting_yun/configuration/manager.rb +232 -0
  63. data/lib/ting_yun/configuration/manual_source.rb +14 -0
  64. data/lib/ting_yun/configuration/server_source.rb +88 -0
  65. data/lib/ting_yun/configuration/yaml_source.rb +136 -0
  66. data/lib/ting_yun/configuration.rb +9 -0
  67. data/lib/ting_yun/environment_report.rb +123 -0
  68. data/lib/ting_yun/frameworks/class_methods.rb +47 -0
  69. data/lib/ting_yun/frameworks/external.rb +15 -0
  70. data/lib/ting_yun/frameworks/instance_methods.rb +120 -0
  71. data/lib/ting_yun/frameworks/instrumentation.rb +67 -0
  72. data/lib/ting_yun/frameworks/rails.rb +63 -0
  73. data/lib/ting_yun/frameworks/rails3.rb +26 -0
  74. data/lib/ting_yun/frameworks/rails4.rb +14 -0
  75. data/lib/ting_yun/frameworks/ruby.rb +17 -0
  76. data/lib/ting_yun/frameworks/sinatra.rb +10 -0
  77. data/lib/ting_yun/frameworks.rb +34 -0
  78. data/lib/ting_yun/http/generic_request.rb +8 -0
  79. data/lib/ting_yun/http/net_http_request.rb +46 -0
  80. data/lib/ting_yun/instrumentation/active_record.rb +103 -0
  81. data/lib/ting_yun/instrumentation/middleware_proxy.rb +77 -0
  82. data/lib/ting_yun/instrumentation/middleware_tracing.rb +84 -0
  83. data/lib/ting_yun/instrumentation/mongo.rb +103 -0
  84. data/lib/ting_yun/instrumentation/mongo2.rb +37 -0
  85. data/lib/ting_yun/instrumentation/mongo_command_log_subscriber.rb +97 -0
  86. data/lib/ting_yun/instrumentation/moped.rb +95 -0
  87. data/lib/ting_yun/instrumentation/net.rb +59 -0
  88. data/lib/ting_yun/instrumentation/rack.rb +109 -0
  89. data/lib/ting_yun/instrumentation/rails3/action_controller.rb +63 -0
  90. data/lib/ting_yun/instrumentation/rails3/action_view.rb +115 -0
  91. data/lib/ting_yun/instrumentation/rails4/action_controller_subscriber.rb +124 -0
  92. data/lib/ting_yun/instrumentation/rails4/action_view_subscriber.rb +118 -0
  93. data/lib/ting_yun/instrumentation/rails4/active_record_subscriber.rb +124 -0
  94. data/lib/ting_yun/instrumentation/rails_middleware.rb +38 -0
  95. data/lib/ting_yun/instrumentation/redis.rb +70 -0
  96. data/lib/ting_yun/instrumentation/support/active_record_helper.rb +178 -0
  97. data/lib/ting_yun/instrumentation/support/controller_instrumentation.rb +54 -0
  98. data/lib/ting_yun/instrumentation/support/database.rb +38 -0
  99. data/lib/ting_yun/instrumentation/support/event_formatter.rb +19 -0
  100. data/lib/ting_yun/instrumentation/support/evented_subscriber.rb +97 -0
  101. data/lib/ting_yun/instrumentation/support/external_error.rb +52 -0
  102. data/lib/ting_yun/instrumentation/support/metric_translator.rb +84 -0
  103. data/lib/ting_yun/instrumentation/support/mongo_formatter.rb +49 -0
  104. data/lib/ting_yun/instrumentation/support/parameter_filtering.rb +21 -0
  105. data/lib/ting_yun/instrumentation/support/queue_time.rb +76 -0
  106. data/lib/ting_yun/instrumentation/support/transaction_namer.rb +68 -0
  107. data/lib/ting_yun/instrumentation/thrift.rb +329 -0
  108. data/lib/ting_yun/logger/agent_logger.rb +196 -0
  109. data/lib/ting_yun/logger/log_once.rb +38 -0
  110. data/lib/ting_yun/logger/memory_logger.rb +56 -0
  111. data/lib/ting_yun/logger/null_logger.rb +31 -0
  112. data/lib/ting_yun/logger/startup_logger.rb +13 -0
  113. data/lib/ting_yun/logger.rb +8 -0
  114. data/lib/ting_yun/metrics/metric_data.rb +86 -0
  115. data/lib/ting_yun/metrics/metric_spec.rb +89 -0
  116. data/lib/ting_yun/metrics/stats.rb +158 -0
  117. data/lib/ting_yun/metrics.rb +12 -0
  118. data/lib/ting_yun/support/coerce.rb +86 -0
  119. data/lib/ting_yun/support/collector.rb +29 -0
  120. data/lib/ting_yun/support/exception.rb +79 -0
  121. data/lib/ting_yun/support/hash_extensions.rb +25 -0
  122. data/lib/ting_yun/support/helper.rb +54 -0
  123. data/lib/ting_yun/support/hostname.rb +13 -0
  124. data/lib/ting_yun/support/http_clients/uri_util.rb +49 -0
  125. data/lib/ting_yun/support/language_support.rb +155 -0
  126. data/lib/ting_yun/support/library_detection.rb +129 -0
  127. data/lib/ting_yun/support/local_environment.rb +185 -0
  128. data/lib/ting_yun/support/path.rb +13 -0
  129. data/lib/ting_yun/support/serialize/encodes.rb +61 -0
  130. data/lib/ting_yun/support/serialize/encoding_normalizer.rb +84 -0
  131. data/lib/ting_yun/support/serialize/json_marshaller.rb +73 -0
  132. data/lib/ting_yun/support/serialize/json_wrapper.rb +78 -0
  133. data/lib/ting_yun/support/serialize/marshaller.rb +69 -0
  134. data/lib/ting_yun/support/serialize/ok_json.rb +651 -0
  135. data/lib/ting_yun/support/system_info.rb +206 -0
  136. data/lib/ting_yun/support/timer_lib.rb +29 -0
  137. data/lib/ting_yun/support/version_number.rb +70 -0
  138. data/lib/ting_yun/ting_yun_service/connection.rb +118 -0
  139. data/lib/ting_yun/ting_yun_service/http.rb +41 -0
  140. data/lib/ting_yun/ting_yun_service/request.rb +90 -0
  141. data/lib/ting_yun/ting_yun_service/ssl.rb +45 -0
  142. data/lib/ting_yun/ting_yun_service/upload_service.rb +149 -0
  143. data/lib/ting_yun/ting_yun_service.rb +124 -0
  144. data/lib/ting_yun/version.rb +17 -0
  145. data/lib/tingyun_rpm.rb +47 -0
  146. data/tingyun_rpm.gemspec +60 -0
  147. metadata +415 -0
@@ -0,0 +1,219 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under Ting Yun's license terms.
3
+ require 'ting_yun/frameworks'
4
+ require 'ting_yun/version'
5
+
6
+ # before the real start,do check and log things
7
+ module TingYun
8
+ module Agent
9
+ module InstanceMethods
10
+ module Start
11
+
12
+ # Check to see if the agent should start, returning +true+ if it should.
13
+ # should hava the vaild app_name, unstart-state and able to start
14
+ def agent_should_start?
15
+ return false if already_started? || disabled?
16
+ unless app_name_configured?
17
+ TingYun::Agent.logger.error "No application name configured.",
18
+ "The Agent cannot start without at least one. Please check your ",
19
+ "tingyun.yml and ensure that it is valid and has at least one ",
20
+ "value set for app_name in the",
21
+ "environment."
22
+ return false
23
+ end
24
+ return true
25
+ end
26
+
27
+ def started?
28
+ @started
29
+ end
30
+
31
+ # Check whether we have already started, which is an error condition
32
+ def already_started?
33
+ if started?
34
+ TingYun::Agent.logger.info("Agent Started Already!")
35
+ true
36
+ end
37
+ end
38
+
39
+ # The agent is disabled when it is not force enabled by the
40
+ # 'nbs.agent_enabled' option (e.g. in a manual start), or
41
+ # enabled normally through the configuration file
42
+ def disabled?
43
+ !TingYun::Agent.config[:'nbs.agent_enabled']
44
+ end
45
+
46
+ def log_startup
47
+ log_environment
48
+ log_dispatcher
49
+ log_app_name
50
+ end
51
+
52
+ def log_environment
53
+ Agent.logger.info "Environment: #{::TingYun::Frameworks.framework.env}"
54
+ end
55
+
56
+ # Logs the dispatcher to the log file to assist with
57
+ # debugging. When no debugger is present, logs this fact to
58
+ # assist with proper dispatcher detection
59
+ def log_dispatcher
60
+ dispatcher_name = TingYun::Agent.config[:dispatcher].to_s
61
+
62
+ if dispatcher_name.empty?
63
+ TingYun::Agent.logger.info 'No known dispatcher detected.'
64
+ else
65
+ TingYun::Agent.logger.info "Dispatcher: #{dispatcher_name}"
66
+ end
67
+ end
68
+
69
+ def log_app_name
70
+ TingYun::Agent.logger.info "Application: #{TingYun::Agent.config.app_names.join(", ")}"
71
+ end
72
+
73
+ def sinatra_app?
74
+ (
75
+ defined?(Sinatra::Application) &&
76
+ Sinatra::Application.respond_to?(:run) &&
77
+ Sinatra::Application.run?
78
+ )
79
+ end
80
+
81
+ # Classy logging of the agent version and the current pid,
82
+ # so we can disambiguate processes in the log file and make
83
+ # sure they're running a reasonable version
84
+ def log_version_and_pid
85
+ TingYun::Agent.logger.debug "Ting Yun Ruby Agent #{TingYun::VERSION::STRING} Initialized: pid = #{$$}"
86
+ end
87
+
88
+ # Warn the user if they have configured their agent not to
89
+ # send data, that way we can see this clearly in the log file
90
+ def monitoring?
91
+ if TingYun::Agent.config[:monitor_mode]
92
+ true
93
+ else
94
+ TingYun::Agent.logger.warn('Agent configured not to send data in this environment.')
95
+ false
96
+ end
97
+ end
98
+
99
+ # Tell the user when the license key is missing so they can
100
+ # fix it by adding it to the file
101
+ def has_license_key?
102
+ if TingYun::Agent.config[:license_key] && TingYun::Agent.config[:license_key].length > 0
103
+ true
104
+ else
105
+ TingYun::Agent.logger.warn("No license key found. " +
106
+ "This often means your tingyun.yml file was not found, or it lacks a section for the running environment,'#{::TingYun::Frameworks.framework.env}'. You may also want to try linting your tingyun.yml to ensure it is valid YML.")
107
+ false
108
+ end
109
+ end
110
+
111
+ # A license key is an arbitrary 40 character string,
112
+ # usually looks something like a SHA1 hash
113
+ def correct_license_length
114
+ key = TingYun::Agent.config[:license_key]
115
+
116
+ if key.length > 0
117
+ true
118
+ else
119
+ TingYun::Agent.logger.error("Invalid license key: #{key}")
120
+ false
121
+ end
122
+ end
123
+
124
+ # A correct license key exists and is of the proper length
125
+ def has_correct_license_key?
126
+ has_license_key? && correct_license_length
127
+ end
128
+
129
+ # Logs the configured application names
130
+ def app_name_configured?
131
+ names = TingYun::Agent.config.app_names
132
+ return names.respond_to?(:any?) && names.any?
133
+ end
134
+
135
+
136
+ # If we're using a dispatcher that forks before serving
137
+ # requests, we need to wait until the children are forked
138
+ # before connecting, otherwise the parent process sends useless data
139
+ def is_using_forking_dispatcher?
140
+ if [:puma, :passenger, :rainbows, :unicorn].include? TingYun::Agent.config[:dispatcher]
141
+ TingYun::Agent.logger.info "Deferring startup of agent reporting thread because #{TingYun::Agent.config[:dispatcher]} may fork."
142
+ true
143
+ else
144
+ false
145
+ end
146
+ end
147
+
148
+ # Sanity-check the agent configuration and start the agent,
149
+ # setting up the worker thread and the exit handler to shut
150
+ # down the agent
151
+ def check_config_and_start_agent
152
+ return unless monitoring? && has_correct_license_key?
153
+ return if is_using_forking_dispatcher?
154
+ setup_and_start_agent
155
+ end
156
+
157
+ # This is the shared method between the main agent startup and the
158
+ # after_fork call restarting the thread in deferred dispatchers.
159
+ #
160
+ # Treatment of @started and env report is important to get right.
161
+ def setup_and_start_agent(options={})
162
+ @started = true
163
+ @dispatcher.mark_started
164
+ generate_environment_report
165
+ install_exit_handler
166
+ cpu_and_memory
167
+
168
+ start_worker_thread(options)
169
+
170
+ end
171
+
172
+ # This method should be called in a forked process after a fork.
173
+ # It assumes the parent process initialized the agent, but does
174
+ # not assume the agent started.
175
+ #
176
+ # The call is idempotent, but not re-entrant.
177
+ #
178
+ # * It clears any metrics carried over from the parent process
179
+ # * Restarts the sampler thread if necessary
180
+ # * Initiates a new agent run and worker loop unless that was done
181
+ # in the parent process and +:force_reconnect+ is not true
182
+ #
183
+ # Options:
184
+ # * <tt>:force_reconnect => true</tt> to force the spawned process to
185
+ # establish a new connection, such as when forking a long running process.
186
+ # The default is false--it will only connect to the server if the parent
187
+ # had not connected.
188
+ # * <tt>:keep_retrying => false</tt> if we try to initiate a new
189
+ # connection, this tells me to only try it once so this method returns
190
+ # quickly if there is some kind of latency with the server.
191
+ def after_fork(options={})
192
+ needs_restart = false
193
+ @after_fork_lock.synchronize do
194
+ needs_restart = @dispatcher.needs_restart?
195
+ @dispatcher.mark_started
196
+ end
197
+
198
+ return if !needs_restart ||
199
+ !Agent.config[:'nbs.agent_enabled'] ||
200
+ !Agent.config[:monitor_mode] ||
201
+ disconnected?
202
+
203
+ ::TingYun::Agent.logger.debug "Starting the worker thread in #{Process.pid} (parent #{Process.ppid}) after forking."
204
+
205
+ # Clear out locks and stats left over from parent process
206
+ reset_objects_with_locks
207
+ drop_buffered_data
208
+
209
+ setup_and_start_agent(options)
210
+ end
211
+
212
+ def cpu_and_memory
213
+ @middleware.load_samplers
214
+ end
215
+
216
+ end
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,51 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under Ting Yun's license terms.
3
+ require 'ting_yun/agent/threading/agent_thread'
4
+ require 'ting_yun/agent/event/event_loop'
5
+
6
+ module TingYun
7
+ module Agent
8
+ module InstanceMethods
9
+ module StartWorkerThread
10
+ def start_worker_thread(connection_options={})
11
+ TingYun::Agent.logger.debug "Creating Ruby Agent worker thread."
12
+ @worker_thread = TingYun::Agent::Threading::AgentThread.create('Worker Loop') do
13
+ deferred_work!(connection_options)
14
+ end
15
+ end
16
+
17
+ # This is the method that is run in a new thread in order to
18
+ # background the harvesting and sending of data during the
19
+ # normal operation of the agent.
20
+ #
21
+ # Takes connection options that determine how we should
22
+ # connect to the server, and loops endlessly - typically we
23
+ # never return from this method unless we're shutting down
24
+ # the agent
25
+ def deferred_work!(connection_options)
26
+ catch_errors do
27
+ TingYun::Agent.disable_all_tracing do
28
+ connect!(connection_options)
29
+ if connected?
30
+ create_and_run_event_loop
31
+ else
32
+ TingYun::Agent.logger.debug "No connection. Worker thread ending."
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def create_and_run_event_loop
39
+ @event_loop = TingYun::Agent::Event::EventLoop.new
40
+
41
+ @event_loop.on(:report_data) do
42
+ transmit_data
43
+ end
44
+ @event_loop.fire_every(Agent.config[:data_report_period], :report_data)
45
+
46
+ @event_loop.run
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,39 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under Ting Yun's license terms.
3
+ require 'ting_yun/agent/instance_methods/start'
4
+ require 'ting_yun/agent/instance_methods/connect'
5
+ require 'ting_yun/agent/instance_methods/start_worker_thread'
6
+ require 'ting_yun/agent/instance_methods/container_data_manager'
7
+
8
+ module TingYun
9
+ module Agent
10
+ module InstanceMethods
11
+
12
+ include Start
13
+ include Connect
14
+ include ContainerDataManager
15
+ include StartWorkerThread
16
+
17
+
18
+
19
+ def reset_to_default_configuration
20
+ TingYun::Agent.config.remove_config_type(:manual)
21
+ TingYun::Agent.config.remove_config_type(:server)
22
+ end
23
+
24
+ def stop_event_loop
25
+ @event_loop.stop if @event_loop
26
+ end
27
+
28
+
29
+ def push_trace_execution_flag(flag =false)
30
+ TransactionState.tl_get.push_traced(flag)
31
+ end
32
+
33
+ def pop_trace_execution_flag
34
+ TransactionState.tl_get.pop_traced
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,256 @@
1
+ # encoding: utf-8
2
+
3
+ # This file may be independently required to set up method tracing prior to
4
+ # the full agent loading. In those cases, we do need at least this require to
5
+ # bootstrap things.
6
+
7
+ require 'ting_yun/frameworks' unless defined?(TingYun::Frameworks::Framework)
8
+ require 'ting_yun/support/helper'
9
+ require 'ting_yun/agent/method_tracer_helpers'
10
+
11
+ module TingYun
12
+ module Agent
13
+
14
+ # This module contains class methods added to support installing custom
15
+ # metric tracers and executing for individual metrics.
16
+ #
17
+ # == Examples
18
+ #
19
+ # When the agent initializes, it extends Module with these methods.
20
+ # However if you want to use the API in code that might get loaded
21
+ # before the agent is initialized you will need to require
22
+ # this file:
23
+ #
24
+ # require 'ting_yun/agent/method_tracer'
25
+ # class A
26
+ # include TingYun::Agent::MethodTracer
27
+ # def process
28
+ # ...
29
+ # end
30
+ # add_method_tracer :process
31
+ # end
32
+ #
33
+ # To instrument a class method:
34
+ #
35
+ # require 'ting_yun/agent/method_tracer'
36
+ # class An
37
+ # def self.process
38
+ # ...
39
+ # end
40
+ # class << self
41
+ # include TingYun::Agent::MethodTracer
42
+ # add_method_tracer :process
43
+ # end
44
+ # end
45
+ #
46
+ # @api public
47
+ module MethodTracer
48
+
49
+ def self.included klass
50
+ klass.extend ClassMethods
51
+ end
52
+
53
+ def self.extended klass
54
+ klass.extend ClassMethods
55
+ end
56
+
57
+
58
+ def self.trace_execution_scoped(metric_names, options={}, callback = nil) #THREAD_LOCAL_ACCESS
59
+ TingYun::Agent::MethodTracerHelpers.trace_execution_scoped(metric_names, options, callback) do
60
+ # Using an implicit block avoids object allocation for a &block param
61
+ yield
62
+ end
63
+ end
64
+
65
+ # Defines methods used at the class level, for adding instrumentation
66
+ # @api public
67
+ module ClassMethods
68
+
69
+ # contains methods refactored out of the #add_method_tracer method
70
+ module AddMethodTracer
71
+ # Checks to see if the method we are attempting to trace
72
+ # actually exists or not. #add_method_tracer can't do
73
+ # anything if the method doesn't exist.
74
+ def _method_exists?(method_name)
75
+ exists = method_defined?(method_name) || private_method_defined?(method_name)
76
+ ::TingYun::Agent.logger.error("Did not trace #{self.name}##{method_name} because that method does not exist") unless exists
77
+ exists
78
+ end
79
+
80
+ # Default to the class where the method is defined.
81
+ #
82
+ # Example:
83
+ # Foo.default_metric_name_code('bar') #=> "Custom/#{Foo.name}/bar"
84
+ def default_metric_name_code(method_name)
85
+ "Custom/#{self.name}/#{method_name.to_s}"
86
+ end
87
+
88
+ # Checks to see if we have already traced a method with a
89
+ # given metric by checking to see if the traced method
90
+ # exists. Warns the user if methods are being double-traced
91
+ # to help with debugging custom instrumentation.
92
+ def traced_method_exists?(method_name, metric_name_code)
93
+ exists = method_defined?(_traced_method_name(method_name, metric_name_code))
94
+ ::TingYun::Agent.logger.error("Attempt to trace a method twice with the same metric: Method = #{method_name}, Metric Name = #{metric_name_code}") if exists
95
+ exists
96
+ end
97
+
98
+ # Decides which code snippet we should be eval'ing in this
99
+ # context, based on the options.
100
+ def code_to_eval(method_name, metric_name_code, options)
101
+ options = validate_options(method_name, options)
102
+ if options[:push_scope]
103
+ method_with_push_scope(method_name, metric_name_code, options)
104
+ else
105
+ method_without_push_scope(method_name, metric_name_code, options)
106
+ end
107
+ end
108
+
109
+ # returns an eval-able string that contains the tracing code
110
+ # for a fully traced metric including scoping
111
+ def method_with_push_scope(method_name, metric_name_code, options)
112
+ "def #{_traced_method_name(method_name, metric_name_code)}(*args, &block)
113
+ #{options[:code_header]}
114
+ result = ::TingYun::Agent::MethodTracerHelpers.trace_execution_scoped(\"#{metric_name_code}\",
115
+ :metric => #{options[:metric]}) do
116
+ #{_untraced_method_name(method_name, metric_name_code)}(*args, &block)
117
+ end
118
+ #{options[:code_footer]}
119
+ result
120
+ end"
121
+ end
122
+
123
+ # returns an eval-able string that contains the traced
124
+ # method code used if the agent is not creating a scope for
125
+ # use in scoped metrics.
126
+ def method_without_push_scope(method_name, metric_name_code, options)
127
+ "def #{_traced_method_name(method_name, metric_name_code)}(*args, &block)
128
+ #{assemble_code_header(method_name, metric_name_code, options)}
129
+ t0 = Time.now
130
+ begin
131
+ #{_untraced_method_name(method_name, metric_name_code)}(*args, &block)\n
132
+ ensure
133
+ duration = (Time.now - t0).to_f
134
+ ::TingYun::Agent.record_metric(\"#{metric_name_code}\", duration)
135
+ #{options[:code_footer]}
136
+ end
137
+ end"
138
+ end
139
+
140
+ # Returns a code snippet to be eval'd that skips tracing
141
+ # when the agent is not tracing execution. turns
142
+ # instrumentation into effectively one method call overhead
143
+ # when the agent is disabled
144
+ def assemble_code_header(method_name, metric_name_code, options)
145
+ # header = "return #{_untraced_method_name(method_name, metric_name_code)}(*args, &block) unless TingYun::Agent.tl_is_execution_traced?\n"
146
+ header += options[:code_header].to_s
147
+ header
148
+ end
149
+
150
+
151
+
152
+ end
153
+ include AddMethodTracer
154
+
155
+
156
+ # Add a method tracer to the specified method.
157
+ #
158
+ # By default, this will cause invocations of the traced method to be
159
+ # recorded in transaction traces, and in a metric named after the class
160
+ # and method. It will also make the method show up in transaction-level
161
+ # breakdown charts and tables.
162
+ #
163
+ # === Overriding the metric name
164
+ #
165
+ # +metric_name_code+ is a string that is eval'd to get the name of the
166
+ # metric associated with the call, so if you want to use interpolation
167
+ # evaluated at call time, then single quote the value like this:
168
+ #
169
+ # add_method_tracer :foo, 'Custom/#{self.class.name}/foo'
170
+ #
171
+ # This would name the metric according to the class of the runtime
172
+ # intance, as opposed to the class where +foo+ is defined.
173
+ #
174
+ # If not provided, the metric name will be <tt>Custom/ClassName/method_name</tt>.
175
+ #
176
+ # @param [Symbol] method_name the name of the method to trace
177
+ # @param [String] metric_name_code the metric name to record calls to
178
+ # the traced method under. This may be either a static string, or Ruby
179
+ # code to be evaluated at call-time in order to determine the metric
180
+ # name dynamically.
181
+ # @param [Hash] options additional options controlling how the method is
182
+ # traced.
183
+ # @option options [Boolean] :push_scope (true) If false, the traced method will
184
+ # not appear in transaction traces or breakdown charts, and it will
185
+ # only be visible in custom dashboards.
186
+ # @option options [Boolean] :metric (true) If false, the traced method will
187
+ # only appear in transaction traces, but no metrics will be recorded
188
+ # for it.
189
+ # @option options [String] :code_header ('') Ruby code to be inserted and run
190
+ # before the tracer begins timing.
191
+ # @option options [String] :code_footer ('') Ruby code to be inserted and run
192
+ # after the tracer stops timing.
193
+ #
194
+ # @example
195
+ # add_method_tracer :foo
196
+ #
197
+ # # With a custom metric name
198
+ # add_method_tracer :foo, 'Custom/#{self.class.name}/foo'
199
+ #
200
+ # # Instrument foo only for custom dashboards (not in transaction
201
+ # # traces or breakdown charts)
202
+ # add_method_tracer :foo, 'Custom/foo', :push_scope => false
203
+ #
204
+ # # Instrument foo in transaction traces only
205
+ # add_method_tracer :foo, 'Custom/foo', :metric => false
206
+ #
207
+ # @api public
208
+ #
209
+ def add_method_tracer(method_name, metric_name_code=nil, options = {})
210
+ return unless _method_exists?(method_name)
211
+ metric_name_code ||= default_metric_name_code(method_name)
212
+ return if traced_method_exists?(method_name, metric_name_code)
213
+
214
+ traced_method = code_to_eval(method_name, metric_name_code, options)
215
+
216
+ visibility = TingYun::Support::Helper.instance_method_visibility self, method_name
217
+
218
+ class_eval traced_method, __FILE__, __LINE__
219
+ alias_method _untraced_method_name(method_name, metric_name_code), method_name
220
+ alias_method method_name, _traced_method_name(method_name, metric_name_code)
221
+ send visibility, method_name
222
+ send visibility, _traced_method_name(method_name, metric_name_code)
223
+ ::TingYun::Agent.logger.debug("Traced method: class = #{self.name},"+
224
+ "method = #{method_name}, "+
225
+ "metric = '#{metric_name_code}'")
226
+ end
227
+
228
+ private
229
+
230
+ # given a method and a metric, this method returns the
231
+ # untraced alias of the method name
232
+ def _untraced_method_name(method_name, metric_name)
233
+ "#{_sanitize_name(method_name)}_without_trace_#{_sanitize_name(metric_name)}"
234
+ end
235
+
236
+ # given a method and a metric, this method returns the traced
237
+ # alias of the method name
238
+ def _traced_method_name(method_name, metric_name)
239
+ "#{_sanitize_name(method_name)}_with_trace_#{_sanitize_name(metric_name)}"
240
+ end
241
+
242
+ # makes sure that method names do not contain characters that
243
+ # might break the interpreter, for example ! or ? characters
244
+ # that are not allowed in the middle of method names
245
+ def _sanitize_name(name)
246
+ name.to_s.tr_s('^a-zA-Z0-9', '_')
247
+ end
248
+ end
249
+
250
+
251
+ end
252
+
253
+
254
+ end
255
+ end
256
+
@@ -0,0 +1,85 @@
1
+ # encoding: utf-8
2
+ require 'ting_yun/agent'
3
+ require 'ting_yun/agent/transaction/transaction_state'
4
+
5
+ module TingYun
6
+ module Agent
7
+ module MethodTracerHelpers
8
+
9
+ extend self
10
+
11
+ def log_errors(code_area)
12
+ yield
13
+ rescue => e
14
+ ::TingYun::Agent.logger.error("Caught exception in #{code_area}.", e)
15
+ ::TingYun::Agent.notice_error(e, method: code_area, path: "ting_yun/agent/method_tracer_helpers")
16
+ end
17
+
18
+ def trace_execution_scoped_header(state, t0)
19
+ log_errors(:trace_execution_scoped_header) do
20
+ stack = state.traced_method_stack
21
+ stack.push_frame(state, :method_tracer, t0)
22
+ end
23
+ end
24
+
25
+ def trace_execution_scoped_footer(state, t0, first_name, metric_names, expected_frame, options, t1=Time.now.to_f)
26
+ log_errors(:trace_execution_scoped_footer) do
27
+ if expected_frame
28
+ stack = state.traced_method_stack
29
+ create_metrics = options.has_key?(:metric) ? options[:metric] : true
30
+ frame = stack.pop_frame(state, expected_frame, first_name, t1, create_metrics)
31
+
32
+ if create_metrics
33
+ duration = (t1 - t0)*1000
34
+ exclusive = duration - frame.children_time
35
+ if duration < 0
36
+ ::TingYun::Agent.logger.log_once(:warn, "metric_duration_negative:#{first_name}",
37
+ "Metric #{first_name} has negative duration: #{duration} ms")
38
+ end
39
+ if exclusive < 0
40
+ ::TingYun::Agent.logger.log_once(:warn, "metric_exclusive_negative:#{first_name}",
41
+ "Metric #{first_name} has negative exclusive time: duration = #{duration} ms, child_time = #{frame.children_time}")
42
+ end
43
+ record_metrics(state, first_name, metric_names, duration, exclusive, options)
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ def record_metrics(state, first_name, other_names, duration, exclusive, options)
50
+ record_scoped_metric = options.has_key?(:scoped_metric) ? options[:scoped_metric] : true
51
+ stat_engine = TingYun::Agent.instance.stats_engine
52
+ if record_scoped_metric
53
+ stat_engine.record_scoped_and_unscoped_metrics(state, first_name, other_names, duration, exclusive)
54
+ else
55
+ metrics = [first_name].concat(other_names)
56
+ stat_engine.record_unscoped_metrics(state, metrics, duration, exclusive)
57
+ end
58
+ end
59
+
60
+ def trace_execution_scoped(metric_names, options={}, callback = nil) #THREAD_LOCAL_ACCESS
61
+ state = TingYun::Agent::TransactionState.tl_get
62
+
63
+ metric_names = Array(metric_names)
64
+ first_name = metric_names.shift
65
+ return yield unless first_name
66
+
67
+ start_time = Time.now.to_f
68
+ expected_scope = trace_execution_scoped_header(state, start_time)
69
+
70
+ begin
71
+ yield
72
+ ensure
73
+ elapsed_time = (Time.now.to_f - start_time)
74
+ if callback
75
+ callback.call(elapsed_time)
76
+ end
77
+ trace_execution_scoped_footer(state, start_time, first_name, metric_names, expected_scope, options)
78
+ end
79
+ end
80
+
81
+
82
+
83
+ end
84
+ end
85
+ end