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 13, 2024
1 parent e09e6a0 commit 8c09383
Show file tree
Hide file tree
Showing 26 changed files with 3,120 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,95 @@
/*******************************************************************************
* 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() {
Request request = new Request.Builder()
.url(REMOTE)
.addHeader("Content-Type", "application/json")
.build();
try (Response response = client.newCall(request).execute()) {
if (response.body() != null) {
Files.copy(response.body().byteStream(), LOCAL, StandardCopyOption.REPLACE_EXISTING);
}
return getLocal();
} catch (Throwable e) {
LOGGER.warn("Could not download remote limits configurations.", e);
return null;
}
}

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

public FileTime getLocalLastModified() {
try {
if (!Files.exists(LOCAL)) {
return null;
}
return Files.getLastModifiedTime(LOCAL);
} catch (Throwable e) {
return null;
}
}

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

public String getEmbedded() {
return toString(Configurations.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,146 @@
/*******************************************************************************
* 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.application.ApplicationManager;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.Service;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.util.xmlb.XmlSerializerUtil;
import com.redhat.devtools.intellij.telemetry.core.service.Event;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.Map;

@Service
@State(
name = " com.redhat.devtools.intellij.telemetry.core.configuration.limits.EventState",
storages = @Storage(value = "eventState.xml")
)
public final class EventState implements PersistentStateComponent<EventState> {

public static EventState getInstance() {
return ApplicationManager.getApplication().getService(EventState.class);
}

private static final String COUNT_VALUES_SEPARATOR = ",";

public final Map<String, String> properties = new HashMap<>();

@Override
public EventState getState() {
return this;
}

@Override
public void loadState(@NotNull EventState state) {
XmlSerializerUtil.copyBean(state, this);
}

@Override
public void noStateLoaded() {
PersistentStateComponent.super.noStateLoaded();
}

@Override
public void initializeComponent() {
PersistentStateComponent.super.initializeComponent();
}

Count getCount(@NotNull Event event) {
String key = toKey(event);
String countString = properties.get(key);
return toCount(countString);
}

private Count toCount(String string) {
if (StringUtils.isEmpty(string)) {
return null;
}
String[] split = string.split(COUNT_VALUES_SEPARATOR);
LocalDateTime lastOccurrence = toLastOccurrence(split[1]);
int total = toTotal(split[0]);
return new Count(lastOccurrence, total);
}

private LocalDateTime toLastOccurrence(String value) {
try {
long epochSeconds = Long.parseLong(value);
return LocalDateTime.ofEpochSecond(epochSeconds, 0, getOffset());
} catch (NumberFormatException e) {
return null;
}
}

private int toTotal(String value) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
return 0;
}
}

public void setCount(Event event, Count count) {
if (event == null) {
return;
}
String key = toKey(event);
count = count.addOccurrence();
properties.put(key, toString(count));
}

private String toKey(@NotNull Event event) {
return event.getName().replaceAll("[^a-zA-Z0-9]", "_");
}

String toString(Count count) {
;
long epochSecond = count.lastOccurrence.toEpochSecond(getOffset());
return epochSecond + COUNT_VALUES_SEPARATOR + count.total;
}

private ZoneOffset getOffset() {
return ZoneId.systemDefault().getRules().getOffset(Instant.now());
}

static class Count {
private final LocalDateTime lastOccurrence;
private final int total;

Count() {
this(LocalDateTime.now(), 0);
}

Count(LocalDateTime lastOccurrence, int total) {
this.lastOccurrence = lastOccurrence;
this.total = total;
}

public LocalDateTime lastOccurrence() {
return lastOccurrence;
}

public int total() {
return total;
}

public Count addOccurrence() {
return new Count(LocalDateTime.now(), total + 1);
}
}
}
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;
}
}
}
Loading

0 comments on commit 8c09383

Please sign in to comment.