Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
143 changes: 130 additions & 13 deletions config/src/main/java/dev/faststats/config/SimpleConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
import org.jspecify.annotations.Nullable;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
Expand All @@ -21,15 +21,7 @@
import static java.nio.charset.StandardCharsets.UTF_8;

@ApiStatus.Internal
public record SimpleConfig(
UUID serverId,
boolean enabled,
boolean additionalMetrics,
boolean debug,
boolean submitMetrics,
boolean errorTracking,
boolean firstRun
) implements Config {
public final class SimpleConfig implements Config {

Check warning on line 24 in config/src/main/java/dev/faststats/config/SimpleConfig.java

View workflow job for this annotation

GitHub Actions / build

no comment
private static final int CONFIG_VERSION = 2;

private static final String COMMENT = """
Expand Down Expand Up @@ -58,7 +50,86 @@
Learn more at: https://faststats.dev/info

Since this is your first start with FastStats, submission will not start
until you restart the server to allow you to opt out if you prefer.""";
until after a restart, to allow you to opt out if you prefer.""";

private final Path file;
private final UUID serverId;
private final boolean debug;
private final boolean firstRun;
private volatile boolean additionalMetrics;
private volatile boolean enabled;
private volatile boolean errorTracking;
private volatile boolean submitMetrics;

public SimpleConfig(

Check warning on line 64 in config/src/main/java/dev/faststats/config/SimpleConfig.java

View workflow job for this annotation

GitHub Actions / build

no comment
final Path file,
final UUID serverId,
final boolean enabled,
final boolean additionalMetrics,
final boolean debug,
final boolean submitMetrics,
final boolean errorTracking,
final boolean firstRun
) {
this.file = file;
this.serverId = serverId;
this.enabled = enabled;
this.additionalMetrics = additionalMetrics;
this.debug = debug;
this.submitMetrics = submitMetrics;
this.errorTracking = errorTracking;
this.firstRun = firstRun;
}

@Override
public UUID serverId() {
return serverId;
}

@Override
public boolean enabled() {
return enabled;
}

public void enabled(final boolean enabled) {
this.enabled = enabled;
}

@Override
public boolean additionalMetrics() {
return additionalMetrics;
}

public void additionalMetrics(final boolean additionalMetrics) {
this.additionalMetrics = additionalMetrics;
}

@Override
public boolean debug() {
return debug;
}

@Override
public boolean submitMetrics() {
return submitMetrics;
}

public void submitMetrics(final boolean submitMetrics) {
this.submitMetrics = submitMetrics;
}

@Override
public boolean errorTracking() {
return errorTracking;
}

public void errorTracking(final boolean errorTracking) {
this.errorTracking = errorTracking;
}

public boolean firstRun() {
return firstRun;
}

@Contract(mutates = "io")
public static SimpleConfig read(final Path file, final LoggerFactory factory) throws RuntimeException {
Expand Down Expand Up @@ -91,8 +162,7 @@
if (configVersion != null && configVersion < CONFIG_VERSION)
logger.info("Updating config version from %s to %s", configVersion, CONFIG_VERSION);
Files.createDirectories(file.getParent());
try (final var out = Files.newOutputStream(file);
final var writer = new OutputStreamWriter(out, UTF_8)) {
try (final var writer = Files.newBufferedWriter(file, UTF_8)) {
final var store = new Properties();

store.setProperty("enabled", Boolean.toString(enabled));
Expand All @@ -112,6 +182,7 @@
}

return new SimpleConfig(
file,
serverId,
enabled && enabledFlag,
enabled && enabledFlag && additionalMetrics,
Expand All @@ -122,6 +193,23 @@
);
}

@Contract(mutates = "io")
public void persist() throws RuntimeException {
final var properties = readOrEmpty(file);
if (properties == null) throw new IllegalStateException("Metrics config has not been initialized");

properties.setProperty("enabled", Boolean.toString(enabled()));
properties.setProperty("submitAdditionalMetrics", Boolean.toString(additionalMetrics()));
properties.setProperty("submitErrors", Boolean.toString(errorTracking()));
properties.setProperty("submitMetrics", Boolean.toString(submitMetrics()));

try (final var writer = Files.newBufferedWriter(file, UTF_8)) {
properties.store(writer, COMMENT);
} catch (final IOException e) {
throw new RuntimeException("Failed to save metrics config", e);
}
}

// fixme: this code sucks ass
@Contract(value = "_, _, _, !null, _, _-> !null")
private static <T> @Nullable T parse(
Expand Down Expand Up @@ -180,4 +268,33 @@
}
return true;
}

@Override
public boolean equals(final Object object) {
if (this == object) return true;
if (!(object instanceof final SimpleConfig that)) return false;
return enabled == that.enabled
&& additionalMetrics == that.additionalMetrics
&& debug == that.debug
&& submitMetrics == that.submitMetrics
&& errorTracking == that.errorTracking
&& firstRun == that.firstRun
&& Objects.equals(serverId, that.serverId);
}

@Override
public int hashCode() {
return Objects.hash(serverId, enabled, additionalMetrics, debug, submitMetrics, errorTracking, firstRun);
}

@Override
public String toString() {
return "SimpleConfig[serverId=" + serverId
+ ", enabled=" + enabled
+ ", additionalMetrics=" + additionalMetrics
+ ", debug=" + debug
+ ", submitMetrics=" + submitMetrics
+ ", errorTracking=" + errorTracking
+ ", firstRun=" + firstRun + ']';
}
}
22 changes: 22 additions & 0 deletions core/src/main/java/dev/faststats/FastStatsRegistration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package dev.faststats;

import java.util.Set;

/**
* A non-sensitive description of a live FastStats consumer.
*
* @since 0.29.2
*/
public record FastStatsRegistration(
String projectName,
String sdkName,
String sdkVersion,
boolean metrics,
boolean errorTracking,
boolean featureFlags,
Set<String> additionalMetrics
) {
public FastStatsRegistration {
additionalMetrics = Set.copyOf(additionalMetrics);
}
}
78 changes: 78 additions & 0 deletions core/src/main/java/dev/faststats/FastStatsRegistry.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package dev.faststats;

import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.Nullable;

import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;

/**
* Registry of contexts that opt into live onboarding lifecycle updates.
*
* @since 0.29.2
*/
public final class FastStatsRegistry {
private static final FastStatsRegistry INSTANCE = new FastStatsRegistry();

private final Map<SimpleContext, Entry> contexts = new IdentityHashMap<>();
private @Nullable Config config;

private FastStatsRegistry() {
}

@Contract(pure = true)
public static FastStatsRegistry instance() {
return INSTANCE;
}

/** Registers a context and the shared mutable configuration used by its platform. */
public synchronized void register(final SimpleContext context) {
if (this.config == null) this.config = context.getConfig();
contexts.put(context, new Entry(context, context.registration()));
}

/** Removes a context when its platform lifecycle ends. */
public synchronized void unregister(final SimpleContext context) {
contexts.remove(context);
if (contexts.isEmpty()) config = null;
}

/** Returns a snapshot of every registered FastStats consumer. */
public synchronized List<FastStatsRegistration> registrations() {
return contexts.values().stream().map(Entry::registration).toList();
}

/** Enables submission for all contexts using the shared configuration. */
public void start() {
final var config = config();
for (final var entry : entries()) {
entry.context().startSubmissions(config);
}
}

/** Disables submission for all contexts using the shared configuration. */
public void shutdown() {
final var config = config();
for (final var entry : entries()) {
entry.context().stopSubmissions(config);
}
}

private synchronized List<Entry> entries() {
return new ArrayList<>(contexts.values());
}

public synchronized Config config() {
final var config = this.config;
if (config == null) throw new IllegalStateException("No FastStats contexts are registered");
return config;
}

private record Entry(
SimpleContext context,
FastStatsRegistration registration
) {
}
}
Loading
Loading