blob: 6a4278fee69cdce0679c4ee7e24dd530fe3b55a6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.wayang.commons.util.profiledb.instrumentation;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.wayang.commons.util.profiledb.model.Experiment;
import org.apache.wayang.commons.util.profiledb.model.Measurement;
import org.apache.wayang.commons.util.profiledb.model.measurement.TimeMeasurement;
/**
* Utility to create {@link TimeMeasurement}s for an {@link Experiment}.
*/
public class StopWatch {
private final Map<String, TimeMeasurement> rounds = new LinkedHashMap<>();
private final Experiment experiment;
/**
* Creates a new instance for the given {@link Experiment}.
*
* @param experiment that should be instrumented
*/
public StopWatch(Experiment experiment) {
this.experiment = experiment;
for (Measurement measurement : this.experiment.getMeasurements()) {
if (measurement instanceof TimeMeasurement) {
this.rounds.put(measurement.getId(), (TimeMeasurement) measurement);
}
}
}
/**
* Retrieves a {@link TimeMeasurement} by its ID from {@link #rounds}. If it does not exist, it will be
* created and also registered with the {@link #experiment}.
*
* @param id the ID of the {@link TimeMeasurement}
* @param furtherIds IDs of further nested {@link TimeMeasurement}s
* @return the {@link TimeMeasurement}
*/
public TimeMeasurement getOrCreateRound(String id, String... furtherIds) {
TimeMeasurement round = this.rounds.get(id);
if (round == null) {
round = new TimeMeasurement(id);
this.rounds.put(round.getId(), round);
this.experiment.addMeasurement(round);
}
for (String furtherId : furtherIds) {
round = round.getOrCreateRound(furtherId);
}
return round;
}
/**
* Starts a {@link TimeMeasurement}. Any required parent {@link TimeMeasurement} will be started if necessary.
*
* @param id ID of the root {@link TimeMeasurement}
* @param furtherIds IDs of further nested {@link TimeMeasurement}s
* @return the (potentially new) {@link TimeMeasurement}
*/
public TimeMeasurement start(String id, String... furtherIds) {
return this.getOrCreateRound(id).start(furtherIds);
}
/**
* Stops a {@link TimeMeasurement} (if it exists).
*
* @param id ID of the root {@link TimeMeasurement}
* @param furtherIds IDs of further nested {@link TimeMeasurement}s
*/
public void stop(String id, String... furtherIds) {
final TimeMeasurement rootRound = this.rounds.get(id);
if (rootRound != null) {
rootRound.stop(furtherIds);
}
}
/**
* Formats the {@link TimeMeasurement}s to something easily readable.
*
* @return the "pretty" {@link String}
*/
public String toPrettyString() {
return this.toPrettyString(" ", "* ");
}
/**
* Formats the {@link TimeMeasurement}s to something easily readable.
*
* @param indent indent to be used when formatting sub-{@link TimeMeasurement}s
* @param bullet bullet point character
* @return the "pretty" {@link String}
*/
public String toPrettyString(String indent, String bullet) {
StringBuilder sb = new StringBuilder(1000);
int firstColumnWidth = this.determineFirstColumnWidth(this.rounds.values(), indent.length(), 0, bullet.length());
this.rounds.values().forEach(round -> this.append(round, indent, 0, bullet, firstColumnWidth, sb));
// Trim trailing newline.
if (sb.length() > 0 && sb.charAt(sb.length() - 1) == '\n') {
sb.setLength(sb.length() - 1);
}
return sb.toString();
}
private int determineFirstColumnWidth(Collection<TimeMeasurement> rounds, int indentWidth, int numIndents, int bulletWidth) {
return rounds.stream()
.map(round -> this.determineFirstColumnWidth(round, indentWidth, numIndents, bulletWidth))
.reduce(0, Math::max);
}
private int determineFirstColumnWidth(TimeMeasurement round, int indentWidth, int numIndents, int bulletWidth) {
return Math.max(
round.getId().length() + (numIndents * indentWidth) + bulletWidth,
this.determineFirstColumnWidth(round.getRounds(), indentWidth, numIndents + 1, bulletWidth)
);
}
private void append(TimeMeasurement round, String indent, int numIndents, String bullet, int firstColumnWidth, StringBuilder sb) {
int originalLength = sb.length();
for (int i = 0; i < numIndents; i++) {
sb.append(indent);
}
sb.append(bullet).append(round.getId());
while (sb.length() < originalLength + firstColumnWidth) sb.append(" ");
sb.append(" - ").append(TimeMeasurement.formatDuration(round.getMillis())).append('\n');
round.getRounds().forEach(subround -> this.append(subround, indent, numIndents + 1, bullet, firstColumnWidth, sb));
}
/**
* Stops all {@link TimeMeasurement}s known to this instance.
*/
public void stopAll() {
this.rounds.values().forEach(TimeMeasurement::stop);
}
/**
* Get the {@link Experiment} managed by this instance.
*
* @return the {@link Experiment}
*/
public Experiment getExperiment() {
return this.experiment;
}
}