Mercurial > stress-tester
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'