tingyun_rpm 1.1.4.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/Guardfile +10 -0
  4. data/lib/ting_yun/agent.rb +1 -0
  5. data/lib/ting_yun/agent/agent.rb +16 -27
  6. data/lib/ting_yun/agent/collector/error_collector.rb +7 -18
  7. data/lib/ting_yun/agent/collector/error_collector/noticed_error.rb +26 -21
  8. data/lib/ting_yun/agent/collector/middle_ware_collector/cpu_sampler.rb +4 -9
  9. data/lib/ting_yun/agent/collector/sql_sampler.rb +32 -188
  10. data/lib/ting_yun/agent/collector/sql_sampler/slow_sql.rb +47 -0
  11. data/lib/ting_yun/agent/collector/sql_sampler/sql_trace.rb +73 -0
  12. data/lib/ting_yun/agent/collector/sql_sampler/transaction_sql_data.rb +26 -0
  13. data/lib/ting_yun/agent/collector/stats_engine/metric_stats.rb +6 -5
  14. data/lib/ting_yun/agent/collector/stats_engine/stats_hash.rb +2 -2
  15. data/lib/ting_yun/agent/collector/transaction_sampler.rb +23 -159
  16. data/lib/ting_yun/agent/collector/transaction_sampler/class_method.rb +130 -0
  17. data/lib/ting_yun/agent/collector/transaction_sampler/slowest_sample_buffer.rb +1 -1
  18. data/lib/ting_yun/agent/collector/transaction_sampler/transaction_sample_buffer_base.rb +1 -1
  19. data/lib/ting_yun/agent/cross_app/cross_app_monitor.rb +29 -79
  20. data/lib/ting_yun/agent/cross_app/cross_app_tracing.rb +36 -66
  21. data/lib/ting_yun/agent/database.rb +41 -349
  22. data/lib/ting_yun/agent/database/connection_manager.rb +44 -0
  23. data/lib/ting_yun/agent/database/explain_plan_helpers.rb +173 -0
  24. data/lib/ting_yun/agent/database/obfuscator.rb +151 -0
  25. data/lib/ting_yun/agent/database/statement.rb +70 -0
  26. data/lib/ting_yun/agent/event/event_loop.rb +1 -2
  27. data/lib/ting_yun/agent/instance_methods/connect.rb +8 -20
  28. data/lib/ting_yun/agent/instance_methods/container_data_manager.rb +2 -3
  29. data/lib/ting_yun/agent/instance_methods/handle_errors.rb +6 -1
  30. data/lib/ting_yun/agent/instance_methods/start.rb +13 -81
  31. data/lib/ting_yun/agent/transaction.rb +48 -391
  32. data/lib/ting_yun/agent/transaction/apdex.rb +53 -0
  33. data/lib/ting_yun/agent/transaction/attributes.rb +2 -1
  34. data/lib/ting_yun/agent/transaction/class_method.rb +127 -0
  35. data/lib/ting_yun/agent/transaction/exceptions.rb +42 -0
  36. data/lib/ting_yun/agent/transaction/instance_method.rb +139 -0
  37. data/lib/ting_yun/agent/transaction/request_attributes.rb +9 -39
  38. data/lib/ting_yun/agent/transaction/trace.rb +7 -5
  39. data/lib/ting_yun/agent/transaction/trace_node.rb +1 -3
  40. data/lib/ting_yun/agent/transaction/traced_method_stack.rb +2 -3
  41. data/lib/ting_yun/agent/transaction/transaction_sample_builder.rb +6 -1
  42. data/lib/ting_yun/agent/transaction/transaction_state.rb +59 -17
  43. data/lib/ting_yun/agent/transaction/transaction_timings.rb +72 -0
  44. data/lib/ting_yun/configuration.rb +11 -0
  45. data/lib/ting_yun/configuration/default_source.rb +20 -17
  46. data/lib/ting_yun/configuration/manager.rb +50 -21
  47. data/lib/ting_yun/frameworks.rb +1 -0
  48. data/lib/ting_yun/frameworks/rails.rb +15 -0
  49. data/lib/ting_yun/instrumentation/active_record.rb +12 -18
  50. data/lib/ting_yun/instrumentation/middleware_tracing.rb +8 -14
  51. data/lib/ting_yun/instrumentation/mongo.rb +21 -27
  52. data/lib/ting_yun/instrumentation/mongo_command_log_subscriber.rb +7 -3
  53. data/lib/ting_yun/instrumentation/moped.rb +2 -2
  54. data/lib/ting_yun/instrumentation/net.rb +4 -5
  55. data/lib/ting_yun/instrumentation/rack.rb +1 -2
  56. data/lib/ting_yun/instrumentation/rails4/active_record_subscriber.rb +22 -20
  57. data/lib/ting_yun/instrumentation/redis.rb +2 -2
  58. data/lib/ting_yun/instrumentation/support/controller_instrumentation.rb +1 -1
  59. data/lib/ting_yun/instrumentation/support/external_error.rb +19 -16
  60. data/lib/ting_yun/instrumentation/support/javascript_instrumentor.rb +92 -0
  61. data/lib/ting_yun/instrumentation/support/thrift_helper.rb +73 -0
  62. data/lib/ting_yun/instrumentation/thrift.rb +19 -222
  63. data/lib/ting_yun/logger.rb +1 -0
  64. data/lib/ting_yun/logger/agent_logger.rb +11 -67
  65. data/lib/ting_yun/logger/create_logger_helper.rb +72 -0
  66. data/lib/ting_yun/metrics/metric_data.rb +9 -31
  67. data/lib/ting_yun/metrics/metric_spec.rb +11 -0
  68. data/lib/ting_yun/metrics/stats.rb +24 -1
  69. data/lib/ting_yun/middleware/agent_middleware.rb +28 -0
  70. data/lib/ting_yun/middleware/browser_monitoring.rb +111 -0
  71. data/lib/ting_yun/support/coerce.rb +1 -0
  72. data/lib/ting_yun/support/exception.rb +2 -33
  73. data/lib/ting_yun/support/local_environment.rb +7 -7
  74. data/lib/ting_yun/support/serialize/marshaller.rb +7 -25
  75. data/lib/ting_yun/ting_yun_service.rb +12 -9
  76. data/lib/ting_yun/ting_yun_service/connection.rb +3 -0
  77. data/lib/ting_yun/ting_yun_service/http.rb +4 -1
  78. data/lib/ting_yun/ting_yun_service/request.rb +5 -13
  79. data/lib/ting_yun/ting_yun_service/upload_service.rb +5 -7
  80. data/lib/ting_yun/version.rb +3 -5
  81. data/lib/tingyun_rpm.rb +12 -10
  82. data/tingyun_rpm.gemspec +3 -0
  83. metadata +49 -5
  84. data/.DS_Store +0 -0
  85. data/lib/ting_yun/agent/collector/base_sampler.rb +0 -2
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+
3
+ module TingYun
4
+ module Agent
5
+ module Database
6
+ # Returns a cached connection for a given ActiveRecord
7
+ # configuration - these are stored or reopened as needed, and if
8
+ # we cannot get one, we ignore it and move on without explaining
9
+ # the sql
10
+ class ConnectionManager
11
+ include Singleton
12
+
13
+ def get_connection(config, &connector)
14
+ @connections ||= {}
15
+
16
+ connection = @connections[config]
17
+
18
+ return connection if connection
19
+
20
+ begin
21
+ @connections[config] = connector.call(config)
22
+ rescue => e
23
+ ::TingYun::Agent.logger.error("Caught exception trying to get connection to DB for explain.", e)
24
+ nil
25
+ end
26
+ end
27
+
28
+ # Closes all the connections in the internal connection cache
29
+ def close_connections
30
+ @connections ||= {}
31
+ @connections.values.each do |connection|
32
+ begin
33
+ connection.disconnect!
34
+ rescue
35
+ end
36
+ end
37
+
38
+ @connections = {}
39
+ end
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,173 @@
1
+ # encoding: utf-8
2
+ require 'ting_yun/agent/database/obfuscator'
3
+
4
+ module TingYun
5
+ module Agent
6
+ module Database
7
+ module ExplainPlanHelpers
8
+
9
+ def handle_exception_in_explain
10
+ yield
11
+ rescue => e
12
+ begin
13
+ # guarantees no throw from explain_sql
14
+ ::TingYun::Agent.logger.error("Error getting query plan:", e)
15
+ nil
16
+ rescue
17
+ # double exception. throw up your hands
18
+ nil
19
+ end
20
+ end
21
+
22
+ def is_select?(sql)
23
+ parse_operation_from_query(sql) == 'select'
24
+ end
25
+
26
+ def parameterized?(sql)
27
+ TingYun::Agent::Database::Obfuscator.instance.obfuscate_single_quote_literals(sql) =~ /\$\d+/
28
+ end
29
+
30
+ SQL_COMMENT_REGEX = Regexp.new('/\*.*?\*/', Regexp::MULTILINE).freeze
31
+ EMPTY_STRING = ''.freeze
32
+
33
+
34
+ KNOWN_OPERATIONS = [
35
+ 'alter',
36
+ 'select',
37
+ 'update',
38
+ 'delete',
39
+ 'insert',
40
+ 'create',
41
+ 'show',
42
+ 'set',
43
+ 'exec',
44
+ 'execute',
45
+ 'call'
46
+ ]
47
+
48
+ def parse_operation_from_query(sql)
49
+ sql = TingYun::Helper.correctly_encoded(sql).gsub(SQL_COMMENT_REGEX, EMPTY_STRING)
50
+ if sql =~ /(\w+)/
51
+ op = $1.downcase
52
+ return op if KNOWN_OPERATIONS.include?(op)
53
+ end
54
+ end
55
+
56
+
57
+
58
+
59
+ POSTGRES_PREFIX = 'postgres'.freeze
60
+ MYSQL_PREFIX = 'mysql'.freeze
61
+ MYSQL2_PREFIX = 'mysql2'.freeze
62
+ SQLITE_PREFIX = 'sqlite'.freeze
63
+
64
+ def symbolized_adapter(adapter)
65
+ if adapter.start_with? POSTGRES_PREFIX
66
+ :postgres
67
+ elsif adapter == MYSQL_PREFIX
68
+ :mysql
69
+ # For the purpose of fetching explain plans, we need to maintain the distinction
70
+ # between usage of mysql and mysql2. Obfuscation is the same, though.
71
+ elsif adapter == MYSQL2_PREFIX
72
+ :mysql2
73
+ elsif adapter.start_with? SQLITE_PREFIX
74
+ :sqlite
75
+ else
76
+ adapter.to_sym
77
+ end
78
+ end
79
+
80
+
81
+ def process_resultset(results, adapter)
82
+ if adapter == :postgres
83
+ return process_explain_results_postgres(results)
84
+ elsif defined?(::ActiveRecord::Result) && results.is_a?(::ActiveRecord::Result)
85
+ # Note if adapter is mysql, will only have headers, not values
86
+ return [results.columns, results.rows]
87
+ elsif results.is_a?(String)
88
+ return string_explain_plan_results(results)
89
+ end
90
+
91
+ case adapter
92
+ when :mysql2
93
+ process_explain_results_mysql2(results)
94
+ when :mysql
95
+ process_explain_results_mysql(results)
96
+ when :sqlite
97
+ process_explain_results_sqlite(results)
98
+ else
99
+ return {}
100
+ end
101
+ end
102
+
103
+ QUERY_PLAN = 'QUERY PLAN'.freeze
104
+
105
+ def process_explain_results_postgres(results)
106
+ if defined?(::ActiveRecord::Result) && results.is_a?(::ActiveRecord::Result)
107
+ query_plan_string = results.rows.join("\n")
108
+ elsif results.is_a?(String)
109
+ query_plan_string = results
110
+ else
111
+ lines = []
112
+ results.each { |row| lines << row[QUERY_PLAN] }
113
+ query_plan_string = lines.join("\n")
114
+ end
115
+
116
+ unless TingYun::Agent::Database.record_sql_method("nbs.action_tracer.record_sql") == :raw
117
+ query_plan_string = TingYun::Agent::Database::Obfuscator.instance.obfuscate_postgres_explain(query_plan_string)
118
+ end
119
+ values = query_plan_string.split("\n").map { |line| [line] }
120
+
121
+ {"dialect"=> "PostgreSQL", "keys"=>[QUERY_PLAN], "values"=>values}
122
+ end
123
+
124
+
125
+ def string_explain_plan_results(adpater, results)
126
+ {"dialect"=> adpater, "keys"=>[], "values"=>[results]}
127
+ end
128
+
129
+ def process_explain_results_mysql2(results)
130
+ headers = results.fields
131
+ values = []
132
+ results.each { |row| values << row }
133
+ {"dialect"=> "MySQL", "keys"=>headers, "values"=>values}
134
+ end
135
+
136
+ def process_explain_results_mysql(results)
137
+ headers = []
138
+ values = []
139
+ if results.is_a?(Array)
140
+ # We're probably using the jdbc-mysql gem for JRuby, which will give
141
+ # us an array of hashes.
142
+ headers = results.first.keys
143
+ results.each do |row|
144
+ values << headers.map { |h| row[h] }
145
+ end
146
+ else
147
+ # We're probably using the native mysql driver gem, which will give us
148
+ # a Mysql::Result object that responds to each_hash
149
+ results.each_hash do |row|
150
+ headers = row.keys
151
+ values << headers.map { |h| row[h] }
152
+ end
153
+ end
154
+ {"dialect"=> "MySQL", "keys"=>headers, "values"=>values}
155
+ end
156
+
157
+ SQLITE_EXPLAIN_COLUMNS = %w[addr opcode p1 p2 p3 p4 p5 comment]
158
+
159
+ def process_explain_results_sqlite(results)
160
+ headers = SQLITE_EXPLAIN_COLUMNS
161
+ values = []
162
+ results.each do |row|
163
+ values << headers.map { |h| row[h] }
164
+ end
165
+ {"dialect"=> "sqlite", "keys"=>headers, "values"=>values}
166
+ end
167
+
168
+
169
+
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,151 @@
1
+ # encoding: utf-8
2
+
3
+ module TingYun
4
+ module Agent
5
+ module Database
6
+
7
+ #混淆器
8
+ class Obfuscator
9
+ include Singleton
10
+
11
+ attr_reader :obfuscator
12
+
13
+ def initialize
14
+ reset
15
+ end
16
+
17
+ def reset
18
+ @obfuscator = method(:default_sql_obfuscator)
19
+ end
20
+
21
+ QUERY_TOO_LARGE_MESSAGE = "Query too large (over 16k characters) to safely obfuscate"
22
+ FAILED_TO_OBFUSCATE_MESSAGE = "Failed to obfuscate SQL query - quote characters remained after obfuscation"
23
+
24
+ def default_sql_obfuscator(sql)
25
+ stmt = sql.kind_of?(Statement) ? sql : Statement.new(sql)
26
+
27
+ if stmt.sql[-3,3] == '...'
28
+ return QUERY_TOO_LARGE_MESSAGE
29
+ end
30
+
31
+ obfuscate(stmt.sql, stmt.adapter).to_s
32
+ end
33
+
34
+
35
+
36
+
37
+
38
+
39
+ module ObfuscationHelpers
40
+ COMPONENTS_REGEX_MAP = {
41
+ :single_quotes => /'(?:[^']|'')*?(?:\\'.*|'(?!'))/,
42
+ :double_quotes => /"(?:[^"]|"")*?(?:\\".*|"(?!"))/,
43
+ :dollar_quotes => /(\$(?!\d)[^$]*?\$).*?(?:\1|$)/,
44
+ :uuids => /\{?(?:[0-9a-fA-F]\-*){32}\}?/,
45
+ :numeric_literals => /\b-?(?:[0-9]+\.)?[0-9]+([eE][+-]?[0-9]+)?\b/,
46
+ :boolean_literals => /\b(?:true|false|null)\b/i,
47
+ :hexadecimal_literals => /0x[0-9a-fA-F]+/,
48
+ :comments => /(?:#|--).*?(?=\r|\n|$)/i,
49
+ :multi_line_comments => /\/\*(?:[^\/]|\/[^*])*?(?:\*\/|\/\*.*)/,
50
+ :oracle_quoted_strings => /q'\[.*?(?:\]'|$)|q'\{.*?(?:\}'|$)|q'\<.*?(?:\>'|$)|q'\(.*?(?:\)'|$)/
51
+ }
52
+
53
+ DIALECT_COMPONENTS = {
54
+ :fallback => COMPONENTS_REGEX_MAP.keys,
55
+ :mysql => [:single_quotes, :double_quotes, :numeric_literals, :boolean_literals,
56
+ :hexadecimal_literals, :comments, :multi_line_comments],
57
+ :postgres => [:single_quotes, :dollar_quotes, :uuids, :numeric_literals,
58
+ :boolean_literals, :comments, :multi_line_comments],
59
+ :sqlite => [:single_quotes, :numeric_literals, :boolean_literals, :hexadecimal_literals,
60
+ :comments, :multi_line_comments],
61
+ :oracle => [:single_quotes, :oracle_quoted_strings, :numeric_literals, :comments,
62
+ :multi_line_comments],
63
+ :cassandra => [:single_quotes, :uuids, :numeric_literals, :boolean_literals,
64
+ :hexadecimal_literals, :comments, :multi_line_comments]
65
+ }
66
+
67
+ # We use these to check whether the query contains any quote characters
68
+ # after obfuscation. If so, that's a good indication that the original
69
+ # query was malformed, and so our obfuscation can't reliably find
70
+ # literals. In such a case, we'll replace the entire query with a
71
+ # placeholder.
72
+ CLEANUP_REGEX = {
73
+ :mysql => /'|"|\/\*|\*\//,
74
+ :mysql2 => /'|"|\/\*|\*\//,
75
+ :postgres => /'|\/\*|\*\/|\$(?!\?)/,
76
+ :sqlite => /'|\/\*|\*\//,
77
+ :cassandra => /'|\/\*|\*\//,
78
+ :oracle => /'|\/\*|\*\//,
79
+ :oracle_enhanced => /'|\/\*|\*\//
80
+ }
81
+
82
+
83
+ QUOTED_STRINGS_REGEX = /'(?:[^']|'')*'|"(?:[^"]|"")*"/
84
+ LABEL_LINE_REGEX = /^([^:\n]*:\s+).*$/.freeze
85
+
86
+
87
+ def obfuscate_postgres_explain(sql)
88
+ sql.gsub!(QUOTED_STRINGS_REGEX) do |match|
89
+ match.start_with?('"') ? match : '?'
90
+ end
91
+
92
+ sql.gsub!(LABEL_LINE_REGEX, '\1?')
93
+ sql
94
+ end
95
+
96
+
97
+ PLACEHOLDER = '?'.freeze
98
+ FAILED_TO_OBFUSCATE_MESSAGE = "Failed to obfuscate SQL query - quote characters remained after obfuscation".freeze
99
+
100
+
101
+
102
+ def obfuscate_single_quote_literals(sql)
103
+ return sql unless sql =~ COMPONENTS_REGEX_MAP[:single_quotes]
104
+ sql.gsub(COMPONENTS_REGEX_MAP[:single_quotes], PLACEHOLDER)
105
+ end
106
+
107
+ def self.generate_regex(dialect)
108
+ components = DIALECT_COMPONENTS[dialect]
109
+ Regexp.union(components.map{|component| COMPONENTS_REGEX_MAP[component]})
110
+ end
111
+
112
+ MYSQL_COMPONENTS_REGEX = self.generate_regex(:mysql)
113
+ POSTGRES_COMPONENTS_REGEX = self.generate_regex(:postgres)
114
+ SQLITE_COMPONENTS_REGEX = self.generate_regex(:sqlite)
115
+ ORACLE_COMPONENTS_REGEX = self.generate_regex(:oracle)
116
+ CASSANDRA_COMPONENTS_REGEX = self.generate_regex(:cassandra)
117
+ FALLBACK_REGEX = self.generate_regex(:fallback)
118
+
119
+ def obfuscate(sql, adapter)
120
+ case adapter
121
+ when :mysql, :mysql2
122
+ regex = MYSQL_COMPONENTS_REGEX
123
+ when :postgres
124
+ regex = POSTGRES_COMPONENTS_REGEX
125
+ when :sqlite
126
+ regex = SQLITE_COMPONENTS_REGEX
127
+ when :oracle, :oracle_enhanced
128
+ regex = ORACLE_COMPONENTS_REGEX
129
+ when :cassandra
130
+ regex = CASSANDRA_COMPONENTS_REGEX
131
+ else
132
+ regex = FALLBACK_REGEX
133
+ end
134
+ obfuscated = sql.gsub(regex, PLACEHOLDER)
135
+ obfuscated = FAILED_TO_OBFUSCATE_MESSAGE if detect_unmatched_pairs(obfuscated, adapter)
136
+ obfuscated
137
+ end
138
+
139
+ def detect_unmatched_pairs(obfuscated, adapter)
140
+ if CLEANUP_REGEX[adapter]
141
+ CLEANUP_REGEX[adapter].match(obfuscated)
142
+ else
143
+ CLEANUP_REGEX[:mysql].match(obfuscated)
144
+ end
145
+ end
146
+ end
147
+ include ObfuscationHelpers
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,70 @@
1
+ # encoding: utf-8
2
+ require 'ting_yun/support/helper'
3
+ require 'ting_yun/agent/database/explain_plan_helpers'
4
+
5
+ module TingYun
6
+ module Agent
7
+ module Database
8
+ class Statement
9
+ include TingYun::Agent::Database::ExplainPlanHelpers
10
+
11
+ attr_accessor :sql, :config, :explainer, :binds, :name
12
+
13
+ def initialize(sql, config={}, explainer=nil, binds=[], name=DEFAULT_QUERY_NAME)
14
+ @sql = TingYun::Agent::Database.capture_query(sql)
15
+ @config = config
16
+ @explainer = explainer
17
+ @binds = binds
18
+ @name = name
19
+ end
20
+
21
+ def adapter
22
+ return unless @config
23
+
24
+ @adapter ||= if @config[:adapter]
25
+ symbolized_adapter(@config[:adapter].to_s.downcase)
26
+ elsif @config[:uri] && @config[:uri].to_s =~ /^jdbc:([^:]+):/
27
+ # This case is for Sequel with the jdbc-mysql, jdbc-postgres, or jdbc-sqlite3 gems.
28
+ symbolized_adapter($1)
29
+ else
30
+ nil
31
+ end
32
+ end
33
+
34
+
35
+
36
+ SUPPORTED_ADAPTERS_FOR_EXPLAIN = [:postgres, :mysql2, :mysql, :sqlite]
37
+
38
+ def explain
39
+ return unless explainable?
40
+ handle_exception_in_explain do
41
+ plan = explainer.call(self)
42
+ return process_resultset(plan, adapter) if plan
43
+ end
44
+ end
45
+
46
+
47
+ def explainable?
48
+ return false unless @explainer && is_select?(sql)
49
+
50
+ if sql[-3,3] == '...'
51
+ TingYun::Agent.logger.debug('Unable to collect explain plan for truncated query.')
52
+ return false
53
+ end
54
+
55
+ if parameterized?(@sql) && @binds.empty?
56
+ TingYun::Agent.logger.debug('Unable to collect explain plan for parameter-less parameterized query.')
57
+ return false
58
+ end
59
+
60
+ if !SUPPORTED_ADAPTERS_FOR_EXPLAIN.include?(adapter)
61
+ TingYun::Agent.logger.debug("Not collecting explain plan because an unknown connection adapter ('#{adapter}') was used.")
62
+ return false
63
+ end
64
+
65
+ true
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -146,10 +146,9 @@ module TingYun
146
146
  @subscriptions[event].each do |s|
147
147
  begin
148
148
  s.call(*args)
149
- rescue TingYun::Support::Exception::ExpiredConfigurationException, TingYun::Support::Exception::InvalidDataTokenException, TingYun::Support::Exception::InvalidDataException
150
- raise
151
149
  rescue => e
152
150
  errors << e
151
+ raise
153
152
  end
154
153
  end
155
154