atomicshop 3.3.8__py3-none-any.whl → 3.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of atomicshop might be problematic. Click here for more details.
- atomicshop/__init__.py +1 -1
- atomicshop/a_mains/get_local_tcp_ports.py +85 -0
- atomicshop/a_mains/install_ca_certificate.py +172 -0
- atomicshop/a_mains/process_from_port.py +119 -0
- atomicshop/a_mains/set_default_dns_gateway.py +90 -0
- atomicshop/basics/strings.py +1 -1
- atomicshop/certificates.py +2 -2
- atomicshop/dns.py +26 -28
- atomicshop/etws/traces/trace_tcp.py +1 -2
- atomicshop/mitm/centered_settings.py +133 -0
- atomicshop/mitm/config_static.py +22 -44
- atomicshop/mitm/connection_thread_worker.py +383 -165
- atomicshop/mitm/engines/__parent/recorder___parent.py +1 -1
- atomicshop/mitm/engines/__parent/requester___parent.py +1 -1
- atomicshop/mitm/engines/__parent/responder___parent.py +15 -2
- atomicshop/mitm/engines/create_module_template.py +1 -2
- atomicshop/mitm/import_config.py +91 -89
- atomicshop/mitm/initialize_engines.py +1 -2
- atomicshop/mitm/message.py +5 -4
- atomicshop/mitm/mitm_main.py +238 -122
- atomicshop/mitm/recs_files.py +61 -5
- atomicshop/mitm/ssh_tester.py +82 -0
- atomicshop/mitm/statistic_analyzer.py +33 -12
- atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +104 -31
- atomicshop/networks.py +160 -92
- atomicshop/package_mains_processor.py +84 -0
- atomicshop/permissions/ubuntu_permissions.py +47 -0
- atomicshop/print_api.py +3 -5
- atomicshop/process.py +11 -4
- atomicshop/python_functions.py +23 -108
- atomicshop/speech_recognize.py +8 -0
- atomicshop/ssh_remote.py +140 -164
- atomicshop/web.py +63 -22
- atomicshop/web_apis/google_llm.py +22 -14
- atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
- atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +2 -1
- atomicshop/wrappers/dockerw/dockerw.py +2 -2
- atomicshop/wrappers/elasticsearchw/config_basic.py +0 -12
- atomicshop/wrappers/elasticsearchw/elastic_infra.py +0 -190
- atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +5 -5
- atomicshop/wrappers/githubw.py +180 -68
- atomicshop/wrappers/loggingw/consts.py +1 -1
- atomicshop/wrappers/loggingw/handlers.py +1 -1
- atomicshop/wrappers/loggingw/loggingw.py +20 -4
- atomicshop/wrappers/loggingw/reading.py +18 -0
- atomicshop/wrappers/mongodbw/mongo_infra.py +0 -38
- atomicshop/wrappers/netshw.py +124 -3
- atomicshop/wrappers/playwrightw/scenarios.py +1 -1
- atomicshop/wrappers/powershell_networking.py +80 -0
- atomicshop/wrappers/psutilw/psutil_networks.py +9 -0
- atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +3 -105
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +3 -57
- atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +12 -27
- atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +15 -9
- atomicshop/wrappers/socketw/certificator.py +19 -9
- atomicshop/wrappers/socketw/creator.py +101 -14
- atomicshop/wrappers/socketw/dns_server.py +17 -5
- atomicshop/wrappers/socketw/exception_wrapper.py +21 -16
- atomicshop/wrappers/socketw/process_getter.py +86 -0
- atomicshop/wrappers/socketw/receiver.py +29 -9
- atomicshop/wrappers/socketw/sender.py +10 -9
- atomicshop/wrappers/socketw/sni.py +31 -10
- atomicshop/wrappers/socketw/{base.py → socket_base.py} +33 -1
- atomicshop/wrappers/socketw/socket_client.py +11 -10
- atomicshop/wrappers/socketw/socket_wrapper.py +125 -32
- atomicshop/wrappers/socketw/ssl_base.py +6 -2
- atomicshop/wrappers/ubuntu_terminal.py +21 -18
- atomicshop/wrappers/win_auditw.py +189 -0
- {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/METADATA +25 -30
- {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/RECORD +83 -109
- atomicshop/_basics_temp.py +0 -101
- atomicshop/a_installs/ubuntu/docker_rootless.py +0 -11
- atomicshop/a_installs/ubuntu/docker_sudo.py +0 -11
- atomicshop/a_installs/ubuntu/elastic_search_and_kibana.py +0 -10
- atomicshop/a_installs/ubuntu/mongodb.py +0 -12
- atomicshop/a_installs/win/fibratus.py +0 -9
- atomicshop/a_installs/win/mongodb.py +0 -9
- atomicshop/a_installs/win/wsl_ubuntu_lts.py +0 -10
- atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
- atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
- atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
- atomicshop/addons/package_setup/Setup.cmd +0 -7
- atomicshop/archiver/__init__.py +0 -0
- atomicshop/archiver/_search_in_zip.py +0 -189
- atomicshop/archiver/search_in_archive.py +0 -284
- atomicshop/archiver/sevenz_app_w.py +0 -86
- atomicshop/archiver/sevenzs.py +0 -73
- atomicshop/archiver/shutils.py +0 -34
- atomicshop/archiver/zips.py +0 -353
- atomicshop/file_types.py +0 -24
- atomicshop/pbtkmultifile_argparse.py +0 -88
- atomicshop/script_as_string_processor.py +0 -42
- atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
- atomicshop/ssh_scripts/process_from_port.py +0 -27
- atomicshop/wrappers/_process_wrapper_curl.py +0 -27
- atomicshop/wrappers/_process_wrapper_tar.py +0 -21
- atomicshop/wrappers/dockerw/install_docker.py +0 -449
- atomicshop/wrappers/elasticsearchw/install_elastic.py +0 -233
- atomicshop/wrappers/ffmpegw.py +0 -125
- atomicshop/wrappers/fibratusw/__init__.py +0 -0
- atomicshop/wrappers/fibratusw/install.py +0 -80
- atomicshop/wrappers/mongodbw/install_mongodb_ubuntu.py +0 -100
- atomicshop/wrappers/mongodbw/install_mongodb_win.py +0 -244
- atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
- atomicshop/wrappers/socketw/get_process.py +0 -123
- atomicshop/wrappers/wslw.py +0 -192
- atomicshop-3.3.8.dist-info/entry_points.txt +0 -2
- /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compile.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.dll +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.exp +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.lib +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +0 -0
- {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/WHEEL +0 -0
- {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/licenses/LICENSE.txt +0 -0
- {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
|
|
3
|
+
import paramiko
|
|
4
|
+
|
|
5
|
+
from .. import package_mains_processor, ssh_remote, config_init
|
|
6
|
+
from ..wrappers.socketw import process_getter
|
|
7
|
+
from ..print_api import print_api
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
PORT_TO_CMD_FILE: str = 'process_from_port'
|
|
11
|
+
TCP_PORTS_FILE: str = 'get_local_tcp_ports'
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_ssh_main(config: dict) -> int:
|
|
15
|
+
hosts: list = config['main']['hosts_or_ips']
|
|
16
|
+
|
|
17
|
+
for host in hosts:
|
|
18
|
+
print("-----------------------------------")
|
|
19
|
+
print_api(f"Testing cmd for host: {host}", color='blue')
|
|
20
|
+
|
|
21
|
+
if host in config['main']:
|
|
22
|
+
print("Using host-specific credentials")
|
|
23
|
+
username = config[host]['user']
|
|
24
|
+
password = config[host]['pass']
|
|
25
|
+
else:
|
|
26
|
+
print("Didn't find host-specific credential, using defaults")
|
|
27
|
+
username = config['all_hosts']['user']
|
|
28
|
+
password = config['all_hosts']['pass']
|
|
29
|
+
|
|
30
|
+
ssh_client = ssh_remote.SSHRemote(ip_address=host, username=username, password=password)
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
ssh_client.connect()
|
|
34
|
+
except socket.gaierror as e:
|
|
35
|
+
if e.errno == 11001:
|
|
36
|
+
print_api(f"Couldn't resolve IP to {host}: {str(e)}\n"
|
|
37
|
+
f"Try providing IP address instead of hostname", color='red')
|
|
38
|
+
continue
|
|
39
|
+
else:
|
|
40
|
+
raise e
|
|
41
|
+
except paramiko.ssh_exception.NoValidConnectionsError as e:
|
|
42
|
+
print_api(f"Couldn't connect to {host}: {str(e)}", color='red')
|
|
43
|
+
continue
|
|
44
|
+
|
|
45
|
+
# Read the TCP ports file to string.
|
|
46
|
+
tcp_ports_package_processor: package_mains_processor.PackageMainsProcessor = package_mains_processor.PackageMainsProcessor(
|
|
47
|
+
script_file_stem=TCP_PORTS_FILE)
|
|
48
|
+
tcp_ports_script_string: str = tcp_ports_package_processor.read_script_file_to_string()
|
|
49
|
+
|
|
50
|
+
# Execute the TCP ports script remotely via SSH to get the list of open TCP ports.
|
|
51
|
+
tcp_ports_output, tcp_ports_error = ssh_client.remote_execution_python(script_string=tcp_ports_script_string)
|
|
52
|
+
if tcp_ports_error:
|
|
53
|
+
print_api(f"Error getting TCP ports from host {host}: {tcp_ports_error}", color='red')
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
tcp_ports_list: list = tcp_ports_output.strip().splitlines()
|
|
57
|
+
if not tcp_ports_list:
|
|
58
|
+
print_api(f"No TCP ports found on host {host}", color='red')
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
last_port: int = int(tcp_ports_list[-1])
|
|
62
|
+
|
|
63
|
+
port_to_cmd_package_processor: package_mains_processor.PackageMainsProcessor = package_mains_processor.PackageMainsProcessor(
|
|
64
|
+
script_file_stem=PORT_TO_CMD_FILE)
|
|
65
|
+
get_command_instance = process_getter.GetCommandLine(
|
|
66
|
+
client_ip=host,
|
|
67
|
+
client_port=last_port,
|
|
68
|
+
package_processor=port_to_cmd_package_processor,
|
|
69
|
+
ssh_client=ssh_client)
|
|
70
|
+
process_name = get_command_instance.get_process_name()
|
|
71
|
+
print(f"Process for port {last_port} on host {host}: {process_name}")
|
|
72
|
+
|
|
73
|
+
print("Closing SSH connection")
|
|
74
|
+
ssh_client.close()
|
|
75
|
+
|
|
76
|
+
if not process_name:
|
|
77
|
+
print_api(f"Failed to get process name for port {last_port} on host {host}", color='red')
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
print_api(f"SSH test success!", color='green')
|
|
81
|
+
|
|
82
|
+
return 0
|
|
@@ -346,11 +346,13 @@ def analyze(main_file_path: str):
|
|
|
346
346
|
|
|
347
347
|
|
|
348
348
|
def deviation_calculator_by_moving_average(
|
|
349
|
-
statistics_file_directory: str,
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
349
|
+
statistics_file_directory: str = None,
|
|
350
|
+
statistics_content: dict = None,
|
|
351
|
+
by_type: Literal['host', 'url'] = 'url',
|
|
352
|
+
moving_average_window_days: int = 5,
|
|
353
|
+
top_bottom_deviation_percentage: float = 0.25,
|
|
353
354
|
get_deviation_for_last_day_only: bool = False,
|
|
355
|
+
get_deviation_for_date: str = None,
|
|
354
356
|
summary: bool = False,
|
|
355
357
|
output_file_path: str = None,
|
|
356
358
|
output_file_type: Literal['json', 'csv'] = 'json',
|
|
@@ -360,9 +362,13 @@ def deviation_calculator_by_moving_average(
|
|
|
360
362
|
"""
|
|
361
363
|
This function is the main function for the moving average calculator.
|
|
362
364
|
|
|
363
|
-
:param statistics_file_directory: string, the directory where 'statistics.csv' file resides
|
|
365
|
+
:param statistics_file_directory: string, can be either providing the directory where 'statistics.csv' file resides
|
|
366
|
+
or None. If None, the 'statistics_content' must be provided.
|
|
367
|
+
The directory where 'statistics.csv' file resides.
|
|
364
368
|
Also, all the rotated files like: statistics_2021-01-01.csv, statistics_2021-01-02.csv, etc.
|
|
365
369
|
These will be analyzed in the order of the date in the file name.
|
|
370
|
+
:param statistics_content: dict, if specified, this will be used instead of reading the files from the directory.
|
|
371
|
+
The dict should be a result of the 'atomicshop.mitm.statistic_analyzer_helper.moving_average_helper.get_all_files_content'.
|
|
366
372
|
:param by_type: string, 'host' or 'url'. The type of the deviation calculation.
|
|
367
373
|
'host' will calculate the deviation by the host name. Example: maps.google.com, yahoo.com, etc.
|
|
368
374
|
'url' will calculate the deviation by the URL. Example: maps.google.com/maps, yahoo.com/news, etc.
|
|
@@ -381,6 +387,8 @@ def deviation_calculator_by_moving_average(
|
|
|
381
387
|
Files 01 to 05 will be used for moving average and the file 06 for deviation.
|
|
382
388
|
Meaning the average calculated for 2021-01-06 will be compared to the values moving average of 2021-01-01
|
|
383
389
|
to 2021-01-05.
|
|
390
|
+
:param get_deviation_for_date: string, if specified, only the specified date will be analyzed.
|
|
391
|
+
The date must be in the format of 'YYYY-MM-DD'. Example: '2021-01-06'.
|
|
384
392
|
:param summary: bool, if True, Only the summary will be generated without all the numbers that were used
|
|
385
393
|
to calculate the averages and the moving average data.
|
|
386
394
|
:param output_file_path: string, if None, no file will be written.
|
|
@@ -414,27 +422,40 @@ def deviation_calculator_by_moving_average(
|
|
|
414
422
|
sys.exit(main())
|
|
415
423
|
"""
|
|
416
424
|
|
|
425
|
+
def convert_data_value_to_string(value_key: str, list_index: int) -> None:
|
|
426
|
+
deviation_list[list_index]['data'][value_key] = json.dumps(deviation_list[list_index]['data'][value_key])
|
|
427
|
+
|
|
428
|
+
def convert_value_to_string(value_key: str, list_index: int) -> None:
|
|
429
|
+
if value_key in deviation_list[list_index]:
|
|
430
|
+
deviation_list[list_index][value_key] = json.dumps(deviation_list[list_index][value_key])
|
|
431
|
+
|
|
432
|
+
if not statistics_file_directory and not statistics_content:
|
|
433
|
+
raise ValueError('Either [statistics_file_directory] or [statistics_content] must be provided.')
|
|
434
|
+
if statistics_file_directory and statistics_content:
|
|
435
|
+
raise ValueError('Either [statistics_file_directory] or [statistics_content] must be provided, not both.')
|
|
436
|
+
|
|
417
437
|
if output_file_type not in ['json', 'csv']:
|
|
418
438
|
raise ValueError(f'output_file_type must be "json" or "csv", not [{output_file_type}]')
|
|
419
439
|
|
|
420
440
|
if by_type not in ['host', 'url']:
|
|
421
441
|
raise ValueError(f'by_type must be "host" or "url", not [{by_type}]')
|
|
422
442
|
|
|
423
|
-
|
|
443
|
+
if get_deviation_for_last_day_only and get_deviation_for_date:
|
|
444
|
+
raise ValueError('Either [get_deviation_for_last_day_only] or [get_deviation_for_date] can be provided, not both.')
|
|
424
445
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
if value_key in deviation_list[list_index]:
|
|
430
|
-
deviation_list[list_index][value_key] = json.dumps(deviation_list[list_index][value_key])
|
|
446
|
+
if statistics_file_directory:
|
|
447
|
+
statistics_file_path: str | None = f'{statistics_file_directory}{os.sep}{STATISTICS_FILE_NAME}'
|
|
448
|
+
else:
|
|
449
|
+
statistics_file_path: str | None = None
|
|
431
450
|
|
|
432
451
|
deviation_list = moving_average_helper.calculate_moving_average(
|
|
433
452
|
statistics_file_path,
|
|
453
|
+
statistics_content,
|
|
434
454
|
by_type,
|
|
435
455
|
moving_average_window_days,
|
|
436
456
|
top_bottom_deviation_percentage,
|
|
437
457
|
get_deviation_for_last_day_only,
|
|
458
|
+
get_deviation_for_date,
|
|
438
459
|
skip_total_count_less_than
|
|
439
460
|
)
|
|
440
461
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import statistics
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from typing import Literal
|
|
4
|
+
import datetime
|
|
4
5
|
|
|
5
6
|
from ...print_api import print_api
|
|
6
7
|
from ...wrappers.loggingw import reading, consts
|
|
@@ -9,11 +10,13 @@ from ... import urls, filesystem
|
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
def calculate_moving_average(
|
|
12
|
-
file_path: str,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
file_path: str = None,
|
|
14
|
+
statistics_content: dict = None,
|
|
15
|
+
by_type: Literal['host', 'url'] = 'url',
|
|
16
|
+
moving_average_window_days: int = 5,
|
|
17
|
+
top_bottom_deviation_percentage: float = 0.25,
|
|
16
18
|
get_deviation_for_last_day_only: bool = False,
|
|
19
|
+
get_deviation_for_date: str = None,
|
|
17
20
|
skip_total_count_less_than: int = None,
|
|
18
21
|
print_kwargs: dict = None
|
|
19
22
|
) -> list:
|
|
@@ -21,14 +24,75 @@ def calculate_moving_average(
|
|
|
21
24
|
This function calculates the moving average of the daily statistics.
|
|
22
25
|
|
|
23
26
|
:param file_path: string, the path to the 'statistics.csv' file.
|
|
27
|
+
:param statistics_content: dict, the statistics content dictionary. If provided, 'file_path' will be ignored.
|
|
28
|
+
The dictionary should be in the format returned by 'get_all_files_content' function.
|
|
24
29
|
:param by_type: string, the type to calculate the moving average by. Can be 'host' or 'url'.
|
|
25
30
|
:param moving_average_window_days: integer, the window size for the moving average.
|
|
26
31
|
:param top_bottom_deviation_percentage: float, the percentage of deviation from the moving average to the top or
|
|
27
32
|
bottom.
|
|
33
|
+
:param get_deviation_for_last_day_only: bool, check the 'get_all_files_content' function.
|
|
34
|
+
:param get_deviation_for_date: str, check the 'get_all_files_content' function.
|
|
35
|
+
:param skip_total_count_less_than: integer, if the total count is less than this number, skip the deviation.
|
|
36
|
+
:param print_kwargs: dict, the print_api arguments.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
if not file_path and not statistics_content:
|
|
40
|
+
raise ValueError('Either file_path or statistics_content must be provided.')
|
|
41
|
+
if file_path and statistics_content:
|
|
42
|
+
raise ValueError('Only one of file_path or statistics_content must be provided.')
|
|
43
|
+
|
|
44
|
+
if get_deviation_for_last_day_only and get_deviation_for_date:
|
|
45
|
+
raise ValueError('Only one of get_deviation_for_last_day_only or get_deviation_for_date can be set.')
|
|
46
|
+
|
|
47
|
+
if not statistics_content:
|
|
48
|
+
statistics_content: dict = get_all_files_content(
|
|
49
|
+
file_path=file_path, moving_average_window_days=moving_average_window_days,
|
|
50
|
+
get_deviation_for_last_day_only=get_deviation_for_last_day_only,
|
|
51
|
+
get_deviation_for_date=get_deviation_for_date,
|
|
52
|
+
print_kwargs=print_kwargs)
|
|
53
|
+
|
|
54
|
+
for date_string, day_dict in statistics_content.items():
|
|
55
|
+
day_dict['content_no_useless'] = get_content_without_useless(day_dict['content'])
|
|
56
|
+
|
|
57
|
+
# Get the data dictionary from the statistics content.
|
|
58
|
+
day_dict['statistics_daily'] = compute_statistics_from_content(
|
|
59
|
+
day_dict['content_no_useless'], by_type)
|
|
60
|
+
|
|
61
|
+
moving_average_dict: dict = compute_moving_averages_from_average_statistics(
|
|
62
|
+
statistics_content,
|
|
63
|
+
moving_average_window_days
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Add the moving average to the statistics content.
|
|
67
|
+
for day, day_dict in statistics_content.items():
|
|
68
|
+
try:
|
|
69
|
+
day_dict['moving_average'] = moving_average_dict[day]
|
|
70
|
+
except KeyError:
|
|
71
|
+
day_dict['moving_average'] = {}
|
|
72
|
+
|
|
73
|
+
# Find deviation from the moving average to the bottom or top by specified percentage.
|
|
74
|
+
deviation_list: list = find_deviation_from_moving_average(
|
|
75
|
+
statistics_content, top_bottom_deviation_percentage, skip_total_count_less_than)
|
|
76
|
+
|
|
77
|
+
return deviation_list
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def get_all_files_content(
|
|
81
|
+
file_path: str,
|
|
82
|
+
moving_average_window_days: int,
|
|
83
|
+
get_deviation_for_last_day_only: bool = False,
|
|
84
|
+
get_deviation_for_date: str = None,
|
|
85
|
+
print_kwargs: dict = None
|
|
86
|
+
) -> dict:
|
|
87
|
+
"""
|
|
88
|
+
Get the dictionary that will contain all the details of the file, like date, header and content, to prepare for the MA analysis.
|
|
89
|
+
|
|
90
|
+
:param file_path: string, the path to the 'statistics.csv' file.
|
|
91
|
+
:param moving_average_window_days: integer, the window size for the moving average.
|
|
28
92
|
:param get_deviation_for_last_day_only: bool, if True, only the last day will be analyzed.
|
|
29
93
|
Example: With 'moving_average_window_days=5', the last 6 days will be analyzed.
|
|
30
94
|
5 days for moving average and the last day for deviation.
|
|
31
|
-
File names example:
|
|
95
|
+
File names example the last day is 2021-01-06:
|
|
32
96
|
statistics_2021-01-01.csv
|
|
33
97
|
statistics_2021-01-02.csv
|
|
34
98
|
statistics_2021-01-03.csv
|
|
@@ -38,12 +102,24 @@ def calculate_moving_average(
|
|
|
38
102
|
Files 01 to 05 will be used for moving average and the file 06 for deviation.
|
|
39
103
|
Meaning the average calculated for 2021-01-06 will be compared to the values moving average of 2021-01-01
|
|
40
104
|
to 2021-01-05.
|
|
41
|
-
:param
|
|
105
|
+
:param get_deviation_for_date: str, if set, the last day is considered the date that you set here.
|
|
106
|
+
The format should be the same as in the file names, e.g. 'YYYY-MM-DD'.
|
|
42
107
|
:param print_kwargs: dict, the print_api arguments.
|
|
108
|
+
:return:
|
|
43
109
|
"""
|
|
44
110
|
|
|
111
|
+
if get_deviation_for_last_day_only and get_deviation_for_date:
|
|
112
|
+
raise ValueError('Only one of get_deviation_for_last_day_only or get_deviation_for_date can be set.')
|
|
113
|
+
|
|
45
114
|
date_format: str = consts.DEFAULT_ROTATING_SUFFIXES_FROM_WHEN['midnight']
|
|
46
115
|
|
|
116
|
+
def is_valid_date(date_str: str) -> bool:
|
|
117
|
+
try:
|
|
118
|
+
datetime.datetime.strptime(date_str, date_format)
|
|
119
|
+
return True
|
|
120
|
+
except ValueError:
|
|
121
|
+
return False
|
|
122
|
+
|
|
47
123
|
# Get all the file paths and their midnight rotations.
|
|
48
124
|
logs_paths: list[filesystem.AtomicPath] = reading.get_logs_paths(
|
|
49
125
|
log_file_path=file_path,
|
|
@@ -54,6 +130,24 @@ def calculate_moving_average(
|
|
|
54
130
|
days_back_to_analyze: int = moving_average_window_days + 1
|
|
55
131
|
logs_paths = logs_paths[-days_back_to_analyze:]
|
|
56
132
|
|
|
133
|
+
if get_deviation_for_date:
|
|
134
|
+
# Check if the date format is correct.
|
|
135
|
+
if not is_valid_date(get_deviation_for_date):
|
|
136
|
+
raise ValueError(f'Date [{get_deviation_for_date}] is not in the correct format: {date_format}')
|
|
137
|
+
|
|
138
|
+
# Find the index of the date in the logs_paths list.
|
|
139
|
+
date_index: int | None = None
|
|
140
|
+
for index, log_atomic_path in enumerate(logs_paths):
|
|
141
|
+
if log_atomic_path.datetime_string == get_deviation_for_date:
|
|
142
|
+
date_index = index
|
|
143
|
+
break
|
|
144
|
+
|
|
145
|
+
if date_index is None:
|
|
146
|
+
raise ValueError(f'Date {get_deviation_for_date} not found in the log files.')
|
|
147
|
+
|
|
148
|
+
start_index: int = max(0, date_index - moving_average_window_days)
|
|
149
|
+
logs_paths = logs_paths[start_index:date_index + 1]
|
|
150
|
+
|
|
57
151
|
statistics_content: dict = {}
|
|
58
152
|
# Read each file to its day.
|
|
59
153
|
for log_atomic_path in logs_paths:
|
|
@@ -67,29 +161,7 @@ def calculate_moving_average(
|
|
|
67
161
|
statistics_content[date_string]['content'] = log_file_content
|
|
68
162
|
statistics_content[date_string]['header'] = log_file_header
|
|
69
163
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
# Get the data dictionary from the statistics content.
|
|
73
|
-
statistics_content[date_string]['statistics_daily'] = compute_statistics_from_content(
|
|
74
|
-
statistics_content[date_string]['content_no_useless'], by_type)
|
|
75
|
-
|
|
76
|
-
moving_average_dict: dict = compute_moving_averages_from_average_statistics(
|
|
77
|
-
statistics_content,
|
|
78
|
-
moving_average_window_days
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
# Add the moving average to the statistics content.
|
|
82
|
-
for day, day_dict in statistics_content.items():
|
|
83
|
-
try:
|
|
84
|
-
day_dict['moving_average'] = moving_average_dict[day]
|
|
85
|
-
except KeyError:
|
|
86
|
-
day_dict['moving_average'] = {}
|
|
87
|
-
|
|
88
|
-
# Find deviation from the moving average to the bottom or top by specified percentage.
|
|
89
|
-
deviation_list: list = find_deviation_from_moving_average(
|
|
90
|
-
statistics_content, top_bottom_deviation_percentage, skip_total_count_less_than)
|
|
91
|
-
|
|
92
|
-
return deviation_list
|
|
164
|
+
return statistics_content
|
|
93
165
|
|
|
94
166
|
|
|
95
167
|
def get_content_without_useless(content: list) -> list:
|
|
@@ -227,7 +299,7 @@ def compute_moving_averages_from_average_statistics(
|
|
|
227
299
|
|
|
228
300
|
# Create list of the last 'moving_average_window_days' days, including the current day.
|
|
229
301
|
last_x_window_days_content_list = (
|
|
230
|
-
list(average_statistics_dict.values()))[current_day-moving_average_window_days:current_day]
|
|
302
|
+
list(average_statistics_dict.values()))[current_day - moving_average_window_days:current_day]
|
|
231
303
|
|
|
232
304
|
# Compute the moving averages.
|
|
233
305
|
moving_average[day] = compute_average_for_current_day_from_past_x_days(
|
|
@@ -410,7 +482,8 @@ def find_deviation_from_moving_average(
|
|
|
410
482
|
if day_index == 0:
|
|
411
483
|
previous_day_moving_average_dict = {}
|
|
412
484
|
else:
|
|
413
|
-
previous_day_moving_average_dict = list(statistics_content.values())[day_index-1].get('moving_average',
|
|
485
|
+
previous_day_moving_average_dict = list(statistics_content.values())[day_index - 1].get('moving_average',
|
|
486
|
+
{})
|
|
414
487
|
|
|
415
488
|
# If there is no moving average for previous day continue to the next day.
|
|
416
489
|
if not previous_day_moving_average_dict:
|