add withdrawal metrics scrape

This commit is contained in:
Anatoly Karlov 2023-05-28 16:27:01 +06:00
parent b259b99318
commit f3c9a699eb
12 changed files with 349 additions and 12 deletions

View File

@ -0,0 +1,44 @@
with w4 as (with w3 as (with w2 as (with w1 as (select w.wallet_id,
coalesce(w.provider_id, -1) as provider_id,
coalesce(w.terminal_id, '-1') as terminal_id,
w.currency_code,
w.withdrawal_status
from dw.withdrawal as w
where w.event_created_at > now() - interval '60 second'
and w.current)
select w1.*,
p.name as provider_name
from w1
inner join dw.provider as p
on w1.provider_id = p.provider_ref_id and
p.current)
select w2.*,
t.name as terminal_name
from w2
inner join dw.terminal as t
on w2.terminal_id = t.terminal_ref_id::varchar and
t.current)
select w3.*,
w.wallet_name as wallet_name
from w3
inner join dw.wallet as w
on w3.wallet_id = w.wallet_id and
w.current)
select provider_id,
provider_name,
terminal_id,
terminal_name,
wallet_id,
wallet_name,
currency_code,
withdrawal_status,
count(withdrawal_status)
from w4
group by provider_id,
provider_name,
terminal_id,
terminal_name,
wallet_id,
wallet_name,
currency_code,
withdrawal_status

View File

@ -16,4 +16,12 @@ public class GaugeConfig {
.baseUnit(Metric.PAYMENTS_COUNT.getUnit())
.register(meterRegistry);
}
@Bean
public MultiGauge multiGaugeWithdrawalsCount(MeterRegistry meterRegistry) {
return MultiGauge.builder(Metric.WITHDRAWALS_COUNT.getName())
.description(Metric.WITHDRAWALS_COUNT.getDescription())
.baseUnit(Metric.WITHDRAWALS_COUNT.getUnit())
.register(meterRegistry);
}
}

View File

@ -0,0 +1,34 @@
package dev.vality.exporter.businessmetrics.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "withdrawal")
public class WithdrawalEntity implements Serializable {
@EmbeddedId
private WithdrawalPk pk;
@Column(name = "wallet_id")
private String walletId;
@Column(name = "provider_id")
private String providerId;
@Column(name = "terminal_id")
private String terminalId;
}

View File

@ -0,0 +1,25 @@
package dev.vality.exporter.businessmetrics.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.io.Serializable;
@Embeddable
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class WithdrawalPk implements Serializable {
@Column(name = "withdrawal_id")
private String withdrawalId;
@Column(name = "sequence_id")
private String sequenceId;
}

View File

@ -0,0 +1,101 @@
package dev.vality.exporter.businessmetrics.entity;
import lombok.Data;
import javax.persistence.*;
@Data
@Entity
@NamedNativeQuery(
name = "getWithdrawalsMetricsByInterval",
query = """
with w4 as (with w3 as (with w2 as (with w1 as (select w.wallet_id,
coalesce(w.provider_id, -1) as provider_id,
coalesce(w.terminal_id, '-1') as terminal_id,
w.currency_code,
w.withdrawal_status
from dw.withdrawal as w
where w.event_created_at > :startPeriodDate
and w.current)
select w1.*,
p.name as provider_name
from w1
inner join dw.provider as p
on w1.provider_id = p.provider_ref_id and
p.current)
select w2.*,
t.name as terminal_name
from w2
inner join dw.terminal as t
on w2.terminal_id = t.terminal_ref_id::varchar and
t.current)
select w3.*,
w.wallet_name as wallet_name
from w3
inner join dw.wallet as w
on w3.wallet_id = w.wallet_id and
w.current)
select provider_id as providerId,
provider_name as providerName,
terminal_id as terminalId,
terminal_name as terminalName,
wallet_id as walletId,
wallet_name as walletName,
currency_code as currencyCode,
withdrawal_status as status,
count(withdrawal_status) as count
from w4
group by provider_id,
provider_name,
terminal_id,
terminal_name,
wallet_id,
wallet_name,
currency_code,
withdrawal_status
""",
resultSetMapping = "WithdrawalsMetricDtoList")
@SqlResultSetMapping(
name = "WithdrawalsMetricDtoList",
classes = @ConstructorResult(
targetClass = WithdrawalsMetricDto.class,
columns = {
@ColumnResult(name = "providerId", type = String.class),
@ColumnResult(name = "providerName", type = String.class),
@ColumnResult(name = "terminalId", type = String.class),
@ColumnResult(name = "terminalName", type = String.class),
@ColumnResult(name = "walletId", type = String.class),
@ColumnResult(name = "walletName", type = String.class),
@ColumnResult(name = "currencyCode", type = String.class),
@ColumnResult(name = "status", type = String.class),
@ColumnResult(name = "count", type = String.class)}))
@SuppressWarnings("LineLength")
public class WithdrawalsMetricDto {
@Id
private Long id;
private String providerId;
private String providerName;
private String terminalId;
private String terminalName;
private String walletId;
private String walletName;
private String currencyCode;
private String status;
private String count;
public WithdrawalsMetricDto() {
}
public WithdrawalsMetricDto(String providerId, String providerName, String terminalId, String terminalName, String walletId, String walletName, String currencyCode, String status, String count) {
this.providerId = providerId;
this.providerName = providerName;
this.terminalId = terminalId;
this.terminalName = terminalName;
this.walletId = walletId;
this.walletName = walletName;
this.currencyCode = currencyCode;
this.status = status;
this.count = count;
}
}

View File

@ -17,6 +17,8 @@ public class CustomTag {
public static final String BANK_TAG = "issuer_bank";
public static final String BANK_CARD_PAYMENT_SYSTEM_TAG = "issuer_bank_card_payment_system";
public static final String STATUS_TAG = "status";
public static final String WALLET_ID_TAG = "wallet_id";
public static final String WALLET_NAME_TAG = "wallet_name";
public static Tag providerId(String providerId) {
return Tag.of(PROVIDER_ID_TAG, providerId);
@ -61,4 +63,12 @@ public class CustomTag {
public static Tag status(String status) {
return Tag.of(STATUS_TAG, status);
}
public static Tag walletId(String walletId) {
return Tag.of(WALLET_ID_TAG, walletId);
}
public static Tag walletName(String walletName) {
return Tag.of(WALLET_NAME_TAG, walletName);
}
}

View File

@ -9,6 +9,11 @@ public enum Metric {
PAYMENTS_COUNT(
formatWithPrefix("payments"),
"Payments count since last scrape",
"count"),
WITHDRAWALS_COUNT(
formatWithPrefix("withdrawals"),
"Withdrawals count since last scrape",
"count");
@Getter

View File

@ -0,0 +1,20 @@
package dev.vality.exporter.businessmetrics.repository;
import dev.vality.exporter.businessmetrics.entity.WithdrawalEntity;
import dev.vality.exporter.businessmetrics.entity.WithdrawalPk;
import dev.vality.exporter.businessmetrics.entity.WithdrawalsMetricDto;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.util.List;
@Repository
public interface WithdrawalRepository extends JpaRepository<WithdrawalEntity, WithdrawalPk> {
@Query(name = "getWithdrawalsMetricsByInterval", nativeQuery = true)
List<WithdrawalsMetricDto> getWithdrawalsMetricsByInterval(@Param("startPeriodDate") LocalDateTime startPeriodDate);
}

View File

@ -10,12 +10,10 @@ import org.springframework.stereotype.Service;
public class MetricsService {
private final PaymentService paymentService;
private final WithdrawalService withdrawalService;
public void registerMetrics() {
registerPaymentsMetrics();
}
private void registerPaymentsMetrics() {
paymentService.registerMetrics();
withdrawalService.registerMetrics();
}
}

View File

@ -32,14 +32,14 @@ public class PaymentService {
private final MeterRegistry meterRegistry;
public void registerMetrics() {
var paymentsMetrics = paymentRepository.getPaymentsMetricsByInterval(getStartPeriodDate());
var metrics = paymentRepository.getPaymentsMetricsByInterval(getStartPeriodDate());
log.info("Actual payments metrics have been got from 'daway' db, " +
"interval = {}, count = {}", intervalTime, paymentsMetrics.size());
"interval = {}, count = {}", intervalTime, metrics.size());
final var pendingCount = new LongAdder();
final var failedCount = new LongAdder();
final var capturedCount = new LongAdder();
final var otherStatusCount = new LongAdder();
var rows = paymentsMetrics.stream()
var rows = metrics.stream()
.peek(dto -> {
switch (dto.getStatus()) {
case "pending" -> pendingCount.increment();

View File

@ -0,0 +1,77 @@
package dev.vality.exporter.businessmetrics.service;
import dev.vality.exporter.businessmetrics.entity.WithdrawalsMetricDto;
import dev.vality.exporter.businessmetrics.model.CustomTag;
import dev.vality.exporter.businessmetrics.model.Metric;
import dev.vality.exporter.businessmetrics.repository.WithdrawalRepository;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.MultiGauge;
import io.micrometer.core.instrument.Tags;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.atomic.LongAdder;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@Slf4j
@SuppressWarnings("LineLength")
public class WithdrawalService {
@Value("${interval.time}")
private String intervalTime;
private final WithdrawalRepository withdrawalRepository;
private final MultiGauge multiGaugeWithdrawalsCount;
private final MeterRegistry meterRegistry;
public void registerMetrics() {
var metrics = withdrawalRepository.getWithdrawalsMetricsByInterval(getStartPeriodDate());
log.info("Actual withdrawal metrics have been got from 'daway' db, " +
"interval = {}, count = {}", intervalTime, metrics.size());
final var pendingCount = new LongAdder();
final var failedCount = new LongAdder();
final var succeededCount = new LongAdder();
final var otherStatusCount = new LongAdder();
var rows = metrics.stream()
.peek(dto -> {
switch (dto.getStatus()) {
case "pending" -> pendingCount.increment();
case "succeeded" -> succeededCount.increment();
case "failed" -> failedCount.increment();
default -> otherStatusCount.increment();
}
})
.map(dto -> {
final var value = Double.parseDouble(dto.getCount());
return MultiGauge.Row.of(getTags(dto), this, o -> value);
})
.collect(Collectors.<MultiGauge.Row<?>>toList());
multiGaugeWithdrawalsCount.register(rows, true);
var registeredMetricsSize = meterRegistry.get(Metric.WITHDRAWALS_COUNT.getName()).gauges().size();
log.info("Actual withdrawal metrics have been registered to 'prometheus', " +
"registeredMetricsSize = {}, pendingCount = {}, failedCount = {}, succeededCount = {}, otherStatusCount = {}", registeredMetricsSize, pendingCount, failedCount, succeededCount, otherStatusCount);
}
private LocalDateTime getStartPeriodDate() {
return LocalDateTime.now(ZoneOffset.UTC).minus(Long.parseLong(intervalTime), ChronoUnit.SECONDS);
}
private Tags getTags(WithdrawalsMetricDto dto) {
return Tags.of(
CustomTag.providerId(dto.getProviderId()),
CustomTag.providerName(dto.getProviderName()),
CustomTag.terminalId(dto.getTerminalId()),
CustomTag.terminalName(dto.getTerminalName()),
CustomTag.walletId(dto.getWalletId()),
CustomTag.walletName(dto.getWalletName()),
CustomTag.currency(dto.getCurrencyCode()),
CustomTag.status(dto.getStatus()));
}
}

View File

@ -1,7 +1,9 @@
package dev.vality.exporter.businessmetrics;
import dev.vality.exporter.businessmetrics.entity.PaymentsMetricDto;
import dev.vality.exporter.businessmetrics.entity.WithdrawalsMetricDto;
import dev.vality.exporter.businessmetrics.repository.PaymentRepository;
import dev.vality.exporter.businessmetrics.repository.WithdrawalRepository;
import dev.vality.exporter.businessmetrics.service.SchedulerRegisterMetricsService;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
@ -35,6 +37,9 @@ public class FlowTest {
@MockBean
private PaymentRepository paymentRepository;
@MockBean
private WithdrawalRepository withdrawalRepository;
@Autowired
private MockMvc mockMvc;
@ -48,7 +53,7 @@ public class FlowTest {
@BeforeEach
public void init() {
mocks = MockitoAnnotations.openMocks(this);
preparedMocks = new Object[]{paymentRepository};
preparedMocks = new Object[]{paymentRepository, withdrawalRepository};
}
@AfterEach
@ -60,21 +65,31 @@ public class FlowTest {
@Test
public void metricsHaveBeenRegisteredTest() throws Exception {
var paymentsMetrics = getPaymentsMetricDtos();
var withdrawalsMetrics = getWithdrawalsMetricDto();
when(paymentRepository.getPaymentsMetricsByInterval(any())).thenReturn(paymentsMetrics);
when(withdrawalRepository.getWithdrawalsMetricsByInterval(any())).thenReturn(withdrawalsMetrics);
schedulerRegisterMetricsService.registerMetricsTask();
verify(paymentRepository, times(1)).getPaymentsMetricsByInterval(any());
verify(withdrawalRepository, times(1)).getWithdrawalsMetricsByInterval(any());
var mvcResult = mockMvc.perform(get("/actuator/prometheus"))
.andReturn();
var prometheusResponse = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
var actualMetrics = Arrays.stream(prometheusResponse.split("\n"))
.filter(row -> row.startsWith("ebm_")).toList();
Assertions.assertEquals(paymentsMetrics.size(), actualMetrics.size());
Assertions.assertEquals(paymentsMetrics.size() + withdrawalsMetrics.size(), actualMetrics.size());
}
private List<PaymentsMetricDto> getPaymentsMetricDtos() {
return List.of(
new PaymentsMetricDto("1", "mts", "1", "mts rub", "1", "gucci", "rub", "rus", "kaspi jsp", "pending", "1"),
new PaymentsMetricDto("2", "xxx", "2", "xxx usd", "1", "kaspi", "kzt", "kz", "kaspi jsp", "captured", "1"),
new PaymentsMetricDto("3", "reppay", "3", "reppay kzt", "1", "kaspi", "usd", "usa", "undefined", "failed", "1"));
new PaymentsMetricDto("1", "mts", "1", "mts rub", "1", "gucci", "rub", "rus", "kaspi jsp", "visa", "pending", "1"),
new PaymentsMetricDto("2", "xxx", "2", "xxx usd", "1", "kaspi", "kzt", "kz", "kaspi jsp", "visa", "captured", "1"),
new PaymentsMetricDto("3", "reppay", "3", "reppay kzt", "1", "kaspi", "usd", "usa", "undefined", "visa", "failed", "1"));
}
private List<WithdrawalsMetricDto> getWithdrawalsMetricDto() {
return List.of(
new WithdrawalsMetricDto("1", "mts", "1", "mts rub", "1", "gucci", "rub", "pending", "1"),
new WithdrawalsMetricDto("2", "xxx", "2", "xxx usd", "1", "kaspi", "kzt", "succeeded", "1"),
new WithdrawalsMetricDto("3", "reppay", "3", "reppay kzt", "1", "kaspi", "usd", "failed", "1"));
}
}