Hide keyboard shortcuts

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. 

6#pylint: disable-msg=E1101 

7 

8import os 

9from time import sleep 

10 

11from xpra.server.mixins.stub_server_mixin import StubServerMixin 

12from xpra.scripts.config import parse_with_unit 

13from xpra.simple_stats import std_unit 

14from xpra.os_util import livefds, POSIX 

15from xpra.util import envbool, envint, detect_leaks, typedict 

16from xpra.log import Logger 

17 

18log = Logger("network") 

19bandwidthlog = Logger("bandwidth") 

20 

21DETECT_MEMLEAKS = envint("XPRA_DETECT_MEMLEAKS", 0) 

22DETECT_FDLEAKS = envbool("XPRA_DETECT_FDLEAKS", False) 

23 

24MIN_BANDWIDTH_LIMIT = envint("XPRA_MIN_BANDWIDTH_LIMIT", 1024*1024) 

25MAX_BANDWIDTH_LIMIT = envint("XPRA_MAX_BANDWIDTH_LIMIT", 10*1024*1024*1024) 

26CPUINFO = envbool("XPRA_CPUINFO", False) 

27 

28""" 

29Mixin for adding client / network state monitoring functions: 

30- ping and echo 

31- bandwidth management 

32- leak detection (file descriptors, memory) 

33""" 

34class NetworkStateServer(StubServerMixin): 

35 

36 def __init__(self): 

37 self.pings = False 

38 self.ping_timer = None 

39 self.mem_bytes = 0 

40 self.cpu_info = None 

41 self.print_memleaks = None 

42 

43 def init(self, opts): 

44 self.pings = opts.pings 

45 self.bandwidth_limit = parse_with_unit("bandwidth-limit", opts.bandwidth_limit) 

46 bandwidthlog("bandwidth-limit(%s)=%s", opts.bandwidth_limit, self.bandwidth_limit) 

47 self.init_cpuinfo() 

48 

49 def setup(self): 

50 self.init_leak_detection() 

51 if self.pings>0: 

52 self.ping_timer = self.timeout_add(1000*self.pings, self.send_ping) 

53 

54 def threaded_setup(self): 

55 self.init_memcheck() 

56 

57 def cleanup(self): 

58 pt = self.ping_timer 

59 if pt: 

60 self.ping_timer = None 

61 self.source_remove(pt) 

62 pm = self.print_memleaks 

63 if pm: 

64 pm() 

65 

66 def get_info(self, _source=None) -> dict: 

67 info = { 

68 "pings" : self.pings, 

69 "bandwidth-limit" : self.bandwidth_limit or 0, 

70 } 

71 if POSIX: 

72 info["load"] = tuple(int(x*1000) for x in os.getloadavg()) 

73 if self.mem_bytes: 

74 info["total-memory"] = self.mem_bytes 

75 if self.cpu_info: 

76 info["cpuinfo"] = dict((k,v) for k,v in self.cpu_info.items() if k!="python_version") 

77 return info 

78 

79 def get_server_features(self, _source) -> dict: 

80 return { 

81 "connection-data" : True, #added in v2.3 

82 "network" : { 

83 "bandwidth-limit-change" : True, #added in v2.2 

84 "bandwidth-limit" : self.bandwidth_limit or 0, 

85 } 

86 } 

87 

88 

89 def init_leak_detection(self): 

90 if DETECT_MEMLEAKS: 

91 self.print_memleaks = detect_leaks() 

92 if bool(self.print_memleaks): 

93 def leak_thread(): 

94 while not self._closing: 

95 self.print_memleaks() 

96 sleep(DETECT_MEMLEAKS) 

97 from xpra.make_thread import start_thread 

98 start_thread(leak_thread, "leak thread", daemon=True) 

99 if DETECT_FDLEAKS: 

100 self.fds = livefds() 

101 def print_fds(): 

102 fds = livefds() 

103 newfds = fds-self.fds 

104 self.fds = fds 

105 log.info("print_fds() new fds=%s (total=%s)", newfds, len(fds)) 

106 return True 

107 self.timeout_add(10, print_fds) 

108 

109 def init_memcheck(self): 

110 #verify we have enough memory: 

111 if POSIX and self.mem_bytes==0: 

112 try: 

113 self.mem_bytes = os.sysconf('SC_PAGE_SIZE') * os.sysconf('SC_PHYS_PAGES') # e.g. 4015976448 

114 LOW_MEM_LIMIT = 512*1024*1024 

115 if self.mem_bytes<=LOW_MEM_LIMIT: 

116 log.warn("Warning: only %iMB total system memory available", self.mem_bytes//(1024**2)) 

117 log.warn(" this may not be enough to run a server") 

118 else: 

119 log.info("%.1fGB of system memory", self.mem_bytes/(1024.0**3)) 

120 except Exception: 

121 pass 

122 

123 def init_cpuinfo(self): 

124 if not CPUINFO: 

125 return 

126 #this crashes if not run from the UI thread! 

127 try: 

128 from cpuinfo import get_cpu_info 

129 except ImportError as e: 

130 log("no cpuinfo: %s", e) 

131 return 

132 self.cpu_info = get_cpu_info() 

133 if self.cpu_info: 

134 c = typedict(self.cpu_info) 

135 count = c.intget("count", 0) 

136 brand = c.strget("brand") 

137 if count>0 and brand: 

138 log.info("%ix %s", count, brand) 

139 

140 

141 def _process_connection_data(self, proto, packet): 

142 ss = self.get_server_source(proto) 

143 if ss: 

144 ss.update_connection_data(packet[1]) 

145 

146 def _process_bandwidth_limit(self, proto, packet): 

147 log("_process_bandwidth_limit(%s, %s)", proto, packet) 

148 ss = self.get_server_source(proto) 

149 if not ss: 

150 return 

151 bandwidth_limit = packet[1] 

152 if not isinstance(bandwidth_limit, int): 

153 raise TypeError("bandwidth-limit must be an integer, not %s" % type(bandwidth_limit)) 

154 if (self.bandwidth_limit and bandwidth_limit>=self.bandwidth_limit) or bandwidth_limit<=0: 

155 bandwidth_limit = self.bandwidth_limit or 0 

156 if ss.bandwidth_limit==bandwidth_limit: 

157 #unchanged 

158 log("bandwidth limit unchanged: %s", std_unit(bandwidth_limit)) 

159 return 

160 if bandwidth_limit<MIN_BANDWIDTH_LIMIT: 

161 log.warn("Warning: bandwidth limit requested is too low (%s)", std_unit(bandwidth_limit)) 

162 bandwidth_limit = MIN_BANDWIDTH_LIMIT 

163 if bandwidth_limit>=MAX_BANDWIDTH_LIMIT: 

164 log("bandwidth limit over maximum, using no-limit instead") 

165 bandwidth_limit = 0 

166 ss.bandwidth_limit = bandwidth_limit 

167 #we can't assume to have a full ClientConnection object: 

168 client_id = getattr(ss, "counter", "") 

169 if bandwidth_limit==0: 

170 bandwidthlog.info("bandwidth-limit restrictions removed for client %s", client_id) 

171 else: 

172 bandwidthlog.info("bandwidth-limit changed to %sbps for client %s", std_unit(bandwidth_limit), client_id) 

173 

174 def send_ping(self) -> bool: 

175 from xpra.server.source.networkstate_mixin import NetworkStateMixin 

176 for ss in self._server_sources.values(): 

177 if isinstance(ss, NetworkStateMixin): 

178 ss.ping() 

179 return True 

180 

181 def _process_ping_echo(self, proto, packet): 

182 ss = self.get_server_source(proto) 

183 if ss: 

184 ss.process_ping_echo(packet) 

185 

186 def _process_ping(self, proto, packet): 

187 time_to_echo = packet[1] 

188 sid = "" 

189 if len(packet)>=4: 

190 sid = packet[3] 

191 ss = self.get_server_source(proto) 

192 if ss: 

193 ss.process_ping(time_to_echo, sid) 

194 

195 

196 def init_packet_handlers(self): 

197 self.add_packet_handlers({ 

198 "ping": self._process_ping, 

199 "ping_echo": self._process_ping_echo, 

200 "connection-data": self._process_connection_data, 

201 "bandwidth-limit": self._process_bandwidth_limit, 

202 }, False)