아래 코드는 작업관리자의 "사용자"탭처럼 사용자별 프로세스의 메모리 용량을 체크하는 소스입니다.
회사의 개발PC에서는 아주 잘 작동하고 메모리 크기도 98% 정확도로 표시가됩니다.
그래서 다른 PC에 설치를 해서 테스트를 해보니 어떤 PC에서는 프로세스 한개의 메모리를 체크하는데 10초~30초정도 걸리고, 어떤 PC에서는 "카운터는 단일 인스턴스가 아니며 인스턴스 이름을 지정해야 합니다."라는 에러가 발생합니다.
아무리 검색해도 딱히 방법이 없던데 혹시나 경험해보신분이 있다면 조언 부탁드립니다.
아래는 컴파일 가능한 전체 소스입니다. (콘솔앱, 닷넷4.8.1에서 테스트)
static Dictionary<int, string> processId2MemoryProcessName = new Dictionary<int, string>();
[DllImport("psapi.dll", SetLastError = true)]
static extern bool GetProcessMemoryInfo(IntPtr hProcess, out PROCESS_MEMORY_COUNTERS_EX counters, uint size);
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_MEMORY_COUNTERS_EX
{
public uint cb;
public uint PageFaultCount;
public UIntPtr PeakWorkingSetSize;
public UIntPtr WorkingSetSize;
public UIntPtr QuotaPeakPagedPoolUsage;
public UIntPtr QuotaPagedPoolUsage;
public UIntPtr QuotaPeakNonPagedPoolUsage;
public UIntPtr QuotaNonPagedPoolUsage;
public UIntPtr PagefileUsage;
public UIntPtr PeakPagefileUsage;
public UIntPtr PrivateUsage;
}
[DllImport("Wtsapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern bool WTSQuerySessionInformation(
IntPtr hServer,
int sessionId,
WTS_INFO_CLASS wtsInfoClass,
out IntPtr ppBuffer,
out uint pbytesReturned);
[DllImport("Wtsapi32.dll")]
static extern void WTSFreeMemory(IntPtr pointer);
enum WTS_INFO_CLASS
{
WTSUserName = 5
}
static void Main(string[] args)
{
CheckUserMemory();
Console.ReadLine();
}
static void CheckUserMemory()
{
Dictionary<string, long> userMemory = new Dictionary<string, long>();
Process[] processes = Process.GetProcesses();
foreach (Process p in processes)
{
int sessionId = p.SessionId;
string user = GetUserName(sessionId);
if (string.IsNullOrWhiteSpace(user)) continue;
long privateWS = CurrentMemoryUsage(p);
if (userMemory.ContainsKey(user))
userMemory[user] += privateWS;
else
userMemory.Add(user, privateWS);
}
foreach (var entry in userMemory)
{
double memMB = entry.Value / (1024.0 * 1024.0);
Console.WriteLine($"{entry.Key} : {memMB:F1} MB");
}
}
static string GetUserName(int sessionId)
{
IntPtr buffer;
uint bytesReturned;
bool result = WTSQuerySessionInformation(IntPtr.Zero, sessionId, WTS_INFO_CLASS.WTSUserName, out buffer, out bytesReturned);
if (!result) return null;
string userName = Marshal.PtrToStringUni(buffer);
WTSFreeMemory(buffer);
return userName;
}
static long CurrentMemoryUsage(Process proc)
{
long currentMemoryUsage = 0L;
var nameToUseForMemory = GetNameToUseForMemory(proc);
using (var procPerfCounter = new PerformanceCounter("Process", "Working Set - Private", nameToUseForMemory))
{
//에러 발생 : "카운터는 단일 인스턴스가 아니며 인스턴스 이름을 지정해야 합니다."
currentMemoryUsage = procPerfCounter.RawValue;
}
return currentMemoryUsage;
}
static string GetNameToUseForMemory(Process proc)
{
if (processId2MemoryProcessName.ContainsKey(proc.Id))
return processId2MemoryProcessName[proc.Id];
var nameToUseForMemory = string.Empty;
var category = new PerformanceCounterCategory("Process");
var instanceNames = category.GetInstanceNames().Where(x => x.Contains(proc.ProcessName));
foreach (var instancename in instanceNames)
{
// PerformanceCounter가 엄청 느린 부분
using (var procPerfCounter = new PerformanceCounter("Process", "ID Process", instancename, true))
{
if (procPerfCounter.RawValue != proc.Id) continue;
nameToUseForMemory = instancename;
break;
}
}
if (!processId2MemoryProcessName.ContainsKey(proc.Id))
{
processId2MemoryProcessName.Add(proc.Id, nameToUseForMemory);
}
return nameToUseForMemory;
}