*/ | */ | ||||
package org.sonar.ce.monitoring; | package org.sonar.ce.monitoring; | ||||
import java.util.Optional; | |||||
import org.sonar.api.ce.ComputeEngineSide; | import org.sonar.api.ce.ComputeEngineSide; | ||||
@ComputeEngineSide | @ComputeEngineSide | ||||
long addSuccess(long processingTime); | 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 | * @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 #getErrorCount() | ||||
* @see #getInProgressCount() | * @see #getInProgressCount() | ||||
long addError(long processingTime); | 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(); | 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(); | 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(); | 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(); | long getSuccessCount(); | ||||
/** | /** | ||||
* Time spent processing batch reports since startup, in milliseconds. | |||||
* Time spent processing tasks since startup, in milliseconds. | |||||
*/ | */ | ||||
long getProcessingTime(); | long getProcessingTime(); | ||||
import java.util.Optional; | import java.util.Optional; | ||||
import java.util.concurrent.atomic.AtomicLong; | import java.util.concurrent.atomic.AtomicLong; | ||||
import org.sonar.api.utils.System2; | |||||
import org.sonar.db.DbClient; | import org.sonar.db.DbClient; | ||||
import org.sonar.db.DbSession; | import org.sonar.db.DbSession; | ||||
import org.sonar.db.ce.CeQueueDto; | import org.sonar.db.ce.CeQueueDto; | ||||
public class CEQueueStatusImpl implements CEQueueStatus { | public class CEQueueStatusImpl implements CEQueueStatus { | ||||
private final DbClient dbClient; | private final DbClient dbClient; | ||||
private final System2 system; | |||||
private final AtomicLong inProgress = new AtomicLong(0); | private final AtomicLong inProgress = new AtomicLong(0); | ||||
private final AtomicLong error = new AtomicLong(0); | private final AtomicLong error = new AtomicLong(0); | ||||
private final AtomicLong success = new AtomicLong(0); | private final AtomicLong success = new AtomicLong(0); | ||||
private final AtomicLong processingTime = new AtomicLong(0); | private final AtomicLong processingTime = new AtomicLong(0); | ||||
public CEQueueStatusImpl(DbClient dbClient) { | |||||
public CEQueueStatusImpl(DbClient dbClient, System2 system) { | |||||
this.dbClient = dbClient; | this.dbClient = dbClient; | ||||
this.system = system; | |||||
} | } | ||||
@Override | @Override | ||||
} | } | ||||
} | } | ||||
@Override | |||||
public Optional<Long> getLongestTimePending() { | |||||
try (DbSession dbSession = dbClient.openSession(false)) { | |||||
return dbClient.ceQueueDao().selectCreationDateOfOldestPendingByMainComponentUuid(dbSession, null) | |||||
.map(creationDate -> system.now() - creationDate); | |||||
} | |||||
} | |||||
@Override | @Override | ||||
public boolean areWorkersPaused() { | public boolean areWorkersPaused() { | ||||
try (DbSession dbSession = dbClient.openSession(false)) { | try (DbSession dbSession = dbClient.openSession(false)) { |
package org.sonar.ce.monitoring; | package org.sonar.ce.monitoring; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.Optional; | |||||
public interface CeTasksMBean { | public interface CeTasksMBean { | ||||
String OBJECT_NAME = "SonarQube:name=ComputeEngineTasks"; | 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(); | 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(); | 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(); | 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(); | long getSuccessCount(); | ||||
/** | /** | ||||
* Time spent processing reports since startup, in milliseconds. | |||||
* Time spent processing tasks since startup, in milliseconds. | |||||
*/ | */ | ||||
long getProcessingTime(); | long getProcessingTime(); | ||||
package org.sonar.ce.monitoring; | package org.sonar.ce.monitoring; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.Optional; | |||||
import java.util.Set; | import java.util.Set; | ||||
import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
import org.picocontainer.Startable; | import org.picocontainer.Startable; | ||||
return queueStatus.getPendingCount(); | return queueStatus.getPendingCount(); | ||||
} | } | ||||
@Override | |||||
public Optional<Long> getLongestTimePending() { | |||||
return queueStatus.getLongestTimePending(); | |||||
} | |||||
@Override | @Override | ||||
public long getInProgressCount() { | public long getInProgressCount() { | ||||
return queueStatus.getInProgressCount(); | return queueStatus.getInProgressCount(); | ||||
ProtobufSystemInfo.Section.Builder builder = ProtobufSystemInfo.Section.newBuilder(); | ProtobufSystemInfo.Section.Builder builder = ProtobufSystemInfo.Section.newBuilder(); | ||||
builder.setName("Compute Engine Tasks"); | builder.setName("Compute Engine Tasks"); | ||||
builder.addAttributesBuilder().setKey("Pending").setLongValue(getPendingCount()).build(); | 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("In Progress").setLongValue(getInProgressCount()).build(); | ||||
builder.addAttributesBuilder().setKey("Processed With Error").setLongValue(getErrorCount()).build(); | builder.addAttributesBuilder().setKey("Processed With Error").setLongValue(getErrorCount()).build(); | ||||
builder.addAttributesBuilder().setKey("Processed With Success").setLongValue(getSuccessCount()).build(); | builder.addAttributesBuilder().setKey("Processed With Success").setLongValue(getSuccessCount()).build(); |
*/ | */ | ||||
package org.sonar.ce.monitoring; | package org.sonar.ce.monitoring; | ||||
import org.sonar.api.utils.System2; | |||||
import org.sonar.db.DbClient; | import org.sonar.db.DbClient; | ||||
public class DistributedCEQueueStatusImpl extends CEQueueStatusImpl { | public class DistributedCEQueueStatusImpl extends CEQueueStatusImpl { | ||||
public DistributedCEQueueStatusImpl(DbClient dbClient) { | |||||
super(dbClient); | |||||
public DistributedCEQueueStatusImpl(DbClient dbClient, System2 system2) { | |||||
super(dbClient, system2); | |||||
} | } | ||||
@Override | @Override |
import java.util.concurrent.TimeUnit; | import java.util.concurrent.TimeUnit; | ||||
import org.junit.After; | import org.junit.After; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
import org.sonar.api.utils.System2; | |||||
import org.sonar.db.DbClient; | import org.sonar.db.DbClient; | ||||
import static org.assertj.core.api.Assertions.assertThat; | import static org.assertj.core.api.Assertions.assertThat; | ||||
return new Thread(r, CEQueueStatusImplConcurrentTest.class.getSimpleName() + cnt++); | 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 | @After | ||||
public void tearDown() { | public void tearDown() { |
import org.junit.Test; | import org.junit.Test; | ||||
import org.mockito.Mockito; | import org.mockito.Mockito; | ||||
import org.sonar.api.utils.System2; | |||||
import org.sonar.db.DbClient; | import org.sonar.db.DbClient; | ||||
import org.sonar.db.ce.CeQueueDto; | import org.sonar.db.ce.CeQueueDto; | ||||
import static org.mockito.Mockito.when; | import static org.mockito.Mockito.when; | ||||
public class CEQueueStatusImplTest extends CommonCEQueueStatusImplTest { | public class CEQueueStatusImplTest extends CommonCEQueueStatusImplTest { | ||||
private CEQueueStatusImpl underTest = new CEQueueStatusImpl(getDbClient()); | |||||
private CEQueueStatusImpl underTest = new CEQueueStatusImpl(getDbClient(), mock(System2.class)); | |||||
public CEQueueStatusImplTest() { | public CEQueueStatusImplTest() { | ||||
super(mock(DbClient.class, Mockito.RETURNS_DEEP_STUBS)); | super(mock(DbClient.class, Mockito.RETURNS_DEEP_STUBS)); |
import com.google.common.collect.ImmutableSet; | import com.google.common.collect.ImmutableSet; | ||||
import java.lang.management.ManagementFactory; | import java.lang.management.ManagementFactory; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.Optional; | |||||
import java.util.Random; | import java.util.Random; | ||||
import java.util.Set; | import java.util.Set; | ||||
import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
public class CeTasksMBeanImplTest { | public class CeTasksMBeanImplTest { | ||||
private static final long PENDING_COUNT = 2; | 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 IN_PROGRESS_COUNT = 5; | ||||
private static final long ERROR_COUNT = 10; | private static final long ERROR_COUNT = 10; | ||||
private static final long SUCCESS_COUNT = 13; | private static final long SUCCESS_COUNT = 13; | ||||
@Test | @Test | ||||
public void get_methods_delegate_to_the_CEQueueStatus_instance() { | public void get_methods_delegate_to_the_CEQueueStatus_instance() { | ||||
assertThat(underTest.getPendingCount()).isEqualTo(PENDING_COUNT); | assertThat(underTest.getPendingCount()).isEqualTo(PENDING_COUNT); | ||||
assertThat(underTest.getLongestTimePending()).isEqualTo(PENDING_TIME); | |||||
assertThat(underTest.getInProgressCount()).isEqualTo(IN_PROGRESS_COUNT); | assertThat(underTest.getInProgressCount()).isEqualTo(IN_PROGRESS_COUNT); | ||||
assertThat(underTest.getErrorCount()).isEqualTo(ERROR_COUNT); | assertThat(underTest.getErrorCount()).isEqualTo(ERROR_COUNT); | ||||
assertThat(underTest.getSuccessCount()).isEqualTo(SUCCESS_COUNT); | assertThat(underTest.getSuccessCount()).isEqualTo(SUCCESS_COUNT); | ||||
public void export_system_info() { | public void export_system_info() { | ||||
ProtobufSystemInfo.Section section = underTest.toProtobuf(); | ProtobufSystemInfo.Section section = underTest.toProtobuf(); | ||||
assertThat(section.getName()).isEqualTo("Compute Engine Tasks"); | assertThat(section.getName()).isEqualTo("Compute Engine Tasks"); | ||||
assertThat(section.getAttributesCount()).isEqualTo(8); | |||||
assertThat(section.getAttributesCount()).isEqualTo(9); | |||||
} | } | ||||
private static class DumbCEQueueStatus implements CEQueueStatus { | private static class DumbCEQueueStatus implements CEQueueStatus { | ||||
return PENDING_COUNT; | return PENDING_COUNT; | ||||
} | } | ||||
@Override | |||||
public Optional<Long> getLongestTimePending() { | |||||
return PENDING_TIME; | |||||
} | |||||
@Override | @Override | ||||
public long addInProgress() { | public long addInProgress() { | ||||
return methodNotImplemented(); | return methodNotImplemented(); | ||||
} | } | ||||
} | } | ||||
private static class DumbCeConfiguration implements CeConfiguration { | private static class DumbCeConfiguration implements CeConfiguration { | ||||
@Override | @Override |
import org.junit.Test; | import org.junit.Test; | ||||
import org.mockito.Mockito; | import org.mockito.Mockito; | ||||
import org.sonar.api.utils.System2; | |||||
import org.sonar.db.DbClient; | import org.sonar.db.DbClient; | ||||
import static org.assertj.core.api.Assertions.assertThat; | import static org.assertj.core.api.Assertions.assertThat; | ||||
import static org.mockito.Mockito.verifyZeroInteractions; | import static org.mockito.Mockito.verifyZeroInteractions; | ||||
public class DistributedCEQueueStatusImplTest extends CommonCEQueueStatusImplTest { | public class DistributedCEQueueStatusImplTest extends CommonCEQueueStatusImplTest { | ||||
private DistributedCEQueueStatusImpl underTest = new DistributedCEQueueStatusImpl(getDbClient()); | |||||
private DistributedCEQueueStatusImpl underTest = new DistributedCEQueueStatusImpl(getDbClient(), mock(System2.class)); | |||||
public DistributedCEQueueStatusImplTest() { | public DistributedCEQueueStatusImplTest() { | ||||
super(mock(DbClient.class, Mockito.RETURNS_DEEP_STUBS)); | super(mock(DbClient.class, Mockito.RETURNS_DEEP_STUBS)); |
private DbSession session = db.getSession(); | private DbSession session = db.getSession(); | ||||
private UuidFactory uuidFactory = UuidFactoryImpl.INSTANCE; | 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 DefaultOrganizationProvider defaultOrganizationProvider = mock(DefaultOrganizationProvider.class); | ||||
private ComputeEngineStatus computeEngineStatus = mock(ComputeEngineStatus.class); | private ComputeEngineStatus computeEngineStatus = mock(ComputeEngineStatus.class); | ||||
private InternalCeQueue underTest = new InternalCeQueueImpl(system2, db.getDbClient(), uuidFactory, queueStatus, defaultOrganizationProvider, computeEngineStatus); | private InternalCeQueue underTest = new InternalCeQueueImpl(system2, db.getDbClient(), uuidFactory, queueStatus, defaultOrganizationProvider, computeEngineStatus); |
return mapper(dbSession).countByStatusAndMainComponentUuid(status, mainComponentUuid); | 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. | * 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. | * 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). | * all entries have a value >= 0). | ||||
*/ | */ |
int countByStatusAndMainComponentUuid(@Param("status") CeQueueDto.Status status, @Nullable @Param("mainComponentUuid") String mainComponentUuid); | 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); | List<QueueCount> countByStatusAndMainComponentUuids(@Param("status") CeQueueDto.Status status, @Param("mainComponentUuids") List<String> mainComponentUuids); | ||||
void insert(CeQueueDto dto); | void insert(CeQueueDto dto); |
</if> | </if> | ||||
</select> | </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 id="countByStatusAndMainComponentUuids" resultType="org.sonar.db.ce.QueueCount"> | ||||
select | select | ||||
main_component_uuid as mainComponentUuid, | main_component_uuid as mainComponentUuid, |
import java.util.Map; | import java.util.Map; | ||||
import java.util.Optional; | import java.util.Optional; | ||||
import java.util.Random; | import java.util.Random; | ||||
import java.util.function.Consumer; | |||||
import java.util.stream.Stream; | import java.util.stream.Stream; | ||||
import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||
import org.junit.Rule; | import org.junit.Rule; | ||||
.containsOnly("p1", "p2", "p3"); | .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 | @Test | ||||
public void selectWornout_returns_task_pending_with_a_non_null_startedAt() { | public void selectWornout_returns_task_pending_with_a_non_null_startedAt() { | ||||
insertPending("p1"); | insertPending("p1"); | ||||
} | } | ||||
private CeQueueDto insertPending(String uuid) { | private CeQueueDto insertPending(String uuid) { | ||||
return insertPending(uuid, (Consumer<CeQueueDto>) null); | |||||
} | |||||
private CeQueueDto insertPending(String uuid, @Nullable Consumer<CeQueueDto> dtoConsumer) { | |||||
CeQueueDto dto = new CeQueueDto(); | CeQueueDto dto = new CeQueueDto(); | ||||
dto.setUuid(uuid); | dto.setUuid(uuid); | ||||
dto.setTaskType(CeTaskTypes.REPORT); | dto.setTaskType(CeTaskTypes.REPORT); | ||||
dto.setStatus(PENDING); | dto.setStatus(PENDING); | ||||
dto.setSubmitterUuid("henri"); | dto.setSubmitterUuid("henri"); | ||||
if (dtoConsumer != null) { | |||||
dtoConsumer.accept(dto); | |||||
} | |||||
underTestAlwaysIncreasingSystem2.insert(db.getSession(), dto); | underTestAlwaysIncreasingSystem2.insert(db.getSession(), dto); | ||||
db.getSession().commit(); | db.getSession().commit(); | ||||
return dto; | return dto; | ||||
} | } | ||||
private void verifyCeQueueStatuses(String taskUuid1, CeQueueDto.Status taskStatus1, String taskUuid2, CeQueueDto.Status taskStatus2) { | 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) { | private void verifyCeQueueStatuses(String[] taskUuids, CeQueueDto.Status[] statuses) { |
*/ | */ | ||||
package org.sonar.server.ce.ws; | 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.Change; | ||||
import org.sonar.api.server.ws.Response; | import org.sonar.api.server.ws.Response; | ||||
import org.sonar.api.server.ws.WebService; | import org.sonar.api.server.ws.WebService; | ||||
import org.sonar.api.utils.System2; | |||||
import org.sonar.api.web.UserRole; | import org.sonar.api.web.UserRole; | ||||
import org.sonar.core.util.Uuids; | import org.sonar.core.util.Uuids; | ||||
import org.sonar.db.DbClient; | import org.sonar.db.DbClient; | ||||
private final UserSession userSession; | private final UserSession userSession; | ||||
private final DbClient dbClient; | private final DbClient dbClient; | ||||
private final ComponentFinder componentFinder; | 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.userSession = userSession; | ||||
this.dbClient = dbClient; | this.dbClient = dbClient; | ||||
this.componentFinder = componentFinder; | this.componentFinder = componentFinder; | ||||
this.system2 = system2; | |||||
} | } | ||||
@Override | @Override | ||||
public void define(WebService.NewController controller) { | public void define(WebService.NewController controller) { | ||||
WebService.NewAction action = controller | WebService.NewAction action = controller | ||||
.createAction("activity_status") | .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.") | "Requires 'Administer System' permission or 'Administer' rights on the specified project.") | ||||
.setSince("5.5") | .setSince("5.5") | ||||
.setResponseExample(getClass().getResource("activity_status-example.json")) | .setResponseExample(getClass().getResource("activity_status-example.json")) | ||||
.setExampleValue(KeyExamples.KEY_PROJECT_EXAMPLE_001); | .setExampleValue(KeyExamples.KEY_PROJECT_EXAMPLE_001); | ||||
action.setChangelog(new Change("6.6", "New field 'inProgress' in response")); | 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 | @Override | ||||
private ActivityStatusWsResponse doHandle(Request request) { | private ActivityStatusWsResponse doHandle(Request request) { | ||||
try (DbSession dbSession = dbClient.openSession(false)) { | try (DbSession dbSession = dbClient.openSession(false)) { | ||||
Optional<ComponentDto> component = searchComponent(dbSession, request); | Optional<ComponentDto> component = searchComponent(dbSession, request); | ||||
String componentUuid = component.isPresent() ? component.get().uuid() : null; | |||||
String componentUuid = component.map(ComponentDto::uuid).orElse(null); | |||||
checkPermissions(component); | checkPermissions(component); | ||||
int pendingCount = dbClient.ceQueueDao().countByStatusAndMainComponentUuid(dbSession, CeQueueDto.Status.PENDING, componentUuid); | int pendingCount = dbClient.ceQueueDao().countByStatusAndMainComponentUuid(dbSession, CeQueueDto.Status.PENDING, componentUuid); | ||||
int inProgressCount = dbClient.ceQueueDao().countByStatusAndMainComponentUuid(dbSession, CeQueueDto.Status.IN_PROGRESS, componentUuid); | int inProgressCount = dbClient.ceQueueDao().countByStatusAndMainComponentUuid(dbSession, CeQueueDto.Status.IN_PROGRESS, componentUuid); | ||||
int failingCount = dbClient.ceActivityDao().countLastByStatusAndMainComponentUuid(dbSession, CeActivityDto.Status.FAILED, 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) | .setPending(pendingCount) | ||||
.setInProgress(inProgressCount) | .setInProgress(inProgressCount) | ||||
.setFailing(failingCount) | |||||
.build(); | |||||
.setFailing(failingCount); | |||||
creationDate.ifPresent(d -> { | |||||
long ageOfOldestPendingTime = system2.now() - d; | |||||
builder.setPendingTime(ageOfOldestPendingTime); | |||||
}); | |||||
return builder.build(); | |||||
} | } | ||||
} | } | ||||
if (hasComponentInRequest(request)) { | if (hasComponentInRequest(request)) { | ||||
component = componentFinder.getByUuidOrKey(dbSession, request.getComponentId(), request.getComponentKey(), COMPONENT_ID_AND_KEY); | 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) { | private void checkPermissions(Optional<ComponentDto> component) { | ||||
} | } | ||||
private static Request toWsRequest(org.sonar.api.server.ws.Request request) { | 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 static class Request { | ||||
private String componentId; | private String componentId; | ||||
private String componentKey; | private String componentKey; | ||||
public Request setComponentId(String componentId) { | |||||
Request(@Nullable String componentId, @Nullable String componentKey) { | |||||
this.componentId = componentId; | this.componentId = componentId; | ||||
return this; | |||||
this.componentKey = componentKey; | |||||
} | } | ||||
@CheckForNull | |||||
public String getComponentId() { | public String getComponentId() { | ||||
return componentId; | return componentId; | ||||
} | } | ||||
public Request setComponentKey(String componentKey) { | |||||
this.componentKey = componentKey; | |||||
return this; | |||||
} | |||||
@CheckForNull | |||||
public String getComponentKey() { | public String getComponentKey() { | ||||
return componentKey; | return componentKey; | ||||
} | } |
{ | { | ||||
"pending": 2, | "pending": 2, | ||||
"inProgress": 1, | "inProgress": 1, | ||||
"failing": 5 | |||||
"failing": 5, | |||||
"pendingTime": 100123 | |||||
} | } |
import org.sonarqube.ws.Ce; | import org.sonarqube.ws.Ce; | ||||
import static org.assertj.core.api.Assertions.assertThat; | 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.ce.CeQueueTesting.newCeQueueDto; | ||||
import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto; | import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto; | ||||
import static org.sonar.server.ce.ws.CeWsParameters.DEPRECATED_PARAM_COMPONENT_KEY; | import static org.sonar.server.ce.ws.CeWsParameters.DEPRECATED_PARAM_COMPONENT_KEY; | ||||
@Rule | @Rule | ||||
public DbTester db = DbTester.create(System2.INSTANCE); | public DbTester db = DbTester.create(System2.INSTANCE); | ||||
private System2 system2 = mock(System2.class); | |||||
private DbClient dbClient = db.getDbClient(); | private DbClient dbClient = db.getDbClient(); | ||||
private DbSession dbSession = db.getSession(); | 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 | @Test | ||||
public void test_definition() { | public void test_definition() { | ||||
@Test | @Test | ||||
public void json_example() { | 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-2").setStatus(CeQueueDto.Status.PENDING)); | ||||
dbClient.ceQueueDao().insert(dbSession, newCeQueueDto("ce-queue-uuid-3").setStatus(CeQueueDto.Status.IN_PROGRESS)); | dbClient.ceQueueDao().insert(dbSession, newCeQueueDto("ce-queue-uuid-3").setStatus(CeQueueDto.Status.IN_PROGRESS)); | ||||
for (int i = 0; i < 5; i++) { | for (int i = 0; i < 5; i++) { | ||||
assertThat(result.getFailing()).isEqualTo(1); | 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 | @Test | ||||
public void empty_status() { | public void empty_status() { | ||||
Ce.ActivityStatusWsResponse result = call(); | Ce.ActivityStatusWsResponse result = call(); | ||||
} | } | ||||
private void insertInQueue(CeQueueDto.Status status, @Nullable ComponentDto componentDto) { | 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) | .setStatus(status) | ||||
.setComponent(componentDto)); | |||||
.setComponent(componentDto); | |||||
if (createdAt != null) { | |||||
ceQueueDto.setCreatedAt(createdAt); | |||||
} | |||||
dbClient.ceQueueDao().insert(dbSession, ceQueueDto); | |||||
db.commit(); | db.commit(); | ||||
} | } | ||||
optional int32 pending = 1; | optional int32 pending = 1; | ||||
optional int32 failing = 2; | optional int32 failing = 2; | ||||
optional int32 inProgress = 3; | optional int32 inProgress = 3; | ||||
optional int64 pendingTime = 4; | |||||
} | } | ||||
// GET api/ce/analysis_status | // GET api/ce/analysis_status |