hydrus/hydrus/client/networking/ClientNetworkingBandwidth.py

675 lines
23 KiB
Python
Raw Normal View History

2018-04-18 22:10:15 +00:00
import collections
2020-05-20 21:36:02 +00:00
import threading
2021-01-13 21:48:58 +00:00
import typing
2020-05-20 21:36:02 +00:00
2020-04-22 21:00:35 +00:00
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusSerialisable
2021-04-07 21:26:45 +00:00
from hydrus.core.networking import HydrusNetworking
2018-04-18 22:10:15 +00:00
2020-07-29 20:52:44 +00:00
from hydrus.client import ClientConstants as CC
from hydrus.client.networking import ClientNetworkingContexts
2021-01-13 21:48:58 +00:00
class NetworkBandwidthManagerTrackerContainer( HydrusSerialisable.SerialisableBaseNamed ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_BANDWIDTH_MANAGER_TRACKER_CONTAINER
SERIALISABLE_NAME = 'Bandwidth Manager Tracker Container'
SERIALISABLE_VERSION = 1
def __init__( self, name, network_context = None, bandwidth_tracker = None ):
if network_context is None:
network_context = ClientNetworkingContexts.GLOBAL_NETWORK_CONTEXT
if bandwidth_tracker is None:
bandwidth_tracker = HydrusNetworking.BandwidthTracker()
HydrusSerialisable.SerialisableBaseNamed.__init__( self, name )
self.network_context = network_context
self.bandwidth_tracker = bandwidth_tracker
def _GetSerialisableInfo( self ):
serialisable_network_context = self.network_context.GetSerialisableTuple()
serialisable_bandwidth_tracker = self.bandwidth_tracker.GetSerialisableTuple()
return ( serialisable_network_context, serialisable_bandwidth_tracker )
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
( serialisable_network_context, serialisable_bandwidth_tracker ) = serialisable_info
self.network_context = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_network_context )
self.bandwidth_tracker = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_bandwidth_tracker )
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_BANDWIDTH_MANAGER_TRACKER_CONTAINER ] = NetworkBandwidthManagerTrackerContainer
2018-04-18 22:10:15 +00:00
class NetworkBandwidthManager( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_BANDWIDTH_MANAGER
SERIALISABLE_NAME = 'Bandwidth Manager'
SERIALISABLE_VERSION = 1
def __init__( self ):
HydrusSerialisable.SerialisableBase.__init__( self )
self.engine = None
self._dirty = False
self._lock = threading.Lock()
2018-08-22 21:10:59 +00:00
self._last_pages_gallery_query_timestamps = collections.defaultdict( lambda: 0 )
self._last_subscriptions_gallery_query_timestamps = collections.defaultdict( lambda: 0 )
self._last_watchers_query_timestamps = collections.defaultdict( lambda: 0 )
2021-01-13 21:48:58 +00:00
self._tracker_container_names_to_tracker_containers = {}
self._network_contexts_to_tracker_containers = {}
self._tracker_container_names = set()
self._dirty_tracker_container_names = set()
self._deletee_tracker_container_names = set()
2021-01-20 22:22:03 +00:00
self._my_bandwidth_tracker = HydrusNetworking.BandwidthTracker()
2018-04-18 22:10:15 +00:00
self._network_contexts_to_bandwidth_rules = collections.defaultdict( HydrusNetworking.BandwidthRules )
2018-05-23 21:05:06 +00:00
for context_type in [ CC.NETWORK_CONTEXT_GLOBAL, CC.NETWORK_CONTEXT_HYDRUS, CC.NETWORK_CONTEXT_DOMAIN, CC.NETWORK_CONTEXT_DOWNLOADER_PAGE, CC.NETWORK_CONTEXT_SUBSCRIPTION, CC.NETWORK_CONTEXT_WATCHER_PAGE ]:
2018-04-18 22:10:15 +00:00
self._network_contexts_to_bandwidth_rules[ ClientNetworkingContexts.NetworkContext( context_type ) ] = HydrusNetworking.BandwidthRules()
def _CanStartRequest( self, network_contexts ):
for network_context in network_contexts:
bandwidth_rules = self._GetRules( network_context )
2021-01-13 21:48:58 +00:00
bandwidth_tracker = self._GetTracker( network_context )
2018-04-18 22:10:15 +00:00
if not bandwidth_rules.CanStartRequest( bandwidth_tracker ):
return False
return True
def _GetRules( self, network_context ):
if network_context not in self._network_contexts_to_bandwidth_rules:
network_context = ClientNetworkingContexts.NetworkContext( network_context.context_type ) # i.e. the default
return self._network_contexts_to_bandwidth_rules[ network_context ]
def _GetSerialisableInfo( self ):
2021-01-13 21:48:58 +00:00
all_tracker_container_names = sorted( self._tracker_container_names )
2019-01-09 22:59:03 +00:00
all_serialisable_rules = [ ( network_context.GetSerialisableTuple(), rules.GetSerialisableTuple() ) for ( network_context, rules ) in list(self._network_contexts_to_bandwidth_rules.items()) ]
2018-04-18 22:10:15 +00:00
2021-01-13 21:48:58 +00:00
return ( all_tracker_container_names, all_serialisable_rules )
2018-04-18 22:10:15 +00:00
2021-01-13 21:48:58 +00:00
def _GetTracker( self, network_context: ClientNetworkingContexts.NetworkContext, making_it_dirty = False ):
2018-04-18 22:10:15 +00:00
2021-01-13 21:48:58 +00:00
if network_context not in self._network_contexts_to_tracker_containers:
2018-04-18 22:10:15 +00:00
2021-01-13 21:48:58 +00:00
bandwidth_tracker = HydrusNetworking.BandwidthTracker()
tracker_container_name = HydrusData.GenerateKey().hex()
2018-04-18 22:10:15 +00:00
2021-01-13 21:48:58 +00:00
tracker_container = NetworkBandwidthManagerTrackerContainer( tracker_container_name, network_context = network_context, bandwidth_tracker = bandwidth_tracker )
2018-04-18 22:10:15 +00:00
2021-01-13 21:48:58 +00:00
self._tracker_container_names_to_tracker_containers[ tracker_container_name ] = tracker_container
self._network_contexts_to_tracker_containers[ network_context ] = tracker_container
# note this discards ephemeral network contexts, which have temporary identifiers that are generally invisible to the user
if not network_context.IsEphemeral():
self._tracker_container_names.add( tracker_container_name )
self._dirty_tracker_container_names.add( tracker_container_name )
self._SetDirty()
tracker_container = self._network_contexts_to_tracker_containers[ network_context ]
if making_it_dirty and not network_context.IsEphemeral():
self._dirty_tracker_container_names.add( tracker_container.GetName() )
return tracker_container.bandwidth_tracker
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
( all_tracker_container_names, all_serialisable_rules ) = serialisable_info
self._tracker_container_names = set( all_tracker_container_names )
2018-04-18 22:10:15 +00:00
for ( serialisable_network_context, serialisable_rules ) in all_serialisable_rules:
network_context = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_network_context )
rules = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_rules )
if network_context.context_type == CC.NETWORK_CONTEXT_DOWNLOADER: # no longer use this
continue
self._network_contexts_to_bandwidth_rules[ network_context ] = rules
2021-01-20 22:22:03 +00:00
def _ReportDataUsed( self, network_contexts, num_bytes ):
for network_context in network_contexts:
bandwidth_tracker = self._GetTracker( network_context, making_it_dirty = True )
bandwidth_tracker.ReportDataUsed( num_bytes )
self._my_bandwidth_tracker.ReportDataUsed( num_bytes )
2018-04-18 22:10:15 +00:00
def _ReportRequestUsed( self, network_contexts ):
for network_context in network_contexts:
2021-01-13 21:48:58 +00:00
bandwidth_tracker = self._GetTracker( network_context, making_it_dirty = True )
bandwidth_tracker.ReportRequestUsed()
2018-04-18 22:10:15 +00:00
2021-01-20 22:22:03 +00:00
self._my_bandwidth_tracker.ReportRequestUsed()
2018-04-18 22:10:15 +00:00
def _SetDirty( self ):
self._dirty = True
2018-10-03 21:00:15 +00:00
def AlreadyHaveExactlyTheseBandwidthRules( self, network_context, bandwidth_rules ):
with self._lock:
if network_context in self._network_contexts_to_bandwidth_rules:
if self._network_contexts_to_bandwidth_rules[ network_context ].GetSerialisableTuple() == bandwidth_rules.GetSerialisableTuple():
return True
return False
def AutoAddDomainMetadatas( self, domain_metadatas ):
for domain_metadata in domain_metadatas:
if not domain_metadata.HasBandwidthRules():
return
with self._lock:
domain = domain_metadata.GetDomain()
network_context = ClientNetworkingContexts.NetworkContext( CC.NETWORK_CONTEXT_DOMAIN, domain )
bandwidth_rules = domain_metadata.GetBandwidthRules()
self._network_contexts_to_bandwidth_rules[ network_context ] = bandwidth_rules
2018-04-18 22:10:15 +00:00
def CanContinueDownload( self, network_contexts ):
with self._lock:
for network_context in network_contexts:
bandwidth_rules = self._GetRules( network_context )
2021-01-13 21:48:58 +00:00
bandwidth_tracker = self._GetTracker( network_context )
2018-04-18 22:10:15 +00:00
if not bandwidth_rules.CanContinueDownload( bandwidth_tracker ):
return False
return True
2018-07-18 21:07:15 +00:00
def CanDoWork( self, network_contexts, expected_requests = 1, expected_bytes = 1048576, threshold = 30 ):
2018-04-18 22:10:15 +00:00
with self._lock:
for network_context in network_contexts:
bandwidth_rules = self._GetRules( network_context )
2021-01-13 21:48:58 +00:00
bandwidth_tracker = self._GetTracker( network_context )
2018-04-18 22:10:15 +00:00
if not bandwidth_rules.CanDoWork( bandwidth_tracker, expected_requests = expected_requests, expected_bytes = expected_bytes, threshold = threshold ):
return False
return True
def CanStartRequest( self, network_contexts ):
with self._lock:
return self._CanStartRequest( network_contexts )
def DeleteRules( self, network_context ):
with self._lock:
if network_context.context_data is None:
return # can't delete 'default' network contexts
else:
if network_context in self._network_contexts_to_bandwidth_rules:
del self._network_contexts_to_bandwidth_rules[ network_context ]
self._SetDirty()
def DeleteHistory( self, network_contexts ):
with self._lock:
for network_context in network_contexts:
2021-01-13 21:48:58 +00:00
if network_context in self._network_contexts_to_tracker_containers:
2018-04-18 22:10:15 +00:00
2021-01-13 21:48:58 +00:00
tracker_container = self._network_contexts_to_tracker_containers[ network_context ]
2018-04-18 22:10:15 +00:00
2021-01-13 21:48:58 +00:00
del self._network_contexts_to_tracker_containers[ network_context ]
tracker_container_name = tracker_container.GetName()
if tracker_container_name in self._tracker_container_names_to_tracker_containers:
2018-04-18 22:10:15 +00:00
2021-01-13 21:48:58 +00:00
del self._tracker_container_names_to_tracker_containers[ tracker_container_name ]
2018-04-18 22:10:15 +00:00
2021-01-13 21:48:58 +00:00
self._tracker_container_names.discard( tracker_container_name )
2021-01-20 22:22:03 +00:00
self._dirty_tracker_container_names.discard( tracker_container_name )
2021-01-13 21:48:58 +00:00
self._deletee_tracker_container_names.add( tracker_container_name )
if network_context == ClientNetworkingContexts.GLOBAL_NETWORK_CONTEXT:
# just to reset it and have it in the system, so we have a 0 global context at all times
self._GetTracker( ClientNetworkingContexts.GLOBAL_NETWORK_CONTEXT )
2018-04-18 22:10:15 +00:00
self._SetDirty()
2021-01-13 21:48:58 +00:00
def GetBandwidthStringsAndGaugeTuples( self, network_context ):
with self._lock:
bandwidth_rules = self._GetRules( network_context )
bandwidth_tracker = self._GetTracker( network_context )
return bandwidth_rules.GetBandwidthStringsAndGaugeTuples( bandwidth_tracker )
def GetCurrentMonthSummary( self, network_context ):
with self._lock:
bandwidth_tracker = self._GetTracker( network_context )
return bandwidth_tracker.GetCurrentMonthSummary()
2018-04-18 22:10:15 +00:00
def GetDefaultRules( self ):
with self._lock:
result = []
2019-01-09 22:59:03 +00:00
for ( network_context, bandwidth_rules ) in list(self._network_contexts_to_bandwidth_rules.items()):
2018-04-18 22:10:15 +00:00
if network_context.IsDefault():
result.append( ( network_context, bandwidth_rules ) )
return result
2021-01-13 21:48:58 +00:00
def GetDeleteeTrackerNames( self ):
2018-04-18 22:10:15 +00:00
with self._lock:
2021-01-13 21:48:58 +00:00
return set( self._deletee_tracker_container_names )
2018-04-18 22:10:15 +00:00
2021-01-13 21:48:58 +00:00
def GetDirtyTrackerContainers( self ):
2018-04-18 22:10:15 +00:00
with self._lock:
2021-01-13 21:48:58 +00:00
return [ self._tracker_container_names_to_tracker_containers[ tracker_container_name ] for tracker_container_name in self._dirty_tracker_container_names ]
2018-04-18 22:10:15 +00:00
2021-01-20 22:22:03 +00:00
def GetMySessionTracker( self ):
with self._lock:
return self._my_bandwidth_tracker
2018-04-18 22:10:15 +00:00
def GetNetworkContextsForUser( self, history_time_delta_threshold = None ):
with self._lock:
result = set()
2021-01-13 21:48:58 +00:00
for tracker_container in self._network_contexts_to_tracker_containers.values():
network_context = tracker_container.network_context
2018-04-18 22:10:15 +00:00
if network_context.IsDefault() or network_context.IsEphemeral():
continue
2021-01-13 21:48:58 +00:00
bandwidth_tracker = tracker_container.bandwidth_tracker
2018-04-18 22:10:15 +00:00
if network_context != ClientNetworkingContexts.GLOBAL_NETWORK_CONTEXT and history_time_delta_threshold is not None:
if bandwidth_tracker.GetUsage( HC.BANDWIDTH_TYPE_REQUESTS, history_time_delta_threshold ) == 0:
continue
result.add( network_context )
return result
def GetRules( self, network_context ):
with self._lock:
return self._GetRules( network_context )
def GetTracker( self, network_context ):
with self._lock:
2021-01-13 21:48:58 +00:00
if network_context in self._network_contexts_to_tracker_containers:
2018-04-18 22:10:15 +00:00
2021-01-13 21:48:58 +00:00
return self._GetTracker( network_context )
2018-04-18 22:10:15 +00:00
else:
return HydrusNetworking.BandwidthTracker()
2019-01-23 22:19:16 +00:00
def GetWaitingEstimateAndContext( self, network_contexts ):
2018-04-18 22:10:15 +00:00
with self._lock:
estimates = []
for network_context in network_contexts:
bandwidth_rules = self._GetRules( network_context )
2021-01-13 21:48:58 +00:00
bandwidth_tracker = self._GetTracker( network_context )
2018-04-18 22:10:15 +00:00
2019-01-23 22:19:16 +00:00
estimates.append( ( bandwidth_rules.GetWaitingEstimate( bandwidth_tracker ), network_context ) )
2018-04-18 22:10:15 +00:00
2019-01-23 22:19:16 +00:00
estimates.sort( key = lambda pair: -pair[0] ) # biggest first
2018-04-18 22:10:15 +00:00
if len( estimates ) == 0:
2019-01-23 22:19:16 +00:00
return ( 0, ClientNetworkingContexts.GLOBAL_NETWORK_CONTEXT )
2018-04-18 22:10:15 +00:00
else:
2019-01-23 22:19:16 +00:00
return estimates[0]
2018-04-18 22:10:15 +00:00
2021-01-13 21:48:58 +00:00
def HasDirtyTrackerContainers( self ):
with self._lock:
return len( self._dirty_tracker_container_names ) > 0 or len( self._deletee_tracker_container_names ) > 0
2018-10-03 21:00:15 +00:00
def HasRules( self, network_context ):
with self._lock:
return network_context in self._network_contexts_to_bandwidth_rules
2018-04-18 22:10:15 +00:00
def IsDirty( self ):
with self._lock:
return self._dirty
def ReportDataUsed( self, network_contexts, num_bytes ):
with self._lock:
2021-01-20 22:22:03 +00:00
self._ReportDataUsed( network_contexts, num_bytes )
2018-04-18 22:10:15 +00:00
def ReportRequestUsed( self, network_contexts ):
with self._lock:
self._ReportRequestUsed( network_contexts )
def SetClean( self ):
with self._lock:
self._dirty = False
2021-01-13 21:48:58 +00:00
self._dirty_tracker_container_names = set()
self._deletee_tracker_container_names = set()
def SetDirty( self ):
with self._lock:
self._SetDirty()
2018-04-18 22:10:15 +00:00
def SetRules( self, network_context, bandwidth_rules ):
with self._lock:
2021-01-13 21:48:58 +00:00
if len( bandwidth_rules.GetRules() ) == 0 and not network_context.IsDefault():
2018-04-18 22:10:15 +00:00
if network_context in self._network_contexts_to_bandwidth_rules:
del self._network_contexts_to_bandwidth_rules[ network_context ]
else:
self._network_contexts_to_bandwidth_rules[ network_context ] = bandwidth_rules
self._SetDirty()
2021-01-13 21:48:58 +00:00
def SetTrackerContainers( self, tracker_containers: typing.Collection[ NetworkBandwidthManagerTrackerContainer ], set_all_trackers_dirty = False ):
with self._lock:
self._tracker_container_names_to_tracker_containers = {}
self._network_contexts_to_tracker_containers = {}
self._tracker_container_names = set()
self._dirty_tracker_container_names = set()
self._deletee_tracker_container_names = set()
for tracker_container in tracker_containers:
tracker_container_name = tracker_container.GetName()
network_context = tracker_container.network_context
self._tracker_container_names_to_tracker_containers[ tracker_container_name ] = tracker_container
self._network_contexts_to_tracker_containers[ network_context ] = tracker_container
if not network_context.IsEphemeral():
self._tracker_container_names.add( tracker_container_name )
if set_all_trackers_dirty:
self._dirty_tracker_container_names.add( tracker_container_name )
2018-08-22 21:10:59 +00:00
def TryToConsumeAGalleryToken( self, second_level_domain, query_type ):
with self._lock:
if query_type == 'download page':
timestamps_dict = self._last_pages_gallery_query_timestamps
delay = HG.client_controller.new_options.GetInteger( 'gallery_page_wait_period_pages' )
elif query_type == 'subscription':
timestamps_dict = self._last_subscriptions_gallery_query_timestamps
delay = HG.client_controller.new_options.GetInteger( 'gallery_page_wait_period_subscriptions' )
elif query_type == 'watcher':
timestamps_dict = self._last_watchers_query_timestamps
delay = HG.client_controller.new_options.GetInteger( 'watcher_page_wait_period' )
next_timestamp = timestamps_dict[ second_level_domain ] + delay
if HydrusData.TimeHasPassed( next_timestamp ):
timestamps_dict[ second_level_domain ] = HydrusData.GetNow()
return ( True, 0 )
else:
return ( False, next_timestamp )
raise NotImplementedError( 'Unknown query type' )
2018-04-18 22:10:15 +00:00
def TryToStartRequest( self, network_contexts ):
# this wraps canstart and reportrequest in one transaction to stop 5/1 rq/s happening due to race condition
with self._lock:
if not self._CanStartRequest( network_contexts ):
return False
self._ReportRequestUsed( network_contexts )
return True
def UsesDefaultRules( self, network_context ):
with self._lock:
return network_context not in self._network_contexts_to_bandwidth_rules
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_BANDWIDTH_MANAGER ] = NetworkBandwidthManager