Make timeout parameters configurable.

This commit is contained in:
Dave Shawley 2021-03-11 07:31:24 -05:00
parent 65b5bacbee
commit 28e369c122
No known key found for this signature in database
GPG key ID: 44A9C9992CCFAB82
3 changed files with 70 additions and 15 deletions

View file

@ -13,13 +13,11 @@ class Application(web.Application):
This mix-in is configured by the ``statsd`` settings key. The
value should be a dictionary with the following keys.
+--------+---------------------------------------------+
| host | the statsd host to send metrics to |
+--------+---------------------------------------------+
| port | TCP port number that statsd is listening on |
+--------+---------------------------------------------+
| prefix | segment to prefix to metrics |
+--------+---------------------------------------------+
+-------------------+---------------------------------------------+
| host | the statsd host to send metrics to |
+-------------------+---------------------------------------------+
| port | TCP port number that statsd is listening on |
+-------------------+---------------------------------------------+
*host* defaults to the :envvar:`STATSD_HOST` environment variable.
If this value is not set, then the statsd connector **WILL NOT**
@ -29,10 +27,30 @@ class Application(web.Application):
with a back up default of 8125 if the environment variable is not
set.
The following keys MAY also be specified to fine tune the statsd
connection.
+-------------------+---------------------------------------------+
| prefix | segment to prefix to metrics. |
+-------------------+---------------------------------------------+
| reconnect_timeout | number of seconds to sleep after a statsd |
| | connection attempt fails |
+-------------------+---------------------------------------------+
| wait_timeout | number of seconds to wait for a metric to |
| | arrive on the queue before verifying the |
| | connection |
+-------------------+---------------------------------------------+
*prefix* defaults to ``applications.<service>.<environment>`` where
*<service>* and *<environment>* are replaced with the keys from
`settings` if they are present.
*reconnect_timeout* defaults to 1.0 seconds which limits the
aggressiveness of creating new TCP connections.
*wait_timeout* defaults to 0.1 seconds which ensures that the
processor quickly responds to connection faults.
"""
def __init__(self, *args, **settings):
statsd_settings = settings.setdefault('statsd', {})
@ -63,8 +81,15 @@ class Application(web.Application):
"""
statsd_settings = self.settings['statsd']
if statsd_settings.get('_connector') is None:
connector = statsd.Connector(host=statsd_settings['host'],
port=statsd_settings['port'])
kwargs = {
'host': statsd_settings['host'],
'port': statsd_settings['port'],
}
if 'reconnect_sleep' in statsd_settings:
kwargs['reconnect_sleep'] = statsd_settings['reconnect_sleep']
if 'wait_timeout' in statsd_settings:
kwargs['wait_timeout'] = statsd_settings['wait_timeout']
connector = statsd.Connector(**kwargs)
await connector.start()
self.settings['statsd']['_connector'] = connector

View file

@ -8,6 +8,8 @@ class Connector:
:param host: statsd server to send metrics to
:param port: TCP port that the server is listening on
:param kwargs: additional keyword parameters are passed
to the :class:`.Processor` initializer
This class maintains a TCP connection to a statsd server and
sends metric lines to it asynchronously. You must call the
@ -29,8 +31,8 @@ class Connector:
sends the metric payloads.
"""
def __init__(self, host: str, port: int = 8125):
self.processor = Processor(host=host, port=port)
def __init__(self, host: str, port: int = 8125, **kwargs):
self.processor = Processor(host=host, port=port, **kwargs)
self._processor_task = None
async def start(self):
@ -75,6 +77,10 @@ class Processor(asyncio.Protocol):
:param host: statsd server to send metrics to
:param port: TCP port that the server is listening on
:param reconnect_sleep: number of seconds to sleep after socket
error occurs when connecting
:param wait_timeout: number os seconds to wait for a message to
arrive on the queue
This class implements :class:`~asyncio.Protocol` for the statsd
TCP connection. The :meth:`.run` method is run as a background
@ -126,7 +132,12 @@ class Processor(asyncio.Protocol):
until the task stops.
"""
def __init__(self, *, host, port: int = 8125):
def __init__(self,
*,
host,
port: int = 8125,
reconnect_sleep: float = 1.0,
wait_timeout: float = 0.1):
super().__init__()
if not host:
raise RuntimeError('host must be set')
@ -135,6 +146,8 @@ class Processor(asyncio.Protocol):
self.host = host
self.port = port
self._reconnect_sleep = reconnect_sleep
self._wait_timeout = wait_timeout
self.running = asyncio.Event()
self.stopped = asyncio.Event()
@ -205,9 +218,9 @@ class Processor(asyncio.Protocol):
self.logger.warning('statsd server connection lost')
self.connected.clear()
async def _connect_if_necessary(self, wait_time: float = 0.1):
async def _connect_if_necessary(self):
try:
await asyncio.wait_for(self.connected.wait(), wait_time)
await asyncio.wait_for(self.connected.wait(), self._wait_timeout)
except asyncio.TimeoutError:
try:
self.logger.debug('starting connection to %s:%s', self.host,
@ -219,12 +232,14 @@ class Processor(asyncio.Protocol):
except IOError as error:
self.logger.warning('connection to %s:%s failed: %s',
self.host, self.port, error)
await asyncio.sleep(self._reconnect_sleep)
async def _process_metric(self):
processing_failed_send = False
if not self._failed_sends:
try:
metric = await asyncio.wait_for(self.queue.get(), 0.1)
metric = await asyncio.wait_for(self.queue.get(),
self._wait_timeout)
self.logger.debug('received %r from queue', metric)
self.queue.task_done()
except asyncio.TimeoutError:

View file

@ -115,6 +115,21 @@ class ApplicationTests(testing.AsyncTestCase):
})
self.io_loop.run_sync(app.stop_statsd)
def test_optional_parameters(self):
app = sprockets_statsd.mixins.Application(
statsd={
'host': 'localhost',
'port': '8125',
'reconnect_sleep': 0.5,
'wait_timeout': 0.25,
})
self.io_loop.run_sync(app.start_statsd)
processor = app.settings['statsd']['_connector'].processor
self.assertEqual(0.5, processor._reconnect_sleep)
self.assertEqual(0.25, processor._wait_timeout)
self.io_loop.run_sync(app.stop_statsd)
class RequestHandlerTests(testing.AsyncHTTPTestCase):
def setUp(self):