@@ -2555,6 +2555,110 @@ TEST_F(TaskTest, updateStatsWhileCloseOffThreadDriver) {
25552555 ASSERT_GE (totalOffThreadTime.sum , 0 );
25562556}
25572557
2558+ // Verifies that driver-level lifecycle timing metrics
2559+ // (driverQueuedWallNanos, driverOnThreadWallNanos, driverBlockedWallNanos)
2560+ // are reported correctly for each pipeline.
2561+ TEST_F (TaskTest, driverLifecycleTimingStats) {
2562+ auto data = makeRowVector ({
2563+ makeFlatVector<int64_t >(1'000 , [](auto row) { return row % 10 ; }),
2564+ makeFlatVector<int64_t >(1'000 , [](auto row) { return row; }),
2565+ });
2566+
2567+ auto buildData = makeRowVector (
2568+ {" u0" , " u1" },
2569+ {
2570+ makeFlatVector<int64_t >(100 , [](auto row) { return row % 10 ; }),
2571+ makeFlatVector<int64_t >(100 , [](auto row) { return row; }),
2572+ });
2573+
2574+ auto planNodeIdGenerator = std::make_shared<core::PlanNodeIdGenerator>();
2575+
2576+ // Build a hash join plan to get multiple pipelines:
2577+ // Pipeline 0 (probe): Values -> HashProbe -> sink
2578+ // Pipeline 1 (build): Values -> HashBuild
2579+ core::PlanNodeId probeScanId;
2580+ auto plan =
2581+ PlanBuilder (planNodeIdGenerator)
2582+ .values ({data})
2583+ .capturePlanNodeId (probeScanId)
2584+ .hashJoin (
2585+ {" c0" },
2586+ {" u0" },
2587+ PlanBuilder (planNodeIdGenerator).values ({buildData}).planNode (),
2588+ " " ,
2589+ {" c0" , " c1" })
2590+ .planNode ();
2591+
2592+ auto result = AssertQueryBuilder (plan).copyResults (pool_.get ());
2593+ ASSERT_GT (result->size (), 0 );
2594+
2595+ // Get task stats from the most recently completed task.
2596+ auto tasks = Task::getRunningTasks ();
2597+ // Tasks may already be cleaned up. Use a cursor-based approach instead.
2598+
2599+ // Re-run using CursorParameters to access the task directly.
2600+ CursorParameters params;
2601+ params.planNode = plan;
2602+ params.queryCtx = core::QueryCtx::create (driverExecutor_.get ());
2603+ params.maxDrivers = 2 ;
2604+
2605+ auto cursor = TaskCursor::create (params);
2606+ while (cursor->moveNext ()) {
2607+ }
2608+ auto task = cursor->task ();
2609+ ASSERT_TRUE (waitForTaskCompletion (task.get ()));
2610+
2611+ auto taskStats = task->taskStats ();
2612+ // We should have at least 2 pipelines (probe and build).
2613+ ASSERT_GE (taskStats.pipelineStats .size (), 2 );
2614+
2615+ // Helper to find a driver stat by substring match across all pipelines.
2616+ auto findDriverStat =
2617+ [&taskStats](
2618+ const std::string& statSubstr) -> std::optional<RuntimeMetric> {
2619+ for (const auto & pipeline : taskStats.pipelineStats ) {
2620+ for (const auto & ds : pipeline.driverStats ) {
2621+ for (const auto & [name, metric] : ds.runtimeStats ) {
2622+ if (name.find (statSubstr) != std::string::npos) {
2623+ return metric;
2624+ }
2625+ }
2626+ }
2627+ }
2628+ return std::nullopt ;
2629+ };
2630+
2631+ // Verify driver lifecycle metrics exist and have sensible values.
2632+ auto queued = findDriverStat (" driverQueuedWallNanos" );
2633+ auto onThread = findDriverStat (" driverOnThreadWallNanos" );
2634+ auto blocked = findDriverStat (" driverBlockedWallNanos" );
2635+
2636+ ASSERT_TRUE (queued.has_value ()) << " driverQueuedWallNanos not found" ;
2637+ ASSERT_TRUE (onThread.has_value ()) << " driverOnThreadWallNanos not found" ;
2638+ ASSERT_TRUE (blocked.has_value ()) << " driverBlockedWallNanos not found" ;
2639+
2640+ // Each metric should have count >= 1 (at least one driver reported).
2641+ ASSERT_GE (queued->count , 1 );
2642+ ASSERT_GE (onThread->count , 1 );
2643+ ASSERT_GE (blocked->count , 1 );
2644+
2645+ // On-thread time must be positive (drivers did real work).
2646+ ASSERT_GT (onThread->sum , 0 );
2647+ ASSERT_GT (onThread->max , 0 );
2648+
2649+ // Queued and blocked times must be non-negative.
2650+ ASSERT_GE (queued->sum , 0 );
2651+ ASSERT_GE (blocked->sum , 0 );
2652+
2653+ // Verify the stat names contain the pipeline prefix and source operator.
2654+ // Look for probe pipeline stats (source is Values with probeScanId).
2655+ auto probePrefix = fmt::format (" P0-Values.{}" , probeScanId);
2656+ auto probeOnThread = findDriverStat (probePrefix + " .driverOnThreadWallNanos" );
2657+ ASSERT_TRUE (probeOnThread.has_value ())
2658+ << " Probe pipeline stat not found with prefix: " << probePrefix;
2659+ ASSERT_GT (probeOnThread->max , 0 );
2660+ }
2661+
25582662DEBUG_ONLY_TEST_F (TaskTest, driverEnqueAfterFailedAndPausedTask) {
25592663 const auto data = makeRowVector ({
25602664 makeFlatVector<int64_t >(50 , [](auto row) { return row; }),
0 commit comments