Skip to content

Commit

Permalink
#1806 provide configuration for operator defined custom metrics
Browse files Browse the repository at this point in the history
* add new as singleton started Actor OperatorMetricsProviderActor responsible for gathering the metrics and reporting via DittoMetrics gauges
* provide configuration options for scrape-interval, namespaces to count and and RQL filter to apply, including optional tags to add to gauges
* provide example in configuration

Signed-off-by: Thomas Jäckle <thomas.jaeckle@beyonnex.io>
  • Loading branch information
thjaeckle committed Nov 15, 2023
1 parent be74bea commit f996b8a
Show file tree
Hide file tree
Showing 17 changed files with 868 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,32 @@
package org.eclipse.ditto.thingsearch.api.commands.sudo;


import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

import org.eclipse.ditto.json.JsonFactory;
import org.eclipse.ditto.json.JsonField;
import org.eclipse.ditto.json.JsonFieldDefinition;
import org.eclipse.ditto.json.JsonObject;
import org.eclipse.ditto.json.JsonObjectBuilder;
import org.eclipse.ditto.base.model.headers.DittoHeaders;
import org.eclipse.ditto.base.model.json.FieldType;
import org.eclipse.ditto.base.model.json.JsonParsableCommand;
import org.eclipse.ditto.base.model.json.JsonSchemaVersion;
import org.eclipse.ditto.base.model.signals.commands.AbstractCommand;
import org.eclipse.ditto.base.model.signals.commands.CommandJsonDeserializer;
import org.eclipse.ditto.json.JsonArray;
import org.eclipse.ditto.json.JsonCollectors;
import org.eclipse.ditto.json.JsonFactory;
import org.eclipse.ditto.json.JsonField;
import org.eclipse.ditto.json.JsonFieldDefinition;
import org.eclipse.ditto.json.JsonObject;
import org.eclipse.ditto.json.JsonObjectBuilder;
import org.eclipse.ditto.json.JsonValue;
import org.eclipse.ditto.utils.jsr305.annotations.AllValuesAreNonnullByDefault;


Expand All @@ -57,12 +65,25 @@ public final class SudoCountThings extends AbstractCommand<SudoCountThings>
JsonFactory.newStringFieldDefinition("filter", FieldType.REGULAR,
JsonSchemaVersion.V_2);

static final JsonFieldDefinition<JsonArray> JSON_NAMESPACES =
JsonFactory.newJsonArrayFieldDefinition("namespaces", FieldType.REGULAR,
JsonSchemaVersion.V_2);

@Nullable
private final String filter;

private SudoCountThings(final DittoHeaders dittoHeaders, @Nullable final String filter) {
@Nullable
private final Set<String> namespaces;

private SudoCountThings(final DittoHeaders dittoHeaders, @Nullable final String filter,
@Nullable final Collection<String> namespaces) {
super(TYPE, dittoHeaders);
this.filter = filter;
if (namespaces != null) {
this.namespaces = Collections.unmodifiableSet(new HashSet<>(namespaces));
} else {
this.namespaces = null;
}
}

/**
Expand All @@ -74,7 +95,20 @@ private SudoCountThings(final DittoHeaders dittoHeaders, @Nullable final String
* @throws NullPointerException if {@code dittoHeaders} is {@code null}.
*/
public static SudoCountThings of(@Nullable final String filter, final DittoHeaders dittoHeaders) {
return new SudoCountThings(dittoHeaders, filter);
return new SudoCountThings(dittoHeaders, filter, null);
}

/**
* Returns a new instance of {@code SudoCountThings}.
*
* @param filter the optional filter string
* @param dittoHeaders the headers of the command.
* @return a new command for counting Things.
* @throws NullPointerException if {@code dittoHeaders} is {@code null}.
*/
public static SudoCountThings of(@Nullable final String filter, @Nullable final Collection<String> namespaces,
final DittoHeaders dittoHeaders) {
return new SudoCountThings(dittoHeaders, filter, namespaces);
}

/**
Expand All @@ -85,7 +119,7 @@ public static SudoCountThings of(@Nullable final String filter, final DittoHeade
* @throws NullPointerException if any argument is {@code null}.
*/
public static SudoCountThings of(final DittoHeaders dittoHeaders) {
return new SudoCountThings(dittoHeaders, null);
return new SudoCountThings(dittoHeaders, null, null);
}

/**
Expand Down Expand Up @@ -119,7 +153,14 @@ public static SudoCountThings fromJson(final JsonObject jsonObject, final DittoH
return new CommandJsonDeserializer<SudoCountThings>(TYPE, jsonObject).deserialize(() -> {
final String extractedFilter = jsonObject.getValue(JSON_FILTER).orElse(null);

return new SudoCountThings(dittoHeaders, extractedFilter);
final Set<String> extractedNamespaces = jsonObject.getValue(JSON_NAMESPACES)
.map(jsonValues -> jsonValues.stream()
.filter(JsonValue::isString)
.map(JsonValue::asString)
.collect(Collectors.toSet()))
.orElse(null);

return new SudoCountThings(dittoHeaders, extractedFilter, extractedNamespaces);
});
}

Expand All @@ -132,12 +173,24 @@ public Optional<String> getFilter() {
return Optional.ofNullable(filter);
}

/**
* Get the optional set of namespaces.
*
* @return the optional set of namespaces.
*/
public Optional<Set<String>> getNamespaces() {
return Optional.ofNullable(namespaces);
}

@Override
protected void appendPayload(final JsonObjectBuilder jsonObjectBuilder, final JsonSchemaVersion schemaVersion,
final Predicate<JsonField> thePredicate) {

final Predicate<JsonField> predicate = schemaVersion.and(thePredicate);
getFilter().ifPresent(theFilter -> jsonObjectBuilder.set(JSON_FILTER, theFilter, predicate));
getNamespaces().ifPresent(presentOptions -> jsonObjectBuilder.set(JSON_NAMESPACES, presentOptions.stream()
.map(JsonValue::of)
.collect(JsonCollectors.valuesToArray()), predicate));
}

@Override
Expand All @@ -147,7 +200,7 @@ public Category getCategory() {

@Override
public SudoCountThings setDittoHeaders(final DittoHeaders dittoHeaders) {
return of(filter, dittoHeaders);
return of(filter, namespaces, dittoHeaders);
}

@Override
Expand All @@ -159,17 +212,21 @@ public boolean equals(@Nullable final Object o) {
if (!super.equals(o))
return false;
final SudoCountThings that = (SudoCountThings) o;
return Objects.equals(filter, that.filter);
return Objects.equals(filter, that.filter) &&
Objects.equals(namespaces, that.namespaces);
}

@Override
public int hashCode() {
return Objects.hash(super.hashCode(), filter);
return Objects.hash(super.hashCode(), filter, namespaces);
}

@Override
public String toString() {
return getClass().getSimpleName() + "[" + "filter='" + filter + "']";
return getClass().getSimpleName() + "[" +
"filter='" + filter + "'" +
", namespaces=" + namespaces +
"]";
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

import org.eclipse.ditto.base.model.headers.DittoHeaders;
import org.eclipse.ditto.base.model.json.FieldType;
import org.eclipse.ditto.base.model.json.JsonParsableCommand;
import org.eclipse.ditto.base.model.json.JsonSchemaVersion;
import org.eclipse.ditto.base.model.signals.commands.AbstractCommand;
import org.eclipse.ditto.base.model.signals.commands.CommandJsonDeserializer;
import org.eclipse.ditto.json.JsonArray;
import org.eclipse.ditto.json.JsonCollectors;
import org.eclipse.ditto.json.JsonFactory;
Expand All @@ -32,12 +38,6 @@
import org.eclipse.ditto.json.JsonObject;
import org.eclipse.ditto.json.JsonObjectBuilder;
import org.eclipse.ditto.json.JsonValue;
import org.eclipse.ditto.base.model.headers.DittoHeaders;
import org.eclipse.ditto.base.model.json.FieldType;
import org.eclipse.ditto.base.model.json.JsonParsableCommand;
import org.eclipse.ditto.base.model.json.JsonSchemaVersion;
import org.eclipse.ditto.base.model.signals.commands.AbstractCommand;
import org.eclipse.ditto.base.model.signals.commands.CommandJsonDeserializer;
import org.eclipse.ditto.thingsearch.model.signals.commands.ThingSearchCommand;

/**
Expand Down Expand Up @@ -87,7 +87,7 @@ private CountThings(final DittoHeaders dittoHeaders, @Nullable final String filt
* @return a new command for counting Things.
* @throws NullPointerException if any argument is {@code null}.
*/
public static CountThings of(@Nullable final String filter, @Nullable final Set<String> namespaces,
public static CountThings of(@Nullable final String filter, @Nullable final Collection<String> namespaces,
final DittoHeaders dittoHeaders) {

return new CountThings(dittoHeaders, filter, namespaces);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Copyright (c) 2023 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.ditto.thingsearch.service.common.config;

import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.eclipse.ditto.internal.utils.config.KnownConfigValue;

/**
* Provides the configuration settings for a single custom operator metric.
*/
public interface CustomMetricConfig {

/**
* Returns whether this specific search operator metric gathering is turned on.
*
* @return true or false.
*/
boolean isEnabled();

/**
* Returns the optional scrape interval override for this specific custom metric, how often the metrics should be
* gathered.
*
* @return the optional scrape interval override.
*/
Optional<Duration> getScrapeInterval();

/**
* Returns the namespaces the custom metric should be executed in or an empty list for gathering metrics in all
* namespaces.
*
* @return a list of namespaces.
*/
List<String> getNamespaces();

/**
* Returns the filter (RQL statement) to include in the "CountThings" request or an empty string of no filter
* should be applied.
*
* @return the filter RQL statement.
*/
String getFilter();

/**
* Return optional tags to report to the custom Gauge metric.
*
* @return optional tags to report.
*/
Map<String, String> getTags();

enum CustomMetricConfigValue implements KnownConfigValue {

/**
* Whether the metrics should be gathered.
*/
ENABLED("enabled", true),

/**
* The optional custom scrape interval, how often the metrics should be gathered.
* If this is {@code Duration.ZERO}, then there is no overwrite for the "global" scrape-interval to be applied.
*/
SCRAPE_INTERVAL("scrape-interval", Duration.ZERO),

/**
* The namespaces the custom metric should be executed in or an empty list for gathering metrics in all
* namespaces.
*/
NAMESPACES("namespaces", List.of()),

/**
* The filter RQL statement.
*/
FILTER("filter", ""),

/**
* The optional tags to report to the custom Gauge metric.
*/
TAGS("tags", Map.of());

private final String path;
private final Object defaultValue;

CustomMetricConfigValue(final String thePath, final Object theDefaultValue) {
path = thePath;
defaultValue = theDefaultValue;
}

@Override
public Object getDefaultValue() {
return defaultValue;
}

@Override
public String getConfigPath() {
return path;
}
}
}

0 comments on commit f996b8a

Please sign in to comment.