| /* |
| * 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.tinkerpop.gremlin.process.traversal.util; |
| |
| import org.apache.commons.lang.StringUtils; |
| |
| import java.io.Serializable; |
| import java.util.*; |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * @author Bob Briody (http://bobbriody.com) |
| */ |
| public final class StandardTraversalMetrics implements TraversalMetrics, Serializable { |
| // toString() specific headers |
| private static final String[] HEADERS = {"Step", "Count", "Traversers", "Time (ms)", "% Dur"}; |
| |
| private boolean dirty = true; |
| private final Map<String, MutableMetrics> metrics = new HashMap<>(); |
| private final Map<String, MutableMetrics> allMetrics = new HashMap<>(); |
| private final TreeMap<Integer, String> indexToLabelMap = new TreeMap<>(); |
| |
| /* |
| The following are computed values upon the completion of profiling in order to report the results back to the user |
| */ |
| private long totalStepDuration; |
| private Map<String, ImmutableMetrics> computedMetrics; |
| |
| public StandardTraversalMetrics() { |
| } |
| |
| public void start(final String metricsId) { |
| dirty = true; |
| allMetrics.get(metricsId).start(); |
| } |
| |
| public void stop(final String metricsId) { |
| dirty = true; |
| allMetrics.get(metricsId).stop(); |
| } |
| |
| public void finish(final String metricsId, final long bulk) { |
| dirty = true; |
| final MutableMetrics metrics = allMetrics.get(metricsId); |
| metrics.stop(); |
| metrics.incrementCount(TRAVERSER_COUNT_ID, 1); |
| metrics.incrementCount(ELEMENT_COUNT_ID, bulk); |
| } |
| |
| @Override |
| public long getDuration(final TimeUnit unit) { |
| computeTotals(); |
| return unit.convert(totalStepDuration, MutableMetrics.SOURCE_UNIT); |
| } |
| |
| @Override |
| public Metrics getMetrics(final int index) { |
| computeTotals(); |
| // adjust index to account for the injected profile steps |
| return (Metrics) computedMetrics.get(indexToLabelMap.get(index)); |
| } |
| |
| @Override |
| public Metrics getMetrics(final String id) { |
| computeTotals(); |
| return computedMetrics.get(id); |
| } |
| |
| @Override |
| public Collection<ImmutableMetrics> getMetrics() { |
| computeTotals(); |
| return computedMetrics.values(); |
| } |
| |
| @Override |
| public String toString() { |
| computeTotals(); |
| |
| // Build a pretty table of metrics data. |
| |
| // Append headers |
| final StringBuilder sb = new StringBuilder("Traversal Metrics\n") |
| .append(String.format("%-50s %21s %11s %15s %8s", HEADERS)); |
| |
| sb.append("\n============================================================================================================="); |
| |
| appendMetrics(computedMetrics.values(), sb, 0); |
| |
| // Append total duration |
| sb.append(String.format("%n%50s %21s %11s %15.3f %8s", |
| ">TOTAL", "-", "-", getDuration(TimeUnit.MICROSECONDS) / 1000.0, "-")); |
| |
| return sb.toString(); |
| } |
| |
| private void appendMetrics(final Collection<? extends Metrics> metrics, final StringBuilder sb, final int indent) { |
| // Append each StepMetric's row. indexToLabelMap values are ordered by index. |
| for (Metrics m : metrics) { |
| String rowName = m.getName(); |
| |
| // Handle indentation |
| for (int ii = 0; ii < indent; ii++) { |
| rowName = " " + rowName; |
| } |
| // Abbreviate if necessary |
| rowName = StringUtils.abbreviate(rowName, 50); |
| |
| // Grab the values |
| final Long itemCount = m.getCount(ELEMENT_COUNT_ID); |
| final Long traverserCount = m.getCount(TRAVERSER_COUNT_ID); |
| Double percentDur = (Double) m.getAnnotation(PERCENT_DURATION_KEY); |
| |
| // Build the row string |
| |
| sb.append(String.format("%n%-50s", rowName)); |
| |
| if (itemCount != null) { |
| sb.append(String.format(" %21d", itemCount)); |
| } else { |
| sb.append(String.format(" %21s", "")); |
| } |
| |
| if (traverserCount != null) { |
| sb.append(String.format(" %11d", traverserCount)); |
| } else { |
| sb.append(String.format(" %11s", "")); |
| } |
| |
| sb.append(String.format(" %15.3f", m.getDuration(TimeUnit.MICROSECONDS) / 1000.0)); |
| |
| if (percentDur!=null){ |
| sb.append(String.format(" %8.2f", percentDur )); |
| } |
| |
| appendMetrics(m.getNested(), sb, indent + 1); |
| } |
| } |
| |
| private void computeTotals() { |
| if (!dirty) { |
| // already good to go |
| return; |
| } |
| |
| // Create temp list of ordered metrics |
| List<MutableMetrics> tempMetrics = new ArrayList<>(metrics.size()); |
| for (String label : indexToLabelMap.values()) { |
| // The indexToLabelMap is sorted by index (key) |
| tempMetrics.add(metrics.get(label).clone()); |
| } |
| |
| // Calculate total duration |
| this.totalStepDuration = 0; |
| tempMetrics.forEach(m -> this.totalStepDuration += m.getDuration(MutableMetrics.SOURCE_UNIT)); |
| |
| // Assign %'s |
| tempMetrics.forEach(m -> { |
| double dur = m.getDuration(TimeUnit.NANOSECONDS) * 100.d / this.totalStepDuration; |
| m.setAnnotation(PERCENT_DURATION_KEY, dur); |
| }); |
| |
| // Store immutable instances of the calculated metrics |
| computedMetrics = new LinkedHashMap<>(metrics.size()); |
| tempMetrics.forEach(it -> computedMetrics.put(it.getId(), it.getImmutableClone())); |
| |
| dirty = false; |
| } |
| |
| public static StandardTraversalMetrics merge(final Iterator<StandardTraversalMetrics> toMerge) { |
| final StandardTraversalMetrics newTraversalMetrics = new StandardTraversalMetrics(); |
| |
| // iterate the incoming TraversalMetrics |
| toMerge.forEachRemaining(inTraversalMetrics -> { |
| // aggregate the internal Metrics |
| inTraversalMetrics.metrics.forEach((metricsId, toAggregate) -> { |
| |
| MutableMetrics aggregateMetrics = newTraversalMetrics.metrics.get(metricsId); |
| if (null == aggregateMetrics) { |
| // need to create a Metrics to aggregate into |
| aggregateMetrics = new MutableMetrics(toAggregate.getId(), toAggregate.getName()); |
| |
| newTraversalMetrics.metrics.put(metricsId, aggregateMetrics); |
| // Set the index of the Metrics |
| for (Map.Entry<Integer, String> entry : inTraversalMetrics.indexToLabelMap.entrySet()) { |
| if (metricsId.equals(entry.getValue())) { |
| newTraversalMetrics.indexToLabelMap.put(entry.getKey(), metricsId); |
| break; |
| } |
| } |
| } |
| aggregateMetrics.aggregate(toAggregate); |
| }); |
| }); |
| return newTraversalMetrics; |
| } |
| |
| public void addMetrics(final MutableMetrics newMetrics, final String id, final int index, final boolean isTopLevel, final String profileStepId) { |
| if (isTopLevel) { |
| // The index is necessary to ensure that step order is preserved after a merge. |
| indexToLabelMap.put(index, id); |
| metrics.put(id, newMetrics); |
| } |
| allMetrics.put(profileStepId, newMetrics); |
| } |
| } |