Skip to content

Commit

Permalink
More time metrics (#26398)
Browse files Browse the repository at this point in the history
  • Loading branch information
victoralfaro-dotcms authored Oct 11, 2023
1 parent 6489162 commit 8f7f778
Show file tree
Hide file tree
Showing 8 changed files with 263 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.dotcms.analytics.bayesian.beta.BetaDistributionWrapper;
import com.dotcms.analytics.bayesian.model.*;
import com.dotcms.metrics.timing.TimeMetric;
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.util.Config;
import com.dotmarketing.util.Logger;
Expand Down Expand Up @@ -34,20 +35,15 @@ public class BayesianAPIImpl implements BayesianAPI {
*/
@Override
public BayesianResult doBayesian(final BayesianInput input) {
final UUID metricId = UUID.randomUUID();
final long start = System.currentTimeMillis();
Logger.debug(this, String.format("BAYESIAN-CALCULATION [%s] START <<<<<", metricId));
final TimeMetric timeMetric = TimeMetric.mark(getClass().getSimpleName());

// validate input
final BayesianResult bayesianResult = noopFallback(input)
.orElseGet(() -> input.type() == ABTestingType.AB
? getAbBayesianResult(input)
: getAbcBayesianResult(input));

final long end = System.currentTimeMillis();
Logger.debug(
this,
String.format("BAYESIAN-CALCULATION [%s] END - took [%d] secs >>>>>", metricId, (end - start) / 1000));
timeMetric.stop();

return bayesianResult;
}
Expand Down
22 changes: 10 additions & 12 deletions dotCMS/src/main/java/com/dotcms/cube/CubeJSClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,19 @@
import com.dotcms.http.CircuitBreakerUrl;
import com.dotcms.http.CircuitBreakerUrl.Method;
import com.dotcms.http.CircuitBreakerUrl.Response;
import com.dotcms.metrics.timing.TimeMetric;
import com.dotcms.util.DotPreconditions;
import com.dotcms.util.JsonUtil;
import com.dotmarketing.util.Logger;
import com.dotmarketing.util.UtilMethods;
import com.google.common.collect.ImmutableMap;
import org.jetbrains.annotations.NotNull;

import javax.ws.rs.core.HttpHeaders;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
* CubeJS Client it allow to send a Request to a Cube JS Server.
Expand Down Expand Up @@ -102,20 +101,21 @@ public CubeJSResultSet send(final CubeJSQuery query) {

final CircuitBreakerUrl cubeJSClient;
final String cubeJsUrl = String.format("%s/cubejs-api/v1/load", url);
final String queryAsString = query.toString();
try {
cubeJSClient = CircuitBreakerUrl.builder()
.setMethod(Method.GET)
.setHeaders(cubeJsHeaders(accessToken))
.setUrl(cubeJsUrl)
.setParams(map("query", query.toString()))
.setParams(map("query", queryAsString))
.setTimeout(4000)
.setThrowWhenNot2xx(false)
.build();
} catch (AnalyticsException e) {
throw new RuntimeException(e);
}

final Response<String> response = getStringResponse(cubeJSClient, cubeJsUrl);
final Response<String> response = getStringResponse(cubeJSClient, cubeJsUrl, queryAsString);

try {
final String responseAsString = response.getResponse();
Expand All @@ -129,17 +129,15 @@ public CubeJSResultSet send(final CubeJSQuery query) {
}
}

private static Response<String> getStringResponse(final CircuitBreakerUrl cubeJSClient, final String url) {
final long start = System.currentTimeMillis();
final UUID metricId = UUID.randomUUID();
Logger.debug(CubeJSClient.class, String.format("CUBEJS-REQUEST [%s] START <<<<<", metricId));
private Response<String> getStringResponse(final CircuitBreakerUrl cubeJSClient,
final String cubeJsUrl,
final String queryAsString) {
final TimeMetric timeMetric = TimeMetric.mark(getClass().getSimpleName());

Logger.debug(this, String.format("Getting results from CubeJs [%s] with query [%s]", cubeJsUrl, queryAsString));
final Response<String> response = cubeJSClient.doResponse();

final long end = System.currentTimeMillis();
Logger.debug(
CubeJSClient.class,
String.format("CUBEJS-REQUEST [%s] END - took [%d] secs >>>>>", metricId, (end - start) / 1000));
timeMetric.stop();

if (!CircuitBreakerUrl.isWithin2xx(response.getStatusCode())) {
throw new RuntimeException("CubeJS Server is not available");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import com.dotcms.experiments.model.TargetingCondition;
import com.dotcms.experiments.model.TrafficProportion;

import com.dotcms.metrics.timing.TimeMetric;
import com.dotcms.rest.exception.NotFoundException;
import com.dotcms.system.event.local.model.EventSubscriber;
import com.dotcms.util.CollectionsUtils;
Expand Down Expand Up @@ -1236,6 +1237,8 @@ private BayesianResult calcBayesian(final ExperimentResults experimentResults, f
@Override
public List<BrowserSession> getEvents(final Experiment experiment,
final User user) throws DotDataException, DotSecurityException {
final TimeMetric timeMetric = TimeMetric.mark(getClass().getSimpleName() + ".getEvents()");

final CubeJSClient cubeClient = cubeJSClientFactory.create(user);
final CubeJSQuery cubeJSQuery = ExperimentResultsQueryFactory.INSTANCE
.create(experiment);
Expand Down Expand Up @@ -1280,6 +1283,8 @@ public List<BrowserSession> getEvents(final Experiment experiment,
e.getMessage());
Logger.error(this, message, e);
throw new DotDataException(message, e);
} finally {
timeMetric.stop();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.dotcms.experiments.model.ExperimentVariant;
import com.dotcms.experiments.model.Goal.GoalType;
import com.dotcms.experiments.model.Goals;
import com.dotcms.metrics.timing.TimeMetric;
import com.dotmarketing.beans.Host;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.business.FactoryLocator;
Expand Down Expand Up @@ -78,6 +79,8 @@ public ExperimentResults getExperimentResult(final Experiment experiment,
final List<BrowserSession> browserSessions)
throws DotDataException, DotSecurityException {

final TimeMetric timeMetric = TimeMetric.mark(getClass().getSimpleName() + ".getExperimentResult()");

final Goals goals = experiment.goals()
.orElseThrow(() -> new IllegalArgumentException("The Experiment must have a Goal"));

Expand Down Expand Up @@ -106,6 +109,8 @@ public ExperimentResults getExperimentResult(final Experiment experiment,
analyzeBrowserSessions(browserSessions, primaryGoal, builder, experiment);
}

timeMetric.stop();

return builder.build();
}

Expand Down
110 changes: 110 additions & 0 deletions dotCMS/src/main/java/com/dotcms/metrics/timing/TimeMetric.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package com.dotcms.metrics.timing;

import com.dotmarketing.util.Logger;
import org.apache.commons.lang3.StringUtils;

import java.util.UUID;

/**
* A utility class for measuring and formatting time metrics.
*
* <p>This class allows you to create and track time metrics, measure the duration,
* and format the duration in seconds using the default format. It also provides
* methods for equality comparison based on the metric's name.</p>
*
* @author vico
*/
public class TimeMetric {

private final String name;
private long start = Long.MIN_VALUE;
private long stop = Long.MIN_VALUE;

private TimeMetric(final String name) {
final String metricId = UUID.randomUUID().toString();
this.name = (StringUtils.isNotBlank(name) ? name + "_" : "") + metricId;
}

public long getStart() {
return start;
}

public long getStop() {
return stop;
}

/**
* Factory method to create a new TimeMetric instance with a given name and start timing.
*
* @param name The name of the time metric.
* @return A new TimeMetric instance with the specified name and started timer.
*/
public static TimeMetric mark(final String name) {
return new TimeMetric(name).start();
}

/**
* Start the timer for the TimeMetric instance.
*
* @return The TimeMetric instance with the timer started.
*/
public TimeMetric start() {
start = System.currentTimeMillis();
reportStart();
return this;
}

/**
* Stop the timer for the TimeMetric instance.
*
* @return The TimeMetric instance with the timer stopped.
*/
public TimeMetric stop() {
stop = System.currentTimeMillis();
reportStop();
return this;
}

/**
* Get the name of the TimeMetric.
*
* @return The name of the TimeMetric.
*/
public String getName() {
return name;
}

/**
* Get the duration of the TimeMetric in milliseconds.
*
* @return The duration of the TimeMetric in milliseconds.
* @throws IllegalStateException if the TimeMetric is not started or stopped.
*/
public long getDuration() {
if (start == Long.MIN_VALUE || stop == Long.MIN_VALUE) {
throw new IllegalStateException("TimeMetric not started or stopped");
}
return stop - start;
}

/**
* Report the start of a time metric.
*/
private void reportStart() {
Logger.debug(this, String.format(">>>>> START [%s] at [%d]", getName(), getStart()));
}

/**
* Report the stop of a time metric.
*/
private void reportStop() {
Logger.debug(
this,
String.format(
"<<<<< STOP [%s] at [%d] / duration [%s]",
getName(),
getStop(),
TimeMetricHelper.get().formatDuration(this)));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.dotcms.metrics.timing;

import io.vavr.Lazy;


/**
* Utility class for formatting time metrics.
*
* @author vico
*/
public class TimeMetricHelper {

private static final Lazy<TimeMetricHelper> INSTANCE = Lazy.of(TimeMetricHelper::new);

private TimeMetricHelper() {}

/**
* Get an instance of the {@link TimeMetricHelper} class.
*
* @return The singleton instance of {@link TimeMetricHelper}.
*/
public static TimeMetricHelper get() {
return INSTANCE.get();
}

/**
* Format a time metric in seconds using a custom mask.
*
* @param timeMetric The time metric to format.
* @param mask The custom format mask (e.g., "%.2f").
* @return The formatted string representing the time metric.
*/
public String formatDuration(final TimeMetric timeMetric, final String mask) {
return String.format(mask, (float) timeMetric.getDuration() / 1000);
}

/**
* Format a time metric in seconds using the default mask "%.4f".
*
* @param timeMetric The time metric to format.
* @return The formatted string representing the time metric.
*/
public String formatDuration(final TimeMetric timeMetric) {
return formatDuration(timeMetric, "%.4f");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.dotcms.metrics.timing;

import com.dotcms.UnitTestBase;
import org.junit.Before;
import org.junit.Test;

import static graphql.Assert.assertTrue;

/**
* Unit tests for the {@link TimeMetricHelper} class.
*
* @author vico
*/
public class TimeMetricHelperTest extends UnitTestBase {

private TimeMetricHelper timeMetricHelper;

@Before
public void setUp() {
timeMetricHelper = TimeMetricHelper.get();
}

/**
* Test the {@link TimeMetricHelper#formatDuration(TimeMetric, String)} method with a custom mask.
*/
@Test
public void testFormatSecondsWithCustomMask() throws InterruptedException {
final TimeMetric timeMetric = TimeMetric.mark("some-time-metric");
Thread.sleep(100);
timeMetric.stop();
final String formatted = timeMetricHelper.formatDuration(timeMetric, "%.2f");
assertTrue(formatted.startsWith("0.1"));
}

/**
* Test the {@link TimeMetricHelper#formatDuration(TimeMetric)} method with the default mask.
*/
@Test
public void testFormatSecondsWithDefaultMask() throws InterruptedException {
final TimeMetric timeMetric = TimeMetric.mark("some-time-metric");
Thread.sleep(100);
timeMetric.stop();
final String formatted = timeMetricHelper.formatDuration(timeMetric);
assertTrue(formatted.startsWith("0.10"));
}

}
36 changes: 36 additions & 0 deletions dotCMS/src/test/java/com/dotcms/metrics/timing/TimeMetricTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.dotcms.metrics.timing;

import com.dotcms.UnitTestBase;
import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

/**
* Unit tests for the {@link TimeMetric} class.
*/
public class TimeMetricTest extends UnitTestBase {

private TimeMetric timeMetric;

/**
* Set up the test environment by initializing the {@link TimeMetric} instance.
*/
@Before
public void setUp() {
timeMetric = TimeMetric.mark("TestMetric");
}

/**
* Test the stop() method of the {@link TimeMetric} class.
*/
@Test
public void testStartAndStop() {
assertNotNull("TimeMetric instance should not be null", timeMetric);
timeMetric.start();
assertTrue("Timer should be stopped after calling stop()", timeMetric.stop().getDuration() >= 0);
}

}

0 comments on commit 8f7f778

Please sign in to comment.