Skip to content

Commit 80fe205

Browse files
committed
ft-analyzer fix parallel execution
1 parent dd83d94 commit 80fe205

1 file changed

Lines changed: 106 additions & 92 deletions

File tree

tools/ft-analyzer/ftanalyzer/models/statistical_model.py

Lines changed: 106 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -334,10 +334,28 @@ def validate(
334334
"""
335335
start = time.time()
336336

337+
# run _validate_helper in parallel on different files
338+
self._future_flow_values = self._executor.submit(
339+
_validate_helper, self._flows_path, rules, self._generator_stats
340+
)
341+
self._future_ref_values = self._executor.submit(
342+
_validate_helper, self._ref_path, rules, self._generator_stats, is_ref=True
343+
)
344+
flow_values, all_flow_masks = self._future_flow_values.result()
345+
337346
report = StatisticalReport(self._log_dir)
347+
if check_complement:
348+
complement_rules = [SMRule(SMMetricType.__members__.values())]
349+
self._future_complement_values = self._executor.submit(
350+
_validate_helper,
351+
self._flows_path,
352+
complement_rules,
353+
self._generator_stats,
354+
complement=True,
355+
flow_masks=all_flow_masks,
356+
)
338357

339-
flow_values, all_flow_masks = self._validate_helper(self._flows_path, rules)
340-
ref_values, _ = self._validate_helper(self._ref_path, rules, is_ref=True)
358+
ref_values, _ = self._future_ref_values.result()
341359

342360
for rule, flow_dict, ref_dict in zip(rules, flow_values, ref_values):
343361
for metric in rule.metrics:
@@ -356,11 +374,7 @@ def validate(
356374
)
357375

358376
if check_complement:
359-
rules = [SMRule(SMMetricType.__members__.values())]
360-
361-
complement_values, _ = self._validate_helper(
362-
self._flows_path, rules, complement=True, flow_masks=all_flow_masks
363-
)
377+
complement_values, _ = self._future_complement_values.result()
364378

365379
rule = rules[0]
366380
values = complement_values[0]
@@ -378,8 +392,10 @@ def validate(
378392
)
379393
)
380394

381-
statistic_objects = self._future_sim.result() if self._future_sim else {}
382-
ref_statistic_objects = self._future_ref.result() if self._future_ref else {}
395+
statistic_objects = self._future_sim.result() if self._future_sim else dict()
396+
ref_statistic_objects = (
397+
self._future_ref.result() if self._future_ref else dict()
398+
)
383399
if self._executor:
384400
self._executor.shutdown()
385401
for objects in zip(statistic_objects.values(), ref_statistic_objects.values()):
@@ -392,82 +408,6 @@ def validate(
392408

393409
return report
394410

395-
def _validate_helper(
396-
self,
397-
flows_file,
398-
rules: List[SMRule],
399-
chunksize=10_000,
400-
is_ref=False,
401-
complement=False,
402-
flow_masks=[],
403-
):
404-
all_flow_masks = {}
405-
values: List[dict] = []
406-
for chunk in pd.read_csv(
407-
flows_file, dtype=self.CSV_COLUMN_TYPES, chunksize=chunksize
408-
):
409-
if complement:
410-
chunk = chunk[~(reduce(operator.or_, flow_masks))].reset_index(
411-
drop=True
412-
)
413-
for i, rule in zip(range(len(rules)), rules):
414-
if not complement:
415-
flows, mask = self._filter_segment(rule.segment, chunk)
416-
all_flow_masks.update(mask)
417-
else:
418-
flows = chunk
419-
420-
values_dict = values[i] if i < len(values) else dict()
421-
422-
# Check duplicated metrics.
423-
if len({m.key for m in rule.metrics}) != len(rule.metrics):
424-
raise SMException(
425-
f"Rule contains duplicated metrics: {rule.metrics}"
426-
)
427-
428-
if is_ref:
429-
duration = (
430-
self._generator_stats.end_time
431-
- self._generator_stats.start_time
432-
+ 1
433-
) / 1000
434-
else:
435-
duration = (
436-
flows["END_TIME"].max() - flows["START_TIME"].min() + 1
437-
) / 1000
438-
439-
for metric in rule.metrics:
440-
match metric.key:
441-
case SMMetricType.FLOWS:
442-
value = len(flows.index)
443-
case SMMetricType.MBPS:
444-
value = 0 # later calculated
445-
case SMMetricType.PPS:
446-
value = 0 # later calculated
447-
case SMMetricType.DURATION:
448-
value = duration
449-
case _:
450-
value = flows[metric.key.value].sum()
451-
452-
if metric.key in values_dict:
453-
values_dict[metric.key] += value
454-
else:
455-
values_dict[metric.key] = value
456-
457-
if len(values) <= i:
458-
values.append(values_dict)
459-
else:
460-
values[i] = values_dict
461-
462-
for values_dict in values:
463-
duration = values_dict[SMMetricType.DURATION]
464-
values_dict[SMMetricType.MBPS] = (
465-
values_dict[SMMetricType.BYTES] / duration / 10**6
466-
)
467-
values_dict[SMMetricType.PPS] = values_dict[SMMetricType.PACKETS] / duration
468-
469-
return values, all_flow_masks
470-
471411
def _filter_multicast(self, flows: pd.DataFrame):
472412
# ipv4
473413
flows.drop(flows[flows["DST_IP"] == "255.255.255.255"].index, inplace=True)
@@ -522,7 +462,8 @@ def _merge_flows(self, biflows_ts_correction: bool) -> None:
522462

523463
flows_df.to_csv(self._flows_path, index=False)
524464

525-
def _convert_ip_addresses(self, flows_df: pd.DataFrame) -> None:
465+
@staticmethod
466+
def _convert_ip_addresses(flows_df: pd.DataFrame) -> None:
526467
"""Convert str ip addresses to objects (ipaddress library) in DataFrames."""
527468

528469
logging.getLogger().debug("Start applying ip_address...")
@@ -538,8 +479,8 @@ def _convert_ip_addresses(self, flows_df: pd.DataFrame) -> None:
538479
end = time.time()
539480
logging.getLogger().debug("IP address applied in %.2f seconds.", (end - start))
540481

482+
@staticmethod
541483
def _filter_segment(
542-
self,
543484
segment: Optional[Union[SMSubnetSegment, SMTimeSegment]],
544485
flows_df: pd.DataFrame,
545486
) -> Tuple[pd.DataFrame, pd.Series]:
@@ -557,17 +498,18 @@ def _filter_segment(
557498
"""
558499

559500
if isinstance(segment, SMSubnetSegment):
560-
self._convert_ip_addresses(flows_df)
561-
return self._filter_subnet_segment(segment)
501+
StatisticalModel._convert_ip_addresses(flows_df)
502+
return StatisticalModel._filter_subnet_segment(segment)
562503

563504
if isinstance(segment, SMTimeSegment):
564-
return self._filter_time_segment(segment)
505+
return StatisticalModel._filter_time_segment(segment)
565506

566507
assert segment is None
567508
return flows_df, pd.Series([True] * flows_df.shape[0])
568509

510+
@staticmethod
569511
def _filter_subnet_segment(
570-
self, segment: SMSubnetSegment, flows_df: pd.DataFrame
512+
segment: SMSubnetSegment, flows_df: pd.DataFrame
571513
) -> Tuple[pd.DataFrame, pd.Series]:
572514
"""Create subsets of data frames based on subnets.
573515
@@ -622,8 +564,9 @@ def _filter_subnet_segment(
622564
mask_flow,
623565
)
624566

567+
@staticmethod
625568
def _filter_time_segment(
626-
self, segment: SMTimeSegment, flows_df: pd.DataFrame
569+
segment: SMTimeSegment, flows_df: pd.DataFrame
627570
) -> Tuple[pd.DataFrame, pd.Series]:
628571
"""Create subsets of data frames based on time interval.
629572
@@ -1018,3 +961,74 @@ def all_instance_of(iterable: Iterable, cls):
1018961
Check if all elements in `iterable` are instances of `cls`.
1019962
"""
1020963
return all(isinstance(item, cls) for item in iterable)
964+
965+
966+
def _validate_helper(
967+
flows_file,
968+
rules: List[SMRule],
969+
generator_stats: GeneratorStats,
970+
chunksize=10_000,
971+
is_ref=False,
972+
complement=False,
973+
flow_masks=[],
974+
):
975+
all_flow_masks = {}
976+
values: List[dict] = []
977+
for chunk in pd.read_csv(
978+
flows_file, dtype=StatisticalModel.CSV_COLUMN_TYPES, chunksize=chunksize
979+
):
980+
if complement:
981+
chunk = chunk[~(reduce(operator.or_, flow_masks))].reset_index(drop=True)
982+
for i, rule in zip(range(len(rules)), rules):
983+
if not complement:
984+
flows, mask = StatisticalModel._filter_segment(rule.segment, chunk)
985+
all_flow_masks.update(mask)
986+
else:
987+
flows = chunk
988+
989+
values_dict = values[i] if i < len(values) else dict()
990+
991+
# Check duplicated metrics.
992+
if len({m.key for m in rule.metrics}) != len(rule.metrics):
993+
raise SMException(f"Rule contains duplicated metrics: {rule.metrics}")
994+
995+
if is_ref:
996+
duration = (
997+
generator_stats.end_time - generator_stats.start_time + 1
998+
) / 1000
999+
else:
1000+
duration = (
1001+
flows["END_TIME"].max() - flows["START_TIME"].min() + 1
1002+
) / 1000
1003+
1004+
for metric in rule.metrics:
1005+
match metric.key:
1006+
case SMMetricType.FLOWS:
1007+
value = len(flows.index)
1008+
case SMMetricType.MBPS:
1009+
value = 0 # later calculated
1010+
case SMMetricType.PPS:
1011+
value = 0 # later calculated
1012+
case SMMetricType.DURATION:
1013+
value = duration
1014+
case _:
1015+
value = flows[metric.key.value].sum()
1016+
1017+
if metric.key in values_dict:
1018+
values_dict[metric.key] += value
1019+
else:
1020+
values_dict[metric.key] = value
1021+
1022+
if len(values) <= i:
1023+
values.append(values_dict)
1024+
else:
1025+
values[i] = values_dict
1026+
1027+
for values_dict in values:
1028+
duration = values_dict[SMMetricType.DURATION]
1029+
values_dict[SMMetricType.MBPS] = (
1030+
values_dict[SMMetricType.BYTES] / duration / 10**6
1031+
)
1032+
values_dict[SMMetricType.PPS] = values_dict[SMMetricType.PACKETS] / duration
1033+
1034+
return values, all_flow_masks

0 commit comments

Comments
 (0)