Skip to content

Commit

Permalink
ObjectExplorer: Add "Reset Stats" button for hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
praydog committed Mar 30, 2024
1 parent aecead6 commit 2c9d332
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 32 deletions.
53 changes: 32 additions & 21 deletions src/mods/tools/ObjectExplorer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,14 @@ void ObjectExplorer::display_hooks() {
if (ImGui::TreeNode("Options")) {
ImGui::Checkbox("Hide uncalled methods", &m_hooks_context.hide_uncalled_methods);

ImGui::SameLine();

if (ImGui::Button("Reset Stats")) {
for (auto& h : m_hooked_methods) {
h.reset_stats();
}
}

// Combobox of the sort method instead
if (ImGui::BeginCombo("Sort by", HooksContext::s_sort_method_names[(uint8_t)m_hooks_context.sort_method])) {
for (int i = 0; i < HooksContext::s_sort_method_names.size(); i++) {
Expand All @@ -713,7 +721,7 @@ void ObjectExplorer::display_hooks() {

std::vector<HookedMethod*> hooks_to_iterate{};
for (auto& h : m_hooked_methods) {
if (m_hooks_context.hide_uncalled_methods && h.call_count == 0) {
if (m_hooks_context.hide_uncalled_methods && h.stats.call_count == 0) {
continue;
}

Expand All @@ -723,22 +731,22 @@ void ObjectExplorer::display_hooks() {
switch (m_hooks_context.sort_method) {
case HooksContext::SortMethod::CALL_COUNT:
std::sort(hooks_to_iterate.begin(), hooks_to_iterate.end(), [](const auto& a, const auto& b) {
return a->call_count > b->call_count;
return a->stats.call_count > b->stats.call_count;
});
break;
case HooksContext::SortMethod::CALL_TIME_LAST:
std::sort(hooks_to_iterate.begin(), hooks_to_iterate.end(), [](const auto& a, const auto& b) {
return a->last_call_time > b->last_call_time;
return a->stats.last_call_time > b->stats.last_call_time;
});
break;
case HooksContext::SortMethod::CALL_TIME_DELTA:
std::sort(hooks_to_iterate.begin(), hooks_to_iterate.end(), [](const auto& a, const auto& b) {
return a->last_call_delta > b->last_call_delta;
return a->stats.last_call_delta > b->stats.last_call_delta;
});
break;
case HooksContext::SortMethod::CALL_TIME_TOTAL:
std::sort(hooks_to_iterate.begin(), hooks_to_iterate.end(), [](const auto& a, const auto& b) {
return a->total_call_time > b->total_call_time;
return a->stats.total_call_time > b->stats.total_call_time;
});
break;
case HooksContext::SortMethod::METHOD_NAME:
Expand All @@ -753,7 +761,7 @@ void ObjectExplorer::display_hooks() {
break;
case HooksContext::SortMethod::NUMBER_OF_THREADS_CALLED_FROM:
std::sort(hooks_to_iterate.begin(), hooks_to_iterate.end(), [](const auto& a, const auto& b) {
return a->thread_ids.size() > b->thread_ids.size();
return a->stats.thread_ids.size() > b->stats.thread_ids.size();
});
break;
default:
Expand All @@ -770,11 +778,11 @@ void ObjectExplorer::display_hooks() {

if (made_node) {
ImGui::Checkbox("Skip function call", &h.skip);
ImGui::TextWrapped("Call count: %i", h.call_count);
ImGui::TextWrapped("Call count: %i", h.stats.call_count);

ImGui::SameLine();
const float delta_ms = std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(h.last_call_delta).count();
const float total_ms = std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(h.total_call_time).count();
const float delta_ms = std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(h.stats.last_call_delta).count();
const float total_ms = std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(h.stats.total_call_time).count();
ImGui::TextWrapped("Time (ms): Delta %f, Total %f", delta_ms, total_ms);

if (ImGui::TreeNode("Info")) {
Expand Down Expand Up @@ -806,7 +814,7 @@ void ObjectExplorer::display_hooks() {
switch (h.sort_callers_method) {
case HookedMethod::SortCallersMethod::CALL_COUNT:
std::sort(callers_to_iterate.begin(), callers_to_iterate.end(), [&h](const auto& a, const auto& b) {
return h.callers_context[a].call_count > h.callers_context[b].call_count;
return h.stats.callers_context[a].call_count > h.stats.callers_context[b].call_count;
});
break;
case HookedMethod::SortCallersMethod::METHOD_NAME:
Expand All @@ -819,7 +827,7 @@ void ObjectExplorer::display_hooks() {
};

for (auto& caller : callers_to_iterate) {
const auto& context = h.callers_context[caller];
const auto& context = h.stats.callers_context[caller];
const auto declaring_type = caller->get_declaring_type();
//auto method_name = declaring_type != nullptr ? declaring_type->get_full_name() + "." + caller->get_name() : caller->get_name();
//method_name += " [" + std::to_string(context.call_count) + "]";
Expand All @@ -845,7 +853,7 @@ void ObjectExplorer::display_hooks() {
}

if (ImGui::TreeNode("Thread IDs")) {
for (auto tid : h.thread_ids) {
for (auto tid : h.stats.thread_ids) {
ImGui::Text("%i", tid);
}
ImGui::TreePop();
Expand Down Expand Up @@ -4679,9 +4687,7 @@ HookManager::PreHookResult ObjectExplorer::pre_hooked_method_internal(std::vecto
auto& hooked_method = *it;

std::scoped_lock _{m_hooks_context.mtx};
++hooked_method.call_count;
hooked_method.last_call_time = std::chrono::high_resolution_clock::now();
hooked_method.thread_ids.insert(std::this_thread::get_id()._Get_underlying_id());
++hooked_method.stats.call_count;

if (!hooked_method.return_addresses.contains(ret_addr)) {
spdlog::info("Creating new entry for {}", hooked_method.name);
Expand Down Expand Up @@ -4770,10 +4776,13 @@ HookManager::PreHookResult ObjectExplorer::pre_hooked_method_internal(std::vecto

if (auto it2 = hooked_method.return_addresses_to_methods.find(ret_addr); it2 != hooked_method.return_addresses_to_methods.end()) {
auto caller = it2->second;
auto& context = hooked_method.callers_context[caller];
auto& context = hooked_method.stats.callers_context[caller];
++context.call_count;
}

hooked_method.stats.last_call_time = std::chrono::high_resolution_clock::now();
hooked_method.stats.thread_ids.insert(std::this_thread::get_id()._Get_underlying_id());

auto result = HookManager::PreHookResult::CALL_ORIGINAL;

if (hooked_method.skip) {
Expand All @@ -4794,20 +4803,22 @@ void ObjectExplorer::post_hooked_method_internal(uintptr_t& ret_val, sdk::REType
return;
}

const auto now = std::chrono::high_resolution_clock::now();

auto& hooked_method = *it;

std::scoped_lock _{m_hooks_context.mtx};

// Reset the last call time if this is the first time we're calling it
// because in between this, there will be a large hitch
// that the game is not causing.
if (hooked_method.call_count <= 1) {
hooked_method.last_call_time = std::chrono::high_resolution_clock::now();
if (hooked_method.stats.call_count <= 1) {
hooked_method.stats.last_call_time = now;
}

hooked_method.last_call_end_time = std::chrono::high_resolution_clock::now();
hooked_method.last_call_delta = hooked_method.last_call_end_time - hooked_method.last_call_time;
hooked_method.total_call_time += hooked_method.last_call_delta;
hooked_method.stats.last_call_end_time = now;
hooked_method.stats.last_call_delta = hooked_method.stats.last_call_end_time - hooked_method.stats.last_call_time;
hooked_method.stats.total_call_time += hooked_method.stats.last_call_delta;
}

void ObjectExplorer::post_hooked_method(uintptr_t& ret_val, sdk::RETypeDefinition*& ret_ty, uintptr_t ret_addr, sdk::REMethodDefinition* method) {
Expand Down
30 changes: 19 additions & 11 deletions src/mods/tools/ObjectExplorer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -280,22 +280,30 @@ class ObjectExplorer : public Tool {
uintptr_t jitted_function_post{};
bool skip{false};
size_t hook_id{};
uint32_t call_count{};
SortCallersMethod sort_callers_method{SortCallersMethod::NONE};

// Not considered stats because resetting these causes large fluctuations in the stats
std::unordered_set<uintptr_t> return_addresses{};
std::unordered_map<uintptr_t, sdk::REMethodDefinition*> return_addresses_to_methods{};
std::unordered_set<sdk::REMethodDefinition*> callers{};
std::unordered_set<uint32_t> thread_ids{};
struct CallerContext {
size_t call_count{};
};
std::unordered_map<uintptr_t, sdk::REMethodDefinition*> return_addresses_to_methods{};

std::unordered_map<sdk::REMethodDefinition*, CallerContext> callers_context{};
std::chrono::high_resolution_clock::time_point last_call_time{std::chrono::high_resolution_clock::now()};
std::chrono::high_resolution_clock::time_point last_call_end_time{std::chrono::high_resolution_clock::now()}; // assuming not recursive...
std::chrono::high_resolution_clock::duration last_call_delta{};
std::chrono::high_resolution_clock::duration total_call_time{};
struct Stats {
uint32_t call_count{};
std::unordered_set<uint32_t> thread_ids{};
struct CallerContext {
size_t call_count{};
};

std::unordered_map<sdk::REMethodDefinition*, CallerContext> callers_context{};
std::chrono::high_resolution_clock::time_point last_call_time{std::chrono::high_resolution_clock::now()};
std::chrono::high_resolution_clock::time_point last_call_end_time{std::chrono::high_resolution_clock::now()}; // assuming not recursive...
std::chrono::high_resolution_clock::duration last_call_delta{};
std::chrono::high_resolution_clock::duration total_call_time{};
} stats;

void reset_stats() {
stats = Stats{};
}
};

// Contains extra information about the method
Expand Down

0 comments on commit 2c9d332

Please sign in to comment.