@@ -1694,6 +1694,121 @@ void close_timeout_zero() throws Exception {
16941694 "close(0, SECONDS) should return quickly, elapsed=" + elapsed + "ms" );
16951695 }
16961696
1697+ // ---- D4: Pool Metrics ----
1698+
1699+ @ Test
1700+ void metrics_snapshotAfterBuild () throws Exception {
1701+ PythonEmbedPool pool = PythonEmbedPool .builder ().minPool (2 ).maxPool (4 ).build ();
1702+ try {
1703+ waitForPoolSize (pool , 2 , 5000 );
1704+
1705+ PoolMetrics m = pool .metrics ();
1706+ assertEquals (2 , m .poolSize ());
1707+ assertEquals (0 , m .activeCount ());
1708+ assertEquals (2 , m .idleCount ());
1709+ assertEquals (2 , m .minPool ());
1710+ assertEquals (4 , m .maxPool ());
1711+ assertTrue (m .uptimeMs () >= 0 , "uptimeMs should be non-negative" );
1712+ } finally {
1713+ pool .close ();
1714+ }
1715+ }
1716+
1717+ @ Test
1718+ @ Timeout (value = 30 , unit = TimeUnit .SECONDS )
1719+ void metrics_idleCount_reflectsBusyInstances () throws Exception {
1720+ PythonEmbedPool pool = PythonEmbedPool .builder ().maxPool (2 ).build ();
1721+ try {
1722+ // Use exec instead of eval because Python eval() does not
1723+ // allow statement-level constructs like import + sleep.
1724+ CompletableFuture <Void > task = pool .exec (
1725+ "import time; time.sleep(3)" );
1726+
1727+ // Poll until the task is acquired and instance becomes busy
1728+ long deadline = System .currentTimeMillis () + 5000 ;
1729+ while (pool .activeCount () == 0 && System .currentTimeMillis () < deadline ) {
1730+ Thread .sleep (50 );
1731+ }
1732+
1733+ PoolMetrics m = pool .metrics ();
1734+ assertEquals (1 , m .poolSize ());
1735+ assertEquals (1 , m .activeCount (), "Instance should be busy while task runs" );
1736+ assertEquals (0 , m .idleCount ());
1737+
1738+ task .get (10 , TimeUnit .SECONDS );
1739+
1740+ // Poll until instance is released
1741+ deadline = System .currentTimeMillis () + 5000 ;
1742+ while (pool .activeCount () > 0 && System .currentTimeMillis () < deadline ) {
1743+ Thread .sleep (50 );
1744+ }
1745+ m = pool .metrics ();
1746+ assertEquals (1 , m .poolSize ());
1747+ assertEquals (0 , m .activeCount (), "Instance should be idle after task completes" );
1748+ assertEquals (1 , m .idleCount ());
1749+ } finally {
1750+ pool .close ();
1751+ }
1752+ }
1753+
1754+ @ Test
1755+ @ Timeout (value = 30 , unit = TimeUnit .SECONDS )
1756+ void metrics_idleCount_afterScaleUp () throws Exception {
1757+ PythonEmbedPool pool = PythonEmbedPool .builder ().minPool (1 ).maxPool (2 ).build ();
1758+ try {
1759+ // Submit two long-running tasks to trigger scale-up
1760+ CompletableFuture <Void > t1 = pool .exec (
1761+ "import time; time.sleep(3)" );
1762+ // Poll until first task makes the instance busy
1763+ long deadline = System .currentTimeMillis () + 5000 ;
1764+ while (pool .activeCount () == 0 && System .currentTimeMillis () < deadline ) {
1765+ Thread .sleep (50 );
1766+ }
1767+ CompletableFuture <Void > t2 = pool .exec (
1768+ "import time; time.sleep(3)" );
1769+
1770+ waitForPoolSize (pool , 2 , 5000 );
1771+
1772+ PoolMetrics m = pool .metrics ();
1773+ assertEquals (2 , m .poolSize ());
1774+ assertEquals (2 , m .activeCount (),
1775+ "Both instances should be busy during concurrent execution" );
1776+ assertEquals (0 , m .idleCount ());
1777+
1778+ t1 .get (10 , TimeUnit .SECONDS );
1779+ t2 .get (10 , TimeUnit .SECONDS );
1780+
1781+ // Poll until both instances are released
1782+ deadline = System .currentTimeMillis () + 5000 ;
1783+ while (pool .activeCount () > 0 && System .currentTimeMillis () < deadline ) {
1784+ Thread .sleep (50 );
1785+ }
1786+ m = pool .metrics ();
1787+ assertEquals (0 , m .activeCount (),
1788+ "All instances should be idle after tasks complete" );
1789+ assertEquals (2 , m .idleCount ());
1790+ } finally {
1791+ pool .close ();
1792+ }
1793+ }
1794+
1795+ @ Test
1796+ void metrics_uptimeMs_increases () throws Exception {
1797+ PythonEmbedPool pool = PythonEmbedPool .builder ().maxPool (1 ).build ();
1798+ try {
1799+ PoolMetrics m1 = pool .metrics ();
1800+ Thread .sleep (200 );
1801+ PoolMetrics m2 = pool .metrics ();
1802+
1803+ assertTrue (m2 .uptimeMs () > m1 .uptimeMs (),
1804+ "uptimeMs should increase over time" );
1805+ assertTrue (m2 .uptimeMs () >= 200 ,
1806+ "uptimeMs should be at least the sleep duration" );
1807+ } finally {
1808+ pool .close ();
1809+ }
1810+ }
1811+
16971812 /**
16981813 * Waits for the pool to reach the given size, polling every 100ms.
16991814 */
0 commit comments