Skip to content

Commit 5385301

Browse files
authored
Add ExceptionlessWindowsEnvironmentInfoCollector to collect Handle, UserObjects, and GDIObjects (#270)
* Add ExceptionlessWindowsEnvironmentInfoCollector to collect Handle, UserObjects, and GDIObjects. * Remove redundant white space * Changes from review and other refactors * changes from review * changes from review * Include peak counts * changes from review
1 parent 65cc535 commit 5385301

File tree

3 files changed

+104
-25
lines changed

3 files changed

+104
-25
lines changed

src/Exceptionless/Services/DefaultEnvironmentInfoCollector.cs

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,23 @@
1313
namespace Exceptionless.Services {
1414
public class DefaultEnvironmentInfoCollector : IEnvironmentInfoCollector {
1515
private static EnvironmentInfo _environmentInfo;
16-
private readonly ExceptionlessConfiguration _config;
17-
private readonly IExceptionlessLog _log;
16+
protected ExceptionlessConfiguration Config { get; }
17+
protected IExceptionlessLog Log { get; }
1818

1919
public DefaultEnvironmentInfoCollector(ExceptionlessConfiguration config, IExceptionlessLog log) {
20-
_config = config;
21-
_log = log;
20+
Config = config;
21+
Log = log;
2222
}
2323

24-
public EnvironmentInfo GetEnvironmentInfo() {
24+
public virtual EnvironmentInfo GetEnvironmentInfo() {
2525
if (_environmentInfo != null) {
2626
PopulateThreadInfo(_environmentInfo);
2727
PopulateMemoryInfo(_environmentInfo);
2828
return _environmentInfo;
2929
}
3030

3131
var info = new EnvironmentInfo();
32+
PopulateApplicationInfo(info);
3233
PopulateRuntimeInfo(info);
3334
PopulateProcessInfo(info);
3435
PopulateThreadInfo(info);
@@ -42,16 +43,16 @@ private void PopulateApplicationInfo(EnvironmentInfo info) {
4243
try {
4344
info.Data.Add("AppDomainName", AppDomain.CurrentDomain.FriendlyName);
4445
} catch (Exception ex) {
45-
_log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get AppDomain friendly name. Error message: {0}", ex.Message);
46+
Log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get AppDomain friendly name. Error message: {0}", ex.Message);
4647
}
4748

48-
if (_config.IncludeIpAddress) {
49+
if (Config.IncludeIpAddress) {
4950
try {
5051
IPHostEntry hostEntry = Dns.GetHostEntryAsync(Dns.GetHostName()).ConfigureAwait(false).GetAwaiter().GetResult();
5152
if (hostEntry != null && hostEntry.AddressList.Any())
5253
info.IpAddress = String.Join(", ", hostEntry.AddressList.Where(x => x.AddressFamily == AddressFamily.InterNetwork).Select(a => a.ToString()).ToArray());
5354
} catch (Exception ex) {
54-
_log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get ip address. Error message: {0}", ex.Message);
55+
Log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get ip address. Error message: {0}", ex.Message);
5556
}
5657
}
5758
}
@@ -60,44 +61,46 @@ private void PopulateProcessInfo(EnvironmentInfo info) {
6061
try {
6162
info.ProcessorCount = Environment.ProcessorCount;
6263
} catch (Exception ex) {
63-
_log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get processor count. Error message: {0}", ex.Message);
64+
Log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get processor count. Error message: {0}", ex.Message);
6465
}
6566

6667
try {
67-
Process process = Process.GetCurrentProcess();
68-
info.ProcessName = process.ProcessName;
69-
info.ProcessId = process.Id.ToString(NumberFormatInfo.InvariantInfo);
68+
using (Process process = Process.GetCurrentProcess()) {
69+
info.ProcessName = process.ProcessName;
70+
info.ProcessId = process.Id.ToString(NumberFormatInfo.InvariantInfo);
71+
}
7072
} catch (Exception ex) {
71-
_log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get process name or id. Error message: {0}", ex.Message);
73+
Log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get process name or id. Error message: {0}", ex.Message);
7274
}
7375

7476
try {
7577
info.CommandLine = Environment.CommandLine;
7678
} catch (Exception ex) {
77-
_log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get command line. Error message: {0}", ex.Message);
79+
Log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get command line. Error message: {0}", ex.Message);
7880
}
7981
}
8082

8183
private void PopulateThreadInfo(EnvironmentInfo info) {
8284
try {
8385
info.ThreadId = Thread.CurrentThread.ManagedThreadId.ToString(NumberFormatInfo.InvariantInfo);
8486
} catch (Exception ex) {
85-
_log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get thread id. Error message: {0}", ex.Message);
87+
Log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get thread id. Error message: {0}", ex.Message);
8688
}
8789

8890
try {
8991
info.ThreadName = Thread.CurrentThread.Name;
9092
} catch (Exception ex) {
91-
_log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get current thread name. Error message: {0}", ex.Message);
93+
Log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get current thread name. Error message: {0}", ex.Message);
9294
}
9395
}
9496

9597
private void PopulateMemoryInfo(EnvironmentInfo info) {
9698
try {
97-
Process process = Process.GetCurrentProcess();
98-
info.ProcessMemorySize = process.PrivateMemorySize64;
99+
using (Process process = Process.GetCurrentProcess()) {
100+
info.ProcessMemorySize = process.PrivateMemorySize64;
101+
}
99102
} catch (Exception ex) {
100-
_log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get process memory size. Error message: {0}", ex.Message);
103+
Log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get process memory size. Error message: {0}", ex.Message);
101104
}
102105

103106
#if NET45
@@ -116,7 +119,7 @@ private void PopulateMemoryInfo(EnvironmentInfo info) {
116119
info.AvailablePhysicalMemory = Convert.ToInt64(computerInfo.AvailablePhysicalMemory);
117120
}
118121
} catch (Exception ex) {
119-
_log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get physical memory. Error message: {0}", ex.Message);
122+
Log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get physical memory. Error message: {0}", ex.Message);
120123
}
121124
#endif
122125
}
@@ -142,11 +145,11 @@ private void PopulateRuntimeInfo(EnvironmentInfo info) {
142145
info.Data["ProcessArchitecture"] = RuntimeInformation.ProcessArchitecture.ToString();
143146
#endif
144147

145-
if (_config.IncludeMachineName) {
148+
if (Config.IncludeMachineName) {
146149
try {
147150
info.MachineName = Environment.MachineName;
148151
} catch (Exception ex) {
149-
_log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get machine name. Error message: {0}", ex.Message);
152+
Log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get machine name. Error message: {0}", ex.Message);
150153
}
151154
}
152155

@@ -163,7 +166,7 @@ private void PopulateRuntimeInfo(EnvironmentInfo info) {
163166
computerInfo = new Microsoft.VisualBasic.Devices.ComputerInfo();
164167
#endif
165168
} catch (Exception ex) {
166-
_log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get computer info. Error message: {0}", ex.Message);
169+
Log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get computer info. Error message: {0}", ex.Message);
167170
}
168171

169172
if (computerInfo == null)
@@ -182,7 +185,7 @@ private void PopulateRuntimeInfo(EnvironmentInfo info) {
182185
info.Architecture = Is64BitOperatingSystem() ? "x64" : "x86";
183186
#endif
184187
} catch (Exception ex) {
185-
_log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get populate runtime info. Error message: {0}", ex.Message);
188+
Log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get populate runtime info. Error message: {0}", ex.Message);
186189
}
187190
}
188191

@@ -225,7 +228,7 @@ private bool Is64BitOperatingSystem() {
225228

226229
return ((methodExist && KernelNativeMethods.IsWow64Process(KernelNativeMethods.GetCurrentProcess(), out is64)) && is64);
227230
} catch (Exception ex) {
228-
_log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get CPU architecture. Error message: {0}", ex.Message);
231+
Log.FormattedWarn(typeof(DefaultEnvironmentInfoCollector), "Unable to get CPU architecture. Error message: {0}", ex.Message);
229232
}
230233

231234
return false;
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using System;
2+
using System.Diagnostics;
3+
using Exceptionless.Logging;
4+
using Exceptionless.Models.Data;
5+
using Exceptionless.Services;
6+
7+
namespace Exceptionless {
8+
public class ExceptionlessWindowsEnvironmentInfoCollector : DefaultEnvironmentInfoCollector {
9+
public ExceptionlessWindowsEnvironmentInfoCollector(ExceptionlessConfiguration config, IExceptionlessLog log) : base(config, log) { }
10+
11+
public override EnvironmentInfo GetEnvironmentInfo() {
12+
EnvironmentInfo info = base.GetEnvironmentInfo();
13+
PopulateHandleInfo(info);
14+
return info;
15+
}
16+
17+
private void PopulateHandleInfo(EnvironmentInfo info) {
18+
try {
19+
using (Process currentProcess = Process.GetCurrentProcess()) {
20+
info.Data["Handles"] = currentProcess.HandleCount;
21+
info.Data["UserObjects"] = User32NativeMethods.GetGuiResources(currentProcess.Handle, (int)User32NativeMethods.UIFlags.UserObjectCount);
22+
info.Data["GDIObjects"] = User32NativeMethods.GetGuiResources(currentProcess.Handle, (int)User32NativeMethods.UIFlags.GDIObjectCount);
23+
info.Data["UserObjectsPeak"] = User32NativeMethods.GetGuiResources(currentProcess.Handle, (int)User32NativeMethods.UIFlags.UserObjectsPeakCount);
24+
info.Data["GDIObjectsPeak"] = User32NativeMethods.GetGuiResources(currentProcess.Handle, (int)User32NativeMethods.UIFlags.GDIObjectsPeakCount);
25+
}
26+
}
27+
catch (Exception ex) {
28+
Log.FormattedWarn(typeof(ExceptionlessWindowsEnvironmentInfoCollector), "Unable to get process handles, User objects, or GDI objects. Error message: {0}", ex.Message);
29+
}
30+
}
31+
32+
private static class User32NativeMethods {
33+
#region User32
34+
35+
/// <summary>
36+
/// <para>
37+
/// Retrieves the count of handles to graphical user interface
38+
/// (GUI) objects in use by the specified process.
39+
/// </para>
40+
/// API reference: <see href="https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getguiresources"/>
41+
/// </summary>
42+
/// <param name="hProcess"></param>
43+
/// <param name="uiFlags"></param>
44+
/// <returns></returns>
45+
[System.Runtime.InteropServices.DllImport("User32.dll")]
46+
public static extern int GetGuiResources(IntPtr hProcess, uint uiFlags);
47+
48+
#endregion
49+
50+
/// <summary>
51+
/// Enum representing the possible values to pass to <see cref="GetGuiResources(IntPtr, UInt32)"/>.
52+
/// </summary>
53+
public enum UIFlags {
54+
/// <summary>
55+
/// Return the count of GDI objects.
56+
/// </summary>
57+
GDIObjectCount = 0,
58+
/// <summary>
59+
/// Return the count of USER objects.
60+
/// </summary>
61+
UserObjectCount = 1,
62+
/// <summary>
63+
/// Return the peak count of GDI objects.
64+
/// </summary>
65+
GDIObjectsPeakCount = 2,
66+
/// <summary>
67+
/// Return the peak count of USER objects.
68+
/// </summary>
69+
UserObjectsPeakCount = 4,
70+
}
71+
72+
}
73+
}
74+
}

src/Platforms/Exceptionless.Windows/ExceptionlessWindowsExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Exceptionless.Dialogs;
55
using Exceptionless.Logging;
66
using Exceptionless.Plugins.Default;
7+
using Exceptionless.Services;
78
using Exceptionless.Windows.Extensions;
89

910
namespace Exceptionless {
@@ -18,6 +19,7 @@ public static void Register(this ExceptionlessClient client, bool showDialog = t
1819
throw new ArgumentNullException(nameof(client));
1920

2021
client.Configuration.AddPlugin<SetEnvironmentUserPlugin>();
22+
client.Configuration.Resolver.Register<IEnvironmentInfoCollector, ExceptionlessWindowsEnvironmentInfoCollector>();
2123
client.Startup();
2224

2325
client.RegisterApplicationThreadExceptionHandler();

0 commit comments

Comments
 (0)