Skip to content

Commit

Permalink
#1688: when we detect multiple congestion events (more than XPRA_CONG…
Browse files Browse the repository at this point in the history
…ESTION_WARNING_EVENT_COUNT=10 in the last 10 seconds), send a notification to the client with an optional action to lower the bandwidth limit

git-svn-id: https://xpra.org/svn/Xpra/trunk@18194 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed Jan 30, 2018
1 parent 4fcde68 commit 16464be
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 10 deletions.
2 changes: 2 additions & 0 deletions src/xpra/client/ui_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2114,12 +2114,14 @@ def on_server_setting_changed(self, setting, cb):

def _process_setting_change(self, packet):
setting, value = packet[1:3]
setting = bytestostr(setting)
#convert "hello" / "setting" variable names to client variables:
if setting in (
"bell", "randr", "cursors", "notifications", "dbus-proxy", "clipboard",
"clipboard-direction", "session_name",
"sharing", "sharing-toggle", "lock", "lock-toggle",
"start-new-commands", "client-shutdown", "webcam",
"bandwidth-limit",
):
setattr(self, "server_%s" % setting.replace("-", "_"), value)
else:
Expand Down
18 changes: 12 additions & 6 deletions src/xpra/server/server_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -504,13 +504,19 @@ def _process_notification_close(self, _proto, packet):
if active:
self.notifications_forwarder.NotificationClosed(nid, reason)

def _process_notification_action(self, _proto, packet):
def _process_notification_action(self, proto, packet):
assert self.notifications
nid, action_key = packet[1:3]
active = self.notifications_forwarder.is_notification_active(nid)
notifylog("notification-action nid=%i, action key=%s, active=%s", nid, action_key, active)
if active:
self.notifications_forwarder.ActionInvoked(nid, action_key)
ss = self._server_sources.get(proto)
assert ss
client_callback = ss.notification_callbacks.get(nid)
if client_callback:
client_callback(nid, action_key)
else:
active = self.notifications_forwarder.is_notification_active(nid)
notifylog("notification-action nid=%i, action key=%s, active=%s", nid, action_key, active)
if active:
self.notifications_forwarder.ActionInvoked(nid, action_key)


def init_pulseaudio(self):
Expand Down Expand Up @@ -1341,7 +1347,7 @@ def get_window_id(wid):
bandwidth_limit = self.get_client_bandwidth_limit(proto)
ServerSourceClass = self.get_server_source_class()
ss = ServerSourceClass(proto, drop_client,
self.idle_add, self.timeout_add, self.source_remove,
self.idle_add, self.timeout_add, self.source_remove, self.setting_changed,
self.idle_timeout, self.idle_timeout_cb, self.idle_grace_timeout_cb,
self._socket_dir, self.unix_socket_paths, not is_request, self.dbus_control,
self.get_transient_for, self.get_focus, self.get_cursor_data,
Expand Down
62 changes: 59 additions & 3 deletions src/xpra/server/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
from xpra.make_thread import start_thread
from xpra.os_util import platform_name, Queue, get_machine_id, get_user_uuid, monotonic_time, BytesIOClass, strtobytes, bytestostr, WIN32, POSIX
from xpra.server.background_worker import add_work_item
from xpra.platform.paths import get_icon_dir
from xpra.util import csv, std, typedict, updict, flatten_dict, notypedict, get_screen_info, envint, envbool, AtomicInteger, \
CLIENT_PING_TIMEOUT, WORKSPACE_UNSET, DEFAULT_METADATA_SUPPORTED
def no_legacy_names(v):
Expand All @@ -65,6 +66,7 @@ def no_legacy_names(v):
PING_DETAILS = envbool("XPRA_PING_DETAILS", True)
PING_TIMEOUT = envint("XPRA_PING_TIMEOUT", 60)
BANDWIDTH_DETECTION = envbool("XPRA_BANDWIDTH_DETECTION", True)
CONGESTION_WARNING_EVENT_COUNT = envint("XPRA_CONGESTION_WARNING_EVENT_COUNT", 10)

PRINTER_LOCATION_STRING = os.environ.get("XPRA_PRINTER_LOCATION_STRING", "via xpra")
PROPERTIES_DEBUG = [x.strip() for x in os.environ.get("XPRA_WINDOW_PROPERTIES_DEBUG", "").split(",")]
Expand Down Expand Up @@ -230,7 +232,7 @@ class ServerSource(FileTransferHandler):
and added to the damage_packet_queue.
"""

def __init__(self, protocol, disconnect_cb, idle_add, timeout_add, source_remove,
def __init__(self, protocol, disconnect_cb, idle_add, timeout_add, source_remove, setting_changed,
idle_timeout, idle_timeout_cb, idle_grace_timeout_cb,
socket_dir, unix_socket_paths, log_disconnect, dbus_control,
get_transient_for, get_focus, get_cursor_data_cb,
Expand All @@ -247,7 +249,7 @@ def __init__(self, protocol, disconnect_cb, idle_add, timeout_add, source_remove
speaker_codecs, microphone_codecs,
default_quality, default_min_quality,
default_speed, default_min_speed):
log("ServerSource%s", (protocol, disconnect_cb, idle_add, timeout_add, source_remove,
log("ServerSource%s", (protocol, disconnect_cb, idle_add, timeout_add, source_remove, setting_changed,
idle_timeout, idle_timeout_cb, idle_grace_timeout_cb,
socket_dir, unix_socket_paths, log_disconnect, dbus_control,
get_transient_for, get_focus,
Expand All @@ -274,6 +276,7 @@ def __init__(self, protocol, disconnect_cb, idle_add, timeout_add, source_remove
self.idle_add = idle_add
self.timeout_add = timeout_add
self.source_remove = source_remove
self.setting_changed = setting_changed
self.idle = False
self.idle_timeout = idle_timeout
self.idle_timeout_cb = idle_timeout_cb
Expand Down Expand Up @@ -426,6 +429,7 @@ def init_vars(self):
self.send_bell = False
self.send_notifications = False
self.send_notifications_actions = False
self.notification_callbacks = {}
self.send_windows = True
self.pointer_grabs = False
self.randr_notify = False
Expand Down Expand Up @@ -455,6 +459,8 @@ def init_vars(self):
self.double_click_distance = -1, -1
self.bandwidth_limit = self.server_bandwidth_limit
self.soft_bandwidth_limit = self.bandwidth_limit
self.bandwidth_warnings = True
self.bandwidth_warning_time = 0
#what we send back in hello packet:
self.ui_client = True
self.wants_aliases = True
Expand Down Expand Up @@ -1987,7 +1993,7 @@ def bell(self, wid, device, percent, pitch, duration, bell_class, bell_id, bell_
return
self.send_async("bell", wid, device, percent, pitch, duration, bell_class, bell_id, bell_name)

def notify(self, dbus_id, nid, app_name, replaces_nid, app_icon, summary, body, actions, hints, expire_timeout, icon):
def notify(self, dbus_id, nid, app_name, replaces_nid, app_icon, summary, body, actions, hints, expire_timeout, icon, user_callback=None):
args = (dbus_id, nid, app_name, replaces_nid, app_icon, summary, body, actions, hints, expire_timeout, icon)
notifylog("notify%s types=%s", args, tuple(type(x) for x in args))
if not self.send_notifications:
Expand All @@ -1996,6 +2002,8 @@ def notify(self, dbus_id, nid, app_name, replaces_nid, app_icon, summary, body,
if self.suspended:
notifylog("client %s is suspended, notification not sent", self)
return False
if user_callback:
self.notification_callbacks[nid] = user_callback
#this is one of the few places where we actually do care about character encoding:
try:
summary = summary.encode("utf8")
Expand Down Expand Up @@ -2508,6 +2516,54 @@ def record_congestion_event(self, source, late_pct=0, send_speed=0):
now = monotonic_time()
gs.last_congestion_time = now
gs.congestion_send_speed.append((now, late_pct, send_speed))
if self.bandwidth_warnings and now-self.bandwidth_warning_time>60:
#enough congestion events?
min_time = now-10
count = len(tuple(True for x in gs.congestion_send_speed if x[0]>min_time))
if count>CONGESTION_WARNING_EVENT_COUNT:
self.bandwidth_warning_time = now
dbus_id = ""
nid = 2**31
summary = "Network Performance Issue"
body = "Your network connection is struggling to keep up,\n" + \
"consider lowering the bandwidth limit,\n" + \
"or lowering the picture quality"
actions = []
if self.bandwidth_limit==0 or self.bandwidth_limit>1*1000*1000:
actions += ["lower-bandwidth", "Lower bandwidth limit"]
#if self.default_min_quality>10:
# actions += ["lower-quality", "Lower quality"]
actions += ["ignore", "Ignore"]
hints = {}
expire_timeout = 10
icon = ""
try:
icon_filename = os.path.join(get_icon_dir(), "connect.png")
if os.path.exists(icon_filename):
from xpra.notifications.common import parse_image_path
icon = parse_image_path(icon_filename) or ""
except Exception:
notifylog.error("Error: failed to load network notification icon", exc_info=True)
self.notify(dbus_id, nid, "Xpra", 0, "", summary, body, actions, hints, expire_timeout, icon, user_callback=self.congestion_notification_callback)

def congestion_notification_callback(self, nid, action_id):
log("congestion_notification_callback(%i, %s)", nid, action_id)
if action_id=="lower-bandwidth":
bandwidth_limit = 50*1024*1024
if self.bandwidth_limit>256*1024:
bandwidth_limit = self.bandwidth_limit//2
css = 50*1024*1024
if self.statistics.avg_congestion_send_speed>256*1024:
#round up:
css = int(1+self.statistics.avg_congestion_send_speed//16/1024)*16*1024
self.bandwidth_limit = min(bandwidth_limit, css)
self.setting_changed("bandwidth-limit", self.bandwidth_limit)
#elif action_id=="lower-quality":
# self.default_min_quality = max(1, self.default_min_quality-15)
# self.set_min_quality(self.default_min_quality)
# self.setting_changed("min-quality", self.default_min_quality)
elif action_id=="ignore":
self.bandwidth_warnings = False


def queue_size(self):
Expand Down
2 changes: 1 addition & 1 deletion src/xpra/server/window/video_subregion.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def set_exclusion_zones(self, zones):

def set_auto_refresh_delay(self, d):
refreshlog("subregion auto-refresh delay: %s", d)
assert isinstance(d, int)
assert isinstance(d, int),"delay is not an int: %s (%s)" % (d, type(d))
self.auto_refresh_delay = d

def cancel_refresh_timer(self):
Expand Down
1 change: 1 addition & 0 deletions src/xpra/server/window/window_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -1183,6 +1183,7 @@ def update_refresh_attributes(self):
self.refresh_speed = rs

def do_set_auto_refresh_delay(self, min_delay, delay):
refreshlog("do_set_auto_refresh_delay%s", (min_delay, delay))
self.min_auto_refresh_delay = min_delay
self.base_auto_refresh_delay = delay

Expand Down

0 comments on commit 16464be

Please sign in to comment.