#include "pch.h"
#include "System/Diagnostics/SystemMetrics.h"
#include "System/Diagnostics/ProcessStreamer.h"
#include "System/ArgumentException.h"
#include "System/SystemException.h"
#include "System/Utils/StringConvert.h"
#include <algorithm>
#include <vector>
#include <map>

#if defined(_WIN32)
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <winternl.h>
#include <tlhelp32.h>
#include <psapi.h>
#include <iphlpapi.h>
#include <pdh.h>
#include <winsvc.h>
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "psapi.lib")
#pragma comment(lib, "pdh.lib")
#pragma comment(lib, "advapi32.lib")
#else
#include <fstream>
#include <sstream>
#include <dirent.h>
#include <unistd.h>
#include <sys/statvfs.h>
#include <sys/types.h>
#include <arpa/inet.h>
#endif

namespace DotNetDupe {
    namespace System {
        namespace Diagnostics {

            SystemMetrics::SystemMetrics() {}
            SystemMetrics::~SystemMetrics() {}

            SmartPointer<ProcessStreamer> SystemMetrics::CreateProcessStreamer(const ProcessStreamOptions& options) {
                return SmartPointer<ProcessStreamer>::NewShared(options);
            }

            void SystemMetrics::EnumerateProcessesAsync(const Action<const ProcessInfo&>& fnOnProcess, const Action<>& fnOnComplete) {
                ProcessStreamOptions options;
                options.eDetailLevel = ProcessMetricsDetail::FastDiscoveryOnly;
                auto pStreamer = CreateProcessStreamer(options);
                pStreamer->OnProcess(fnOnProcess);
                if (fnOnComplete) pStreamer->OnCompleted(fnOnComplete);
                pStreamer->Start();
            }

#if defined(_WIN32)
            struct ProcessCpuSample {
                uint64_t uProcessTime;
                uint64_t uSystemTime;
            };
            static std::map<int, ProcessCpuSample> s_mapProcessCpuSamples;

            struct SystemMetricsWin32Helper {
                static MemoryInfo GetSystemMemory() {
                    MemoryInfo info;
                    MEMORYSTATUSEX memStatus;
                    memStatus.dwLength = sizeof(MEMORYSTATUSEX);
                    if (::GlobalMemoryStatusEx(&memStatus)) {
                        info.dMemoryUsagePercent = static_cast<double>(memStatus.dwMemoryLoad);
                        info.uMemoryTotalBytes = static_cast<unsigned long long>(memStatus.ullTotalPhys);
                        info.uMemoryUsedBytes = static_cast<unsigned long long>(memStatus.ullTotalPhys - memStatus.ullAvailPhys);
                    }
                    return info;
                }

                static double CalculateCpuDelta(uint64_t uIdle, uint64_t uKernel, uint64_t uUser) {
                    static uint64_t s_uPrevIdle = 0, s_uPrevTotal = 0;
                    uint64_t uTotal = uKernel + uUser;
                    double dCpu = 0.0;
                    if (s_uPrevTotal > 0 && uTotal > s_uPrevTotal) {
                        uint64_t uTotalDiff = uTotal - s_uPrevTotal;
                        uint64_t uIdleDiff = uIdle - s_uPrevIdle;
                        dCpu = static_cast<double>((uTotalDiff - uIdleDiff) * 100.0 / uTotalDiff);
                    } else if (uTotal > 0) {
                        dCpu = static_cast<double>((uTotal - uIdle) * 100.0 / uTotal);
                    }
                    s_uPrevIdle = uIdle; s_uPrevTotal = uTotal;
                    return (dCpu < 0.0) ? 0.0 : ((dCpu > 100.0) ? 100.0 : dCpu);
                }

                static double GetSystemCpu() {
                    FILETIME ftIdle, ftKernel, ftUser;
                    if (!::GetSystemTimes(&ftIdle, &ftKernel, &ftUser)) return 0.0;
                    uint64_t uIdle = (static_cast<uint64_t>(ftIdle.dwHighDateTime) << 32) | ftIdle.dwLowDateTime;
                    uint64_t uKernel = (static_cast<uint64_t>(ftKernel.dwHighDateTime) << 32) | ftKernel.dwLowDateTime;
                    uint64_t uUser = (static_cast<uint64_t>(ftUser.dwHighDateTime) << 32) | ftUser.dwLowDateTime;
                    return CalculateCpuDelta(uIdle, uKernel, uUser);
                }

                static void InitDiskQuery(HQUERY& hQuery, HCOUNTER& hRead, HCOUNTER& hWrite) {
                    static bool s_bPdhInitialized = false;
                    if (!s_bPdhInitialized && ::PdhOpenQueryW(NULL, 0, &hQuery) == ERROR_SUCCESS) {
                        ::PdhAddEnglishCounterW(hQuery, L"\\PhysicalDisk(_Total)\\Disk Read Bytes/sec", 0, &hRead);
                        ::PdhAddEnglishCounterW(hQuery, L"\\PhysicalDisk(_Total)\\Disk Write Bytes/sec", 0, &hWrite);
                        ::PdhCollectQueryData(hQuery);
                        s_bPdhInitialized = true;
                    }
                }

                static DiskInfo GetSystemDisk() {
                    DiskInfo info;
                    static HQUERY s_hQuery = NULL; static HCOUNTER s_hRead = NULL, s_hWrite = NULL;
                    InitDiskQuery(s_hQuery, s_hRead, s_hWrite);
                    if (s_hQuery && ::PdhCollectQueryData(s_hQuery) == ERROR_SUCCESS) {
                        PDH_FMT_COUNTERVALUE fmtRead, fmtWrite;
                        if (::PdhGetFormattedCounterValue(s_hRead, PDH_FMT_LARGE, NULL, &fmtRead) == ERROR_SUCCESS) info.lDiskReadBytes = static_cast<long long>(fmtRead.largeValue);
                        if (::PdhGetFormattedCounterValue(s_hWrite, PDH_FMT_LARGE, NULL, &fmtWrite) == ERROR_SUCCESS) info.lDiskWriteBytes = static_cast<long long>(fmtWrite.largeValue);
                    }
                    return info;
                }

                static double CalculateNetRate(uint64_t totalOctets) {
                    static uint64_t s_uPrevOctets = 0; static DWORD s_dwPrevTick = 0;
                    DWORD dwNow = ::GetTickCount(); double dMbps = 0.0;
                    if (s_dwPrevTick > 0 && dwNow > s_dwPrevTick && totalOctets >= s_uPrevOctets) {
                        double dElapsedSec = (dwNow - s_dwPrevTick) / 1000.0;
                        dMbps = ((totalOctets - s_uPrevOctets) / dElapsedSec * 8.0) / 1000000.0;
                    }
                    s_uPrevOctets = totalOctets; s_dwPrevTick = dwNow;
                    return dMbps;
                }

                static double GetSystemNetwork() {
                    DWORD dwSize = 0;
                    if (::GetIfTable(NULL, &dwSize, FALSE) != ERROR_INSUFFICIENT_BUFFER) return 0.0;
                    std::vector<uint8_t> buf(dwSize, 0);
                    MIB_IFTABLE* pTable = reinterpret_cast<MIB_IFTABLE*>(buf.data());
                    if (::GetIfTable(pTable, &dwSize, FALSE) != NO_ERROR) return 0.0;
                    uint64_t totalOctets = 0;
                    for (DWORD i = 0; i < pTable->dwNumEntries; ++i) {
                        if (pTable->table[i].dwType != IF_TYPE_SOFTWARE_LOOPBACK && pTable->table[i].dwOperStatus == IF_OPER_STATUS_OPERATIONAL)
                            totalOctets += pTable->table[i].dwInOctets + pTable->table[i].dwOutOctets;
                    }
                    return CalculateNetRate(totalOctets);
                }

                static HANDLE FindProcessHandle(HANDLE hSnapshot, const std::wstring& wTarget, unsigned long dwAccess, int& iOutPid) {
                    PROCESSENTRY32W pe32; pe32.dwSize = sizeof(PROCESSENTRY32W);
                    if (!::Process32FirstW(hSnapshot, &pe32)) return NULL;
                    do {
                        std::wstring wExeFile(pe32.szExeFile);
                        std::transform(wExeFile.begin(), wExeFile.end(), wExeFile.begin(), ::tolower);
                        if (wExeFile == wTarget || wExeFile == wTarget + L".exe") {
                            iOutPid = pe32.th32ProcessID;
                            return ::OpenProcess(dwAccess, FALSE, pe32.th32ProcessID);
                        }
                    } while (::Process32NextW(hSnapshot, &pe32));
                    return NULL;
                }

                static void* OpenProcByName(const String& sProcessName, unsigned long dwAccess, int& iOutPid) {
                    iOutPid = -1;
                    HANDLE hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
                    if (hSnapshot == INVALID_HANDLE_VALUE) return NULL;
                    std::string sTarget = sProcessName.GetRawString() ? sProcessName.GetRawString() : "";
                    std::wstring wTarget(sTarget.begin(), sTarget.end());
                    std::transform(wTarget.begin(), wTarget.end(), wTarget.begin(), ::tolower);
                    HANDLE hResult = FindProcessHandle(hSnapshot, wTarget, dwAccess, iOutPid);
                    ::CloseHandle(hSnapshot);
                    return hResult;
                }

                static String ReadPebCommandLine(HANDLE hProc, PVOID pebBase) {
                    PEB peb; SIZE_T bytesRead = 0;
                    if (!::ReadProcessMemory(hProc, pebBase, &peb, sizeof(peb), &bytesRead)) return String("");
                    RTL_USER_PROCESS_PARAMETERS upp;
                    if (!::ReadProcessMemory(hProc, peb.ProcessParameters, &upp, sizeof(upp), &bytesRead) || !upp.CommandLine.Buffer || upp.CommandLine.Length == 0) return String("");
                    std::vector<wchar_t> wCmd(upp.CommandLine.Length / sizeof(wchar_t) + 1, 0);
                    if (::ReadProcessMemory(hProc, upp.CommandLine.Buffer, wCmd.data(), upp.CommandLine.Length, &bytesRead)) return String(wCmd.data());
                    return String("");
                }

                static String ReadProcCmdLine(void* hProc) {
                    typedef NTSTATUS(NTAPI* pfnNtQuery)(HANDLE, ULONG, PVOID, ULONG, PULONG);
                    HMODULE hNtDll = ::GetModuleHandleW(L"ntdll.dll");
                    pfnNtQuery fnNtQuery = hNtDll ? (pfnNtQuery)::GetProcAddress(hNtDll, "NtQueryInformationProcess") : NULL;
                    if (!fnNtQuery || !hProc) return String("");
                    PROCESS_BASIC_INFORMATION pbi; DWORD dwLen = 0;
                    if (fnNtQuery(static_cast<HANDLE>(hProc), 0, &pbi, sizeof(pbi), &dwLen) == 0 && pbi.PebBaseAddress) {
                        return ReadPebCommandLine(static_cast<HANDLE>(hProc), pbi.PebBaseAddress);
                    }
                    return String("");
                }

                static MemoryInfo ReadProcMemory(void* hProc) {
                    MemoryInfo info;
                    if (hProc) {
                        PROCESS_MEMORY_COUNTERS_EX pmc;
                        if (::GetProcessMemoryInfo(static_cast<HANDLE>(hProc), reinterpret_cast<PROCESS_MEMORY_COUNTERS*>(&pmc), sizeof(pmc))) {
                            info.lPhysicalMemoryBytes = static_cast<long long>(pmc.WorkingSetSize);
                            info.lPrivateBytes = static_cast<long long>(pmc.PrivateUsage);
                        }
                    }
                    return info;
                }

                static void QueryPdhIoCounters(const std::wstring& wProcName, DiskInfo& info) {
                    std::wstring rPath = L"\\Process(" + wProcName + L")\\IO Read Bytes/sec";
                    std::wstring wPath = L"\\Process(" + wProcName + L")\\IO Write Bytes/sec";
                    HQUERY hQuery = NULL; HCOUNTER hRead = NULL, hWrite = NULL;
                    if (::PdhOpenQueryW(NULL, 0, &hQuery) != ERROR_SUCCESS) return;
                    bool rOk = (::PdhAddEnglishCounterW(hQuery, rPath.c_str(), 0, &hRead) == ERROR_SUCCESS);
                    bool wOk = (::PdhAddEnglishCounterW(hQuery, wPath.c_str(), 0, &hWrite) == ERROR_SUCCESS);
                    if (::PdhCollectQueryData(hQuery) == ERROR_SUCCESS) {
                        PDH_FMT_COUNTERVALUE fmtR, fmtW;
                        if (rOk && ::PdhGetFormattedCounterValue(hRead, PDH_FMT_LARGE, NULL, &fmtR) == ERROR_SUCCESS) info.lDiskReadBytes = static_cast<long long>(fmtR.largeValue);
                        if (wOk && ::PdhGetFormattedCounterValue(hWrite, PDH_FMT_LARGE, NULL, &fmtW) == ERROR_SUCCESS) info.lDiskWriteBytes = static_cast<long long>(fmtW.largeValue);
                    }
                    ::PdhCloseQuery(hQuery);
                }

                static DiskInfo ReadProcDisk(void* hProc, const String& sProcessName) {
                    DiskInfo info;
                    std::string sStd(sProcessName.GetRawString() ? sProcessName.GetRawString() : "");
                    std::wstring wProc(sStd.begin(), sStd.end());
                    size_t pos = wProc.rfind(L'.'); if (pos != std::wstring::npos) wProc = wProc.substr(0, pos);
                    QueryPdhIoCounters(wProc, info);
                    if ((info.lDiskReadBytes == -1 || info.lDiskWriteBytes == -1) && hProc) {
                        IO_COUNTERS io;
                        if (::GetProcessIoCounters(static_cast<HANDLE>(hProc), &io)) {
                            if (info.lDiskReadBytes == -1) info.lDiskReadBytes = static_cast<long long>(io.ReadTransferCount);
                            if (info.lDiskWriteBytes == -1) info.lDiskWriteBytes = static_cast<long long>(io.WriteTransferCount);
                        }
                    }
                    return info;
                }

                static NetworkUsageInfo ReadProcNetwork(void* hProc, const String& sProcessName) {
                    NetworkUsageInfo info;
                    std::string sStd(sProcessName.GetRawString() ? sProcessName.GetRawString() : "");
                    std::wstring wProc(sStd.begin(), sStd.end());
                    size_t pos = wProc.rfind(L'.'); if (pos != std::wstring::npos) wProc = wProc.substr(0, pos);
                    DiskInfo di;
                    QueryPdhIoCounters(wProc, di);
                    info.lNetworkReadBytes = di.lDiskReadBytes;
                    info.lNetworkWriteBytes = di.lDiskWriteBytes;
                    return info;
                }

                static void ReadUdpPorts(int iPid, Collections::Generic::List<int>& lst) {
                    if (iPid <= 0) return;
                    DWORD dwSize = 0;
                    if (::GetExtendedUdpTable(NULL, &dwSize, FALSE, AF_INET, UDP_TABLE_OWNER_PID, 0) != ERROR_INSUFFICIENT_BUFFER) return;
                    std::vector<uint8_t> buf(dwSize, 0);
                    MIB_UDPTABLE_OWNER_PID* pTable = reinterpret_cast<MIB_UDPTABLE_OWNER_PID*>(buf.data());
                    if (::GetExtendedUdpTable(pTable, &dwSize, FALSE, AF_INET, UDP_TABLE_OWNER_PID, 0) != NO_ERROR) return;
                    for (DWORD i = 0; i < pTable->dwNumEntries; ++i) {
                        if (pTable->table[i].dwOwningPid == static_cast<DWORD>(iPid)) {
                            int p = ntohs(static_cast<u_short>(pTable->table[i].dwLocalPort));
                            if (!lst.Contains(p)) lst.Add(p);
                        }
                    }
                }

                static Collections::Generic::List<int> ReadProcPorts(int iPid) {
                    Collections::Generic::List<int> lst;
                    if (iPid <= 0) return lst;
                    DWORD dwSize = 0;
                    if (::GetExtendedTcpTable(NULL, &dwSize, FALSE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0) == ERROR_INSUFFICIENT_BUFFER) {
                        std::vector<uint8_t> buf(dwSize, 0);
                        MIB_TCPTABLE_OWNER_PID* pTable = reinterpret_cast<MIB_TCPTABLE_OWNER_PID*>(buf.data());
                        if (::GetExtendedTcpTable(pTable, &dwSize, FALSE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0) == NO_ERROR) {
                            for (DWORD i = 0; i < pTable->dwNumEntries; ++i) {
                                if (pTable->table[i].dwOwningPid == static_cast<DWORD>(iPid) && pTable->table[i].dwState == MIB_TCP_STATE_LISTEN) {
                                    int p = ntohs(static_cast<u_short>(pTable->table[i].dwLocalPort));
                                    if (!lst.Contains(p)) lst.Add(p);
                                }
                            }
                        }
                    }
                    ReadUdpPorts(iPid, lst);
                    return lst;
                }

                static void ExtractTcpConnection(MIB_TCPROW_OWNER_PID& row, NetworkConnectionInfo& conn) {
                    in_addr lAddr, rAddr;
                    lAddr.S_un.S_addr = row.dwLocalAddr; rAddr.S_un.S_addr = row.dwRemoteAddr;
                    char szL[INET_ADDRSTRLEN] = { 0 }, szR[INET_ADDRSTRLEN] = { 0 };
                    ::inet_ntop(AF_INET, &lAddr, szL, sizeof(szL));
                    ::inet_ntop(AF_INET, &rAddr, szR, sizeof(szR));
                    conn.sLocalAddress = String(szL); conn.iLocalPort = ntohs(static_cast<u_short>(row.dwLocalPort));
                    conn.sRemoteAddress = String(szR); conn.iRemotePort = ntohs(static_cast<u_short>(row.dwRemotePort));
                    conn.sState = (row.dwState == MIB_TCP_STATE_LISTEN) ? "LISTEN" : ((row.dwState == MIB_TCP_STATE_ESTAB) ? "ESTABLISHED" : "OTHER");
                }

                static void ReadUdpConnections(int iPid, ProcessNetworkConnectionInfo& info) {
                    if (iPid <= 0) return;
                    DWORD dwSize = 0;
                    if (::GetExtendedUdpTable(NULL, &dwSize, FALSE, AF_INET, UDP_TABLE_OWNER_PID, 0) != ERROR_INSUFFICIENT_BUFFER) return;
                    std::vector<uint8_t> buf(dwSize, 0);
                    MIB_UDPTABLE_OWNER_PID* pTable = reinterpret_cast<MIB_UDPTABLE_OWNER_PID*>(buf.data());
                    if (::GetExtendedUdpTable(pTable, &dwSize, FALSE, AF_INET, UDP_TABLE_OWNER_PID, 0) != NO_ERROR) return;
                    for (DWORD i = 0; i < pTable->dwNumEntries; ++i) {
                        if (pTable->table[i].dwOwningPid == static_cast<DWORD>(iPid)) {
                            int p = ntohs(static_cast<u_short>(pTable->table[i].dwLocalPort));
                            if (!info.lstOpenPorts.Contains(p)) info.lstOpenPorts.Add(p);
                            NetworkConnectionInfo conn; conn.iLocalPort = p; conn.sState = "UDP";
                            in_addr lAddr; lAddr.S_un.S_addr = pTable->table[i].dwLocalAddr;
                            char szL[INET_ADDRSTRLEN] = { 0 };
                            ::inet_ntop(AF_INET, &lAddr, szL, sizeof(szL));
                            conn.sLocalAddress = String(szL);
                            info.lstConnections.Add(conn);
                        }
                    }
                }

                static ProcessNetworkConnectionInfo ReadProcNetInfo(int iPid) {
                    ProcessNetworkConnectionInfo info;
                    if (iPid <= 0) return info;
                    DWORD dwSize = 0;
                    if (::GetExtendedTcpTable(NULL, &dwSize, FALSE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0) == ERROR_INSUFFICIENT_BUFFER) {
                        std::vector<uint8_t> buf(dwSize, 0);
                        MIB_TCPTABLE_OWNER_PID* pTable = reinterpret_cast<MIB_TCPTABLE_OWNER_PID*>(buf.data());
                        if (::GetExtendedTcpTable(pTable, &dwSize, FALSE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0) == NO_ERROR) {
                            for (DWORD i = 0; i < pTable->dwNumEntries; ++i) {
                                if (pTable->table[i].dwOwningPid == static_cast<DWORD>(iPid)) {
                                    NetworkConnectionInfo conn;
                                    ExtractTcpConnection(pTable->table[i], conn);
                                    info.lstConnections.Add(conn);
                                    if (pTable->table[i].dwState == MIB_TCP_STATE_LISTEN && !info.lstOpenPorts.Contains(conn.iLocalPort)) info.lstOpenPorts.Add(conn.iLocalPort);
                                    else if (pTable->table[i].dwState == MIB_TCP_STATE_ESTAB) info.bHasEstablishedInboundConnection = true;
                                }
                            }
                        }
                    }
                    ReadUdpConnections(iPid, info);
                    return info;
                }

                static void CalculateProcessCpu(HANDLE hProc, int iPid, double& dCpu) {
                    FILETIME ftCreate, ftExit, ftKernel, ftUser, ftSysIdle, ftSysKernel, ftSysUser, ftNow;
                    if (!::GetProcessTimes(hProc, &ftCreate, &ftExit, &ftKernel, &ftUser) || !::GetSystemTimes(&ftSysIdle, &ftSysKernel, &ftSysUser)) return;
                    uint64_t uProc = ((static_cast<uint64_t>(ftKernel.dwHighDateTime) << 32) | ftKernel.dwLowDateTime) + ((static_cast<uint64_t>(ftUser.dwHighDateTime) << 32) | ftUser.dwLowDateTime);
                    uint64_t uSys = ((static_cast<uint64_t>(ftSysKernel.dwHighDateTime) << 32) | ftSysKernel.dwLowDateTime) + ((static_cast<uint64_t>(ftSysUser.dwHighDateTime) << 32) | ftSysUser.dwLowDateTime);
                    auto it = s_mapProcessCpuSamples.find(iPid);
                    if (it != s_mapProcessCpuSamples.end() && uSys > it->second.uSystemTime && uProc >= it->second.uProcessTime) {
                        double c = (static_cast<double>(uProc - it->second.uProcessTime) * 100.0 / static_cast<double>(uSys - it->second.uSystemTime));
                        dCpu = (c > 100.0) ? 100.0 : ((c < 0.0) ? 0.0 : c);
                    } else {
                        ::GetSystemTimeAsFileTime(&ftNow); SYSTEM_INFO si; ::GetSystemInfo(&si);
                        uint64_t uNow = (static_cast<uint64_t>(ftNow.dwHighDateTime) << 32) | ftNow.dwLowDateTime; uint64_t uCreate = (static_cast<uint64_t>(ftCreate.dwHighDateTime) << 32) | ftCreate.dwLowDateTime;
                        if (uNow > uCreate && uProc > 0) { double c = (static_cast<double>(uProc) * 100.0) / (static_cast<double>(uNow - uCreate) * static_cast<double>(si.dwNumberOfProcessors > 0 ? si.dwNumberOfProcessors : 1)); dCpu = (c > 100.0) ? 100.0 : ((c < 0.0) ? 0.0 : c); }
                    }
                    s_mapProcessCpuSamples[iPid] = { uProc, uSys };
                }

                static void PopulateProcNetwork(int iPid, ProcessInfo& proc) {
                    auto netInfo = ReadProcNetInfo(iPid);
                    proc.lstOpenPorts = netInfo.lstOpenPorts;
                    proc.lstConnections = netInfo.lstConnections;
                    proc.bHasEstablishedConnection = netInfo.bHasEstablishedInboundConnection;
                }

                static void PopulateProc(PROCESSENTRY32W* pe32, ProcessInfo& proc) {
                    proc.iProcessId = pe32->th32ProcessID; proc.sName = String(pe32->szExeFile); proc.dCpuUsagePercent = 0.0;
                    proc.memory.lPhysicalMemoryBytes = 0; proc.memory.lPrivateBytes = 0;
                    DWORD dwSess = 0;
                    if (::ProcessIdToSessionId(pe32->th32ProcessID, &dwSess)) proc.iSessionId = static_cast<int>(dwSess);
                    if (pe32->th32ProcessID == 0) return;
                    HANDLE hProc = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ, FALSE, pe32->th32ProcessID);
                    if (!hProc) return;
                    WCHAR szPath[MAX_PATH] = { 0 }; DWORD dwLen = MAX_PATH;
                    if (::QueryFullProcessImageNameW(hProc, 0, szPath, &dwLen)) proc.sPath = String(szPath);
                    proc.sCommandLine = ReadProcCmdLine(hProc);
                    if (proc.sCommandLine.IsEmpty()) proc.sCommandLine = proc.sPath;
                    proc.memory = ReadProcMemory(hProc);
                    proc.disk = ReadProcDisk(hProc, proc.sName);
                    proc.network = ReadProcNetwork(hProc, proc.sName);
                    CalculateProcessCpu(hProc, proc.iProcessId, proc.dCpuUsagePercent);
                    PopulateProcNetwork(proc.iProcessId, proc);
                    ::CloseHandle(hProc);
                }

                static String GetServiceStartType(SC_HANDLE hSCM, LPCWSTR lpServiceName) {
                    SC_HANDLE hService = ::OpenServiceW(hSCM, lpServiceName, SERVICE_QUERY_CONFIG);
                    if (!hService) return "Manual";
                    BYTE buffer[1024]; DWORD dwNeeded = 0;
                    QUERY_SERVICE_CONFIGW* pConfig = reinterpret_cast<QUERY_SERVICE_CONFIGW*>(buffer);
                    String sType = "Manual";
                    if (::QueryServiceConfigW(hService, pConfig, sizeof(buffer), &dwNeeded)) {
                        switch (pConfig->dwStartType) {
                        case SERVICE_AUTO_START: sType = "Automatic"; break;
                        case SERVICE_DEMAND_START: sType = "Manual"; break;
                        case SERVICE_DISABLED: sType = "Disabled"; break;
                        case SERVICE_BOOT_START: sType = "Boot"; break;
                        case SERVICE_SYSTEM_START: sType = "System"; break;
                        default: sType = "Manual"; break;
                        }
                    }
                    ::CloseServiceHandle(hService);
                    return sType;
                }

                static void ParseServiceStatus(ENUM_SERVICE_STATUS_PROCESSW& svc, SC_HANDLE hSCM, ServiceInfo& info) {
                    info.sServiceName = String(svc.lpServiceName); info.sDisplayName = String(svc.lpDisplayName);
                    info.iProcessId = static_cast<int>(svc.ServiceStatusProcess.dwProcessId);
                    switch (svc.ServiceStatusProcess.dwCurrentState) {
                    case SERVICE_RUNNING: info.sStatus = "Running"; break;
                    case SERVICE_STOPPED: info.sStatus = "Stopped"; break;
                    case SERVICE_PAUSED: info.sStatus = "Paused"; break;
                    case SERVICE_START_PENDING: info.sStatus = "StartPending"; break;
                    case SERVICE_STOP_PENDING: info.sStatus = "StopPending"; break;
                    default: info.sStatus = "Unknown"; break;
                    }
                    info.sStartType = GetServiceStartType(hSCM, svc.lpServiceName);
                }
            };

            MemoryInfo SystemMetrics::GetSystemMemoryUsage() { return SystemMetricsWin32Helper::GetSystemMemory(); }
            double SystemMetrics::GetSystemCpuUsage() { return SystemMetricsWin32Helper::GetSystemCpu(); }
            DiskInfo SystemMetrics::GetSystemDiskUsage() { return SystemMetricsWin32Helper::GetSystemDisk(); }
            double SystemMetrics::GetSystemNetworkUsage() { return SystemMetricsWin32Helper::GetSystemNetwork(); }

            void* SystemMetrics::OpenProcessByName(const String& sProcessName, unsigned long dwDesiredAccess, int& iOutProcessId) {
                return SystemMetricsWin32Helper::OpenProcByName(sProcessName, dwDesiredAccess, iOutProcessId);
            }

            String SystemMetrics::ReadProcessCommandLineHandle(void* hProc) {
                return SystemMetricsWin32Helper::ReadProcCmdLine(hProc);
            }

            String SystemMetrics::GetProcessCommandLine(const String& sProcessName) {
                int iPid = -1;
                HANDLE hProc = static_cast<HANDLE>(OpenProcessByName(sProcessName, PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ, iPid));
                if (!hProc) return String("");
                String sCmd = ReadProcessCommandLineHandle(hProc);
                if (sCmd.IsEmpty()) {
                    WCHAR szPath[MAX_PATH] = { 0 }; DWORD dwLen = MAX_PATH;
                    if (::QueryFullProcessImageNameW(hProc, 0, szPath, &dwLen)) sCmd = String(szPath);
                }
                ::CloseHandle(hProc);
                return sCmd;
            }

            MemoryInfo SystemMetrics::ReadProcessMemoryHandle(void* hProc) { return SystemMetricsWin32Helper::ReadProcMemory(hProc); }
            MemoryInfo SystemMetrics::GetProcessMemoryUsage(const String& sProcessName) {
                int iPid = -1; HANDLE hProc = static_cast<HANDLE>(OpenProcessByName(sProcessName, PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ, iPid));
                if (!hProc) return MemoryInfo();
                MemoryInfo mem = ReadProcessMemoryHandle(hProc);
                ::CloseHandle(hProc);
                return mem;
            }

            DiskInfo SystemMetrics::ReadProcessDiskHandle(void* hProc, const String& sProcessName) { return SystemMetricsWin32Helper::ReadProcDisk(hProc, sProcessName); }
            DiskInfo SystemMetrics::GetProcessDiskUsage(const String& sProcessName) {
                int iPid = -1; HANDLE hProc = static_cast<HANDLE>(OpenProcessByName(sProcessName, PROCESS_QUERY_LIMITED_INFORMATION, iPid));
                DiskInfo di = ReadProcessDiskHandle(hProc, sProcessName);
                if (hProc) ::CloseHandle(hProc);
                return di;
            }

            NetworkUsageInfo SystemMetrics::ReadProcessNetworkHandle(void* hProc, const String& sProcessName) { return SystemMetricsWin32Helper::ReadProcNetwork(hProc, sProcessName); }
            NetworkUsageInfo SystemMetrics::GetProcessNetworkUsage(const String& sProcessName) {
                int iPid = -1; HANDLE hProc = static_cast<HANDLE>(OpenProcessByName(sProcessName, PROCESS_QUERY_LIMITED_INFORMATION, iPid));
                NetworkUsageInfo net = ReadProcessNetworkHandle(hProc, sProcessName);
                if (hProc) ::CloseHandle(hProc);
                return net;
            }

            Collections::Generic::List<int> SystemMetrics::ReadProcessNetworkPortInternal(int iProcessId) { return SystemMetricsWin32Helper::ReadProcPorts(iProcessId); }
            Collections::Generic::List<int> SystemMetrics::GetProcessNetworkPort(const String& sProcessName) {
                int iPid = -1; HANDLE hProc = static_cast<HANDLE>(OpenProcessByName(sProcessName, PROCESS_QUERY_LIMITED_INFORMATION, iPid));
                if (hProc) ::CloseHandle(hProc);
                return ReadProcessNetworkPortInternal(iPid);
            }
            Collections::Generic::List<int> SystemMetrics::GetProcessNetworkPort(int iProcessId) {
                return ReadProcessNetworkPortInternal(iProcessId);
            }

            ProcessNetworkConnectionInfo SystemMetrics::ReadProcessNetworkInfoInternal(int iProcessId) { return SystemMetricsWin32Helper::ReadProcNetInfo(iProcessId); }
            ProcessNetworkConnectionInfo SystemMetrics::GetProcessNetworkInfo(const String& sProcessName) {
                int iPid = -1; HANDLE hProc = static_cast<HANDLE>(OpenProcessByName(sProcessName, PROCESS_QUERY_LIMITED_INFORMATION, iPid));
                if (hProc) ::CloseHandle(hProc);
                return ReadProcessNetworkInfoInternal(iPid);
            }
            ProcessNetworkConnectionInfo SystemMetrics::GetProcessNetworkInfo(int iProcessId) {
                return ReadProcessNetworkInfoInternal(iProcessId);
            }

            void SystemMetrics::EnrichProcessInfo(ProcessInfo& proc, bool bIncludeNetwork) {
                if (proc.iProcessId <= 0) throw ArgumentException("Process ID must be greater than zero.");
                HANDLE hProc = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ, FALSE, proc.iProcessId);
                if (hProc) {
                    if (proc.sPath.IsEmpty()) {
                        WCHAR szPath[MAX_PATH] = { 0 }; DWORD dwLen = MAX_PATH;
                        if (::QueryFullProcessImageNameW(hProc, 0, szPath, &dwLen)) proc.sPath = String(szPath);
                    }
                    proc.sCommandLine = SystemMetricsWin32Helper::ReadProcCmdLine(hProc);
                    if (proc.sCommandLine.IsEmpty()) proc.sCommandLine = proc.sPath;
                    proc.memory = SystemMetricsWin32Helper::ReadProcMemory(hProc);
                    proc.disk = SystemMetricsWin32Helper::ReadProcDisk(hProc, proc.sName);
                    proc.network = SystemMetricsWin32Helper::ReadProcNetwork(hProc, proc.sName);
                    SystemMetricsWin32Helper::CalculateProcessCpu(hProc, proc.iProcessId, proc.dCpuUsagePercent);
                    ::CloseHandle(hProc);
                }
                if (bIncludeNetwork) SystemMetricsWin32Helper::PopulateProcNetwork(proc.iProcessId, proc);
            }

            void SystemMetrics::PopulateProcessInfo(void* pEntry32, ProcessInfo& proc) {
                SystemMetricsWin32Helper::PopulateProc(static_cast<PROCESSENTRY32W*>(pEntry32), proc);
            }

            Collections::Generic::List<ProcessInfo> SystemMetrics::GetAllProcesses(int iSessionId) {
                Collections::Generic::List<ProcessInfo> lst;
                HANDLE hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
                if (hSnapshot == INVALID_HANDLE_VALUE) return lst;
                PROCESSENTRY32W pe32; pe32.dwSize = sizeof(PROCESSENTRY32W);
                if (::Process32FirstW(hSnapshot, &pe32)) {
                    do {
                        if (pe32.th32ProcessID == 0) continue;
                        ProcessInfo proc; PopulateProcessInfo(&pe32, proc);
                        if (proc.iProcessId > 0 && (iSessionId == -1 || proc.iSessionId == iSessionId)) lst.Add(proc);
                    } while (::Process32NextW(hSnapshot, &pe32));
                }
                ::CloseHandle(hSnapshot);
                return lst;
            }


            Collections::Generic::List<ServiceInfo> SystemMetrics::GetAllServices() {
                Collections::Generic::List<ServiceInfo> lst;
                SC_HANDLE hSCM = ::OpenSCManagerW(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
                if (!hSCM) return lst;
                DWORD dwNeeded = 0, dwReturned = 0, dwResume = 0;
                ::EnumServicesStatusExW(hSCM, SC_ENUM_PROCESS_INFO, SERVICE_WIN32, SERVICE_STATE_ALL, NULL, 0, &dwNeeded, &dwReturned, &dwResume, NULL);
                if (dwNeeded > 0) {
                    std::vector<uint8_t> buf(dwNeeded, 0);
                    ENUM_SERVICE_STATUS_PROCESSW* pSvcs = reinterpret_cast<ENUM_SERVICE_STATUS_PROCESSW*>(buf.data());
                    if (::EnumServicesStatusExW(hSCM, SC_ENUM_PROCESS_INFO, SERVICE_WIN32, SERVICE_STATE_ALL, buf.data(), dwNeeded, &dwNeeded, &dwReturned, &dwResume, NULL)) {
                        for (DWORD i = 0; i < dwReturned; ++i) {
                            ServiceInfo svc; SystemMetricsWin32Helper::ParseServiceStatus(pSvcs[i], hSCM, svc);
                            lst.Add(svc);
                        }
                    }
                }
                ::CloseServiceHandle(hSCM);
                return lst;
            }
#endif

        }
    }
}

Generated by OpenCppCoverage (Version: 0.9.9.0)