Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
f517fde
update pom and config
Hazel-Datastax Jun 24, 2026
00de470
add BillingS3LogHandler
Hazel-Datastax Jun 30, 2026
4c711c1
update config
Hazel-Datastax Jun 30, 2026
e141bda
Handler - rename+add comment
Hazel-Datastax Jul 1, 2026
adc2856
Add S3BatchUploader
Hazel-Datastax Jul 6, 2026
a8405fb
Update BillingS3ExportConfig
Hazel-Datastax Jul 6, 2026
9172016
Update retry
Hazel-Datastax Jul 6, 2026
1a60514
Update config
Hazel-Datastax Jul 6, 2026
fb8bcf9
Update config
Hazel-Datastax Jul 7, 2026
8da34f4
Add BillingS3HandlerInstaller
Hazel-Datastax Jul 7, 2026
948c4e1
update comments
Hazel-Datastax Jul 7, 2026
888bd02
update installer
Hazel-Datastax Jul 7, 2026
758cd0c
update S3BatchUploader and BillingS3HandlerInstaller
Hazel-Datastax Jul 7, 2026
51e9549
update BillingS3HandlerInstaller
Hazel-Datastax Jul 7, 2026
caef7ee
remove retry
Hazel-Datastax Jul 7, 2026
a7a0f70
Update accumulate method
Hazel-Datastax Jul 7, 2026
109b5d9
Update handler
Hazel-Datastax Jul 13, 2026
a4e0d9f
Add bytes gate
Hazel-Datastax Jul 14, 2026
4f2abee
Update S3BatchUploader
Hazel-Datastax Jul 15, 2026
87baefd
Update BillingQueue
Hazel-Datastax Jul 15, 2026
b8c1687
Update batch oldestEventAt
Hazel-Datastax Jul 15, 2026
bfe1814
Update uploader
Hazel-Datastax Jul 15, 2026
720e34a
Update BillingS3LogHandler
Hazel-Datastax Jul 15, 2026
cd89686
Update size()
Hazel-Datastax Jul 15, 2026
eb19142
Update BillingS3ExportConfig
Hazel-Datastax Jul 15, 2026
d2f06c2
Update BillingS3LogHandler
Hazel-Datastax Jul 15, 2026
ed28346
Add BillingMetrics
Hazel-Datastax Jul 15, 2026
8e524d0
Add BillingMetrics doc
Hazel-Datastax Jul 15, 2026
df72274
Add BillingQueueTest
Hazel-Datastax Jul 15, 2026
5c8010e
Add S3BatchUploaderTest
Hazel-Datastax Jul 15, 2026
8c26fc4
Add BillingS3HandlerInstallerTest
Hazel-Datastax Jul 15, 2026
718ded3
Add BillingMetricsTest
Hazel-Datastax Jul 15, 2026
113de68
format
Hazel-Datastax Jul 15, 2026
2818ff4
update BillingQueue java doc
Hazel-Datastax Jul 15, 2026
1977782
update BillingS3ExportConfig java doc
Hazel-Datastax Jul 15, 2026
4c4f503
update BillingMetrics java doc
Hazel-Datastax Jul 15, 2026
4c9e540
update BillingS3LogHandler
Hazel-Datastax Jul 15, 2026
503072c
Add @VisibleForTesting
Hazel-Datastax Jul 16, 2026
7ae577d
Add BillingS3LogHandler unit test
Hazel-Datastax Jul 16, 2026
34b84cb
Add BillingS3ExportIntegrationTest
Hazel-Datastax Jul 16, 2026
994bd1c
Add java doc for S3BatchUploader
Hazel-Datastax Jul 16, 2026
4748005
Add java comments for tests
Hazel-Datastax Jul 16, 2026
7e2408f
Add test
Hazel-Datastax Jul 16, 2026
62ab29c
update test
Hazel-Datastax Jul 16, 2026
a751dde
update test order
Hazel-Datastax Jul 16, 2026
7826578
add comments
Hazel-Datastax Jul 16, 2026
7844ecc
update tests
Hazel-Datastax Jul 16, 2026
650d8a8
update test
Hazel-Datastax Jul 16, 2026
de68c32
update test
Hazel-Datastax Jul 16, 2026
483c2c2
update S3MockTestResource
Hazel-Datastax Jul 16, 2026
6490544
update BillingS3ExportIntegrationTest
Hazel-Datastax Jul 16, 2026
9ebff99
update path prefix
Hazel-Datastax Jul 20, 2026
5f61a28
update path prefix test
Hazel-Datastax Jul 20, 2026
dc3c496
update path prefix test
Hazel-Datastax Jul 20, 2026
ab07823
empty commit
Hazel-Datastax Jul 22, 2026
60b02c8
empty commit
Hazel-Datastax Jul 22, 2026
72de98c
empty commit
Hazel-Datastax Jul 22, 2026
7bccc50
empty commit
Hazel-Datastax Jul 22, 2026
a917c32
WIP
amorton Aug 4, 2026
22fa931
WIP
amorton Aug 11, 2026
0fcd64f
WIP
amorton Aug 17, 2026
b1a3d2f
WIP
amorton Aug 19, 2026
b167eb4
WIP - BatchedLogBuffer & BatchedLogBufferTest done
amorton Sep 1, 2026
6bef90e
changes from review
amorton Sep 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,11 @@
<groupId>software.amazon.awssdk</groupId>
<artifactId>sts</artifactId>
</dependency>
<!-- S3 export of billing.events log lines. -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
</dependency>
<dependency>
<groupId>com.datastax.oss</groupId>
<artifactId>java-driver-core</artifactId>
Expand Down Expand Up @@ -300,6 +305,13 @@
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<!-- S3Mock for the billing S3 export integration test. -->
<dependency>
<groupId>com.adobe.testing</groupId>
<artifactId>s3mock-testcontainers</artifactId>
<version>5.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.docker-java</groupId>
<artifactId>docker-java-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import io.stargate.sgv2.jsonapi.config.feature.ApiFeatures;
import io.stargate.sgv2.jsonapi.config.feature.FeaturesConfig;
import io.stargate.sgv2.jsonapi.logging.LoggingMDCContext;
import io.stargate.sgv2.jsonapi.service.provider.Billing;
import io.stargate.sgv2.jsonapi.service.billing.Billing;
import io.stargate.sgv2.jsonapi.service.schema.SchemaRegistry;
import io.vertx.ext.web.RoutingContext;
import jakarta.enterprise.context.RequestScoped;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import io.smallrye.config.ConfigMapping;
import io.smallrye.config.WithDefault;
import io.stargate.sgv2.jsonapi.service.provider.BillingEventType;
import io.stargate.sgv2.jsonapi.service.billing.BillingEventType;
import java.util.List;
import java.util.Optional;
import java.util.Set;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package io.stargate.sgv2.jsonapi.config;

import io.smallrye.config.ConfigMapping;
import io.smallrye.config.WithDefault;
import java.time.Duration;
import java.util.Optional;

/** Configuration for the billing S3 export (see BillingS3HandlerInstaller). */
@ConfigMapping(prefix = "stargate.jsonapi.billing.s3")
public interface BillingS3ExportConfig {

/** when false the export handler is never installed. */
@WithDefault("false")
boolean enabled();

/** S3 bucket region */
@WithDefault("us-east-2")
String region();

/** S3 bucket name */
@WithDefault("serverless-usage-dev")
String bucket();

/**
* Only for non-AWS S3 endpoints (e.g. S3Mock in tests). TODO: XXX EXPLAIN WHAT THIS SHOULD SET
* SET TO
*/
Optional<String> endpointOverride();

/** */
@WithDefault("2048")
int maxEventsPerBatch();

/**
* Max bytes to include in a batch, NOTE: if a single event is bigger than this it will be sent in
* a batch still. 2097152 == 2 MB
*/
@WithDefault("2097152")
long maxBytesPerBatch();

/** Age flush period: buffered events are shipped at least this often */
@WithDefault("PT30S")
Duration maxAge();

/** Bound on buffered events; beyond it new lines are dropped. */
@WithDefault("10000")
int queueCapacity();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.stargate.sgv2.jsonapi.metrics;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.stargate.sgv2.jsonapi.service.billing.BatchedLogBuffer;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
* Metrics for the billing buffer in {@link
* io.stargate.sgv2.jsonapi.service.billing.BatchedLogBuffer}
*/
public final class BatchedLogBufferMetrics extends MetricsBase {

private final AtomicBoolean bufferRegister = new AtomicBoolean(false);

private final Counter offered;
private final Counter dropped;

/** */
public BatchedLogBufferMetrics(MeterRegistry meterRegistry, String prefix) {
super(meterRegistry, prefix);

this.offered = newCounter("buffer.offered");
this.dropped = newCounter("buffer.dropped");

// Note: not recording events dropped at shutdown as a metric because when shutting down
// the metrics still need to be scrapped to be useful. Do it as a log message that is
// persistent.
}

public void registerBuffer(BatchedLogBuffer buffer) {

if (!bufferRegister.compareAndSet(false, true)) {
throw new IllegalStateException("registerBuffer() already called");
}

newTimeGauge(
"buffer.head_age_ms", () -> buffer.headEntryAge().toMillis(), TimeUnit.MILLISECONDS);
newGauge("buffer.size", buffer::size);
newGauge("buffer.remaining_capacity", buffer::remainingCapacity);
newGauge("buffer.bytes", buffer::queuedBytes);
}

public void offered() {
offered.increment();
}

public void dropped() {
dropped.increment();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package io.stargate.sgv2.jsonapi.metrics;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import io.stargate.sgv2.jsonapi.service.billing.BatchedLogBuffer;
import java.util.Objects;

/** Metrics for billing events, mostly around what is sent to S3 */
public final class BatchedLogUploaderMetrics extends MetricsBase {

public final Counter uploadedBatches;
public final Counter uploadedBytes;
public final Timer uploadedHeadAgeMs;
public final Counter uploadedEvents;

public final Counter failedBatches;
public final Counter failedEvents;

/** */
public BatchedLogUploaderMetrics(MeterRegistry meterRegistry, String prefix) {
super(meterRegistry, prefix);

this.uploadedBatches = newCounter("s3.uploaded.batches");
this.uploadedBytes = newCounter("s3.uploaded.bytes");
this.uploadedEvents = newCounter("s3.uploaded.events");
this.uploadedHeadAgeMs = newTimer("s3.uploaded.oldest_event");

this.failedBatches = newCounter("s3.failed.batches");
this.failedEvents = newCounter("s3.failed.events");
}

public void recordBatchDelivered(BatchedLogBuffer.Batch batch) {

Objects.requireNonNull(batch, "batch must not be null");
uploadedBatches.increment();
uploadedBytes.increment(batch.bytes());
uploadedHeadAgeMs.record(batch.oldestEventAtDuration());
uploadedEvents.increment(batch.size());
}

public void recordBatchFailed(BatchedLogBuffer.Batch batch) {

Objects.requireNonNull(batch, "batch must not be null");
failedBatches.increment();
failedEvents.increment(batch.size());
}
}
61 changes: 61 additions & 0 deletions src/main/java/io/stargate/sgv2/jsonapi/metrics/MetricsBase.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package io.stargate.sgv2.jsonapi.metrics;

import io.micrometer.core.instrument.*;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/** Common base for classes that create metric measures */
public abstract class MetricsBase {

protected final MeterRegistry meterRegistry;
protected final String prefix;

protected MetricsBase(MeterRegistry meterRegistry, String prefix) {

this.meterRegistry = Objects.requireNonNull(meterRegistry, "meterRegistry must not be null");
this.prefix = Objects.requireNonNull(prefix, "prefix must not be null");
if (prefix.isBlank()) {
throw new IllegalArgumentException("prefix must not be blank");
}
}

protected String validateName(String name) {
Objects.requireNonNull(name, "name must not be null");
if (name.isBlank()) {
throw new IllegalArgumentException("name must not be blank");
}

return name.charAt(0) == '.' ? name : "." + name;
}

protected String fullName(String name) {
return prefix + validateName(name);
}

protected Counter newCounter(String name) {
return meterRegistry.counter(fullName(name));
}

protected Gauge newGauge(String name, Supplier<Number> func) {
// no null checks in the builder below
Objects.requireNonNull(func, "func must not be null");

return Gauge.builder(fullName(name), func)
.strongReference(true) // is set in builder() above just being explicit
.register(meterRegistry);
}

protected Timer newTimer(String name) {
return newTimer(name, 0.5, 0.95, 0.99);
}

protected Timer newTimer(String name, double... percentiles) {
return Timer.builder(fullName(name)).publishPercentiles(percentiles).register(meterRegistry);
}

protected TimeGauge newTimeGauge(String name, Supplier<Number> func, TimeUnit unit) {

return TimeGauge.builder(fullName(name), func, unit).register(meterRegistry);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.stargate.sgv2.jsonapi.service.billing;

import io.smallrye.mutiny.Uni;

/** A function that uploads a batch of log records, normally to S3. */
@FunctionalInterface
public interface AsyncBatchedLogUploader extends AutoCloseable {

/**
* Called to upload the batch of records.
*
* @param batch The batch of log records to upload
* @return A Uni of the result of the operation
*/
Uni<UploadResult> upload(BatchedLogBuffer.Batch batch);

@Override
default void close() {}

/**
* Result of the upload call.
*
* @param success <code>true</code> if the operation succeeded, false otherwise.
* @param throwable The throwable associated with an error state.
* @param batch The batch that was uploaded, or attempted to be uploaded.
*/
record UploadResult(boolean success, Throwable throwable, BatchedLogBuffer.Batch batch) {}
}
Loading
Loading