Skip to content

Commit

Permalink
v1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
ethlo committed Aug 15, 2019
1 parent 830d8a0 commit 8e9e5b0
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 20 deletions.
6 changes: 3 additions & 3 deletions src/main/java/com/ethlo/time/Chronograph.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

Expand All @@ -54,7 +53,7 @@ public void start(String task)
throw new IllegalArgumentException("task cannot be null");
}

final TaskInfo taskTiming = taskInfos.computeIfAbsent(task, taskName->{
final TaskInfo taskTiming = taskInfos.computeIfAbsent(task, taskName -> {
order.add(taskName);
return new TaskInfo(taskName);
});
Expand All @@ -64,7 +63,7 @@ public void start(String task)
public void stop()
{
final long ts = System.nanoTime();
taskInfos.values().forEach(task->task.stopped(ts, true));
taskInfos.values().forEach(task -> task.stopped(ts, true));
}

public boolean isAnyRunning()
Expand Down Expand Up @@ -117,6 +116,7 @@ public boolean isRunning(String task)

/**
* See {@link Report#prettyPrint(Chronograph)}
*
* @return A formatted string with the task details
*/
public String prettyPrint()
Expand Down
104 changes: 104 additions & 0 deletions src/main/java/com/ethlo/time/DurationUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package com.ethlo.time;

/*-
* #%L
* chronograph
* %%
* Copyright (C) 2019 Morten Haraldsen (ethlo)
* %%
* Licensed 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.
* #L%
*/

import java.math.RoundingMode;
import java.text.NumberFormat;
import java.time.Duration;

public class DurationUtil
{
public static final int SECONDS_PER_HOUR = 3_600;
public static final int SECONDS_PER_MINUTE = 60;
public static final int NANOS_PER_MILLI = 1_000_000;
private static final int NANOS_PER_MICRO = 1_000;

public static String humanReadable(Duration duration)
{
final long seconds = duration.getSeconds();
final int hours = (int) seconds / SECONDS_PER_HOUR;
int remainder = (int) seconds - hours * SECONDS_PER_HOUR;
final int mins = remainder / SECONDS_PER_MINUTE;
remainder = remainder - mins * SECONDS_PER_MINUTE;
final int secs = remainder;

final long nanos = duration.getNano();
final int millis = (int) nanos / NANOS_PER_MILLI;
remainder = (int) nanos - millis * NANOS_PER_MILLI;
final int micros = remainder / NANOS_PER_MICRO;
remainder = remainder - micros * NANOS_PER_MICRO;
final int nano = remainder;

final NumberFormat nf = NumberFormat.getNumberInstance();
nf.setMinimumIntegerDigits(2);

final NumberFormat df = NumberFormat.getNumberInstance();
df.setMinimumFractionDigits(2);
df.setMaximumFractionDigits(2);
df.setRoundingMode(RoundingMode.HALF_UP);

final StringBuilder sb = new StringBuilder();
if (hours > 0)
{
sb.append(nf.format(hours)).append(":");
}
if (hours > 0 || mins > 0)
{
sb.append(nf.format(mins)).append(":");
}

final boolean hasMinuteOrMore = hours > 0 || mins > 0;
final boolean hasSecondOrMore = hasMinuteOrMore || secs > 0;
if (hasSecondOrMore && !hasMinuteOrMore)
{
final NumberFormat dfSec = NumberFormat.getNumberInstance();
dfSec.setMinimumFractionDigits(0);
dfSec.setMaximumFractionDigits(0);
dfSec.setMinimumIntegerDigits(3);
dfSec.setMaximumIntegerDigits(3);
sb.append(seconds).append('.').append(dfSec.format(nanos / (double) NANOS_PER_MILLI)).append("s");
}
else if (hasSecondOrMore)
{
sb.append(nf.format(secs)).append(".").append(millis);
}
else
{
// Sub-second
if (millis > 0)
{
sb.append(df.format(nanos / (double) NANOS_PER_MILLI)).append("m ");
}

if (millis == 0 && micros > 0)
{
sb.append(df.format(nanos / (double) NANOS_PER_MICRO)).append("μ ");
}

if (millis == 0 && micros == 0 && nano > 0)
{
sb.append(nano).append("n ");
}
}

return sb.toString().trim();
}
}
65 changes: 48 additions & 17 deletions src/main/java/com/ethlo/time/Report.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,13 @@ public class Report
public static String prettyPrint(Chronograph chronograph)
{
final StringBuilder sb = new StringBuilder();
sb.append("\n-------------------------------------------------------------------------------\n");
sb.append("| Task | Average | Total | Invocations | % | \n");
sb.append("-------------------------------------------------------------------------------\n");
sb.append("\n--------------------------------------------------------------------------------\n");
sb.append("| Task | Average | Total | Invocations | % | \n");
sb.append("--------------------------------------------------------------------------------\n");

final NumberFormat pf = NumberFormat.getPercentInstance();
pf.setMinimumFractionDigits(1);
pf.setMaximumFractionDigits(1);
pf.setMinimumIntegerDigits(2);
pf.setGroupingUsed(false);

final NumberFormat nf = NumberFormat.getNumberInstance();
Expand All @@ -53,32 +52,48 @@ public static String prettyPrint(Chronograph chronograph)
{
final TaskInfo task = chronograph.getTaskInfo(name);

final String totalTaskTimeStr = humanReadableFormat(Duration.ofNanos(task.getTotalTaskTime()));
final String avgTaskTimeStr = humanReadableFormat(task.getAverageTaskTime());
final String totalTaskTimeStr = DurationUtil.humanReadable(Duration.ofNanos(task.getTotalTaskTime()));
final String avgTaskTimeStr = DurationUtil.humanReadable(task.getAverageTaskTime());
final String invocationsStr = nf.format(task.getInvocationCount());

sb.append("| ");
sb.append(adjustWidth(task.getName(), 15)).append(" | ");
sb.append(adjustWidth(avgTaskTimeStr, 14)).append(" | ");
sb.append(adjustWidth(totalTaskTimeStr, 15)).append(" | ");
sb.append(adjustWidth(invocationsStr, 13)).append(" | ");
sb.append(adjustPadRight(task.getName(), 21)).append(" | ");
sb.append(adjustPadLeft(avgTaskTimeStr, 12)).append(" | ");
sb.append(adjustPadLeft(totalTaskTimeStr, 12)).append(" | ");
sb.append(adjustPadLeft(invocationsStr, 13)).append(" | ");
final Duration totalTime = chronograph.getTotalTime();
final double pct = totalTime.isZero() ? 0D : task.getTotalTaskTime() / (double) totalTime.toNanos();
sb.append(adjustWidth(pf.format(pct), 6)).append(" |");
sb.append(adjustPadLeft(pf.format(pct), 6)).append(" |");
sb.append("\n");
}

if (chronograph.getTaskNames().size() > 1)
{
sb.append(totals(chronograph));
}

return sb.toString();
}

private static String humanReadableFormat(Duration duration)
private static String totals(final Chronograph chronograph)
{
return repeat("-", 80) + "\n" +
"| " + adjustPadRight("Total" + ": " + DurationUtil.humanReadable(chronograph.getTotalTime()), 76) + " |" + "\n" +
repeat("-", 80) + "\n";
}

private static String repeat(final String s, final int count)
{
return duration.toString()
.substring(2)
.replaceAll("(\\d[HMS])(?!$)", "$1 ")
.toLowerCase();
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < count; i++)
{
sb.append(s);
}
return sb.toString();
}

private static String adjustWidth(final String s, final int width)

private static String adjustPadRight(final String s, final int width)
{
if (s.length() >= width)
{
Expand All @@ -92,4 +107,20 @@ private static String adjustWidth(final String s, final int width)
}
return new String(result);
}

private static String adjustPadLeft(final String s, final int width)
{
if (s.length() >= width)
{
return s.substring(0, width);
}

final char[] result = new char[width];
Arrays.fill(result, ' ');
for (int i = 0; i < s.length(); i++)
{
result[i + (width - s.length())] = s.charAt(i);
}
return new String(result);
}
}
43 changes: 43 additions & 0 deletions src/test/java/com/ethlo/time/ChronographTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,49 @@ public void resetAll()
assertThat(chronograph.getTaskNames()).isEmpty();
}

@Test
public void testIsRunning()
{
final Chronograph chronograph = Chronograph.create();
assertThat(chronograph.isRunning(taskName)).isFalse();
chronograph.start(taskName);
assertThat(chronograph.isRunning(taskName)).isTrue();
chronograph.stop(taskName);
assertThat(chronograph.isRunning(taskName)).isFalse();
}

@Test
public void getTaskInfo()
{
final Chronograph chronograph = Chronograph.create();
chronograph.start("a");
chronograph.start("b");
chronograph.start("c");
chronograph.stop();
assertThat(chronograph.getTaskInfo()).hasSize(3);
}

@Test
public void getTotalTaskTimeForEmpty()
{
final Chronograph chronograph = Chronograph.create();
assertThat(chronograph.getTotalTime()).isEqualTo(Duration.ZERO);
chronograph.start(taskName);
assertThat(chronograph.getTotalTime()).isEqualTo(Duration.ZERO);
chronograph.stop();
assertThat(chronograph.getTotalTime()).isNotEqualTo(Duration.ZERO);
}

@Test
public void testIsAnyRunning()
{
final Chronograph chronograph = Chronograph.create();
chronograph.start(taskName);
assertThat(chronograph.isAnyRunning()).isTrue();
chronograph.stop();
assertThat(chronograph.isAnyRunning()).isFalse();
}

private static final long SLEEP_PRECISION = TimeUnit.MILLISECONDS.toNanos(2);
private static final long SPIN_YIELD_PRECISION = TimeUnit.MILLISECONDS.toNanos(2);

Expand Down
78 changes: 78 additions & 0 deletions src/test/java/com/ethlo/time/DurationUtilTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.ethlo.time;

/*-
* #%L
* chronograph
* %%
* Copyright (C) 2019 Morten Haraldsen (ethlo)
* %%
* Licensed 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.
* #L%
*/

import static org.assertj.core.api.Assertions.assertThat;

import java.time.Duration;

import org.junit.Test;

public class DurationUtilTest
{
@Test
public void humanReadableFormatMoreThanHour()
{
assertThat(DurationUtil.humanReadable(Duration.ofSeconds(4712).withNanos(123456789))).isEqualTo("01:18:32.123");
}

@Test
public void humanReadableFormatLessThanHour()
{
assertThat(DurationUtil.humanReadable(Duration.ofSeconds(2000).withNanos(123456789))).isEqualTo("33:20.123");
}

@Test
public void humanReadableFormatLessThanMinute()
{
assertThat(DurationUtil.humanReadable(Duration.ofSeconds(8).withNanos(125_956_789))).isEqualTo("8.126s");
}

@Test
public void humanReadableFormatLessThanMinute2()
{
assertThat(DurationUtil.humanReadable(Duration.ofSeconds(8).withNanos(1_000_000))).isEqualTo("8.001s");
}

@Test
public void humanReadableFormatLessThanMinute3()
{
assertThat(DurationUtil.humanReadable(Duration.ofSeconds(8))).isEqualTo("8.000s");
}

@Test
public void humanReadableFormatLessThanSecond()
{
assertThat(DurationUtil.humanReadable(Duration.ofNanos(123_456_789))).isEqualTo("123.46m");
}

@Test
public void humanReadableFormatLessThanMillisecond()
{
assertThat(DurationUtil.humanReadable(Duration.ofNanos(456_789))).isEqualTo("456.79μ");
}

@Test
public void humanReadableFormatLessThanMicrosecond()
{
assertThat(DurationUtil.humanReadable(Duration.ofNanos(489))).isEqualTo("489n");
}
}

0 comments on commit 8e9e5b0

Please sign in to comment.