uwsgi stats middleware for Django

105 views
Skip to first unread message

Etienne Robillard

unread,
Dec 7, 2017, 10:14:35 AM12/7/17
to uw...@lists.unbit.it, django-...@googlegroups.com, django...@googlegroups.com
Hi,

I would like to access the uWSGI stats API from within Django by
creating a custom WSGI middleware.

Could it be possible to compute the number of requests currently being
used by uWSGI workers in Python/Django ?

Ideally, i could then retrieve the active connections in use in a
standard Django view:

def someview(request):

    # retrieve the number of active requests (connections)

    connections = request.environ['uwsgi.requests']


What do you think?


Etienne


--
Etienne Robillard
tka...@yandex.com
https://www.isotopesoftware.ca/

Ádler Oliveira Silva Neves

unread,
Dec 7, 2017, 1:30:23 PM12/7/17
to django...@googlegroups.com

Sounds like you want retrieving statistics from uWSGI's stats server.

http://uwsgi-docs.readthedocs.io/en/latest/StatsServer.html

In order to get that JSON via HTTP requests, your server ".ini" should receive extra arguments to start an stats server listening to the specified port and application must be somehow aware of such port number, which has a side-effect of increasing coupling.

Sincerely,

Adler

Etienne Robillard

unread,
Dec 8, 2017, 7:09:41 AM12/8/17
to django...@googlegroups.com

Hi Ádler,

Thank you for your reply.

Can you please review this code before I commit? 


"""uWSGIController API Version 0.8.3

Middleware for storing uWSGI statistics into the environ object.
"""

import sys
import os
import logging
import demjson
import urllib

logger = logging.getLogger(__name__)

from notmm.controllers.wsgi import WSGIController

__all__ = ('uWSGIController',)


class uWSGIController(WSGIController):

    def __init__(self, wsgi_app, stats_url='http://localhost:9001'):

        self.wsgi_app = wsgi_app
        self.stats_url = stats_url
        super(uWSGIController, self).__init__() # hack

    def init_request(self, request):
        logger.debug('In uWSGIController.init_request...')
        request.environ['uwsgi.requests'] = self.connections(self.stats_url)
        self._request = request
        self._environ = request.environ
        return self._request

    def connections(self, url):
        """Return the amount of live connections (requests) for all workers"""
        fp = urllib.urlopen(url)
        json = demjson.decode(fp.read())

        connections = 0

        for worker in json['workers']:
            connections += worker['requests']

        fp.close()

        return connections


Best regards,

Etienne

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.
To post to this group, send email to django...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/c6d79973-28b8-5236-a64e-4c24ed3c30a7%40gmail.com.
For more options, visit https://groups.google.com/d/optout.

Adler Neves

unread,
Dec 8, 2017, 11:07:42 AM12/8/17
to Django users
Hello again, Etienne!

The way you coded that, you are counting how many requests the server has replied before your request has been processed.

A bit differently than you, I implemented it as a view (which is not hard to convert into middleware component) that uses bootstrap, so some small changes are needed.

Let me paste some code from my views.py:
"""
import json
from urllib.request import urlopen

class ServerStatsView(TemplateView):
    template_name = 'server-stats.html'
    def get(self, request):
        stats = json.loads(ServerStatsJsonGetter(request).content.decode())
        processed_stats = dict()
        processed_stats['workers_total'] = 0
        processed_stats['workers_busy'] = 0
        processed_stats['workers_accepting_connections'] = 0
        processed_stats['threads_total'] = 0
        processed_stats['threads_busy'] = 0
        processed_stats['requests_processed'] = 0
        processed_stats['requests_processing'] = 0
        for worker in stats['workers']:
            processed_stats['workers_total']+=1
            processed_stats['workers_accepting_connections']+= int(bool(worker['accepting']))
            processed_stats['workers_busy']+=int(worker['status']=='busy')
            for thread in worker['cores']:
                processed_stats['threads_total']+=1
                processed_stats['threads_busy']+=int(bool(thread['in_request']))
            processed_stats['requests_processed']+=worker['requests']
        processed_stats['requests_processing']=processed_stats['threads_busy']
        processed_stats['workers_busy_pct'] = 100*processed_stats['workers_busy']/processed_stats['workers_total']
        processed_stats['workers_avail_pct'] = 100*processed_stats['workers_accepting_connections']/processed_stats['workers_total']
        processed_stats['threads_busy_pct'] = 100*processed_stats['threads_busy']/processed_stats['threads_total']
        return render(request, self.template_name, {
            'stats':processed_stats,
        })

def ServerStatsJsonGetter(request):
    with urlopen('http://127.0.0.1:14549') as urlstream:
        return HttpResponse(
            json.dumps(
                json.loads(urlstream.read().decode()),
                indent=4
            ),
            content_type='application/json'
        )

"""

and now the template server-stats.html:
"""
{% extends "base.html" %}
{% load i18n %}

{% block content %}
<h5>
    {% trans "Workers available for new connections" %}
</h5>
<div class="progress">
     <div class="progress-bar" role="progressbar" style="width: {{stats.workers_avail_pct}}%" aria-valuenow="{{stats.workers_accepting_connections}}" aria-valuemax="{{stats.workers_total}}" aria-valuemin="0">
         {{stats.workers_accepting_connections}}
         {% trans "of" %}
         {{stats.workers_total}}
     </div>
</div>
<hr>
<h5>
    {% trans "Workers busy" %}
</h5>
<div class="progress">
     <div class="progress-bar" role="progressbar" style="width: {{stats.workers_busy_pct}}%" aria-valuenow="{{stats.workers_busy}}" aria-valuemax="{{stats.workers_total}}" aria-valuemin="0">
         {{stats.workers_busy}}
         {% trans "of" %}
         {{stats.workers_total}}
     </div>
</div>
<hr>
<h5>
    {% trans "Threads busy" %}
</h5>
<div class="progress">
     <div class="progress-bar" role="progressbar" style="width: {{stats.threads_busy_pct}}%" aria-valuenow="{{stats.threads_busy}}" aria-valuemax="{{stats.threads_total}}" aria-valuemin="0">
         {{stats.threads_busy}}
         {% trans "of" %}
         {{stats.threads_total}}
     </div>
</div>
<hr>
<h5>
    Table
</h5>
<div>
    <table class="table">
        <thead>
            <tr>
                <th>Aspect</th>
                <th>Count</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>
                    {% trans "Total workers" %}
                </td>
                <td>
                    {{stats.workers_total}}
                </td>
            </tr>
            <tr>
                <td>
                    {% trans "Busy workers" %}
                </td>
                <td>
                    {{stats.workers_busy}}
                </td>
            </tr>
            <tr>
                <td>
                    {% trans "Workers accepting connections" %}
                </td>
                <td>
                    {{stats.workers_accepting_connections}}
                </td>
            </tr>
            <tr>
                <td>
                    {% trans "Total threads" %}
                </td>
                <td>
                    {{stats.threads_total}}
                </td>
            </tr>
            <tr>
                <td>
                    {% trans "Busy threads" %}
                </td>
                <td>
                    {{stats.threads_busy}}
                </td>
            </tr>
            <tr>
                <td>
                    {% trans "Total processed requests" %}
                </td>
                <td>
                    {{stats.requests_processed}}
                </td>
            </tr>
        </tbody>
    </table>
</div>
{% endblock content %}

"""

A request dispatched while ApacheBench was generating some load (concurrency = 15) into the server rendered the image you can find attached to this message.

Sincerely,
Adler
Screenshot-2017-12-8--etienne-thread-server-load.png

Adler Neves

unread,
Dec 8, 2017, 11:19:38 AM12/8/17
to Django users
forgot mentioning:
the number of active requests is given by "Threads busy" progress bar and in "Busy threads" line of the table of the previous reply.

Etienne Robillard

unread,
Dec 9, 2017, 9:35:05 AM12/9/17
to Adler Neves, django...@googlegroups.com

Dear Adler,

Thanks again for sharing your code with us.

Do you think it would be possible to use asyncio to defer the computation of the active connections from the view or middleware ?


Best regards,

Etienne

For more options, visit https://groups.google.com/d/optout.

Ádler Oliveira Silva Neves

unread,
Dec 9, 2017, 11:53:06 AM12/9/17
to Etienne Robillard, django...@googlegroups.com

Hello once more time, Etienne!

There's a GitHub gist from two years ago on making a Django application use asyncio. A consequence of bringing such data through middlewares is that you will trigger a HTTP request against localhost and compute such data for every request on every single page loaded even if you don't use them anywhere (example: redirects defined by application logic that doesn't check server usage).

Solutions I can quickly imagine:

1) If you need such data in one or few views and don't want the slowdown created by the middleware on every page load, consider removing such middleware and moving its code into the view(s).

2) If you need such data to be displayed in every loaded page in browser, but you can do multiple requests to build a page, consider removing the middleware and creating a view that outputs the stats data as JSON, XML or pre-rendered HTML fragment and modifying your base template to contain a script that loads (and renders, if necessary) such information from a XMLHttpRequest, Ajax or something equivalent in client side.

3) If every view needs such information to make different decisions in your business logic according server load, plus code smells aren't allowed in your code base, nothing can be done except have patience and wait the request be completed.

4) If you are in the same situation as in the 3rd topic except that code smells are allowed in your code base and that you don't need the most up-to-date stats data, consider adopting an ugly solution (which I don't recommend) that consists in defining a class which inherits from threading.Thread, where its overridden run method is an infinite loop that queries uWSGI periodically and caches the result in an object attribute, being instanced in middleware's __init__ method and having its threading.Thread::start() method called there, while the body of the middleware retrieves the cached data. One of the reasons that I consider this solution to be ugly is because if your uWSGI ini file defines 512 workers, then you will have 512 processes polling uWSGI's stats (which may generate some constant CPU load even while the server is "idle").

Sincerely,

Adler

Reply all
Reply to author
Forward
0 new messages