Preface
During the development process, performance monitoring and debugging are problems we often face.
While there are many mature performance monitoring tools available on the market, sometimes we need a lightweight, flexible and elegant solution.
Of course, you can also manually track it in the business code yourself, such as recording the startTime first, and then subtracting the startTime after the execution is completed, and then calculating the time-consuming.
But after all, a lot of duplicate code will be created.
This article will introduce how to design and implement a simple and elegant TimeTracker tool class. It not only meets basic performance tracking needs, but also supports functional interfaces, try-with-resources and other calling mechanisms.
The initial pain points
Remember how we recorded the code execution time? There are codes like this everywhere:
long start = (); try { // Business logic} finally { // Calculation time}
Every time I have to write such repetitive and long-winded code, or I have to copy and paste it, which is easy to miss it. CV method is good, but lazy people always want a lazy way.
Evolution: Embrace try-with-resources
By chance, I thought of the AutoCloseable interface, and then every time I process the stream, I try a packet in it and don’t have to care about anything. Can I also handle the execution time in this way?
Imagine if I could write like this, wouldn’t it be very elegant:
try (TimeTracker ignored = new TimeTracker("Database Operation")) { // Business code, time-consuming and automatic processing!}
In an instant, the code became much more refreshing! Automatic resource management, time-consuming and automatic calculation, the gospel is not!
Do it right away, create a new oneTimeTracker
Class, implementationAutoCloseable
, a simple trick, the key point is,close()
Medium calculations are time-consuming and fully automated. So the first edition was found.
Of course, this is the beginning.
Pro: Functional interface
But, can you be lazy? sure!
Try the functional interface!
For example, the following is:
("User Query", () -> { return (123); });
I don't even have to write try! Isn't it very good to do performance monitoring with one line of code? This is the question!
What? You said this is obviously 3 lines?
So what if I write like this?
("operate", () -> riskyMethod());
Now there is nothing wrong.
If you want to return a value, it is also very simple. Just write it like this:
String result = ("Simple Task", () -> { (1000); return "Finish"; });
It is no difference from ordinary calls, and there is no mental burden.
Pro Max: Exception handling
Although it is done in one line now, there is a key function missing, that is, exception handling.
The standard for considering whether a programmer is powerful is never how high-level code he can write, and he has rich development experience and strong problem-tracking ability.
Because how can exception handling be missing here?
In the above version, no exception is involved, because.track()
Digested and repackagedRuntimeException
。
public static <T> T track(String operationName, ThrowableSupplier<T> execution) { try { return trackThrows(operationName, execution); } catch (Exception e) { throw new RuntimeException("Execution failed: " + operationName, e); } }
Considering that different scenarios have different requirements for exception handling, an additional mode must be provided to allow the caller to explicitly perform exception handling and hand over the choice to the user.
For example, the following is:
try { ("operate", () -> { return riskyMethod(); // Keep the original exception }); } catch (SpecificException e) { // Accurate processing}
Then this will be done.
Complete code
Here is the complete code.
All kinds of comments are written in it, which can be said to be very detailed.
Including usage examples, it is also written in JavaDoc, so that there are more comments than code.
/** * Performance tracking tool class, used to measure code execution time and provide flexible exception handling mechanisms. * * <p>Main Features: * <ul> * <li>Precisely measure code execution time</li> * <li>Support method tracking with return value and no return value</li> * <li>Providing two exception handling modes</li> * <li>Support automatic resource management</li> * </ul> * * <h2>Usage example:</h2> * * <h3> try-with-resources manual tracking</h3> * <pre>{@code * // Manually manage resource and performance tracking * try (TimeTracker tracker = new TimeTracker("Database Operation")) { * (); * (); * } // Automatically close and print the execution time * * // try-with-resources with return value * try (TimeTracker tracker = new TimeTracker("Complex Computation"); * Resource resource = acquireResource()) { * return performComplexCalculation(resource); * } * }</pre> * * <h3>Try-with-resources combined with static methods</h3> * <pre>{@code * try (TimeTracker ignored = ("Network Request")) { * (); * (); * } * }</pre> * * <p>Note: Using try-with-resources can ensure that the resource is closed correctly. * And automatically record the execution time. </p> * * <h3>lambda automatically handles exceptions</h3> * <pre>{@code * // No return value method * ("Data Processing", () -> { * processData(); // Methods that may throw exceptions * }); * * // There is a return value method * String result = ("Query user", () -> { * return (123); * }); * }</pre> * * <h3>lambda explicit exception handling</h3> * <pre>{@code * try { * // Allows to throw original exceptions * String result = ("Complex query", () -> { * return complexQuery(); // Check exception may be thrown * }); * } catch (SQLException e) { * // Accurately handle specific exceptions * ("Database query failed", e); * } * }</pre> * * <h3> lambda nested use</h3> * <pre>{@code * ("Overall Process", () -> { * // Subtask 1 * ("Data preparation", () -> prepareData()); * * // Subtask 2 * return ("Data processing", () -> processData()); * }); * }</pre> * * <p>Note: By default, execution time is printed to the console. For production environments, * It is recommended to customize the logging mechanism as needed. </p> * * @author [Your Name] * @version 1.0 * @since [version number] */ public class TimeTracker implements AutoCloseable { /** Operation name */ private final String operationName; /** Start time (nanoseconds) */ private final long startTime; /** Whether to enable logging */ private final boolean logEnabled; /** * Create a new TimeTracker instance. * * @param operationName The name of the operation to be tracked */ public TimeTracker(String operationName) { this(operationName, true); } /** * Private constructor for creating TimeTracker instances. * * @param operationName operation name * @param logEnabled Whether to enable log output */ private TimeTracker(String operationName, boolean logEnabled) { = operationName; = (); = logEnabled; if (logEnabled) { ("Start execution: %s%n", operationName); } } /** * Static factory method to create a new TimeTracker instance. * * @param operationName The name of the operation to be tracked * @return New TimeTracker instance */ public static TimeTracker of(String operationName) { return new TimeTracker(operationName); } /** * Track the execution time of the code block with the return value, and the exception will be wrapped as a RuntimeException. * * @param operationName operation name * @param execution block to execute * @param <T> Return value type * @return The execution result of the code block * @throws RuntimeException If an exception occurs during execution */ public static <T> T track(String operationName, ThrowableSupplier<T> execution) { try { return trackThrows(operationName, execution); } catch (Exception e) { throw new RuntimeException("Execution failed: " + operationName, e); } } /** * Track the execution time of the code block with the return value, allowing exceptions to be thrown. * * @param operationName operation name * @param execution block to execute * @param <T> Return value type * @return The execution result of the code block * @throws Exception If an exception occurs during execution */ public static <T> T trackThrows(String operationName, ThrowableSupplier<T> execution) throws Exception { try (TimeTracker ignored = new TimeTracker(operationName, true)) { return (); } } /** * Track the execution time of the code block without a return value, and the exception will be wrapped as a RuntimeException. * * @param operationName operation name * @param execution block to execute * @throws RuntimeException If an exception occurs during execution */ public static void track(String operationName, ThrowableRunnable execution) { try { trackThrows(operationName, execution); } catch (Exception e) { throw new RuntimeException("Execution failed: " + operationName, e); } } /** * Track the execution time of code blocks without return values, allowing exceptions to be thrown. * * @param operationName operation name * @param execution block to execute * @throws Exception If an exception occurs during execution */ public static void trackThrows(String operationName, ThrowableRunnable execution) throws Exception { try (TimeTracker ignored = new TimeTracker(operationName, true)) { (); } } @Override public void close() { if (logEnabled) { // Calculate the execution time (convert to milliseconds) long timeElapsed = (() - startTime) / 1_000_000; ("%s execution is completed, time taken: %d ms%n", operationName, timeElapsed); } } /** * Supplier functional interface that can throw exceptions. * * @param <T> Return value type */ @FunctionalInterface public interface ThrowableSupplier<T> { /** * Get the results. * * @return Execution result * @throws Exception If an error occurs during execution */ T get() throws Exception; } /** * Runnable functional interface that can throw exceptions. */ @FunctionalInterface public interface ThrowableRunnable { /** * Perform an action. * * @throws Exception If an error occurs during execution */ void run() throws Exception; } }
A DEMO
The call example has been clearly stated in JavaDoc. Here is an additional demo class, which may be clearer
import ; public class TimeTrackerDemo { public void demonstrateUsage() { // 1. Use a version that does not throw an exception check (exception is wrapped as a RuntimeException) ("Simple Task", () -> { (1000); return "Finish"; }); // 2. Use versions that may throw exceptions try { ("Possible Mission", () -> { if (() < 0.5) { throw new IOException("Simulate IO exception"); } return "success"; }); } catch (Exception e) { // Handle exceptions (); } // 3. Nested usage example try { ("Complex Process", () -> { // Subtask 1: Use version that does not throw exceptions ("Submission 1", () -> { (500); }); // Subtask 2: Use the version that throws exception return ("Submission 2", () -> { (500); return "Done all"; }); }); } catch (Exception e) { // Handle exceptions (); } // 4. try-with-resources example try (TimeTracker tracker = ("Resource Management Demo")) { // Simulate resource operations performResourceIntensiveTask(); } // 5. Multi-resources management try-with-resources try ( TimeTracker tracker1 = ("Phase One"); TimeTracker tracker2 = ("Stage 2"); // Can manage other resources at the same time CustomResource resource = acquireResource() ) { processResourcesSequentially(resource); } catch (Exception e) { // Exception handling (); } // 6. Ignore the try-with-resources of the return value try (TimeTracker ignored = ("Background Tasks")) { performBackgroundTask(); } } // Auxiliary method (for example only) private void performResourceIntensiveTask() { (1000); ("Resource-intensive task completion"); } private CustomResource acquireResource() { return new CustomResource(); } private void processResourcesSequentially(CustomResource resource) { // Example method for processing resources (); } private void performBackgroundTask() { // Backend task example ("Execute backend tasks"); } // Simulate custom resource classes private static class CustomResource implements AutoCloseable { public void process() { ("Processing Resources"); } @Override public void close() { ("Close Resources"); } } }
Improvement suggestions
Of course, there is still a lot of room for improvement in this category. I will briefly list a few. You can gradually optimize them according to your real scene.
- Integrated logging frameworks, such as Slf4j, support more flexible output methods
- Add more time statistics dimensions (maximum, minimum, average, etc.)
- Add performance metric collection to support monitoring data statistics
- Support asynchronous operation
The revolution has not been successful yet, and comrades still need to work hard.
Summarize
A little experience
Let’s first summarize some experience. Everyone has their own opinions.
- Tool design must focus on the balance between practicality and ease of use
- Tools are just tools, and you must not involve business in tool classes.
- Exception handling needs to consider actual real usage scenarios
- Rational use of language features can greatly simplify the code
- Robustness is very important
Written at the end
During the years when writing code, you often have to record some execution time. It was simple at first.()
After putting it before and after, you will know how many milliseconds it took to subtract. Later, I felt that writing this way was cumbersome and it was easy to forget to deal with exceptions, so I simply made such a tool.
It's nothing new, it's just using the AutoCloseable interface in Java and a lambda expression to make the code look cleaner. However, it took some thought to deal with exceptions. After all, in actual development, exception handling is often more complicated than the main logic.
Looking back at this code, I don’t think there is much technical content, but it does solve the actual problem. This is probably the meaning of writing a program: it is not to write such shocking code, but to make the originally tedious things simple and make the users feel comfortable.
The above is the detailed content of Java's time-consuming performance tracking with a line of Java code. For more information about Java's time-consuming performance tracking, please follow my other related articles!