Время прочтения: 3 мин.
Чтобы не использовать код внутреннего приложения, для примера возьму одну из известных задач по анализу данных кадастровых участков на сайте Росреестра. У меня был список номеров по которым необходимо было найти адрес, проверить наличие и посчитать занимаемую площадь зданий на участке. Используя библиотеки selenium и opencv я написал программу для сбора информации и расчёта необходимых параметров участка. Код этого приложения я и буду исследовать при помощи нестандартного метода используя лог запуска приложения и построенный на его основе граф.
Для проведения исследования процесса выполнения программы необходимо получить лог файл запуска блоков кода. Можно добавить блок с логгером в каждый конструктор каждого создаваемого класса, но этот метод не подходит, так как нежелательно менять исходный код исследуемого приложения. Для анализа необходимо записать время запуска и время окончания запуска функции конструктора — для этого можно воспользоваться декоратором. Декоратор – это паттерн проектирования, который позволит расширить функциональность кода без его изменения.
Для исследуемой программы создам новый модуль, в котором необходимо объявить функцию декоратор. На вход подается функция, которую необходимо расширить, а внутри объявляется еще одна функция которая будет содержать логику. В данном случае это вызов метода записи информации в лог файл:
import logging
import logging
import datetime
def log(func):
def wrapper():
logging.info("{};{};{};{}".format(datetime.datetime.now(), func.__module__, func.__name__, "func start"))
func()
logging.info("{};{};{};{}".format(datetime.datetime.now(), func.__module__, func.__name__), "func end")
return wrapper
После этого просто подключаем модуль и используем декоратор для конструкторов наших классов:
import selenium
from selenium import webdriver
import os
import screenShot
import LogDecorator
class Map:
@log #Вызов декоратора
def __init__(self, webDriver, registryNumber):
self.driver = webDriver
self.registryNumber = str(registryNumber)
def getScreenShot(self, registryNumber):
...
Теперь, запустив программу, используя подборку кадастровых номеров, получаю дата сет адресов и площадей кадастровых участков и лог запуска. С последним буду работать далее для исследования.
Так как формат лога в функции декоратора был составлен таким образом, чтобы без проблем перевести файл лога в csv — перевожу файл и используя методы библиотеки pandas создаю на его основе дата фрейм:
import pandas as pd
# Загрузка лога в дата фрейм
# Для того чтобы построился граф надо переименовать колонки с параметрами в определенные библиотекой форматы:
# case:concept:name - номер кейса
# concept:name- наименование события
# time:timestamp - время события
df = pd.read_csv("./log_cad.csv")
df['dt_call']= pd.to_datetime(df['dt_call'])
df.rename(columns = {'dt_call': 'time:timestamp', 'iter':'case:concept:name'})
df['concept:name'] = df[['method', 'module', 'status']].agg(':'.join, axis=1)
df.head()
Далее используя библиотеку pm4py конвертирую дата фрейм в лог событий и используя эвристический алгоритм строю граф процесса:
import pm4py as pm
df = pm4py.format_dataframe(df, case_id='case:concept:name', activity_key='concept:name', timestamp_key='time:timestamp')
event_log = pm4py.convert_to_event_log(df)
heu_net = pm4py.discover_heuristics_net(event_log, dependency_threshold=0.99)
pm4py.view_heuristics_net(heu_net)
По графу процесса видно, что несколько классов были инициализированы несколько раз, если посмотреть на код, то можно увидеть, что классы Map и Filter инициализируются с различными параметрами, что не является отклонением в данном случае, а вот класс ScreenShot выбивается из общей структуры кода и вызывается несколько раз без необходимости:
def cropScreenShot(self, imageLinks):
images = []
for imageLink in imageLinks:
imageShot = screenShot.Image()
img = imageShot.cropImage(imageLink)
images.append(img)
return images
Данный метод можно использовать как дополнительный контроль, чтобы сохранять логику работы приложения и обезопасить себя от логических ошибок при написании кода.
Решая задачу данным методом мне удалось найти несколько кейсов по вызову конструктора внутри цикла, а также некоторые неисполняемые части кода.