mirror of
https://codeberg.org/privacy1st/snowflake-stats
synced 2024-12-23 01:56:04 +01:00
init
This commit is contained in:
parent
12f1809a45
commit
13bbb37b19
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/.idea/
|
24
README.md
24
README.md
@ -1,2 +1,24 @@
|
||||
# snoflake-stats
|
||||
# snowflake-stats
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
(venv-310) [user@linux snoflake-stats]$ ./main.py
|
||||
```
|
||||
|
||||
## Example output
|
||||
|
||||
```
|
||||
[DEBUG] execute_and_capture: ['ssh', 'root_at_my_server', 'docker logs snowflake-proxy']
|
||||
From 2022-03-28 11:58:25 until 2022-04-06 12:31:40:
|
||||
2022-03-28: 1.8 GB, 0.1 GB -- (335199 OnMessages, 1799508 sends, 151390 seconds)
|
||||
2022-03-29: 3.2 GB, 0.2 GB -- (509488 OnMessages, 2925767 sends, 117434 seconds)
|
||||
2022-03-30: 3.5 GB, 0.2 GB -- (815323 OnMessages, 3441369 sends, 907236 seconds)
|
||||
2022-03-31: 1.7 GB, 0.1 GB -- (350893 OnMessages, 1801615 sends, 222564 seconds)
|
||||
2022-04-01: 7.0 GB, 0.4 GB -- (1358655 OnMessages, 6719817 sends, 239774 seconds)
|
||||
2022-04-02: 1.8 GB, 0.1 GB -- (340389 OnMessages, 1702678 sends, 92243 seconds)
|
||||
2022-04-03: 4.9 GB, 0.3 GB -- (972402 OnMessages, 4976213 sends, 1249335 seconds)
|
||||
2022-04-04: 4.8 GB, 0.3 GB -- (824929 OnMessages, 5106120 sends, 265337 seconds)
|
||||
2022-04-05: 2.1 GB, 0.2 GB -- (475139 OnMessages, 2322166 sends, 298093 seconds)
|
||||
2022-04-06: 0.5 GB, 0.2 GB -- (553513 OnMessages, 894773 sends, 206981 seconds)
|
||||
```
|
||||
|
65
exec.py
Normal file
65
exec.py
Normal file
@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
from typing import List
|
||||
import sys
|
||||
import subprocess
|
||||
import shlex
|
||||
|
||||
|
||||
class NonZeroExitCode(Exception):
|
||||
def __init__(self, exit_code, stdout, stderr):
|
||||
super(NonZeroExitCode, self).__init__(f'exit_code: {exit_code}, stdout: {stdout}, stderr: {stderr}')
|
||||
|
||||
|
||||
def wrap_command_with_ssh(command: List[str], ssh_host: str) -> List[str]:
|
||||
return ['ssh', ssh_host, ' '.join([shlex.quote(arg) for arg in command])]
|
||||
|
||||
|
||||
def execute(command: List[str], ssh_host: str = None) -> None:
|
||||
"""
|
||||
https://docs.python.org/3.10/library/subprocess.html#frequently-used-arguments
|
||||
|
||||
Executes the given command using stdin, stdout, stderr of the parent process.
|
||||
|
||||
The output is not captured.
|
||||
|
||||
:raises subprocess.CalledProcessError: In case of non-zero exit code.
|
||||
"""
|
||||
if ssh_host is not None:
|
||||
command = wrap_command_with_ssh(command, ssh_host)
|
||||
|
||||
print(f'[DEBUG] execute: {command}')
|
||||
|
||||
subprocess.run(
|
||||
command,
|
||||
stdin=sys.stdin,
|
||||
stdout=sys.stdout,
|
||||
stderr=sys.stderr,
|
||||
check=True,
|
||||
)
|
||||
|
||||
|
||||
def execute_and_capture(command: List[str], ssh_host: str = None, file='stdout') -> str:
|
||||
"""
|
||||
Executes the given command and captures it stdout.
|
||||
|
||||
:return: Stdout of subprocess.
|
||||
:raises NonZeroExitCode: In case of non-zero exit code. This exception includes the exit_code, stdout and stderr in it's message.
|
||||
"""
|
||||
if ssh_host is not None:
|
||||
command = wrap_command_with_ssh(command, ssh_host)
|
||||
|
||||
print(f'[DEBUG] execute_and_capture: {command}')
|
||||
|
||||
completed: subprocess.CompletedProcess = subprocess.run(
|
||||
command,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if completed.returncode != 0:
|
||||
raise NonZeroExitCode(completed.returncode, completed.stdout, completed.stderr)
|
||||
|
||||
if file == 'stdout':
|
||||
return completed.stdout
|
||||
if file == 'stderr':
|
||||
return completed.stderr
|
||||
raise ValueError('invalid argument')
|
124
main.py
Executable file
124
main.py
Executable file
@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
from typing import List
|
||||
from datetime import datetime
|
||||
|
||||
import exec
|
||||
|
||||
|
||||
def main():
|
||||
log: str = docker_logs(container_name='snowflake-proxy', ssh_host='rootnas')
|
||||
filtered: List[str] = [line for line in log.splitlines()
|
||||
if Throughput.PATTERN in line]
|
||||
# filtered = filtered_example()
|
||||
|
||||
tps = [Throughput.from_str(line) for line in filtered]
|
||||
print(f'From {tps[0].dt} until {tps[-1].dt}:')
|
||||
|
||||
grouped_by_day = {}
|
||||
for tp in tps:
|
||||
if tp.dt.date() in grouped_by_day.keys():
|
||||
grouped_by_day[tp.dt.date()].append(tp)
|
||||
else:
|
||||
grouped_by_day[tp.dt.date()] = [tp]
|
||||
|
||||
for day, tp_list in grouped_by_day.items():
|
||||
tps_sum = sum(tp_list, Throughput.zero())
|
||||
print(f'{day}: {tps_sum}')
|
||||
|
||||
|
||||
def filtered_example() -> List[str]:
|
||||
return [
|
||||
'2022/04/04 15:08:10 Traffic throughput (up|down): 4 MB|259 KB -- (691 OnMessages, 3886 Sends, over 269 seconds)',
|
||||
'2022/04/04 16:00:06 Traffic throughput (up|down): 13 MB|15 MB -- (46326 OnMessages, 32325 Sends, over 36634 seconds)',
|
||||
'2022/04/04 15:57:04 Traffic throughput (up|down): 61 KB|8 KB -- (69 OnMessages, 91 Sends, over 157 seconds)',
|
||||
]
|
||||
|
||||
|
||||
def docker_logs(container_name: str, ssh_host: str = None) -> str:
|
||||
return exec.execute_and_capture(['docker', 'logs', container_name], ssh_host, 'stderr')
|
||||
|
||||
|
||||
class Throughput:
|
||||
FORMAT_EXAMPLE = '2022/04/06 10:37:42'
|
||||
FORMAT_STR = '%Y/%m/%d %H:%M:%S'
|
||||
FORMAT_LENGTH = len(FORMAT_EXAMPLE)
|
||||
|
||||
PATTERN = ' Traffic throughput (up|down): '
|
||||
|
||||
_unit_dict = {
|
||||
'B': 1,
|
||||
'KB': 10 ** 3,
|
||||
'MB': 10 ** 6,
|
||||
'GB': 10 ** 9,
|
||||
'TB': 10 ** 12,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_str(cls, line: str) -> Throughput:
|
||||
dt_str = line[0:Throughput.FORMAT_LENGTH]
|
||||
dt = datetime.strptime(dt_str, Throughput.FORMAT_STR)
|
||||
|
||||
_, tail = line.split(Throughput.PATTERN)
|
||||
up, tail = tail.split('|')
|
||||
down, tail = tail.split(' -- (')
|
||||
on_messages, tail = tail.split(', ', maxsplit=1)
|
||||
sends, tail = tail.split(', ')
|
||||
seconds, tail = tail.split(')')
|
||||
|
||||
bytes_up = cls._split_to_bytes(up)
|
||||
bytes_down = cls._split_to_bytes(down)
|
||||
on_messages = int(on_messages.split(' ')[0])
|
||||
sends = int(sends.split(' ')[0])
|
||||
seconds = int(seconds.split(' ')[1])
|
||||
|
||||
return cls(dt, bytes_up, bytes_down, on_messages, sends, seconds)
|
||||
|
||||
@classmethod
|
||||
def from_args(cls, dt, bytes_up, bytes_down, on_messages, sends, seconds) -> Throughput:
|
||||
return cls(dt, bytes_up, bytes_down, on_messages, sends, seconds)
|
||||
|
||||
@classmethod
|
||||
def zero(cls) -> Throughput:
|
||||
return cls(None, 0, 0, 0, 0, 0)
|
||||
|
||||
def __init__(self, dt, bytes_up, bytes_down, on_messages, sends, seconds):
|
||||
self.dt = dt
|
||||
self.bytes_up = bytes_up
|
||||
self.bytes_down = bytes_down
|
||||
self.on_messages = on_messages
|
||||
self.sends = sends
|
||||
self.seconds = seconds
|
||||
|
||||
def __add__(self, other):
|
||||
if not isinstance(other, Throughput):
|
||||
raise ValueError('invalid argument')
|
||||
return Throughput.from_args(
|
||||
self.dt,
|
||||
self.bytes_up + other.bytes_up,
|
||||
self.bytes_down + other.bytes_down,
|
||||
self.on_messages + other.on_messages,
|
||||
self.sends + other.sends,
|
||||
self.seconds + other.seconds,
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{Throughput._to_gb(self.bytes_up)} GB, {Throughput._to_gb(self.bytes_down)} GB -- ({self.on_messages} OnMessages, {self.sends} sends, {self.seconds} seconds)'
|
||||
|
||||
@classmethod
|
||||
def _split_to_bytes(cls, num_unit: str) -> int:
|
||||
num, unit = num_unit.split(' ')
|
||||
num = int(num)
|
||||
return cls._to_bytes(num, unit)
|
||||
|
||||
@classmethod
|
||||
def _to_bytes(cls, num: int, unit: str) -> int:
|
||||
return num * cls._unit_dict[unit]
|
||||
|
||||
@classmethod
|
||||
def _to_gb(cls, num_bytes: int) -> int:
|
||||
return round(num_bytes / cls._unit_dict['GB'], 1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in New Issue
Block a user