17
17
18
18
package org .apache .spark .ui .jobs
19
19
20
+ import scala .collection .mutable .ListBuffer
20
21
import scala .xml .{Node , NodeSeq , Unparsed }
21
22
22
23
import java .util .Date
23
24
import javax .servlet .http .HttpServletRequest
24
25
25
26
import org .apache .spark .ui .{UIUtils , WebUIPage }
26
- import org .apache .spark .ui .jobs .UIData .JobUIData
27
+ import org .apache .spark .ui .jobs .UIData .{ ExecutorUIData , JobUIData }
27
28
import org .apache .spark .JobExecutionStatus
28
29
29
30
/** Page showing list of all ongoing and recently finished jobs */
30
31
private [ui] class AllJobsPage (parent : JobsTab ) extends WebUIPage (" " ) {
31
32
private val startTime : Option [Long ] = parent.sc.map(_.startTime)
32
33
private val listener = parent.listener
34
+ private val JOBS_LEGEND =
35
+ <div class =" legend-area" ><svg width =" 200px" height =" 85px" >
36
+ < rect x= " 5px" y= " 5px" width= " 20px" height= " 15px"
37
+ rx= " 2px" ry= " 2px" stroke= " #97B0F8" fill= " #D5DDF6" ></ rect>
38
+ <text x =" 35px" y =" 17px" >Succeeded Job </text >
39
+ < rect x= " 5px" y= " 35px" width= " 20px" height= " 15px"
40
+ rx= " 2px" ry= " 2px" stroke= " #97B0F8" fill= " #FF5475" ></ rect>
41
+ <text x =" 35px" y =" 47px" >Failed Job </text >
42
+ < rect x= " 5px" y= " 65px" width= " 20px" height= " 15px"
43
+ rx= " 2px" ry= " 2px" stroke= " #97B0F8" fill= " #FDFFCA" ></ rect>
44
+ <text x =" 35px" y =" 77px" >Running Job </text >
45
+ </svg ></div >.toString.filter(_ != '\n ' )
33
46
34
47
private def getlastStageDescription (job : JobUIData ) = {
35
48
val lastStageInfo = Option (job.stageIds)
@@ -41,71 +54,97 @@ private[ui] class AllJobsPage(parent: JobsTab) extends WebUIPage("") {
41
54
lastStageData.flatMap(_.description).getOrElse(" " )
42
55
}
43
56
44
- private def applicationTimelineView (jobs : Seq [JobUIData ], now : Long ): Seq [Node ] = {
45
- val jobEventJsonAsStrSeq = jobs.flatMap { jobUIData =>
46
- val jobId = jobUIData.jobId
47
- val status = jobUIData.status
48
- val jobDescription = getlastStageDescription(jobUIData)
49
- val submissionTimeOpt = jobUIData.submissionTime
50
- val completionTimeOpt = jobUIData.completionTime
51
-
52
- if (status == JobExecutionStatus .UNKNOWN || submissionTimeOpt.isEmpty ||
53
- completionTimeOpt.isEmpty && status != JobExecutionStatus .RUNNING ) {
54
- None
55
- }
57
+ private def makeTimeline (jobs : Seq [JobUIData ], executors : Seq [ExecutorUIData ]): Seq [Node ] = {
56
58
57
- val submissionTime = submissionTimeOpt.get
58
- val completionTime = completionTimeOpt.getOrElse(now)
59
- val classNameByStatus = status match {
60
- case JobExecutionStatus .SUCCEEDED => " succeeded"
61
- case JobExecutionStatus .FAILED => " failed"
62
- case JobExecutionStatus .RUNNING => " running"
63
- }
59
+ def makeJobEvent (jobUIDatas : Seq [JobUIData ]): Seq [String ] = {
60
+ jobUIDatas.flatMap { jobUIData =>
61
+ val jobId = jobUIData.jobId
62
+ val status = jobUIData.status
63
+ val jobDescription = getlastStageDescription(jobUIData)
64
+ val submissionTimeOpt = jobUIData.submissionTime
65
+ val completionTimeOpt = jobUIData.completionTime
66
+
67
+ if (status == JobExecutionStatus .UNKNOWN || submissionTimeOpt.isEmpty ||
68
+ completionTimeOpt.isEmpty && status != JobExecutionStatus .RUNNING ) {
69
+ None
70
+ }
71
+
72
+ val submissionTime = submissionTimeOpt.get
73
+ val completionTime = completionTimeOpt.getOrElse(System .currentTimeMillis())
74
+ val classNameByStatus = status match {
75
+ case JobExecutionStatus .SUCCEEDED => " succeeded"
76
+ case JobExecutionStatus .FAILED => " failed"
77
+ case JobExecutionStatus .RUNNING => " running"
78
+ }
64
79
65
- val jobEventJsonAsStr =
66
- s """
80
+ val jobEventJsonAsStr =
81
+ s """
67
82
|{
68
83
| 'className': 'job application-timeline-object ${classNameByStatus}',
69
84
| 'group': 'jobs',
70
85
| 'start': new Date( ${submissionTime}),
71
86
| 'end': new Date( ${completionTime}),
72
- | 'content': '<div class="application-timeline-content"> ${jobDescription} (Job ${jobId})</div>',
87
+ | 'content': '<div class="application-timeline-content">' +
88
+ | ' ${jobDescription} (Job ${jobId})</div>',
73
89
| 'title': ' ${jobDescription} (Job ${jobId}) \\ nStatus: ${status}\\ n' +
74
90
| 'Submission Time: ${UIUtils .formatDate(new Date (submissionTime))}' +
75
91
| ' ${
76
- if (status != JobExecutionStatus .RUNNING ) {
77
- s """ \\ nCompletion Time: ${UIUtils .formatDate(new Date (completionTime))}"""
78
- } else {
79
- " "
80
- }
92
+ if (status != JobExecutionStatus .RUNNING ) {
93
+ s """ \\ nCompletion Time: ${UIUtils .formatDate(new Date (completionTime))}"""
94
+ } else {
95
+ " "
96
+ }
81
97
}'
82
98
|}
83
99
""" .stripMargin
84
- Some (jobEventJsonAsStr)
100
+ Some (jobEventJsonAsStr)
101
+ }
85
102
}
86
103
87
- val executorEventJsonAsStrSeq =
88
- (listener.executorIdToAddedTime ++
89
- listener.executorIdToRemovedTimeAndReason).map { event =>
90
- val (executorId : String , status : String , time : Long , reason : Option [String ]) =
91
- event match {
92
- case (executorId, (removedTime, reason)) =>
93
- (executorId, " removed" , removedTime, Some (reason))
94
- case (executorId, addedTime) =>
95
- (executorId, " added" , addedTime, None )
96
- }
97
- s """
98
- |{
99
- | 'className': 'executor ${status}',
100
- | 'group': 'executors',
101
- | 'start': new Date( ${time}),
102
- | 'content': '<div>Executor ${executorId} ${status}</div>',
103
- | 'title': ' ${if (status == " added" ) " Added" else " Removed" } ' +
104
- | 'at ${UIUtils .formatDate(new Date (time))}' +
105
- | ' ${if (reason.isDefined) s """ \\ nReason: ${reason.get}""" else " " }'
106
- |}
107
- """ .stripMargin
104
+ def makeExecutorEvent (executorUIDatas : Seq [ExecutorUIData ]): Seq [String ] = {
105
+ val events = ListBuffer [String ]()
106
+ executorUIDatas.foreach { event =>
107
+
108
+ if (event.startTime.isDefined) {
109
+ val addedEvent =
110
+ s """
111
+ |{
112
+ | 'className': 'executor added',
113
+ | 'group': 'executors',
114
+ | 'start': new Date( ${event.startTime.get}),
115
+ | 'content': '<div>Executor ${event.executorId} added</div>',
116
+ | 'title': 'Added at ${UIUtils .formatDate(new Date (event.startTime.get))}'
117
+ |}
118
+ """ .stripMargin
119
+ events += addedEvent
120
+ }
121
+
122
+ if (event.finishTime.isDefined) {
123
+ val removedEvent =
124
+ s """
125
+ |{
126
+ | 'className': 'executor removed',
127
+ | 'group': 'executors',
128
+ | 'start': new Date( ${event.finishTime.get}),
129
+ | 'content': '<div>Executor ${event.executorId} removed</div>',
130
+ | 'title': 'Removed at ${UIUtils .formatDate(new Date (event.finishTime.get))}' +
131
+ | ' ${
132
+ if (event.finishReason.isDefined) {
133
+ s """ \\ nReason: ${event.finishReason.get}"""
134
+ } else {
135
+ " "
136
+ }
137
+ }'
138
+ |}
139
+ """ .stripMargin
140
+ events += removedEvent
141
+ }
108
142
}
143
+ events.toSeq
144
+ }
145
+
146
+ val jobEventJsonAsStrSeq = makeJobEvent(jobs)
147
+ val executorEventJsonAsStrSeq = makeExecutorEvent(executors)
109
148
110
149
val executorsLegend =
111
150
<div class =" legend-area" ><svg width =" 200px" height =" 55px" >
@@ -117,19 +156,6 @@ private[ui] class AllJobsPage(parent: JobsTab) extends WebUIPage("") {
117
156
<text x =" 35px" y =" 47px" >Executor Removed </text >
118
157
</svg ></div >.toString.filter(_ != '\n ' )
119
158
120
- val jobsLegend =
121
- <div class =" legend-area" ><svg width =" 200px" height =" 85px" >
122
- < rect x= " 5px" y= " 5px" width= " 20px" height= " 15px"
123
- rx= " 2px" ry= " 2px" stroke= " #97B0F8" fill= " #D5DDF6" ></ rect>
124
- <text x =" 35px" y =" 17px" >Succeeded Job </text >
125
- < rect x= " 5px" y= " 35px" width= " 20px" height= " 15px"
126
- rx= " 2px" ry= " 2px" stroke= " #97B0F8" fill= " #FF5475" ></ rect>
127
- <text x =" 35px" y =" 47px" >Failed Job </text >
128
- < rect x= " 5px" y= " 65px" width= " 20px" height= " 15px"
129
- rx= " 2px" ry= " 2px" stroke= " #97B0F8" fill= " #FDFFCA" ></ rect>
130
- <text x =" 35px" y =" 77px" >Running Job </text >
131
- </svg ></div >.toString.filter(_ != '\n ' )
132
-
133
159
val groupJsonArrayAsStr =
134
160
s """
135
161
|[
@@ -139,7 +165,7 @@ private[ui] class AllJobsPage(parent: JobsTab) extends WebUIPage("") {
139
165
| },
140
166
| {
141
167
| 'id': 'jobs',
142
- | 'content': '<div>Jobs</div> ${jobsLegend }',
168
+ | 'content': '<div>Jobs</div> ${JOBS_LEGEND }',
143
169
| }
144
170
|]
145
171
""" .stripMargin
@@ -155,7 +181,8 @@ private[ui] class AllJobsPage(parent: JobsTab) extends WebUIPage("") {
155
181
</div > ++
156
182
<div id =" application-timeline" ></div > ++
157
183
<script type =" text/javascript" >
158
- {Unparsed (s " drawApplicationTimeline( ${groupJsonArrayAsStr}, ${eventArrayAsStr}); " )}
184
+ {Unparsed (s " drawApplicationTimeline( ${groupJsonArrayAsStr}, " +
185
+ s " ${eventArrayAsStr}, ${startTime.getOrElse(- 1L )}); " )}
159
186
</script >
160
187
}
161
188
@@ -180,7 +207,7 @@ private[ui] class AllJobsPage(parent: JobsTab) extends WebUIPage("") {
180
207
}
181
208
182
209
val lastStageName = lastStageInfo.map(_.name).getOrElse(" (Unknown Stage Name)" )
183
- val lastStageDescription = getlastStageDescription(job)// lastStageData.flatMap(_.description).getOrElse("")
210
+ val lastStageDescription = getlastStageDescription(job)
184
211
val duration : Option [Long ] = {
185
212
job.submissionTime.map { start =>
186
213
val end = job.completionTime.getOrElse(System .currentTimeMillis())
@@ -229,7 +256,6 @@ private[ui] class AllJobsPage(parent: JobsTab) extends WebUIPage("") {
229
256
val activeJobs = listener.activeJobs.values.toSeq
230
257
val completedJobs = listener.completedJobs.reverse.toSeq
231
258
val failedJobs = listener.failedJobs.reverse.toSeq
232
- val now = System .currentTimeMillis
233
259
234
260
val activeJobsTable =
235
261
jobsTable(activeJobs.sortBy(_.submissionTime.getOrElse(- 1L )).reverse)
@@ -249,7 +275,7 @@ private[ui] class AllJobsPage(parent: JobsTab) extends WebUIPage("") {
249
275
// Total duration is not meaningful unless the UI is live
250
276
<li >
251
277
<strong >Total Duration : </strong >
252
- {UIUtils .formatDuration(now - startTime.get)}
278
+ {UIUtils .formatDuration(System .currentTimeMillis() - startTime.get)}
253
279
</li >
254
280
}}
255
281
<li >
@@ -284,8 +310,9 @@ private[ui] class AllJobsPage(parent: JobsTab) extends WebUIPage("") {
284
310
</div >
285
311
286
312
var content = summary
313
+ val appStartTime =
287
314
content ++= <h4 >Events on Application Timeline </h4 > ++
288
- applicationTimelineView (activeJobs ++ completedJobs ++ failedJobs, now )
315
+ makeTimeline (activeJobs ++ completedJobs ++ failedJobs, listener.executors )
289
316
290
317
if (shouldShowActiveJobs) {
291
318
content ++= <h4 id =" active" >Active Jobs ({activeJobs.size})</h4 > ++
0 commit comments