Skip to content

Commit

Permalink
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 May 29, 2024
1 parent e09e6a0 commit 870c518
Show file tree
Hide file tree
Showing 17 changed files with 2,210 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,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 isIncludedRatio(float hashLocation);

boolean isExcludedRatio(float hashLocation);

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) {
Object value = event.getProperties().get(name);
return value instanceof String
&& glob.matches((String) value);
}

@Override
public boolean isIncludedRatio(float hashLocation) {
return true;
}

@Override
public boolean isExcludedRatio(float hashLocation) {
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 isIncludedRatio(float hashLocation) {
return hashLocation <= ratio;
}

@Override
public boolean isExcludedRatio(float hashLocation) {
return hashLocation > 1 - ratio;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*******************************************************************************
* 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.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

public class MessageLimits {

private static MessageLimits INSTANCE = null;

public static MessageLimits getInstance() {
if (INSTANCE == null) {
INSTANCE = Factory.create();
}
return INSTANCE;
}

static class Factory {

static MessageLimits create() {
return create(readConfig());
}

static MessageLimits create(String config) {
List<PluginLimits> limits = createLimits(config);
return new MessageLimits(limits);
}

static String readConfig() {
InputStream inputStream = MessageLimits.class.getResourceAsStream("/telemetry-config.json");
if (inputStream == null) {
return null;
}
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
return reader.lines().collect(Collectors.joining());
}


private static List<PluginLimits> createLimits(String config) {
if (config == null) {
return Collections.emptyList();
}
try {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(List.class, new MessageLimitsDeserialization());
mapper.registerModule(module);
return mapper.readValue(config, List.class);
} catch (IOException e) {
return new ArrayList<>();
}
}
}

private final List<PluginLimits> limits;

MessageLimits(List<PluginLimits> limits) {
this.limits = limits;
}

List<PluginLimits> get() {
return limits;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*******************************************************************************
* 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.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.intellij.openapi.util.text.StringUtil;
import com.redhat.devtools.intellij.telemetry.core.configuration.limits.Filter.EventNameFilter;
import com.redhat.devtools.intellij.telemetry.core.configuration.limits.Filter.EventPropertyFilter;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

class MessageLimitsDeserialization extends StdDeserializer<List<PluginLimits>> {

public static final String FIELDNAME_ENABLED = "enabled";
public static final String FIELDNAME_REFRESH = "refresh";
public static final String FIELDNAME_RATIO = "ratio";
public static final String FIELDNAME_INCLUDES = "includes";
public static final String FIELDNAME_EXCLUDES = "excludes";
public static final String FIELDNAME_PROPERTY = "property";
public static final String FIELDNAME_VALUE = "value";
public static final String FIELDNAME_DAILY_LIMIT = "dailyLimit";
public static final String FIELDNAME_NAME = "name";

MessageLimitsDeserialization() {
this(null);
}

MessageLimitsDeserialization(Class<?> clazz) {
super(clazz);
}

@Override
public List<PluginLimits> deserialize(JsonParser parser, DeserializationContext ctx) throws IOException {
JsonNode node = parser.getCodec().readTree(parser);
Spliterator<Map.Entry<String, JsonNode>> spliterator = Spliterators.spliteratorUnknownSize(node.fields(), Spliterator.IMMUTABLE);
return StreamSupport.stream(spliterator, false)
.map(this::createMessageLimit)
.collect(Collectors.toList());
}

@NotNull
private PluginLimits createMessageLimit(Map.Entry<String, JsonNode> entry) {
String pattern = entry.getKey();
JsonNode properties = entry.getValue();
Enabled enabled = getEnabled(properties.get(FIELDNAME_ENABLED));
int refresh = getRefresh(properties.get(FIELDNAME_REFRESH));
float ratio = getRatio(properties.get(FIELDNAME_RATIO));
List<Filter> includes = getFilters(properties.get(FIELDNAME_INCLUDES));
List<Filter> excludes = getFilters(properties.get(FIELDNAME_EXCLUDES));

return new PluginLimits(pattern, enabled, refresh, ratio, includes, excludes);
}

private Enabled getEnabled(JsonNode node) {
String value = node != null ? node.asText() : null;
return Enabled.safeValueOf(value);
}

private int getRefresh(JsonNode node) {
int numeric = -1;
if (node != null) {
String refresh = getNumericPortion(node.asText().toCharArray());
if (!StringUtil.isEmptyOrSpaces(refresh)) {
try {
numeric = Integer.parseInt(refresh);
} catch (NumberFormatException e) {
// swallow
}
}
}
return numeric;
}

private List<Filter> getFilters(JsonNode node) {
if (node == null
|| !node.isArray()) {
return Collections.emptyList();
}
Spliterator<JsonNode> spliterator = Spliterators.spliteratorUnknownSize(node.elements(), Spliterator.IMMUTABLE);
return StreamSupport.stream(spliterator, false)
.map(this::createMessageLimitFilter)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}

private Filter createMessageLimitFilter(JsonNode node) {
if (node.has(FIELDNAME_NAME)) {
return createEventNameFilter(node);
} else if (node.has(FIELDNAME_PROPERTY)
&& node.has(FIELDNAME_VALUE)) {
return createEventPropertyFilter(node);
} else {
return null;
}
}

private EventNameFilter createEventNameFilter(JsonNode node) {
String name = getStringValue(FIELDNAME_NAME, node);
float ratio = getRatio(node.get(FIELDNAME_RATIO));
String dailyLimit = getStringValue(FIELDNAME_DAILY_LIMIT, node);
return new EventNameFilter(name, ratio, dailyLimit);
}

private EventPropertyFilter createEventPropertyFilter(JsonNode node) {
String property = getStringValue(FIELDNAME_PROPERTY, node);
String value = getStringValue(FIELDNAME_VALUE, node);
return new EventPropertyFilter(property, value);
}

private static float getRatio(JsonNode node) {
float numeric = 1f;
if (node != null) {
try {
numeric = Float.parseFloat(node.asText());
} catch (NumberFormatException e) {
// swallow
}
}
return numeric;
}

private static String getStringValue(String name, JsonNode node) {
if (node == null
|| node.get(name) == null) {
return null;
}
return node.get(name).asText();
}

private static String getNumericPortion(char[] characters) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < characters.length && Character.isDigit(characters[i]); i++) {
builder.append(characters[i]);
}
return builder.toString();
}
}
Loading

0 comments on commit 870c518

Please sign in to comment.