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.
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