optic-rails 1.0.0 → 1.0.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9768eeeb47c2c8c2eef8cdf95244e88644f2ee9455e8425ff20f184e9caab8ea
4
- data.tar.gz: edb291080a2cd6d69fca3cc7ce665dd7035a5a521610ccd93b7820fb5c1796c8
3
+ metadata.gz: 4862c212205f52f736e51c5b74da8d0bd141d941fe27adb6b872efa715a59aac
4
+ data.tar.gz: b77ac04f9ae6cccc80168fc5a77ef27c249c75a6767859611022e7aac941d1ef
5
5
  SHA512:
6
- metadata.gz: 24f8ed8c4713484642f57a814b9e3a4a2855e9094c10077a5d098f684a86109f0428af77cedb0ef76779b1a47b7dc07722d43846ffb5973c01d43a7648d3313a
7
- data.tar.gz: aff41fee8f75733a7498c5c112799589bac2e7fd9b6c9b661ef51f75a3fa1e729d4cfafa8773456569f301a88f19f8beb625b33c58bad25ce4dc0f5941c28a3c
6
+ metadata.gz: 93a490e91f071a3c1089b4072a6ef273f291115d27c828d5e73593329e6ca924833a64991fca2ad0a6534bc52dcef2c357bca92bee4c53b0dbd9959967451a1a
7
+ data.tar.gz: 5298316a14f2c3668c7a1f2968ee7d0ca5df05a797b8ea75d32f165d54672de3e5441233c327171f5d86b627fb6152e1615f45b3fc7cdc56e5a217de4448e0c1
data/Rakefile CHANGED
@@ -1,26 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  begin
2
- require 'bundler/setup'
4
+ require "bundler/setup"
3
5
  rescue LoadError
4
- puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ puts "You must `gem install bundler` and `bundle install` to run rake tasks"
5
7
  end
6
8
 
7
- require 'rdoc/task'
9
+ require "rdoc/task"
8
10
 
9
11
  RDoc::Task.new(:rdoc) do |rdoc|
10
- rdoc.rdoc_dir = 'rdoc'
11
- rdoc.title = 'Optic::Rails'
12
- rdoc.options << '--line-numbers'
13
- rdoc.rdoc_files.include('README.md')
14
- rdoc.rdoc_files.include('lib/**/*.rb')
12
+ rdoc.rdoc_dir = "rdoc"
13
+ rdoc.title = "Optic::Rails"
14
+ rdoc.options << "--line-numbers"
15
+ rdoc.rdoc_files.include("README.md")
16
+ rdoc.rdoc_files.include("lib/**/*.rb")
15
17
  end
16
18
 
17
- require 'bundler/gem_tasks'
19
+ require "bundler/gem_tasks"
18
20
 
19
- require 'rake/testtask'
21
+ require "rake/testtask"
20
22
 
21
23
  Rake::TestTask.new(:test) do |t|
22
- t.libs << 'test'
23
- t.pattern = 'test/**/*_test.rb'
24
+ t.libs << "test"
25
+ t.pattern = "test/**/*_test.rb"
24
26
  t.verbose = false
25
27
  end
26
28
 
data/lib/optic/rails.rb CHANGED
@@ -1,150 +1,65 @@
1
- require "optic/rails/railtie"
1
+ # frozen_string_literal: true
2
2
 
3
- require "rgl/adjacency"
4
- require "rgl/dot"
5
- require "rgl/dijkstra"
3
+ require "optic/rails/railtie"
6
4
 
7
5
  module Optic
8
6
  module Rails
9
- # From https://gist.github.com/hongo35/7513104
10
- class PageRank
11
- EPS = 0.00001
12
-
13
- def initialize(matrix)
14
- @dim = matrix.size
15
-
16
- @p = []
17
- @dim.times do |i|
18
- @p[i] = []
19
- @dim.times do |j|
20
- total = matrix[i].inject(:+)
21
- @p[i][j] = total == 0 ? 0 : matrix[i][j] / (total * 1.0)
22
- end
23
- end
24
- end
25
-
26
- def calc(curr, alpha)
27
- loop do
28
- prev = curr.clone
29
-
30
- @dim.times do |i|
31
- ip = 0
32
- @dim.times do |j|
33
- ip += @p.transpose[i][j] * prev[j]
7
+ class << self
8
+ def entities
9
+ with_connection do
10
+ {
11
+ schema_version: ActiveRecord::Migrator.current_version,
12
+ entities: active_record_klasses.map do |klass|
13
+ {
14
+ name: klass.name,
15
+ table_name: klass.table_name,
16
+ attribute_names: klass.attribute_names,
17
+ table_exists: klass.table_exists?,
18
+ associations: klass.reflect_on_all_associations.map do |reflection|
19
+ {
20
+ name: reflection.name,
21
+ macro: reflection.macro,
22
+ options: reflection.options.map { |k, v| [k, v.to_s] }.to_h,
23
+ klass_name: reflection.options[:polymorphic] ? nil : reflection.klass.name,
24
+ }
25
+ end
26
+ }
34
27
  end
35
- curr[i] = (alpha * ip) + ((1.0 - alpha) / @dim * 1.0)
36
- end
37
-
38
- err = 0
39
- @dim.times do |i|
40
- err += (prev[i] - curr[i]).abs
41
- end
42
-
43
- if err < EPS
44
- return curr
45
- elsif err.nan?
46
- raise "PageRank failed" # TODO just ignore and move on
47
- end
48
- end
49
- end
50
- end
51
-
52
- def self.qualified_primary_key(vertex)
53
- %Q|"#{vertex.table_name}"."#{vertex.primary_key}"|
54
- end
55
-
56
- def self.entity_graph
57
- graph = RGL::DirectedAdjacencyGraph.new
58
-
59
- base_klass = ApplicationRecord rescue ActiveRecord::Base
60
- klasses = ObjectSpace.each_object(Class).find_all { |klass| klass < base_klass }.find_all(&:table_exists?)
61
-
62
- graph.add_vertices *klasses
63
-
64
- klasses.each do |klass|
65
- klass.reflect_on_all_associations(:belongs_to).each do |reflection|
66
- next if reflection.options[:polymorphic] # TODO
67
-
68
- # TODO should the source be reflection.active_record or klass?
69
- graph.add_edge klass, reflection.klass
28
+ }
70
29
  end
71
30
  end
72
31
 
73
- graph
74
- end
75
-
76
- def self.get_entities
77
- graph = entity_graph
78
-
79
- # TODO send entire graph to server
80
- entities = graph.vertices.map { |v| { name: v.name, table_name: v.table_name } }
81
-
82
- {
83
- schema_version: ActiveRecord::Migrator.current_version,
84
- entities: entities # TODO also return entity attributes?
85
- }
86
- end
32
+ def metrics
33
+ with_connection do |connection|
34
+ result = { entity_totals: [] }
35
+ active_record_klasses.find_all(&:table_exists?).each do |klass|
36
+ count_query = klass.unscoped.select("COUNT(*)").to_sql
37
+ result[:entity_totals] << { name: klass.name, total: connection.execute(count_query).first["count"] }
38
+ end
87
39
 
88
- # Try to be defensive with our DB connection:
89
- # 1) Check a connection out from the thread pool instead of using an implicit one
90
- # 2) Make the connection read-only
91
- # 3) Time out any queries that take more than 100ms
92
- def self.with_connection
93
- ActiveRecord::Base.connection_pool.with_connection do |connection|
94
- connection.transaction do
95
- connection.execute "SET TRANSACTION READ ONLY"
96
- connection.execute "SET LOCAL statement_timeout = 100"
97
- yield connection
40
+ result
98
41
  end
99
42
  end
100
- end
101
-
102
- def self.get_metrics(pivot_name)
103
- with_connection do |connection|
104
- result = {entity_totals: []}
105
- pivot = nil
106
43
 
107
- if pivot_name
108
- pivot = pivot_name.constantize
109
- result[:pivot_name] = pivot.name
110
- result[:pivot_values] = connection.execute(pivot.unscoped.select("*").to_sql).to_a
111
- result[:pivoted_totals] = []
112
- end
113
-
114
- graph = entity_graph
115
-
116
- # Spit out counts for each entity by the customer pivot
117
-
118
- edge_weights = lambda { |_| 1 }
119
-
120
- graph.vertices.each do |vertex|
121
- count_query = vertex.unscoped.select("COUNT(*)").to_sql
122
- result[:entity_totals] << { name: vertex.name, total: connection.execute(count_query).first["count"] }
123
-
124
- if pivot && vertex != pivot
125
- # TODO weight edges to give preference to non-optional belongs_to (and other attributes?)
126
- path = graph.dijkstra_shortest_path(edge_weights, vertex, pivot)
127
- if path
128
- # Generate a SQL query to count the number of vertex instances grouped by pivot id, with appropriate joins from the path
129
- belongs_to_names = path.each_cons(2).map do |join_from, join_to|
130
- # TODO we shouldn't have to look up the edge again - use a graph model that allows us to annotate the edges with the reflections
131
- reflections = join_from.reflect_on_all_associations(:belongs_to).find_all { |reflection| !reflection.options[:polymorphic] && reflection.klass == join_to }
132
- # TODO warn if more than one reflection
133
- reflection = reflections.min_by { |r| r.options.size }
134
- reflection.name
135
- end
136
-
137
- joins = belongs_to_names.reverse.inject { |acc, elt| { elt => acc } }
138
- query = vertex.unscoped.joins(joins).group(qualified_primary_key(pivot)).select(qualified_primary_key(pivot), "COUNT(*)").to_sql
139
-
140
- result[:pivoted_totals] << { entity_name: vertex.name, totals: Hash[connection.execute(query).map { |record| [record["id"], record["count"]] }] }
141
- else
142
- # TODO print warning that we couldn't find a path from the pivot to the vertex
143
- end
44
+ private
45
+
46
+ # Try to be defensive with our DB connection:
47
+ # 1) Check a connection out from the thread pool instead of using an implicit one
48
+ # 2) Make the connection read-only
49
+ # 3) Time out any queries that take more than 100ms
50
+ def with_connection
51
+ ActiveRecord::Base.connection_pool.with_connection do |connection|
52
+ connection.transaction do
53
+ connection.execute "SET TRANSACTION READ ONLY"
54
+ connection.execute "SET LOCAL statement_timeout = 100"
55
+ yield connection
144
56
  end
145
57
  end
58
+ end
146
59
 
147
- result
60
+ def active_record_klasses
61
+ base_klass = ApplicationRecord rescue ActiveRecord::Base
62
+ ObjectSpace.each_object(Class).find_all { |klass| klass < base_klass }
148
63
  end
149
64
  end
150
65
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "action_cable_client"
2
4
  require "eventmachine"
3
5
  require "logger"
@@ -31,7 +33,7 @@ module Optic
31
33
  logger.debug "Starting worker thread"
32
34
  worker = Thread.new do
33
35
  EventMachine.run do
34
- client = ActionCableClient.new(uri, { channel: "MetricsChannel" }, true, { "Authorization" => "Bearer #{project_key}" })
36
+ client = ActionCableClient.new(uri, { channel: "MetricsChannel" }, true, "Authorization" => "Bearer #{project_key}")
35
37
 
36
38
  client.connected do
37
39
  logger.debug "Optic agent connected"
@@ -64,11 +66,11 @@ module Optic
64
66
  case command
65
67
  when "request_schema"
66
68
  logger.debug "Optic agent got schema request"
67
- client.perform "schema", message: Optic::Rails.get_entities
69
+ client.perform "schema", message: Optic::Rails.entities
68
70
  logger.debug "Optic agent sent schema"
69
71
  when "request_metrics"
70
72
  logger.debug "Optic agent got metrics request"
71
- client.perform "metrics", message: Optic::Rails.get_metrics(message["message"]["pivot"])
73
+ client.perform "metrics", message: Optic::Rails.metrics
72
74
  logger.debug "Optic agent sent metrics"
73
75
  else
74
76
  logger.warn "Optic agent got unknown command: #{command}"
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Optic
2
4
  module Rails
3
- VERSION = '1.0.0'
5
+ VERSION = "1.0.1"
4
6
  end
5
7
  end
metadata CHANGED
@@ -1,20 +1,23 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: optic-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anton Vaynshtok
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-05-31 00:00:00.000000000 Z
11
+ date: 2018-06-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: action_cable_client
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ - - ">="
18
21
  - !ruby/object:Gem::Version
19
22
  version: 2.0.2
20
23
  type: :runtime
@@ -22,6 +25,9 @@ dependencies:
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
27
  - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '2.0'
30
+ - - ">="
25
31
  - !ruby/object:Gem::Version
26
32
  version: 2.0.2
27
33
  - !ruby/object:Gem::Dependency
@@ -52,35 +58,9 @@ dependencies:
52
58
  - - "~>"
53
59
  - !ruby/object:Gem::Version
54
60
  version: '5.0'
55
- - !ruby/object:Gem::Dependency
56
- name: rgl
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: 0.5.3
62
- type: :runtime
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: 0.5.3
69
- - !ruby/object:Gem::Dependency
70
- name: sqlite3
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
- description: Rails plugin for Optic.
61
+ description: optic.watch is the easiest way to get notified when business metrics
62
+ change. This gem intelligently collects metrics from your production database using
63
+ ActiveRecord to automatically understand your data.
84
64
  email:
85
65
  - avaynshtok@gmail.com
86
66
  executables: []
@@ -93,7 +73,6 @@ files:
93
73
  - lib/optic/rails.rb
94
74
  - lib/optic/rails/railtie.rb
95
75
  - lib/optic/rails/version.rb
96
- - lib/tasks/optic/rails_tasks.rake
97
76
  homepage: https://www.sutrolabs.com/
98
77
  licenses:
99
78
  - MIT
@@ -117,5 +96,5 @@ rubyforge_project:
117
96
  rubygems_version: 2.7.3
118
97
  signing_key:
119
98
  specification_version: 4
120
- summary: Rails plugin for Optic.
99
+ summary: optic.watch for Rails
121
100
  test_files: []
@@ -1,4 +0,0 @@
1
- # desc "Explaining what the task does"
2
- # task :optic_rails do
3
- # # Task goes here
4
- # end