monitor page because yeah
authorhgn <hgodden00@gmail.com>
Wed, 18 Jun 2025 23:35:23 +0000 (00:35 +0100)
committerhgn <hgodden00@gmail.com>
Wed, 18 Jun 2025 23:35:23 +0000 (00:35 +0100)
src/gameserver.c
src/gameserver.h
src/gameserver_monitor.c [new file with mode: 0644]
src/gameserver_monitor.h [new file with mode: 0644]
src/gameserver_requests.c

index 32c4fb67837dbc0cbdcb8229b8cb33e0ef3b9b22..942df60599776a4e990830314d732818f16fc8db 100644 (file)
@@ -20,6 +20,7 @@ volatile sig_atomic_t sig_stop;
 #include "vg/vg_console.h"
 #include "gameserver_replay.h"
 #include "gameserver_requests.h"
+#include "gameserver_monitor.h"
 
 struct _gameserver _gameserver = 
 {
@@ -56,6 +57,7 @@ static void gameserver_send_to_client( i32 client_id, const void *pData, u32 cbD
    if( !client->steamid )
       return;
 
+   _gameserver.bytes_send += cbData;
    SteamAPI_ISteamNetworkingSockets_SendMessageToConnection( hSteamNetworkingSockets, client->connection,
                                                              pData, cbData, nSendFlags, NULL );
 }
@@ -440,6 +442,7 @@ static void gameserver_rx_auth( SteamNetworkingMessage_t *msg )
    vg_success( "User is authenticated! steamid %lu (%u) [%s]\n", steamid.m_unAll64Bits, msg->m_conn, 
                   client->admin? "Admin": "User" );
    gameserver_player_join( client_id );
+   _gs_monitor_playerjoin();
 }
 
 /*
@@ -692,6 +695,8 @@ static void gameserver_rx_300_400( SteamNetworkingMessage_t *msg )
 
 static void process_network_message( SteamNetworkingMessage_t *msg )
 {
+   _gameserver.bytes_recv += msg->m_cbSize;
+
    if( msg->m_cbSize < sizeof(netmsg_blank) )
    {
       vg_warn( "Discarding message (too small: %d)\n",  msg->m_cbSize );
@@ -864,7 +869,7 @@ static int _rcon_edit_user( int argc, const char *argv[] )
    }
    else 
    {
-      vg_info( "Usage: edit_profile\n"
+      vg_info( "Usage: edit_user <steamid>\n"
                " Options:\n"
                "  +FLAG   (LEGEND,EARLY,ADMIN)\n"
                "  -FLAG\n"
@@ -912,6 +917,16 @@ int main( int argc, const char *argv[] )
       if( vg_long_opt( "replay-info", "Print replay info periodically" ) )
          _gs_replay.print_info = 1;
 
+      if( (arg = vg_long_opt_arg( "journal", "Journal important events into file" )) )
+         if( !_gs_monitor_start_journal( arg ) )
+            return 0;
+
+      if( (arg = vg_long_opt_arg( "status-html", "Record server status to HTML file" )) )
+         _gs_monitor_start_html( arg );
+
+      if( (arg = vg_long_opt_arg( "status-interval", "Inverval which HTML page gets written" )) )
+         _gs_monitor_set_interval( atof( arg ) );
+
       if( !_vg_opt_check() )
          return 0;
    }
@@ -976,6 +991,7 @@ int main( int argc, const char *argv[] )
       poll_connections();
       _gs_replay_server_tick();
       _gs_requests_tick();
+      _gs_monitor_tick();
 
       usleep(10000);
       _gameserver.ticks ++;
@@ -996,19 +1012,21 @@ int main( int argc, const char *argv[] )
       }
    }
 
-EE:vg_info( "Program ends\n" );
+EE:vg_info( "Server end\n" );
    
    SteamAPI_ISteamNetworkingSockets_DestroyPollGroup( hSteamNetworkingSockets, _gameserver.client_group );
    SteamAPI_ISteamNetworkingSockets_CloseListenSocket( hSteamNetworkingSockets, listener );
 
    SteamGameServer_Shutdown();
 E1:db_free();
-E0:return 0;
+E0:_gs_monitor_cleanup();
+   return 0;
 }
 
 #include "gameserver_replay.c"
 #include "gameserver_requests.c"
 #include "gameserver_database.c"
+#include "gameserver_monitor.c"
 #include "vg/vg_async2.c"
 #include "vg/vg_mem_pool.c"
 #include "vg/vg_db.c"
index 10bccecc78642e314439c8d8d9b64e7d2c801757..123d2c99783badb645b04a7b39823a12a76b4fd8 100644 (file)
@@ -25,7 +25,7 @@ struct _gameserver
 
    struct gameserver_client 
    {
-      bool active, authenticated, admin;
+      bool active, admin;
       u32 version;
       HSteamNetConnection connection;
       char username[ NETWORK_USERNAME_MAX ];
@@ -50,6 +50,8 @@ struct _gameserver
    u64 ticks;
    u64 global_uid;
 
+   u64 bytes_recv, bytes_send, bytes_send1;
+
    vg_async_queue tasks;
    pthread_t thread;
 }
diff --git a/src/gameserver_monitor.c b/src/gameserver_monitor.c
new file mode 100644 (file)
index 0000000..ed0e24a
--- /dev/null
@@ -0,0 +1,222 @@
+struct
+{
+   const char *html_path;
+   u64 interval, timer;
+   bool joiner_notify;
+
+   FILE *journal_fp;
+}
+_gs_monitor;
+
+struct task_monitor_write
+{
+   char buf[ 20000 ];
+};
+
+void _monitor_write_task( vg_async_task *task )
+{
+   THREAD_1;
+   struct task_monitor_write *info = (void *)task->data;
+
+   FILE *fp = fopen( _gs_monitor.html_path, "w" );
+   if( fp )
+   {
+      fputs( info->buf, fp );
+      fclose( fp );
+      vg_low( "Written monitor HTML (%s)\n", _gs_monitor.html_path );
+   }
+   else
+      vg_warn( "Failed to open '%s' (server monitor html path)\n" );
+}
+
+void _gs_monitor_tick(void)
+{
+   if( _gs_monitor.html_path )
+   {
+      _gs_monitor.timer ++;
+      if( _gs_monitor.timer >= _gs_monitor.interval )
+      {
+         if( _gs_monitor.interval < 10 )
+            _gs_monitor_set_interval( 30.0f );
+
+         vg_async_task *task = vg_allocate_async_task( &_gs_db.tasks, sizeof(struct task_monitor_write), 0 );
+         if( task )
+         {
+            _gs_monitor.timer = 0;
+            struct task_monitor_write *info = (void *)task->data;
+            
+            vg_str str;
+            vg_strnull( &str, info->buf, sizeof(info->buf) );
+            vg_strcat( &str, "<html>\n"
+                             " <head>\n"
+                             "  <meta http-equiv=\"refresh\" content=\"30\">\n"
+                             " </head>\n"
+                             " <body>\n"
+                             "  <p>Written: <span id=\"elapsed\">JavaScript Disabled.</span></p>\n"
+                             "  <p>Notifications: <span id=\"notif\">None.</span></p>\n"
+                             "  <table>\n"
+                             "   <tr>\n"
+                             "    <th>Item</th>\n"
+                             "    <th>Value</th>\n"
+                             "   </tr>\n" );
+
+            vg_strcat( &str, "   <tr>\n"
+                             "    <td>Tick</td>\n"
+                             "    <td>" );
+            vg_strcatu64( &str, _gameserver.ticks );
+            vg_strcat( &str, "</td>\n"
+                             "   </tr>\n" );
+
+            vg_strcat( &str, "   <tr>\n"
+                             "    <td>Global UID</td>\n"
+                             "    <td>" );
+            vg_strcatu64( &str, _gameserver.global_uid );
+            vg_strcat( &str, "</td>\n"
+                             "   </tr>\n" );
+
+            u32 online = 0, authenticated = 0, admins = 0;
+            for( u32 i=0; i< NETWORK_MAX_PLAYERS; i ++ )
+            {
+               if( _gameserver.clients[i].active )
+                  online ++;
+
+               if( _gameserver.clients[i].steamid )
+                  authenticated ++;
+
+               if( _gameserver.clients[i].admin )
+                  admins ++;
+            }
+
+            static u32 peak = 0;
+            if( authenticated > peak )
+               peak = authenticated;
+
+            vg_strcat( &str, "   <tr>\n"
+                             "    <td>Active Players</td>\n"
+                             "    <td>" );
+            vg_strcati32( &str, online );
+            vg_strcat( &str, "</td>\n"
+                             "   </tr>\n" );
+
+            vg_strcat( &str, "   <tr>\n"
+                             "    <td>Peak Players</td>\n"
+                             "    <td>" );
+            vg_strcati32( &str, peak );
+            vg_strcat( &str, "</td>\n"
+                             "   </tr>\n" );
+
+            vg_strcat( &str, "   <tr>\n"
+                             "    <td>Authenticated Players</td>"
+                             "    <td>" );
+            vg_strcati32( &str, authenticated );
+            vg_strcat( &str, "</td>\n"
+                             "   </tr>\n" );
+
+            vg_strcat( &str, "   <tr>\n"
+                             "    <td>Admins</td>"
+                             "    <td>" );
+            vg_strcati32( &str, admins );
+            vg_strcat( &str, "</td>\n"
+                             "   </tr>\n" );
+
+            vg_strcat( &str, "   <tr>\n"
+                             "    <td>Bytes send (channel 0)</td>"
+                             "    <td>" );
+            vg_strcatu64( &str, _gameserver.bytes_send );
+            vg_strcat( &str, "</td>\n"
+                             "   </tr>\n" );
+
+            vg_strcat( &str, "   <tr>\n"
+                             "    <td>Bytes send (channel 1)</td>"
+                             "    <td>" );
+            vg_strcatu64( &str, _gameserver.bytes_send1 );
+            vg_strcat( &str, "</td>\n"
+                             "   </tr>\n" );
+
+            vg_strcat( &str, "   <tr>\n"
+                             "    <td>Bytes recv (channel 0)</td>"
+                             "    <td>" );
+            vg_strcatu64( &str, _gameserver.bytes_recv );
+            vg_strcat( &str, "</td>\n"
+                             "   </tr>\n" );
+
+            vg_strcat( &str, "  </table>\n"
+                             " </body>\n"
+                             "  <script>\n"
+                             "   function rept(){\n"
+                             "    const now = new Date().getTime() / 1000;\n"
+                             "    var reference = " );
+            vg_strcatu64( &str, time(NULL) );
+            vg_strcat( &str, ";\n"
+                             "    var secs = Math.floor(now - reference);\n"
+                             "    document.getElementById('elapsed').textContent = secs.toString() + \" Seconds Ago\";\n"
+                             "    var audio = 0;"
+                             "    if( secs > 80.0 )\n"
+                             "    {\n"
+                             "     if( audio == 0 )\n"
+                             "      audio = new Audio('alarm.ogg');\n"
+                             "     audio.play();\n"
+                             "     document.getElementById('notif').textContent += \"NO UPDATE? \";\n"
+                             "    }\n"
+                             "   }\n"
+                             "   rept(); setInterval(rept,2000);\n" );
+
+            if( _gs_monitor.joiner_notify )
+            {
+               _gs_monitor.joiner_notify = 0;
+               vg_strcat( &str, "   var audio = new Audio('joiner.ogg');\n"
+                                "   audio.play();\n"
+                                "   document.getElementById('notif').textContent += \"PLAYER JOIN! \";\n" );
+            }
+
+            vg_strcat( &str, "  </script>\n"
+                             "</html>\n" );
+
+            vg_async_task_dispatch( task, _monitor_write_task );
+         }
+      }
+   }
+}
+
+bool _gs_monitor_start_journal( const char *path )
+{
+   _gs_monitor.journal_fp = fopen( path, "a+" );
+
+   if( !_gs_monitor.journal_fp )
+   {
+      vg_error( "Failed to open journal for writing (%s)\n", path );
+      return 0;
+   }
+
+   return 1;
+}
+
+void _gs_monitor_start_html( const char *path )
+{
+   _gs_monitor.html_path = path;
+}
+
+void _gs_monitor_set_interval( f64 seconds )
+{
+   _gs_monitor.interval = seconds_to_server_ticks( seconds );
+   _gs_monitor.timer = 0;
+}
+
+void _gs_monitor_log_event( const char *event )
+{
+   /* TODO */
+}
+
+void _gs_monitor_playerjoin(void)
+{
+   _gs_monitor.joiner_notify = 1;
+}
+
+void _gs_monitor_cleanup(void)
+{
+   if( _gs_monitor.journal_fp )
+   {
+      fclose( _gs_monitor.journal_fp );
+      _gs_monitor.journal_fp = NULL;
+   }
+}
diff --git a/src/gameserver_monitor.h b/src/gameserver_monitor.h
new file mode 100644 (file)
index 0000000..593da76
--- /dev/null
@@ -0,0 +1,8 @@
+#pragma once
+void _gs_monitor_tick(void);
+bool _gs_monitor_start_journal( const char *path );
+void _gs_monitor_start_html( const char *path );
+void _gs_monitor_set_interval( f64 seconds );
+void _gs_monitor_log_event( const char *event );
+void _gs_monitor_cleanup(void);
+void _gs_monitor_playerjoin(void);
index 50bf9ee7ad902ee6c09d59386b22bf93d01a750b..e167e21551dde0bc26d2ad8a9d3a09c6909bc333 100644 (file)
@@ -489,6 +489,6 @@ void _gs_handle_request_message( u32 client_id, SteamNetworkingMessage_t *msg )
    res->id = client_packet->id;
    res->status = error_status;
    SteamAPI_ISteamNetworkingSockets_SendMessages( hSteamNetworkingSockets, 1, &reply_msg, NULL );
-
    SteamAPI_SteamNetworkingMessage_t_Release( msg );
+   _gameserver.bytes_send1 += size;
 }