blob: 2df1418b806bbccb0a09173066b9690df5fff5cc [file]
/*
* Copyright (C) 2016-2023 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 "ICStats.h"
#include <cstdlib>
#include <mutex>
#include <wtf/Hasher.h>
#include <wtf/TZoneMallocInlines.h>
namespace JSC {
WTF_MAKE_TZONE_ALLOCATED_IMPL(ICStats);
class ICHandlerChain {
public:
ICHandlerChain() = default;
ICHandlerChain(WTF::HashTableDeletedValueType)
: m_isDeleted(true)
{
}
void append(ICEvent::Kind kind) { m_chain.append(kind); }
bool NODELETE isEmpty() const { return m_chain.isEmpty(); }
void NODELETE clear()
{
m_chain.shrink(0);
m_totalNumberOfHandlersInChain = 0;
}
unsigned NODELETE size() const { return m_chain.size(); }
ICEvent::Kind operator[](unsigned i) const { return m_chain[i]; }
void NODELETE setTotalNumberOfHandlersInChain(unsigned length) { m_totalNumberOfHandlersInChain = length; }
unsigned NODELETE totalNumberOfHandlersInChain() const { return m_totalNumberOfHandlersInChain; }
bool operator==(const ICHandlerChain& other) const
{
return m_chain == other.m_chain && m_totalNumberOfHandlersInChain == other.m_totalNumberOfHandlersInChain && m_isDeleted == other.m_isDeleted;
}
bool operator>(const ICHandlerChain& other) const
{
for (unsigned i = 0; i < std::min(m_chain.size(), other.m_chain.size()); i++) {
if (m_chain[i] != other.m_chain[i])
return m_chain[i] > other.m_chain[i];
}
if (m_chain.size() != other.m_chain.size())
return m_chain.size() > other.m_chain.size();
return m_totalNumberOfHandlersInChain > other.m_totalNumberOfHandlersInChain;
}
unsigned hash() const
{
return pairIntHash(computeHash(m_chain), WTF::IntHash<unsigned>::hash(m_totalNumberOfHandlersInChain));
}
bool isHashTableDeletedValue() const { return m_isDeleted; }
static constexpr bool safeToCompareToHashTableEmptyOrDeletedValue = true;
private:
Vector<ICEvent::Kind> m_chain;
unsigned m_totalNumberOfHandlersInChain { 0 };
bool m_isDeleted { false };
};
} // namespace JSC
namespace WTF {
template<> struct HashTraits<JSC::ICHandlerChain> : SimpleClassHashTraits<JSC::ICHandlerChain> {
static constexpr bool emptyValueIsZero = true;
};
} // namespace WTF
namespace JSC {
static LazyNeverDestroyed<ICHandlerChain> s_currentChain;
static LazyNeverDestroyed<Spectrum<ICHandlerChain, uint64_t>> s_chainSpectrum;
bool ICEvent::operator<(const ICEvent& other) const
{
if (m_classInfo != other.m_classInfo) {
if (!m_classInfo)
return true;
if (!other.m_classInfo)
return false;
WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN
return strcmp(m_classInfo->className, other.m_classInfo->className) < 0;
WTF_ALLOW_UNSAFE_BUFFER_USAGE_END
}
if (m_kind != other.m_kind)
return m_kind < other.m_kind;
return m_propertyLocation < other.m_propertyLocation;
}
void ICEvent::dump(PrintStream& out) const
{
out.print(m_kind, "(", m_classInfo ? m_classInfo->className : "<null>", ")");
if (m_propertyLocation != Unknown)
out.print(m_propertyLocation == BaseObject ? " self" : " proto lookup");
}
void ICEvent::log() const
{
ICStats::singleton().add(*this);
}
Atomic<ICStats*> ICStats::s_instance;
ICStats::ICStats()
{
ASSERT(Options::useICStats());
s_currentChain.construct();
s_chainSpectrum.construct();
std::atexit([] {
ICStats& stats = singleton();
dataLog("ICStats at exit:\n");
Locker spectrumLocker { stats.m_spectrum.getLock() };
auto list = stats.m_spectrum.buildList(spectrumLocker);
uint64_t totalCount = 0;
for (auto& entry : list)
totalCount += entry.count;
for (unsigned i = list.size(); i--;) {
dataLog(" ", *list[i].key, ": ", list[i].count);
if (totalCount)
dataLogF(" (%.1f%%)", 100.0 * list[i].count / totalCount);
dataLog("\n");
}
stats.dumpChains();
});
m_thread = Thread::create(
"JSC ICStats"_s,
[this] () {
Locker locker { m_lock };
for (;;) {
m_condition.waitFor(
m_lock, Seconds(1), [this] () -> bool { return m_shouldStop; });
if (m_shouldStop)
break;
dataLog("ICStats:\n");
{
Locker spectrumLocker { m_spectrum.getLock() };
auto list = m_spectrum.buildList(spectrumLocker);
uint64_t totalCount = 0;
for (auto& entry : list)
totalCount += entry.count;
for (unsigned i = list.size(); i--;) {
dataLog(" ", *list[i].key, ": ", list[i].count);
if (totalCount)
dataLogF(" (%.1f%%)", 100.0 * list[i].count / totalCount);
dataLog("\n");
}
}
dumpChains();
}
});
}
ICStats::~ICStats()
{
{
Locker locker { m_lock };
m_shouldStop = true;
m_condition.notifyAll();
}
m_thread->waitForCompletion();
}
static bool NODELETE shouldRecord()
{
constexpr bool shouldMeasureOutsideSignpost = false;
if (shouldMeasureOutsideSignpost)
return true;
return activeJSGlobalObjectSignpostIntervalCount.load();
}
void ICStats::add(const ICEvent& event)
{
if (shouldRecord())
m_spectrum.add(event);
}
ICStats& ICStats::singleton()
{
static std::once_flag onceFlag;
std::call_once(onceFlag, [] {
s_instance.store(new ICStats());
});
return *s_instance.load();
}
void ICStats::startNewChain(unsigned newChainLength)
{
Locker locker { s_chainSpectrum->getLock() };
if (!s_currentChain->isEmpty() && shouldRecord())
s_chainSpectrum->add(locker, s_currentChain.get());
s_currentChain->clear();
s_currentChain->setTotalNumberOfHandlersInChain(newChainLength);
}
void ICStats::appendToCurrentChain(ICEvent::Kind kind)
{
if (shouldRecord())
s_currentChain->append(kind);
}
void ICStats::dumpChains()
{
startNewChain(0);
Locker locker { s_chainSpectrum->getLock() };
auto list = s_chainSpectrum->buildList(locker);
if (list.isEmpty())
return;
dataLog("IC Handler Chains:\n");
for (unsigned i = list.size(); i--;) {
auto& chain = *list[i].key;
dataLog(" ", list[i].count, "x [");
for (unsigned j = 0; j < chain.size(); j++) {
if (j)
dataLog(", ");
printInternal(WTF::dataFile(), chain[j]);
}
dataLog("] (chain length: ", chain.totalNumberOfHandlersInChain(), ")\n");
}
auto dumpHistogram = [&](const char* title, auto keyFn) {
UncheckedKeyHashMap<unsigned, uint64_t, DefaultHash<unsigned>, WTF::UnsignedWithZeroKeyHashTraits<unsigned>> histogram;
uint64_t total = 0;
for (unsigned i = 0; i < list.size(); i++) {
histogram.add(keyFn(i), 0).iterator->value += list[i].count;
total += list[i].count;
}
Vector<std::pair<unsigned, uint64_t>> entries;
for (auto& [len, count] : histogram)
entries.append({ len, count });
std::sort(entries.begin(), entries.end());
dataLog("\n", title, "\n");
for (auto& [len, count] : entries) {
dataLog(" ", len, ": ", count);
if (total)
dataLogF(" (%.1f%%)", 100.0 * count / total);
dataLog("\n");
}
};
dumpHistogram("IC Chain Length Histogram (total handlers in chain): ",
[&](unsigned i) { return list[i].key->totalNumberOfHandlersInChain(); });
dumpHistogram("IC Chain Length Histogram (executed handlers): ",
[&](unsigned i) { return list[i].key->size(); });
}
} // namespace JSC
namespace WTF {
using namespace JSC;
void printInternal(PrintStream& out, ICEvent::Kind kind)
{
switch (kind) {
#define ICEVENT_KIND_DUMP(name) case ICEvent::name: out.print(#name); return;
FOR_EACH_ICEVENT_KIND(ICEVENT_KIND_DUMP);
#undef ICEVENT_KIND_DUMP
}
RELEASE_ASSERT_NOT_REACHED();
}
} // namespace WTF