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 +4 -4
- data/Rakefile +14 -12
- data/lib/optic/rails.rb +47 -132
- data/lib/optic/rails/railtie.rb +5 -3
- data/lib/optic/rails/version.rb +3 -1
- metadata +12 -33
- data/lib/tasks/optic/rails_tasks.rake +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4862c212205f52f736e51c5b74da8d0bd141d941fe27adb6b872efa715a59aac
|
4
|
+
data.tar.gz: b77ac04f9ae6cccc80168fc5a77ef27c249c75a6767859611022e7aac941d1ef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
4
|
+
require "bundler/setup"
|
3
5
|
rescue LoadError
|
4
|
-
puts
|
6
|
+
puts "You must `gem install bundler` and `bundle install` to run rake tasks"
|
5
7
|
end
|
6
8
|
|
7
|
-
require
|
9
|
+
require "rdoc/task"
|
8
10
|
|
9
11
|
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
-
rdoc.rdoc_dir =
|
11
|
-
rdoc.title =
|
12
|
-
rdoc.options <<
|
13
|
-
rdoc.rdoc_files.include(
|
14
|
-
rdoc.rdoc_files.include(
|
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
|
19
|
+
require "bundler/gem_tasks"
|
18
20
|
|
19
|
-
require
|
21
|
+
require "rake/testtask"
|
20
22
|
|
21
23
|
Rake::TestTask.new(:test) do |t|
|
22
|
-
t.libs <<
|
23
|
-
t.pattern =
|
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
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
4
|
-
require "rgl/dot"
|
5
|
-
require "rgl/dijkstra"
|
3
|
+
require "optic/rails/railtie"
|
6
4
|
|
7
5
|
module Optic
|
8
6
|
module Rails
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
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
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
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
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
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
|
-
|
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
|
data/lib/optic/rails/railtie.rb
CHANGED
@@ -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,
|
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.
|
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.
|
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}"
|
data/lib/optic/rails/version.rb
CHANGED
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.
|
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-
|
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
|
-
|
56
|
-
|
57
|
-
|
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:
|
99
|
+
summary: optic.watch for Rails
|
121
100
|
test_files: []
|