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

perf: cache datetime formatter in __time function #5934

Merged
merged 1 commit into from
May 19, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -1333,7 +1333,7 @@ throughput_control_perthread_label=Per User
throughput_control_title=Throughput Controller
throughput_control_tplabel=Throughput
time_format=Format string for SimpleDateFormat (optional)
time_format_changed=Formatters for time function has been changed from SimpleDateFormat to DateTimeFormatter. Especially the meaning of 'u' has changed from day-of-week to year. Please check and update your format strings accordingly
time_format_changed=Formatters for time function has been changed from SimpleDateFormat to DateTimeFormatter. Especially the meaning of ''u'' has changed from day-of-week to year. Please check and update your format strings accordingly: {0}
time_format_random=Format string for DateTimeFormatter (optional) (default yyyy-MM-dd)
time_format_shift=Format string for DateTimeFormatter (optional) (default unix timestamp in millisecond)
timelim=Time limit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1322,7 +1322,7 @@ throughput_control_perthread_label=Par utilisateur
throughput_control_title=Contrôleur Débit
throughput_control_tplabel=Débit \:
time_format=Chaîne de formatage sur le modèle SimpleDateFormat (optionnel)
time_format_changed=Les formateurs pour la fonction de temps ont été modifiés de SimpleDateFormat à DateTimeFormatter. En particulier, la signification de 'u' a changé du jour de la semaine à l'année. Veuillez vérifier et mettre à jour vos chaînes de format en conséquence
time_format_changed=Le format de la fonction __time a été modifié de SimpleDateFormat à DateTimeFormatter. En particulier, la signification de ''u'' a changé de jour de la semaine à l''année. Veuillez vérifier et mettre à jour vos chaînes de formatage en conséquence: {0}
time_format_random=Chaîne de formatage sur le modèle DateTimeFormatter (optionnel) ( défaut \: yyyy-MM-dd )
time_format_shift=Chaîne de formatage sur le modèle DateTimeFormatter (optionnel) ( défaut \: unix timestamp en millisecondes )
timelim=Limiter le temps de réponses à (ms)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,30 @@

package org.apache.jmeter.functions;

import java.text.MessageFormat;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.regex.Pattern;

import org.apache.jmeter.engine.util.CompoundVariable;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.samplers.Sampler;
import org.apache.jmeter.testelement.TestStateListener;
import org.apache.jmeter.threads.JMeterVariables;
import org.apache.jmeter.util.JMeterUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.auto.service.AutoService;

// See org.apache.jmeter.functions.TestTimeFunction for unit tests
Expand All @@ -44,7 +50,7 @@
* @since 2.2
*/
@AutoService(Function.class)
public class TimeFunction extends AbstractFunction {
public class TimeFunction extends AbstractFunction implements TestStateListener {

private static final String KEY = "__time"; // $NON-NLS-1$

Expand All @@ -57,6 +63,34 @@ public class TimeFunction extends AbstractFunction {

private static final Logger log = LoggerFactory.getLogger(TimeFunction.class);

private static final LoadingCache<String, Supplier<String>> DATE_TIME_FORMATTER_CACHE =
Caffeine.newBuilder()
.maximumSize(1000)
.build((fmt) -> {
if (DIVISOR_PATTERN.matcher(fmt).matches()) {
long div = Long.parseLong(fmt.substring(1)); // should never case NFE
return () -> Long.toString(System.currentTimeMillis() / div);
}
DateTimeFormatter df = DateTimeFormatter
.ofPattern(fmt)
.withZone(ZoneId.systemDefault());
if (isPossibleUsageOfUInFormat(df, fmt)) {
log.warn(
MessageFormat.format(
JMeterUtils.getResString("time_format_changed"),
fmt));
}
return () -> df.format(Instant.now());
});

private static boolean isPossibleUsageOfUInFormat(DateTimeFormatter df, String fmt) {
ZoneId mst = ZoneId.of("-07:00");
return fmt.contains("u") &&
df.withZone(mst)
.format(ZonedDateTime.of(2006, 1, 2, 15, 4, 5, 6, mst))
.contains("2006");
}

static {
desc.add(JMeterUtils.getResString("time_format")); //$NON-NLS-1$
desc.add(JMeterUtils.getResString("function_name_paropt")); //$NON-NLS-1$
Expand Down Expand Up @@ -95,18 +129,7 @@ public String execute(SampleResult previousResult, Sampler currentSampler) throw
if (fmt == null) {
fmt = format;// Not found
}
if (DIVISOR_PATTERN.matcher(fmt).matches()) { // divisor is a positive number
long div = Long.parseLong(fmt.substring(1)); // should never case NFE
datetime = Long.toString(System.currentTimeMillis() / div);
} else {
if (fmt.contains("u")) {
log.warn(JMeterUtils.getResString("time_format_changed"));
}
DateTimeFormatter df = DateTimeFormatter // Not synchronised, so can't be shared
.ofPattern(fmt)
.withZone(ZoneId.systemDefault());
datetime = df.format(Instant.now());
}
datetime = DATE_TIME_FORMATTER_CACHE.get(fmt).get();
}

if (!variable.isEmpty()) {
Expand Down Expand Up @@ -148,4 +171,26 @@ public String getReferenceKey() {
public List<String> getArgumentDesc() {
return desc;
}

@Override
public void testStarted() {
// We invalidate the cache so it will parse the formats again and will raise a warning if there are
// %u usages.
DATE_TIME_FORMATTER_CACHE.invalidateAll();
}

@Override
public void testStarted(String host) {
testStarted();
}

@Override
public void testEnded() {
DATE_TIME_FORMATTER_CACHE.invalidateAll();
}

@Override
public void testEnded(String host) {
testEnded();
}
}
1 change: 1 addition & 0 deletions xdocs/changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ Summary
<li><pr>5920</pr>Cache bean properties in <code>TestBeanHelper</code> and avoid synchronization, so test plans with <code>TestBean</code>-based elements is faster</li>
<li><pr>5920</pr>Improve computation when many threads actively produce samplers by using <code>LongAdder</code> and similar concurrency classes to avoid synchronization in <code>Calculator</code></li>
<li><pr>5920</pr>Reduce synchronization contention on <code>AbstractTestElement</code> that are shared between threads (the ones that implement <code>NoThreadClone</code>)</li>
<li><pr>5934</pr>Added caching for date formatters for <code>__time</code> function</li>
</ul>

<ch_section>Non-functional changes</ch_section>
Expand Down