Skip to content

Commit

Permalink
Push initial version
Browse files Browse the repository at this point in the history
  • Loading branch information
martomi committed Apr 3, 2021
1 parent 7049a5b commit 9f95801
Show file tree
Hide file tree
Showing 33 changed files with 1,128 additions and 0 deletions.
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# secrets
config.yaml

# dev files
.idea
venv

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
114 changes: 114 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<p align="center">
<img src="https://raw.githubusercontent.com/martomi/chiadog/main/logo.jpg" />
</p>
<p align="center">
Photo by <a href="https://unsplash.com/@tukacszoltan1984?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Zoltan Tukacs</a> on <a href="https://unsplash.com/s/photos/dog-grass?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>
</p>

--------------------------------------------------------------------

# Watchdog for your Chia farm

So you've become a [Chia](https://www.chia.net) farmer and are now looking to improve the uptime of your farm? How about
some automated monitoring that sends notifications directly to your phone?

This tool parses the `debug.log` generated by the chia process and runs various checks to determine the health of your
farming operations. It can detect if your node has lost sync and the farmer is no longer participating in challenges, or
if one of your external HDDs disconnected and your harvester doesn't have access to the full amount of plots you have.

## List of checked conditions:

- Time since last eligible farming event
- Non-decreasing number of total plots farmed
- Quick plot seeking time when responding to a challenge
- Whether a challenge proof was found

## Smartphone Notifications

In case of deviations from the expected values, notifications with low, normal, or high priority can be triggered.
Currently the tool supports integration with [Pushover](https://pushover.net/)
which is available for both Android and iOS. High priority notifications can be configured from the Pushover app to
overwrite any Silence or Do-Not-Disturb modes on your phone and sound a loud alarm at any time of the day to make you
aware of any issues in a timely manner.

# Getting started

**So far only tested on Ubuntu 20.04. Requires Python 3.7+.**

1. Clone the repository

```
git clone https://github.com/martomi/chiadog.git
cd chiadog
```

2. Create virtual env (Recommended)

```
python3 -m venv venv
. ./venv/bin/activate
```

3. Install the dependencies:

```
pip3 install -r requirements.txt
```

4. Copy the example config file

```
cp config-example.yaml config.yaml
```

5. Edit the configuration file
- Enable Pushover Notifier
- Set your Pushover API token
- Set your Pushover user key
- Double-check the path to your chia logs

6. Start the watchdog

```
python3 main.py --config config.yaml
```

7. Verify that your plots are detected. Within a few seconds you should see INFO log:

```
Detected new plots. Farming with 42 plots.
```

## Troubleshooting

The best way to check that everything works on your system is to run the unit tests:

```
PUSHOVER_API_TOKEN=<your_token> PUSHOVER_USER_KEY=<your_key> python3 -m unittest
```

If your Pushover API token and user key are correctly set in the snippet above, you should receive notifications on your
phone and the tests should complete with an `OK` status:

You can also enable more verbose logging from `config.yaml` by changing `INFO` to `DEBUG`. Then you'll be able to see
logs for every keep-alive event from the harvester.

# Extending & Contributing

Contributions are welcome, I've added a good amount of doxygen style comments and structured the code in a way that
should make this easier. Happy to provide feedback on PRs.

## Modular and extendable design

Here are some jump-off points where you can start adding extensions:

- additional [condition checks](src/chia_log/handlers/harvester_activity_handler.py) e.g. time since last eligible plot
- additional [parsers](src/chia_log/parsers) to monitor other parts of the log output (e.g. timelord)
- additional [log consumers](src/chia_log/log_consumer.py) to support watchdog over the network (NetworkLogConsumer)
- additional [notifiers](src/notifier) to support notifications over Slack, WhatsApp, E-mail, etc.

## Further feature wishes

- Handlers for automated recovery
- auto-restart chia processes if node cannot re-sync
- auto-restart xHCI devices to recover external HDDs
10 changes: 10 additions & 0 deletions config-example.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
log_level: INFO
chia_logs:
file_log_consumer:
enable: true
file_path: '~/.chia/mainnet/log/debug.log'
notifier:
pushover:
enable: false
api_token: 'dummy_token'
user_key: 'dummy_key'
Binary file added logo.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
81 changes: 81 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# std
import argparse
import logging
import signal
from pathlib import Path

# project
from src.chia_log.log_consumer import FileLogConsumer
from src.chia_log.log_handler import LogHandler
from src.config import Config
from src.notifier.keep_alive_monitor import KeepAliveMonitor
from src.notifier.notify_manager import NotifyManager


def parse_arguments():
parser = argparse.ArgumentParser(description='ChiaFarmWatch: Watch your crops '
'with a piece in mind for the yield.')
parser.add_argument('--config', required=True, type=str, help='path to config.yaml')
return parser.parse_args()


def get_log_level(log_level: str) -> int:
if log_level == "CRITICAL":
return logging.CRITICAL
if log_level == "ERROR":
return logging.ERROR
if log_level == "WARNING":
return logging.WARNING
if log_level == "INFO":
return logging.INFO
if log_level == "DEBUG":
return logging.DEBUG

logging.warning(f"Unsupported log level: {log_level}. Fallback to INFO level.")
return logging.INFO


if __name__ == '__main__':
# Parse config and configure logger
args = parse_arguments()
config = Config(Path(args.config))
log_level = get_log_level(config.get_log_level_config())
logging.basicConfig(
format='[%(asctime)s] [%(levelname)8s] --- %(message)s (%(filename)s:%(lineno)s)',
level=log_level, datefmt='%Y-%m-%d %H:%M:%S')

# Using file log consumer by default TODO: make configurable
chia_logs_config = config.get_chia_logs_config()
if not chia_logs_config["file_log_consumer"]["enable"]:
logging.warning("Currently only file log consumer is supported. Cannot be disabled.")
log_path = Path(chia_logs_config["file_log_consumer"]["file_path"])
log_consumer = FileLogConsumer(log_path=log_path)

# Keep a reference here so we can stop the thread
# TODO: read keep-alive thresholds from config
keep_alive_monitor = KeepAliveMonitor()

# Notify manager is responsible for the lifecycle of all notifiers
notify_manager = NotifyManager(
config=config.get_notifier_config(),
keep_alive_monitor=keep_alive_monitor
)

# Link stuff up in the log handler
# Pipeline: Consume -> Handle -> Notify
log_handler = LogHandler(
log_consumer=log_consumer,
notify_manager=notify_manager
)


def interrupt(signal_number, frame):
if signal_number == signal.SIGINT:
logging.info("Received interrupt. Stopping...")
log_consumer.stop()
keep_alive_monitor.stop()
exit(0)


signal.signal(signal.SIGINT, interrupt)
signal.pause()
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
python-dateutil~=2.8.1
PyYAML==5.3.1
Empty file added src/__init__.py
Empty file.
Empty file added src/chia_log/__init__.py
Empty file.
31 changes: 31 additions & 0 deletions src/chia_log/handlers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""A LogHandler is a single responsibility
class that analyses a specific part of the log
stream and generates noteworthy events.
A single log handler could check for multiple
conditions. It delegates the task to ConditionCheckers.
"""

# std
from abc import ABC, abstractmethod
from typing import List

# project
from src.notifier import Event


class LogHandler(ABC):
"""Common interface for log handlers
"""

@abstractmethod
def handle(self, logs: str) -> List[Event]:
pass


class ConditionChecker(ABC):
"""Common interface for condition checkers"""

@abstractmethod
def check(self, obj: object) -> Event:
pass
Loading

0 comments on commit 9f95801

Please sign in to comment.