diff --git a/capa/engine.py b/capa/engine.py index 5a295b0d..7a2dea31 100644 --- a/capa/engine.py +++ b/capa/engine.py @@ -10,6 +10,7 @@ import copy import collections from typing import Set, Dict, List, Tuple, Union, Mapping, Iterable +import capa.perf import capa.rules import capa.features.common from capa.features.common import Feature @@ -125,6 +126,9 @@ class And(Statement): self.children = children def evaluate(self, ctx): + capa.perf.counters["evaluate.feature"] += 1 + capa.perf.counters["evaluate.feature.and"] += 1 + results = [child.evaluate(ctx) for child in self.children] success = all(results) return Result(success, self, results) @@ -138,6 +142,9 @@ class Or(Statement): self.children = children def evaluate(self, ctx): + capa.perf.counters["evaluate.feature"] += 1 + capa.perf.counters["evaluate.feature.or"] += 1 + results = [child.evaluate(ctx) for child in self.children] success = any(results) return Result(success, self, results) @@ -151,6 +158,9 @@ class Not(Statement): self.child = child def evaluate(self, ctx): + capa.perf.counters["evaluate.feature"] += 1 + capa.perf.counters["evaluate.feature.not"] += 1 + results = [self.child.evaluate(ctx)] success = not results[0] return Result(success, self, results) @@ -165,6 +175,9 @@ class Some(Statement): self.children = children def evaluate(self, ctx): + capa.perf.counters["evaluate.feature"] += 1 + capa.perf.counters["evaluate.feature.some"] += 1 + results = [child.evaluate(ctx) for child in self.children] # note that here we cast the child result as a bool # because we've overridden `__bool__` above. @@ -184,6 +197,9 @@ class Range(Statement): self.max = max if max is not None else (1 << 64 - 1) def evaluate(self, ctx): + capa.perf.counters["evaluate.feature"] += 1 + capa.perf.counters["evaluate.feature.range"] += 1 + count = len(ctx.get(self.child, [])) if self.min == 0 and count == 0: return Result(True, self, []) diff --git a/capa/features/common.py b/capa/features/common.py index 8a3d0ec2..9fa5d8bf 100644 --- a/capa/features/common.py +++ b/capa/features/common.py @@ -12,6 +12,7 @@ import logging import collections from typing import Set, Dict, Union +import capa.perf import capa.engine import capa.features import capa.features.extractors.elf @@ -97,6 +98,8 @@ class Feature: return str(self) def evaluate(self, ctx: Dict["Feature", Set[int]]) -> "capa.engine.Result": + capa.perf.counters["evaluate.feature"] += 1 + capa.perf.counters["evaluate.feature." + self.name] += 1 return capa.engine.Result(self in ctx, self, [], locations=ctx.get(self, [])) def freeze_serialize(self): @@ -141,6 +144,9 @@ class Substring(String): self.value = value def evaluate(self, ctx): + capa.perf.counters["evaluate.feature"] += 1 + capa.perf.counters["evaluate.feature.substring"] += 1 + # mapping from string value to list of locations. # will unique the locations later on. matches = collections.defaultdict(list) @@ -226,6 +232,9 @@ class Regex(String): ) def evaluate(self, ctx): + capa.perf.counters["evaluate.feature"] += 1 + capa.perf.counters["evaluate.feature.regex"] += 1 + # mapping from string value to list of locations. # will unique the locations later on. matches = collections.defaultdict(list) @@ -309,6 +318,9 @@ class Bytes(Feature): self.value = value def evaluate(self, ctx): + capa.perf.counters["evaluate.feature"] += 1 + capa.perf.counters["evaluate.feature.bytes"] += 1 + for feature, locations in ctx.items(): if not isinstance(feature, (Bytes,)): continue diff --git a/capa/main.py b/capa/main.py index 4b4248b6..a830b2ac 100644 --- a/capa/main.py +++ b/capa/main.py @@ -28,6 +28,7 @@ import colorama from pefile import PEFormatError from elftools.common.exceptions import ELFError +import capa.perf import capa.rules import capa.engine import capa.version @@ -1024,6 +1025,9 @@ def main(argv=None): meta["analysis"].update(counts) meta["analysis"]["layout"] = compute_layout(rules, extractor, capabilities) + for (counter, count) in capa.perf.counters.most_common(): + logger.debug("perf: counter: %s: %d", counter, count) + if has_file_limitation(rules, capabilities): # bail if capa encountered file limitation e.g. a packed binary # do show the output in verbose mode, though. diff --git a/capa/perf.py b/capa/perf.py new file mode 100644 index 00000000..d1e4083d --- /dev/null +++ b/capa/perf.py @@ -0,0 +1,3 @@ +import collections + +counters = collections.Counter() diff --git a/capa/rules.py b/capa/rules.py index a8e65b22..6960e02b 100644 --- a/capa/rules.py +++ b/capa/rules.py @@ -27,6 +27,7 @@ from typing import Any, Dict, List, Union, Iterator import yaml import ruamel.yaml +import capa.perf import capa.engine as ceng import capa.features import capa.features.file @@ -620,6 +621,8 @@ class Rule: yield new_rule def evaluate(self, features: FeatureSet): + capa.perf.counters["evaluate.feature"] += 1 + capa.perf.counters["evaluate.feature.rule"] += 1 return self.statement.evaluate(features) @classmethod