summaryrefslogtreecommitdiff
path: root/scripts/perf/perfbot_run.py
blob: 006d5f8934bcbd12d5cf286a8021b61df81535d2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
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)
    working_dir = args.cwd
    if len(args.cwd) == 0:
        working_dir = os.path.dirname(os.path.abspath(args.viewer))
    print(f"Working directory is {working_dir} {args.cwd=}")
    environ = os.environ
    environ["cwd"] = 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,env=environ)

        # 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)