@@ -29,6 +29,7 @@ class FrameTimeResult(object):
2929 Represents frametimes from a single frame time layer log.
3030 '''
3131 NanosPerSecond = 10 ** 9
32+ NonosPerMilli = 10 ** 6
3233 TargetFPS = 45
3334 TargetFrameTime = NanosPerSecond / TargetFPS
3435
@@ -43,7 +44,13 @@ def __init__(self):
4344 self .state_to_duration_ms = {}
4445
4546 @staticmethod
46- def from_file (log_path , drop_first_seconds = None ):
47+ def from_file (log_path , gameplay_state = None , gameplay_duration = None , drop_first_seconds = None ):
48+ """
49+ Creates a FrameTimeResult from a file.
50+ - if |gameplay_state| is specified, all frame time statistics will include frames only in that state;
51+ - if |gameplay_duration| is specified, all frame times after the specified duration (in seconds) will be discarded;
52+ - if |drop_first_seconds| is specified, the specified number of initial benchmark seconds will be discarded.
53+ """
4754 full_path = path .realpath (log_path )
4855 base = path .basename (full_path )
4956 parent_dir = path .basename (path .dirname (full_path ))
@@ -59,11 +66,21 @@ def from_file(log_path, drop_first_seconds=None):
5966 if i == 0 :
6067 continue
6168 assert len (row ) == 2
62- result .raw_frametimes .append (int (row [0 ]))
63- result .frame_states .append (int (row [1 ]))
64- seen_states .add (int (row [1 ]))
6569
66- if drop_first_seconds is not None and drop_first_seconds > 0 :
70+ frametime_nanos = int (row [0 ])
71+ frame_state = int (row [1 ])
72+ seen_states .add (frame_state )
73+ if frame_state not in result .state_to_duration_ms :
74+ result .state_to_duration_ms [frame_state ] = 0
75+
76+ result .state_to_duration_ms [frame_state ] += frametime_nanos / result .NonosPerMilli
77+ if gameplay_state is not None and gameplay_state != frame_state :
78+ continue
79+
80+ result .raw_frametimes .append (frametime_nanos )
81+ result .frame_states .append (frame_state )
82+
83+ if drop_first_seconds is not None :
6784 drop_first_nanos = drop_first_seconds * result .NanosPerSecond
6885 curr_duration = 0
6986 drop_end = 0
@@ -77,18 +94,23 @@ def from_file(log_path, drop_first_seconds=None):
7794 result .raw_frametimes = result .raw_frametimes [drop_end :]
7895 result .frame_states = result .frame_states [drop_end :]
7996
80- nanos_per_millis = 10 ** 6
81- result .total_duration_ms = np .sum (result .raw_frametimes ) / nanos_per_millis
82- result .average_frametime_ms = np .average (result .raw_frametimes ) / nanos_per_millis
83- result .percentile_frametime_ms = [np .percentile (result .raw_frametimes , p ) / nanos_per_millis for p in range (100 )]
97+ if gameplay_duration is not None :
98+ target_duration_nanos = gameplay_duration * result .NanosPerSecond
99+ curr_duration = 0
100+ first_frame_to_discard = 0
101+ for frametime in result .raw_frametimes :
102+ curr_duration += frametime
103+ first_frame_to_discard += 1
104+ if curr_duration > target_duration_nanos :
105+ break
84106
85- for state in sorted (seen_states ):
86- nanos_in_state = 0
87- for ft_ns , frame_state in zip (result .raw_frametimes , result .frame_states ):
88- if frame_state == state :
89- nanos_in_state += ft_ns
107+ result .raw_frametimes = result .raw_frametimes [:first_frame_to_discard ]
108+ result .frame_states = result .frame_states [:first_frame_to_discard ]
90109
91- result .state_to_duration_ms [state ] = nanos_in_state / nanos_per_millis
110+ result .total_duration_ms = np .sum (result .raw_frametimes ) / result .NonosPerMilli
111+ result .average_frametime_ms = np .average (result .raw_frametimes ) / result .NonosPerMilli
112+ result .percentile_frametime_ms = \
113+ [np .percentile (result .raw_frametimes , p ) / result .NonosPerMilli for p in range (100 )]
92114
93115 return result
94116
@@ -206,13 +228,17 @@ def relative_noise(data):
206228def main ():
207229 parser = argparse .ArgumentParser (description = 'Process and analyze series of frame time logs.' )
208230 parser .add_argument ('--dataset' , type = str , nargs = '+' , action = 'append' , help = 'Dataset name followed by a list of log files: dataset_name log_file+' )
209- parser .add_argument ('-c' , '--cutoff' , type = int , default = None , help = 'Number of seconds of the initial data to ignore' )
231+ parser .add_argument ('--duration' , type = int , default = None , help = 'Number of seconds of gameplay data to analyze' )
232+ parser .add_argument ('--drop_front' , type = int , default = None , help = 'Number of seconds of the initial data to ignore' )
233+ parser .add_argument ('--gameplay_state' , type = int , default = None , help = 'The only state number to consider when calculating frame times statistics' )
210234 parser .add_argument ('--print_csv' , type = bool , default = False , help = 'Prints stats as comma separated values (CSV)' )
211235 parser .add_argument ('-v' , '--verbose' , type = bool , default = False , help = 'Output gif file name' )
212236 args = parser .parse_args ()
213237
214238 datasets = args .dataset
215- cutoff = args .cutoff
239+ gameplay_duration = args .duration
240+ drop_front = args .drop_front
241+ gameplay_state = args .gameplay_state
216242 use_csv = args .print_csv
217243 verbose = args .verbose
218244
@@ -221,7 +247,7 @@ def main():
221247 print (f'~~~~ Processing dataset { dataset_name } ~~~~' )
222248 results = []
223249 for file in dataset [1 :]:
224- results .append (FrameTimeResult .from_file (file , cutoff ))
250+ results .append (FrameTimeResult .from_file (file , gameplay_state , gameplay_duration , drop_front ))
225251 if verbose :
226252 results [- 1 ].dump ()
227253 print ()
0 commit comments