Leakage class (#1510)

* Leakage class

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix lint issues

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Abhinav Singh 2024-11-22 10:07:27 +05:30 committed by GitHub
parent 71d796baa9
commit ad2e1074b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 113 additions and 0 deletions

52
proxy/common/leakage.py Normal file
View File

@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
"""
proxy.py
~~~~~~~~
Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on
Network monitoring, controls & Application development, testing, debugging.
:copyright: (c) 2013-present by Abhinav Singh and contributors.
:license: BSD, see LICENSE for more details.
"""
import time
class Leakage:
"""Leaky Bucket algorithm."""
def __init__(self, rate: int) -> None:
"""Initialize the leaky bucket with a specified leak rate in bytes per second."""
# Maximum number of tokens the bucket can hold (bytes per second)
self.rate = rate
self.tokens = rate
self.last_check = time.time()
def _refill(self) -> None:
"""Refill tokens based on the elapsed time since the last check."""
now = time.time()
elapsed = now - self.last_check
# Add tokens proportional to elapsed time, up to the rate
self.tokens += int(elapsed * self.rate)
# Cap tokens at the maximum rate to enforce the rate limit
self.tokens = min(self.tokens, self.rate)
self.last_check = now
def release(self, tokens: int) -> None:
"""When you are unable to consume amount units of token, release them into the bucket.
E.g. say you wanted to read 1024 units, but only 24 units were read, then put
back unconsumed 1000 tokens back in the bucket."""
if tokens < 0:
raise ValueError('Cannot release a negative number of tokens')
self.tokens += tokens
self.tokens = min(self.tokens, self.rate)
def consume(self, amount: int) -> int:
"""Attempt to consume the amount from the bucket.
Returns the amount allowed to be sent, up to the available tokens (rate).
"""
self._refill()
allowed = min(amount, self.tokens)
self.tokens -= allowed
return allowed

View File

@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
"""
proxy.py
~~~~~~~~
Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on
Network monitoring, controls & Application development, testing, debugging.
:copyright: (c) 2013-present by Abhinav Singh and contributors.
:license: BSD, see LICENSE for more details.
"""
import time
import unittest
from proxy.common.leakage import Leakage
class TestLeakage(unittest.TestCase):
def test_initial_consume_no_tokens(self) -> None:
# Test consuming with no tokens available initially
rate = 100 # bytes per second
bucket = Leakage(rate)
self.assertEqual(
bucket.consume(150),
100,
) # No tokens yet, so expect 0 bytes to be sent
def test_consume_with_refill(self) -> None:
# Test consuming with refill after waiting
rate = 100 # bytes per second
bucket = Leakage(rate)
time.sleep(1) # Wait for a second to allow refill
self.assertEqual(bucket.consume(50), 50) # 50 bytes should be available
def test_consume_above_leak_rate(self) -> None:
# Test attempting to consume more than the leak rate after a refill
rate = 100 # bytes per second
bucket = Leakage(rate)
time.sleep(1) # Wait for a second to allow refill
self.assertEqual(bucket.consume(150), 100) # Only 100 bytes should be allowed
def test_repeated_consume_with_partial_refill(self) -> None:
# Test repeated consumption with partial refill
rate = 100 # bytes per second
bucket = Leakage(rate)
time.sleep(1) # Allow tokens to accumulate
bucket.consume(80) # Consume 80 bytes, should leave 20
time.sleep(0.5) # Wait half a second to refill by 50 bytes
self.assertEqual(bucket.consume(50), 50) # 50 bytes should be available now
def test_negative_token_guard(self) -> None:
# Ensure tokens do not go negative
rate = 100 # bytes per second
bucket = Leakage(rate)
time.sleep(1) # Allow tokens to accumulate
bucket.consume(150) # Consume all available tokens
self.assertEqual(bucket.consume(10), 0) # Should return 0 as no tokens are left
self.assertEqual(bucket.tokens, 0) # Tokens should not be negative