/* * 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 . * */ package sun.evtracing.processing.statistics; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import sun.evtracing.parser.GlobalSequenceOrderedEvent; import sun.evtracing.parser.ParkReturnCode; import sun.evtracing.parser.metadata.JavaStack; import sun.evtracing.parser.metadata.JavaThread; import sun.evtracing.processing.Sanity; import sun.evtracing.processing.statistics.aggregator.ContentionProcessor; import sun.evtracing.processing.statistics.metadata.Contention; import sun.evtracing.processing.statistics.metadata.Group; import sun.evtracing.processing.statistics.metadata.JavaObjectStack; import sun.evtracing.processing.statistics.metadata.ParkEventTuple; public abstract class ParkBlockerAbstractQueuedSynchronizerAnalyser implements ParkBlockerAnalyser { // Defines how to handle time between unpark and parkEnd public enum BlameShiftingMode { ACCURATE, // shift to unknown PREVIOUS, // shift to previous lock holder NEXT, // shift to next lock holder } @FunctionalInterface public interface PrevNextConsumer { public void accept(T prev, T cur, T next); } private static final BlameShiftingMode blameShiftingMode = BlameShiftingMode.valueOf(System.getProperty("sun.evtracing.jucBlameShifitingMode", "NEXT")); private final ContentionProcessor cproc; private final Set parker = new HashSet<>(); private long lastEnd; private long submittedDuration; public ParkBlockerAbstractQueuedSynchronizerAnalyser(ContentionProcessor cproc) { this.cproc = cproc; } @Override public void begin(ParkEventTuple tuple) { parker.add(tuple); } @Override public void end(ParkEventTuple tuple) { parker.remove(tuple); for (ParkEventTuple t : parker) { t.addOverlapping(tuple); } } @Override public void finish(ParkEventTuple tuple) { try { List overlapping = tuple.overlapping().stream() .filter(t -> !t.isInterrupted() && t.knownOwner()) .sorted(new ParkEventTupleUnparkComparator()) .collect(Collectors.toList()); overlapping.add(tuple); lastEnd = tuple.begin().timestamp(); submittedDuration = 0; iterateWithPrevNext(overlapping, (prev, cur, next) -> processOverlappingContention(tuple, prev, cur, next)); assert tuple.end().timestamp() - tuple.begin().timestamp() == submittedDuration; } catch (AssertionError | RuntimeException e) { ParkEventTupleJsonSerializer.serialize("traceviewer_timestamp.json", tuple, false); ParkEventTupleJsonSerializer.serialize("traceviewer_sequence.json", tuple, true); throw e; } finally { lastEnd = 0; submittedDuration = 0; } } private void iterateWithPrevNext(Iterable iterable, PrevNextConsumer consumer) { T prev; T cur = null; T next = null; for (T t : iterable) { prev = cur; cur = next; next = t; if (cur != null) { consumer.accept(prev, cur, next); } } if (next != null) { consumer.accept(cur, next, null); } } private void processOverlappingContention(ParkEventTuple tuple, ParkEventTuple prev, ParkEventTuple cur, ParkEventTuple next) { if (Sanity.isEnabled()) { assertContentionTransition(tuple, prev, cur, next); } boolean submitKnown; if (next != null) { submitKnown = cur.knownOwner() && tuple.begin().sequenceNumber() < cur.unpark().sequenceNumber(); } else { assert cur == tuple; submitKnown = cur.knownOwner() && cur.causedContention(); if (!submitKnown && cur.end().parkReturnCode() == ParkReturnCode.TimedOut) { // TODO: Special handling necessary for pushing remaining duration } } if (submitKnown) { JavaThread contendingThread = cur.end().thread().metadata(); JavaThread ownerThread; JavaStack ownerSite; if (isShared(cur)) { ownerThread = sharedThread(); ownerSite = sharedSite(); } else { ownerThread = cur.unpark().thread().metadata(); assert contendingThread.threadId() != ownerThread.threadId(); ownerSite = cur.unpark().stack().metadata(); } long unparkTime; if (blameShiftingMode == BlameShiftingMode.PREVIOUS) { unparkTime = cur.end().timestamp(); } else { unparkTime = cur.unpark().timestamp(); } long duration = unparkTime - lastEnd; submitContention(tuple, ownerThread, ownerSite, lastEnd, duration); lastEnd = unparkTime; } if (next == null || (blameShiftingMode != BlameShiftingMode.NEXT && isSequentialUnpark(cur, next))) { long endTime = cur.end().timestamp(); long duration = endTime - lastEnd; if (duration != 0) { submitContention(tuple, JavaThread.UNKNOWN, JavaStack.UNKNOWN, lastEnd, duration); lastEnd = endTime; } } } abstract boolean isShared(ParkEventTuple cur); abstract JavaThread sharedThread(); abstract JavaStack sharedSite(); private boolean isSequentialUnpark(ParkEventTuple cur, ParkEventTuple next) { return cur.end().sequenceNumber() < next.end().unparkSequenceNumber(); } private void assertContentionTransition(ParkEventTuple tuple, ParkEventTuple prev, ParkEventTuple cur, ParkEventTuple next) { if (next != null) { // cur.begin().sequenceNumber() < tuple.begin().sequenceNumber() is not always fulfilled // as a thread may have to park multiple times because the lock was sneaked by other threads assert tuple.begin().sequenceNumber() < cur.end().sequenceNumber(); assert cur.end().sequenceNumber() < tuple.end().sequenceNumber(); } if (prev != null) { boolean sequentialUnpark = cur.knownOwner() && prev.end().sequenceNumber() < cur.unpark().sequenceNumber(); GlobalSequenceOrderedEvent prevLastEvent; if (sequentialUnpark) { prevLastEvent = prev.end(); } else { prevLastEvent = prev.unpark(); } // prev.begin().sequenceNumber() < cur.begin().sequenceNumber() is not always fulfilled // as the begin sequence number does not correspond to the enqueuing order assert prevLastEvent.sequenceNumber() < cur.end().sequenceNumber(); if (next != null || sequentialUnpark) { assert prevLastEvent.sequenceNumber() < cur.unpark().sequenceNumber(); } if (!cur.knownOwner()) { assert next == null; assert cur == tuple; // ignore } else if (!sequentialUnpark) { // unpark out of queue order -> last owner sneaked the lock // concurrent unpark -> multiple unpark/end overlap if (prev.end().sequenceNumber() < cur.end().sequenceNumber()) { if (!prev.causedContention() || !cur.causedContention()) { // unharmful concurrent unpark } else { Sanity.warn("inaccurate blame-shifting during concurrent unpark"); } } else if (next != null || prev.unpark().sequenceNumber() < cur.unpark().sequenceNumber()) { //assert !prev.causedContention() || !cur.causedContention(); } } else if (prev.begin().thread().identifier() == cur.unpark().thread().identifier()) { // unpark according to queue order -> last owner followed queue order // cur.begin().sequenceNumber() < prevLastEvent.sequenceNumber() is not always fulfilled // as the begin sequence number does not correspond to the enqueuing order } else if (prev.begin().thread().identifier() == cur.begin().thread().identifier()) { // unpark out of queue order -> last owner sneaked the lock // thread already parked again assert prevLastEvent.sequenceNumber() < cur.begin().sequenceNumber(); } else if (!prev.causedContention()) { // unpark out of queue order -> last owner sneaked the lock // previous wakeup was spurious } else { // unpark out of queue order -> last owner sneaked the lock // special case: // * T1 owns lock, T2, T4 is next on the wait queue // * T1 unparks T2 // * T2 returns from park call // * some thread interrupts T2 // * T2 does not acquire lock and cancels its queue entry // * T3 sneaks the lock // * T3 unparks T4 as T2 is already canceled // * T2 tries to unpark a successor } } } private void submitContention(ParkEventTuple tuple, JavaThread ownerThread, JavaStack ownerSite, long startTime, long duration) { JavaThread contendingThread = tuple.end().thread().metadata(); JavaStack contendingSite = tuple.begin().stack().metadata(); JavaObjectStack blocker = tuple.blocker(); cproc.submit(new Contention(Group.JavaUtilConcurrent, contendingThread, ownerThread, contendingSite, ownerSite, blocker, startTime, duration)); submittedDuration += duration; } }