@@ -186,6 +186,25 @@ def ts_data(ts):
186
186
}
187
187
188
188
189
+ def determine_aggregation_function (function_name ):
190
+ """
191
+ Return the aggregation function associated to the provided function name, or None if
192
+ the function name is unsupported.
193
+
194
+ This is used by dropdown menus that allow selecting from multiple aggregation functions.
195
+ """
196
+ if function_name == 'min' :
197
+ return lnt .util .stats .safe_min
198
+ elif function_name == 'max' :
199
+ return lnt .util .stats .safe_max
200
+ elif function_name == 'mean' :
201
+ return lnt .util .stats .mean
202
+ elif function_name == 'median' :
203
+ return lnt .util .stats .median
204
+ else :
205
+ return None
206
+
207
+
189
208
@db_route ('/submitRun' , methods = ('GET' , 'POST' ))
190
209
def submit_run ():
191
210
"""Compatibility url that hardcodes testsuite to 'nts'"""
@@ -353,10 +372,10 @@ def __init__(self, run_id):
353
372
abort (404 , "Invalid run id {}" .format (run_id ))
354
373
355
374
# Get the aggregation function to use.
356
- aggregation_fn_name = request .args .get ('aggregation_fn ' )
357
- self . aggregation_fn = { 'min' : lnt . util . stats . safe_min ,
358
- 'median' : lnt . util . stats . median }. get (
359
- aggregation_fn_name , lnt . util . stats . safe_min )
375
+ fn_name = request .args .get ('aggregation_function' , 'min ' )
376
+ aggregation_fn = determine_aggregation_function ( fn_name )
377
+ if aggregation_fn is None :
378
+ abort ( 404 , "Invalid aggregation function name {}" . format ( fn_name ) )
360
379
361
380
# Get the MW confidence level.
362
381
try :
@@ -428,7 +447,7 @@ def __init__(self, run_id):
428
447
session , self .run , baseurl = db_url_for ('.index' , _external = False ),
429
448
result = None , compare_to = compare_to , baseline = baseline ,
430
449
num_comparison_runs = self .num_comparison_runs ,
431
- aggregation_fn = self . aggregation_fn , confidence_lv = confidence_lv ,
450
+ aggregation_fn = aggregation_fn , confidence_lv = confidence_lv ,
432
451
styles = styles , classes = classes )
433
452
self .sri = self .data ['sri' ]
434
453
note = self .data ['visible_note' ]
@@ -516,7 +535,7 @@ def v4_run(id):
516
535
else :
517
536
test_min_value_filter = 0.0
518
537
519
- options ['aggregation_fn ' ] = request .args .get ('aggregation_fn ' , 'min' )
538
+ options ['aggregation_function ' ] = request .args .get ('aggregation_function ' , 'min' )
520
539
521
540
# Get the test names.
522
541
test_info = session .query (ts .Test .name , ts .Test .id ).\
@@ -961,30 +980,13 @@ def v4_tableau():
961
980
962
981
@v4_route ("/graph" )
963
982
def v4_graph ():
964
-
965
983
session = request .session
966
984
ts = request .get_testsuite ()
967
- switch_min_mean_local = False
968
985
969
- if 'switch_min_mean_session' not in flask .session :
970
- flask .session ['switch_min_mean_session' ] = False
971
986
# Parse the view options.
972
- options = {'min_mean_checkbox' : 'min()' }
973
- if 'submit' in request .args : # user pressed a button
974
- if 'switch_min_mean' in request .args : # user checked mean() checkbox
975
- flask .session ['switch_min_mean_session' ] = \
976
- options ['switch_min_mean' ] = \
977
- bool (request .args .get ('switch_min_mean' ))
978
- switch_min_mean_local = flask .session ['switch_min_mean_session' ]
979
- else : # mean() check box is not checked
980
- flask .session ['switch_min_mean_session' ] = \
981
- options ['switch_min_mean' ] = \
982
- bool (request .args .get ('switch_min_mean' ))
983
- switch_min_mean_local = flask .session ['switch_min_mean_session' ]
984
- else : # new page was loaded by clicking link, not submit button
985
- options ['switch_min_mean' ] = switch_min_mean_local = \
986
- flask .session ['switch_min_mean_session' ]
987
-
987
+ options = {}
988
+ options ['aggregation_function' ] = \
989
+ request .args .get ('aggregation_function' ) # default determined later based on the field being graphed
988
990
options ['hide_lineplot' ] = bool (request .args .get ('hide_lineplot' ))
989
991
show_lineplot = not options ['hide_lineplot' ]
990
992
options ['show_mad' ] = show_mad = bool (request .args .get ('show_mad' ))
@@ -1198,15 +1200,18 @@ def trace_name(name, test_name, field_name):
1198
1200
1199
1201
is_multisample = (len (values ) > 1 )
1200
1202
1201
- aggregation_fn = min
1202
- if switch_min_mean_local :
1203
- aggregation_fn = lnt .util .stats .agg_mean
1204
- if field .bigger_is_better :
1205
- aggregation_fn = max
1203
+ fn_name = options .get ('aggregation_function' ) or ('max' if field .bigger_is_better else 'min' )
1204
+ aggregation_fn = determine_aggregation_function (fn_name )
1205
+ if aggregation_fn is None :
1206
+ abort (404 , "Invalid aggregation function name {}" .format (fn_name ))
1207
+ agg_value = aggregation_fn (values )
1208
+
1209
+ # When aggregating multiple samples, it becomes unclear which sample to use for
1210
+ # associated data like the run date, the order, etc. Use the index of the closest
1211
+ # value in all the samples.
1212
+ closest_value = sorted (values , key = lambda val : abs (val - agg_value ))[0 ]
1213
+ agg_index = values .index (closest_value )
1206
1214
1207
- agg_value , agg_index = \
1208
- aggregation_fn ((value , index )
1209
- for (index , value ) in enumerate (values ))
1210
1215
pts_y .append (agg_value )
1211
1216
1212
1217
# Plotly does not sort X axis in case of type: 'category'.
0 commit comments