--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Microsoft.Diagnostics.Tools.Counters
+{
+ public interface ICounterPayload
+ {
+ string GetName();
+ string GetValue();
+ string GetDisplay();
+ }
+
+
+ class CounterPayload : ICounterPayload
+ {
+ public string m_Name;
+ public string m_Value;
+ public string m_DisplayName;
+ public CounterPayload(IDictionary<string, object> payloadFields)
+ {
+ m_Name = payloadFields["Name"].ToString();
+ m_Value = payloadFields["Mean"].ToString();
+ m_DisplayName = payloadFields["DisplayName"].ToString();
+ }
+
+ public string GetName()
+ {
+ return m_Name;
+ }
+
+ public string GetValue()
+ {
+ return m_Value;
+ }
+
+ public string GetDisplay()
+ {
+ return m_DisplayName;
+ }
+ }
+
+ class IncrementingCounterPayload : ICounterPayload
+ {
+ public string m_Name;
+ public string m_Value;
+ public string m_DisplayName;
+ public string m_DisplayRateTimeScale;
+ public IncrementingCounterPayload(IDictionary<string, object> payloadFields)
+ {
+ m_Name = payloadFields["Name"].ToString();
+ m_Value = payloadFields["Increment"].ToString();
+ m_DisplayName = payloadFields["DisplayName"].ToString();
+ m_DisplayRateTimeScale = TimeSpan.Parse(payloadFields["DisplayRateTimeScale"].ToString()).ToString("%s' sec'");
+ }
+
+ public string GetName()
+ {
+ return m_Name;
+ }
+
+ public string GetValue()
+ {
+ return m_Value;
+ }
+
+ public string GetDisplay()
+ {
+ return $"{m_DisplayName} / {m_DisplayRateTimeScale}";
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.Diagnostics.Tools.Counters
+{
+ public class CounterProvider
+ {
+ public string Name { get; }
+ public string Description { get; }
+ public string Keywords { get; }
+ public string Level { get; }
+ public Dictionary<string, CounterProfile> Counters { get; }
+
+ public CounterProvider(string name, string description, string keywords, string level, IEnumerable<CounterProfile> counters)
+ {
+ Name = name;
+ Description = description;
+ Keywords = keywords;
+ Level = level;
+ Counters = new Dictionary<string, CounterProfile>();
+ foreach (CounterProfile counter in counters)
+ {
+ Counters.Add(counter.Name, counter);
+ }
+ }
+
+ public string TryGetDisplayName(string counterName)
+ {
+ if (Counters.ContainsKey(counterName))
+ return Counters[counterName].DisplayName;
+ return null;
+ }
+
+ public string ToProviderString(int interval)
+ {
+ return $"{Name}:{Keywords}:{Level}:EventCounterIntervalSec={interval}";
+ }
+
+ public static string SerializeUnknownProviderName(string unknownCounterProviderName, int interval)
+ {
+ return $"{unknownCounterProviderName}:ffffffff:4:EventCounterIntervalSec={interval}";
+ }
+
+ public IReadOnlyList<CounterProfile> GetAllCounters() => Counters.Values.ToList();
+
+ }
+
+ public class CounterProfile
+ {
+ public string Name { get; set; }
+ public string DisplayName { get; set; }
+ public string Description { get; set; }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Tracing;
+using System.Linq;
+using Microsoft.Diagnostics.Tracing.Parsers;
+
+namespace Microsoft.Diagnostics.Tools.Counters
+{
+ internal static class KnownData
+ {
+ private static readonly IReadOnlyDictionary<string, CounterProvider> _knownProviders =
+ CreateKnownProviders().ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase);
+
+
+ private static IEnumerable<CounterProvider> CreateKnownProviders()
+ {
+ yield return new CounterProvider(
+ "System.Runtime", // Name
+ "A default set of performance counters provided by the .NET runtime.", // Description
+ "0xffffffff", // Keywords
+ "5", // Level
+ new[] { // Counters
+ new CounterProfile{ Name="cpu-usage", Description="Amount of time the process has utilized the CPU (ms)", DisplayName="CPU Usage (%)" },
+ new CounterProfile{ Name="working-set", Description="Amount of working set used by the process (MB)", DisplayName="Working Set (MB)" },
+ new CounterProfile{ Name="gc-heap-size", Description="Total heap size reported by the GC (MB)", DisplayName="GC Heap Size (MB)" },
+ new CounterProfile{ Name="gen-0-gc-count", Description="Number of Gen 0 GCs / sec", DisplayName="Gen 0 GC / sec" },
+ new CounterProfile{ Name="gen-1-gc-count", Description="Number of Gen 1 GCs / sec", DisplayName="Gen 1 GC / sec" },
+ new CounterProfile{ Name="gen-2-gc-count", Description="Number of Gen 2 GCs / sec", DisplayName="Gen 2 GC / sec" },
+ new CounterProfile{ Name="exception-count", Description="Number of Exceptions / sec", DisplayName="Exceptions / sec" },
+ });
+ // TODO: Add more providers (ex. ASP.NET ones)
+ }
+
+ public static IReadOnlyList<CounterProvider> GetAllProviders() => _knownProviders.Values.ToList();
+
+ public static bool TryGetProvider(string providerName, out CounterProvider provider) => _knownProviders.TryGetValue(providerName, out provider);
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.CommandLine;
+using System.CommandLine.Builder;
+using System.CommandLine.Invocation;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Text;
+using System.IO;
+
+using Microsoft.Diagnostics.Tools.RuntimeClient;
+using Microsoft.Diagnostics.Tracing;
+
+namespace Microsoft.Diagnostics.Tools.Counters
+{
+ internal class Program
+ {
+ private static ulong _sessionId;
+ private static int threshold;
+ private static int pid;
+
+
+ private static void Main(string[] args)
+ {
+ if(args.Length<2)
+ {
+ Console.WriteLine("triggerdump <pid> <mem threshold in MB>");
+ }
+ else
+ {
+ pid = Convert.ToInt32(args[0]);
+ threshold = Convert.ToInt32(args[1]);
+ CounterProvider provider=null;
+ StringBuilder sb = new StringBuilder("");
+ KnownData.TryGetProvider("System.Runtime", out provider);
+ string prov=provider.ToProviderString(1);
+
+ Task monitorTask = new Task(() =>
+ {
+ var configuration = new SessionConfiguration(
+ circularBufferSizeMB: 1000,
+ outputPath: "",
+ providers: Trace.Extensions.ToProviders(prov));
+
+
+ var binaryReader = EventPipeClient.CollectTracing(Int32.Parse(args[0]), configuration, out _sessionId);
+ EventPipeEventSource source = new EventPipeEventSource(binaryReader);
+ source.Dynamic.All += Dynamic_All;
+ source.Process();
+ });
+
+ Task commandTask = new Task(() =>
+ {
+ while(true)
+ {
+ while (!Console.KeyAvailable) { }
+ ConsoleKey cmd = Console.ReadKey(true).Key;
+ if (cmd == ConsoleKey.Q)
+ {
+ break;
+ }
+ }
+ });
+
+ monitorTask.Start();
+ commandTask.Start();
+ commandTask.Wait();
+
+ try
+ {
+ EventPipeClient.StopTracing(Int32.Parse(args[0]), _sessionId);
+ }
+ catch (System.IO.EndOfStreamException) {}
+ }
+ }
+
+ private static void Dynamic_All(TraceEvent obj)
+ {
+ if (obj.EventName.Equals("EventCounters"))
+ {
+ IDictionary<string, object> payloadVal = (IDictionary<string, object>)(obj.PayloadValue(0));
+ IDictionary<string, object> payloadFields = (IDictionary<string, object>)(payloadVal["Payload"]);
+
+ ICounterPayload payload = payloadFields.Count == 6 ? (ICounterPayload)new IncrementingCounterPayload(payloadFields) : (ICounterPayload)new CounterPayload(payloadFields);
+ string displayName = payload.GetDisplay();
+ if (string.IsNullOrEmpty(displayName))
+ {
+ displayName = payload.GetName();
+ }
+
+ if(string.Compare(displayName, "GC Heap Size") == 0 && Convert.ToInt32(payload.GetValue())>threshold)
+ {
+ Console.WriteLine("Memory threshold has been breached....");
+ System.Diagnostics.Process process = System.Diagnostics.Process.GetProcessById(pid);
+
+ System.Diagnostics.ProcessModule coreclr = process.Modules.Cast<System.Diagnostics.ProcessModule>().FirstOrDefault(m => string.Equals(m.ModuleName, "libcoreclr.so"));
+ if (coreclr == null)
+ {
+ Console.WriteLine("Unable to locate .NET runtime associated with this process!");
+ Environment.Exit(1);
+ }
+ else
+ {
+ string runtimeDirectory = Path.GetDirectoryName(coreclr.FileName);
+ string createDumpPath = Path.Combine(runtimeDirectory, "createdump");
+ if (!File.Exists(createDumpPath))
+ {
+ Console.WriteLine("Unable to locate 'createdump' tool in '{runtimeDirectory}'");
+ Environment.Exit(1);
+ }
+
+ var createdump = new System.Diagnostics.Process()
+ {
+ StartInfo = new System.Diagnostics.ProcessStartInfo()
+ {
+ FileName = createDumpPath,
+ Arguments = $"--name coredump --withheap {pid}",
+ },
+ EnableRaisingEvents = true,
+ };
+
+ createdump.Start();
+ createdump.WaitForExit();
+
+ Environment.Exit(0);
+ }
+ }
+ }
+ }
+
+ }
+}
--- /dev/null
+{
+ "rollForwardOnNoCandidateFx": 2
+}
\ No newline at end of file
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>netcoreapp2.1</TargetFramework>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <ToolCommandName>triggerdump</ToolCommandName>
+ <RootNamespace>Diagnostics.TriggerDump</RootNamespace>
+ <Description>Trigger dump</Description>
+ <PackageTags>Diagnostic</PackageTags>
+ <PackageReleaseNotes>$(Description)</PackageReleaseNotes>
+ <TargetName>triggerdump</TargetName>
+ <OutputType>Exe</OutputType>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <Compile Include="$(MSBuildThisFileDirectory)..\..\..\..\src\Tools\dotnet-trace\Extensions.cs" Link="Extensions.cs" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\..\..\src\Microsoft.Diagnostic.Repl\Microsoft.Diagnostic.Repl.csproj" />
+ <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\..\..\src\Microsoft.Diagnostics.Tools.RuntimeClient\Microsoft.Diagnostics.Tools.RuntimeClient.csproj" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="2.0.41" />
+ </ItemGroup>
+
+</Project>