forked from mirrors/gecko-dev
snprintf returns the number of bytes it would have written when it runs out of space. This patch makes sure we properly handle this unlikely event in FPSCounter. This patch also makes sure we don't print out the contents of an uninitialized buffer. --HG-- extra : rebase_source : 3b0efdc90aeb1ee17e10a75493af51f2878bfcda
375 lines
10 KiB
C++
375 lines
10 KiB
C++
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include <stddef.h> // for size_t
|
|
#include "Units.h" // for ScreenIntRect
|
|
#include "gfxRect.h" // for gfxRect
|
|
#include "gfxPrefs.h" // for gfxPrefs
|
|
#include "mozilla/gfx/Point.h" // for IntSize, Point
|
|
#include "mozilla/gfx/Rect.h" // for Rect
|
|
#include "mozilla/gfx/Types.h" // for Color, SurfaceFormat
|
|
#include "mozilla/layers/Compositor.h" // for Compositor
|
|
#include "mozilla/layers/CompositorTypes.h"
|
|
#include "mozilla/layers/Effects.h" // for Effect, EffectChain, etc
|
|
#include "mozilla/TimeStamp.h" // for TimeStamp, TimeDuration
|
|
#include "nsPoint.h" // for nsIntPoint
|
|
#include "nsRect.h" // for mozilla::gfx::IntRect
|
|
#include "nsIFile.h" // for nsIFile
|
|
#include "nsDirectoryServiceDefs.h" // for NS_OS_TMP_DIR
|
|
#include "mozilla/Sprintf.h"
|
|
#include "FPSCounter.h"
|
|
|
|
namespace mozilla {
|
|
namespace layers {
|
|
|
|
using namespace mozilla::gfx;
|
|
|
|
FPSCounter::FPSCounter(const char* aName)
|
|
: mWriteIndex(0)
|
|
, mIteratorIndex(-1)
|
|
, mFPSName(aName)
|
|
{
|
|
Init();
|
|
}
|
|
|
|
FPSCounter::~FPSCounter() { }
|
|
|
|
void
|
|
FPSCounter::Init()
|
|
{
|
|
for (int i = 0; i < kMaxFrames; i++) {
|
|
mFrameTimestamps.AppendElement(TimeStamp());
|
|
}
|
|
mLastInterval = TimeStamp::Now();
|
|
}
|
|
|
|
// Returns true if we captured a full interval of data
|
|
bool
|
|
FPSCounter::CapturedFullInterval(TimeStamp aTimestamp) {
|
|
TimeDuration duration = aTimestamp - mLastInterval;
|
|
return duration.ToSeconds() >= kFpsDumpInterval;
|
|
}
|
|
|
|
void
|
|
FPSCounter::AddFrame(TimeStamp aTimestamp) {
|
|
NS_ASSERTION(mWriteIndex < kMaxFrames, "We probably have a bug with the circular buffer");
|
|
NS_ASSERTION(mWriteIndex >= 0, "Circular Buffer index should never be negative");
|
|
|
|
int index = mWriteIndex++;
|
|
if (mWriteIndex == kMaxFrames) {
|
|
mWriteIndex = 0;
|
|
}
|
|
|
|
mFrameTimestamps[index] = aTimestamp;
|
|
|
|
if (CapturedFullInterval(aTimestamp)) {
|
|
PrintFPS();
|
|
WriteFrameTimeStamps();
|
|
mLastInterval = aTimestamp;
|
|
}
|
|
}
|
|
|
|
double
|
|
FPSCounter::AddFrameAndGetFps(TimeStamp aTimestamp) {
|
|
AddFrame(aTimestamp);
|
|
return GetFPS(aTimestamp);
|
|
}
|
|
|
|
int
|
|
FPSCounter::GetLatestReadIndex()
|
|
{
|
|
if (mWriteIndex == 0) {
|
|
return kMaxFrames - 1;
|
|
}
|
|
|
|
return mWriteIndex - 1;
|
|
}
|
|
|
|
TimeStamp
|
|
FPSCounter::GetLatestTimeStamp()
|
|
{
|
|
TimeStamp timestamp = mFrameTimestamps[GetLatestReadIndex()];
|
|
MOZ_ASSERT(!timestamp.IsNull(), "Cannot use null timestamps");
|
|
return timestamp;
|
|
}
|
|
|
|
// Returns true if we iterated over a full interval of data
|
|
bool
|
|
FPSCounter::IteratedFullInterval(TimeStamp aTimestamp, double aDuration) {
|
|
MOZ_ASSERT(mIteratorIndex >= 0, "Cannot be negative");
|
|
MOZ_ASSERT(mIteratorIndex < kMaxFrames, "Iterator index cannot be greater than kMaxFrames");
|
|
|
|
TimeStamp currentStamp = mFrameTimestamps[mIteratorIndex];
|
|
TimeDuration duration = aTimestamp - currentStamp;
|
|
return duration.ToSeconds() >= aDuration;
|
|
}
|
|
|
|
void
|
|
FPSCounter::ResetReverseIterator()
|
|
{
|
|
mIteratorIndex = GetLatestReadIndex();
|
|
}
|
|
|
|
/***
|
|
* Returns true if we have another timestamp that is valid and
|
|
* is within the given duration that we're interested in.
|
|
* Duration is in seconds
|
|
*/
|
|
bool FPSCounter::HasNext(TimeStamp aTimestamp, double aDuration)
|
|
{
|
|
// Order of evaluation here has to stay the same
|
|
// otherwise IteratedFullInterval reads from mFrameTimestamps which cannot
|
|
// be null
|
|
return (mIteratorIndex != mWriteIndex) // Didn't loop around the buffer
|
|
&& !mFrameTimestamps[mIteratorIndex].IsNull() // valid data
|
|
&& !IteratedFullInterval(aTimestamp, aDuration);
|
|
}
|
|
|
|
TimeStamp
|
|
FPSCounter::GetNextTimeStamp()
|
|
{
|
|
TimeStamp timestamp = mFrameTimestamps[mIteratorIndex--];
|
|
MOZ_ASSERT(!timestamp.IsNull(), "Reading Invalid Timestamp Data");
|
|
|
|
if (mIteratorIndex == -1) {
|
|
mIteratorIndex = kMaxFrames - 1;
|
|
}
|
|
return timestamp;
|
|
}
|
|
|
|
/**
|
|
* GetFPS calculates how many frames we've already composited from the current
|
|
* frame timestamp and we iterate from the latest timestamp we recorded,
|
|
* going back in time. When we hit a frame that is longer than the 1 second
|
|
* from the current composited frame, we return how many frames we've counted.
|
|
* Just a visualization:
|
|
*
|
|
* aTimestamp
|
|
* Frames: 1 2 3 4 5 6 7 8 9 10 11 12
|
|
* Time -------------------------->
|
|
*
|
|
* GetFPS iterates from aTimestamp, which is the current frame.
|
|
* Then starting at frame 12, going back to frame 11, 10, etc, we calculate
|
|
* the duration of the recorded frame timestamp from aTimestamp.
|
|
* Once duration is greater than 1 second, we return how many frames
|
|
* we composited.
|
|
*/
|
|
double
|
|
FPSCounter::GetFPS(TimeStamp aTimestamp)
|
|
{
|
|
int frameCount = 0;
|
|
int duration = 1.0; // Only care about the last 1s of data
|
|
|
|
ResetReverseIterator();
|
|
while (HasNext(aTimestamp, duration)) {
|
|
GetNextTimeStamp();
|
|
frameCount++;
|
|
}
|
|
|
|
return frameCount;
|
|
}
|
|
|
|
// Iterate the same way we do in GetFPS()
|
|
int
|
|
FPSCounter::BuildHistogram(std::map<int, int>& aFpsData)
|
|
{
|
|
TimeStamp currentIntervalStart = GetLatestTimeStamp();
|
|
TimeStamp currentTimeStamp = GetLatestTimeStamp();
|
|
TimeStamp startTimeStamp = GetLatestTimeStamp();
|
|
|
|
int frameCount = 0;
|
|
int totalFrameCount = 0;
|
|
|
|
ResetReverseIterator();
|
|
while (HasNext(startTimeStamp)) {
|
|
currentTimeStamp = GetNextTimeStamp();
|
|
TimeDuration interval = currentIntervalStart - currentTimeStamp;
|
|
|
|
if (interval.ToSeconds() >= 1.0 ) {
|
|
currentIntervalStart = currentTimeStamp;
|
|
aFpsData[frameCount]++;
|
|
frameCount = 0;
|
|
}
|
|
|
|
frameCount++;
|
|
totalFrameCount++;
|
|
}
|
|
|
|
TimeDuration totalTime = currentIntervalStart - currentTimeStamp;
|
|
printf_stderr("Discarded %d frames over %f ms in histogram for %s\n",
|
|
frameCount, totalTime.ToMilliseconds(), mFPSName);
|
|
return totalFrameCount;
|
|
}
|
|
|
|
// Iterate the same way we do in GetFPS()
|
|
void
|
|
FPSCounter::WriteFrameTimeStamps(PRFileDesc* fd)
|
|
{
|
|
const int bufferSize = 256;
|
|
char buffer[bufferSize];
|
|
int writtenCount = SprintfLiteral(buffer, "FPS Data for: %s\n", mFPSName);
|
|
MOZ_ASSERT(writtenCount < bufferSize);
|
|
if (writtenCount >= bufferSize) {
|
|
return;
|
|
}
|
|
PR_Write(fd, buffer, writtenCount);
|
|
|
|
ResetReverseIterator();
|
|
TimeStamp startTimeStamp = GetLatestTimeStamp();
|
|
|
|
MOZ_ASSERT(HasNext(startTimeStamp));
|
|
TimeStamp previousSample = GetNextTimeStamp();
|
|
|
|
MOZ_ASSERT(HasNext(startTimeStamp));
|
|
TimeStamp nextTimeStamp = GetNextTimeStamp();
|
|
|
|
while (HasNext(startTimeStamp)) {
|
|
TimeDuration duration = previousSample - nextTimeStamp;
|
|
writtenCount = SprintfLiteral(buffer, "%f,\n", duration.ToMilliseconds());
|
|
MOZ_ASSERT(writtenCount < bufferSize);
|
|
if (writtenCount >= bufferSize) {
|
|
continue;
|
|
}
|
|
PR_Write(fd, buffer, writtenCount);
|
|
|
|
previousSample = nextTimeStamp;
|
|
nextTimeStamp = GetNextTimeStamp();
|
|
}
|
|
}
|
|
|
|
double
|
|
FPSCounter::GetMean(std::map<int, int> aHistogram)
|
|
{
|
|
double average = 0.0;
|
|
double samples = 0.0;
|
|
|
|
for (std::map<int, int>::iterator iter = aHistogram.begin();
|
|
iter != aHistogram.end(); ++iter)
|
|
{
|
|
int fps = iter->first;
|
|
int count = iter->second;
|
|
|
|
average += fps * count;
|
|
samples += count;
|
|
}
|
|
|
|
return average / samples;
|
|
}
|
|
|
|
double
|
|
FPSCounter::GetStdDev(std::map<int, int> aHistogram)
|
|
{
|
|
double sumOfDifferences = 0;
|
|
double average = GetMean(aHistogram);
|
|
double samples = 0.0;
|
|
|
|
for (std::map<int, int>::iterator iter = aHistogram.begin();
|
|
iter != aHistogram.end(); ++iter)
|
|
{
|
|
int fps = iter->first;
|
|
int count = iter->second;
|
|
|
|
double diff = ((double) fps) - average;
|
|
diff *= diff;
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
sumOfDifferences += diff;
|
|
}
|
|
samples += count;
|
|
}
|
|
|
|
double stdDev = sumOfDifferences / samples;
|
|
return sqrt(stdDev);
|
|
}
|
|
|
|
void
|
|
FPSCounter::PrintFPS()
|
|
{
|
|
if (!gfxPrefs::FPSPrintHistogram()) {
|
|
return;
|
|
}
|
|
|
|
std::map<int, int> histogram;
|
|
int totalFrames = BuildHistogram(histogram);
|
|
|
|
TimeDuration measurementInterval = mFrameTimestamps[GetLatestReadIndex()] - mLastInterval;
|
|
printf_stderr("FPS for %s. Total Frames: %d Time Interval: %f seconds\n",
|
|
mFPSName, totalFrames, measurementInterval.ToSecondsSigDigits());
|
|
|
|
PrintHistogram(histogram);
|
|
}
|
|
|
|
void
|
|
FPSCounter::PrintHistogram(std::map<int, int>& aHistogram)
|
|
{
|
|
if (aHistogram.size() == 0) {
|
|
return;
|
|
}
|
|
|
|
int length = 0;
|
|
const int kBufferLength = 512;
|
|
int availableSpace = kBufferLength;
|
|
char buffer[kBufferLength];
|
|
|
|
for (std::map<int, int>::iterator iter = aHistogram.begin();
|
|
iter != aHistogram.end(); iter++)
|
|
{
|
|
int fps = iter->first;
|
|
int count = iter->second;
|
|
|
|
int lengthRequired = snprintf(buffer + length, availableSpace,
|
|
"FPS: %d = %d. ", fps, count);
|
|
// Ran out of buffer space. Oh well - just print what we have.
|
|
if (lengthRequired > availableSpace) {
|
|
break;
|
|
}
|
|
length += lengthRequired;
|
|
availableSpace -= lengthRequired;
|
|
}
|
|
|
|
printf_stderr("%s\n", buffer);
|
|
printf_stderr("Mean: %f , std dev %f\n", GetMean(aHistogram), GetStdDev(aHistogram));
|
|
}
|
|
|
|
// Write FPS timestamp data to a file only if
|
|
// draw-fps.write-to-file is true
|
|
nsresult
|
|
FPSCounter::WriteFrameTimeStamps()
|
|
{
|
|
if (!gfxPrefs::WriteFPSToFile()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_ASSERT(mWriteIndex == 0);
|
|
|
|
nsCOMPtr<nsIFile> resultFile;
|
|
nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(resultFile));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (!strncmp(mFPSName, "Compositor", strlen(mFPSName))) {
|
|
resultFile->Append(NS_LITERAL_STRING("fps.txt"));
|
|
} else {
|
|
resultFile->Append(NS_LITERAL_STRING("txn.txt"));
|
|
}
|
|
|
|
PRFileDesc* fd = nullptr;
|
|
int mode = 644;
|
|
int openFlags = PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE;
|
|
rv = resultFile->OpenNSPRFileDesc(openFlags, mode, &fd);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
WriteFrameTimeStamps(fd);
|
|
PR_Close(fd);
|
|
|
|
nsAutoCString path;
|
|
rv = resultFile->GetNativePath(path);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
printf_stderr("Wrote FPS data to file: %s\n", path.get());
|
|
return NS_OK;
|
|
}
|
|
|
|
} // end namespace layers
|
|
} // end namespace mozilla
|