From ac67a7c7a11c188281f357f8f2e6e10eeb3a5b78 Mon Sep 17 00:00:00 2001 From: "Brad Payne (Vir Linden)" Date: Wed, 20 Sep 2023 18:24:18 +0100 Subject: DRTVWR-589 - added play animation and started to collect demo scripts --- scripts/lua/avatar.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 scripts/lua/avatar.lua (limited to 'scripts') diff --git a/scripts/lua/avatar.lua b/scripts/lua/avatar.lua new file mode 100644 index 0000000000..7c419a740c --- /dev/null +++ b/scripts/lua/avatar.lua @@ -0,0 +1,14 @@ +function call_once_func() + run_ui_command("World.EnvSettings", "midnight") + sleep(1) + run_ui_command("World.EnvSettings", "noon") + sleep(1) + wear_by_name("* AVL") + run_ui_command("Avatar.ResetSelfSkeletonAndAnimations") + sleep(5) + wear_by_name("* Elephant") + sleep(5) + play_animation("Elephant_Fly"); + sleep(5) + play_animation("Elephant_Fly",1); +end \ No newline at end of file -- cgit v1.2.3 From a5415b6afc17474a9462fb8e395263ceef887162 Mon Sep 17 00:00:00 2001 From: "Brad Payne (Vir Linden)" Date: Fri, 22 Sep 2023 15:43:07 +0100 Subject: DRTVWR-589 - updates to demo.lua, take avs from library --- scripts/lua/demo.lua | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 scripts/lua/demo.lua (limited to 'scripts') diff --git a/scripts/lua/demo.lua b/scripts/lua/demo.lua new file mode 100644 index 0000000000..9748384d75 --- /dev/null +++ b/scripts/lua/demo.lua @@ -0,0 +1,99 @@ +function popup_and_wait_ok(message) + args = {{"MESSAGE", message}} + notif_response = nil + show_notification("GenericAlertOK", args, "notif_response") + while not notif_response do + sleep(0.2) + end + + local response = notif_response + return response +end + +function demo_environment() + popup_and_wait_ok("Change Environment") + run_ui_command("World.EnvSettings", "midnight") + sleep(2) + run_ui_command("World.EnvSettings", "sunrise") + sleep(2) + run_ui_command("World.EnvSettings", "noon") + sleep(2) +end + +function demo_avatar() + popup_and_wait_ok("Change Avatar") + wear_by_name("Greg") + run_ui_command("Avatar.ResetSelfSkeletonAndAnimations") + sleep(8) + + wear_by_name("Petrol Sue") + sleep(8) + +end + +function demo_ui() + + + -- adding items to 'Build' menu + -- popup_and_wait_ok("Extend UI") + + notif_response = nil + args = {{"MESSAGE", "Extend the UI now?"}} + show_notification("GenericAlertYesCancel", args, "notif_response") + while not notif_response do + sleep(0.2) + end + if notif_response ~= 0 then + popup_and_wait_ok("Exiting") + return + end + + menu_name = "BuildTools" + add_menu_separator(menu_name) + + params = {{"name", "user_sit"}, {"label", "Sit!"}, + {"function", "Self.ToggleSitStand"}} + + add_menu_item(menu_name, params) + + params = {{"name", "user_midnight"}, {"label", "Set night"}, + {"function", "World.EnvSettings"}, {"parameter", "midnight"}} + + add_menu_item(menu_name, params) + + -- adding new custom menu + new_menu_name = "user_menu" + params = {{"name", new_menu_name}, {"label", "My Secret Menu"}, {"tear_off", "true"}} + add_menu(params) + + -- adding new item to the new menu + params = {{"name", "user_debug"}, {"label", "Console"}, + {"function", "Advanced.ToggleConsole"}, {"parameter", "debug"}} + + add_menu_item(new_menu_name, params) + + -- adding new branch + new_branch = "user_floaters" + params = {{"name", new_branch}, {"label", "Open Floater"}, {"tear_off", "true"}} + add_branch(new_menu_name, params) + + -- adding items to the branch + params = {{"name", "user_permissions"}, {"label", "Default permissions"}, + {"function", "Floater.ToggleOrBringToFront"}, {"parameter", "perms_default"}} + + add_menu_item(new_branch, params) + + params = {{"name", "user_beacons"}, {"label", "Beacons"}, + {"function", "Floater.ToggleOrBringToFront"}, {"parameter", "beacons"}} + + add_menu_item(new_branch, params) + sleep(5) + +end + +function call_once_func() + + demo_environment() + demo_avatar() + demo_ui() +end -- cgit v1.2.3 From b1833bb01bf3a6ba3839b59441b0c18e37a33a19 Mon Sep 17 00:00:00 2001 From: "Brad Payne (Vir Linden)" Date: Fri, 22 Sep 2023 20:50:13 +0100 Subject: DRTVWR-589 - more demo.lua tweaks --- scripts/lua/demo.lua | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) (limited to 'scripts') diff --git a/scripts/lua/demo.lua b/scripts/lua/demo.lua index 9748384d75..dc2d9ef669 100644 --- a/scripts/lua/demo.lua +++ b/scripts/lua/demo.lua @@ -29,6 +29,17 @@ function demo_avatar() wear_by_name("Petrol Sue") sleep(8) + run_ui_command("Self.ToggleSitStand") + sleep(2) + run_ui_command("Self.ToggleSitStand") + sleep(2) + + run_ui_command("View.ZoomOut") + run_ui_command("View.ZoomOut") + run_ui_command("EditShape") + sleep(6) + close_floater("appearance") + end function demo_ui() @@ -37,8 +48,18 @@ function demo_ui() -- adding items to 'Build' menu -- popup_and_wait_ok("Extend UI") + popup_and_wait_ok("UI interaction") + open_floater("inventory") + open_floater("preferences") + open_floater("nearby_chat") + nearby_chat_send("Hello World!") + + sleep(5) + close_all_floaters() + + notif_response = nil - args = {{"MESSAGE", "Extend the UI now?"}} + args = {{"MESSAGE", "Customize the UI now?"}} show_notification("GenericAlertYesCancel", args, "notif_response") while not notif_response do sleep(0.2) -- cgit v1.2.3 From 38a6ef9921e6b305bdb8a3165cf45da0aa76adee Mon Sep 17 00:00:00 2001 From: "Brad Payne (Vir Linden)" Date: Tue, 3 Oct 2023 21:47:00 +0100 Subject: DRTVWR-589 - more demo work --- scripts/lua/demo.lua | 59 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 16 deletions(-) (limited to 'scripts') diff --git a/scripts/lua/demo.lua b/scripts/lua/demo.lua index dc2d9ef669..3442c18b0d 100644 --- a/scripts/lua/demo.lua +++ b/scripts/lua/demo.lua @@ -1,5 +1,5 @@ function popup_and_wait_ok(message) - args = {{"MESSAGE", message}} + args = {MESSAGE=message} notif_response = nil show_notification("GenericAlertOK", args, "notif_response") while not notif_response do @@ -20,8 +20,31 @@ function demo_environment() sleep(2) end +function demo_rez() + for x=-1,1,1 do + for y=-1,1,1 do + rez_prim2({x,y,-1},1) + end + end +end + function demo_avatar() popup_and_wait_ok("Change Avatar") + + local dest = {10,10,0} + move_by(dest, "autopilot_response") + while not autopilot_response do + sleep(0.2) + end + + local response = autopilot_response + + if response == 1 then + sleep(1) + demo_rez() + sleep(2) + end + wear_by_name("Greg") run_ui_command("Avatar.ResetSelfSkeletonAndAnimations") sleep(8) @@ -34,8 +57,7 @@ function demo_avatar() run_ui_command("Self.ToggleSitStand") sleep(2) - run_ui_command("View.ZoomOut") - run_ui_command("View.ZoomOut") + --run_ui_command("View.ZoomOut") run_ui_command("EditShape") sleep(6) close_floater("appearance") @@ -59,7 +81,7 @@ function demo_ui() notif_response = nil - args = {{"MESSAGE", "Customize the UI now?"}} + args = {MESSAGE="Customize the UI now?"} show_notification("GenericAlertYesCancel", args, "notif_response") while not notif_response do sleep(0.2) @@ -72,40 +94,45 @@ function demo_ui() menu_name = "BuildTools" add_menu_separator(menu_name) - params = {{"name", "user_sit"}, {"label", "Sit!"}, - {"function", "Self.ToggleSitStand"}} + params = {name="user_sit", + label="Sit!"} + params["function"]="Self.ToggleSitStand" add_menu_item(menu_name, params) - params = {{"name", "user_midnight"}, {"label", "Set night"}, - {"function", "World.EnvSettings"}, {"parameter", "midnight"}} + params = {name="user_midnight",label="Set night",parameter="midnight"} + params["function"] = "World.EnvSettings" add_menu_item(menu_name, params) -- adding new custom menu new_menu_name = "user_menu" - params = {{"name", new_menu_name}, {"label", "My Secret Menu"}, {"tear_off", "true"}} + params = {name=new_menu_name,label="My Secret Menu",tear_off="true"} add_menu(params) -- adding new item to the new menu - params = {{"name", "user_debug"}, {"label", "Console"}, - {"function", "Advanced.ToggleConsole"}, {"parameter", "debug"}} + params = {name="user_debug",label="Console", + parameter="debug"} + params["function"] = "Advanced.ToggleConsole" add_menu_item(new_menu_name, params) -- adding new branch new_branch = "user_floaters" - params = {{"name", new_branch}, {"label", "Open Floater"}, {"tear_off", "true"}} + params = {name=new_branch, label="Open Floater",tear_off="true"} add_branch(new_menu_name, params) -- adding items to the branch - params = {{"name", "user_permissions"}, {"label", "Default permissions"}, - {"function", "Floater.ToggleOrBringToFront"}, {"parameter", "perms_default"}} + params = {name="user_permissions",label="Default permissions", + parameter="perms_default"} + params["function"] = "Floater.ToggleOrBringToFront" + add_menu_item(new_branch, params) - params = {{"name", "user_beacons"}, {"label", "Beacons"}, - {"function", "Floater.ToggleOrBringToFront"}, {"parameter", "beacons"}} + params = {name="user_beacons",label="Beacons", + parameter="beacons"} + params["function"] = "Floater.ToggleOrBringToFront" add_menu_item(new_branch, params) sleep(5) -- cgit v1.2.3 From 2acb8bdf937dda337a591caa227d604efec5ca4a Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Mon, 29 Jan 2024 20:50:30 +0200 Subject: DRTVWR-589: get rid of pragma and update windows libs --- scripts/lua/avatar.lua | 4 +--- scripts/lua/demo.lua | 8 +++----- 2 files changed, 4 insertions(+), 8 deletions(-) (limited to 'scripts') diff --git a/scripts/lua/avatar.lua b/scripts/lua/avatar.lua index 7c419a740c..159014fa04 100644 --- a/scripts/lua/avatar.lua +++ b/scripts/lua/avatar.lua @@ -1,4 +1,3 @@ -function call_once_func() run_ui_command("World.EnvSettings", "midnight") sleep(1) run_ui_command("World.EnvSettings", "noon") @@ -10,5 +9,4 @@ function call_once_func() sleep(5) play_animation("Elephant_Fly"); sleep(5) - play_animation("Elephant_Fly",1); -end \ No newline at end of file + play_animation("Elephant_Fly",1); \ No newline at end of file diff --git a/scripts/lua/demo.lua b/scripts/lua/demo.lua index 3442c18b0d..90eaf667bb 100644 --- a/scripts/lua/demo.lua +++ b/scripts/lua/demo.lua @@ -139,9 +139,7 @@ function demo_ui() end -function call_once_func() +demo_environment() +demo_avatar() +demo_ui() - demo_environment() - demo_avatar() - demo_ui() -end -- cgit v1.2.3 From ab1f2c2f6f9b854b95db3733fd6ff6d02e677ebd Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Tue, 30 Jan 2024 15:44:08 +0200 Subject: strip lua testing functions --- scripts/lua/avatar.lua | 12 ---- scripts/lua/demo.lua | 145 ------------------------------------------------- 2 files changed, 157 deletions(-) delete mode 100644 scripts/lua/avatar.lua delete mode 100644 scripts/lua/demo.lua (limited to 'scripts') diff --git a/scripts/lua/avatar.lua b/scripts/lua/avatar.lua deleted file mode 100644 index 159014fa04..0000000000 --- a/scripts/lua/avatar.lua +++ /dev/null @@ -1,12 +0,0 @@ - run_ui_command("World.EnvSettings", "midnight") - sleep(1) - run_ui_command("World.EnvSettings", "noon") - sleep(1) - wear_by_name("* AVL") - run_ui_command("Avatar.ResetSelfSkeletonAndAnimations") - sleep(5) - wear_by_name("* Elephant") - sleep(5) - play_animation("Elephant_Fly"); - sleep(5) - play_animation("Elephant_Fly",1); \ No newline at end of file diff --git a/scripts/lua/demo.lua b/scripts/lua/demo.lua deleted file mode 100644 index 90eaf667bb..0000000000 --- a/scripts/lua/demo.lua +++ /dev/null @@ -1,145 +0,0 @@ -function popup_and_wait_ok(message) - args = {MESSAGE=message} - notif_response = nil - show_notification("GenericAlertOK", args, "notif_response") - while not notif_response do - sleep(0.2) - end - - local response = notif_response - return response -end - -function demo_environment() - popup_and_wait_ok("Change Environment") - run_ui_command("World.EnvSettings", "midnight") - sleep(2) - run_ui_command("World.EnvSettings", "sunrise") - sleep(2) - run_ui_command("World.EnvSettings", "noon") - sleep(2) -end - -function demo_rez() - for x=-1,1,1 do - for y=-1,1,1 do - rez_prim2({x,y,-1},1) - end - end -end - -function demo_avatar() - popup_and_wait_ok("Change Avatar") - - local dest = {10,10,0} - move_by(dest, "autopilot_response") - while not autopilot_response do - sleep(0.2) - end - - local response = autopilot_response - - if response == 1 then - sleep(1) - demo_rez() - sleep(2) - end - - wear_by_name("Greg") - run_ui_command("Avatar.ResetSelfSkeletonAndAnimations") - sleep(8) - - wear_by_name("Petrol Sue") - sleep(8) - - run_ui_command("Self.ToggleSitStand") - sleep(2) - run_ui_command("Self.ToggleSitStand") - sleep(2) - - --run_ui_command("View.ZoomOut") - run_ui_command("EditShape") - sleep(6) - close_floater("appearance") - -end - -function demo_ui() - - - -- adding items to 'Build' menu - -- popup_and_wait_ok("Extend UI") - - popup_and_wait_ok("UI interaction") - open_floater("inventory") - open_floater("preferences") - open_floater("nearby_chat") - nearby_chat_send("Hello World!") - - sleep(5) - close_all_floaters() - - - notif_response = nil - args = {MESSAGE="Customize the UI now?"} - show_notification("GenericAlertYesCancel", args, "notif_response") - while not notif_response do - sleep(0.2) - end - if notif_response ~= 0 then - popup_and_wait_ok("Exiting") - return - end - - menu_name = "BuildTools" - add_menu_separator(menu_name) - - params = {name="user_sit", - label="Sit!"} - params["function"]="Self.ToggleSitStand" - - add_menu_item(menu_name, params) - - params = {name="user_midnight",label="Set night",parameter="midnight"} - params["function"] = "World.EnvSettings" - - add_menu_item(menu_name, params) - - -- adding new custom menu - new_menu_name = "user_menu" - params = {name=new_menu_name,label="My Secret Menu",tear_off="true"} - add_menu(params) - - -- adding new item to the new menu - params = {name="user_debug",label="Console", - parameter="debug"} - params["function"] = "Advanced.ToggleConsole" - - add_menu_item(new_menu_name, params) - - -- adding new branch - new_branch = "user_floaters" - params = {name=new_branch, label="Open Floater",tear_off="true"} - add_branch(new_menu_name, params) - - -- adding items to the branch - params = {name="user_permissions",label="Default permissions", - parameter="perms_default"} - params["function"] = "Floater.ToggleOrBringToFront" - - - add_menu_item(new_branch, params) - - params = {name="user_beacons",label="Beacons", - parameter="beacons"} - params["function"] = "Floater.ToggleOrBringToFront" - - add_menu_item(new_branch, params) - sleep(5) - -end - -demo_environment() -demo_avatar() -demo_ui() - -- cgit v1.2.3 From dc91db0b85c5db1c20dccebd64c98419e371a29f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 14 Jun 2024 11:28:22 -0400 Subject: Fix a minor but nagging Python build-time warning: invalid regexp. --- scripts/packages-formatter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'scripts') diff --git a/scripts/packages-formatter.py b/scripts/packages-formatter.py index 4449111e46..5d31702e76 100755 --- a/scripts/packages-formatter.py +++ b/scripts/packages-formatter.py @@ -42,7 +42,7 @@ _autobuild_env=os.environ.copy() # Coerce stdout encoding to utf-8 as cygwin's will be detected as cp1252 otherwise. _autobuild_env["PYTHONIOENCODING"] = "utf-8" -pkg_line=re.compile('^([\w-]+):\s+(.*)$') +pkg_line=re.compile(r'^([\w-]+):\s+(.*)$') def autobuild(*args): """ -- cgit v1.2.3 From ab3083819793a30911354670a7929b0d3f7c104c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 12:28:05 -0400 Subject: Add a JSON frame profile stats file pretty-printer script. --- scripts/perf/profile_pretty.py | 54 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 scripts/perf/profile_pretty.py (limited to 'scripts') diff --git a/scripts/perf/profile_pretty.py b/scripts/perf/profile_pretty.py new file mode 100644 index 0000000000..ca52fe366a --- /dev/null +++ b/scripts/perf/profile_pretty.py @@ -0,0 +1,54 @@ +#!/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 logsdir +import json +from pathlib import Path +import sys + +class Error(Exception): + pass + +def pretty(path=None): + if not path: + logs = logsdir.logsdir() + profiles = Path(logs).glob('profile.*.json') + sort = [(p.stat().st_mtime, p) for p in profiles] + sort.sort(reverse=True) + try: + path = sort[0][1] + except IndexError: + raise Error(f'No profile.*.json files in {logs}') + # print path to sys.stderr in case user is redirecting stdout + print(path, file=sys.stderr) + + with open(path) as inf: + data = json.load(inf) + 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) + +if __name__ == "__main__": + try: + sys.exit(main(*sys.argv[1:])) + except (Error, OSError, json.JSONDecodeError) as err: + sys.exit(str(err)) -- cgit v1.2.3 From d60b1f92213ace6a8ab6a4a60cb01a43f45d3955 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 13:43:36 -0400 Subject: Add script to convert frame profile JSON file to CSV. Also slightly refactor profile_pretty.py. --- scripts/perf/profile_csv.py | 72 ++++++++++++++++++++++++++++++++++++++++++ scripts/perf/profile_pretty.py | 26 +++++++-------- 2 files changed, 85 insertions(+), 13 deletions(-) create mode 100644 scripts/perf/profile_csv.py (limited to 'scripts') diff --git a/scripts/perf/profile_csv.py b/scripts/perf/profile_csv.py new file mode 100644 index 0000000000..273e3b7434 --- /dev/null +++ b/scripts/perf/profile_csv.py @@ -0,0 +1,72 @@ +#!/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 logsdir +import json +from pathlib import Path +import sys + +class Error(Exception): + pass + +def convert(path, totals=True, unused=True, file=sys.stdout): + with open(path) as inf: + data = json.load(inf) + 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) + if not args.path: + logs = logsdir.logsdir() + profiles = Path(logs).glob('profile.*.json') + sort = [(p.stat().st_mtime, p) for p in profiles] + sort.sort(reverse=True) + try: + args.path = sort[0][1] + except IndexError: + raise Error(f'No profile.*.json files in {logs}') + # print path to sys.stderr in case user is redirecting stdout + print(args.path, file=sys.stderr) + + convert(args.path, 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)) diff --git a/scripts/perf/profile_pretty.py b/scripts/perf/profile_pretty.py index ca52fe366a..15b6efd94d 100644 --- a/scripts/perf/profile_pretty.py +++ b/scripts/perf/profile_pretty.py @@ -18,19 +18,7 @@ import sys class Error(Exception): pass -def pretty(path=None): - if not path: - logs = logsdir.logsdir() - profiles = Path(logs).glob('profile.*.json') - sort = [(p.stat().st_mtime, p) for p in profiles] - sort.sort(reverse=True) - try: - path = sort[0][1] - except IndexError: - raise Error(f'No profile.*.json files in {logs}') - # print path to sys.stderr in case user is redirecting stdout - print(path, file=sys.stderr) - +def pretty(path): with open(path) as inf: data = json.load(inf) json.dump(data, sys.stdout, indent=4) @@ -45,6 +33,18 @@ The file produced by the viewer is a single dense line of JSON. help="""profile filename to pretty-print (default is most recent)""") args = parser.parse_args(raw_args) + if not args.path: + logs = logsdir.logsdir() + profiles = Path(logs).glob('profile.*.json') + sort = [(p.stat().st_mtime, p) for p in profiles] + sort.sort(reverse=True) + try: + args.path = sort[0][1] + except IndexError: + raise Error(f'No profile.*.json files in {logs}') + # print path to sys.stderr in case user is redirecting stdout + print(args.path, file=sys.stderr) + pretty(args.path) if __name__ == "__main__": -- cgit v1.2.3 From 1b7fdac4689e29ec3f64c37f10b843a114d7805d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 15:09:24 -0400 Subject: Add frame_profile.lua to TP to known spot and take frame profile. frame_profile.lua teleports home when done. Further add frame_profile bash script to run the specified viewer, automatically log into said known spot, take frame profile and quit. The frame_profile bash script runs frame_profile_quit.lua. frame_profile_quit.lua is derived from frame_profile.lua, but different: it doesn't teleport either way because it assumes autologin to the target location, and because it logs out instead of returning home. --- scripts/perf/frame_profile | 4 ++++ 1 file changed, 4 insertions(+) create mode 100755 scripts/perf/frame_profile (limited to 'scripts') diff --git a/scripts/perf/frame_profile b/scripts/perf/frame_profile new file mode 100755 index 0000000000..75bf0a3105 --- /dev/null +++ b/scripts/perf/frame_profile @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +"$1" --autologin --luafile frame_profile_quit.lua \ + http://maps.secondlife.com/secondlife/Bug%20Island/220/224/27 -- cgit v1.2.3 From d6f3f20af6cccf53746cbf1fdf39bc4e235c4f0d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 16:56:05 -0400 Subject: Convenience tweak for passing a Mac "Second Life Mumble.app" bundle --- scripts/perf/frame_profile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'scripts') diff --git a/scripts/perf/frame_profile b/scripts/perf/frame_profile index 75bf0a3105..0a4e0a74ff 100755 --- a/scripts/perf/frame_profile +++ b/scripts/perf/frame_profile @@ -1,4 +1,10 @@ #!/usr/bin/env bash -"$1" --autologin --luafile frame_profile_quit.lua \ +exe="$1" +if [[ "$OSTYPE" == darwin* && -d "$exe" && "$exe" == *.app ]] +then + exe="$(ls "$exe/Contents/MacOS/Second Life "*)" +fi + +"$exe" --autologin --luafile frame_profile_quit.lua \ http://maps.secondlife.com/secondlife/Bug%20Island/220/224/27 -- cgit v1.2.3 From 439cfc97a81f221daaf8ba13aa5daa87e8511047 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 13 Sep 2024 16:32:04 -0400 Subject: Add script to compare a Frame Profile JSON stats file vs. baseline. Extract `latest_file()` logic replicated in profile_pretty.py and profile_csv.py out to logsdir.py, and use for new profile_cmp.py. --- scripts/perf/logsdir.py | 46 ++++++++++++++++++ scripts/perf/profile_cmp.py | 104 +++++++++++++++++++++++++++++++++++++++++ scripts/perf/profile_csv.py | 24 +++------- scripts/perf/profile_pretty.py | 22 ++------- 4 files changed, 160 insertions(+), 36 deletions(-) create mode 100644 scripts/perf/logsdir.py create mode 100644 scripts/perf/profile_cmp.py (limited to 'scripts') diff --git a/scripts/perf/logsdir.py b/scripts/perf/logsdir.py new file mode 100644 index 0000000000..c8b498cf78 --- /dev/null +++ b/scripts/perf/logsdir.py @@ -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}') diff --git a/scripts/perf/profile_cmp.py b/scripts/perf/profile_cmp.py new file mode 100644 index 0000000000..9dbfa3145b --- /dev/null +++ b/scripts/perf/profile_cmp.py @@ -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)) diff --git a/scripts/perf/profile_csv.py b/scripts/perf/profile_csv.py index 273e3b7434..7a6b2b338e 100644 --- a/scripts/perf/profile_csv.py +++ b/scripts/perf/profile_csv.py @@ -10,17 +10,16 @@ Copyright (c) 2024, Linden Research, Inc. $/LicenseInfo$ """ -import logsdir import json -from pathlib import Path +from logsdir import Error, latest_file, logsdir import sys -class Error(Exception): - pass - 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: @@ -51,19 +50,8 @@ shaders list to full shaders lines. help="""profile filename to convert (default is most recent)""") args = parser.parse_args(raw_args) - if not args.path: - logs = logsdir.logsdir() - profiles = Path(logs).glob('profile.*.json') - sort = [(p.stat().st_mtime, p) for p in profiles] - sort.sort(reverse=True) - try: - args.path = sort[0][1] - except IndexError: - raise Error(f'No profile.*.json files in {logs}') - # print path to sys.stderr in case user is redirecting stdout - print(args.path, file=sys.stderr) - - convert(args.path, totals=args.totals, unused=args.unused) + convert(args.path or latest_file(logsdir(), 'profile.*.json'), + totals=args.totals, unused=args.unused) if __name__ == "__main__": try: diff --git a/scripts/perf/profile_pretty.py b/scripts/perf/profile_pretty.py index 15b6efd94d..405b14b373 100644 --- a/scripts/perf/profile_pretty.py +++ b/scripts/perf/profile_pretty.py @@ -10,17 +10,15 @@ Copyright (c) 2024, Linden Research, Inc. $/LicenseInfo$ """ -import logsdir import json -from pathlib import Path +from logsdir import Error, latest_file, logsdir import sys -class Error(Exception): - pass - 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): @@ -33,19 +31,7 @@ The file produced by the viewer is a single dense line of JSON. help="""profile filename to pretty-print (default is most recent)""") args = parser.parse_args(raw_args) - if not args.path: - logs = logsdir.logsdir() - profiles = Path(logs).glob('profile.*.json') - sort = [(p.stat().st_mtime, p) for p in profiles] - sort.sort(reverse=True) - try: - args.path = sort[0][1] - except IndexError: - raise Error(f'No profile.*.json files in {logs}') - # print path to sys.stderr in case user is redirecting stdout - print(args.path, file=sys.stderr) - - pretty(args.path) + pretty(args.path or latest_file(logsdir(), 'profile.*.json')) if __name__ == "__main__": try: -- cgit v1.2.3 From 30238772354d4a99c2867f35b7e87c4f1a748222 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 13 Sep 2024 16:50:33 -0400 Subject: Zap stray trailing space. --- scripts/perf/logsdir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'scripts') diff --git a/scripts/perf/logsdir.py b/scripts/perf/logsdir.py index c8b498cf78..5ab45a28b6 100644 --- a/scripts/perf/logsdir.py +++ b/scripts/perf/logsdir.py @@ -25,7 +25,7 @@ def logsdir(): if (system == 'Darwin'): base_dir = os.path.join(os.path.expanduser('~'), 'Library','Application Support',app) - elif (system == 'Linux'): + elif (system == 'Linux'): base_dir = os.path.join(os.path.expanduser('~'), '.' + app.lower()) elif (system == 'Windows'): -- cgit v1.2.3 From 277ee6830f68030ece6f469a86a49009a9c1450a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 12:28:05 -0400 Subject: Add a JSON frame profile stats file pretty-printer script. (cherry picked from commit ab3083819793a30911354670a7929b0d3f7c104c) --- scripts/perf/profile_pretty.py | 54 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 scripts/perf/profile_pretty.py (limited to 'scripts') diff --git a/scripts/perf/profile_pretty.py b/scripts/perf/profile_pretty.py new file mode 100644 index 0000000000..ca52fe366a --- /dev/null +++ b/scripts/perf/profile_pretty.py @@ -0,0 +1,54 @@ +#!/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 logsdir +import json +from pathlib import Path +import sys + +class Error(Exception): + pass + +def pretty(path=None): + if not path: + logs = logsdir.logsdir() + profiles = Path(logs).glob('profile.*.json') + sort = [(p.stat().st_mtime, p) for p in profiles] + sort.sort(reverse=True) + try: + path = sort[0][1] + except IndexError: + raise Error(f'No profile.*.json files in {logs}') + # print path to sys.stderr in case user is redirecting stdout + print(path, file=sys.stderr) + + with open(path) as inf: + data = json.load(inf) + 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) + +if __name__ == "__main__": + try: + sys.exit(main(*sys.argv[1:])) + except (Error, OSError, json.JSONDecodeError) as err: + sys.exit(str(err)) -- cgit v1.2.3 From ee1b0061c36c36ab019438c2a722696801de04f9 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Sep 2024 13:43:36 -0400 Subject: Add script to convert frame profile JSON file to CSV. Also slightly refactor profile_pretty.py. (cherry picked from commit d60b1f92213ace6a8ab6a4a60cb01a43f45d3955) --- scripts/perf/profile_csv.py | 72 ++++++++++++++++++++++++++++++++++++++++++ scripts/perf/profile_pretty.py | 26 +++++++-------- 2 files changed, 85 insertions(+), 13 deletions(-) create mode 100644 scripts/perf/profile_csv.py (limited to 'scripts') diff --git a/scripts/perf/profile_csv.py b/scripts/perf/profile_csv.py new file mode 100644 index 0000000000..273e3b7434 --- /dev/null +++ b/scripts/perf/profile_csv.py @@ -0,0 +1,72 @@ +#!/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 logsdir +import json +from pathlib import Path +import sys + +class Error(Exception): + pass + +def convert(path, totals=True, unused=True, file=sys.stdout): + with open(path) as inf: + data = json.load(inf) + 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) + if not args.path: + logs = logsdir.logsdir() + profiles = Path(logs).glob('profile.*.json') + sort = [(p.stat().st_mtime, p) for p in profiles] + sort.sort(reverse=True) + try: + args.path = sort[0][1] + except IndexError: + raise Error(f'No profile.*.json files in {logs}') + # print path to sys.stderr in case user is redirecting stdout + print(args.path, file=sys.stderr) + + convert(args.path, 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)) diff --git a/scripts/perf/profile_pretty.py b/scripts/perf/profile_pretty.py index ca52fe366a..15b6efd94d 100644 --- a/scripts/perf/profile_pretty.py +++ b/scripts/perf/profile_pretty.py @@ -18,19 +18,7 @@ import sys class Error(Exception): pass -def pretty(path=None): - if not path: - logs = logsdir.logsdir() - profiles = Path(logs).glob('profile.*.json') - sort = [(p.stat().st_mtime, p) for p in profiles] - sort.sort(reverse=True) - try: - path = sort[0][1] - except IndexError: - raise Error(f'No profile.*.json files in {logs}') - # print path to sys.stderr in case user is redirecting stdout - print(path, file=sys.stderr) - +def pretty(path): with open(path) as inf: data = json.load(inf) json.dump(data, sys.stdout, indent=4) @@ -45,6 +33,18 @@ The file produced by the viewer is a single dense line of JSON. help="""profile filename to pretty-print (default is most recent)""") args = parser.parse_args(raw_args) + if not args.path: + logs = logsdir.logsdir() + profiles = Path(logs).glob('profile.*.json') + sort = [(p.stat().st_mtime, p) for p in profiles] + sort.sort(reverse=True) + try: + args.path = sort[0][1] + except IndexError: + raise Error(f'No profile.*.json files in {logs}') + # print path to sys.stderr in case user is redirecting stdout + print(args.path, file=sys.stderr) + pretty(args.path) if __name__ == "__main__": -- cgit v1.2.3 From 705ec153c5ee3f6d1781647c1bbbfcd7c398c987 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 13 Sep 2024 16:32:04 -0400 Subject: Add script to compare a Frame Profile JSON stats file vs. baseline. Extract `latest_file()` logic replicated in profile_pretty.py and profile_csv.py out to logsdir.py, and use for new profile_cmp.py. (cherry picked from commit 439cfc97a81f221daaf8ba13aa5daa87e8511047) --- scripts/perf/logsdir.py | 46 ++++++++++++++++++ scripts/perf/profile_cmp.py | 104 +++++++++++++++++++++++++++++++++++++++++ scripts/perf/profile_csv.py | 24 +++------- scripts/perf/profile_pretty.py | 22 ++------- 4 files changed, 160 insertions(+), 36 deletions(-) create mode 100644 scripts/perf/logsdir.py create mode 100644 scripts/perf/profile_cmp.py (limited to 'scripts') diff --git a/scripts/perf/logsdir.py b/scripts/perf/logsdir.py new file mode 100644 index 0000000000..c8b498cf78 --- /dev/null +++ b/scripts/perf/logsdir.py @@ -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}') diff --git a/scripts/perf/profile_cmp.py b/scripts/perf/profile_cmp.py new file mode 100644 index 0000000000..9dbfa3145b --- /dev/null +++ b/scripts/perf/profile_cmp.py @@ -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)) diff --git a/scripts/perf/profile_csv.py b/scripts/perf/profile_csv.py index 273e3b7434..7a6b2b338e 100644 --- a/scripts/perf/profile_csv.py +++ b/scripts/perf/profile_csv.py @@ -10,17 +10,16 @@ Copyright (c) 2024, Linden Research, Inc. $/LicenseInfo$ """ -import logsdir import json -from pathlib import Path +from logsdir import Error, latest_file, logsdir import sys -class Error(Exception): - pass - 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: @@ -51,19 +50,8 @@ shaders list to full shaders lines. help="""profile filename to convert (default is most recent)""") args = parser.parse_args(raw_args) - if not args.path: - logs = logsdir.logsdir() - profiles = Path(logs).glob('profile.*.json') - sort = [(p.stat().st_mtime, p) for p in profiles] - sort.sort(reverse=True) - try: - args.path = sort[0][1] - except IndexError: - raise Error(f'No profile.*.json files in {logs}') - # print path to sys.stderr in case user is redirecting stdout - print(args.path, file=sys.stderr) - - convert(args.path, totals=args.totals, unused=args.unused) + convert(args.path or latest_file(logsdir(), 'profile.*.json'), + totals=args.totals, unused=args.unused) if __name__ == "__main__": try: diff --git a/scripts/perf/profile_pretty.py b/scripts/perf/profile_pretty.py index 15b6efd94d..405b14b373 100644 --- a/scripts/perf/profile_pretty.py +++ b/scripts/perf/profile_pretty.py @@ -10,17 +10,15 @@ Copyright (c) 2024, Linden Research, Inc. $/LicenseInfo$ """ -import logsdir import json -from pathlib import Path +from logsdir import Error, latest_file, logsdir import sys -class Error(Exception): - pass - 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): @@ -33,19 +31,7 @@ The file produced by the viewer is a single dense line of JSON. help="""profile filename to pretty-print (default is most recent)""") args = parser.parse_args(raw_args) - if not args.path: - logs = logsdir.logsdir() - profiles = Path(logs).glob('profile.*.json') - sort = [(p.stat().st_mtime, p) for p in profiles] - sort.sort(reverse=True) - try: - args.path = sort[0][1] - except IndexError: - raise Error(f'No profile.*.json files in {logs}') - # print path to sys.stderr in case user is redirecting stdout - print(args.path, file=sys.stderr) - - pretty(args.path) + pretty(args.path or latest_file(logsdir(), 'profile.*.json')) if __name__ == "__main__": try: -- cgit v1.2.3 From be40936881a747893d03c5c003914efb3867ccd1 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 19 Sep 2024 12:40:56 -0400 Subject: trailing spaces from other branches --- scripts/perf/logsdir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'scripts') diff --git a/scripts/perf/logsdir.py b/scripts/perf/logsdir.py index c8b498cf78..5ab45a28b6 100644 --- a/scripts/perf/logsdir.py +++ b/scripts/perf/logsdir.py @@ -25,7 +25,7 @@ def logsdir(): if (system == 'Darwin'): base_dir = os.path.join(os.path.expanduser('~'), 'Library','Application Support',app) - elif (system == 'Linux'): + elif (system == 'Linux'): base_dir = os.path.join(os.path.expanduser('~'), '.' + app.lower()) elif (system == 'Windows'): -- cgit v1.2.3 From 4fc8f2ed98b5ea80f763e1b6910b7bc3843ee7e2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 20 Sep 2024 17:12:00 -0400 Subject: Reverse the sort order for profile_cmp.py to put the biggest performance hits at the top of the list. --- scripts/perf/profile_cmp.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'scripts') diff --git a/scripts/perf/profile_cmp.py b/scripts/perf/profile_cmp.py index 9dbfa3145b..34281b8d01 100644 --- a/scripts/perf/profile_cmp.py +++ b/scripts/perf/profile_cmp.py @@ -60,8 +60,9 @@ def compare(baseline, test, epsilon=DEFAULT_EPSILON): if abs(delta) > epsilon: deltas.append((delta, shader, bthruput, tthruput)) - # descending order of performance gain - deltas.sort(reverse=True) + # ascending order of performance gain: put the most egregious performance + # hits at the top of the list + deltas.sort() 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 -- cgit v1.2.3 From 8efba1db81fc1294114cea70de0df53f4d4ab9a4 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 23 Sep 2024 10:23:37 -0400 Subject: Use Lua command-line args to make frame_profile_quit.lua generic. Now the location to which to teleport and the camera focus point can both be specified by the caller, in this case the frame_profile bash script. --- scripts/perf/frame_profile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'scripts') diff --git a/scripts/perf/frame_profile b/scripts/perf/frame_profile index 0a4e0a74ff..dcbb954536 100755 --- a/scripts/perf/frame_profile +++ b/scripts/perf/frame_profile @@ -6,5 +6,5 @@ then exe="$(ls "$exe/Contents/MacOS/Second Life "*)" fi -"$exe" --autologin --luafile frame_profile_quit.lua \ +"$exe" --autologin --luafile 'frame_profile_quit.lua 228 232 26' \ http://maps.secondlife.com/secondlife/Bug%20Island/220/224/27 -- cgit v1.2.3 From dc5c10f6b74793cab5c188e6bec24ab28b336693 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 23 Sep 2024 10:46:54 -0400 Subject: Make frame_profile bash script find its workarea's viewer build. --- scripts/perf/frame_profile | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) (limited to 'scripts') diff --git a/scripts/perf/frame_profile b/scripts/perf/frame_profile index dcbb954536..84eb1166d5 100755 --- a/scripts/perf/frame_profile +++ b/scripts/perf/frame_profile @@ -1,6 +1,39 @@ #!/usr/bin/env bash exe="$1" + +if [[ -z "$exe" ]] +then + # this script lives in scripts/perf + base="$(dirname "$0")/../.." + case $OSTYPE in + darwin*) + # Don't assume a build type (e.g. RelWithDebInfo). Collect all of + # both, and pick the most recent build. + exe="$(ls -t "$base"/build-darwin-x86_64/newview/*/"Second Life"*.app/Contents/MacOS/"Second Life"* | head -1)" + ;; + + cygwin) + exe="$(ls -t "$base"/build-*/newview/*/secondlife-bin.exe | head -1)" + ;; + + linux-gnu) + exe="$(ls -t "$base"/build-linux-*/newview/packaged/secondlife | head -1)" + ;; + + *) + stderr "Unknown platform $OSTYPE" + exit 1 + ;; + esac +fi + +if [ -z "$exe" ] +then stderr "No viewer package build found" + exit 1 +fi + +# If a Mac user specified the .app bundle itself, dig in for the executable. if [[ "$OSTYPE" == darwin* && -d "$exe" && "$exe" == *.app ]] then exe="$(ls "$exe/Contents/MacOS/Second Life "*)" -- cgit v1.2.3