From: j-h.choi Date: Wed, 10 Feb 2021 02:06:54 +0000 (+0900) Subject: Add line number to extractor tool X-Git-Tag: submit/tizen/20210225.050444^0 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=db54c824e71c4c03a9f575659f2135e388089083;p=platform%2Fcore%2Fdotnet%2Flauncher.git Add line number to extractor tool Change-Id: I5da17e91d8d5bc59a5e59e5385ae413ef93ac178 --- diff --git a/tools/Extractor/README.md b/tools/Extractor/README.md new file mode 100644 index 0000000..360656d --- /dev/null +++ b/tools/Extractor/README.md @@ -0,0 +1,74 @@ +# Line Number Extractor tool + +### Usage + +``` +dotnet-extractor line-number [Log] [Options] +``` + +#### Log +* Filepath + Path to the exception log file (Filename extension: xxxxx.log) + +#### Options +* -h, --help + Show this help message + +* -a, --assembly [Path1:Path2:...] + Multiple paths with assembly directories separated by colon(':') + +* -p, --pdb [Pdb path] + Path to the pdb directory (Can be omitted if it is the same as the assembly directory path) + +* -o, --out [Output path] + Path to the output file (Default: Output to console. If omitted, the xxxxx.out file is created in the same location as the log file) + +#### Example +* If both assembly and pdb are in the current directory +``` + # dotnet extractor line-number /tmp/Exception1.log +``` + +* If both assembly and pdb are in the same directory specified +``` + # dotnet extractor /tmp/Exception2.log --assembly /opt/usr/globalapps/org.tizen.example.TestApp/:/usr/share/dotnet.tizen/ +``` + +* If assembly and pdb are separated in each directory +``` + # dotnet extractor /tmp/Exception3.log --assembly /usr/share/dotnet.tizen/framework/ --pdb /tmp/pdbs/ +``` + +#### Others +* Log format +``` + I/DOTNET_LAUNCHER(27298): System.NullReferenceException: Object reference not set to an instance of an object. + I/DOTNET_LAUNCHER(27298): at TestApp.Program.TestMethod() in TestApp.dll: method_token(0x6000001), il_offset(0x5) + I/DOTNET_LAUNCHER(27298): at TestApp.Program.OnCreate() in TestApp.dll: method_token(0x6000002), il_offset(0x1) + ------- Format ------- + I/DOTNET_LAUNCHER(00000): at {typeName}.{methodName}() in {moduleName}: method_token{methodToken}, il_offset{ilOffset} +``` + +---- + +#### Sample +``` +sh-3.2# cat /tmp/Exception.log +I/DOTNET_LAUNCHER(27298): System.NullReferenceException: Object reference not set to an instance of an object. +I/DOTNET_LAUNCHER(27298): at LineNumberExtract.Program.ExceptionMethod2() in LineNumberExtract.Tizen.dll: method_token(0x6000001), il_offset(0x5) +I/DOTNET_LAUNCHER(27298): at LineNumberExtract.Program.ExceptionMethod1() in LineNumberExtract.Tizen.dll: method_token(0x6000002), il_offset(0x1) +I/DOTNET_LAUNCHER(27298): at LineNumberExtract.Program.OnCreate() in LineNumberExtract.Tizen.dll: method_token(0x6000004), il_offset(0x15) +sh-3.2# +sh-3.2# dotnet eExtractor /tmp/Exception.log --assembly /opt/usr/globalapps/org.tizen.example.LineNumberExtract.Tizen/ + +##### Line Number Extractor Tool (v1.0) ##### + +Extraction result: + at LineNumberExtract.Program.ExceptionMethod2(i) in U:\PTX\LineNumberExtract\LineNumberExtract\LineNumberExtract.Tizen\LineNumberExtract.Tizen.cs:line 14 + at LineNumberExtract.Program.ExceptionMethod1(t) in U:\PTX\LineNumberExtract\LineNumberExtract\LineNumberExtract.Tizen\LineNumberExtract.Tizen.cs:line 19 + at LineNumberExtract.Program.OnCreate() in U:\PTX\LineNumberExtract\LineNumberExtract\LineNumberExtract.Tizen\LineNumberExtract.Tizen.cs:line 58 + +Output: /tmp/Exception.out + +sh-3.2# +``` diff --git a/tools/Extractor/build.sh b/tools/Extractor/build.sh new file mode 100755 index 0000000..52f490a --- /dev/null +++ b/tools/Extractor/build.sh @@ -0,0 +1,78 @@ +#! /bin/bash + +SCRIPT_DIR=$(dirname $(readlink -f $0)) + +CONFIGURATION=Release +SLN_NAME=dotnet-extractor/dotnet-extractor +TARGET_FRAMEWORK=netcoreapp3.1 +SLN_FILE=$SCRIPT_DIR/$SLN_NAME.sln +OUTDIR=$SCRIPT_DIR/$SLN_NAME/bin/$CONFIGURATION/$TARGET_FRAMEWORK +TARGET_ASSEMBLY_DIR=/home/owner/share/.dotnet/tools + +usage() { + echo "Usage: $0 [command]" + echo "Commands:" + echo " -b, --build Build a ExtractLine module" + echo " -i, --install Install assemblies to the target device" + echo " -c, --clean Clean all artifacts" +} + +remove_intermediates() { + find $1 -type d \( -name bin -o -name obj \) -print0 | xargs -0 -I {} rm -fr "{}" +} + +clean() { + remove_intermediates $SCRIPT_DIR/$SLN_NAME + rm -f msbuild.log +} + +build() { + clean + dotnet build -c $CONFIGURATION $SLN_FILE +} + +install() { + DEVICE_ID=$1 + + RUNTIME_ASSEMBLIES="$OUTDIR/*.dll" + + device_cnt=$(sdb devices | grep -v "List" | wc -l) + + if [ $device_cnt -eq 0 ]; then + echo "No connected devices" + exit 1 + fi + + if [ $device_cnt -gt 1 ] && [ -z "$DEVICE_ID" ]; then + echo "Multiple devices are connected. Specify the device. (ex: ./build.sh --install [device-id])" + sdb devices + exit 1 + fi + + SDB_OPTIONS="" + if [ -n "$DEVICE_ID" ]; then + SDB_OPTIONS="-s $DEVICE_ID" + fi + + sdb $SDB_OPTIONS root on + sdb $SDB_OPTIONS shell mount -o remount,rw / + sdb $SDB_OPTIONS push $RUNTIME_ASSEMBLIES $TARGET_ASSEMBLY_DIR + + nifile_cnt=$(sdb $SDB_OPTIONS shell find $TARGET_ASSEMBLY_DIR -name 'Microsoft.DiaSymReader*.ni.dll' | wc -l) + if [ $nifile_cnt -gt 0 ]; then + sdb $SDB_OPTIONS shell "rm -f $TARGET_ASSEMBLY_DIR/Microsoft.DiaSymReader*.ni.dll" + fi + + #sdb $SDB_OPTIONS shell dotnettool --ni-dll $TARGET_ASSEMBLY_DIR/Microsoft.DiaSymReader*.dll + sdb $SDB_OPTIONS shell chsmack -a '_' $TARGET_ASSEMBLY_DIR/*.dll + sdb $SDB_OPTIONS shell chown owner:users $TARGET_ASSEMBLY_DIR/*.dll + sdb $SDB_OPTIONS shell chmod 644 $TARGET_ASSEMBLY_DIR/*.dll +} + +cmd=$1; [ $# -gt 0 ] && shift; +case "$cmd" in + build|--build|-b) build ;; + install|--install|-i) install $@ ;; + clean|--clean|-c) clean ;; + *) usage ;; +esac diff --git a/tools/Extractor/dotnet-extractor/dotnet-extractor.sln b/tools/Extractor/dotnet-extractor/dotnet-extractor.sln new file mode 100644 index 0000000..b54341f --- /dev/null +++ b/tools/Extractor/dotnet-extractor/dotnet-extractor.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29613.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotnet-extractor", "dotnet-extractor\dotnet-extractor.csproj", "{452A02D6-B803-4F73-BF0F-62A53D6FF367}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {452A02D6-B803-4F73-BF0F-62A53D6FF367}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {452A02D6-B803-4F73-BF0F-62A53D6FF367}.Debug|Any CPU.Build.0 = Debug|Any CPU + {452A02D6-B803-4F73-BF0F-62A53D6FF367}.Release|Any CPU.ActiveCfg = Release|Any CPU + {452A02D6-B803-4F73-BF0F-62A53D6FF367}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B0A7D6D5-EC91-4FB0-8A1B-DB9B39D1073B} + EndGlobalSection +EndGlobal diff --git a/tools/Extractor/dotnet-extractor/dotnet-extractor/CodeBlock.cs b/tools/Extractor/dotnet-extractor/dotnet-extractor/CodeBlock.cs new file mode 100644 index 0000000..d4e7db5 --- /dev/null +++ b/tools/Extractor/dotnet-extractor/dotnet-extractor/CodeBlock.cs @@ -0,0 +1,19 @@ +using System; +using System.IO; +using System.Text; +using System.Xml; + +namespace Tizen.Runtime.Tools +{ + public class CodeBlock + { + public CodeBlock() + { + } + + public void CodeBlocks() + { + Console.WriteLine("Not implemented yet"); + } + } +} \ No newline at end of file diff --git a/tools/Extractor/dotnet-extractor/dotnet-extractor/Extractor.cs b/tools/Extractor/dotnet-extractor/dotnet-extractor/Extractor.cs new file mode 100644 index 0000000..b13e0ce --- /dev/null +++ b/tools/Extractor/dotnet-extractor/dotnet-extractor/Extractor.cs @@ -0,0 +1,68 @@ +using System; +using System.IO; + +namespace Tizen.Runtime.Tools +{ + class Extractor + { + private static readonly string version = "1.0"; + + public static void Main(string[] args) + { + Console.WriteLine($"\n### Dotnet Extractor Tool (v{version}) ###\n"); + + try + { + string command = null; + if (0 < args.Length) + { + switch (args[0]) + { + case "-h": + case "--help": + UsageCommand(); + Environment.Exit(0); + break; + case "line-number": + command = "line"; + break; + case "code-block": + command = "code"; + break; + default: + UsageCommand(); + Console.WriteLine($"Unknown option [{args[0]}]\n"); + Environment.Exit(0); + break; + } + } + if (command == null) + { + UsageCommand(); + throw new InvalidDataException("Required command was not provided\n"); + } + else if (command == "line") + { + new LineNumber().LineNumbers(args); + } + else if (command == "code") + { + //new CodeBlock().CodeBlocks(); + } + } + catch (Exception e) + { + Console.Error.WriteLine(e.Message); + } + } + + public static void UsageCommand() + { + string UsageMsg = "Usage: dotnet-extractor [Command]\n\n" + + "Commands:\n" + + " line-number Get line number\n"; + //+ " code-block Get code block\n"; + Console.WriteLine(UsageMsg); + } + } +} diff --git a/tools/Extractor/dotnet-extractor/dotnet-extractor/LineNumber.cs b/tools/Extractor/dotnet-extractor/dotnet-extractor/LineNumber.cs new file mode 100644 index 0000000..792792a --- /dev/null +++ b/tools/Extractor/dotnet-extractor/dotnet-extractor/LineNumber.cs @@ -0,0 +1,422 @@ +using Microsoft.DiaSymReader.Tools; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using System.Xml; + +namespace Tizen.Runtime.Tools +{ + public class LineNumber + { + private static readonly string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + + public LineNumber() + { + } + + internal sealed class Args + { + public readonly string[] AssemblyPaths; + public readonly string[] PdbPaths; + public readonly string ExceptionPath; + public readonly string OutputPath; + + public Args(string[] assemblyPaths, string[] pdbPaths, string exceptionPath, string outputPath) + { + AssemblyPaths = assemblyPaths; + PdbPaths = pdbPaths; + ExceptionPath = exceptionPath; + OutputPath = outputPath; + } + } + + public static void UsageLine() + { + string UsageMsg = "Usage: dotnet-extractor line-number [Log] [Options]\n\n" + + "Log:\n" + + " Path to the exception log file (Filename extension: xxxxx.log)\n\n" + //+ " Input the log message directly\n\n" + + "Options:\n" + + " -h, --help Show this help message\n" + + " -a, --assembly Multiple paths with assembly directories separated by colon(':')\n" + + " -p, --pdb Path to the pdb directory (Can be omitted if it is the same as the assembly directory path)\n" + + " -o, --out Path to the output file (Default: Output to console. If omitted, the xxxxx.out file is created in the same location as the log file)\n\n" + + "Example:\n" + + "1. If both assembly and pdb are in the current directory\n" + + " # dotnet extractor line-number /tmp/Exception1.log\n" + + "2. If both assembly and pdb are in the same directory specified\n" + + " # dotnet extractor line-number /tmp/Exception2.log --assembly /opt/usr/globalapps/org.tizen.example.TestApp/:/usr/share/dotnet.tizen/\n" + + "3. If assembly and pdb are separated in each directory\n" + + " # dotnet extractor line-number /tmp/Exception3.log --assembly /usr/share/dotnet.tizen/framework/ --pdb /tmp/pdbs/\n"; + Console.WriteLine(UsageMsg); + } + + public void LineNumbers(string[] args) + { + try + { + Args parsedArgs = ParseArgs(args); + Extract(parsedArgs, Convert(parsedArgs)); + } + catch (Exception e) + { + UsageLine(); + Console.Error.WriteLine(e.Message); + } + } + + internal static Args ParseArgs(string[] args) + { + string[] assemblyPaths = null; + string[] pdbPaths = null; + string exceptionPath = null; + string outputPath = null; + + int i = 1; + while (i < args.Length) + { + var arg = args[i++]; + string ReadValue() => (i < args.Length) ? args[i++] : throw new InvalidDataException(string.Format("Missing value for option '{0}'", arg)); + switch (arg) + { + case "-h": + case "--help": + UsageLine(); + Environment.Exit(0); + break; + case "-p": + case "--pdb": + pdbPaths = ReadValue().Split(":"); + break; + case "-o": + case "--out": + outputPath = ReadValue(); + break; + case "-a": + case "--assembly": + assemblyPaths = ReadValue().Split(":"); + break; + default: + if (arg.Contains("-")) + { + UsageLine(); + Console.WriteLine($"Unknown option [{arg}]\n"); + Environment.Exit(0); + break; + } + exceptionPath ??= arg; + if (!File.Exists(exceptionPath)) + { + throw new FileNotFoundException("Log file not found\n"); + } + break; + } + } + if (exceptionPath == null) + { + throw new InvalidDataException("Missing exception log path\n"); + } + assemblyPaths ??= new string[] { Directory.GetCurrentDirectory() }; + pdbPaths ??= assemblyPaths; + try + { + outputPath ??= Path.ChangeExtension(exceptionPath, "out"); + } + catch (Exception e) + { + throw new InvalidDataException(e.Message); + } + + return new Args( + assemblyPaths: assemblyPaths, + pdbPaths: pdbPaths, + exceptionPath: exceptionPath, + outputPath: outputPath); + } + + private static List GrabFiles(string[] paths, string searchPattern) + { + List files = new List(); + foreach (var assemDir in paths) + { + if (Directory.Exists(assemDir)) + { + foreach (var peFile in Directory.GetFiles(assemDir, searchPattern, SearchOption.AllDirectories)) + { + files.Add(peFile); + } + } + } + return files; + } + + /* + * Convert + */ + private static List Convert(Args args) + { + List peFiles = GrabFiles(args.AssemblyPaths, "*.dll"); + if (peFiles.Count == 0) + { + throw new FileNotFoundException("Assembly file not found\n"); + } + + List pdbFiles = GrabFiles(args.PdbPaths, "*.pdb"); + if (pdbFiles.Count == 0) + { + throw new FileNotFoundException("PDB file not found\n"); + } + + Console.Write("Converting pdb to xml..."); + Console.SetCursorPosition(0, Console.CursorTop); + + Directory.CreateDirectory(tempDirectory); + + List xmlList = new List(); + foreach (var pdbFile in pdbFiles) + { + foreach (var peFile in peFiles) + { + if (Path.GetFileNameWithoutExtension(peFile) == Path.GetFileNameWithoutExtension(pdbFile)) + { + string xmlPath = Path.ChangeExtension(peFile, "xml"); + if (xmlPath.Contains("/usr/share/dotnet")) + { + xmlPath = tempDirectory + "/" + Path.GetFileName(xmlPath); + } + GetXmlFromPdb(peFile, pdbFile, xmlPath); + xmlList.Add(xmlPath); + break; + } + } + } + return xmlList; + } + + private static void GetXmlFromPdb(string assemblyPath, string pdbPath, string xmlPath) + { + using var peStream = new FileStream(assemblyPath, FileMode.Open, FileAccess.Read); + using var pdbStream = new FileStream(pdbPath, FileMode.Open, FileAccess.Read); + using var dstFileStream = new FileStream(xmlPath, FileMode.Create, FileAccess.ReadWrite); + using var sw = new StreamWriter(dstFileStream, Encoding.UTF8); + PdbToXmlOptions options = PdbToXmlOptions.ResolveTokens | PdbToXmlOptions.IncludeModuleDebugInfo | PdbToXmlOptions.IncludeTokens; + + PdbToXmlConverter.ToXml(sw, pdbStream, peStream, options); + } + + /* + * Extract + */ + private static void Extract(Args args, List xmlList) + { + string logFile = args.ExceptionPath; + string outputPath = args.OutputPath; + + if (xmlList.Count == 0) + { + throw new FileNotFoundException("Xml file not found\n"); + } + + GetLineFromLog(logFile, xmlList, outputPath); + + if (Directory.Exists(tempDirectory)) + { + Directory.Delete(tempDirectory, true); + } + } + + internal sealed class StackTraceInfo + { + public string Type; + public string Method; + public string Param; + public string Assembly; + public string Token; + public string Offset; + public string Document; + public string Filepath; + public string Filename; + public string StartLine; + public string EndLine; + } + + private static void GetLineFromLog(string logPath, List xmlList, string outputPath) + { + Console.WriteLine("Extraction result: "); + try + { + using StreamReader fsr = new StreamReader(new FileStream(logPath, FileMode.Open, FileAccess.Read)); + using StreamWriter fsw = new StreamWriter(new FileStream(outputPath, FileMode.Create, FileAccess.Write)); + + bool isParsed = false; + while (!fsr.EndOfStream) + { + string line = fsr.ReadLine(); + if (!line.Contains(" at ")) + { + continue; + } + string typeMethodStr = Regex.Match(line, " at (?.*?)\\(\\)").Groups["splitdata"].Value; + string methodStr = typeMethodStr.Split(".")[^1]; + string typenameStr = typeMethodStr.Replace("." + methodStr, ""); + string assemblyStr = Regex.Match(line, " in (?.*?)\\: ").Groups["splitdata"].Value; + string tokenStr = Regex.Match(line, " method_token\\((?.*?)\\)\\,").Groups["splitdata"].Value; + string offsetStr = Regex.Match(line, " il_offset\\((?.*?)\\)").Groups["splitdata"].Value; + string xmlStr = assemblyStr.Replace(".dll", ".xml"); + + StackTraceInfo stInfo = new StackTraceInfo() { Type = typenameStr, Method = methodStr, Assembly = assemblyStr, Token = tokenStr, Offset = offsetStr/*, Xml = xmlStr*/ }; + + foreach (var xmlPath in xmlList) + { + if (xmlPath.Contains(xmlStr)) + { + isParsed = true; + GetLineFromXml(xmlPath, stInfo); + if (stInfo.Filepath == null || stInfo.StartLine == null) + { + Console.WriteLine(" ===== PARSE ERROR FOR EXCEPTION LOG IN THIS LINE. PLEASE RECHECK THE EXCEPTION LOG ====="); + break; + } + string ret = $" at {stInfo.Type}.{stInfo.Method}({stInfo.Param}) in {stInfo.Filepath}:line {stInfo.StartLine}"; + fsw.WriteLine(ret); + Console.WriteLine(ret); + } + } + } + if (!isParsed) + { + Console.WriteLine(" There is no content matching the exception log."); + Console.WriteLine(" Please recheck the assembly and pdb directory path.\n"); + } + else + { + Console.WriteLine($"\nOutput: {outputPath}\n"); + } + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + + private static void GetLineFromXml(string xmlPath, StackTraceInfo stInfo) + { + try + { + XmlDocument xmlDoc = new XmlDocument(); + xmlDoc.Load(xmlPath); + XmlElement xRoot = xmlDoc.DocumentElement; + XmlNodeList xnList = xRoot.ChildNodes; + int xnCount = xnList.Count; + if (xnCount > 0) + { + for (int i = xnCount - 1; i >= 0; i--) + { + XmlNode node = xnList[i]; + if (node.Name == "files") + { + ParseFile(node.ChildNodes, stInfo); + } + else if (node.Name == "methods") + { + ParseMethod(node.ChildNodes, stInfo); + } + } + } + } + catch (ArgumentException e) + { + Console.WriteLine(e); + } + } + + private static void ParseFile(XmlNodeList xn, StackTraceInfo stInfo) + { + try + { + foreach (XmlNode node in xn) + { + if (stInfo.Document == node.Attributes["id"].Value) + { + stInfo.Filepath = node.Attributes["name"].Value; + stInfo.Filename = Path.GetFileName(node.Attributes["name"].Value); + } + } + } + catch (ArgumentException e) + { + Console.WriteLine(e); + } + } + + private static void ParseMethod(XmlNodeList xn, StackTraceInfo stInfo) + { + try + { + foreach (XmlNode node in xn) + { + if (stInfo.Type == node.Attributes["containingType"].Value && + stInfo.Method == node.Attributes["name"].Value && + stInfo.Token == node.Attributes["token"].Value) + { + if (node.Attributes.Item(2).Name == "parameterNames") + { + stInfo.Param = node.Attributes["parameterNames"].Value; + } + ParseSequence(node.ChildNodes, stInfo); + } + } + } + catch (ArgumentException e) + { + Console.WriteLine(e); + } + } + + private static void ParseSequence(XmlNodeList xn, StackTraceInfo stInfo) + { + try + { + foreach (XmlNode node in xn) + { + if (node.Name == "sequencePoints") + { + ParseEntry(node.ChildNodes, stInfo); + } + } + } + catch (ArgumentException e) + { + Console.WriteLine(e); + } + } + + private static void ParseEntry(XmlNodeList xn, StackTraceInfo stInfo) + { + try + { + foreach (XmlNode node in xn) + { + if (stInfo.Offset == node.Attributes["offset"].Value) + { + if (node.Attributes.Item(1).Name == "startLine") + { + stInfo.StartLine = node.Attributes["startLine"].Value; + } + if (node.Attributes.Item(3).Name == "endLine") + { + stInfo.EndLine = node.Attributes["endLine"].Value; + } + stInfo.Document = node.Attributes["document"].Value; + } + } + } + catch (ArgumentException e) + { + Console.WriteLine(e); + } + } + } +} diff --git a/tools/Extractor/dotnet-extractor/dotnet-extractor/Microsoft.DiaSymReader.Converter.Xml.dll b/tools/Extractor/dotnet-extractor/dotnet-extractor/Microsoft.DiaSymReader.Converter.Xml.dll new file mode 100644 index 0000000..0d204d2 Binary files /dev/null and b/tools/Extractor/dotnet-extractor/dotnet-extractor/Microsoft.DiaSymReader.Converter.Xml.dll differ diff --git a/tools/Extractor/dotnet-extractor/dotnet-extractor/dotnet-extractor.csproj b/tools/Extractor/dotnet-extractor/dotnet-extractor/dotnet-extractor.csproj new file mode 100644 index 0000000..6b388ca --- /dev/null +++ b/tools/Extractor/dotnet-extractor/dotnet-extractor/dotnet-extractor.csproj @@ -0,0 +1,20 @@ + + + + Exe + netcoreapp3.1 + dotnet_extractor + + + + + + + + + + Microsoft.DiaSymReader.Converter.Xml.dll + + + + diff --git a/tools/Performance/README b/tools/Performance/README new file mode 100755 index 0000000..41e934b --- /dev/null +++ b/tools/Performance/README @@ -0,0 +1,95 @@ +README file for performance test of dotnet launcher + + +[*] SUMMARY +------------------------------------------------------------------------------- +Performance test is a tool to calcuate launching time. +This tool is performed on the host where the device is connected with sdb + +[*] PREPARATIONS +------------------------------------------------------------------------------- +The test consists of two files. + - performance_test.sh : main executable script + - timestamp.sh : used by "performance_test.sh" +The files should be in the same directory in host. + +For running automatic test, you have to locate ".tpk" files in specific directory +More detail, it describes in "USAGE" section. + +This test need below package + - inotify-tools +If the package is not exist, it will try to install the package. + +The host can connect device with sdb. + + +[*] TEST MODES +------------------------------------------------------------------------------- +There are two modes in performance test + - auto test mode + - manual test mode + +Each mode has the following characteristics. +[Auto test mode] + This mode automatically installs applications and measures and records performance. + This mode need ".tpk" packaged dotnet application. It should located in "tpk" directory. + So, files locates likes below. + . + ├── performance_test.sh + ├── README + ├── timestamp.sh + └── tpk + └── .tpk + There can be several tpk files. And tpk files can be freely added or deleted by the user. +[Manual test mode] + This mode measures the execution time when the user executes the installed applications. + After executing this mode, the user runs and terminates the application. + And then, result of launching time and making report file. + + +[*] USAGE +------------------------------------------------------------------------------- +The test has two modes. + - automatic test + - manual test + +Each test can be run through shell below options. + -a --auto : execute automatic test + -m --manual : execute manual test + +In the auto test mode does not require user input. +In the manual test mode, the user executes / terminates the application after executing the test. + + +[*] TEST RESULT +------------------------------------------------------------------------------- +Test results are generated as files in the "result/" directory. +The resulting file name is determined by the date and time, likes "result/result_YYYYMMDD_HHmm.log" + +Test results consist of launching time(ms) and applicatoin id. +example of result : + T(ms) APP ID + 680 org.tizen.example.BasicSampleXamarine.Tizen + 710 org.tizen.example.XamarinApplication1.Tizen + 1460 org.tizen.example.EmailUI.Tizen + 770 org.tizen.example.FormsTizenGallery.Tizen + 2390 org.tizen.example.SNSUI.Tizen + + +[*] ERROR CASE +------------------------------------------------------------------------------- +Some system can be occur error with some points. +In this section, we would like to share some solutions to solve the problems. + +[>] Inotify-tools - 'max_user_watchers' error + + [ERROR CASE] + Failed to watch stream.log; upper limit on inotify watches reached! + Please increase the amount of inotify watches allowed per user via `/proc/sys/fs/inotify/max_user_watches'. + + execute below command for expand max_user_watchers for inotify-tool + + [SOLVE] + echo 32768 | sudo tee /proc/sys/fs/inotify/max_user_watches + echo fs.inotify.max_user_watches=32768 | sudo tee -a /etc/sysctl.conf + sudo sysctl -p diff --git a/tools/Performance/memorystamp.sh b/tools/Performance/memorystamp.sh new file mode 100755 index 0000000..6db944f --- /dev/null +++ b/tools/Performance/memorystamp.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +if [[ -z $1 ]] || [[ -z $2 ]] +then + echo "[!] Execute [./performance_test.sh]" + exit 0 +fi + +LOG_FILE=$1 +RESULT_FILE=$2 +WAIT_FOR_LAUNCH=10 + +APP_MEMORY=$(sdb shell "memps -v | grep 'Available'" | tail -1) +BEFORE_MEMORY=($(echo $APP_MEMORY | awk '{print $5}')) +while inotifywait -qqre modify "$LOG_FILE"; do + sleep $WAIT_FOR_LAUNCH + APP_MEMORY=$(sdb shell "memps -v | grep 'Tizen.exe'" | tail -1) + MEMORY_PID=($(echo $APP_MEMORY | awk '{print $1}')) + MEMORY_PSS=($(echo $APP_MEMORY | awk '{print $7}')) + MEMORY_3D=($(echo $APP_MEMORY | awk '{print $8}')) + MEMORY_GEM=($(echo $APP_MEMORY | awk '{print $9}')) + APP_MEMORY=$(sdb shell "memps -v | grep 'Available'" | tail -1) + AFTER_MEMORY=($(echo $APP_MEMORY | awk '{print $5}')) + let MEMORY_FREE=$BEFORE_MEMORY-$AFTER_MEMORY + echo -e "PSS\t3D\tGEM\tFREE" | tee -a $RESULT_FILE + echo -e "$MEMORY_PSS\t$MEMORY_3D\t$MEMORY_GEM\t$MEMORY_FREE" | tee -a $RESULT_FILE + DETAIL_MEMORY=$(sdb shell "memps $MEMORY_PID" | while read line + do + DETAIL_MEMORY_LIST=$line + DETAIL_PCODE=($(echo $DETAIL_MEMORY_LIST | awk '{print $3}' | tr -d '\r')) + DETAIL_PDATA=($(echo $DETAIL_MEMORY_LIST | awk '{print $4}' | tr -d '\r')) + if [[ $DETAIL_PCODE != "P(CODE)" ]] && [[ $DETAIL_PDATA != "P(DATA)" ]] && + [[ $DETAIL_PCODE != "--------" ]] && [[ $DETAIL_PDATA != "--------" ]] + then + let DETAIL_SUM=$DETAIL_PCODE+$DETAIL_PDATA + if [[ $DETAIL_SUM -ge 100 ]] + then + DETAIL_OBJECT=($(echo $DETAIL_MEMORY_LIST | awk '{print $6}')) + echo -n "$DETAIL_SUM\t$DETAIL_OBJECT" + echo "" + fi + fi + done | sort -n -r | tr -d '\r' + ) + echo -e "P(C+D)\tOBJECT NAME" | tee -a $RESULT_FILE + for item in ${DETAIL_MEMORY[*]} + do + echo -e $item | tee -a $RESULT_FILE + done + echo -e "" | tee -a $RESULT_FILE + sleep 3 + sdb shell kill -9 $MEMORY_PID + sleep $WAIT_FOR_LAUNCH + echo -e "T(ms)\tAPP ID" | tee -a $RESULT_FILE + APP_MEMORY=$(sdb shell "memps -v | grep 'Available'" | tail -1) + BEFORE_MEMORY=($(echo $APP_MEMORY | awk '{print $5}')) +done diff --git a/tools/Performance/performance_test.sh b/tools/Performance/performance_test.sh new file mode 100755 index 0000000..3519cc4 --- /dev/null +++ b/tools/Performance/performance_test.sh @@ -0,0 +1,386 @@ +#!/bin/bash + +STREAM_LOG_FILE=stream.log +DATE=$(date +%Y%m%d_%H%M) +RESULT_LOG_FILE='result/result_'$DATE'.log' + +WAIT_FOR_LAUNCH=10 +WAIT_FOR_LONG_LAUNCH=30 +WAIT_FOR_KILL=5 + +PKG_IDS=() +APP_IDS=() + +LONG_LAUNCHING_APP=( + "org.tizen.example.SNSUI.Tizen", +) + +initialize () +{ + echo "" + echo "[>] Initialize for Performance Test" + if [ $(sdb get-state 2>/dev/null | grep -c "device") -eq 0 ]; + then + echo "" + echo "[!] device is not connected - cannot execute" + echo "" + exit 0 + fi + + if [ $(dpkg-query -W -f='${Status}' inotify-tools 2>/dev/null | grep -c "ok installed") -eq 0 ]; + then + echo "" + echo "[!] inotify-tools package should install" + echo "[!] starting install inotify-tools .. " + sudo apt-get install inotify-tools + if [ $(dpkg-query -W -f='${Status}' inotify-tools 2>/dev/null | grep -c "ok installed") -eq 0 ]; + then + echo "" + echo "[!] install inotify-tools fail - cannot execute" + echo "" + exit 0 + fi + echo 32768 | sudo tee /proc/sys/fs/inotify/max_user_watches + echo fs.inotify.max_user_watches=32768 | sudo tee -a /etc/sysctl.conf + sudo sysctl -p + fi + sdb root on + sdb shell "devicectl display stop">/dev/null 2>&1 + mkdir result>/dev/null 2>&1 + rm $STREAM_LOG_FILE>/dev/null 2>&1 + touch $STREAM_LOG_FILE +} + +install_tpk () +{ +#install tpk packages + echo "[>] Installing package in tpk directory" + TPKS=($(ls tpk | grep .tpk)) + + + for item in ${TPKS[*]} + do + INSTALL_MSG=$(sdb install tpk/$item | grep start) + INSTALL_MSG=$(echo $INSTALL_MSG | sed "s/\[/ /g") + INSTALL_MSG=$(echo $INSTALL_MSG | sed "s/\]/ /g") + PKG_IDS+=($(echo $INSTALL_MSG | awk '{print $7}' | tr -d '\r')) + echo " [>] ($(echo $INSTALL_MSG | awk '{print $7}')) installs complete" + done +} + +get_current_tpk_apps () +{ + echo "[>] Get application list in device" +# PKG_IDS+=$( +# sdb shell "su - owner -c 'pkgcmd -l | grep tpk'" | while read line +# do +# APP_LIST_ENTITY=$line +# APP_LIST_ENTITY=$(echo $APP_LIST_ENTITY | sed "s/\[/ /g") +# APP_LIST_ENTITY=$(echo $APP_LIST_ENTITY | sed "s/\]/ /g") +# APP_OWNER=($(echo $APP_LIST_ENTITY | awk '{print $1}')) +# if [[ $APP_OWNER == 'user' ]] +# then +# echo $APP_LIST_ENTITY | awk '{print $6}' +# fi +# done | sort | tr -d '\r' +# ) +#In 3.0 mobile / wearable, appfw install all of application to global application + PKG_IDS+=$( + sdb shell "su - owner -c 'ls -al /opt/usr/globalapps/ | grep tizenglobalapp'" | while read line + do + APP_LIST_ENTITY=$line + APP_GLOBAL=($(echo $APP_LIST_ENTITY | awk '{print $3}')) + if [[ $APP_GLOBAL == 'tizenglobalapp' ]] + then + echo $APP_LIST_ENTITY | awk '{print $9}' + fi + done | sort | tr -d '\r' + ) +} + +make_appid_list () +{ +#get app ids + echo "[>] Get application id that installed" + for item in ${PKG_IDS[*]} + do + APP_LIST_MSG=$(sdb shell "su - owner -c 'pkginfo --pkg $item' | grep mainappid" | tail -1) + APP_IDS+=($(echo $APP_LIST_MSG | awk '{print $3}' | tr -d '\r')) + done +} + +initialize_first_launch () +{ + if [[ -z ${APP_IDS[0]} ]]; then + echo "" + echo "[!] No tpk Packages for test" + echo "[!] Copy tpk files in [./tpk] directory" + echo "" + exit 0 + fi + echo "[>] Initial launch an application" + APP_LIST_MSG=$(sdb shell "su - owner -c 'app_launcher -s ${APP_IDS[0]}'") + sleep 10 + APP_LIST_MSG=$(sdb shell "su - owner -c 'app_launcher -t ${APP_IDS[0]}'") + sleep 5 +} + +execute_time_stamp_auto () +{ + echo "" + echo "[>] Start performance test that applciation launching time" + echo "" +#execute dlogstreamer + sdb shell "dlogutil -c" + sdb shell "dlogutil -v time AUL LAUNCH|grep -E 'launch.*app_request_to_launchpad_for_uid.*request.*appid|Launching:done'" >> $STREAM_LOG_FILE & + DLOG_STREAMER_PID=$! +#execute timestamp + /bin/bash ./timestamp.sh $STREAM_LOG_FILE $RESULT_LOG_FILE & + TIMESTAMP_PID=$! +} + +execute_time_stamp_auto_memory () +{ + echo "" + echo "[>] Start performance test that applciation launching memory" + echo "" + sdb shell "dlogutil -c" + sdb shell "dlogutil -v time AUL LAUNCH|grep -E 'launch.*app_request_to_launchpad_for_uid.*request.*appid|Launching:done'" >> $STREAM_LOG_FILE & + DLOG_STREAMER_PID=$! +#execute timestamp + /bin/bash ./timestamp.sh $STREAM_LOG_FILE $RESULT_LOG_FILE & + TIMESTAMP_PID=$! +#execute memorystamp + /bin/bash ./memorystamp.sh $STREAM_LOG_FILE $RESULT_LOG_FILE & + MEMORYSTAMP_PID=$! +} + +execute_time_stamp_manual () +{ +#execute dlogstreamer + echo "" + echo "[>] Start performance test that applciation launching time" + echo "" + rm $STREAM_LOG_FILE + touch $STREAM_LOG_FILE + sdb shell "dlogutil -c" + sdb shell "dlogutil -v time AUL LAUNCH|grep -E 'launch.*app_request_to_launchpad_for_uid.*request.*appid|Launching:done'" >> $STREAM_LOG_FILE & + DLOG_STREAMER_PID=$! +#execute timestamp + /bin/bash ./timestamp.sh $STREAM_LOG_FILE $RESULT_LOG_FILE + TIMESTAMP_PID=$! + wait $TIMESTAMP_PID +} + +execute_time_stamp_manual_trace () +{ +#execute dlogstreamer + echo "" + echo "[>] Start performance test that applciation launching time" + echo "" + rm $STREAM_LOG_FILE + touch $STREAM_LOG_FILE + sdb shell "dlogutil -c" + sdb shell "dlogutil -v time AUL LAUNCH|grep -E 'launch.*app_request_to_launchpad_for_uid.*request.*appid|Launching:done'" >> $STREAM_LOG_FILE & + DLOG_STREAMER_PID=$! +#execute timestamp + /bin/bash ./timestamp.sh $STREAM_LOG_FILE $RESULT_LOG_FILE & + TIMESTAMP_PID=$! +#execute ttrace + ~/tizen-sdk/tools/ttrace/ttrace.py -b 20480 -t 10 -o result/trace.html idle app view am & + TTRACE_PID=$! + wait $TTRACE_PID + rm result/*.ftrace + rm result/*.raw +} + +execute_time_stamp_manual_memory () +{ + echo "" + echo "[>] Start performance test that applciation launching memory" + echo "" + rm $STREAM_LOG_FILE + touch $STREAM_LOG_FILE + sdb shell "dlogutil -c" + sdb shell "dlogutil -v time AUL LAUNCH|grep -E 'launch.*app_request_to_launchpad_for_uid.*request.*appid|Launching:done'" >> $STREAM_LOG_FILE & + DLOG_STREAMER_PID=$! +#execute timestamp + /bin/bash ./timestamp.sh $STREAM_LOG_FILE $RESULT_LOG_FILE & + TIMESTAMP_PID=$! +#execute memorystamp + /bin/bash ./memorystamp.sh $STREAM_LOG_FILE $RESULT_LOG_FILE & + MEMORYSTAMP_PID=$! + wait $MEMORYSTAMP_PID +} + +execute_all_app () +{ +#execute each apps + for item in ${APP_IDS[*]} + do + APP_LIST_MSG=$(sdb shell "su - owner -c 'app_launcher -s $item'") + if [[ "${LONG_LAUNCHING_APP[@]}" =~ "${item}" ]]; then + sleep $WAIT_FOR_LONG_LAUNCH + else + sleep $WAIT_FOR_LAUNCH + fi + APP_LIST_MSG=$(sdb shell "su - owner -c 'app_launcher -t $item'") + sleep $WAIT_FOR_KILL + done +} + +execute_all_app_trace () +{ +#execute each apps + for item in ${APP_IDS[*]} + do + ~/tizen-sdk/tools/ttrace/ttrace.py -b 20480 -t 13 -o result/${item}.html idle app view am & > /dev/null 2>&1 + TTRACE_PID=$! + sleep 1 + APP_LIST_MSG=$(sdb shell "su - owner -c 'app_launcher -s $item'") + if [[ "${LONG_LAUNCHING_APP[@]}" =~ "${item}" ]]; then + sleep $WAIT_FOR_LONG_LAUNCH + else + sleep $WAIT_FOR_LAUNCH + fi + APP_LIST_MSG=$(sdb shell "su - owner -c 'app_launcher -t $item'") + sleep $WAIT_FOR_KILL + sleep 4 + done + rm result/*.ftrace + rm result/*.raw +} + +execute_all_app_memory () +{ + sleep $WAIT_FOR_LAUNCH +#execute each apps + for item in ${APP_IDS[*]} + do + if [[ "${LONG_LAUNCHING_APP[@]}" =~ "${item}" ]]; then + echo "" + else + APP_LIST_MSG=$(sdb shell "su - owner -c 'app_launcher -s $item'") + sleep $WAIT_FOR_LONG_LAUNCH + #APP_LIST_MSG=$(sdb shell "su - owner -c 'app_launcher -t $item'") + sleep $WAIT_FOR_KILL + fi + sleep $WAIT_FOR_LONG_LAUNCH + done +} + +finalize () +{ + echo "" + echo "[>] Result" + echo "" + cat $RESULT_LOG_FILE + echo "" + echo "[>] Result file : [ $RESULT_LOG_FILE ]" +} + +destory () +{ + echo "" + kill -9 $DLOG_STREAMER_PID>/dev/null 2>&1 + kill -9 $TIMESTAMP_PID>/dev/null 2>&1 + kill -9 $TTRACE_PID>/dev/null 2>&1 + kill -9 $MEMORYSTAMP_PID>/dev/null 2>&1 + rm $STREAM_LOG_FILE>/dev/null 2>&1 + sdb shell "devicectl display start">/dev/null 2>&1 + echo "[>] Finalize for Performance Test" + echo "" +} + +help () +{ + echo "" + echo "[!] usage :