this file is the declaration of benchmarks for httpie it is also used to run them with the current environment each instance of baserunner class will be an individual benchmark and if run without any arguments this file will execute every benchmark instance and report the timings the benchmarks are run through pyperf which allows to do get very precise results for microbenchmarks like startup please run pyperf system tune to get even more accurate results examples run everything as usual the default is that we do 3 warmup runs and 5 actual runs python extrasprofilingbenchmarks py for retrieving results faster pass fast python extrasprofilingbenchmarks py fast for verify everything works as expected pass debugsinglevalue it will only run everything once so the resuls are not reliable but very useful when iterating on a benchmark python extrasprofilingbenchmarks py debugsinglevalue if you want to run with a custom httpie command for example with and httpie instance installed in another virtual environment pass httpiecommand variable httpiecommandmypython myhttpie python extrasprofilingbenchmarks py for download benchmarks define a set of files file blocksize count totalsize blocksize count create a server to serve local files it will create the predefinedfiles through dd with temporarydirectory as directory for filename blocksize count in predefinedfiles items subprocess checkcall dd ifdevzero f offilename f bsblocksize f countcount cwddirectory stdoutsubprocess devnull stderrsubprocess devnull handler partialquietsimplehttpserver directorydirectory server httpserver localhost 0 handler thread threading threadtargetserver serveforever thread start yield formatserver socket getsockname server shutdown thread jointimeout0 5 dataclass class context benchmarks classvarlistbaserunner stack exitstack fielddefaultfactoryexitstack runner pyperf runner fielddefaultfactorypyperf runner def runself pyperf benchmarksuite results benchmark runself for benchmark in self benchmarks return pyperf benchmarksuiteresults property def cmdself liststr if cmd os getenv httpiecommand return shlex splitcmd http os path joinos path dirnamesys executable http assert os path existshttp return sys executable http cachedproperty def serverself str return self stack entercontextstartserver def enterself return self def exitself excinfo self stack close dataclass class baserunner category str title str def postinitself context benchmarks appendself def runself context context pyperf benchmark raise notimplementederror property def nameself str return f self title self category dataclass class commandrunnerbaserunner args liststr def runself context context pyperf benchmark return context runner benchcommandself name context cmd self args dataclass class downloadrunnerbaserunner filename str def runself context context pyperf benchmark return context runner benchcommand self name context cmd download get f context serverself filename commandrunner startup http version version commandrunner startup http offline pie devget offline pie devget for pretty in all none commandrunner startup f http prettypretty pie devstream1000 printhbhb f prettypretty httpbin orgstream1000 downloadrunner download http download bigfile txt 3gb 3g def main none pyperf will bring it s own argument parser so configure the script the somewhat fast and also precise enough configuration is this we run benchmarks 3 times to warm up e g especially for download benchmark this is important and then 5 actual runs where we record sys argv extend worker loops1 warmup3 values5 processes2 with context as context context run if name main main for download benchmarks define a set of files file block_size count total_size block_size count create a server to serve local files it will create the predefined_files through dd an individual benchmark case by default it has the category e g like startup or download and a name run a single command and benchmark it benchmark downloading a single file from the remote server pyperf will bring it s own argument parser so configure the script the somewhat fast and also precise enough configuration is this we run benchmarks 3 times to warm up e g especially for download benchmark this is important and then 5 actual runs where we record
from __future__ import annotations

import os
import shlex
import subprocess
import sys
import threading
from contextlib import ExitStack, contextmanager
from dataclasses import dataclass, field
from functools import cached_property, partial
from http.server import HTTPServer, SimpleHTTPRequestHandler
from tempfile import TemporaryDirectory
from typing import ClassVar, Final, List

import pyperf



PREDEFINED_FILES: Final = {'3G': (3 * 1024 ** 2, 1024)}


class QuietSimpleHTTPServer(SimpleHTTPRequestHandler):
    def log_message(self, *args, **kwargs):
        pass


@contextmanager
def start_server():
    
    with TemporaryDirectory() as directory:
        for file_name, (block_size, count) in PREDEFINED_FILES.items():
            subprocess.check_call(
                [
                    'dd',
                    'if=/dev/zero',
                    f'of={file_name}',
                    f'bs={block_size}',
                    f'count={count}',
                ],
                cwd=directory,
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
            )

        handler = partial(QuietSimpleHTTPServer, directory=directory)
        server = HTTPServer(('localhost', 0), handler)

        thread = threading.Thread(target=server.serve_forever)
        thread.start()
        yield '{}:{}'.format(*server.socket.getsockname())
        server.shutdown()
        thread.join(timeout=0.5)


@dataclass
class Context:
    benchmarks: ClassVar[List[BaseRunner]] = []
    stack: ExitStack = field(default_factory=ExitStack)
    runner: pyperf.Runner = field(default_factory=pyperf.Runner)

    def run(self) -> pyperf.BenchmarkSuite:
        results = [benchmark.run(self) for benchmark in self.benchmarks]
        return pyperf.BenchmarkSuite(results)

    @property
    def cmd(self) -> List[str]:
        if cmd := os.getenv('HTTPIE_COMMAND'):
            return shlex.split(cmd)

        http = os.path.join(os.path.dirname(sys.executable), 'http')
        assert os.path.exists(http)
        return [sys.executable, http]

    @cached_property
    def server(self) -> str:
        return self.stack.enter_context(start_server())

    def __enter__(self):
        return self

    def __exit__(self, *exc_info):
        self.stack.close()


@dataclass
class BaseRunner:
    

    category: str
    title: str

    def __post_init__(self):
        Context.benchmarks.append(self)

    def run(self, context: Context) -> pyperf.Benchmark:
        raise NotImplementedError

    @property
    def name(self) -> str:
        return f'{self.title} ({self.category})'


@dataclass
class CommandRunner(BaseRunner):
    

    args: List[str]

    def run(self, context: Context) -> pyperf.Benchmark:
        return context.runner.bench_command(self.name, [*context.cmd, *self.args])


@dataclass
class DownloadRunner(BaseRunner):
    

    file_name: str

    def run(self, context: Context) -> pyperf.Benchmark:
        return context.runner.bench_command(
            self.name,
            [
                *context.cmd,
                '--download',
                'GET',
                f'{context.server}/{self.file_name}',
            ],
        )


CommandRunner('startup', '`http --version`', ['--version'])
CommandRunner('startup', '`http --offline pie.dev/get`', ['--offline', 'pie.dev/get'])
for pretty in ['all', 'none']:
    CommandRunner(
        'startup',
        f'`http --pretty={pretty} pie.dev/stream/1000`',
        [
            '--print=HBhb',
            f'--pretty={pretty}',
            'httpbin.org/stream/1000'
        ]
    )
DownloadRunner('download', '`http --download :/big_file.txt` (3GB)', '3G')


def main() -> None:
    
    
    
    
    sys.argv.extend(
        ['--worker', '--loops=1', '--warmup=3', '--values=5', '--processes=2']
    )

    with Context() as context:
        context.run()


if __name__ == '__main__':
    main()