Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -968,15 +968,17 @@ case class StageAggTaskMetricsProfileResult(
def aggregateStageProfileMetric(
other: StageAggTaskMetricsProfileResult
): StageAggTaskMetricsProfileResult = {
val mergedNumTasks = this.numTasks + other.numTasks
val mergedDurationSum = this.durationSum + other.durationSum
StageAggTaskMetricsProfileResult(
id = this.id,
numTasks = this.numTasks + other.numTasks,
numTasks = mergedNumTasks,
duration = Option(this.duration.getOrElse(0L) + other.duration.getOrElse(0L)),
diskBytesSpilledSum = this.diskBytesSpilledSum + other.diskBytesSpilledSum,
durationSum = this.durationSum + other.durationSum,
durationSum = mergedDurationSum,
durationMax = Math.max(this.durationMax, other.durationMax),
durationMin = Math.min(this.durationMin, other.durationMin),

@amahussein amahussein Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flagging for awareness; no change requested here. An attempt that ran zero tasks still produces a row, and finalizeAggregation calls resetFields() when numTasks < 1, which sets durationMin to 0. Math.min(0, x) then publishes 0 as the stage minimum. Measured: a stage whose only task took 907 ms reports duration_min of 0 once a second attempt is submitted and aborted before any task end, in an otherwise complete log. Your fix makes durationAvg immune to this, which leaves durationMin the last field in this merge that a zero-task attempt corrupts.

Filed separately as #2151.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for isolating and filing this. I have kept the zero-task durationMin behavior out of this PR and referenced #2151 in the updated description.

durationAvg = (this.durationAvg + other.durationAvg) / 2,
durationAvg = ToolUtils.calculateAverage(mergedDurationSum, mergedNumTasks, 1),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is character-for-character what TaskMetricsAccumRec.finalizeAggregation uses for a single attempt, so a merged stage now publishes what one attempt carrying the same tasks would. Job and SQL rows already pooled this way, so the stage row was the only member of the family that did not.

executorCPUTimeSum = this.executorCPUTimeSum + other.executorCPUTimeSum,
executorDeserializeCpuTimeSum = this.executorDeserializeCpuTimeSum +
other.executorDeserializeCpuTimeSum,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,50 @@ class AnalysisSuite extends AnyFunSuite {
"fixture no longer distinguishes the two rollup formulas; the guard is now vacuous")
}

test("stage attempt avg pools task durations rather than averaging attempt means") {
val firstAttempt = StageAggTaskMetricsProfileResult(
id = 1L,
numTasks = 2,
duration = None,
diskBytesSpilledSum = 0L,
durationSum = 800L,
durationMax = 0L,
durationMin = 0L,
durationAvg = 400.0,
executorCPUTimeSum = 0L,
executorDeserializeCpuTimeSum = 0L,
executorDeserializeTimeSum = 0L,
executorRunTimeSum = 0L,
inputBytesReadSum = 0L,
inputBytesReadMax = 0L,
inputRecordsReadSum = 0L,
jvmGCTimeSum = 0L,
memoryBytesSpilledSum = 0L,
outputBytesWrittenSum = 0L,
outputRecordsWrittenSum = 0L,
peakExecutionMemoryMax = 0L,
resultSerializationTimeSum = 0L,
resultSizeMax = 0L,
srFetchWaitTimeSum = 0L,
srLocalBlocksFetchedSum = 0L,
srcLocalBytesReadSum = 0L,
srRemoteBlocksFetchSum = 0L,
srRemoteBytesReadSum = 0L,
srRemoteBytesReadToDiskSum = 0L,
srTotalBytesReadSum = 0L,
swBytesWrittenSum = 0L,
swRecordsWrittenSum = 0L,
swWriteTimeSum = 0L)
val retryAttempt = firstAttempt.copy(
numTasks = 1,
durationSum = 200L,
durationAvg = 200.0)

val result = firstAttempt.aggregateStageProfileMetric(retryAttempt)

assert(result.durationAvg === 333.3)
}

test("dispersion columns are consistent with the row they sit in") {
val logs = Array(s"$logDir/gpu_oom_eventlog.zstd")
val apps = ToolTestUtils.processProfileApps(logs, sparkSession)
Expand Down
Loading