Safety timings w/ Configurable parallel mode
This commit is contained in:
127
sources/src/main/java/co/aikar/timings/TimingData.java
Normal file
127
sources/src/main/java/co/aikar/timings/TimingData.java
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* This file is licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package co.aikar.timings;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.LongAdder;
|
||||||
|
|
||||||
|
import static co.aikar.util.JSONUtil.toArray;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Akarin Changes Note
|
||||||
|
* 1) Thread safe timing (safety)
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* <p>Lightweight object for tracking timing data</p>
|
||||||
|
*
|
||||||
|
* This is broken out to reduce memory usage
|
||||||
|
*/
|
||||||
|
class TimingData {
|
||||||
|
private final int id;
|
||||||
|
private int count = 0;
|
||||||
|
private int lagCount = 0;
|
||||||
|
private long totalTime = 0;
|
||||||
|
private long lagTotalTime = 0;
|
||||||
|
private AtomicInteger curTickCount = new AtomicInteger(); // Akarin
|
||||||
|
private LongAdder curTickTotal = new LongAdder(); // Akarin
|
||||||
|
|
||||||
|
TimingData(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TimingData(TimingData data) {
|
||||||
|
this.id = data.id;
|
||||||
|
this.totalTime = data.totalTime;
|
||||||
|
this.lagTotalTime = data.lagTotalTime;
|
||||||
|
this.count = data.count;
|
||||||
|
this.lagCount = data.lagCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(long diff) {
|
||||||
|
curTickCount.incrementAndGet();
|
||||||
|
curTickTotal.add(diff);
|
||||||
|
}
|
||||||
|
|
||||||
|
void processTick(boolean violated) {
|
||||||
|
totalTime += curTickTotal.sum();
|
||||||
|
count += curTickCount.get();
|
||||||
|
if (violated) {
|
||||||
|
lagTotalTime += curTickTotal.sum();
|
||||||
|
lagCount += curTickCount.get();
|
||||||
|
}
|
||||||
|
curTickTotal.reset();
|
||||||
|
curTickCount.set(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
count = 0;
|
||||||
|
lagCount = 0;
|
||||||
|
curTickTotal.reset();
|
||||||
|
curTickCount.set(0);
|
||||||
|
totalTime = 0;
|
||||||
|
lagTotalTime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TimingData clone() {
|
||||||
|
return new TimingData(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Object> export() {
|
||||||
|
List<Object> list = toArray(
|
||||||
|
id,
|
||||||
|
count,
|
||||||
|
totalTime);
|
||||||
|
if (lagCount > 0) {
|
||||||
|
list.add(lagCount);
|
||||||
|
list.add(lagTotalTime);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasData() {
|
||||||
|
return count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
long getTotalTime() {
|
||||||
|
return totalTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getCurTickCount() {
|
||||||
|
return curTickCount.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCurTickCount(int curTickCount) {
|
||||||
|
this.curTickCount.getAndSet(curTickCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
long getCurTickTotal() {
|
||||||
|
return curTickTotal.sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCurTickTotal(long curTickTotal) {
|
||||||
|
this.curTickTotal.reset();
|
||||||
|
this.curTickTotal.add(curTickTotal);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,17 +24,20 @@
|
|||||||
package co.aikar.timings;
|
package co.aikar.timings;
|
||||||
|
|
||||||
import co.aikar.util.LoadingIntMap;
|
import co.aikar.util.LoadingIntMap;
|
||||||
|
import io.akarin.api.internal.Akari;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Akarin Changes Note
|
* Akarin Changes Note
|
||||||
* 1) Make class public (compatibility)
|
* 1) Thread safe timing (safety)
|
||||||
* 2) Add stopTiming method that accept given start time (compatibility)
|
|
||||||
*/
|
*/
|
||||||
public class TimingHandler implements Timing { // Akarin
|
class TimingHandler implements Timing {
|
||||||
|
|
||||||
private static int idPool = 1;
|
private static int idPool = 1;
|
||||||
final int id = idPool++;
|
final int id = idPool++;
|
||||||
@@ -47,12 +50,12 @@ public class TimingHandler implements Timing { // Akarin
|
|||||||
final TimingData record;
|
final TimingData record;
|
||||||
private final TimingHandler groupHandler;
|
private final TimingHandler groupHandler;
|
||||||
|
|
||||||
private long start = 0;
|
private AtomicLong start = new AtomicLong(); // Akarin
|
||||||
private int timingDepth = 0;
|
private AtomicInteger timingDepth = new AtomicInteger(); // Akarin
|
||||||
private boolean added;
|
private volatile boolean added; // Akarin
|
||||||
private boolean timed;
|
private boolean timed;
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
private TimingHandler parent;
|
private volatile TimingHandler parent; // Akarin
|
||||||
|
|
||||||
TimingHandler(TimingIdentifier id) {
|
TimingHandler(TimingIdentifier id) {
|
||||||
if (id.name.startsWith("##")) {
|
if (id.name.startsWith("##")) {
|
||||||
@@ -75,16 +78,18 @@ public class TimingHandler implements Timing { // Akarin
|
|||||||
}
|
}
|
||||||
|
|
||||||
void processTick(boolean violated) {
|
void processTick(boolean violated) {
|
||||||
if (timingDepth != 0 || record.getCurTickCount() == 0) {
|
if (timingDepth.get() != 0 || record.getCurTickCount() == 0) {
|
||||||
timingDepth = 0;
|
timingDepth.set(0);
|
||||||
start = 0;
|
start.set(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
record.processTick(violated);
|
record.processTick(violated);
|
||||||
|
Akari.timingsLock.lock(); // Akarin
|
||||||
for (TimingData handler : children.values()) {
|
for (TimingData handler : children.values()) {
|
||||||
handler.processTick(violated);
|
handler.processTick(violated);
|
||||||
}
|
}
|
||||||
|
Akari.timingsLock.unlock(); // Akarin
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -102,42 +107,32 @@ public class TimingHandler implements Timing { // Akarin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Timing startTiming() {
|
public Timing startTiming() {
|
||||||
if (enabled && ++timingDepth == 1) {
|
if (enabled && timingDepth.incrementAndGet() == 1) {
|
||||||
start = System.nanoTime();
|
start.getAndSet(System.nanoTime());
|
||||||
parent = TimingsManager.CURRENT;
|
parent = TimingsManager.CURRENT;
|
||||||
TimingsManager.CURRENT = this;
|
TimingsManager.CURRENT = this;
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stopTiming() {
|
public void stopTiming() {
|
||||||
if (enabled && --timingDepth == 0 && start != 0) {
|
if (enabled && timingDepth.decrementAndGet() == 0 && start.get() != 0) {
|
||||||
if (!Bukkit.isPrimaryThread()) {
|
if (!Bukkit.isPrimaryThread()) {
|
||||||
Bukkit.getLogger().log(Level.SEVERE, "stopTiming called async for " + name);
|
Bukkit.getLogger().log(Level.SEVERE, "stopTiming called async for " + name);
|
||||||
new Throwable().printStackTrace();
|
new Throwable().printStackTrace();
|
||||||
start = 0;
|
start.getAndSet(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
addDiff(System.nanoTime() - start);
|
long prev = start.getAndSet(0); // Akarin
|
||||||
start = 0;
|
addDiff(System.nanoTime() - prev); // Akarin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Akarin start
|
|
||||||
public void stopTiming(long start) {
|
|
||||||
if (enabled && --timingDepth == 0 && start != 0) {
|
|
||||||
addDiff(System.nanoTime() - start);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Akarin end
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void abort() {
|
public void abort() {
|
||||||
if (enabled && timingDepth > 0) {
|
if (enabled && timingDepth.get() > 0) {
|
||||||
start = 0;
|
start.getAndSet(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,18 +140,24 @@ public class TimingHandler implements Timing { // Akarin
|
|||||||
if (TimingsManager.CURRENT == this) {
|
if (TimingsManager.CURRENT == this) {
|
||||||
TimingsManager.CURRENT = parent;
|
TimingsManager.CURRENT = parent;
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
|
Akari.timingsLock.lock(); // Akarin
|
||||||
parent.children.get(id).add(diff);
|
parent.children.get(id).add(diff);
|
||||||
|
Akari.timingsLock.unlock(); // Akarin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
record.add(diff);
|
record.add(diff);
|
||||||
if (!added) {
|
if (!added) {
|
||||||
added = true;
|
added = true;
|
||||||
timed = true;
|
timed = true;
|
||||||
|
Akari.timingsLock.lock(); // Akarin
|
||||||
TimingsManager.HANDLERS.add(this);
|
TimingsManager.HANDLERS.add(this);
|
||||||
|
Akari.timingsLock.unlock(); // Akarin
|
||||||
}
|
}
|
||||||
if (groupHandler != null) {
|
if (groupHandler != null) {
|
||||||
groupHandler.addDiff(diff);
|
groupHandler.addDiff(diff);
|
||||||
|
Akari.timingsLock.lock(); // Akarin
|
||||||
groupHandler.children.get(id).add(diff);
|
groupHandler.children.get(id).add(diff);
|
||||||
|
Akari.timingsLock.unlock(); // Akarin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,10 +171,12 @@ public class TimingHandler implements Timing { // Akarin
|
|||||||
if (full) {
|
if (full) {
|
||||||
timed = false;
|
timed = false;
|
||||||
}
|
}
|
||||||
start = 0;
|
start.set(0);
|
||||||
timingDepth = 0;
|
timingDepth.set(0);
|
||||||
added = false;
|
added = false;
|
||||||
|
Akari.timingsLock.lock(); // Akarin
|
||||||
children.clear();
|
children.clear();
|
||||||
|
Akari.timingsLock.unlock(); // Akarin
|
||||||
checkEnabled();
|
checkEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,11 +217,13 @@ public class TimingHandler implements Timing { // Akarin
|
|||||||
}
|
}
|
||||||
|
|
||||||
TimingData[] cloneChildren() {
|
TimingData[] cloneChildren() {
|
||||||
final TimingData[] clonedChildren = new TimingData[children.size()];
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
Akari.timingsLock.lock(); // Akarin
|
||||||
|
final TimingData[] clonedChildren = new TimingData[children.size()];
|
||||||
for (TimingData child : children.values()) {
|
for (TimingData child : children.values()) {
|
||||||
clonedChildren[i++] = child.clone();
|
clonedChildren[i++] = child.clone();
|
||||||
}
|
}
|
||||||
|
Akari.timingsLock.unlock(); // Akarin
|
||||||
return clonedChildren;
|
return clonedChildren;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
355
sources/src/main/java/co/aikar/timings/TimingHistory.java
Normal file
355
sources/src/main/java/co/aikar/timings/TimingHistory.java
Normal file
@@ -0,0 +1,355 @@
|
|||||||
|
/*
|
||||||
|
* This file is licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package co.aikar.timings;
|
||||||
|
|
||||||
|
import co.aikar.timings.TimingHistory.RegionData.RegionId;
|
||||||
|
import co.aikar.util.JSONUtil;
|
||||||
|
import com.google.common.base.Function;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Chunk;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.block.BlockState;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import co.aikar.util.LoadingMap;
|
||||||
|
import co.aikar.util.MRUMapCache;
|
||||||
|
import io.akarin.api.internal.Akari;
|
||||||
|
|
||||||
|
import java.lang.management.ManagementFactory;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static co.aikar.timings.TimingsManager.FULL_SERVER_TICK;
|
||||||
|
import static co.aikar.timings.TimingsManager.MINUTE_REPORTS;
|
||||||
|
import static co.aikar.util.JSONUtil.*;
|
||||||
|
|
||||||
|
@SuppressWarnings({"deprecation", "SuppressionAnnotation", "Convert2Lambda", "Anonymous2MethodRef"})
|
||||||
|
public class TimingHistory {
|
||||||
|
public static long lastMinuteTime;
|
||||||
|
public static long timedTicks;
|
||||||
|
public static long playerTicks;
|
||||||
|
public static long entityTicks;
|
||||||
|
public static long tileEntityTicks;
|
||||||
|
public static long activatedEntityTicks;
|
||||||
|
private static int worldIdPool = 1;
|
||||||
|
static Map<String, Integer> worldMap = LoadingMap.newHashMap(new Function<String, Integer>() {
|
||||||
|
@Override
|
||||||
|
public Integer apply(String input) {
|
||||||
|
return worldIdPool++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
private final long endTime;
|
||||||
|
private final long startTime;
|
||||||
|
private final long totalTicks;
|
||||||
|
private final long totalTime; // Represents all time spent running the server this history
|
||||||
|
private final MinuteReport[] minuteReports;
|
||||||
|
|
||||||
|
private final TimingHistoryEntry[] entries;
|
||||||
|
final Set<Material> tileEntityTypeSet = Sets.newHashSet();
|
||||||
|
final Set<EntityType> entityTypeSet = Sets.newHashSet();
|
||||||
|
private final Map<Object, Object> worlds;
|
||||||
|
|
||||||
|
TimingHistory() {
|
||||||
|
this.endTime = System.currentTimeMillis() / 1000;
|
||||||
|
this.startTime = TimingsManager.historyStart / 1000;
|
||||||
|
if (timedTicks % 1200 != 0 || MINUTE_REPORTS.isEmpty()) {
|
||||||
|
this.minuteReports = MINUTE_REPORTS.toArray(new MinuteReport[MINUTE_REPORTS.size() + 1]);
|
||||||
|
this.minuteReports[this.minuteReports.length - 1] = new MinuteReport();
|
||||||
|
} else {
|
||||||
|
this.minuteReports = MINUTE_REPORTS.toArray(new MinuteReport[MINUTE_REPORTS.size()]);
|
||||||
|
}
|
||||||
|
long ticks = 0;
|
||||||
|
for (MinuteReport mp : this.minuteReports) {
|
||||||
|
ticks += mp.ticksRecord.timed;
|
||||||
|
}
|
||||||
|
this.totalTicks = ticks;
|
||||||
|
this.totalTime = FULL_SERVER_TICK.record.getTotalTime();
|
||||||
|
Akari.timingsLock.lock(); // Akarin
|
||||||
|
this.entries = new TimingHistoryEntry[TimingsManager.HANDLERS.size()];
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (TimingHandler handler : TimingsManager.HANDLERS) {
|
||||||
|
entries[i++] = new TimingHistoryEntry(handler);
|
||||||
|
}
|
||||||
|
Akari.timingsLock.unlock(); // Akarin
|
||||||
|
|
||||||
|
|
||||||
|
// Information about all loaded chunks/entities
|
||||||
|
//noinspection unchecked
|
||||||
|
this.worlds = toObjectMapper(Bukkit.getWorlds(), new Function<World, JSONPair>() {
|
||||||
|
@Override
|
||||||
|
public JSONPair apply(World world) {
|
||||||
|
Map<RegionId, RegionData> regions = LoadingMap.newHashMap(RegionData.LOADER);
|
||||||
|
|
||||||
|
for (Chunk chunk : world.getLoadedChunks()) {
|
||||||
|
RegionData data = regions.get(new RegionId(chunk.getX(), chunk.getZ()));
|
||||||
|
|
||||||
|
for (Entity entity : chunk.getEntities()) {
|
||||||
|
if (entity == null) {
|
||||||
|
Bukkit.getLogger().warning("Null entity detected in chunk at position x: " + chunk.getX() + ", z: " + chunk.getZ());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.entityCounts.get(entity.getType()).increment();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (BlockState tileEntity : chunk.getTileEntities()) {
|
||||||
|
if (tileEntity == null) {
|
||||||
|
Bukkit.getLogger().warning("Null tileentity detected in chunk at position x: " + chunk.getX() + ", z: " + chunk.getZ());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.tileEntityCounts.get(tileEntity.getBlock().getType()).increment();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pair(
|
||||||
|
worldMap.get(world.getName()),
|
||||||
|
toArrayMapper(regions.values(),new Function<RegionData, Object>() {
|
||||||
|
@Override
|
||||||
|
public Object apply(RegionData input) {
|
||||||
|
return toArray(
|
||||||
|
input.regionId.x,
|
||||||
|
input.regionId.z,
|
||||||
|
toObjectMapper(input.entityCounts.entrySet(),
|
||||||
|
new Function<Map.Entry<EntityType, Counter>, JSONPair>() {
|
||||||
|
@Override
|
||||||
|
public JSONPair apply(Map.Entry<EntityType, Counter> entry) {
|
||||||
|
entityTypeSet.add(entry.getKey());
|
||||||
|
return pair(
|
||||||
|
String.valueOf(entry.getKey().getTypeId()),
|
||||||
|
entry.getValue().count()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
toObjectMapper(input.tileEntityCounts.entrySet(),
|
||||||
|
new Function<Map.Entry<Material, Counter>, JSONPair>() {
|
||||||
|
@Override
|
||||||
|
public JSONPair apply(Map.Entry<Material, Counter> entry) {
|
||||||
|
tileEntityTypeSet.add(entry.getKey());
|
||||||
|
return pair(
|
||||||
|
String.valueOf(entry.getKey().getId()),
|
||||||
|
entry.getValue().count()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
static class RegionData {
|
||||||
|
final RegionId regionId;
|
||||||
|
@SuppressWarnings("Guava")
|
||||||
|
static Function<RegionId, RegionData> LOADER = new Function<RegionId, RegionData>() {
|
||||||
|
@Override
|
||||||
|
public RegionData apply(RegionId id) {
|
||||||
|
return new RegionData(id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
RegionData(RegionId id) {
|
||||||
|
this.regionId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegionData that = (RegionData) o;
|
||||||
|
|
||||||
|
return regionId.equals(that.regionId);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return regionId.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final Map<EntityType, Counter> entityCounts = MRUMapCache.of(LoadingMap.of(
|
||||||
|
new EnumMap<EntityType, Counter>(EntityType.class), Counter.LOADER
|
||||||
|
));
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final Map<Material, Counter> tileEntityCounts = MRUMapCache.of(LoadingMap.of(
|
||||||
|
new EnumMap<Material, Counter>(Material.class), Counter.LOADER
|
||||||
|
));
|
||||||
|
|
||||||
|
static class RegionId {
|
||||||
|
final int x, z;
|
||||||
|
final long regionId;
|
||||||
|
RegionId(int x, int z) {
|
||||||
|
this.x = x >> 5 << 5;
|
||||||
|
this.z = z >> 5 << 5;
|
||||||
|
this.regionId = ((long) (this.x) << 32) + (this.z >> 5 << 5) - Integer.MIN_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
RegionId regionId1 = (RegionId) o;
|
||||||
|
|
||||||
|
return regionId == regionId1.regionId;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return (int) (regionId ^ (regionId >>> 32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static void resetTicks(boolean fullReset) {
|
||||||
|
if (fullReset) {
|
||||||
|
// Non full is simply for 1 minute reports
|
||||||
|
timedTicks = 0;
|
||||||
|
}
|
||||||
|
lastMinuteTime = System.nanoTime();
|
||||||
|
playerTicks = 0;
|
||||||
|
tileEntityTicks = 0;
|
||||||
|
entityTicks = 0;
|
||||||
|
activatedEntityTicks = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object export() {
|
||||||
|
return createObject(
|
||||||
|
pair("s", startTime),
|
||||||
|
pair("e", endTime),
|
||||||
|
pair("tk", totalTicks),
|
||||||
|
pair("tm", totalTime),
|
||||||
|
pair("w", worlds),
|
||||||
|
pair("h", toArrayMapper(entries, new Function<TimingHistoryEntry, Object>() {
|
||||||
|
@Override
|
||||||
|
public Object apply(TimingHistoryEntry entry) {
|
||||||
|
TimingData record = entry.data;
|
||||||
|
if (!record.hasData()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return entry.export();
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
pair("mp", toArrayMapper(minuteReports, new Function<MinuteReport, Object>() {
|
||||||
|
@Override
|
||||||
|
public Object apply(MinuteReport input) {
|
||||||
|
return input.export();
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class MinuteReport {
|
||||||
|
final long time = System.currentTimeMillis() / 1000;
|
||||||
|
|
||||||
|
final TicksRecord ticksRecord = new TicksRecord();
|
||||||
|
final PingRecord pingRecord = new PingRecord();
|
||||||
|
final TimingData fst = TimingsManager.FULL_SERVER_TICK.minuteData.clone();
|
||||||
|
final double tps = 1E9 / ( System.nanoTime() - lastMinuteTime ) * ticksRecord.timed;
|
||||||
|
final double usedMemory = TimingsManager.FULL_SERVER_TICK.avgUsedMemory;
|
||||||
|
final double freeMemory = TimingsManager.FULL_SERVER_TICK.avgFreeMemory;
|
||||||
|
final double loadAvg = ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage();
|
||||||
|
|
||||||
|
List<Object> export() {
|
||||||
|
return toArray(
|
||||||
|
time,
|
||||||
|
Math.round(tps * 100D) / 100D,
|
||||||
|
Math.round(pingRecord.avg * 100D) / 100D,
|
||||||
|
fst.export(),
|
||||||
|
toArray(ticksRecord.timed,
|
||||||
|
ticksRecord.player,
|
||||||
|
ticksRecord.entity,
|
||||||
|
ticksRecord.activatedEntity,
|
||||||
|
ticksRecord.tileEntity
|
||||||
|
),
|
||||||
|
usedMemory,
|
||||||
|
freeMemory,
|
||||||
|
loadAvg
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TicksRecord {
|
||||||
|
final long timed;
|
||||||
|
final long player;
|
||||||
|
final long entity;
|
||||||
|
final long tileEntity;
|
||||||
|
final long activatedEntity;
|
||||||
|
|
||||||
|
TicksRecord() {
|
||||||
|
timed = timedTicks - (TimingsManager.MINUTE_REPORTS.size() * 1200);
|
||||||
|
player = playerTicks;
|
||||||
|
entity = entityTicks;
|
||||||
|
tileEntity = tileEntityTicks;
|
||||||
|
activatedEntity = activatedEntityTicks;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PingRecord {
|
||||||
|
final double avg;
|
||||||
|
|
||||||
|
PingRecord() {
|
||||||
|
final Collection<? extends Player> onlinePlayers = Bukkit.getOnlinePlayers();
|
||||||
|
int totalPing = 0;
|
||||||
|
for (Player player : onlinePlayers) {
|
||||||
|
totalPing += player.spigot().getPing();
|
||||||
|
}
|
||||||
|
avg = onlinePlayers.isEmpty() ? 0 : totalPing / onlinePlayers.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static class Counter {
|
||||||
|
private int count = 0;
|
||||||
|
@SuppressWarnings({"rawtypes", "SuppressionAnnotation", "Guava"})
|
||||||
|
static Function LOADER = new LoadingMap.Feeder<Counter>() {
|
||||||
|
@Override
|
||||||
|
public Counter apply() {
|
||||||
|
return new Counter();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public int increment() {
|
||||||
|
return ++count;
|
||||||
|
}
|
||||||
|
public int count() {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
207
sources/src/main/java/co/aikar/timings/TimingsManager.java
Normal file
207
sources/src/main/java/co/aikar/timings/TimingsManager.java
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
/*
|
||||||
|
* This file is licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Daniel Ennis <http://aikar.co>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package co.aikar.timings;
|
||||||
|
|
||||||
|
import com.google.common.base.Function;
|
||||||
|
import com.google.common.collect.EvictingQueue;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Server;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
import org.bukkit.plugin.java.PluginClassLoader;
|
||||||
|
import co.aikar.util.LoadingMap;
|
||||||
|
import io.akarin.api.internal.Akari;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Akarin Changes Note
|
||||||
|
* 1) Thread safe timing (safety)
|
||||||
|
*/
|
||||||
|
public final class TimingsManager {
|
||||||
|
static final Map<TimingIdentifier, TimingHandler> TIMING_MAP =
|
||||||
|
Collections.synchronizedMap(LoadingMap.newHashMap(
|
||||||
|
new Function<TimingIdentifier, TimingHandler>() {
|
||||||
|
@Override
|
||||||
|
public TimingHandler apply(TimingIdentifier id) {
|
||||||
|
return (id.protect ?
|
||||||
|
new UnsafeTimingHandler(id) :
|
||||||
|
new TimingHandler(id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
256, .5F
|
||||||
|
));
|
||||||
|
public static final FullServerTickHandler FULL_SERVER_TICK = new FullServerTickHandler();
|
||||||
|
public static final TimingHandler TIMINGS_TICK = Timings.ofSafe("Timings Tick", FULL_SERVER_TICK);
|
||||||
|
public static final Timing PLUGIN_GROUP_HANDLER = Timings.ofSafe("Plugins");
|
||||||
|
public static List<String> hiddenConfigs = new ArrayList<String>();
|
||||||
|
public static boolean privacy = false;
|
||||||
|
|
||||||
|
static final Collection<TimingHandler> HANDLERS = new ArrayDeque<TimingHandler>();
|
||||||
|
static final ArrayDeque<TimingHistory.MinuteReport> MINUTE_REPORTS = new ArrayDeque<TimingHistory.MinuteReport>();
|
||||||
|
|
||||||
|
static EvictingQueue<TimingHistory> HISTORY = EvictingQueue.create(12);
|
||||||
|
static TimingHandler CURRENT;
|
||||||
|
static long timingStart = 0;
|
||||||
|
static long historyStart = 0;
|
||||||
|
static boolean needsFullReset = false;
|
||||||
|
static boolean needsRecheckEnabled = false;
|
||||||
|
|
||||||
|
private TimingsManager() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets all timing data on the next tick
|
||||||
|
*/
|
||||||
|
static void reset() {
|
||||||
|
needsFullReset = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ticked every tick by CraftBukkit to count the number of times a timer
|
||||||
|
* caused TPS loss.
|
||||||
|
*/
|
||||||
|
static void tick() {
|
||||||
|
if (Timings.timingsEnabled) {
|
||||||
|
boolean violated = FULL_SERVER_TICK.isViolated();
|
||||||
|
|
||||||
|
Akari.timingsLock.lock(); // Akarin
|
||||||
|
for (TimingHandler handler : HANDLERS) {
|
||||||
|
if (handler.isSpecial()) {
|
||||||
|
// We manually call this
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
handler.processTick(violated);
|
||||||
|
}
|
||||||
|
Akari.timingsLock.unlock(); // Akarin
|
||||||
|
|
||||||
|
TimingHistory.playerTicks += Bukkit.getOnlinePlayers().size();
|
||||||
|
TimingHistory.timedTicks++;
|
||||||
|
// Generate TPS/Ping/Tick reports every minute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static void stopServer() {
|
||||||
|
Timings.timingsEnabled = false;
|
||||||
|
recheckEnabled();
|
||||||
|
}
|
||||||
|
static void recheckEnabled() {
|
||||||
|
synchronized (TIMING_MAP) {
|
||||||
|
for (TimingHandler timings : TIMING_MAP.values()) {
|
||||||
|
timings.checkEnabled();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
needsRecheckEnabled = false;
|
||||||
|
}
|
||||||
|
static void resetTimings() {
|
||||||
|
if (needsFullReset) {
|
||||||
|
// Full resets need to re-check every handlers enabled state
|
||||||
|
// Timing map can be modified from async so we must sync on it.
|
||||||
|
synchronized (TIMING_MAP) {
|
||||||
|
for (TimingHandler timings : TIMING_MAP.values()) {
|
||||||
|
timings.reset(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Bukkit.getLogger().log(Level.INFO, "Timings Reset");
|
||||||
|
HISTORY.clear();
|
||||||
|
needsFullReset = false;
|
||||||
|
needsRecheckEnabled = false;
|
||||||
|
timingStart = System.currentTimeMillis();
|
||||||
|
} else {
|
||||||
|
// Soft resets only need to act on timings that have done something
|
||||||
|
// Handlers can only be modified on main thread.
|
||||||
|
Akari.timingsLock.lock(); // Akarin
|
||||||
|
for (TimingHandler timings : HANDLERS) {
|
||||||
|
timings.reset(false);
|
||||||
|
}
|
||||||
|
Akari.timingsLock.unlock(); // Akarin
|
||||||
|
}
|
||||||
|
|
||||||
|
Akari.timingsLock.lock(); // Akarin
|
||||||
|
HANDLERS.clear();
|
||||||
|
Akari.timingsLock.unlock(); // Akarin
|
||||||
|
MINUTE_REPORTS.clear();
|
||||||
|
|
||||||
|
TimingHistory.resetTicks(true);
|
||||||
|
historyStart = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
static TimingHandler getHandler(String group, String name, Timing parent, boolean protect) {
|
||||||
|
return TIMING_MAP.get(new TimingIdentifier(group, name, parent, protect));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Due to access restrictions, we need a helper method to get a Command TimingHandler with String group</p>
|
||||||
|
*
|
||||||
|
* Plugins should never call this
|
||||||
|
*
|
||||||
|
* @param pluginName Plugin this command is associated with
|
||||||
|
* @param command Command to get timings for
|
||||||
|
* @return TimingHandler
|
||||||
|
*/
|
||||||
|
public static Timing getCommandTiming(String pluginName, Command command) {
|
||||||
|
Plugin plugin = null;
|
||||||
|
final Server server = Bukkit.getServer();
|
||||||
|
if (!( server == null || pluginName == null ||
|
||||||
|
"minecraft".equals(pluginName) || "bukkit".equals(pluginName) ||
|
||||||
|
"spigot".equalsIgnoreCase(pluginName) || "paper".equals(pluginName)
|
||||||
|
)) {
|
||||||
|
plugin = server.getPluginManager().getPlugin(pluginName);
|
||||||
|
}
|
||||||
|
if (plugin == null) {
|
||||||
|
// Plugin is passing custom fallback prefix, try to look up by class loader
|
||||||
|
plugin = getPluginByClassloader(command.getClass());
|
||||||
|
}
|
||||||
|
if (plugin == null) {
|
||||||
|
return Timings.ofSafe("Command: " + pluginName + ":" + command.getTimingName());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Timings.ofSafe(plugin, "Command: " + pluginName + ":" + command.getTimingName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks up the class loader for the specified class, and if it is a PluginClassLoader, return the
|
||||||
|
* Plugin that created this class.
|
||||||
|
*
|
||||||
|
* @param clazz Class to check
|
||||||
|
* @return Plugin if created by a plugin
|
||||||
|
*/
|
||||||
|
public static Plugin getPluginByClassloader(Class<?> clazz) {
|
||||||
|
if (clazz == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final ClassLoader classLoader = clazz.getClassLoader();
|
||||||
|
if (classLoader instanceof PluginClassLoader) {
|
||||||
|
PluginClassLoader pluginClassLoader = (PluginClassLoader) classLoader;
|
||||||
|
return pluginClassLoader.getPlugin();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,9 @@ import java.lang.reflect.Field;
|
|||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.ExecutorCompletionService;
|
import java.util.concurrent.ExecutorCompletionService;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
import java.util.concurrent.ThreadFactory;
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
@@ -14,8 +16,14 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
|||||||
|
|
||||||
import co.aikar.timings.Timing;
|
import co.aikar.timings.Timing;
|
||||||
import co.aikar.timings.Timings;
|
import co.aikar.timings.Timings;
|
||||||
|
import io.akarin.api.internal.Akari.AssignableFactory;
|
||||||
|
import io.akarin.api.internal.Akari.TimingSignal;
|
||||||
|
import io.akarin.api.internal.utils.ReentrantSpinningLock;
|
||||||
|
import io.akarin.api.internal.utils.thread.SuspendableExecutorCompletionService;
|
||||||
|
import io.akarin.api.internal.utils.thread.SuspendableThreadPoolExecutor;
|
||||||
import io.akarin.server.core.AkarinGlobalConfig;
|
import io.akarin.server.core.AkarinGlobalConfig;
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.World;
|
||||||
import net.minecraft.server.WorldServer;
|
import net.minecraft.server.WorldServer;
|
||||||
|
|
||||||
@SuppressWarnings("restriction")
|
@SuppressWarnings("restriction")
|
||||||
@@ -61,8 +69,43 @@ public abstract class Akari {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ExecutorCompletionService<WorldServer> STAGE_ENTITY_TICK;
|
public static class TimingSignal {
|
||||||
public static ExecutorCompletionService<WorldServer> STAGE_WORLD_TICK;
|
public final World tickedWorld;
|
||||||
|
public final boolean isEntities;
|
||||||
|
|
||||||
|
public TimingSignal(World world, boolean entities) {
|
||||||
|
tickedWorld = world;
|
||||||
|
isEntities = entities;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SuspendableExecutorCompletionService<TimingSignal> STAGE_TICK;
|
||||||
|
|
||||||
|
static {
|
||||||
|
resizeTickExecutors(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void resizeTickExecutors(int worlds) {
|
||||||
|
int parallelism;
|
||||||
|
switch (AkarinGlobalConfig.parallelMode) {
|
||||||
|
case -1:
|
||||||
|
return;
|
||||||
|
case 0:
|
||||||
|
parallelism = 2;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
parallelism = worlds + 1;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
default:
|
||||||
|
parallelism = worlds * 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
STAGE_TICK = new SuspendableExecutorCompletionService<>(new SuspendableThreadPoolExecutor(parallelism, parallelism,
|
||||||
|
0L, TimeUnit.MILLISECONDS,
|
||||||
|
new LinkedBlockingQueue<Runnable>(),
|
||||||
|
new AssignableFactory("Akarin Parallel Ticking Thread - $")));
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isPrimaryThread() {
|
public static boolean isPrimaryThread() {
|
||||||
return isPrimaryThread(true);
|
return isPrimaryThread(true);
|
||||||
@@ -97,6 +140,8 @@ public abstract class Akari {
|
|||||||
return serverVersion + " (MC: " + MinecraftServer.getServer().getVersion() + ")";
|
return serverVersion + " (MC: " + MinecraftServer.getServer().getVersion() + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final ReentrantSpinningLock timingsLock = new ReentrantSpinningLock();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Timings
|
* Timings
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package io.akarin.api.internal.utils;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
public class ReentrantSpinningLock {
|
||||||
|
/*
|
||||||
|
* Impl Note:
|
||||||
|
* A write lock can reentrant as a read lock, while a
|
||||||
|
* read lock is not allowed to reentrant as a write lock.
|
||||||
|
*/
|
||||||
|
private final AtomicBoolean writeLocked = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
// --------- Thread local restricted fields ---------
|
||||||
|
private long heldThreadId = 0;
|
||||||
|
private int reentrantLocks = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lock as a typical write lock
|
||||||
|
*/
|
||||||
|
public void lock() {
|
||||||
|
long currentThreadId = Thread.currentThread().getId();
|
||||||
|
if (heldThreadId == currentThreadId) {
|
||||||
|
reentrantLocks++;
|
||||||
|
} else {
|
||||||
|
while (!writeLocked.compareAndSet(false, true)) ; // In case acquire one lock concurrently
|
||||||
|
heldThreadId = currentThreadId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unlock() {
|
||||||
|
if (reentrantLocks == 0) {
|
||||||
|
heldThreadId = 0;
|
||||||
|
if (readerThreads.get() == 0 || readerThreads.getAndDecrement() == 1) { // Micro-optimization: this saves one subtract
|
||||||
|
writeLocked.set(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
--reentrantLocks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final AtomicInteger readerThreads = new AtomicInteger(0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lock as a typical read lock
|
||||||
|
*/
|
||||||
|
public void lockWeak() {
|
||||||
|
long currentThreadId = Thread.currentThread().getId();
|
||||||
|
if (heldThreadId == currentThreadId) {
|
||||||
|
reentrantLocks++;
|
||||||
|
} else {
|
||||||
|
if (readerThreads.get() == 0) {
|
||||||
|
while (!writeLocked.compareAndSet(false, true)) ; // Block future write lock
|
||||||
|
}
|
||||||
|
heldThreadId = currentThreadId;
|
||||||
|
readerThreads.getAndIncrement(); // Micro-optimization: this saves one plus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unlockWeak() {
|
||||||
|
if (reentrantLocks == 0) {
|
||||||
|
heldThreadId = 0;
|
||||||
|
writeLocked.set(false);
|
||||||
|
} else {
|
||||||
|
--reentrantLocks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------- Wrappers to allow typical usages ---------
|
||||||
|
private SpinningWriteLock wrappedWriteLock = new SpinningWriteLock();
|
||||||
|
private SpinningReadLock wrappedReadLock = new SpinningReadLock();
|
||||||
|
|
||||||
|
public class SpinningWriteLock {
|
||||||
|
public void lock() {
|
||||||
|
lock();
|
||||||
|
}
|
||||||
|
public void unlock() {
|
||||||
|
unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SpinningReadLock {
|
||||||
|
public void lock() {
|
||||||
|
lockWeak();
|
||||||
|
}
|
||||||
|
public void unlock() {
|
||||||
|
unlockWeak();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpinningWriteLock writeLock() {
|
||||||
|
return wrappedWriteLock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpinningReadLock readLocked() {
|
||||||
|
return wrappedReadLock;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package io.akarin.api.internal.utils.thread;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
public class OpenExecutionException extends ExecutionException {
|
||||||
|
private static final long serialVersionUID = 7830266012832686185L;
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
/*
|
||||||
|
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Written by Doug Lea with assistance from members of JCP JSR-166
|
||||||
|
* Expert Group and released to the public domain, as explained at
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.akarin.api.internal.utils.thread;
|
||||||
|
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.CompletionService;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.FutureTask;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.RunnableFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class SuspendableExecutorCompletionService<V> implements CompletionService<V> {
|
||||||
|
private final SuspendableThreadPoolExecutor executor;
|
||||||
|
private final BlockingQueue<Future<V>> completionQueue;
|
||||||
|
|
||||||
|
public void suspend() {
|
||||||
|
executor.suspend();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resume() {
|
||||||
|
executor.resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FutureTask extension to enqueue upon completion
|
||||||
|
*/
|
||||||
|
private class QueueingFuture extends FutureTask<Void> {
|
||||||
|
QueueingFuture(RunnableFuture<V> task) {
|
||||||
|
super(task, null);
|
||||||
|
this.task = task;
|
||||||
|
}
|
||||||
|
protected void done() { completionQueue.add(task); }
|
||||||
|
private final Future<V> task;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RunnableFuture<V> newTaskFor(Callable<V> task) {
|
||||||
|
return new FutureTask<V>(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RunnableFuture<V> newTaskFor(Runnable task, V result) {
|
||||||
|
return new FutureTask<V>(task, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an ExecutorCompletionService using the supplied
|
||||||
|
* executor for base task execution and a
|
||||||
|
* {@link LinkedBlockingQueue} as a completion queue.
|
||||||
|
*
|
||||||
|
* @param executor the executor to use
|
||||||
|
* @throws NullPointerException if executor is {@code null}
|
||||||
|
*/
|
||||||
|
public SuspendableExecutorCompletionService(SuspendableThreadPoolExecutor executor) {
|
||||||
|
if (executor == null)
|
||||||
|
throw new NullPointerException();
|
||||||
|
this.executor = executor;
|
||||||
|
this.completionQueue = new LinkedBlockingQueue<Future<V>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an ExecutorCompletionService using the supplied
|
||||||
|
* executor for base task execution and the supplied queue as its
|
||||||
|
* completion queue.
|
||||||
|
*
|
||||||
|
* @param executor the executor to use
|
||||||
|
* @param completionQueue the queue to use as the completion queue
|
||||||
|
* normally one dedicated for use by this service. This
|
||||||
|
* queue is treated as unbounded -- failed attempted
|
||||||
|
* {@code Queue.add} operations for completed tasks cause
|
||||||
|
* them not to be retrievable.
|
||||||
|
* @throws NullPointerException if executor or completionQueue are {@code null}
|
||||||
|
*/
|
||||||
|
public SuspendableExecutorCompletionService(SuspendableThreadPoolExecutor executor,
|
||||||
|
BlockingQueue<Future<V>> completionQueue) {
|
||||||
|
if (executor == null || completionQueue == null)
|
||||||
|
throw new NullPointerException();
|
||||||
|
this.executor = executor;
|
||||||
|
this.completionQueue = completionQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<V> submit(Callable<V> task) {
|
||||||
|
if (task == null) throw new NullPointerException();
|
||||||
|
RunnableFuture<V> f = newTaskFor(task);
|
||||||
|
executor.execute(new QueueingFuture(f));
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<V> submit(Runnable task, V result) {
|
||||||
|
if (task == null) throw new NullPointerException();
|
||||||
|
RunnableFuture<V> f = newTaskFor(task, result);
|
||||||
|
executor.execute(new QueueingFuture(f));
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<V> take() throws InterruptedException {
|
||||||
|
return completionQueue.take();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<V> poll() {
|
||||||
|
return completionQueue.poll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<V> poll(long timeout, TimeUnit unit)
|
||||||
|
throws InterruptedException {
|
||||||
|
return completionQueue.poll(timeout, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -160,11 +160,6 @@ public class AkarinGlobalConfig {
|
|||||||
playersPerIOThread = getInt("core.players-per-chunk-io-thread", 50);
|
playersPerIOThread = getInt("core.players-per-chunk-io-thread", 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean silentAsyncTimings;
|
|
||||||
private static void silentAsyncTimings() {
|
|
||||||
silentAsyncTimings = getBoolean("core.always-silent-async-timing", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long timeUpdateInterval;
|
public static long timeUpdateInterval;
|
||||||
private static void timeUpdateInterval() {
|
private static void timeUpdateInterval() {
|
||||||
timeUpdateInterval = getSeconds(getString("core.tick-rate.world-time-update-interval", "1s")) * 10;
|
timeUpdateInterval = getSeconds(getString("core.tick-rate.world-time-update-interval", "1s")) * 10;
|
||||||
@@ -257,4 +252,9 @@ public class AkarinGlobalConfig {
|
|||||||
private static void fileIOThreads() {
|
private static void fileIOThreads() {
|
||||||
fileIOThreads = getInt("core.chunk-save-threads", 2);
|
fileIOThreads = getInt("core.chunk-save-threads", 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int parallelMode;
|
||||||
|
private static void parallelMode() {
|
||||||
|
parallelMode = getInt("core.parallel-mode", 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,11 +60,9 @@ public abstract class MixinMinecraftServer {
|
|||||||
shift = At.Shift.BEFORE
|
shift = At.Shift.BEFORE
|
||||||
))
|
))
|
||||||
private void prerun(CallbackInfo info) {
|
private void prerun(CallbackInfo info) {
|
||||||
Akari.STAGE_ENTITY_TICK = new ExecutorCompletionService<>(Executors.newFixedThreadPool((cachedWorldSize = worlds.size()), new AssignableFactory("Akarin Parallel Entity Ticking Thread - $")));
|
|
||||||
Akari.STAGE_WORLD_TICK = new ExecutorCompletionService<>(Executors.newFixedThreadPool(cachedWorldSize - 1, new AssignableFactory("Akarin Parallel World Ticking Thread - $")));
|
|
||||||
|
|
||||||
primaryThread.setPriority(AkarinGlobalConfig.primaryThreadPriority < Thread.NORM_PRIORITY ? Thread.NORM_PRIORITY :
|
primaryThread.setPriority(AkarinGlobalConfig.primaryThreadPriority < Thread.NORM_PRIORITY ? Thread.NORM_PRIORITY :
|
||||||
(AkarinGlobalConfig.primaryThreadPriority > Thread.MAX_PRIORITY ? 10 : AkarinGlobalConfig.primaryThreadPriority));
|
(AkarinGlobalConfig.primaryThreadPriority > Thread.MAX_PRIORITY ? 10 : AkarinGlobalConfig.primaryThreadPriority));
|
||||||
|
Akari.resizeTickExecutors((cachedWorldSize = worlds.size()));
|
||||||
|
|
||||||
for (int i = 0; i < worlds.size(); ++i) {
|
for (int i = 0; i < worlds.size(); ++i) {
|
||||||
WorldServer world = worlds.get(i);
|
WorldServer world = worlds.get(i);
|
||||||
@@ -157,7 +155,11 @@ public abstract class MixinMinecraftServer {
|
|||||||
|
|
||||||
private boolean tickEntities(WorldServer world) {
|
private boolean tickEntities(WorldServer world) {
|
||||||
try {
|
try {
|
||||||
|
world.timings.tickEntities.startTiming();
|
||||||
world.tickEntities();
|
world.tickEntities();
|
||||||
|
world.timings.tickEntities.stopTiming();
|
||||||
|
world.getTracker().updatePlayers();
|
||||||
|
world.explosionDensityCache.clear(); // Paper - Optimize explosions
|
||||||
} catch (Throwable throwable) {
|
} catch (Throwable throwable) {
|
||||||
CrashReport crashreport;
|
CrashReport crashreport;
|
||||||
try {
|
try {
|
||||||
@@ -173,7 +175,9 @@ public abstract class MixinMinecraftServer {
|
|||||||
|
|
||||||
private void tickWorld(WorldServer world) {
|
private void tickWorld(WorldServer world) {
|
||||||
try {
|
try {
|
||||||
|
world.timings.doTick.startTiming();
|
||||||
world.doTick();
|
world.doTick();
|
||||||
|
world.timings.doTick.stopTiming();
|
||||||
} catch (Throwable throwable) {
|
} catch (Throwable throwable) {
|
||||||
CrashReport crashreport;
|
CrashReport crashreport;
|
||||||
try {
|
try {
|
||||||
@@ -213,70 +217,87 @@ public abstract class MixinMinecraftServer {
|
|||||||
ChunkIOExecutor.tick();
|
ChunkIOExecutor.tick();
|
||||||
MinecraftTimings.chunkIOTickTimer.stopTiming();
|
MinecraftTimings.chunkIOTickTimer.stopTiming();
|
||||||
|
|
||||||
Akari.worldTiming.startTiming();
|
if (cachedWorldSize != worlds.size()) Akari.resizeTickExecutors((cachedWorldSize = worlds.size()));
|
||||||
// Resize
|
switch (AkarinGlobalConfig.parallelMode) {
|
||||||
if (cachedWorldSize != worlds.size()) {
|
case 1:
|
||||||
Akari.STAGE_ENTITY_TICK = new ExecutorCompletionService<>(Executors.newFixedThreadPool(cachedWorldSize = worlds.size(), new AssignableFactory("Akarin Parallel Entity Ticking Thread - $")));
|
case 2:
|
||||||
Akari.STAGE_WORLD_TICK = new ExecutorCompletionService<>(Executors.newFixedThreadPool(cachedWorldSize - 1, new AssignableFactory("Akarin Parallel World Ticking Thread - $")));
|
default:
|
||||||
}
|
|
||||||
tickedPrimaryEntities = false;
|
|
||||||
// Never tick one world concurrently!
|
// Never tick one world concurrently!
|
||||||
int worldSize = worlds.size();
|
for (int i = 0; i < cachedWorldSize; i++) {
|
||||||
for (int i = 0; i < worldSize; i++) {
|
|
||||||
// Impl Note:
|
// Impl Note:
|
||||||
// Entities ticking: index 2 -> ... -> 0 -> 1 (parallel)
|
// Entities ticking: index 1 -> ... -> 0 (parallel)
|
||||||
// World ticking: index 1 -> ... (parallel) | 0 (main thread)
|
// World ticking: index 0 -> ... (parallel)
|
||||||
int interlaceEntity = i + 2;
|
int interlace = i + 1;
|
||||||
WorldServer entityWorld = null;
|
WorldServer entityWorld = worlds.get(interlace < cachedWorldSize ? interlace : 0);
|
||||||
if (interlaceEntity < worldSize) {
|
Akari.STAGE_TICK.submit(() -> {
|
||||||
entityWorld = worlds.get(interlaceEntity);
|
synchronized (((IMixinWorldServer) entityWorld).lock()) {
|
||||||
} else {
|
tickEntities(entityWorld);
|
||||||
if (tickedPrimaryEntities) {
|
|
||||||
entityWorld = worlds.get(1);
|
|
||||||
} else {
|
|
||||||
entityWorld = worlds.get(0);
|
|
||||||
tickedPrimaryEntities = true;
|
|
||||||
}
|
}
|
||||||
}
|
}, null/*new TimingSignal(entityWorld, true)*/);
|
||||||
entityWorld.timings.tickEntities.startTiming();
|
|
||||||
WorldServer fEntityWorld = entityWorld;
|
|
||||||
Akari.STAGE_ENTITY_TICK.submit(() -> {
|
|
||||||
synchronized (((IMixinWorldServer) fEntityWorld).lock()) {
|
|
||||||
tickEntities(fEntityWorld);
|
|
||||||
fEntityWorld.getTracker().updatePlayers();
|
|
||||||
fEntityWorld.explosionDensityCache.clear(); // Paper - Optimize explosions
|
|
||||||
}
|
|
||||||
}, entityWorld);
|
|
||||||
|
|
||||||
int interlaceWorld = i + 1;
|
if (AkarinGlobalConfig.parallelMode != 1) {
|
||||||
if (interlaceWorld < worldSize) {
|
int fi = i;
|
||||||
WorldServer world = worlds.get(interlaceWorld);
|
Akari.STAGE_TICK.submit(() -> {
|
||||||
world.timings.doTick.startTiming();
|
WorldServer world = worlds.get(fi);
|
||||||
Akari.STAGE_WORLD_TICK.submit(() -> {
|
|
||||||
synchronized (((IMixinWorldServer) world).lock()) {
|
synchronized (((IMixinWorldServer) world).lock()) {
|
||||||
tickWorld(world);
|
tickWorld(world);
|
||||||
}
|
}
|
||||||
}, world);
|
}, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WorldServer primaryWorld = worlds.get(0);
|
if (AkarinGlobalConfig.parallelMode == 1)
|
||||||
primaryWorld.timings.doTick.startTiming();
|
Akari.STAGE_TICK.submit(() -> {
|
||||||
synchronized (((IMixinWorldServer) primaryWorld).lock()) {
|
for (int i = 0; i < cachedWorldSize; i++) {
|
||||||
tickWorld(primaryWorld);
|
|
||||||
}
|
|
||||||
primaryWorld.timings.doTick.stopTiming();
|
|
||||||
|
|
||||||
for (int i = 0; i < worldSize; i++) {
|
|
||||||
WorldServer world = worlds.get(i);
|
WorldServer world = worlds.get(i);
|
||||||
|
synchronized (((IMixinWorldServer) world).lock()) {
|
||||||
|
tickWorld(world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
|
|
||||||
|
for (int i = (AkarinGlobalConfig.parallelMode == 1 ? cachedWorldSize + 1 : cachedWorldSize * 2); i --> 0 ;) {
|
||||||
|
Akari.STAGE_TICK.take();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* for (int i = (AkarinGlobalConfig.parallelMode == 1 ? cachedWorldSize : cachedWorldSize * 2); i --> 0 ;) {
|
||||||
long startTiming = System.nanoTime();
|
long startTiming = System.nanoTime();
|
||||||
if (i != 0) {
|
TimingSignal signal = Akari.STAGE_TICK.take().get();
|
||||||
((TimingHandler) Akari.STAGE_WORLD_TICK.take().get().timings.doTick).stopTiming(startTiming);
|
IMixinTimingHandler timing = (IMixinTimingHandler) (signal.isEntities ? signal.tickedWorld.timings.tickEntities : signal.tickedWorld.timings.doTick);
|
||||||
startTiming = System.nanoTime();
|
timing.stopTiming(startTiming); // The overlap will be ignored
|
||||||
|
} */
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 0:
|
||||||
|
Akari.STAGE_TICK.submit(() -> {
|
||||||
|
for (int i = 1; i <= cachedWorldSize; ++i) {
|
||||||
|
WorldServer world = worlds.get(i < cachedWorldSize ? i : 0);
|
||||||
|
synchronized (((IMixinWorldServer) world).lock()) {
|
||||||
|
tickEntities(world);
|
||||||
}
|
}
|
||||||
((TimingHandler) Akari.STAGE_ENTITY_TICK.take().get().timings.tickEntities).stopTiming(startTiming);
|
|
||||||
}
|
}
|
||||||
Akari.worldTiming.stopTiming();
|
}, null);
|
||||||
|
|
||||||
|
Akari.STAGE_TICK.submit(() -> {
|
||||||
|
for (int i = 0; i < cachedWorldSize; ++i) {
|
||||||
|
WorldServer world = worlds.get(i);
|
||||||
|
synchronized (((IMixinWorldServer) world).lock()) {
|
||||||
|
tickWorld(world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
|
|
||||||
|
Akari.STAGE_TICK.take();
|
||||||
|
Akari.STAGE_TICK.take();
|
||||||
|
break;
|
||||||
|
case -1:
|
||||||
|
for (int i = 0; i < cachedWorldSize; ++i) {
|
||||||
|
WorldServer world = worlds.get(i);
|
||||||
|
tickWorld(world);
|
||||||
|
tickEntities(world);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
Akari.callbackTiming.startTiming();
|
Akari.callbackTiming.startTiming();
|
||||||
while ((runnable = Akari.callbackQueue.poll()) != null) runnable.run();
|
while ((runnable = Akari.callbackQueue.poll()) != null) runnable.run();
|
||||||
|
|||||||
@@ -1,52 +1,35 @@
|
|||||||
package io.akarin.server.mixin.core;
|
package io.akarin.server.mixin.core;
|
||||||
|
|
||||||
import java.util.logging.Level;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.spongepowered.asm.mixin.Final;
|
import org.spongepowered.asm.mixin.Final;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Overwrite;
|
import org.spongepowered.asm.mixin.Overwrite;
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
|
||||||
|
|
||||||
import co.aikar.timings.Timing;
|
import co.aikar.timings.Timing;
|
||||||
import co.aikar.timings.TimingHandler;
|
|
||||||
import io.akarin.api.internal.Akari;
|
|
||||||
import io.akarin.api.internal.Akari.AssignableThread;
|
|
||||||
import io.akarin.server.core.AkarinGlobalConfig;
|
|
||||||
import net.minecraft.server.MinecraftServer;
|
|
||||||
|
|
||||||
@Mixin(value = TimingHandler.class, remap = false)
|
@Mixin(targets = "co.aikar.timings.TimingHandler", remap = false)
|
||||||
public abstract class MixinTimingHandler {
|
public abstract class MixinTimingHandler {
|
||||||
@Shadow @Final String name;
|
@Shadow @Final String name;
|
||||||
@Shadow private boolean enabled;
|
@Shadow private boolean enabled;
|
||||||
@Shadow private volatile long start;
|
@Shadow private AtomicLong start;
|
||||||
@Shadow private volatile int timingDepth;
|
@Shadow private AtomicInteger timingDepth;
|
||||||
|
|
||||||
@Shadow abstract void addDiff(long diff);
|
@Shadow abstract void addDiff(long diff);
|
||||||
@Shadow public abstract Timing startTiming();
|
@Shadow public abstract Timing startTiming();
|
||||||
|
|
||||||
@Overwrite
|
@Overwrite
|
||||||
public Timing startTimingIfSync() {
|
public Timing startTimingIfSync() {
|
||||||
if (Akari.isPrimaryThread(false)) {
|
|
||||||
startTiming();
|
startTiming();
|
||||||
}
|
|
||||||
return (Timing) this;
|
return (Timing) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
|
||||||
@Inject(method = "startTiming", at = @At("HEAD"), cancellable = true)
|
|
||||||
public void onStartTiming(CallbackInfoReturnable cir) {
|
|
||||||
if (!Akari.isPrimaryThread(false)) cir.setReturnValue(this); // Avoid modify any field
|
|
||||||
}
|
|
||||||
|
|
||||||
@Overwrite
|
@Overwrite
|
||||||
public void stopTimingIfSync() {
|
public void stopTimingIfSync() {
|
||||||
if (Akari.isPrimaryThread(false)) {
|
//if (Akari.isPrimaryThread(false)) {
|
||||||
stopTiming(true); // Avoid twice thread check
|
stopTiming(true); // Avoid twice thread check
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Overwrite
|
@Overwrite
|
||||||
@@ -54,20 +37,26 @@ public abstract class MixinTimingHandler {
|
|||||||
stopTiming(false);
|
stopTiming(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stopTiming(boolean alreadySync) {
|
public void stopTiming(long start) {
|
||||||
Thread curThread = Thread.currentThread();
|
if (enabled) addDiff(System.nanoTime() - start);
|
||||||
if (!enabled || curThread instanceof AssignableThread) return;
|
}
|
||||||
if (!alreadySync && curThread != MinecraftServer.getServer().primaryThread) {
|
|
||||||
if (AkarinGlobalConfig.silentAsyncTimings) return;
|
|
||||||
|
|
||||||
|
public void stopTiming(boolean alreadySync) {
|
||||||
|
if (!enabled || timingDepth.decrementAndGet() != 0 || start.get() == 0) return;
|
||||||
|
/*if (!alreadySync) {
|
||||||
|
Thread curThread = Thread.currentThread();
|
||||||
|
if (curThread != MinecraftServer.getServer().primaryThread) {
|
||||||
|
if (false && !AkarinGlobalConfig.silentAsyncTimings) {
|
||||||
Bukkit.getLogger().log(Level.SEVERE, "stopTiming called async for " + name);
|
Bukkit.getLogger().log(Level.SEVERE, "stopTiming called async for " + name);
|
||||||
Thread.dumpStack();
|
Thread.dumpStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main thread ensured
|
|
||||||
if (--timingDepth == 0 && start != 0) {
|
|
||||||
addDiff(System.nanoTime() - start);
|
|
||||||
start = 0;
|
start = 0;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// Safety ensured
|
||||||
|
long prev = start.getAndSet(0); // Akarin
|
||||||
|
addDiff(System.nanoTime() - prev); // Akarin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user