mirror of
https://github.com/aljazceru/lnd-manageJ.git
synced 2026-01-28 02:14:40 +01:00
report online percentage in online report
This commit is contained in:
@@ -3,15 +3,19 @@ package de.cotto.lndmanagej.service;
|
||||
import de.cotto.lndmanagej.model.Node;
|
||||
import de.cotto.lndmanagej.model.OnlineReport;
|
||||
import de.cotto.lndmanagej.model.OnlineStatus;
|
||||
import de.cotto.lndmanagej.model.Pubkey;
|
||||
import de.cotto.lndmanagej.onlinepeers.OnlinePeersDao;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class OnlinePeersService {
|
||||
private static final int DAYS_FOR_OFFLINE_PERCENTAGE = 7;
|
||||
private final OnlinePeersDao dao;
|
||||
|
||||
public OnlinePeersService(OnlinePeersDao dao) {
|
||||
@@ -20,11 +24,44 @@ public class OnlinePeersService {
|
||||
|
||||
public OnlineReport getOnlineReport(Node node) {
|
||||
boolean online = node.online();
|
||||
OnlineStatus mostRecentOnlineStatus = dao.getMostRecentOnlineStatus(node.pubkey()).orElse(null);
|
||||
if (mostRecentOnlineStatus != null && mostRecentOnlineStatus.online() == online) {
|
||||
return OnlineReport.createFromStatus(mostRecentOnlineStatus);
|
||||
OnlineStatus mostRecentOnlineStatus = dao.getMostRecentOnlineStatus(node.pubkey())
|
||||
.orElse(new OnlineStatus(online, now()));
|
||||
int onlinePercentageLastWeek = getOnlinePercentageLastWeek(node.pubkey());
|
||||
if (mostRecentOnlineStatus.online() == online) {
|
||||
return OnlineReport.createFromStatus(mostRecentOnlineStatus, onlinePercentageLastWeek);
|
||||
}
|
||||
return new OnlineReport(online, now());
|
||||
return new OnlineReport(online, now(), onlinePercentageLastWeek);
|
||||
}
|
||||
|
||||
public int getOnlinePercentageLastWeek(Pubkey pubkey) {
|
||||
Duration total = Duration.ZERO;
|
||||
Duration online = Duration.ZERO;
|
||||
ZonedDateTime intervalStart = ZonedDateTime.now(ZoneOffset.UTC);
|
||||
ZonedDateTime cutoff = intervalStart.minusDays(DAYS_FOR_OFFLINE_PERCENTAGE);
|
||||
boolean shouldContinue = true;
|
||||
List<OnlineStatus> allForPeer = dao.getAllForPeer(pubkey);
|
||||
for (int i = 0; i < allForPeer.size() && shouldContinue; i++) {
|
||||
OnlineStatus onlineStatus = allForPeer.get(i);
|
||||
ZonedDateTime intervalEnd = onlineStatus.since();
|
||||
if (intervalEnd.isBefore(cutoff)) {
|
||||
intervalEnd = cutoff;
|
||||
shouldContinue = false;
|
||||
}
|
||||
Duration difference = Duration.between(intervalEnd, intervalStart);
|
||||
total = total.plus(difference);
|
||||
if (onlineStatus.online()) {
|
||||
online = online.plus(difference);
|
||||
}
|
||||
intervalStart = intervalEnd;
|
||||
}
|
||||
if (total.isZero()) {
|
||||
return 0;
|
||||
}
|
||||
return getRoundedPercentage(total, online);
|
||||
}
|
||||
|
||||
private int getRoundedPercentage(Duration total, Duration offline) {
|
||||
return (int) (offline.getSeconds() * 100.0 / total.getSeconds());
|
||||
}
|
||||
|
||||
private ZonedDateTime now() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.cotto.lndmanagej.service;
|
||||
|
||||
import de.cotto.lndmanagej.model.OnlineReport;
|
||||
import de.cotto.lndmanagej.model.OnlineStatus;
|
||||
import de.cotto.lndmanagej.onlinepeers.OnlinePeersDao;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -10,6 +11,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static de.cotto.lndmanagej.model.NodeFixtures.NODE;
|
||||
@@ -23,6 +25,8 @@ import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class OnlinePeersServiceTest {
|
||||
private static final ZonedDateTime NOW = ZonedDateTime.now(ZoneOffset.UTC);
|
||||
|
||||
@InjectMocks
|
||||
private OnlinePeersService onlinePeersService;
|
||||
|
||||
@@ -32,6 +36,7 @@ class OnlinePeersServiceTest {
|
||||
@Test
|
||||
void with_time_if_given_status_matches_last_known_status() {
|
||||
when(dao.getMostRecentOnlineStatus(PUBKEY)).thenReturn(Optional.of(ONLINE_STATUS));
|
||||
mockFor23PercentOffline();
|
||||
assertThat(onlinePeersService.getOnlineReport(NODE_PEER)).isEqualTo(ONLINE_REPORT);
|
||||
}
|
||||
|
||||
@@ -57,9 +62,92 @@ class OnlinePeersServiceTest {
|
||||
assertVeryRecentSince(report);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOnlinePercentageLastWeek_no_data() {
|
||||
assertThat(onlinePeersService.getOnlinePercentageLastWeek(PUBKEY)).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOnlinePercentageLastWeek_always_online() {
|
||||
ZonedDateTime early = ZonedDateTime.of(2018, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC);
|
||||
when(dao.getAllForPeer(PUBKEY)).thenReturn(List.of(new OnlineStatus(true, early)));
|
||||
assertThat(onlinePeersService.getOnlinePercentageLastWeek(PUBKEY)).isEqualTo(100);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOnlinePercentageLastWeek_always_offline() {
|
||||
ZonedDateTime early = ZonedDateTime.of(2018, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC);
|
||||
when(dao.getAllForPeer(PUBKEY)).thenReturn(List.of(new OnlineStatus(false, early)));
|
||||
assertThat(onlinePeersService.getOnlinePercentageLastWeek(PUBKEY)).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOnlinePercentageLastWeek_limited_data_offline() {
|
||||
ZonedDateTime oneHourAgo = NOW.minusHours(1);
|
||||
when(dao.getAllForPeer(PUBKEY)).thenReturn(List.of(new OnlineStatus(false, oneHourAgo)));
|
||||
assertThat(onlinePeersService.getOnlinePercentageLastWeek(PUBKEY)).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOnlinePercentageLastWeek_limited_data_online() {
|
||||
ZonedDateTime oneHourAgo = NOW.minusHours(1);
|
||||
when(dao.getAllForPeer(PUBKEY)).thenReturn(List.of(new OnlineStatus(true, oneHourAgo)));
|
||||
assertThat(onlinePeersService.getOnlinePercentageLastWeek(PUBKEY)).isEqualTo(100);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOnlinePercentageLastWeek_limited_data_online_then_offline() {
|
||||
ZonedDateTime twoHoursAgo = NOW.minusHours(2);
|
||||
ZonedDateTime oneHourAgo = NOW.minusHours(1);
|
||||
when(dao.getAllForPeer(PUBKEY)).thenReturn(List.of(
|
||||
new OnlineStatus(true, oneHourAgo),
|
||||
new OnlineStatus(false, twoHoursAgo)
|
||||
));
|
||||
assertThat(onlinePeersService.getOnlinePercentageLastWeek(PUBKEY)).isEqualTo(50);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOnlinePercentageLastWeek_limited_data_offline_then_online() {
|
||||
ZonedDateTime twoHoursAgo = NOW.minusHours(2);
|
||||
ZonedDateTime oneHourAgo = NOW.minusHours(1);
|
||||
when(dao.getAllForPeer(PUBKEY)).thenReturn(List.of(
|
||||
new OnlineStatus(false, oneHourAgo),
|
||||
new OnlineStatus(true, twoHoursAgo)
|
||||
));
|
||||
assertThat(onlinePeersService.getOnlinePercentageLastWeek(PUBKEY)).isEqualTo(49);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOnlinePercentageLastWeek_cuts_off_old_data() {
|
||||
ZonedDateTime twoYearsAgo = NOW.minusYears(2);
|
||||
ZonedDateTime oneYearAgo = NOW.minusYears(1);
|
||||
ZonedDateTime thirteenDaysAgo = NOW.minusDays(6);
|
||||
when(dao.getAllForPeer(PUBKEY)).thenReturn(List.of(
|
||||
new OnlineStatus(true, thirteenDaysAgo),
|
||||
new OnlineStatus(false, oneYearAgo),
|
||||
new OnlineStatus(true, twoYearsAgo)
|
||||
));
|
||||
assertThat(onlinePeersService.getOnlinePercentageLastWeek(PUBKEY)).isEqualTo(85);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOnlinePercentageLastWeek_is_rounded() {
|
||||
mockFor23PercentOffline();
|
||||
assertThat(onlinePeersService.getOnlinePercentageLastWeek(PUBKEY)).isEqualTo(77);
|
||||
}
|
||||
|
||||
private void assertVeryRecentSince(OnlineReport report) {
|
||||
assertThat(report.since())
|
||||
.isAfter(ZonedDateTime.now(ZoneOffset.UTC).minusSeconds(1))
|
||||
.isAfter(NOW.minusSeconds(1))
|
||||
.asString().hasSize(20);
|
||||
}
|
||||
|
||||
private void mockFor23PercentOffline() {
|
||||
ZonedDateTime longAgo = NOW.minusDays(14);
|
||||
ZonedDateTime offlineSince = NOW.minusMinutes(2318);
|
||||
when(dao.getAllForPeer(PUBKEY)).thenReturn(List.of(
|
||||
new OnlineStatus(false, offlineSince),
|
||||
new OnlineStatus(true, longAgo)
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@ package de.cotto.lndmanagej.model;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
public record OnlineReport(boolean online, ZonedDateTime since) {
|
||||
public static OnlineReport createFromStatus(OnlineStatus onlineStatus) {
|
||||
return new OnlineReport(onlineStatus.online(), onlineStatus.since());
|
||||
public record OnlineReport(boolean online, ZonedDateTime since, int onlinePercentageLastWeek) {
|
||||
public static OnlineReport createFromStatus(OnlineStatus onlineStatus, int onlinePercentageLastWeek) {
|
||||
return new OnlineReport(onlineStatus.online(), onlineStatus.since(), onlinePercentageLastWeek);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,13 @@ class OnlineReportTest {
|
||||
assertThat(ONLINE_REPORT.since()).isEqualTo(TIMESTAMP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void onlinePercentageLastWeek() {
|
||||
assertThat(ONLINE_REPORT.onlinePercentageLastWeek()).isEqualTo(77);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createFromOnlineStatus() {
|
||||
assertThat(OnlineReport.createFromStatus(ONLINE_STATUS)).isEqualTo(ONLINE_REPORT);
|
||||
assertThat(OnlineReport.createFromStatus(ONLINE_STATUS, 77)).isEqualTo(ONLINE_REPORT);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,6 @@ import static java.time.ZoneOffset.UTC;
|
||||
|
||||
public class OnlineReportFixtures {
|
||||
public static final ZonedDateTime TIMESTAMP = LocalDateTime.of(2021, 12, 23, 1, 2, 3).atZone(UTC);
|
||||
public static final OnlineReport ONLINE_REPORT = new OnlineReport(true, TIMESTAMP);
|
||||
public static final OnlineReport ONLINE_REPORT_OFFLINE = new OnlineReport(false, TIMESTAMP);
|
||||
public static final OnlineReport ONLINE_REPORT = new OnlineReport(true, TIMESTAMP, 77);
|
||||
public static final OnlineReport ONLINE_REPORT_OFFLINE = new OnlineReport(false, TIMESTAMP, 66);
|
||||
}
|
||||
|
||||
@@ -51,4 +51,22 @@ class OnlinePeersRepositoryIT {
|
||||
assertThat(repository.findTopByPubkeyOrderByTimestampDesc(PUBKEY.toString()))
|
||||
.map(OnlinePeerJpaDto::isOnline).contains(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findByPubkeyOrderByTimestampDesc() {
|
||||
repository.save(new OnlinePeerJpaDto(PUBKEY, true, TIMESTAMP));
|
||||
repository.save(new OnlinePeerJpaDto(PUBKEY, false, TIMESTAMP.minusSeconds(1)));
|
||||
repository.save(new OnlinePeerJpaDto(PUBKEY_2, false, TIMESTAMP));
|
||||
assertThat(repository.findByPubkeyOrderByTimestampDesc(PUBKEY.toString())).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findByPubkeyOrderByTimestampDesc_ordered_new_to_old() {
|
||||
repository.save(new OnlinePeerJpaDto(PUBKEY, false, TIMESTAMP.minusSeconds(1)));
|
||||
repository.save(new OnlinePeerJpaDto(PUBKEY, false, TIMESTAMP.plusSeconds(1)));
|
||||
repository.save(new OnlinePeerJpaDto(PUBKEY, true, TIMESTAMP));
|
||||
long timestamp = TIMESTAMP.toEpochSecond();
|
||||
assertThat(repository.findByPubkeyOrderByTimestampDesc(PUBKEY.toString())).map(OnlinePeerJpaDto::getTimestamp)
|
||||
.containsExactlyInAnyOrder(timestamp + 1, timestamp, timestamp - 1);
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,13 @@ import de.cotto.lndmanagej.model.OnlineStatus;
|
||||
import de.cotto.lndmanagej.model.Pubkey;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface OnlinePeersDao {
|
||||
void saveOnlineStatus(Pubkey pubkey, boolean online, ZonedDateTime timestamp);
|
||||
|
||||
Optional<OnlineStatus> getMostRecentOnlineStatus(Pubkey pubkey);
|
||||
|
||||
List<OnlineStatus> getAllForPeer(Pubkey pubkey);
|
||||
}
|
||||
|
||||
@@ -52,4 +52,8 @@ public class OnlinePeerJpaDto {
|
||||
public String getPubkey() {
|
||||
return pubkey;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.transaction.Transactional;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Component
|
||||
@@ -27,4 +28,11 @@ class OnlinePeersDaoImpl implements OnlinePeersDao {
|
||||
public Optional<OnlineStatus> getMostRecentOnlineStatus(Pubkey pubkey) {
|
||||
return repository.findTopByPubkeyOrderByTimestampDesc(pubkey.toString()).map(OnlinePeerJpaDto::toModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OnlineStatus> getAllForPeer(Pubkey pubkey) {
|
||||
return repository.findByPubkeyOrderByTimestampDesc(pubkey.toString()).stream()
|
||||
.map(OnlinePeerJpaDto::toModel)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,11 @@ package de.cotto.lndmanagej.onlinepeers.persistence;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface OnlinePeersRepository extends JpaRepository<OnlinePeerJpaDto, Long> {
|
||||
Optional<OnlinePeerJpaDto> findTopByPubkeyOrderByTimestampDesc(String pubkey);
|
||||
|
||||
List<OnlinePeerJpaDto> findByPubkeyOrderByTimestampDesc(String pubkey);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -53,6 +54,14 @@ class OnlinePeersDaoImplTest {
|
||||
assertThat(dao.getMostRecentOnlineStatus(PUBKEY)).contains(ONLINE_STATUS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAllForPeer() {
|
||||
OnlinePeerJpaDto dto1 = new OnlinePeerJpaDto(PUBKEY, true, TIMESTAMP);
|
||||
OnlinePeerJpaDto dto2 = new OnlinePeerJpaDto(PUBKEY, false, TIMESTAMP.plusSeconds(1));
|
||||
when(repository.findByPubkeyOrderByTimestampDesc(PUBKEY.toString())).thenReturn(List.of(dto1, dto2));
|
||||
assertThat(dao.getAllForPeer(PUBKEY)).containsExactly(dto1.toModel(), dto2.toModel());
|
||||
}
|
||||
|
||||
private void verifySave(Pubkey pubkey, boolean expected) {
|
||||
verify(repository).save(argThat(privateChannelJpaDto -> privateChannelJpaDto.isOnline() == expected));
|
||||
verify(repository).save(argThat(dto -> pubkey.equals(Pubkey.create(Objects.requireNonNull(dto.getPubkey())))));
|
||||
|
||||
@@ -101,6 +101,7 @@ class NodeControllerIT {
|
||||
.andExpect(jsonPath("$.onChainCosts.closeCosts", is("2000")))
|
||||
.andExpect(jsonPath("$.onChainCosts.sweepCosts", is("3000")))
|
||||
.andExpect(jsonPath("$.onlineReport.online", is(true)))
|
||||
.andExpect(jsonPath("$.onlineReport.onlinePercentageLastWeek", is(77)))
|
||||
.andExpect(jsonPath("$.onlineReport.since", is("2021-12-23T01:02:03Z")));
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ import de.cotto.lndmanagej.model.OnlineReport;
|
||||
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
public record OnlineReportDto(boolean online, String since) {
|
||||
public record OnlineReportDto(boolean online, String since, int onlinePercentageLastWeek) {
|
||||
public static OnlineReportDto createFromModel(OnlineReport onlineReport) {
|
||||
String formattedDateTime = onlineReport.since().format(DateTimeFormatter.ISO_INSTANT);
|
||||
return new OnlineReportDto(onlineReport.online(), formattedDateTime);
|
||||
return new OnlineReportDto(onlineReport.online(), formattedDateTime, onlineReport.onlinePercentageLastWeek());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,11 @@ class OnlineReportDtoTest {
|
||||
@Test
|
||||
void createFromModel() {
|
||||
assertThat(OnlineReportDto.createFromModel(ONLINE_REPORT)).isEqualTo(
|
||||
new OnlineReportDto(ONLINE_REPORT.online(), ONLINE_REPORT.since().toString())
|
||||
new OnlineReportDto(
|
||||
ONLINE_REPORT.online(),
|
||||
ONLINE_REPORT.since().toString(),
|
||||
ONLINE_REPORT.onlinePercentageLastWeek()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user