mirror of
https://github.com/Awakenchan/LanternEngine.git
synced 2026-11-12 18:08:45 -10:00
226 lines
7.7 KiB
C++
226 lines
7.7 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
#include "bbport_usage.h"
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#ifdef _WIN32
|
|
#ifndef NOMINMAX
|
|
#define NOMINMAX
|
|
#endif
|
|
#include <windows.h>
|
|
#include <pdh.h>
|
|
#include <pdhmsg.h>
|
|
#include <tlhelp32.h>
|
|
#endif
|
|
|
|
namespace BbUsage {
|
|
|
|
#ifdef _WIN32
|
|
namespace {
|
|
|
|
std::uint64_t Ticks(const FILETIME& t) {
|
|
return (std::uint64_t(t.dwHighDateTime) << 32) | t.dwLowDateTime;
|
|
}
|
|
|
|
float CpuPercent() {
|
|
static std::uint64_t last_idle = 0, last_total = 0;
|
|
FILETIME idle, kernel, user;
|
|
if (!GetSystemTimes(&idle, &kernel, &user)) {
|
|
return -1.0f;
|
|
}
|
|
const std::uint64_t i = Ticks(idle), total = Ticks(kernel) + Ticks(user);
|
|
const std::uint64_t di = i - last_idle, dt = total - last_total;
|
|
const bool first = last_total == 0;
|
|
last_idle = i;
|
|
last_total = total;
|
|
if (first || dt == 0) {
|
|
return -1.0f;
|
|
}
|
|
return std::clamp(100.0f * float(dt - std::min(di, dt)) / float(dt), 0.0f, 100.0f);
|
|
}
|
|
|
|
class GpuCounter {
|
|
public:
|
|
float cpu = -1.0f;
|
|
|
|
float Percent() {
|
|
cpu = -1.0f;
|
|
if (!Open()) {
|
|
return -1.0f;
|
|
}
|
|
if (collect(query) != ERROR_SUCCESS) {
|
|
return -1.0f;
|
|
}
|
|
PDH_FMT_COUNTERVALUE one{};
|
|
if (cpu_counter && value && value(cpu_counter, PDH_FMT_DOUBLE, nullptr, &one) == ERROR_SUCCESS &&
|
|
one.CStatus == ERROR_SUCCESS) {
|
|
cpu = float(std::clamp(one.doubleValue, 0.0, 100.0));
|
|
}
|
|
DWORD bytes = 0, count = 0;
|
|
PDH_STATUS status = array(counter, PDH_FMT_DOUBLE, &bytes, &count, nullptr);
|
|
if (status != PDH_MORE_DATA || bytes == 0) {
|
|
return -1.0f;
|
|
}
|
|
buffer.resize(bytes);
|
|
auto* items = reinterpret_cast<PDH_FMT_COUNTERVALUE_ITEM_W*>(buffer.data());
|
|
if (array(counter, PDH_FMT_DOUBLE, &bytes, &count, items) != ERROR_SUCCESS) {
|
|
return -1.0f;
|
|
}
|
|
double sum = 0.0;
|
|
for (DWORD k = 0; k < count; ++k) {
|
|
if (items[k].FmtValue.CStatus == ERROR_SUCCESS) {
|
|
sum += items[k].FmtValue.doubleValue;
|
|
}
|
|
}
|
|
return float(std::clamp(sum, 0.0, 100.0));
|
|
}
|
|
|
|
private:
|
|
bool Open() {
|
|
if (tried) {
|
|
return query != nullptr;
|
|
}
|
|
tried = true;
|
|
HMODULE pdh = LoadLibraryW(L"pdh.dll");
|
|
if (!pdh) {
|
|
return false;
|
|
}
|
|
const auto open = reinterpret_cast<decltype(&PdhOpenQueryW)>(GetProcAddress(pdh, "PdhOpenQueryW"));
|
|
const auto add = reinterpret_cast<decltype(&PdhAddEnglishCounterW)>(
|
|
GetProcAddress(pdh, "PdhAddEnglishCounterW"));
|
|
collect = reinterpret_cast<decltype(&PdhCollectQueryData)>(GetProcAddress(pdh, "PdhCollectQueryData"));
|
|
array = reinterpret_cast<decltype(&PdhGetFormattedCounterArrayW)>(
|
|
GetProcAddress(pdh, "PdhGetFormattedCounterArrayW"));
|
|
value = reinterpret_cast<decltype(&PdhGetFormattedCounterValue)>(
|
|
GetProcAddress(pdh, "PdhGetFormattedCounterValue"));
|
|
if (!open || !add || !collect || !array) {
|
|
return false;
|
|
}
|
|
PDH_HQUERY q = nullptr;
|
|
if (open(nullptr, 0, &q) != ERROR_SUCCESS) {
|
|
return false;
|
|
}
|
|
if (add(q, L"\\GPU Engine(*engtype_3D)\\Utilization Percentage", 0, &counter) != ERROR_SUCCESS) {
|
|
return false;
|
|
}
|
|
if (add(q, L"\\Processor Information(_Total)\\% Processor Utility", 0, &cpu_counter) != ERROR_SUCCESS) {
|
|
cpu_counter = nullptr;
|
|
}
|
|
query = q;
|
|
collect(query);
|
|
return true;
|
|
}
|
|
|
|
bool tried = false;
|
|
PDH_HQUERY query = nullptr;
|
|
PDH_HCOUNTER counter = nullptr, cpu_counter = nullptr;
|
|
decltype(&PdhCollectQueryData) collect = nullptr;
|
|
decltype(&PdhGetFormattedCounterArrayW) array = nullptr;
|
|
decltype(&PdhGetFormattedCounterValue) value = nullptr;
|
|
std::vector<unsigned char> buffer;
|
|
};
|
|
|
|
void ThreadReportLoop() {
|
|
using Describe = HRESULT(WINAPI*)(HANDLE, PWSTR*);
|
|
const auto describe = reinterpret_cast<Describe>(
|
|
GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "GetThreadDescription"));
|
|
std::vector<std::pair<DWORD, std::uint64_t>> last;
|
|
auto previous = std::chrono::steady_clock::now();
|
|
for (;;) {
|
|
Sleep(5000);
|
|
const auto now = std::chrono::steady_clock::now();
|
|
const double wall = std::chrono::duration<double>(now - previous).count() * 1e7;
|
|
previous = now;
|
|
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
|
|
if (snapshot == INVALID_HANDLE_VALUE) continue;
|
|
struct Row { double percent; std::wstring name; DWORD id; };
|
|
std::vector<Row> rows;
|
|
std::vector<std::pair<DWORD, std::uint64_t>> current;
|
|
double total = 0.0;
|
|
THREADENTRY32 entry{sizeof(entry)};
|
|
for (BOOL ok = Thread32First(snapshot, &entry); ok; ok = Thread32Next(snapshot, &entry)) {
|
|
if (entry.th32OwnerProcessID != GetCurrentProcessId()) continue;
|
|
HANDLE thread = OpenThread(THREAD_QUERY_LIMITED_INFORMATION, FALSE, entry.th32ThreadID);
|
|
if (!thread) continue;
|
|
FILETIME created, exited, kernel, user;
|
|
if (GetThreadTimes(thread, &created, &exited, &kernel, &user)) {
|
|
const std::uint64_t time = Ticks(kernel) + Ticks(user);
|
|
current.emplace_back(entry.th32ThreadID, time);
|
|
const auto it = std::find_if(last.begin(), last.end(),
|
|
[&](const auto& p) { return p.first == entry.th32ThreadID; });
|
|
if (it != last.end() && wall > 0) {
|
|
const double percent = 100.0 * double(time - it->second) / wall;
|
|
total += percent;
|
|
std::wstring name;
|
|
PWSTR text = nullptr;
|
|
if (describe && SUCCEEDED(describe(thread, &text)) && text) {
|
|
name = text;
|
|
LocalFree(text);
|
|
}
|
|
rows.push_back({percent, name, entry.th32ThreadID});
|
|
}
|
|
}
|
|
CloseHandle(thread);
|
|
}
|
|
CloseHandle(snapshot);
|
|
last = std::move(current);
|
|
if (rows.empty()) continue;
|
|
std::sort(rows.begin(), rows.end(), [](const Row& a, const Row& b) { return a.percent > b.percent; });
|
|
std::printf("Thread CPU (5 s, 100%% = one core): all %.0f%%;", total);
|
|
for (std::size_t k = 0; k < rows.size() && k < 12; ++k) {
|
|
std::printf(" %ls %.0f%%%s", rows[k].name.empty() ? L"(unnamed)" : rows[k].name.c_str(),
|
|
rows[k].percent, k + 1 < rows.size() && k < 11 ? "," : "");
|
|
}
|
|
std::printf("\n");
|
|
std::fflush(stdout);
|
|
}
|
|
}
|
|
|
|
const bool thread_report = [] {
|
|
const char* env = std::getenv("BB_THREAD_CPU");
|
|
if (env && env[0] == '1') {
|
|
std::thread(ThreadReportLoop).detach();
|
|
}
|
|
return true;
|
|
}();
|
|
|
|
}
|
|
|
|
Sample Get(bool cpu, bool gpu) {
|
|
static Sample last;
|
|
static auto next = std::chrono::steady_clock::time_point{};
|
|
static GpuCounter counter;
|
|
const auto now = std::chrono::steady_clock::now();
|
|
if (now < next) {
|
|
return last;
|
|
}
|
|
next = now + std::chrono::milliseconds(500);
|
|
static const bool utility = [] {
|
|
const char* env = std::getenv("BB_CPU_UTILITY");
|
|
return env && env[0] == '1';
|
|
}();
|
|
const float gpu_now = counter.Percent();
|
|
const float system_cpu = CpuPercent();
|
|
if (cpu) {
|
|
last.cpu = utility && counter.cpu >= 0.0f ? counter.cpu : system_cpu;
|
|
}
|
|
if (gpu) {
|
|
last.gpu = gpu_now;
|
|
}
|
|
return last;
|
|
}
|
|
#else
|
|
Sample Get(bool, bool) {
|
|
return {};
|
|
}
|
|
#endif
|
|
|
|
}
|