Re #580 - New functionality - Random "jitter" delay to requests (#681)

pull/691/head
dgtlmoon 3 years ago committed by GitHub
parent ba8cf2c8cf
commit a07ca4b136
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1287,8 +1287,11 @@ def notification_runner():
# Thread runner to check every minute, look for new watches to feed into the Queue. # Thread runner to check every minute, look for new watches to feed into the Queue.
def ticker_thread_check_time_launch_checks(): def ticker_thread_check_time_launch_checks():
import random
from changedetectionio import update_worker from changedetectionio import update_worker
import logging
recheck_time_minimum_seconds = int(os.getenv('MINIMUM_SECONDS_RECHECK_TIME', 20))
print("System env MINIMUM_SECONDS_RECHECK_TIME", recheck_time_minimum_seconds)
# Spin up Workers that do the fetching # Spin up Workers that do the fetching
# Can be overriden by ENV or use the default settings # Can be overriden by ENV or use the default settings
@ -1321,14 +1324,12 @@ def ticker_thread_check_time_launch_checks():
while update_q.qsize() >= 2000: while update_q.qsize() >= 2000:
time.sleep(1) time.sleep(1)
# Check for watches outside of the time threshold to put in the thread queue.
now = time.time()
recheck_time_minimum_seconds = int(os.getenv('MINIMUM_SECONDS_RECHECK_TIME', 60)) recheck_time_system_seconds = int(datastore.threshold_seconds)
recheck_time_system_seconds = datastore.threshold_seconds
# Check for watches outside of the time threshold to put in the thread queue.
for uuid in watch_uuid_list: for uuid in watch_uuid_list:
now = time.time()
watch = datastore.data['watching'].get(uuid) watch = datastore.data['watching'].get(uuid)
if not watch: if not watch:
logging.error("Watch: {} no longer present.".format(uuid)) logging.error("Watch: {} no longer present.".format(uuid))
@ -1339,20 +1340,33 @@ def ticker_thread_check_time_launch_checks():
continue continue
# If they supplied an individual entry minutes to threshold. # If they supplied an individual entry minutes to threshold.
threshold = now
watch_threshold_seconds = watch.threshold_seconds() watch_threshold_seconds = watch.threshold_seconds()
if watch_threshold_seconds: threshold = watch_threshold_seconds if watch_threshold_seconds > 0 else recheck_time_system_seconds
threshold -= watch_threshold_seconds
else: # #580 - Jitter plus/minus amount of time to make the check seem more random to the server
threshold -= recheck_time_system_seconds jitter = datastore.data['settings']['requests'].get('jitter_seconds', 0)
if jitter > 0:
if watch.jitter_seconds == 0:
watch.jitter_seconds = random.uniform(-abs(jitter), jitter)
# Yeah, put it in the queue, it's more than time
if watch['last_checked'] <= max(threshold, recheck_time_minimum_seconds): seconds_since_last_recheck = now - watch['last_checked']
if seconds_since_last_recheck >= (threshold + watch.jitter_seconds) and seconds_since_last_recheck >= recheck_time_minimum_seconds:
if not uuid in running_uuids and uuid not in update_q.queue: if not uuid in running_uuids and uuid not in update_q.queue:
print("Queued watch UUID {} last checked at {} queued at {:0.2f} jitter {:0.2f}s, {:0.2f}s since last checked".format(uuid,
watch['last_checked'],
now,
watch.jitter_seconds,
now - watch['last_checked']))
# Into the queue with you
update_q.put(uuid) update_q.put(uuid)
# Wait a few seconds before checking the list again # Reset for next time
time.sleep(3) watch.jitter_seconds = 0
# Wait before checking the list again - saves CPU
time.sleep(1)
# Should be low so we can break this out in testing # Should be low so we can break this out in testing
app.config.exit.wait(1) app.config.exit.wait(1)

@ -363,7 +363,9 @@ class watchForm(commonSettingsForm):
class globalSettingsRequestForm(Form): class globalSettingsRequestForm(Form):
time_between_check = FormField(TimeBetweenCheckForm) time_between_check = FormField(TimeBetweenCheckForm)
proxy = RadioField('Proxy') proxy = RadioField('Proxy')
jitter_seconds = IntegerField('Random jitter seconds ± check',
render_kw={"style": "width: 5em;"},
validators=[validators.NumberRange(min=0, message="Should contain zero or more seconds")])
# datastore.data['settings']['application'].. # datastore.data['settings']['application']..
class globalSettingsApplicationForm(commonSettingsForm): class globalSettingsApplicationForm(commonSettingsForm):

@ -23,6 +23,7 @@ class model(dict):
'requests': { 'requests': {
'timeout': 15, # Default 15 seconds 'timeout': 15, # Default 15 seconds
'time_between_check': {'weeks': None, 'days': None, 'hours': 3, 'minutes': None, 'seconds': None}, 'time_between_check': {'weeks': None, 'days': None, 'hours': 3, 'minutes': None, 'seconds': None},
'jitter_seconds': 0,
'workers': 10, # Number of threads, lower is better for slow connections 'workers': 10, # Number of threads, lower is better for slow connections
'proxy': None # Preferred proxy connection 'proxy': None # Preferred proxy connection
}, },

@ -13,7 +13,6 @@ from changedetectionio.notification import (
class model(dict): class model(dict):
__newest_history_key = None __newest_history_key = None
__history_n=0 __history_n=0
__base_config = { __base_config = {
'url': None, 'url': None,
'tag': None, 'tag': None,
@ -48,7 +47,8 @@ class model(dict):
'time_between_check': {'weeks': None, 'days': None, 'hours': None, 'minutes': None, 'seconds': None}, 'time_between_check': {'weeks': None, 'days': None, 'hours': None, 'minutes': None, 'seconds': None},
'webdriver_delay': None 'webdriver_delay': None
} }
jitter_seconds = 0
mtable = {'seconds': 1, 'minutes': 60, 'hours': 3600, 'days': 86400, 'weeks': 86400 * 7}
def __init__(self, *arg, **kw): def __init__(self, *arg, **kw):
import uuid import uuid
self.update(self.__base_config) self.update(self.__base_config)
@ -157,8 +157,7 @@ class model(dict):
def threshold_seconds(self): def threshold_seconds(self):
seconds = 0 seconds = 0
mtable = {'seconds': 1, 'minutes': 60, 'hours': 3600, 'days': 86400, 'weeks': 86400 * 7} for m, n in self.mtable.items():
for m, n in mtable.items():
x = self.get('time_between_check', {}).get(m, None) x = self.get('time_between_check', {}).get(m, None)
if x: if x:
seconds += x * n seconds += x * n

@ -9,6 +9,8 @@
# exit when any command fails # exit when any command fails
set -e set -e
export MINIMUM_SECONDS_RECHECK_TIME=0
find tests/test_*py -type f|while read test_name find tests/test_*py -type f|while read test_name
do do
echo "TEST RUNNING $test_name" echo "TEST RUNNING $test_name"

@ -159,12 +159,11 @@ class ChangeDetectionStore:
def threshold_seconds(self): def threshold_seconds(self):
seconds = 0 seconds = 0
mtable = {'seconds': 1, 'minutes': 60, 'hours': 3600, 'days': 86400, 'weeks': 86400 * 7} mtable = {'seconds': 1, 'minutes': 60, 'hours': 3600, 'days': 86400, 'weeks': 86400 * 7}
minimum_seconds_recheck_time = int(os.getenv('MINIMUM_SECONDS_RECHECK_TIME', 60))
for m, n in mtable.items(): for m, n in mtable.items():
x = self.__data['settings']['requests']['time_between_check'].get(m) x = self.__data['settings']['requests']['time_between_check'].get(m)
if x: if x:
seconds += x * n seconds += x * n
return max(seconds, minimum_seconds_recheck_time) return seconds
@property @property
def has_unviewed(self): def has_unviewed(self):

@ -32,6 +32,11 @@
{{ render_field(form.requests.form.time_between_check, class="time-check-widget") }} {{ render_field(form.requests.form.time_between_check, class="time-check-widget") }}
<span class="pure-form-message-inline">Default time for all watches, when the watch does not have a specific time setting.</span> <span class="pure-form-message-inline">Default time for all watches, when the watch does not have a specific time setting.</span>
</div> </div>
<div class="pure-control-group">
{{ render_field(form.requests.form.jitter_seconds, class="jitter_seconds") }}
<span class="pure-form-message-inline">Example - 3 seconds random jitter could trigger up to 3 seconds earlier or up to 3 seconds later</span>
</div>
<div class="pure-control-group"> <div class="pure-control-group">
{% if not hide_remove_pass %} {% if not hide_remove_pass %}
{% if current_user.is_authenticated %} {% if current_user.is_authenticated %}

Loading…
Cancel
Save