Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(Telemetry) #30079 : Move telemetry plugin into core - Part 1 #30520

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions dotCMS/src/main/java/com/dotcms/telemetry/Metric.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.dotcms.telemetry;

/**
* Represents all the information needed to identify a Metric:
* <ul>
* <li>Name</li>
* <li>Description</li>
* <li>Category</li>
* <li>Feature</li>
* </ul>
*/
public class Metric {

private final String name;
private final String description;
private final MetricCategory category;
private final MetricFeature feature;

public Metric(final Builder builder) {
this.name = builder.name;
this.description = builder.description;
this.category = builder.category;
this.feature = builder.feature;
}

public String getName() {
return name;
}

public String getDescription() {
return description;
}

public MetricCategory getCategory() {
return category;
}

public MetricFeature getFeature() {
return feature;
}

@Override
public String toString() {
return "Metric{" +
"name='" + name + '\'' +
", description='" + description + '\'' +
", category=" + category +
", feature=" + feature +
'}';
}

public static class Builder {
String name;
String description;
MetricCategory category;
MetricFeature feature;

public Builder name(final String name) {
this.name = name;
return this;
}

public Builder description(final String description) {
this.description = description;
return this;
}

public Builder category(final MetricCategory category) {
this.category = category;
return this;
}

public Builder feature(final MetricFeature feature) {
this.feature = feature;
return this;
}

public Metric build() {
return new Metric(this);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.dotcms.telemetry;

import com.fasterxml.jackson.annotation.JsonUnwrapped;

/**
* Represent an error that occurs while calculating the metric.
*/
public class MetricCalculationError {

@JsonUnwrapped
private final Metric metric;
private final String error;

public MetricCalculationError(Metric metric, String error) {
this.metric = metric;
this.error = error;
}

public Metric getType() {
return metric;
}

public String getError() {
return error;
}

@Override
public String toString() {
return "MetricCalculationError{" +
"metric=" + metric +
", error='" + error + '\'' +
'}';
}

}
27 changes: 27 additions & 0 deletions dotCMS/src/main/java/com/dotcms/telemetry/MetricCategory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.dotcms.telemetry;

/**
* Represents the Metric Category where the {@link MetricType} belongs to
*/
public enum MetricCategory {

DIFFERENTIATING_FEATURES("Differentiating Features"),
PAID_FEATURES("Paid Features"),
PLATFORM_SPECIFIC_CUSTOMIZATION("Platform-Specific Customization"),
SOPHISTICATED_CONTENT_ARCHITECTURE("Sophisticated Content Architecture"),
POSITIVE_USER_EXPERIENCE("Positive User Experience (incl. content velocity)"),
PLATFORM_SPECIFIC_DEVELOPMENT("Platform-Specific Development"),
RECENT_ACTIVITY("Recent Activity"),
HISTORY_LEGACY_DEPRECATED_FEATURES("History (incl. legacy/deprecated features");

private final String label;

MetricCategory(final String label) {
this.label = label;
}

public String getLabel() {
return label;
}

}
20 changes: 20 additions & 0 deletions dotCMS/src/main/java/com/dotcms/telemetry/MetricFeature.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.dotcms.telemetry;

/**
* Represents the dotCMS feature that the {@link MetricType} belongs to
*/
public enum MetricFeature {

CONTENTLETS,
LANGUAGES,
CONTENT_TYPES,
SITES,
URL_MAPS,
WORKFLOW,
USERS,
SITE_SEARCH,
IMAGE_API,
LAYOUT,
CONTENT_TYPE_FIELDS

}
59 changes: 59 additions & 0 deletions dotCMS/src/main/java/com/dotcms/telemetry/MetricType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.dotcms.telemetry;

import com.dotmarketing.exception.DotDataException;

import java.util.Optional;

/**
* This interface represents a Metric that needs to be calculated and included in a MetricSnapshot.
* For each of these a new concrete class implements this interface must be created. The interface
* provides a set of methods to define the metadata for the Metric, such as its name, description,
* category, and feature. It also includes a method to calculate the Metric's value.
* <ul>
* <li>Metadata Methods: The getName, getDescription, getCategory, and getFeature methods can
* be overridden to set the metadata for a specific Metric. The getMetric method creates a
* Metric object using the values returned by these methods. The Metric class represents the
* metadata of a single Metric.</li>
* <li>Value Calculation Method: You can override the getValue method to define how the
* Metric value is calculated. This method returns an object, so the value can be of any type.
* To perform this calculation, you can use SQL queries, dotCMS API methods, Java core code, or
* any other approach that suits your needs.</li>
* </ul>
* <p>Some of the Metrics to collect are:</p>
* <ul>
* <li>Count of workflow schemes</li>
* <li>Count of Site Search indexes</li>
* <li>Count of sites</li>
* <li>Count of content types with URL maps</li>
* <li>etc</li>
* </ul>
*
* @see MetricCategory
* @see MetricFeature
*/
public interface MetricType {

String getName();

String getDescription();

MetricCategory getCategory();

MetricFeature getFeature();

Optional<Object> getValue();

default Metric getMetric() {
return new Metric.Builder()
.name(getName())
.description(getDescription())
.category(getCategory())
.feature(getFeature())
.build();
}

default Optional<MetricValue> getStat() throws DotDataException {
return getValue().map(o -> new MetricValue(this.getMetric(), o));
}

}
62 changes: 62 additions & 0 deletions dotCMS/src/main/java/com/dotcms/telemetry/MetricValue.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.dotcms.telemetry;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import org.apache.commons.lang.math.NumberUtils;

import java.text.DecimalFormat;

/**
* Represents the value for a {@link MetricType}
*/
public class MetricValue {

private static final DecimalFormat FORMAT = new DecimalFormat("0.##");

@JsonUnwrapped
final Metric metric;

final Object value;

public MetricValue(final Metric metric, final Object value) {
this.metric = metric;
this.value = value;
}

/**
* Check if the value of the Metric is a numeric value.
*
* @return true if the value is a numeric value
*/
@JsonIgnore
public boolean isNumeric() {
return NumberUtils.isNumber(value.toString());
}

public Metric getMetric() {
return metric;
}

/**
* Return the value of the Metric if it is a numeric value then return it as a String formatted
* with two decimals
*
* @return
*/
public Object getValue() {
if (isNumeric()) {
return FORMAT.format(Double.parseDouble(value.toString()));
} else {
return value;
}
}

@Override
public String toString() {
return "MetricValue{" +
"metric=" + metric +
", value=" + value +
'}';
}

}
93 changes: 93 additions & 0 deletions dotCMS/src/main/java/com/dotcms/telemetry/MetricsSnapshot.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package com.dotcms.telemetry;

import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
* Represents a snapshot of metric statistics, including all calculated metrics, separated into
* numeric and non-numeric categories. It also contains a list of errors, indicating any metrics
* that encountered exceptions during calculation.
*/
public class MetricsSnapshot {

/**
* Collection of Numeric Metrics
*/
final Collection<MetricValue> stats;

/**
* Collection of No Numeric Metrics
*/
final Collection<MetricValue> notNumericStats;

/**
* Metric that thrown an Exception during the calculation process
*/
final Collection<MetricCalculationError> errors;

public MetricsSnapshot(final Builder builder) {
this.stats = builder.stats;
this.notNumericStats = builder.notNumericStats;
this.errors = builder.errors;
}

@JsonProperty
public Collection<MetricValue> getStats() {
return stats;
}

@JsonProperty
public Collection<MetricCalculationError> getErrors() {
return errors;
}

@JsonAnyGetter
public Map<String, String> getNotNumericStats() {
final Map<String, String> result = new HashMap<>();

for (final MetricValue stat : notNumericStats) {
result.put(stat.getMetric().getName(), stat.getValue().toString());
}

return result;
}

@Override
public String toString() {
return "MetricsSnapshot{" +
"stats=" + stats +
", notNumericStats=" + notNumericStats +
", errors=" + errors +
'}';
}

public static class Builder {
private Collection<MetricValue> stats;
private Collection<MetricValue> notNumericStats;
private Collection<MetricCalculationError> errors;

public Builder stats(Collection<MetricValue> stats) {
this.stats = stats;
return this;
}

public Builder notNumericStats(Collection<MetricValue> notNumericStats) {
this.notNumericStats = notNumericStats;
return this;
}

public Builder errors(Collection<MetricCalculationError> errors) {
this.errors = errors;
return this;
}

public MetricsSnapshot build() {
return new MetricsSnapshot(this);
}
}

}
Loading