Comments 35
Можете считать это глубоким имхо, но я должен это написать:
- сейчас рекомендовать Perl в качестве скриптового языка в сфере системного администрирования стоит только тем, кто уже знает Perl, и почему-то принципиально против изучения питона
- рекомендовать Go в качестве скриптового языка в сфере системного администрирования стоит только тем, кто в своих специфических задачах упирается в производительность, и не может найти подходящего модуля для питона
- всем остальным 99,9% системных администраторов всё-таки стоит взять питон
А я в статье и не призываю никого учить Perl и ничего против питона не имею.
В точку про производительность!
Скорее всего соглашусь с вами
А я в статье и не призываю никого учить Perl и ничего против питона не имею
Я просто счёл нужным оставить этот коммент на тот случай, если статью будет читать кто-то, кто пока не ориентируется в языках, но присматривает подходящий инструмент автоматизации )
Кстати, а нет ли у вас желания воспроизвести такие же скрипты на питоне (можно без замеров), чтобы можно было наглядно сравнить?
Хорошая мысль, я займусь этим в выходные.
Вот так получается на питоне:
import re
separator = re.compile('\s+')
with open("access.log") as log_file:
for line in log_file:
parts = re.split(separator, line)
if parts[8] == '500':
print(line)
import psycopg2
conn = psycopg2.connect(dbname='database', user='db_user', password='mypassword', host='localhost')
cursor = conn.cursor()
cursor.execute('SELECT deal_city_id, "ShortName", "FullName" FROM public.deal_city ORDER BY deal_city_id')
for row in cursor:
print(f"{row['deal_city_id']} {row['ShortName']} {row['FullName']}")
cursor.close()
conn.close()
import OpenSSL
import ssl
cert = ssl.get_server_certificate(('www.example.com', 443))
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
print(x509.get_subject())
print(x509.get_notBefore())
print(x509.get_notAfter())
import json
json_str = '''[
{"id":1,"name":"Larry"},
{"id":2,"name":"Robert"},
{"id":3,"name":"Rob"},
{"id":4,"name":"Ken"}
]'''
developers = json.loads(json_str)
for d in developers:
print(d)
Разбор логов
По умолчанию print ставит перевод строки, поэтому я чуток поправил вот так print(line , end='')
real 0m19,840s
user 0m18,834s
sys 0m1,006s
9728 python ./script.py
9728 python ./script.py
9728 python ./script.py
Работа с базой
У меня ошибка (
Traceback (most recent call last):
File "/home/pasha/src_my/habr_perl_golang/./task2/script.py", line 11, in <module>
print(f"{row['deal_city_id']} {row['ShortName']} {row['FullName']}")
~~~^^^^^^^^^^^^^^^^
TypeError: tuple indices must be integers or slices, not str
Пришлось поправить вывод результата вот так print(row)
real 0m2,338s
user 0m0,061s
sys 0m0,053s
19284 python ./script.py
20308 python ./script.py
Получение информации о сертефикате
real 0m0,475s
user 0m0,054s
sys 0m0,011s
25668 python ./script.py
вывод скрипта
<X509Name object '/C=US/ST=California/L=Los Angeles/O=Internet\xC2\xA0Corporation\xC2\xA0for\xC2\xA0Assigned\xC2\xA0Names\xC2\xA0and\xC2\xA0Numbers/CN=www.example.org'>
b'20230113000000Z'
b'20240213235959Z'
Работа с джсоном
Тут я прям сильно удивился простоте скрипта. Вы, наверное, не поняли сути задачи, ибо в массиве developers явно находятся не строки, а какие-то структуры, т.к. каждая строка вывода скрипта не является валидным json объектом.
вывод скрипта
{'id': 1, 'name': 'Larry'}
{'id': 2, 'name': 'Robert'}
{'id': 3, 'name': 'Rob'}
{'id': 4, 'name': 'Ken'}
В чём преимущества Python по сравнению с Perl? Perl - это де-факто стандарт языка системного администрирования.
perl навсегда в моем сердечке?
Компилировать regex конечно надо вне цикла, да и в целом тесты производительности с выводом на внешнее устройство такое себе.. Но perl удивил, да.
Компилировать regex конечно надо вне цикла
точно, а вы ведь правы
я проверил и Golang отработал быстрее (везде он чуток, но быстрее, елки зеленые :-)), но по памяти проиграл
real 0m15,796s
user 0m15,603s
sys 0m0,691s
8232 /tmp/go-build260055243/b001/exe/script
8300 /tmp/go-build260055243/b001/exe/script
8120 /tmp/go-build260055243/b001/exe/script
/занудаon/ Решение немножко в лоб в коде. Так будет шустрее. /занудаoff/
package main
import (
"bufio"
"fmt"
"log"
"os"
"regexp"
)
func main() {
file, err := os.Open("access.log")
if err != nil {
log.Fatalf("%s", err)
}
fileScanner := bufio.NewScanner(file)
re := regexp.MustCompile(`(?s)(?:.+?\s){8}(.+?)\s`)
for fileScanner.Scan() {
s := re.FindStringSubmatch(fileScanner.Text())
if s[1] == "500" {
fmt.Println(s)
}
}
}
Да, так работает быстрее
real 0m8,611s
user 0m8,331s
sys 0m0,539s
Но надо сделать оговорку, что в данной регулярке вы отбрасываете все, что идет после кода ответа сервера.
И если захватывать остаток строки вот так
re := regexp.MustCompile(`(?s)(?:.+?\s){8}(.+?)\s(.+)`)
то результат будет другой
real 0m14,968s
user 0m14,719s
sys 0m0,560s
В golang необязательно описывать весь json, можно только те поля что вас интересуют.
Я знаю, что так можно, но как раз в этом-то и заключается ключевое отличие. В Perl ты получаешь сразу весь десериализованный json, а в Golang ты должен его описать настолько подробно, насколько тебе надо прежде чем десериализовать. Этот момент понять было сложнее всего.
И да, у Perl то же есть песочница )
смысл сравнения изначально неправилен.
perl силён в операциях со строками, go - в многопоточности.
если в go работа исключительно со строками (или внешними сервисами) и без горутин - это признак неправильного выбора инструмента.
это не прямые конкуренты, а инструменты, заточенные каждый на свою область.
Соглашусь с вами - это не конкуренты, я этого и не утверждал. Просто мне было интересно, можно ли использовать Golang (не слишком ли он сложен) как сриптовый язык для простых нужд сисадмина. Да, горутины классные, я пробовал, но скажу вам, что для рядового сисадмина горутины почти не нужны, да и на Perl есть куча модулей позволяющих так же элегантно породить (fork) кучу воркеров. Конечно же fork не сравнится с горутинами, но например для опроса кучи сайтов можно использовать не воркеры, а асинхронно это сделать.
И да, для всего нужен свой инструмент и правильный сисадмин умеет его выбирать под задачу )
time grep -E "\s500\s" ./access.log
real 0m1,165s
user 0m1,051s
sys 0m0,114s
time awk '$9~500' ./access.log
real 0m1,258s
user 0m1,077s
sys 0m0,182s
если что, то в логе вот сколько строк
wc -l ./access.log
3984116 ./access.log
ripgrep
- не встречал такого (
ripgrep отсутвует в дефолтных дистрибутивах обычно, если это не какой-ниубдь gentoo.
`apt install ripgrep` должен сработать или через что вы там ставите. Если пользоваться также как и обычным грепом, то в среднем скорость х5 получается, иногда выше.
Если вывод никуда не перенаправляется - вывод ripgrep примерно вдвое медленее. Если отправлять его в /dev/null grep кажется вообще ничего не делает и выводит примерно нисколько времени. Если делать вывод в файл, то grep выдаёт
real 0m2.240s
user 0m2.032s
sys 0m0.204s
а rg
real 0m0.430s
user 0m0.312s
sys 0m0.112s
что в примерно равно среднему времени.
awk выдал что-то близкое к вашим цифрам
Данных генерил как-то так
import random
request_methods = ["GET", "POST", "PUT", "DELETE"]
urls = ["/page1", "/page2", "/page3", "/page4"]
status_codes = [200, 200, 200, 200, 200, 404, 500, 500]
with open("access_log.txt", "w") as file:
for _ in range(3984116):
ip_address = "127.0.0.1" # Example IP address
# Randomly select request method, URL, and status code
request_method = random.choice(request_methods)
url = random.choice(urls)
status_code = random.choice(status_codes)
# Determine the response size based on the status code
if status_code == 200:
response_size = random.randint(1024, 4096)
elif status_code == 500:
response_size = random.randint(512, 2048)
else:
response_size = 0
# Generate the log entry and write it to the file
log_entry = f"{ip_address} - - [30/Jun/2023:12:00:00 +0000] \"{request_method} {url} HTTP/1.1\" {status_code} {response_size}\n"
file.write(log_entry)
правда размер лога примерно 300 Мб получился.
Я access.log не генерил, а взял с рабочего сервера
У меня 500-х кодов 226 строк
Во всех тестах вывод был на терминал, никуда ничего не перенаправлял
если в go работа исключительно со строками (или внешними сервисами) и без горутин - это признак неправильного выбора инструмента
сами разработчики go из Google используют этот язык в том числе и именно так, в boringssl например: https://github.com/google/boringssl/tree/master/crypto/obj
причем на go тут были переписаны как раз перловые скрипты из openssl
А уж бенчить коннекты к sql базе и подавно бред.
Ну почему же. В данном случае это оправдано, т.к. все развернуто локально и база в том числе, а это значит, что она в момент теста не была нагуржена от слова совсем и замеры проводились несколько раз чтобы прогреть всякие буферы, кеши и прочее.
Движок SQL вроде в итоге получается ровно тот же самый и фактическая разница это собственно копирование данных из базы в контекст программы, что может иметь некоторые накладные расходы от выбранного языка, и собственно IO, производительность которого в принципе непредсказуема. Разница скорость исполнения запроса в таком случае должна иметь чисто статистическую погрешность. Вот если бы это всё поверх ORM как-то происходило, вот тогда б можно было пободаться у кого он быстрее.
Задача 1. Найти 500-е коды ответов в access.log размером ~1G
В случае перла, для данной задачи не нужно писать скрипт, а можно использовать однострочник:perl -alnE 'say if $F[9] == 500' access.log
~25 лет пишу на перле и очень его люблю, но...
первая задача быстрее/эффективнее решается через cat ... | awk '$8 == 500'
вторая через psql one liner
третья - openssl + awk
четвертая - jq
для именно описанных задач и перл и го кажутся инструментами очень не первого выбора...
первая задача быстрее/эффективнее решается через cat ... | awk '$8 == 500'
В awk нумерация начинается с 1, а не с 0, поэтому правильно вот так: cat ... | awk '$9 == 500'
. Тут мы разобрали много способов решения первой задачи, в том числе и awk.
Что касаемо остального, то могу только сказать, что вы (как истинный любитель Perl) следуете главному девизу TMTOWTDI )))
Инструменты сисадмина: Perl и Golang