libhoney 1.12.1 → 1.13.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e6c5ff396b0f0a043e1ee4b1ae1d89342fdfbc99861e5cdb22d7c5843acd112b
4
- data.tar.gz: ee861538fa2020c201dc5be4477372c9778c89b910d0f8e2faa2168371ea6f97
3
+ metadata.gz: '085a461457305c6c626057ffa084b66bb3d364d3bf538a66116de45c8195b78e'
4
+ data.tar.gz: 25cdef56d6c38db3b91f411763049acf5f536514002c1ca4ad519c406cb4309c
5
5
  SHA512:
6
- metadata.gz: 3e53c57343b02b2de9b028a06ed6800a78593267169ed9ea302cc2a7783bdc2ea4db4053321bd97162b0afdf9d8a97e3abbcd3c63f599d5c2ef02b69ea7fe886
7
- data.tar.gz: 4d513bbdd0decf922d3a6462a9dd644805cec48d353a2059286b5e408fb3ff94554300fd0ffe1b7028b21de6fbe2970e5a0effa7b1f4647f3fe812db00ab1e2c
6
+ metadata.gz: 5bd46c22d978845d9b124fda903632186ab69003f8a319db662718e222da0d18ce2215a9f54c71e413856a112b32fb6871a5a9d5f1539121120da2ea5476b30e
7
+ data.tar.gz: 8982666d9a862224d7ce2200ac79f0876ded8ecc05a6b010c7ff57e77cb90e978c8ffb64fa1edbc3b1951a2b0ab4c1ba2722c9bacc1a24ee980347535f7caf93
data/.rubocop.yml CHANGED
@@ -10,7 +10,11 @@ Lint/RescueException:
10
10
  Exclude:
11
11
  - 'lib/libhoney/transmission.rb'
12
12
 
13
+ Metrics/BlockLength:
14
+ Max: 35
15
+
13
16
  Metrics/ClassLength:
17
+ Max: 200
14
18
  Exclude:
15
19
  - test/*
16
20
 
@@ -5,11 +5,11 @@ module Libhoney
5
5
  attr_accessor :duration, :status_code, :metadata, :error
6
6
 
7
7
  def initialize(duration: 0,
8
- status_code: HTTP::Response::Status.new(0),
8
+ status_code: 0,
9
9
  metadata: nil,
10
10
  error: nil)
11
11
  @duration = duration
12
- @status_code = status_code
12
+ @status_code = HTTP::Response::Status.new(status_code)
13
13
  @metadata = metadata
14
14
  @error = error
15
15
  end
@@ -1,3 +1,5 @@
1
+ require 'json'
2
+ require 'timeout'
1
3
  require 'libhoney/response'
2
4
 
3
5
  module Libhoney
@@ -17,16 +19,19 @@ module Libhoney
17
19
  @block_on_send = block_on_send
18
20
  @block_on_responses = block_on_responses
19
21
  @max_batch_size = max_batch_size
20
- @send_frequency = send_frequency
22
+ # convert to seconds
23
+ @send_frequency = send_frequency.fdiv(1000)
21
24
  @max_concurrent_batches = max_concurrent_batches
22
25
  @pending_work_capacity = pending_work_capacity
23
26
  @send_timeout = send_timeout
24
27
  @user_agent = build_user_agent(user_agent_addition).freeze
25
28
 
26
- # use a SizedQueue so the producer will block on adding to the send_queue when @block_on_send is true
27
- @send_queue = SizedQueue.new(@pending_work_capacity)
28
- @threads = []
29
- @lock = Mutex.new
29
+ @send_queue = Queue.new
30
+ @threads = []
31
+ @lock = Mutex.new
32
+ # use a SizedQueue so the producer will block on adding to the batch_queue when @block_on_send is true
33
+ @batch_queue = SizedQueue.new(@pending_work_capacity)
34
+ @batch_thread = nil
30
35
  end
31
36
 
32
37
  def add(event)
@@ -35,7 +40,7 @@ module Libhoney
35
40
  raise ArgumentError, "No Dataset for Honeycomb. Can't send datasetless." if event.dataset == ''
36
41
 
37
42
  begin
38
- @send_queue.enq(event, !@block_on_send)
43
+ @batch_queue.enq(event, !@block_on_send)
39
44
  rescue ThreadError
40
45
  # happens if the queue was full and block_on_send = false.
41
46
  end
@@ -44,57 +49,53 @@ module Libhoney
44
49
  end
45
50
 
46
51
  def send_loop
47
- http_clients = Hash.new do |h, api_host|
48
- h[api_host] = HTTP.timeout(connect: @send_timeout, write: @send_timeout, read: @send_timeout)
49
- .persistent(api_host)
50
- .headers(
51
- 'User-Agent' => @user_agent,
52
- 'Content-Type' => 'application/json'
53
- )
54
- end
52
+ http_clients = build_http_clients
55
53
 
56
54
  # eat events until we run out
57
55
  loop do
58
- event = @send_queue.pop
59
- break if event.nil?
56
+ api_host, writekey, dataset, batch = @send_queue.pop
57
+ break if batch.nil?
60
58
 
61
59
  before = Time.now
62
60
 
63
61
  begin
64
- http = http_clients[event.api_host]
65
- url = '/1/events/' + Addressable::URI.escape(event.dataset.dup)
66
-
67
- resp = http.post(url,
68
- json: event.data,
69
- headers: {
70
- 'X-Honeycomb-Team' => event.writekey,
71
- 'X-Honeycomb-SampleRate' => event.sample_rate,
72
- 'X-Event-Time' => event.timestamp.iso8601(3)
73
- })
74
-
75
- # "You must consume response before sending next request via persistent connection"
76
- # https://github.com/httprb/http/wiki/Persistent-Connections-%28keep-alive%29#note-using-persistent-requests-correctly
77
- resp.flush
78
-
79
- response = Response.new(status_code: resp.status)
80
- rescue Exception => error
62
+ http = http_clients[api_host]
63
+ body = serialize_batch(batch)
64
+ next if body.nil?
65
+
66
+ headers = {
67
+ 'Content-Type' => 'application/json',
68
+ 'X-Honeycomb-Team' => writekey
69
+ }
70
+
71
+ response = http.post(
72
+ "/1/batch/#{Addressable::URI.escape(dataset)}",
73
+ body: body,
74
+ headers: headers
75
+ )
76
+ process_response(response, before, batch)
77
+ rescue Exception => e
81
78
  # catch a broader swath of exceptions than is usually good practice,
82
79
  # because this is effectively the top-level exception handler for the
83
80
  # sender threads, and we don't want those threads to die (leaving
84
81
  # nothing consuming the queue).
85
- response = Response.new(error: error)
86
- ensure
87
- if response
88
- response.duration = Time.now - before
89
- response.metadata = event.metadata
82
+ begin
83
+ batch.each do |event|
84
+ # nil events in the batch should already have had an error
85
+ # response enqueued in #serialize_batch
86
+ next if event.nil?
87
+
88
+ Response.new(error: e).tap do |error_response|
89
+ error_response.metadata = event.metadata
90
+ begin
91
+ @responses.enq(error_response, !@block_on_responses)
92
+ rescue ThreadError
93
+ end
94
+ end
95
+ end
96
+ rescue ThreadError
90
97
  end
91
98
  end
92
-
93
- begin
94
- @responses.enq(response, !@block_on_responses) if response
95
- rescue ThreadError
96
- # happens if the queue was full and block_on_send = false.
97
- end
98
99
  end
99
100
  ensure
100
101
  http_clients.each do |_, http|
@@ -108,7 +109,13 @@ module Libhoney
108
109
 
109
110
  def close(drain)
110
111
  # if drain is false, clear the remaining unprocessed events from the queue
111
- @send_queue.clear if drain == false
112
+ unless drain
113
+ @batch_queue.clear
114
+ @send_queue.clear
115
+ end
116
+
117
+ @batch_queue.enq(nil)
118
+ @batch_thread.join
112
119
 
113
120
  # send @threads.length number of nils so each thread will fall out of send_loop
114
121
  @threads.length.times { @send_queue << nil }
@@ -121,8 +128,78 @@ module Libhoney
121
128
  0
122
129
  end
123
130
 
131
+ def batch_loop
132
+ next_send_time = Time.now + @send_frequency
133
+ batched_events = Hash.new do |h, key|
134
+ h[key] = []
135
+ end
136
+
137
+ loop do
138
+ begin
139
+ while (event = Timeout.timeout(@send_frequency) { @batch_queue.pop })
140
+ key = [event.api_host, event.writekey, event.dataset]
141
+ batched_events[key] << event
142
+ end
143
+
144
+ break
145
+ rescue Exception
146
+ ensure
147
+ next_send_time = flush_batched_events(batched_events) if Time.now > next_send_time
148
+ end
149
+ end
150
+
151
+ flush_batched_events(batched_events)
152
+ end
153
+
124
154
  private
125
155
 
156
+ def process_response(http_response, before, batch)
157
+ index = 0
158
+ http_response.parse.each do |event|
159
+ index += 1 while batch[index].nil? && index < batch.size
160
+ break unless (batched_event = batch[index])
161
+
162
+ Response.new(status_code: event['status']).tap do |response|
163
+ response.duration = Time.now - before
164
+ response.metadata = batched_event.metadata
165
+ begin
166
+ @responses.enq(response, !@block_on_responses)
167
+ rescue ThreadError
168
+ end
169
+ end
170
+ end
171
+ end
172
+
173
+ def serialize_batch(batch)
174
+ payload = []
175
+ batch.map! do |event|
176
+ begin
177
+ e = {
178
+ time: event.timestamp.iso8601(3),
179
+ samplerate: event.sample_rate,
180
+ data: event.data
181
+ }
182
+ payload << JSON.generate(e)
183
+
184
+ event
185
+ rescue StandardError => e
186
+ Response.new(error: e).tap do |response|
187
+ response.metadata = event.metadata
188
+ begin
189
+ @responses.enq(response, !@block_on_responses)
190
+ rescue ThreadError
191
+ end
192
+ end
193
+
194
+ nil
195
+ end
196
+ end
197
+
198
+ return if payload.empty?
199
+
200
+ "[#{payload.join(',')}]"
201
+ end
202
+
126
203
  def build_user_agent(user_agent_addition)
127
204
  ua = "libhoney-rb/#{VERSION}"
128
205
  ua << " #{user_agent_addition}" if user_agent_addition
@@ -131,9 +208,32 @@ module Libhoney
131
208
 
132
209
  def ensure_threads_running
133
210
  @lock.synchronize do
211
+ @batch_thread = Thread.new { batch_loop } unless @batch_thread && @batch_thread.alive?
134
212
  @threads.select!(&:alive?)
135
213
  @threads << Thread.new { send_loop } while @threads.length < @max_concurrent_batches
136
214
  end
137
215
  end
216
+
217
+ def flush_batched_events(batched_events)
218
+ batched_events.each do |(api_host, writekey, dataset), events|
219
+ events.each_slice(@max_batch_size) do |batch|
220
+ @send_queue << [api_host, writekey, dataset, batch]
221
+ end
222
+ end
223
+ batched_events.clear
224
+
225
+ Time.now + @send_frequency
226
+ end
227
+
228
+ def build_http_clients
229
+ Hash.new do |h, api_host|
230
+ h[api_host] = HTTP.timeout(connect: @send_timeout, write: @send_timeout, read: @send_timeout)
231
+ .persistent(api_host)
232
+ .headers(
233
+ 'User-Agent' => @user_agent,
234
+ 'Content-Type' => 'application/json'
235
+ )
236
+ end
237
+ end
138
238
  end
139
239
  end
@@ -1,3 +1,3 @@
1
1
  module Libhoney
2
- VERSION = '1.12.1'.freeze
2
+ VERSION = '1.13.0'.freeze
3
3
  end
data/libhoney.gemspec CHANGED
@@ -26,7 +26,9 @@ Gem::Specification.new do |spec|
26
26
  spec.add_development_dependency 'bundler'
27
27
  spec.add_development_dependency 'minitest', '~> 5.0'
28
28
  spec.add_development_dependency 'rake', '~> 12.3'
29
- spec.add_development_dependency 'rubocop'
29
+ spec.add_development_dependency 'rubocop', '< 0.69'
30
+ spec.add_development_dependency 'sinatra'
31
+ spec.add_development_dependency 'sinatra-contrib'
30
32
  spec.add_development_dependency 'webmock', '~> 3.4'
31
33
  spec.add_development_dependency 'yard'
32
34
  spec.add_development_dependency 'yardstick', '~> 0.9'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: libhoney
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.12.1
4
+ version: 1.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - The Honeycomb.io Team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-03-13 00:00:00.000000000 Z
11
+ date: 2019-06-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bump
@@ -68,6 +68,34 @@ dependencies:
68
68
  version: '12.3'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "<"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.69'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "<"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.69'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sinatra
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: sinatra-contrib
71
99
  requirement: !ruby/object:Gem::Requirement
72
100
  requirements:
73
101
  - - ">="
@@ -206,7 +234,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
206
234
  - !ruby/object:Gem::Version
207
235
  version: '0'
208
236
  requirements: []
209
- rubygems_version: 3.0.3
237
+ rubyforge_project:
238
+ rubygems_version: 2.7.7
210
239
  signing_key:
211
240
  specification_version: 4
212
241
  summary: send data to Honeycomb