blob: c595a343955c8355789ddf018fa6ef45114aed4e [file] [log] [blame]
/*
* Copyright 2017 HugeGraph Authors
*
* 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.hugegraph.perf;
import java.util.List;
import java.util.function.BiFunction;
import org.apache.hugegraph.util.Log;
import org.slf4j.Logger;
import org.apache.hugegraph.testutil.Whitebox;
public final class NormalStopwatch implements Stopwatch {
private static final Logger LOG = Log.logger(Stopwatch.class);
private static final String MULTI_THREAD_ACCESS_ERROR =
"There may be multi-threaded access, ensure " +
"not call PerfUtil.profileSingleThread(true) when " +
"multithreading.";
private long lastStartTime = -1L;
private long times = 0L;
private long totalCost = 0L;
private long minCost = Long.MAX_VALUE;
private long maxCost = 0L;
private long totalSelfWasted = 0L;
private long totalChildrenWasted = -1L;
private long totalChildrenTimes = -1L;
private final String name;
private final Path parent;
private final Path id;
private final PerfUtil.FastMap<String, Stopwatch> children;
public NormalStopwatch(String name, Stopwatch parent) {
this(name, parent.id());
parent.child(name, this);
}
public NormalStopwatch(String name, Path parent) {
this.name = name;
this.parent = parent;
this.id = Stopwatch.id(parent, name);
this.children = new PerfUtil.FastMap<>();
}
@Override
public Path id() {
return this.id;
}
@Override
public String name() {
return this.name;
}
@Override
public Path parent() {
return this.parent;
}
@Override
public void lastStartTime(long startTime) {
this.lastStartTime = startTime;
}
@Override
public void startTime(long startTime) {
assert this.lastStartTime == -1L : MULTI_THREAD_ACCESS_ERROR;
this.times++;
this.lastStartTime = startTime;
long endTime = PerfUtil.now();
long wastedTime = endTime - startTime;
if (wastedTime <= 0L) {
wastedTime += eachStartWastedLost;
}
this.totalSelfWasted += wastedTime;
}
@Override
public void endTime(long startTime) {
assert startTime >= this.lastStartTime && this.lastStartTime != -1L :
MULTI_THREAD_ACCESS_ERROR;
long endTime = PerfUtil.now();
// The following code cost about 3ns~4ns
long wastedTime = endTime - startTime;
if (wastedTime <= 0L) {
wastedTime += eachEndWastedLost;
}
long cost = endTime - this.lastStartTime;
if (this.minCost > cost) {
this.minCost = cost;
}
if (this.maxCost < cost) {
this.maxCost = cost;
}
this.totalCost += cost;
this.totalSelfWasted += wastedTime;
this.lastStartTime = -1L;
}
@Override
public long times() {
return this.times;
}
@Override
public long totalCost() {
return this.totalCost;
}
@Override
public void totalCost(long totalCost) {
this.totalCost = totalCost;
}
@Override
public long minCost() {
return this.minCost;
}
@Override
public long maxCost() {
return this.maxCost;
}
@Override
public long totalTimes() {
if (this.totalChildrenTimes > 0L) {
return this.times + this.totalChildrenTimes;
}
return this.times;
}
@Override
public long totalChildrenTimes() {
return this.totalChildrenTimes;
}
@Override
public long totalWasted() {
if (this.totalChildrenWasted > 0L) {
return this.totalSelfWasted + this.totalChildrenWasted;
}
return this.totalSelfWasted;
}
@Override
public long totalSelfWasted() {
return this.totalSelfWasted;
}
@Override
public long totalChildrenWasted() {
return this.totalChildrenWasted;
}
@Override
public void fillChildrenTotal(List<Stopwatch> children) {
// Fill total wasted cost of children
this.totalChildrenWasted = children.stream().mapToLong(Stopwatch::totalWasted).sum();
// Fill total times of children
this.totalChildrenTimes = children.stream().mapToLong(Stopwatch::totalTimes).sum();
}
@Override
public Stopwatch copy() {
try {
return (Stopwatch) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
@Override
public Stopwatch child(String name) {
return this.children.get(name);
}
@Override
public Stopwatch child(String name, Stopwatch watch) {
if (watch == null) {
return this.children.remove(name);
}
return this.children.put(name, watch);
}
@Override
public boolean empty() {
return this.children.size() == 0;
}
@Override
public void clear() {
this.lastStartTime = -1L;
this.times = 0L;
this.totalCost = 0L;
this.minCost = Long.MAX_VALUE;
this.maxCost = 0L;
this.totalSelfWasted = 0L;
this.totalChildrenWasted = -1L;
this.totalChildrenTimes = -1L;
this.children.clear();
}
@Override
public String toString() {
return String.format("{parent:%s,name:%s," +
"times:%s,totalChildrenTimes:%s," +
"totalCost:%s,minCost:%s,maxCost:%s," +
"totalSelfWasted:%s,totalChildrenWasted:%s}",
this.parent, this.name,
this.times, this.totalChildrenTimes,
this.totalCost, this.minCost, this.maxCost,
this.totalSelfWasted, this.totalChildrenWasted);
}
private static long eachStartWastedLost = 0L;
private static long eachEndWastedLost = 0L;
protected static void initEachWastedLost() {
int times = 100000000;
PerfUtil.LocalStack<Stopwatch> callStack = Whitebox.getInternalState(
PerfUtil.instance(), "callStack");
long baseStart = PerfUtil.now();
for (int i = 0; i < times; i++) {
PerfUtil.instance();
}
long baseCost = PerfUtil.now() - baseStart;
BiFunction<String, Runnable, Long> testEachCost = (name, test) -> {
long start = PerfUtil.now();
test.run();
long end = PerfUtil.now();
long cost = end - start - baseCost;
if (cost < 0L) {
cost = 0L;
}
long eachCost = cost / times;
LOG.info("Wasted time test: cost={}ms, base_cost={}ms, {}={}ns",
cost / 1000000.0, baseCost / 1000000.0, name, eachCost);
return eachCost;
};
String startName = "each_start_cost";
eachStartWastedLost = testEachCost.apply(startName, () -> {
Stopwatch watch = PerfUtil.instance().start(startName);
PerfUtil.instance().end(startName);
for (int i = 0; i < times; i++) {
// Test call start()
PerfUtil.instance().start(startName);
// Mock end()
watch.lastStartTime(-1L);
callStack.pop();
}
});
String endName = "each_end_cost";
eachEndWastedLost = testEachCost.apply(endName, () -> {
Stopwatch watch = PerfUtil.instance().start(endName);
PerfUtil.instance().end(endName);
for (int i = 0; i < times; i++) {
// Mock start()
callStack.push(watch);
watch.lastStartTime(0L);
// Test call start()
PerfUtil.instance().end(endName);
watch.totalCost(0L);
}
});
}
}