Files
FEX-Emu--FEX/Source/Tools/FEXLogServer/Main.cpp
T
Ryan Houdek 24ad208778 FEXLogServer: Stop improper use of std::erase_if
std::erase_if shouldn't allow you to modify the object passed in to the
predicate.
libstdc++ hasn't always enforced this but now it does with libstdc++12
2022-05-20 03:47:24 -07:00

275 lines
7.5 KiB
C++

#include "Common/Config.h"
#include "Common/SocketLogging.h"
#include "Tools/CommonGUI/IMGui.h"
#include <map>
#include <unordered_set>
#include <unistd.h>
#include <vector>
namespace Common {
const char *GetCharLevel(uint32_t Level) {
switch (Level) {
case LogMan::NONE:
return "NONE";
break;
case LogMan::ASSERT:
return "ASSERT";
break;
case LogMan::ERROR:
return "ERROR";
break;
case LogMan::DEBUG:
return "DEBUG";
break;
case LogMan::INFO:
return "Info";
break;
case LogMan::STDOUT:
return "STDOUT";
break;
case LogMan::STDERR:
return "STDERR";
break;
default:
return "???";
break;
}
}
}
namespace CLI {
static void MsgHandler(int FD, uint64_t Timestamp, uint32_t PID, uint32_t TID, uint32_t Level, const char* Msg) {
auto CharLevel = Common::GetCharLevel(Level);
const auto Output = fmt::format("[{}][{}][{}.{}] {}\n", CharLevel, Timestamp, PID, TID, Msg);
write(STDERR_FILENO, Output.c_str(), Output.size());
}
}
namespace GUI {
static std::chrono::time_point<std::chrono::high_resolution_clock> GlobalTime{};
// Configuration
static size_t MaxLogLines = 1000;
static LogMan::DebugLevels LevelFilter = LogMan::DebugLevels::INFO;
bool AlsoCLI {true};
// PID information
struct PIDMapping {
std::unordered_set<uint32_t> FDs{};
std::unordered_set<uint32_t> TIDs{};
};
// PID -> FD/TID mapping
std::map<uint32_t, PIDMapping> PIDs{};
std::mutex PIDMappingMutex{};
// Log window
static int LogSelected{};
struct LogData {
uint64_t Timestamp;
uint32_t PID;
uint32_t TID;
uint32_t Level;
int FD;
std::string Log{};
};
std::mutex LogMutex{};
std::vector<LogData> LogLines{};
static void MsgHandler(int FD, uint64_t Timestamp, uint32_t PID, uint32_t TID, uint32_t Level, const char* Msg) {
if (Level > LevelFilter) {
// Skip this log
return;
}
{
std::unique_lock lk{PIDMappingMutex};
auto it = PIDs.find(PID);
if (it == PIDs.end()) {
it = PIDs.try_emplace(PID, PIDMapping {
}).first;
}
it->second.FDs.emplace(FD);
it->second.TIDs.emplace(TID);
}
if (AlsoCLI) {
CLI::MsgHandler(FD, Timestamp, PID, TID, Level, Msg);
}
std::unique_lock lk{LogMutex};
auto CharLevel = Common::GetCharLevel(Level);
LogLines.emplace_back(LogData {
Timestamp,
PID,
TID,
Level,
FD,
fmt::format("[{}][{}][{}.{}] {}\n", CharLevel, Timestamp, PID, TID, Msg),
});
if (LogLines.size() > MaxLogLines) {
size_t NumberToRemove = LogLines.size() - MaxLogLines;
LogLines.erase(LogLines.begin(), LogLines.begin()+NumberToRemove);
}
FEX::GUI::HadUpdate();
}
static void FDClosedHandler(int FD) {
std::unique_lock lk{PIDMappingMutex};
for (auto it = PIDs.begin(); it != PIDs.end(); ) {
it->second.FDs.erase(FD);
if (it->second.FDs.empty()) {
it = PIDs.erase(it);
}
else {
++it;
}
}
FEX::GUI::HadUpdate();
}
bool LoglineFiller(void *data, int idx, const char** out_text) {
std::unique_lock lk{LogMutex};
if (idx < LogLines.size()) {
auto &Log = LogLines.at(idx);
*out_text = Log.Log.c_str();
return true;
}
return false;
}
void DrawUI() {
ImGuiIO& io = ImGui::GetIO();
auto current_time = std::chrono::high_resolution_clock::now();
auto Diff = std::chrono::duration_cast<std::chrono::milliseconds>(current_time - GlobalTime);
io.DeltaTime = Diff.count() > 0 ? Diff.count() : 1.0f/60.0f;
GlobalTime = current_time;
ImGui::NewFrame();
// We are using the ImGuiWindowFlags_NoDocking flag to make the parent window not dockable into,
// because it would be confusing to have two docking targets within each others.
ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking;
ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->Pos);
ImGui::SetNextWindowSize(viewport->Size);
ImGui::SetNextWindowViewport(viewport->ID);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::Begin("DockSpace", nullptr, window_flags);
ImGui::PopStyleVar(3);
ImGui::DockSpace(ImGui::GetID("DockSpace"));
if (ImGui::BeginMenuBar()) {
// XXX: Nothing here yet
ImGui::EndMenuBar();
}
{
if (ImGui::Begin("#Config")) {
if (ImGui::RadioButton("Info", LevelFilter == LogMan::DebugLevels::INFO)) {
LevelFilter = LogMan::DebugLevels::INFO;
}
if (ImGui::RadioButton("Debug", LevelFilter == LogMan::DebugLevels::DEBUG)) {
LevelFilter = LogMan::DebugLevels::DEBUG;
}
if (ImGui::RadioButton("Error", LevelFilter == LogMan::DebugLevels::ERROR)) {
LevelFilter = LogMan::DebugLevels::ERROR;
}
if (ImGui::RadioButton("Assert", LevelFilter == LogMan::DebugLevels::ASSERT)) {
LevelFilter = LogMan::DebugLevels::ASSERT;
}
if (ImGui::RadioButton("None", LevelFilter == LogMan::DebugLevels::NONE)) {
LevelFilter = LogMan::DebugLevels::NONE;
}
ImGui::Checkbox("Print to stderr", &AlsoCLI);
if (ImGui::TreeNodeEx("PIDs", ImGuiTreeNodeFlags_DefaultOpen)) {
std::unique_lock lk{PIDMappingMutex};
for (const auto& it : PIDs) {
ImGui::PushID(it.first);
ImGui::Text("PID: %d", it.first);
ImGui::Indent();
for (auto TID : it.second.TIDs) {
if (TID != it.first) {
ImGui::Text("%d", TID);
}
}
ImGui::Unindent();
ImGui::PopID();
}
ImGui::TreePop();
}
ImGui::End();
}
if (ImGui::Begin("#Log")) {
auto Region = ImGui::GetWindowContentRegionMax();
ImGui::PushItemWidth(Region.x);
const ImGuiStyle& style = ImGui::GetStyle();
float FitableItems = Region.y / ImGui::GetTextLineHeightWithSpacing() - style.FramePadding.y * 2.0;
ImGui::ListBox("##Log", &LogSelected, LoglineFiller, nullptr, LogLines.size(), FitableItems);
ImGui::End();
}
}
ImGui::End(); // End dockspace
ImGui::Render();
}
}
int main(int argc, char **argv, char **envp) {
const char *Port = "8087";
bool Graphical {};
for (size_t i = 1; i < argc; ++i) {
if (strcmp(argv[i], "-g") == 0) {
Graphical = true;
}
else {
Port = argv[i];
}
}
auto Listener = FEX::SocketLogging::Server::StartListening(Port);
if (!Graphical) {
Listener->SetMsgHandler(CLI::MsgHandler);
Listener->WaitForShutdown();
return 0;
}
Listener->SetMsgHandler(GUI::MsgHandler);
Listener->SetFDClosedHandler(GUI::FDClosedHandler);
// Imgui based UI
std::string ImGUIConfig = FEXCore::Config::GetConfigDirectory(false) + "FEXLogServer_imgui.ini";
GUI::GlobalTime = std::chrono::high_resolution_clock::now();
auto [window, gl_context] = FEX::GUI::SetupIMGui("#FEXLogServer", ImGUIConfig);
FEX::GUI::DrawUI(window, GUI::DrawUI);
FEX::GUI::Shutdown(window, gl_context);
Listener->WaitForShutdown();
return 0;
}