Skip to content

Commit

Permalink
chore(Telemetry) #30079 : Move telemetry plugin into core - Part 1 (#…
Browse files Browse the repository at this point in the history
…30520)

### Proposed Changes
* Migrates all the code from the `Telemetry` plug-in into our `core`
project.
* This is the first part of the process, so it just takes the code "as
is" from the plugin, and places it inside the `core` project. It only
includes a few adjustments on some Javadoc, code formatting, and the
addition of the `toString()` method in a few classes.
* These changes can safely co-exist with the current `Telemetry` plugin.
* In order to have a more effective Code Review process, the goal is to
have two pull requests:
  * The first one with the existing code from the plugin -- this one.
* The second one with the code changes in `core` that effectively add
the REST Endpoint, Web Interceptor, and Quartz Job which are currently
being added via the `Activator` class.
  • Loading branch information
jcastro-dotcms authored Oct 31, 2024
1 parent c0a01e6 commit 98b2439
Show file tree
Hide file tree
Showing 124 changed files with 5,852 additions and 0 deletions.
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

0 comments on commit 98b2439

Please sign in to comment.