[tests] Measure .apk sizes and selected content (#791)

- extracted parts of `ProcessLogcatTiming` into new base class called
   `ProcessPlotInput`

 - we measure and process the apk sizes now to observe the changes and
   possible regressions on Jenkins CI

 - it is now possible to add results to cvs plot files, so we can
   merge values from multiple test runs. we do that with apk size
   values and we will do it with startup times in future
This commit is contained in:
Radek Doulik 2017-08-29 15:27:29 +02:00 коммит произвёл Jonathan Pryor
Родитель 9ead8486d7
Коммит fff81b0315
8 изменённых файлов: 164 добавлений и 66 удалений

Просмотреть файл

@ -151,6 +151,6 @@
<_LogcatFilenameEnd>-logcat-$(Configuration)$(_AotName).txt</_LogcatFilenameEnd>
</PropertyGroup>
<Exec Command="&quot;$(AdbToolPath)\$(AdbToolExe)&quot; $(_AdbTarget) $(AdbOptions) logcat -v threadtime -d > %(UnitTestApk.Package)$(_LogcatFilenameEnd)" />
<ProcessLogcatTiming LogcatFilename="%(UnitTestApk.Package)$(_LogcatFilenameEnd)" ApplicationPackageName="%(UnitTestApk.Package)" ResultsFilename="%(UnitTestApk.ResultsPath)" DefinitionsFilename="$(_DefinitionsFilename)" />
<ProcessLogcatTiming InputFilename="%(UnitTestApk.Package)$(_LogcatFilenameEnd)" ApplicationPackageName="%(UnitTestApk.Package)" ResultsFilename="%(UnitTestApk.ResultsPath)" DefinitionsFilename="$(_DefinitionsFilename)" />
</Target>
</Project>

Просмотреть файл

@ -8,53 +8,12 @@ using Microsoft.Build.Utilities;
namespace Xamarin.Android.BuildTools.PrepTasks
{
public class ProcessLogcatTiming : Task
public class ProcessLogcatTiming : ProcessPlotInput
{
[Required]
public string LogcatFilename { get; set; }
[Required]
public string ApplicationPackageName { get; set; }
[Required]
public string DefinitionsFilename { get; set; }
public string ResultsFilename { get; set; }
Dictionary<string, Regex> definedRegexs = new Dictionary<string, Regex> ();
Dictionary<string, string> results = new Dictionary<string, string> ();
void LoadDefinitions ()
{
using (var reader = new StreamReader (DefinitionsFilename)) {
string line;
while ((line = reader.ReadLine ()) != null) {
if (line.StartsWith ("#", StringComparison.Ordinal))
continue;
int index = line.IndexOf ('=');
if (index < 1 || index == line.Length)
continue;
var label = line.Substring (0, index);
var pattern = line.Substring (index + 1);
Regex regex;
try {
regex = new Regex (pattern);
} catch (Exception e) {
Log.LogWarning ($"unable to create regex for label: {label} from pattern: {pattern}\n{e}");
continue;
}
if (definedRegexs.ContainsKey (label))
Log.LogWarning ($"label '{label}' is defined multiple times. the last definition will be used");
definedRegexs [label] = regex;
}
}
}
public override bool Execute ()
{
LoadDefinitions ();
using (var reader = new StreamReader (LogcatFilename)) {
using (var reader = new StreamReader (InputFilename)) {
string line;
int pid = -1;
var procStartRegex = new Regex ($@"^(?<timestamp>\d+-\d+\s+[\d:\.]+)\s+.*ActivityManager: Start proc.*for added application {ApplicationPackageName}: pid=(?<pid>\d+)");
@ -102,13 +61,7 @@ namespace Xamarin.Android.BuildTools.PrepTasks
Log.LogMessage (MessageImportance.Normal, " -- Performance summary --");
Log.LogMessage (MessageImportance.Normal, $"Last timing message: {(last - start).TotalMilliseconds}ms");
if (ResultsFilename != null) {
using (var resultsFile = new StreamWriter (Path.Combine (Path.GetDirectoryName (ResultsFilename), $"{Path.GetFileNameWithoutExtension (ResultsFilename)}-times.csv"))) {
WriteValues (resultsFile, results.Keys);
WriteValues (resultsFile, results.Values);
resultsFile.Close ();
}
}
WriteResults ("times");
} else
Log.LogWarning ("Wasn't able to collect the performance data");
@ -118,18 +71,6 @@ namespace Xamarin.Android.BuildTools.PrepTasks
return true;
}
void WriteValues (StreamWriter writer, ICollection<string> values)
{
bool first = true;
foreach (var key in values) {
if (!first)
writer.Write (',');
writer.Write (key);
first = false;
}
writer.WriteLine ();
}
static Regex timeRegex = new Regex (@"(?<month>\d+)-(?<day>\d+)\s+(?<hour>\d+):(?<minute>\d+):(?<second>\d+)\.(?<millisecond>\d+)");
DateTime ParseTime (string s)
{

Просмотреть файл

@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Xamarin.Android.BuildTools.PrepTasks
{
public class ProcessPlotInput : Task
{
[Required]
public string InputFilename { get; set; }
[Required]
public string ApplicationPackageName { get; set; }
[Required]
public string DefinitionsFilename { get; set; }
public string ResultsFilename { get; set; }
public bool AddResults { get; set; }
protected Dictionary<string, Regex> definedRegexs = new Dictionary<string, Regex> ();
protected Dictionary<string, string> results = new Dictionary<string, string> ();
protected void LoadDefinitions ()
{
using (var reader = new StreamReader (DefinitionsFilename)) {
string line;
while ((line = reader.ReadLine ()) != null) {
if (line.StartsWith ("#", StringComparison.Ordinal))
continue;
int index = line.IndexOf ('=');
if (index < 1 || index == line.Length)
continue;
var label = line.Substring (0, index);
var pattern = line.Substring (index + 1);
Regex regex;
try {
regex = new Regex (pattern);
} catch (Exception e) {
Log.LogWarning ($"unable to create regex for label: {label} from pattern: {pattern}\n{e}");
continue;
}
if (definedRegexs.ContainsKey (label))
Log.LogWarning ($"label '{label}' is defined multiple times. the last definition will be used");
definedRegexs [label] = regex;
}
}
}
public override bool Execute ()
{
LoadDefinitions ();
using (var reader = new StreamReader (InputFilename)) {
string line;
while ((line = reader.ReadLine ()) != null) {
foreach (var regex in definedRegexs) {
var definedMatch = regex.Value.Match (line);
if (!definedMatch.Success)
continue;
string logMessage = definedMatch.Value;
var v = definedMatch.Groups ["value"];
if (!v.Success)
continue;
results [regex.Key] = v.Value;
var m = definedMatch.Groups ["message"];
if (m.Success)
logMessage = m.Value;
Log.LogMessage (MessageImportance.Low, $"Message: {logMessage} Value: {v.Value}");
}
}
WriteResults ("values");
reader.Close ();
}
return true;
}
protected void WriteResults (string filenameEnd)
{
if (ResultsFilename != null) {
string filename = Path.Combine (Path.GetDirectoryName (ResultsFilename), $"{Path.GetFileNameWithoutExtension (ResultsFilename)}-{filenameEnd}.csv");
string line1 = null, line2 = null;
if (AddResults && File.Exists (filename))
using (var reader = new StreamReader (filename)) {
try {
line1 = reader.ReadLine ();
line2 = reader.ReadLine ();
} catch (Exception e) {
Log.LogWarning ($"unable to read previous results from {filename}\n{e}");
line1 = line2 = null;
}
}
using (var resultsFile = new StreamWriter (filename)) {
WriteValues (resultsFile, results.Keys, line1);
WriteValues (resultsFile, results.Values, line2);
resultsFile.Close ();
}
}
}
void WriteValues (StreamWriter writer, ICollection<string> values, string line)
{
bool first;
if (string.IsNullOrEmpty (line))
first = true;
else {
writer.Write (line);
first = false;
}
foreach (var key in values) {
if (!first)
writer.Write (',');
writer.Write (key);
first = false;
}
writer.WriteLine ();
}
}
}

Просмотреть файл

@ -53,6 +53,7 @@
<Compile Include="Xamarin.Android.BuildTools.PrepTasks\PrepareInstall.cs" />
<Compile Include="Xamarin.Android.BuildTools.PrepTasks\AcceptAndroidSdkLicenses.cs" />
<Compile Include="Xamarin.Android.BuildTools.PrepTasks\Sleep.cs" />
<Compile Include="Xamarin.Android.BuildTools.PrepTasks\ProcessPlotInput.cs" />
<Compile Include="Xamarin.Android.BuildTools.PrepTasks\ProcessLogcatTiming.cs" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -1,10 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask AssemblyFile="$(MSBuildThisFileDirectory)..\..\..\bin\Build$(Configuration)\xa-prep-tasks.dll" TaskName="Xamarin.Android.BuildTools.PrepTasks.ProcessPlotInput" />
<PropertyGroup>
<_MonoAndroidTestResultsPath>$(MSBuildThisFileDirectory)..\..\..\TestResult-Mono.Android_Tests-$(Configuration)$(_AotName).xml</_MonoAndroidTestResultsPath>
<_MonoAndroidTestPackage>Mono.Android_Tests</_MonoAndroidTestPackage>
<_MonoAndroidTestApkFile>$(OutputPath)Mono.Android_Tests-Signed.apk</_MonoAndroidTestApkFile>
<_MonoAndroidTestApkSizesInput>apk-sizes-$(_MonoAndroidTestPackage)-$(Configuration)$(_AotName).txt</_MonoAndroidTestApkSizesInput>
</PropertyGroup>
<ItemGroup>
<UnitTestApk Include="$(OutputPath)Mono.Android_Tests-Signed.apk">
<Package>Mono.Android_Tests</Package>
<UnitTestApk Include="$(_MonoAndroidTestApkFile)">
<Package>$(_MonoAndroidTestPackage)</Package>
<InstrumentationType>xamarin.android.runtimetests.TestInstrumentation</InstrumentationType>
<ResultsPath>$(MSBuildThisFileDirectory)..\..\..\TestResult-Mono.Android_Tests-$(Configuration)$(_AotName).xml</ResultsPath>
<ResultsPath>$(_MonoAndroidTestResultsPath)</ResultsPath>
</UnitTestApk>
</ItemGroup>
<Target Name="_RecordApkSizes" AfterTargets="DeployUnitTestApks">
<Delete Files="$(MSBuildThisFileDirectory)..\..\..\TestResult-Mono.Android_Tests-values.csv" Condition=" '$(Configuration)' == 'Debug' " />
<Exec Command="stat -f &quot;stat: %z %N&quot; &quot;$(_MonoAndroidTestApkFile)&quot; > $(_MonoAndroidTestApkSizesInput)" />
<Exec Command="unzip -l &quot;$(_MonoAndroidTestApkFile)&quot; >> $(_MonoAndroidTestApkSizesInput)" />
<ProcessPlotInput InputFilename="$(_MonoAndroidTestApkSizesInput)" ApplicationPackageName="$(_MonoAndroidTestPackage)" ResultsFilename="$(MSBuildThisFileDirectory)..\..\..\TestResult-Mono.Android_Tests.xml" DefinitionsFilename="$(MSBuildThisFileDirectory)apk-sizes-definitions-$(Configuration)$(_AotName).txt" AddResults="true" />
</Target>
</Project>

Просмотреть файл

@ -0,0 +1,4 @@
apk-Debug=^stat: (?<value>\d+)\s+(?<message>.*)$
Mono.Android.dll-Debug=^\s*(?<value>\d+)\s+.*(?<message>Mono.Android.dll)$
mscorlib.dll-Debug=^\s*(?<value>\d+)\s+.*(?<message>mscorlib.dll)$
monosgen-armeabi-v7a-Debug=^\s*(?<value>\d+)\s+.*(?<message>armeabi-v7a/libmonosgen-2.0.so)$

Просмотреть файл

@ -0,0 +1,6 @@
apk-Release-Aot=^stat: (?<value>\d+)\s+(?<message>.*)$
Mono.Android.dll-Release-Aot=^\s*(?<value>\d+)\s+.*(?<message>Mono.Android.dll)$
Mono.Android.dll.so-Release-Aot=^\s*(?<value>\d+)\s+.*(?<message>Mono.Android.dll.so)$
mscorlib.dll-Release-Aot=^\s*(?<value>\d+)\s+.*(?<message>mscorlib.dll)$
mscorlib.dll.so-Release-Aot=^\s*(?<value>\d+)\s+.*(?<message>mscorlib.dll.so)$
monosgen-armeabi-v7a-Release-Aot=^\s*(?<value>\d+)\s+.*(?<message>armeabi-v7a/libmonosgen-2.0.so)$

Просмотреть файл

@ -0,0 +1,4 @@
apk-Release=^stat: (?<value>\d+)\s+(?<message>.*)$
Mono.Android.dll-Release=^\s*(?<value>\d+)\s+.*(?<message>Mono.Android.dll)$
mscorlib.dll-Release=^\s*(?<value>\d+)\s+.*(?<message>mscorlib.dll)$
monosgen-armeabi-v7a-Release=^\s*(?<value>\d+)\s+.*(?<message>armeabi-v7a/libmonosgen-2.0.so)$