changeset 1197:fa27df670d8c

SequenceFilter - exact mode
author Devel 2
date Mon, 22 Jun 2020 12:26:14 +0200
parents 5f5bfab3fc4a
children 6370efaa0ef5
files stress-tester/src/main/java/com/passus/st/Pair.java stress-tester/src/main/java/com/passus/st/filter/SequenceFilter.java stress-tester/src/main/java/com/passus/st/filter/SequenceFilterActionNodeTransformer.java stress-tester/src/main/java/com/passus/st/filter/SequenceFilterStageNodeTransformer.java stress-tester/src/test/java/com/passus/st/filter/SequenceFilterTest.java stress-tester/src/test/resources/com/passus/st/client/http/filter/sequence.yml
diffstat 6 files changed, 248 insertions(+), 39 deletions(-) [+]
line wrap: on
line diff
--- a/stress-tester/src/main/java/com/passus/st/Pair.java	Mon Jun 22 12:18:38 2020 +0200
+++ b/stress-tester/src/main/java/com/passus/st/Pair.java	Mon Jun 22 12:26:14 2020 +0200
@@ -17,4 +17,8 @@
     public V getValue2() {
         return value2;
     }
+
+    public static <K, V> Pair<K, V> pair(K value1, V value2) {
+        return new Pair<>(value1, value2);
+    }
 }
--- a/stress-tester/src/main/java/com/passus/st/filter/SequenceFilter.java	Mon Jun 22 12:18:38 2020 +0200
+++ b/stress-tester/src/main/java/com/passus/st/filter/SequenceFilter.java	Mon Jun 22 12:26:14 2020 +0200
@@ -7,6 +7,7 @@
 import com.passus.commons.time.SystemTimeGenerator;
 import com.passus.commons.time.TimeAware;
 import com.passus.commons.time.TimeGenerator;
+import com.passus.commons.utils.UniqueIdGenerator;
 import com.passus.config.Configuration;
 import com.passus.config.ConfigurationContext;
 import com.passus.config.annotations.NodeDefinitionCreate;
@@ -16,7 +17,6 @@
 import com.passus.st.client.SessionPayloadEvent;
 import com.passus.st.client.http.filter.HttpFilterRequestWrapper;
 import com.passus.st.client.http.filter.HttpFilterResponseWrapper;
-import com.passus.st.config.HeaderOperationNodeDefinition;
 import com.passus.st.emitter.SessionInfo;
 import com.passus.st.metric.MetricSource;
 import com.passus.st.metric.MetricsContainer;
@@ -53,6 +53,8 @@
 
     private List<Metric> metrics;
 
+    private boolean exact = false;
+
     public SequenceFilter() {
         this(FlowFilterFactory.DEFAULT_FACTORY);
     }
@@ -62,6 +64,7 @@
         this.filterFactory = filterFactory;
     }
 
+
     public void init(final Stage[] stages, final Action[] actions) {
         Assert.notNull(stages, "stages");
         Assert.notNull(actions, "actions");
@@ -105,6 +108,14 @@
         this.actions = actions;
     }
 
+    public boolean isExact() {
+        return exact;
+    }
+
+    public void setExact(boolean exact) {
+        this.exact = exact;
+    }
+
     public FilterDirection getDirection() {
         return direction;
     }
@@ -157,6 +168,7 @@
 
     @Override
     public void configure(Configuration config, ConfigurationContext context) {
+        exact = config.getBoolean("exact", false);
         List<Stage> stagesList = (List<Stage>) config.get("sequence");
         Stage[] stagesArr = stagesList.toArray(new Stage[0]);
 
@@ -188,7 +200,12 @@
         }
 
         if (chain.stage.hasNext()) {
-            chain.setStage(now, chain.stage.next);
+            Stage lastStage = null;
+            if (exact && chain.stage.repeats > 0) {
+                lastStage = chain.stage;
+            }
+
+            chain.setStage(now, chain.stage.next, lastStage);
             return true;
         }
 
@@ -201,26 +218,46 @@
         if (it.hasNext()) {
             StageChain chain = it.next();
             for (; ; ) {
-                if (chain.expire < now) {
-                    if (chain.stage.mustOccur) {
-                        it.remove();
-                        continue;
-                    }
-
-                    if (!nextStage(chain, now, value, context)) {
-                        it.remove();
-                    } else {
-                        continue;
-                    }
-                } else if (chain.stage.mustOccur) {
+                if (exact) {
                     Map<String, Object> filterable = chain.mergeValue(value);
                     if (chain.stage.match(filterable)) {
                         if (!nextStage(chain, now, value, context)) {
                             it.remove();
                         }
+                    } else if (chain.lastStage != null) {
+                        if (chain.lastStage.match(filterable)) {
+                            chain.lastStageRepeats--;
+                            if (chain.lastStageRepeats == 0) {
+                                it.remove();
+                            }
+                        } else {
+                            it.remove();
+                        }
+                    } else {
+                        it.remove();
                     }
-                } else if (!nextStage(chain, now, null, context)) {
-                    it.remove();
+                } else {
+                    if (chain.expire < now) {
+                        if (chain.stage.mustOccur) {
+                            it.remove();
+                            continue;
+                        }
+
+                        if (!nextStage(chain, now, value, context)) {
+                            it.remove();
+                        } else {
+                            continue;
+                        }
+                    } else if (chain.stage.mustOccur) {
+                        Map<String, Object> filterable = chain.mergeValue(value);
+                        if (chain.stage.match(filterable)) {
+                            if (!nextStage(chain, now, value, context)) {
+                                it.remove();
+                            }
+                        }
+                    } else if (!nextStage(chain, now, null, context)) {
+                        it.remove();
+                    }
                 }
 
                 if (!it.hasNext()) break;
@@ -233,7 +270,7 @@
         valueMap.put("resp", value.getResp());
         if (rootStage.match(valueMap)) {
             StageChain chain = new StageChain(valueMap);
-            chain.setStage(now, rootStage);
+            chain.setStage(now, rootStage, null);
             if (nextStage(chain, now, value, context)) {
                 chains.add(chain);
             }
@@ -271,15 +308,18 @@
 
         private final String alias;
 
+        private final int repeats;
+
         private String[] aliases;
 
         Stage next;
 
-        public Stage(Predicate predicate, long time, boolean mustOccur, String alias) {
+        public Stage(Predicate predicate, long time, boolean mustOccur, String alias, int repeats) {
             this.predicate = predicate;
             this.time = time;
             this.mustOccur = mustOccur;
             this.alias = alias;
+            this.repeats = repeats;
 
             if (alias != null) {
                 aliases = new String[2];
@@ -309,6 +349,10 @@
             return alias;
         }
 
+        public int getRepeats() {
+            return repeats;
+        }
+
         private boolean hasNext() {
             return next != null;
         }
@@ -326,6 +370,10 @@
 
         private Stage stage;
 
+        private Stage lastStage;
+
+        private int lastStageRepeats;
+
         private final Map<String, Object> valueMap;
 
         private long expire;
@@ -334,9 +382,14 @@
             this.valueMap = valueMap;
         }
 
-        public void setStage(long now, Stage stage) {
+        public void setStage(long now, Stage stage, Stage lastStage) {
             this.expire = now + stage.time;
             this.stage = stage;
+
+            this.lastStage = lastStage;
+            if (lastStage != null) {
+                lastStageRepeats = lastStage.repeats;
+            }
         }
 
         public Map<String, Object> mergeValue(MessageWrapper newValue) {
@@ -356,7 +409,7 @@
 
     public static abstract class Action {
 
-        public abstract void execute(StageChain chain, FlowContext flowContext);
+        protected abstract void execute(StageChain chain, FlowContext flowContext);
 
     }
 
@@ -364,14 +417,17 @@
 
         private final Map<String, ValueExtractor> values;
 
+        private final boolean suffix;
+
         private List<Metric> metrics;
 
         public CreateMetricsAction() {
-            this(Collections.EMPTY_MAP);
+            this(Collections.EMPTY_MAP, false);
         }
 
-        public CreateMetricsAction(Map<String, ValueExtractor> values) {
+        public CreateMetricsAction(Map<String, ValueExtractor> values, boolean suffix) {
             this.values = values;
+            this.suffix = suffix;
         }
 
         Map<String, ValueExtractor> getValues() {
@@ -379,7 +435,7 @@
         }
 
         @Override
-        public void execute(StageChain chain, FlowContext flowContext) {
+        protected void execute(StageChain chain, FlowContext flowContext) {
             Map<String, Serializable> result;
 
             if (values.isEmpty()) {
@@ -391,18 +447,25 @@
                     Object value = e.getValue().extract(persisted);
                     if (value instanceof Serializable) {
                         result.put(e.getKey(), (Serializable) value);
+                    } else if (value != null) {
+                        result.put(e.getKey(), value.toString());
                     }
                 }
             }
 
-            MapMetric metric = new MapMetric("sequence", result);
+            String name = "sequence";
+            if (suffix) {
+                name += "." + UniqueIdGenerator.generate();
+            }
+
+            MapMetric metric = new MapMetric(name, result);
             synchronized (metrics) {
                 metrics.add(metric);
             }
         }
     }
 
-    public static final class MultiplicationAction extends Action {
+    protected static final class MultiplicationAction extends Action {
 
         private final int multiplier;
 
@@ -418,7 +481,7 @@
         }
 
         @Override
-        public void execute(StageChain chain, FlowContext flowContext) {
+        protected void execute(StageChain chain, FlowContext flowContext) {
             for (int i = 0; i < multiplier; i++) {
                 for (int j = (stagesNum - 1); j >= 0; j--) {
                     Object value = chain.valueMap.get(STAGE_PREFIX + j);
@@ -452,14 +515,11 @@
                     tupleDef("match", MSG_PREDICATE_DEF),
                     tupleDef("mustOccur", BOOLEAN_DEF).setRequired(false),
                     tupleDef("time", LONG_GREATER_EQUAL_ZERO_DEF).setRequired(false),
-                    tupleDef("alias", STRING_DEF).setRequired(false)
+                    tupleDef("alias", STRING_DEF).setRequired(false),
+                    tupleDef("repeats", INT_GREATER_EQUAL_ZERO_DEF).setRequired(false)
             );
             stageDef.setTransformer(new SequenceFilterStageNodeTransformer());
 
-            HeaderOperationNodeDefinition metricActionDef = new HeaderOperationNodeDefinition(
-                    valueDef()
-            );
-
             KeyNameVaryListNodeDefinition actionsDef = new KeyNameVaryListNodeDefinition()
                     .setNodeTransformer(new SequenceFilterActionNodeTransformer())
                     .add("metric", new MappingNodeDefinition().setTransformer(new SequenceFilterValuesNodeTransformer()))
@@ -468,6 +528,7 @@
 
             return mapDef(
                     tupleDef("dir", enumDef(FilterDirection.class)).setRequired(false),
+                    tupleDef("exact", BOOLEAN_DEF).setRequired(false),
                     tupleDef("sequence", new ListNodeDefinition(stageDef).setMinNumberOfValues(2)),
                     tupleDef("action", actionsDef)
             );
--- a/stress-tester/src/main/java/com/passus/st/filter/SequenceFilterActionNodeTransformer.java	Mon Jun 22 12:18:38 2020 +0200
+++ b/stress-tester/src/main/java/com/passus/st/filter/SequenceFilterActionNodeTransformer.java	Mon Jun 22 12:26:14 2020 +0200
@@ -65,7 +65,7 @@
                                 }
                             }
 
-                            action = new SequenceFilter.CreateMetricsAction(values);
+                            action = new SequenceFilter.CreateMetricsAction(values, false);
                         }
 
                         break;
--- a/stress-tester/src/main/java/com/passus/st/filter/SequenceFilterStageNodeTransformer.java	Mon Jun 22 12:18:38 2020 +0200
+++ b/stress-tester/src/main/java/com/passus/st/filter/SequenceFilterStageNodeTransformer.java	Mon Jun 22 12:26:14 2020 +0200
@@ -3,6 +3,7 @@
 import com.passus.config.*;
 import com.passus.config.schema.NodeTransformer;
 import com.passus.config.validation.Errors;
+import com.passus.data.type.Type;
 import com.passus.filter.config.PredicateNodeTransformer;
 import com.passus.st.filter.SequenceFilter.Stage;
 
@@ -23,6 +24,7 @@
             boolean mustOccur = true;
             long time = 0;
             String alias = null;
+            int repeats = 0;
             for (CTupleNode tupleNode : tupleNodes) {
                 String name = tupleNode.getName();
                 CNode valueNode = tupleNode.getNode();
@@ -31,18 +33,21 @@
                         predicate = TRANSFORMER.transform(valueNode);
                         break;
                     case "mustOccur":
-                        mustOccur = (Boolean) getValue(valueNode);
+                        mustOccur = Type.BOOLEAN.convert(getValue(valueNode));
                         break;
                     case "time":
-                        time = (Long) getValue(valueNode);
+                        time = Type.LONG.convert(getValue(valueNode));
                         break;
                     case "alias":
-                        alias = (String) getValue(valueNode);
+                        alias = Type.STRING.convert(getValue(valueNode));
+                        break;
+                    case "repeats":
+                        repeats = Type.INTEGER.convert(getValue(valueNode));
                         break;
                 }
             }
 
-            Stage stage = new Stage(predicate, time, mustOccur, alias);
+            Stage stage = new Stage(predicate, time, mustOccur, alias, repeats);
             return new CValueNode(stage);
         } catch (Exception ex) {
             return node;
--- a/stress-tester/src/test/java/com/passus/st/filter/SequenceFilterTest.java	Mon Jun 22 12:18:38 2020 +0200
+++ b/stress-tester/src/test/java/com/passus/st/filter/SequenceFilterTest.java	Mon Jun 22 12:26:14 2020 +0200
@@ -33,7 +33,7 @@
 import java.util.function.Predicate;
 
 import static com.passus.st.utils.HttpMessageAssert.assertMessages;
-import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.*;
 
 public class SequenceFilterTest {
 
@@ -59,6 +59,23 @@
         AppUtils.unregisterAll();
     }
 
+    private static HttpRequest req(String url) {
+        return HttpRequestBuilder.get(url).build();
+    }
+
+    public static Pair<HttpRequest, HttpResponse> pair(HttpRequest value1, HttpResponse value2) {
+        return new Pair<>(value1, value2);
+    }
+
+    public static List<Pair<HttpRequest, HttpResponse>> pairs(Pair<HttpRequest, HttpResponse>... pairs) {
+        List<Pair<HttpRequest, HttpResponse>> pairsList = new ArrayList<>(pairs.length);
+        for (Pair<HttpRequest, HttpResponse> pair : pairs) {
+            pairsList.add(pair);
+        }
+
+        return pairsList;
+    }
+
     private static Stage stage(String predicateString) throws Exception {
         return stage(predicateString, null);
     }
@@ -67,13 +84,25 @@
         return stage(predicateString, 60_000, true, alias);
     }
 
+    private static Stage stage(String predicateString, int repeats) throws Exception {
+        return stage(predicateString, 60_000, true, null, repeats);
+    }
+
     private static Stage stage(String predicateString, long time, boolean mustOccur, String alias) throws Exception {
+        return stage(predicateString, time, mustOccur, alias, 0);
+    }
+
+    private static Stage stage(String predicateString, long time, boolean mustOccur, String alias, int repeats) throws Exception {
         Predicate predicate = TRANSFORMER.transform(predicateString);
-        return new Stage(predicate, time, mustOccur, alias);
+        return new Stage(predicate, time, mustOccur, alias, repeats);
     }
 
     private static Action metricsAction(Map<String, ValueExtractor> values) {
-        return new CreateMetricsAction(values);
+        return metricsAction(values, true);
+    }
+
+    private static Action metricsAction(Map<String, ValueExtractor> values, boolean suffix) {
+        return new CreateMetricsAction(values, suffix);
     }
 
     private static Action multiAction(int multiplier) {
@@ -84,6 +113,39 @@
         return ValueExtractorParser.DEFAULT.parse(s);
     }
 
+    private List<Metric> processStages(Stage[] stages, Collection<Pair<HttpRequest, HttpResponse>> pairs) throws Exception {
+        return processStages(stages, pairs, false);
+    }
+
+    private List<Metric> processStages(Stage[] stages, Collection<Pair<HttpRequest, HttpResponse>> pairs, boolean exact) throws Exception {
+        Map<String, ValueExtractor> values = new HashMap<>();
+        for (int i = 0; i < stages.length; i++) {
+            values.put("url" + i, value("@_i" + i + ".req.url"));
+        }
+
+        Action[] actions = {
+                metricsAction(values)
+        };
+
+        SequenceFilter filter = new SequenceFilter();
+        filter.setExact(exact);
+        filter.init(stages, actions);
+
+        for (Pair<HttpRequest, HttpResponse> p : pairs) {
+            filter.filterInbound(p.getValue1(), p.getValue2(), null);
+        }
+
+        PerNameMetricsContainer metricsContainer = new PerNameMetricsContainer();
+        filter.writeMetrics(metricsContainer);
+
+        Collection<Metric> metrics = metricsContainer.getMetrics();
+        if (metrics.isEmpty()) {
+            return null;
+        }
+
+        return new ArrayList<>(metrics);
+    }
+
     @Test
     public void testSequenceMetricAction() throws Exception {
         Stage[] stages = {
@@ -163,6 +225,78 @@
     }
 
     @Test
+    public void testSequenceExactMatch() throws Exception {
+        Stage[] stages = {
+                stage("{\"@req.uri\": \"/res1\"}", 10),
+                stage("{\"@req.uri\": \"/res2\"}", 0),
+                stage("{\"@req.uri\": \"/res3\"}", 0)
+        };
+
+        List<Pair<HttpRequest, HttpResponse>> pairs = pairs(
+                pair(req("http://example.com/res1"), null),
+                pair(req("http://example.com/res1"), null),
+                pair(req("http://example.com/res1"), null),
+                pair(req("http://example.com/res2"), null),
+                pair(req("http://example.com/res3"), null)
+        );
+
+        List<Metric> metrics = processStages(stages, pairs, true);
+        assertNotNull(metrics);
+        assertEquals(3, metrics.size());
+
+        for (Metric metric : metrics) {
+            assertEquals("http://example.com/res1", metric.getAttributeValue("url0").toString());
+            assertEquals("http://example.com/res2", metric.getAttributeValue("url1").toString());
+            assertEquals("http://example.com/res3", metric.getAttributeValue("url2").toString());
+        }
+    }
+
+    @Test
+    public void testSequenceExactNotMatch() throws Exception {
+        Stage[] stages = {
+                stage("{\"@req.uri\": \"/res1\"}", 10),
+                stage("{\"@req.uri\": \"/res2\"}", 0),
+                stage("{\"@req.uri\": \"/res3\"}", 0)
+        };
+
+        List<Pair<HttpRequest, HttpResponse>> pairs = pairs(
+                pair(req("http://example.com/res1"), null),
+                pair(req("http://example.com/res3"), null),
+                pair(req("http://example.com/res2"), null)
+        );
+
+        List<Metric> metrics = processStages(stages, pairs, true);
+        assertNull(metrics);
+    }
+
+    @Test
+    public void testSequenceExactNotMatchRepeatsExceeded() throws Exception {
+        Stage[] stages = {
+                stage("{\"@req.uri\": \"/res1\"}", 2),
+                stage("{\"@req.uri\": \"/res2\"}", 0),
+                stage("{\"@req.uri\": \"/res3\"}", 0)
+        };
+
+        List<Pair<HttpRequest, HttpResponse>> pairs = pairs(
+                pair(req("http://example.com/res1"), null),
+                pair(req("http://example.com/res1"), null),
+                pair(req("http://example.com/res1"), null),
+                pair(req("http://example.com/res2"), null),
+                pair(req("http://example.com/res3"), null)
+        );
+
+        List<Metric> metrics = processStages(stages, pairs, true);
+        assertNotNull(metrics);
+        assertEquals(2, metrics.size());
+
+        for (Metric metric : metrics) {
+            assertEquals("http://example.com/res1", metric.getAttributeValue("url0").toString());
+            assertEquals("http://example.com/res2", metric.getAttributeValue("url1").toString());
+            assertEquals("http://example.com/res3", metric.getAttributeValue("url2").toString());
+        }
+    }
+
+    @Test
     public void testConfigure() throws Exception {
         File file = ResourceUtils.getFile("com/passus/st/client/http/filter/sequence.yml");
         String filterConfig = new String(Files.readAllBytes(Paths.get(file.toURI())));
@@ -176,6 +310,7 @@
         SequenceFilter filter = (SequenceFilter) filters.get(0);
         Action[] actions = filter.getActions();
 
+        assertTrue(filter.isExact());
         assertEquals(2, actions.length);
         CreateMetricsAction cmAction = (CreateMetricsAction) actions[0];
 
@@ -187,9 +322,11 @@
             stagesNum++;
             if (stagesNum == 1) {
                 assertEquals(null, stage.getAlias());
+                assertEquals(0, stage.getRepeats());
             } else if (stagesNum == 2) {
                 assertEquals(20000, stage.getTime());
                 assertEquals("last", stage.getAlias());
+                assertEquals(100, stage.getRepeats());
             }
 
             stage = stage.next;
--- a/stress-tester/src/test/resources/com/passus/st/client/http/filter/sequence.yml	Mon Jun 22 12:18:38 2020 +0200
+++ b/stress-tester/src/test/resources/com/passus/st/client/http/filter/sequence.yml	Mon Jun 22 12:26:14 2020 +0200
@@ -1,11 +1,13 @@
 filters:
   - type: sequence
+    exact: true
     sequence:
       - match: {"@req.uri": "/res1", "@resp.getCookie('id')": {$neq: "123"}}
       - match: {"@req.getHeader('xyz')": "abc"}
         mustOccur: true
         time: 20000
         alias: last
+        repeats: 100
     action:
       metric:
         code: '@_i0.resp.status.code'