Skip to content

Commit

Permalink
Add support for json output, improve EXAMPLE.lua.
Browse files Browse the repository at this point in the history
Signed-off-by: Ross Allan <rallanpcl@gmail.com>
  • Loading branch information
LunNova committed May 30, 2013
1 parent 0f92f0a commit bc9df61
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 43 deletions.
19 changes: 13 additions & 6 deletions EXAMPLE.lua
Expand Up @@ -8,6 +8,7 @@ else
end

function explode(inSplitPattern, str)
str = str .. ""
local outResults = { }
local theStart = 1
local theSplitStart, theSplitEnd = string.find( str, inSplitPattern, theStart )
Expand Down Expand Up @@ -45,16 +46,22 @@ function profile()
local file = fs.open("profile.txt", "r")
local text = file.readAll()
file.close()
local tables = explode("\n\n", text)
local tables = explode("\n\n", string.gsub(text, "\r\n", "\n"))
term.clear()
local i, j
for i = 1, #tables do
lines = explode("\n", tables[i] .. "")
for j = 1, #lines do
printColouredBars(lines[j] .. "\n", j == 1)
end
if i ~= #tables then
io.write("\n")
if #lines == 1 then
term.setTextColor(colors.green)
print(lines[1])
term.setTextColor(colors.white)
else
for j = 1, #lines do
printColouredBars(lines[j] .. "\n", j == 1)
end
if i ~= #tables then
io.write("\n")
end
end
end
end
Expand Down
4 changes: 4 additions & 0 deletions build.xml
Expand Up @@ -96,6 +96,8 @@
<artifact:dependencies filesetId="dependency.fileset"
versionsId="dependency.versions">
<artifact:dependency groupId="com.github.stephenc.high-scale-lib" artifactId="high-scale-lib" version="1.1.4"/>
<artifact:dependency groupId="org.codehaus.jackson" artifactId="jackson-core-lgpl" version="1.9.12"/>
<artifact:dependency groupId="org.codehaus.jackson" artifactId="jackson-mapper-lgpl" version="1.9.12"/>
</artifact:dependencies>
<copy todir="${lib.dir}">
<fileset refid="dependency.fileset"/>
Expand Down Expand Up @@ -198,6 +200,8 @@

<target name="packageDependencies">
<unzip dest="${classes.dir}/" src="${lib.dir}/high-scale-lib.jar"/>
<unzip dest="${classes.dir}/" src="${lib.dir}/jackson-core-lgpl.jar"/>
<unzip dest="${classes.dir}/" src="${lib.dir}/jackson-mapper-lgpl.jar"/>
<delete file="${classes.dir}/net/minecraft/server/MinecraftServer.class"/>
<delete dir="${classes.dir}/META-INF"/>
<delete dir="${classes.dir}/net/minecraftforge"/>
Expand Down
14 changes: 11 additions & 3 deletions src/common/me/nallar/tickprofiler/minecraft/TickProfiler.java
Expand Up @@ -55,6 +55,7 @@ public class TickProfiler {
public boolean requireOpForDumpCommand = true;
private int profilingInterval = 0;
private String profilingFileName = "world/computer/<computer id>/profile.txt";
private boolean profilingJson = false;

static {
new Metrics("TickProfiler", VersionUtil.versionNumber());
Expand All @@ -67,7 +68,7 @@ public TickProfiler() {
@Mod.Init
public void init(FMLInitializationEvent event) {
MinecraftForge.EVENT_BUS.register(this);
TickRegistry.registerScheduledTickHandler(new ProfilingScheduledTickHandler(profilingInterval, new File(".", profilingFileName)), Side.SERVER);
TickRegistry.registerScheduledTickHandler(new ProfilingScheduledTickHandler(profilingInterval, new File(".", profilingFileName), profilingJson), Side.SERVER);
}

@SuppressWarnings ("FieldRepeatedlyAccessedInMethod")
Expand All @@ -83,6 +84,7 @@ public void preInit(FMLPreInitializationEvent event) {
requireOpForDumpCommand = config.get(GENERAL, "requireOpForProfileCommand", requireOpForDumpCommand, "If a player must be opped to use /dump").getBoolean(requireOpForDumpCommand);
profilingInterval = config.get(GENERAL, "profilingInterval", profilingInterval, "Interval, in minutes, to record profiling information to disk. 0 = never. Recommended >= 2.").getInt();
profilingFileName = config.get(GENERAL, "profilingFileName", profilingFileName, "Location to store profiling information to, relative to the server folder. For example, why not store it in a computercraft computer's folder?").value;
profilingJson = config.get(GENERAL, "profilingJson", profilingJson, "Whether to write periodic profiling in JSON format").getBoolean(profilingJson);
config.save();
PacketCount.allowCounting = false;
}
Expand Down Expand Up @@ -154,11 +156,13 @@ private static class ProfilingScheduledTickHandler implements IScheduledTickHand
private static final EnumSet<TickType> TICKS = EnumSet.of(TickType.SERVER);
private final int profilingInterval;
private final File profilingFile;
private final boolean json;
private int counter = 0;

public ProfilingScheduledTickHandler(final int profilingInterval, final File profilingFile) {
public ProfilingScheduledTickHandler(final int profilingInterval, final File profilingFile, final boolean json) {
this.profilingInterval = profilingInterval;
this.profilingFile = profilingFile;
this.json = json;
}

@Override
Expand All @@ -180,7 +184,11 @@ public void run() {
try {
TableFormatter tf = new TableFormatter(MinecraftServer.getServer());
tf.tableSeparator = "\n";
Files.write(entityTickProfiler.writeData(tf).toString(), profilingFile, Charsets.UTF_8);
if (json) {
entityTickProfiler.writeJSONData(profilingFile);
} else {
Files.write(entityTickProfiler.writeStringData(tf, 6).toString(), profilingFile, Charsets.UTF_8);
}
} catch (Throwable t) {
Log.severe("Failed to save periodic profiling data to " + profilingFile, t);
}
Expand Down
Expand Up @@ -69,7 +69,7 @@ public void processCommand(final ICommandSender commandSender, List<String> argu
if (!entityTickProfiler.startProfiling(new Runnable() {
@Override
public void run() {
sendChat(commandSender, entityTickProfiler.writeData(new TableFormatter(commandSender)).toString());
sendChat(commandSender, entityTickProfiler.writeStringData(new TableFormatter(commandSender)).toString());
}
}, location ? ProfilingState.CHUNK : ProfilingState.GLOBAL, time, worlds)) {
sendChat(commandSender, "Someone else is currently profiling.");
Expand Down
Expand Up @@ -13,7 +13,6 @@ public LoadedEntityList(World world, Field overriddenField) {

@Override
public void tick() {
EntityTickProfiler.ENTITY_TICK_PROFILER.tick();
EntityTickProfiler.ENTITY_TICK_PROFILER.runEntities(world, innerList);
}
}
@@ -1,5 +1,7 @@
package me.nallar.tickprofiler.minecraft.profiling;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
Expand All @@ -16,15 +18,18 @@
import me.nallar.tickprofiler.minecraft.commands.ProfileCommand;
import me.nallar.tickprofiler.util.MappingUtil;
import me.nallar.tickprofiler.util.TableFormatter;
import me.nallar.tickprofiler.util.stringfillers.StringFiller;
import net.minecraft.crash.CrashReport;
import net.minecraft.crash.CrashReportCategory;
import net.minecraft.entity.Entity;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ReportedException;
import net.minecraft.world.World;
import net.minecraft.world.WorldProvider;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.IChunkProvider;
import org.cliffc.high_scale_lib.NonBlockingHashMap;
import org.codehaus.jackson.map.ObjectMapper;

public class EntityTickProfiler {
public static final EntityTickProfiler ENTITY_TICK_PROFILER = new EntityTickProfiler();
Expand Down Expand Up @@ -204,7 +209,7 @@ public void clear() {
invocationCount.clear();
time.clear();
totalTime.set(0);
singleTime.clear();
map.clear();
singleInvocationCount.clear();
ticks = 0;
}
Expand All @@ -215,29 +220,50 @@ public void tick() {
}
}

public TableFormatter writeData(TableFormatter tf) {
public void writeJSONData(File file) throws IOException {
TableFormatter tf = new TableFormatter(StringFiller.FIXED_WIDTH);
tf.recordTables();
writeData(tf, 20);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.writeValue(file, tf.getTables());
}

private static <T> List<T> sortedKeys(Map<T, ? extends Comparable<?>> map, int elements) {
List<T> list = Ordering.natural().reverse().onResultOf(Functions.forMap(map)).immutableSortedCopy(map.keySet());
return list.size() > elements ? list.subList(0, elements) : list;
}

public TableFormatter writeStringData(TableFormatter tf) {
return writeStringData(tf, 5);
}

public TableFormatter writeStringData(TableFormatter tf, int elements) {
long timeProfiled = System.currentTimeMillis() - startTime;
float tps = ticks * 1000f / timeProfiled;
tf.sb.append("TPS: ").append(tps).append('\n').append(tf.tableSeparator);
return writeData(tf, elements);
}

public TableFormatter writeData(TableFormatter tf, int elements) {
Map<Class<?>, Long> time = new HashMap<Class<?>, Long>();
for (Map.Entry<Class<?>, AtomicLong> entry : this.time.entrySet()) {
time.put(entry.getKey(), entry.getValue().get());
}
Map<Object, Long> singleTime = new HashMap<Object, Long>();
for (Map.Entry<Object, AtomicLong> entry : this.singleTime.entrySet()) {
for (Map.Entry<Object, AtomicLong> entry : this.map.entrySet()) {
singleTime.put(entry.getKey(), entry.getValue().get());
}
double totalTime = this.totalTime.get();
final List<Object> sortedSingleKeysByTime = Ordering.natural().reverse().onResultOf(Functions.forMap(singleTime)).immutableSortedCopy(singleTime.keySet());
tf
.heading("Single Entity")
.heading("Time/Tick")
.heading("%");
for (int i = 0; i < 5 && i < sortedSingleKeysByTime.size(); i++) {
final List<Object> sortedSingleKeysByTime = sortedKeys(singleTime, elements);
for (Object o : sortedSingleKeysByTime) {
tf
.row(niceName(sortedSingleKeysByTime.get(i)))
.row(singleTime.get(sortedSingleKeysByTime.get(i)) / (1000000d * singleInvocationCount.get(sortedSingleKeysByTime.get(i)).get()))
.row((singleTime.get(sortedSingleKeysByTime.get(i)) / totalTime) * 100);
.row(niceName(o))
.row(singleTime.get(o) / (1000000d * singleInvocationCount.get(o).get()))
.row((singleTime.get(o) / totalTime) * 100);
}
tf.finishTable();
tf.sb.append('\n');
Expand All @@ -253,9 +279,10 @@ public ComparableLongHolder get(Object key_) {
return value;
}
};
for (Object o : sortedSingleKeysByTime) {
for (final Map.Entry<Object, Long> singleTimeEntry : singleTime.entrySet()) {
int x = Integer.MIN_VALUE;
int z = Integer.MIN_VALUE;
Object o = singleTimeEntry.getKey();
if (o instanceof Entity) {
x = ((Entity) o).chunkCoordX;
z = ((Entity) o).chunkCoordZ;
Expand All @@ -264,61 +291,67 @@ public ComparableLongHolder get(Object key_) {
z = ((TileEntity) o).zCoord >> 4;
}
if (x != Integer.MIN_VALUE) {
chunkTimeMap.get(new ChunkCoords(x, z)).value += singleTime.get(o);
chunkTimeMap.get(new ChunkCoords(x, z)).value += singleTimeEntry.getValue();
}
}
final List<ChunkCoords> sortedChunkCoordsByTime = Ordering.natural().reverse().onResultOf(Functions.forMap(chunkTimeMap)).immutableSortedCopy(chunkTimeMap.keySet());
tf
.heading("Chunk")
.heading("Time/Tick")
.heading("%");
for (int i = 0; i < 5 && i < sortedChunkCoordsByTime.size(); i++) {
ChunkCoords chunkCoordIntPair = sortedChunkCoordsByTime.get(i);
long chunkTime = chunkTimeMap.get(chunkCoordIntPair).value;
for (ChunkCoords chunkCoords : sortedKeys(chunkTimeMap, elements)) {
long chunkTime = chunkTimeMap.get(chunkCoords).value;
tf
.row(chunkCoordIntPair.chunkXPos + ", " + chunkCoordIntPair.chunkZPos)
.row(chunkCoords.chunkXPos + ", " + chunkCoords.chunkZPos)
.row(chunkTime / (1000000d * ticks))
.row((chunkTime / totalTime) * 100);
}
tf.finishTable();
tf.sb.append('\n');
final List<Class<?>> sortedKeysByTime = Ordering.natural().reverse().onResultOf(Functions.forMap(time)).immutableSortedCopy(time.keySet());
tf
.heading("All Entities of Type")
.heading("Time/Tick")
.heading("%");
for (int i = 0; i < 5 && i < sortedKeysByTime.size(); i++) {
for (Class c : sortedKeys(time, elements)) {
tf
.row(niceName(sortedKeysByTime.get(i)))
.row(time.get(sortedKeysByTime.get(i)) / (1000000d * ticks))
.row((time.get(sortedKeysByTime.get(i)) / totalTime) * 100);
.row(niceName(c))
.row(time.get(c) / (1000000d * ticks))
.row((time.get(c) / totalTime) * 100);
}
tf.finishTable();
tf.sb.append('\n');
Map<Class<?>, Long> timePerTick = new HashMap<Class<?>, Long>();
for (Map.Entry<Class<?>, AtomicLong> entry : this.time.entrySet()) {
timePerTick.put(entry.getKey(), entry.getValue().get() / invocationCount.get(entry.getKey()).get());
}
final List<Class<?>> sortedKeysByTimePerTick = Ordering.natural().reverse().onResultOf(Functions.forMap(timePerTick)).immutableSortedCopy(timePerTick.keySet());
tf
.heading("Average Entity of Type")
.heading("Time/tick")
.heading("Calls");
for (int i = 0; i < 5 && i < sortedKeysByTimePerTick.size(); i++) {
for (Class c : sortedKeys(timePerTick, elements)) {
tf
.row(niceName(sortedKeysByTimePerTick.get(i)))
.row(timePerTick.get(sortedKeysByTimePerTick.get(i)) / 1000000d)
.row(invocationCount.get(sortedKeysByTimePerTick.get(i)));
.row(niceName(c))
.row(timePerTick.get(c) / 1000000d)
.row(invocationCount.get(c));
}
tf.finishTable();
return tf;
}

private static int getDimension(TileEntity o) {
WorldProvider worldProvider = o.worldObj.provider;
return worldProvider == null ? -999 : worldProvider.dimensionId;
}

private static int getDimension(Entity o) {
WorldProvider worldProvider = o.worldObj.provider;
return worldProvider == null ? -999 : worldProvider.dimensionId;
}

private static Object niceName(Object o) {
if (o instanceof TileEntity) {
return niceName(o.getClass()) + ' ' + ((TileEntity) o).xCoord + ',' + ((TileEntity) o).yCoord + ',' + ((TileEntity) o).zCoord;
return niceName(o.getClass()) + ' ' + ((TileEntity) o).xCoord + ',' + ((TileEntity) o).yCoord + ',' + ((TileEntity) o).zCoord + ':' + getDimension((TileEntity) o);
} else if (o instanceof Entity) {
return niceName(o.getClass()) + ' ' + (int) ((Entity) o).posX + ',' + (int) ((Entity) o).posY + ',' + (int) ((Entity) o).posZ;
return niceName(o.getClass()) + ' ' + (int) ((Entity) o).posX + ',' + (int) ((Entity) o).posY + ',' + (int) ((Entity) o).posZ + ':' + getDimension((Entity) o);
}
return o.toString().substring(0, 48);
}
Expand All @@ -338,7 +371,7 @@ private static String niceName(Class<?> clazz) {

private final Map<Class<?>, AtomicInteger> invocationCount = new NonBlockingHashMap<Class<?>, AtomicInteger>();
private final Map<Class<?>, AtomicLong> time = new NonBlockingHashMap<Class<?>, AtomicLong>();
private final Map<Object, AtomicLong> singleTime = new NonBlockingHashMap<Object, AtomicLong>();
private final Map<Object, AtomicLong> map = new NonBlockingHashMap<Object, AtomicLong>();
private final Map<Object, AtomicLong> singleInvocationCount = new NonBlockingHashMap<Object, AtomicLong>();

private AtomicLong getSingleInvocationCount(Object o) {
Expand Down Expand Up @@ -373,13 +406,13 @@ private AtomicInteger getInvocationCount(Class<?> clazz) {
}

private AtomicLong getSingleTime(Object o) {
AtomicLong t = singleTime.get(o);
AtomicLong t = map.get(o);
if (t == null) {
synchronized (o) {
t = singleTime.get(o);
t = map.get(o);
if (t == null) {
t = new AtomicLong();
singleTime.put(o, t);
map.put(o, t);
}
}
}
Expand Down
1 change: 0 additions & 1 deletion src/common/me/nallar/tickprofiler/reporting/Metrics.java
Expand Up @@ -53,7 +53,6 @@
import me.nallar.tickprofiler.Log;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;
import net.minecraftforge.common.Configuration;
import net.minecraftforge.common.DimensionManager;

Expand Down

0 comments on commit bc9df61

Please sign in to comment.