@@ -2693,7 +2693,8 @@ def rules(self, out, benchmarks, bmSuiteArgs):
26932693 "ktor-hello-world" : {},
26942694 "play-scala-hello-world" : {},
26952695 },
2696- "latency_percentiles" : [50.0 , 75.0 , 90.0 , 99.0 , 99.9 , 99.99 , 99.999 , 100.0 ],
2696+ # Should currently only contain round numbers due to the field incorrectly being indexed as integer in the DB (GR-57487)
2697+ "latency_percentiles" : [50.0 , 75.0 , 90.0 , 99.0 , 100.0 ],
26972698 "rss_percentiles" : [100 , 99 , 98 , 97 , 96 , 95 , 90 , 75 , 50 , 25 ],
26982699 "disable_trackers" : [mx_benchmark .RssTracker , mx_benchmark .PsrecordTracker , mx_benchmark .PsrecordMaxrssTracker , mx_benchmark .RssPercentilesTracker , mx_benchmark .RssPercentilesAndMaxTracker ],
26992700}
@@ -2825,43 +2826,6 @@ def rules(self, out, benchmarks, bmSuiteArgs):
28252826 "load-tester.id" : ("<startup.id>" , str ),
28262827 "load-tester.method-type" : "requests"
28272828 }, ["startup.id" , "startup.measurements.iteration" , "startup.measurements.response_time" ]))
2828- # copy the response_time with iteration 0 into time-to-first-response
2829- class DeriveTimeToFirstResponseRule (mx_benchmark .JsonArrayStdOutFileRule ):
2830- def parse (self , text ) -> Iterable [DataPoint ]:
2831- datapoints = super ().parse (text )
2832- iteration_0_datapoints = [datapoint for datapoint in datapoints if datapoint ["metric.iteration" ] == 0 ]
2833- for datapoint in iteration_0_datapoints :
2834- del datapoint ["metric.iteration" ]
2835- return iteration_0_datapoints
2836- all_rules .append (DeriveTimeToFirstResponseRule (json_file_pattern , json_file_group_name , {
2837- "benchmark" : self .context .benchmark ,
2838- "metric.name" : "time-to-first-response" ,
2839- "metric.type" : "numeric" ,
2840- "metric.unit" : "ms" ,
2841- "metric.value" : ("<startup.measurements.response_time>" , float ),
2842- "metric.better" : "lower" ,
2843- "metric.iteration" : ("<startup.measurements.iteration>" , int ),
2844- "load-tester.id" : ("<startup.id>" , str ),
2845- "load-tester.method-type" : "requests"
2846- }, ["startup.id" , "startup.measurements.iteration" , "startup.measurements.response_time" ]))
2847- # copy the worst response_time into max-time
2848- class DeriveMaxTimeRule (mx_benchmark .JsonArrayStdOutFileRule ):
2849- def parse (self , text ) -> Iterable [DataPoint ]:
2850- datapoints = super ().parse (text )
2851- if len (datapoints ) == 0 :
2852- return []
2853- max_value_datapoints = [max (datapoints , key = lambda x : x ["metric.value" ])]
2854- return max_value_datapoints
2855- all_rules .append (DeriveMaxTimeRule (json_file_pattern , json_file_group_name , {
2856- "benchmark" : self .context .benchmark ,
2857- "metric.name" : "max-time" ,
2858- "metric.type" : "numeric" ,
2859- "metric.unit" : "ms" ,
2860- "metric.value" : ("<startup.measurements.response_time>" , float ),
2861- "metric.better" : "lower" ,
2862- "load-tester.id" : ("<startup.id>" , str ),
2863- "load-tester.method-type" : "requests"
2864- }, ["startup.id" , "startup.measurements.response_time" ]))
28652829
28662830 # Warmup
28672831 all_rules .append (mx_benchmark .JsonArrayStdOutFileRule (json_file_pattern , json_file_group_name , {
@@ -2920,21 +2884,66 @@ def parse(self, text) -> Iterable[DataPoint]:
29202884 }, [
29212885 f"resource_usage__rss__p{ float (percentile )} "
29222886 ], indexer_str = "__" ) for percentile in _baristaConfig ["rss_percentiles" ]]
2923- # Ensure we are reporting the analogous numbers across suites (p99 at the time of writing this comment)
2924- percentile_to_copy_into_max_rss = float (mx_benchmark .RssPercentilesTracker .MaxRssCopyRule .percentile_to_copy_into_max_rss )
2925- all_rules .append (mx_benchmark .JsonArrayStdOutFileRule (json_file_pattern , json_file_group_name , {
2926- "benchmark" : self .context .benchmark ,
2927- "metric.name" : "max-rss" ,
2928- "metric.type" : "numeric" ,
2929- "metric.unit" : "MB" ,
2930- "metric.value" : (f"<resource_usage__rss__p{ percentile_to_copy_into_max_rss } >" , float ),
2931- "metric.better" : "lower" ,
2932- }, [f"resource_usage__rss__p{ percentile_to_copy_into_max_rss } " ], indexer_str = "__" ))
29332887
29342888 return all_rules
29352889
29362890 def validateStdoutWithDimensions (self , out , benchmarks , bmSuiteArgs , retcode = None , dims = None , extraRules = None ) -> DataPoints :
29372891 datapoints = super ().validateStdoutWithDimensions (out , benchmarks , bmSuiteArgs , retcode = retcode , dims = dims , extraRules = extraRules )
2892+ datapoints = self .computeDerivedDatapoints (datapoints )
2893+ datapoints = self .extendDatapoints (datapoints )
2894+ return datapoints
2895+
2896+ def computeDerivedDatapoints (self , datapoints : DataPoints ) -> DataPoints :
2897+ """Adds derived datapoints to the list of datapoints captured from the benchmark stdout or generated files.
2898+ Adds datapoints such as:
2899+ * max-rss: copied from specific rss percentile values
2900+ * time-to-first-response: copied from response_time with iteration 0
2901+ * max-time: copied from response_time with the highest value
2902+ * ops-per-GB-second: computed as throughput divided by max-rss
2903+ """
2904+ # max-rss
2905+ percentile_to_copy_into_max_rss = float (mx_benchmark .RssPercentilesTracker .MaxRssCopyRule .percentile_to_copy_into_max_rss )
2906+ rss_dp_to_copy_from = next (filter (lambda dp : dp ["metric.name" ] == "rss" and dp ["metric.percentile" ] == percentile_to_copy_into_max_rss , datapoints ), None )
2907+ if rss_dp_to_copy_from is not None :
2908+ max_rss_dp = rss_dp_to_copy_from .copy ()
2909+ max_rss_dp ["metric.name" ] = "max-rss"
2910+ del max_rss_dp ["metric.percentile" ]
2911+ datapoints .append (max_rss_dp )
2912+
2913+ # time-to-first-response
2914+ first_request_time_dp = next (filter (lambda dp : dp ["metric.name" ] == "request-time" and dp ["metric.iteration" ] == 0 , datapoints ), None )
2915+ if first_request_time_dp is not None :
2916+ time_to_first_response_dp = first_request_time_dp .copy ()
2917+ time_to_first_response_dp ["metric.name" ] = "time-to-first-response"
2918+ del time_to_first_response_dp ["metric.iteration" ]
2919+ datapoints .append (time_to_first_response_dp )
2920+
2921+ # max-time
2922+ request_time_dps = filter (lambda dp : dp ["metric.name" ] == "request-time" , datapoints )
2923+ worst_request_time_dp = max (request_time_dps , key = lambda dp : dp ["metric.value" ], default = None )
2924+ if worst_request_time_dp is not None :
2925+ max_time_dp = worst_request_time_dp .copy ()
2926+ max_time_dp ["metric.name" ] = "max-time"
2927+ del max_time_dp ["metric.iteration" ]
2928+ datapoints .append (max_time_dp )
2929+
2930+ # ops-per-GB-second
2931+ throughput_dp = next (filter (lambda dp : dp ["metric.name" ] == "throughput" , datapoints ), None )
2932+ if rss_dp_to_copy_from is not None and throughput_dp is not None :
2933+ ops_per_gb_sec = throughput_dp ["metric.value" ] / (max_rss_dp ["metric.value" ] / 1024 )
2934+ ops_per_gb_sec_dp = throughput_dp .copy ()
2935+ ops_per_gb_sec_dp ["metric.name" ] = "ops-per-GB-second"
2936+ ops_per_gb_sec_dp ["metric.unit" ] = "op/GB*s"
2937+ ops_per_gb_sec_dp ["metric.value" ] = ops_per_gb_sec
2938+ datapoints .append (ops_per_gb_sec_dp )
2939+
2940+ return datapoints
2941+
2942+ def extendDatapoints (self , datapoints : DataPoints ) -> DataPoints :
2943+ """
2944+ Extends the datapoints with 'load-tester' fields.
2945+ Relies on the intermediate 'load-tester.command' field being set up beforehand.
2946+ """
29382947 for datapoint in datapoints :
29392948 # Expand the 'load-tester' field group
29402949 if "load-tester.command" in datapoint :
0 commit comments