--- /dev/null 2016-10-25 08:46:44.038854975 +0200 +++ new/src/share/vm/evtrace/traceStack.cpp 2016-10-25 10:40:15.706781847 +0200 @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2014, 2015, Dynatrace and/or its affiliates. All rights reserved. + * + * This file is part of the Lock Contention Tracing Subsystem for the HotSpot + * Virtual Machine, which is developed at Christian Doppler Laboratory on + * Monitoring and Evolution of Very-Large-Scale Software Systems. Please + * contact us at if you need additional information + * or have any questions. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work. If not, see . + * + */ + +#include "evtrace/traceStack.hpp" + +#include "evtrace/traceEvents.hpp" +#include "evtrace/traceMetadata.hpp" + +#include "runtime/vframe.hpp" +#include "runtime/init.hpp" + +// +// TraceStackBuilder +// + +void TraceStackBuilder::add(const TraceStackFrame &f) { + assert(!is_full(), "already full"); + _frames[_count] = f; + _hash ^= f.hash(); + _count++; +} + +void TraceStackBuilder::add_frame(const frame *fr) { + TraceStackFrame f; + f.is_compiled = fr->cb()->is_nmethod(); + if (f.is_compiled) { + assert(fr->is_compiled_frame() || fr->is_native_frame(), "unsupported frame type"); + f.compiled.pc = fr->pc(); + f.compiled.nm = (nmethod *) fr->cb(); + } else { + assert(fr->is_interpreted_frame(), "unsupported frame type"); + f.interpreted.method = fr->interpreter_frame_method(); + f.interpreted.bci = fr->interpreter_frame_bci(); + } + add(f); +} + +bool TraceStackBuilder::range_equals(size_t offset, const CachedTraceStack *cts, size_t cts_offset, size_t count) const { + for (size_t i = 0; i < count; i++) { + if (!frame_at(offset + i)->equals(*cts->frame_at(cts_offset + i))) { + return false; + } + } + return true; +} + +// +// CachedTraceStack +// + +CachedTraceStack *CachedTraceStack::create(TraceTypes::stack_id id, const CompositeTraceStack &ts) { + assert (in_bytes(byte_offset_of(CachedTraceStack, _frames)) == sizeof(CachedTraceStack), + "variable-sized frame array must be last field"); + return new (ts.count()) CachedTraceStack(id, ts); +} + +void* CachedTraceStack::operator new(size_t size, size_t nframes) throw () { + return CHeapObj::operator new(size + sizeof(_frames[0]) * nframes, CALLER_PC); +} + +void CachedTraceStack::operator delete(void* p) { + CHeapObj::operator delete(p); +} + +CachedTraceStack::CachedTraceStack(TraceTypes::stack_id id, const CompositeTraceStack& ts) + : _id(id), + _count(ts.count()), + _hash(ts.hash()), + _truncated(ts.is_truncated()), + _valid(true) +{ + for (size_t i = 0; i < ts.count(); i++) { + _frames[i] = *ts.frame_at(i); + } +} + +bool CachedTraceStack::has_interpreted_method_from_classloader(const ClassLoaderData* loader) const { + assert(is_valid(), "sanity"); + for (size_t i = 0; i < _count; i++) { + if (!_frames[i].is_compiled && _frames[i].interpreted.method->method_holder()->class_loader_data() == loader) { + return true; + } + } + return false; +} + +bool CachedTraceStack::has_nmethod(const nmethod *nm) const { + assert(is_valid(), "sanity"); + for (size_t i = 0; i < _count; i++) { + if (_frames[i].is_compiled && _frames[i].compiled.nm == nm) { + return true; + } + } + return false; +} + +bool CachedTraceStack::range_equals(size_t offset, const CachedTraceStack *other, size_t other_offset, size_t count) const { + for (size_t i = 0; i < count; i++) { + if (!frame_at(offset + i)->equals(*other->frame_at(other_offset + i))) { + return false; + } + } + return true; +} + +// +// CompositeTraceStack +// + +void CompositeTraceStack::set_bottom(const CachedTraceStack *bottom, size_t offset) { + _hash = _top.hash(); + _count = _top.count(); + _truncated = _top.is_truncated(); + + _bottom = bottom; + _bottom_offset = offset; + if (bottom != NULL) { + if (_top.count() < EventTracingStackDepthLimit) { + assert(!_top.is_truncated(), "missing frames between top and bottom"); + _count += bottom->count() - offset; + _truncated = _truncated || bottom->is_truncated(); + if (_count > EventTracingStackDepthLimit) { + _truncated = true; + _count = EventTracingStackDepthLimit; + } + _hash ^= bottom->hash(); + // drop frames before offset from hash + for (size_t i = 0; i < offset; i++) { + _hash ^= bottom->frame_at(i)->hash(); + } + // drop truncated frames from hash + for (size_t i = EventTracingStackDepthLimit - _top.count() + offset; i < _bottom->count(); i++) { + _hash ^= bottom->frame_at(i)->hash(); + } + } else { + _truncated = _truncated || (bottom->count() > offset); + } + } + +#ifdef ASSERT + intptr_t h = 0; + for (size_t i = 0; i < count(); i++) { + h ^= frame_at(i)->hash(); + } + assert(h == hash(), "hash mismatch"); +#endif +} + +bool CompositeTraceStack::equals(const CachedTraceStack* cts) const { + if (hash() != cts->hash() || count() != cts->count() || is_truncated() != cts->is_truncated()) { + return false; + } + return _top.range_equals(0, cts, 0, _top.count()) + && (_bottom == NULL || _bottom->range_equals(_bottom_offset, cts, _top.count(), count() - _top.count())); +} + +bool CompositeTraceStack::equals(const CompositeTraceStack &other) const { + if (hash() != other.hash() || count() != other.count() || is_truncated() != other.is_truncated()) { + return false; + } + for (size_t i = 0; i < count(); i++) { + if (!frame_at(i)->equals(*other.frame_at(i))) { + return false; + } + } + return true; +} + +// +// TraceStackCache +// + +TraceStackCache::TraceStackCache(TraceMetadata *tm) +{ + _metadata = tm; + + _table = NULL; + _count = 0; + _has_invalid_stacks = false; + + _lookup_counter = _lookup_miss_counter = _lookup_collision_counter = _probe_counter = _probe_collision_counter = 0; + + _size = (1 << 12); + assert(is_power_of_2(_size), "sanity"); + _table = (CachedTraceStack **) os::malloc(_size * sizeof(_table[0]), mtEventTracing, CURRENT_PC); + memset(_table, 0, _size * sizeof(_table[0])); +} + +TraceStackCache::~TraceStackCache() { + for (size_t i = 0; i < _size; i++) { + CachedTraceStack *p = _table[i]; + while (p != NULL) { + CachedTraceStack *next = p->cache_next(); + delete p; + p = next; + } + } + os::free(_table, mtEventTracing); +} + +void TraceStackCache::add_for_rehash(CachedTraceStack *cts) { + const size_t mask = (_size - 1); + intptr_t index = cts->hash() & mask; + cts->set_cache_next(_table[index]); + _table[index] = cts; + _count++; +} + +inline void TraceStackCache::update_counters_after_lookup(bool present, jlong probes, jlong collisions) { + if (EnableEventTracingDiagnostics) { + Atomic::inc_ptr(&_lookup_counter); + Atomic::add_ptr(probes, &_probe_counter); + if (!present) { + Atomic::inc_ptr(&_lookup_miss_counter); + } + if (collisions > 0) { + Atomic::inc_ptr(&_lookup_collision_counter); + Atomic::add_ptr(collisions, &_probe_collision_counter); + } + } +} + +const CachedTraceStack * TraceStackCache::get_or_try_add(const CompositeTraceStack &ts, bool &known, TraceTypes::stack_id preallocated_id) { + jlong probes = 0, collisions = 0; + + CachedTraceStack *created = NULL; + + const size_t mask = (_size - 1); + const size_t index = ts.hash() & mask; + // XXX: probably need barriers here on non-x86 + for(;;) { + CachedTraceStack *head = _table[index]; + if (head == NULL) { + probes++; + } + CachedTraceStack *p = head; + while (p != NULL) { + probes++; + if (ts.hash() == p->hash()) { + if (ts.equals(p)) { + delete created; + known = true; + update_counters_after_lookup(true, probes, collisions); + return p; + } else { + collisions++; + } + } + p = p->cache_next(); + } + // not found + if (created == NULL) { + TraceTypes::stack_id id = preallocated_id; + if (id == 0) { + id = _metadata->next_stack_id(); + } + created = CachedTraceStack::create(id, ts); + } + created->set_cache_next(head); + if (Atomic::cmpxchg_ptr(created, &_table[index], head) == head) { + Atomic::inc_ptr(&_count); + known = false; + update_counters_after_lookup(false, probes, collisions); + return created; + } + // head of collision chain changed: walk the entire chain again in the next + // next iteration to check whether the stack trace has been inserted by + // another thread (head is not enough, multiple threads may have inserted) + } +} + +class TraceStackCache::CachedTraceStackPredicate { +public: + virtual bool test(CachedTraceStack *cts) = 0; +}; + +inline void TraceStackCache::purge_matches(TraceStackCache::CachedTraceStackPredicate *pr) { + if (EnableEventTracingDiagnostics) { + _purge_timer.start(); + } + + for (size_t i = 0; i < _size; i++) { + CachedTraceStack *p = _table[i]; + while (p != NULL) { + if (p->is_valid() && pr->test(p)) { + p->invalidate(); + _has_invalid_stacks = true; + } + p = p->cache_next(); + } + } + + if (EnableEventTracingDiagnostics) { + _purge_timer.stop(); + } +} + +class TraceStackCache::UnloadingClassPredicate : public TraceStackCache::CachedTraceStackPredicate { + const ClassLoaderData *_loader; +public: + UnloadingClassPredicate(const ClassLoaderData *loader) : _loader(loader) { } + virtual bool test(CachedTraceStack *stack) { + return stack->has_interpreted_method_from_classloader(_loader); + } +}; + +void TraceStackCache::purge_unloading_classes(const ClassLoaderData* loader) { + assert(SafepointSynchronize::is_at_safepoint() && Thread::current()->is_VM_thread(), "must be done in VM thread at safepoint"); + + // NOTE: only purges stack traces with *interpreted* frames that refer to + // unloading classes. nmethods with code from unloaded classes are unloaded + // in a later step, at which point we also unload the stack traces that + // contain those nmethods. + UnloadingClassPredicate pr(loader); + purge_matches(&pr); +} + +class TraceStackCache::UnloadingNmethodPredicate : public TraceStackCache::CachedTraceStackPredicate { + const nmethod *_nmethod; +public: + UnloadingNmethodPredicate(const nmethod *nm) : _nmethod(nm) { } + virtual bool test(CachedTraceStack *stack) { + return stack->has_nmethod(_nmethod); + } +}; + +void TraceStackCache::purge_unloading_nmethod(const nmethod *nm) { + UnloadingNmethodPredicate pr(nm); + purge_matches(&pr); +} + +class TraceStackCache::AnyPredicate : public TraceStackCache::CachedTraceStackPredicate { +public: + virtual bool test(CachedTraceStack *stack) { + return true; + } +}; + +void TraceStackCache::purge_all() { + AnyPredicate pr; + purge_matches(&pr); +} + +void TraceStackCache::do_maintenance() { + assert(SafepointSynchronize::is_at_safepoint() && Thread::current()->is_VM_thread(), "must be done in VM thread at safepoint"); + + bool should_grow = (_count / (float) _size > 0.7f); + if (should_grow || has_invalid_stacks()) { + if (EnableEventTracingDiagnostics) { + _maintenance_timer.start(); + } + + CachedTraceStack **old_table = _table; + size_t old_capacity = _size; + + if (should_grow) { + _size <<= 1; + assert(is_power_of_2(_size), "sanity"); + } + _count = 0; + _table = (CachedTraceStack **) os::malloc(_size * sizeof(_table[0]), mtEventTracing, CURRENT_PC); + memset(_table, 0, _size * sizeof(_table[0])); + for (size_t i = 0; i < old_capacity; i++) { + CachedTraceStack *p = old_table[i]; + while (p != NULL) { + CachedTraceStack *next = p->cache_next(); + if (p->is_valid()) { + add_for_rehash(p); + } else { + delete p; + } + p = next; + } + } + os::free(old_table, mtEventTracing); + + if (EnableEventTracingDiagnostics) { + _maintenance_timer.stop(); + } + + _has_invalid_stacks = false; + } +} + +void TraceStackCache::reset_stats() { + _lookup_counter = _lookup_miss_counter = _lookup_collision_counter = _probe_counter = _probe_collision_counter = 0; + + // these are only used in a safepoint: we should be fine either way + _purge_timer.reset(); + _maintenance_timer.reset(); +}