influx_reporter 1.0.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 (103) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +51 -0
  5. data/.travis.yml +42 -0
  6. data/.yardopts +3 -0
  7. data/Dockerfile +9 -0
  8. data/Gemfile +7 -0
  9. data/HISTORY.md +1 -0
  10. data/LICENSE +14 -0
  11. data/README.md +189 -0
  12. data/Rakefile +27 -0
  13. data/gemfiles/Gemfile.base +29 -0
  14. data/gemfiles/Gemfile.rails-3.2.x +4 -0
  15. data/gemfiles/Gemfile.rails-4.0.x +4 -0
  16. data/gemfiles/Gemfile.rails-4.1.x +4 -0
  17. data/gemfiles/Gemfile.rails-4.2.x +5 -0
  18. data/gemfiles/Gemfile.rails-5.0.x +5 -0
  19. data/gemfiles/Gemfile.rails-HEAD +7 -0
  20. data/influx_reporter.gemspec +26 -0
  21. data/lib/influx_reporter.rb +142 -0
  22. data/lib/influx_reporter/client.rb +306 -0
  23. data/lib/influx_reporter/configuration.rb +88 -0
  24. data/lib/influx_reporter/data_builders.rb +18 -0
  25. data/lib/influx_reporter/data_builders/error.rb +52 -0
  26. data/lib/influx_reporter/data_builders/transactions.rb +120 -0
  27. data/lib/influx_reporter/error.rb +7 -0
  28. data/lib/influx_reporter/error_message.rb +85 -0
  29. data/lib/influx_reporter/error_message/exception.rb +14 -0
  30. data/lib/influx_reporter/error_message/http.rb +73 -0
  31. data/lib/influx_reporter/error_message/stacktrace.rb +76 -0
  32. data/lib/influx_reporter/error_message/user.rb +25 -0
  33. data/lib/influx_reporter/filter.rb +66 -0
  34. data/lib/influx_reporter/http_client.rb +140 -0
  35. data/lib/influx_reporter/influx_db_client.rb +74 -0
  36. data/lib/influx_reporter/injections.rb +89 -0
  37. data/lib/influx_reporter/injections/json.rb +21 -0
  38. data/lib/influx_reporter/injections/net_http.rb +50 -0
  39. data/lib/influx_reporter/injections/redis.rb +25 -0
  40. data/lib/influx_reporter/injections/sequel.rb +37 -0
  41. data/lib/influx_reporter/injections/sinatra.rb +59 -0
  42. data/lib/influx_reporter/integration/delayed_job.rb +30 -0
  43. data/lib/influx_reporter/integration/rails/inject_exceptions_catcher.rb +25 -0
  44. data/lib/influx_reporter/integration/railtie.rb +56 -0
  45. data/lib/influx_reporter/integration/resque.rb +18 -0
  46. data/lib/influx_reporter/integration/sidekiq.rb +130 -0
  47. data/lib/influx_reporter/line_cache.rb +21 -0
  48. data/lib/influx_reporter/logging.rb +39 -0
  49. data/lib/influx_reporter/middleware.rb +63 -0
  50. data/lib/influx_reporter/normalizers.rb +65 -0
  51. data/lib/influx_reporter/normalizers/action_controller.rb +34 -0
  52. data/lib/influx_reporter/normalizers/action_view.rb +72 -0
  53. data/lib/influx_reporter/normalizers/active_record.rb +45 -0
  54. data/lib/influx_reporter/sql_summarizer.rb +31 -0
  55. data/lib/influx_reporter/subscriber.rb +80 -0
  56. data/lib/influx_reporter/tasks.rb +28 -0
  57. data/lib/influx_reporter/trace.rb +48 -0
  58. data/lib/influx_reporter/trace_helpers.rb +31 -0
  59. data/lib/influx_reporter/transaction.rb +114 -0
  60. data/lib/influx_reporter/util.rb +18 -0
  61. data/lib/influx_reporter/util/constantize.rb +56 -0
  62. data/lib/influx_reporter/util/inspector.rb +72 -0
  63. data/lib/influx_reporter/util/timestamp.rb +11 -0
  64. data/lib/influx_reporter/version.rb +5 -0
  65. data/lib/influx_reporter/worker.rb +56 -0
  66. data/spec/influx_reporter/client_spec.rb +264 -0
  67. data/spec/influx_reporter/configuration_spec.rb +42 -0
  68. data/spec/influx_reporter/data_builders/error_spec.rb +40 -0
  69. data/spec/influx_reporter/data_builders/transactions_spec.rb +48 -0
  70. data/spec/influx_reporter/error_message/exception_spec.rb +22 -0
  71. data/spec/influx_reporter/error_message/http_spec.rb +55 -0
  72. data/spec/influx_reporter/error_message/stacktrace_spec.rb +56 -0
  73. data/spec/influx_reporter/error_message/user_spec.rb +26 -0
  74. data/spec/influx_reporter/error_message_spec.rb +102 -0
  75. data/spec/influx_reporter/filter_spec.rb +33 -0
  76. data/spec/influx_reporter/injections/net_http_spec.rb +35 -0
  77. data/spec/influx_reporter/injections/sequel_spec.rb +33 -0
  78. data/spec/influx_reporter/injections/sinatra_spec.rb +13 -0
  79. data/spec/influx_reporter/injections_spec.rb +50 -0
  80. data/spec/influx_reporter/integration/delayed_job_spec.rb +37 -0
  81. data/spec/influx_reporter/integration/json_spec.rb +41 -0
  82. data/spec/influx_reporter/integration/rails_spec.rb +92 -0
  83. data/spec/influx_reporter/integration/redis_spec.rb +20 -0
  84. data/spec/influx_reporter/integration/resque_spec.rb +42 -0
  85. data/spec/influx_reporter/integration/sidekiq_spec.rb +40 -0
  86. data/spec/influx_reporter/integration/sinatra_spec.rb +99 -0
  87. data/spec/influx_reporter/line_cache_spec.rb +38 -0
  88. data/spec/influx_reporter/logging_spec.rb +49 -0
  89. data/spec/influx_reporter/middleware_spec.rb +32 -0
  90. data/spec/influx_reporter/normalizers/action_controller_spec.rb +37 -0
  91. data/spec/influx_reporter/normalizers/action_view_spec.rb +78 -0
  92. data/spec/influx_reporter/normalizers/active_record_spec.rb +70 -0
  93. data/spec/influx_reporter/normalizers_spec.rb +16 -0
  94. data/spec/influx_reporter/sql_summarizer_spec.rb +35 -0
  95. data/spec/influx_reporter/subscriber_spec.rb +83 -0
  96. data/spec/influx_reporter/trace_spec.rb +43 -0
  97. data/spec/influx_reporter/transaction_spec.rb +98 -0
  98. data/spec/influx_reporter/util/inspector_spec.rb +41 -0
  99. data/spec/influx_reporter/util_spec.rb +20 -0
  100. data/spec/influx_reporter/worker_spec.rb +54 -0
  101. data/spec/influx_reporter_spec.rb +50 -0
  102. data/spec/spec_helper.rb +86 -0
  103. metadata +188 -0
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :influx_reporter do
4
+ desc 'Notify InfluxReporter of a release'
5
+ task release: :environment do
6
+ unless rev = ENV['REV']
7
+ puts "Please specify a revision in an env variable\n" \
8
+ 'eg. REV=abc123 rake influx_reporter:release'
9
+ exit 1
10
+ end
11
+
12
+ # empty env means dev
13
+ ENV['RAILS_ENV'] ||= 'development'
14
+
15
+ # log to STDOUT
16
+ InfluxReporter::Client.inst.config.logger = Logger.new STDOUT
17
+
18
+ unless InfluxReporter.release({
19
+ rev: rev,
20
+ branch: ENV['BRANCH'],
21
+ status: 'completed'
22
+ }, inline: true)
23
+ exit 1 # release returned nil
24
+ end
25
+ end
26
+
27
+ task deployment: :release
28
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'influx_reporter/util'
4
+
5
+ module InfluxReporter
6
+ class Trace
7
+ DEFAULT_KIND = 'code.custom'
8
+
9
+ def initialize(transaction, signature, kind = nil, parents = [], extra = nil)
10
+ @transaction = transaction
11
+ @signature = signature
12
+ @kind = kind || DEFAULT_KIND
13
+ @parents = parents || []
14
+ @extra = extra || {}
15
+
16
+ @timestamp = Util.nanos
17
+ end
18
+
19
+ attr_accessor :signature, :kind, :parents, :extra
20
+ attr_reader :transaction, :timestamp, :duration, :relative_start, :start_time
21
+
22
+ def start(relative_to)
23
+ @start_time = Util.nanos
24
+ @relative_start = start_time - relative_to
25
+
26
+ self
27
+ end
28
+
29
+ def done(ms = Util.nanos)
30
+ @duration = ms - start_time
31
+
32
+ self
33
+ end
34
+
35
+ def done?
36
+ !!duration
37
+ end
38
+
39
+ def running?
40
+ !done?
41
+ end
42
+
43
+ def inspect
44
+ info = %w[signature kind parents extra timestamp duration relative_start]
45
+ "<Trace #{info.map { |m| "#{m}:#{send(m).inspect}" }.join(' ')}>"
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InfluxReporter
4
+ module TraceHelpers
5
+ module ClassMethods
6
+ def trace_class_method(method, signature, kind)
7
+ __trace_method_on(singleton_class, method, signature, kind)
8
+ end
9
+
10
+ private
11
+
12
+ def __trace_method_on(klass, method, signature, kind)
13
+ klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
14
+ alias :"__without_opb_#{method}" :"#{method}"
15
+
16
+ def #{method}(*args, &block)
17
+ InfluxReporter.trace "#{signature}", "#{kind}" do
18
+ __without_opb_#{method}(*args, &block)
19
+ end
20
+ end
21
+ RUBY
22
+ end
23
+ end
24
+
25
+ def self.included(kls)
26
+ kls.class_eval do
27
+ extend ClassMethods
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'influx_reporter/util'
4
+
5
+ module InfluxReporter
6
+ class Transaction
7
+ ROOT_TRACE_NAME = 'transaction'
8
+
9
+ # @param client [InfluxReporter::Client]
10
+ # @param endpoint [String]
11
+ # @param kind [String]
12
+ # @param result [Integer]
13
+ def initialize(client, endpoint, kind = 'code.custom', result = nil)
14
+ @client = client
15
+ @config = client.config if client.respond_to?(:config)
16
+ @endpoint = endpoint
17
+ @kind = kind
18
+ @result = result
19
+
20
+ @timestamp = Util.nanos
21
+
22
+ @root_trace = Trace.new(self, ROOT_TRACE_NAME, ROOT_TRACE_NAME)
23
+ @traces = [@root_trace]
24
+ @notifications = []
25
+
26
+ @start_time = Util.nanos
27
+ @root_trace.start @start_time
28
+ end
29
+
30
+ attr_accessor :endpoint, :kind, :result, :duration
31
+ attr_reader :timestamp, :start_time, :traces, :notifications, :root_trace, :config
32
+
33
+ def release
34
+ @client.current_transaction = nil
35
+ end
36
+
37
+ def done(result = nil)
38
+ @result = result
39
+
40
+ @root_trace.done Util.nanos
41
+ @duration = @root_trace.duration
42
+
43
+ self
44
+ end
45
+
46
+ def done?
47
+ @root_trace.done?
48
+ end
49
+
50
+ def submit(result = nil)
51
+ done result
52
+
53
+ release
54
+
55
+ @client.submit_transaction self
56
+
57
+ self
58
+ end
59
+
60
+ def trace(signature, kind = nil, extra = nil)
61
+ trace = Trace.new(self, signature, kind, running_traces, extra)
62
+
63
+ rel_time = current_offset
64
+
65
+ traces << trace
66
+
67
+ trace.start rel_time
68
+
69
+ return trace unless block_given?
70
+
71
+ begin
72
+ result = yield trace
73
+ ensure
74
+ trace.done
75
+ end
76
+
77
+ result
78
+ end
79
+
80
+ def extra_tags
81
+ @root_trace.extra[:tags] ||= {}
82
+ yield @root_trace.extra[:tags]
83
+ end
84
+
85
+ def extra_values
86
+ @root_trace.extra[:values] ||= {}
87
+ yield @root_trace.extra[:values]
88
+ end
89
+
90
+ def running_traces
91
+ traces.select(&:running?)
92
+ end
93
+
94
+ def current_trace
95
+ traces.reverse.find(&:running?)
96
+ end
97
+
98
+ def current_offset
99
+ if curr = current_trace
100
+ return curr.start_time
101
+ end
102
+
103
+ start_time
104
+ end
105
+
106
+ def inspect
107
+ info = %w[endpoint kind result duration timestamp start_time]
108
+ <<~TEXT
109
+ <Transaction #{info.map { |m| "#{m}:#{send(m).inspect}" }.join(' ')}>
110
+ #{traces.map(&:inspect).join("\n ")}"
111
+ TEXT
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InfluxReporter
4
+ # @api private
5
+ module Util
6
+ def self.nearest_minute
7
+ now = Time.now.utc
8
+ now - now.to_i % 60
9
+ end
10
+
11
+ def self.nanos
12
+ now = Time.now.utc
13
+ now.to_i * 1_000_000_000 + now.nsec
14
+ end
15
+ end
16
+
17
+ require 'influx_reporter/util/inspector'
18
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InfluxReporter
4
+ module Util
5
+ # From https://github.com/rails/rails/blob/861b70e92f4a1fc0e465ffcf2ee62680519c8f6f/activesupport/lib/active_support/inflector/methods.rb#L249
6
+ #
7
+ # Tries to find a constant with the name specified in the argument string.
8
+ #
9
+ # 'Module'.constantize # => Module
10
+ # 'Test::Unit'.constantize # => Test::Unit
11
+ #
12
+ # The name is assumed to be the one of a top-level constant, no matter
13
+ # whether it starts with "::" or not. No lexical context is taken into
14
+ # account:
15
+ #
16
+ # C = 'outside'
17
+ # module M
18
+ # C = 'inside'
19
+ # C # => 'inside'
20
+ # 'C'.constantize # => 'outside', same as ::C
21
+ # end
22
+ #
23
+ # NameError is raised when the name is not in CamelCase or the constant is
24
+ # unknown.
25
+ def self.constantize(camel_cased_word)
26
+ names = camel_cased_word.split('::')
27
+
28
+ # Trigger a built-in NameError exception including the ill-formed constant in the message.
29
+ Object.const_get(camel_cased_word) if names.empty?
30
+
31
+ # Remove the first blank element in case of '::ClassName' notation.
32
+ names.shift if names.size > 1 && names.first.empty?
33
+
34
+ names.inject(Object) do |constant, name|
35
+ if constant == Object
36
+ constant.const_get(name)
37
+ else
38
+ candidate = constant.const_get(name)
39
+ next candidate if constant.const_defined?(name, false)
40
+ next candidate unless Object.const_defined?(name)
41
+
42
+ # Go down the ancestors to check if it is owned directly. The check
43
+ # stops when we reach Object or the end of ancestors tree.
44
+ constant = constant.ancestors.inject do |const, ancestor|
45
+ break const if ancestor == Object
46
+ break ancestor if ancestor.const_defined?(name, false)
47
+ const
48
+ end
49
+
50
+ # owner is in Object, so raise
51
+ constant.const_get(name, false)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InfluxReporter
4
+ module Util
5
+ class Inspector
6
+ DEFAULTS = {
7
+ width: 120
8
+ }.freeze
9
+
10
+ NEWLINE = "\n"
11
+ SPACE = ' '
12
+
13
+ def initialize(config = {})
14
+ @config = DEFAULTS.merge(config)
15
+ end
16
+
17
+ def ms(nanos)
18
+ nanos.to_f / 1_000_000
19
+ end
20
+
21
+ def transaction(transaction, opts = {})
22
+ w = @config[:width].to_f
23
+ f = w / ms(transaction.duration)
24
+
25
+ traces = transaction.traces
26
+
27
+ traces = traces.each_with_object([]) do |trace, state|
28
+ descriptions = [
29
+ "#{trace.signature} - #{trace.kind}",
30
+ "transaction:#{trace.transaction.endpoint}"
31
+ ]
32
+
33
+ if opts[:include_parents]
34
+ descriptions << "parents:#{trace.parents.map(&:signature).join(',')}"
35
+ end
36
+
37
+ descriptions << "duration:#{ms trace.duration}ms - rel:#{ms trace.relative_start}ms"
38
+
39
+ start_diff = ms(trace.start_time) - ms(transaction.start_time)
40
+ indent = (start_diff.floor * f).to_i
41
+
42
+ longest_desc_length = descriptions.map(&:length).max
43
+ desc_indent = [[indent, w - longest_desc_length].min, 0].max
44
+
45
+ lines = descriptions.map do |desc|
46
+ "#{SPACE * desc_indent}#{desc}"
47
+ end
48
+
49
+ if trace.duration
50
+ span = (ms(trace.duration) * f).ceil.to_i
51
+ lines << "#{SPACE * indent}+#{'-' * [(span - 2), 0].max}+"
52
+ else
53
+ lines << "#{SPACE * indent}UNFINISHED"
54
+ end
55
+
56
+ state << lines.join("\n")
57
+ end.join("\n")
58
+
59
+ <<-STR.gsub(/^\s{8}/, '')
60
+ \n#{'=' * w.to_i}
61
+ #{transaction.endpoint} - kind:#{transaction.kind} - #{transaction.duration.to_f / 1_000_000}ms
62
+ +#{'-' * (w.to_i - 2)}+
63
+ #{traces}
64
+ STR
65
+ rescue => e
66
+ puts e
67
+ puts e.backtrace.join("\n")
68
+ transaction.inspect
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InfluxReporter
4
+ module Util
5
+ module Timestamp
6
+ def formatted_timestamp(t = Time.now)
7
+ format('%d%09d', t.to_i, t.nsec).to_i
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InfluxReporter
4
+ VERSION = '1.0.0'
5
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InfluxReporter
4
+ # @api private
5
+ class Worker
6
+ include Logging
7
+
8
+ class PostRequest < Struct.new(:path, :data)
9
+ # require all parameters
10
+ def initialize(path, data)
11
+ super(path, data)
12
+ end
13
+ end
14
+
15
+ class StopMessage; end
16
+
17
+ def initialize(config, queue, influx_client)
18
+ @config = config
19
+ @queue = queue
20
+ @influx_client = influx_client
21
+ end
22
+
23
+ attr_reader :config
24
+
25
+ def run
26
+ loop do
27
+ while action = @queue.pop
28
+ case action
29
+ when PostRequest
30
+ process_request action
31
+ when StopMessage
32
+ Thread.exit
33
+ else
34
+ raise Error, "Unknown entity in worker queue: #{action.inspect}"
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def process_request(req)
43
+ unless config.validate!
44
+ info 'Invalid config - Skipping posting to influxdb'
45
+ return
46
+ end
47
+
48
+ begin
49
+ @influx_client.post(req.path, req.data)
50
+ rescue => e
51
+ fatal "Failed POST: #{e.inspect}"
52
+ debug e.backtrace.join("\n")
53
+ end
54
+ end
55
+ end
56
+ end