| /* |
| * 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; |
| } |
| } |