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 30, 2023
1 parent 9a28a35 commit 40e1e83
Show file tree
Hide file tree
Showing 17 changed files with 844 additions and 66 deletions.
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 +
"]";
}

}
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
@@ -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 40e1e83

Please sign in to comment.