@@ -8,7 +8,7 @@ use anyhow::Context;
88use bytes:: Bytes ;
99use dashmap:: DashMap ;
1010use futures_util:: { StreamExt , TryStreamExt } ;
11- use sysinfo:: System ;
11+ use sysinfo:: { ProcessRefreshKind , ProcessesToUpdate , System , get_current_pid } ;
1212use teloxide:: prelude:: * ;
1313use teloxide:: sugar:: request:: RequestLinkPreviewExt ;
1414use teloxide:: types:: {
@@ -104,21 +104,22 @@ struct SpeedSnapshot {
104104#[ derive( Debug , Clone , Copy , Default ) ]
105105struct ResourceSnapshot {
106106 cpu_percent : f32 ,
107- used_memory_mb : u64 ,
108- total_memory_mb : u64 ,
109- available_memory_mb : u64 ,
107+ system_used_memory_mb : u64 ,
108+ system_total_memory_mb : u64 ,
109+ bot_memory_mb : Option < u64 > ,
110110}
111111
112112static STATUS_RESOURCE_CACHE : LazyLock < std:: sync:: Mutex < ( System , Instant , ResourceSnapshot ) > > =
113113 LazyLock :: new ( || {
114114 let mut system = System :: new ( ) ;
115115 system. refresh_cpu_usage ( ) ;
116116 system. refresh_memory ( ) ;
117+ let bot_memory_mb = sample_current_process_memory_mb ( & mut system) ;
117118 let snapshot = ResourceSnapshot {
118119 cpu_percent : system. global_cpu_usage ( ) ,
119- used_memory_mb : system. used_memory ( ) / ( 1024 * 1024 ) ,
120- total_memory_mb : system. total_memory ( ) / ( 1024 * 1024 ) ,
121- available_memory_mb : system . available_memory ( ) / ( 1024 * 1024 ) ,
120+ system_used_memory_mb : system. used_memory ( ) / ( 1024 * 1024 ) ,
121+ system_total_memory_mb : system. total_memory ( ) / ( 1024 * 1024 ) ,
122+ bot_memory_mb ,
122123 } ;
123124 std:: sync:: Mutex :: new ( ( system, Instant :: now ( ) , snapshot) )
124125 } ) ;
@@ -388,17 +389,75 @@ fn sample_resource_snapshot() -> ResourceSnapshot {
388389 if last_refresh. elapsed ( ) >= STATUS_RESOURCE_REFRESH_INTERVAL {
389390 system. refresh_cpu_usage ( ) ;
390391 system. refresh_memory ( ) ;
392+ let bot_memory_mb = sample_current_process_memory_mb ( system) ;
391393 * snapshot = ResourceSnapshot {
392394 cpu_percent : system. global_cpu_usage ( ) ,
393- used_memory_mb : system. used_memory ( ) / ( 1024 * 1024 ) ,
394- total_memory_mb : system. total_memory ( ) / ( 1024 * 1024 ) ,
395- available_memory_mb : system . available_memory ( ) / ( 1024 * 1024 ) ,
395+ system_used_memory_mb : system. used_memory ( ) / ( 1024 * 1024 ) ,
396+ system_total_memory_mb : system. total_memory ( ) / ( 1024 * 1024 ) ,
397+ bot_memory_mb ,
396398 } ;
397399 * last_refresh = Instant :: now ( ) ;
398400 }
399401 * snapshot
400402}
401403
404+ fn sample_current_process_memory_mb ( system : & mut System ) -> Option < u64 > {
405+ let current_pid = get_current_pid ( ) . ok ( ) ?;
406+ let pids = [ current_pid] ;
407+ system. refresh_processes_specifics (
408+ ProcessesToUpdate :: Some ( & pids) ,
409+ false ,
410+ ProcessRefreshKind :: nothing ( ) . with_memory ( ) ,
411+ ) ;
412+ system
413+ . process ( current_pid)
414+ . map ( |process| process. memory ( ) / ( 1024 * 1024 ) )
415+ }
416+
417+ fn format_bot_memory ( bot_memory_mb : Option < u64 > ) -> String {
418+ bot_memory_mb
419+ . map ( |mb| format ! ( "{mb} MB" ) )
420+ . unwrap_or_else ( || "n/a" . to_string ( ) )
421+ }
422+
423+ fn build_status_text (
424+ total_count : i64 ,
425+ user_count : i64 ,
426+ chat_count : i64 ,
427+ cache_snapshot : CacheSnapshot ,
428+ resource_snapshot : ResourceSnapshot ,
429+ uptime : & str ,
430+ download_line : & str ,
431+ upload_line : & str ,
432+ ) -> String {
433+ format ! (
434+ "Status\n \n \
435+ Cache:\n \
436+ Total: {total_count}\n \
437+ User: {user_count}\n \
438+ Chat: {chat_count}\n \n \
439+ Runtime Cache:\n \
440+ Hits: {hits}\n \
441+ Misses: {misses}\n \
442+ Hit Rate: {hit_rate:.2}%\n \n \
443+ Resources:\n \
444+ CPU: {cpu:.1}%\n \
445+ System Memory: {system_used} / {system_total} MB\n \
446+ Bot Memory: {bot_memory}\n \
447+ Uptime: {uptime}\n \n \
448+ Transfer:\n \
449+ {download_line}\n \
450+ {upload_line}",
451+ hits = cache_snapshot. hits,
452+ misses = cache_snapshot. misses,
453+ hit_rate = cache_snapshot. hit_rate_percent,
454+ cpu = resource_snapshot. cpu_percent,
455+ system_used = resource_snapshot. system_used_memory_mb,
456+ system_total = resource_snapshot. system_total_memory_mb,
457+ bot_memory = format_bot_memory( resource_snapshot. bot_memory_mb) ,
458+ )
459+ }
460+
402461fn format_uptime ( duration : std:: time:: Duration ) -> String {
403462 let secs = duration. as_secs ( ) ;
404463 let hours = secs / 3600 ;
@@ -2673,6 +2732,68 @@ mod tests {
26732732 assert ! ( ok) ;
26742733 }
26752734
2735+ #[ test]
2736+ fn parse_telegram_api_response_returns_error_when_http_200_ok_false ( ) {
2737+ let body = r#"{"ok": false, "description": "chat not found"}"# ;
2738+ let err = super :: parse_telegram_api_response ( body, reqwest:: StatusCode :: OK , "sendAudio" )
2739+ . expect_err ( "ok=false should be treated as Telegram API error" ) ;
2740+ let err_msg = err. to_string ( ) ;
2741+
2742+ assert ! ( err_msg. contains( "chat not found" ) ) ;
2743+ assert ! ( err_msg. contains( "HTTP 200" ) ) ;
2744+ }
2745+
2746+ #[ test]
2747+ fn parse_telegram_api_response_returns_error_when_http_500_for_any_ok_flag ( ) {
2748+ let cases = [
2749+ ( r#"{"ok": true, "result": {}}"# , "unknown error" ) ,
2750+ (
2751+ r#"{"ok": false, "description": "server failed"}"# ,
2752+ "server failed" ,
2753+ ) ,
2754+ ] ;
2755+
2756+ for ( body, expected_desc) in cases {
2757+ let err = super :: parse_telegram_api_response (
2758+ body,
2759+ reqwest:: StatusCode :: INTERNAL_SERVER_ERROR ,
2760+ "sendAudio" ,
2761+ )
2762+ . expect_err ( "non-2xx status should always be treated as error" ) ;
2763+ let err_msg = err. to_string ( ) ;
2764+
2765+ assert ! ( err_msg. contains( expected_desc) ) ;
2766+ assert ! ( err_msg. contains( "HTTP 500" ) ) ;
2767+ }
2768+ }
2769+
2770+ #[ test]
2771+ fn parse_telegram_api_response_returns_parse_error_for_non_json_body ( ) {
2772+ let err = super :: parse_telegram_api_response (
2773+ "<html>502 bad gateway</html>" ,
2774+ reqwest:: StatusCode :: OK ,
2775+ "sendAudio" ,
2776+ )
2777+ . expect_err ( "non-JSON body should fail response parsing" ) ;
2778+ let err_msg = err. to_string ( ) ;
2779+
2780+ assert ! ( err_msg. contains( "Failed to parse upload response" ) ) ;
2781+ }
2782+
2783+ #[ test]
2784+ fn parse_telegram_api_response_uses_unknown_error_when_description_missing ( ) {
2785+ let err = super :: parse_telegram_api_response (
2786+ r#"{"ok": false}"# ,
2787+ reqwest:: StatusCode :: OK ,
2788+ "sendAudio" ,
2789+ )
2790+ . expect_err ( "missing description should still return a Telegram API error" ) ;
2791+ let err_msg = err. to_string ( ) ;
2792+
2793+ assert ! ( err_msg. contains( "unknown error" ) ) ;
2794+ assert ! ( err_msg. contains( "HTTP 200" ) ) ;
2795+ }
2796+
26762797 #[ test]
26772798 fn extract_file_id_reads_audio_field ( ) {
26782799 let payload = serde_json:: json!( {
@@ -2852,6 +2973,34 @@ mod tests {
28522973 let line = super :: format_speed_line ( "Download" , None ) ;
28532974 assert ! ( line. contains( "no non-cache samples" ) ) ;
28542975 }
2976+
2977+ #[ test]
2978+ fn status_text_uses_section_layout_and_split_memory_fields ( ) {
2979+ let cache_snapshot = super :: CacheSnapshot {
2980+ hits : 9 ,
2981+ misses : 3 ,
2982+ hit_rate_percent : 75.0 ,
2983+ } ;
2984+ let resource_snapshot = super :: ResourceSnapshot {
2985+ cpu_percent : 12.5 ,
2986+ system_used_memory_mb : 512 ,
2987+ system_total_memory_mb : 1024 ,
2988+ bot_memory_mb : Some ( 12 ) ,
2989+ } ;
2990+ let text = super :: build_status_text (
2991+ 100 ,
2992+ 20 ,
2993+ 8 ,
2994+ cache_snapshot,
2995+ resource_snapshot,
2996+ "00:10:00" ,
2997+ "Download speed: 6.00 MB/s" ,
2998+ "Upload speed: 2.00 MB/s" ,
2999+ ) ;
3000+ assert ! ( text. contains( "Cache:\n Total: 100\n User: 20\n Chat: 8" ) ) ;
3001+ assert ! ( text. contains( "System Memory: 512 / 1024 MB" ) ) ;
3002+ assert ! ( text. contains( "Bot Memory: 12 MB" ) ) ;
3003+ }
28553004}
28563005
28573006async fn handle_music_url (
@@ -3141,27 +3290,15 @@ async fn handle_status_command(
31413290 let uptime = format_uptime ( state. runtime_metrics . uptime ( ) ) ;
31423291 let download_line = format_speed_line ( "Download" , download_speed) ;
31433292 let upload_line = format_speed_line ( "Upload" , upload_speed) ;
3144-
3145- let status_text = format ! (
3146- "Status\n \n \
3147- Cache total: {total_count}\n \
3148- Cache user: {user_count}\n \
3149- Cache chat: {chat_count}\n \n \
3150- Runtime cache hits: {hits}\n \
3151- Runtime cache misses: {misses}\n \
3152- Runtime hit rate: {hit_rate:.2}%\n \n \
3153- CPU usage: {cpu:.1}%\n \
3154- Memory: {used} / {total} MB (available {available} MB)\n \
3155- Uptime: {uptime}\n \n \
3156- {download_line}\n \
3157- {upload_line}",
3158- hits = cache_snapshot. hits,
3159- misses = cache_snapshot. misses,
3160- hit_rate = cache_snapshot. hit_rate_percent,
3161- cpu = resource_snapshot. cpu_percent,
3162- used = resource_snapshot. used_memory_mb,
3163- total = resource_snapshot. total_memory_mb,
3164- available = resource_snapshot. available_memory_mb
3293+ let status_text = build_status_text (
3294+ total_count,
3295+ user_count,
3296+ chat_count,
3297+ cache_snapshot,
3298+ resource_snapshot,
3299+ & uptime,
3300+ & download_line,
3301+ & upload_line,
31653302 ) ;
31663303
31673304 bot. send_message ( msg. chat . id , status_text)
0 commit comments