@@ -22,10 +22,13 @@ import java.util.concurrent.Semaphore
2222import scala .collection .mutable
2323import scala .collection .JavaConverters ._
2424
25+ import org .mockito .Mockito
2526import org .scalatest .Matchers
2627
2728import org .apache .spark ._
2829import org .apache .spark .executor .TaskMetrics
30+ import org .apache .spark .internal .config .LISTENER_BUS_EVENT_QUEUE_SIZE
31+ import org .apache .spark .metrics .MetricsSystem
2932import org .apache .spark .util .{ResetSystemProperties , RpcUtils }
3033
3134class SparkListenerSuite extends SparkFunSuite with LocalSparkContext with Matchers
@@ -36,14 +39,17 @@ class SparkListenerSuite extends SparkFunSuite with LocalSparkContext with Match
3639
3740 val jobCompletionTime = 1421191296660L
3841
42+ private val mockSparkContext : SparkContext = Mockito .mock(classOf [SparkContext ])
43+ private val mockMetricsSystem : MetricsSystem = Mockito .mock(classOf [MetricsSystem ])
44+
3945 test(" don't call sc.stop in listener" ) {
4046 sc = new SparkContext (" local" , " SparkListenerSuite" , new SparkConf ())
4147 val listener = new SparkContextStoppingListener (sc)
42- val bus = new LiveListenerBus (sc)
48+ val bus = new LiveListenerBus (sc.conf )
4349 bus.addListener(listener)
4450
4551 // Starting listener bus should flush all buffered events
46- bus.start()
52+ bus.start(sc, sc.env.metricsSystem )
4753 bus.post(SparkListenerJobEnd (0 , jobCompletionTime, JobSucceeded ))
4854 bus.waitUntilEmpty(WAIT_TIMEOUT_MILLIS )
4955
@@ -52,35 +58,50 @@ class SparkListenerSuite extends SparkFunSuite with LocalSparkContext with Match
5258 }
5359
5460 test(" basic creation and shutdown of LiveListenerBus" ) {
55- sc = new SparkContext ( " local " , " SparkListenerSuite " , new SparkConf () )
61+ val conf = new SparkConf ()
5662 val counter = new BasicJobCounter
57- val bus = new LiveListenerBus (sc )
63+ val bus = new LiveListenerBus (conf )
5864 bus.addListener(counter)
5965
60- // Listener bus hasn't started yet, so posting events should not increment counter
66+ // Metrics are initially empty.
67+ assert(bus.metrics.numEventsReceived.getCount === 0 )
68+ assert(bus.metrics.numDroppedEvents.getCount === 0 )
69+ assert(bus.metrics.queueSize.getValue === 0 )
70+ assert(bus.metrics.eventProcessingTime.getCount === 0 )
71+
72+ // Post five events:
6173 (1 to 5 ).foreach { _ => bus.post(SparkListenerJobEnd (0 , jobCompletionTime, JobSucceeded )) }
74+
75+ // Five messages should be marked as received and queued, but no messages should be posted to
76+ // listeners yet because the the listener bus hasn't been started.
77+ assert(bus.metrics.numEventsReceived.getCount === 5 )
78+ assert(bus.metrics.queueSize.getValue === 5 )
6279 assert(counter.count === 0 )
6380
6481 // Starting listener bus should flush all buffered events
65- bus.start()
82+ bus.start(mockSparkContext, mockMetricsSystem)
83+ Mockito .verify(mockMetricsSystem).registerSource(bus.metrics)
6684 bus.waitUntilEmpty(WAIT_TIMEOUT_MILLIS )
6785 assert(counter.count === 5 )
86+ assert(bus.metrics.queueSize.getValue === 0 )
87+ assert(bus.metrics.eventProcessingTime.getCount === 5 )
6888
6989 // After listener bus has stopped, posting events should not increment counter
7090 bus.stop()
7191 (1 to 5 ).foreach { _ => bus.post(SparkListenerJobEnd (0 , jobCompletionTime, JobSucceeded )) }
7292 assert(counter.count === 5 )
93+ assert(bus.metrics.numEventsReceived.getCount === 5 )
7394
7495 // Listener bus must not be started twice
7596 intercept[IllegalStateException ] {
76- val bus = new LiveListenerBus (sc )
77- bus.start()
78- bus.start()
97+ val bus = new LiveListenerBus (conf )
98+ bus.start(mockSparkContext, mockMetricsSystem )
99+ bus.start(mockSparkContext, mockMetricsSystem )
79100 }
80101
81102 // ... or stopped before starting
82103 intercept[IllegalStateException ] {
83- val bus = new LiveListenerBus (sc )
104+ val bus = new LiveListenerBus (conf )
84105 bus.stop()
85106 }
86107 }
@@ -107,12 +128,11 @@ class SparkListenerSuite extends SparkFunSuite with LocalSparkContext with Match
107128 drained = true
108129 }
109130 }
110- sc = new SparkContext (" local" , " SparkListenerSuite" , new SparkConf ())
111- val bus = new LiveListenerBus (sc)
131+ val bus = new LiveListenerBus (new SparkConf ())
112132 val blockingListener = new BlockingListener
113133
114134 bus.addListener(blockingListener)
115- bus.start()
135+ bus.start(mockSparkContext, mockMetricsSystem )
116136 bus.post(SparkListenerJobEnd (0 , jobCompletionTime, JobSucceeded ))
117137
118138 listenerStarted.acquire()
@@ -138,6 +158,44 @@ class SparkListenerSuite extends SparkFunSuite with LocalSparkContext with Match
138158 assert(drained)
139159 }
140160
161+ test(" metrics for dropped listener events" ) {
162+ val bus = new LiveListenerBus (new SparkConf ().set(LISTENER_BUS_EVENT_QUEUE_SIZE , 1 ))
163+
164+ val listenerStarted = new Semaphore (0 )
165+ val listenerWait = new Semaphore (0 )
166+
167+ bus.addListener(new SparkListener {
168+ override def onJobEnd (jobEnd : SparkListenerJobEnd ): Unit = {
169+ listenerStarted.release()
170+ listenerWait.acquire()
171+ }
172+ })
173+
174+ bus.start(mockSparkContext, mockMetricsSystem)
175+
176+ // Post a message to the listener bus and wait for processing to begin:
177+ bus.post(SparkListenerJobEnd (0 , jobCompletionTime, JobSucceeded ))
178+ listenerStarted.acquire()
179+ assert(bus.metrics.queueSize.getValue === 0 )
180+ assert(bus.metrics.numDroppedEvents.getCount === 0 )
181+
182+ // If we post an additional message then it should remain in the queue because the listener is
183+ // busy processing the first event:
184+ bus.post(SparkListenerJobEnd (0 , jobCompletionTime, JobSucceeded ))
185+ assert(bus.metrics.queueSize.getValue === 1 )
186+ assert(bus.metrics.numDroppedEvents.getCount === 0 )
187+
188+ // The queue is now full, so any additional events posted to the listener will be dropped:
189+ bus.post(SparkListenerJobEnd (0 , jobCompletionTime, JobSucceeded ))
190+ assert(bus.metrics.queueSize.getValue === 1 )
191+ assert(bus.metrics.numDroppedEvents.getCount === 1 )
192+
193+
194+ // Allow the the remaining events to be processed so we can stop the listener bus:
195+ listenerWait.release(2 )
196+ bus.stop()
197+ }
198+
141199 test(" basic creation of StageInfo" ) {
142200 sc = new SparkContext (" local" , " SparkListenerSuite" )
143201 val listener = new SaveStageAndTaskInfo
@@ -354,14 +412,13 @@ class SparkListenerSuite extends SparkFunSuite with LocalSparkContext with Match
354412 val badListener = new BadListener
355413 val jobCounter1 = new BasicJobCounter
356414 val jobCounter2 = new BasicJobCounter
357- sc = new SparkContext (" local" , " SparkListenerSuite" , new SparkConf ())
358- val bus = new LiveListenerBus (sc)
415+ val bus = new LiveListenerBus (new SparkConf ())
359416
360417 // Propagate events to bad listener first
361418 bus.addListener(badListener)
362419 bus.addListener(jobCounter1)
363420 bus.addListener(jobCounter2)
364- bus.start()
421+ bus.start(mockSparkContext, mockMetricsSystem )
365422
366423 // Post events to all listeners, and wait until the queue is drained
367424 (1 to 5 ).foreach { _ => bus.post(SparkListenerJobEnd (0 , jobCompletionTime, JobSucceeded )) }
0 commit comments