Compare commits

...

2 Commits

4 changed files with 64 additions and 99 deletions

View File

@ -6,75 +6,46 @@ It is mainly used as a tool for statistical analysis of residential internet con
## Usage
You can set these options in a json file and pass it with the `--config` option.
Additional command line parameters override options defined in a config.
The program has two subcommands
### log
Get the current connection status to the default host (1.1.1.1) and timeout (2).
```txt
If no argument is passed then de defaults are assumed.
--config Path to a config file in JSON format.
To set a command line argument, use it as a key and set its value.
Config is not used unless explicitly set.
--log Path to a the file where results should be appended to.
Created the file if it does not already exist.
Default: ./connection.log
--host IP or hostname of a server to query.
Default: 1.1.1.1
--timeout Set the timeout in seconds to use for the connection test
Default: 2
--outages Print the outages to stdout.
Set the lenght in minutes where a connection loss is condidered an outage
This option can only be used with --log.
Default: 3
--stdout Return the resulting logline in the terminal.
Default Behaviour: Do not print to stdout.
--help Print this menu
```
### Config
Save an example config options as a `.json` file with this format:
```json
{
"host": "1.1.1.1",
"timeout": null, // To use the default value of 2
"log": "./connection.log",
}
```
### Example 1
```txt
$ outage_detector --config ./config.json --host 1.1.1.1 --log --stdout
$ outage_detector log
> [YYYY-MM-DD HH:MM:SS][1.1.1.1][OK]
```
Loads values from `./config.json`.
Overrides `host` with `example.com`.
Appends the result to `log.txt` and prints it to `stdout`
Or use a custom host and timeout with
```txt
$ outage_detector --config ./config.json --host example.com --log another.log --stdout
$ outage_detector log --host 1.0.0.1 --timeout 4
> [YYYY-MM-DD HH:MM:SS][1.1.1.1][FAIL]
```
Loads values from `./config.json`.
Overrides `host` with `example.com`.
Appends the result to `another.log` and prints it to `stdout`
### Example 2
To append a log to `outages.log`.
Will create the file if it does not exist.
This will suppress output to stdout.
```txt
$ outage_detector --log ./outage-detector.log --outages 5
> [YYYY-MM-DD HH:MM:SS][1.1.1.1] lasting for X Hours and Y Minutes
> [YYYY-MM-DD HH:MM:SS][1.1.1.1] lasting for X Hours and Y Minutes
$ outage_detector log outages.log --host 1.0.0.1 --timeout 4
> [YYYY-MM-DD HH:MM:SS][1.1.1.1][FAIL]
```
Print to `stdout` all outages from `./outage-detector.log` in chronological order that lasted at least 5 minutes.
### outages
Print the major outages to stdout.
```txt
$ outage_detector log outages.log
> [YYYY-MM-DD HH:MM:SS][1.1.1.1] lasting for 3 Minutes
```
You can also specify the time in minutes a connection loss should count as an outage.
Default is 3 Minutes.
```txt
$ outage_detector log outages.log --time 6
>
```

View File

@ -8,9 +8,6 @@ inputs: {
cfg = config.outage-detector;
package = inputs.self.packages.${pkgs.stdenv.hostPlatform.system}.default;
inherit (lib) mkIf mkEnableOption mkOption types;
format = pkgs.formats.json {};
configFile = format.generate "config.json" cfg.settings;
in {
options.outage-detector = {
enable = mkEnableOption "outage-detector";
@ -37,7 +34,11 @@ in {
]);
in
valueType;
default = throw "Please specify outage-detector.settings";
default = {
filename = throw "Please specify outage-detector.settings.";
host = "1.1.1.1";
timeout = 2;
};
};
};
@ -46,7 +47,7 @@ in {
systemd.services.outage-detector = mkIf (cfg.timer
!= null) {
script = "${package}/bin/outage_detector --config ${configFile}";
script = "${package}/bin/outage_detector log ${cfg.settings.filename} --host ${cfg.settings.host} --timeout ${cfg.settings.timeout}";
serviceConfig = {
Type = "oneshot";

View File

@ -1,5 +1,5 @@
import sys
import json
import argparse
from datetime import datetime
import requests
@ -14,10 +14,10 @@ class LogEntry:
self.failed = 'FAIL' in log_entry
def printOutages(config: dict) -> None:
def printOutages(filepath: str, time: int) -> None:
"""Print outages from a log file"""
with open(config['log'], 'r') as file:
with open(filepath, 'r') as file:
log = list(map(lambda x: LogEntry(x), file.readlines()))
# cluster log entries by host
@ -29,7 +29,7 @@ def printOutages(config: dict) -> None:
log_by_host[entry.host].append(entry)
# cluster fails to outages
# we consider the time of subsequent failed connection attemts to the same host an outage
# we consider the time of subsequent failed connection attempts to the same host an outage
outages: list = []
last_failed = False
for entries in log_by_host.values():
@ -49,7 +49,7 @@ def printOutages(config: dict) -> None:
outage_duration = round(
(outage[-1].date - outage[0].date).seconds / 60)
# continue if outage is shorter than the minimum duration
if outage_duration < config['outages']:
if outage_duration < time:
continue
hours = outage_duration // 60
minutes = outage_duration - (hours * 60)
@ -69,74 +69,67 @@ def isOnline(host: str, timeout: int) -> bool:
return False
def log(host: int, timeout: int, no_stdout: bool, log_path: None | str = None) -> None:
def log(host: str, timeout: int, filepath: None | str = None) -> None:
"""Log the connection status of a host"""
logline = f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}][{host}][{'OK' if isOnline(host, timeout) else 'FAIL'}]\n"
logline = f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}][{host}][{'OK' if isOnline(host, timeout) else 'FAIL'}]"
if log_path is not None:
with open(log_path, 'a') as file:
file.write(logline)
if not no_stdout:
if filepath is not None:
with open(filepath, 'a') as file:
file.write(logline + '\n')
else:
print(logline)
def parseArgs(args: list[str]):
import argparse
def parseArgs(args: list[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser(
prog='outage_detector', description='Log outages and print statistics.', formatter_class=argparse.ArgumentDefaultsHelpFormatter
prog='outage_detector', description='Log outages and print statistics.'
)
subparsers = parser.add_subparsers()
subparsers = parser.add_subparsers(dest='command')
# Arguments for the log command
parser_log = subparsers.add_parser(
'log', help='Log the connection status.', formatter_class=argparse.ArgumentDefaultsHelpFormatter
'log', help='Log the connection status.'
)
parser_log.add_argument(
'--path', type=str, default=None,
help='Path to a the file where results should be appended to. Creates the file if it does not exist.'
'filename', type=str, default=None, nargs='?',
help='Path to a the file where results should be appended to. Creates the file if it does not exist. If not specified, results are printed to stdout.'
)
parser_log.add_argument(
'--host', type=str, default='1.1.1.1',
help='IP or hostname of a server to query.'
help='IP or hostname of a server to query. (default: %(default)s)'
)
parser_log.add_argument(
'--timeout', type=int, default=2,
help='Set the timeout in seconds to use for the connection test.'
)
parser_log.add_argument(
'--no-stdout', action='store_true', default=argparse.SUPPRESS,
help='A flag that disables printing to stdout.'
help='Set the timeout in seconds to use for the connection test. (default: %(default)s)'
)
# Arguments for the outages command
parser_outages = subparsers.add_parser(
'outages', help='Print outages', formatter_class=argparse.ArgumentDefaultsHelpFormatter
'outages', help='Print/log outages'
)
parser_outages.add_argument(
'--log-path', type=str, default='./connection.log',
'filename', type=str, nargs='?',
help='Path to a file with connection logs.'
)
parser_outages.add_argument(
'--time', type=int, default=3,
help='Minimum duration for a connection loss to be considered an outage.'
help='Minimum duration for a connection loss to be considered an outage. (default: %(default)s)'
)
print(parser.parse_args(args))
return parser.parse_args(args)
def main():
from sys import argv
parseArgs(argv[1:])
# config, mode = loadConfig()
# if mode == 'log':
# log(config)
# elif mode == 'outages':
# printOutages(config)
args = parseArgs(sys.argv[1:])
if args.command == 'log':
log(args.host, args.timeout, args.filename)
elif args.command == 'outages':
printOutages(args.filename, args.time)
if __name__ == '__main__':

View File