You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jmeter.apache.org by vl...@apache.org on 2023/05/19 12:59:05 UTC

[jmeter] branch master updated: perf: cache datetime formatter in __time function

This is an automated email from the ASF dual-hosted git repository.

vladimirsitnikov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/jmeter.git


The following commit(s) were added to refs/heads/master by this push:
     new 6e637e9f7a perf: cache datetime formatter in __time function
6e637e9f7a is described below

commit 6e637e9f7a3de4234fe411348a7b46dfa3c88a0d
Author: Vladimir Sitnikov <si...@gmail.com>
AuthorDate: Sun Mar 26 12:31:08 2023 +0200

    perf: cache datetime formatter in __time function
    
    Previous JMeter version used SimpleDateFormat for the time format,
    and u had a different meaning.
    We log a warning only at the first encounter of a problematic pattern.
    
    This implementation has a few loopholes/problems:
     * 'u' could still be used for something different and the year
       could be coming from a 'YYYY' format string
    
    Co-authored-by: Felix Schumacher <fe...@internetallee.de>
    
    Part of #5694
    Closes https://github.com/apache/jmeter/pull/5796
---
 .../apache/jmeter/resources/messages.properties    |  2 +-
 .../apache/jmeter/resources/messages_fr.properties |  2 +-
 .../org/apache/jmeter/functions/TimeFunction.java  | 71 ++++++++++++++++++----
 xdocs/changes.xml                                  |  1 +
 4 files changed, 61 insertions(+), 15 deletions(-)

diff --git a/src/core/src/main/resources/org/apache/jmeter/resources/messages.properties b/src/core/src/main/resources/org/apache/jmeter/resources/messages.properties
index 535e175cd2..851b4a63ed 100644
--- a/src/core/src/main/resources/org/apache/jmeter/resources/messages.properties
+++ b/src/core/src/main/resources/org/apache/jmeter/resources/messages.properties
@@ -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
diff --git a/src/core/src/main/resources/org/apache/jmeter/resources/messages_fr.properties b/src/core/src/main/resources/org/apache/jmeter/resources/messages_fr.properties
index fe279153c6..c904d122cb 100644
--- a/src/core/src/main/resources/org/apache/jmeter/resources/messages_fr.properties
+++ b/src/core/src/main/resources/org/apache/jmeter/resources/messages_fr.properties
@@ -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)
diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/TimeFunction.java b/src/functions/src/main/java/org/apache/jmeter/functions/TimeFunction.java
index 1dd087b7ed..f704985286 100644
--- a/src/functions/src/main/java/org/apache/jmeter/functions/TimeFunction.java
+++ b/src/functions/src/main/java/org/apache/jmeter/functions/TimeFunction.java
@@ -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
@@ -44,7 +50,7 @@ import com.google.auto.service.AutoService;
  * @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$
 
@@ -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$
@@ -95,18 +129,7 @@ public class TimeFunction extends AbstractFunction {
             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()) {
@@ -148,4 +171,26 @@ public class TimeFunction extends AbstractFunction {
     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();
+    }
 }
diff --git a/xdocs/changes.xml b/xdocs/changes.xml
index e9628c58b7..ea98f66f17 100644
--- a/xdocs/changes.xml
+++ b/xdocs/changes.xml
@@ -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>