Skip to content

Instantly share code, notes, and snippets.

@303248153
Created August 23, 2018 10:26
Show Gist options
  • Save 303248153/b4164b9cb812b9f02df8d1601300be71 to your computer and use it in GitHub Desktop.
Save 303248153/b4164b9cb812b9f02df8d1601300be71 to your computer and use it in GitHub Desktop.
Cpu Profiler for .Net Framework on Windows
// 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