/* 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 "CheckerboardEvent.h"
#include "mozilla/Logging.h"

#include <algorithm>  // for std::sort

static mozilla::LazyLogModule sApzCheckLog("apz.checkerboard");

namespace mozilla {
namespace layers {

// Relatively arbitrary limit to prevent a perma-checkerboard event from
// eating up gobs of memory. Ideally we shouldn't have perma-checkerboarding
// but better to guard against it.
#define LOG_LENGTH_LIMIT (50 * 1024)

const char* CheckerboardEvent::sDescriptions[] = {
    "page",
    "painted displayport",
    "requested displayport",
    "viewport",
};

const char* CheckerboardEvent::sColors[] = {
    "brown",
    "lightgreen",
    "yellow",
    "red",
};

CheckerboardEvent::CheckerboardEvent(bool aRecordTrace)
    : mRecordTrace(aRecordTrace),
      mOriginTime(TimeStamp::Now()),
      mCheckerboardingActive(false),
      mLastSampleTime(mOriginTime),
      mFrameCount(0),
      mTotalPixelMs(0),
      mPeakPixels(0),
      mRendertraceLock("Rendertrace") {}

uint32_t CheckerboardEvent::GetSeverity() {
  // Scale the total into a 32-bit value
  return (uint32_t)sqrt((double)mTotalPixelMs);
}

uint32_t CheckerboardEvent::GetPeak() { return mPeakPixels; }

TimeDuration CheckerboardEvent::GetDuration() { return mEndTime - mStartTime; }

std::string CheckerboardEvent::GetLog() {
  MonitorAutoLock lock(mRendertraceLock);
  return mRendertraceInfo.str();
}

bool CheckerboardEvent::IsRecordingTrace() { return mRecordTrace; }

void CheckerboardEvent::UpdateRendertraceProperty(
    RendertraceProperty aProperty, const CSSRect& aRect,
    const std::string& aExtraInfo) {
  if (!mRecordTrace) {
    return;
  }
  MonitorAutoLock lock(mRendertraceLock);
  if (!mCheckerboardingActive) {
    mBufferedProperties[aProperty].Update(aProperty, aRect, aExtraInfo, lock);
  } else {
    LogInfo(aProperty, TimeStamp::Now(), aRect, aExtraInfo, lock);
  }
}

void CheckerboardEvent::LogInfo(RendertraceProperty aProperty,
                                const TimeStamp& aTimestamp,
                                const CSSRect& aRect,
                                const std::string& aExtraInfo,
                                const MonitorAutoLock& aProofOfLock) {
  MOZ_ASSERT(mRecordTrace);
  if (mRendertraceInfo.tellp() >= LOG_LENGTH_LIMIT) {
    // The log is already long enough, don't put more things into it. We'll
    // append a truncation message when this event ends.
    return;
  }
  // The log is consumed by the page at about:checkerboard. The format is not
  // formally specced, but an informal description can be found at
  // https://searchfox.org/mozilla-central/rev/d866b96d74ec2a63f09ee418f048d23f4fd379a2/toolkit/components/aboutcheckerboard/content/aboutCheckerboard.js#86
  mRendertraceInfo << "RENDERTRACE "
                   << (aTimestamp - mOriginTime).ToMilliseconds() << " rect "
                   << sColors[aProperty] << " " << aRect.X() << " " << aRect.Y()
                   << " " << aRect.Width() << " " << aRect.Height() << " "
                   << "// " << sDescriptions[aProperty] << aExtraInfo << '\n';
}

bool CheckerboardEvent::RecordFrameInfo(uint32_t aCssPixelsCheckerboarded) {
  TimeStamp sampleTime = TimeStamp::Now();
  bool eventEnding = false;
  if (aCssPixelsCheckerboarded > 0) {
    if (!mCheckerboardingActive) {
      StartEvent();
    }
    MOZ_ASSERT(mCheckerboardingActive);
    MOZ_ASSERT(sampleTime >= mLastSampleTime);
    mTotalPixelMs +=
        (uint64_t)((sampleTime - mLastSampleTime).ToMilliseconds() *
                   aCssPixelsCheckerboarded);
    if (aCssPixelsCheckerboarded > mPeakPixels) {
      mPeakPixels = aCssPixelsCheckerboarded;
    }
    mFrameCount++;
  } else {
    if (mCheckerboardingActive) {
      StopEvent();
      eventEnding = true;
    }
    MOZ_ASSERT(!mCheckerboardingActive);
  }
  mLastSampleTime = sampleTime;
  return eventEnding;
}

void CheckerboardEvent::StartEvent() {
  MOZ_LOG(sApzCheckLog, LogLevel::Debug, ("Starting checkerboard event"));
  MOZ_ASSERT(!mCheckerboardingActive);
  mCheckerboardingActive = true;
  mStartTime = TimeStamp::Now();

  if (!mRecordTrace) {
    return;
  }
  MonitorAutoLock lock(mRendertraceLock);
  std::vector<PropertyValue> history;
  for (PropertyBuffer& bufferedProperty : mBufferedProperties) {
    bufferedProperty.Flush(history, lock);
  }
  std::sort(history.begin(), history.end());
  for (const PropertyValue& p : history) {
    LogInfo(p.mProperty, p.mTimeStamp, p.mRect, p.mExtraInfo, lock);
  }
  mRendertraceInfo << " -- checkerboarding starts below --\n";
}

void CheckerboardEvent::StopEvent() {
  MOZ_LOG(sApzCheckLog, LogLevel::Debug, ("Stopping checkerboard event"));
  mCheckerboardingActive = false;
  mEndTime = TimeStamp::Now();

  if (!mRecordTrace) {
    return;
  }
  MonitorAutoLock lock(mRendertraceLock);
  if (mRendertraceInfo.tellp() >= LOG_LENGTH_LIMIT) {
    mRendertraceInfo << "[logging aborted due to length limitations]\n";
  }
  mRendertraceInfo << "Checkerboarded for " << mFrameCount << " frames ("
                   << (mEndTime - mStartTime).ToMilliseconds() << " ms), "
                   << mPeakPixels << " peak, " << GetSeverity() << " severity."
                   << '\n';
}

bool CheckerboardEvent::PropertyValue::operator<(
    const PropertyValue& aOther) const {
  if (mTimeStamp < aOther.mTimeStamp) {
    return true;
  } else if (mTimeStamp > aOther.mTimeStamp) {
    return false;
  }
  return mProperty < aOther.mProperty;
}

CheckerboardEvent::PropertyBuffer::PropertyBuffer() : mIndex(0) {}

void CheckerboardEvent::PropertyBuffer::Update(
    RendertraceProperty aProperty, const CSSRect& aRect,
    const std::string& aExtraInfo, const MonitorAutoLock& aProofOfLock) {
  mValues[mIndex] = {aProperty, TimeStamp::Now(), aRect, aExtraInfo};
  mIndex = (mIndex + 1) % BUFFER_SIZE;
}

void CheckerboardEvent::PropertyBuffer::Flush(
    std::vector<PropertyValue>& aOut, const MonitorAutoLock& aProofOfLock) {
  for (uint32_t i = 0; i < BUFFER_SIZE; i++) {
    uint32_t ix = (mIndex + i) % BUFFER_SIZE;
    if (!mValues[ix].mTimeStamp.IsNull()) {
      aOut.push_back(mValues[ix]);
      mValues[ix].mTimeStamp = TimeStamp();
    }
  }
}

}  // namespace layers
}  // namespace mozilla
