Skip to content

Commit

Permalink
feat(base): top 10 processes output
Browse files Browse the repository at this point in the history
- The Process button now displays an inline button that returns a table with information about the TOP 10 processes in terms of CPU and memory usage.
- If the bot is running in a Docker environment, limited information about processes is displayed (usually about bot processes)
  • Loading branch information
orenlab committed Jan 12, 2025
1 parent aac16bf commit 9cadd55
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 8 deletions.
44 changes: 39 additions & 5 deletions pytmbot/adapters/psutil/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,22 @@

import psutil

from pytmbot.adapters.psutil.types import LoadAverage, MemoryStats, DiskStats, SwapStats, SensorStats, ProcessStats, \
NetworkIOStats, UserInfo, NetworkInterfaceStats, CPUFrequencyStats, CPUUsageStats
from pytmbot.adapters.psutil.adapter_types import (
LoadAverage,
MemoryStats,
DiskStats,
SwapStats,
SensorStats,
ProcessStats,
NetworkIOStats,
UserInfo,
NetworkInterfaceStats,
CPUFrequencyStats,
CPUUsageStats, TopProcess
)
from pytmbot.logs import Logger
from pytmbot.utils import set_naturalsize

# Type definitions


logger = Logger()


Expand Down Expand Up @@ -230,3 +238,29 @@ def _get_cpu_usage():
"cpu_percent": 0.0,
"cpu_percent_per_core": []
})

def get_top_processes(self, count: int = 10) -> TopProcess:
"""Get the top processes by CPU and memory usage."""

def _get_top_processes():
processes = []
for proc in self._psutil.process_iter(['pid', 'name', 'cpu_percent', 'memory_percent']):
with suppress(Exception): # Suppress errors for inaccessible processes
cpu_percent = proc.info['cpu_percent'] or 0.0
memory_percent = proc.info['memory_percent'] or 0.0
processes.append({
"pid": proc.info['pid'],
"name": proc.info['name'],
"cpu_percent": cpu_percent,
"memory_percent": memory_percent
})

# Sort by CPU and memory usage, then take the top `count` processes
sorted_processes = sorted(
processes,
key=lambda p: (p['cpu_percent'], p['memory_percent']),
reverse=True
)
return sorted_processes[:count]

return self._safe_execute("top processes", _get_top_processes, [])
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,10 @@ class CPUFrequencyStats(TypedDict):

class CPUUsageStats(TypedDict):
cpu_percent: float
cpu_percent_per_core: list[float]
cpu_percent_per_core: list[float]

class TopProcess(TypedDict):
pid: int
name: str
cpu_percent: float
memory_percent: float
7 changes: 7 additions & 0 deletions pytmbot/handlers/handler_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
)
from pytmbot.handlers.server_handlers.filesystem import handle_file_system
from pytmbot.handlers.server_handlers.inline.swap import handle_swap_info
from pytmbot.handlers.server_handlers.inline.top_process import handle_process_info
from pytmbot.handlers.server_handlers.load_average import handle_load_average
from pytmbot.handlers.server_handlers.memory import handle_memory
from pytmbot.handlers.server_handlers.network import handle_network
Expand Down Expand Up @@ -251,6 +252,12 @@ def inline_handler_factory() -> HandlerType:
filter_func=lambda call: call.data == "__swap_info__"
)
],
"process_info": [
HandlerConfig(
callback=handle_process_info,
filter_func=lambda call: call.data == "__process_info__"
)
],
"update_info": [
HandlerConfig(
callback=handle_update_info,
Expand Down
9 changes: 7 additions & 2 deletions pytmbot/handlers/server_handlers/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from pytmbot import exceptions
from pytmbot.exceptions import ErrorContext
from pytmbot.globals import em, psutil_adapter, running_in_docker
from pytmbot.globals import em, psutil_adapter, running_in_docker, keyboards, button_data
from pytmbot.logs import Logger
from pytmbot.parsers.compiler import Compiler

Expand Down Expand Up @@ -38,6 +38,11 @@ def handle_process(message: Message, bot: TeleBot):
message.chat.id, text="⚠️ Some error occurred. Please try again later("
)

inline_key = button_data(
text="Top 10 processes", callback_data="__process_info__"
)
keyboard = keyboards.build_inline_keyboard(inline_key)

with Compiler(
template_name="b_process.jinja2",
context=process_count,
Expand All @@ -46,7 +51,7 @@ def handle_process(message: Message, bot: TeleBot):
) as compiler:
message_text = compiler.compile()

return bot.send_message(message.chat.id, text=message_text, parse_mode="HTML")
return bot.send_message(message.chat.id, text=message_text, parse_mode="HTML", reply_markup=keyboard)

except Exception as error:
bot.send_message(
Expand Down
26 changes: 26 additions & 0 deletions pytmbot/templates/base_templates/b_top_processes.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{# templates/b_top_processes.jinja2 #}
{# Template for displaying top CPU and memory consuming processes #}

{{ thought_balloon }} System Resource Usage - Top Processes

<pre language="bash">
PID | Process Name | CPU | Memory
-------------------------------------
{%- for process in context.processes %}

{{ "%-5d"|format(process.pid) }} | {{ "%-13s"|format(process.name[:18] + ('...' if process.name|length > 13 else '')) }} | {{ "%3.1f%%"|format(process.cpu_percent) }} | {{ "%5.1f%%"|format(process.memory_percent) }}
{%- endfor %}
</pre>

{{ information }} Updated: {{ context.timestamp }}

{%- if context.running_in_docker %}


{{ warning }} <i>Limited resource information available in Docker environment</i>
{%- endif %}

{# Display warning if no processes found #}
{%- if not context.processes %}
{{ warning }} No process data available
{%- endif %}

0 comments on commit 9cadd55

Please sign in to comment.