influxdb-rails 1.0.0.beta1 → 1.0.1.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +11 -0
  3. data/.gitignore +2 -0
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +7 -1
  6. data/.travis.yml +9 -10
  7. data/CHANGELOG.md +48 -11
  8. data/Gemfile +6 -0
  9. data/README.md +278 -100
  10. data/Rakefile +17 -18
  11. data/gemfiles/Gemfile.rails-5.0.x +2 -0
  12. data/gemfiles/Gemfile.rails-6.0.x +10 -0
  13. data/influxdb-rails.gemspec +20 -10
  14. data/lib/influxdb-rails.rb +35 -118
  15. data/lib/influxdb/rails/configuration.rb +99 -179
  16. data/lib/influxdb/rails/context.rb +26 -0
  17. data/lib/influxdb/rails/helpers/rspec_matchers.rb +48 -0
  18. data/lib/influxdb/rails/metric.rb +39 -0
  19. data/lib/influxdb/rails/middleware/active_job_subscriber.rb +67 -0
  20. data/lib/influxdb/rails/middleware/active_record_subscriber.rb +26 -0
  21. data/lib/influxdb/rails/middleware/block_instrumentation_subscriber.rb +24 -0
  22. data/lib/influxdb/rails/middleware/render_subscriber.rb +32 -0
  23. data/lib/influxdb/rails/middleware/request_subscriber.rb +44 -0
  24. data/lib/influxdb/rails/middleware/sql_subscriber.rb +37 -0
  25. data/lib/influxdb/rails/middleware/subscriber.rb +68 -0
  26. data/lib/influxdb/rails/railtie.rb +28 -25
  27. data/lib/influxdb/rails/sql/normalizer.rb +27 -0
  28. data/lib/influxdb/rails/sql/query.rb +32 -0
  29. data/lib/influxdb/rails/tags.rb +33 -0
  30. data/lib/influxdb/rails/test_client.rb +13 -0
  31. data/lib/influxdb/rails/values.rb +24 -0
  32. data/lib/influxdb/rails/version.rb +1 -1
  33. data/lib/rails/generators/influxdb/influxdb_generator.rb +1 -1
  34. data/lib/rails/generators/influxdb/templates/initializer.rb +39 -9
  35. data/sample-dashboard/Dockerfile +24 -0
  36. data/sample-dashboard/README.md +74 -0
  37. data/sample-dashboard/Rakefile +9 -0
  38. data/sample-dashboard/Ruby On Rails Performance (per Action).json +1576 -0
  39. data/sample-dashboard/Ruby On Rails Performance (per Request).json +1053 -0
  40. data/sample-dashboard/Ruby On Rails Performance.json +2041 -0
  41. data/sample-dashboard/docker-compose.yml +34 -0
  42. data/sample-dashboard/provisioning/grafana-dashboards.yml +12 -0
  43. data/sample-dashboard/provisioning/grafana-datasource.yml +10 -0
  44. data/sample-dashboard/provisioning/performance-action.json +1576 -0
  45. data/sample-dashboard/provisioning/performance-request.json +1053 -0
  46. data/sample-dashboard/provisioning/performance.json +2041 -0
  47. data/spec/requests/action_controller_metrics_spec.rb +83 -0
  48. data/spec/requests/action_view_collection_metrics_spec.rb +66 -0
  49. data/spec/requests/action_view_partial_metrics_spec.rb +62 -0
  50. data/spec/requests/action_view_template_metrics_spec.rb +62 -0
  51. data/spec/requests/active_job_enqueue_metrics_spec.rb +65 -0
  52. data/spec/requests/active_job_perform_metrics_spec.rb +68 -0
  53. data/spec/requests/active_job_perform_start_metrics_spec.rb +68 -0
  54. data/spec/requests/active_record_instantiation_metrics_spec.rb +65 -0
  55. data/spec/requests/active_record_sql_metrics_spec.rb +103 -0
  56. data/spec/requests/block_inistrumentation_spec.rb +64 -0
  57. data/spec/requests/context_spec.rb +27 -0
  58. data/spec/requests/logger_spec.rb +10 -0
  59. data/spec/spec_helper.rb +12 -4
  60. data/spec/support/broken_client.rb +11 -0
  61. data/spec/support/rails5/app.rb +44 -5
  62. data/spec/support/rails6/app.rb +70 -0
  63. data/spec/support/views/metrics/_item.html.erb +1 -0
  64. data/spec/support/views/metrics/index.html.erb +5 -0
  65. data/spec/support/views/metrics/show.html.erb +4 -0
  66. data/spec/unit/block_instrumentation_spec.rb +18 -0
  67. data/spec/unit/configuration_spec.rb +64 -65
  68. data/spec/unit/sql/normalizer_spec.rb +15 -0
  69. data/spec/unit/sql/query_spec.rb +29 -0
  70. metadata +167 -44
  71. data/gemfiles/Gemfile.rails-4.2.x +0 -7
  72. data/lib/influxdb/rails/air_traffic_controller.rb +0 -41
  73. data/lib/influxdb/rails/backtrace.rb +0 -44
  74. data/lib/influxdb/rails/exception_presenter.rb +0 -94
  75. data/lib/influxdb/rails/instrumentation.rb +0 -34
  76. data/lib/influxdb/rails/logger.rb +0 -16
  77. data/lib/influxdb/rails/middleware/hijack_render_exception.rb +0 -16
  78. data/lib/influxdb/rails/middleware/hijack_rescue_action_everywhere.rb +0 -31
  79. data/lib/influxdb/rails/rack.rb +0 -24
  80. data/spec/controllers/widgets_controller_spec.rb +0 -15
  81. data/spec/integration/exceptions_spec.rb +0 -37
  82. data/spec/integration/integration_helper.rb +0 -1
  83. data/spec/integration/metrics_spec.rb +0 -21
  84. data/spec/support/rails4/app.rb +0 -30
  85. data/spec/unit/backtrace_spec.rb +0 -85
  86. data/spec/unit/exception_presenter_spec.rb +0 -23
  87. data/spec/unit/influxdb_rails_spec.rb +0 -164
@@ -0,0 +1,26 @@
1
+ module InfluxDB
2
+ module Rails
3
+ class Context
4
+ def reset
5
+ Thread.current[:_influxdb_rails_tags] = {}
6
+ Thread.current[:_influxdb_rails_values] = {}
7
+ end
8
+
9
+ def tags
10
+ Thread.current[:_influxdb_rails_tags].to_h
11
+ end
12
+
13
+ def tags=(tags)
14
+ Thread.current[:_influxdb_rails_tags] = self.tags.merge(tags)
15
+ end
16
+
17
+ def values
18
+ Thread.current[:_influxdb_rails_values].to_h
19
+ end
20
+
21
+ def values=(values)
22
+ Thread.current[:_influxdb_rails_values] = self.values.merge(values)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,48 @@
1
+ require_relative "../test_client"
2
+ require "launchy"
3
+
4
+ module InfluxDB
5
+ module Rails
6
+ module Matchers
7
+ def expect_metric(name: "rails", **options)
8
+ expect(metrics).to include(
9
+ a_hash_including(options.merge(name: name))
10
+ )
11
+ end
12
+
13
+ def expect_no_metric(name: "rails", **options)
14
+ expect(metrics).not_to include(
15
+ a_hash_including(options.merge(name: name))
16
+ )
17
+ end
18
+
19
+ def save_and_open_metrics
20
+ dir = File.join(File.dirname(__FILE__), "..", "..", "tmp")
21
+ FileUtils.mkdir_p(dir)
22
+ file_path = File.join(dir, "metrics.json")
23
+ output = JSON.pretty_generate(metrics)
24
+ File.write(file_path, output, mode: "wb")
25
+ ::Launchy.open(file_path)
26
+ end
27
+
28
+ def metrics
29
+ TestClient.metrics
30
+ end
31
+
32
+ RSpec.configure do |config|
33
+ config.before :each do
34
+ InfluxDB::Rails.instance_variable_set :@configuration, nil
35
+ InfluxDB::Rails.configure
36
+
37
+ allow(InfluxDB::Rails).to receive(:client).and_return(InfluxDB::Rails::TestClient.new)
38
+ allow_any_instance_of(InfluxDB::Rails::Configuration)
39
+ .to receive(:ignored_environments).and_return(%w[development])
40
+
41
+ InfluxDB::Rails::TestClient.metrics.clear
42
+ end
43
+
44
+ config.include InfluxDB::Rails::Matchers
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,39 @@
1
+ require "influxdb/rails/values"
2
+ require "influxdb/rails/tags"
3
+
4
+ module InfluxDB
5
+ module Rails
6
+ class Metric
7
+ def initialize(configuration:, timestamp:, tags: {}, values: {})
8
+ @configuration = configuration
9
+ @timestamp = timestamp
10
+ @tags = tags
11
+ @values = values
12
+ end
13
+
14
+ def write
15
+ client.write_point configuration.measurement_name, options
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :configuration, :tags, :values, :timestamp
21
+
22
+ def options
23
+ {
24
+ values: Values.new(values: values).to_h,
25
+ tags: Tags.new(tags: tags, config: configuration).to_h,
26
+ timestamp: timestamp_with_precision,
27
+ }
28
+ end
29
+
30
+ def timestamp_with_precision
31
+ InfluxDB.convert_timestamp(timestamp.utc, configuration.client.time_precision)
32
+ end
33
+
34
+ def client
35
+ InfluxDB::Rails.client
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,67 @@
1
+ require "influxdb/rails/middleware/subscriber"
2
+
3
+ module InfluxDB
4
+ module Rails
5
+ module Middleware
6
+ class ActiveJobSubscriber < Subscriber # :nodoc:
7
+ private
8
+
9
+ def values
10
+ {
11
+ value: value,
12
+ }
13
+ end
14
+
15
+ def tags
16
+ {
17
+ hook: short_hook_name,
18
+ state: job_state,
19
+ job: job.class.name,
20
+ queue: job.queue_name,
21
+ }
22
+ end
23
+
24
+ def job_state
25
+ return "failed" if failed?
26
+
27
+ case short_hook_name
28
+ when "enqueue"
29
+ "queued"
30
+ when "perform_start"
31
+ "running"
32
+ when "perform"
33
+ "succeeded"
34
+ end
35
+ end
36
+
37
+ def measure_performance?
38
+ short_hook_name == "perform"
39
+ end
40
+
41
+ def short_hook_name
42
+ @short_hook_name ||= fetch_short_hook_name
43
+ end
44
+
45
+ def fetch_short_hook_name
46
+ return "enqueue" if hook_name.include?("enqueue")
47
+ return "perform_start" if hook_name.include?("perform_start")
48
+ return "perform" if hook_name.include?("perform")
49
+ end
50
+
51
+ def job
52
+ @job ||= payload[:job]
53
+ end
54
+
55
+ def value
56
+ return duration if measure_performance?
57
+
58
+ 1
59
+ end
60
+
61
+ def failed?
62
+ payload[:exception_object]
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,26 @@
1
+ require "influxdb/rails/middleware/subscriber"
2
+ require "influxdb/rails/sql/query"
3
+
4
+ module InfluxDB
5
+ module Rails
6
+ module Middleware
7
+ class ActiveRecordSubscriber < Subscriber # :nodoc:
8
+ private
9
+
10
+ def values
11
+ {
12
+ value: duration,
13
+ record_count: payload[:record_count],
14
+ }
15
+ end
16
+
17
+ def tags
18
+ {
19
+ hook: "instantiation",
20
+ class_name: payload[:class_name],
21
+ }
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,24 @@
1
+ require "influxdb/rails/middleware/subscriber"
2
+
3
+ module InfluxDB
4
+ module Rails
5
+ module Middleware
6
+ class BlockInstrumentationSubscriber < Subscriber
7
+ private
8
+
9
+ def values
10
+ {
11
+ value: duration,
12
+ }.merge(payload[:values].to_h)
13
+ end
14
+
15
+ def tags
16
+ {
17
+ hook: "block_instrumentation",
18
+ name: payload[:name],
19
+ }.merge(payload[:tags].to_h)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,32 @@
1
+ require "influxdb/rails/middleware/subscriber"
2
+
3
+ module InfluxDB
4
+ module Rails
5
+ module Middleware
6
+ class RenderSubscriber < Subscriber # :nodoc:
7
+ private
8
+
9
+ def values
10
+ {
11
+ value: duration,
12
+ count: payload[:count],
13
+ cache_hits: payload[:cache_hits],
14
+ }
15
+ end
16
+
17
+ def tags
18
+ {
19
+ hook: short_hook_name,
20
+ filename: payload[:identifier],
21
+ }
22
+ end
23
+
24
+ def short_hook_name
25
+ return "render_template" if hook_name.include?("render_template")
26
+ return "render_partial" if hook_name.include?("render_partial")
27
+ return "render_collection" if hook_name.include?("render_collection")
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,44 @@
1
+ require "influxdb/rails/middleware/subscriber"
2
+
3
+ module InfluxDB
4
+ module Rails
5
+ module Middleware
6
+ class RequestSubscriber < Subscriber # :nodoc:
7
+ def write
8
+ super
9
+ ensure
10
+ InfluxDB::Rails.current.reset
11
+ end
12
+
13
+ private
14
+
15
+ def tags
16
+ {
17
+ method: "#{payload[:controller]}##{payload[:action]}",
18
+ hook: "process_action",
19
+ status: payload[:status],
20
+ format: payload[:format],
21
+ http_method: payload[:method],
22
+ exception: payload[:exception]&.first,
23
+ }
24
+ end
25
+
26
+ def values
27
+ {
28
+ controller: duration,
29
+ view: (payload[:view_runtime] || 0).ceil,
30
+ db: (payload[:db_runtime] || 0).ceil,
31
+ started: started,
32
+ }
33
+ end
34
+
35
+ def started
36
+ InfluxDB.convert_timestamp(
37
+ start.utc,
38
+ configuration.client.time_precision
39
+ )
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,37 @@
1
+ require "influxdb/rails/middleware/subscriber"
2
+ require "influxdb/rails/sql/query"
3
+
4
+ module InfluxDB
5
+ module Rails
6
+ module Middleware
7
+ class SqlSubscriber < Subscriber # :nodoc:
8
+ private
9
+
10
+ def values
11
+ {
12
+ value: duration,
13
+ sql: InfluxDB::Rails::Sql::Normalizer.new(payload[:sql]).perform,
14
+ }
15
+ end
16
+
17
+ def tags
18
+ {
19
+ hook: "sql",
20
+ operation: query.operation,
21
+ class_name: query.class_name,
22
+ name: query.name,
23
+ location: :raw,
24
+ }
25
+ end
26
+
27
+ def disabled?
28
+ super || !query.track?
29
+ end
30
+
31
+ def query
32
+ @query ||= InfluxDB::Rails::Sql::Query.new(payload)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,68 @@
1
+ require "influxdb/rails/metric"
2
+
3
+ module InfluxDB
4
+ module Rails
5
+ module Middleware
6
+ # Subscriber acts as base class for different *Subscriber classes,
7
+ # which are intended as ActiveSupport::Notifications.subscribe
8
+ # consumers.
9
+ class Subscriber
10
+ def initialize(configuration:, hook_name:, start:, finish:, payload:)
11
+ @configuration = configuration
12
+ @hook_name = hook_name
13
+ @start = start
14
+ @finish = finish
15
+ @payload = payload
16
+ end
17
+
18
+ def self.call(name, start, finish, _id, payload)
19
+ new(
20
+ configuration: InfluxDB::Rails.configuration,
21
+ start: start,
22
+ finish: finish,
23
+ payload: payload,
24
+ hook_name: name
25
+ ).write
26
+ end
27
+
28
+ def write
29
+ return if disabled?
30
+
31
+ metric.write
32
+ rescue StandardError => e
33
+ ::Rails.logger.error("[InfluxDB::Rails] Unable to write points: #{e.message}")
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :configuration, :hook_name, :start, :finish, :payload
39
+
40
+ def metric
41
+ InfluxDB::Rails::Metric.new(
42
+ values: values,
43
+ tags: tags,
44
+ configuration: configuration,
45
+ timestamp: finish
46
+ )
47
+ end
48
+
49
+ def tags
50
+ raise NotImplementedError, "must be implemented in subclass"
51
+ end
52
+
53
+ def values
54
+ raise NotImplementedError, "must be implemented in subclass"
55
+ end
56
+
57
+ def duration
58
+ ((finish - start) * 1000).ceil
59
+ end
60
+
61
+ def disabled?
62
+ configuration.ignore_current_environment? ||
63
+ configuration.ignored_hooks.include?(hook_name)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -4,39 +4,42 @@ require "rails"
4
4
  module InfluxDB
5
5
  module Rails
6
6
  class Railtie < ::Rails::Railtie # :nodoc:
7
- initializer "influxdb.insert_rack_middleware" do |app|
8
- app.config.middleware.insert 0, InfluxDB::Rails::Rack
9
- end
10
-
7
+ # rubocop:disable Metrics/BlockLength
11
8
  config.after_initialize do
12
- InfluxDB::Rails.configure(true, &:load_rails_defaults)
9
+ InfluxDB::Rails.configure do |config|
10
+ config.environment ||= ::Rails.env
11
+ end
13
12
 
14
13
  ActiveSupport.on_load(:action_controller) do
15
- require "influxdb/rails/air_traffic_controller"
16
- include InfluxDB::Rails::AirTrafficController
17
- require "influxdb/rails/instrumentation"
18
- include InfluxDB::Rails::Instrumentation
14
+ before_action do
15
+ current = InfluxDB::Rails.current
16
+ current.values = { request_id: request.request_id } if request.respond_to?(:request_id)
17
+ end
19
18
  end
20
19
 
21
- require "influxdb/rails/middleware/hijack_render_exception"
22
- ::ActionDispatch::DebugExceptions.prepend InfluxDB::Rails::Middleware::HijackRenderException
23
-
24
- if defined?(ActiveSupport::Notifications)
25
- listen = lambda do |name, start, finish, id, payload|
26
- c = InfluxDB::Rails.configuration
27
-
28
- if c.instrumentation_enabled? && !c.ignore_current_environment?
29
- begin
30
- InfluxDB::Rails.handle_action_controller_metrics(name, start, finish, id, payload)
31
- rescue StandardError => e
32
- c.logger.error "[InfluxDB::Rails] Failed writing points to InfluxDB: #{e.message}"
33
- end
34
- end
35
- end
20
+ cache = lambda do |_, _, _, _, payload|
21
+ current = InfluxDB::Rails.current
22
+ location = [payload[:controller], payload[:action]].join("#")
23
+ current.tags = { location: location }
24
+ end
25
+ ActiveSupport::Notifications.subscribe "start_processing.action_controller", &cache
36
26
 
37
- ActiveSupport::Notifications.subscribe "process_action.action_controller", &listen
27
+ {
28
+ "process_action.action_controller" => Middleware::RequestSubscriber,
29
+ "render_template.action_view" => Middleware::RenderSubscriber,
30
+ "render_partial.action_view" => Middleware::RenderSubscriber,
31
+ "render_collection.action_view" => Middleware::RenderSubscriber,
32
+ "sql.active_record" => Middleware::SqlSubscriber,
33
+ "instantiation.active_record" => Middleware::ActiveRecordSubscriber,
34
+ "enqueue.active_job" => Middleware::ActiveJobSubscriber,
35
+ "perform_start.active_job" => Middleware::ActiveJobSubscriber,
36
+ "perform.active_job" => Middleware::ActiveJobSubscriber,
37
+ "block_instrumentation.influxdb_rails" => Middleware::BlockInstrumentationSubscriber,
38
+ }.each do |hook_name, subscriber|
39
+ ActiveSupport::Notifications.subscribe(hook_name, subscriber)
38
40
  end
39
41
  end
42
+ # rubocop:enable Metrics/BlockLength
40
43
  end
41
44
  end
42
45
  end