mirror of
https://github.com/tiennm99/tjahzi.git
synced 2026-10-15 22:15:14 +00:00
Merge pull request #24 from tkowalcz/21-send-logic
Re #21: Added more elaborate logic to decide when we should send data…
This commit is contained in:
14 files changed
+271
-21
No files matched your search
@@ -16,6 +16,7 @@ import pl.tkowalcz.tjahzi.stats.MutableMonitoringModuleWrapper;
|
||||
import pl.tkowalcz.tjahzi.stats.StandardMonitoringModule;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.Arrays.stream;
|
||||
@@ -58,6 +59,12 @@ public class LokiAppenderBuilder<B extends LokiAppenderBuilder<B>> extends Abstr
|
||||
@PluginBuilderAttribute
|
||||
private String logLevelLabel;
|
||||
|
||||
@PluginBuilderAttribute
|
||||
private final long batchSize = 10_2400;
|
||||
|
||||
@PluginBuilderAttribute
|
||||
private final long batchWait = 5;
|
||||
|
||||
@PluginBuilderAttribute
|
||||
private int maxRequestsInFlight = 100;
|
||||
|
||||
@@ -109,6 +116,8 @@ public class LokiAppenderBuilder<B extends LokiAppenderBuilder<B>> extends Abstr
|
||||
httpClient,
|
||||
monitoringModuleWrapper,
|
||||
lokiLabels,
|
||||
batchSize,
|
||||
TimeUnit.SECONDS.toMillis(batchWait),
|
||||
bufferSizeBytes,
|
||||
isUseOffHeapBuffer()
|
||||
);
|
||||
@@ -196,6 +205,14 @@ public class LokiAppenderBuilder<B extends LokiAppenderBuilder<B>> extends Abstr
|
||||
this.logLevelLabel = logLevelLabel;
|
||||
}
|
||||
|
||||
public long getBatchSize() {
|
||||
return batchSize;
|
||||
}
|
||||
|
||||
public long getBatchWait() {
|
||||
return batchWait;
|
||||
}
|
||||
|
||||
public void setMaxRequestsInFlight(int maxRequestsInFlight) {
|
||||
this.maxRequestsInFlight = maxRequestsInFlight;
|
||||
}
|
||||
|
||||
@@ -11,26 +11,41 @@ import org.agrona.concurrent.ringbuffer.ManyToOneRingBuffer;
|
||||
import pl.tkowalcz.tjahzi.http.NettyHttpClient;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Clock;
|
||||
import java.util.Map;
|
||||
|
||||
public class LogBufferAgent implements Agent, MessageHandler {
|
||||
|
||||
public static final int MESSAGE_COUNT_LIMIT = 100;
|
||||
public static final int MAX_MESSAGES_TO_RETRIEVE = 100;
|
||||
|
||||
private final Clock clock;
|
||||
|
||||
private final ManyToOneRingBuffer logBuffer;
|
||||
private final NettyHttpClient httpClient;
|
||||
private final long batchSize;
|
||||
private final long batchWaitMillis;
|
||||
|
||||
private final Map<String, String> staticLabels;
|
||||
private final LogBufferDeserializer logBufferDeserializer = new LogBufferDeserializer();
|
||||
|
||||
private Logproto.PushRequest.Builder request = Logproto.PushRequest.newBuilder();
|
||||
|
||||
private int estimatedBytesPending;
|
||||
private long timeoutDeadline;
|
||||
|
||||
public LogBufferAgent(
|
||||
Clock clock,
|
||||
ManyToOneRingBuffer logBuffer,
|
||||
NettyHttpClient httpClient,
|
||||
long batchSizeBytes,
|
||||
long batchWaitMillis,
|
||||
Map<String, String> staticLabels
|
||||
) {
|
||||
this.clock = clock;
|
||||
|
||||
this.logBuffer = logBuffer;
|
||||
this.batchSize = batchSizeBytes;
|
||||
this.batchWaitMillis = batchWaitMillis;
|
||||
this.httpClient = httpClient;
|
||||
|
||||
this.staticLabels = staticLabels;
|
||||
@@ -38,9 +53,10 @@ public class LogBufferAgent implements Agent, MessageHandler {
|
||||
|
||||
@Override
|
||||
public int doWork() throws IOException {
|
||||
int workDone = logBuffer.read(this, MESSAGE_COUNT_LIMIT);
|
||||
int workDone = logBuffer.read(this, MAX_MESSAGES_TO_RETRIEVE);
|
||||
|
||||
if (workDone > 0) {
|
||||
long currentTimeMillis = clock.millis();
|
||||
if (exceededBatchSizeThreshold() || exceededWaitTimeThreshold(currentTimeMillis)) {
|
||||
ByteBuf buffer = PooledByteBufAllocator.DEFAULT.buffer();
|
||||
request.build().writeTo(
|
||||
new ByteBufOutputStream(buffer)
|
||||
@@ -48,6 +64,9 @@ public class LogBufferAgent implements Agent, MessageHandler {
|
||||
|
||||
httpClient.log(buffer);
|
||||
request = Logproto.PushRequest.newBuilder();
|
||||
|
||||
estimatedBytesPending = 0;
|
||||
timeoutDeadline = currentTimeMillis + batchWaitMillis;
|
||||
}
|
||||
|
||||
return workDone;
|
||||
@@ -61,14 +80,28 @@ public class LogBufferAgent implements Agent, MessageHandler {
|
||||
int length
|
||||
) {
|
||||
if (msgTypeId == TjahziLogger.LOG_MESSAGE_TYPE_ID) {
|
||||
estimatedBytesPending += length;
|
||||
processMessage(buffer, index);
|
||||
} else {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String roleName() {
|
||||
return "ReadingLogBufferAndSendingHttp";
|
||||
}
|
||||
|
||||
private boolean exceededWaitTimeThreshold(long currentTimeMillis) {
|
||||
return currentTimeMillis > timeoutDeadline & estimatedBytesPending > 0;
|
||||
}
|
||||
|
||||
private boolean exceededBatchSizeThreshold() {
|
||||
return estimatedBytesPending > batchSize;
|
||||
}
|
||||
|
||||
private void processMessage(MutableDirectBuffer buffer, int index) {
|
||||
Logproto.StreamAdapter stream = logBufferDeserializer.deserialize(
|
||||
Logproto.StreamAdapter stream = logBufferDeserializer.deserializeIntoProtobuf(
|
||||
buffer,
|
||||
index,
|
||||
staticLabels
|
||||
@@ -76,9 +109,4 @@ public class LogBufferAgent implements Agent, MessageHandler {
|
||||
|
||||
request.addStreams(stream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String roleName() {
|
||||
return "ReadingLogBufferAndSendingHttp";
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import java.util.Map;
|
||||
|
||||
public class LogBufferDeserializer {
|
||||
|
||||
public Logproto.StreamAdapter deserialize(
|
||||
public Logproto.StreamAdapter deserializeIntoProtobuf(
|
||||
DirectBuffer buffer,
|
||||
int index,
|
||||
Map<String, String> staticLabels
|
||||
|
||||
@@ -19,6 +19,8 @@ public class TjahziInitializer {
|
||||
NettyHttpClient httpClient,
|
||||
MonitoringModule monitoringModule,
|
||||
Map<String, String> staticLabels,
|
||||
long batchSizeBytes,
|
||||
long batchWaitMillis,
|
||||
int bufferSizeBytes,
|
||||
boolean offHeap) {
|
||||
bufferSizeBytes = findNearestPowerOfTwo(bufferSizeBytes);
|
||||
@@ -29,8 +31,11 @@ public class TjahziInitializer {
|
||||
);
|
||||
|
||||
LogBufferAgent agent = new LogBufferAgent(
|
||||
monitoringModule.getClock(),
|
||||
logBuffer,
|
||||
httpClient,
|
||||
batchSizeBytes,
|
||||
batchWaitMillis,
|
||||
staticLabels
|
||||
);
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ public class NettyHttpClient implements Closeable {
|
||||
public void log(ByteBuf dataBuffer) {
|
||||
ByteBuf output = PooledByteBufAllocator.DEFAULT.buffer();
|
||||
snappy.encode(dataBuffer, output, dataBuffer.readableBytes());
|
||||
dataBuffer.release();
|
||||
|
||||
FullHttpRequest request = new DefaultFullHttpRequest(
|
||||
HttpVersion.HTTP_1_1,
|
||||
|
||||
@@ -8,6 +8,7 @@ import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpStatusClass;
|
||||
import io.netty.handler.codec.http.HttpUtil;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import pl.tkowalcz.tjahzi.stats.MonitoringModule;
|
||||
|
||||
@ChannelHandler.Sharable
|
||||
@@ -41,6 +42,8 @@ class RequestAndResponseHandler extends ChannelDuplexHandler {
|
||||
if (!HttpUtil.isKeepAlive(msg)) {
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
ReferenceCountUtil.release(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,7 +17,6 @@ import java.util.concurrent.TimeUnit;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.*;
|
||||
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
class HeadersTest {
|
||||
|
||||
@@ -38,7 +37,7 @@ class HeadersTest {
|
||||
wireMockServer.start();
|
||||
|
||||
initializer = new TjahziInitializer();
|
||||
monitoringModule = mock(StandardMonitoringModule.class);
|
||||
monitoringModule = new StandardMonitoringModule();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
@@ -83,6 +82,8 @@ class HeadersTest {
|
||||
httpClient,
|
||||
monitoringModule,
|
||||
Map.of(),
|
||||
0,
|
||||
0,
|
||||
1024 * 1024,
|
||||
false
|
||||
);
|
||||
@@ -134,6 +135,8 @@ class HeadersTest {
|
||||
httpClient,
|
||||
monitoringModule,
|
||||
Map.of(),
|
||||
0,
|
||||
0,
|
||||
1024 * 1024,
|
||||
false
|
||||
);
|
||||
@@ -187,6 +190,8 @@ class HeadersTest {
|
||||
httpClient,
|
||||
monitoringModule,
|
||||
Map.of(),
|
||||
0,
|
||||
0,
|
||||
1024 * 1024,
|
||||
false
|
||||
);
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
package pl.tkowalcz.tjahzi;
|
||||
|
||||
import org.agrona.concurrent.UnsafeBuffer;
|
||||
import org.agrona.concurrent.ringbuffer.ManyToOneRingBuffer;
|
||||
import org.agrona.concurrent.ringbuffer.RingBufferDescriptor;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import pl.tkowalcz.tjahzi.http.NettyHttpClient;
|
||||
import pl.tkowalcz.tjahzi.stats.SettableClock;
|
||||
import pl.tkowalcz.tjahzi.stats.StandardMonitoringModule;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class LogBufferAgentTest {
|
||||
|
||||
private ManyToOneRingBuffer logBuffer;
|
||||
private TjahziLogger logger;
|
||||
private SettableClock clock;
|
||||
|
||||
@BeforeEach
|
||||
private void setUp() {
|
||||
logBuffer = new ManyToOneRingBuffer(
|
||||
new UnsafeBuffer(new byte[1024 * 1024 + RingBufferDescriptor.TRAILER_LENGTH])
|
||||
);
|
||||
|
||||
logger = new TjahziLogger(logBuffer, new StandardMonitoringModule());
|
||||
clock = new SettableClock();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSendDataIfOverSizeLimit() throws IOException {
|
||||
// Given
|
||||
long waitForever = Long.MAX_VALUE;
|
||||
int sentBatchAfter5kb = 5 * 1024;
|
||||
|
||||
NettyHttpClient httpClient = mock(NettyHttpClient.class);
|
||||
LogBufferAgent agent = new LogBufferAgent(
|
||||
clock,
|
||||
logBuffer,
|
||||
httpClient,
|
||||
sentBatchAfter5kb,
|
||||
waitForever,
|
||||
Map.of()
|
||||
);
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
logger.log(
|
||||
42L,
|
||||
Map.of(),
|
||||
null,
|
||||
null,
|
||||
ByteBuffer.wrap((
|
||||
"Cupcake ipsum dolor sit amet cake wafer. " +
|
||||
"Soufflé jelly beans biscuit topping. " +
|
||||
"Danish bonbon gummies powder caramels. " +
|
||||
"Danish jelly beans sweet roll topping jelly beans oat cake toffee. " +
|
||||
"Chocolate cake sesame snaps brownie biscuit cheesecake. " +
|
||||
"Ice cream dessert sweet donut marshmallow. " +
|
||||
"Muffin bear claw cookie jelly-o sugar plum jelly beans apple pie fruitcake cookie. " +
|
||||
"Tootsie roll carrot cake pastry jujubes jelly beans chupa chups. " +
|
||||
"Soufflé cake muffin liquorice tart soufflé pie sesame snaps."
|
||||
).getBytes())
|
||||
);
|
||||
}
|
||||
|
||||
// When
|
||||
agent.doWork();
|
||||
|
||||
// Then
|
||||
verify(httpClient).log(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotSendDataBelowSizeLimit() throws IOException {
|
||||
// Given
|
||||
int wait5s = 5000;
|
||||
int sentBatchAfter5kb = 5 * 1024;
|
||||
|
||||
NettyHttpClient httpClient = mock(NettyHttpClient.class);
|
||||
LogBufferAgent agent = new LogBufferAgent(
|
||||
clock,
|
||||
logBuffer,
|
||||
httpClient,
|
||||
sentBatchAfter5kb,
|
||||
wait5s,
|
||||
Map.of()
|
||||
);
|
||||
|
||||
logger.log(
|
||||
42L,
|
||||
Map.of(),
|
||||
null,
|
||||
null,
|
||||
ByteBuffer.wrap("Test".getBytes())
|
||||
);
|
||||
|
||||
// When
|
||||
agent.doWork();
|
||||
|
||||
// Then
|
||||
verifyZeroInteractions(httpClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotAttemptSendingDataIfThereIsNothingToSend() throws IOException {
|
||||
// Given
|
||||
int wait5s = 5000;
|
||||
int sentBatchAfter5kb = 5 * 1024;
|
||||
|
||||
NettyHttpClient httpClient = mock(NettyHttpClient.class);
|
||||
LogBufferAgent agent = new LogBufferAgent(
|
||||
clock,
|
||||
logBuffer,
|
||||
httpClient,
|
||||
sentBatchAfter5kb,
|
||||
wait5s,
|
||||
Map.of()
|
||||
);
|
||||
|
||||
// When
|
||||
agent.doWork();
|
||||
clock.tick(wait5s + 1);
|
||||
agent.doWork();
|
||||
clock.tick(wait5s + 1);
|
||||
agent.doWork();
|
||||
|
||||
// Then
|
||||
verifyZeroInteractions(httpClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSendDataBelowSizeLimitIfTimeoutExpires() throws IOException {
|
||||
// Given
|
||||
int wait5s = 5000;
|
||||
int sentBatchAfter5kb = 5 * 1024;
|
||||
|
||||
ManyToOneRingBuffer logBuffer = new ManyToOneRingBuffer(
|
||||
new UnsafeBuffer(new byte[1024 + RingBufferDescriptor.TRAILER_LENGTH])
|
||||
);
|
||||
|
||||
TjahziLogger logger = new TjahziLogger(logBuffer, new StandardMonitoringModule());
|
||||
|
||||
NettyHttpClient httpClient = mock(NettyHttpClient.class);
|
||||
SettableClock clock = new SettableClock();
|
||||
|
||||
LogBufferAgent agent = new LogBufferAgent(
|
||||
clock,
|
||||
logBuffer,
|
||||
httpClient,
|
||||
sentBatchAfter5kb,
|
||||
wait5s,
|
||||
Map.of()
|
||||
);
|
||||
|
||||
// When
|
||||
logger.log(
|
||||
42L,
|
||||
Map.of(),
|
||||
null,
|
||||
null,
|
||||
ByteBuffer.wrap("Test".getBytes())
|
||||
);
|
||||
clock.tick(wait5s + 1);
|
||||
agent.doWork();
|
||||
|
||||
logger.log(
|
||||
42L,
|
||||
Map.of(),
|
||||
null,
|
||||
null,
|
||||
ByteBuffer.wrap("Test".getBytes())
|
||||
);
|
||||
clock.tick(wait5s + 1);
|
||||
agent.doWork();
|
||||
|
||||
// Then
|
||||
verify(httpClient, times(2)).log(any());
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ class LogBufferDeserializerTest {
|
||||
|
||||
// When
|
||||
LogBufferDeserializer deserializer = new LogBufferDeserializer();
|
||||
Logproto.StreamAdapter stream = deserializer.deserialize(
|
||||
Logproto.StreamAdapter stream = deserializer.deserializeIntoProtobuf(
|
||||
buffer,
|
||||
0,
|
||||
staticLabels
|
||||
@@ -101,7 +101,7 @@ class LogBufferDeserializerTest {
|
||||
|
||||
// When
|
||||
LogBufferDeserializer deserializer = new LogBufferDeserializer();
|
||||
Logproto.StreamAdapter stream = deserializer.deserialize(
|
||||
Logproto.StreamAdapter stream = deserializer.deserializeIntoProtobuf(
|
||||
buffer,
|
||||
0,
|
||||
staticLabels
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@ class LogBufferSerializerDeserializerTest {
|
||||
|
||||
// Then
|
||||
LogBufferDeserializer deserializer = new LogBufferDeserializer();
|
||||
Logproto.StreamAdapter stream = deserializer.deserialize(buffer, 0, Map.of());
|
||||
Logproto.StreamAdapter stream = deserializer.deserializeIntoProtobuf(buffer, 0, Map.of());
|
||||
|
||||
assertThat(stream.getEntriesList()).hasSize(1);
|
||||
assertThat(stream.getEntriesList().get(0).getTimestamp()).isEqualTo(
|
||||
|
||||
@@ -25,7 +25,6 @@ import java.util.concurrent.TimeUnit;
|
||||
import static io.restassured.RestAssured.given;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.hasItems;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
@Testcontainers
|
||||
class LoggingSystemSanityCheckTest {
|
||||
@@ -43,7 +42,7 @@ class LoggingSystemSanityCheckTest {
|
||||
.withExposedPorts(3100);
|
||||
|
||||
private LoggingSystem loggingSystem;
|
||||
private final StandardMonitoringModule monitoringModule = mock(StandardMonitoringModule.class);
|
||||
private final StandardMonitoringModule monitoringModule = new StandardMonitoringModule();
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
@@ -65,6 +64,8 @@ class LoggingSystemSanityCheckTest {
|
||||
httpClient,
|
||||
monitoringModule,
|
||||
Map.of("version", "0.43", "server", "127.0.0.1"),
|
||||
0,
|
||||
0,
|
||||
1024 * 1024,
|
||||
false
|
||||
);
|
||||
|
||||
@@ -28,7 +28,6 @@ import java.util.concurrent.TimeUnit;
|
||||
import static io.restassured.RestAssured.given;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.hasItems;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
@Testcontainers
|
||||
class LoggingSystemTest {
|
||||
@@ -45,7 +44,7 @@ class LoggingSystemTest {
|
||||
)
|
||||
.withExposedPorts(3100);
|
||||
|
||||
private final StandardMonitoringModule monitoringModule = mock(StandardMonitoringModule.class);
|
||||
private final StandardMonitoringModule monitoringModule = new StandardMonitoringModule();
|
||||
private LoggingSystem loggingSystem;
|
||||
|
||||
@BeforeEach
|
||||
@@ -69,6 +68,8 @@ class LoggingSystemTest {
|
||||
monitoringModule,
|
||||
Map.of("version", "0.43", "server", "127.0.0.1"),
|
||||
1024 * 1024,
|
||||
0,
|
||||
0,
|
||||
false
|
||||
);
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import java.util.concurrent.TimeUnit;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.*;
|
||||
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class ReconnectTest {
|
||||
|
||||
@@ -41,7 +40,7 @@ public class ReconnectTest {
|
||||
));
|
||||
|
||||
initializer = new TjahziInitializer();
|
||||
monitoringModule = mock(StandardMonitoringModule.class);
|
||||
monitoringModule = new StandardMonitoringModule();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
@@ -78,6 +77,8 @@ public class ReconnectTest {
|
||||
httpClient,
|
||||
monitoringModule,
|
||||
Map.of(),
|
||||
0,
|
||||
0,
|
||||
1024 * 1024,
|
||||
false
|
||||
);
|
||||
@@ -127,6 +128,8 @@ public class ReconnectTest {
|
||||
httpClient,
|
||||
monitoringModule,
|
||||
Map.of(),
|
||||
0,
|
||||
0,
|
||||
1024 * 1024,
|
||||
false
|
||||
);
|
||||
|
||||
@@ -68,6 +68,8 @@ class ResourcesCleanupOnCloseTest {
|
||||
httpClient,
|
||||
monitoringModule,
|
||||
Map.of(),
|
||||
0,
|
||||
0,
|
||||
1024 * 1024,
|
||||
false
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user