Created
August 23, 2018 10:26
-
-
Save 303248153/b4164b9cb812b9f02df8d1601300be71 to your computer and use it in GitHub Desktop.
Cpu Profiler for .Net Framework on Windows
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// A cpu profiler used to find out hot functions | |
// modified from https://github.com/jitbit/cpu-analyzer | |
// require nuget package: | |
// https://www.nuget.org/packages/Microsoft.Samples.Debugging.CorApi | |
// https://www.nuget.org/packages/Microsoft.Samples.Debugging.MdbgEngine | |
// | |
// Copyright 2012 - Sam Saffron | |
// | |
// Licensed under the Apache License, Version 2.0 (the "License"); | |
// you may not use this file except in compliance with the License. | |
// You may obtain a copy of the License at | |
// | |
// http://www.apache.org/licenses/LICENSE-2.0 | |
// | |
// Unless required by applicable law or agreed to in writing, software | |
// distributed under the License is distributed on an "AS IS" BASIS, | |
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
// See the License for the specific language governing permissions and | |
// limitations under the License. | |
using System; | |
using System.IO; | |
using System.Collections.Generic; | |
using System.Collections.Concurrent; | |
using System.Diagnostics; | |
using System.Runtime.InteropServices; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using System.Linq; | |
using System.Text; | |
using Microsoft.Samples.Debugging.MdbgEngine; | |
namespace CpuProfiler { | |
class ThreadSnapshot { | |
public int ThreadId { get; set; } | |
public long KernelTime { get; set; } | |
public long UserTime { get; set; } | |
public IList<string> StackTrace { get; set; } | |
public ThreadSnapshot() { | |
StackTrace = new List<string>(); | |
} | |
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] | |
private static extern bool GetThreadTimes(IntPtr handle, | |
out long creation, out long exit, out long kernel, out long user); | |
public static ThreadSnapshot GetThreadSnapshot(MDbgThread thread) { | |
var snapshot = new ThreadSnapshot(); | |
snapshot.ThreadId = thread.Id; | |
long creation, exit, kernel, user; | |
GetThreadTimes(thread.CorThread.Handle, | |
out creation, out exit, out kernel, out user); | |
snapshot.KernelTime = kernel; | |
snapshot.UserTime = user; | |
try { | |
foreach (MDbgFrame frame in thread.Frames) { | |
try { | |
snapshot.StackTrace.Add(frame.Function.FullName); | |
} catch { | |
// get information of frame failed | |
} | |
} | |
} catch { | |
// get frames of thread failed | |
} | |
return snapshot; | |
} | |
} | |
class Collector { | |
private int _pid; | |
private bool _keepRunning; | |
private int _sleepTime; | |
private MDbgEngine _debugger; | |
private MDbgProcess _process; | |
public IList<ThreadSnapshot> _snapshots; | |
public IList<ThreadSnapshot> Snapshots { get { return _snapshots; } } | |
public Collector(int pid) { | |
_pid = pid; | |
_keepRunning = true; | |
_sleepTime = 50; | |
_debugger = new MDbgEngine(); | |
_process = null; | |
_snapshots = new List<ThreadSnapshot>(); | |
} | |
public void Start() { | |
try { | |
_process = null; | |
_process = _debugger.Attach(_pid, | |
MdbgVersionPolicy.GetDefaultAttachVersion(_pid)); | |
} catch (Exception ex) { | |
throw new ArgumentException(string.Format( | |
"attach process failed: {0}", ex)); | |
} | |
_process.Go().WaitOne(); | |
_keepRunning = true; | |
while (_keepRunning) { | |
_process.AsyncStop().WaitOne(); | |
foreach (MDbgThread thread in _process.Threads) { | |
var snapshot = ThreadSnapshot.GetThreadSnapshot(thread); | |
if (snapshot.StackTrace.Count > 0) { | |
_snapshots.Add(snapshot); | |
} | |
} | |
_process.Go(); | |
Thread.Sleep(_sleepTime); | |
} | |
} | |
public void Stop() { | |
_keepRunning = false; | |
} | |
public void Cleanup() { | |
var process = Interlocked.Exchange(ref _process, null); | |
if (process != null) { | |
process.AsyncStop().WaitOne(); | |
process.Detach().WaitOne(); | |
} | |
} | |
} | |
class Analyzer { | |
private IList<ThreadSnapshot> _snapshots; | |
public class HotFunction { | |
public string Name { get; set; } | |
public int Count { get; set; } | |
public override string ToString() { | |
return string.Format("{0}\t{1}", Count, Name); | |
} | |
} | |
public class HotTree { | |
public string Name { get; set; } | |
public int Count { get; set; } | |
public IDictionary<string, HotTree> Childs { get; set; } | |
public HotTree() { | |
Childs = new Dictionary<string, HotTree>(); | |
} | |
public void AddChilds(IList<string> stackTrace, int index, int count) { | |
Count += count; | |
if (index < 0) { | |
return; | |
} | |
var name = stackTrace[index]; | |
HotTree child; | |
if (!Childs.TryGetValue(name, out child)) { | |
child = new HotTree() { Name = name }; | |
Childs[name] = child; | |
} | |
child.AddChilds(stackTrace, index - 1, count); | |
} | |
private void AppendToStringBuilder( | |
StringBuilder builder, int indent, int totalCount) { | |
builder.Append('\t', indent); | |
builder.AppendFormat("{0}% {1} ({2})", | |
((decimal)Count / totalCount).ToString("F"), | |
Name, | |
Count); | |
builder.AppendLine(); | |
foreach (var child in Childs.Values.OrderByDescending(x => x.Count)) { | |
child.AppendToStringBuilder(builder, indent + 1, totalCount); | |
} | |
} | |
public override string ToString() { | |
var builder = new StringBuilder(); | |
AppendToStringBuilder(builder, 0, Math.Max(Count, 1)); | |
return builder.ToString(); | |
} | |
} | |
public enum CostType { | |
Count, | |
UserTime, | |
KernelTime, | |
TotalTime | |
} | |
public Analyzer(IList<ThreadSnapshot> snapshots) { | |
_snapshots = snapshots; | |
} | |
public IList<HotFunction> GetHotFunctions(int count) { | |
var summary = new Dictionary<string, HotFunction>(); | |
foreach (var name in _snapshots.SelectMany(x => x.StackTrace)) { | |
HotFunction function; | |
if (summary.TryGetValue(name, out function)) { | |
++function.Count; | |
} else { | |
summary[name] = new HotFunction() { Name = name, Count = 1 }; | |
} | |
} | |
return summary.Select(x => x.Value) | |
.OrderByDescending(x => x.Count) | |
.Take(count).ToList(); | |
} | |
public void DumpHotFunctions(string filename, int count) { | |
var hotFunctions = GetHotFunctions(count); | |
var content = "Samples\tName\r\n"; | |
content += string.Join("\r\n", hotFunctions); | |
File.WriteAllText(filename, content); | |
} | |
public HotTree GetHotTree(CostType costType) { | |
var hotTree = new HotTree() { Name = "All" }; | |
var snapshortGroups = _snapshots.GroupBy(x => x.ThreadId); | |
foreach (var snapshots in snapshortGroups) { | |
long prevTime = 0; | |
foreach (var snapshot in snapshots) { | |
var index = snapshot.StackTrace.Count - 1; | |
if (costType == CostType.Count) { | |
hotTree.AddChilds(snapshot.StackTrace, index, 1); | |
} else { | |
long time; | |
if (costType == CostType.UserTime) { | |
time = snapshot.UserTime; | |
} else if (costType == CostType.KernelTime) { | |
time = snapshot.KernelTime; | |
} else if (costType == CostType.TotalTime) { | |
time = snapshot.UserTime + snapshot.KernelTime; | |
} else { | |
throw new ArgumentException(string.Format( | |
"unsupported cost type:", costType)); | |
} | |
if (prevTime > 0 && prevTime < time) { | |
var cost = (int)(time - prevTime); | |
hotTree.AddChilds(snapshot.StackTrace, index, cost); | |
} | |
prevTime = time; | |
} | |
} | |
} | |
return hotTree; | |
} | |
public void DumpHotTree(string filename, CostType costType) { | |
var hotTree = GetHotTree(costType); | |
var content = hotTree.ToString(); | |
File.WriteAllText(filename, content); | |
} | |
} | |
static class Program { | |
private static void Main(string[] args) { | |
Collector collector = null; | |
try { | |
if (args.Length < 1) { | |
Console.WriteLine("CpuProfiler.exe PID"); | |
return; | |
} | |
var pid = int.Parse(args[0]); | |
collector = new Collector(pid); | |
Console.CancelKeyPress += (_, e) => { | |
e.Cancel = true; | |
collector.Stop(); | |
}; | |
collector.Start(); | |
collector.Cleanup(); | |
Console.WriteLine("captured {0} samples", collector.Snapshots.Count); | |
var analyzer = new Analyzer(collector.Snapshots); | |
analyzer.DumpHotFunctions("Result.HotFunctions.txt", 100); | |
analyzer.DumpHotTree("Result.HotTree.Count.txt", Analyzer.CostType.Count); | |
analyzer.DumpHotTree("Result.HotTree.UserTime.txt", Analyzer.CostType.UserTime); | |
analyzer.DumpHotTree("Result.HotTree.KernelTime.txt", Analyzer.CostType.KernelTime); | |
analyzer.DumpHotTree("Result.HotTree.TotalTime.txt", Analyzer.CostType.TotalTime); | |
Console.WriteLine("done"); | |
} catch (Exception ex) { | |
Console.WriteLine(ex.ToString()); | |
} finally { | |
if (collector != null) { | |
collector.Cleanup(); | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment