lens 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|