Skip to content

Commit 5fdd709

Browse files
committed
Add byte_limit to Emitter (close snowplow#170)
1 parent 8a01e1e commit 5fdd709

2 files changed

Lines changed: 44 additions & 7 deletions

File tree

snowplow_tracker/emitters.py

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ class Emitter(object):
7070
"""
7171

7272
@contract
73-
def __init__(self, endpoint, protocol="http", port=None, method="get", buffer_size=None, on_success=None, on_failure=None):
73+
def __init__(self, endpoint, protocol="http", port=None, method="get", buffer_size=None, on_success=None, on_failure=None, byte_limit=None):
7474
"""
7575
:param endpoint: The collector URL. Don't include "http://" - this is done automatically.
7676
:type endpoint: string
@@ -91,6 +91,8 @@ def __init__(self, endpoint, protocol="http", port=None, method="get", buffer_si
9191
2) If method is "post": The unsent data in string form;
9292
If method is "get": An array of dictionaries corresponding to the unsent events' payloads
9393
:type on_failure: function | None
94+
:param byte_limit: The size event list after reaching which queued events will be flushed
95+
:type byte_limit: int | None
9496
"""
9597
self.endpoint = Emitter.as_collector_uri(endpoint, protocol, port, method)
9698

@@ -103,6 +105,8 @@ def __init__(self, endpoint, protocol="http", port=None, method="get", buffer_si
103105
buffer_size = 1
104106
self.buffer_size = buffer_size
105107
self.buffer = []
108+
self.byte_limit = byte_limit
109+
self.bytes_queued = None if byte_limit is None else 0
106110

107111
self.on_success = on_success
108112
self.on_failure = on_failure
@@ -123,6 +127,8 @@ def as_collector_uri(endpoint, protocol="http", port=None, method="get"):
123127
:type protocol: protocol
124128
:param port: The collector port to connect to
125129
:type port: int | None
130+
:param method: Either `get` or `post` HTTP method
131+
:type method: method
126132
:rtype: string
127133
"""
128134
if method == "get":
@@ -144,14 +150,28 @@ def input(self, payload):
144150
:type payload: dict(string:*)
145151
"""
146152
with self.lock:
153+
if self.bytes_queued is not None:
154+
self.bytes_queued += len(str(payload))
155+
147156
if self.method == "post":
148157
self.buffer.append({key: str(payload[key]) for key in payload})
149158
else:
150159
self.buffer.append(payload)
151160

152-
if len(self.buffer) >= self.buffer_size:
161+
if self.reached_limit():
153162
self.flush()
154163

164+
def reached_limit(self):
165+
"""
166+
Checks if event-size or bytes limit are reached
167+
168+
:rtype: bool
169+
"""
170+
if self.byte_limit is None:
171+
return len(self.buffer) >= self.buffer_size
172+
else:
173+
return self.bytes_queued >= self.byte_limit or len(self.buffer) >= self.buffer_size
174+
155175
@task(name="Flush")
156176
def flush(self):
157177
"""
@@ -160,6 +180,8 @@ def flush(self):
160180
with self.lock:
161181
self.send_events(self.buffer)
162182
self.buffer = []
183+
if self.bytes_queued is not None:
184+
self.bytes_queued = 0
163185

164186
@contract
165187
def http_post(self, data):
@@ -191,7 +213,7 @@ def sync_flush(self):
191213
This is guaranteed to be blocking, not asynchronous.
192214
"""
193215
logger.debug("Starting synchronous flush...")
194-
result = Emitter.flush(self)
216+
Emitter.flush(self)
195217
logger.info("Finished synchrous flush")
196218

197219
@staticmethod
@@ -306,7 +328,8 @@ def __init__(
306328
buffer_size=None,
307329
on_success=None,
308330
on_failure=None,
309-
thread_count=1):
331+
thread_count=1,
332+
byte_limit=None):
310333
"""
311334
:param endpoint: The collector URL. Don't include "http://" - this is done automatically.
312335
:type endpoint: string
@@ -329,8 +352,10 @@ def __init__(
329352
:type on_failure: function | None
330353
:param thread_count: Number of worker threads to use for HTTP requests
331354
:type thread_count: int
355+
:param byte_limit: The size event list after reaching which queued events will be flushed
356+
:type byte_limit: int | None
332357
"""
333-
super(AsyncEmitter, self).__init__(endpoint, protocol, port, method, buffer_size, on_success, on_failure)
358+
super(AsyncEmitter, self).__init__(endpoint, protocol, port, method, buffer_size, on_success, on_failure, byte_limit)
334359
self.queue = Queue()
335360
for i in range(thread_count):
336361
t = threading.Thread(target=self.consume)
@@ -352,6 +377,8 @@ def flush(self):
352377
with self.lock:
353378
self.queue.put(self.buffer)
354379
self.buffer = []
380+
if self.bytes_queued is not None:
381+
self.bytes_queued = 0
355382

356383
def consume(self):
357384
while True:
@@ -366,8 +393,8 @@ class CeleryEmitter(Emitter):
366393
Works like the base Emitter class,
367394
but on_success and on_failure callbacks cannot be set.
368395
"""
369-
def __init__(self, endpoint, protocol="http", port=None, method="get", buffer_size=None):
370-
super(CeleryEmitter, self).__init__(endpoint, protocol, port, method, buffer_size, None, None)
396+
def __init__(self, endpoint, protocol="http", port=None, method="get", buffer_size=None, byte_limit=None):
397+
super(CeleryEmitter, self).__init__(endpoint, protocol, port, method, buffer_size, None, None, byte_limit)
371398

372399
def flush(self):
373400
"""

snowplow_tracker/test/integration/test_integration.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,3 +320,13 @@ def test_timestamps(self):
320320
self.assertEquals(request["data"][i].get("stm"), expected_timestamps[i]["stm"])
321321
self.assertEquals(request["data"][i].get("page"), "stamp" + str(i))
322322

323+
def test_bytelimit(self):
324+
post_emitter = emitters.Emitter("localhost", protocol="http", port=80, method='post', buffer_size=5, byte_limit=420)
325+
t = tracker.Tracker(post_emitter, default_subject)
326+
with HTTMock(pass_post_response_content):
327+
t.track_struct_event("Test", "A") # 140 bytes
328+
t.track_struct_event("Test", "A") # 280 bytes
329+
t.track_struct_event("Test", "A") # 420 bytes. Send
330+
t.track_struct_event("Test", "AA") # 141
331+
self.assertEquals(len(querystrings[-1]["data"]), 3)
332+
self.assertEqual(post_emitter.bytes_queued, 141)

0 commit comments

Comments
 (0)