Skip to content

Instantly share code, notes, and snippets.

@glic3rinu
Created May 23, 2024 14:22
Show Gist options
  • Save glic3rinu/0878f9c2d1e72dc07932325bca8f6a4a to your computer and use it in GitHub Desktop.
Save glic3rinu/0878f9c2d1e72dc07932325bca8f6a4a to your computer and use it in GitHub Desktop.
FROM debian:bookworm-slim
ENV DEBIAN_FRONTEND noninteractive
ARG APT_PY_VERSION=3.11
ARG ALT_PY_VERSION=3.11.8
ARG PIP_BUILD_DEPENDENCIES=" \
build-essential \
clang-15 \
python$APT_PY_VERSION-dev \
libcap-dev"
ARG ALT_PY_BUILD_DEPENDENCIES=" \
curl \
build-essential \
unzip \
clang-15 \
pkg-config \
zlib1g-dev \
libgdbm-dev \
libnss3-dev \
libffi-dev \
uuid-dev \
lzma-dev \
liblzma-dev \
libbz2-dev"
RUN apt-get update
RUN apt-get install -y \
${ALT_PY_BUILD_DEPENDENCIES} \
${PIP_BUILD_DEPENDENCIES}
ENV CC=/usr/bin/clang-15
ENV CXX=/usr/bin/clang++-15
ENV LLVM_PROFDATA=/usr/bin/llvm-profdata-15
ENV LLVM_AR=/usr/bin/llvm-ar-15
# https://github.com/openssl/openssl/issues/17064
#RUN curl -O https://www.openssl.org/source/openssl-3.3.0.tar.gz && \
RUN curl -O https://www.openssl.org/source/old/1.1.1/openssl-1.1.1v.tar.gz && \
tar xvfz openssl-*.tar.gz && \
( cd openssl-* && ./config --prefix=/usr/local --openssldir=/usr/local/ssl --libdir=lib shared zlib; ) && \
( cd openssl-* && make -j8 && make install_sw && make install_ssldirs; ) && \
ln -sf /etc/ssl/certs/ca-certificates.crt /usr/local/ssl/cert.pem && \
rm -r /usr/local/ssl/certs && ln -sf /etc/ssl/certs /usr/local/ssl/certs && \
rm -r /usr/local/ssl/private && ln -sf /etc/ssl/private /usr/local/ssl/private && \
strip /usr/local/lib/libssl.* && \
strip /usr/local/bin/openssl && \
ldconfig && \
/usr/local/bin/openssl version && \
rm -rf openssl-*
RUN curl -O https://www.python.org/ftp/python/${ALT_PY_VERSION}/Python-${ALT_PY_VERSION}.tgz && \
tar xvf Python-${ALT_PY_VERSION}.tgz && \
( cd Python-${ALT_PY_VERSION} && LDFLAGS="${LDFLAGS} -Wl,-rpath=/usr/local/ssl/lib" ./configure \
--with-openssl=/usr/local/ \
--with-ssl-default-suites=openssl \
--enable-option-checking=fatal \
--enable-optimizations \
--with-lto=thin \
) && \
( cd Python-${ALT_PY_VERSION} && make -j8 && make -j8 altinstall ) && \
rm -rf Python-${ALT_PY_VERSION} && \
strip /usr/local/bin/python${ALT_PY_VERSION%.*} && \
strip /usr/local/lib/libpython* && \
find /usr/local/lib/python${ALT_PY_VERSION%.*} | grep "\.so$" | grep -v cffi | xargs strip && \
rm -fr /tmp/*
RUN LDFLAGS=-flto /usr/local/bin/pip${ALT_PY_VERSION%.*} install \
gevent \
requests
@glic3rinu
Copy link
Author

glic3rinu commented May 23, 2024

docker build . --platform=linux/amd64 --progress plain -t perftest1
docker run -it --volume /tmp/:/mnt/ perftest1 bash 


/usr/local/bin/python3.11 -c "
import gevent.monkey
# Comment to run on native OS threads
gevent.monkey.patch_all()

import threading
import requests
import time
import gevent.pool
import urllib3

import multiprocessing.pool

urllib3.disable_warnings()

def run():
    # you might need to hardcode www.google.com into /etc/hosts to avoid DNS errors
    return requests.get('https://www.google.com/', verify=False, allow_redirects=False)

start_time = time.monotonic()
threads = []
num_req = 3_000

with multiprocessing.pool.ThreadPool(processes=300) as pool:
    for ix in range(num_req):
        threads.append(pool.apply_async(run))
    for thread in threads:
        resp = thread.get()
        if resp.status_code in (200,):
            print('.', end='', flush=True)
        else:
            print(resp.status_code, end=' ', flush=True)

print()
runtime = time.monotonic()-start_time
print(round(runtime, 1), 'sec')
print(round(num_req/runtime, 1), 'req/sec')

"

@glic3rinu
Copy link
Author

glic3rinu commented May 24, 2024

  • Added alternative http client using urllib (reusing ssl context) vs using requests (not reusing ssl context)
  • Added 6 second timeout
  • Using gevent pool imap_unordered to avoid head-of-the-line blocking problem with multiprocessing pool.
import gevent.monkey
gevent.monkey.patch_all()

import threading
import time
import gevent.pool
import multiprocessing.pool

import requests
import urllib3
urllib3.disable_warnings()

import urllib.request
import ssl


class NoRedirectHandler(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, hdrs, newurl):
        return None  # do not redirect, HTTPError will be raised


ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
opener = urllib.request.build_opener(urllib.request.HTTPSHandler(context=ctx), NoRedirectHandler())


def run_requests(ix):
    # you might need to hardcode www.google.com into /etc/hosts to avoid DNS errors
    try:
        return requests.get('https://www.google.com/', verify=False, allow_redirects=False, headers={
                'Connection': 'close',
            }, timeout=6).status_code
    except Exception as exc:
        if 'timeout' in str(exc).lower():
            return 'timeout'
        return exc.__class__.__name__.rsplit('.', 1)[-1]


def run_urllib(ix):
    req = urllib.request.Request(
        url='https://www.google.com/',
        method='GET',
        headers={
            'Connection': 'close',
            'Accept-Encoding': 'gzip',
        },
    )
    try:
        with opener.open(req, timeout=6) as resp:
            resp.read()
    except urllib.error.HTTPError as exc:
        return exc.status
    except Exception as exc:
        if 'timed out' in str(exc).lower():
            return 'timeout'
        return exc.__class__.__name__.rsplit('.', 1)[-1]

start_time = time.monotonic()
threads = []
num_req = 3_000
concurrency = 300

for status in gevent.pool.Pool(concurrency).imap_unordered(run_urllib, range(num_req)):
    if status in (200,):
        print('.', end='', flush=True)
    else:
        print(status, end=' ', flush=True)

print()

runtime = time.monotonic()-start_time
print(ssl.OPENSSL_VERSION, '-', round(runtime, 1), 'sec')
print(ssl.OPENSSL_VERSION, '-', round(num_req/runtime, 1), 'req/sec')

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment