Browse Source

API-Hour FrameworkBenchmark: First draft (WIP)

Ludovic Gasc (GMLudo) 10 years ago
parent
commit
be86f92fd2

+ 15 - 0
frameworks/Python/API-Hour/api_hour_conf.py

@@ -0,0 +1,15 @@
+import multiprocessing
+import os
+import sys
+
+_is_travis = os.environ.get('TRAVIS') == 'true'
+
+workers = multiprocessing.cpu_count() * 3
+if _is_travis:
+    workers = 2
+
+bind = "0.0.0.0:8008"
+keepalive = 120
+errorlog = '-'
+pidfile = 'api_hour.pid'
+pythonpath = 'hello'

+ 4 - 0
frameworks/Python/API-Hour/bash_profile.sh

@@ -0,0 +1,4 @@
+export PY3_ROOT=$IROOT/py3
+export PY3=$PY3_ROOT/bin/python
+export PY3_PIP=$PY3_ROOT/bin/pip3
+export PY3_API_HOUR=$PY3_ROOT/bin/api_hour

+ 24 - 0
frameworks/Python/API-Hour/benchmark_config

@@ -0,0 +1,24 @@
+{
+  "framework": "API-Hour",
+  "tests": [{
+    "default": {
+      "setup_file": "setup",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "port": 8000,
+      "approach": "Realistic",
+      "classification": "Platform",
+      "database": "Postgres",
+      "framework": "API-Hour",
+      "language": "Python",
+      "orm": "Raw",
+      "platform": "API-Hour",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "API-Hour",
+      "notes": "Python 3.4 + AsyncIO"
+    }
+  }]
+}

+ 64 - 0
frameworks/Python/API-Hour/hello/.gitignore

@@ -0,0 +1,64 @@
+# Created by .gitignore support plugin (hsz.mobi)
+### Python template
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.cache
+nosetests.xml
+coverage.xml
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# PyCharm/IDEA files
+.idea
+*.iml
+
+# Virtualenvs
+venv/
+pyvenv/

+ 13 - 0
frameworks/Python/API-Hour/hello/LICENSE

@@ -0,0 +1,13 @@
+Copyright [2015] [Eyepea Dev Team]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.

+ 22 - 0
frameworks/Python/API-Hour/hello/README.rst

@@ -0,0 +1,22 @@
+hello
+=====
+
+Install
+-------
+
+#. Follow pythonz install doc
+#. pythonz install 3.4.2
+#. cd /opt
+#. Git clone your app here
+#. cd /opt/hello/
+#. /usr/local/pythonz/pythons/CPython-3.4.2/bin/pyvenv pyvenv
+#. . pyvenv/bin/activate
+#. pip install -r requirements.txt
+#. cd /etc/init.d/ && ln -s /opt/hello/etc/init.d/hello
+#. cd /etc/default/ && ln -s /opt/hello/etc/default/hello
+#. cd /etc/monit/conf.d/ && ln -s /opt/hello/etc/monit/conf.d/hello
+#. update-rc.d hello defaults
+#. cp -a /opt/hello/etc/hello /etc/
+#. Adapt rsyslog and lograte
+#. service hello start
+#. service monit restart

+ 3 - 0
frameworks/Python/API-Hour/hello/etc/default/hello

@@ -0,0 +1,3 @@
+# Start the daemon by default, let the user disable it.
+START_DAEMON=yes
+DAEMON_ARGS="--config_dir=/etc/hello"

+ 71 - 0
frameworks/Python/API-Hour/hello/etc/hello/logging.ini

@@ -0,0 +1,71 @@
+[formatters]
+keys=detailed,simple
+
+[handlers]
+keys=console,syslog,smtp
+
+[loggers]
+keys=root,warnings,asyncio,gunicorn,aiohttp,api_hour,hello
+
+[formatter_simple]
+format=%(name)s:%(levelname)s %(asctime)s %(module)s.py => %(message)s
+
+[formatter_detailed]
+format=%(name)s:%(levelname)s %(asctime)s %(module)s.py:%(lineno)d => %(message)s
+
+[handler_console]
+class=StreamHandler
+args=(sys.stdout,)
+formatter=detailed
+
+[handler_syslog]
+class=handlers.SysLogHandler
+args=('/dev/log', handlers.SysLogHandler.LOG_LOCAL6)
+formatter=detailed
+
+[handler_smtp]
+class=handlers.SMTPHandler
+level=WARN
+args=('127.0.0.1', '[email protected]', ['[email protected]'], 'hello error on server: hello')
+formatter=detailed
+
+[logger_root]
+level=ERROR
+handlers=console,smtp,syslog
+
+# You can add smtp in handlers list to receive e-mail alerts
+[logger_warnings]
+level=ERROR
+handlers=console,syslog
+qualname=py.warnings
+propagate=0
+
+[logger_asyncio]
+level=ERROR
+handlers=console,syslog
+qualname=asyncio
+propagate=0
+
+[logger_gunicorn]
+level=ERROR
+handlers=console,syslog
+qualname=gunicorn
+propagate=0
+
+[logger_aiohttp]
+level=ERROR
+handlers=console,syslog
+qualname=aiohttp
+propagate=0
+
+[logger_api_hour]
+level=ERROR
+handlers=console,syslog
+qualname=api_hour
+propagate=0
+
+[logger_hello]
+level=ERROR
+handlers=console,syslog
+qualname=hello
+propagate=0

+ 21 - 0
frameworks/Python/API-Hour/hello/etc/hello/main.conf

@@ -0,0 +1,21 @@
+[main]
+    hostname = hello
+    host = 0.0.0.0
+    port = 5050
+    pid = /run/lock/hello
+    access_log_format = %(p)s %(h)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"
+
+[engines]
+    [[pg]]
+        host = 127.0.0.1
+        port = 5432
+        dbname = hello_world
+        user = benchmarkdbuser
+        password = benchmarkdbpass
+        minsize = 20
+        maxsize = 20
+
+[performance]
+    # Don't change worker value, WIP, it can bite your wife and children
+    workers = 1
+    backlog = 1024

+ 79 - 0
frameworks/Python/API-Hour/hello/etc/init.d/hello

@@ -0,0 +1,79 @@
+#!/bin/sh
+#
+### BEGIN INIT INFO
+# Provides:          hello
+# Required-Start:    $local_fs $remote_fs $network $syslog
+# Required-Stop:     $local_fs $remote_fs $network $syslog
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: Startup daemon script for hello
+### END INIT INFO
+#
+# Author: Ludovic Gasc <[email protected]>
+set -e
+
+PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin
+DAEMONNAME=hello
+RUNDIR=/run/$DAEMONNAME
+DAEMON=/opt/hello/${DAEMONNAME}_cli
+PIDFILE=/run/lock/${DAEMONNAME}_0
+DAEMON_ARGS=""
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Create RUNDIR if it doesn't exist
+[ -d "$RUNDIR" ] || mkdir -p "$RUNDIR"
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Load the VERBOSE setting and other rcS variables
+[ -f /etc/default/rcS ] && . /etc/default/rcS
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
+. /lib/lsb/init-functions
+
+case "$1" in
+  start)
+    log_daemon_msg "Starting" "$DAEMONNAME"
+    if start-stop-daemon -b --start --pidfile $PIDFILE --startas $DAEMON -- $DAEMON_ARGS;
+    then
+        log_end_msg 0
+    else
+        log_end_msg 1
+    fi
+    ;;
+  stop)
+    log_daemon_msg "Stopping" "$DAEMONNAME"
+    if start-stop-daemon --stop --retry 5 --pidfile $PIDFILE;
+     then
+         log_end_msg 0
+     else
+         log_end_msg 1
+     fi
+    ;;
+  reload|force-reload)
+    log_daemon_msg "Reloading" "$DAEMONNAME"
+    if start-stop-daemon --stop --signal 1 --pidfile $PIDFILE --startas $DAEMON;
+     then
+         log_end_msg 0
+     else
+         log_end_msg 1
+     fi
+    ;;
+  restart)
+    $0 stop
+    $0 start
+    ;;
+  status)
+    status_of_proc -p $PIDFILE "$DAEMON" $DAEMONNAME && exit 0 || exit $?
+    ;;
+  *)
+    echo "Usage: $0 {start|stop|reload|force-reload|restart|status}"
+    exit 1
+    ;;
+esac
+
+exit 0

+ 7 - 0
frameworks/Python/API-Hour/hello/etc/logrotate.d/hello

@@ -0,0 +1,7 @@
+/var/log/hello/hello.log {
+    daily
+    rotate 15
+    compress
+    copytruncate
+    missingok
+}

+ 5 - 0
frameworks/Python/API-Hour/hello/etc/monit/conf.d/hello

@@ -0,0 +1,5 @@
+check process cache_updater with pidfile /run/lock/hello_0
+	group hello
+	start program = "/etc/init.d/hello start"
+	stop program = "/etc/init.d/hello stop"
+	if 5 restarts within 5 cycles then timeout

+ 126 - 0
frameworks/Python/API-Hour/hello/etc/rsyslog.conf

@@ -0,0 +1,126 @@
+#  /etc/rsyslog.conf	Configuration file for rsyslog.
+#
+#			For more information see
+#			/usr/share/doc/rsyslog-doc/html/rsyslog_conf.html
+
+
+#################
+#### MODULES ####
+#################
+
+$ModLoad imuxsock # provides support for local system logging
+$ModLoad imklog   # provides kernel logging support
+#$ModLoad immark  # provides --MARK-- message capability
+$SystemLogRateLimitInterval 1
+$SystemLogRateLimitBurst 1000
+
+# provides UDP syslog reception
+#$ModLoad imudp
+#$UDPServerRun 514
+
+# provides TCP syslog reception
+#$ModLoad imtcp
+#$InputTCPServerRun 514
+
+
+###########################
+#### GLOBAL DIRECTIVES ####
+###########################
+
+#
+# Use traditional timestamp format.
+# To enable high precision timestamps, comment out the following line.
+#
+$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat
+
+#
+# Set the default permissions for all log files.
+#
+$FileOwner root
+$FileGroup adm
+$FileCreateMode 0640
+$DirCreateMode 0755
+$Umask 0022
+
+#
+# Where to place spool and state files
+#
+$WorkDirectory /var/spool/rsyslog
+
+#
+# Include all config files in /etc/rsyslog.d/
+#
+$IncludeConfig /etc/rsyslog.d/*.conf
+
+
+###############
+#### RULES ####
+###############
+
+#
+# First some standard log files.  Log by facility.
+#
+auth,authpriv.*			/var/log/auth.log
+*.*;auth,authpriv.none;\
+    local6.none                 -/var/log/syslog
+#cron.*				/var/log/cron.log
+daemon.*			-/var/log/daemon.log
+kern.*				-/var/log/kern.log
+lpr.*				-/var/log/lpr.log
+mail.*				-/var/log/mail.log
+user.*				-/var/log/user.log
+
+local6.*                        /var/log/hello/hello.log
+
+#
+# Logging for the mail system.  Split it up so that
+# it is easy to write scripts to parse these files.
+#
+mail.info			-/var/log/mail.info
+mail.warn			-/var/log/mail.warn
+mail.err			/var/log/mail.err
+
+#
+# Logging for INN news system.
+#
+news.crit			/var/log/news/news.crit
+news.err			/var/log/news/news.err
+news.notice			-/var/log/news/news.notice
+
+#
+# Some "catch-all" log files.
+#
+*.=debug;\
+	auth,authpriv.none;\
+	news.none;mail.none;local7.none	-/var/log/debug
+*.=info;*.=notice;*.=warn;\
+	auth,authpriv.none;\
+	cron,daemon.none;\
+	mail,news.none;local7.none		-/var/log/messages
+
+#
+# Emergencies are sent to everybody logged in.
+#
+*.emerg				:omusrmsg:*
+
+#
+# I like to have messages displayed on the console, but only on a virtual
+# console I usually leave idle.
+#
+#daemon,mail.*;\
+#	news.=crit;news.=err;news.=notice;\
+#	*.=debug;*.=info;\
+#	*.=notice;*.=warn	/dev/tty8
+
+# The named pipe /dev/xconsole is for the `xconsole' utility.  To use it,
+# you must invoke `xconsole' with the `-file' option:
+# 
+#    $ xconsole -file /dev/xconsole [...]
+#
+# NOTE: adjust the list below, or you'll go crazy if you have a reasonably
+#      busy site..
+#
+daemon.*;mail.*;\
+	news.err;\
+	*.=debug;*.=info;\
+	*.=notice;*.=warn	|/dev/xconsole

+ 61 - 0
frameworks/Python/API-Hour/hello/hello/__init__.py

@@ -0,0 +1,61 @@
+import logging
+import asyncio
+
+import aiopg
+import os
+import psycopg2.extras
+
+import api_hour
+import api_hour.aiorest
+
+from . import endpoints
+
+
+LOG = logging.getLogger(__name__)
+
+
+class Container(api_hour.Container):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        # Servers
+        self.servers['http'] = api_hour.aiorest.Application(*args, **kwargs)
+        self.servers['http'].ah_container = self # keep a reference to Container
+        # routes
+        self.servers['http'].add_url('GET', '/json', endpoints.world.json)
+        self.servers['http'].add_url('GET', '/db', endpoints.world.db)
+        self.servers['http'].add_url('GET', '/queries', endpoints.world.queries)
+
+    def make_servers(self):
+        return [self.servers['http'].make_handler]
+
+    @asyncio.coroutine
+    def start(self):
+        yield from super().start()
+        LOG.info('Starting engines...')
+        # Add your custom engine here, example with PostgreSQL:
+        self.engines['pg'] = self.loop.create_task(aiopg.create_pool(host=os.environ.get('DBHOST', '127.0.0.1'),
+                                                                     sslmode='disable',
+                                                                     port=int(self.config['engines']['pg']['port']),
+                                                                     dbname=self.config['engines']['pg']['dbname'],
+                                                                     user=self.config['engines']['pg']['user'],
+                                                                     password=self.config['engines']['pg']['password'],
+                                                                     cursor_factory=psycopg2.extras.RealDictCursor,
+                                                                     minsize=int(self.config['engines']['pg']['minsize']),
+                                                                     maxsize=int(self.config['engines']['pg']['maxsize'])))
+        yield from asyncio.wait([self.engines['pg']], return_when=asyncio.ALL_COMPLETED)
+
+        LOG.info('All engines ready !')
+
+
+    @asyncio.coroutine
+    def stop(self):
+        LOG.info('Stopping engines...')
+        # Add your custom end here, example with PostgreSQL:
+        if 'pg' in self.engines:
+            if self.engines['pg'].done():
+                self.engines['pg'].result().terminate()
+                yield from self.engines['pg'].result().wait_closed()
+            else:
+                yield from self.engines['pg'].cancel()
+        LOG.info('All engines stopped !')
+        yield from super().stop()

+ 1 - 0
frameworks/Python/API-Hour/hello/hello/endpoints/__init__.py

@@ -0,0 +1 @@
+from . import world

+ 28 - 0
frameworks/Python/API-Hour/hello/hello/endpoints/world.py

@@ -0,0 +1,28 @@
+import logging
+import asyncio
+from random import randint
+
+from ..services import queries_number
+from ..services.world import get_random_record, get_random_records
+
+LOG = logging.getLogger(__name__)
+
[email protected]
+def json(request):
+    """Test type 1: JSON serialization"""
+    return {'message': 'Hello, World!'}
+
[email protected]
+def db(request):
+    """Test type 2: Single database query"""
+    container = request.application.ah_container
+
+    return (yield from get_random_record(container))
+
[email protected]
+def queries(request):
+    """Test type 3: Multiple database queries"""
+    container = request.application.ah_container
+    limit = queries_number(request.args.get('queries', 1))
+
+    return (yield from get_random_records(container, limit))

+ 14 - 0
frameworks/Python/API-Hour/hello/hello/services/__init__.py

@@ -0,0 +1,14 @@
+from . import world
+
+# get from Django hello application
+def queries_number(number):
+    try:
+        queries = int(number)
+    except Exception:
+        queries = 1
+    if queries < 1:
+        queries = 1
+    if queries > 500:
+        queries = 500
+    return queries
+

+ 24 - 0
frameworks/Python/API-Hour/hello/hello/services/world.py

@@ -0,0 +1,24 @@
+import asyncio
+from pprint import pprint
+from random import randint
+
[email protected]
+def get_random_record(container):
+    pg = yield from container.engines['pg']
+
+    with (yield from pg.cursor()) as cur:
+            yield from cur.execute('SELECT id AS "Id", randomnumber AS "RandomNumber" FROM world WHERE id=%(idx)s LIMIT 1',
+                                   {'idx': randint(1, 10000)})
+            world = yield from cur.fetchone()
+    return world
+
[email protected]
+def get_random_records(container, limit):
+    tasks = []
+    results = []
+    for i in range(limit):
+        tasks.append(container.loop.create_task(get_random_record(container)))
+    yield from asyncio.wait(tasks)
+    for task in tasks:
+        results.append(task.result())
+    return results

+ 4 - 0
frameworks/Python/API-Hour/hello/hello/utils/__init__.py

@@ -0,0 +1,4 @@
+import logging
+import asyncio
+
+LOG = logging.getLogger(__name__)

+ 8 - 0
frameworks/Python/API-Hour/install.sh

@@ -0,0 +1,8 @@
+#!/bin/bash
+
+mkdir -p $IROOT/.pip_cache
+export PIP_DOWNLOAD_CACHE=$IROOT/.pip_cache
+
+fw_depends python3
+
+$IROOT/py3/bin/pip3 install --install-option="--prefix=${IROOT}/py3" -r $TROOT/requirements.txt

+ 7 - 0
frameworks/Python/API-Hour/requirements.txt

@@ -0,0 +1,7 @@
+api_hour==0.5.0
+aiopg==0.5.2
+gunicorn==19.1.1
+psycopg2==2.5.4
+setproctitle==1.1.8
+six==1.8.0
+ujson==1.33

+ 3 - 0
frameworks/Python/API-Hour/setup.sh

@@ -0,0 +1,3 @@
+#!/bin/bash
+
+$PY3_API_HOUR -k api_hour.Worker hello:Container -c api_hour_conf.py &

+ 8 - 0
frameworks/Python/API-Hour/source_code

@@ -0,0 +1,8 @@
+./API-Hour/etc/hello/main.conf
+./API-Hour/etc/hello/logging.ini
+./API-Hour/hello/__init__.py
+./API-Hour/hello/hello/endpoints/__init__.py
+./API-Hour/hello/hello/endpoints/world.py
+./API-Hour/hello/hello/services/__init__.py
+./API-Hour/hello/hello/services/world.py
+./API-Hour/hello/hello/utils/__init__.py