diff --git a/development/github_pages/GITHUB_PAGES_PLAN.md b/development/github_pages/GITHUB_PAGES_PLAN.md new file mode 100644 index 0000000..9b07f1f --- /dev/null +++ b/development/github_pages/GITHUB_PAGES_PLAN.md @@ -0,0 +1,194 @@ +# Timer Ninja — GitHub Pages Planning Document + +## Overview + +A Jekyll-based GitHub Pages site serving as both a visually striking landing page and full documentation hub for the Timer Ninja library. Deployed from the `docs/` folder on the `main` branch. + +--- + +## Decisions + +| Decision | Choice | +|---|---| +| **Tech stack** | Jekyll (GitHub Pages native, no build CI needed) | +| **Theme** | Fully custom (no base theme) for max creative control | +| **Page scope** | Multi-page: Landing + User Guide + Examples + Advanced Usage | +| **Navigation** | Sticky top navbar with logo, page links, GitHub link, day/night toggle | +| **Hero style** | Full-screen with animated gradient/particle background + ninja sloth mascot | +| **Code comparison** | Side-by-side panels (Traditional vs Timer Ninja) | +| **Animations** | Rich — parallax hero, scroll-triggered fade/slide, animated trace output, typing code effect | +| **Mascot usage** | Heavy — hero, CTA section, footer, 404 page | +| **Day/night mode** | CSS custom properties + JS toggle, preference saved in localStorage | +| **Primary color** | `#46bfc6` (light blue) with complementary palette | + +--- + +## Design System + +### Color Palette + +**Light Mode:** +| Token | Value | Usage | +|---|---|---| +| Primary | `#46bfc6` | Brand color, buttons, links, accents | +| Primary Dark | `#3aa3a9` | Hover states | +| Primary Light | `#6dd5db` | Gradients, glow | +| Background | `#ffffff` | Page background | +| Surface | `#f5fafa` | Section backgrounds | +| Text | `#1a2b3c` | Body text | +| Text Muted | `#5a6b7c` | Secondary text | +| Code BG | `#f0f6f6` | Code block backgrounds | + +**Dark Mode:** +| Token | Value | Usage | +|---|---|---| +| Primary | `#46bfc6` | Unchanged | +| Primary Light | `#6dd5db` | Highlighted elements | +| Background | `#0d1520` | Page background | +| Surface | `#14202e` | Section backgrounds | +| Text | `#e8f0f2` | Body text | +| Text Muted | `#8fa3b2` | Secondary text | +| Code BG | `#111d2b` | Code block backgrounds | + +### Typography +- **Body:** Inter (Google Fonts) +- **Code:** JetBrains Mono (Google Fonts) + +--- + +## Site Structure + +``` +docs/ +├── _config.yml # Jekyll configuration +├── _data/ +│ └── navigation.yml # Navbar links +├── _includes/ +│ ├── head.html # Meta, fonts, CSS, theme init script +│ ├── navbar.html # Sticky navbar with theme toggle +│ └── footer.html # Footer with mascot +├── _layouts/ +│ ├── default.html # Base layout +│ ├── home.html # Landing page layout (includes particles + typing JS) +│ └── docs.html # Documentation layout (sidebar TOC + scrollspy) +├── _sass/ +│ ├── _variables.scss # Design tokens, CSS custom properties +│ ├── _base.scss # Reset, typography, global styles +│ ├── _navbar.scss # Sticky nav, hamburger menu +│ ├── _hero.scss # Hero section, float animation +│ ├── _features.scss # Feature cards grid +│ ├── _code.scss # Code panels, trace output, quickstart steps, tabs +│ ├── _docs.scss # Documentation sidebar + content styles +│ ├── _animations.scss # Keyframes, scroll-triggered classes +│ ├── _footer.scss # Footer styles +│ └── _dark-mode.scss # Dark mode overrides +├── assets/ +│ ├── css/main.scss # SCSS entry point +│ ├── js/ +│ │ ├── theme-toggle.js # Day/night mode + hamburger + nav scroll +│ │ ├── animations.js # IntersectionObserver scroll reveals + tabs + trace +│ │ ├── particles.js # Canvas particle system for hero +│ │ ├── typing-effect.js # Typing animation for code comparison +│ │ └── docs-toc.js # Auto-generated TOC + scrollspy for docs +│ └── images/ +│ └── mascot.png # Ninja sloth mascot +├── index.html # Landing page +├── user-guide.md # User Guide (from wiki) +├── examples.md # Examples (from wiki) +├── advanced-usage.md # Advanced Usage (from wiki) +├── 404.html # Custom 404 page +└── Gemfile # Jekyll dependencies +``` + +--- + +## Landing Page Sections + +1. **Hero** — Full-screen animated gradient with canvas particles, floating mascot, tagline, CTA buttons, version badge +2. **Why Timer Ninja?** — 6 feature cards in responsive grid: One Annotation, Visual Call Tree, Block Tracking, Smart Thresholds, Zero Dependencies, Thread-Safe +3. **Before & After** — Side-by-side code comparison with typing animation: 6 lines of boilerplate → 1 annotation +4. **See It In Action** — Terminal-style trace output with line-by-line reveal animation +5. **Quick Start** — 4-step guide with tabbed Maven/Gradle code blocks +6. **Block Tracking Highlight** — Dedicated showcase of `TimerNinjaBlock.measure()` API +7. **CTA** — Final call-to-action with mascot + +--- + +## Documentation Pages + +| Page | Source | Layout | +|---|---|---| +| User Guide | `wiki/User-Guide.md` | `docs` (sidebar TOC) | +| Examples | `wiki/Examples.md` | `docs` (sidebar TOC) | +| Advanced Usage | `wiki/Advanced-Usage.md` | `docs` (sidebar TOC) | + +Each page includes: +- Auto-generated sidebar TOC from H2/H3 headings +- Scrollspy highlighting current section +- Previous/Next page navigation + +--- + +## Features + +### Day/Night Mode +- Toggle button in navbar (sun/moon icon) +- CSS custom properties for all colors +- Persisted in `localStorage` +- Falls back to `prefers-color-scheme` system preference +- Prevents FOUC with inline ` diff --git a/docs/_includes/navbar.html b/docs/_includes/navbar.html new file mode 100644 index 0000000..b13f7e4 --- /dev/null +++ b/docs/_includes/navbar.html @@ -0,0 +1,36 @@ + diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html new file mode 100644 index 0000000..62e399f --- /dev/null +++ b/docs/_layouts/default.html @@ -0,0 +1,26 @@ + + +
+ {% include head.html %} + + + {% include navbar.html %} + +` blocks. Prism expects
+ // `` inside a ``.
+ // This script normalizes Rouge output for Prism compatibility.
+
+ function bridgeRougeToprism() {
+ // Handle Rouge-generated blocks:
+ document.querySelectorAll('div[class*="language-"]').forEach(function (div) {
+ var classes = div.className.split(/\s+/);
+ var lang = '';
+ classes.forEach(function (cls) {
+ var match = cls.match(/^language-(.+)$/);
+ if (match) lang = match[1];
+ });
+
+ if (!lang) return;
+
+ var pre = div.querySelector('pre');
+ var code = div.querySelector('code');
+ if (pre && code) {
+ code.className = 'language-' + lang;
+ pre.className = 'language-' + lang;
+ }
+ });
+
+ // Handle plain markdown ```java blocks that Jekyll may render as
+ //
+ document.querySelectorAll('pre code[class*="language-"]').forEach(function (code) {
+ var pre = code.parentElement;
+ if (pre && pre.tagName === 'PRE' && !pre.className.match(/language-/)) {
+ var langClass = code.className.match(/language-\S+/);
+ if (langClass) {
+ pre.classList.add(langClass[0]);
+ }
+ }
+ });
+
+ // Handle code without language class — treat as plain text
+ document.querySelectorAll('pre code:not([class*="language-"])').forEach(function (code) {
+ code.classList.add('language-none');
+ });
+ }
+
+ // Sync Prism stylesheet with current theme
+ function syncPrismTheme() {
+ var theme = document.documentElement.getAttribute('data-theme');
+ var lightSheet = document.getElementById('prism-light');
+ var darkSheet = document.getElementById('prism-dark');
+
+ if (lightSheet && darkSheet) {
+ lightSheet.disabled = (theme === 'dark');
+ darkSheet.disabled = (theme !== 'dark');
+ }
+ }
+
+ // Watch for theme changes (from theme-toggle.js)
+ var observer = new MutationObserver(function (mutations) {
+ mutations.forEach(function (mutation) {
+ if (mutation.attributeName === 'data-theme') {
+ syncPrismTheme();
+ }
+ });
+ });
+
+ observer.observe(document.documentElement, {
+ attributes: true,
+ attributeFilter: ['data-theme']
+ });
+
+ // Initialize
+ function init() {
+ bridgeRougeToprism();
+ syncPrismTheme();
+
+ // Re-highlight with Prism
+ if (window.Prism) {
+ Prism.highlightAll();
+ }
+ }
+
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', init);
+ } else {
+ init();
+ }
+})();
diff --git a/docs/assets/js/theme-toggle.js b/docs/assets/js/theme-toggle.js
new file mode 100644
index 0000000..ce67651
--- /dev/null
+++ b/docs/assets/js/theme-toggle.js
@@ -0,0 +1,86 @@
+// ==============================================
+// Theme Toggle — Timer Ninja
+// Day/Night mode with localStorage persistence
+// ==============================================
+
+(function () {
+ 'use strict';
+
+ var STORAGE_KEY = 'timer-ninja-theme';
+ var toggle = document.getElementById('themeToggle');
+ var html = document.documentElement;
+
+ function getPreferredTheme() {
+ var stored = localStorage.getItem(STORAGE_KEY);
+ if (stored) return stored;
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
+ }
+
+ function setTheme(theme) {
+ html.setAttribute('data-theme', theme);
+ localStorage.setItem(STORAGE_KEY, theme);
+ }
+
+ // Initialize
+ setTheme(getPreferredTheme());
+
+ // Toggle
+ if (toggle) {
+ toggle.addEventListener('click', function () {
+ var current = html.getAttribute('data-theme');
+ setTheme(current === 'dark' ? 'light' : 'dark');
+ });
+ }
+
+ // Listen for system preference changes
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function (e) {
+ if (!localStorage.getItem(STORAGE_KEY)) {
+ setTheme(e.matches ? 'dark' : 'light');
+ }
+ });
+
+ // Hamburger menu
+ var hamburger = document.getElementById('navHamburger');
+ var navLinks = document.getElementById('navLinks');
+
+ if (hamburger && navLinks) {
+ hamburger.addEventListener('click', function () {
+ hamburger.classList.toggle('is-open');
+ navLinks.classList.toggle('is-open');
+ });
+
+ // Close on link click (mobile)
+ navLinks.querySelectorAll('.navbar__link').forEach(function (link) {
+ link.addEventListener('click', function () {
+ hamburger.classList.remove('is-open');
+ navLinks.classList.remove('is-open');
+ });
+ });
+ }
+
+ // Navbar background on scroll + back-to-top
+ var navbar = document.getElementById('navbar');
+ var backToTop = document.getElementById('backToTop');
+
+ if (navbar || backToTop) {
+ window.addEventListener('scroll', function () {
+ var scrolled = window.scrollY > 50;
+ if (navbar) {
+ navbar.style.boxShadow = scrolled ? '0 2px 20px rgba(0,0,0,0.08)' : 'none';
+ }
+ if (backToTop) {
+ if (window.scrollY > 400) {
+ backToTop.classList.add('is-visible');
+ } else {
+ backToTop.classList.remove('is-visible');
+ }
+ }
+ }, { passive: true });
+ }
+
+ if (backToTop) {
+ backToTop.addEventListener('click', function () {
+ window.scrollTo({ top: 0, behavior: 'smooth' });
+ });
+ }
+})();
diff --git a/docs/assets/js/typing-effect.js b/docs/assets/js/typing-effect.js
new file mode 100644
index 0000000..ac8dc28
--- /dev/null
+++ b/docs/assets/js/typing-effect.js
@@ -0,0 +1,82 @@
+// ==============================================
+// Typing Effect — Timer Ninja
+// Animated code typing for comparison section
+// ==============================================
+
+(function () {
+ 'use strict';
+
+ var codeTraditional = document.getElementById('codeTraditional');
+ var codeTimerNinja = document.getElementById('codeTimerNinja');
+
+ if (!codeTraditional || !codeTimerNinja) return;
+
+ function animateTyping(element, text, speed) {
+ element.textContent = '';
+ element.style.visibility = 'visible';
+
+ var i = 0;
+ var cursor = document.createElement('span');
+ cursor.className = 'typing-cursor';
+ element.appendChild(cursor);
+
+ return new Promise(function (resolve) {
+ function type() {
+ if (i < text.length) {
+ element.insertBefore(
+ document.createTextNode(text.charAt(i)),
+ cursor
+ );
+ i++;
+ setTimeout(type, speed);
+ } else {
+ setTimeout(function () {
+ if (cursor.parentNode) {
+ cursor.parentNode.removeChild(cursor);
+ }
+ // Restore text and apply Prism highlighting
+ element.textContent = text;
+ if (window.Prism) {
+ Prism.highlightElement(element);
+ }
+ resolve();
+ }, 600);
+ }
+ }
+ type();
+ });
+ }
+
+ // Store original text
+ var traditionalText = codeTraditional.textContent;
+ var ninjaText = codeTimerNinja.textContent;
+
+ // Only run animation once when section is visible
+ var comparisonSection = document.getElementById('comparison');
+ if (!comparisonSection) return;
+
+ var hasAnimated = false;
+
+ var observer = new IntersectionObserver(function (entries) {
+ entries.forEach(function (entry) {
+ if (entry.isIntersecting && !hasAnimated) {
+ hasAnimated = true;
+ observer.unobserve(entry.target);
+
+ // Start typing animation on both panels
+ codeTraditional.textContent = '';
+ codeTimerNinja.textContent = '';
+
+ setTimeout(function () {
+ animateTyping(codeTraditional, traditionalText, 18).then(function () {
+ return animateTyping(codeTimerNinja, ninjaText, 22);
+ });
+ }, 400);
+ }
+ });
+ }, {
+ threshold: 0.4
+ });
+
+ observer.observe(comparisonSection);
+})();
diff --git a/docs/examples.md b/docs/examples.md
new file mode 100644
index 0000000..9503fe4
--- /dev/null
+++ b/docs/examples.md
@@ -0,0 +1,321 @@
+---
+layout: docs
+title: Examples
+description: "Real-world examples demonstrating Timer Ninja usage patterns."
+prev_page:
+ title: User Guide
+ url: /user-guide/
+next_page:
+ title: Advanced Usage
+ url: /advanced-usage/
+---
+
+# Examples
+
+This page provides real-world examples demonstrating Timer Ninja usage patterns.
+
+---
+
+## Basic Method Tracking
+
+### Simple Tracking
+
+```java
+@TimerNinjaTracker
+public void processRequest() {
+ System.out.println("Processing request...");
+}
+```
+
+**Output:**
+```
+{===== Start of trace context id: abc123... =====}
+public void processRequest() - 42 ms
+{====== End of trace context id: abc123... ======}
+```
+
+### With Time Unit
+
+```java
+@TimerNinjaTracker(timeUnit = ChronoUnit.MICROS)
+public void calculateMetrics() {
+ // Precision calculation
+}
+```
+
+**Output:**
+```
+public void calculateMetrics() - 52341 µs
+```
+
+---
+
+## Banking Service Example
+
+This example shows a comprehensive banking service with multiple tracking scenarios.
+
+### Money Transfer Service
+
+```java
+public class BankService {
+ private BalanceService balanceService;
+ private UserService userService;
+ private NotificationService notificationService;
+
+ public BankService() {
+ BankRecordBook masterRecordBook = BankRecordBook.getInstance();
+ this.notificationService = new NotificationService();
+ this.balanceService = new BalanceService(masterRecordBook, notificationService);
+ this.userService = new UserService(masterRecordBook);
+ }
+
+ @TimerNinjaTracker(threshold = 200)
+ public void requestMoneyTransfer(int sourceUserId, int targetUserId, int amount) {
+ User sourceUser = userService.findUser(sourceUserId);
+ User targetUser = userService.findUser(targetUserId);
+ balanceService.deductAmount(sourceUser, amount);
+ balanceService.increaseAmount(targetUser, amount);
+ }
+
+ @TimerNinjaTracker(includeArgs = true, threshold = 500)
+ public void depositMoney(int userId, int amount) {
+ // Deposit logic
+ }
+
+ @TimerNinjaTracker(includeArgs = true)
+ public void payWithCard(int userId, BankCard card, int amount) {
+ User user = userService.findUser(userId);
+ // Card payment logic
+ }
+}
+```
+
+### Output Example
+
+```
+{===== Start of trace context id: 851ac23b-2669-4883-8c97-032b8fd2d45c =====}
+public void requestMoneyTransfer(int sourceUserId, int targetUserId, int amount) - 1037 ms ¤ [Threshold Exceed !!: 200 ms]
+ |-- public User findUser(int userId) - 105 ms
+ |-- public User findUser(int userId) - 108 ms
+ |-- public void deductAmount(User user, int amount) - 306 ms
+ |-- public void increaseAmount(User user, int amount) - 418 ms
+{====== End of trace context id: 851ac23b-2669-4883-8c97-032b8fd2d45c ======}
+```
+
+---
+
+## Notification Service Example
+
+Demonstrates nested method tracking with multiple levels.
+
+```java
+public class NotificationService {
+
+ @TimerNinjaTracker
+ public void notify(User user) {
+ notifyViaSMS(user);
+ notifyViaEmail(user);
+ }
+
+ @TimerNinjaTracker
+ private void notifyViaSMS(User user) {
+ try { Thread.sleep(50); }
+ catch (InterruptedException e) { throw new RuntimeException(e); }
+ }
+
+ @TimerNinjaTracker
+ private void notifyViaEmail(User user) {
+ try { Thread.sleep(200); }
+ catch (InterruptedException e) { throw new RuntimeException(e); }
+ }
+}
+```
+
+**Output:**
+```
+{===== Start of trace context id: abc123... =====}
+public void notify(User user) - 258 ms
+ |-- private void notifyViaSMS(User user) - 53 ms
+ |-- private void notifyViaEmail(User user) - 205 ms
+{====== End of trace context id: abc123... ======}
+```
+
+---
+
+## Constructor Tracking
+
+### Service Initialization Chain
+
+```java
+public class TransportationService {
+ private ShippingService shippingService;
+
+ @TimerNinjaTracker
+ public TransportationService() {
+ this.shippingService = new ShippingService();
+ }
+}
+
+public class ShippingService {
+ @TimerNinjaTracker
+ public ShippingService() {
+ // Shipping service initialization
+ }
+}
+```
+
+**Output:**
+```
+{===== Start of trace context id: def456... =====}
+public TransportationService() - 150 ms
+ |-- public ShippingService() - 80 ms
+{====== End of trace context id: def456... ======}
+```
+
+---
+
+## Loan Processing Example
+
+Combines annotation-based tracking with block tracking.
+
+```java
+public class LoanService {
+ private UserService userService;
+
+ @TimerNinjaTracker(includeArgs = true, threshold = 100)
+ public void processLoanApplication(int userId, double loanAmount, int termMonths) {
+ User user = userService.findUser(userId);
+
+ // Phase 1: Credit check
+ TimerNinjaBlock.measure("credit score check", () -> {
+ simulateDelay(60);
+ });
+
+ // Phase 2: Income verification
+ TimerNinjaBlock.measure("income verification", () -> {
+ simulateDelay(80);
+ });
+
+ // Phase 3: Risk assessment with custom config
+ BlockTrackerConfig riskConfig = new BlockTrackerConfig()
+ .setTimeUnit(ChronoUnit.MILLIS)
+ .setThreshold(30);
+
+ TimerNinjaBlock.measure("risk assessment", riskConfig, () -> {
+ simulateDelay(40);
+ });
+
+ // Phase 4: Final approval with return value
+ String approvalStatus = TimerNinjaBlock.measure("final approval", () -> {
+ simulateDelay(50);
+ return "APPROVED";
+ });
+ }
+}
+```
+
+**Output:**
+```
+{===== Start of trace context id: ghi789... =====}
+public void processLoanApplication(int userId, double loanAmount, int termMonths) - Args: [userId={123}, loanAmount={50000.0}, termMonths={36}] - 345 ms
+ |-- [Block] credit score check - 60 ms
+ |-- [Block] income verification - 80 ms
+ |-- [Block] risk assessment - 40 ms
+ |-- [Block] final approval - 50 ms
+{====== End of trace context id: ghi789... ======}
+```
+
+---
+
+## E-commerce Order Processing
+
+```java
+@Service
+public class OrderService {
+
+ @TimerNinjaTracker
+ public Order createOrder(OrderRequest request) {
+ Order order = validateAndCreateOrder(request);
+ PaymentResult paymentResult = processPayment(order);
+ updateInventory(order);
+ sendConfirmation(order);
+ return order;
+ }
+
+ @TimerNinjaTracker(threshold = 500, includeArgs = true)
+ private PaymentResult processPayment(Order order) {
+ return paymentService.charge(
+ order.getUserId(), order.getPaymentMethod(), order.getTotalAmount()
+ );
+ }
+
+ @TimerNinjaTracker(threshold = 200)
+ private void updateInventory(Order order) {
+ order.getItems().forEach(item ->
+ inventoryService.deductStock(item.getProductId(), item.getQuantity())
+ );
+ }
+
+ @TimerNinjaTracker
+ private void sendConfirmation(Order order) {
+ notificationService.sendEmailConfirmation(order.getUserEmail(), order);
+ }
+}
+```
+
+**Output:**
+```
+{===== Start of trace context id: jkl012... =====}
+public Order createOrder(OrderRequest request) - 2150 ms
+ |-- public Order validateAndCreateOrder(OrderRequest request) - 120 ms
+ |-- public PaymentResult processPayment(Order order) - Args: [order={id=ORD-12345, ...}] - 1250 ms ¤ [Threshold Exceed !!: 500 ms]
+ |-- public PaymentResult charge(int userId, String paymentMethod, double amount) - 1180 ms
+ |-- public void updateInventory(Order order) - 450 ms
+ |-- public void sendConfirmation(Order order) - 330 ms
+{====== End of trace context id: jkl012... ======}
+```
+
+---
+
+## API Controller Example
+
+```java
+@RestController
+@RequestMapping("/api/users")
+public class UserController {
+
+ @TimerNinjaTracker
+ @GetMapping("/{id}")
+ public ResponseEntity getUser(@PathVariable Long id) {
+ User user = userService.findById(id);
+ return ResponseEntity.ok(user);
+ }
+
+ @TimerNinjaTracker(includeArgs = true, threshold = 100)
+ @PostMapping
+ public ResponseEntity createUser(@RequestBody CreateUserRequest request) {
+ User user = userService.create(request);
+ return ResponseEntity.status(HttpStatus.CREATED).body(user);
+ }
+}
+```
+
+**Output for GET request:**
+```
+{===== Start of trace context id: mno345... =====}
+public ResponseEntity getUser(Long id) - 85 ms
+ |-- public User findById(Long id) - 70 ms
+ |-- public User queryDatabase(Long id) - 65 ms
+{====== End of trace context id: mno345... ======}
+```
+
+---
+
+## Key Takeaways
+
+1. **Entry Point Tracking** — Track high-level methods to capture full call hierarchies
+2. **Threshold Usage** — Use thresholds to filter noise and focus on slow operations
+3. **Argument Tracking** — Enable `includeArgs` for debugging and performance analysis
+4. **Block Tracking** — Use `TimerNinjaBlock` for granular tracking without method extraction
+5. **Constructor Tracking** — Track initialization chains to identify slow startup times
+6. **Mixed Tracking** — Combine annotation and block tracking for comprehensive monitoring
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 0000000..72d98bd
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,317 @@
+---
+layout: home
+title: Home
+description: "Timer Ninja — A sneaky library for Java Method Timing. Track execution time with a single annotation. Zero boilerplate."
+---
+
+
+
+
+
+
+
+
+
+ Open Source · Java Library
+
+
+ A Sneaky Library for
Java Method Timing
+
+
+ Track execution time with a single annotation. Preserve call hierarchies. Zero boilerplate. Built on AspectJ.
+
+
+
+ Latest
+ io.github.thanglequoc:timer-ninja:1.3.0
+
+
+
+
+
+
+
+
+
+ Why Timer Ninja?
+ Performance Tracking,
Without the Pain
+ Everything you need to understand your code's timing behavior — nothing you don't.
+
+
+
+ 🎯
+ One Annotation
+ Replace 6 lines of timestamp boilerplate with @TimerNinjaTracker. That's it.
+
+
+ 🌳
+ Visual Call Tree
+ Nested method calls render as a clear, indented hierarchy. See the full execution flow at a glance.
+
+
+ 🧩
+ Block Tracking
+ Measure arbitrary code blocks with TimerNinjaBlock.measure() — no need to extract separate methods.
+
+
+ ⚠️
+ Smart Thresholds
+ Only surface slow operations. Set a threshold and Timer Ninja filters the noise automatically.
+
+
+ 🪶
+ Zero Dependencies
+ Just AspectJ + SLF4J. No framework lock-in. Works with Spring Boot, plain Java, or anything in between.
+
+
+ 🛡️
+ Thread-Safe
+ Isolated per-thread context via ThreadLocal. Safe for concurrent and multi-threaded applications.
+
+
+
+
+
+
+
+
+
+ Before & After
+ Stop Timing Methods
The Hard Way
+ See how Timer Ninja eliminates manual timestamp boilerplate.
+
+
+
+
+
+ ❌ Traditional Approach
+
+
+ long before = System.currentTimeMillis();
+doSomethingInteresting();
+long after = System.currentTimeMillis();
+System.out.println(
+ "Execution time (ms): " + (after - before)
+);
+
+
+
+
+
+ ✔️ Timer Ninja
+
+
+ @TimerNinjaTracker
+public String doSomethingInteresting() {
+ // Your business logic — that's it!
+}
+
+
+
+
+
+
+ 6 lines of boilerplate
+ →
+ 1 annotation
+
+
+
+
+
+
+
+
+
+ See It In Action
+ Beautiful Trace Output
+ Timer Ninja prints a visual call tree showing the full execution hierarchy, timing, and arguments.
+
+
+
+
+
+
+
+ Timer Ninja Trace Output
+
+
+Timer Ninja trace context id: 851ac23b-2669-4883-8c97-032b8fd2d45c
+Trace timestamp: 2023-04-03T07:16:48.491Z
+{===== Start of trace context id: 851ac23b... =====}
+public void requestMoneyTransfer(...) - Args: [sourceUserId={1}, targetUserId={2}, amount={500}] - 1747 ms
+ |-- public User findUser(int userId) - 105000 µs
+ |-- public void processPayment(User user, int amount) - 770 ms
+ |-- public boolean changeAmount(User user, int amount) - 306 ms
+ |-- public void notify(User user) - 258 ms
+ |-- private void notifyViaSMS(User user) - 53 ms
+ |-- private void notifyViaEmail(User user) - 205 ms ¤ [Threshold Exceed !!: 200 ms]
+{====== End of trace context id: 851ac23b... ======}
+
+
+
+
+
+
+
+
+
+ Quick Start
+ Get Started in 60 Seconds
+ Four simple steps to start tracking method execution time.
+
+
+
+
+
+ 1
+
+ Add the Dependency
+ Add Timer Ninja from Maven Central to your project.
+
+
+
+
+
+ implementation 'io.github.thanglequoc:timer-ninja:1.3.0'
+aspect 'io.github.thanglequoc:timer-ninja:1.3.0'
+
+
+ <dependency>
+ <groupId>io.github.thanglequoc</groupId>
+ <artifactId>timer-ninja</artifactId>
+ <version>1.3.0</version>
+</dependency>
+
+
+
+
+
+
+ 2
+
+ Add AspectJ Plugin
+ Enable AspectJ compilation so annotations get woven.
+
+
+
+
+
+ plugins {
+ id "io.freefair.aspectj.post-compile-weaving" version '9.1.0'
+}
+
+
+ <plugin>
+ <groupId>dev.aspectj</groupId>
+ <artifactId>aspectj-maven-plugin</artifactId>
+ <version>1.14.1</version>
+ <configuration>
+ <aspectLibraries>
+ <aspectLibrary>
+ <groupId>io.github.thanglequoc</groupId>
+ <artifactId>timer-ninja</artifactId>
+ </aspectLibrary>
+ </aspectLibraries>
+ </configuration>
+</plugin>
+
+
+
+
+
+
+ 3
+
+ Annotate Your Methods
+ Place @TimerNinjaTracker on any method or constructor.
+ @TimerNinjaTracker
+public void processPayment(User user, int amount) {
+ // Your business logic
+}
+
+
+
+
+
+ 4
+
+ Run & See the Trace 🐇
+ Execute your code — Timer Ninja automatically logs the execution trace.
+ public void processPayment(User user, int amount) - 770 ms
+ |-- public boolean changeAmount(User user, int amount) - 306 ms
+ |-- public void notify(User user) - 258 ms
+
+
+
+
+
+
+
+
+
+
+ Advanced Feature
+ Block Tracking with
TimerNinjaBlock
+ Measure any code block — no need to extract separate methods.
+
+
+
+
+
+ 🧩 Block Tracking Example
+
+
+ @TimerNinjaTracker(includeArgs = true, threshold = 100)
+public void processLoanApplication(int userId, double amount, int months) {
+ User user = userService.findUser(userId);
+
+ TimerNinjaBlock.measure("credit score check", () -> {
+ simulateDelay(60);
+ });
+
+ TimerNinjaBlock.measure("income verification", () -> {
+ simulateDelay(80);
+ });
+
+ String status = TimerNinjaBlock.measure("final approval", () -> {
+ simulateDelay(50);
+ return "APPROVED";
+ });
+}
+
+
+
+
+
+
+
+
+
+
+
+
+ Ready to Track Like a Ninja?
+
+ Start measuring method execution time the smart way. Zero boilerplate, full visibility.
+
+
+
+
diff --git a/docs/user-guide.md b/docs/user-guide.md
new file mode 100644
index 0000000..b3506e0
--- /dev/null
+++ b/docs/user-guide.md
@@ -0,0 +1,420 @@
+---
+layout: docs
+title: User Guide
+description: "Comprehensive guide on how to use Timer Ninja's features effectively."
+prev_page:
+ title: Home
+ url: /
+next_page:
+ title: Examples
+ url: /examples/
+---
+
+# User Guide
+
+This guide provides detailed documentation on how to use Timer Ninja's features effectively.
+
+---
+
+## Annotation-based Tracking
+
+The `@TimerNinjaTracker` annotation is the primary way to track method execution time.
+
+### Basic Usage
+
+Annotate any method or constructor to start tracking:
+
+```java
+@TimerNinjaTracker
+public void performTask() {
+ // Your business logic
+}
+```
+
+### Tracking Constructors
+
+You can also track constructor execution:
+
+```java
+@TimerNinjaTracker
+public class NotificationService {
+ public NotificationService() {
+ // Constructor logic
+ }
+}
+```
+
+**Output:**
+```
+{===== Start of trace context id: abc123... =====}
+public NotificationService() - 80 ms
+{====== End of trace context id: abc123... ======}
+```
+
+### Annotation Attributes
+
+The `@TimerNinjaTracker` annotation supports several configuration options:
+
+| Attribute | Type | Default | Description |
+|-----------|------|---------|-------------|
+| `enabled` | `boolean` | `true` | Enable or disable tracking for this method |
+| `timeUnit` | `ChronoUnit` | `MILLIS` | Time unit for measurement (SECONDS, MILLIS, MICROS) |
+| `includeArgs` | `boolean` | `false` | Include method arguments in the log trace |
+| `threshold` | `int` | `-1` | Minimum execution time required to log (in specified timeUnit) |
+
+---
+
+## Configuration Options
+
+### 1. Enable/Disable Tracking
+
+Control whether a specific method is tracked:
+
+```java
+@TimerNinjaTracker(enabled = true)
+public void trackThis() {
+ // This will be tracked
+}
+
+@TimerNinjaTracker(enabled = false)
+public void dontTrackThis() {
+ // This will NOT be tracked
+}
+```
+
+**Use Case:** Temporarily disable tracking for a method without removing the annotation.
+
+### 2. Time Unit Selection
+
+Choose the appropriate time unit for your measurement needs:
+
+```java
+import java.time.temporal.ChronoUnit;
+
+@TimerNinjaTracker(timeUnit = ChronoUnit.SECONDS)
+public void longRunningOperation() {
+ // For operations taking seconds
+}
+
+@TimerNinjaTracker(timeUnit = ChronoUnit.MILLIS)
+public void standardOperation() {
+ // For operations taking milliseconds (default)
+}
+
+@TimerNinjaTracker(timeUnit = ChronoUnit.MICROS)
+public void preciseOperation() {
+ // For operations requiring microsecond precision
+}
+```
+
+**Supported Units:**
+- `ChronoUnit.SECONDS` — Seconds
+- `ChronoUnit.MILLIS` — Milliseconds (default)
+- `ChronoUnit.MICROS` — Microseconds
+
+### 3. Include Method Arguments
+
+Log method arguments for better debugging context:
+
+```java
+@TimerNinjaTracker(includeArgs = true)
+public void processUser(int userId, String name, String email) {
+ // Method logic
+}
+```
+
+**Output:**
+```
+public void processUser(int userId, String name, String email) - Args: [userId={123}, name={John Doe}, email={john@example.com}] - 42 ms
+```
+
+> **Important:** Ensure your objects have proper `toString()` implementations for meaningful output.
+
+### 4. Threshold Filtering
+
+Filter out fast methods to focus on performance issues:
+
+```java
+@TimerNinjaTracker(threshold = 500) // Only log if execution > 500ms
+public void potentiallySlowMethod() {
+ // Method logic
+}
+```
+
+**When Threshold is Exceeded:**
+```
+public void potentiallySlowMethod() - 723 ms ¤ [Threshold Exceed !!: 500 ms]
+```
+
+**When Below Threshold:** The method is suppressed from the trace output. If all methods in a trace are below threshold, a summary is shown.
+
+**Combining with Arguments:**
+```java
+@TimerNinjaTracker(includeArgs = true, threshold = 200)
+public void requestMoneyTransfer(int sourceUserId, int targetUserId, int amount) {
+ // Only logs slow transfers with full argument details
+}
+```
+
+---
+
+## Block Tracking
+
+For granular tracking within a method without extracting separate methods, use `TimerNinjaBlock`.
+
+### Basic Block Tracking
+
+```java
+public void processData() {
+ TimerNinjaBlock.measure("database query", () -> {
+ database.query("SELECT * FROM users");
+ });
+}
+```
+
+### Block with Return Value
+
+```java
+public void processData() {
+ String result = TimerNinjaBlock.measure("fetch data", () -> {
+ return api.fetchUserData();
+ });
+ System.out.println(result);
+}
+```
+
+### Block with Custom Configuration
+
+```java
+import java.time.temporal.ChronoUnit;
+
+public void processData() {
+ BlockTrackerConfig config = new BlockTrackerConfig()
+ .setTimeUnit(ChronoUnit.SECONDS)
+ .setThreshold(2);
+
+ TimerNinjaBlock.measure("long operation", config, () -> {
+ performLongRunningTask();
+ });
+}
+```
+
+### Nested Block Tracking
+
+```java
+public void complexProcess() {
+ TimerNinjaBlock.measure("overall process", () -> {
+ loadData();
+ TimerNinjaBlock.measure("data transformation", () -> {
+ transformData();
+ });
+ saveData();
+ });
+}
+```
+
+**Output:**
+```
+{===== Start of trace context id: ... =====}
+[Block] overall process - 1500 ms
+ |-- [Block] data transformation - 500 ms
+{====== End of trace context id: ... ======}
+```
+
+---
+
+## Understanding Trace Output
+
+### Trace Structure
+
+```
+Timer Ninja trace context id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
+Trace timestamp: 2023-04-03T14:27:50.322Z
+{===== Start of trace context id: a1b2c3d4-e5f6-7890-abcd-ef1234567890 =====}
+public void parentMethod() - 100 ms
+ |-- public void childMethod() - 50 ms
+ |-- public void anotherChildMethod() - 30 ms
+{====== End of trace context id: a1b2c3d4-e5f6-7890-abcd-ef1234567890 ======}
+```
+
+### Elements Explained
+
+1. **Trace Context ID** — Auto-generated UUID for a trace. All tracked methods in the same call stack share this ID.
+2. **Trace Timestamp** — When the trace context was initiated (UTC timezone).
+3. **Start/End Markers** — Delimit the trace boundaries.
+4. **Method Lines** — Each tracked method shows: signature, arguments (if enabled), execution time, threshold indicator.
+5. **Indentation (`|--`)** — Shows call hierarchy. Indented methods are called by the method above.
+
+### Summary Output
+
+When all methods in a trace are below their thresholds:
+
+```
+Timer Ninja trace context id: abc123...
+Trace timestamp: 2023-04-03T14:27:50.322Z
+All 3 tracked items within threshold. min: 5 ms, max: 45 ms, total: 50 ms
+```
+
+---
+
+## Installation
+
+### Add the Timer Ninja Dependency
+
+**Gradle:**
+```groovy
+implementation group: 'io.github.thanglequoc', name: 'timer-ninja', version: '1.3.0'
+```
+
+**Maven:**
+```xml
+
+ io.github.thanglequoc
+ timer-ninja
+ 1.3.0
+ compile
+
+```
+
+### Declare AspectJ Plugin
+
+**Gradle** — using [FreeFair AspectJ Gradle plugin](https://github.com/freefair/gradle-plugins):
+
+```groovy
+plugins {
+ id "io.freefair.aspectj.post-compile-weaving" version '9.1.0'
+}
+
+dependencies {
+ implementation group: 'io.github.thanglequoc', name: 'timer-ninja', version: '1.3.0'
+ aspect 'io.github.thanglequoc:timer-ninja:1.3.0'
+
+ // Enable this if you want to track methods in test classes
+ testAspect("io.github.thanglequoc:timer-ninja:1.3.0")
+}
+```
+
+**Maven** — using [Forked Mojo's AspectJ Plugin](https://github.com/dev-aspectj/aspectj-maven-plugin):
+
+```xml
+
+ dev.aspectj
+ aspectj-maven-plugin
+ 1.14.1
+
+
+ org.aspectj
+ aspectjtools
+ 1.9.25
+
+
+
+ ${java.version}
+
+
+ io.github.thanglequoc
+ timer-ninja
+
+
+
+
+
+
+ compile
+ test-compile
+
+
+
+
+```
+
+---
+
+## Global Configuration
+
+### Enable System.out Logging
+
+For simple console applications or quick testing:
+
+```java
+TimerNinjaConfiguration.getInstance().toggleSystemOutLog(true);
+```
+
+> Call this once at application startup. By default, Timer Ninja uses SLF4J logging.
+
+### Log Level
+
+The logger class is `io.github.thanglequoc.timerninja.TimerNinjaUtil` with default level `INFO`.
+
+To enable debug information:
+
+```xml
+
+```
+
+---
+
+## Best Practices
+
+### Choose Appropriate Time Units
+
+- **Seconds** — For long-running operations (API calls, file I/O, batch processing)
+- **Milliseconds** — For general application logic (default)
+- **Microseconds** — For performance-critical code (algorithms, calculations)
+
+### Use Thresholds Strategically
+
+```java
+// Too low - creates noise
+@TimerNinjaTracker(threshold = 10)
+
+// Too high - miss issues
+@TimerNinjaTracker(threshold = 5000)
+
+// Balanced - catches real issues
+@TimerNinjaTracker(threshold = 200)
+```
+
+### Track Entry Points
+
+Add tracking to high-level entry points (REST controllers, main methods) to capture full execution traces:
+
+```java
+@RestController
+public class UserController {
+ @TimerNinjaTracker
+ @GetMapping("/users/{id}")
+ public User getUser(@PathVariable Long id) {
+ return userService.findById(id);
+ }
+}
+```
+
+### Don't Track Everything
+
+**Focus on:**
+- Critical business logic
+- External API calls
+- Database operations
+- File I/O operations
+
+**Avoid tracking:**
+- Simple getters/setters
+- Very fast operations (< 1ms)
+- Trivial utility methods
+
+---
+
+## Troubleshooting
+
+### No Output in Logs
+
+1. Check if SLF4J provider is configured
+2. Verify log level is at least `INFO`
+3. Enable `System.out` for testing: `TimerNinjaConfiguration.getInstance().toggleSystemOutLog(true);`
+
+### Methods Not Being Tracked
+
+1. Verify AspectJ plugin is configured correctly
+2. Check that the dependency includes the aspect: `aspect 'io.github.thanglequoc:timer-ninja:1.3.0'`
+3. Ensure `enabled = true` (or not set) on the annotation