atomicshop 2.14.3__py3-none-any.whl → 2.14.5__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/datetimes.py +3 -2
- atomicshop/etws/providers.py +18 -2
- atomicshop/etws/sessions.py +1 -1
- atomicshop/etws/trace.py +0 -1
- atomicshop/file_io/csvs.py +1 -1
- atomicshop/mitm/initialize_engines.py +8 -2
- atomicshop/mitm/initialize_mitm_server.py +24 -7
- atomicshop/mitm/statistic_analyzer.py +376 -3
- atomicshop/wrappers/ctyping/etw_winapi/const.py +130 -20
- atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py +141 -5
- atomicshop/wrappers/loggingw/formatters.py +66 -40
- atomicshop/wrappers/loggingw/handlers.py +51 -11
- atomicshop/wrappers/loggingw/loggingw.py +180 -167
- atomicshop/wrappers/loggingw/reading.py +20 -19
- {atomicshop-2.14.3.dist-info → atomicshop-2.14.5.dist-info}/METADATA +1 -1
- {atomicshop-2.14.3.dist-info → atomicshop-2.14.5.dist-info}/RECORD +20 -20
- {atomicshop-2.14.3.dist-info → atomicshop-2.14.5.dist-info}/LICENSE.txt +0 -0
- {atomicshop-2.14.3.dist-info → atomicshop-2.14.5.dist-info}/WHEEL +0 -0
- {atomicshop-2.14.3.dist-info → atomicshop-2.14.5.dist-info}/top_level.txt +0 -0
atomicshop/__init__.py
CHANGED
atomicshop/datetimes.py
CHANGED
|
@@ -47,7 +47,7 @@ class MonthToNumber:
|
|
|
47
47
|
'דצמבר': '12'}
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
def get_datetime_from_complex_string_by_pattern(complex_string: str, date_pattern: str):
|
|
50
|
+
def get_datetime_from_complex_string_by_pattern(complex_string: str, date_pattern: str) -> tuple[datetime, str, float]:
|
|
51
51
|
"""
|
|
52
52
|
Function will get datetime object from a complex string by pattern.
|
|
53
53
|
|
|
@@ -65,7 +65,8 @@ def get_datetime_from_complex_string_by_pattern(complex_string: str, date_patter
|
|
|
65
65
|
if date_str:
|
|
66
66
|
# Convert the date string to a datetime object based on the given pattern
|
|
67
67
|
date_obj = datetime.datetime.strptime(date_str.group(), date_pattern)
|
|
68
|
-
|
|
68
|
+
date_timestamp = date_obj.timestamp()
|
|
69
|
+
return date_obj, date_str.group(), date_timestamp
|
|
69
70
|
else:
|
|
70
71
|
raise ValueError("No valid date found in the string")
|
|
71
72
|
|
atomicshop/etws/providers.py
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
1
3
|
from ..wrappers.ctyping.etw_winapi import etw_functions
|
|
2
4
|
|
|
3
5
|
|
|
4
|
-
def get_providers():
|
|
5
|
-
return etw_functions.get_all_providers()
|
|
6
|
+
def get_providers(key_as: Literal['name', 'guid'] = 'name'):
|
|
7
|
+
return etw_functions.get_all_providers(key_as=key_as)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_provider_guid_by_name(provider_name):
|
|
11
|
+
providers = get_providers(key_as='name')
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
provider_guid = providers[provider_name]
|
|
15
|
+
except KeyError:
|
|
16
|
+
provider_guid = None
|
|
17
|
+
|
|
18
|
+
if not provider_guid:
|
|
19
|
+
raise ValueError(f"Provider '{provider_name}' not found")
|
|
20
|
+
|
|
21
|
+
return provider_guid
|
atomicshop/etws/sessions.py
CHANGED
atomicshop/etws/trace.py
CHANGED
atomicshop/file_io/csvs.py
CHANGED
|
@@ -36,7 +36,7 @@ def read_csv_to_list_of_dicts_by_header(
|
|
|
36
36
|
All the lines of the CSV file will be considered as content.
|
|
37
37
|
:param file_object: file object of the 'open()' function in the decorator. Decorator executes the 'with open()'
|
|
38
38
|
statement and passes to this function. That's why the default is 'None', since we get it from the decorator.
|
|
39
|
-
:return: list.
|
|
39
|
+
:return: tuple(list of entries, header(list of cell names)).
|
|
40
40
|
"""
|
|
41
41
|
|
|
42
42
|
# The header fields will be separated to list of "csv_reader.fieldnames".
|
|
@@ -83,8 +83,14 @@ class ModuleCategory:
|
|
|
83
83
|
|
|
84
84
|
# Initiating logger for each engine by its name
|
|
85
85
|
# initiate_logger(current_module.engine_name, log_file_extension)
|
|
86
|
-
loggingw.
|
|
87
|
-
logger_name=self.engine_name,
|
|
86
|
+
loggingw.get_complex_logger(
|
|
87
|
+
logger_name=self.engine_name,
|
|
88
|
+
directory_path=logs_path,
|
|
89
|
+
add_stream=True,
|
|
90
|
+
add_timedfile=True,
|
|
91
|
+
formatter_streamhandler='DEFAULT',
|
|
92
|
+
formatter_filehandler='DEFAULT'
|
|
93
|
+
)
|
|
88
94
|
|
|
89
95
|
|
|
90
96
|
# Assigning external class object by message domain received from client. If the domain is not in the list,
|
|
@@ -41,8 +41,15 @@ def initialize_mitm_server(config_static):
|
|
|
41
41
|
config['certificates']['sni_server_certificate_from_server_socket_download_directory'])
|
|
42
42
|
|
|
43
43
|
# Create a logger that will log messages to file, Initiate System logger.
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
logger_name = "system"
|
|
45
|
+
system_logger = loggingw.get_complex_logger(
|
|
46
|
+
logger_name=logger_name,
|
|
47
|
+
file_path=f'{config['log']['logs_path']}{os.sep}{logger_name}.txt',
|
|
48
|
+
add_stream=True,
|
|
49
|
+
add_timedfile=True,
|
|
50
|
+
formatter_streamhandler='DEFAULT',
|
|
51
|
+
formatter_filehandler='DEFAULT'
|
|
52
|
+
)
|
|
46
53
|
|
|
47
54
|
# Writing first log.
|
|
48
55
|
system_logger.info("======================================")
|
|
@@ -175,14 +182,24 @@ def initialize_mitm_server(config_static):
|
|
|
175
182
|
config_static.CONFIG_EXTENDED['certificates']['domains_all_times'] = list(domains_engine_list_full)
|
|
176
183
|
|
|
177
184
|
# Creating Statistics logger.
|
|
178
|
-
statistics_logger = loggingw.
|
|
179
|
-
logger_name="statistics",
|
|
180
|
-
|
|
185
|
+
statistics_logger = loggingw.get_complex_logger(
|
|
186
|
+
logger_name="statistics",
|
|
187
|
+
directory_path=config['log']['logs_path'],
|
|
188
|
+
add_timedfile=True,
|
|
189
|
+
formatter_filehandler='MESSAGE',
|
|
190
|
+
file_type='csv',
|
|
191
|
+
header=STATISTICS_HEADER
|
|
181
192
|
)
|
|
182
193
|
|
|
183
194
|
network_logger_name = "network"
|
|
184
|
-
network_logger = loggingw.
|
|
185
|
-
logger_name=network_logger_name,
|
|
195
|
+
network_logger = loggingw.get_complex_logger(
|
|
196
|
+
logger_name=network_logger_name,
|
|
197
|
+
directory_path=config['log']['logs_path'],
|
|
198
|
+
add_stream=True,
|
|
199
|
+
add_timedfile=True,
|
|
200
|
+
formatter_streamhandler='DEFAULT',
|
|
201
|
+
formatter_filehandler='DEFAULT'
|
|
202
|
+
)
|
|
186
203
|
system_logger.info(f"Loaded network logger: {network_logger}")
|
|
187
204
|
|
|
188
205
|
# Initiate Listener logger, which is a child of network logger, so he uses the same settings and handlers
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import os
|
|
1
2
|
import datetime
|
|
3
|
+
import statistics
|
|
4
|
+
import json
|
|
5
|
+
from typing import Literal
|
|
2
6
|
|
|
3
7
|
from .. import filesystem, domains, datetimes, urls
|
|
4
8
|
from ..basics import dicts
|
|
5
|
-
from ..file_io import tomls, xlsxs
|
|
9
|
+
from ..file_io import tomls, xlsxs, csvs, jsons
|
|
6
10
|
from ..wrappers.loggingw import reading
|
|
7
11
|
from ..print_api import print_api
|
|
8
12
|
|
|
@@ -154,10 +158,10 @@ def analyze(main_file_path: str):
|
|
|
154
158
|
summary_path: str = filesystem.check_absolute_path___add_full(config['report_file_path'], script_directory)
|
|
155
159
|
|
|
156
160
|
# Get the content from statistics files.
|
|
157
|
-
statistics_content: list = reading.
|
|
161
|
+
statistics_content: list = reading.get_all_log_files_into_list(
|
|
158
162
|
config['statistic_files_path'],
|
|
159
163
|
file_name_pattern='statistics*.csv',
|
|
160
|
-
log_type='csv'
|
|
164
|
+
log_type='csv'
|
|
161
165
|
)
|
|
162
166
|
|
|
163
167
|
# Initialize loop.
|
|
@@ -465,3 +469,372 @@ def analyze(main_file_path: str):
|
|
|
465
469
|
xlsxs.write_xlsx(combined_sorted_stats, file_path=summary_path)
|
|
466
470
|
|
|
467
471
|
return
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
# ======================================================================================================================
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def calculate_moving_average(
|
|
478
|
+
file_path: str,
|
|
479
|
+
moving_average_window_days,
|
|
480
|
+
top_bottom_deviation_percentage: float,
|
|
481
|
+
print_kwargs: dict = None
|
|
482
|
+
):
|
|
483
|
+
"""
|
|
484
|
+
This function calculates the moving average of the daily statistics.
|
|
485
|
+
|
|
486
|
+
:param file_path: string, the path to the 'statistics.csv' file.
|
|
487
|
+
:param moving_average_window_days: integer, the window size for the moving average.
|
|
488
|
+
:param top_bottom_deviation_percentage: float, the percentage of deviation from the moving average to the top or
|
|
489
|
+
bottom.
|
|
490
|
+
:param print_kwargs: dict, the print_api arguments.
|
|
491
|
+
"""
|
|
492
|
+
|
|
493
|
+
date_pattern: str = '%Y_%m_%d'
|
|
494
|
+
|
|
495
|
+
# Get all the file paths and their midnight rotations.
|
|
496
|
+
logs_paths: list = reading.get_logs_paths(
|
|
497
|
+
log_file_path=file_path,
|
|
498
|
+
date_pattern=date_pattern
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
statistics_content: dict = {}
|
|
502
|
+
# Read each file to its day.
|
|
503
|
+
for log_path_dict in logs_paths:
|
|
504
|
+
date_string = log_path_dict['date_string']
|
|
505
|
+
statistics_content[date_string] = {}
|
|
506
|
+
|
|
507
|
+
statistics_content[date_string]['file'] = log_path_dict
|
|
508
|
+
|
|
509
|
+
log_file_content, log_file_header = (
|
|
510
|
+
csvs.read_csv_to_list_of_dicts_by_header(log_path_dict['file_path'], **(print_kwargs or {})))
|
|
511
|
+
statistics_content[date_string]['content'] = log_file_content
|
|
512
|
+
statistics_content[date_string]['header'] = log_file_header
|
|
513
|
+
|
|
514
|
+
statistics_content[date_string]['content_no_errors'] = get_content_without_errors(log_file_content)
|
|
515
|
+
|
|
516
|
+
# Get the data dictionary from the statistics content.
|
|
517
|
+
statistics_content[date_string]['statistics_daily'] = compute_statistics_from_content(
|
|
518
|
+
statistics_content[date_string]['content_no_errors']
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
moving_average_dict: dict = compute_moving_averages_from_average_statistics(
|
|
522
|
+
statistics_content,
|
|
523
|
+
moving_average_window_days
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
# Add the moving average to the statistics content.
|
|
527
|
+
for day, day_dict in statistics_content.items():
|
|
528
|
+
try:
|
|
529
|
+
day_dict['moving_average'] = moving_average_dict[day]
|
|
530
|
+
except KeyError:
|
|
531
|
+
day_dict['moving_average'] = {}
|
|
532
|
+
|
|
533
|
+
# Find deviation from the moving average to the bottom or top by specified percentage.
|
|
534
|
+
deviation_list: list = find_deviation_from_moving_average(
|
|
535
|
+
statistics_content, top_bottom_deviation_percentage)
|
|
536
|
+
|
|
537
|
+
return deviation_list
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def get_content_without_errors(content: list) -> list:
|
|
541
|
+
"""
|
|
542
|
+
This function gets the 'statistics.csv' file content without errors from the 'content' list.
|
|
543
|
+
|
|
544
|
+
:param content: list, the content list.
|
|
545
|
+
:return: list, the content without errors.
|
|
546
|
+
"""
|
|
547
|
+
|
|
548
|
+
traffic_statistics_without_errors: list = []
|
|
549
|
+
for line in content:
|
|
550
|
+
# Skip empty lines, headers and errors.
|
|
551
|
+
if line['host'] == 'host' or line['command'] == '':
|
|
552
|
+
continue
|
|
553
|
+
|
|
554
|
+
traffic_statistics_without_errors.append(line)
|
|
555
|
+
|
|
556
|
+
return traffic_statistics_without_errors
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def get_data_dict_from_statistics_content(content: list) -> dict:
|
|
560
|
+
"""
|
|
561
|
+
This function gets the data dictionary from the 'statistics.csv' file content.
|
|
562
|
+
|
|
563
|
+
:param content: list, the content list.
|
|
564
|
+
:return: dict, the data dictionary.
|
|
565
|
+
"""
|
|
566
|
+
|
|
567
|
+
hosts_requests_responses: dict = {}
|
|
568
|
+
for line in content:
|
|
569
|
+
# If subdomain is not in the dictionary, add it.
|
|
570
|
+
if line['host'] not in hosts_requests_responses:
|
|
571
|
+
hosts_requests_responses[line['host']] = {
|
|
572
|
+
'request_sizes': [],
|
|
573
|
+
'response_sizes': []
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
# Append the sizes.
|
|
577
|
+
try:
|
|
578
|
+
hosts_requests_responses[line['host']]['request_sizes'].append(int(line['request_size_bytes']))
|
|
579
|
+
hosts_requests_responses[line['host']]['response_sizes'].append(
|
|
580
|
+
int(line['response_size_bytes']))
|
|
581
|
+
except ValueError:
|
|
582
|
+
print_api(line, color='yellow')
|
|
583
|
+
raise
|
|
584
|
+
|
|
585
|
+
return hosts_requests_responses
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def compute_statistics_from_data_dict(data_dict: dict):
|
|
589
|
+
"""
|
|
590
|
+
This function computes the statistics from the data dictionary.
|
|
591
|
+
|
|
592
|
+
:param data_dict: dict, the data dictionary.
|
|
593
|
+
:return: dict, the statistics dictionary.
|
|
594
|
+
"""
|
|
595
|
+
|
|
596
|
+
for host, host_dict in data_dict.items():
|
|
597
|
+
count = len(host_dict['request_sizes'])
|
|
598
|
+
avg_request_size = statistics.mean(host_dict['request_sizes']) if count > 0 else 0
|
|
599
|
+
median_request_size = statistics.median(host_dict['request_sizes']) if count > 0 else 0
|
|
600
|
+
avg_response_size = statistics.mean(host_dict['response_sizes']) if count > 0 else 0
|
|
601
|
+
median_response_size = statistics.median(host_dict['response_sizes']) if count > 0 else 0
|
|
602
|
+
|
|
603
|
+
data_dict[host]['count'] = count
|
|
604
|
+
data_dict[host]['avg_request_size'] = avg_request_size
|
|
605
|
+
data_dict[host]['median_request_size'] = median_request_size
|
|
606
|
+
data_dict[host]['avg_response_size'] = avg_response_size
|
|
607
|
+
data_dict[host]['median_response_size'] = median_response_size
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def compute_statistics_from_content(content: list):
|
|
611
|
+
"""
|
|
612
|
+
This function computes the statistics from the 'statistics.csv' file content.
|
|
613
|
+
|
|
614
|
+
:param content: list, the content list.
|
|
615
|
+
:return: dict, the statistics dictionary.
|
|
616
|
+
"""
|
|
617
|
+
|
|
618
|
+
hosts_requests_responses: dict = get_data_dict_from_statistics_content(content)
|
|
619
|
+
compute_statistics_from_data_dict(hosts_requests_responses)
|
|
620
|
+
|
|
621
|
+
return hosts_requests_responses
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
def compute_moving_averages_from_average_statistics(
|
|
625
|
+
average_statistics_dict: dict,
|
|
626
|
+
moving_average_window_days: int
|
|
627
|
+
):
|
|
628
|
+
"""
|
|
629
|
+
This function computes the moving averages from the average statistics dictionary.
|
|
630
|
+
|
|
631
|
+
:param average_statistics_dict: dict, the average statistics dictionary.
|
|
632
|
+
:param moving_average_window_days: integer, the window size for the moving average.
|
|
633
|
+
:return: dict, the moving averages dictionary.
|
|
634
|
+
"""
|
|
635
|
+
|
|
636
|
+
moving_average: dict = {}
|
|
637
|
+
for day_index, (day, day_dict) in enumerate(average_statistics_dict.items()):
|
|
638
|
+
current_day = day_index + 1
|
|
639
|
+
if current_day < moving_average_window_days:
|
|
640
|
+
continue
|
|
641
|
+
|
|
642
|
+
# Create list of the previous 'moving_average_window_days' days.
|
|
643
|
+
previous_days_content_list = (
|
|
644
|
+
list(average_statistics_dict.values()))[current_day-moving_average_window_days:current_day]
|
|
645
|
+
|
|
646
|
+
# Compute the moving averages.
|
|
647
|
+
moving_average[day] = compute_average_for_current_day_from_past_x_days(previous_days_content_list)
|
|
648
|
+
|
|
649
|
+
return moving_average
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def compute_average_for_current_day_from_past_x_days(previous_days_content_list: list) -> dict:
|
|
653
|
+
"""
|
|
654
|
+
This function computes the average for the current day from the past x days.
|
|
655
|
+
|
|
656
|
+
:param previous_days_content_list: list, the list of the previous days content.
|
|
657
|
+
:return: dict, the average dictionary.
|
|
658
|
+
"""
|
|
659
|
+
|
|
660
|
+
moving_average: dict = {}
|
|
661
|
+
for entry in previous_days_content_list:
|
|
662
|
+
statistics_daily = entry['statistics_daily']
|
|
663
|
+
for host, host_dict in statistics_daily.items():
|
|
664
|
+
if host not in moving_average:
|
|
665
|
+
moving_average[host] = {
|
|
666
|
+
'counts': [],
|
|
667
|
+
'avg_request_sizes': [],
|
|
668
|
+
'avg_response_sizes': [],
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
moving_average[host]['counts'].append(int(host_dict['count']))
|
|
672
|
+
moving_average[host]['avg_request_sizes'].append(float(host_dict['avg_request_size']))
|
|
673
|
+
moving_average[host]['avg_response_sizes'].append(float(host_dict['avg_response_size']))
|
|
674
|
+
|
|
675
|
+
# Compute the moving average.
|
|
676
|
+
moving_average_results: dict = {}
|
|
677
|
+
for host, host_dict in moving_average.items():
|
|
678
|
+
ma_count = statistics.mean(host_dict['counts'])
|
|
679
|
+
ma_request_size = statistics.mean(host_dict['avg_request_sizes'])
|
|
680
|
+
ma_response_size = statistics.mean(host_dict['avg_response_sizes'])
|
|
681
|
+
|
|
682
|
+
moving_average_results[host] = {
|
|
683
|
+
'ma_count': ma_count,
|
|
684
|
+
'ma_request_size': ma_request_size,
|
|
685
|
+
'ma_response_size': ma_response_size,
|
|
686
|
+
'counts': host_dict['counts'],
|
|
687
|
+
'avg_request_sizes': host_dict['avg_request_sizes'],
|
|
688
|
+
'avg_response_sizes': host_dict['avg_response_sizes']
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
return moving_average_results
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
def find_deviation_from_moving_average(
|
|
695
|
+
statistics_content: dict,
|
|
696
|
+
top_bottom_deviation_percentage: float
|
|
697
|
+
) -> list:
|
|
698
|
+
"""
|
|
699
|
+
This function finds the deviation from the moving average to the bottom or top by specified percentage.
|
|
700
|
+
|
|
701
|
+
:param statistics_content: dict, the statistics content dictionary.
|
|
702
|
+
:param top_bottom_deviation_percentage: float, the percentage of deviation from the moving average to the top or
|
|
703
|
+
bottom.
|
|
704
|
+
:return: list, the deviation list.
|
|
705
|
+
"""
|
|
706
|
+
|
|
707
|
+
def _check_deviation(
|
|
708
|
+
check_type: Literal['count', 'avg_request_size', 'avg_response_size'],
|
|
709
|
+
ma_check_type: Literal['ma_count', 'ma_request_size', 'ma_response_size'],
|
|
710
|
+
day_statistics_content_dict: dict,
|
|
711
|
+
moving_averages_dict: dict
|
|
712
|
+
):
|
|
713
|
+
"""
|
|
714
|
+
This function checks the deviation for the host.
|
|
715
|
+
"""
|
|
716
|
+
|
|
717
|
+
nonlocal message
|
|
718
|
+
|
|
719
|
+
host_moving_average_by_type = moving_averages_dict[host][ma_check_type]
|
|
720
|
+
check_type_moving_by_percent = (
|
|
721
|
+
host_moving_average_by_type * top_bottom_deviation_percentage)
|
|
722
|
+
check_type_moving_above = host_moving_average_by_type + check_type_moving_by_percent
|
|
723
|
+
check_type_moving_below = host_moving_average_by_type - check_type_moving_by_percent
|
|
724
|
+
|
|
725
|
+
deviation_type = None
|
|
726
|
+
if day_statistics_content_dict[check_type] > check_type_moving_above:
|
|
727
|
+
deviation_type = 'above'
|
|
728
|
+
elif day_statistics_content_dict[check_type] < check_type_moving_below:
|
|
729
|
+
deviation_type = 'below'
|
|
730
|
+
|
|
731
|
+
if deviation_type:
|
|
732
|
+
message = f'[{check_type}] is [{deviation_type}] the moving average.'
|
|
733
|
+
deviation_list.append({
|
|
734
|
+
'day': day,
|
|
735
|
+
'host': host,
|
|
736
|
+
'message': message,
|
|
737
|
+
'value': day_statistics_content_dict[check_type],
|
|
738
|
+
'ma_value': host_moving_average_by_type,
|
|
739
|
+
'check_type': check_type,
|
|
740
|
+
'percentage': top_bottom_deviation_percentage,
|
|
741
|
+
'ma_value_checked': check_type_moving_above,
|
|
742
|
+
'deviation_type': deviation_type,
|
|
743
|
+
'data': day_statistics_content_dict,
|
|
744
|
+
'ma_data': moving_averages_dict[host]
|
|
745
|
+
})
|
|
746
|
+
|
|
747
|
+
deviation_list: list = []
|
|
748
|
+
for day_index, (day, day_dict) in enumerate(statistics_content.items()):
|
|
749
|
+
# If it's the first day, there is no previous day moving average.
|
|
750
|
+
if day_index == 0:
|
|
751
|
+
previous_day_moving_average_dict = {}
|
|
752
|
+
else:
|
|
753
|
+
previous_day_moving_average_dict = list(statistics_content.values())[day_index-1].get('moving_average', {})
|
|
754
|
+
|
|
755
|
+
# If there is no moving average for previous day continue to the next day.
|
|
756
|
+
if not previous_day_moving_average_dict:
|
|
757
|
+
continue
|
|
758
|
+
|
|
759
|
+
for host, host_dict in day_dict['statistics_daily'].items():
|
|
760
|
+
# If the host is not in the moving averages, then this is clear deviation.
|
|
761
|
+
# It means that in the current day, there were no requests for this host.
|
|
762
|
+
if host not in previous_day_moving_average_dict:
|
|
763
|
+
message = f'Host not in the moving averages: {host}'
|
|
764
|
+
deviation_list.append({
|
|
765
|
+
'day': day,
|
|
766
|
+
'host': host,
|
|
767
|
+
'data': host_dict,
|
|
768
|
+
'message': message,
|
|
769
|
+
'type': 'clear'
|
|
770
|
+
})
|
|
771
|
+
continue
|
|
772
|
+
|
|
773
|
+
_check_deviation(
|
|
774
|
+
'count', 'ma_count', host_dict, previous_day_moving_average_dict)
|
|
775
|
+
_check_deviation(
|
|
776
|
+
'avg_request_size', 'ma_request_size', host_dict, previous_day_moving_average_dict)
|
|
777
|
+
_check_deviation(
|
|
778
|
+
'avg_response_size', 'ma_response_size', host_dict, previous_day_moving_average_dict)
|
|
779
|
+
|
|
780
|
+
return deviation_list
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
def moving_average_calculator_main(
|
|
784
|
+
statistics_file_path: str,
|
|
785
|
+
output_directory: str,
|
|
786
|
+
moving_average_window_days: int,
|
|
787
|
+
top_bottom_deviation_percentage: float
|
|
788
|
+
) -> int:
|
|
789
|
+
"""
|
|
790
|
+
This function is the main function for the moving average calculator.
|
|
791
|
+
|
|
792
|
+
:param statistics_file_path: string, the statistics file path.
|
|
793
|
+
:param output_directory: string, the output directory.
|
|
794
|
+
:param moving_average_window_days: integer, the moving average window days.
|
|
795
|
+
:param top_bottom_deviation_percentage: float, the top bottom deviation percentage. Example: 0.1 for 10%.
|
|
796
|
+
:return: integer, the return code.
|
|
797
|
+
-----------------------------
|
|
798
|
+
|
|
799
|
+
Example:
|
|
800
|
+
import sys
|
|
801
|
+
from atomicshop.mitm import statistic_analyzer
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
def main():
|
|
805
|
+
return statistic_analyzer.moving_average_calculator_main(
|
|
806
|
+
statistics_file_path='statistics.csv',
|
|
807
|
+
output_directory='output',
|
|
808
|
+
moving_average_window_days=7,
|
|
809
|
+
top_bottom_deviation_percentage=0.1
|
|
810
|
+
)
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
if __name__ == '__main__':
|
|
814
|
+
sys.exit(main())
|
|
815
|
+
"""
|
|
816
|
+
|
|
817
|
+
def convert_data_value_to_string(value_key: str, list_index: int) -> None:
|
|
818
|
+
deviation_list[list_index]['data'][value_key] = json.dumps(deviation_list[list_index]['data'][value_key])
|
|
819
|
+
|
|
820
|
+
def convert_value_to_string(value_key: str, list_index: int) -> None:
|
|
821
|
+
if value_key in deviation_list[list_index]:
|
|
822
|
+
deviation_list[list_index][value_key] = json.dumps(deviation_list[list_index][value_key])
|
|
823
|
+
|
|
824
|
+
deviation_list = calculate_moving_average(
|
|
825
|
+
statistics_file_path,
|
|
826
|
+
moving_average_window_days,
|
|
827
|
+
top_bottom_deviation_percentage
|
|
828
|
+
)
|
|
829
|
+
|
|
830
|
+
if deviation_list:
|
|
831
|
+
for deviation_list_index, deviation in enumerate(deviation_list):
|
|
832
|
+
convert_data_value_to_string('request_sizes', deviation_list_index)
|
|
833
|
+
convert_data_value_to_string('response_sizes', deviation_list_index)
|
|
834
|
+
convert_value_to_string('ma_data', deviation_list_index)
|
|
835
|
+
|
|
836
|
+
file_path = output_directory + os.sep + 'deviation.json'
|
|
837
|
+
print_api(f'Deviation Found, saving to file: {file_path}', color='blue')
|
|
838
|
+
jsons.write_json_file(deviation_list, file_path, use_default_indent=True)
|
|
839
|
+
|
|
840
|
+
return 0
|