Объединение нескольких больших структур данных разных классов в Python; Как я могу объединять и хранить нужные мне данные, уменьшая при этом использование памяти?
В чем дело
Я собираю данные с нескольких тысяч сетевых устройств каждые несколько минут в Python 2.7.8 через пакет netsnmp
, Я также использую fastsnmpy
чтобы я мог получить доступ к (более эффективной) команде Net-SNMP snmpbulkwalk
,
Я пытаюсь сократить объем памяти, используемый моим сценарием. Я запускаю три экземпляра одного и того же сценария, который спит в течение двух минут, а затем запрашивает у всех устройств данные, которые нам нужны. Когда я создал оригинальный скрипт в bash
они будут использовать менее 500 МБ, когда активны одновременно. Однако, когда я преобразовал это в Python, каждый экземпляр занимает 4 ГБ каждый, что указывает (мне), что мои структуры данных должны управляться более эффективно. Даже в режиме ожидания они потребляют всего 4 ГБ.
Код активности
Мой сценарий начинается с создания списка, в котором я открываю файл и добавляю имя хоста наших целевых устройств в виде отдельных значений. Обычно они содержат от 80 до 1200 имен.
expand = []
f = open(self.deviceList, 'r')
for line in f:
line = line.strip()
expand.append(line)
Оттуда я настраиваю сеансы SNMP и выполняю запросы
expandsession = SnmpSession ( timeout = 1000000 ,
retries = 1, # I slightly modified the original fastsnmpy
verbose = debug, # to reduce verbose messages and limit
oidlist = var, # the number of attempts to reach devices
targets = expand,
community = 'expand'
)
expandresults = expandsession.multiwalk(mode = 'bulkwalk')
Из-за того, как ведут себя оба пакета SNMP, ответы устройства анализируются в списки и сохраняются в одну гигантскую структуру данных. Например,
for output in expandresults:
print ouput.hostname, output.iid, output.val
#
host1 1 1
host1 2 2
host1 3 3
host2 1 4
host2 2 5
host2 3 6
# Object 'output' itself cannot be printed directly; the value returned from this is obscure
...
Мне приходится перебирать каждый ответ, объединять связанные данные, а затем выводить полный ответ каждого устройства. Это немного сложно. Например,
host1,1,2,3
host2,4,5,6
host3,7,8,9,10,11,12
host4,13,14
host5,15,16,17,18
...
Каждое устройство имеет различное количество ответов. Я не могу зацикливаться на ожидании, что каждое устройство, имеющее одинаковое произвольное количество значений, будет объединено в строку для записи в CSV.
Как я обращаюсь с данными
Я считаю, что именно здесь я потребляю много памяти, но я не могу решить, как упростить процесс, одновременно удаляя посещенные данные.
expandarrays = dict()
for output in expandresults:
if output.val is not None:
if output.hostname in expandarrays:
expandarrays[output.hostname] += ',' + output.val
else:
expandarrays[output.hostname] = ',' + output.val
for key in expandarrays:
self.WriteOut(key,expandarrays[key])
В настоящее время я создаю новый словарь, проверяю, что ответ устройства не является нулевым, затем добавляю значение ответа в строку, которая будет использоваться для записи в файл CSV.
Проблема в том, что я клонирую существующий словарь, то есть я использую в два раза больше системной памяти. Я хотел бы удалить значения, которые я посетил в expandresults
когда я перевожу их expandarrays
так что я не использую столько ОЗУ. Есть ли эффективный способ сделать это? Есть ли лучший способ уменьшить сложность моего кода, чтобы было легче следовать?
Виновник
Спасибо тем, кто ответил. Для тех, кто в будущем столкнется с этой темой из-за подобных проблем: fastsnmpy
Пакет является виновником широкого использования системной памяти. multiwalk()
Функция создает поток для каждого хоста, но делает это сразу, а не устанавливает какой-то верхний предел. Поскольку каждый экземпляр моего скрипта обрабатывал до 1200 устройств, это означало, что 1200 потоков были созданы и поставлены в очередь всего за несколько секунд. С использованием bulkwalk()
Функция была медленнее, но все еще достаточно быстрой, чтобы удовлетворить мои потребности. Разница между ними составляла 4 ГБ против 250 МБ (использования системной памяти).
3 ответа
Если ответы устройства в порядке и сгруппированы по хосту, то вам не нужен словарь, только три списка:
last_host = None
hosts = [] # the list of hosts
host_responses = [] # the list of responses for each host
responses = []
for output in expandresults:
if output.val is not None:
if output.hostname != last_host: # new host
if last_host: # only append host_responses after a new host
host_responses.append(responses)
hosts.append(output.hostname)
responses = [output.val] # start the new list of responses
last_host = output.hostname
else: # same host, append the response
responses.append(output.val)
host_responses.append(responses)
for host, responses in zip(hosts, host_responses):
self.WriteOut(host, ','.join(responses))
Потребление памяти было вызвано созданием нескольких рабочих несвязанным образом.
Я обновил fastsnmpy (последняя версия 1.2.1) и загрузил его в PyPi. Вы можете выполнить поиск из "Pytsi" для "fastsnmpy" или получить его прямо со страницы PyPi здесь, на FastSNMPy.
Только что закончил обновлять документы и разместил их на странице проекта в fastSNMPy DOCS
В основном я здесь заменил более раннюю модель несвязанных рабочих на пул процессов из многопроцессорных. Это может быть передано в качестве аргумента или по умолчанию 1.
Теперь у вас есть только 2 метода для простоты. snmpwalk(процессы =n) и snmpbulkwalk(процессы =n)
Вы не должны больше видеть проблему с памятью. Если вы это сделаете, пожалуйста, пинг меня на GitHub.
Возможно, вам будет проще выяснить, куда идет память, используя профилировщик:
https://pypi.python.org/pypi/memory_profiler
Кроме того, если вы уже настраиваете классы fastsnmpy, вы можете просто изменить реализацию так, чтобы результаты, основанные на словаре, объединялись для вас вместо того, чтобы сначала создать гигантский список.
Как долго вы держитесь за сеанс? Список результатов будет расти бесконечно, если вы будете использовать его повторно.