Skip to content

Commit

Permalink
feat: allow message limits (#82)
Browse files Browse the repository at this point in the history
Signed-off-by: Andre Dietisheim <[email protected]>
  • Loading branch information
adietish committed Jun 10, 2024
1 parent e09e6a0 commit 01aa5ce
Show file tree
Hide file tree
Showing 25 changed files with 3,040 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class TelemetryConfiguration extends CompositeConfiguration {
private static final SaveableFileConfiguration FILE = new SaveableFileConfiguration(
Directories.RED_HAT.resolve("com.redhat.devtools.intellij.telemetry"));

private static TelemetryConfiguration INSTANCE = new TelemetryConfiguration();
private static final TelemetryConfiguration INSTANCE = new TelemetryConfiguration();

public static TelemetryConfiguration getInstance() {
return INSTANCE;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*******************************************************************************
* Copyright (c) 2024 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package com.redhat.devtools.intellij.telemetry.core.configuration.limits;

import com.intellij.openapi.diagnostic.Logger;
import com.redhat.devtools.intellij.telemetry.core.util.Directories;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

class Configurations {

private static final Logger LOGGER = Logger.getInstance(Configurations.class);
static final Path LOCAL = Directories.RED_HAT.resolve("telemetry-config.json");
static final String EMBEDDED = "/telemetry-config.json";
static final String REMOTE = "https://raw.githubusercontent.com/adietish/intellij-redhat-telemetry/issue-82/src/main/resources/telemetry-config.json";

private final OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.writeTimeout(5, TimeUnit.SECONDS)
.build();

public String getRemote() {
return getRemote(
in -> Files.copy(in, LOCAL, StandardCopyOption.REPLACE_EXISTING),
client,
this::getLocal);
}

/* for testing purposes */
String getRemote(ThrowingInputStreamConsumer fileWriter, OkHttpClient client, ThrowingSupplier<String, Throwable> fileReader) {
Request request = new Request.Builder()
.url(REMOTE)
.addHeader("Content-Type", "application/json")
.build();
try (Response response = client.newCall(request).execute()) {
if (response.body() != null) {
fileWriter.accept(response.body().byteStream());
}
return fileReader.get();
} catch (Throwable e) {
LOGGER.warn("Could not download remote limits configurations.", e);
return null;
}
}

/* for testing purposes */
interface ThrowingInputStreamConsumer {
void accept(InputStream in) throws IOException;
}

public boolean localExists() {
return Files.exists(LOCAL);
}

public FileTime getLocalLastModified() {
return getLocalLastModified(
() -> Files.exists(LOCAL),
() -> Files.readAttributes(LOCAL, BasicFileAttributes.class));
}

/* for testing purposes */
FileTime getLocalLastModified(ThrowingSupplier<Boolean, Throwable> exists, ThrowingSupplier<BasicFileAttributes, Throwable> attributesSupplier) {
try {
if (!exists.get()) {
return null;
}
BasicFileAttributes attributes = attributesSupplier.get();
return attributes.lastModifiedTime();
} catch (Throwable e) {
return null;
}
}

interface ThrowingSupplier<T, E extends Throwable> {
T get() throws E;
}

public String getLocal() {
try {
return toString(Files.newInputStream(LOCAL));
} catch (IOException e) {
return null;
}
}

public String getEmbedded() {
return toString(MessageLimits.class.getResourceAsStream(EMBEDDED));
}

private String toString(InputStream in) {
if (in == null) {
return null;
}
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
return reader.lines().collect(Collectors.joining());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*******************************************************************************
* Copyright (c) 2024 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package com.redhat.devtools.intellij.telemetry.core.configuration.limits;

import com.intellij.openapi.util.text.StringUtil;

import java.util.Arrays;

public enum Enabled {
ALL("all"),
ERROR("error"),
CRASH("crash"),
OFF("off");

private final String value;

Enabled(String value) {
this.value = value;
}

private boolean hasValue(String value) {
if (StringUtil.isEmptyOrSpaces(value)) {
return this.value == null;
}
return value.equals(this.value);
}

public static Enabled safeValueOf(String value) {
return Arrays.stream(values())
.filter(instance -> instance.hasValue(value))
.findAny()
.orElse(ALL);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*******************************************************************************
* Copyright (c) 2024 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package com.redhat.devtools.intellij.telemetry.core.configuration.limits;

import com.redhat.devtools.intellij.telemetry.core.service.Event;
import com.redhat.devtools.intellij.telemetry.core.util.BasicGlobPattern;

public interface Filter {

boolean isMatching(Event event);

boolean isIncludedByRatio(float percentile);

boolean isExcludedByRatio(float percentile);

class EventPropertyFilter implements Filter {
private final String name;
private final BasicGlobPattern glob;

EventPropertyFilter(String name, String valueGlob) {
this.name = name;
this.glob = BasicGlobPattern.compile(valueGlob);
}

@Override
public boolean isMatching(Event event) {
String value = event.getProperties().get(name);
return glob.matches(value);
}

@Override
public boolean isIncludedByRatio(float percentile) {
return true;
}

@Override
public boolean isExcludedByRatio(float percentile) {
return false;
}

}

class EventNameFilter implements Filter {
private final BasicGlobPattern name;
private final float ratio;
private final String dailyLimit;

EventNameFilter(String name, float ratio, String dailyLimit) {
this.name = BasicGlobPattern.compile(name);
this.ratio = ratio;
this.dailyLimit = dailyLimit;
}

public float getRatio() {
return ratio;
}

public String getDailyLimit() {
return dailyLimit;
}

@Override
public boolean isMatching(Event event) {
return name.matches(event.getName());
}

@Override
public boolean isIncludedByRatio(float percentile) {
return ratio != 0
&& percentile <= ratio;
}

@Override
public boolean isExcludedByRatio(float percentile) {
return 1 - ratio < percentile;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*******************************************************************************
* Copyright (c) 2024 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package com.redhat.devtools.intellij.telemetry.core.configuration.limits;

import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.nio.file.attribute.FileTime;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Collections;
import java.util.List;

public class MessageLimits {

private static final Duration DEFAULT_REFRESH_PERIOD = Duration.ofHours(6);
private static MessageLimits INSTANCE = null;
private final PluginLimitsFactory factory;
private final Configurations configuration;
private List<PluginLimits> limits;

static MessageLimits getInstance() {
if (INSTANCE == null) {
INSTANCE = new MessageLimits(PluginLimitsDeserialization::create, new Configurations());
}
return INSTANCE;
}

interface PluginLimitsFactory {
List<PluginLimits> create(String json) throws IOException;
}

MessageLimits(PluginLimitsFactory factory, Configurations configuration) {
this.factory = factory;
this.configuration = configuration;
}

/** for testing purposes **/
MessageLimits(List<PluginLimits> limits, PluginLimitsFactory factory, Configurations configuration) {
this(factory,configuration);
this.limits = limits;
}

List<PluginLimits> get() {
PluginLimits defaultLimits = getDefaultLimits(limits);
Duration refreshAfter = getRefreshAfter(defaultLimits);
FileTime lastModified = configuration.getLocalLastModified();
if (needsRefresh(refreshAfter, lastModified)) {
this.limits = createLimits(configuration.getRemote(), factory);
}
return limits;
}

private boolean needsRefresh(Duration refreshAfter, FileTime modified) {
if (modified == null) {
return true;
}
LocalDateTime modificationLocalTime = LocalDateTime.ofInstant(modified.toInstant(), ZoneId.systemDefault());
LocalDateTime refreshAt = modificationLocalTime.plus(refreshAfter);
return refreshAt.isBefore(LocalDateTime.now());
}

@Nullable
private PluginLimits getDefaultLimits(List<PluginLimits> limits) {
if (limits == null) {
return null;
}
return limits.stream()
.filter(PluginLimits::isDefault)
.findAny()
.orElse(null);
}

private Duration getRefreshAfter(PluginLimits limits) {
if (limits == null
|| limits.getRefresh() == -1) {
return DEFAULT_REFRESH_PERIOD;
}
return Duration.ofHours(limits.getRefresh());
}

private List<PluginLimits> createLimits(String config, PluginLimitsFactory factory) {
try {
if (StringUtil.isEmptyOrSpaces(config)) {
return Collections.emptyList();
}
return factory.create(config);
} catch (IOException e) {
return Collections.emptyList();
}
}
}
Loading

0 comments on commit 01aa5ce

Please sign in to comment.