Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/server/source/networkstate_mixin.py : 98%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2# This file is part of Xpra.
3# Copyright (C) 2010-2020 Antoine Martin <antoine@xpra.org>
4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
5# later version. See the file COPYING for details.
7import os
8import time
10from xpra.util import envbool, envint, typedict, CLIENT_PING_TIMEOUT
11from xpra.os_util import monotonic_time, POSIX
12from xpra.server.source.stub_source_mixin import StubSourceMixin
13from xpra.log import Logger
15log = Logger("network")
17PING_DETAILS = envbool("XPRA_PING_DETAILS", True)
18PING_TIMEOUT = envint("XPRA_PING_TIMEOUT", 60)
21class NetworkStateMixin(StubSourceMixin):
23 @classmethod
24 def is_needed(cls, caps : typedict) -> bool:
25 #the 'network-state' capability were only added in v4,
26 #so we have to enable the mixin by default:
27 return caps.boolget("network-state", True)
29 def init_state(self):
30 self.last_ping_echoed_time = 0
31 self.check_ping_echo_timers = {}
32 self.ping_timer = None
33 self.bandwidth_limit = 0
35 def cleanup(self):
36 self.cancel_ping_echo_timers()
37 self.cancel_ping_timer()
39 def get_caps(self) -> dict:
40 return {"ping-echo-sourceid" : True}
42 def get_info(self) -> dict:
43 lpe = 0
44 if self.last_ping_echoed_time>0:
45 lpe = int(monotonic_time()*1000-self.last_ping_echoed_time)
46 info = {
47 "bandwidth-limit" : {
48 "setting" : self.bandwidth_limit or 0,
49 },
50 "last-ping-echo" : lpe,
51 }
52 return info
54 ######################################################################
55 # pings:
56 def ping(self):
57 self.ping_timer = None
58 #NOTE: all ping time/echo time/load avg values are in milliseconds
59 now_ms = int(1000*monotonic_time())
60 log("sending ping to %s with time=%s", self.protocol, now_ms)
61 self.send_async("ping", now_ms, int(time.time()*1000), will_have_more=False)
62 timeout = PING_TIMEOUT
63 self.check_ping_echo_timers[now_ms] = self.timeout_add(timeout*1000,
64 self.check_ping_echo_timeout, now_ms, timeout)
66 def check_ping_echo_timeout(self, now_ms, timeout):
67 self.check_ping_echo_timers.pop(now_ms, None)
68 if self.last_ping_echoed_time<now_ms and not self.is_closed():
69 self.disconnect(CLIENT_PING_TIMEOUT, "waited %s seconds without a response" % timeout)
71 def cancel_ping_echo_timers(self):
72 timers = self.check_ping_echo_timers.values()
73 self.check_ping_echo_timers = {}
74 for t in timers:
75 self.source_remove(t)
77 def process_ping(self, time_to_echo, sid):
78 l1,l2,l3 = 0,0,0
79 cl = -1
80 if PING_DETAILS:
81 #send back the load average:
82 if POSIX:
83 fl1, fl2, fl3 = os.getloadavg()
84 l1,l2,l3 = int(fl1*1000), int(fl2*1000), int(fl3*1000)
85 #and the last client ping latency we measured (if any):
86 stats = getattr(self, "statistics", None)
87 if stats and stats.client_ping_latency:
88 _, cl = stats.client_ping_latency[-1]
89 cl = int(1000.0*cl)
90 self.send_async("ping_echo", time_to_echo, l1, l2, l3, cl, sid, will_have_more=False)
91 #if the client is pinging us, ping it too:
92 if not self.ping_timer:
93 self.ping_timer = self.timeout_add(500, self.ping)
95 def cancel_ping_timer(self):
96 pt = self.ping_timer
97 if pt:
98 self.ping_timer = None
99 self.source_remove(pt)
101 def process_ping_echo(self, packet):
102 echoedtime, l1, l2, l3, server_ping_latency = packet[1:6]
103 timer = self.check_ping_echo_timers.pop(echoedtime, None)
104 if timer:
105 self.source_remove(timer)
106 self.last_ping_echoed_time = echoedtime
107 client_ping_latency = monotonic_time()-echoedtime/1000.0
108 stats = getattr(self, "statistics", None)
109 if stats and 0<client_ping_latency<60:
110 stats.client_ping_latency.append((monotonic_time(), client_ping_latency))
111 self.client_load = l1, l2, l3
112 if 0<=server_ping_latency<60000 and stats:
113 stats.server_ping_latency.append((monotonic_time(), server_ping_latency/1000.0))
114 log("ping echo client load=%s, measured server latency=%s", self.client_load, server_ping_latency)
117 def update_connection_data(self, data):
118 log("update_connection_data(%s)", data)
119 if not isinstance(data, dict):
120 raise TypeError("connection-data must be a dictionary")
121 self.client_connection_data = data