Mercurial > stress-tester
changeset 1217:83f0366550e1
new flow script
author | Devel 1 |
---|---|
date | Wed, 24 Jun 2020 13:27:52 +0200 |
parents | c4f9e819ebe0 |
children | bb727af8a7ee |
files | stress-tester/src/main/resources/flow_analyzer.py stress-tester/src/main/resources/flow_analyzer_fold.py |
diffstat | 2 files changed, 641 insertions(+), 3 deletions(-) [+] |
line wrap: on
line diff
--- a/stress-tester/src/main/resources/flow_analyzer.py Wed Jun 24 09:32:09 2020 +0200 +++ b/stress-tester/src/main/resources/flow_analyzer.py Wed Jun 24 13:27:52 2020 +0200 @@ -469,7 +469,8 @@ return self.markovMapped - def analyze_markov2(self, historyLength = 1, targetLength = 5, shareLimit = 0.0001, foldSequences=False, foldDepth=2): + def analyze_markov2(self, historyLength = 1, targetLength = 5, shareLimit = 0.0001, + foldSequences=False, foldDepth=2): ''' 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. @@ -538,9 +539,8 @@ analyzer.get_files_list(pathString=sys.argv[1]) analyzer.get_events_line_csv(dateFormat='%Y-%m-%d_%H_%M_%S_%f') #yyyy-MM-dd_HH_mm_ss_SSS000 analyzer.prep_data() - # print(analyzer.data.iloc[0]) - # analyzer.explore() analyzer.prep_sequences(dropDuplicates=True) + for length in range(3, 8): name = "seq_L{}.json".format(length) seq = analyzer.analyze_markov(targetLength=length)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/stress-tester/src/main/resources/flow_analyzer_fold.py Wed Jun 24 13:27:52 2020 +0200 @@ -0,0 +1,638 @@ +import csv +import re + +import pandas as pd +import ujson as json + +from collections import Counter +from itertools import groupby +from pathlib import Path +from urllib import parse + + +class flowAnalyzer: + def get_files_list(self, pathString="", glob=False, globString="", disallowedSuffixex = [".gz", ".zip"], debug=False): + '''Funkcja, której celem jest przygotowanie listy plików z logami do wczytania + Może działać na dwa sposoby przez podanie po prostu ścieżki pliku z logami + lub przez znalezienie wszystkich plików przez glob + ''' + # Tutaj stworzona zostanie na nowo lista plików w obiekcie + self.fileList = [] + + if glob: + # Wykorzystujemy pathlib aby lepiej działać między systemowo + path = Path(pathString) + for filename in path.glob(globString): + # Upewniamy się, że plik nie ma zakazanego rozszerzenia + if filename.suffix not in disallowedSuffixex: + self.fileList.append(filename) + else: + self.fileList.append(pathString) + + if debug: + print(self.fileList) + + def extract_lines(self, disallowedStrings, debug = False): + ''' + Czytanie wszystkich plików linia po linii. + Dodajemy sobie tylko proste warunki sprawdzenia poprawności linii przez zawieranie się stringów + ''' + # nie robimy self.lines, żeby nie marnować pamięci przy większych plikach i nie musieć explicite kasować + lines = [] + skippedLines = 0 + for file in self.fileList: + with open(file) as fp: + for line in fp: + # Sprawdzamy czy ta linijka nie zawiera jakiś "niedozwolonych fragmentów" + # UWAGA, poniższy if na pewno nie jest królem wydajności + if any([x in line for x in disallowedStrings]): + skippedLines +=1 + continue + lines.append(line.rstrip('\n')) + if debug: + print("Liczba pominiętych linii:", skippedLines) + return lines + + def get_events_line_regex(self, rePatter, disallowedStrings = ["system overloaded"], + dateFormat = "%d/%b/%Y:%H:%M:%S", debug = False): + ''' + Funkcja, której celem jest parsowanie logów z założeniem, że + możemy przejść regexem grupowym każdą linijkę każdego pliku na naszej liści + + UWAGA: zastanawiałem się czy lepsze jest takie podejście czy może robienie regexów linia po linii. + Zostawiłem takie (gdzie najpierw zbieramy wszystkie linie, a potem robimy regexpy) dla czytelności. + Wszystko jedno jak to będzie w praktyce. + ''' + lines = self.extract_lines(disallowedStrings=disallowedStrings, debug=debug) + + pat = re.compile(rePatter) + rows = [] + for line in lines: + row = re.match(pat, line) + tdict = row.groupdict() + rows.append([tdict["id"], tdict["datetime"], tdict["requestMethod"], tdict["url"], tdict["protocol"], tdict["status"], tdict["agent"]]) + self.data = df = pd.DataFrame(rows, columns=["id", "datetime", "requestMethod", "url", "protocol", "status", "agent"]) + + # 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ć + self.data["datetime"] = pd.to_datetime(self.data.datetime, format=dateFormat) + if debug: + print("Wczytana liczba wierszy i kolumn: ", self.data.shape) + + def get_events_line_json(self, disallowedStrings = ["system overloaded"], + dateFormat = None, columnMap = {}, debug = False): + ''' + Funkcja, której celem jest parsowanie logów z założeniem, że na początku jest data, a dalej spacją rozdzielone datetime + + UWAGA: zastanawiałem się czy lepsze jest takie podejście czy może robienie regexów linia po linii. + Zostawiłem takie (gdzie najpierw zbieramy wszystkie linie, a potem robimy regexpy) dla czytelności. + Wszystko jedno jak to będzie w praktyce. + ''' + lines = self.extract_lines(disallowedStrings=disallowedStrings, debug=debug) + + rows = [] + for line in lines: + line = json.loads(line) + try: + line["httpreqheaders"] = line["httpreqheaders"][re.search("SESSION=", line["httpreqheaders"]).end():] + line["httpreqheaders"] = line["httpreqheaders"][:re.search("; ", line["httpreqheaders"]).start()] + except: + if debug: + print(line) + continue + rows.append([ + line[columnMap.get("id", "id")], + line[columnMap.get("datetime", "datetime")], + line[columnMap.get("requestMethod", "requestMethod")], + line[columnMap.get("url", "url")], + line[columnMap.get("protocol", "protocol")], + line[columnMap.get("status", "status")], + line[columnMap.get("agent", "agent")] + ]) + + self.data = df = pd.DataFrame(rows, columns=["id", "datetime", "requestMethod", "url", "protocol", "status", "agent"]) + + # Linijskan ap otrzeby 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ć + self.data["datetime"] = pd.to_datetime(self.data.datetime, format=dateFormat) + if debug: + print("Wczytana liczba wierszy i kolumn: ", self.data.shape) + + def get_events_line_csv(self, disallowedStrings = ["system overloaded"], + dateFormat = None, columnMap = {}, debug = False): + lines = self.extract_lines(disallowedStrings=disallowedStrings, debug=debug) + + rows = [] + for row in csv.reader(lines, delimiter=',', quotechar='"'): + rows.append(row) + # print(row) + self.data = df = pd.DataFrame(rows, columns=["id", "datetime", "requestMethod", "url", "protocol", "status", "agent"]) + + # Zmiana tekstowego formatu daty na datę pandasową, żeby można bylo skutecznie sortować + self.data["datetime"] = pd.to_datetime(self.data.datetime, format=dateFormat) + if debug: + print("Wczytana liczba wierszy i kolumn: ", self.data.shape) + + def get_data_excel(self, filename="", columnMap = {}): + df = pd.read_excel(filename) + df = df.rename(columns=columnMap) + df = df[columnMap.values()] + df["datetime"] = pd.to_datetime(df.datetime) + self.data = df + + 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: + self.cleanRules = cleanRules + + + # Rozbijamy ścieżkę na abzowy url i argumenty przez znak "?" + + temp = self.data['url'].str.split('?', 1, expand=True) + + + # Konwersja na stringi + self.data["pathBase"] = temp[0].astype(str) + if temp.shape[1]<2: + self.data["pathArgs"] = "" + else: + self.data["pathArgs"] = temp[1].fillna("").astype(str) + + + # Proste czyszczenie i normalizowanie stringów (url, request method) + self.data["pathArgs"] = self.data["pathArgs"].str.lower().str.strip() + self.data["pathBase"] = self.data["pathBase"].str.lower().str.strip() + self.data["requestMethod"] = self.data["requestMethod"].str.lower().str.strip() + + if dropMissing: + shapeBefore = self.data.shape + self.data = self.data.dropna(subset=["id", "datetime", "pathBase"]) + if debug: + print("Rozmiar przed missingami:", shapeBefore) + print("Rozmiar po missingach:", self.data.shape) + + if cleanRows: + shapeBefore = self.data.shape + idsToRemove = [] + if debug: + print("Liczba wierszy spełniających regułę:") + for rule in self.cleanRules: + # UWAGA + # Poniższa linia nie jest na pewno bardzo wydajna, ale jest prosta i szybka + # Sposób czyszczenia to detal który jest do dopracowania / zmiany wedle uznania + temp = self.data.loc[self.data.pathBase.str.contains(rule, case=False, regex=False)].index.tolist() + if debug: + print(rule, len(temp)) + idsToRemove.extend(temp.copy()) + self.data = self.data.drop(idsToRemove) + + if debug: + print("Rozmiar przed czyszczeniem:", shapeBefore) + print("Rozmiar po czyszczeniu:", self.data.shape) + + def clean_base(self, maxPathDepth=False, whiteListParts = False): + if maxPathDepth: + splitted = [x.split("/")[0:maxPathDepth] for x in self.data["pathBase"]] + # Rozbijam na potrzeby czytelności i ewentualnie wydajności. + # Jeżeli jest max to najpierw go ograniczamy, jezlei robimy whitelist, ale bez max to tez musimy splitować + # Jeżeli jest tylko whitelist to nie znamy wartosci maxPathDepth + # Można tez przyjąć maxPathDept 1000 jako wartośc domyślną i mieć w jednej linii + elif whiteListParts: + splitted = [x.split("/") for x in self.data["pathBase"]] + + # Jeżeli white list to filtrujemy + if whiteListParts: + # splitted to lista list + # ulr to element splitted + # part to element url + splitted = [part for part in url if part in whiteListParts for url in splitted] + + # Musimy teraz "skleić" + if maxPathDepth or whiteListParts: + filteredBase = ["/".join(parts) for parts in splitted] + else: + filteredBase = self.data["pathBase"].tolist() + return filteredBase + + def clean_args(self, whiteListArgs = False, blacklistArgs=False): + + args = self.data["pathArgs"].map(parse.parse_qsl) + if whiteListArgs: + args1 = [] + for argList in args: + args1.append([(x,y) for x,y in argList if x in whiteListArgs]) + # Cały for i nadpisanie mogą być w jednej linii, ale wtedy kod jest mniej czytelny + # Możliwe też, że w implementacje w jave whitelist będzie to wczesniej już na etapie parsowania + + args = args1 + elif blacklistArgs: + args1 = [] + for argList in args: + args1.append([(x,y) for x,y in argList if x not in blacklistArgs]) + # Cały for i nadpisanie mogą być w jednej linii, ale wtedy kod jest mniej czytelny + # Możliwe też, że w implementacje w jave blacklist będzie to wczesniej już na etapie parsowania + + args = args1 + return args + + def explore(self, topN = 20, maxPathDepth=False, whiteListParts = False, whiteListArgs = False, blacklistArgs=False): + ''' + Zwraca statystyki dotyczące ścieżek, argumentów i wartości. + Pozwala zrozumieć co powinniśmy rozumieć przez unikatowy "krok" i z jakimi ustawieniami dokonać analizy + + maxPathDepth : mówi jak wiele elementów urla bierzemy pod uwagę (jaka maksymalna głebokość) + allowedParts : mówi jakie elementy urla są dozwolone. + + Filtrowanie argumentów pozwala na to aby nie brać pod uwage jakiś argumentów w analizie lub brać tylko wybrane + Jeżeli whiteListArgs jest zbiorem lub listą, to blacklistArgs nie jest brane pod uwagę + ''' + + filteredBase = self.clean_base(maxPathDepth, whiteListParts) + + countBase = sorted(list(Counter(filteredBase).items()), key=lambda x: x[1], reverse=True) + print("Top base url:") + for url in countBase[0:topN]: + print(url) + + args = self.clean_args(whiteListArgs, blacklistArgs) + + + urlArgs = [] + for url, argList in zip(filteredBase, args): + urlArgs.append(url+"|"+"_".join(sorted([x for x,y in argList]))) + + countUrlArg = sorted(list(Counter(urlArgs).items()), key=lambda x: x[1], reverse=True) + print("\n\nTop base url with args") + for value in countUrlArg[0:topN]: + print(value) + + + argPairs = sum(args, []) + argsOnly = [x for x,y in argPairs] + + countArgs = sorted(list(Counter(argsOnly).items()), key=lambda x: x[1], reverse=True) + print("\n\nTop args:") + for value in countArgs[0:topN]: + print(value) + + countPairs = sorted(list(Counter(argPairs).items()), key=lambda x: x[1], reverse=True) + print("\n\nTop pairs") + for value in countPairs[0:topN]: + print(value) + + def prep_sequences(self, useId=True, + useBase=True, maxPathDepth=False, whiteListParts = False, + useArgs = False, whiteListArgs = False, blacklistArgs=False, + useValues=False, + perUser = True, maxTimeDelta = 600, dropDuplicates = False, + groupOthers = False, othersLimit = 0.01, + debug = False): + ''' + Funkcja musi przygotować identyfikator "zdarzenia". + Każde "zdarzenie" może być identyfikowane przez dowolne kombinacje urli, czesci argumentów i wartości + Tutaj można mieć jeszcze więcej pomysłów, ale chyba na tym etapie wystarczy tyle. + + useId mówi czy mamy jakieś "id" które kontrolumy i czy chcemy go używać, czy moze lecieć po wszystkim chronologicznie. + + Bo można definiować jakieś white listy (na całe fragmenty nie tylko czesci) regexy na czesci itp. + Podobnie z argumentami. + W tej chwili możemy mieć: + * base + * base+args + * args + * args+values + * base+args+values + + Przy czym argsy i partsyBasa możemy kontrolować + + perUser: czy łaczyć wszystkie zdarzenia w jedną sekwencję po czasie czy działać indywidualnie epr user. + maxTimeDelta: sekundy maksymalny czas w ramach jednego "usera", + który może minąć aby kolejne zdrzenia pozostały w jednej sekwencji + dropDuplicates: czy jeżeli w sekwencja mamy ABBDCA to zamieniamy je na ABDCA przed rozpoczeciem analizy. + Duplikaty rozumiemy jako dwa zdarzenia tego samego usera w ramac htej samej sesji czasowej. + groupOthers: Pozwala na to aby wszystkie id rzadko wystepujące zamienić na "OTHER" + othersLimit: procentowa wartość (0-1) poniżej której zamieniamy na "OTHER" + + ''' + ids = [] + + if useBase: + filteredBase = self.clean_base(maxPathDepth, whiteListParts) + else: + filteredBase = ["url" for x in self.data["pathBase"]] + + + if useArgs: + args = self.clean_args(whiteListArgs, blacklistArgs) + + if useValues: + for url, argList in zip(filteredBase, args): + ids.append(url+"|"+"_".join(sorted([x+"+"+y for x,y in argList]))) + else: + for url, argList in zip(filteredBase, args): + ids.append(url+"|"+"_".join(sorted([x for x,y in argList]))) + + else: + ids = filteredBase + + if groupOthers: + icounts = dict(Counter(ids)) + icounts = {k:v/len(ids) for k,v in icounts.items()} + ids = [x if icounts[x] > othersLimit else "OTHER" for x in ids ] + + # Mapujemy "nazwy" sekwencji na integery, żeby nei trzymać w pamięci miliona niepotrzebnych stringów + uniqueIds = sorted(list(set(ids))) + self.idsMap = {idi:i for i, idi in enumerate(uniqueIds)} + self.idsMapRev = {i:idi for i, idi in enumerate(uniqueIds)} + + # Tworzymy w naszym zbiorze data kolumnę z id sekwencji + # Zbór data zawiera informacje o czasie i "id" więc sekwencje będziemy grupować później. + self.data["idsn"] = [self.idsMap[idi] for idi in ids] + + if perUser: + # Pogrupujmy wiersze po id + groupped = self.data.groupby("id") + else: + # Jeżeli nie grupujemy to i tak stworzymy strukturę danych analogiczną + # do efektu grupowania (lista krotek z nazwą i danymi) + groupped = [("all", self.data)] + + + + # Przygotujmy sobie liste wszystkich sekwencji z uwzględnieniem usera i maxTimeDelta + allSequences = [] + for name, df in groupped: + # Sortujemy po czasie w gramach grupy ID + df = df.sort_values("datetime", ascending=True) + + # Liczymy sobie deltę czasu pomiędzy zdarzeniami + df['datetimeDelta'] = (df['datetime']-df['datetime'].shift()).fillna(pd.Timedelta(seconds=0)).dt.seconds + + for i, (seqId, timeDelta) in enumerate(zip(df["idsn"], df["datetimeDelta"])): + # Poniżej nei biję rekordu wydajności, ale czytelności: + if i ==0 : + # Jeżeli to nowa gruap danego id to tworzymy nową listę + newSeq = [seqId] + elif timeDelta < maxTimeDelta: + # Jeżeli kolejny wiersz mieści się w czasie maxTimeDelta to dodajemy do dotychczasowej listy + newSeq.append(seqId) + else: + # W przeciwnym wypadku zamykamy poprzednią listę dodając do całego zbioru + allSequences.append(newSeq) + # I od nowa rozpoczynamy sekwencję + newSeq = [seqId] + # Na koniec dodajemy ostatni element który został + allSequences.append(newSeq) + + if dropDuplicates: + # usuwanei duplikatów można by zrobić wcześniej n apotrzeby zwiększenia wydajności + allSequences = [[k for k,g in groupby(seq)] for seq in allSequences] + + if debug: + print(allSequences[0:20]) + + 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 = sorted([(x, y/totalNextSum) for x,y in sequences], reverse=True, key=lambda x:x[1]) + self.markovMapped = sorted([([self.idsMapRev[int(z)] for z in x], y/totalNextSum) for x,y in sequences], reverse=True, key=lambda x:x[1]) + self.markov_not_norm = sorted([(x, y) for x,y in sequences], reverse=True, key=lambda x:x[1]) + return self.markovMapped + + + def analyze_markov2(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. + + ''' + + # 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()))) + + self.markovLoops = [] + + # Duża pętla + 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.copy() + if debug: + print("sequences", len(sequences), sequences[0:10]) + if foldLoops: + newSequences = [] + newLoops = [] + for seq, number in sequences: + seqIsLoop = False + for foldWidth in range(1, foldMaxElements+1): + # Zataczamy pętlę jeżeli mamy {Z}XYX, gdzie: + # Z - cokolwiek wcześniej + # X ma szerokość historyLength + # 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:]: + # 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)) + seqIsLoop = True + # breakujemy fora bo jak jest lupem n to nei ma sensu szukać n+1 + break + if not seqIsLoop: + # Nie ma pętli zostawiamy sekwencję nieruszoną + newSequences.append((seq, number)) + + # W tym miejscu każda sekwencja z sequences trafia albo na listę newSequences albo newLoops + # Poniżej robimy "dodawanie", którego nei trzeba robić jeżeli nei ma pętli + 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 + + if debug: + print("New loops", len(newLoops), newLoops[0:10]) + print("sequences", len(sequences), sequences[0:10]) + print("newSequences", len(newSequences), newSequences[0:10]) + + # Teraz musimy te liczby zabrane z pętli "zwrócić" tam gdzie się należą + # Zwroty musimy zrobić proporcjonalnie więc trzeba policzyć sumę subsekwencji + newLoopsCounts = [] + # Zapiszemy sobie te numery sekwencji które trzeba zmodyfikować, + # żeby nie lecieć niepotrzebnie kolejny raz po wszystkim + # docelowo może być tego sporo + seqId2mod = set() + + for loop, loopID, loopNumber in newLoops: + # subSeqLoopCounter to mianownik dla konkretnej pętli + # czyli liczba wszystkich sekwencji z pocątkiem ZX, ale nie ZXYX ani ZXQX + subSeqLoopCounter = 0 + # Znajdźmy wszystkie sekwencje ZX na które musimy rozdysponować loopNumber + for i, (seq, number) in enumerate(newSequences): + if loopID == seq[:len(loopID)]: + subSeqLoopCounter+=number + # Do tej sekwencji musimy dodać liczebność + seqId2mod.add(i) + newLoopsCounts.append((loop, loopID, loopNumber, subSeqLoopCounter)) + + if debug: + print("newLoopsCounts", newLoopsCounts[0:10]) + + for i in seqId2mod: + seq, number = newSequences[i] + # Musimy zrobić licznik bo do jednej sekwencji możemy musieć dodać kilka razy jak były dwie pętle + toAdd = 0 + for loop, loopID, loopNumber, subSeqLoopCounter in newLoopsCounts: + if loopID == seq[:len(loopID)]: + # Dodajemy taką część loopNumber (udział) + # Jaki jest udział tej sekwencji seq we wszystkich które się zaczynały + # od seq[:len(loopID)] (ZX) czyli subSeqLoopCounter + # number - liczba wystąpień sekwencji DO której dodajemy + toAdd += loopNumber * (number/subSeqLoopCounter) + if debug: + print(seq, number, toAdd) + # Dodajemy sekwencję do naszej listy ze zmodyfikowaną liczebnością + newSequences[i] = (seq,number+toAdd) + + self.markovLoops.extend(newLoopsCounts.copy()) + + # Nadpisujemy liste sekwencji nad którą pracujemy w pętli. + sequences = newSequences + + # Normalizujemy i sortujemy + self.markov = sorted([(x, y/totalNextSum) for x,y in sequences], reverse=True, key=lambda x:x[1]) + self.markovMapped = sorted([([self.idsMapRev[int(z)] for z in x], y/totalNextSum) for x,y in sequences], reverse=True, key=lambda x:x[1]) + self.markov_not_norm = sorted([(x, y) for x,y in sequences], reverse=True, key=lambda x:x[1]) + return self.markovMapped + + +if __name__ == "__main__": + import sys + if len(sys.argv) < 2: + raise Exception('Input expected') + + analyzer = flowAnalyzer() + analyzer.get_files_list(pathString=sys.argv[1]) + analyzer.get_events_line_csv(dateFormat='%Y-%m-%d_%H_%M_%S_%f') #yyyy-MM-dd_HH_mm_ss_SSS000 + analyzer.prep_data() + analyzer.prep_sequences(dropDuplicates=True) + + for length in range(3, 8): + name = "seq_L{}.json".format(length) + seq = analyzer.analyze_markov2(targetLength=length, foldLoops=True) + with open(name, "w") as f: + json.dump(seq, f) \ No newline at end of file