changeset 1237:e8d302936240

flow - switching to deterministic algo
author Devel 1
date Tue, 30 Jun 2020 10:56:04 +0200
parents d526a06722f7
children 457d050a95f6
files stress-tester/src/main/resources/flow_analyzer.py
diffstat 1 files changed, 155 insertions(+), 131 deletions(-) [+]
line wrap: on
line diff
--- a/stress-tester/src/main/resources/flow_analyzer.py	Tue Jun 30 09:04:20 2020 +0200
+++ b/stress-tester/src/main/resources/flow_analyzer.py	Tue Jun 30 10:56:04 2020 +0200
@@ -93,14 +93,24 @@
         lines = self.extract_lines(disallowedStrings=disallowedStrings, debug=debug)
 
         rows = []
-        for line in lines:
-            line = json.loads(line)
+        if debug:
+            self.jsonReject = []
+            self.jsonReject1 = []
+            self.jsonAccepted = []
+        for rline in lines:
+            line = json.loads(rline)
             try:
                 line["httpreqheaders"] = line["httpreqheaders"][re.search("SESSION=", line["httpreqheaders"]).end():]
-                line["httpreqheaders"] = line["httpreqheaders"][:re.search("; ", line["httpreqheaders"]).start()]
+                line["httpreqheaders"] = line["httpreqheaders"][:re.search(";|\r\n", line["httpreqheaders"]).start()]
+                if debug:
+                    self.jsonAccepted.append(rline)
             except:
                 if debug:
-                    print(line)
+                    self.jsonReject.append(rline)
+                continue
+            if line["_type"] !="session.http.request":
+                if debug:
+                    self.jsonReject1.append(rline)
                 continue
             rows.append([
                     line[columnMap.get("id", "id")],
@@ -114,7 +124,7 @@
 
         self.data = df = pd.DataFrame(rows, columns=["id", "datetime", "requestMethod", "url", "protocol", "status", "agent"])
 
-        # Linijskan ap otrzeby szybszych testów do skasowania
+        # Linijka na potrzeby szybszych testów do skasowania
         # self.data = self.data.iloc[0:10000]
 
         # Zmiana tekstowego formatu daty na datę pandasową, żeby można bylo skutecznie sortować
@@ -147,15 +157,10 @@
     def prep_data(self, cleanRows = True, cleanRules = None, dropMissing = True, debug=False):
         if cleanRules is None:
             self.cleanRules = [
-                # Zasoby doatkowe typu css/js
                 ".css", ".js",
-                # Elementy graficzne
                 ".jpg", ".png", ".svg", ".gif", ".ico",
-                # Czcionki
                 ".ttf", ".woff", ".woff2", ".eot",
-                # Dokumenty
                 ".pdf", ".doc", ".docx", ".ppt", ".pptx", ".txt",
-                # Inne elementy jak np strony sprawdzające captch
                 "captcha"
              ]
         else:
@@ -407,66 +412,8 @@
 
         self.allSequences = allSequences
 
-    def analyze_markov(self, historyLength = 1, targetLength = 5, shareLimit = 0.0001):
-        """
-        Funkcja która wykona analizę sekwencji. Funkcja zakłada, że id sekwencji już zostały przygotowane:
-        historyLength: dla jak wielu elementów bierzemy historię by przewidywać następny element.
-        """
-
-        # transitions to słownik słowników mówiący ile razy przechodzimy zjednej historii do kolejnego zdarzenia
-        transitions = {}
-        # To ile będzie kluczy w pierwszym słowniku zalezy od głębokosci historii i liczby unikatowych kombinacji id
-        # To ile będzie kluczy w drugich słownikach zalezy od liczby unikatowych id
-
-        totalNextSum = 0
-        for seq in self.allSequences:
-            if len(seq) >= historyLength+1:
-                # Początek sekwencji to start
-                for i in range(historyLength, len(seq)):
-                    keyName = "|".join([str(x) for x in seq[i-historyLength:i]])
-                    # Znajdujemy słownik z przejściami dla danej historii.
-                    val = transitions.get(keyName, {})
-                    # Aktualizujemy słownik dodajć 1
-                    val[seq[i]] = val.get(seq[i], 0)+1
-                    totalNextSum +=1
-                    # Aktualizujemy słownik w głównym słowniku
-                    transitions[keyName] = val
-        # Skoro już mamy wszystkie zdarzenia to wiemy ile razy która sekwencja będzie startowa
-        # w tradycyjnym podejściu markowa jesteśmy pewni gdzie jest początek sekwencji
-        # tutaj nie możemy być pewni więc zakładamy że może być w dowolnym miejscu
-
-        # na początku będzie tyle "starterów" sekwencji ile mamy takich fragmentów w historii
-        sequences = []
-        for key, value in transitions.items():
-            # key to stringowe id sekwencji
-            # value to słownik z przejściami
-            # value.values() zawiera liczebności
-            # dana sekwencja key wystąpiła tyle razy co suma wartości liczników w wartościach
-            sequences.append((key.split("|"), sum(value.values())))
-
-        for k in range(targetLength-historyLength):
-            newSequences = []
-            # Pogłębiamy sekwencje probabilistycznie
-            for seq, number in sequences:
-                # 
-                keyName = "|".join([str(x) for x in seq[-historyLength:]])
-                # Znajdujemy słownik z przejściami dla danej historii.
-                val = transitions.get(keyName, {})
-                total = sum(val.values())
-
-                for nextEl, numberNext in val.items():
-                    if (number*numberNext/total)/totalNextSum > shareLimit:
-                        newSequences.append((seq+[str(nextEl)], number*numberNext/total))
-            sequences = newSequences
-
-        # Normalizujemy i sortujemy
-        self.markov_not_norm = sorted([xy for xy in sequences], reverse=True, key=lambda x: x[1])
-        self.markov = [(x, y / totalNextSum) for x, y in self.markov_not_norm]
-        self.markovMapped = [([self.idsMapRev[int(z)] for z in x], y / totalNextSum) for x, y in self.markov_not_norm]
-        return self.markovMapped
-
-    def analyze_markov_fold(self, historyLength=1, targetLength=5, shareLimit=0.0001,
-                             foldLoops=False, foldMaxElements=1, debug=False):
+    def analyze_markov(self, historyLength = 1, targetLength = 5, shareLimit = 0.0001,
+                        foldLoops=False, foldMaxElements=1, debug=False):
         """
         Funkcja która wykona analizę sekwencji. Funkcja zakłada, że id sekwencji już zostały przygotowane:
         historyLength: dla jak wielu elementów bierzemy historię by przewidywać następny element.
@@ -482,7 +429,7 @@
             if len(seq) >= historyLength + 1:
                 # Początek sekwencji to start
                 for i in range(historyLength, len(seq)):
-                    keyName = "|".join([str(x) for x in seq[i - historyLength:i]])
+                    keyName = "|".join(str(x) for x in seq[i - historyLength:i])
                     # Znajdujemy słownik z przejściami dla danej historii.
                     val = transitions.get(keyName, {})
                     # Aktualizujemy słownik dodajć 1
@@ -511,7 +458,7 @@
             # Pogłębiamy sekwencje probabilistycznie
             for seq, number in sequences:
                 #
-                keyName = "|".join([str(x) for x in seq[-historyLength:]])
+                keyName = "|".join(str(x) for x in seq[-historyLength:])
                 # Znajdujemy słownik z przejściami dla danej historii.
                 val = transitions.get(keyName, {})
                 total = sum(val.values())
@@ -530,19 +477,15 @@
                     for foldWidth in range(1, foldMaxElements + 1):
                         # Zataczamy pętlę jeżeli mamy {Z}XYX, gdzie:
                         # Z - cokolwiek wcześniej
-                        # X ma szerokość historyLength
+                        # X ma szerokość 1
                         # zaś Y ma szerokość określoną w foldWidth maksymalnie foldMaxElements
-                        if len(seq) >= (2 * historyLength + foldWidth):
-                            #                             print(seq,
-                            #                                   seq[-(2*historyLength+foldWidth):-(historyLength+foldWidth)],
-                            #                                  seq[-historyLength:],
-                            #                                  seq[-(2*historyLength+foldWidth):-(historyLength+foldWidth)] == seq[-historyLength:])
-                            if seq[-(2 * historyLength + foldWidth):-(historyLength + foldWidth)] == seq[-historyLength:]:
+                        if len(seq) >= (2 + foldWidth):
+                            if seq[-(2 + foldWidth):-(1 + foldWidth)] == seq[-1:]:
                                 # Jest pętle, usuwamy sekwencję (nie dodajemy)
                                 # Sekwencję przenosimy do listę pętli
                                 # Pierwszy element to ZXY - potrzebny do zaraportowania
                                 # Drugi to ZX - potrzebny do modyfikacji liczebności
-                                newLoops.append((seq.copy(), seq[:-(historyLength + foldWidth)], number))
+                                newLoops.append((seq.copy(), seq[:-(1 + foldWidth)], number))
                                 seqIsLoop = True
                                 # breakujemy fora  bo jak jest lupem n to nei ma sensu szukać n+1
                                 break
@@ -586,6 +529,27 @@
 
                 if debug:
                     print("newLoopsCounts", newLoopsCounts[0:10])
+                # Jeżeli okazuje się że są pętle, których nie ma na co przełożyć to musimy je dać spowrotem na liste sekwencji
+                # Chyba się nie da tego zrobić prościej poniewaz musimy ro zrobić nawet jeżeli mamy dwie pętle
+                # Ale i tak nie ma tego na co przerzucić
+                # Przykładowo: 
+                idsToPop = set()
+                for i, (loop, loopID, loopNumber, subSeqLoopCounter) in enumerate(newLoopsCounts):
+                    if subSeqLoopCounter == 0:
+                        idsToPop.add(i)
+                        newSequences.append((loop, loopNumber))
+                        # To jest pętla której nie ma na co przerzucić
+                        # Dodajmy ją więc do sekwencji
+                # Jeżeli coś trafiło na liste sekwencji to musi zniknąć z listy loopsów
+                newLoopsCounts = [x for i, x in enumerate(newLoopsCounts) if i not in idsToPop]
+                
+                # Może się okazać, że znów nie mamy żadnych loopsów.
+                if len(newLoops)==0:
+                    # Jeżeli to spełnione to ekwiwalent zakończ iteracji fora z Duża pętla
+                    # Jeżeli nie znaleźlismy żadnej pętli to nie ma co mielić
+                    # Przechodzimy do kolejnej iteracji
+                    sequences = newSequences.copy()
+                    continue
 
                 for i in seqId2mod:
                     seq, number = newSequences[i]
@@ -609,55 +573,102 @@
                 sequences = newSequences
 
         # Normalizujemy i sortujemy
-        self.markov_not_norm = sorted([xy for xy in sequences], reverse=True, key=lambda x: x[1])
-        self.markov = [(x, y / totalNextSum) for x, y in self.markov_not_norm]
-        self.markovMapped = [([self.idsMapRev[int(z)] for z in x], y / totalNextSum) for x, y in self.markov_not_norm]
-        self.markovLoopsMapped = [([self.idsMapRev[int(x)] for x in xx], [self.idsMapRev[int(y)] for y in yy], z, v) for xx, yy, z, v in self.markovLoops]
+        self.markovNotNorm = sorted(sequences, reverse=True, key=lambda x: x[1])
+        self.markov = [(x, y / totalNextSum) for x, y in self.markovNotNorm]
+        self.markovMapped = [(self.seq_id_to_path(x), y / totalNextSum) for x, y in self.markovNotNorm]
+        #self.markovLoopsMapped = [(self.seq_id_to_path(x), self.seq_id_to_path(y), z, v) for x, y, z, v in self.markovLoops]
         return self.markovMapped
 
+    def analyze(self, targetLength = 5, foldLoops=False, foldMaxElements=1, debug=False):
+        def foldSeq(seq, foldMaxElements=1, seqLoops = []):
+            for k in range(len(seq)):
+                for width in range(1, foldMaxElements+1):
 
-def find_sub_list(haystack, needle):
-    results = []
-    sll = len(needle)
-    for ind in (i for i, e in enumerate(haystack) if e == needle[0]):
-        if haystack[ind: ind + sll] == needle:
-            results.append(ind)
-    return results
+                    if len(seq) < k+2+width:
+                        continue
+                    # Sprawdzamy czy znaleźliśmy pętlę
+                    if seq[k:k+1] == seq[k+width+1:k+width+2]:
+                        # Jeżeli tak to musimy skasować pętlę:
+                        tmp = seq[0:k] + seq[k+width+1:]
+                        # Dodać tę pętlę do listy pętli tej sekwencji
+                        seqLoops.append((seq[:k+1], seq[k+1:k+width+2]))
+                        # Skoro znaleźlismy pętle to nie chcemy pracować już na oryginalnej sekwencji
+                        # Ale na takiej z wyciętą pętlą
+                        # Zwróćmy więc wynik pracy na wycętej sekwencji
+                        # Pamiętając o tym co już wycieliśmy
+                        return foldSeq(tmp, foldMaxElements, seqLoops)
+                # Jeżeli przeszliśmy przez całego for
+                # To w sekwencji nie znaleźlismy pętli
+                # Możemy zwrócić sekwencje i wcześniej znalezione
+            return seq, seqLoops
 
 
-def replace_all_sublists(haystack, needle, replacement, locs):
-    delta = len(replacement) - len(needle)
-    off = 0
-
-    for loc in locs:
-        bgn = loc + off
-        del haystack[bgn:bgn + len(needle)]
-        for i, r in enumerate(replacement):
-            haystack.insert(bgn + i, replacement[i])
-        off += delta
-
+        allSeqFolded = []
+        allLoops = []
+        if foldLoops:
+            for seq in self.allSequences:
+                # Tutaj z jakiegoś powodu musiałem podawać wartość dla seqLoops
+                folded, loops = foldSeq(seq, foldMaxElements=foldMaxElements, seqLoops=[])
+                allSeqFolded.append(folded)
+                allLoops.append(loops)
+        else:
+            allSeqFolded = self.allSequences
+        self.allLoops = allLoops.copy()
+        self.allSeqFolded = allSeqFolded.copy()
+        
 
-def replace_sublist(haystack, needle, replacement, bgn):
-    del haystack[bgn:bgn + len(needle)]
-    for i, r in enumerate(replacement):
-        haystack.insert(bgn + i, replacement[i])
-
+        seqCounts = dict()
+        self.seqMap = dict()
+        for i, seq in enumerate(allSeqFolded):
+            for k in range(max(1, len(seq)-targetLength+1)):
+                tmp = seq[k:k+targetLength]
+                key = "|".join(str(x) for x in tmp)
+                # Zliczamy wszystkie subsekwencje
+                seqCounts[key] = seqCounts.get(key, 0)+1
+                # Na wszelki wypadek zapiszmy z której oryginalnej sekwencji są tesubsekwencje
+                # Będziemy mogli łatwo znaleźć "źródło"
+                tmpl = self.seqMap.get(key, [])
+                tmpl.append(i)
+                self.seqMap[key] = tmpl.copy()
+        tmp = sorted(list(Counter(seqCounts).items()), key=lambda x: x[1], reverse=True)
+        totalN = sum(y for x,y in tmp)
+        
+        self.analyzedNotNorm = [(x.split("|"), y) for x,y in tmp]
+        self.analyzed = [(x, y / totalN) for x, y in self.analyzedNotNorm]
+        self.analyzedMapped = [(self.seq_id_to_path(x), y / totalN) for x, y in self.analyzedNotNorm]
+        return self.analyzedMapped
 
-def expand(seqs, folds):
-    # print('{\n"seqs" :', json.dumps(seqs), ",")
-    # print('"folds": ', json.dumps(folds), "\n}\n")
-    results = []
-    for seq in seqs:
-        results.append(seq)
-        # print("processing ", seq)
-        for repl, needle, x, y in folds:
-            locs = find_sub_list(seq, needle)
-            if len(locs) > 0:
-                newseq = seq.copy()
-                replace_sublist(newseq, needle, repl, locs[0])
-                results.append(newseq)
-                # print(seq, needle, locs, repl, ' => ', newseq)
-    return results
+    def get_unfolded(self, needle):
+        unfolded = set()
+        needleL= len(needle)
+        for k in self.seqMap["|".join(str(x) for x in needle)]:
+            seqFolded = self.allSeqFolded[k]
+            seq = self.allSequences[k]
+            loops = self.allLoops[k]
+
+            positions = []
+            for pos in range(len(seqFolded) - needleL + 1):
+                if seqFolded[pos:pos+needleL] == needle:
+                    positions.append((pos, pos + needleL))
+            newPositions = positions.copy()
+
+            for loop, appendix in loops:
+                pos = len(loop)
+                width = len(appendix)
+                for i, (start, finish) in enumerate(positions):
+                    nstart, nfisnish = newPositions[i]
+                    if start > pos:
+                        nstart += width
+                    if finish >= pos:
+                        nfisnish += width
+                    newPositions[i] = (nstart, nfisnish)
+            for start, finish in newPositions:
+                unfolded.add("|".join(str(x) for x in seq[start:finish]))
+        unfolded = [x.split("|") for x in unfolded]
+        return unfolded
+
+    def seq_id_to_path(self, seq):
+        return [self.idsMapRev[int(z)] for z in seq]
 
 
 if __name__ == "__main__":
@@ -693,16 +704,29 @@
     for length in range(args.lmin, args.lmax + 1):
         if args.fold:
             suffix = 'f'
-            res = analyzer.analyze_markov_fold(targetLength=length, foldLoops=True)
+            res = analyzer.analyze(targetLength=length, foldLoops=True)
             seqs_prob = [(s, prob) for s, prob in res if prob >= args.pmin]
             seqs = [s for s, prob in res if prob >= args.pmin]
-            loops = analyzer.markovLoopsMapped
-            seqs_exp = expand(seqs, loops)
+            seqs_id = [(s, prob) for s, prob in analyzer.analyzed if prob >= args.pmin]
+
+            unfolded = []
+            num_unfolded = 0
+            for (s, prob) in seqs_id:
+                seq_mapped = analyzer.seq_id_to_path(s)
+                si = [int(x) for x in s]
+                unfolded_ids = analyzer.get_unfolded(si)
+                unfolded_mapped = [analyzer.seq_id_to_path(x) for x in unfolded_ids]
+                unfolded.append({
+                    'sequence': seq_mapped,
+                    'prob': prob,
+                    'unfolded': unfolded_mapped
+                })
+                num_unfolded += len(unfolded_ids)
+
             stats = {
                 'seqCount': len(res),
                 'seqCountP': len(seqs),
-                'loopCount': len(loops),
-                'expandedCount': len(seqs_exp)
+                'unfoldedCount': num_unfolded
             }
             seqs = seqs[:args.maxSeq]
             seqs_prob = seqs_prob[:args.maxSeq]
@@ -711,7 +735,7 @@
             res = analyzer.analyze_markov(targetLength=length)
             seqs_prob = [(s, prob) for s, prob in res if prob >= args.pmin]
             seqs = [s for s, prob in res if prob >= args.pmin]
-            loops = None
+            unfolded = None
             stats = {
                 'seqCount': len(res),
                 'seqCountP': len(seqs)
@@ -729,6 +753,6 @@
         with open("seq_L{}{}_stats.json".format(length, suffix), "w") as f:
             json.dump(stats, f)
         # input to next stage
-        if loops:
-            with open("seq_L{}{}_loops.json".format(length, suffix), "w") as f:
-                json.dump(loops, f)
+        if unfolded:
+            with open("seq_L{}{}_unfolded.json".format(length, suffix), "w") as f:
+                json.dump(unfolded, f)