@@ -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