Apertura del repositorio

This commit is contained in:
2025-05-24 18:09:39 -03:00
parent 76e6359dad
commit d883ddd0d0
35253 changed files with 2891973 additions and 2 deletions
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env python3
"""\
@file logsdir.py
@author Nat Goodspeed
@date 2024-09-12
@brief Locate the Second Life logs directory for the current user on the
current platform.
$LicenseInfo:firstyear=2024&license=viewerlgpl$
Copyright (c) 2024, Linden Research, Inc.
$/LicenseInfo$
"""
import os
from pathlib import Path
import platform
class Error(Exception):
pass
# logic used by SLVersionChecker
def logsdir():
app = 'SecondLife'
system = platform.system()
if (system == 'Darwin'):
base_dir = os.path.join(os.path.expanduser('~'),
'Library','Application Support',app)
elif (system == 'Linux'):
base_dir = os.path.join(os.path.expanduser('~'),
'.' + app.lower())
elif (system == 'Windows'):
appdata = os.getenv('APPDATA')
base_dir = os.path.join(appdata, app)
else:
raise ValueError("Unsupported platform '%s'" % system)
return os.path.join(base_dir, 'logs')
def latest_file(dirpath, pattern):
files = Path(dirpath).glob(pattern)
sort = [(p.stat().st_mtime, p) for p in files if p.is_file()]
sort.sort(reverse=True)
try:
return sort[0][1]
except IndexError:
raise Error(f'No {pattern} files in {dirpath}')
+204
View File
@@ -0,0 +1,204 @@
#!/usr/bin/env python3
"""\
@file perfbot_run.py
@brief Run a number of non interactive Viewers (PerfBots) with
a variety of options and settings. Pass --help for details.
$LicenseInfo:firstyear=2007&license=viewerlgpl$
Second Life Viewer Source Code
Copyright (C) 2021, Linden Research, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation;
version 2.1 of the License only.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
$/LicenseInfo$
"""
import argparse
import subprocess
import os
import math
import time
# Required parameters that are always passed in
# Specify noninteractive mode (SL-15999 for details)
PARAM_NON_INTERACTIVE = "--noninteractive"
# Run multiple Viewers at once
PARAM_MULTI = "--multiple"
# Specify username (first and last) and password
PARAM_LOGIN = "--login"
# SLURL to teleport to after login
PARAM_SLURL = "--slurl"
def gen_niv_script(args):
print(f"Reading creds from {(args.creds)} folder")
print(f"Using the non interactive Viewer from {(args.viewer)}")
print(f"Sleeping for {args.sleep}ms between Viewer launches")
# Read the lines from the creds file. Typically this will be
# stored in the build-secrets-git private repository but you
# can point to any location with the --creds parameter
creds_lines = []
with open(args.creds) as file:
creds_lines = file.readlines()
creds_lines = [line.rstrip() for line in creds_lines]
creds_lines = [line for line in creds_lines if not line.startswith("#") and len(line)]
# We cannot log in more users than we have credentials for
if args.num==0:
args.num = len(creds_lines)
if args.num > len(creds_lines):
print(
f"The number of agents specified ({(args.num)}) exceeds "
f"the number of valid entries ({(len(creds_lines))}) in "
f"the creds file "
)
return
print(f"Launching {(args.num)} instances of the Viewer")
# The Viewer (in dev environments at least) needs a well specified
# working directory to function properly. We try to guess what it
# might be based on the full path to the Viewer executable but
# you can also specify it explicitly with the --cwd parameter
# (required for dev builds)
args.viewer = os.path.abspath(args.viewer)
if len(args.cwd) == 0:
working_dir = os.path.dirname(os.path.abspath(args.viewer))
else:
working_dir = os.path.abspath(args.cwd)
print(f"Working directory is {working_dir}, cwd {args.cwd}")
os.chdir(working_dir)
if args.dryrun:
print("Running in dry-run mode - no Viewers will be started")
print("")
for inst in range(args.num):
# Format of each cred line is username_first username_last password
# A space is used to separate each and a # at the start of a line
# removes it from the pool (useful if someone else is using a subset
# of the available ones)
creds = creds_lines[inst].split(" ")
username_first = creds[0]
username_last = creds[1]
password = creds[2]
# The default layout is an evenly spaced circle in the
# center of the region. We may extend this to allow other
# patterns like a square/rectangle or a spiral. (Hint: it
# likely won't be needed :))
center_x = 128
center_y = 128
if args.layout == "circle":
radius = 6
angle = (2 * math.pi / args.num) * inst
region_x = int(math.sin(angle) * radius + center_x)
region_y = int(math.cos(angle) * radius + center_y)
region_z = 0
elif args.layout == "square":
region_x = center_x
region_y = center_y
elif args.layout == "spiral":
region_x = center_x
region_y = center_y
slurl = f"secondlife://{args.region}/{region_x}/{region_y}/{region_z}"
# Build the script line
script_cmd = [args.viewer]
script_cmd.append(PARAM_NON_INTERACTIVE)
script_cmd.append(PARAM_MULTI)
script_cmd.append(PARAM_LOGIN)
script_cmd.append(username_first)
script_cmd.append(username_last)
script_cmd.append(password)
script_cmd.append(PARAM_SLURL)
script_cmd.append(slurl)
# Display the script we will execute.
cmd = ""
for p in script_cmd:
cmd = cmd + " " + p
print(cmd)
# If --dry-run is specified, we do everything (including, most
# usefully, display the script lines) but do not start the Viewer
if args.dryrun == False:
print("opening viewer session with",script_cmd)
viewer_session = subprocess.Popen(script_cmd)
# Sleeping a bit between launches seems to help avoid a CPU
# surge when N Viewers are started simulatanously. The default
# value can be changed with the --sleep parameter
time.sleep(args.sleep / 1000)
if __name__ == "__main__":
parser = argparse.ArgumentParser(allow_abbrev=False)
parser.add_argument(
"--num",
type=int,
default=0,
dest="num",
help="How many avatars to add to the script",
)
parser.add_argument(
"--creds",
default="../../../build-secrets-git/perf/perfbot_creds.txt",
dest="creds",
help="Location of the text file containing user credentials",
)
parser.add_argument(
"--viewer",
default="C:/Program Files/SecondLife/SecondLifeViewer.exe",
dest="viewer",
help="Location of the non interactive Viewer build",
)
parser.add_argument(
"--cwd",
default="",
dest="cwd",
help="Location of the current working directory to use",
)
parser.add_argument(
"--region",
default="Lag Me 5",
dest="region",
help="The SLURL for the Second Life region to visit",
)
parser.add_argument(
"--layout",
default="circle",
dest="layout",
choices={"circle", "square", "spiral"},
help="The geometric layout of the avatar destination locations",
)
parser.add_argument(
"--sleep",
type=int,
default=1000,
dest="sleep",
help="Time to sleep between launches in milliseconds",
)
parser.add_argument(
"--dry-run",
action="store_true",
dest="dryrun",
help="Dryrun mode - display parameters and script lines but do not start any Viewers",
)
args = parser.parse_args()
gen_niv_script(args)
+104
View File
@@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""\
@file profile_cmp.py
@author Nat Goodspeed
@date 2024-09-13
@brief Compare a frame profile stats file with a similar baseline file.
$LicenseInfo:firstyear=2024&license=viewerlgpl$
Copyright (c) 2024, Linden Research, Inc.
$/LicenseInfo$
"""
from datetime import datetime
import json
from logsdir import Error, latest_file, logsdir
from pathlib import Path
import sys
# variance that's ignorable
DEFAULT_EPSILON = 0.03 # 3%
def compare(baseline, test, epsilon=DEFAULT_EPSILON):
if Path(baseline).samefile(test):
print(f'{baseline} same as\n{test}\nAnalysis moot.')
return
with open(baseline) as inf:
bdata = json.load(inf)
with open(test) as inf:
tdata = json.load(inf)
print(f'baseline {baseline}\ntestfile {test}')
for k, tv in tdata['context'].items():
bv = bdata['context'].get(k)
if bv != tv:
print(f'baseline {k}={bv} vs.\ntestfile {k}={tv}')
btime = bdata['context'].get('time')
ttime = tdata['context'].get('time')
if btime and ttime:
print('testfile newer by',
datetime.fromisoformat(ttime) - datetime.fromisoformat(btime))
# The following ignores totals and unused shaders, except to the extent
# that some shaders were used in the baseline but not in the recent test
# or vice-versa. While the viewer considers that a shader has been used if
# 'binds' is nonzero, we exclude any whose 'time' is zero to avoid zero
# division.
bshaders = {s['name']: s for s in bdata['shaders'] if s['time'] and s['samples']}
tshaders = {s['name']: s for s in tdata['shaders'] if s['time']}
bothshaders = set(bshaders).intersection(tshaders)
deltas = []
for shader in bothshaders:
bshader = bshaders[shader]
tshader = tshaders[shader]
bthruput = bshader['samples']/bshader['time']
tthruput = tshader['samples']/tshader['time']
delta = (tthruput - bthruput)/bthruput
if abs(delta) > epsilon:
deltas.append((delta, shader, bthruput, tthruput))
# descending order of performance gain
deltas.sort(reverse=True)
print(f'{len(deltas)} shaders showed nontrivial performance differences '
'(millon samples/sec):')
namelen = max(len(s[1]) for s in deltas) if deltas else 0
for delta, shader, bthruput, tthruput in deltas:
print(f' {shader.rjust(namelen)} {delta*100:6.1f}% '
f'{bthruput/1000000:8.2f} -> {tthruput/1000000:8.2f}')
tunused = set(bshaders).difference(tshaders)
print(f'{len(tunused)} baseline shaders not used in test:')
for s in tunused:
print(f' {s}')
bunused = set(tshaders).difference(bshaders)
print(f'{len(bunused)} shaders newly used in test:')
for s in bunused:
print(f' {s}')
def main(*raw_args):
from argparse import ArgumentParser
parser = ArgumentParser(description="""
%(prog)s compares a baseline JSON file from Develop -> Render Tests -> Frame
Profile to another such file from a more recent test. It identifies shaders
that have gained and lost in throughput.
""")
parser.add_argument('-e', '--epsilon', type=float, default=int(DEFAULT_EPSILON*100),
help="""percent variance considered ignorable (default %(default)s%%)""")
parser.add_argument('baseline',
help="""baseline profile filename to compare against""")
parser.add_argument('test', nargs='?',
help="""test profile filename to compare
(default is most recent)""")
args = parser.parse_args(raw_args)
compare(args.baseline,
args.test or latest_file(logsdir(), 'profile.*.json'),
epsilon=(args.epsilon / 100.))
if __name__ == "__main__":
try:
sys.exit(main(*sys.argv[1:]))
except (Error, OSError, json.JSONDecodeError) as err:
sys.exit(str(err))
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env python3
"""\
@file profile_csv.py
@author Nat Goodspeed
@date 2024-09-12
@brief Convert a JSON file from Develop -> Render Tests -> Frame Profile to CSV
$LicenseInfo:firstyear=2024&license=viewerlgpl$
Copyright (c) 2024, Linden Research, Inc.
$/LicenseInfo$
"""
import json
from logsdir import Error, latest_file, logsdir
import sys
def convert(path, totals=True, unused=True, file=sys.stdout):
with open(path) as inf:
data = json.load(inf)
# print path to sys.stderr in case user is redirecting stdout
print(path, file=sys.stderr)
print('"name", "file1", "file2", "time", "binds", "samples", "triangles"', file=file)
if totals:
t = data['totals']
print(f'"totals", "", "", {t["time"]}, {t["binds"]}, {t["samples"]}, {t["triangles"]}',
file=file)
for sh in data['shaders']:
print(f'"{sh["name"]}", "{sh["files"][0]}", "{sh["files"][1]}", '
f'{sh["time"]}, {sh["binds"]}, {sh["samples"]}, {sh["triangles"]}', file=file)
if unused:
for u in data['unused']:
print(f'"{u}", "", "", 0, 0, 0, 0', file=file)
def main(*raw_args):
from argparse import ArgumentParser
parser = ArgumentParser(description="""
%(prog)s converts a JSON file from Develop -> Render Tests -> Frame Profile to
a more-or-less equivalent CSV file. It expands the totals stats and unused
shaders list to full shaders lines.
""")
parser.add_argument('-t', '--totals', action='store_false', default=True,
help="""omit totals from CSV file""")
parser.add_argument('-u', '--unused', action='store_false', default=True,
help="""omit unused shaders from CSV file""")
parser.add_argument('path', nargs='?',
help="""profile filename to convert (default is most recent)""")
args = parser.parse_args(raw_args)
convert(args.path or latest_file(logsdir(), 'profile.*.json'),
totals=args.totals, unused=args.unused)
if __name__ == "__main__":
try:
sys.exit(main(*sys.argv[1:]))
except (Error, OSError, json.JSONDecodeError) as err:
sys.exit(str(err))
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env python3
"""\
@file profile_pretty.py
@author Nat Goodspeed
@date 2024-09-12
@brief Pretty-print a JSON file from Develop -> Render Tests -> Frame Profile
$LicenseInfo:firstyear=2024&license=viewerlgpl$
Copyright (c) 2024, Linden Research, Inc.
$/LicenseInfo$
"""
import json
from logsdir import Error, latest_file, logsdir
import sys
def pretty(path):
with open(path) as inf:
data = json.load(inf)
# print path to sys.stderr in case user is redirecting stdout
print(path, file=sys.stderr)
json.dump(data, sys.stdout, indent=4)
def main(*raw_args):
from argparse import ArgumentParser
parser = ArgumentParser(description="""
%(prog)s pretty-prints a JSON file from Develop -> Render Tests -> Frame Profile.
The file produced by the viewer is a single dense line of JSON.
""")
parser.add_argument('path', nargs='?',
help="""profile filename to pretty-print (default is most recent)""")
args = parser.parse_args(raw_args)
pretty(args.path or latest_file(logsdir(), 'profile.*.json'))
if __name__ == "__main__":
try:
sys.exit(main(*sys.argv[1:]))
except (Error, OSError, json.JSONDecodeError) as err:
sys.exit(str(err))