Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add frame timing information #128

Closed
jdm opened this issue Feb 11, 2020 · 9 comments · Fixed by servo/servo#25810
Closed

Add frame timing information #128

jdm opened this issue Feb 11, 2020 · 9 comments · Fixed by servo/servo#25810

Comments

@jdm
Copy link
Member

jdm commented Feb 11, 2020

jdm@9e3ddc5 is an example of timing information that I added in a branch. I added a cargo feature in jdm@ab0a653 that avoided all of the precise_time_ns calls when profiling was not desired.

@jdm
Copy link
Member Author

jdm commented Feb 11, 2020

jdm/servo@14cd0e7 was the equivalent changes in Servo, along with jdm/servo@1de0d5d.

@jdm
Copy link
Member Author

jdm commented Feb 11, 2020

We'll want to add timing data in a way that will let us answer the following questions:

  • what is the most common frame time?
  • what is the slowest part of the frame pipeline?
  • when a frame takes longer than average, how are the various sub-times affected?
  • proportionally, how long is each part of the frame pipeline? is that consistent across frames?

@jdm
Copy link
Member Author

jdm commented Feb 11, 2020

We will also want the timing data to be possible to meaningfully compare between different runs, to easily determine if we're improving or not.

@jdm
Copy link
Member Author

jdm commented Feb 12, 2020

This is the postprocessing script I used for the commits I linked earlier. This script is good at answering questions about the most common times for part of the frame pipeline, but doesn't include overall frame duration and can't answer questions about the other parts of the frame pipeline when one of the parts takes longer than usual.

import sys

lines = []
buckets = {}
with open(sys.argv[1]) as f:
    for line in f.readlines():
        if not line.startswith("!!!"):
            continue
        parts = line.split(' ')
        value = float(parts[-1][:-3])
        name = ' '.join(parts[1:-1])
        if name not in buckets:
            buckets[name] = []
        buckets[name] += [value]

def mean(numbers):
    return float(sum(numbers)) / max(len(numbers), 1)

def histo(numbers):
    buckets = [1, 2, 4, 8, 16, 32, 32]
    bucket_values = [0, 0, 0, 0, 0, 0, 0]
    for number in numbers:
        for (i, max_val) in enumerate(buckets):
            if number < max_val:
                bucket_values[i] += 1
                break
        else:
            bucket_values[-1] += 1
    return zip(buckets, bucket_values)

buckets["raf receive"].pop(0) # First timing information compares against 0 and is invalid

print("Name\t\tmin\t\tmax\t\tmean")
for (name, values) in buckets.iteritems():
    print("%s\t%f\t%f\t%f" % (name, min(values), max(values), mean(values)))
    histogram = histo(values)
    for (i, (bucket, count)) in enumerate(histogram):
        print("%s%d%sms: %d" % ("<" if i != len(histogram) - 1 else "", bucket, "+" if i == len(histogram) - 1 else "", count))
    print

@jdm
Copy link
Member Author

jdm commented Feb 18, 2020

Another feature that would be helpful - ignoring timing information that falls outside of a particular window. This would help provide insight into why the FPS falls over time if the scene doesn't change.

@Manishearth
Copy link
Member

Manishearth commented Feb 21, 2020

import sys
import re
REGEX = re.compile("WEBXR PROFILING \[(.*)\]:\s*([0-9.]+)ms")
lines = []
buckets = {}
with open(sys.argv[1]) as f:
    for line in f.readlines():
        if not line.startswith("WEBXR PROFILING"):
            continue
        result = REGEX.match(line)
        name = result.group(1)
        value = float(result.group(2))
        if name not in buckets:
            buckets[name] = []
        buckets[name] += [value]

def mean(numbers):
    return float(sum(numbers)) / max(len(numbers), 1)

def histo(numbers):
    buckets = [1, 2, 4, 8, 16, 32, 32]
    bucket_values = [0, 0, 0, 0, 0, 0, 0]
    for number in numbers:
        for (i, max_val) in enumerate(buckets):
            if number < max_val:
                bucket_values[i] += 1
                break
        else:
            bucket_values[-1] += 1
    return zip(buckets, bucket_values)

buckets["raf receive"].pop(0) # First timing information compares against 0 and is invalid

print("Name\t\tmin\t\tmax\t\tmean")
for (name, values) in buckets.iteritems():
    print("%s\t%f\t%f\t%f" % (name, min(values), max(values), mean(values)))
    histogram = histo(values)
    for (i, (bucket, count)) in enumerate(histogram):
        print("%s%d%sms: %d" % ("<" if i != len(histogram) - 1 else "", bucket, "+" if i == len(histogram) - 1 else "", count))
    print

@Manishearth
Copy link
Member

Updated script, it can now generate a csv for graphing as well:

import sys
import re
REGEX = re.compile("WEBXR PROFILING \[(.*)\]:\s*([0-9.]+)ms")
lines = []
buckets = {}
with open(sys.argv[1]) as f:
    for line in f.readlines():
        if not line.startswith("WEBXR PROFILING"):
            continue
        result = REGEX.match(line)
        name = result.group(1)
        value = float(result.group(2))
        if name not in buckets:
            buckets[name] = []
        buckets[name] += [value]

def mean(numbers):
    return float(sum(numbers)) / max(len(numbers), 1)

def histo(numbers):
    buckets = [1, 2, 4, 8, 16, 32, 32]
    bucket_values = [0, 0, 0, 0, 0, 0, 0]
    for number in numbers:
        for (i, max_val) in enumerate(buckets):
            if number < max_val:
                bucket_values[i] += 1
                break
        else:
            bucket_values[-1] += 1
    return zip(buckets, bucket_values)

buckets["raf receive"].pop(0) # First timing information compares against 0 and is invalid

if sys.argv[2] == "csv":
    l = min(len(values) for (_, values) in buckets.iteritems())
    print(",".join([name for name in buckets]))
    for i in range(l):
        print(",".join([str(buckets[name][i]) for name in buckets]))

else:
    print("Name\t\tmin\t\tmax\t\tmean")
    for (name, values) in buckets.iteritems():
        print("%s\t%f\t%f\t%f" % (name, min(values), max(values), mean(values)))
        histogram = histo(values)
        for (i, (bucket, count)) in enumerate(histogram):
            print("%s%d%sms: %d" % ("<" if i != len(histogram) - 1 else "", bucket, "+" if i == len(histogram) - 1 else "", count))
        print

@Manishearth
Copy link
Member

Annoyingly the framerate is down to 30 when i'm doing these measurements, so I can't quite grab the situation where it started out fast and went down over a period of 20ish seconds

@jdm
Copy link
Member Author

jdm commented Feb 21, 2020

It would be worth checking if memory usage stays constant as the framerate drops, to rule out one possibility.

bors-servo pushed a commit that referenced this issue Feb 22, 2020
Add profiling feature

Hold off on landing for now.

I cleaned up the cfgs a bit, and also made the print output a bit better-looking (the scripts will need to be updated for this, which I'm working on.

part of #128
r? @jdm
bors-servo pushed a commit that referenced this issue Feb 22, 2020
Add profiling feature

Hold off on landing for now.

I cleaned up the cfgs a bit, and also made the print output a bit better-looking (the scripts will need to be updated for this, which I'm working on.

part of #128
r? @jdm
bors-servo pushed a commit to servo/servo that referenced this issue Feb 22, 2020
bors-servo pushed a commit to servo/servo that referenced this issue Feb 22, 2020
bors-servo pushed a commit to servo/servo that referenced this issue Feb 23, 2020
bors-servo pushed a commit to servo/servo that referenced this issue Feb 23, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants