1 | #!/usr/bin/env python |
---|
2 | # This file is part of Xpra. |
---|
3 | # Copyright (C) 2012, 2013 Antoine Martin <antoine@devloop.org.uk> |
---|
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 | # |
---|
7 | # To create multiple output files which can be used to generate charts (using test_measure_perf_charts.py) |
---|
8 | # build a config class (copy from perf_config_default.py -- make changes as necessary). |
---|
9 | # |
---|
10 | # Then determine the values of the following variables: |
---|
11 | # prefix: a string to identify the data set |
---|
12 | # id: a string to identify the variable that the data set is testing (for example '14' because we're testing xpra v14 in this data set) |
---|
13 | # repetitions: decide how many times you want to run the tests |
---|
14 | # |
---|
15 | # The data file names you will produce will then be in the format: |
---|
16 | # prefix_id_rep#.csv |
---|
17 | # |
---|
18 | # With this information in hand you can now create a script that will run the tests, containing commands like: |
---|
19 | # ./test_measure_perf.py as an example: |
---|
20 | |
---|
21 | # For example: |
---|
22 | # |
---|
23 | # ./test_measure_perf.py all_tests_40 ./data/all_tests_40_14_1.csv 1 14 > ./data//all_tests_40_14_1.log |
---|
24 | # ./test_measure_perf.py all_tests_40 ./data/all_tests_40_14_2.csv 2 14 > ./data//all_tests_40_14_2.log |
---|
25 | # |
---|
26 | # In this example script, I'm running test_measure_perf 2 times, using a config class named "all_tests_40.py", |
---|
27 | # and outputting the results to data files using the prefix "all_tests_40", for version 14. |
---|
28 | # |
---|
29 | # The additional arguments "1 14", "2 14" are custom paramaters which will be written to the "Custom Params" column |
---|
30 | # in the corresponding data files. |
---|
31 | # |
---|
32 | # Where you see "1", "2" in the file names or params, that's referring to the corresponding repetition of the tests. |
---|
33 | # |
---|
34 | # Once this script has run, you can open up test_measure_perf_charts.py and take a look at the |
---|
35 | # instructions there for generating the charts. |
---|
36 | # |
---|
37 | |
---|
38 | import re |
---|
39 | import sys |
---|
40 | import subprocess |
---|
41 | import os.path |
---|
42 | import time |
---|
43 | |
---|
44 | from xpra.log import Logger |
---|
45 | log = Logger() |
---|
46 | |
---|
47 | def getoutput(cmd, env=None): |
---|
48 | try: |
---|
49 | process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env, close_fds=True) |
---|
50 | except Exception as e: |
---|
51 | print("error running %s: %s" % (cmd, e)) |
---|
52 | raise e |
---|
53 | (out,err) = process.communicate() |
---|
54 | code = process.poll() |
---|
55 | if code!=0: |
---|
56 | raise Exception("command '%s' returned error code %s, out=%s, err=%s" % (cmd, code, out, err)) |
---|
57 | return out |
---|
58 | |
---|
59 | def get_config(config_name): |
---|
60 | try: |
---|
61 | mod = __import__(config_name) |
---|
62 | except (ImportError, SyntaxError) as e: |
---|
63 | sys.stderr.write("Error loading module %s (%s)\n" % (config_name, e)) |
---|
64 | return None |
---|
65 | return mod.Config() |
---|
66 | |
---|
67 | if (len(sys.argv) > 1): |
---|
68 | config_name = sys.argv[1] |
---|
69 | else: |
---|
70 | config_name = 'perf_config_default' |
---|
71 | config = get_config(config_name) |
---|
72 | if (config==None): |
---|
73 | raise Exception("Could not load config file") |
---|
74 | |
---|
75 | XPRA_BIN = "/usr/bin/xpra" |
---|
76 | XPRA_VERSION_OUTPUT = getoutput([XPRA_BIN, "--version"]) |
---|
77 | XPRA_VERSION = "" |
---|
78 | for x in XPRA_VERSION_OUTPUT.splitlines(): |
---|
79 | if x.startswith("xpra v"): |
---|
80 | XPRA_VERSION = x[len("xpra v"):].replace("\n", "").replace("\r", "") |
---|
81 | XPRA_VERSION = XPRA_VERSION.split("-")[0] |
---|
82 | XPRA_VERSION_NO = [int(x) for x in XPRA_VERSION.split(".")] |
---|
83 | XPRA_SERVER_STOP_COMMANDS = [ |
---|
84 | [XPRA_BIN, "stop", ":%s" % config.DISPLAY_NO], |
---|
85 | "ps -ef | grep -i [X]vfb-for-Xpra-:%s | awk '{print $2}' | xargs kill" % config.DISPLAY_NO |
---|
86 | ] |
---|
87 | XPRA_INFO_COMMAND = [XPRA_BIN, "info", "tcp:%s:%s" % (config.IP, config.PORT)] |
---|
88 | print ("XPRA_VERSION_NO=%s" % XPRA_VERSION_NO) |
---|
89 | |
---|
90 | STRICT_ENCODINGS = False |
---|
91 | if STRICT_ENCODINGS: |
---|
92 | #beware: only enable this flag if the version being tested |
---|
93 | # also supports the same environment overrides, |
---|
94 | # or the comparison will not be fair. |
---|
95 | os.environ["XPRA_ENCODING_STRICT_MODE"] = "1" |
---|
96 | os.environ["XPRA_MAX_PIXELS_PREFER_RGB"] = "0" |
---|
97 | os.environ["XPRA_MAX_NONVIDEO_PIXELS"] = "0" |
---|
98 | |
---|
99 | XPRA_SPEAKER_OPTIONS = [None] |
---|
100 | XPRA_MICROPHONE_OPTIONS = [None] |
---|
101 | if config.TEST_SOUND: |
---|
102 | from xpra.sound.gstreamer_util import CODEC_ORDER, has_codec |
---|
103 | if XPRA_VERSION_NO>=[0, 9]: |
---|
104 | #0.9 onwards supports all codecs defined: |
---|
105 | XPRA_SPEAKER_OPTIONS = [x for x in CODEC_ORDER if has_codec(x)] |
---|
106 | elif XPRA_VERSION_NO==[0, 8]: |
---|
107 | #only mp3 works in 0.8: |
---|
108 | XPRA_SPEAKER_OPTIONS = ["mp3"] |
---|
109 | else: |
---|
110 | #none before that |
---|
111 | XPRA_SPEAKER_OPTIONS = [None] |
---|
112 | |
---|
113 | if (config.XPRA_USE_PASSWORD): |
---|
114 | password_filename = "./test-password.txt" |
---|
115 | import uuid |
---|
116 | with open(password_filename, 'wb') as f: |
---|
117 | f.write(uuid.uuid4().hex) |
---|
118 | |
---|
119 | check = [config.TRICKLE_BIN] |
---|
120 | if config.TEST_XPRA: |
---|
121 | check.append(XPRA_BIN) |
---|
122 | if config.TEST_VNC: |
---|
123 | check.append(config.XVNC_BIN) |
---|
124 | check.append(config.VNCVIEWER_BIN) |
---|
125 | for x in check: |
---|
126 | if not os.path.exists(x): |
---|
127 | raise Exception("cannot run tests: %s is missing!" % x) |
---|
128 | |
---|
129 | HEADERS = ["Test Name", "Remoting Tech", "Server Version", "Client Version", "Custom Params", "SVN Version", |
---|
130 | "Encoding", "Quality", "Speed","OpenGL", "Test Command", "Sample Duration (s)", "Sample Time (epoch)", |
---|
131 | "CPU info", "Platform", "Kernel Version", "Xorg version", "OpenGL", "Client Window Manager", "Screen Size", |
---|
132 | "Compression", "Encryption", "Connect via", "download limit (KB)", "upload limit (KB)", "latency (ms)", |
---|
133 | "packets in/s", "packets in: bytes/s", "packets out/s", "packets out: bytes/s", |
---|
134 | "Regions/s", "Pixels/s Sent", "Encoding Pixels/s", "Decoding Pixels/s", |
---|
135 | "Application packets in/s", "Application bytes in/s", |
---|
136 | "Application packets out/s", "Application bytes out/s", "mmap bytes/s", |
---|
137 | "Video Encoder", "CSC", "CSC Mode", "Scaling", |
---|
138 | ] |
---|
139 | for x in ("client", "server"): |
---|
140 | HEADERS += [x+" user cpu_pct", x+" system cpu pct", x+" number of threads", x+" vsize (MB)", x+" rss (MB)"] |
---|
141 | #all these headers have min/max/avg: |
---|
142 | for h in ("Batch Delay (ms)", "Actual Batch Delay (ms)", |
---|
143 | "Client Latency (ms)", "Client Ping Latency (ms)", "Server Ping Latency (ms)", |
---|
144 | "Damage Latency (ms)", |
---|
145 | "Quality", "Speed"): |
---|
146 | for x in ("Min", "Avg", "Max"): |
---|
147 | HEADERS.append(x+" "+h) |
---|
148 | |
---|
149 | def is_process_alive(process, grace=0): |
---|
150 | i = 0 |
---|
151 | while i<grace: |
---|
152 | if not process or process.poll() is not None: |
---|
153 | return False |
---|
154 | time.sleep(1) |
---|
155 | i += 1 |
---|
156 | return process and process.poll() is None |
---|
157 | |
---|
158 | def try_to_stop(process, grace=0): |
---|
159 | if is_process_alive(process, grace): |
---|
160 | try: |
---|
161 | process.terminate() |
---|
162 | except Exception as e: |
---|
163 | print("could not stop process %s: %s" % (process, e)) |
---|
164 | def try_to_kill(process, grace=0): |
---|
165 | if is_process_alive(process, grace): |
---|
166 | try: |
---|
167 | process.kill() |
---|
168 | except Exception as e: |
---|
169 | print("could not stop process %s: %s" % (process, e)) |
---|
170 | |
---|
171 | def find_matching_lines(out, pattern): |
---|
172 | lines = [] |
---|
173 | for line in out.splitlines(): |
---|
174 | if line.find(pattern)>=0: |
---|
175 | lines.append(line) |
---|
176 | return lines |
---|
177 | |
---|
178 | def getoutput_lines(cmd, pattern, setup_info): |
---|
179 | out = getoutput(cmd) |
---|
180 | return find_matching_lines(out, pattern) |
---|
181 | |
---|
182 | def getoutput_line(cmd, pattern, setup_info): |
---|
183 | lines = getoutput_lines(cmd, pattern, setup_info) |
---|
184 | if len(lines)!=1: |
---|
185 | print("WARNING: expected 1 line matching '%s' from %s but found %s" % (pattern, cmd, len(lines))) |
---|
186 | return "not found" |
---|
187 | return lines[0] |
---|
188 | |
---|
189 | def get_cpu_info(): |
---|
190 | lines = getoutput_lines(["cat", "/proc/cpuinfo"], "model name", "cannot find cpu info") |
---|
191 | assert len(lines)>0, "coult not find 'model name' in '/proc/cpuinfo'" |
---|
192 | cpu0 = lines[0] |
---|
193 | n = len(lines) |
---|
194 | for x in lines[1:]: |
---|
195 | if x!=cpu0: |
---|
196 | return " - ".join(lines), n |
---|
197 | cpu_name = cpu0.split(":")[1] |
---|
198 | for o,r in [("Processor", ""), ("(R)", ""), ("(TM)", ""), ("(tm)", ""), (" ", " ")]: |
---|
199 | while cpu_name.find(o)>=0: |
---|
200 | cpu_name = cpu_name.replace(o, r) |
---|
201 | cpu_info = "%sx %s" % (len(lines), cpu_name.strip()) |
---|
202 | print("CPU_INFO=%s" % cpu_info) |
---|
203 | return cpu_info, n |
---|
204 | |
---|
205 | XORG_VERSION = getoutput_line([config.XORG_BIN, "-version"], "X.Org X Server", "Cannot detect Xorg server version") |
---|
206 | print("XORG_VERSION=%s" % XORG_VERSION) |
---|
207 | CPU_INFO, N_CPUS = get_cpu_info() |
---|
208 | KERNEL_VERSION = getoutput(["uname", "-r"]).replace("\n", "").replace("\r", "") |
---|
209 | PAGE_SIZE = int(getoutput(["getconf", "PAGESIZE"]).replace("\n", "").replace("\r", "")) |
---|
210 | PLATFORM = getoutput(["uname", "-p"]).replace("\n", "").replace("\r", "") |
---|
211 | OPENGL_INFO = getoutput_line(["glxinfo"], "OpenGL renderer string", "Cannot detect OpenGL renderer string").split("OpenGL renderer string:")[1].strip() |
---|
212 | |
---|
213 | import pygtk |
---|
214 | pygtk.require("2.0") |
---|
215 | import gtk #@UnusedImport |
---|
216 | from gtk import gdk #@UnusedImport |
---|
217 | SCREEN_SIZE = gdk.get_default_root_window().get_size() |
---|
218 | print("screen size=%s" % str(SCREEN_SIZE)) |
---|
219 | |
---|
220 | #detect Xvnc version: |
---|
221 | XVNC_VERSION = "" |
---|
222 | VNCVIEWER_VERSION = "" |
---|
223 | DETECT_XVNC_VERSION_CMD = [config.XVNC_BIN, "--help"] |
---|
224 | DETECT_VNCVIEWER_VERSION_CMD = [config.VNCVIEWER_BIN, "--help"] |
---|
225 | def get_stderr(command): |
---|
226 | try: |
---|
227 | process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
---|
228 | _,err = process.communicate() |
---|
229 | return err |
---|
230 | except Exception as e: |
---|
231 | print("error running %s: %s" % (DETECT_XVNC_VERSION_CMD, e)) |
---|
232 | |
---|
233 | err = get_stderr(DETECT_XVNC_VERSION_CMD) |
---|
234 | if err: |
---|
235 | v_lines = find_matching_lines(err, "Xvnc TigerVNC") |
---|
236 | if len(v_lines)==1: |
---|
237 | XVNC_VERSION = " ".join(v_lines[0].split()[:3]) |
---|
238 | print ("XVNC_VERSION=%s" % XVNC_VERSION) |
---|
239 | err = get_stderr(DETECT_VNCVIEWER_VERSION_CMD) |
---|
240 | if err: |
---|
241 | v_lines = find_matching_lines(err, "TigerVNC Viewer for X version") |
---|
242 | if len(v_lines)==1: |
---|
243 | VNCVIEWER_VERSION = "TigerVNC Viewer %s" % (v_lines[0].split()[5]) |
---|
244 | print ("VNCVIEWER_VERSION=%s" % VNCVIEWER_VERSION) |
---|
245 | |
---|
246 | #get svnversion, prefer directly from svn: |
---|
247 | try: |
---|
248 | SVN_VERSION = getoutput(["svnversion", "-n"]).split(":")[-1].strip() |
---|
249 | except: |
---|
250 | SVN_VERSION = "" |
---|
251 | if not SVN_VERSION: |
---|
252 | #fallback to getting it from xpra's src_info: |
---|
253 | try: |
---|
254 | from xpra.src_info import REVISION, LOCAL_MODIFICATIONS |
---|
255 | SVN_VERSION = 'r%s' % REVISION |
---|
256 | if LOCAL_MODIFICATIONS: |
---|
257 | SVN_VERSION += "M" |
---|
258 | except: |
---|
259 | pass |
---|
260 | if not SVN_VERSION: |
---|
261 | #fallback to running python: |
---|
262 | SVN_VERSION = getoutput(["python", "-c", "from xpra.src_info import REVISION,LOCAL_MODIFICATIONS;print(('r%s%s' % (REVISION, ' M'[int(bool(LOCAL_MODIFICATIONS))])).strip())"]) |
---|
263 | print("Found xpra revision: '%s'" % str(SVN_VERSION)) |
---|
264 | |
---|
265 | WINDOW_MANAGER = os.environ.get("DESKTOP_SESSION", "unknown") |
---|
266 | |
---|
267 | def clean_sys_state(): |
---|
268 | #clear the caches |
---|
269 | cmd = ["echo", "3", ">", "/proc/sys/vm/drop_caches"] |
---|
270 | process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
---|
271 | assert process.wait()==0, "failed to run %s" % str(cmd) |
---|
272 | |
---|
273 | def zero_iptables(): |
---|
274 | if not config.USE_IPTABLES: |
---|
275 | return |
---|
276 | cmds = [config.IPTABLES_CMD+['-Z', 'INPUT'], config.IPTABLES_CMD+['-Z', 'OUTPUT']] |
---|
277 | for cmd in cmds: |
---|
278 | getoutput(cmd) |
---|
279 | #out = getoutput(cmd) |
---|
280 | #print("output(%s)=%s" % (cmd, out)) |
---|
281 | |
---|
282 | def update_proc_stat(): |
---|
283 | with open("/proc/stat", "rU") as proc_stat: |
---|
284 | time_total = 0 |
---|
285 | for line in proc_stat: |
---|
286 | values = line.split() |
---|
287 | if values[0]=="cpu": |
---|
288 | time_total = sum([int(x) for x in values[1:]]) |
---|
289 | #print("time_total=%s" % time_total) |
---|
290 | break |
---|
291 | return time_total |
---|
292 | |
---|
293 | def update_pidstat(pid): |
---|
294 | with open("/proc/%s/stat" % pid, "rU") as stat_file: |
---|
295 | data = stat_file.read() |
---|
296 | pid_stat = data.split() |
---|
297 | #print("update_pidstat(%s): %s" % (pid, pid_stat)) |
---|
298 | return pid_stat |
---|
299 | |
---|
300 | def compute_stat(prefix, time_total_diff, old_pid_stat, new_pid_stat): |
---|
301 | #found help here: |
---|
302 | #http://stackoverflow.com/questions/1420426/calculating-cpu-usage-of-a-process-in-linux |
---|
303 | old_utime = int(old_pid_stat[13]) |
---|
304 | old_stime = int(old_pid_stat[14]) |
---|
305 | new_utime = int(new_pid_stat[13]) |
---|
306 | new_stime = int(new_pid_stat[14]) |
---|
307 | #normalize to 100% (single process) by multiplying by number of CPUs: |
---|
308 | user_pct = int(N_CPUS * 1000 * (new_utime - old_utime) / time_total_diff)/10.0 |
---|
309 | sys_pct = int(N_CPUS * 1000 * (new_stime - old_stime) / time_total_diff)/10.0 |
---|
310 | nthreads = int((int(old_pid_stat[19])+int(new_pid_stat[19]))/2) |
---|
311 | vsize = int(max(int(old_pid_stat[22]), int(new_pid_stat[22]))/1024/1024) |
---|
312 | rss = int(max(int(old_pid_stat[23]), int(new_pid_stat[23]))*PAGE_SIZE/1024/1024) |
---|
313 | return {prefix+" user cpu_pct" : user_pct, |
---|
314 | prefix+" system cpu pct" : sys_pct, |
---|
315 | prefix+" number of threads" : nthreads, |
---|
316 | prefix+" vsize (MB)" : vsize, |
---|
317 | prefix+" rss (MB)" : rss, |
---|
318 | } |
---|
319 | |
---|
320 | def getiptables_line(chain, pattern, setup_info): |
---|
321 | cmd = config.IPTABLES_CMD + ["-vnL", chain] |
---|
322 | line = getoutput_line(cmd, pattern, setup_info) |
---|
323 | if not line: |
---|
324 | raise Exception("no line found matching %s, make sure you have a rule like: %s" % (pattern, setup_info)) |
---|
325 | return line |
---|
326 | |
---|
327 | def parse_ipt(chain, pattern, setup_info): |
---|
328 | if not config.USE_IPTABLES: |
---|
329 | return 0, 0 |
---|
330 | line = getiptables_line(chain, pattern, setup_info) |
---|
331 | parts = line.split() |
---|
332 | assert len(parts)>2 |
---|
333 | def parse_num(part): |
---|
334 | U = 1024 |
---|
335 | m = {"K":U, "M":U**2, "G":U**3}.get(part[-1], 1) |
---|
336 | num = "".join([x for x in part if x in "0123456789"]) |
---|
337 | return int(num)*m/config.MEASURE_TIME |
---|
338 | return parse_num(parts[0]), parse_num(parts[1]) |
---|
339 | |
---|
340 | def get_iptables_INPUT_count(): |
---|
341 | setup = "iptables -I INPUT -p tcp --dport %s -j ACCEPT" % config.PORT |
---|
342 | return parse_ipt("INPUT", "tcp dpt:%s" % config.PORT, setup) |
---|
343 | |
---|
344 | def get_iptables_OUTPUT_count(): |
---|
345 | setup = "iptables -I OUTPUT -p tcp --sport %s -j ACCEPT" % config.PORT |
---|
346 | return parse_ipt("OUTPUT", "tcp spt:%s" % config.PORT, setup) |
---|
347 | |
---|
348 | def measure_client(server_pid, name, cmd, get_stats_cb): |
---|
349 | print("starting client: %s" % cmd) |
---|
350 | try: |
---|
351 | client_process = subprocess.Popen(cmd) |
---|
352 | #give it time to settle down: |
---|
353 | time.sleep(config.SETTLE_TIME) |
---|
354 | code = client_process.poll() |
---|
355 | assert code is None, "client failed to start, return code is %s" % code |
---|
356 | #clear counters |
---|
357 | initial_stats = get_stats_cb() |
---|
358 | zero_iptables() |
---|
359 | old_time_total = update_proc_stat() |
---|
360 | old_pid_stat = update_pidstat(client_process.pid) |
---|
361 | if server_pid>0: |
---|
362 | old_server_pid_stat = update_pidstat(server_pid) |
---|
363 | #we start measuring |
---|
364 | t = 0 |
---|
365 | all_stats = [initial_stats] |
---|
366 | while t<config.MEASURE_TIME: |
---|
367 | time.sleep(config.COLLECT_STATS_TIME) |
---|
368 | t += config.COLLECT_STATS_TIME |
---|
369 | |
---|
370 | code = client_process.poll() |
---|
371 | assert code is None, "client crashed, return code is %s" % code |
---|
372 | |
---|
373 | stats = get_stats_cb(initial_stats, all_stats) |
---|
374 | |
---|
375 | #stop the counters |
---|
376 | new_time_total = update_proc_stat() |
---|
377 | new_pid_stat = update_pidstat(client_process.pid) |
---|
378 | if server_pid>0: |
---|
379 | new_server_pid_stat = update_pidstat(server_pid) |
---|
380 | ni,isize = get_iptables_INPUT_count() |
---|
381 | no,osize = get_iptables_OUTPUT_count() |
---|
382 | #[ni, isize, no, osize] |
---|
383 | iptables_stat = {"packets in/s" : ni, |
---|
384 | "packets in: bytes/s" : isize, |
---|
385 | "packets out/s" : no, |
---|
386 | "packets out: bytes/s" : osize} |
---|
387 | #now collect the data |
---|
388 | client_process_data = compute_stat("client", new_time_total-old_time_total, old_pid_stat, new_pid_stat) |
---|
389 | if server_pid>0: |
---|
390 | server_process_data = compute_stat("server", new_time_total-old_time_total, old_server_pid_stat, new_server_pid_stat) |
---|
391 | else: |
---|
392 | server_process_data = [] |
---|
393 | print("process_data (client/server): %s / %s" % (client_process_data, server_process_data)) |
---|
394 | print("input/output on tcp PORT %s: %s / %s packets, %s / %s KBytes" % (config.PORT, ni, no, isize, osize)) |
---|
395 | data = {} |
---|
396 | data.update(iptables_stat) |
---|
397 | data.update(stats) |
---|
398 | data.update(client_process_data) |
---|
399 | data.update(server_process_data) |
---|
400 | return data |
---|
401 | finally: |
---|
402 | #stop the process |
---|
403 | if client_process and client_process.poll() is None: |
---|
404 | try_to_stop(client_process) |
---|
405 | try_to_kill(client_process, 5) |
---|
406 | code = client_process.poll() |
---|
407 | assert code is not None, "failed to stop client!" |
---|
408 | |
---|
409 | def with_server(start_server_command, stop_server_commands, in_tests, get_stats_cb): |
---|
410 | tests = in_tests[config.STARTING_TEST:config.LIMIT_TESTS] |
---|
411 | print("going to run %s tests: %s" % (len(tests), [x[0] for x in tests])) |
---|
412 | print("*******************************************") |
---|
413 | print("ETA: %s minutes" % int((config.SERVER_SETTLE_TIME+config.DEFAULT_TEST_COMMAND_SETTLE_TIME+config.SETTLE_TIME+config.MEASURE_TIME+1)*len(tests)/60)) |
---|
414 | print("*******************************************") |
---|
415 | |
---|
416 | server_process = None |
---|
417 | test_command_process = None |
---|
418 | env = {} |
---|
419 | for k,v in os.environ.items(): |
---|
420 | #whitelist what we want to keep: |
---|
421 | if k.startswith("XPRA") or k in ("LOGNAME", "XDG_RUNTIME_DIR", "USER", "HOME", "PATH", "LD_LIBRARY_PATH", "XAUTHORITY", "SHELL", "TERM", "USERNAME", "HOSTNAME", "PWD"): |
---|
422 | env[k] = v |
---|
423 | env["DISPLAY"] = ":%s" % config.DISPLAY_NO |
---|
424 | errors = 0 |
---|
425 | results = [] |
---|
426 | count = 0 |
---|
427 | for name, tech_name, server_version, client_version, encoding, quality, speed, \ |
---|
428 | opengl, compression, encryption, ssh, (down,up,latency), test_command, client_cmd in tests: |
---|
429 | try: |
---|
430 | print("**************************************************************") |
---|
431 | count += 1 |
---|
432 | test_command_settle_time = config.TEST_COMMAND_SETTLE_TIME.get(test_command[0], config.DEFAULT_TEST_COMMAND_SETTLE_TIME) |
---|
433 | eta = int((config.SERVER_SETTLE_TIME+test_command_settle_time+config.SETTLE_TIME+config.MEASURE_TIME+1)*(len(tests)-count)/60) |
---|
434 | print("%s/%s: %s ETA=%s minutes" % (count, len(tests), name, eta)) |
---|
435 | test_command_process = None |
---|
436 | try: |
---|
437 | clean_sys_state() |
---|
438 | #start the server: |
---|
439 | if config.START_SERVER: |
---|
440 | print("starting server: %s" % str(start_server_command)) |
---|
441 | server_process = subprocess.Popen(start_server_command, stdin=None) |
---|
442 | #give it time to settle down: |
---|
443 | t = config.SERVER_SETTLE_TIME |
---|
444 | if count==1: |
---|
445 | #first run, give it enough time to cleanup the socket |
---|
446 | t += 5 |
---|
447 | time.sleep(t) |
---|
448 | server_pid = server_process.pid |
---|
449 | code = server_process.poll() |
---|
450 | assert code is None, "server failed to start, return code is %s, please ensure that you can run the server command line above and that a server does not already exist on that port or DISPLAY" % code |
---|
451 | else: |
---|
452 | server_pid = 0 |
---|
453 | |
---|
454 | try: |
---|
455 | #start the test command: |
---|
456 | if config.USE_VIRTUALGL: |
---|
457 | if type(test_command)==str: |
---|
458 | cmd = config.VGLRUN_BIN + "-d "+os.environ.get("DISPLAY")+" -- "+ test_command |
---|
459 | elif type(test_command) in (list, tuple): |
---|
460 | cmd = [config.VGLRUN_BIN, "-d", os.environ.get("DISPLAY"), "--"] + list(test_command) |
---|
461 | else: |
---|
462 | raise Exception("invalid test command type: %s for %s" % (type(test_command), test_command)) |
---|
463 | else: |
---|
464 | cmd = test_command |
---|
465 | |
---|
466 | print("starting test command: %s with env=%s, settle time=%s" % (cmd, env, test_command_settle_time)) |
---|
467 | shell = type(cmd)==str |
---|
468 | test_command_process = subprocess.Popen(cmd, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, shell=shell) |
---|
469 | |
---|
470 | if config.PREVENT_SLEEP: |
---|
471 | subprocess.Popen(config.PREVENT_SLEEP_COMMAND) |
---|
472 | |
---|
473 | time.sleep(test_command_settle_time) |
---|
474 | code = test_command_process.poll() |
---|
475 | assert code is None, "test command %s failed to start: exit code is %s" % (cmd, code) |
---|
476 | print("test command %s is running with pid=%s" % (cmd, test_command_process.pid)) |
---|
477 | |
---|
478 | #run the client test |
---|
479 | data = {"Test Name" : name, |
---|
480 | "Remoting Tech" : tech_name, |
---|
481 | "Server Version" : server_version, |
---|
482 | "Client Version" : client_version, |
---|
483 | "Custom Params" : config.CUSTOM_PARAMS, |
---|
484 | "SVN Version" : SVN_VERSION, |
---|
485 | "Encoding" : encoding, |
---|
486 | "Quality" : quality, |
---|
487 | "Speed" : speed, |
---|
488 | "OpenGL" : opengl, |
---|
489 | "Test Command" : get_command_name(test_command), |
---|
490 | "Sample Duration (s)" : config.MEASURE_TIME, |
---|
491 | "Sample Time (epoch)" : time.time(), |
---|
492 | "CPU info" : CPU_INFO, |
---|
493 | "Platform" : PLATFORM, |
---|
494 | "Kernel Version" : KERNEL_VERSION, |
---|
495 | "Xorg version" : XORG_VERSION, |
---|
496 | "OpenGL" : OPENGL_INFO, |
---|
497 | "Client Window Manager" : WINDOW_MANAGER, |
---|
498 | "Screen Size" : "%sx%s" % gdk.get_default_root_window().get_size(), |
---|
499 | "Compression" : compression, |
---|
500 | "Encryption" : encryption, |
---|
501 | "Connect via" : ssh, |
---|
502 | "download limit (KB)" : down, |
---|
503 | "upload limit (KB)" : up, |
---|
504 | "latency (ms)" : latency, |
---|
505 | } |
---|
506 | data.update(measure_client(server_pid, name, client_cmd, get_stats_cb)) |
---|
507 | results.append([data.get(x, "") for x in HEADERS]) |
---|
508 | except Exception as e: |
---|
509 | import traceback |
---|
510 | traceback.print_exc() |
---|
511 | errors += 1 |
---|
512 | print("error during client command run for %s: %s" % (name, e)) |
---|
513 | if errors>config.MAX_ERRORS: |
---|
514 | print("too many errors, aborting tests") |
---|
515 | break |
---|
516 | finally: |
---|
517 | if test_command_process: |
---|
518 | print("stopping '%s' with pid=%s" % (test_command, test_command_process.pid)) |
---|
519 | try_to_stop(test_command_process) |
---|
520 | try_to_kill(test_command_process, 2) |
---|
521 | if config.START_SERVER: |
---|
522 | try_to_stop(server_process) |
---|
523 | time.sleep(2) |
---|
524 | for s in stop_server_commands: |
---|
525 | print("stopping server with: %s" % (s)) |
---|
526 | try: |
---|
527 | stop_process = subprocess.Popen(s, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) |
---|
528 | stop_process.wait() |
---|
529 | except Exception as e: |
---|
530 | print("error: %s" % e) |
---|
531 | try_to_kill(server_process, 5) |
---|
532 | time.sleep(1) |
---|
533 | except KeyboardInterrupt as e: |
---|
534 | print("caught %s: stopping this series of tests" % e) |
---|
535 | break |
---|
536 | return results |
---|
537 | |
---|
538 | def trickle_command(down, up, latency): |
---|
539 | if down<=0 and up<=0 and latency<=0: |
---|
540 | return [] |
---|
541 | cmd = [config.TRICKLE_BIN, "-s"] |
---|
542 | if down>0: |
---|
543 | cmd += ["-d", str(down)] |
---|
544 | if up>0: |
---|
545 | cmd += ["-u", str(up)] |
---|
546 | if latency>0: |
---|
547 | cmd += ["-L", str(latency)] |
---|
548 | return cmd |
---|
549 | |
---|
550 | def trickle_str(down, up, latency): |
---|
551 | if down<=0 and up<=0 and latency<=0: |
---|
552 | return "unthrottled" |
---|
553 | s = "/".join(str(x) for x in [down,up,latency]) |
---|
554 | return "throttled:%s" % s |
---|
555 | |
---|
556 | def get_command_name(command_arg): |
---|
557 | try: |
---|
558 | name = config.TEST_NAMES.get(command_arg) |
---|
559 | if name: |
---|
560 | return name |
---|
561 | except: |
---|
562 | pass |
---|
563 | if type(command_arg)==list: |
---|
564 | c = command_arg[0] #["/usr/bin/xterm", "blah"] -> "/usr/bin/xterm" |
---|
565 | else: |
---|
566 | c = command_arg.split(" ")[0] #"/usr/bin/xterm -e blah" -> "/usr/bin/xterm" |
---|
567 | assert type(c)==str |
---|
568 | return c.split("/")[-1] #/usr/bin/xterm -> xterm |
---|
569 | |
---|
570 | def get_auth_args(): |
---|
571 | cmd = [] |
---|
572 | if config.XPRA_USE_PASSWORD: |
---|
573 | cmd.append("--password-file=%s" % password_filename) |
---|
574 | if XPRA_VERSION_NO>=[0, 14]: |
---|
575 | cmd.append("--auth=file") |
---|
576 | if XPRA_VERSION_NO>=[0, 15]: |
---|
577 | cmd.append("--auth=none") |
---|
578 | cmd.append("--tcp-auth=file") |
---|
579 | return cmd |
---|
580 | |
---|
581 | def xpra_get_stats(initial_stats=None, all_stats=[]): |
---|
582 | if XPRA_VERSION_NO<[0, 3]: |
---|
583 | return {} |
---|
584 | info_cmd = XPRA_INFO_COMMAND[:] + get_auth_args() |
---|
585 | out = getoutput(info_cmd) |
---|
586 | if not out: |
---|
587 | return {} |
---|
588 | #parse output: |
---|
589 | d = {} |
---|
590 | for line in out.splitlines(): |
---|
591 | parts = line.split("=") |
---|
592 | if len(parts)==2: |
---|
593 | d[parts[0]] = parts[1] |
---|
594 | #functions for accessing the data: |
---|
595 | def iget(names, default_value=""): |
---|
596 | """ some of the fields got renamed, try both old and new names """ |
---|
597 | for n in names: |
---|
598 | v = d.get(n) |
---|
599 | if v is not None: |
---|
600 | return int(v) |
---|
601 | return default_value |
---|
602 | #values always based on initial data only: |
---|
603 | #(difference from initial value) |
---|
604 | lookup = initial_stats or {} |
---|
605 | initial_input_packetcount = lookup.get("Application packets in/s", 0) |
---|
606 | initial_input_bytecount = lookup.get("Application bytes in/s", 0) |
---|
607 | initial_output_packetcount = lookup.get("Application packets out/s", 0) |
---|
608 | initial_output_bytecount = lookup.get("Application bytes out/s", 0) |
---|
609 | initial_mmap_bytes = lookup.get("mmap bytes/s", 0) |
---|
610 | data = { |
---|
611 | "Application packets in/s" : (iget(["client.connection.input.packetcount", "input_packetcount"], 0)-initial_input_packetcount)/config.MEASURE_TIME, |
---|
612 | "Application bytes in/s" : (iget(["client.connection.input.bytecount", "input_bytecount"], 0)-initial_input_bytecount)/config.MEASURE_TIME, |
---|
613 | "Application packets out/s" : (iget(["client.connection.output.packetcount", "output_packetcount"], 0)-initial_output_packetcount)/config.MEASURE_TIME, |
---|
614 | "Application bytes out/s" : (iget(["client.connection.output.bytecount", "output_bytecount"], 0)-initial_output_bytecount)/config.MEASURE_TIME, |
---|
615 | "mmap bytes/s" : (iget(["client.connection.output.mmap_bytecount", "output_mmap_bytecount"], 0)-initial_mmap_bytes)/config.MEASURE_TIME, |
---|
616 | } |
---|
617 | |
---|
618 | #values that are averages or min/max: |
---|
619 | def add(prefix, op, name, prop_names): |
---|
620 | values = [] |
---|
621 | #cook the property names using the lowercase prefix if needed |
---|
622 | #(all xpra info properties are lowercase): |
---|
623 | actual_prop_names = [] |
---|
624 | full_search = [] |
---|
625 | for prop_name in prop_names: |
---|
626 | if prop_name.find("%s")>=0: |
---|
627 | prop_name = prop_name % prefix.lower() |
---|
628 | actual_prop_names.append(prop_name) |
---|
629 | if prop_name.find("*")>=0 or prop_name.find("+")>=0: #ie: "window\[\d+\].encoding.quality.avg" |
---|
630 | #make it a proper python regex: |
---|
631 | full_search.append(prop_name) |
---|
632 | if len(full_search)>0: |
---|
633 | for s in full_search: |
---|
634 | regex = re.compile(s) |
---|
635 | matches = [d.get(x) for x in d.keys() if regex.match(x)] |
---|
636 | for v in matches: |
---|
637 | values.append(int(v)) |
---|
638 | #print("add(%s, %s, %s, %s) values from full_search=%s: %s" % (prefix, op, name, prop_names, full_search, values)) |
---|
639 | else: |
---|
640 | #match just one record: |
---|
641 | values.append(iget(actual_prop_names)) |
---|
642 | #print("add(%s, %s, %s, %s) values from iget: %s" % (prefix, op, name, prop_names, values)) |
---|
643 | #this is the stat property name: |
---|
644 | full_name = name #ie: "Application packets in/s" |
---|
645 | if prefix: |
---|
646 | full_name = prefix+" "+name #ie: "Min" + " " + "Batch Delay" |
---|
647 | for s in all_stats: #add all previously found values to list |
---|
648 | values.append(s.get(full_name)) |
---|
649 | #strip missing values: |
---|
650 | values = [x for x in values if x is not None and x!=""] |
---|
651 | if len(values)>0: |
---|
652 | v = op(values) #ie: avg([4,5,4]) or max([4,5,4]) |
---|
653 | #print("%s: %s(%s)=%s" % (full_name, op, values, v)) |
---|
654 | data[full_name] = v |
---|
655 | |
---|
656 | def avg(l): |
---|
657 | return sum(l)/len(l) |
---|
658 | |
---|
659 | add("", avg, "Regions/s", ["encoding.regions_per_second", "regions_per_second"]) |
---|
660 | add("", avg, "Pixels/s Sent", ["encoding.pixels_per_second", "pixels_per_second"]) |
---|
661 | add("", avg, "Encoding Pixels/s", ["encoding.pixels_encoded_per_second", "pixels_encoded_per_second"]) |
---|
662 | add("", avg, "Decoding Pixels/s", ["encoding.pixels_decoded_per_second", "pixels_decoded_per_second"]) |
---|
663 | |
---|
664 | for prefix, op in (("Min", min), ("Max", max), ("Avg", avg)): |
---|
665 | add(prefix, op, "Batch Delay (ms)", ["batch.delay.%s", "batch_delay.%s", "%s_batch_delay"]) |
---|
666 | add(prefix, op, "Actual Batch Delay (ms)", ["batch.actual_delay.%s"]) |
---|
667 | add(prefix, op, "Client Latency (ms)", ["client.latency.%s", "client_latency.%s", "%s_client_latency"]) |
---|
668 | add(prefix, op, "Client Ping Latency (ms)", ["client.ping_latency.%s", "client_ping_latency.%s"]) |
---|
669 | add(prefix, op, "Server Ping Latency (ms)", ["server.ping_latency.%s", "server_ping_latency.%s", "server_latency.%s", "%s_server_latency"]) |
---|
670 | add(prefix, op, "Damage Latency (ms)", ["damage.in_latency.%s", "damage_in_latency.%s"]) |
---|
671 | |
---|
672 | add(prefix, op, "Quality", ["^window\[\d+\].encoding.quality.%s$"]) |
---|
673 | add(prefix, op, "Speed", ["^window\[\d+\].encoding.speed.%s$"]) |
---|
674 | |
---|
675 | def addset(name, prop_name): |
---|
676 | regex = re.compile(prop_name) |
---|
677 | def getdictvalues(from_dict): |
---|
678 | return [from_dict.get(x) for x in from_dict.keys() if regex.match(x)] |
---|
679 | values = getdictvalues(d) |
---|
680 | for s in all_stats: #add all previously found values to list |
---|
681 | values += getdictvalues(s) |
---|
682 | data[name] = list(set(values)) |
---|
683 | |
---|
684 | #video encoder |
---|
685 | addset("Video Encoder", "^window\[\d+\].encoder$") |
---|
686 | #record CSC: |
---|
687 | addset("CSC", "^window\[\d+\].csc$") |
---|
688 | addset("CSC Mode", "^window\[\d+\].csc.dst_format$") |
---|
689 | addset("Scaling", "^window\[\d+\].scaling$") |
---|
690 | #packet layer: |
---|
691 | addset("Compressors", "connection.compression$") |
---|
692 | addset("Packet Encoders", "connection.encoder$") |
---|
693 | #add this record to the list: |
---|
694 | all_stats.append(data) |
---|
695 | return data |
---|
696 | |
---|
697 | def get_xpra_start_server_command(): |
---|
698 | cmd = [XPRA_BIN, "--no-daemon", "--bind-tcp=0.0.0.0:%s" % config.PORT] |
---|
699 | if config.XPRA_FORCE_XDUMMY: |
---|
700 | cmd.append("--xvfb=%s -nolisten tcp +extension GLX +extension RANDR +extension RENDER -logfile %s -config %s" % (config.XORG_BIN, config.XORG_LOG, config.XORG_CONFIG)) |
---|
701 | if XPRA_VERSION_NO>=[0, 5]: |
---|
702 | cmd.append("--no-notifications") |
---|
703 | cmd += get_auth_args() |
---|
704 | cmd.append("--no-pulseaudio") |
---|
705 | # NOTE: This is added for testing |
---|
706 | cmd.append("-d dbus") |
---|
707 | cmd += ["start", ":%s" % config.DISPLAY_NO] |
---|
708 | return cmd |
---|
709 | |
---|
710 | def test_xpra(): |
---|
711 | print("") |
---|
712 | print("*********************************************************") |
---|
713 | print(" Xpra tests") |
---|
714 | print("") |
---|
715 | tests = [] |
---|
716 | for connect_option, encryption in config.XPRA_CONNECT_OPTIONS: |
---|
717 | shaping_options = config.TRICKLE_SHAPING_OPTIONS |
---|
718 | if connect_option=="unix-domain": |
---|
719 | shaping_options = [config.NO_SHAPING] |
---|
720 | for down,up,latency in shaping_options: |
---|
721 | for x11_test_command in config.X11_TEST_COMMANDS: |
---|
722 | for encoding in config.XPRA_TEST_ENCODINGS: |
---|
723 | if XPRA_VERSION_NO>=[0, 10]: |
---|
724 | opengl_options = config.XPRA_OPENGL_OPTIONS.get(encoding, [True]) |
---|
725 | elif XPRA_VERSION_NO>=[0, 9]: |
---|
726 | opengl_options = config.XPRA_OPENGL_OPTIONS.get(encoding, [False]) |
---|
727 | else: |
---|
728 | opengl_options = [False] |
---|
729 | for opengl in opengl_options: |
---|
730 | quality_options = config.XPRA_ENCODING_QUALITY_OPTIONS.get(encoding, [-1]) |
---|
731 | for quality in quality_options: |
---|
732 | speed_options = config.XPRA_ENCODING_SPEED_OPTIONS.get(encoding, [-1]) |
---|
733 | for speed in speed_options: |
---|
734 | for speaker in XPRA_SPEAKER_OPTIONS: |
---|
735 | for mic in XPRA_MICROPHONE_OPTIONS: |
---|
736 | comp_options = [None] |
---|
737 | if XPRA_VERSION_NO>=[0, 13]: |
---|
738 | comp_options = config.XPRA_COMPRESSORS_OPTIONS |
---|
739 | for comp in comp_options: |
---|
740 | comp_level_options = config.XPRA_COMPRESSION_LEVEL_OPTIONS |
---|
741 | for compression in comp_level_options: |
---|
742 | packet_encoders_options = [None] |
---|
743 | if XPRA_VERSION_NO>=[0, 14]: |
---|
744 | packet_encoders_options = config.XPRA_PACKET_ENCODERS_OPTIONS |
---|
745 | for packet_encoders in packet_encoders_options: |
---|
746 | cmd = trickle_command(down, up, latency) |
---|
747 | cmd += [XPRA_BIN, "attach"] |
---|
748 | if connect_option=="ssh": |
---|
749 | cmd.append("ssh:%s:%s" % (config.IP, config.DISPLAY_NO)) |
---|
750 | elif connect_option=="tcp": |
---|
751 | cmd.append("tcp:%s:%s" % (config.IP, config.PORT)) |
---|
752 | else: |
---|
753 | cmd.append(":%s" % (config.DISPLAY_NO)) |
---|
754 | if XPRA_VERSION_NO>=[0, 15]: |
---|
755 | cmd.append("--readonly=yes") |
---|
756 | else: |
---|
757 | cmd.append("--readonly") |
---|
758 | cmd += get_auth_args() |
---|
759 | if packet_encoders: |
---|
760 | cmd += ["--packet-encoders=%s" % packet_encoders] |
---|
761 | if comp: |
---|
762 | cmd += ["--compressors=%s" % comp] |
---|
763 | if compression is not None: |
---|
764 | cmd += ["-z", str(compression)] |
---|
765 | if XPRA_VERSION_NO>=[0, 3]: |
---|
766 | cmd.append("--enable-pings") |
---|
767 | cmd.append("--no-clipboard") |
---|
768 | if XPRA_VERSION_NO>=[0, 5]: |
---|
769 | cmd.append("--no-bell") |
---|
770 | cmd.append("--no-cursors") |
---|
771 | cmd.append("--no-notifications") |
---|
772 | if XPRA_VERSION_NO>=[0, 12]: |
---|
773 | if config.XPRA_MDNS: |
---|
774 | cmd.append("--mdns") |
---|
775 | else: |
---|
776 | cmd.append("--no-mdns") |
---|
777 | if XPRA_VERSION_NO>=[0, 8] and encryption: |
---|
778 | cmd.append("--encryption=%s" % encryption) |
---|
779 | if speed>=0: |
---|
780 | cmd.append("--speed=%s" % speed) |
---|
781 | if quality>=0: |
---|
782 | if XPRA_VERSION_NO>=[0, 7]: |
---|
783 | cmd.append("--quality=%s" % quality) |
---|
784 | else: |
---|
785 | cmd.append("--jpeg-quality=%s" % quality) |
---|
786 | name = "%s-%s" % (encoding, quality) |
---|
787 | else: |
---|
788 | name = encoding |
---|
789 | if speaker is None: |
---|
790 | if XPRA_VERSION_NO>=[0, 8]: |
---|
791 | cmd.append("--no-speaker") |
---|
792 | else: |
---|
793 | cmd.append("--speaker-codec=%s" % speaker) |
---|
794 | if mic is None: |
---|
795 | if XPRA_VERSION_NO>=[0, 8]: |
---|
796 | cmd.append("--no-microphone") |
---|
797 | else: |
---|
798 | cmd.append("--microphone-codec=%s" % mic) |
---|
799 | if encoding!="mmap": |
---|
800 | cmd.append("--no-mmap") |
---|
801 | cmd.append("--encoding=%s" % encoding) |
---|
802 | if XPRA_VERSION_NO>=[0, 9]: |
---|
803 | cmd.append("--opengl=%s" % opengl) |
---|
804 | command_name = get_command_name(x11_test_command) |
---|
805 | test_name = "%s (%s - %s - %s - %s - via %s)" % \ |
---|
806 | (name, command_name, compression, encryption, trickle_str(down, up, latency), connect_option) |
---|
807 | tests.append((test_name, "xpra", XPRA_VERSION, XPRA_VERSION, \ |
---|
808 | encoding, quality, speed, |
---|
809 | opengl, compression, encryption, connect_option, \ |
---|
810 | (down,up,latency), x11_test_command, cmd)) |
---|
811 | return with_server(get_xpra_start_server_command(), XPRA_SERVER_STOP_COMMANDS, tests, xpra_get_stats) |
---|
812 | |
---|
813 | def get_x11_client_window_info(display, *app_name_strings): |
---|
814 | env = os.environ.copy() |
---|
815 | if display: |
---|
816 | env["DISPLAY"] = display |
---|
817 | wininfo = getoutput(["xwininfo", "-root", "-tree"], env) |
---|
818 | for line in wininfo.splitlines(): |
---|
819 | if not line: |
---|
820 | continue |
---|
821 | found = True |
---|
822 | for x in app_name_strings: |
---|
823 | if not line.find(x)>=0: |
---|
824 | found = False |
---|
825 | break |
---|
826 | if not found: |
---|
827 | continue |
---|
828 | parts = line.split() |
---|
829 | if not parts[0].startswith("0x"): |
---|
830 | continue |
---|
831 | #found a window which matches the name we are looking for! |
---|
832 | wid = parts[0] |
---|
833 | x, y, w, h = 0, 0, 0, 0 |
---|
834 | dims = parts[-2] #ie: 400x300+20+10 |
---|
835 | dp = dims.split("+") #["400x300", "20", "10"] |
---|
836 | if len(dp)==3: |
---|
837 | d = dp[0] #"400x300" |
---|
838 | x = int(dp[1]) #20 |
---|
839 | y = int(dp[2]) #10 |
---|
840 | wh = d.split("x") #["400", "300"] |
---|
841 | if len(wh)==2: |
---|
842 | w = int(wh[0]) #400 |
---|
843 | h = int(wh[1]) #300 |
---|
844 | print("Found window for '%s': %s - %sx%s" % (app_name_strings, wid, w, h)) |
---|
845 | return wid, x, y, w, h |
---|
846 | return None |
---|
847 | |
---|
848 | def get_vnc_stats(initial_stats=None, all_stats=[]): |
---|
849 | #print("get_vnc_stats(%s)" % last_record) |
---|
850 | if initial_stats==None: |
---|
851 | #this is the initial call, |
---|
852 | #start the thread to watch the output of tcbench |
---|
853 | #we first need to figure out the dimensions of the client window |
---|
854 | #within the Xvnc server, the use those dimensions to tell tcbench |
---|
855 | #where to look in the vncviewer client window |
---|
856 | test_window_info = get_x11_client_window_info(":%s" % config.DISPLAY_NO) |
---|
857 | print("info for client test window: %s" % str(test_window_info)) |
---|
858 | info = get_x11_client_window_info(None, "TigerVNC: x11", "Vncviewer") |
---|
859 | if not info: |
---|
860 | return {} |
---|
861 | print("info for TigerVNC: %s" % str(info)) |
---|
862 | wid, _, _, w, h = info |
---|
863 | if not wid: |
---|
864 | return {} |
---|
865 | if test_window_info: |
---|
866 | _, _, _, w, h = test_window_info |
---|
867 | command = [config.TCBENCH, "-wh%s" % wid, "-t%s" % (config.MEASURE_TIME-5)] |
---|
868 | if w>0 and h>0: |
---|
869 | command.append("-x%s" % int(w/2)) |
---|
870 | command.append("-y%s" % int(h/2)) |
---|
871 | if os.path.exists(config.TCBENCH_LOG): |
---|
872 | os.unlink(config.TCBENCH_LOG) |
---|
873 | tcbench_log = open(config.TCBENCH_LOG, 'w') |
---|
874 | try: |
---|
875 | print("tcbench starting: %s, logging to %s" % (command, config.TCBENCH_LOG)) |
---|
876 | proc = subprocess.Popen(command, stdin=None, stdout=tcbench_log, stderr=tcbench_log) |
---|
877 | return {"tcbench" : proc} |
---|
878 | except Exception as e: |
---|
879 | import traceback |
---|
880 | traceback.print_exc() |
---|
881 | print("error running %s: %s" % (command, e)) |
---|
882 | return {} #we failed... |
---|
883 | regions_s = "" |
---|
884 | if "tcbench" in initial_stats: |
---|
885 | #found the process watcher, |
---|
886 | #parse the tcbench output and look for frames/sec: |
---|
887 | process = initial_stats.get("tcbench") |
---|
888 | assert type(process)==subprocess.Popen |
---|
889 | #print("get_vnc_stats(%s) process.poll()=%s" % (last_record, process.poll())) |
---|
890 | if process.poll() is None: |
---|
891 | try_to_stop(process) |
---|
892 | try_to_kill(process, 2) |
---|
893 | else: |
---|
894 | with open(config.TCBENCH_LOG, mode='rb') as f: |
---|
895 | out = f.read() |
---|
896 | #print("get_vnc_stats(%s) tcbench output=%s" % (last_record, out)) |
---|
897 | for line in out.splitlines(): |
---|
898 | if not line.find("Frames/sec:")>=0: |
---|
899 | continue |
---|
900 | parts = line.split() |
---|
901 | regions_s = parts[-1] |
---|
902 | print("Frames/sec=%s" % regions_s) |
---|
903 | return { |
---|
904 | "Regions/s" : regions_s, |
---|
905 | } |
---|
906 | |
---|
907 | def test_vnc(): |
---|
908 | print("") |
---|
909 | print("*********************************************************") |
---|
910 | print(" VNC tests") |
---|
911 | print("") |
---|
912 | tests = [] |
---|
913 | for down,up,latency in config.TRICKLE_SHAPING_OPTIONS: |
---|
914 | for x11_test_command in config.X11_TEST_COMMANDS: |
---|
915 | for encoding in config.VNC_ENCODINGS: |
---|
916 | for zlib in config.VNC_ZLIB_OPTIONS: |
---|
917 | for compression in config.VNC_COMPRESSION_OPTIONS: |
---|
918 | jpeg_quality = [8] |
---|
919 | if encoding=="Tight": |
---|
920 | jpeg_quality = config.VNC_JPEG_OPTIONS |
---|
921 | for jpegq in jpeg_quality: |
---|
922 | cmd = trickle_command(down, up, latency) |
---|
923 | cmd += [config.VNCVIEWER_BIN, "%s::%s" % (config.IP, config.PORT), |
---|
924 | "--ViewOnly", |
---|
925 | "--ZlibLevel=%s" % str(zlib), |
---|
926 | "--CompressLevel=%s" % str(compression), |
---|
927 | ] |
---|
928 | if encoding=="auto": |
---|
929 | cmd.append("--AutoSelect=1") |
---|
930 | else: |
---|
931 | cmd.append("--AutoSelect=0") |
---|
932 | cmd.append("--PreferredEncoding=%s" % encoding) |
---|
933 | if jpegq<0: |
---|
934 | cmd.append("--NoJPEG=1") |
---|
935 | jpegtxt = "nojpeg" |
---|
936 | else: |
---|
937 | cmd.append("--NoJPEG=0") |
---|
938 | cmd.append("--QualityLevel=%s" % jpegq) |
---|
939 | jpegtxt = "jpeg=%s" % jpegq |
---|
940 | #make a descriptive title: |
---|
941 | if zlib==-1: |
---|
942 | zlibtxt = "nozlib" |
---|
943 | else: |
---|
944 | zlibtxt = "zlib=%s" % zlib |
---|
945 | command_name = get_command_name(x11_test_command) |
---|
946 | test_name = "vnc (%s - %s - %s - compression=%s - %s - %s)" % \ |
---|
947 | (command_name, encoding, zlibtxt, compression, jpegtxt, trickle_str(down, up, latency)) |
---|
948 | tests.append((test_name, "vnc", XVNC_VERSION, VNCVIEWER_VERSION, \ |
---|
949 | encoding, False, compression, None, False, \ |
---|
950 | (down,up,latency), x11_test_command, cmd)) |
---|
951 | return with_server(config.XVNC_SERVER_START_COMMAND, config.XVNC_SERVER_STOP_COMMANDS, tests, get_vnc_stats) |
---|
952 | |
---|
953 | def main(): |
---|
954 | #before doing anything, check that the firewall is setup correctly: |
---|
955 | get_iptables_INPUT_count() |
---|
956 | get_iptables_OUTPUT_count() |
---|
957 | |
---|
958 | #If CUSTOM_PARAMS are supplied on the command line, they override what's in config |
---|
959 | if (len(sys.argv) > 3): |
---|
960 | config.CUSTOM_PARAMS = " ".join(sys.argv[3:]) |
---|
961 | config.print_options() |
---|
962 | |
---|
963 | xpra_results = [] |
---|
964 | if config.TEST_XPRA: |
---|
965 | xpra_results = test_xpra() |
---|
966 | vnc_results = [] |
---|
967 | if config.TEST_VNC: |
---|
968 | vnc_results = test_vnc() |
---|
969 | |
---|
970 | if (len(sys.argv) > 2): |
---|
971 | csv_name = sys.argv[2] |
---|
972 | else: |
---|
973 | csv_name = None |
---|
974 | |
---|
975 | print("*"*80) |
---|
976 | print("RESULTS:") |
---|
977 | print("") |
---|
978 | |
---|
979 | out_lines = [] |
---|
980 | out_line = ", ".join(HEADERS) |
---|
981 | print out_line |
---|
982 | out_lines.append(out_line) |
---|
983 | |
---|
984 | def s(x): |
---|
985 | if x is None: |
---|
986 | return "" |
---|
987 | elif type(x) in (list, tuple, set): |
---|
988 | return '"' + (", ".join(list(x))) + '"' |
---|
989 | elif type(x) in (unicode, str): |
---|
990 | if len(x)==0: |
---|
991 | return "" |
---|
992 | return '"%s"' % x |
---|
993 | elif type(x) in (float, long, int): |
---|
994 | return str(x) |
---|
995 | else: |
---|
996 | return "unhandled-type: %s" % type(x) |
---|
997 | |
---|
998 | for result in xpra_results+vnc_results: |
---|
999 | out_line = ", ".join([s(x) for x in result]) |
---|
1000 | print(out_line) |
---|
1001 | out_lines.append(out_line) |
---|
1002 | |
---|
1003 | if (csv_name != None): |
---|
1004 | with open(csv_name, "w") as csv: |
---|
1005 | for line in out_lines: |
---|
1006 | csv.write(line+"\n") |
---|
1007 | |
---|
1008 | if __name__ == "__main__": |
---|
1009 | main() |
---|