/*
 * Copyright (C) 2012-2019 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */

#include "config.h"
#include <wtf/StackStats.h>

#if ENABLE(STACK_STATS) 

#include <wtf/Assertions.h>
#include <wtf/DataLog.h>

// Define the following flag if you want to collect stats on every single
// checkpoint. By default, we only log checkpoints that establish new
// max values.

#define ENABLE_VERBOSE_STACK_STATS 1


namespace WTF {

// CheckPoint management:
Lock StackStats::s_sharedMutex;
StackStats::CheckPoint* StackStats::s_topCheckPoint = 0;
StackStats::LayoutCheckPoint* StackStats::s_firstLayoutCheckPoint = 0;
StackStats::LayoutCheckPoint* StackStats::s_topLayoutCheckPoint = 0;

// High watermark stats:
int StackStats::s_maxCheckPointDiff = 0;
int StackStats::s_maxStackHeight = 0;
int StackStats::s_maxReentryDepth = 0;

int StackStats::s_maxLayoutCheckPointDiff = 0;
int StackStats::s_maxTotalLayoutCheckPointDiff = 0;
int StackStats::s_maxLayoutReentryDepth = 0;


StackStats::PerThreadStats::PerThreadStats()
{
    const StackBounds& stack = Thread::currentSingleton().stack();
    m_reentryDepth = 0;
    m_stackStart = (char*)stack.origin();
    m_currentCheckPoint = 0;

    dataLogF(" === THREAD new stackStart %p ========\n", m_stackStart);
}

StackStats::CheckPoint::CheckPoint()
{
    Locker locker { StackStats::s_sharedMutex };
    auto& thread = Thread::currentSingleton();
    StackStats::PerThreadStats& t = thread.stackStats();
    const StackBounds& stack = thread.stack();

    bool needToLog = false;
    char* current = reinterpret_cast<char*>(this);
    char* last = reinterpret_cast<char*>(t.m_currentCheckPoint);

    // If there was no previous checkpoint, measure from the start of the stack:
    if (!last)
        last = t.m_stackStart;

    // Update the reentry depth stats:
    t.m_reentryDepth++;
    if (t.m_reentryDepth > StackStats::s_maxReentryDepth) {
        StackStats::s_maxReentryDepth = t.m_reentryDepth;
        needToLog = true;
    }

    // Update the stack height stats:
    int height = t.m_stackStart - current;
    if (height > StackStats::s_maxStackHeight) {
        StackStats::s_maxStackHeight = height;
        needToLog = true;
    }

    // Update the checkpoint diff stats:
    int diff = last - current;
    if (diff > StackStats::s_maxCheckPointDiff) {
        StackStats::s_maxCheckPointDiff = diff;
        needToLog = true;
    }

    // Push this checkpoint:
    m_prev = t.m_currentCheckPoint;
    t.m_currentCheckPoint = this;

#if ENABLE(VERBOSE_STACK_STATS)
    needToLog = true; // always log.
#endif

    // Log this checkpoint if needed:
    if (needToLog)
        dataLogF(" CHECKPOINT %p diff %d/%.1fk/max %.1fk | reentry %d/max %d | height %.1fk/max %.1fk | stack %p size %.1fk\n",
            this, diff, diff / 1024.0, StackStats::s_maxCheckPointDiff / 1024.0,
            t.m_reentryDepth, StackStats::s_maxReentryDepth,
            height / 1024.0, StackStats::s_maxStackHeight / 1024.0,
            stack.origin(), stack.size() / 1024.0);
}

StackStats::CheckPoint::~CheckPoint()
{
    Locker locker { StackStats::s_sharedMutex };
    auto& thread = Thread::currentSingleton();
    StackStats::PerThreadStats& t = thread.stackStats();

    // Pop to previous checkpoint:
    t.m_currentCheckPoint = m_prev;
    --t.m_reentryDepth;

    // Log this checkpoint if needed:
#if ENABLE(VERBOSE_STACK_STATS)
    if (!m_prev) {
        const StackBounds& stack = thread.stack();

        char* current = reinterpret_cast<char*>(this);
        int height = t.m_stackStart - current;

        dataLogF(" POP to %p diff max %.1fk | reentry %d/%d max | height %.1fk/max %.1fk | stack %p size %.1fk)\n",
            this, StackStats::s_maxCheckPointDiff / 1024.0,
            t.m_reentryDepth, StackStats::s_maxReentryDepth,
            height / 1024.0, StackStats::s_maxStackHeight / 1024.0,
            stack.origin(), stack.size() / 1024.0);
    }
#endif
}

void StackStats::probe()
{
    Locker locker { StackStats::s_sharedMutex };
    auto& thread = Thread::currentSingleton();
    StackStats::PerThreadStats& t = thread.stackStats();
    const StackBounds& stack = thread.stack();

    bool needToLog = false;

    int dummy;
    char* current = reinterpret_cast<char*>(&dummy);
    char* last = reinterpret_cast<char*>(t.m_currentCheckPoint);

    // If there was no previous checkpoint, measure from the start of the stack:
    if (!last)
        last = t.m_stackStart;

    // We did not reach another checkpoint yet. Hence, we do not touch the
    // reentry stats.

    // Update the stack height stats:
    int height = t.m_stackStart - current;
    if (height > StackStats::s_maxStackHeight) {
        StackStats::s_maxStackHeight = height;
        needToLog = true;
    }

    // Update the checkpoint diff stats:
    int diff = last - current;
    if (diff > StackStats::s_maxCheckPointDiff) {
        StackStats::s_maxCheckPointDiff = diff;
        needToLog = true;
    }

#if ENABLE(VERBOSE_STACK_STATS)
    needToLog = true; // always log.
#endif

    if (needToLog)
        dataLogF(" PROBE %p diff %d/%.1fk/max %.1fk | reentry %d/max %d | height %.1fk/max %.1fk | stack %p size %.1fk\n",
            current, diff, diff / 1024.0, StackStats::s_maxCheckPointDiff / 1024.0,
            t.m_reentryDepth, StackStats::s_maxReentryDepth,
            height / 1024.0, StackStats::s_maxStackHeight / 1024.0,
            stack.origin(), stack.size() / 1024.0);
}

StackStats::LayoutCheckPoint::LayoutCheckPoint()
{
    // While a layout checkpoint is not necessarily a checkpoint where we
    // we will do a recursion check, it is a convenient spot for doing a
    // probe to measure the height of stack usage.
    //
    // We'll do this probe before we commence with the layout checkpoint.
    // This is because the probe also locks the sharedLock. By calling the
    // probe first, we can avoid re-entering the lock.
    StackStats::probe();

    Locker locker { StackStats::s_sharedMutex };
    auto& thread = Thread::currentSingleton();
    StackStats::PerThreadStats& t = thread.stackStats();
    const StackBounds& stack = thread.stack();

    // Push this checkpoint:
    m_prev = StackStats::s_topLayoutCheckPoint;
    if (m_prev)
        m_depth = m_prev->m_depth + 1;
    else {
        StackStats::s_firstLayoutCheckPoint = this;
        m_depth = 0;
    }
    StackStats::s_topLayoutCheckPoint = this;

    // 
    char* current = reinterpret_cast<char*>(this);
    char* last = reinterpret_cast<char*>(m_prev);
    char* root = reinterpret_cast<char*>(StackStats::s_firstLayoutCheckPoint);
    bool needToLog = false;

    int diff = last - current;
    if (!last)
        diff = 0;
    int totalDiff = root - current;
    if (!root)
        totalDiff = 0;

    // Update the stack height stats:
    int height = t.m_stackStart - current;
    if (height > StackStats::s_maxStackHeight) {
        StackStats::s_maxStackHeight = height;
        needToLog = true;
    }

    // Update the layout checkpoint diff stats:
    if (diff > StackStats::s_maxLayoutCheckPointDiff) {
        StackStats::s_maxLayoutCheckPointDiff = diff;
        needToLog = true;
    }

    // Update the total layout checkpoint diff stats:
    if (totalDiff > StackStats::s_maxTotalLayoutCheckPointDiff) {
        StackStats::s_maxTotalLayoutCheckPointDiff = totalDiff;
        needToLog = true;
    }

#if ENABLE(VERBOSE_STACK_STATS)
    needToLog = true; // always log.
#endif

    if (needToLog)
        dataLogF(" LAYOUT %p diff %d/%.1fk/max %.1fk | reentry %d/max %d | height %.1fk/max %.1fk | stack %p size %.1fk\n",
            current, diff, diff / 1024.0, StackStats::s_maxLayoutCheckPointDiff / 1024.0,
            m_depth, StackStats::s_maxLayoutReentryDepth,
            totalDiff / 1024.0, StackStats::s_maxTotalLayoutCheckPointDiff / 1024.0,
            stack.origin(), stack.size() / 1024.0);
}

StackStats::LayoutCheckPoint::~LayoutCheckPoint()
{
    Locker locker { StackStats::s_sharedMutex };

    // Pop to the previous layout checkpoint:
    StackStats::s_topLayoutCheckPoint = m_prev;
    if (!m_depth)
        StackStats::s_firstLayoutCheckPoint = 0;
}

} // namespace WTF

#endif // ENABLE(STACK_STATS)

