-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
253 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
#!/venv/bin/python3 | ||
""" | ||
(c) Copyright 2024, Denis Rozhnovskiy <[email protected]> | ||
pyTMBot - A simple Telegram bot to handle Docker containers and images, | ||
also providing basic information about the status of local servers. | ||
""" | ||
from concurrent.futures import ThreadPoolExecutor, as_completed | ||
from typing import Dict, Any, Optional | ||
|
||
from telebot import TeleBot | ||
from telebot.types import Message | ||
|
||
from pytmbot import exceptions | ||
from pytmbot.adapters.docker.containers_info import fetch_docker_counters | ||
from pytmbot.exceptions import ErrorContext | ||
from pytmbot.globals import psutil_adapter, em | ||
from pytmbot.logs import Logger | ||
from pytmbot.parsers.compiler import Compiler | ||
|
||
logger = Logger() | ||
|
||
|
||
def _get_uptime() -> Optional[str]: | ||
"""Get system uptime.""" | ||
try: | ||
return psutil_adapter.get_uptime() | ||
except Exception as e: | ||
logger.error(f"Failed to get uptime: {e}") | ||
return None | ||
|
||
|
||
def _get_load() -> Optional[tuple]: | ||
"""Get load average.""" | ||
try: | ||
return psutil_adapter.get_load_average() | ||
except Exception as e: | ||
logger.error(f"Failed to get load average: {e}") | ||
return None | ||
|
||
|
||
def _get_memory() -> Optional[Dict]: | ||
"""Get memory statistics.""" | ||
try: | ||
return psutil_adapter.get_memory() | ||
except Exception as e: | ||
logger.error(f"Failed to get memory stats: {e}") | ||
return None | ||
|
||
|
||
def _get_processes() -> Optional[Dict]: | ||
"""Get process counts.""" | ||
try: | ||
return psutil_adapter.get_process_counts() | ||
except Exception as e: | ||
logger.error(f"Failed to get process counts: {e}") | ||
return None | ||
|
||
|
||
def _get_cpu() -> Optional[Dict]: | ||
"""Get CPU usage.""" | ||
try: | ||
return psutil_adapter.get_cpu_usage() | ||
except Exception as e: | ||
logger.error(f"Failed to get CPU usage: {e}") | ||
return None | ||
|
||
|
||
def _get_docker() -> Optional[Dict]: | ||
"""Get Docker statistics.""" | ||
try: | ||
return fetch_docker_counters() | ||
except Exception as e: | ||
logger.error(f"Failed to get Docker stats: {e}") | ||
return None | ||
|
||
|
||
def _collect_metrics() -> Dict[str, Any]: | ||
""" | ||
Collect all metrics concurrently using ThreadPoolExecutor. | ||
Returns: | ||
Dict containing all collected metrics. | ||
""" | ||
metrics = {} | ||
|
||
# Define tasks to run concurrently | ||
tasks = { | ||
'uptime': _get_uptime, | ||
'load_average': _get_load, | ||
'memory': _get_memory, | ||
'processes': _get_processes, | ||
'cpu': _get_cpu, | ||
'docker': _get_docker | ||
} | ||
|
||
with ThreadPoolExecutor(max_workers=len(tasks)) as executor: | ||
# Start all tasks | ||
future_to_task = { | ||
executor.submit(func): name | ||
for name, func in tasks.items() | ||
} | ||
|
||
# Collect results as they complete | ||
for future in as_completed(future_to_task): | ||
task_name = future_to_task[future] | ||
try: | ||
result = future.result() | ||
if result is not None: | ||
metrics[task_name] = result | ||
else: | ||
logger.warning(f"Task {task_name} returned None") | ||
except Exception as e: | ||
logger.error(f"Task {task_name} generated an exception: {e}") | ||
|
||
return metrics | ||
|
||
|
||
# regexp="Quick view|Quick status|qv" | ||
@logger.session_decorator | ||
def handle_quick_view(message: Message, bot: TeleBot): | ||
"""Handle quick view command to show system and Docker summary.""" | ||
emojis = { | ||
"computer": em.get_emoji("desktop_computer"), | ||
"chart": em.get_emoji("bar_chart"), | ||
"memory": em.get_emoji("brain"), | ||
"cpu": em.get_emoji("electric_plug"), | ||
"process": em.get_emoji("gear"), | ||
"docker": em.get_emoji("whale"), | ||
"warning": em.get_emoji("warning"), | ||
} | ||
|
||
try: | ||
bot.send_chat_action(message.chat.id, "typing") | ||
|
||
# Collect all metrics concurrently | ||
metrics = _collect_metrics() | ||
|
||
if not metrics: | ||
logger.error("Failed to collect any metrics for quick view") | ||
return bot.send_message( | ||
message.chat.id, | ||
text="⚠️ Failed to get system metrics. Please try again later." | ||
) | ||
|
||
# Prepare context for template | ||
context = { | ||
"system": { | ||
"uptime": metrics.get('uptime', 'N/A'), | ||
"load_average": metrics.get('load_average', (0, 0, 0)), | ||
"memory": metrics.get('memory', {}), | ||
"processes": metrics.get('processes', {}), | ||
"cpu": metrics.get('cpu', {}) | ||
} | ||
} | ||
|
||
# Add Docker metrics if available | ||
if 'docker' in metrics: | ||
context['docker'] = metrics['docker'] | ||
|
||
with Compiler( | ||
template_name="b_quick_view.jinja2", | ||
context=context, | ||
**emojis | ||
) as compiler: | ||
bot_answer = compiler.compile() | ||
|
||
bot.send_message( | ||
message.chat.id, | ||
text=bot_answer, | ||
parse_mode="Markdown" | ||
) | ||
|
||
except Exception as error: | ||
bot.send_message( | ||
message.chat.id, | ||
"⚠️ An error occurred while processing the command." | ||
) | ||
raise exceptions.HandlingException(ErrorContext( | ||
message="Failed handling quick view command", | ||
error_code="HAND_QV1", | ||
metadata={"exception": str(error)} | ||
)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,41 +1,32 @@ | ||
{{ thought_balloon }} Here is: | ||
|
||
🏗 **Updating the image** | ||
|
||
To update the image to the latest version, please follow these steps: | ||
|
||
1. **Stop the running pyTMbot container:** | ||
```bash | ||
sudo docker stop pytmbot | ||
``` | ||
|
||
2. **Delete the outdated container:** | ||
```bash | ||
sudo docker rm pytmbot | ||
``` | ||
|
||
3. **Delete the outdated image:** | ||
```bash | ||
sudo docker rmi pytmbot | ||
``` | ||
|
||
4. **Pull the updated image:** | ||
```bash | ||
sudo docker pull orenlab/pytmbot:latest | ||
``` | ||
|
||
5. **Run the container as you would if you had just installed the bot:** | ||
|
||
```bash | ||
sudo docker run -d \ | ||
-v /var/run/docker.sock:/var/run/docker.sock:ro \ | ||
-v /root/pytmbot.yaml:/opt/pytmbot/pytmbot.yaml:ro \ | ||
--env TZ="Asia/Yekaterinburg" \ | ||
--restart=always \ | ||
--name=pytmbot \ | ||
--pid=host \ | ||
--security-opt=no-new-privileges \ | ||
orenlab/pytmbot:latest | ||
``` | ||
|
||
*Note: Please don't forget to adjust the time zone!* | ||
{{ thought_balloon }} <b>Updating PyTMBot to Latest Version</b> | ||
|
||
Choose your preferred update method: | ||
|
||
<b>A. Using Docker Compose (Recommended)</b> | ||
|
||
1) Update your docker-compose.yml: | ||
|
||
<code>docker compose pull && docker compose up -d</code> | ||
|
||
<b>B. Using Docker CLI</b> | ||
|
||
1) <code>docker stop pytmbot && docker rm pytmbot</code> | ||
|
||
2) <code>docker pull orenlab/pytmbot:latest</code> | ||
|
||
3) Run new container: | ||
<pre>docker run -d \ | ||
--name pytmbot \ | ||
--restart on-failure \ | ||
--env TZ="Asia/Yekaterinburg" \ | ||
-v /var/run/docker.sock:/var/run/docker.sock:ro \ | ||
-v /root/pytmbot.yaml:/opt/app/pytmbot.yaml:ro \ | ||
--security-opt no-new-privileges \ | ||
--read-only \ | ||
--cap-drop ALL \ | ||
--pid=host \ | ||
--log-opt max-size=10m \ | ||
--log-opt max-file=3 \ | ||
orenlab/pytmbot:latest --plugins SOME_PLUGINS</pre> | ||
|
||
⚠️ <b>Note</b>: <i>Adjust timezone as needed!</i> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
{# templates/b_quick_view.jinja2 #} | ||
🖥️ *System Quick View* | ||
|
||
```bash | ||
{% if context.system %} | ||
🖥️ | ||
├─ ⏱️ Uptime: {{ "{:>10}".format(context.system.uptime if context.system.uptime else 'N/A') }} | ||
├─ 📊 Load Average: | ||
│ ├─ 1 min: {{ "{:>10.2f}".format(context.system.load_average[0] if context.system.load_average and context.system.load_average[0] is defined else 'N/A') }} | ||
│ ├─ 5 min: {{ "{:>10.2f}".format(context.system.load_average[1] if context.system.load_average and context.system.load_average[1] is defined else 'N/A') }} | ||
│ └─ 15 min: {{ "{:>10.2f}".format(context.system.load_average[2] if context.system.load_average and context.system.load_average[2] is defined else 'N/A') }} | ||
├─ 🧠 Memory: | ||
│ ├─ Used: {{ "{:>10}".format(context.system.memory.used if context.system.memory and context.system.memory.used else 'N/A') }} | ||
│ ├─ Free: {{ "{:>10}".format(context.system.memory.free if context.system.memory and context.system.memory.free else 'N/A') }} | ||
│ └─ Usage: {{ "{:>10}".format((context.system.memory.percent | string + '%') if context.system.memory and context.system.memory.percent is not none else 'N/A') }} | ||
├─ ⚡ CPU Usage: | ||
│ └─ {{ "{:>10}".format((context.system.cpu.cpu_percent | string + '%') if context.system.cpu and context.system.cpu.cpu_percent is not none else 'N/A') }} | ||
└─ ⚙️ Processes: | ||
├─ Running: {{ "{:>10}".format(context.system.processes.running if context.system.processes and context.system.processes.running else 'N/A') }} | ||
└─ Total: {{ "{:>10}".format(context.system.processes.total if context.system.processes and context.system.processes.total else 'N/A') }} | ||
{% else %} | ||
⚠️ System data is not available. | ||
{% endif %} | ||
{% if context.docker %} | ||
🐳 | ||
└─ 🐳 Docker Status | ||
├─ Containers: {{ "{:>10}".format(context.docker.containers_count if context.docker.containers_count else 'N/A') }} | ||
└─ Images: {{ "{:>10}".format(context.docker.images_count if context.docker.images_count else 'N/A') }} | ||
{% endif %} | ||
``` |
Empty file.