From 4f643772903cd8eaa336445b10974efbb937cdfe Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Mon, 23 Oct 2017 16:00:40 +0000 Subject: [PATCH] #417: bandwidth-limit option * adjust batch delay to slow things down when we're using up the bandwidth budget too quickly * delay compression of the next screen update when we have used up the budget * expose "bandwidth-limit" via xpra info * command line option, parsing, man page, etc.. git-svn-id: https://xpra.org/svn/Xpra/trunk@17232 3bb7dfac-3a0b-4e04-842a-767bc560f471 --- src/NEWS | 3 +- src/etc/xpra/conf.d/10_network.conf.in | 11 +++++ src/man/xpra.1 | 9 ++++ src/xpra/client/ui_client_base.py | 16 ++++--- src/xpra/scripts/config.py | 6 ++- src/xpra/scripts/main.py | 45 ++++++++++++++++--- src/xpra/server/server_base.py | 1 + src/xpra/server/server_core.py | 3 ++ src/xpra/server/source.py | 44 +++++++++++++++++- .../server/window/batch_delay_calculator.py | 4 +- src/xpra/server/window/window_source.py | 21 ++++++--- src/xpra/server/window/window_stats.py | 25 ++++++++++- 12 files changed, 163 insertions(+), 25 deletions(-) diff --git a/src/NEWS b/src/NEWS index 0eba63707c..30b2cb90e4 100644 --- a/src/NEWS +++ b/src/NEWS @@ -1,9 +1,10 @@ -v2.2 (2017-09-30) +v2.2 (2017-10-23) ====================== -- support RFB clients (ie: VNC) with bind-rfb or rfb-upgrade options -- UDP transport (experimental) with bind-udp and udp://host:port URLs -- TCP sockets can be upgrade to Websockets and / or SSL, RFB -- multiple bind options for all socket types supported: tcp, ssl, ws, wss, udp, rfb + -- bandwidth-limit option -- "xpra sessions" browser tool for both mDNS and local sessions -- support arbitrary resolutions with Xvfb (not with Xdummy yet) -- new OpenGL backends, with support for GTK3 on most platforms diff --git a/src/etc/xpra/conf.d/10_network.conf.in b/src/etc/xpra/conf.d/10_network.conf.in index 3668b7d068..13f767dde6 100644 --- a/src/etc/xpra/conf.d/10_network.conf.in +++ b/src/etc/xpra/conf.d/10_network.conf.in @@ -47,3 +47,14 @@ idle-timeout = 0 # Server idle timeout in seconds: #server-idle-timeout = 600 server-idle-timeout = 0 + +# Bandwidth limit: +#no limit: +#bandwidth-limit = 0 +#1Mbps: +#bandwidth-limit = 1000000 +#bandwidth-limit = 1000Kbps +#bandwidth-limit = 1M +#10Mbps: +#bandwidth-limit = 10Mbps +bandwidth-limit = 0 diff --git a/src/man/xpra.1 b/src/man/xpra.1 index 4c7a950e34..9727b6a66f 100644 --- a/src/man/xpra.1 +++ b/src/man/xpra.1 @@ -62,6 +62,7 @@ xpra \- viewer for remote, persistent X applications [\fB\-\-microphone\fP=\fIyes\fP|\fIno\fP] [\fB\-\-microphone\-codec\fP=\fICODEC\fP] [\fB\-\-sharing\fP=\fIyes\fP|\fIno\fP] +[\fB\-\-bandwidth\-limit\fP=\fIBITSPERSECOND\fP] [\fB\-\-bind\fP=\fIBIND_LOCATION\fP] [\fB\-\-bind\-tcp\fP=\fI[HOST]:PORT\fP] [\fB\-\-bind\-udp\fP=\fI[HOST]:PORT\fP] @@ -175,6 +176,7 @@ xpra \- viewer for remote, persistent X applications [\fB\-\-mouse\-polling\fP=\fIVALUE\fP] [\fB\-\-socket\-dir\fP=\fIDIR\fP] [\fB\-\-socket\-dirs\fP=\fIDIRS\fP] +[\fB\-\-bandwidth\-limit\fP=\fIBITSPERSECOND\fP] [\fB\-\-pings\fP=\fIyes\fP|\fIno\fP] [\fB\-\-encryption\fP=\fICIPHER\fP] [\fB\-\-encryption\-keyfile\fP=\fIFILENAME\fP] @@ -204,6 +206,7 @@ xpra \- viewer for remote, persistent X applications [\fB\-\-speaker\-codec\fP=\fICODEC\fP] [\fB\-\-microphone\fP=\fIon\fP|\fIoff\fP|\fIdisabled\fP] [\fB\-\-microphone\-codec\fP=\fICODEC\fP] +[\fB\-\-bandwidth\-limit\fP=\fIBITSPERSECOND\fP] [\fB\-\-bind\fP=\fISOCKET|DIRECTORY\fP] [\fB\-\-bind\-tcp\fP=\fI[HOST]:PORT\fP] [\fB\-\-bind\-udp\fP=\fI[HOST]:PORT\fP] @@ -704,6 +707,12 @@ This may be a security risk if you are using xpra to constrain what the clients can execute on the server. .TP +\fB\-\-bandwidth\-limit\fP=\fIBITSPERSECOND\fP +Restrict bandwidth usage to below the limit given. +The client's value cannot raise the limit of the server. +The value may be specified using standard units, ie: +\fI1Mbps\fP or \fI500K\fP. +.TP .SS Options for start, start-desktop, upgrade, proxy and shadow .TP diff --git a/src/xpra/client/ui_client_base.py b/src/xpra/client/ui_client_base.py index 7fdcd98cc9..80dde7214c 100644 --- a/src/xpra/client/ui_client_base.py +++ b/src/xpra/client/ui_client_base.py @@ -178,11 +178,6 @@ def __init__(self): self.server_display = None self.server_randr = False self.pixel_counter = deque(maxlen=1000) - self.server_ping_latency = deque(maxlen=1000) - self.server_load = None - self.client_ping_latency = deque(maxlen=1000) - self._server_ok = True - self.last_ping_echoed_time = 0 self.server_last_info = None self.info_request_pending = False self.screen_size_change_pending = False @@ -190,6 +185,14 @@ def __init__(self): self.core_encodings = None self.encoding = None + #network state: + self.server_ping_latency = deque(maxlen=1000) + self.server_load = None + self.client_ping_latency = deque(maxlen=1000) + self._server_ok = True + self.last_ping_echoed_time = 0 + self.bandwidth_limit = 0 + #webcam: self.webcam_option = "" self.webcam_forwarding = False @@ -333,6 +336,7 @@ def init(self, opts): self.title = opts.title self.session_name = opts.session_name self.auto_refresh_delay = opts.auto_refresh_delay + self.bandwidth_limit = opts.bandwidth_limit if opts.max_size: try: self.max_window_size = [int(x.strip()) for x in opts.max_size.split("x", 1)] @@ -1568,6 +1572,8 @@ def make_hello(self): "sound.ogg-latency-fix" : True, "av-sync" : self.av_sync, "av-sync.delay.default" : 0, #start at 0 and rely on sound-control packets to set the correct value + #network: + "bandwidth-limit" : self.bandwidth_limit, }) updict(capabilities, "window", { "raise" : True, diff --git a/src/xpra/scripts/config.py b/src/xpra/scripts/config.py index bbc6c5aa64..58e6aba52d 100755 --- a/src/xpra/scripts/config.py +++ b/src/xpra/scripts/config.py @@ -538,6 +538,7 @@ def may_create_user_config(xpra_conf_filename=DEFAULT_XPRA_CONF_FILENAME): "gid" : int, "min-port" : int, "rfb-upgrade" : int, + "bandwidth-limit" : int, #float options: "auto-refresh-delay": float, #boolean options: @@ -625,7 +626,7 @@ def may_create_user_config(xpra_conf_filename=DEFAULT_XPRA_CONF_FILENAME): #keep track of the options added since v1, #so we can generate command lines that work with older supported versions: -OPTIONS_ADDED_SINCE_V1 = ["attach", "open-files", "pixel-depth", "uid", "gid", "chdir", "min-port", "rfb-upgrade"] +OPTIONS_ADDED_SINCE_V1 = ["attach", "open-files", "pixel-depth", "uid", "gid", "chdir", "min-port", "rfb-upgrade", "bandwidth-limit"] CLIENT_OPTIONS = ["title", "username", "password", "session-name", "dock-icon", "tray-icon", "window-icon", @@ -687,7 +688,7 @@ def may_create_user_config(xpra_conf_filename=DEFAULT_XPRA_CONF_FILENAME): "mmap", "mmap-group", "mdns", "auth", "vsock-auth", "tcp-auth", "udp-auth", "ws-auth", "wss-auth", "ssl-auth", "rfb-auth", "bind", "bind-vsock", "bind-tcp", "bind-udp", "bind-ssl", "bind-ws", "bind-wss", "bind-rfb", - "rfb-upgrade", + "rfb-upgrade", "bandwidth-limit", "start", "start-child", "start-after-connect", "start-child-after-connect", "start-on-connect", "start-child-on-connect", @@ -894,6 +895,7 @@ def addtrailingslash(v): "gid" : getgid(), "min-port" : 1024, "rfb-upgrade" : 5, + "bandwidth-limit" : 0, "auto-refresh-delay": 0.15, "daemon" : CAN_DAEMONIZE, "start-via-proxy" : None, diff --git a/src/xpra/scripts/main.py b/src/xpra/scripts/main.py index 7151daf1fe..ed4c98ff7b 100755 --- a/src/xpra/scripts/main.py +++ b/src/xpra/scripts/main.py @@ -206,9 +206,12 @@ def err(*args): def do_replace_option(cmdline, oldoption, newoption): - if oldoption in cmdline: - cmdline.remove(oldoption) - cmdline.append(newoption) + for i, x in enumerate(cmdline): + if x==oldoption: + cmdline[i] = newoption + elif newoption.find("=")<0 and x.startswith("%s=" % oldoption): + cmdline[i] = "%s=%s" % (newoption, x.split("=", 1)[1]) + def do_legacy_bool_parse(cmdline, optionname, newoptionname=None): #find --no-XYZ or --XYZ #and replace it with --XYZ=yes|no @@ -617,10 +620,13 @@ def ignore(defaults): }) group = optparse.OptionGroup(parser, "Server Controlled Features", - "These options can be used to turn certain features on or off, " - "they can be specified on the client or on the server, " - "but the client cannot enable them if they are disabled on the server.") + "These options be specified on the client or on the server, " + "but the server's settings will have precedence over the client's.") parser.add_option_group(group) + replace_option("--bwlimit", "--bandwidth-limit") + group.add_option("--bandwidth-limit", action="store", + dest="bandwidth_limit", default=defaults.bandwidth_limit, + help="Limit the bandwidth used. The value is specified in bits per second, use the value '0' to disable restrictions. Default: '%default'.") replace_option("--readwrite", "--readonly=no") replace_option("--readonly", "--readonly=yes") group.add_option("--readonly", action="store", metavar="yes|no", @@ -1127,10 +1133,35 @@ def ignore(defaults): #and may have "none" or "all" special values fixup_options(options, defaults) + #special case for bandwidth-limit, which can be specified using units: + try: + import re + v = options.bandwidth_limit + if not v: + options.bandwidth_limit = 0 + else: + r = re.match('([0-9]*)(.*)', options.bandwidth_limit) + assert r + i = int(r.group(1)) + unit = r.group(2).lower() + if unit.endswith("bps"): + unit = unit[:-3] + if unit=="b": + pass + elif unit=="k": + i *= 1000 + elif unit=="m": + i *= 1000000 + elif unit=="g": + i *= 1000000000 + assert i>=250000, "value is too low" + options.bandwidth_limit = i + except Exception as e: + raise InitException("invalid bandwidth limit value '%s': %s" % (options.bandwidth_limit, e)) try: options.dpi = int(options.dpi) except Exception as e: - raise InitException("invalid dpi: %s" % e) + raise InitException("invalid dpi value '%s': %s" % (options.dpi, e)) if options.max_size: try: #split on "," or "x": diff --git a/src/xpra/server/server_base.py b/src/xpra/server/server_base.py index d0e43233bf..3c0442e3a5 100644 --- a/src/xpra/server/server_base.py +++ b/src/xpra/server/server_base.py @@ -1206,6 +1206,7 @@ def get_window_id(wid): self.window_filters, self.file_transfer, self.supports_mmap, self.mmap_filename, + self.bandwidth_limit, self.av_sync, self.core_encodings, self.encodings, self.default_encoding, self.scaling_control, self.sound_properties, diff --git a/src/xpra/server/server_core.py b/src/xpra/server/server_core.py index 576b655c29..fce059841b 100644 --- a/src/xpra/server/server_core.py +++ b/src/xpra/server/server_core.py @@ -187,6 +187,7 @@ def __init__(self): self.exit_with_client = False self.server_idle_timeout = 0 self.server_idle_timer = None + self.bandwidth_limit = 0 self.init_uuid() self.init_control_commands() @@ -208,6 +209,7 @@ def init(self, opts): self.session_name = opts.session_name set_name("Xpra", self.session_name or "Xpra") + self.bandwidth_limit = opts.bandwidth_limit self.unix_socket_paths = [] self._socket_dir = opts.socket_dir or opts.socket_dirs[0] self.encryption = opts.encryption @@ -1553,6 +1555,7 @@ def up(prefix, d): "sockets" : self.get_socket_info(), "encryption" : self.encryption or "", "tcp-encryption" : self.tcp_encryption or "", + "bandwidth-limit": self.bandwidth_limit, }) up("network", ni) up("threads", self.get_thread_info(proto)) diff --git a/src/xpra/server/source.py b/src/xpra/server/source.py index 9bc5187e88..6597162d9b 100644 --- a/src/xpra/server/source.py +++ b/src/xpra/server/source.py @@ -233,6 +233,7 @@ def __init__(self, protocol, disconnect_cb, idle_add, timeout_add, source_remove window_filters, file_transfer, supports_mmap, mmap_filename, + bandwidth_limit, av_sync, core_encodings, encodings, default_encoding, scaling_control, sound_properties, @@ -249,6 +250,7 @@ def __init__(self, protocol, disconnect_cb, idle_add, timeout_add, source_remove window_filters, file_transfer, supports_mmap, mmap_filename, + bandwidth_limit, av_sync, core_encodings, encodings, default_encoding, scaling_control, sound_properties, @@ -293,6 +295,8 @@ def __init__(self, protocol, disconnect_cb, idle_add, timeout_add, source_remove self.mmap_client_token = None #the token we write that the client may check self.mmap_client_token_index = 512 self.mmap_client_token_bytes = 0 + # network constraints: + self.server_bandwidth_limit = bandwidth_limit # mouse echo: self.mouse_show = False self.mouse_last_position = None @@ -433,6 +437,7 @@ def init_vars(self): self.vrefresh = -1 self.double_click_time = -1 self.double_click_distance = -1, -1 + self.bandwidth_limit = self.server_bandwidth_limit #what we send back in hello packet: self.ui_client = True self.wants_aliases = True @@ -490,20 +495,43 @@ def close(self): self.idle_add(ds.cleanup) + def update_bandwidth_limits(self): + if self.bandwidth_limit<=0: + return + #figure out how to distribute the bandwidth amongst the windows, + #we use the window size, + #(we should actually use the number of bytes actually sent: framerate, compression, etc..) + window_weight = {} + for wid, ws in self.window_sources.items(): + weight = 0 + if not ws.suspended: + ww, wh = ws.window_dimensions + #try to reserve bandwidth for at least one screen update, + #and add the number of pixels damaged: + weight = ww*wh + ws.statistics.get_damage_pixels() + window_weight[wid] = weight + log("update_bandwidth_limits() window weights=%s", window_weight) + total_weight = sum(window_weight.values()) + for wid, ws in self.window_sources.items(): + weight = window_weight.get(wid) + if weight is not None: + ws.bandwidth_limit = max(1, self.bandwidth_limit*weight/total_weight) + def recalculate_delays(self): """ calls update_averages() on ServerSource.statistics (GlobalStatistics) and WindowSource.statistics (WindowPerformanceStatistics) for each window id in calculate_window_ids, this runs in the worker thread. """ - log("recalculate_delays()") if self.is_closed(): return + self.update_bandwidth_limits() self.statistics.update_averages() wids = list(self.calculate_window_ids) #make a copy so we don't clobber new wids focus = self.get_focus() sources = self.window_sources.items() maximized_wids = [wid for wid, source in sources if source is not None and source.maximized] fullscreen_wids = [wid for wid, source in sources if source is not None and source.fullscreen] + log("recalculate_delays() wids=%s, focus=%s, maximized=%s, fullscreen=%s", wids, focus, maximized_wids, fullscreen_wids) for wid in wids: #this is safe because we only add to this set from other threads: self.calculate_window_ids.remove(wid) @@ -745,6 +773,12 @@ def parse_batch_int(value, varname): self.double_click_time = c.intget("double_click.time") self.double_click_distance = c.intpair("double_click.distance") self.window_frame_sizes = typedict(c.dictget("window.frame_sizes") or {}) + bandwidth_limit = c.intget("bandwidth-limit", 0) + if self.server_bandwidth_limit<=0: + self.bandwidth_limit = bandwidth_limit + else: + self.bandwidth_limit = min(self.server_bandwidth_limit, bandwidth_limit) + netlog("server bandwidth-limit=%s, client bandwidth-limit=%s, value=%s", self.server_bandwidth_limit, bandwidth_limit, self.bandwidth_limit) self.desktop_size = c.intpair("desktop_size") if self.desktop_size is not None: @@ -1559,6 +1593,7 @@ def get_info(self): "suspended" : self.suspended, "counter" : self.counter, "hello-sent" : self.hello_sent, + "bandwidth-limit" : self.bandwidth_limit, } if self.desktop_mode_size: info["desktop_mode_size"] = self.desktop_mode_size @@ -2273,8 +2308,10 @@ def make_window_source(self, wid, window): ws = self.window_sources.get(wid) if ws is None: batch_config = self.make_batch_config(wid, window) + ww, wh = window.get_dimensions() ws = WindowVideoSource( self.idle_add, self.timeout_add, self.source_remove, + ww, wh, self.queue_size, self.call_in_encode_thread, self.queue_packet, self.compressed_wrapper, self.statistics, wid, window, batch_config, self.auto_refresh_delay, @@ -2284,8 +2321,11 @@ def make_window_source(self, wid, window): self.encoding, self.encodings, self.core_encodings, self.window_icon_encodings, self.encoding_options, self.icons_encoding_options, self.rgb_formats, self.default_encoding_options, - self.mmap, self.mmap_size) + self.mmap, self.mmap_size, self.bandwidth_limit) self.window_sources[wid] = ws + if len(self.window_sources)>1: + #re-distribute bandwidth: + self.update_bandwidth_limits() return ws def damage(self, wid, window, x, y, w, h, options=None): diff --git a/src/xpra/server/window/batch_delay_calculator.py b/src/xpra/server/window/batch_delay_calculator.py index 08d4555876..a2f05d582d 100644 --- a/src/xpra/server/window/batch_delay_calculator.py +++ b/src/xpra/server/window/batch_delay_calculator.py @@ -26,7 +26,7 @@ def get_low_limit(mmap_enabled, window_dimensions): return low_limit -def calculate_batch_delay(wid, window_dimensions, has_focus, other_is_fullscreen, other_is_maximized, is_OR, soft_expired, batch, global_statistics, statistics): +def calculate_batch_delay(wid, window_dimensions, has_focus, other_is_fullscreen, other_is_maximized, is_OR, soft_expired, batch, global_statistics, statistics, bandwidth_limit): """ Calculates a new batch delay. We first gather some statistics, @@ -36,7 +36,7 @@ def calculate_batch_delay(wid, window_dimensions, has_focus, other_is_fullscreen low_limit = get_low_limit(global_statistics.mmap_size>0, window_dimensions) #for each indicator: (description, factor, weight) - factors = statistics.get_factors() + factors = statistics.get_factors(bandwidth_limit) statistics.target_latency = statistics.get_target_client_latency(global_statistics.min_client_latency, global_statistics.avg_client_latency) factors += global_statistics.get_factors(low_limit) #damage pixels waiting in the packet queue: (extract data for our window id only) diff --git a/src/xpra/server/window/window_source.py b/src/xpra/server/window/window_source.py index a933ac9830..a16efec965 100644 --- a/src/xpra/server/window/window_source.py +++ b/src/xpra/server/window/window_source.py @@ -85,6 +85,7 @@ class WindowSource(object): def __init__(self, idle_add, timeout_add, source_remove, + ww, wh, queue_size, call_in_encode_thread, queue_packet, compressed_wrapper, statistics, wid, window, batch_config, auto_refresh_delay, @@ -94,7 +95,7 @@ def __init__(self, encoding, encodings, core_encodings, window_icon_encodings, encoding_options, icons_encoding_options, rgb_formats, default_encoding_options, - mmap, mmap_size): + mmap, mmap_size, bandwidth_limit): self.idle_add = idle_add self.timeout_add = timeout_add self.source_remove = source_remove @@ -160,7 +161,7 @@ def __init__(self, self.is_tray = window.is_tray() self.is_shadow = window.is_shadow() self.has_alpha = window.has_alpha() - self.window_dimensions = 0, 0 + self.window_dimensions = ww, wh self.mapped_at = None self.fullscreen = not self.is_tray and window.get("fullscreen") self.scaling_control = default_encoding_options.intget("scaling.control", 1) #ServerSource sets defaults with the client's scaling.control value @@ -186,6 +187,8 @@ def __init__(self, self.max_small_regions = 10 self.max_bytes_percent = 25 self.small_packet_cost = 4096 + self.bandwidth_limit = bandwidth_limit + self.pixel_format = None #ie: BGRX try: self.image_depth = window.get_property("depth") @@ -292,6 +295,7 @@ def init_vars(self): self.scaling = None self.maximized = False # + self.bandwidth_limit = 0 self.max_small_regions = 0 self.max_bytes_percent = 0 self.small_packet_cost = 0 @@ -377,6 +381,7 @@ def get_info(self): info.update({ "dimensions" : self.window_dimensions, "suspended" : self.suspended or False, + "bandwidth-limit" : self.bandwidth_limit, "av-sync" : { "enabled" : self.av_sync, "current" : self.av_sync_delay, @@ -966,7 +971,7 @@ def calculate_batch_delay(self, has_focus, other_is_fullscreen, other_is_maximiz statslog("calculate_batch_delay for wid=%i, skipping - only %i bytes sent since the last update", self.wid, nbytes) return statslog("calculate_batch_delay for wid=%i, %i bytes sent since the last update", self.wid, nbytes) - calculate_batch_delay(self.wid, self.window_dimensions, has_focus, other_is_fullscreen, other_is_maximized, self.is_OR, self.soft_expired, self.batch_config, self.global_statistics, self.statistics) + calculate_batch_delay(self.wid, self.window_dimensions, has_focus, other_is_fullscreen, other_is_maximized, self.is_OR, self.soft_expired, self.batch_config, self.global_statistics, self.statistics, self.bandwidth_limit) self.statistics.last_recalculate = now self.update_av_sync_frame_delay() @@ -1172,7 +1177,7 @@ def damage_now(): self.expire_timer = self.timeout_add(delay, self.expire_delayed_region, delay) def must_batch(self, delay): - if FORCE_BATCH or self.batch_config.always or delay>self.batch_config.min_delay: + if FORCE_BATCH or self.batch_config.always or delay>self.batch_config.min_delay or self.bandwidth_limit>0: return True try: t, _ = self.batch_config.last_delays[-5] @@ -1273,7 +1278,7 @@ def may_send_delayed(self): self.wid, packets_backlog, self.batch_config.delay, actual_delay) #this method will fire again from damage_packet_acked return - #if we're here, there is no packet backlog, but there may be damage acks pending. + #if we're here, there is no packet backlog, but there may be damage acks pending or a bandwidth limit to honour, #if there are acks pending, may_send_delayed() should be called again from damage_packet_acked, #if not, we must either process the region now or set a timer to check again later def check_again(delay=actual_delay/10.0): @@ -1291,6 +1296,12 @@ def check_again(delay=actual_delay/10.0): else: self.do_send_delayed() return + if self.bandwidth_limit>0: + used = self.statistics.get_bits_encoded() + log("may_send_delayed() bandwidth limit=%i, used=%i : %i%%", self.bandwidth_limit, used, 100*used//self.bandwidth_limit) + if used>=self.bandwidth_limit: + check_again(50) + return pixels_encoding_backlog, enc_backlog_count = self.statistics.get_pixels_encoding_backlog() ww, wh = self.window_dimensions if pixels_encoding_backlog>=(ww*wh): diff --git a/src/xpra/server/window/window_stats.py b/src/xpra/server/window/window_stats.py index 29e55661c6..545ef968e8 100644 --- a/src/xpra/server/window/window_stats.py +++ b/src/xpra/server/window/window_stats.py @@ -100,7 +100,7 @@ def update_averages(self): self.avg_damage_out_latency, self.recent_damage_out_latency] self.max_latency = max(all_l) - def get_factors(self): + def get_factors(self, bandwidth_limit=0): factors = [] #ratio of "in" and "out" latency indicates network bottleneck: #(the difference between the two is the time it takes to send) @@ -152,6 +152,21 @@ def get_factors(self): info = {"elapsed" : int(1000.0*elapsed), "max_latency" : int(1000.0*self.max_latency)} factors.append((metric, info, target, weight)) + if bandwidth_limit>0: + #calculate how much bandwith we have used in the last second (in bps): + #encoding_stats.append((end, coding, w*h, bpp, len(data), end-start)) + cutoff = monotonic_time()-1 + used = sum(v[4] for v in self.encoding_stats if v[0]>cutoff) * 8 + info = { + "bandwidth-limit" : bandwidth_limit, + "bandwidth-used" : used, + } + #aim for 10% below the limit: + target = used*110.0/100.0/bandwidth_limit + #if we are getting close to or above the limit, + #the certainty of this factor goes up: + weight = max(0, target-1)*(5+logp(target)) + factors.append(("bandwidth-limit", info, target, weight)) return factors @@ -265,3 +280,11 @@ def get_pixels_encoding_backlog(self): pixels += w*h count += 1 return pixels, count + + def get_bits_encoded(self, elapsed=1): + cutoff = monotonic_time()-elapsed + return sum(v[4] for v in self.encoding_stats if v[0]>cutoff) * 8 + + def get_damage_pixels(self, elapsed=1): + cutoff = monotonic_time()-elapsed + return sum(v[3]*v[4] for v in self.last_damage_events if v[0]>cutoff)