Files
FEX-Emu--FEX/Source/Tools/FEXLogServer/Main.cpp
T
Ryan Houdek fc858ca0a5 Adds FEXLogServer
This tool works in both gui-less and gui modes.
FEXLogServer executed alone will open a socket on `localhost:8087` and
pass log output to stderr.

If passed the `-g` argument then it will open the IMGui UI which has
some more options.
It's fairly basic right now but eventually should allow filtering by
PIDs, TIDs, and log level type
2021-11-30 14:58:06 -08:00

269 lines
7.4 KiB
C++

#include "Common/Config.h"
#include "Common/SocketLogging.h"
#include "Tools/CommonGUI/IMGui.h"
#include <map>
#include <unordered_set>
#include <unistd.h>
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};
std::erase_if(PIDs, [FD](auto& Entry) {
Entry.second.FDs.erase(FD);
return Entry.second.FDs.empty();
});
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;
}