lens 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
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('start_processing.action_controller') do |name, started, finished, id, data|
6
- Trace.create(id)
7
- end
8
-
9
- ActiveSupport::Notifications.subscribe('sql.active_record') do |*args|
10
- next unless Trace.current
11
- event = ActiveSupport::Notifications::Event.new(*args)
12
- Trace.current.add(event) if event.name != 'SCHEMA'
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
- ActiveSupport::Notifications.subscribe(/^render_(template|action|collection)\.action_view/) do |*args|
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
- ActiveSupport::Notifications.subscribe('process_action.action_controller') do |*args|
22
- next unless Trace.current
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/data/rec'
7
- HTTP_ERRORS = [Timeout::Error,
8
- Errno::EINVAL,
9
- Errno::ECONNRESET,
10
- EOFError,
11
- Net::HTTPBadResponse,
12
- Net::HTTPHeaderSyntaxError,
13
- Net::ProtocolError,
14
- Errno::ECONNREFUSED].freeze
15
-
16
- def initialize(options = {})
17
- [ :app_key,
18
- :protocol,
19
- :host,
20
- :port
21
- ].each do |option|
22
- instance_variable_set("@#{option}", options.send(option))
23
- end
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
- response = send_request(url.path, data)
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
- def setup_http_connection
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, headers = {})
49
- http_connection.post(path, data, http_headers(headers))
50
- rescue *HTTP_ERRORS => e
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(headers=nil)
56
- {}.tap do |hash|
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(duration: event.duration)
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
- formatted_data = Lens::EventFormatter.new(event, @data).json_formatted
16
- log(formatted_data)
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
- private
35
+ private
22
36
 
23
37
  def send(data)
24
- log(data)
25
- Lens.sender.send_to_lens(data)
38
+ Worker.instance.push(data)
26
39
  end
40
+ end
27
41
 
28
- def log(data)
29
- Rails.logger.info "all [LENS] >>> #{data}" if verbose?
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 verbose?
33
- true
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
@@ -1,3 +1,3 @@
1
1
  module Lens
2
- VERSION = "0.0.6"
2
+ VERSION = "0.0.7"
3
3
  end
@@ -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
- require "lens/core"
2
- require 'pry'
1
+ # encoding: utf-8
3
2
 
4
- module Lens
5
- HEADERS = {
6
- 'Content-type' => 'application/json',
7
- 'Content-Encoding' => 'deflate',
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.sender
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(payload: {}, duration: 2.33) }
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
@@ -2,12 +2,11 @@ require 'spec_helper'
2
2
  require 'support/helpers'
3
3
 
4
4
  describe Lens::Sender do
5
- before { reset_config }
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.should_receive(:post).once
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