This is an automated email from the ASF dual-hosted git repository.

rickyma pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-uniffle.git


The following commit(s) were added to refs/heads/master by this push:
     new bcd6b0788 [#1860] feat(CLI/Coordinator/Dashboard): Add Reg time to 
AppInfo and Application (#1861)
bcd6b0788 is described below

commit bcd6b0788a57eb07d4b52796dcdc6e8826d2a14a
Author: maobaolong <baoloong...@tencent.com>
AuthorDate: Thu Jul 4 15:10:55 2024 +0800

    [#1860] feat(CLI/Coordinator/Dashboard): Add Reg time to AppInfo and 
Application (#1861)
    
    ### What changes were proposed in this pull request?
    
    Add Reg time to AppInfo and Application
    
    ### Why are the changes needed?
    
    Fix: #1860
    
    ### Does this PR introduce _any_ user-facing change?
    
    `bin/uniffle  client-cli -app -host localhost -port 19998` and dashboard 
app page will display registrationTime of each application.
    
    ### How was this patch tested?
    
    ```Console
    bin/uniffle  client-cli -app -host localhost -port 19998
    host:localhost,port:19998
    
+--------------------------------------------------------------------------------------------------------------+
    |                                             Uniffle Applications          
                                   |
    
+---------------------------------------+------+---------------------+---------------------+-------------------+
    |             ApplicationId             | User |    Registration Time    | 
Last HeartBeatTime  | RemoteStoragePath |
    
+---------------------------------------+------+---------------------+---------------------+-------------------+
    | app-20240703231912-0003_1720019950472 | user | 2024-07-03 23:19:40 | 
2024-07-03 23:21:05 |       null        |
    | app-20240703232127-0004_1720020086199 | user | 2024-07-03 23:21:51 | 
2024-07-03 23:22:16 |       null        |
    
+---------------------------------------+------+---------------------+---------------------+-------------------+
    ```
    
    <img width="1508" alt="image" 
src="https://github.com/apache/incubator-uniffle/assets/17329931/96f9703d-6c99-4600-adb5-2ddd8ae48c4d";>
---
 .../java/org/apache/uniffle/cli/UniffleCLI.java    |  4 +-
 .../org/apache/uniffle/common/Application.java     | 21 ++++++++++
 .../{web/vo/AppInfoVO.java => AppInfo.java}        | 46 ++++++++++++----------
 .../uniffle/coordinator/ApplicationManager.java    | 43 ++++++++++++--------
 .../apache/uniffle/coordinator/QuotaManager.java   | 29 ++++++++++----
 .../web/resource/ApplicationResource.java          | 19 +++++----
 .../uniffle/coordinator/web/vo/AppInfoVO.java      | 17 ++++++--
 .../uniffle/coordinator/QuotaManagerTest.java      | 37 +++++++++++------
 .../src/main/webapp/src/pages/ApplicationPage.vue  |  3 +-
 9 files changed, 151 insertions(+), 68 deletions(-)

diff --git a/cli/src/main/java/org/apache/uniffle/cli/UniffleCLI.java 
b/cli/src/main/java/org/apache/uniffle/cli/UniffleCLI.java
index 7c7c66fb4..f1ce006c3 100644
--- a/cli/src/main/java/org/apache/uniffle/cli/UniffleCLI.java
+++ b/cli/src/main/java/org/apache/uniffle/cli/UniffleCLI.java
@@ -59,7 +59,8 @@ public class UniffleCLI extends AbstractCustomCommandLine {
   private final Option help;
 
   private static final List<String> APPLICATIONS_HEADER =
-      Arrays.asList("ApplicationId", "User", "Last HeartBeatTime", 
"RemoteStoragePath");
+      Arrays.asList(
+          "ApplicationId", "User", "Registration Time", "Last HeartBeatTime", 
"RemoteStoragePath");
 
   public UniffleCLI(String shortPrefix, String longPrefix) {
     allOptions = new Options();
@@ -222,6 +223,7 @@ public class UniffleCLI extends AbstractCustomCommandLine {
                     formattingCLIUtils.addLine(
                         app.getApplicationId(),
                         app.getUser(),
+                        app.getRegistrationTime(),
                         app.getLastHeartBeatTime(),
                         app.getRemoteStoragePath()));
           }
diff --git a/common/src/main/java/org/apache/uniffle/common/Application.java 
b/common/src/main/java/org/apache/uniffle/common/Application.java
index 688144223..27d683e7e 100644
--- a/common/src/main/java/org/apache/uniffle/common/Application.java
+++ b/common/src/main/java/org/apache/uniffle/common/Application.java
@@ -28,6 +28,7 @@ public class Application implements Comparable<Application> {
   private String user;
   private String lastHeartBeatTime;
   private String remoteStoragePath;
+  private String registrationTime;
 
   public Application() {}
 
@@ -36,12 +37,14 @@ public class Application implements Comparable<Application> 
{
     this.user = builder.user;
     this.lastHeartBeatTime = builder.lastHeartBeatTime;
     this.remoteStoragePath = builder.remoteStoragePath;
+    this.registrationTime = builder.registrationTime;
   }
 
   public static class Builder {
     private String applicationId;
     private String user;
     private String lastHeartBeatTime;
+    private String registrationTime;
     private String remoteStoragePath;
 
     public Builder() {}
@@ -61,6 +64,11 @@ public class Application implements Comparable<Application> {
       return this;
     }
 
+    public Builder registrationTime(long registrationTime) {
+      this.registrationTime = DateFormatUtils.format(registrationTime, 
DATE_PATTERN);
+      return this;
+    }
+
     public Builder remoteStoragePath(RemoteStorageInfo remoteStorageInfo) {
       if (remoteStorageInfo != null) {
         this.remoteStoragePath = remoteStorageInfo.getPath();
@@ -85,6 +93,10 @@ public class Application implements Comparable<Application> {
     return lastHeartBeatTime;
   }
 
+  public String getRegistrationTime() {
+    return registrationTime;
+  }
+
   public String getRemoteStoragePath() {
     return remoteStoragePath;
   }
@@ -101,6 +113,10 @@ public class Application implements 
Comparable<Application> {
     this.lastHeartBeatTime = lastHeartBeatTime;
   }
 
+  public void setRegistrationTime(String registrationTime) {
+    this.registrationTime = registrationTime;
+  }
+
   public void setRemoteStoragePath(String remoteStoragePath) {
     this.remoteStoragePath = remoteStoragePath;
   }
@@ -115,6 +131,7 @@ public class Application implements Comparable<Application> 
{
         .append(this.getApplicationId(), otherImpl.getApplicationId())
         .append(this.getUser(), otherImpl.getUser())
         .append(this.getLastHeartBeatTime(), otherImpl.getLastHeartBeatTime())
+        .append(this.getRegistrationTime(), otherImpl.getRegistrationTime())
         .append(this.getRemoteStoragePath(), otherImpl.getRemoteStoragePath())
         .isEquals();
   }
@@ -125,6 +142,7 @@ public class Application implements Comparable<Application> 
{
         .append(this.getApplicationId())
         .append(this.getUser())
         .append(this.getLastHeartBeatTime())
+        .append(this.getRegistrationTime())
         .append(this.getRemoteStoragePath())
         .toHashCode();
   }
@@ -146,6 +164,9 @@ public class Application implements Comparable<Application> 
{
         + ", lastHeartBeatTime='"
         + lastHeartBeatTime
         + '\''
+        + ", registrationTime='"
+        + registrationTime
+        + '\''
         + ", remoteStoragePath='"
         + remoteStoragePath
         + '\''
diff --git 
a/coordinator/src/main/java/org/apache/uniffle/coordinator/web/vo/AppInfoVO.java
 b/coordinator/src/main/java/org/apache/uniffle/coordinator/AppInfo.java
similarity index 60%
copy from 
coordinator/src/main/java/org/apache/uniffle/coordinator/web/vo/AppInfoVO.java
copy to coordinator/src/main/java/org/apache/uniffle/coordinator/AppInfo.java
index 87ee82683..d9e9f9953 100644
--- 
a/coordinator/src/main/java/org/apache/uniffle/coordinator/web/vo/AppInfoVO.java
+++ b/coordinator/src/main/java/org/apache/uniffle/coordinator/AppInfo.java
@@ -15,27 +15,19 @@
  * limitations under the License.
  */
 
-package org.apache.uniffle.coordinator.web.vo;
+package org.apache.uniffle.coordinator;
 
 import java.util.Objects;
 
-public class AppInfoVO implements Comparable<AppInfoVO> {
-  private String userName;
+public class AppInfo implements Comparable<AppInfo> {
   private String appId;
   private long updateTime;
+  private long registrationTime;
 
-  public AppInfoVO(String userName, String appId, long updateTime) {
-    this.userName = userName;
+  public AppInfo(String appId, long updateTime, long registrationTime) {
     this.appId = appId;
     this.updateTime = updateTime;
-  }
-
-  public String getUserName() {
-    return userName;
-  }
-
-  public void setUserName(String userName) {
-    this.userName = userName;
+    this.registrationTime = registrationTime;
   }
 
   public String getAppId() {
@@ -54,9 +46,17 @@ public class AppInfoVO implements Comparable<AppInfoVO> {
     this.updateTime = updateTime;
   }
 
+  public long getRegistrationTime() {
+    return registrationTime;
+  }
+
+  public void setRegistrationTime(long registrationTime) {
+    this.registrationTime = registrationTime;
+  }
+
   @Override
-  public int compareTo(AppInfoVO appInfoVO) {
-    return Long.compare(updateTime, appInfoVO.getUpdateTime());
+  public int compareTo(AppInfo appInfo) {
+    return Long.compare(registrationTime, appInfo.getRegistrationTime());
   }
 
   @Override
@@ -64,17 +64,21 @@ public class AppInfoVO implements Comparable<AppInfoVO> {
     if (this == o) {
       return true;
     }
-    if (!(o instanceof AppInfoVO)) {
+    if (!(o instanceof AppInfo)) {
       return false;
     }
-    AppInfoVO appInfoVO = (AppInfoVO) o;
-    return updateTime == appInfoVO.updateTime
-        && userName.equals(appInfoVO.userName)
-        && appId.equals(appInfoVO.appId);
+    AppInfo appInfo = (AppInfo) o;
+    return updateTime == appInfo.updateTime
+        && registrationTime == appInfo.registrationTime
+        && appId.equals(appInfo.appId);
   }
 
   @Override
   public int hashCode() {
-    return Objects.hash(userName, appId, updateTime);
+    return Objects.hash(appId, updateTime, registrationTime);
+  }
+
+  public static AppInfo createAppInfo(String appId, long updateTime) {
+    return new AppInfo(appId, updateTime, updateTime);
   }
 }
diff --git 
a/coordinator/src/main/java/org/apache/uniffle/coordinator/ApplicationManager.java
 
b/coordinator/src/main/java/org/apache/uniffle/coordinator/ApplicationManager.java
index 70e90ffa6..716c22692 100644
--- 
a/coordinator/src/main/java/org/apache/uniffle/coordinator/ApplicationManager.java
+++ 
b/coordinator/src/main/java/org/apache/uniffle/coordinator/ApplicationManager.java
@@ -72,7 +72,7 @@ public class ApplicationManager implements Closeable {
   private final Map<String, RemoteStorageInfo> availableRemoteStorageInfo;
   private final ScheduledExecutorService detectStorageScheduler;
   private final ScheduledExecutorService checkAppScheduler;
-  private Map<String, Map<String, Long>> currentUserAndApp = 
JavaUtils.newConcurrentMap();
+  private Map<String, Map<String, AppInfo>> currentUserAndApp = 
JavaUtils.newConcurrentMap();
   private Map<String, String> appIdToUser = JavaUtils.newConcurrentMap();
   private QuotaManager quotaManager;
   // it's only for test case to check if status check has problem
@@ -130,17 +130,18 @@ public class ApplicationManager implements Closeable {
     // implementation class
     // in such case by default, there is no currentUserAndApp, so a unified 
user implementation
     // named "user" is used.
-    Map<String, Long> appAndTime =
+    Map<String, AppInfo> appAndTime =
         currentUserAndApp.computeIfAbsent(user, x -> 
JavaUtils.newConcurrentMap());
     appIdToUser.put(appId, user);
     if (!appAndTime.containsKey(appId)) {
       CoordinatorMetrics.counterTotalAppNum.inc();
       LOG.info("New application is registered: {}", appId);
     }
+    AppInfo appInfo = AppInfo.createAppInfo(appId, System.currentTimeMillis());
     if (quotaManager != null) {
       quotaManager.registerApplicationInfo(appId, appAndTime);
     } else {
-      appAndTime.put(appId, System.currentTimeMillis());
+      appAndTime.put(appId, appInfo);
     }
   }
 
@@ -150,8 +151,15 @@ public class ApplicationManager implements Closeable {
     if (user == null) {
       registerApplicationInfo(appId, "");
     } else {
-      Map<String, Long> appAndTime = currentUserAndApp.get(user);
-      appAndTime.put(appId, System.currentTimeMillis());
+      Map<String, AppInfo> appAndTime = currentUserAndApp.get(user);
+      AppInfo appInfo = appAndTime.get(appId);
+      long currentTimeMs = System.currentTimeMillis();
+      if (appInfo != null) {
+        appInfo.setUpdateTime(currentTimeMs);
+      } else {
+        appInfo = new AppInfo(appId, currentTimeMs, currentTimeMs);
+        appAndTime.put(appId, appInfo);
+      }
     }
   }
 
@@ -317,8 +325,8 @@ public class ApplicationManager implements Closeable {
   }
 
   protected void statusCheck() {
-    List<Map<String, Long>> appAndNums = 
Lists.newArrayList(currentUserAndApp.values());
-    Map<String, Long> appIds = Maps.newHashMap();
+    List<Map<String, AppInfo>> appAndNums = 
Lists.newArrayList(currentUserAndApp.values());
+    Map<String, AppInfo> appIds = Maps.newHashMap();
     // The reason for setting an expired uuid here is that there is a scenario 
where accessCluster
     // succeeds,
     // but the registration of shuffle fails, resulting in no normal 
heartbeat, and no normal update
@@ -326,12 +334,12 @@ public class ApplicationManager implements Closeable {
     // Therefore, an expiration time is set to automatically remove expired 
uuids
     Set<String> expiredAppIds = Sets.newHashSet();
     try {
-      for (Map<String, Long> appAndTimes : appAndNums) {
-        for (Map.Entry<String, Long> appAndTime : appAndTimes.entrySet()) {
+      for (Map<String, AppInfo> appAndTimes : appAndNums) {
+        for (Map.Entry<String, AppInfo> appAndTime : appAndTimes.entrySet()) {
           String appId = appAndTime.getKey();
-          long lastReport = appAndTime.getValue();
+          AppInfo lastReport = appAndTime.getValue();
           appIds.put(appId, lastReport);
-          if (System.currentTimeMillis() - lastReport > expired) {
+          if (System.currentTimeMillis() - lastReport.getUpdateTime() > 
expired) {
             expiredAppIds.add(appId);
             appAndTimes.remove(appId);
             appIdToUser.remove(appId);
@@ -416,11 +424,11 @@ public class ApplicationManager implements Closeable {
       String pHeartBeatEndTime,
       String appIdRegex) {
     List<Application> applications = new ArrayList<>();
-    for (Map.Entry<String, Map<String, Long>> entry : 
currentUserAndApp.entrySet()) {
+    for (Map.Entry<String, Map<String, AppInfo>> entry : 
currentUserAndApp.entrySet()) {
       String user = entry.getKey();
-      Map<String, Long> apps = entry.getValue();
+      Map<String, AppInfo> apps = entry.getValue();
       apps.forEach(
-          (appId, heartBeatTime) -> {
+          (appId, appInfo) -> {
             // Filter condition 1: Check whether applicationId is included in 
the filter list.
             boolean match = appIds.size() == 0 || appIds.contains(appId);
 
@@ -435,7 +443,7 @@ public class ApplicationManager implements Closeable {
                 || StringUtils.isNotBlank(pHeartBeatEndTime)) {
               match =
                   matchHeartBeatStartTimeAndEndTime(
-                      pHeartBeatStartTime, pHeartBeatEndTime, heartBeatTime);
+                      pHeartBeatStartTime, pHeartBeatEndTime, 
appInfo.getUpdateTime());
             }
 
             // If it meets expectations, add to the list to be returned.
@@ -446,7 +454,8 @@ public class ApplicationManager implements Closeable {
                   new Application.Builder()
                       .applicationId(appId)
                       .user(user)
-                      .lastHeartBeatTime(heartBeatTime)
+                      .lastHeartBeatTime(appInfo.getUpdateTime())
+                      .registrationTime(appInfo.getRegistrationTime())
                       .remoteStoragePath(remoteStorageInfo)
                       .build();
               applications.add(application);
@@ -527,7 +536,7 @@ public class ApplicationManager implements Closeable {
     return REMOTE_PATH_SCHEMA;
   }
 
-  public Map<String, Map<String, Long>> getCurrentUserAndApp() {
+  public Map<String, Map<String, AppInfo>> getCurrentUserAndApp() {
     return currentUserAndApp;
   }
 
diff --git 
a/coordinator/src/main/java/org/apache/uniffle/coordinator/QuotaManager.java 
b/coordinator/src/main/java/org/apache/uniffle/coordinator/QuotaManager.java
index d661f90df..72c462933 100644
--- a/coordinator/src/main/java/org/apache/uniffle/coordinator/QuotaManager.java
+++ b/coordinator/src/main/java/org/apache/uniffle/coordinator/QuotaManager.java
@@ -44,7 +44,7 @@ import 
org.apache.uniffle.coordinator.metric.CoordinatorMetrics;
 /** QuotaManager is a manager for resource restriction. */
 public class QuotaManager {
   private static final Logger LOG = 
LoggerFactory.getLogger(QuotaManager.class);
-  private final Map<String, Map<String, Long>> currentUserAndApp = 
JavaUtils.newConcurrentMap();
+  private final Map<String, Map<String, AppInfo>> currentUserAndApp = 
JavaUtils.newConcurrentMap();
   private final Map<String, String> appIdToUser = JavaUtils.newConcurrentMap();
   private final String quotaFilePath;
   private final Integer quotaAppNum;
@@ -116,7 +116,7 @@ public class QuotaManager {
   }
 
   public boolean checkQuota(String user, String uuid) {
-    Map<String, Long> appAndTimes =
+    Map<String, AppInfo> appAndTimes =
         currentUserAndApp.computeIfAbsent(user, x -> 
JavaUtils.newConcurrentMap());
     Integer userAppQuotaNum = defaultUserApps.computeIfAbsent(user, x -> 
quotaAppNum);
     synchronized (this) {
@@ -124,26 +124,41 @@ public class QuotaManager {
       if (userAppQuotaNum >= 0 && currentAppNum >= userAppQuotaNum) {
         return true;
       } else {
-        appAndTimes.put(uuid, System.currentTimeMillis());
+        // thread safe is guaranteed by synchronized
+        AppInfo appInfo = appAndTimes.get(uuid);
+        long currentTimeMillis = System.currentTimeMillis();
+        if (appInfo == null) {
+          appInfo = new AppInfo(uuid, currentTimeMillis, currentTimeMillis);
+          appAndTimes.put(uuid, appInfo);
+        } else {
+          appInfo.setUpdateTime(currentTimeMillis);
+        }
         CoordinatorMetrics.gaugeRunningAppNumToUser.labels(user).inc();
         return false;
       }
     }
   }
 
-  public void registerApplicationInfo(String appId, Map<String, Long> 
appAndTime) {
+  public void registerApplicationInfo(String appId, Map<String, AppInfo> 
appAndTime) {
     long currentTimeMillis = System.currentTimeMillis();
     String[] appIdAndUuid = appId.split("_");
     String uuidFromApp = appIdAndUuid[appIdAndUuid.length - 1];
     // if appId created successfully, we need to remove the uuid
     synchronized (this) {
       appAndTime.remove(uuidFromApp);
-      appAndTime.put(appId, currentTimeMillis);
+      // thread safe is guaranteed by synchronized
+      AppInfo appInfo = appAndTime.get(appId);
+      if (appInfo == null) {
+        appInfo = new AppInfo(appId, currentTimeMillis, currentTimeMillis);
+        appAndTime.put(appId, appInfo);
+      } else {
+        appInfo.setUpdateTime(currentTimeMillis);
+      }
     }
   }
 
   protected void updateQuotaMetrics() {
-    for (Map.Entry<String, Map<String, Long>> userAndApp : 
currentUserAndApp.entrySet()) {
+    for (Map.Entry<String, Map<String, AppInfo>> userAndApp : 
currentUserAndApp.entrySet()) {
       String user = userAndApp.getKey();
       try {
         
CoordinatorMetrics.gaugeRunningAppNumToUser.labels(user).set(userAndApp.getValue().size());
@@ -158,7 +173,7 @@ public class QuotaManager {
     return defaultUserApps;
   }
 
-  public Map<String, Map<String, Long>> getCurrentUserAndApp() {
+  public Map<String, Map<String, AppInfo>> getCurrentUserAndApp() {
     return currentUserAndApp;
   }
 
diff --git 
a/coordinator/src/main/java/org/apache/uniffle/coordinator/web/resource/ApplicationResource.java
 
b/coordinator/src/main/java/org/apache/uniffle/coordinator/web/resource/ApplicationResource.java
index 7106d66d1..12558639a 100644
--- 
a/coordinator/src/main/java/org/apache/uniffle/coordinator/web/resource/ApplicationResource.java
+++ 
b/coordinator/src/main/java/org/apache/uniffle/coordinator/web/resource/ApplicationResource.java
@@ -32,6 +32,7 @@ import org.apache.hbase.thirdparty.javax.ws.rs.core.MediaType;
 
 import org.apache.uniffle.common.web.resource.BaseResource;
 import org.apache.uniffle.common.web.resource.Response;
+import org.apache.uniffle.coordinator.AppInfo;
 import org.apache.uniffle.coordinator.ApplicationManager;
 import org.apache.uniffle.coordinator.web.vo.AppInfoVO;
 import org.apache.uniffle.coordinator.web.vo.UserAppNumVO;
@@ -57,10 +58,11 @@ public class ApplicationResource extends BaseResource {
   public Response<List<UserAppNumVO>> getUserApps() {
     return execute(
         () -> {
-          Map<String, Map<String, Long>> currentUserAndApp =
+          Map<String, Map<String, AppInfo>> currentUserAndApp =
               getApplicationManager().getCurrentUserAndApp();
           List<UserAppNumVO> usercnt = new ArrayList<>();
-          for (Map.Entry<String, Map<String, Long>> stringMapEntry : 
currentUserAndApp.entrySet()) {
+          for (Map.Entry<String, Map<String, AppInfo>> stringMapEntry :
+              currentUserAndApp.entrySet()) {
             String userName = stringMapEntry.getKey();
             usercnt.add(new UserAppNumVO(userName, 
stringMapEntry.getValue().size()));
           }
@@ -76,16 +78,19 @@ public class ApplicationResource extends BaseResource {
     return execute(
         () -> {
           List<AppInfoVO> userToAppList = new ArrayList<>();
-          Map<String, Map<String, Long>> currentUserAndApp =
+          Map<String, Map<String, AppInfo>> currentUserAndApp =
               getApplicationManager().getCurrentUserAndApp();
-          for (Map.Entry<String, Map<String, Long>> userAppIdTimestampMap :
+          for (Map.Entry<String, Map<String, AppInfo>> userAppIdTimestampMap :
               currentUserAndApp.entrySet()) {
-            String userName = userAppIdTimestampMap.getKey();
-            for (Map.Entry<String, Long> appIdTimestampMap :
+            for (Map.Entry<String, AppInfo> appIdTimestampMap :
                 userAppIdTimestampMap.getValue().entrySet()) {
+              AppInfo appInfo = appIdTimestampMap.getValue();
               userToAppList.add(
                   new AppInfoVO(
-                      userName, appIdTimestampMap.getKey(), 
appIdTimestampMap.getValue()));
+                      userAppIdTimestampMap.getKey(),
+                      appInfo.getAppId(),
+                      appInfo.getUpdateTime(),
+                      appInfo.getRegistrationTime()));
             }
           }
           // Display is inverted by the submission time of the application.
diff --git 
a/coordinator/src/main/java/org/apache/uniffle/coordinator/web/vo/AppInfoVO.java
 
b/coordinator/src/main/java/org/apache/uniffle/coordinator/web/vo/AppInfoVO.java
index 87ee82683..3d80f0e01 100644
--- 
a/coordinator/src/main/java/org/apache/uniffle/coordinator/web/vo/AppInfoVO.java
+++ 
b/coordinator/src/main/java/org/apache/uniffle/coordinator/web/vo/AppInfoVO.java
@@ -23,11 +23,13 @@ public class AppInfoVO implements Comparable<AppInfoVO> {
   private String userName;
   private String appId;
   private long updateTime;
+  private long registrationTime;
 
-  public AppInfoVO(String userName, String appId, long updateTime) {
+  public AppInfoVO(String userName, String appId, long updateTime, long 
registrationTime) {
     this.userName = userName;
     this.appId = appId;
     this.updateTime = updateTime;
+    this.registrationTime = registrationTime;
   }
 
   public String getUserName() {
@@ -54,9 +56,17 @@ public class AppInfoVO implements Comparable<AppInfoVO> {
     this.updateTime = updateTime;
   }
 
+  public long getRegistrationTime() {
+    return registrationTime;
+  }
+
+  public void setRegistrationTime(long registrationTime) {
+    this.registrationTime = registrationTime;
+  }
+
   @Override
   public int compareTo(AppInfoVO appInfoVO) {
-    return Long.compare(updateTime, appInfoVO.getUpdateTime());
+    return Long.compare(registrationTime, appInfoVO.getRegistrationTime());
   }
 
   @Override
@@ -69,12 +79,13 @@ public class AppInfoVO implements Comparable<AppInfoVO> {
     }
     AppInfoVO appInfoVO = (AppInfoVO) o;
     return updateTime == appInfoVO.updateTime
+        && registrationTime == appInfoVO.registrationTime
         && userName.equals(appInfoVO.userName)
         && appId.equals(appInfoVO.appId);
   }
 
   @Override
   public int hashCode() {
-    return Objects.hash(userName, appId, updateTime);
+    return Objects.hash(userName, appId, updateTime, registrationTime);
   }
 }
diff --git 
a/coordinator/src/test/java/org/apache/uniffle/coordinator/QuotaManagerTest.java
 
b/coordinator/src/test/java/org/apache/uniffle/coordinator/QuotaManagerTest.java
index 41be903c2..54c0f9b47 100644
--- 
a/coordinator/src/test/java/org/apache/uniffle/coordinator/QuotaManagerTest.java
+++ 
b/coordinator/src/test/java/org/apache/uniffle/coordinator/QuotaManagerTest.java
@@ -97,14 +97,20 @@ public class QuotaManagerTest {
     conf.setInteger(CoordinatorConf.COORDINATOR_QUOTA_DEFAULT_APP_NUM, 5);
     try (ApplicationManager applicationManager = new ApplicationManager(conf)) 
{
       final AtomicInteger uuid = new AtomicInteger();
-      Map<String, Long> uuidAndTime = JavaUtils.newConcurrentMap();
-      uuidAndTime.put(String.valueOf(uuid.incrementAndGet()), 
System.currentTimeMillis());
-      uuidAndTime.put(String.valueOf(uuid.incrementAndGet()), 
System.currentTimeMillis());
-      uuidAndTime.put(String.valueOf(uuid.incrementAndGet()), 
System.currentTimeMillis());
-      uuidAndTime.put(String.valueOf(uuid.incrementAndGet()), 
System.currentTimeMillis());
+      Map<String, AppInfo> uuidAndTime = JavaUtils.newConcurrentMap();
+      String appId = String.valueOf(uuid.incrementAndGet());
+      uuidAndTime.put(appId, AppInfo.createAppInfo(appId, 
System.currentTimeMillis()));
+      appId = String.valueOf(uuid.incrementAndGet());
+      uuidAndTime.put(appId, AppInfo.createAppInfo(appId, 
System.currentTimeMillis()));
+      appId = String.valueOf(uuid.incrementAndGet());
+      uuidAndTime.put(appId, AppInfo.createAppInfo(appId, 
System.currentTimeMillis()));
+      appId = String.valueOf(uuid.incrementAndGet());
+      uuidAndTime.put(appId, AppInfo.createAppInfo(appId, 
System.currentTimeMillis()));
       final int i1 = uuid.incrementAndGet();
-      uuidAndTime.put(String.valueOf(i1), System.currentTimeMillis());
-      Map<String, Long> appAndTime =
+      uuidAndTime.put(
+          String.valueOf(i1),
+          AppInfo.createAppInfo(String.valueOf(i1), 
System.currentTimeMillis()));
+      Map<String, AppInfo> appAndTime =
           applicationManager
               .getQuotaManager()
               .getCurrentUserAndApp()
@@ -184,7 +190,7 @@ public class QuotaManagerTest {
           .until(() -> applicationManager.getDefaultUserApps().size() > 2);
 
       QuotaManager quotaManager = applicationManager.getQuotaManager();
-      Map<String, Map<String, Long>> currentUserAndApp = 
quotaManager.getCurrentUserAndApp();
+      Map<String, Map<String, AppInfo>> currentUserAndApp = 
quotaManager.getCurrentUserAndApp();
 
       currentUserAndApp.computeIfAbsent("user1", x -> mockUUidAppAndTime(30));
       currentUserAndApp.computeIfAbsent("user2", x -> mockUUidAppAndTime(20));
@@ -211,11 +217,20 @@ public class QuotaManagerTest {
     return String.valueOf(uuid.incrementAndGet());
   }
 
-  private Map<String, Long> mockUUidAppAndTime(int mockAppNum) {
-    Map<String, Long> uuidAndTime = JavaUtils.newConcurrentMap();
+  private Map<String, AppInfo> mockUUidAppAndTime(int mockAppNum) {
+    Map<String, AppInfo> uuidAndTime = JavaUtils.newConcurrentMap();
     for (int i = 0; i < mockAppNum; i++) {
-      uuidAndTime.put(mockUUidAppId(), System.currentTimeMillis());
+      String appId = mockUUidAppId();
+      long currentTimeMs = System.currentTimeMillis();
+      AppInfo appInfo = uuidAndTime.get(appId);
+      if (appInfo != null) {
+        appInfo.setUpdateTime(currentTimeMs);
+      } else {
+        appInfo = new AppInfo(appId, currentTimeMs, currentTimeMs);
+        uuidAndTime.put(appId, appInfo);
+      }
     }
+
     return uuidAndTime;
   }
 }
diff --git a/dashboard/src/main/webapp/src/pages/ApplicationPage.vue 
b/dashboard/src/main/webapp/src/pages/ApplicationPage.vue
index 33d1e546c..e3ef4cab9 100644
--- a/dashboard/src/main/webapp/src/pages/ApplicationPage.vue
+++ b/dashboard/src/main/webapp/src/pages/ApplicationPage.vue
@@ -43,6 +43,7 @@
       <el-table :data="pageData.appInfoData" height="250" style="width: 100%">
         <el-table-column prop="appId" label="AppId" min-width="180" />
         <el-table-column prop="userName" label="UserName" min-width="180" />
+        <el-table-column prop="registrationTime" label="Registration Time" 
min-width="180" :formatter="dateFormatter" />
         <el-table-column prop="updateTime" label="Update Time" min-width="180" 
:formatter="dateFormatter" />
       </el-table>
     </div>
@@ -60,7 +61,7 @@ export default {
     const pageData = reactive({
       apptotal: {},
       userAppCount: [{}],
-      appInfoData: [{ appId: '', userName: '', updateTime: '' }]
+      appInfoData: [{ appId: '', userName: '', registrationTime: '', 
updateTime: '' }]
     })
     const currentServerStore = useCurrentServerStore()
 

Reply via email to