]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-12038 Add pendingTime to api/ce/activity_status and ComputeEngineTasks JMX...
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Thu, 9 May 2019 15:48:58 +0000 (10:48 -0500)
committerSonarTech <sonartech@sonarsource.com>
Mon, 3 Jun 2019 18:21:18 +0000 (20:21 +0200)
18 files changed:
server/sonar-ce/src/main/java/org/sonar/ce/monitoring/CEQueueStatus.java
server/sonar-ce/src/main/java/org/sonar/ce/monitoring/CEQueueStatusImpl.java
server/sonar-ce/src/main/java/org/sonar/ce/monitoring/CeTasksMBean.java
server/sonar-ce/src/main/java/org/sonar/ce/monitoring/CeTasksMBeanImpl.java
server/sonar-ce/src/main/java/org/sonar/ce/monitoring/DistributedCEQueueStatusImpl.java
server/sonar-ce/src/test/java/org/sonar/ce/monitoring/CEQueueStatusImplConcurrentTest.java
server/sonar-ce/src/test/java/org/sonar/ce/monitoring/CEQueueStatusImplTest.java
server/sonar-ce/src/test/java/org/sonar/ce/monitoring/CeTasksMBeanImplTest.java
server/sonar-ce/src/test/java/org/sonar/ce/monitoring/DistributedCEQueueStatusImplTest.java
server/sonar-ce/src/test/java/org/sonar/ce/queue/InternalCeQueueImplTest.java
server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeQueueDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeQueueMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/ce/CeQueueMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeQueueDaoTest.java
server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityStatusAction.java
server/sonar-server/src/main/resources/org/sonar/server/ce/ws/activity_status-example.json
server/sonar-server/src/test/java/org/sonar/server/ce/ws/ActivityStatusActionTest.java
sonar-ws/src/main/protobuf/ws-ce.proto

index 91e86168a3faf0ed34924ef34fc4cb0ea83a15ce..70f04bcf5a24d17a6b040c999d96c45e62671df3 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.ce.monitoring;
 
+import java.util.Optional;
 import org.sonar.api.ce.ComputeEngineSide;
 
 @ComputeEngineSide
@@ -50,12 +51,12 @@ public interface CEQueueStatus {
   long addSuccess(long processingTime);
 
   /**
-   * Adds 1 to the count of batch reports which processing ended with an error and removes 1 from the count of batch
-   * reports under processing. Adds the specified time to the processing time counter.
+   * Adds 1 to the count of tasks which processing ended with an error and removes 1 from the count of tasks
+   * under processing. Adds the specified time to the processing time counter.
    *
    * @param processingTime duration of processing in ms
    *
-   * @return the new count of batch reports which processing ended with an error
+   * @return the new count of tasks which processing ended with an error
    *
    * @see #getErrorCount()
    * @see #getInProgressCount()
@@ -65,27 +66,32 @@ public interface CEQueueStatus {
   long addError(long processingTime);
 
   /**
-   * Count of batch reports waiting for processing since startup, including reports received before instance startup.
+   * Number of pending tasks, including tasks received before instance startup.
    */
   long getPendingCount();
 
   /**
-   * Count of batch reports under processing.
+   * The age, in ms, of the oldest pending task.
+   */
+  Optional<Long> getLongestTimePending();
+
+  /**
+   * Count of tasks under processing.
    */
   long getInProgressCount();
 
   /**
-   * Count of batch reports which processing ended with an error since instance startup.
+   * Count of tasks which processing ended with an error since instance startup.
    */
   long getErrorCount();
 
   /**
-   * Count of batch reports which processing ended successfully since instance startup.
+   * Count of tasks which processing ended successfully since instance startup.
    */
   long getSuccessCount();
 
   /**
-   * Time spent processing batch reports since startup, in milliseconds.
+   * Time spent processing tasks since startup, in milliseconds.
    */
   long getProcessingTime();
 
index 70dc86d4d73379c8adf1174a4fa7f983fca221e4..8a9dea76ffa8d36ce37ca61c8b0d27afc8874f0b 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.ce.monitoring;
 
 import java.util.Optional;
 import java.util.concurrent.atomic.AtomicLong;
+import org.sonar.api.utils.System2;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.ce.CeQueueDto;
@@ -31,13 +32,15 @@ import static com.google.common.base.Preconditions.checkArgument;
 public class CEQueueStatusImpl implements CEQueueStatus {
 
   private final DbClient dbClient;
+  private final System2 system;
   private final AtomicLong inProgress = new AtomicLong(0);
   private final AtomicLong error = new AtomicLong(0);
   private final AtomicLong success = new AtomicLong(0);
   private final AtomicLong processingTime = new AtomicLong(0);
 
-  public CEQueueStatusImpl(DbClient dbClient) {
+  public CEQueueStatusImpl(DbClient dbClient, System2 system) {
     this.dbClient = dbClient;
+    this.system = system;
   }
 
   @Override
@@ -71,6 +74,14 @@ public class CEQueueStatusImpl implements CEQueueStatus {
     }
   }
 
+  @Override
+  public Optional<Long> getLongestTimePending() {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      return dbClient.ceQueueDao().selectCreationDateOfOldestPendingByMainComponentUuid(dbSession, null)
+        .map(creationDate -> system.now() - creationDate);
+    }
+  }
+
   @Override
   public boolean areWorkersPaused() {
     try (DbSession dbSession = dbClient.openSession(false)) {
index 3d354a3bd696e8153b8f3df7569c9b2670792993..bcf1028a1b5726ae68130c1ef7dd3d978db2e822 100644 (file)
 package org.sonar.ce.monitoring;
 
 import java.util.List;
+import java.util.Optional;
 
 public interface CeTasksMBean {
 
   String OBJECT_NAME = "SonarQube:name=ComputeEngineTasks";
 
   /**
-   * Count of batch reports waiting for processing since startup, including reports received before instance startup.
+   * Number of pending tasks, including tasks received before instance startup.
    */
   long getPendingCount();
 
   /**
-   * Count of batch reports under processing.
+   * The age, in ms, of the oldest pending task.
+   */
+  Optional<Long> getLongestTimePending();
+
+  /**
+   * Count of tasks under processing.
    */
   long getInProgressCount();
 
   /**
-   * Count of batch reports which processing ended with an error since instance startup.
+   * Count of tasks which processing ended with an error since instance startup.
    */
   long getErrorCount();
 
   /**
-   * Count of batch reports which processing ended successfully since instance startup.
+   * Count of tasks which processing ended successfully since instance startup.
    */
   long getSuccessCount();
 
   /**
-   * Time spent processing reports since startup, in milliseconds.
+   * Time spent processing tasks since startup, in milliseconds.
    */
   long getProcessingTime();
 
index d3f8429757da30eb1526b8b6b0bb7637c00219c3..3ae2b3a9dbb8850fb118b55625dc09fe3b252c75 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.ce.monitoring;
 
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 import org.picocontainer.Startable;
@@ -62,6 +63,11 @@ public class CeTasksMBeanImpl implements CeTasksMBean, Startable, SystemInfoSect
     return queueStatus.getPendingCount();
   }
 
+  @Override
+  public Optional<Long> getLongestTimePending() {
+    return queueStatus.getLongestTimePending();
+  }
+
   @Override
   public long getInProgressCount() {
     return queueStatus.getInProgressCount();
@@ -116,6 +122,7 @@ public class CeTasksMBeanImpl implements CeTasksMBean, Startable, SystemInfoSect
     ProtobufSystemInfo.Section.Builder builder = ProtobufSystemInfo.Section.newBuilder();
     builder.setName("Compute Engine Tasks");
     builder.addAttributesBuilder().setKey("Pending").setLongValue(getPendingCount()).build();
+    builder.addAttributesBuilder().setKey("Longest Time Pending (ms)").setLongValue(getLongestTimePending().orElse(0L)).build();
     builder.addAttributesBuilder().setKey("In Progress").setLongValue(getInProgressCount()).build();
     builder.addAttributesBuilder().setKey("Processed With Error").setLongValue(getErrorCount()).build();
     builder.addAttributesBuilder().setKey("Processed With Success").setLongValue(getSuccessCount()).build();
index a32bf941859ddb7831b3ddea5769c1299ced0f1b..a2c8a07dd91ce5110fcd2a0766ad5ad6f916ab2d 100644 (file)
  */
 package org.sonar.ce.monitoring;
 
+import org.sonar.api.utils.System2;
 import org.sonar.db.DbClient;
 
 public class DistributedCEQueueStatusImpl extends CEQueueStatusImpl {
-  public DistributedCEQueueStatusImpl(DbClient dbClient) {
-    super(dbClient);
+  public DistributedCEQueueStatusImpl(DbClient dbClient, System2 system2) {
+    super(dbClient, system2);
   }
 
   @Override
index 5ae56f05aae8030a59c4f2560aaad5012ecbbd18..fd403f7a22ee4445e22ae925a697c549dfb718e7 100644 (file)
@@ -28,6 +28,7 @@ import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 import org.junit.After;
 import org.junit.Test;
+import org.sonar.api.utils.System2;
 import org.sonar.db.DbClient;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -42,7 +43,7 @@ public class CEQueueStatusImplConcurrentTest {
       return new Thread(r, CEQueueStatusImplConcurrentTest.class.getSimpleName() + cnt++);
     }
   });
-  private CEQueueStatusImpl underTest = new CEQueueStatusImpl(mock(DbClient.class));
+  private CEQueueStatusImpl underTest = new CEQueueStatusImpl(mock(DbClient.class), mock(System2.class));
 
   @After
   public void tearDown() {
index 92f133be09e1a0d7fe9b4e5217108b5157d97d86..a8b9710faf28361dadcaa68ecbc8c9de4dcd6cc6 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.ce.monitoring;
 
 import org.junit.Test;
 import org.mockito.Mockito;
+import org.sonar.api.utils.System2;
 import org.sonar.db.DbClient;
 import org.sonar.db.ce.CeQueueDto;
 
@@ -31,7 +32,7 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 public class CEQueueStatusImplTest extends CommonCEQueueStatusImplTest {
-  private CEQueueStatusImpl underTest = new CEQueueStatusImpl(getDbClient());
+  private CEQueueStatusImpl underTest = new CEQueueStatusImpl(getDbClient(), mock(System2.class));
 
   public CEQueueStatusImplTest() {
     super(mock(DbClient.class, Mockito.RETURNS_DEEP_STUBS));
index cbcf7d55dc739f6513370fbc4cf56c736e1e4896..9afec04df38a39a29e0776998469e74fb540b1c4 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.ce.monitoring;
 import com.google.common.collect.ImmutableSet;
 import java.lang.management.ManagementFactory;
 import java.util.List;
+import java.util.Optional;
 import java.util.Random;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -46,6 +47,7 @@ import static org.mockito.Mockito.when;
 
 public class CeTasksMBeanImplTest {
   private static final long PENDING_COUNT = 2;
+  private static final Optional<Long> PENDING_TIME = Optional.of(10_000L);
   private static final long IN_PROGRESS_COUNT = 5;
   private static final long ERROR_COUNT = 10;
   private static final long SUCCESS_COUNT = 13;
@@ -91,6 +93,7 @@ public class CeTasksMBeanImplTest {
   @Test
   public void get_methods_delegate_to_the_CEQueueStatus_instance() {
     assertThat(underTest.getPendingCount()).isEqualTo(PENDING_COUNT);
+    assertThat(underTest.getLongestTimePending()).isEqualTo(PENDING_TIME);
     assertThat(underTest.getInProgressCount()).isEqualTo(IN_PROGRESS_COUNT);
     assertThat(underTest.getErrorCount()).isEqualTo(ERROR_COUNT);
     assertThat(underTest.getSuccessCount()).isEqualTo(SUCCESS_COUNT);
@@ -142,7 +145,7 @@ public class CeTasksMBeanImplTest {
   public void export_system_info() {
     ProtobufSystemInfo.Section section = underTest.toProtobuf();
     assertThat(section.getName()).isEqualTo("Compute Engine Tasks");
-    assertThat(section.getAttributesCount()).isEqualTo(8);
+    assertThat(section.getAttributesCount()).isEqualTo(9);
   }
 
   private static class DumbCEQueueStatus implements CEQueueStatus {
@@ -152,6 +155,11 @@ public class CeTasksMBeanImplTest {
       return PENDING_COUNT;
     }
 
+    @Override
+    public Optional<Long> getLongestTimePending() {
+      return PENDING_TIME;
+    }
+
     @Override
     public long addInProgress() {
       return methodNotImplemented();
@@ -197,6 +205,7 @@ public class CeTasksMBeanImplTest {
     }
 
   }
+
   private static class DumbCeConfiguration implements CeConfiguration {
 
     @Override
index 93405db5b79c41084f933c59fa4a1300ea53c0d9..32ac5ab25150f6923dae3bf5579c9cf6e3ef476d 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.ce.monitoring;
 
 import org.junit.Test;
 import org.mockito.Mockito;
+import org.sonar.api.utils.System2;
 import org.sonar.db.DbClient;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -28,7 +29,7 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verifyZeroInteractions;
 
 public class DistributedCEQueueStatusImplTest extends CommonCEQueueStatusImplTest {
-  private DistributedCEQueueStatusImpl underTest = new DistributedCEQueueStatusImpl(getDbClient());
+  private DistributedCEQueueStatusImpl underTest = new DistributedCEQueueStatusImpl(getDbClient(), mock(System2.class));
 
   public DistributedCEQueueStatusImplTest() {
     super(mock(DbClient.class, Mockito.RETURNS_DEEP_STUBS));
index 79bc47b4fa7065239990a0c85d18831a11beb31a..e54545f41811fe7c4e0f923e04e95bcd5e4eae69 100644 (file)
@@ -80,7 +80,7 @@ public class InternalCeQueueImplTest {
   private DbSession session = db.getSession();
 
   private UuidFactory uuidFactory = UuidFactoryImpl.INSTANCE;
-  private CEQueueStatus queueStatus = new CEQueueStatusImpl(db.getDbClient());
+  private CEQueueStatus queueStatus = new CEQueueStatusImpl(db.getDbClient(), mock(System2.class));
   private DefaultOrganizationProvider defaultOrganizationProvider = mock(DefaultOrganizationProvider.class);
   private ComputeEngineStatus computeEngineStatus = mock(ComputeEngineStatus.class);
   private InternalCeQueue underTest = new InternalCeQueueImpl(system2, db.getDbClient(), uuidFactory, queueStatus, defaultOrganizationProvider, computeEngineStatus);
index 127161d8a3d8a2a6f53a5914d1245920eb07ce49..51025ccf8bf0af158ddfbcb5da40e13f3623b77b 100644 (file)
@@ -142,9 +142,12 @@ public class CeQueueDao implements Dao {
     return mapper(dbSession).countByStatusAndMainComponentUuid(status, mainComponentUuid);
   }
 
+  public Optional<Long> selectCreationDateOfOldestPendingByMainComponentUuid(DbSession dbSession, @Nullable String mainComponentUuid) {
+    return Optional.ofNullable(mapper(dbSession).selectCreationDateOfOldestPendingByMainComponentUuid(mainComponentUuid));
+  }
+
   /**
    * Counts entries in the queue with the specified status for each specified main component uuid.
-   *
    * The returned map doesn't contain any entry for main component uuids for which there is no entry in the queue (ie.
    * all entries have a value >= 0).
    */
index a8c3647547c7ae2039f799f96c0779e7b9cc4fd7..d65484e26e9b52db7bc45e0d7a8bfc0bb9b87a3a 100644 (file)
@@ -68,6 +68,9 @@ public interface CeQueueMapper {
 
   int countByStatusAndMainComponentUuid(@Param("status") CeQueueDto.Status status, @Nullable @Param("mainComponentUuid") String mainComponentUuid);
 
+  @CheckForNull
+  Long selectCreationDateOfOldestPendingByMainComponentUuid(@Nullable @Param("mainComponentUuid") String mainComponentUuid);
+
   List<QueueCount> countByStatusAndMainComponentUuids(@Param("status") CeQueueDto.Status status, @Param("mainComponentUuids") List<String> mainComponentUuids);
 
   void insert(CeQueueDto dto);
index 3da4ae3a217ce950043793fc707f0269c71c1bfb..a55ec38c2f144e1ea3d057c7a0a4085599066b0c 100644 (file)
       </if>
   </select>
 
+  <select id="selectCreationDateOfOldestPendingByMainComponentUuid" parameterType="map" resultType="Long">
+    select
+      min(created_at)
+    from
+      ce_queue
+    where
+      status='PENDING'
+      <if test="mainComponentUuid!=null">
+        and main_component_uuid=#{mainComponentUuid,jdbcType=VARCHAR}
+      </if>
+  </select>
+
   <select id="countByStatusAndMainComponentUuids" resultType="org.sonar.db.ce.QueueCount">
     select
       main_component_uuid as mainComponentUuid,
index 41068aecf287db2997e775efe8fbe79a9d802d6d..7e6bce920e5a7573d2a14b169c66c1f993eb18e0 100644 (file)
@@ -28,6 +28,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Random;
+import java.util.function.Consumer;
 import java.util.stream.Stream;
 import javax.annotation.Nullable;
 import org.junit.Rule;
@@ -172,6 +173,58 @@ public class CeQueueDaoTest {
       .containsOnly("p1", "p2", "p3");
   }
 
+  @Test
+  public void selectCreationDateOfOldestPendingByMainComponentUuid_on_any_component_returns_date() {
+    long time = alwaysIncreasingSystem2.now() + 10_000;
+    insertPending("p1", dto -> {
+      dto.setCreatedAt(time);
+      dto.setUpdatedAt(time+500);
+      dto.setMainComponentUuid("c1");
+    });
+    insertPending("p2", dto -> {
+      dto.setCreatedAt(time + 1000);
+      dto.setUpdatedAt(time + 2000);
+      dto.setMainComponentUuid("c2");
+    });
+
+    makeInProgress("w1", alwaysIncreasingSystem2.now(), insertPending("i1", dto -> dto.setMainComponentUuid("c3")));
+
+    assertThat(underTest.selectCreationDateOfOldestPendingByMainComponentUuid(db.getSession(), null))
+      .isEqualTo(Optional.of(time));
+  }
+
+  @Test
+  public void selectCreationDateOfOldestPendingByMainComponentUuid_on_specific_component_returns_date() {
+    long time = alwaysIncreasingSystem2.now() + 10_000;
+    insertPending("p1", dto -> {
+      dto.setCreatedAt(time);
+      dto.setUpdatedAt(time+500);
+      dto.setMainComponentUuid("c2");
+    });
+    insertPending("p2", dto -> {
+      dto.setCreatedAt(time + 2000);
+      dto.setUpdatedAt(time + 3000);
+      dto.setMainComponentUuid("c1");
+    });
+    insertPending("p3", dto -> {
+      dto.setCreatedAt(time + 4000);
+      dto.setUpdatedAt(time + 5000);
+      dto.setMainComponentUuid("c1");
+    });
+
+    makeInProgress("w1", alwaysIncreasingSystem2.now(), insertPending("i1", dto -> dto.setMainComponentUuid("c1")));
+
+    assertThat(underTest.selectCreationDateOfOldestPendingByMainComponentUuid(db.getSession(), "c1"))
+      .isEqualTo(Optional.of(time+2000));
+  }
+
+  @Test
+  public void selectCreationDateOfOldestPendingByMainComponentUuid_returns_empty_when_no_pending_tasks() {
+    makeInProgress("w1", alwaysIncreasingSystem2.now(), insertPending("i1"));
+    assertThat(underTest.selectCreationDateOfOldestPendingByMainComponentUuid(db.getSession(), null))
+      .isEmpty();
+  }
+
   @Test
   public void selectWornout_returns_task_pending_with_a_non_null_startedAt() {
     insertPending("p1");
@@ -583,11 +636,18 @@ public class CeQueueDaoTest {
   }
 
   private CeQueueDto insertPending(String uuid) {
+    return insertPending(uuid, (Consumer<CeQueueDto>) null);
+  }
+
+  private CeQueueDto insertPending(String uuid, @Nullable Consumer<CeQueueDto> dtoConsumer) {
     CeQueueDto dto = new CeQueueDto();
     dto.setUuid(uuid);
     dto.setTaskType(CeTaskTypes.REPORT);
     dto.setStatus(PENDING);
     dto.setSubmitterUuid("henri");
+    if (dtoConsumer != null) {
+      dtoConsumer.accept(dto);
+    }
     underTestAlwaysIncreasingSystem2.insert(db.getSession(), dto);
     db.getSession().commit();
     return dto;
@@ -629,7 +689,7 @@ public class CeQueueDaoTest {
   }
 
   private void verifyCeQueueStatuses(String taskUuid1, CeQueueDto.Status taskStatus1, String taskUuid2, CeQueueDto.Status taskStatus2) {
-    verifyCeQueueStatuses(new String[]{taskUuid1, taskUuid2}, new CeQueueDto.Status[]{taskStatus1, taskStatus2});
+    verifyCeQueueStatuses(new String[] {taskUuid1, taskUuid2}, new CeQueueDto.Status[] {taskStatus1, taskStatus2});
   }
 
   private void verifyCeQueueStatuses(String[] taskUuids, CeQueueDto.Status[] statuses) {
index 753b2942f37d12822ca9b7d3da8e2917f9257988..11e24208d357986e562945a4d94161793b5c211b 100644 (file)
  */
 package org.sonar.server.ce.ws;
 
-import com.google.common.base.Optional;
+import java.util.Optional;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
 import org.sonar.api.server.ws.Change;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.System2;
 import org.sonar.api.web.UserRole;
 import org.sonar.core.util.Uuids;
 import org.sonar.db.DbClient;
@@ -44,18 +47,20 @@ public class ActivityStatusAction implements CeWsAction {
   private final UserSession userSession;
   private final DbClient dbClient;
   private final ComponentFinder componentFinder;
+  private final System2 system2;
 
-  public ActivityStatusAction(UserSession userSession, DbClient dbClient, ComponentFinder componentFinder) {
+  public ActivityStatusAction(UserSession userSession, DbClient dbClient, ComponentFinder componentFinder, System2 system2) {
     this.userSession = userSession;
     this.dbClient = dbClient;
     this.componentFinder = componentFinder;
+    this.system2 = system2;
   }
 
   @Override
   public void define(WebService.NewController controller) {
     WebService.NewAction action = controller
       .createAction("activity_status")
-      .setDescription("Return CE activity related metrics.<br>" +
+      .setDescription("Returns CE activity related metrics.<br>" +
         "Requires 'Administer System' permission or 'Administer' rights on the specified project.")
       .setSince("5.5")
       .setResponseExample(getClass().getResource("activity_status-example.json"))
@@ -70,6 +75,7 @@ public class ActivityStatusAction implements CeWsAction {
       .setExampleValue(KeyExamples.KEY_PROJECT_EXAMPLE_001);
 
     action.setChangelog(new Change("6.6", "New field 'inProgress' in response"));
+    action.setChangelog(new Change("7.8", "New field 'pendingTime' in response, only included when there are pending tasks"));
   }
 
   @Override
@@ -81,17 +87,25 @@ public class ActivityStatusAction implements CeWsAction {
   private ActivityStatusWsResponse doHandle(Request request) {
     try (DbSession dbSession = dbClient.openSession(false)) {
       Optional<ComponentDto> component = searchComponent(dbSession, request);
-      String componentUuid = component.isPresent() ? component.get().uuid() : null;
+      String componentUuid = component.map(ComponentDto::uuid).orElse(null);
       checkPermissions(component);
       int pendingCount = dbClient.ceQueueDao().countByStatusAndMainComponentUuid(dbSession, CeQueueDto.Status.PENDING, componentUuid);
       int inProgressCount = dbClient.ceQueueDao().countByStatusAndMainComponentUuid(dbSession, CeQueueDto.Status.IN_PROGRESS, componentUuid);
       int failingCount = dbClient.ceActivityDao().countLastByStatusAndMainComponentUuid(dbSession, CeActivityDto.Status.FAILED, componentUuid);
 
-      return ActivityStatusWsResponse.newBuilder()
+      Optional<Long> creationDate = dbClient.ceQueueDao().selectCreationDateOfOldestPendingByMainComponentUuid(dbSession, componentUuid);
+
+      ActivityStatusWsResponse.Builder builder = ActivityStatusWsResponse.newBuilder()
         .setPending(pendingCount)
         .setInProgress(inProgressCount)
-        .setFailing(failingCount)
-        .build();
+        .setFailing(failingCount);
+
+      creationDate.ifPresent(d -> {
+        long ageOfOldestPendingTime = system2.now() - d;
+        builder.setPendingTime(ageOfOldestPendingTime);
+      });
+
+      return builder.build();
     }
   }
 
@@ -100,7 +114,7 @@ public class ActivityStatusAction implements CeWsAction {
     if (hasComponentInRequest(request)) {
       component = componentFinder.getByUuidOrKey(dbSession, request.getComponentId(), request.getComponentKey(), COMPONENT_ID_AND_KEY);
     }
-    return Optional.fromNullable(component);
+    return Optional.ofNullable(component);
   }
 
   private void checkPermissions(Optional<ComponentDto> component) {
@@ -116,30 +130,24 @@ public class ActivityStatusAction implements CeWsAction {
   }
 
   private static Request toWsRequest(org.sonar.api.server.ws.Request request) {
-    return new Request()
-      .setComponentId(request.param(PARAM_COMPONENT_ID))
-      .setComponentKey(request.param(DEPRECATED_PARAM_COMPONENT_KEY));
+    return new Request(request.param(PARAM_COMPONENT_ID), request.param(DEPRECATED_PARAM_COMPONENT_KEY));
   }
 
   private static class Request {
-
     private String componentId;
     private String componentKey;
 
-    public Request setComponentId(String componentId) {
+    Request(@Nullable String componentId, @Nullable String componentKey) {
       this.componentId = componentId;
-      return this;
+      this.componentKey = componentKey;
     }
 
+    @CheckForNull
     public String getComponentId() {
       return componentId;
     }
 
-    public Request setComponentKey(String componentKey) {
-      this.componentKey = componentKey;
-      return this;
-    }
-
+    @CheckForNull
     public String getComponentKey() {
       return componentKey;
     }
index 4cd59c63dd639e6fd160000f96095cc8bab59da4..e6fdfa8b7e65568b10f5ccd76fe381b5ba2f41ca 100644 (file)
@@ -1,5 +1,6 @@
 {
   "pending": 2,
   "inProgress": 1,
-  "failing": 5
+  "failing": 5,
+  "pendingTime": 100123
 }
index 4c942cf118cba2b83171faef1d245bfe3a351714..8e5f8f5ce580bd0befb3e38b3496bb960b37b1c6 100644 (file)
@@ -44,6 +44,8 @@ import org.sonar.server.ws.WsActionTester;
 import org.sonarqube.ws.Ce;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 import static org.sonar.db.ce.CeQueueTesting.newCeQueueDto;
 import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
 import static org.sonar.server.ce.ws.CeWsParameters.DEPRECATED_PARAM_COMPONENT_KEY;
@@ -59,9 +61,11 @@ public class ActivityStatusActionTest {
   @Rule
   public DbTester db = DbTester.create(System2.INSTANCE);
 
+  private System2 system2 = mock(System2.class);
+
   private DbClient dbClient = db.getDbClient();
   private DbSession dbSession = db.getSession();
-  private WsActionTester ws = new WsActionTester(new ActivityStatusAction(userSession, dbClient, TestComponentFinder.from(db)));
+  private WsActionTester ws = new WsActionTester(new ActivityStatusAction(userSession, dbClient, TestComponentFinder.from(db), system2));
 
   @Test
   public void test_definition() {
@@ -74,7 +78,8 @@ public class ActivityStatusActionTest {
 
   @Test
   public void json_example() {
-    dbClient.ceQueueDao().insert(dbSession, newCeQueueDto("ce-queue-uuid-1").setStatus(CeQueueDto.Status.PENDING));
+    when(system2.now()).thenReturn(200123L);
+    dbClient.ceQueueDao().insert(dbSession, newCeQueueDto("ce-queue-uuid-1").setStatus(CeQueueDto.Status.PENDING).setCreatedAt(100000));
     dbClient.ceQueueDao().insert(dbSession, newCeQueueDto("ce-queue-uuid-2").setStatus(CeQueueDto.Status.PENDING));
     dbClient.ceQueueDao().insert(dbSession, newCeQueueDto("ce-queue-uuid-3").setStatus(CeQueueDto.Status.IN_PROGRESS));
     for (int i = 0; i < 5; i++) {
@@ -118,6 +123,23 @@ public class ActivityStatusActionTest {
     assertThat(result.getFailing()).isEqualTo(1);
   }
 
+  @Test
+  public void add_pending_time() {
+    String projectUuid = "project-uuid";
+    OrganizationDto organizationDto = db.organizations().insert();
+    ComponentDto project = newPrivateProjectDto(organizationDto, projectUuid);
+    db.components().insertComponent(project);
+
+    userSession.logIn().addProjectPermission(UserRole.ADMIN, project);
+    when(system2.now()).thenReturn(2000L);
+    insertInQueue(CeQueueDto.Status.PENDING, project, 1000L);
+    Ce.ActivityStatusWsResponse result = call(projectUuid);
+
+    assertThat(result).extracting(Ce.ActivityStatusWsResponse::getPending, Ce.ActivityStatusWsResponse::getFailing,
+      Ce.ActivityStatusWsResponse::getInProgress, Ce.ActivityStatusWsResponse::getPendingTime)
+      .containsOnly(1, 0, 0, 1000L);
+  }
+
   @Test
   public void empty_status() {
     Ce.ActivityStatusWsResponse result = call();
@@ -171,9 +193,17 @@ public class ActivityStatusActionTest {
   }
 
   private void insertInQueue(CeQueueDto.Status status, @Nullable ComponentDto componentDto) {
-    dbClient.ceQueueDao().insert(dbSession, newCeQueueDto(Uuids.createFast())
+    insertInQueue(status, componentDto, null);
+  }
+
+  private void insertInQueue(CeQueueDto.Status status, @Nullable ComponentDto componentDto, @Nullable Long createdAt) {
+    CeQueueDto ceQueueDto = newCeQueueDto(Uuids.createFast())
       .setStatus(status)
-      .setComponent(componentDto));
+      .setComponent(componentDto);
+    if (createdAt != null) {
+      ceQueueDto.setCreatedAt(createdAt);
+    }
+    dbClient.ceQueueDao().insert(dbSession, ceQueueDto);
     db.commit();
   }
 
index 0443f65eed097a179506aeab9adccee3a5ba0304..a758349fa5e8217d21e60e59a56cc95b3c2bf514 100644 (file)
@@ -49,6 +49,7 @@ message ActivityStatusWsResponse {
   optional int32 pending = 1;
   optional int32 failing = 2;
   optional int32 inProgress = 3;
+  optional int64 pendingTime = 4;
 }
 
 // GET api/ce/analysis_status