Pull to refresh

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 - это де-факто стандарт языка системного администрирования.

Компилировать 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

Я по "ТЗ" ориентировался. Зачем собирать лишнее, если оно не требуется)

Я знаю, что так можно, но как раз в этом-то и заключается ключевое отличие. В Perl ты получаешь сразу весь десериализованный json, а в Golang ты должен его описать настолько подробно, насколько тебе надо прежде чем десериализовать. Этот момент понять было сложнее всего.

И да, у Perl то же есть песочница )

смысл сравнения изначально неправилен.

perl силён в операциях со строками, go - в многопоточности.
если в go работа исключительно со строками (или внешними сервисами) и без горутин - это признак неправильного выбора инструмента.

это не прямые конкуренты, а инструменты, заточенные каждый на свою область.

Соглашусь с вами - это не конкуренты, я этого и не утверждал. Просто мне было интересно, можно ли использовать Golang (не слишком ли он сложен) как сриптовый язык для простых нужд сисадмина. Да, горутины классные, я пробовал, но скажу вам, что для рядового сисадмина горутины почти не нужны, да и на Perl есть куча модулей позволяющих так же элегантно породить (fork) кучу воркеров. Конечно же fork не сравнится с горутинами, но например для опроса кучи сайтов можно использовать не воркеры, а асинхронно это сделать.

И да, для всего нужен свой инструмент и правильный сисадмин умеет его выбирать под задачу )

можно ли использовать Golang как сриптовый язык

Так использовали б его для большего юзабилити вроде вот такого.

Мне кстати интересно сколько времени займёт grep/ripgrep/awk для подобной задачи.

rg "\s500\s" ./access.log

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 получается, иногда выше.

уговорили )))

time rg "\s500\s" ./access.log

real	0m0,208s
user	0m0,135s
sys	    0m0,073s

да, действительно, реактивный какой )

спасибо, за полезный инструмент!

Если вывод никуда не перенаправляется - вывод 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 строк

  • Во всех тестах вывод был на терминал, никуда ничего не перенаправлял

Ну, у меня прост не было логов, потому сгенерировал столько же строк, чтобы было чутка чеснее. Правда 500 там было 995570 штук и выводило их 20+/-5 секунд. C 250 штук и выводом в терминал grep стал выводить как rg выше - в районе 0.3 сек, а rg выдал

real    0m0.084s
user    0m0.064s
sys     0m0.020s

Так использовали б его для большего юзабилити вроде вот такого.

поржал )))))

если в 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

Полностью согласен. Однострочники Perl это вообще отдельная тема и я ее не упомянул лишь потому что в Golang нет онострочников.

~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 )))

Sign up to leave a comment.

Articles