diff --git a/indra/cmake/run_build_test.py b/indra/cmake/run_build_test.py
index af2d8a0153c79930aa152109bc5771c57f4e2554..1e92868ae76fdfbad51d94e418d784b865ced85c 100755
--- a/indra/cmake/run_build_test.py
+++ b/indra/cmake/run_build_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 """\
 @file   run_build_test.py
 @author Nat Goodspeed
@@ -17,7 +17,7 @@
 
 Example:
 
-python run_build_test.py -DFOO=bar myprog somearg otherarg
+python3 run_build_test.py -DFOO=bar myprog somearg otherarg
 
 sets environment variable FOO=bar, then runs:
 myprog somearg otherarg
@@ -47,7 +47,7 @@
 import os
 import sys
 import errno
-import HTMLParser
+import html.parser
 import re
 import signal
 import subprocess
@@ -111,10 +111,10 @@ def main(command, arguments=[], libpath=[], vars={}):
     # Now handle arbitrary environment variables. The tricky part is ensuring
     # that all the keys and values we try to pass are actually strings.
     if vars:
-        for key, value in vars.items():
+        for key, value in list(vars.items()):
             # As noted a few lines above, facilitate copy-paste rerunning.
             log.info("%s='%s' \\" % (key, value))
-    os.environ.update(dict([(str(key), str(value)) for key, value in vars.iteritems()]))
+    os.environ.update(dict([(str(key), str(value)) for key, value in vars.items()]))
     # Run the child process.
     command_list = [command]
     command_list.extend(arguments)
@@ -177,7 +177,7 @@ def translate_rc(rc):
         try:
             table = get_windows_table()
             symbol, desc = table[hexrc]
-        except Exception, err:
+        except Exception as err:
             log.error("(%s -- carrying on)" % err)
             log.error("terminated with rc %s (%s)" % (rc, hexrc))
         else:
@@ -194,7 +194,7 @@ def translate_rc(rc):
             strc = str(rc)
         return "terminated by signal %s" % strc
 
-class TableParser(HTMLParser.HTMLParser):
+class TableParser(html.parser.HTMLParser):
     """
     This HTMLParser subclass is designed to parse the table we know exists
     in windows-rcs.html, hopefully without building in too much knowledge of
@@ -204,9 +204,7 @@ class TableParser(HTMLParser.HTMLParser):
     whitespace = re.compile(r'\s*$')
 
     def __init__(self):
-        # Because Python 2.x's HTMLParser is an old-style class, we must use
-        # old-style syntax to forward the __init__() call -- not super().
-        HTMLParser.HTMLParser.__init__(self)
+        super().__init__()
         # this will collect all the data, eventually
         self.table = []
         # Stack whose top (last item) indicates where to append current
diff --git a/indra/fix-incredibuild.py b/indra/fix-incredibuild.py
index 6b13a7b466f59cfed4b13705f525474fe653f092..3bebe24f8092664622d281c09ceced5f849f8f9c 100755
--- a/indra/fix-incredibuild.py
+++ b/indra/fix-incredibuild.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 ## 
 ## $LicenseInfo:firstyear=2011&license=viewerlgpl$
 ## Second Life Viewer Source Code
@@ -27,7 +27,7 @@
 
 def delete_file_types(path, filetypes):
     if os.path.exists(path):
-        print 'Cleaning: ' + path
+        print('Cleaning: ' + path)
         orig_dir = os.getcwd();
         os.chdir(path)
         filelist = []
diff --git a/indra/llcorehttp/tests/test_llcorehttp_peer.py b/indra/llcorehttp/tests/test_llcorehttp_peer.py
index 6e5ca38e89475eb415ee4c6500d81b147fc1a64f..4dea099637a390484aa32a45b77a612e281de2ec 100755
--- a/indra/llcorehttp/tests/test_llcorehttp_peer.py
+++ b/indra/llcorehttp/tests/test_llcorehttp_peer.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 """\
 @file   test_llsdmessage_peer.py
 @author Nat Goodspeed
@@ -34,11 +34,8 @@
 import time
 import select
 import getopt
-try:
-    from cStringIO import StringIO
-except ImportError:
-    from StringIO import StringIO
-from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
+from io import StringIO
+from http.server import HTTPServer, BaseHTTPRequestHandler
 
 from llbase.fastest_elementtree import parse as xml_parse
 from llbase import llsd
@@ -127,8 +124,8 @@ def do_GET(self, withdata=True):
         try:
             self.answer(dict(reply="success", status=200,
                              reason="Your GET operation worked"))
-        except self.ignore_exceptions, e:
-            print >> sys.stderr, "Exception during GET (ignoring): %s" % str(e)
+        except self.ignore_exceptions as e:
+            print("Exception during GET (ignoring): %s" % str(e), file=sys.stderr)
 
     def do_POST(self):
         # Read the provided POST data.
@@ -136,8 +133,8 @@ def do_POST(self):
         try:
             self.answer(dict(reply="success", status=200,
                              reason=self.read()))
-        except self.ignore_exceptions, e:
-            print >> sys.stderr, "Exception during POST (ignoring): %s" % str(e)
+        except self.ignore_exceptions as e:
+            print("Exception during POST (ignoring): %s" % str(e), file=sys.stderr)
 
     def do_PUT(self):
         # Read the provided PUT data.
@@ -145,8 +142,8 @@ def do_PUT(self):
         try:
             self.answer(dict(reply="success", status=200,
                              reason=self.read()))
-        except self.ignore_exceptions, e:
-            print >> sys.stderr, "Exception during PUT (ignoring): %s" % str(e)
+        except self.ignore_exceptions as e:
+            print("Exception during PUT (ignoring): %s" % str(e), file=sys.stderr)
 
     def answer(self, data, withdata=True):
         debug("%s.answer(%s): self.path = %r", self.__class__.__name__, data, self.path)
@@ -255,7 +252,7 @@ def answer(self, data, withdata=True):
             self.end_headers()
 
     def reflect_headers(self):
-        for name in self.headers.keys():
+        for name in list(self.headers.keys()):
             # print "Header:  %s: %s" % (name, self.headers[name])
             self.send_header("X-Reflect-" + name, self.headers[name])
 
@@ -283,10 +280,10 @@ class Server(HTTPServer):
     # default behavior which *shouldn't* cause the program to return
     # a failure status.
     def handle_error(self, request, client_address):
-        print '-'*40
-        print 'Ignoring exception during processing of request from',
-        print client_address
-        print '-'*40
+        print('-'*40)
+        print('Ignoring exception during processing of request from', end=' ')
+        print(client_address)
+        print('-'*40)
 
 if __name__ == "__main__":
     do_valgrind = False
@@ -307,7 +304,7 @@ def handle_error(self, request, client_address):
         # "Then there's Windows"
         # Instantiate a Server(TestHTTPRequestHandler) on the first free port
         # in the specified port range.
-        httpd, port = freeport(xrange(8000, 8020), make_server)
+        httpd, port = freeport(range(8000, 8020), make_server)
 
     # Pass the selected port number to the subject test program via the
     # environment. We don't want to impose requirements on the test program's
diff --git a/indra/llmessage/tests/test_llsdmessage_peer.py b/indra/llmessage/tests/test_llsdmessage_peer.py
index 511fd31fc86e6a0c6dccdd4a5023e48b42907316..5ba0749e3168fd9e92dab5d9162a7c0ca95f0cff 100755
--- a/indra/llmessage/tests/test_llsdmessage_peer.py
+++ b/indra/llmessage/tests/test_llsdmessage_peer.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 """\
 @file   test_llsdmessage_peer.py
 @author Nat Goodspeed
@@ -31,7 +31,7 @@
 
 import os
 import sys
-from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
+from http.server import HTTPServer, BaseHTTPRequestHandler
 
 from llbase.fastest_elementtree import parse as xml_parse
 from llbase import llsd
@@ -165,7 +165,7 @@ class Server(HTTPServer):
         # "Then there's Windows"
         # Instantiate a Server(TestHTTPRequestHandler) on the first free port
         # in the specified port range.
-        httpd, port = freeport(xrange(8000, 8020), make_server)
+        httpd, port = freeport(range(8000, 8020), make_server)
 
     # Pass the selected port number to the subject test program via the
     # environment. We don't want to impose requirements on the test program's
diff --git a/indra/llmessage/tests/testrunner.py b/indra/llmessage/tests/testrunner.py
index baabe05f1b5cd3d1d803dc12d2ee14c11756e4aa..47c09ca245305183c4382e1b2427d177950f3e8f 100755
--- a/indra/llmessage/tests/testrunner.py
+++ b/indra/llmessage/tests/testrunner.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 """\
 @file   testrunner.py
 @author Nat Goodspeed
@@ -41,7 +41,7 @@
 
 if VERBOSE:
     def debug(fmt, *args):
-        print fmt % args
+        print(fmt % args)
         sys.stdout.flush()
 else:
     debug = lambda *args: None
@@ -99,14 +99,14 @@ class Server(HTTPServer):
         # error because we can't return meaningful values. We have no 'port',
         # therefore no 'expr(port)'.
         portiter = iter(portlist)
-        port = portiter.next()
+        port = next(portiter)
 
         while True:
             try:
                 # If this value of port works, return as promised.
                 value = expr(port)
 
-            except socket.error, err:
+            except socket.error as err:
                 # Anything other than 'Address already in use', propagate
                 if err.args[0] != errno.EADDRINUSE:
                     raise
@@ -117,9 +117,9 @@ class Server(HTTPServer):
                 type, value, tb = sys.exc_info()
                 try:
                     try:
-                        port = portiter.next()
+                        port = next(portiter)
                     except StopIteration:
-                        raise type, value, tb
+                        raise type(value).with_traceback(tb)
                 finally:
                     # Clean up local traceback, see docs for sys.exc_info()
                     del tb
@@ -138,7 +138,7 @@ class Server(HTTPServer):
             # If we've actually arrived at this point, portiter.next() delivered a
             # new port value. Loop back to pass that to expr(port).
 
-    except Exception, err:
+    except Exception as err:
         debug("*** freeport() raising %s: %s", err.__class__.__name__, err)
         raise
 
@@ -227,13 +227,13 @@ def test_freeport():
     def exc(exception_class, *args):
         try:
             yield
-        except exception_class, err:
+        except exception_class as err:
             for i, expected_arg in enumerate(args):
                 assert expected_arg == err.args[i], \
                        "Raised %s, but args[%s] is %r instead of %r" % \
                        (err.__class__.__name__, i, err.args[i], expected_arg)
-            print "Caught expected exception %s(%s)" % \
-                  (err.__class__.__name__, ', '.join(repr(arg) for arg in err.args))
+            print("Caught expected exception %s(%s)" % \
+                  (err.__class__.__name__, ', '.join(repr(arg) for arg in err.args)))
         else:
             assert False, "Failed to raise " + exception_class.__class__.__name__
 
@@ -270,18 +270,18 @@ class SomeError(Exception): pass
     # This is the magic exception that should prompt us to retry
     inuse = socket.error(errno.EADDRINUSE, 'Address already in use')
     # Get the iterator to our ports list so we can check later if we've used all
-    ports = iter(xrange(5))
+    ports = iter(range(5))
     with exc(socket.error, errno.EADDRINUSE):
         freeport(ports, lambda port: raiser(inuse))
     # did we entirely exhaust 'ports'?
     with exc(StopIteration):
-        ports.next()
+        next(ports)
 
-    ports = iter(xrange(2))
+    ports = iter(range(2))
     # Any exception but EADDRINUSE should quit immediately
     with exc(SomeError):
         freeport(ports, lambda port: raiser(SomeError()))
-    assert_equals(ports.next(), 1)
+    assert_equals(next(ports), 1)
 
     # ----------- freeport() with platform-dependent socket stuff ------------
     # This is what we should've had unit tests to begin with (see CHOP-661).
@@ -290,14 +290,14 @@ def newbind(port):
         sock.bind(('127.0.0.1', port))
         return sock
 
-    bound0, port0 = freeport(xrange(7777, 7780), newbind)
+    bound0, port0 = freeport(range(7777, 7780), newbind)
     assert_equals(port0, 7777)
-    bound1, port1 = freeport(xrange(7777, 7780), newbind)
+    bound1, port1 = freeport(range(7777, 7780), newbind)
     assert_equals(port1, 7778)
-    bound2, port2 = freeport(xrange(7777, 7780), newbind)
+    bound2, port2 = freeport(range(7777, 7780), newbind)
     assert_equals(port2, 7779)
     with exc(socket.error, errno.EADDRINUSE):
-        bound3, port3 = freeport(xrange(7777, 7780), newbind)
+        bound3, port3 = freeport(range(7777, 7780), newbind)
 
 if __name__ == "__main__":
     test_freeport()
diff --git a/indra/newview/build_win32_appConfig.py b/indra/newview/build_win32_appConfig.py
index 9fdceee1be91149947d95bf8227e1632fcd8af27..ef2685a62549d0ef3c1a6b8d1bc3aa3da3ab24fc 100755
--- a/indra/newview/build_win32_appConfig.py
+++ b/indra/newview/build_win32_appConfig.py
@@ -33,12 +33,12 @@ def munge_binding_redirect_version(src_manifest_name, src_config_name, dst_confi
     config_dom = parse(src_config_name)
     node = config_dom.getElementsByTagName('bindingRedirect')[0]
     node.setAttribute('newVersion', manifest_assm_ver)
-    src_old_ver = re.match('([^-]*-).*', node.getAttribute('oldVersion')).group(1)
+    src_old_ver = re.match(r'([^-]*-).*', node.getAttribute('oldVersion')).group(1)
     node.setAttribute('oldVersion', src_old_ver + manifest_assm_ver)
     comment = config_dom.createComment("This file is automatically generated by the build. see indra/newview/build_win32_appConfig.py")
     config_dom.insertBefore(comment, config_dom.childNodes[0])
 
-    print "Writing: " + dst_config_name
+    print("Writing: " + dst_config_name)
     f = open(dst_config_name, 'w')
     config_dom.writexml(f)
     f.close()
diff --git a/indra/newview/generate_breakpad_symbols.py b/indra/newview/generate_breakpad_symbols.py
index 2fc5abe8bacf6fc65450d688d872a81cf6cc3575..4a2ff0a2caa76eeb9f17c636982ec76701a52768 100755
--- a/indra/newview/generate_breakpad_symbols.py
+++ b/indra/newview/generate_breakpad_symbols.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 """\
 @file generate_breakpad_symbols.py
 @author Brad Kittenbrink <brad@lindenlab.com>
@@ -37,13 +37,13 @@
 import shlex
 import subprocess
 import tarfile
-import StringIO
+import io
 import pprint
 
 DEBUG=False
 
 def usage():
-    print >>sys.stderr, "usage: %s search_dirs viewer_exes libs_suffix dump_syms_tool viewer_symbol_file" % sys.argv[0]
+    print("usage: %s search_dirs viewer_exes libs_suffix dump_syms_tool viewer_symbol_file" % sys.argv[0], file=sys.stderr)
 
 class MissingModuleError(Exception):
     def __init__(self, modules):
@@ -51,10 +51,10 @@ def __init__(self, modules):
         self.modules = modules
 
 def main(configuration, search_dirs, viewer_exes, libs_suffix, dump_syms_tool, viewer_symbol_file):
-    print "generate_breakpad_symbols run with args: %s" % str((configuration, search_dirs, viewer_exes, libs_suffix, dump_syms_tool, viewer_symbol_file))
+    print("generate_breakpad_symbols run with args: %s" % str((configuration, search_dirs, viewer_exes, libs_suffix, dump_syms_tool, viewer_symbol_file)))
 
-    if not re.match("release", configuration, re.IGNORECASE):
-        print "skipping breakpad symbol generation for non-release build."
+    if not re.match(r"release", configuration, re.IGNORECASE):
+        print("skipping breakpad symbol generation for non-release build.")
         return 0
 
     # split up list of viewer_exes
@@ -75,12 +75,12 @@ def list_files():
         for search_dir in search_dirs:
             for (dirname, subdirs, filenames) in os.walk(search_dir):
                 if DEBUG:
-                    print "scanning '%s' for modules..." % dirname
-                for f in itertools.ifilter(matches, filenames):
+                    print("scanning '%s' for modules..." % dirname)
+                for f in filter(matches, filenames):
                     yield os.path.join(dirname, f)
 
     def dump_module(m):
-        print "dumping module '%s' with '%s'..." % (m, dump_syms_tool)
+        print("dumping module '%s' with '%s'..." % (m, dump_syms_tool))
         dsym_full_path = m
         child = subprocess.Popen([dump_syms_tool, dsym_full_path] , stdout=subprocess.PIPE)
         out, err = child.communicate()
@@ -91,27 +91,27 @@ def dump_module(m):
         
     for m in list_files():
         if DEBUG:
-            print "examining module '%s' ... " % m,
+            print("examining module '%s' ... " % m, end=' ')
         filename=os.path.basename(m)
         if -1 != m.find("DWARF"):
             # Just use this module; it has the symbols we want.
             modules[filename] = m
             if DEBUG:
-                print "found dSYM entry"
+                print("found dSYM entry")
         elif filename not in modules:
             # Only use this if we don't already have a (possibly better) entry.
             modules[filename] = m
             if DEBUG:
-                print "found new entry"
+                print("found new entry")
         elif DEBUG:
-            print "ignoring entry"
+            print("ignoring entry")
 
 
-    print "Found these following modules:"
+    print("Found these following modules:")
     pprint.pprint( modules )
 
     out = tarfile.open(viewer_symbol_file, 'w:bz2')
-    for (filename,status,symbols,err) in itertools.imap(dump_module, modules.values()):
+    for (filename,status,symbols,err) in map(dump_module, list(modules.values())):
         if status == 0:
             module_line = symbols[:symbols.index('\n')]
             module_line = module_line.split()
@@ -121,20 +121,20 @@ def dump_module(m):
                 mod_name = module[:module.rindex('.pdb')]
             else:
                 mod_name = module
-            symbolfile = StringIO.StringIO(symbols)
+            symbolfile = io.StringIO(symbols)
             info = tarfile.TarInfo("%(module)s/%(hash_id)s/%(mod_name)s.sym" % dict(module=module, hash_id=hash_id, mod_name=mod_name))
             info.size = symbolfile.len
             out.addfile(info, symbolfile)
         else:
-            print >>sys.stderr, "warning: failed to dump symbols for '%s': %s" % (filename, err)
+            print("warning: failed to dump symbols for '%s': %s" % (filename, err), file=sys.stderr)
 
     out.close()
 
     missing_modules = [m for (m,_) in
-        itertools.ifilter(lambda (k,v): not v, found_required.iteritems())
+        filter(lambda k_v: not k_v[1], iter(found_required.items()))
     ]
     if missing_modules:
-        print >> sys.stderr, "failed to generate %s" % viewer_symbol_file
+        print("failed to generate %s" % viewer_symbol_file, file=sys.stderr)
         os.remove(viewer_symbol_file)
         raise MissingModuleError(missing_modules)
 
@@ -148,13 +148,13 @@ def match_module_basename(m):
                    == os.path.splitext(os.path.basename(m))[0].lower()
         # there must be at least one .sym file in tarfile_members that matches
         # each required module (ignoring file extensions)
-        if not any(itertools.imap(match_module_basename, tarfile_members)):
-            print >> sys.stderr, "failed to find required %s in generated %s" \
-                    % (required_module, viewer_symbol_file)
+        if not any(map(match_module_basename, tarfile_members)):
+            print("failed to find required %s in generated %s" \
+                    % (required_module, viewer_symbol_file), file=sys.stderr)
             os.remove(viewer_symbol_file)
             raise MissingModuleError([required_module])
 
-    print "successfully generated %s including required modules '%s'" % (viewer_symbol_file, viewer_exes)
+    print("successfully generated %s including required modules '%s'" % (viewer_symbol_file, viewer_exes))
 
     return 0
 
diff --git a/indra/newview/tests/test_llxmlrpc_peer.py b/indra/newview/tests/test_llxmlrpc_peer.py
index f644afa1a2a9d3dce82a191e68cf8595c50ce437..365848b8193e24e1f2a72c04bda3d2dc84c25977 100755
--- a/indra/newview/tests/test_llxmlrpc_peer.py
+++ b/indra/newview/tests/test_llxmlrpc_peer.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 """\
 @file   test_llxmlrpc_peer.py
 @author Nat Goodspeed
@@ -31,7 +31,7 @@
 
 import os
 import sys
-from SimpleXMLRPCServer import SimpleXMLRPCServer
+from xmlrpc.server import SimpleXMLRPCServer
 
 mydir = os.path.dirname(__file__)       # expected to be .../indra/newview/tests/
 sys.path.insert(0, os.path.join(mydir, os.pardir, os.pardir, "llmessage", "tests"))
@@ -85,7 +85,7 @@ def log_error(self, format, *args):
         # "Then there's Windows"
         # Instantiate a TestServer on the first free port in the specified
         # port range.
-        xmlrpcd, port = freeport(xrange(8000, 8020), make_server)
+        xmlrpcd, port = freeport(range(8000, 8020), make_server)
 
     # Pass the selected port number to the subject test program via the
     # environment. We don't want to impose requirements on the test program's
diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py
index 4089e37b5ddbd8c2727a7381ee92b1fe544f9741..db0210bf4280c4ed414320dc016c24f288cd8573 100755
--- a/indra/newview/viewer_manifest.py
+++ b/indra/newview/viewer_manifest.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 """\
 @file viewer_manifest.py
 @author Ryan Williams
@@ -26,6 +26,8 @@
 Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 $/LicenseInfo$
 """
+
+from __future__ import print_function, division
 import errno
 import json
 import os
@@ -114,17 +116,17 @@ def construct(self):
                 if sourceid:
                     settings_install['sourceid'] = settings_template['sourceid'].copy()
                     settings_install['sourceid']['Value'] = sourceid
-                    print "Set sourceid in settings_install.xml to '%s'" % sourceid
+                    print("Set sourceid in settings_install.xml to '%s'" % sourceid)
 
                 if self.args.get('channel_suffix'):
                     settings_install['CmdLineChannel'] = settings_template['CmdLineChannel'].copy()
                     settings_install['CmdLineChannel']['Value'] = self.channel_with_pkg_suffix()
-                    print "Set CmdLineChannel in settings_install.xml to '%s'" % self.channel_with_pkg_suffix()
+                    print("Set CmdLineChannel in settings_install.xml to '%s'" % self.channel_with_pkg_suffix())
 
                 if self.args.get('grid'):
                     settings_install['CmdLineGridChoice'] = settings_template['CmdLineGridChoice'].copy()
                     settings_install['CmdLineGridChoice']['Value'] = self.grid()
-                    print "Set CmdLineGridChoice in settings_install.xml to '%s'" % self.grid()
+                    print("Set CmdLineGridChoice in settings_install.xml to '%s'" % self.grid())
 
                 # put_in_file(src=) need not be an actual pathname; it
                 # only needs to be non-empty
@@ -193,7 +195,7 @@ def construct(self):
             #we likely no longer need the test, since we will throw an exception above, but belt and suspenders and we get the
             #return code for free.
             if not self.path2basename(os.pardir, "build_data.json"):
-                print "No build_data.json file"
+                print("No build_data.json file")
 
     def finish_build_data_dict(self, build_data_dict):
         return build_data_dict
@@ -272,20 +274,20 @@ def extract_names(self,src):
         try:
             contrib_file = open(src,'r')
         except IOError:
-            print "Failed to open '%s'" % src
+            print("Failed to open '%s'" % src)
             raise
         lines = contrib_file.readlines()
         contrib_file.close()
 
         # All lines up to and including the first blank line are the file header; skip them
         lines.reverse() # so that pop will pull from first to last line
-        while not re.match("\s*$", lines.pop()) :
+        while not re.match(r"\s*$", lines.pop()) :
             pass # do nothing
 
         # A line that starts with a non-whitespace character is a name; all others describe contributions, so collect the names
         names = []
         for line in lines :
-            if re.match("\S", line) :
+            if re.match(r"\S", line) :
                 names.append(line.rstrip())
         # It's not fair to always put the same people at the head of the list
         random.shuffle(names)
@@ -311,7 +313,7 @@ def symlinkf(self, src, dst=None, catch=True):
         """
         Like ln -sf, but uses os.symlink() instead of running ln. This creates
         a symlink at 'dst' that points to 'src' -- see:
-        https://docs.python.org/2/library/os.html#os.symlink
+        https://docs.python.org/3/library/os.html#os.symlink
 
         If you omit 'dst', this creates a symlink with basename(src) at
         get_dst_prefix() -- in other words: put a symlink to this pathname
@@ -373,11 +375,11 @@ def _symlinkf(self, src, dst, catch):
                         os.remove(dst)
                         os.symlink(src, dst)
                 elif os.path.isdir(dst):
-                    print "Requested symlink (%s) exists but is a directory; replacing" % dst
+                    print("Requested symlink (%s) exists but is a directory; replacing" % dst)
                     shutil.rmtree(dst)
                     os.symlink(src, dst)
                 elif os.path.exists(dst):
-                    print "Requested symlink (%s) exists but is a file; replacing" % dst
+                    print("Requested symlink (%s) exists but is a file; replacing" % dst)
                     os.remove(dst)
                     os.symlink(src, dst)
                 else:
@@ -385,8 +387,8 @@ def _symlinkf(self, src, dst, catch):
                     raise
         except Exception as err:
             # report
-            print "Can't symlink %r -> %r: %s: %s" % \
-                  (dst, src, err.__class__.__name__, err)
+            print("Can't symlink %r -> %r: %s: %s" % \
+                  (dst, src, err.__class__.__name__, err))
             # if caller asked us not to catch, re-raise this exception
             if not catch:
                 raise
@@ -767,19 +769,19 @@ def package_finish(self):
         installer_created=False
         nsis_attempts=3
         nsis_retry_wait=15
-        for attempt in xrange(nsis_attempts):
+        for attempt in range(nsis_attempts):
             try:
                 self.run_command([NSIS_path, '/V2', self.dst_path_of(tempfile)])
             except ManifestError as err:
                 if attempt+1 < nsis_attempts:
-                    print >> sys.stderr, "nsis failed, waiting %d seconds before retrying" % nsis_retry_wait
+                    print("nsis failed, waiting %d seconds before retrying" % nsis_retry_wait, file=sys.stderr)
                     time.sleep(nsis_retry_wait)
                     nsis_retry_wait*=2
             else:
                 # NSIS worked! Done!
                 break
         else:
-            print >> sys.stderr, "Maximum nsis attempts exceeded; giving up"
+            print("Maximum nsis attempts exceeded; giving up", file=sys.stderr)
             raise
 
         self.sign(installer_file)
@@ -791,10 +793,10 @@ def sign(self, exe):
         python  = os.environ.get('PYTHON', sys.executable)
         if os.path.exists(sign_py):
             dst_path = self.dst_path_of(exe)
-            print "about to run signing of: ", dst_path
+            print("about to run signing of: ", dst_path)
             self.run_command([python, sign_py, dst_path])
         else:
-            print "Skipping code signing of %s %s: %s not found" % (self.dst_path_of(exe), exe, sign_py)
+            print("Skipping code signing of %s %s: %s not found" % (self.dst_path_of(exe), exe, sign_py))
 
     def escape_slashes(self, path):
         return path.replace('\\', '\\\\\\\\')
@@ -1040,7 +1042,7 @@ def package_finish(self):
         appname = os.path.basename(application)
 
         vol_icon = self.src_path_of(os.path.join(self.icon_path(), 'alchemy.icns'))
-        print "DEBUG: icon_path '%s'" % vol_icon
+        print("DEBUG: icon_path '%s'" % vol_icon)
 
         dmgoptions = {
             'format': 'ULFO',
@@ -1160,7 +1162,7 @@ def construct(self):
 
         # Get the icons based on the channel type
         icon_path = self.icon_path()
-        print "DEBUG: icon_path '%s'" % icon_path
+        print("DEBUG: icon_path '%s'" % icon_path)
         with self.prefix(src=icon_path) :
             self.path("alchemy_256.png","alchemy_icon.png")
             with self.prefix(dst="res-sdl") :
@@ -1232,14 +1234,14 @@ def package_finish(self):
                                   '--numeric-owner', '-cjf',
                                  tempname + '.tar.bz2', installer_name])
             else:
-                print "Skipping %s.tar.bz2 for non-Release build (%s)" % \
-                      (installer_name, self.args['buildtype'])
+                print("Skipping %s.tar.bz2 for non-Release build (%s)" % \
+                      (installer_name, self.args['buildtype']))
         finally:
             self.run_command(["mv", tempname, realname])
 
     def strip_binaries(self):
         if self.args['buildtype'].lower() == 'release' and self.is_packaging_viewer():
-            print "* Going strip-crazy on the packaged binaries, since this is a RELEASE build"
+            print("* Going strip-crazy on the packaged binaries, since this is a RELEASE build")
             # makes some small assumptions about our packaged dir structure
             self.run_command(
                 ["find"] +
diff --git a/indra/test/test_llmanifest.py b/indra/test/test_llmanifest.py
index b2b2b72c3bb435642178179db3287e2a839ae2c0..c746d59ff22766a3cda5bb3fbe8e6dd2fb13d157 100755
--- a/indra/test/test_llmanifest.py
+++ b/indra/test/test_llmanifest.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 """
 @file test_llmanifest.py
 @author Ryan Williams
@@ -124,10 +124,10 @@ def testpathof(self):
 
     def testcmakedirs(self):
         self.m.cmakedirs("test_dir_DELETE/nested/dir")
-        self.assert_(os.path.exists("test_dir_DELETE/nested/dir"))
-        self.assert_(os.path.isdir("test_dir_DELETE"))
-        self.assert_(os.path.isdir("test_dir_DELETE/nested"))
-        self.assert_(os.path.isdir("test_dir_DELETE/nested/dir"))
+        self.assertTrue(os.path.exists("test_dir_DELETE/nested/dir"))
+        self.assertTrue(os.path.isdir("test_dir_DELETE"))
+        self.assertTrue(os.path.isdir("test_dir_DELETE/nested"))
+        self.assertTrue(os.path.isdir("test_dir_DELETE/nested/dir"))
         os.removedirs("test_dir_DELETE/nested/dir")
 
 if __name__ == '__main__':
diff --git a/scripts/code_tools/modified_strings.py b/scripts/code_tools/modified_strings.py
index 6a763b6ec54e81ca31e8a12e1bf23932c6646fc5..e7a9d239dc7c5da4f93143598f6cbcf76ffc8932 100644
--- a/scripts/code_tools/modified_strings.py
+++ b/scripts/code_tools/modified_strings.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 """\
 
 This script scans the SL codebase for translation-related strings.
@@ -25,7 +25,7 @@
 $/LicenseInfo$
 """
 
-from __future__ import print_function
+
 
 import xml.etree.ElementTree as ET
 import argparse
@@ -75,10 +75,10 @@
 ]
 
 def codify_for_print(val):
-    if isinstance(val, unicode):
+    if isinstance(val, str):
         return val.encode("utf-8")
     else:
-        return unicode(val, 'utf-8').encode("utf-8")
+        return str(val, 'utf-8').encode("utf-8")
 
 # Returns a dict of { name => xml_node }
 def read_xml_elements(blob):
@@ -186,7 +186,7 @@ def make_translation_table(mod_tree, base_tree, lang, args):
         transl_dict = read_xml_elements(transl_blob)
 
         rows = 0
-        for name in mod_dict.keys():
+        for name in list(mod_dict.keys()):
             if not name in base_dict or mod_dict[name].text != base_dict[name].text or (args.missing and not name in transl_dict):
                 elt = mod_dict[name]
                 val = elt.text
@@ -307,7 +307,7 @@ def save_translation_file(per_lang_data, aux_data, outfile):
         print("Added", num_translations, "rows for language", lang)
 
     # Reference info, not for translation
-    for aux, data in aux_data.items():
+    for aux, data in list(aux_data.items()):
         df = pd.DataFrame(data, columns = ["Key", "Value"]) 
         df.to_excel(writer, index=False, sheet_name=aux)
         worksheet = writer.sheets[aux]
diff --git a/scripts/content_tools/anim_tool.py b/scripts/content_tools/anim_tool.py
index 3aef8cd5ab40e175052b6d71bc6675087a062a27..e7b86a88fa07f76f17a532c0e80b11dea0f50b52 100644
--- a/scripts/content_tools/anim_tool.py
+++ b/scripts/content_tools/anim_tool.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python3
 """\
 @file   anim_tool.py
 @author Brad Payne, Nat Goodspeed
@@ -39,7 +39,7 @@
 import math
 import os
 import random
-from cStringIO import StringIO
+from io import StringIO
 import struct
 import sys
 from xml.etree import ElementTree
@@ -179,7 +179,7 @@ def unpack(duration, fup):
         return this
 
     def dump(self, f):
-        print >>f, "    rot_key: t %.3f" % self.time,"st",self.time_short,"rot",",".join("%.3f" % f for f in self.rotation)
+        print("    rot_key: t %.3f" % self.time,"st",self.time_short,"rot",",".join("%.3f" % f for f in self.rotation), file=f)
 
     def pack(self, fp):
         fp.pack("<H",self.time_short)
@@ -215,7 +215,7 @@ def unpack(duration, fup):
         return this
 
     def dump(self, f):
-        print >>f, "    pos_key: t %.3f" % self.time,"pos ",",".join("%.3f" % f for f in self.position)
+        print("    pos_key: t %.3f" % self.time,"pos ",",".join("%.3f" % f for f in self.position), file=f)
         
     def pack(self, fp):
         fp.pack("<H",self.time_short)
@@ -247,18 +247,18 @@ def pack(self, fp):
                 self.ease_out_start, self.ease_out_stop)
 
     def dump(self, f):
-        print >>f, "  constraint:"
-        print >>f, "    chain_length",self.chain_length
-        print >>f, "    constraint_type",self.constraint_type
-        print >>f, "    source_volume",self.source_volume
-        print >>f, "    source_offset",self.source_offset
-        print >>f, "    target_volume",self.target_volume
-        print >>f, "    target_offset",self.target_offset
-        print >>f, "    target_dir",self.target_dir
-        print >>f, "    ease_in_start",self.ease_in_start
-        print >>f, "    ease_in_stop",self.ease_in_stop
-        print >>f, "    ease_out_start",self.ease_out_start
-        print >>f, "    ease_out_stop",self.ease_out_stop
+        print("  constraint:", file=f)
+        print("    chain_length",self.chain_length, file=f)
+        print("    constraint_type",self.constraint_type, file=f)
+        print("    source_volume",self.source_volume, file=f)
+        print("    source_offset",self.source_offset, file=f)
+        print("    target_volume",self.target_volume, file=f)
+        print("    target_offset",self.target_offset, file=f)
+        print("    target_dir",self.target_dir, file=f)
+        print("    ease_in_start",self.ease_in_start, file=f)
+        print("    ease_in_stop",self.ease_in_stop, file=f)
+        print("    ease_out_start",self.ease_out_start, file=f)
+        print("    ease_out_stop",self.ease_out_stop, file=f)
         
 class Constraints(object):
     @staticmethod
@@ -266,7 +266,7 @@ def unpack(duration, fup):
         this = Constraints()
         (num_constraints, ) = fup.unpack("<i")
         this.constraints = [Constraint.unpack(duration, fup)
-                            for i in xrange(num_constraints)]
+                            for i in range(num_constraints)]
         return this
 
     def pack(self, fp):
@@ -275,7 +275,7 @@ def pack(self, fp):
             c.pack(fp)
 
     def dump(self, f):
-        print >>f, "constraints:",len(self.constraints)
+        print("constraints:",len(self.constraints), file=f)
         for c in self.constraints:
             c.dump(f)
 
@@ -296,7 +296,7 @@ def unpack(duration, fup):
         this = PositionCurve()
         (num_pos_keys, ) = fup.unpack("<i")
         this.keys = [PosKey.unpack(duration, fup)
-                     for k in xrange(num_pos_keys)]
+                     for k in range(num_pos_keys)]
         return this
 
     def pack(self, fp):
@@ -305,8 +305,8 @@ def pack(self, fp):
             k.pack(fp)
 
     def dump(self, f):
-        print >>f, "  position_curve:"
-        print >>f, "    num_pos_keys", len(self.keys)
+        print("  position_curve:", file=f)
+        print("    num_pos_keys", len(self.keys), file=f)
         for k in self.keys:
             k.dump(f)
 
@@ -327,7 +327,7 @@ def unpack(duration, fup):
         this = RotationCurve()
         (num_rot_keys, ) = fup.unpack("<i")
         this.keys = [RotKey.unpack(duration, fup)
-                     for k in xrange(num_rot_keys)]
+                     for k in range(num_rot_keys)]
         return this
 
     def pack(self, fp):
@@ -336,8 +336,8 @@ def pack(self, fp):
             k.pack(fp)
 
     def dump(self, f):
-        print >>f, "  rotation_curve:"
-        print >>f, "    num_rot_keys", len(self.keys)
+        print("  rotation_curve:", file=f)
+        print("    num_rot_keys", len(self.keys), file=f)
         for k in self.keys:
             k.dump(f)
             
@@ -364,9 +364,9 @@ def pack(self, fp):
         self.position_curve.pack(fp)
 
     def dump(self, f):
-        print >>f, "joint:"
-        print >>f, "  joint_name:",self.joint_name
-        print >>f, "  joint_priority:",self.joint_priority
+        print("joint:", file=f)
+        print("  joint_name:",self.joint_name, file=f)
+        print("  joint_priority:",self.joint_priority, file=f)
         self.rotation_curve.dump(f)
         self.position_curve.dump(f)
 
@@ -440,10 +440,10 @@ def unpack(self,fup):
             fup.unpack("@ffiffII")
         
         self.joints = [JointInfo.unpack(self.duration, fup)
-                       for j in xrange(num_joints)]
+                       for j in range(num_joints)]
         if self.verbose:
             for joint_info in self.joints:
-                print "unpacked joint",joint_info.joint_name
+                print("unpacked joint",joint_info.joint_name)
         self.constraints = Constraints.unpack(self.duration, fup)
         self.buffer = fup.buffer
         
@@ -461,17 +461,17 @@ def dump(self, filename="-"):
             f = sys.stdout
         else:
             f = open(filename,"w")
-        print >>f, "versions: ", self.version, self.sub_version
-        print >>f, "base_priority: ", self.base_priority
-        print >>f, "duration: ", self.duration
-        print >>f, "emote_name: ", self.emote_name
-        print >>f, "loop_in_point: ", self.loop_in_point
-        print >>f, "loop_out_point: ", self.loop_out_point
-        print >>f, "loop: ", self.loop
-        print >>f, "ease_in_duration: ", self.ease_in_duration
-        print >>f, "ease_out_duration: ", self.ease_out_duration
-        print >>f, "hand_pose", self.hand_pose
-        print >>f, "num_joints", len(self.joints)
+        print("versions: ", self.version, self.sub_version, file=f)
+        print("base_priority: ", self.base_priority, file=f)
+        print("duration: ", self.duration, file=f)
+        print("emote_name: ", self.emote_name, file=f)
+        print("loop_in_point: ", self.loop_in_point, file=f)
+        print("loop_out_point: ", self.loop_out_point, file=f)
+        print("loop: ", self.loop, file=f)
+        print("ease_in_duration: ", self.ease_in_duration, file=f)
+        print("ease_out_duration: ", self.ease_out_duration, file=f)
+        print("hand_pose", self.hand_pose, file=f)
+        print("num_joints", len(self.joints), file=f)
         for j in self.joints:
             j.dump(f)
         self.constraints.dump(f)
@@ -482,7 +482,7 @@ def write(self, filename):
         fp.write(filename)
 
     def write_src_data(self, filename):
-        print "write file",filename
+        print("write file",filename)
         with open(filename,"wb") as f:
             f.write(self.buffer)
 
@@ -501,11 +501,11 @@ def delete_joint(self, name):
         j = self.find_joint(name)
         if j:
             if self.verbose:
-                print "removing joint", name
+                print("removing joint", name)
             self.joints.remove(j)
         else:
             if self.verbose:
-                print "joint not found to remove", name
+                print("joint not found to remove", name)
 
     def summary(self):
         nj = len(self.joints)
@@ -513,13 +513,13 @@ def summary(self):
         nstatic = len([j for j in self.joints
                        if j.rotation_curve.is_static()
                        and j.position_curve.is_static()])
-        print "summary: %d joints, non-zero priority %d, static %d" % (nj, nz, nstatic)
+        print("summary: %d joints, non-zero priority %d, static %d" % (nj, nz, nstatic))
 
     def add_pos(self, joint_names, positions):
         js = [joint for joint in self.joints if joint.joint_name in joint_names]
         for j in js:
             if self.verbose:
-                print "adding positions",j.joint_name,positions
+                print("adding positions",j.joint_name,positions)
             j.joint_priority = 4
             j.position_curve.keys = [PosKey(self.duration * i / (len(positions) - 1),
                                             self.duration,
@@ -529,7 +529,7 @@ def add_pos(self, joint_names, positions):
     def add_rot(self, joint_names, rotations):
         js = [joint for joint in self.joints if joint.joint_name in joint_names]
         for j in js:
-            print "adding rotations",j.joint_name
+            print("adding rotations",j.joint_name)
             j.joint_priority = 4
             j.rotation_curve.keys = [RotKey(self.duration * i / (len(rotations) - 1),
                                             self.duration,
@@ -539,8 +539,8 @@ def add_rot(self, joint_names, rotations):
 def twistify(anim, joint_names, rot1, rot2):
     js = [joint for joint in anim.joints if joint.joint_name in joint_names]
     for j in js:
-        print "twisting",j.joint_name
-        print len(j.rotation_curve.keys)
+        print("twisting",j.joint_name)
+        print(len(j.rotation_curve.keys))
         j.joint_priority = 4
         # Set the joint(s) to rot1 at time 0, rot2 at the full duration.
         j.rotation_curve.keys = [
@@ -563,7 +563,7 @@ def get_joint_by_name(tree,name):
     if len(matches)==1:
         return matches[0]
     elif len(matches)>1:
-        print "multiple matches for name",name
+        print("multiple matches for name",name)
         return None
     else:
         return None
@@ -577,7 +577,7 @@ def get_elt_pos(elt):
         return (0.0, 0.0, 0.0)
 
 def resolve_joints(names, skel_tree, lad_tree, no_hud=False):
-    print "resolve joints, no_hud is",no_hud
+    print("resolve joints, no_hud is",no_hud)
     if skel_tree and lad_tree:
         all_elts = [elt for elt in skel_tree.getroot().iter()]
         all_elts.extend([elt for elt in lad_tree.getroot().iter()])
@@ -641,12 +641,12 @@ def main(*argv):
     parser.add_argument("outfilename", nargs="?", help="name of a .anim file to output")
     args = parser.parse_args(argv)
 
-    print "anim_tool.py: " + " ".join(argv)
-    print "dump is", args.dump
-    print "infilename",args.infilename,"outfilename",args.outfilename
-    print "rot",args.rot
-    print "pos",args.pos
-    print "joints",args.joints
+    print("anim_tool.py: " + " ".join(argv))
+    print("dump is", args.dump)
+    print("infilename",args.infilename,"outfilename",args.outfilename)
+    print("rot",args.rot)
+    print("pos",args.pos)
+    print("joints",args.joints)
 
     anim = Anim(args.infilename, args.verbose)
     skel_tree = None
@@ -663,7 +663,7 @@ def main(*argv):
     if args.joints:
         joints = resolve_joints(args.joints, skel_tree, lad_tree, args.no_hud)
         if args.verbose:
-            print "joints resolved to",joints
+            print("joints resolved to",joints)
         for name in joints:
             anim.add_joint(name,0)
     if args.delete_joints:
@@ -677,8 +677,8 @@ def main(*argv):
         # pick a random sequence of positions for each joint specified
         for joint in joints:
             # generate a list of rand_pos triples
-            pos_array = [tuple(random.uniform(-1,1) for i in xrange(3))
-                         for j in xrange(args.rand_pos)]
+            pos_array = [tuple(random.uniform(-1,1) for i in range(3))
+                         for j in range(args.rand_pos)]
             # close the loop by cycling back to the first entry
             pos_array.append(pos_array[0])
             anim.add_pos([joint], pos_array)
@@ -688,26 +688,26 @@ def main(*argv):
             if elt is not None:
                 anim.add_pos([joint], 2*[get_elt_pos(elt)])
             else:
-                print "no elt or no pos data for",joint
+                print("no elt or no pos data for",joint)
     if args.set_version:
         anim.version, anim.sub_version = args.set_version
     if args.base_priority is not None:
-        print "set base priority",args.base_priority
+        print("set base priority",args.base_priority)
         anim.base_priority = args.base_priority
     # --joint_priority sets priority for ALL joints, not just the explicitly-
     # specified ones
     if args.joint_priority is not None:
-        print "set joint priority",args.joint_priority
+        print("set joint priority",args.joint_priority)
         for joint in anim.joints:
             joint.joint_priority = args.joint_priority
     if args.duration is not None:
-        print "set duration",args.duration
+        print("set duration",args.duration)
         anim.duration = args.duration
     if args.loop_in is not None:
-        print "set loop_in",args.loop_in
+        print("set loop_in",args.loop_in)
         anim.loop_in_point = args.loop_in
     if args.loop_out is not None:
-        print "set loop_out",args.loop_out
+        print("set loop_out",args.loop_out)
         anim.loop_out_point = args.loop_out
     if args.dump:
         anim.dump("-")
diff --git a/scripts/content_tools/arche_tool.py b/scripts/content_tools/arche_tool.py
index f99d7be39ad7be65dfb47bce1223bcfee26e425f..677af62d2f2f1805841cb91c64ebfec6536dcfcc 100644
--- a/scripts/content_tools/arche_tool.py
+++ b/scripts/content_tools/arche_tool.py
@@ -1,4 +1,4 @@
-#!runpy.sh
+#!/usr/bin/env python3
 
 """\
 
@@ -42,23 +42,23 @@ def node_key(e):
 def compare_matched_nodes(key,items,summary):
     tags = list(set([e.tag for e in items]))
     if len(tags) != 1:
-        print "different tag types for key",key
+        print("different tag types for key",key)
         summary.setdefault("tag_mismatch",0)
         summary["tag_mismatch"] += 1
         return
-    all_attrib = list(set(chain.from_iterable([e.attrib.keys() for e in items])))
+    all_attrib = list(set(chain.from_iterable([list(e.attrib.keys()) for e in items])))
     #print key,"all_attrib",all_attrib
     for attr in all_attrib:
         vals = [e.get(attr) for e in items]
         #print "key",key,"attr",attr,"vals",vals
         if len(set(vals)) != 1:
-            print key,"- attr",attr,"multiple values",vals
+            print(key,"- attr",attr,"multiple values",vals)
             summary.setdefault("attr",{})
             summary["attr"].setdefault(attr,0)
             summary["attr"][attr] += 1
 
 def compare_trees(file_trees):
-    print "compare_trees"
+    print("compare_trees")
     summary = {}
     all_keys = list(set([node_key(e) for tree in file_trees for e in tree.getroot().iter() if node_key(e)]))
     #print "keys",all_keys
@@ -70,14 +70,14 @@ def compare_trees(file_trees):
         items = []
         for nodes in tree_nodes:
             if not key in nodes:
-                print "file",i,"missing item for key",key
+                print("file",i,"missing item for key",key)
                 summary.setdefault("missing",0)
                 summary["missing"] += 1
             else:
                 items.append(nodes[key])
         compare_matched_nodes(key,items,summary)
-    print "Summary:"
-    print summary
+    print("Summary:")
+    print(summary)
                 
 def dump_appearance_params(tree):
     vals = []
@@ -88,7 +88,7 @@ def dump_appearance_params(tree):
                 vals.append("{" + e.get("id") + "," +e.get("u8") + "}")
                 #print e.get("id"), e.get("name"), e.get("group"), e.get("u8")
     if len(vals)==253:
-        print ", ".join(vals)
+        print(", ".join(vals))
         
     
 if __name__ == "__main__":
@@ -101,9 +101,9 @@ def dump_appearance_params(tree):
     args = parser.parse_args()
 
 
-    print "files",args.files
+    print("files",args.files)
     file_trees = [etree.parse(filename) for filename in args.files]
-    print args
+    print(args)
     if args.compare:
         compare_trees(file_trees)
     if args.appearance_params:
diff --git a/scripts/content_tools/dae_tool.py b/scripts/content_tools/dae_tool.py
index 823f69cb854d1123cf1cb4a17c755e47ed78e3f6..2454fafa467be6ee44af5535cbd7bdf6d1b90bad 100644
--- a/scripts/content_tools/dae_tool.py
+++ b/scripts/content_tools/dae_tool.py
@@ -1,4 +1,4 @@
-#!runpy.sh
+#!/usr/bin/env python3
 
 """\
 
@@ -35,14 +35,14 @@
 from lxml import etree
 
 def mesh_summary(mesh):
-    print "scenes",mesh.scenes
+    print("scenes",mesh.scenes)
     for scene in mesh.scenes:
-        print "scene",scene
+        print("scene",scene)
         for node in scene.nodes:
-            print "node",node
+            print("node",node)
 
 def mesh_lock_offsets(tree, joints):
-    print "mesh_lock_offsets",tree,joints
+    print("mesh_lock_offsets",tree,joints)
     for joint_node in tree.iter():
         if "node" not in joint_node.tag:
             continue
@@ -57,11 +57,11 @@ def mesh_lock_offsets(tree, joints):
                         floats[7] += 0.0001
                         floats[11] += 0.0001
                         matrix_node.text = " ".join([str(f) for f in floats])
-                        print joint_node.get("name"),matrix_node.tag,"text",matrix_node.text,len(floats),floats
+                        print(joint_node.get("name"),matrix_node.tag,"text",matrix_node.text,len(floats),floats)
         
 
 def mesh_random_offsets(tree, joints):
-    print "mesh_random_offsets",tree,joints
+    print("mesh_random_offsets",tree,joints)
     for joint_node in tree.iter():
         if "node" not in joint_node.tag:
             continue
@@ -73,13 +73,13 @@ def mesh_random_offsets(tree, joints):
             for matrix_node in list(joint_node):
                 if "matrix" in matrix_node.tag:
                     floats = [float(x) for x in matrix_node.text.split()]
-                    print "randomizing",floats
+                    print("randomizing",floats)
                     if len(floats) == 16:
                         floats[3] += random.uniform(-1.0,1.0)
                         floats[7] += random.uniform(-1.0,1.0)
                         floats[11] += random.uniform(-1.0,1.0)
                         matrix_node.text = " ".join([str(f) for f in floats])
-                        print joint_node.get("name"),matrix_node.tag,"text",matrix_node.text,len(floats),floats
+                        print(joint_node.get("name"),matrix_node.tag,"text",matrix_node.text,len(floats),floats)
         
 
 if __name__ == "__main__":
@@ -96,24 +96,24 @@ def mesh_random_offsets(tree, joints):
     tree = None
 
     if args.infilename:
-        print "reading",args.infilename
+        print("reading",args.infilename)
         mesh = Collada(args.infilename)
         tree = etree.parse(args.infilename)
 
     if args.summary:
-        print "summarizing",args.infilename
+        print("summarizing",args.infilename)
         mesh_summary(mesh)
         
     if args.lock_offsets:
-        print "locking offsets for",args.lock_offsets
+        print("locking offsets for",args.lock_offsets)
         mesh_lock_offsets(tree, args.lock_offsets)
 
     if args.random_offsets:
-        print "adding random offsets for",args.random_offsets
+        print("adding random offsets for",args.random_offsets)
         mesh_random_offsets(tree, args.random_offsets)
 
     if args.outfilename:
-        print "writing",args.outfilename
+        print("writing",args.outfilename)
         f = open(args.outfilename,"w")
-        print >>f, etree.tostring(tree, pretty_print=True) #need update to get: , short_empty_elements=True)
+        print(etree.tostring(tree, pretty_print=True), file=f) #need update to get: , short_empty_elements=True)
     
diff --git a/scripts/content_tools/skel_tool.py b/scripts/content_tools/skel_tool.py
index 26f63326f1d67822872c0405e746e22ef2df6691..449ecd6a6ce01ede5688b5b1d10e6d303150efb7 100644
--- a/scripts/content_tools/skel_tool.py
+++ b/scripts/content_tools/skel_tool.py
@@ -1,4 +1,4 @@
-#!runpy.sh
+#!/usr/bin/env python3
 
 """\
 
@@ -32,14 +32,14 @@
  
 def get_joint_names(tree):
     joints = [element.get('name') for element in tree.getroot().iter() if element.tag in ['bone','collision_volume']]
-    print "joints:",joints
+    print("joints:",joints)
     return joints
 
 def get_aliases(tree):
     aliases = {}
     alroot = tree.getroot()
     for element in alroot.iter():
-        for key in element.keys():
+        for key in list(element.keys()):
             if key == 'aliases':
                 name = element.get('name')
                 val = element.get('aliases')
@@ -58,19 +58,19 @@ def float_tuple(str, n=3):
         if len(result)==n:
             return result
         else:
-            print "tuple length wrong:", str,"gave",result,"wanted len",n,"got len",len(result)
+            print("tuple length wrong:", str,"gave",result,"wanted len",n,"got len",len(result))
             raise Exception()
     except:
-        print "convert failed for:",str
+        print("convert failed for:",str)
         raise
 
 def check_symmetry(name, field, vec1, vec2):
     if vec1[0] != vec2[0]:
-        print name,field,"x match fail"
+        print(name,field,"x match fail")
     if vec1[1] != -vec2[1]:
-        print name,field,"y mirror image fail"
+        print(name,field,"y mirror image fail")
     if vec1[2] != vec2[2]:
-        print name,field,"z match fail"
+        print(name,field,"z match fail")
 
 def enforce_symmetry(tree, element, field, fix=False):
     name = element.get("name")
@@ -92,7 +92,7 @@ def get_element_by_name(tree,name):
     if len(matches)==1:
         return matches[0]
     elif len(matches)>1:
-        print "multiple matches for name",name
+        print("multiple matches for name",name)
         return None
     else:
         return None
@@ -100,7 +100,7 @@ def get_element_by_name(tree,name):
 def list_skel_tree(tree):
     for element in tree.getroot().iter():
         if element.tag == "bone":
-            print element.get("name"),"-",element.get("support")
+            print(element.get("name"),"-",element.get("support"))
     
 def validate_child_order(tree, ogtree, fix=False):
     unfixable = 0
@@ -116,12 +116,12 @@ def validate_child_order(tree, ogtree, fix=False):
         if og_element is not None:
             for echild,ochild in zip(list(element),list(og_element)):
                 if echild.get("name") != ochild.get("name"):
-                    print "Child ordering error, parent",element.get("name"),echild.get("name"),"vs",ochild.get("name")
+                    print("Child ordering error, parent",element.get("name"),echild.get("name"),"vs",ochild.get("name"))
                     if fix:
                         tofix.add(element.get("name"))
     children = {}
     for name in tofix:
-        print "FIX",name
+        print("FIX",name)
         element = get_element_by_name(tree,name)
         og_element = get_element_by_name(ogtree,name)
         children = []
@@ -130,20 +130,20 @@ def validate_child_order(tree, ogtree, fix=False):
             elt = get_element_by_name(tree,og_elt.get("name"))
             if elt is not None:
                 children.append(elt)
-                print "b:",elt.get("name")
+                print("b:",elt.get("name"))
             else:
-                print "b missing:",og_elt.get("name")
+                print("b missing:",og_elt.get("name"))
         # then add children that are not present in the original joints
         for elt in list(element):
             og_elt = get_element_by_name(ogtree,elt.get("name"))
             if og_elt is None:
                 children.append(elt)
-                print "e:",elt.get("name")
+                print("e:",elt.get("name"))
         # if we've done this right, we have a rearranged list of the same length
         if len(children)!=len(element):
-            print "children",[e.get("name") for e in children]
-            print "element",[e.get("name") for e in element]
-            print "children changes for",name,", cannot reconcile"
+            print("children",[e.get("name") for e in children])
+            print("element",[e.get("name") for e in element])
+            print("children changes for",name,", cannot reconcile")
         else:
             element[:] = children
 
@@ -163,7 +163,7 @@ def validate_child_order(tree, ogtree, fix=False):
 # - digits of precision should be consistent (again, except for old joints)
 # - new bones should have pos, pivot the same
 def validate_skel_tree(tree, ogtree, reftree, fix=False):
-    print "validate_skel_tree"
+    print("validate_skel_tree")
     (num_bones,num_cvs) = (0,0)
     unfixable = 0
     defaults = {"connected": "false", 
@@ -175,7 +175,7 @@ def validate_skel_tree(tree, ogtree, reftree, fix=False):
         # Preserve values from og_file:
         for f in ["pos","rot","scale","pivot"]:
             if og_element is not None and og_element.get(f) and (str(element.get(f)) != str(og_element.get(f))):
-                print element.get("name"),"field",f,"has changed:",og_element.get(f),"!=",element.get(f)
+                print(element.get("name"),"field",f,"has changed:",og_element.get(f),"!=",element.get(f))
                 if fix:
                     element.set(f, og_element.get(f))
 
@@ -187,17 +187,17 @@ def validate_skel_tree(tree, ogtree, reftree, fix=False):
             fields.extend(["end","connected"])
         for f in fields:
             if not element.get(f):
-                print element.get("name"),"missing required field",f
+                print(element.get("name"),"missing required field",f)
                 if fix:
                     if og_element is not None and og_element.get(f):
-                        print "fix from ogtree"
+                        print("fix from ogtree")
                         element.set(f,og_element.get(f))
                     elif ref_element is not None and ref_element.get(f):
-                        print "fix from reftree"
+                        print("fix from reftree")
                         element.set(f,ref_element.get(f))
                     else:
                         if f in defaults:
-                            print "fix by using default value",f,"=",defaults[f]
+                            print("fix by using default value",f,"=",defaults[f])
                             element.set(f,defaults[f])
                         elif f == "support":
                             if og_element is not None:
@@ -205,7 +205,7 @@ def validate_skel_tree(tree, ogtree, reftree, fix=False):
                             else:
                                 element.set(f,"extended")
                         else:
-                            print "unfixable:",element.get("name"),"no value for field",f
+                            print("unfixable:",element.get("name"),"no value for field",f)
                             unfixable += 1
 
         fix_name(element)
@@ -214,7 +214,7 @@ def validate_skel_tree(tree, ogtree, reftree, fix=False):
             enforce_symmetry(tree, element, field, fix)
         if element.get("support")=="extended":
             if element.get("pos") != element.get("pivot"):
-                print "extended joint",element.get("name"),"has mismatched pos, pivot"
+                print("extended joint",element.get("name"),"has mismatched pos, pivot")
         
 
         if element.tag == "linden_skeleton":
@@ -223,19 +223,19 @@ def validate_skel_tree(tree, ogtree, reftree, fix=False):
             all_bones = [e for e in tree.getroot().iter() if e.tag=="bone"]
             all_cvs = [e for e in tree.getroot().iter() if e.tag=="collision_volume"]
             if num_bones != len(all_bones):
-                print "wrong bone count, expected",len(all_bones),"got",num_bones
+                print("wrong bone count, expected",len(all_bones),"got",num_bones)
                 if fix:
                     element.set("num_bones", str(len(all_bones)))
             if num_cvs != len(all_cvs):
-                print "wrong cv count, expected",len(all_cvs),"got",num_cvs
+                print("wrong cv count, expected",len(all_cvs),"got",num_cvs)
                 if fix:
                     element.set("num_collision_volumes", str(len(all_cvs)))
 
-    print "skipping child order code"
+    print("skipping child order code")
     #unfixable += validate_child_order(tree, ogtree, fix)
 
     if fix and (unfixable > 0):
-        print "BAD FILE:", unfixable,"errs could not be fixed"
+        print("BAD FILE:", unfixable,"errs could not be fixed")
             
 
 def slider_info(ladtree,skeltree):
@@ -243,37 +243,37 @@ def slider_info(ladtree,skeltree):
         for skel_param in param.iter("param_skeleton"):
             bones = [b for b in skel_param.iter("bone")]
         if bones:
-            print "param",param.get("name"),"id",param.get("id")
+            print("param",param.get("name"),"id",param.get("id"))
             value_min = float(param.get("value_min"))
             value_max = float(param.get("value_max"))
             neutral = 100.0 * (0.0-value_min)/(value_max-value_min)
-            print "  neutral",neutral
+            print("  neutral",neutral)
             for b in bones:
                 scale = float_tuple(b.get("scale","0 0 0"))
                 offset = float_tuple(b.get("offset","0 0 0"))
-                print "  bone", b.get("name"), "scale", scale, "offset", offset
+                print("  bone", b.get("name"), "scale", scale, "offset", offset)
                 scale_min = [value_min * s for s in scale]
                 scale_max = [value_max * s for s in scale]
                 offset_min = [value_min * t for t in offset]
                 offset_max = [value_max * t for t in offset]
                 if (scale_min != scale_max):
-                    print "    Scale MinX", scale_min[0]
-                    print "    Scale MinY", scale_min[1]
-                    print "    Scale MinZ", scale_min[2]
-                    print "    Scale MaxX", scale_max[0]
-                    print "    Scale MaxY", scale_max[1]
-                    print "    Scale MaxZ", scale_max[2]
+                    print("    Scale MinX", scale_min[0])
+                    print("    Scale MinY", scale_min[1])
+                    print("    Scale MinZ", scale_min[2])
+                    print("    Scale MaxX", scale_max[0])
+                    print("    Scale MaxY", scale_max[1])
+                    print("    Scale MaxZ", scale_max[2])
                 if (offset_min != offset_max):
-                    print "    Offset MinX", offset_min[0]
-                    print "    Offset MinY", offset_min[1]
-                    print "    Offset MinZ", offset_min[2]
-                    print "    Offset MaxX", offset_max[0]
-                    print "    Offset MaxY", offset_max[1]
-                    print "    Offset MaxZ", offset_max[2]
+                    print("    Offset MinX", offset_min[0])
+                    print("    Offset MinY", offset_min[1])
+                    print("    Offset MinZ", offset_min[2])
+                    print("    Offset MaxX", offset_max[0])
+                    print("    Offset MaxY", offset_max[1])
+                    print("    Offset MaxZ", offset_max[2])
     
 # Check contents of avatar_lad file relative to a specified skeleton
 def validate_lad_tree(ladtree,skeltree,orig_ladtree):
-    print "validate_lad_tree"
+    print("validate_lad_tree")
     bone_names = [elt.get("name") for elt in skeltree.iter("bone")]
     bone_names.append("mScreen")
     bone_names.append("mRoot")
@@ -285,7 +285,7 @@ def validate_lad_tree(ladtree,skeltree,orig_ladtree):
         #print "attachment",att_name
         joint_name = att.get("joint")
         if not joint_name in bone_names:
-            print "att",att_name,"linked to invalid joint",joint_name
+            print("att",att_name,"linked to invalid joint",joint_name)
     for skel_param in ladtree.iter("param_skeleton"):
         skel_param_id = skel_param.get("id")
         skel_param_name = skel_param.get("name")
@@ -297,13 +297,13 @@ def validate_lad_tree(ladtree,skeltree,orig_ladtree):
         for bone in skel_param.iter("bone"):
             bone_name = bone.get("name")
             if not bone_name in bone_names:
-                print "skel param references invalid bone",bone_name
-                print etree.tostring(bone)
+                print("skel param references invalid bone",bone_name)
+                print(etree.tostring(bone))
             bone_scale = float_tuple(bone.get("scale","0 0 0"))
             bone_offset = float_tuple(bone.get("offset","0 0 0"))
             param = bone.getparent().getparent()
             if bone_scale==(0, 0, 0) and bone_offset==(0, 0, 0):
-                print "no-op bone",bone_name,"in param",param.get("id","-1")
+                print("no-op bone",bone_name,"in param",param.get("id","-1"))
             # check symmetry of sliders
             if "Right" in bone.get("name"):
                 left_name = bone_name.replace("Right","Left")
@@ -312,12 +312,12 @@ def validate_lad_tree(ladtree,skeltree,orig_ladtree):
                     if b.get("name")==left_name:
                         left_bone = b
                 if left_bone is None:
-                    print "left_bone not found",left_name,"in",param.get("id","-1")
+                    print("left_bone not found",left_name,"in",param.get("id","-1"))
                 else:
                     left_scale = float_tuple(left_bone.get("scale","0 0 0"))
                     left_offset = float_tuple(left_bone.get("offset","0 0 0"))
                     if left_scale != bone_scale:
-                        print "scale mismatch between",bone_name,"and",left_name,"in param",param.get("id","-1")
+                        print("scale mismatch between",bone_name,"and",left_name,"in param",param.get("id","-1"))
                     param_id = int(param.get("id","-1"))
                     if param_id in [661]: # shear
                         expected_offset = tuple([bone_offset[0],bone_offset[1],-bone_offset[2]])
@@ -326,7 +326,7 @@ def validate_lad_tree(ladtree,skeltree,orig_ladtree):
                     else:
                         expected_offset = tuple([bone_offset[0],-bone_offset[1],bone_offset[2]])
                     if left_offset != expected_offset:
-                        print "offset mismatch between",bone_name,"and",left_name,"in param",param.get("id","-1")
+                        print("offset mismatch between",bone_name,"and",left_name,"in param",param.get("id","-1"))
                     
     drivers = {}
     for driven_param in ladtree.iter("driven"):
@@ -340,15 +340,15 @@ def validate_lad_tree(ladtree,skeltree,orig_ladtree):
             if (actual_param.get("value_min") != driver.get("value_min") or \
                 actual_param.get("value_max") != driver.get("value_max")):
                 if args.verbose:
-                    print "MISMATCH min max:",driver.get("id"),"drives",driven_param.get("id"),"min",driver.get("value_min"),actual_param.get("value_min"),"max",driver.get("value_max"),actual_param.get("value_max")
+                    print("MISMATCH min max:",driver.get("id"),"drives",driven_param.get("id"),"min",driver.get("value_min"),actual_param.get("value_min"),"max",driver.get("value_max"),actual_param.get("value_max"))
 
     for driven_id in drivers:
         dset = drivers[driven_id]
         if len(dset) != 1:
-            print "driven_id",driven_id,"has multiple drivers",dset
+            print("driven_id",driven_id,"has multiple drivers",dset)
         else:
             if args.verbose:
-                print "driven_id",driven_id,"has one driver",dset
+                print("driven_id",driven_id,"has one driver",dset)
     if orig_ladtree:
         # make sure expected message format is unchanged
         orig_message_params_by_id = dict((int(param.get("id")),param) for param in orig_ladtree.iter("param") if param.get("group") in ["0","3"])
@@ -358,25 +358,25 @@ def validate_lad_tree(ladtree,skeltree,orig_ladtree):
         message_ids = sorted(message_params_by_id.keys())
         #print "message_ids",message_ids
         if (set(message_ids) != set(orig_message_ids)):
-            print "mismatch in message ids!"
-            print "added",set(message_ids) - set(orig_message_ids)
-            print "removed",set(orig_message_ids) - set(message_ids)
+            print("mismatch in message ids!")
+            print("added",set(message_ids) - set(orig_message_ids))
+            print("removed",set(orig_message_ids) - set(message_ids))
         else:
-            print "message ids OK"
+            print("message ids OK")
     
 def remove_joint_by_name(tree, name):
-    print "remove joint:",name
+    print("remove joint:",name)
     elt = get_element_by_name(tree,name)
     while elt is not None:
         children = list(elt)
         parent = elt.getparent()
-        print "graft",[e.get("name") for e in children],"into",parent.get("name")
-        print "remove",elt.get("name")
+        print("graft",[e.get("name") for e in children],"into",parent.get("name"))
+        print("remove",elt.get("name"))
         #parent_children = list(parent)
         loc = parent.index(elt)
         parent[loc:loc+1] = children
         elt[:] = []
-        print "parent now:",[e.get("name") for e in list(parent)]
+        print("parent now:",[e.get("name") for e in list(parent)])
         elt = get_element_by_name(tree,name)
     
 def compare_skel_trees(atree,btree):
@@ -386,9 +386,9 @@ def compare_skel_trees(atree,btree):
     b_missing = set()
     a_names = set(e.get("name") for e in atree.getroot().iter() if e.get("name"))
     b_names = set(e.get("name") for e in btree.getroot().iter() if e.get("name"))
-    print "a_names\n  ",str("\n  ").join(sorted(list(a_names)))
-    print
-    print "b_names\n  ","\n  ".join(sorted(list(b_names)))
+    print("a_names\n  ",str("\n  ").join(sorted(list(a_names))))
+    print()
+    print("b_names\n  ","\n  ".join(sorted(list(b_names))))
     all_names = set.union(a_names,b_names)
     for name in all_names:
         if not name:
@@ -396,38 +396,38 @@ def compare_skel_trees(atree,btree):
         a_element = get_element_by_name(atree,name)
         b_element = get_element_by_name(btree,name)
         if a_element is None or b_element is None:
-            print "something not found for",name,a_element,b_element
+            print("something not found for",name,a_element,b_element)
         if a_element is not None and b_element is not None:
             all_attrib = set.union(set(a_element.attrib.keys()),set(b_element.attrib.keys()))
-            print name,all_attrib
+            print(name,all_attrib)
             for att in all_attrib:
                 if a_element.get(att) != b_element.get(att):
                     if not att in diffs:
                         diffs[att] = set()
                     diffs[att].add(name)
-                print "tuples",name,att,float_tuple(a_element.get(att)),float_tuple(b_element.get(att))
+                print("tuples",name,att,float_tuple(a_element.get(att)),float_tuple(b_element.get(att)))
                 if float_tuple(a_element.get(att)) != float_tuple(b_element.get(att)):
-                    print "diff in",name,att
+                    print("diff in",name,att)
                     if not att in realdiffs:
                         realdiffs[att] = set()
                     realdiffs[att].add(name)
     for att in diffs:
-        print "Differences in",att
+        print("Differences in",att)
         for name in sorted(diffs[att]):
-            print "  ",name
+            print("  ",name)
     for att in realdiffs:
-        print "Real differences in",att
+        print("Real differences in",att)
         for name in sorted(diffs[att]):
-            print "  ",name
+            print("  ",name)
     a_missing = b_names.difference(a_names)
     b_missing = a_names.difference(b_names)
     if len(a_missing) or len(b_missing):
-        print "Missing from comparison"
+        print("Missing from comparison")
         for name in a_missing:
-            print "  ",name
-        print "Missing from infile"
+            print("  ",name)
+        print("Missing from infile")
         for name in b_missing:
-            print "  ",name
+            print("  ",name)
 
 if __name__ == "__main__":
 
@@ -499,5 +499,5 @@ def compare_skel_trees(atree,btree):
         
     if args.outfilename:
         f = open(args.outfilename,"w")
-        print >>f, etree.tostring(tree, pretty_print=True) #need update to get: , short_empty_elements=True)
+        print(etree.tostring(tree, pretty_print=True), file=f) #need update to get: , short_empty_elements=True)
 
diff --git a/scripts/md5check.py b/scripts/md5check.py
index 1a54a2844c395e037a321a789f7015e642a0faa6..20ebfa665683893896a4e5242d8e3dcedaca96b7 100755
--- a/scripts/md5check.py
+++ b/scripts/md5check.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 """\
 @file md5check.py
 @brief Replacement for message template compatibility verifier.
@@ -29,14 +29,14 @@
 import hashlib
 
 if len(sys.argv) != 3:
-    print """Usage: %s --create|<hash-digest> <file>
+    print("""Usage: %s --create|<hash-digest> <file>
 
 Creates an md5sum hash digest of the specified file content
 and compares it with the given hash digest.
 
 If --create is used instead of a hash digest, it will simply
 print out the hash digest of specified file content.
-""" % sys.argv[0]
+""" % sys.argv[0])
     sys.exit(1)
 
 if sys.argv[2] == '-':
@@ -48,9 +48,9 @@
 
 hexdigest = hashlib.md5(fh.read()).hexdigest()
 if sys.argv[1] == '--create':
-    print hexdigest
+    print(hexdigest)
 elif hexdigest == sys.argv[1]:
-    print "md5sum check passed:", filename
+    print("md5sum check passed:", filename)
 else:
-    print "md5sum check FAILED:", filename
+    print("md5sum check FAILED:", filename)
     sys.exit(1)
diff --git a/scripts/metrics/viewer_asset_logs.py b/scripts/metrics/viewer_asset_logs.py
index e48286f696d19c05cf79e241aa447e3298a10a08..dbbdfb101866285aa865d3d1dae7ac57c15be8eb 100644
--- a/scripts/metrics/viewer_asset_logs.py
+++ b/scripts/metrics/viewer_asset_logs.py
@@ -1,4 +1,4 @@
-#!runpy.sh
+#!/usr/bin/env python3
 
 """\
 
@@ -40,7 +40,7 @@ def get_metrics_record(infiles):
         context = iter(context)
 
         # get the root element
-        event, root = context.next()
+        event, root = next(context)
         try:
             for event, elem in context:
                 if event == "end" and elem.tag == "llsd":
@@ -48,7 +48,7 @@ def get_metrics_record(infiles):
                     sd = llsd.parse_xml(xmlstr)
                     yield sd
         except etree.XMLSyntaxError:
-            print "Fell off end of document"
+            print("Fell off end of document")
 
         f.close()
 
@@ -56,7 +56,7 @@ def update_stats(stats,rec):
     for region in rec["regions"]:
         region_key = (region["grid_x"],region["grid_y"])
         #print "region",region_key
-        for field, val in region.iteritems():
+        for field, val in region.items():
             if field in ["duration","grid_x","grid_y"]:
                 continue
             if field == "fps":
@@ -96,7 +96,7 @@ def update_stats(stats,rec):
     for key in sorted(stats.keys()):
         val = stats[key]
         if val["count"] > 0:
-            print key,"count",val["count"],"mean_time",val["sum"]/val["count"],"mean_bytes",val["sum_bytes"]/val["count"],"net bytes/sec",val["sum_bytes"]/val["sum"],"enqueued",val["enqueued"],"dequeued",val["dequeued"]
+            print(key,"count",val["count"],"mean_time",val["sum"]/val["count"],"mean_bytes",val["sum_bytes"]/val["count"],"net bytes/sec",val["sum_bytes"]/val["sum"],"enqueued",val["enqueued"],"dequeued",val["dequeued"])
         else:
-            print key,"count",val["count"],"enqueued",val["enqueued"],"dequeued",val["dequeued"]
+            print(key,"count",val["count"],"enqueued",val["enqueued"],"dequeued",val["dequeued"])
 
diff --git a/scripts/metrics/viewerstats.py b/scripts/metrics/viewerstats.py
index f7be3d967e19b14edbf2c0a841943ab6d18bb5a2..576598a30b9f87391679a7eabd1e0c5fb6d85a18 100755
--- a/scripts/metrics/viewerstats.py
+++ b/scripts/metrics/viewerstats.py
@@ -1,4 +1,4 @@
-#!runpy.sh
+#!/usr/bin/env python3
 
 """\