diff --git a/include/Common.h b/include/Common.h index ed34b7f..373f0a2 100644 --- a/include/Common.h +++ b/include/Common.h @@ -97,15 +97,15 @@ public: using SystemStatusMap = std::unordered_map; static const SystemStatusMap& GetSubsystemStatuses() { + std::unique_lock Lock(mSystemStatusMapMutex); return mSystemStatusMap; } - - static void SetSubsystemStatus(const std::string& Subsystem, Status status) { - mSystemStatusMap[Subsystem] = status; - } + + static void SetSubsystemStatus(const std::string& Subsystem, Status status); private: static inline SystemStatusMap mSystemStatusMap {}; + static inline std::mutex mSystemStatusMapMutex {}; static inline std::string mPPS; static inline std::unique_ptr mConsole; static inline std::mutex mShutdownHandlersMutex {}; diff --git a/src/Common.cpp b/src/Common.cpp index 26f7619..620c609 100644 --- a/src/Common.cpp +++ b/src/Common.cpp @@ -73,7 +73,24 @@ bool Application::IsOutdated(const Version& Current, const Version& Newest) { } } +void Application::SetSubsystemStatus(const std::string& Subsystem, Status status) { + switch (status) { + case Status::Good: + beammp_trace("Subsystem '" + Subsystem + "': Good"); + break; + case Status::Bad: + beammp_trace("Subsystem '" + Subsystem + "': Bad"); + break; + case Status::Starting: + beammp_trace("Subsystem '" + Subsystem + "': Starting"); + break; + } + std::unique_lock Lock(mSystemStatusMapMutex); + mSystemStatusMap[Subsystem] = status; +} + void Application::CheckForUpdates() { + Application::SetSubsystemStatus("UpdateCheck", Application::Status::Starting); // checks current version against latest version std::regex VersionRegex { R"(\d+\.\d+\.\d+\n*)" }; auto Response = Http::GET(GetBackendHostname(), 443, "/v/s"); @@ -87,12 +104,14 @@ void Application::CheckForUpdates() { } else { beammp_info("Server up-to-date!"); } + Application::SetSubsystemStatus("UpdateCheck", Application::Status::Good); } else { beammp_warn("Unable to fetch version from backend."); beammp_trace("got " + Response); auto Lock = Sentry.CreateExclusiveContext(); Sentry.SetContext("get-response", { { "response", Response } }); Sentry.LogError("failed to get server version", _file_basename, _line); + Application::SetSubsystemStatus("UpdateCheck", Application::Status::Bad); } } diff --git a/src/Http.cpp b/src/Http.cpp index 813a26d..3f53517 100644 --- a/src/Http.cpp +++ b/src/Http.cpp @@ -264,6 +264,7 @@ void Http::Server::SetupEnvironment() { } Http::Server::THttpServerInstance::THttpServerInstance() { + Application::SetSubsystemStatus("HTTPServer", Application::Status::Starting); mThread = std::thread(&Http::Server::THttpServerInstance::operator(), this); mThread.detach(); } @@ -279,5 +280,6 @@ void Http::Server::THttpServerInstance::operator()() { res.set_content("0", "text/plain"); res.status = 200; }); + Application::SetSubsystemStatus("HTTPServer", Application::Status::Good); HttpLibServerInstance.listen("0.0.0.0", Application::Settings.HTTPServerPort); } diff --git a/src/TConfig.cpp b/src/TConfig.cpp index 9817fc6..db46739 100644 --- a/src/TConfig.cpp +++ b/src/TConfig.cpp @@ -31,6 +31,7 @@ static constexpr std::string_view StrHTTPServerPort = "HTTPServerPort"; TConfig::TConfig(const std::string& ConfigFileName) : mConfigFileName(ConfigFileName) { + Application::SetSubsystemStatus("Config", Application::Status::Starting); if (!fs::exists(mConfigFileName) || !fs::is_regular_file(mConfigFileName)) { beammp_info("No config file found! Generating one..."); CreateConfigFile(mConfigFileName); @@ -115,6 +116,7 @@ void TConfig::CreateConfigFile(std::string_view name) { ofs.close(); } else { beammp_error("Couldn't create " + std::string(name) + ". Check permissions, try again, and contact support if it continues not to work."); + Application::SetSubsystemStatus("Config", Application::Status::Bad); mFailed = true; } } @@ -151,6 +153,7 @@ void TConfig::ParseFromFile(std::string_view name) { } catch (const std::exception& err) { beammp_error("Error parsing config file value: " + std::string(err.what())); mFailed = true; + Application::SetSubsystemStatus("Config", Application::Status::Bad); return; } PrintDebug(); @@ -161,7 +164,13 @@ void TConfig::ParseFromFile(std::string_view name) { // all good so far, let's check if there's a key if (Application::Settings.Key.empty()) { beammp_error("No AuthKey specified in the \"" + std::string(mConfigFileName) + "\" file. Please get an AuthKey, enter it into the config file, and restart this server."); + Application::SetSubsystemStatus("Config", Application::Status::Bad); mFailed = true; + return; + } + Application::SetSubsystemStatus("Config", Application::Status::Good); + if (Application::Settings.Key.size() != 36) { + beammp_warn("AuthKey specified is the wrong length and likely isn't valid."); } } diff --git a/src/TConsole.cpp b/src/TConsole.cpp index 9acf574..e947d65 100644 --- a/src/TConsole.cpp +++ b/src/TConsole.cpp @@ -247,6 +247,34 @@ void TConsole::Command_Status(const std::string&) { return true; }); + size_t SystemsStarting = 0; + size_t SystemsGood = 0; + size_t SystemsBad = 0; + std::string SystemsBadList {}; + std::string SystemsGoodList {}; + std::string SystemsStartingList {}; + auto Statuses = Application::GetSubsystemStatuses(); + for (const auto& NameStatusPair : Statuses) { + switch (NameStatusPair.second) { + case Application::Status::Good: + SystemsGood++; + SystemsGoodList += NameStatusPair.first + ", "; + break; + case Application::Status::Bad: + SystemsBad++; + SystemsBadList += NameStatusPair.first + ", "; + break; + case Application::Status::Starting: + SystemsStarting++; + SystemsStartingList += NameStatusPair.first + ", "; + break; + } + } + // remove ", " at the end + SystemsBadList = SystemsBadList.substr(0, SystemsBadList.size() - 2); + SystemsGoodList = SystemsGoodList.substr(0, SystemsGoodList.size() - 2); + SystemsStartingList = SystemsStartingList.substr(0, SystemsStartingList.size() - 2); + auto ElapsedTime = mLuaEngine->Server().UptimeTimer.GetElapsedTime(); Status << "BeamMP-Server Status:\n" @@ -262,6 +290,11 @@ void TConsole::Command_Status(const std::string&) { << "\t\tStates: " << mLuaEngine->GetLuaStateCount() << "\n" << "\t\tEvent timers: " << mLuaEngine->GetTimedEventsCount() << "\n" << "\t\tEvent handlers: " << mLuaEngine->GetRegisteredEventHandlerCount() << "\n" + << "\tSubsystems:\n" + << "\t\tGood/Starting/Bad: " << SystemsGood << "/" << SystemsStarting << "/" << SystemsBad << "\n" + << "\t\tGood: [ " << SystemsGoodList << " ]\n" + << "\t\tStarting: [ " << SystemsStartingList << " ]\n" + << "\t\tBad: [ " << SystemsBadList << " ]\n" << ""; Application::Console().WriteRaw(Status.str()); diff --git a/src/THeartbeatThread.cpp b/src/THeartbeatThread.cpp index ac7a293..4f7ef55 100644 --- a/src/THeartbeatThread.cpp +++ b/src/THeartbeatThread.cpp @@ -6,7 +6,6 @@ #include void THeartbeatThread::operator()() { - return;/* RegisterThread("Heartbeat"); std::string Body; std::string T; @@ -55,20 +54,28 @@ void THeartbeatThread::operator()() { if ((T.substr(0, 2) != "20" && ResponseCode != 200) || ResponseCode != 200) { beammp_trace("got " + T + " from backend"); + Application::SetSubsystemStatus("Heartbeat", Application::Status::Bad); SentryReportError(Application::GetBackendHostname() + Target, ResponseCode); std::this_thread::sleep_for(std::chrono::milliseconds(500)); T = Http::POST(Application::GetBackup1Hostname(), 443, Target, Body, "application/x-www-form-urlencoded", &ResponseCode); if ((T.substr(0, 2) != "20" && ResponseCode != 200) || ResponseCode != 200) { SentryReportError(Application::GetBackup1Hostname() + Target, ResponseCode); + Application::SetSubsystemStatus("Heartbeat", Application::Status::Bad); std::this_thread::sleep_for(std::chrono::milliseconds(500)); T = Http::POST(Application::GetBackup2Hostname(), 443, Target, Body, "application/x-www-form-urlencoded", &ResponseCode); if ((T.substr(0, 2) != "20" && ResponseCode != 200) || ResponseCode != 200) { beammp_warn("Backend system refused server! Server will not show in the public server list."); - + Application::SetSubsystemStatus("Heartbeat", Application::Status::Bad); isAuth = false; SentryReportError(Application::GetBackup2Hostname() + Target, ResponseCode); + } else { + Application::SetSubsystemStatus("Heartbeat", Application::Status::Good); } + } else { + Application::SetSubsystemStatus("Heartbeat", Application::Status::Good); } + } else { + Application::SetSubsystemStatus("Heartbeat", Application::Status::Good); } if (!isAuth) { @@ -81,9 +88,8 @@ void THeartbeatThread::operator()() { } } - //SocketIO::Get().SetAuthenticated(isAuth); + // SocketIO::Get().SetAuthenticated(isAuth); } - */ } std::string THeartbeatThread::GenerateCall() { @@ -108,6 +114,7 @@ std::string THeartbeatThread::GenerateCall() { THeartbeatThread::THeartbeatThread(TResourceManager& ResourceManager, TServer& Server) : mResourceManager(ResourceManager) , mServer(Server) { + Application::SetSubsystemStatus("Heartbeat", Application::Status::Starting); Application::RegisterShutdownHandler([&] { if (mThread.joinable()) { mShutdown = true; diff --git a/src/TLuaEngine.cpp b/src/TLuaEngine.cpp index 97d2ed7..4deafc6 100644 --- a/src/TLuaEngine.cpp +++ b/src/TLuaEngine.cpp @@ -16,6 +16,7 @@ TLuaEngine* LuaAPI::MP::Engine; TLuaEngine::TLuaEngine() : mPluginMonitor(fs::path(Application::Settings.Resource) / "Server", *this, mShutdown) { + Application::SetSubsystemStatus("LuaEngine", Application::Status::Starting); LuaAPI::MP::Engine = this; if (!fs::exists(Application::Settings.Resource)) { fs::create_directory(Application::Settings.Resource); @@ -36,6 +37,7 @@ TLuaEngine::TLuaEngine() void TLuaEngine::operator()() { RegisterThread("LuaEngine"); + Application::SetSubsystemStatus("LuaEngine", Application::Status::Good); // lua engine main thread CollectAndInitPlugins(); // now call all onInit's diff --git a/src/TNetwork.cpp b/src/TNetwork.cpp index 914bf85..2534052 100644 --- a/src/TNetwork.cpp +++ b/src/TNetwork.cpp @@ -11,6 +11,8 @@ TNetwork::TNetwork(TServer& Server, TPPSMonitor& PPSMonitor, TResourceManager& R : mServer(Server) , mPPSMonitor(PPSMonitor) , mResourceManager(ResourceManager) { + Application::SetSubsystemStatus("TCPNetwork", Application::Status::Starting); + Application::SetSubsystemStatus("UDPNetwork", Application::Status::Starting); Application::RegisterShutdownHandler([&] { beammp_debug("Kicking all players due to shutdown"); Server.ForEachClient([&](std::weak_ptr client) -> bool { @@ -56,10 +58,10 @@ void TNetwork::UDPServerMain() { if (bind(mUDPSock, (sockaddr*)&serverAddr, sizeof(serverAddr)) != 0) { beammp_error("bind() failed: " + GetPlatformAgnosticErrorString()); std::this_thread::sleep_for(std::chrono::seconds(5)); - exit(-1); + exit(-1); // TODO: Wtf. // return; } - + Application::SetSubsystemStatus("UDPNetwork", Application::Status::Good); beammp_info(("Vehicle data network online on port ") + std::to_string(Application::Settings.Port) + (" with a Max of ") + std::to_string(Application::Settings.MaxPlayers) + (" Clients")); while (!mShutdown) { @@ -123,7 +125,7 @@ void TNetwork::TCPServerMain() { if (bind(Listener, (sockaddr*)&addr, sizeof(addr)) != 0) { beammp_error("bind() failed: " + GetPlatformAgnosticErrorString()); std::this_thread::sleep_for(std::chrono::seconds(5)); - exit(-1); + exit(-1); // TODO: Wtf. } if (Listener == -1) { beammp_error("Invalid listening socket"); @@ -134,6 +136,7 @@ void TNetwork::TCPServerMain() { // FIXME leak Listener return; } + Application::SetSubsystemStatus("TCPNetwork", Application::Status::Good); beammp_info(("Vehicle event network online")); do { try { @@ -211,7 +214,7 @@ void TNetwork::Authentication(const TConnection& ClientConnection) { beammp_trace("This thread is ip " + std::string(str)); Client->SetIdentifier("ip", str); - std::string Rc; //TODO: figure out why this is not default constructed + std::string Rc; // TODO: figure out why this is not default constructed beammp_info("Identifying new ClientConnection..."); Rc = TCPRcv(*Client); diff --git a/src/TPPSMonitor.cpp b/src/TPPSMonitor.cpp index 9ea80dc..5d28187 100644 --- a/src/TPPSMonitor.cpp +++ b/src/TPPSMonitor.cpp @@ -4,6 +4,7 @@ TPPSMonitor::TPPSMonitor(TServer& Server) : mServer(Server) { + Application::SetSubsystemStatus("PPSMonitor", Application::Status::Starting); Application::SetPPS("-"); Application::RegisterShutdownHandler([&] { if (mThread.joinable()) { @@ -22,6 +23,7 @@ void TPPSMonitor::operator()() { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } beammp_debug("PPSMonitor starting"); + Application::SetSubsystemStatus("PPSMonitor", Application::Status::Good); std::vector> TimedOutClients; while (!mShutdown) { std::this_thread::sleep_for(std::chrono::seconds(1)); diff --git a/src/TResourceManager.cpp b/src/TResourceManager.cpp index 96bc42a..afb0e6b 100644 --- a/src/TResourceManager.cpp +++ b/src/TResourceManager.cpp @@ -6,6 +6,7 @@ namespace fs = std::filesystem; TResourceManager::TResourceManager() { + Application::SetSubsystemStatus("ResourceManager", Application::Status::Starting); std::string Path = Application::Settings.Resource + "/Client"; if (!fs::exists(Path)) fs::create_directories(Path); @@ -13,11 +14,11 @@ TResourceManager::TResourceManager() { std::string File(entry.path().string()); if (auto pos = File.find(".zip"); pos != std::string::npos) { if (File.length() - pos == 4) { - std::replace(File.begin(), File.end(),'\\','/'); + std::replace(File.begin(), File.end(), '\\', '/'); mFileList += File + ';'; - if(auto i = File.find_last_of('/'); i != std::string::npos){ + if (auto i = File.find_last_of('/'); i != std::string::npos) { ++i; - File = File.substr(i,pos-i); + File = File.substr(i, pos - i); } mTrimmedList += "/" + fs::path(File).filename().string() + ';'; mFileSizes += std::to_string(size_t(fs::file_size(entry.path()))) + ';'; @@ -29,4 +30,6 @@ TResourceManager::TResourceManager() { if (mModsLoaded) beammp_info("Loaded " + std::to_string(mModsLoaded) + " Mods"); + + Application::SetSubsystemStatus("ResourceManager", Application::Status::Good); } diff --git a/src/TSentry.cpp b/src/TSentry.cpp index 447392f..da05c10 100644 --- a/src/TSentry.cpp +++ b/src/TSentry.cpp @@ -55,6 +55,7 @@ void TSentry::SetupUser() { if (!mValid) { return; } + Application::SetSubsystemStatus("Sentry", Application::Status::Good); sentry_value_t user = sentry_value_new_object(); if (Application::Settings.Key.size() == 36) { sentry_value_set_by_key(user, "id", sentry_value_new_string(Application::Settings.Key.c_str())); diff --git a/src/TServer.cpp b/src/TServer.cpp index 69d6bb9..0f1ad32 100644 --- a/src/TServer.cpp +++ b/src/TServer.cpp @@ -17,6 +17,7 @@ namespace json = rapidjson; TServer::TServer(const std::vector& Arguments) { beammp_info("BeamMP Server v" + Application::ServerVersionString()); + Application::SetSubsystemStatus("Server", Application::Status::Starting); if (Arguments.size() > 1) { Application::Settings.CustomIP = Arguments[0]; size_t n = std::count(Application::Settings.CustomIP.begin(), Application::Settings.CustomIP.end(), '.'); @@ -28,6 +29,7 @@ TServer::TServer(const std::vector& Arguments) { beammp_info("server started with custom IP"); } } + Application::SetSubsystemStatus("Server", Application::Status::Good); } void TServer::RemoveClient(const std::weak_ptr& WeakClientPtr) { diff --git a/src/main.cpp b/src/main.cpp index ca29d12..0d3ae5b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -78,6 +78,7 @@ int main(int argc, char** argv) { int BeamMPServerMain(MainArguments Arguments) { setlocale(LC_ALL, "C"); Application::InitializeConsole(); + Application::SetSubsystemStatus("Main", Application::Status::Starting); SetupSignalHandlers(); @@ -148,7 +149,7 @@ int BeamMPServerMain(MainArguments Arguments) { beammp_trace("Running in debug mode on a debug build"); Sentry.SetupUser(); Sentry.PrintWelcome(); - TResourceManager ResourceManager; + TResourceManager ResourceManager; TPPSMonitor PPSMonitor(Server); THeartbeatThread Heartbeat(ResourceManager, Server); TNetwork Network(Server, PPSMonitor, ResourceManager); @@ -161,17 +162,35 @@ int BeamMPServerMain(MainArguments Arguments) { Http::Server::THttpServerInstance HttpServerInstance {}; } + Application::SetSubsystemStatus("Main", Application::Status::Good); RegisterThread("Main(Waiting)"); - auto Statuses = Application::GetSubsystemStatuses(); - for (const auto& NameStatusPair : Statuses) { - if (NameStatusPair.second != Application::Status::Good) { - beammp_info("not good: " + NameStatusPair.first); - } - } - + bool FullyStarted = false; while (!Shutdown) { - std::this_thread::sleep_for(std::chrono::milliseconds(50)); + if (!FullyStarted) { + FullyStarted = true; + bool WithErrors = false; + std::string SystemsBadList {}; + auto Statuses = Application::GetSubsystemStatuses(); + for (const auto& NameStatusPair : Statuses) { + if (NameStatusPair.second == Application::Status::Starting) { + FullyStarted = false; + } else if (NameStatusPair.second == Application::Status::Bad) { + SystemsBadList += NameStatusPair.first + ", "; + WithErrors = true; + } + } + // remove ", " + SystemsBadList = SystemsBadList.substr(0, SystemsBadList.size() - 2); + if (FullyStarted) { + if (!WithErrors) { + beammp_info("ALL SYSTEMS STARTED SUCCESSFULLY, EVERYTHING IS OKAY"); + } else { + beammp_error("STARTUP NOT SUCCESSFUL, SYSTEMS " + SystemsBadList + " HAD ERRORS. THIS MAY OR MAY NOT CAUSE ISSUES."); + } + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); } beammp_info("Shutdown."); return 0;