lens 0.0.6 → 0.0.7
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/.gitignore +2 -0
- data/.hound.yml +2 -0
- data/.rubocop.yml +237 -0
- data/.travis.yml +22 -0
- data/Gemfile +5 -1
- data/README.md +27 -20
- data/lens.gemspec +12 -11
- data/lib/lens/allocations_data.rb +52 -0
- data/lib/lens/compression.rb +13 -0
- data/lib/lens/configuration.rb +12 -2
- data/lib/lens/core.rb +12 -11
- data/lib/lens/event.rb +27 -0
- data/lib/lens/event_formatter.rb +34 -5
- data/lib/lens/exceptions.rb +3 -0
- data/lib/lens/gc.rb +30 -0
- data/lib/lens/gzip_util.rb +12 -0
- data/lib/lens/railtie.rb +11 -21
- data/lib/lens/sender.rb +29 -48
- data/lib/lens/trace.rb +44 -14
- data/lib/lens/version.rb +1 -1
- data/lib/lens/worker.rb +138 -0
- data/lib/lens.rb +17 -10
- data/spec/lens/event_formatter_spec.rb +7 -1
- data/spec/lens/lens_spec.rb +105 -0
- data/spec/lens/sender_spec.rb +2 -3
- data/spec/lens/trace_spec.rb +50 -0
- data/spec/lens/worker_spec.rb +119 -0
- data/spec/spec_helper.rb +26 -6
- data/spec/support/helpers.rb +2 -10
- metadata +47 -17
- data/Gemfile.lock +0 -112
data/lib/lens/railtie.rb
CHANGED
@@ -1,29 +1,19 @@
|
|
1
|
-
require 'rails'
|
2
|
-
|
3
1
|
module Lens
|
4
2
|
class Railtie < Rails::Railtie
|
5
|
-
ActiveSupport::Notifications.subscribe(
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
end
|
3
|
+
ActiveSupport::Notifications.subscribe(/.*/) do |name, start, finish, id, payload|
|
4
|
+
event = Event.new(
|
5
|
+
name: name,
|
6
|
+
started: start,
|
7
|
+
finished: finish,
|
8
|
+
transaction_id: id,
|
9
|
+
payload: payload
|
10
|
+
)
|
14
11
|
|
15
|
-
|
16
|
-
next unless Trace.current
|
17
|
-
event = ActiveSupport::Notifications::Event.new(*args)
|
18
|
-
Trace.current.add(event)
|
12
|
+
Trace.process(event)
|
19
13
|
end
|
20
14
|
|
21
|
-
|
22
|
-
|
23
|
-
event = ActiveSupport::Notifications::Event.new(*args)
|
24
|
-
if event.payload[:controller] && event.payload[:action]
|
25
|
-
Trace.current.complete(event)
|
26
|
-
end
|
15
|
+
config.after_initialize do
|
16
|
+
Lens.start
|
27
17
|
end
|
28
18
|
end
|
29
19
|
end
|
data/lib/lens/sender.rb
CHANGED
@@ -3,66 +3,47 @@ require 'json'
|
|
3
3
|
|
4
4
|
module Lens
|
5
5
|
class Sender
|
6
|
-
NOTICES_URI = 'api/v1/
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
def initialize(options =
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
6
|
+
NOTICES_URI = 'api/v1/events'.freeze
|
7
|
+
HEADERS = {
|
8
|
+
'Content-type' => 'application/json',
|
9
|
+
'Content-Encoding' => 'deflate',
|
10
|
+
'Accept' => 'text/json, application/json',
|
11
|
+
'User-Agent' => "LENS-Ruby client #{VERSION}; #{RUBY_VERSION}; #{RUBY_PLATFORM}"
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
attr_reader :app_key, :protocol, :host, :port, :compressor
|
15
|
+
|
16
|
+
def initialize(options = nil)
|
17
|
+
raise ArgumentError unless options.is_a? Configuration
|
18
|
+
|
19
|
+
@app_key = options.app_key
|
20
|
+
@protocol = options.protocol
|
21
|
+
@host = options.host
|
22
|
+
@port = options.port
|
23
|
+
@compressor = options.compressor
|
24
24
|
end
|
25
25
|
|
26
26
|
def send_to_lens(data)
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
attr_reader :app_key,
|
31
|
-
:protocol,
|
32
|
-
:host,
|
33
|
-
:port
|
34
|
-
|
35
|
-
private
|
36
|
-
|
37
|
-
def http_connection
|
38
|
-
setup_http_connection
|
27
|
+
send_request(url.path, compressor.compress(data), compressor.headers)
|
39
28
|
end
|
40
29
|
|
41
|
-
|
42
|
-
Net::HTTP.new(url.host, url.port)
|
43
|
-
rescue => e
|
44
|
-
log(:error, "[Lens::Sender#setup_http_connection] Failure initializing the HTTP connection.\nError: #{e.class} - #{e.message}\nBacktrace:\n#{e.backtrace.join("\n\t")}")
|
45
|
-
raise e
|
46
|
-
end
|
30
|
+
private
|
47
31
|
|
48
|
-
def send_request(path, data,
|
49
|
-
|
50
|
-
|
51
|
-
raise e
|
52
|
-
nil
|
32
|
+
def send_request(path, data, additional_headers = {})
|
33
|
+
headers = http_headers.merge additional_headers
|
34
|
+
http_connection.post(path, data, headers)
|
53
35
|
end
|
54
36
|
|
55
|
-
def http_headers
|
56
|
-
|
57
|
-
hash.merge!(HEADERS)
|
58
|
-
hash.merge!({'X-Auth-Token' => app_key})
|
59
|
-
hash.merge!({'Content-Type' =>'application/json'})
|
60
|
-
hash.merge!(headers) if headers
|
61
|
-
end
|
37
|
+
def http_headers
|
38
|
+
HEADERS.merge('X-Auth-Token' => app_key)
|
62
39
|
end
|
63
40
|
|
64
41
|
def url
|
65
42
|
URI.parse("#{protocol}://#{host}:#{port}").merge(NOTICES_URI)
|
66
43
|
end
|
44
|
+
|
45
|
+
def http_connection
|
46
|
+
Net::HTTP.new(url.host, url.port)
|
47
|
+
end
|
67
48
|
end
|
68
49
|
end
|
data/lib/lens/trace.rb
CHANGED
@@ -1,40 +1,70 @@
|
|
1
|
-
require 'rails'
|
2
|
-
|
3
1
|
module Lens
|
4
2
|
class Trace
|
5
3
|
def initialize(id)
|
6
4
|
@id = id
|
7
5
|
@data = []
|
6
|
+
|
7
|
+
@gc_statistics = Lens::GC.new
|
8
|
+
@gc_statistics.enable
|
9
|
+
|
10
|
+
@allocations_data = Lens::AllocationsData.new
|
11
|
+
@allocations_data.enable
|
8
12
|
end
|
9
13
|
|
10
14
|
def add(event)
|
11
|
-
@data.push event.payload.merge(
|
15
|
+
@data.push event.payload.merge(
|
16
|
+
etype: event.name,
|
17
|
+
eduration: event.duration,
|
18
|
+
estart: event.time.to_f,
|
19
|
+
efinish: event.end.to_f
|
20
|
+
)
|
12
21
|
end
|
13
22
|
|
14
23
|
def complete(event)
|
15
|
-
|
16
|
-
|
24
|
+
formatter = Lens::EventFormatter.new(
|
25
|
+
event,
|
26
|
+
@data,
|
27
|
+
@gc_statistics.total_time,
|
28
|
+
@allocations_data
|
29
|
+
)
|
30
|
+
formatted_data = formatter.json_formatted
|
17
31
|
send(formatted_data)
|
18
32
|
Thread.current[:__lens_trace] = nil
|
19
33
|
end
|
20
34
|
|
21
|
-
|
35
|
+
private
|
22
36
|
|
23
37
|
def send(data)
|
24
|
-
|
25
|
-
Lens.sender.send_to_lens(data)
|
38
|
+
Worker.instance.push(data)
|
26
39
|
end
|
40
|
+
end
|
27
41
|
|
28
|
-
|
29
|
-
|
42
|
+
class << Trace
|
43
|
+
def process(event)
|
44
|
+
create(event.transaction_id) if first_event?(event)
|
45
|
+
|
46
|
+
if Trace.present?
|
47
|
+
current.add(event)
|
48
|
+
current.complete(event) if last_event?(event)
|
49
|
+
end
|
30
50
|
end
|
31
51
|
|
32
|
-
def
|
33
|
-
|
52
|
+
def present?
|
53
|
+
current.present?
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def first_event?(event)
|
59
|
+
event.name == 'start_processing.action_controller'
|
60
|
+
end
|
61
|
+
|
62
|
+
def last_event?(event)
|
63
|
+
event.name == 'process_action.action_controller' &&
|
64
|
+
event.payload[:controller] &&
|
65
|
+
event.payload[:action]
|
34
66
|
end
|
35
|
-
end
|
36
67
|
|
37
|
-
class << Trace
|
38
68
|
def current
|
39
69
|
Thread.current[:__lens_trace]
|
40
70
|
end
|
data/lib/lens/version.rb
CHANGED
data/lib/lens/worker.rb
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
module Lens
|
2
|
+
class Worker
|
3
|
+
# A queue which enforces a maximum size.
|
4
|
+
# NOTE: can be replaced with SizedQueue?
|
5
|
+
class Queue < ::Queue
|
6
|
+
attr_reader :max_size
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
raise ArgumentError unless options[:max_size]
|
10
|
+
|
11
|
+
@max_size = options[:max_size]
|
12
|
+
super()
|
13
|
+
end
|
14
|
+
|
15
|
+
def push(obj)
|
16
|
+
super unless size >= max_size
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def instance
|
22
|
+
@instance
|
23
|
+
end
|
24
|
+
|
25
|
+
def running?
|
26
|
+
!instance.nil?
|
27
|
+
end
|
28
|
+
|
29
|
+
def start(config)
|
30
|
+
return instance if running?
|
31
|
+
@instance = new(config)
|
32
|
+
end
|
33
|
+
|
34
|
+
def stop(options = {})
|
35
|
+
@instance.public_send(options[:force] ? :shutdown! : :shutdown) if running?
|
36
|
+
ensure
|
37
|
+
@instance = nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
SHUTDOWN = :__lens_worker_shutdown!
|
42
|
+
|
43
|
+
attr_reader :config, :queue, :pid, :mutex, :thread
|
44
|
+
|
45
|
+
def initialize(config)
|
46
|
+
@config = config
|
47
|
+
@queue = Queue.new(max_size: 100)
|
48
|
+
@mutex = Mutex.new
|
49
|
+
@shutdown = false
|
50
|
+
start
|
51
|
+
end
|
52
|
+
|
53
|
+
def push(obj)
|
54
|
+
if start
|
55
|
+
queue.push(obj)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def start
|
60
|
+
mutex.synchronize do
|
61
|
+
return false if @shutdown
|
62
|
+
return true if thread && thread.alive?
|
63
|
+
|
64
|
+
@pid = Process.pid
|
65
|
+
@thread = Thread.new { run }
|
66
|
+
end
|
67
|
+
|
68
|
+
true
|
69
|
+
end
|
70
|
+
|
71
|
+
def run
|
72
|
+
begin
|
73
|
+
loop do
|
74
|
+
case msg = queue.pop
|
75
|
+
when SHUTDOWN
|
76
|
+
break
|
77
|
+
else
|
78
|
+
process(msg)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
rescue Exception => e
|
83
|
+
end
|
84
|
+
|
85
|
+
def process(msg)
|
86
|
+
handle_response(notify_backend(msg))
|
87
|
+
rescue StandardError => e
|
88
|
+
sleep(1)
|
89
|
+
end
|
90
|
+
|
91
|
+
def notify_backend(payload)
|
92
|
+
Lens.sender.send_to_lens(payload)
|
93
|
+
end
|
94
|
+
|
95
|
+
def handle_response(response)
|
96
|
+
# TODO: send message back to queue if response status != 200
|
97
|
+
log "handle_response #{response.code}"
|
98
|
+
end
|
99
|
+
|
100
|
+
# Shutdown the worker after sending remaining data.
|
101
|
+
# Returns true.
|
102
|
+
def shutdown
|
103
|
+
mutex.synchronize do
|
104
|
+
@shutdown = true
|
105
|
+
@pid = nil
|
106
|
+
queue.push(SHUTDOWN)
|
107
|
+
end
|
108
|
+
|
109
|
+
return true unless thread
|
110
|
+
|
111
|
+
r = true
|
112
|
+
unless Thread.current.eql?(thread)
|
113
|
+
begin
|
114
|
+
r = !!thread.join
|
115
|
+
ensure
|
116
|
+
shutdown! unless r
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
r
|
121
|
+
end
|
122
|
+
|
123
|
+
# Immediate shutdown
|
124
|
+
def shutdown!
|
125
|
+
mutex.synchronize do
|
126
|
+
@shutdown = true
|
127
|
+
@pid = nil
|
128
|
+
end
|
129
|
+
|
130
|
+
if thread
|
131
|
+
Thread.kill(thread)
|
132
|
+
thread.join # Allow ensure blocks to execute.
|
133
|
+
end
|
134
|
+
|
135
|
+
true
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
data/lib/lens.rb
CHANGED
@@ -1,14 +1,11 @@
|
|
1
|
-
|
2
|
-
require 'pry'
|
1
|
+
# encoding: utf-8
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
'Accept' => 'text/json, application/json',
|
9
|
-
'User-Agent' => "LENS-Ruby #{VERSION}; #{RUBY_VERSION}; #{RUBY_PLATFORM}"
|
10
|
-
}.freeze
|
3
|
+
raise 'WTF!?!?! Where is Rails man? ❨╯°□°❩╯︵┻━┻' unless defined?(Rails)
|
4
|
+
|
5
|
+
require 'lens/core'
|
6
|
+
require 'lens/railtie'
|
11
7
|
|
8
|
+
module Lens
|
12
9
|
class << self
|
13
10
|
attr_accessor :sender
|
14
11
|
attr_writer :configuration
|
@@ -17,11 +14,21 @@ module Lens
|
|
17
14
|
yield(configuration)
|
18
15
|
|
19
16
|
self.sender = Sender.new(configuration)
|
20
|
-
self
|
17
|
+
self
|
21
18
|
end
|
22
19
|
|
23
20
|
def configuration
|
24
21
|
@configuration ||= Configuration.new
|
25
22
|
end
|
23
|
+
|
24
|
+
def start
|
25
|
+
raise ConfigurationError, 'application key required' unless configuration.app_key
|
26
|
+
|
27
|
+
Worker.start(configuration)
|
28
|
+
end
|
29
|
+
|
30
|
+
def stop
|
31
|
+
Worker.stop
|
32
|
+
end
|
26
33
|
end
|
27
34
|
end
|
@@ -1,7 +1,13 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Lens::EventFormatter do
|
4
|
-
let(:event) { double(
|
4
|
+
let(:event) { double(
|
5
|
+
payload: {},
|
6
|
+
duration: 2.33,
|
7
|
+
time: Time.now - 2.seconds,
|
8
|
+
end: Time.now,
|
9
|
+
name: 'dummy.name')
|
10
|
+
}
|
5
11
|
let(:records) { [] }
|
6
12
|
let(:formatter) { Lens::EventFormatter.new(event, records) }
|
7
13
|
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'support/helpers'
|
3
|
+
|
4
|
+
describe Lens do
|
5
|
+
describe '.configure' do
|
6
|
+
context 'when user params' do
|
7
|
+
let(:params) do
|
8
|
+
{
|
9
|
+
app_key: 'secret-key',
|
10
|
+
protocol: 'https',
|
11
|
+
host: 'example.com',
|
12
|
+
port: 8080
|
13
|
+
}
|
14
|
+
end
|
15
|
+
before { configure(params) }
|
16
|
+
subject { described_class.configuration }
|
17
|
+
|
18
|
+
it { expect(subject.app_key).to eq params[:app_key] }
|
19
|
+
it { expect(subject.protocol).to eq params[:protocol] }
|
20
|
+
it { expect(subject.host).to eq params[:host] }
|
21
|
+
it { expect(subject.port).to eq params[:port] }
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'when default params' do
|
25
|
+
describe 'compressor' do
|
26
|
+
subject { described_class.configuration.compressor }
|
27
|
+
|
28
|
+
it { is_expected.to respond_to :compress }
|
29
|
+
it { is_expected.to be Lens::Compression::Gzip }
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'configuration' do
|
33
|
+
subject { described_class.configuration }
|
34
|
+
|
35
|
+
it { expect(subject.app_key).to eq nil }
|
36
|
+
it { expect(subject.protocol).to eq 'http' }
|
37
|
+
it { expect(subject.host).to eq 'lenshq.io' }
|
38
|
+
it { expect(subject.port).to eq 80 }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '.start' do
|
44
|
+
context 'without configuration' do
|
45
|
+
subject { described_class.start }
|
46
|
+
|
47
|
+
it { expect { subject }.to raise_error Lens::ConfigurationError }
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'with configuration' do
|
51
|
+
let(:params) { { app_key: 'some_key' } }
|
52
|
+
|
53
|
+
around(:example) do |example|
|
54
|
+
configure(params)
|
55
|
+
described_class.start
|
56
|
+
|
57
|
+
example.run
|
58
|
+
|
59
|
+
described_class.stop
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'client has been started properly' do
|
63
|
+
expect(Lens::Worker.running?).to be_truthy
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe '.stop' do
|
69
|
+
context 'not running' do
|
70
|
+
it 'client has been stopped properly' do
|
71
|
+
expect(Lens::Worker.running?).to be_falsey
|
72
|
+
described_class.stop
|
73
|
+
expect(Lens::Worker.running?).to be_falsey
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'running' do
|
78
|
+
let(:params) { { app_key: 'some_key' } }
|
79
|
+
|
80
|
+
around(:example) do |example|
|
81
|
+
configure(params)
|
82
|
+
described_class.start
|
83
|
+
|
84
|
+
example.run
|
85
|
+
|
86
|
+
described_class.stop
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'client has been stopped properly' do
|
90
|
+
expect(Lens::Worker.running?).to be_truthy
|
91
|
+
described_class.stop
|
92
|
+
expect(Lens::Worker.running?).to be_falsey
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def configure(params)
|
99
|
+
described_class.configure do |config|
|
100
|
+
config.app_key = params[:app_key] if params[:app_key].present?
|
101
|
+
config.protocol = params[:protocol] if params[:protocol].present?
|
102
|
+
config.host = params[:host] if params[:host].present?
|
103
|
+
config.port = params[:port] if params[:port].present?
|
104
|
+
end
|
105
|
+
end
|
data/spec/lens/sender_spec.rb
CHANGED
@@ -2,12 +2,11 @@ require 'spec_helper'
|
|
2
2
|
require 'support/helpers'
|
3
3
|
|
4
4
|
describe Lens::Sender do
|
5
|
-
before {
|
5
|
+
before { Lens.configure { |config| config.app_key = 'app_key_123' } }
|
6
6
|
let(:http) { stub_http }
|
7
7
|
|
8
8
|
it "makes a single request when sending notices" do
|
9
|
-
http.
|
9
|
+
expect(http).to receive(:post).once
|
10
10
|
Lens.sender.send_to_lens('abc123')
|
11
11
|
end
|
12
|
-
|
13
12
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Lens::Trace do
|
4
|
+
describe '.process' do
|
5
|
+
let(:first_event) { generate_event('start_processing.action_controller') }
|
6
|
+
let(:last_event) { generate_event('process_action.action_controller') }
|
7
|
+
|
8
|
+
context 'when first message' do
|
9
|
+
before { described_class.process(first_event) }
|
10
|
+
|
11
|
+
it 'creates new thread' do
|
12
|
+
expect(described_class.present?).to be true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'when last message' do
|
17
|
+
before do
|
18
|
+
Lens::Worker.start({})
|
19
|
+
allow_any_instance_of(Lens::Worker).to receive :push
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'kills thread' do
|
23
|
+
described_class.process(first_event)
|
24
|
+
expect(described_class.present?).to be true
|
25
|
+
|
26
|
+
described_class.process(last_event)
|
27
|
+
expect(described_class.present?).to be false
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'pushes data to lens server' do
|
31
|
+
expect_any_instance_of(Lens::Worker).to receive :push
|
32
|
+
described_class.process(first_event)
|
33
|
+
described_class.process(last_event)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def generate_event(name = 'event_name')
|
40
|
+
Lens::Event.new(
|
41
|
+
name: name,
|
42
|
+
started: Time.current,
|
43
|
+
finished: Time.current,
|
44
|
+
transaction_id: 1,
|
45
|
+
payload: {
|
46
|
+
controller: 'controller',
|
47
|
+
action: 'action'
|
48
|
+
}
|
49
|
+
)
|
50
|
+
end
|