@@ -22,6 +22,9 @@ import javax.servlet.http.HttpServletRequest
2222import scala .xml .Node
2323
2424import org .apache .spark .ui .{WebUIPage , UIUtils }
25+ import scala .collection .immutable .ListMap
26+ import scala .collection .mutable .HashMap
27+ import scala .collection .mutable .ArrayBuffer
2528
2629private [history] class HistoryPage (parent : HistoryServer ) extends WebUIPage (" " ) {
2730
@@ -34,18 +37,31 @@ private[history] class HistoryPage(parent: HistoryServer) extends WebUIPage("")
3437 val requestedIncomplete =
3538 Option (request.getParameter(" showIncomplete" )).getOrElse(" false" ).toBoolean
3639
37- val allApps = parent.getApplicationList().filter(_.completed != requestedIncomplete)
38- val actualFirst = if (requestedFirst < allApps.size) requestedFirst else 0
39- val apps = allApps.slice(actualFirst, Math .min(actualFirst + pageSize, allApps.size))
40-
40+ val allCompletedAppsNAttempts =
41+ parent.getApplicationList().filter(_.completed != requestedIncomplete)
42+ val (hasAttemptInfo, appToAttemptMap) = getApplicationLevelList(allCompletedAppsNAttempts)
43+
44+ val allAppsSize = allCompletedAppsNAttempts.size
45+
46+ val actualFirst = if (requestedFirst < allAppsSize) requestedFirst else 0
47+ val apps =
48+ allCompletedAppsNAttempts.slice(actualFirst, Math .min(actualFirst + pageSize, allAppsSize))
49+ val appWithAttemptsDisplayList =
50+ appToAttemptMap.slice(actualFirst, Math .min(actualFirst + pageSize, allAppsSize))
51+
4152 val actualPage = (actualFirst / pageSize) + 1
42- val last = Math .min(actualFirst + pageSize, allApps.size ) - 1
43- val pageCount = allApps.size / pageSize + (if (allApps.size % pageSize > 0 ) 1 else 0 )
53+ val last = Math .min(actualFirst + pageSize, allAppsSize ) - 1
54+ val pageCount = allAppsSize / pageSize + (if (allAppsSize % pageSize > 0 ) 1 else 0 )
4455
4556 val secondPageFromLeft = 2
4657 val secondPageFromRight = pageCount - 1
4758
48- val appTable = UIUtils .listingTable(appHeader, appRow, apps)
59+ val appTable =
60+ if (hasAttemptInfo) {
61+ UIUtils .listingTable(appWithAttemptHeader, appWithAttemptRow, appWithAttemptsDisplayList)
62+ } else {
63+ UIUtils .listingTable(appHeader, appRow, apps)
64+ }
4965 val providerConfig = parent.getProviderConfig()
5066 val content =
5167 <div class =" row-fluid" >
@@ -59,15 +75,15 @@ private[history] class HistoryPage(parent: HistoryServer) extends WebUIPage("")
5975 // to the first and last page. If the current page +/- `plusOrMinus` is greater
6076 // than the 2nd page from the first page or less than the 2nd page from the last
6177 // page, `...` will be displayed.
62- if (allApps.size > 0 ) {
78+ if (allAppsSize > 0 ) {
6379 val leftSideIndices =
6480 rangeIndices(actualPage - plusOrMinus until actualPage, 1 < _, requestedIncomplete)
6581 val rightSideIndices =
6682 rangeIndices(actualPage + 1 to actualPage + plusOrMinus, _ < pageCount,
6783 requestedIncomplete)
6884
6985 <h4 >
70- Showing {actualFirst + 1 }- {last + 1 } of {allApps.size }
86+ Showing {actualFirst + 1 }- {last + 1 } of {allAppsSize }
7187 {if (requestedIncomplete) " (Incomplete applications)" }
7288 <span style =" float: right" >
7389 {
@@ -113,6 +129,36 @@ private[history] class HistoryPage(parent: HistoryServer) extends WebUIPage("")
113129 </div >
114130 UIUtils .basicSparkPage(content, " History Server" )
115131 }
132+
133+ private def getApplicationLevelList (appNattemptList : Iterable [ApplicationHistoryInfo ]) = {
134+ // Create HashMap as per the multiple attempts for one application.
135+ // If there is no attempt specific stuff, then
136+ // do return false, to indicate the same, so that previous UI gets displayed.
137+ var hasAttemptInfo = false
138+ val appToAttemptInfo = new HashMap [String , ArrayBuffer [ApplicationHistoryInfo ]]
139+ for ( appAttempt <- appNattemptList) {
140+ if (! appAttempt.appAttemptId.equals(" " )){
141+ hasAttemptInfo = true
142+ val attemptId = appAttempt.appAttemptId.toInt
143+ if (appToAttemptInfo.contains(appAttempt.id)){
144+ val currentAttempts = appToAttemptInfo.get(appAttempt.id).get
145+ currentAttempts += appAttempt
146+ appToAttemptInfo.put( appAttempt.id, currentAttempts)
147+ } else {
148+ val currentAttempts = new ArrayBuffer [ApplicationHistoryInfo ]()
149+ currentAttempts += appAttempt
150+ appToAttemptInfo.put( appAttempt.id, currentAttempts )
151+ }
152+ }else {
153+ val currentAttempts = new ArrayBuffer [ApplicationHistoryInfo ]()
154+ currentAttempts += appAttempt
155+ appToAttemptInfo.put(appAttempt.id, currentAttempts)
156+ }
157+ }
158+ val sortedMap = ListMap (appToAttemptInfo.toSeq.sortWith(_._1 > _._1):_* )
159+ (hasAttemptInfo, sortedMap)
160+ }
161+
116162
117163 private val appHeader = Seq (
118164 " App ID" ,
@@ -128,6 +174,16 @@ private[history] class HistoryPage(parent: HistoryServer) extends WebUIPage("")
128174 range.filter(condition).map(nextPage =>
129175 <a href ={makePageLink(nextPage, showIncomplete)}> {nextPage} </a >)
130176 }
177+
178+ private val appWithAttemptHeader = Seq (
179+ " App ID" ,
180+ " App Name" ,
181+ " Attempt ID" ,
182+ " Started" ,
183+ " Completed" ,
184+ " Duration" ,
185+ " Spark User" ,
186+ " Last Updated" )
131187
132188 private def appRow (info : ApplicationHistoryInfo ): Seq [Node ] = {
133189 val uiAddress = HistoryServer .UI_PATH_PREFIX + s " / ${info.id}"
@@ -146,6 +202,69 @@ private[history] class HistoryPage(parent: HistoryServer) extends WebUIPage("")
146202 <td sorttable_customkey ={info.lastUpdated.toString}>{lastUpdated}</td >
147203 </tr >
148204 }
205+
206+ private def getAttemptURI (attemptInfo : ApplicationHistoryInfo ,
207+ returnEmptyIfAttemptInfoNull : Boolean = true ) = {
208+ if (attemptInfo.appAttemptId.equals(" " )) {
209+ if (returnEmptyIfAttemptInfoNull) {
210+ attemptInfo.appAttemptId
211+ } else {
212+ HistoryServer .UI_PATH_PREFIX + s " / ${attemptInfo.id}"
213+ }
214+ } else {
215+ HistoryServer .UI_PATH_PREFIX + s " / ${attemptInfo.id}" + " _" + s " ${attemptInfo.appAttemptId}"
216+ }
217+ }
218+
219+ private def firstAttemptRow (attemptInfo : ApplicationHistoryInfo ) = {
220+ val uiAddress =
221+ if (attemptInfo.appAttemptId.equals(" " )) {
222+ attemptInfo.appAttemptId
223+ } else {
224+ HistoryServer .UI_PATH_PREFIX + s " / ${attemptInfo.id}" + " _" + s " ${attemptInfo.appAttemptId}"
225+ }
226+
227+ val startTime = UIUtils .formatDate(attemptInfo.startTime)
228+ val endTime = UIUtils .formatDate(attemptInfo.endTime)
229+ val duration = UIUtils .formatDuration(attemptInfo.endTime - attemptInfo.startTime)
230+ val lastUpdated = UIUtils .formatDate(attemptInfo.lastUpdated)
231+ val attemptId = attemptInfo.appAttemptId
232+ <td ><a href ={uiAddress}>{attemptId}</a ></td >
233+ <td sorttable_customkey ={attemptInfo.startTime.toString}>{startTime}</td >
234+ <td sorttable_customkey ={attemptInfo.endTime.toString}>{endTime}</td >
235+ <td sorttable_customkey ={(attemptInfo.endTime - attemptInfo.startTime).toString}>
236+ {duration}</td >
237+ <td >{attemptInfo.sparkUser}</td >
238+ <td sorttable_customkey ={attemptInfo.lastUpdated.toString}>{lastUpdated}</td >
239+ }
240+
241+ private def attemptRow (attemptInfo : ApplicationHistoryInfo ) = {
242+ <tr >
243+ {firstAttemptRow(attemptInfo)}
244+ </tr >
245+ }
246+
247+ private def appWithAttemptRow (
248+ appAttemptsInfo : (String ,ArrayBuffer [ApplicationHistoryInfo ])): Seq [Node ] = {
249+ val applicationId = appAttemptsInfo._1
250+ val info = appAttemptsInfo._2
251+ val rowSpan = info.length
252+ val rowSpanString = rowSpan.toString
253+ val applicatioName = info(0 ).name
254+ val lastAttemptURI = getAttemptURI(info(0 ), false )
255+ val ttAttempts = info.slice(1 , rowSpan - 1 )
256+ val x = new xml.NodeBuffer
257+ x +=
258+ <tr >
259+ <td rowspan ={rowSpanString}><a href ={lastAttemptURI}>{applicationId}</a ></td >
260+ <td rowspan ={rowSpanString}>{applicatioName}</td >
261+ { firstAttemptRow(info(0 )) }
262+ </tr >;
263+ for ( i <- 1 until rowSpan ){
264+ x += attemptRow(info(i))
265+ }
266+ x
267+ }
149268
150269 private def makePageLink (linkPage : Int , showIncomplete : Boolean ): String = {
151270 " /?" + Array (
0 commit comments