2019-09-24 08:53:40 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Net.Sockets;
|
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
|
using Mono.Options;
|
2019-12-10 18:00:56 +00:00
|
|
|
|
using System.Linq;
|
2019-09-24 08:53:40 +00:00
|
|
|
|
using Mono.Profiler.Aot;
|
|
|
|
|
|
|
|
|
|
using static System.Console;
|
|
|
|
|
|
|
|
|
|
namespace aotprofiletool {
|
|
|
|
|
class MainClass {
|
2020-07-10 08:44:59 +00:00
|
|
|
|
static readonly string Name = "aprofutil";
|
2019-09-24 08:53:40 +00:00
|
|
|
|
|
|
|
|
|
static bool AdbForward;
|
|
|
|
|
static bool Methods;
|
|
|
|
|
static bool Modules;
|
|
|
|
|
static bool Summary;
|
|
|
|
|
static bool Types;
|
|
|
|
|
static bool Verbose;
|
|
|
|
|
|
|
|
|
|
static Regex FilterMethod;
|
2019-12-10 18:00:56 +00:00
|
|
|
|
static Regex SkipMethod;
|
2019-09-24 08:53:40 +00:00
|
|
|
|
static Regex FilterModule;
|
2019-12-10 18:00:56 +00:00
|
|
|
|
static Regex SkipModule;
|
2019-09-24 08:53:40 +00:00
|
|
|
|
static Regex FilterType;
|
2019-12-10 18:00:56 +00:00
|
|
|
|
static Regex SkipType;
|
|
|
|
|
|
|
|
|
|
static int SkipCount = 0;
|
|
|
|
|
static int TakeCount = int.MaxValue;
|
2019-09-24 08:53:40 +00:00
|
|
|
|
|
|
|
|
|
static string Output;
|
|
|
|
|
|
|
|
|
|
static int Port = -1;
|
|
|
|
|
|
|
|
|
|
static string ProcessArguments (string [] args)
|
|
|
|
|
{
|
|
|
|
|
var help = false;
|
|
|
|
|
var options = new OptionSet {
|
|
|
|
|
$"Usage: {Name}.exe OPTIONS* <aotprofile-file>",
|
|
|
|
|
"",
|
|
|
|
|
"Processes AOTPROFILE files created by Mono's AOT Profiler",
|
|
|
|
|
"",
|
|
|
|
|
"Copyright 2019 Microsoft Corporation",
|
|
|
|
|
"",
|
|
|
|
|
"Options:",
|
|
|
|
|
{ "h|help|?",
|
|
|
|
|
"Show this message and exit",
|
|
|
|
|
v => help = v != null },
|
|
|
|
|
{ "a|all",
|
|
|
|
|
"Show modules, types and methods in the profile",
|
|
|
|
|
v => Modules = Types = Methods = true },
|
|
|
|
|
{ "d|modules",
|
|
|
|
|
"Show modules in the profile",
|
|
|
|
|
v => Modules = true },
|
|
|
|
|
{ "f|adb-forward",
|
|
|
|
|
"Set adb socket forwarding for Android",
|
|
|
|
|
v => AdbForward = true },
|
|
|
|
|
{ "filter-method=",
|
2019-12-10 18:00:56 +00:00
|
|
|
|
"Include by method with regex {VALUE}",
|
2019-09-24 08:53:40 +00:00
|
|
|
|
v => FilterMethod = new Regex (v) },
|
2019-12-10 18:00:56 +00:00
|
|
|
|
{ "skip-method=",
|
|
|
|
|
"Exclude by method with regex {VALUE}",
|
|
|
|
|
v => SkipMethod = new Regex (v) },
|
2019-09-24 08:53:40 +00:00
|
|
|
|
{ "filter-module=",
|
2019-12-10 18:00:56 +00:00
|
|
|
|
"Include by module with regex {VALUE}",
|
2019-09-24 08:53:40 +00:00
|
|
|
|
v => FilterModule = new Regex (v) },
|
2019-12-10 18:00:56 +00:00
|
|
|
|
{ "skip-module=",
|
|
|
|
|
"Exclude by module with regex {VALUE}",
|
|
|
|
|
v => SkipModule = new Regex (v) },
|
2019-09-24 08:53:40 +00:00
|
|
|
|
{ "filter-type=",
|
2019-12-10 18:00:56 +00:00
|
|
|
|
"Include by type with regex {VALUE}",
|
2019-09-24 08:53:40 +00:00
|
|
|
|
v => FilterType = new Regex (v) },
|
2019-12-10 18:00:56 +00:00
|
|
|
|
{ "skip-type=",
|
|
|
|
|
"Exclude by type with regex {VALUE}",
|
|
|
|
|
v => SkipType = new Regex (v) },
|
|
|
|
|
{ "take-count=",
|
|
|
|
|
"Take {VALUE} methods that match",
|
|
|
|
|
v => TakeCount = int.Parse (v) },
|
|
|
|
|
{ "skip-count=",
|
|
|
|
|
"Skip the first {VALUE} matching methods",
|
|
|
|
|
v => SkipCount = int.Parse (v) },
|
2019-09-24 08:53:40 +00:00
|
|
|
|
{ "m|methods",
|
|
|
|
|
"Show methods in the profile",
|
|
|
|
|
v => Methods = true },
|
|
|
|
|
{ "o|output=",
|
|
|
|
|
"Write profile to {OUTPUT} file",
|
|
|
|
|
v => Output = v },
|
|
|
|
|
{ "p|port=",
|
|
|
|
|
"Read profile from aot profiler using local connection on {PORT}",
|
|
|
|
|
v => int.TryParse (v, out Port) },
|
|
|
|
|
{ "s|summary",
|
|
|
|
|
"Show summary of the profile",
|
|
|
|
|
v => Summary = true },
|
|
|
|
|
{ "t|types",
|
|
|
|
|
"Show types in the profile",
|
|
|
|
|
v => Types = true },
|
|
|
|
|
{ "v|verbose",
|
|
|
|
|
"Output information about progress during the run of the tool",
|
|
|
|
|
v => Verbose = true },
|
|
|
|
|
"",
|
|
|
|
|
"If no other option than -v is used then --all is used by default"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var remaining = options.Parse (args);
|
|
|
|
|
|
|
|
|
|
if (help || args.Length < 1) {
|
|
|
|
|
options.WriteOptionDescriptions (Out);
|
|
|
|
|
|
|
|
|
|
Environment.Exit (0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (remaining.Count != 1 && Port < 0) {
|
|
|
|
|
Error ("Please specify one <aotprofile-file> to process or network PORT with -p.");
|
|
|
|
|
Environment.Exit (2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return remaining.Count > 0 ? remaining [0] : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static ProfileData ReadProfileFromPort (ProfileReader reader)
|
|
|
|
|
{
|
|
|
|
|
ProfileData pd;
|
|
|
|
|
|
|
|
|
|
if (AdbForward) {
|
|
|
|
|
var cmdArgs = $"forward tcp:{Port} tcp:{Port}";
|
|
|
|
|
if (Verbose)
|
|
|
|
|
ColorWriteLine ($"Calling 'adb {cmdArgs}'...", ConsoleColor.Yellow);
|
|
|
|
|
|
|
|
|
|
System.Diagnostics.Process.Start ("adb", cmdArgs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
using (var client = new TcpClient ("127.0.0.1", Port)) {
|
|
|
|
|
using (var stream = client.GetStream ()) {
|
|
|
|
|
var msgData = System.Text.Encoding.ASCII.GetBytes ("save\n");
|
|
|
|
|
|
|
|
|
|
stream.Write (msgData, 0, msgData.Length);
|
|
|
|
|
|
|
|
|
|
if (Verbose)
|
|
|
|
|
ColorWriteLine ($"Reading from '127.0.0.1:{Port}'...", ConsoleColor.Yellow);
|
|
|
|
|
|
|
|
|
|
using (var memoryStream = new MemoryStream (128 * 1024)) {
|
|
|
|
|
var data = new byte [4 * 1024];
|
|
|
|
|
int len;
|
|
|
|
|
|
|
|
|
|
while ((len = stream.Read (data, 0, data.Length)) > 0) {
|
|
|
|
|
memoryStream.Write (data, 0, len);
|
|
|
|
|
|
|
|
|
|
if (Verbose)
|
|
|
|
|
ColorWrite ($"Read {len} bytes...\r", ConsoleColor.Yellow);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Verbose)
|
|
|
|
|
ColorWriteLine ($"Read total {memoryStream.Length} bytes...", ConsoleColor.Yellow);
|
|
|
|
|
|
|
|
|
|
memoryStream.Seek (0, SeekOrigin.Begin);
|
|
|
|
|
|
|
|
|
|
pd = reader.ReadAllData (memoryStream);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return pd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void Main (string [] args)
|
|
|
|
|
{
|
|
|
|
|
var path = ProcessArguments (args);
|
|
|
|
|
|
|
|
|
|
if (args.Length == 1) {
|
|
|
|
|
Modules = Types = Methods = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var reader = new ProfileReader ();
|
|
|
|
|
ProfileData pd = null;
|
|
|
|
|
|
|
|
|
|
if (path == null) {
|
|
|
|
|
if (Port < 0) {
|
|
|
|
|
Error ($"You should specify path or -p PORT to read the profile.");
|
|
|
|
|
Environment.Exit (4);
|
|
|
|
|
} else {
|
|
|
|
|
try {
|
|
|
|
|
pd = ReadProfileFromPort (reader);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Error ($"Unable to read profile through local port: {Port}.\n{e}");
|
|
|
|
|
Environment.Exit (5);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (!File.Exists (path)) {
|
|
|
|
|
Error ($"'{path}' doesn't exist.");
|
|
|
|
|
Environment.Exit (3);
|
|
|
|
|
} else {
|
|
|
|
|
using (var stream = new FileStream (path, FileMode.Open)) {
|
|
|
|
|
if (Verbose)
|
|
|
|
|
ColorWriteLine ($"Reading '{path}'...", ConsoleColor.Yellow);
|
|
|
|
|
|
|
|
|
|
pd = reader.ReadAllData (stream);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<MethodRecord> methods = new List<MethodRecord> (pd.Methods);
|
|
|
|
|
ICollection<TypeRecord> types = new List<TypeRecord> (pd.Types);
|
|
|
|
|
ICollection<ModuleRecord> modules = new List<ModuleRecord> (pd.Modules);
|
|
|
|
|
|
|
|
|
|
if (FilterMethod != null || FilterType != null || FilterModule != null) {
|
|
|
|
|
types = new HashSet<TypeRecord> ();
|
|
|
|
|
modules = new HashSet<ModuleRecord> ();
|
|
|
|
|
|
2019-12-10 18:00:56 +00:00
|
|
|
|
methods = pd.Methods.Where (method => {
|
2019-09-24 08:53:40 +00:00
|
|
|
|
var type = method.Type;
|
|
|
|
|
var module = type.Module;
|
|
|
|
|
|
|
|
|
|
if (FilterModule != null) {
|
|
|
|
|
var match = FilterModule.Match (module.ToString ());
|
|
|
|
|
|
|
|
|
|
if (!match.Success)
|
2019-12-10 18:00:56 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (SkipModule != null) {
|
|
|
|
|
var skip = SkipModule.Match (module.ToString ());
|
|
|
|
|
|
|
|
|
|
if (skip.Success)
|
|
|
|
|
return false;
|
2019-09-24 08:53:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (FilterType != null) {
|
|
|
|
|
var match = FilterType.Match (method.Type.ToString ());
|
|
|
|
|
|
|
|
|
|
if (!match.Success)
|
2019-12-10 18:00:56 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (SkipType != null) {
|
|
|
|
|
var skip = SkipType.Match (method.Type.ToString ());
|
|
|
|
|
|
|
|
|
|
if (skip.Success)
|
|
|
|
|
return false;
|
2019-09-24 08:53:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (FilterMethod != null) {
|
|
|
|
|
var match = FilterMethod.Match (method.ToString ());
|
|
|
|
|
|
|
|
|
|
if (!match.Success)
|
2019-12-10 18:00:56 +00:00
|
|
|
|
return false;
|
2019-09-24 08:53:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-12-10 18:00:56 +00:00
|
|
|
|
if (SkipMethod != null) {
|
|
|
|
|
var skip = SkipMethod.Match (method.ToString ());
|
|
|
|
|
|
|
|
|
|
if (skip.Success)
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}).Skip (SkipCount).Take (TakeCount).ToList ();
|
|
|
|
|
|
|
|
|
|
foreach (var method in methods) {
|
|
|
|
|
var type = method.Type;
|
|
|
|
|
var module = type.Module;
|
|
|
|
|
|
2019-09-24 08:53:40 +00:00
|
|
|
|
types.Add (type);
|
|
|
|
|
modules.Add (module);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (FilterMethod == null && FilterType != null) {
|
|
|
|
|
foreach (var type in pd.Types) {
|
|
|
|
|
if (types.Contains (type))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
var match = FilterType.Match (type.ToString ());
|
|
|
|
|
|
|
|
|
|
if (!match.Success)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
types.Add (type);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Modules) {
|
|
|
|
|
ColorWriteLine ($"Modules:", ConsoleColor.Green);
|
|
|
|
|
|
|
|
|
|
foreach (var module in modules)
|
|
|
|
|
WriteLine ($"\t{module.Mvid} {module.ToString ()}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Types) {
|
|
|
|
|
ColorWriteLine ($"Types:", ConsoleColor.Green);
|
|
|
|
|
|
|
|
|
|
foreach (var type in types)
|
|
|
|
|
WriteLine ($"\t{type}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Methods) {
|
|
|
|
|
ColorWriteLine ($"Methods:", ConsoleColor.Green);
|
|
|
|
|
|
|
|
|
|
foreach (var method in methods)
|
|
|
|
|
WriteLine ($"\t{method}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Summary) {
|
|
|
|
|
ColorWriteLine ($"Summary:", ConsoleColor.Green);
|
|
|
|
|
WriteLine ($"\tModules: {modules.Count.ToString ("N0"),10}{(modules.Count != pd.Modules.Length ? $" (of {pd.Modules.Length})" : "" )}");
|
|
|
|
|
WriteLine ($"\tTypes: {types.Count.ToString ("N0"),10}{(types.Count != pd.Types.Length ? $" (of {pd.Types.Length})" : "")}");
|
|
|
|
|
WriteLine ($"\tMethods: {methods.Count.ToString ("N0"),10}{(methods.Count != pd.Methods.Length ? $" (of {pd.Methods.Length})" : "")}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty (Output)) {
|
|
|
|
|
if (Verbose)
|
|
|
|
|
ColorWriteLine ($"Going to write the profile to '{Output}'", ConsoleColor.Yellow);
|
|
|
|
|
var modulesArray = new ModuleRecord [modules.Count];
|
|
|
|
|
modules.CopyTo (modulesArray, 0);
|
|
|
|
|
var typesArray = new TypeRecord [types.Count];
|
|
|
|
|
types.CopyTo (typesArray, 0);
|
|
|
|
|
var updatedPD = new ProfileData (modulesArray, typesArray, methods.ToArray ());
|
|
|
|
|
|
|
|
|
|
using (var stream = new FileStream (Output, FileMode.Create)) {
|
|
|
|
|
var writer = new ProfileWriter ();
|
|
|
|
|
writer.WriteAllData (stream, updatedPD);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void ColorMessage (string message, ConsoleColor color, TextWriter writer, bool writeLine = true)
|
|
|
|
|
{
|
|
|
|
|
ForegroundColor = color;
|
|
|
|
|
|
|
|
|
|
if (writeLine)
|
|
|
|
|
writer.WriteLine (message);
|
|
|
|
|
else
|
|
|
|
|
writer.Write (message);
|
|
|
|
|
|
|
|
|
|
ResetColor ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void ColorWriteLine (string message, ConsoleColor color) => ColorMessage (message, color, Out);
|
|
|
|
|
|
|
|
|
|
public static void ColorWrite (string message, ConsoleColor color) => ColorMessage (message, color, Out, false);
|
|
|
|
|
|
|
|
|
|
public static void Error (string message) => ColorMessage ($"Error: {Name}: {message}", ConsoleColor.Red, Console.Error);
|
|
|
|
|
|
|
|
|
|
public static void Warning (string message) => ColorMessage ($"Warning: {Name}: {message}", ConsoleColor.Yellow, Console.Error);
|
|
|
|
|
}
|
|
|
|
|
}
|