Skip to content

Commit 0a57f09

Browse files
authored
Update Android Scenario Startup Flow (#2219)
* Merge new way to do test with old format for calling adb, etc. * Make sure the screen is on, if not, turn it on so the class can actually be started. * Make sure the screen is unlocked. * Add automatic dismissal of permission warnings for the HelloAndroid test program. * First attempt at including time parsing and uploads. * Try using adb install. * Try running an adb command before the main XHarness install call to see if that fixes the stall issue. * Change android adb to call that works for this xharness version. * Cleanup for PR. * Remove the unnecessary exit-code. * Add explanation of the regex and the data it saves
1 parent 36ea5c3 commit 0a57f09

File tree

3 files changed

+162
-89
lines changed

3 files changed

+162
-89
lines changed

src/scenarios/shared/runner.py

Lines changed: 139 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import os
88
import glob
99
import re
10+
import time
1011

1112
from logging import getLogger
1213
from collections import namedtuple
@@ -60,7 +61,6 @@ def parseargs(self):
6061
parseonlyparser.add_argument('--device-type', choices=['android','ios'],type=str.lower,help='Device type for testing', dest='devicetype')
6162
parseonlyparser.add_argument('--package-path', help='Location of test application', dest='packagepath')
6263
parseonlyparser.add_argument('--package-name', help='Classname of application', dest='packagename')
63-
parseonlyparser.add_argument('--exit-code', help='Success exit code', dest='expectedexitcode')
6464
parseonlyparser.add_argument('--startup-iterations', help='Startups to run (1+)', type=int, default=5, dest='startupiterations')
6565
self.add_common_arguments(parseonlyparser)
6666

@@ -143,7 +143,6 @@ def parseargs(self):
143143
self.packagepath = args.packagepath
144144
self.packagename = args.packagename
145145
self.devicetype = args.devicetype
146-
self.expectedexitcode = args.expectedexitcode
147146
self.startupiterations = args.startupiterations
148147

149148
if args.scenarioname:
@@ -305,20 +304,42 @@ def run(self):
305304
startup.runtests(self.traits)
306305

307306

308-
elif self.testtype == const.DEVICESTARTUP:
307+
elif self.testtype == const.DEVICESTARTUP:
308+
# ADB Key Event corresponding numbers: https://gist.github.com/arjunv/2bbcca9a1a1c127749f8dcb6d36fb0bc
309+
# Regex used to split the response from starting the activity and saving each value
310+
#Example:
311+
# Starting: Intent { cmp=net.dot.HelloAndroid/net.dot.MainActivity }
312+
# Status: ok
313+
# LaunchState: COLD
314+
# Activity: net.dot.HelloAndroid/net.dot.MainActivity
315+
# TotalTime: 241
316+
# WaitTime: 242
317+
# Complete
318+
# Saves: [Intent { cmp=net.dot.HelloAndroid/net.dot.MainActivity }, ok, COLD, net.dot.HelloAndroid/net.dot.MainActivity, 241, 242]
319+
# Split results (start at 0) (List is Starting (Intent activity), Status (ok...), LaunchState ([HOT, COLD, WARM]), Activity (started activity name), TotalTime(toFrameOne), WaitTime(toFullLoad))
320+
runSplitRegex = ":\s(.+)"
321+
screenWasOff = False
309322
getLogger().info("Clearing potential previous run nettraces")
310-
for file in glob.glob(os.path.join(const.TRACEDIR, 'PerfTest', 'trace*.nettrace')):
323+
for file in glob.glob(os.path.join(const.TRACEDIR, 'PerfTest', 'runoutput.trace')):
311324
if exists(file):
312325
getLogger().info("Removed: " + os.path.join(const.TRACEDIR, file))
313326
os.remove(file)
314-
327+
315328
cmdline = xharnesscommand() + [self.devicetype, 'state', '--adb']
316329
adb = RunCommand(cmdline, verbose=True)
317330
adb.run()
318-
cmdline = [adb.stdout.strip(), 'shell', 'mkdir', '-p', '/sdcard/PerfTest']
331+
332+
# Do not remove, XHarness install seems to fail without an adb command called before the xharness command
333+
getLogger().info("Preparing ADB")
334+
cmdline = [
335+
adb.stdout.strip(),
336+
'shell',
337+
'wm',
338+
'size'
339+
]
319340
RunCommand(cmdline, verbose=True).run()
320341

321-
cmdline = xharnesscommand() + [
342+
installCmd = xharnesscommand() + [
322343
self.devicetype,
323344
'install',
324345
'--app', self.packagepath,
@@ -328,48 +349,129 @@ def run(self):
328349
const.TRACEDIR,
329350
'-v'
330351
]
352+
RunCommand(installCmd, verbose=True).run()
331353

332-
RunCommand(cmdline, verbose=True).run()
333-
354+
getLogger().info("Completed install, running shell.")
355+
cmdline = [
356+
adb.stdout.strip(),
357+
'shell',
358+
f'cmd package resolve-activity --brief {self.packagename} | tail -n 1'
359+
]
360+
getActivity = RunCommand(cmdline, verbose=True)
361+
getActivity.run()
362+
getLogger().info(f"Target Activity {getActivity.stdout}")
363+
364+
# More setup stuff
365+
checkScreenOnCmd = [
366+
adb.stdout.strip(),
367+
'shell',
368+
f'dumpsys input_method | grep mInteractive'
369+
]
370+
checkScreenOn = RunCommand(checkScreenOnCmd, verbose=True)
371+
checkScreenOn.run()
372+
373+
keyInputCmd = [
374+
adb.stdout.strip(),
375+
'shell',
376+
'input',
377+
'keyevent'
378+
]
334379

380+
if("mInteractive=false" in checkScreenOn.stdout):
381+
# Turn on the screen to make interactive and see if it worked
382+
getLogger().info("Screen was off, turning on.")
383+
screenWasOff = True
384+
RunCommand(keyInputCmd + ['26'], verbose=True).run() # Press the power key
385+
RunCommand(keyInputCmd + ['82'], verbose=True).run() # Unlock the screen with menu key (only works if it is not a password lock)
386+
387+
checkScreenOn = RunCommand(checkScreenOnCmd, verbose=True)
388+
checkScreenOn.run()
389+
if("mInteractive=false" in checkScreenOn.stdout):
390+
getLogger().exception("Failed to make screen interactive.")
391+
392+
# Actual testing some run stuff
393+
getLogger().info("Test run to check if permissions are needed")
394+
activityname = getActivity.stdout
395+
396+
startAppCmd = [
397+
adb.stdout.strip(),
398+
'shell',
399+
'am',
400+
'start-activity',
401+
'-W',
402+
'-n',
403+
activityname
404+
]
405+
testRun = RunCommand(startAppCmd, verbose=True)
406+
testRun.run()
407+
testRunStats = re.findall(runSplitRegex, testRun.stdout) # Split results saving value (List: Starting, Status, LaunchState, Activity, TotalTime, WaitTime)
408+
getLogger().info(f"Test run activity: {testRunStats[3]}")
409+
410+
stopAppCmd = [
411+
adb.stdout.strip(),
412+
'shell',
413+
'am',
414+
'force-stop',
415+
self.packagename
416+
]
417+
RunCommand(stopAppCmd, verbose=True).run()
418+
419+
if "com.google.android.permissioncontroller" in testRunStats[3]:
420+
# On perm screen, use the buttons to close it. it will stay away until the app is reinstalled
421+
RunCommand(keyInputCmd + ['22'], verbose=True).run() # Select next button
422+
time.sleep(1)
423+
RunCommand(keyInputCmd + ['22'], verbose=True).run() # Select next button
424+
time.sleep(1)
425+
RunCommand(keyInputCmd + ['66'], verbose=True).run() # Press enter to close main perm screen
426+
time.sleep(1)
427+
RunCommand(keyInputCmd + ['22'], verbose=True).run() # Select next button
428+
time.sleep(1)
429+
RunCommand(keyInputCmd + ['66'], verbose=True).run() # Press enter to close out of second screen
430+
time.sleep(1)
431+
432+
# Check to make sure it worked
433+
testRun = RunCommand(startAppCmd, verbose=True)
434+
testRun.run()
435+
testRunStats = re.findall(runSplitRegex, testRun.stdout)
436+
getLogger().info(f"Test run activity: {testRunStats[3]}")
437+
RunCommand(stopAppCmd, verbose=True).run()
438+
439+
if "com.google.android.permissioncontroller" in testRunStats[3]:
440+
getLogger().exception("Failed to get past permission screen, run locally to see if enough next button presses were used.")
441+
442+
allResults = []
335443
for i in range(self.startupiterations):
336-
cmdline = xharnesscommand() + [
337-
'android',
338-
'run',
339-
'-o',
340-
const.TRACEDIR,
341-
'--package-name',
342-
self.packagename,
343-
'-v',
344-
'--arg=env:COMPlus_EnableEventPipe=1',
345-
'--arg=env:COMPlus_EventPipeOutputStreaming=1',
346-
'--arg=env:COMPlus_EventPipeOutputPath=/sdcard/PerfTest/trace%s.nettrace' % (i+1),
347-
'--arg=env:COMPlus_EventPipeCircularMB=10',
348-
'--arg=env:COMPlus_EventPipeConfig=Microsoft-Windows-DotNETRuntime:10:5',
349-
'--expected-exit-code',
350-
self.expectedexitcode,
351-
'--dev-out',
352-
'/sdcard/PerfTest/'
353-
]
354-
355-
RunCommand(cmdline, verbose=True).run()
356-
357-
cmdline = xharnesscommand() + [
444+
startStats = RunCommand(startAppCmd, verbose=True)
445+
startStats.run()
446+
RunCommand(stopAppCmd, verbose=True).run()
447+
allResults.append(startStats.stdout) # Save results (List is Intent, Status, LaunchState Activity, TotalTime, WaitTime)
448+
time.sleep(3) # Delay in seconds for ensuring a cold start
449+
450+
getLogger().info("Stopping App for uninstall")
451+
RunCommand(stopAppCmd, verbose=True).run()
452+
453+
getLogger().info("Uninstalling app")
454+
uninstallAppCmd = xharnesscommand() + [
358455
'android',
359456
'uninstall',
360457
'--package-name',
361458
self.packagename
362459
]
460+
RunCommand(uninstallAppCmd, verbose=True).run()
363461

364-
RunCommand(cmdline, verbose=True).run()
365-
366-
cmdline = [adb.stdout.strip(), 'shell', 'rm', '-r', '/sdcard/PerfTest']
367-
RunCommand(cmdline, verbose=True).run()
368-
462+
if screenWasOff:
463+
RunCommand(keyInputCmd + ['26'], verbose=True).run() # Turn the screen back off
369464

465+
# Create traces to store the data so we can keep the current general parse trace flow
466+
getLogger().info(f"Logs: \n{allResults}")
467+
os.makedirs(f"{const.TRACEDIR}/PerfTest", exist_ok=True)
468+
traceFile = open(f"{const.TRACEDIR}/PerfTest/runoutput.trace", "w")
469+
for result in allResults:
470+
traceFile.write(result)
471+
traceFile.close()
370472

371473
startup = StartupWrapper()
372-
self.traits.add_traits(overwrite=True, apptorun="app", startupmetric=const.STARTUP_DEVICETIMETOMAIN, tracefolder='PerfTest/', tracename='trace*.nettrace', scenarioname='Device Startup - Android %s' % (self.packagename))
474+
self.traits.add_traits(overwrite=True, apptorun="app", startupmetric=const.STARTUP_DEVICETIMETOMAIN, tracefolder='PerfTest/', tracename='runoutput.trace', scenarioname='Device Startup - Android %s' % (self.packagename))
373475
startup.parsetraces(self.traits)
374476

375477
elif self.testtype == const.SOD:

src/tools/ScenarioMeasurement/Startup/DeviceTimeToMain.cs

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,34 +26,23 @@ public void EnableUserProviders(ITraceSession user)
2626
}
2727

2828
public IEnumerable<Counter> Parse(string mergeTraceFile, string processName, IList<int> pids, string commandLine)
29-
{
30-
throw new NotImplementedException();
31-
}
32-
33-
public IEnumerable<Counter> Parse(string mergeTraceDirectory, string mergeTraceFilter, string processName, IList<int> pids, string commandLine)
3429
{
3530
var times = new List<double>();
36-
var files = new HashSet<string>();
37-
Console.WriteLine($"Finding files from {mergeTraceDirectory} with filter: {mergeTraceFilter}");
38-
foreach (var file in Directory.GetFiles(mergeTraceDirectory, mergeTraceFilter))
39-
{
40-
Console.WriteLine($"Found {file}");
41-
files.Add(file);
42-
}
31+
Regex totalTimePattern = new Regex(@"TotalTime:\s(?<totalTime>.+)");
4332

44-
foreach (var trace in files)
33+
if (File.Exists(mergeTraceFile))
4534
{
46-
Console.WriteLine($"Parsing {trace}");
47-
using (var source = new EventPipeEventSource(trace))
35+
using(StreamReader sr = new StreamReader(mergeTraceFile))
4836
{
49-
source.Clr.MethodLoadVerbose += evt =>
37+
string line = sr.ReadToEnd();
38+
MatchCollection finds = totalTimePattern.Matches(line);
39+
Console.WriteLine($"Found Startup Times: {finds.Count}");
40+
foreach (Match match in finds)
5041
{
51-
if (evt.MethodName == "Main")
52-
{
53-
times.Add(evt.TimeStampRelativeMSec);
54-
}
55-
};
56-
source.Process();
42+
GroupCollection groups = match.Groups;
43+
Console.WriteLine($"Found Value (ms): {groups["totalTime"].Value}");
44+
times.Add(Double.Parse(groups["totalTime"].Value));
45+
}
5746
}
5847
}
5948

src/tools/ScenarioMeasurement/Startup/Startup.cs

Lines changed: 12 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -305,38 +305,20 @@ static void checkArg(string arg, string name)
305305
// Parse trace files
306306
if (!failed && !string.IsNullOrEmpty(traceFilePath))
307307
{
308-
if (parseOnly)
309-
{
310-
logger.Log($"Parsing glob: {traceName}");
311-
312-
if (guiApp)
313-
{
314-
appExe = Path.Join(workingDir, appExe);
315-
}
316-
string commandLine = $"\"{appExe}\"";
317-
if (!String.IsNullOrEmpty(appArgs))
318-
{
319-
commandLine = commandLine + " " + appArgs;
320-
}
321-
var counters = parser.Parse(traceDirectory, traceName, Path.GetFileNameWithoutExtension(appExe), pids, commandLine);
322-
323-
CreateTestReport(scenarioName, counters, reportJsonPath, logger);
324-
} else {
325-
logger.Log($"Parsing {traceFilePath}");
326-
327-
if (guiApp)
328-
{
329-
appExe = Path.Join(workingDir, appExe);
330-
}
331-
string commandLine = $"\"{appExe}\"";
332-
if (!String.IsNullOrEmpty(appArgs))
333-
{
334-
commandLine = commandLine + " " + appArgs;
335-
}
336-
var counters = parser.Parse(traceFilePath, Path.GetFileNameWithoutExtension(appExe), pids, commandLine);
308+
logger.Log($"Parsing {traceFilePath}");
337309

338-
CreateTestReport(scenarioName, counters, reportJsonPath, logger);
310+
if (guiApp)
311+
{
312+
appExe = Path.Join(workingDir, appExe);
313+
}
314+
string commandLine = $"\"{appExe}\"";
315+
if (!String.IsNullOrEmpty(appArgs))
316+
{
317+
commandLine = commandLine + " " + appArgs;
339318
}
319+
var counters = parser.Parse(traceFilePath, Path.GetFileNameWithoutExtension(appExe), pids, commandLine);
320+
321+
CreateTestReport(scenarioName, counters, reportJsonPath, logger);
340322
}
341323

342324
// Skip unimplemented Linux profiling

0 commit comments

Comments
 (0)