fune/gfx/layers/composite/FPSCounter.cpp
David Keeler 9df1db2e87 Bug 1369560 - Avoid potentially unsafe snprintf usage in FPSCounter. r=mchang
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
2017-06-02 14:39:15 -07:00

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