2017-03-02 02:14:56 +00:00
|
|
|
import calendar
|
|
|
|
import collections
|
|
|
|
import datetime
|
2014-01-29 21:59:42 +00:00
|
|
|
import httplib
|
2017-03-02 02:14:56 +00:00
|
|
|
import HydrusConstants as HC
|
|
|
|
import HydrusData
|
|
|
|
import HydrusSerialisable
|
2015-06-10 19:40:25 +00:00
|
|
|
import socket
|
2017-01-25 22:56:55 +00:00
|
|
|
import ssl
|
2017-03-02 02:14:56 +00:00
|
|
|
import threading
|
|
|
|
import time
|
2014-01-29 21:59:42 +00:00
|
|
|
|
2017-03-02 02:14:56 +00:00
|
|
|
def ConvertBandwidthRuleToString( rule ):
|
|
|
|
|
|
|
|
( bandwidth_type, time_delta, max_allowed ) = rule
|
|
|
|
|
|
|
|
if max_allowed == 0:
|
|
|
|
|
|
|
|
return 'No requests currently permitted.'
|
|
|
|
|
|
|
|
|
|
|
|
if bandwidth_type == HC.BANDWIDTH_TYPE_DATA:
|
|
|
|
|
|
|
|
s = HydrusData.ConvertIntToBytes( max_allowed )
|
|
|
|
|
|
|
|
elif bandwidth_type == HC.BANDWIDTH_TYPE_REQUESTS:
|
|
|
|
|
|
|
|
s = HydrusData.ConvertIntToPrettyString( max_allowed )
|
|
|
|
|
|
|
|
|
|
|
|
if time_delta is None:
|
|
|
|
|
|
|
|
s += ' per month'
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
s += ' per ' + HydrusData.ConvertTimeDeltaToPrettyString( time_delta )
|
|
|
|
|
|
|
|
|
|
|
|
return s
|
|
|
|
|
2017-01-25 22:56:55 +00:00
|
|
|
def GetLocalConnection( port, https = False ):
|
2015-06-10 19:40:25 +00:00
|
|
|
|
|
|
|
old_socket = httplib.socket.socket
|
|
|
|
|
|
|
|
httplib.socket.socket = socket._socketobject
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
2017-01-25 22:56:55 +00:00
|
|
|
if https:
|
|
|
|
|
|
|
|
context = ssl.SSLContext( ssl.PROTOCOL_SSLv23 )
|
|
|
|
context.options |= ssl.OP_NO_SSLv2
|
|
|
|
context.options |= ssl.OP_NO_SSLv3
|
|
|
|
|
|
|
|
connection = httplib.HTTPSConnection( '127.0.0.1', port, timeout = 8, context = context )
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
connection = httplib.HTTPConnection( '127.0.0.1', port, timeout = 8 )
|
|
|
|
|
2015-06-10 19:40:25 +00:00
|
|
|
|
|
|
|
connection.connect()
|
|
|
|
|
|
|
|
finally:
|
|
|
|
|
|
|
|
httplib.socket.socket = old_socket
|
|
|
|
|
|
|
|
|
|
|
|
return connection
|
2017-01-25 22:56:55 +00:00
|
|
|
|
2017-03-02 02:14:56 +00:00
|
|
|
class BandwidthRules( HydrusSerialisable.SerialisableBase ):
|
|
|
|
|
|
|
|
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_BANDWIDTH_RULES
|
|
|
|
SERIALISABLE_VERSION = 1
|
|
|
|
|
|
|
|
def __init__( self ):
|
|
|
|
|
|
|
|
HydrusSerialisable.SerialisableBase.__init__( self )
|
|
|
|
|
|
|
|
self._lock = threading.Lock()
|
|
|
|
|
|
|
|
self._rules = set()
|
|
|
|
|
|
|
|
|
|
|
|
def _GetSerialisableInfo( self ):
|
|
|
|
|
|
|
|
return list( self._rules )
|
|
|
|
|
|
|
|
|
|
|
|
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
|
|
|
|
|
|
|
|
# tuples converted to lists via json
|
|
|
|
|
|
|
|
self._rules = set( ( tuple( rule_list ) for rule_list in serialisable_info ) )
|
|
|
|
|
|
|
|
|
|
|
|
def AddRule( self, bandwidth_type, time_delta, max_allowed ):
|
|
|
|
|
|
|
|
with self._lock:
|
|
|
|
|
|
|
|
rule = ( bandwidth_type, time_delta, max_allowed )
|
|
|
|
|
|
|
|
self._rules.add( rule )
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def GetUsageStringsAndGaugeTuples( self, bandwidth_tracker, threshold = 3600 ):
|
|
|
|
|
|
|
|
with self._lock:
|
|
|
|
|
|
|
|
rows = []
|
|
|
|
|
|
|
|
for ( bandwidth_type, time_delta, max_allowed ) in self._rules:
|
|
|
|
|
|
|
|
time_is_less_than_threshold = time_delta is not None and time_delta < threshold
|
|
|
|
|
|
|
|
if time_is_less_than_threshold or max_allowed == 0:
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
usage = bandwidth_tracker.GetUsage( bandwidth_type, time_delta )
|
|
|
|
|
|
|
|
s = 'used '
|
|
|
|
|
|
|
|
if bandwidth_type == HC.BANDWIDTH_TYPE_DATA:
|
|
|
|
|
|
|
|
s += HydrusData.ConvertValueRangeToBytes( usage, max_allowed )
|
|
|
|
|
|
|
|
elif bandwidth_type == HC.BANDWIDTH_TYPE_REQUESTS:
|
|
|
|
|
|
|
|
s += HydrusData.ConvertValueRangeToPrettyString( usage, max_allowed ) + ' requests'
|
|
|
|
|
|
|
|
|
|
|
|
if time_delta is None:
|
|
|
|
|
|
|
|
s += ' this month'
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
s += ' in the past ' + HydrusData.ConvertTimeDeltaToPrettyString( time_delta )
|
|
|
|
|
|
|
|
|
|
|
|
rows.append( ( s, ( usage, max_allowed ) ) )
|
|
|
|
|
|
|
|
|
|
|
|
return rows
|
|
|
|
|
|
|
|
|
|
|
|
def GetRules( self ):
|
|
|
|
|
|
|
|
with self._lock:
|
|
|
|
|
|
|
|
return list( self._rules )
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-06-07 22:05:15 +00:00
|
|
|
def OK( self, bandwidth_tracker ):
|
2017-03-02 02:14:56 +00:00
|
|
|
|
|
|
|
with self._lock:
|
|
|
|
|
|
|
|
for ( bandwidth_type, time_delta, max_allowed ) in self._rules:
|
|
|
|
|
|
|
|
if max_allowed == 0:
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
if bandwidth_tracker.GetUsage( bandwidth_type, time_delta ) > max_allowed:
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_BANDWIDTH_RULES ] = BandwidthRules
|
|
|
|
|
|
|
|
class BandwidthTracker( HydrusSerialisable.SerialisableBase ):
|
|
|
|
|
|
|
|
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_BANDWIDTH_TRACKER
|
|
|
|
SERIALISABLE_VERSION = 1
|
|
|
|
|
|
|
|
# I want to track and query using smaller periods even when the total time delta is larger than the next step up to increase granularity
|
|
|
|
# for instance, querying minutes for 90 mins time delta is more smooth than watching a juddery sliding two hour window
|
|
|
|
MAX_SECONDS_TIME_DELTA = 240
|
|
|
|
MAX_MINUTES_TIME_DELTA = 180 * 60
|
|
|
|
MAX_HOURS_TIME_DELTA = 72 * 3600
|
|
|
|
MAX_DAYS_TIME_DELTA = 31 * 86400
|
|
|
|
|
|
|
|
CACHE_MAINTENANCE_TIME_DELTA = 120
|
|
|
|
|
|
|
|
def __init__( self ):
|
|
|
|
|
|
|
|
HydrusSerialisable.SerialisableBase.__init__( self )
|
|
|
|
|
|
|
|
self._lock = threading.Lock()
|
|
|
|
|
|
|
|
self._next_cache_maintenance_timestamp = HydrusData.GetNow() + self.CACHE_MAINTENANCE_TIME_DELTA
|
|
|
|
|
|
|
|
self._months_bytes = collections.Counter()
|
|
|
|
self._days_bytes = collections.Counter()
|
|
|
|
self._hours_bytes = collections.Counter()
|
|
|
|
self._minutes_bytes = collections.Counter()
|
|
|
|
self._seconds_bytes = collections.Counter()
|
|
|
|
|
|
|
|
self._months_requests = collections.Counter()
|
|
|
|
self._days_requests = collections.Counter()
|
|
|
|
self._hours_requests = collections.Counter()
|
|
|
|
self._minutes_requests = collections.Counter()
|
|
|
|
self._seconds_requests = collections.Counter()
|
|
|
|
|
|
|
|
|
|
|
|
def _GetSerialisableInfo( self ):
|
|
|
|
|
|
|
|
dicts_flat = []
|
|
|
|
|
|
|
|
for d in ( self._months_bytes, self._days_bytes, self._hours_bytes, self._minutes_bytes, self._seconds_bytes, self._months_requests, self._days_requests, self._hours_requests, self._minutes_requests, self._seconds_requests ):
|
|
|
|
|
|
|
|
dicts_flat.append( d.items() )
|
|
|
|
|
|
|
|
|
|
|
|
return dicts_flat
|
|
|
|
|
|
|
|
|
|
|
|
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
|
|
|
|
|
|
|
|
counters = [ collections.Counter( dict( flat_dict ) ) for flat_dict in serialisable_info ]
|
|
|
|
|
|
|
|
self._months_bytes = counters[ 0 ]
|
|
|
|
self._days_bytes = counters[ 1 ]
|
|
|
|
self._hours_bytes = counters[ 2 ]
|
|
|
|
self._minutes_bytes = counters[ 3 ]
|
|
|
|
self._seconds_bytes = counters[ 4 ]
|
|
|
|
|
|
|
|
self._months_requests = counters[ 5 ]
|
|
|
|
self._days_requests = counters[ 6 ]
|
|
|
|
self._hours_requests = counters[ 7 ]
|
|
|
|
self._minutes_requests = counters[ 8 ]
|
|
|
|
self._seconds_requests = counters[ 9 ]
|
|
|
|
|
|
|
|
|
|
|
|
def _GetMonthTime( self, dt ):
|
|
|
|
|
|
|
|
( year, month ) = ( dt.year, dt.month )
|
|
|
|
|
|
|
|
month_dt = datetime.datetime( year, month, 1 )
|
|
|
|
|
|
|
|
month_time = calendar.timegm( month_dt.timetuple() )
|
|
|
|
|
|
|
|
return month_time
|
|
|
|
|
|
|
|
|
|
|
|
def _GetTimes( self, dt ):
|
|
|
|
|
|
|
|
# collapse each time portion to the latest timestamp it covers
|
|
|
|
|
|
|
|
( year, month, day, hour, minute ) = ( dt.year, dt.month, dt.day, dt.hour, dt.minute )
|
|
|
|
|
|
|
|
month_dt = datetime.datetime( year, month, 1 )
|
|
|
|
day_dt = datetime.datetime( year, month, day )
|
|
|
|
hour_dt = datetime.datetime( year, month, day, hour )
|
|
|
|
minute_dt = datetime.datetime( year, month, day, hour, minute )
|
|
|
|
|
|
|
|
month_time = calendar.timegm( month_dt.timetuple() )
|
|
|
|
day_time = calendar.timegm( day_dt.timetuple() )
|
|
|
|
hour_time = calendar.timegm( hour_dt.timetuple() )
|
|
|
|
minute_time = calendar.timegm( minute_dt.timetuple() )
|
|
|
|
|
|
|
|
second_time = calendar.timegm( dt.timetuple() )
|
|
|
|
|
|
|
|
return ( month_time, day_time, hour_time, minute_time, second_time )
|
|
|
|
|
|
|
|
|
|
|
|
def _GetUsage( self, bandwidth_type, time_delta ):
|
|
|
|
|
|
|
|
if time_delta is None:
|
|
|
|
|
|
|
|
dt = datetime.datetime.utcnow()
|
|
|
|
|
|
|
|
month_time = self._GetMonthTime( dt )
|
|
|
|
|
|
|
|
if bandwidth_type == HC.BANDWIDTH_TYPE_DATA:
|
|
|
|
|
|
|
|
return self._months_bytes[ month_time ]
|
|
|
|
|
|
|
|
elif bandwidth_type == HC.BANDWIDTH_TYPE_REQUESTS:
|
|
|
|
|
|
|
|
return self._months_requests[ month_time ]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if bandwidth_type == HC.BANDWIDTH_TYPE_DATA:
|
|
|
|
|
|
|
|
if time_delta < self.MAX_SECONDS_TIME_DELTA:
|
|
|
|
|
|
|
|
window = 1
|
|
|
|
counter = self._seconds_bytes
|
|
|
|
|
|
|
|
elif time_delta < self.MAX_MINUTES_TIME_DELTA:
|
|
|
|
|
|
|
|
window = 60
|
|
|
|
counter = self._minutes_bytes
|
|
|
|
|
|
|
|
elif time_delta < self.MAX_HOURS_TIME_DELTA:
|
|
|
|
|
|
|
|
window = 3600
|
|
|
|
counter = self._hours_bytes
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
window = 86400
|
|
|
|
counter = self._days_bytes
|
|
|
|
|
|
|
|
|
|
|
|
elif bandwidth_type == HC.BANDWIDTH_TYPE_REQUESTS:
|
|
|
|
|
|
|
|
if time_delta < self.MAX_SECONDS_TIME_DELTA:
|
|
|
|
|
|
|
|
window = 1
|
|
|
|
counter = self._seconds_requests
|
|
|
|
|
|
|
|
elif time_delta < self.MAX_MINUTES_TIME_DELTA:
|
|
|
|
|
|
|
|
window = 60
|
|
|
|
counter = self._minutes_requests
|
|
|
|
|
|
|
|
elif time_delta < self.MAX_HOURS_TIME_DELTA:
|
|
|
|
|
|
|
|
window = 3600
|
|
|
|
counter = self._hours_requests
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
window = 86400
|
|
|
|
counter = self._days_requests
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# we need the 'window' because this tracks backets from the first timestamp and we want to include if since lands anywhere in the bracket
|
|
|
|
|
|
|
|
time_delta += window
|
|
|
|
|
|
|
|
since = HydrusData.GetNow() - time_delta
|
|
|
|
|
|
|
|
self._MaintainCache()
|
|
|
|
|
|
|
|
return sum( ( value for ( key, value ) in counter.items() if key >= since ) )
|
|
|
|
|
|
|
|
|
|
|
|
def _MaintainCache( self ):
|
|
|
|
|
|
|
|
if HydrusData.TimeHasPassed( self._next_cache_maintenance_timestamp ):
|
|
|
|
|
|
|
|
now = HydrusData.GetNow()
|
|
|
|
|
|
|
|
oldest_second = now - self.MAX_SECONDS_TIME_DELTA
|
|
|
|
oldest_minute = now - self.MAX_MINUTES_TIME_DELTA
|
|
|
|
oldest_hour = now - self.MAX_HOURS_TIME_DELTA
|
|
|
|
oldest_day = now - self.MAX_DAYS_TIME_DELTA
|
|
|
|
|
|
|
|
def clear_counter( counter, timestamp ):
|
|
|
|
|
|
|
|
bad_keys = [ key for key in counter.keys() if key < timestamp ]
|
|
|
|
|
|
|
|
for bad_key in bad_keys:
|
|
|
|
|
|
|
|
del counter[ bad_key ]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
clear_counter( self._days_bytes, oldest_day )
|
|
|
|
clear_counter( self._days_requests, oldest_day )
|
|
|
|
clear_counter( self._hours_bytes, oldest_hour )
|
|
|
|
clear_counter( self._hours_requests, oldest_hour )
|
|
|
|
clear_counter( self._minutes_bytes, oldest_minute )
|
|
|
|
clear_counter( self._minutes_requests, oldest_minute )
|
|
|
|
clear_counter( self._seconds_bytes, oldest_second )
|
|
|
|
clear_counter( self._seconds_requests, oldest_second )
|
|
|
|
|
|
|
|
self._next_cache_maintenance_timestamp = HydrusData.GetNow() + self.CACHE_MAINTENANCE_TIME_DELTA
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def GetCurrentMonthSummary( self ):
|
|
|
|
|
2017-05-31 21:50:53 +00:00
|
|
|
with self._lock:
|
|
|
|
|
|
|
|
num_bytes = self._GetUsage( HC.BANDWIDTH_TYPE_DATA, None )
|
|
|
|
num_requests = self._GetUsage( HC.BANDWIDTH_TYPE_REQUESTS, None )
|
|
|
|
|
|
|
|
return 'used ' + HydrusData.ConvertIntToBytes( num_bytes ) + ' in ' + HydrusData.ConvertIntToPrettyString( num_requests ) + ' requests this month'
|
|
|
|
|
2017-03-02 02:14:56 +00:00
|
|
|
|
|
|
|
|
|
|
|
def GetUsage( self, bandwidth_type, time_delta ):
|
|
|
|
|
|
|
|
with self._lock:
|
|
|
|
|
|
|
|
return self._GetUsage( bandwidth_type, time_delta )
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def RequestMade( self, num_bytes ):
|
|
|
|
|
|
|
|
with self._lock:
|
|
|
|
|
|
|
|
dt = datetime.datetime.utcnow()
|
|
|
|
|
|
|
|
( month_time, day_time, hour_time, minute_time, second_time ) = self._GetTimes( dt )
|
|
|
|
|
|
|
|
self._months_bytes[ month_time ] += num_bytes
|
|
|
|
self._months_requests[ month_time ] += 1
|
|
|
|
|
|
|
|
self._days_bytes[ day_time ] += num_bytes
|
|
|
|
self._days_requests[ day_time ] += 1
|
|
|
|
|
|
|
|
self._hours_bytes[ hour_time ] += num_bytes
|
|
|
|
self._hours_requests[ hour_time ] += 1
|
|
|
|
|
|
|
|
self._minutes_bytes[ minute_time ] += num_bytes
|
|
|
|
self._minutes_requests[ minute_time ] += 1
|
|
|
|
|
|
|
|
self._seconds_bytes[ second_time ] += num_bytes
|
|
|
|
self._seconds_requests[ second_time ] += 1
|
|
|
|
|
|
|
|
self._MaintainCache()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_BANDWIDTH_TRACKER ] = BandwidthTracker
|
2017-06-07 22:05:15 +00:00
|
|
|
|
|
|
|
class TransferSpeedTracker( object ):
|
|
|
|
|
|
|
|
CLEAN_PERIOD = 30
|
|
|
|
|
|
|
|
LONG_DELTA = 15
|
|
|
|
SHORT_DELTA = 3
|
|
|
|
|
|
|
|
SHORT_WEIGHT = 3
|
|
|
|
|
|
|
|
def __init__( self ):
|
|
|
|
|
|
|
|
self._lock = threading.Lock()
|
|
|
|
|
|
|
|
self._current_speed = 0
|
|
|
|
|
|
|
|
self._current_speed_timestamp = 0
|
|
|
|
self._current_speed_dirty = False
|
|
|
|
|
|
|
|
self._timestamps_to_amounts = collections.Counter()
|
|
|
|
|
|
|
|
self._next_clean_time = HydrusData.GetNow() + self.CLEAN_PERIOD
|
|
|
|
|
|
|
|
|
|
|
|
def _CleanHistory( self ):
|
|
|
|
|
|
|
|
if HydrusData.TimeHasPassed( self._next_clean_time ):
|
|
|
|
|
|
|
|
now = HydrusData.GetNow()
|
|
|
|
|
|
|
|
invalid_indices = [ timestamp for timestamp in self._timestamps_to_amounts.keys() if timestamp < now - self.LONG_DELTA ]
|
|
|
|
|
|
|
|
for timestamp in invalid_indices:
|
|
|
|
|
|
|
|
del self._timestamps_to_amounts[ timestamp ]
|
|
|
|
|
|
|
|
|
|
|
|
self._next_clean_time = HydrusData.GetNow() + self.CLEAN_PERIOD
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def DataTransferred( self, num_bytes ):
|
|
|
|
|
|
|
|
with self._lock:
|
|
|
|
|
|
|
|
self._CleanHistory()
|
|
|
|
|
|
|
|
now = HydrusData.GetNow()
|
|
|
|
|
|
|
|
self._timestamps_to_amounts[ now ] += num_bytes
|
|
|
|
|
|
|
|
self._current_speed_dirty = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def GetCurrentSpeed( self ):
|
|
|
|
|
|
|
|
with self._lock:
|
|
|
|
|
|
|
|
self._CleanHistory()
|
|
|
|
|
|
|
|
now = HydrusData.GetNow()
|
|
|
|
|
|
|
|
if self._current_speed_dirty or self._current_speed_timestamp != now:
|
|
|
|
|
|
|
|
total_bytes = ( self._timestamps_to_amounts[ timestamp ] for timestamp in range( now - self.LONG_DELTA, now ) )
|
|
|
|
total_bytes += ( self._timestamps_to_amounts[ timestamp ] * self.SHORT_WEIGHT for timestamp in range( now - self.SHORT_DELTA, now ) )
|
|
|
|
|
|
|
|
total_weight = self.LONG_DELTA + ( self.SHORT_DELTA * self.SHORT_WEIGHT )
|
|
|
|
|
|
|
|
self._current_speed = total_bytes // total_weight # since this is in bytes, an int is fine and proper
|
|
|
|
|
|
|
|
self._current_speed_timestamp = now
|
|
|
|
self._current_speed_dirty = False
|
|
|
|
|
|
|
|
|
|
|
|
return self._current_speed
|
|
|
|
|
|
|
|
|
|
|
|
|