optic-rails 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|