Мониторинг заданий Folding@home часть 2

16.02.2010 23:50:27

В предыдущем посте Мониторинг заданий Folding@home часть 1 я рассказал, как организовать оповещение сервера при изменении статуса расчёта задания.
В этом мне помогли программа incron и скрипт на языке Python.

В этот раз я расскажу как эти данные принять и вывести.

Возвращаясь к первой части.
В силу того, что один из моих серверов имеет динамический ip-адрес, пришлось изменить программу-нотификатор так, чтобы она посылала ещё и hostname, т.к. он уникален среди моих машин и в то же время не зависит от ip.

Теперь она выглядит так:

#!/usr/bin/python
import os,sys
import urllib
filename = sys.argv[1]
stats = open(filename).read()
dir = os.path.dirname(filename)
config = open(dir+'/client.cfg')
for line in config:
    if line[:9] == 'machineid':
        mid = line[-2]
data = [('mid', mid), ('stats', stats), ('hostname', os.uname()[1])]
urllib.urlopen( 'http://мой.сайт/принимающий/данные/', urllib.urlencode( data ) )

Вернёмся к нашим баранам – принимающему серверу.
Поскольку сейчас мне интересен python и django, то писать буду соответственно на них. Предполагаю, что туториал уже пройден.

Начнём.
Создадим приложение.
./manage.py startapp folding

vim folding/views.py

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

from datetime import datetime
from django.core.cache import cache
from django.http import HttpResponse
from django.template import RequestContext, loader
from django.contrib.auth.decorators import login_required
def collect(request):
    data = cache.get('folding')
    if data == None:
        data = {}
    mid = request.POST['mid']
    stats = request.POST['stats']
    host = request.POST['hostname']
    name_start = stats.find('Name:') + 6
    name_end = stats.find('Tag:') - 1
    title = stats[name_start:name_end]
    pr_start = stats.rfind('Progress:') + 10
    pr_end = stats.find( '%', pr_start )
    progress = int( stats[pr_start:pr_end] )
    now = datetime.now()
    if data.has_key( host ) != True:
        data[host] = { 'host': host, 'mids': { mid: {'title': title, 'progress': progress, 'last_update': now } } }
    else:
        next_update = 0
        if data[host]['mids'].has_key(mid) == True:
            diff = now - data[host]['mids'][mid]['last_update']
            if diff.seconds > 0:
                next_update = diff + now
            else:
                next_update = data[host]['mids'][mid]['next_update']
        data[host]['mids'][mid] = {'title': title, 'progress': progress, 'last_update': now, 'next_update': next_update }
    cache.set('folding', data, 5400)
    return HttpResponse()

В принципе тут всё просто. Но есть интересная особенность. Здесь я использую прямой доступ к кешу для хранения статистики. Имеется ввиду именно кеш в памяти. Т.к. статистика очень быстро теряет актуальность, а вести её историю я в планах не имею, то это очень интересный метод. Не надо моделей, не надо таблиц в БД, не надо файлов, можно просто хранить эти данные в памяти, благо их совсем чуть.

Это весьма приятный бонус django по сравнению с любым php сайтом. Поскольку сайт работает демоном, то он может хранить данные между запросами.

Тут хорошо бы переписать на конкретный класс для работы с кешом в памяти, но я пока не разобрался как, так что ограничился пока установкой

CACHE_BACKEND = ‘locmem://’

в settings.py

Дабы закончить со сбором статистики напишем folding/urls.py

from django.conf.urls.defaults import *
import views
urlpatterns = patterns('',
                url(r'^collect/', views.collect),
                url(r'^status/', views.display),
            )

и включим его в глобальный urls.py

(r’^folding/’, include(‘folding.urls’)),

И вот уже можно собирать статистику.

Теперь вывод. В тот же folding/views.py добавим функцию display

@login_required
def display(request):
    data = cache.get('folding')
    if data != None:
        tdata = []
        for k in data:
            mids = data[k]['mids']
            tmids = []
            for km in mids:
                mids[km]['mid'] = km
                tmids.append(mids[km])
            data[k]['mids'] = tmids
            tdata.append(data[k])
        t = loader.get_template('folding/status.htm')
        c = RequestContext(request, { 'data': tdata })
        return HttpResponse( t.render(c) )
    return HttpResponse( 'No data' )

Соответственно нужен шаблон для вывода. Вот и он:

<ol>
{% for host in data %}
    <li>
        Host: {{host.host}}
        <ol>
        {% for mid in host.mids %}
            <li>MachineID: {{mid.mid}}, WU Name: {{mid.title}}<br /> Progress: {{mid.progress}}% Last update: {{ mid.last_update|date:"Y-m-d H:i:s" }} Next update: {{ mid.next_update|date:"Y-m-d H:i:s" }}</li>
        {% endfor %}
        </ol>
    </li>
{% endfor %}
</ol>

По-спартански, зато работает.
Шаблон у меня лежит в folding/templates/folding/status.htm
Для того чтобы это работало, необходимо добавить приложение folding в settings.py в INSTALLED_APPS.

Итак теперь по адресу сайт/folding/collect/ собирается статистика, а по адресу сайт/folding/display/ она отображается, причём только зарегистрированному пользователю. На моём сайте это только я.

Для целей мониторинга этого вполне достаточно, но я решил сделать ещё одну полезную штуку. А именно попрактиковаться в написании шаблонных тегов.
Приведённый далее код экстраполирует полученные результаты с целью показать ближайшее время окончания расчёта задания.

from django import template
from django.core.cache import cache
from datetime import datetime
pre.
register = template.Library()
pre.
@register.inclusion_tag('folding/closiest_wu.htm')
def folding_closiest_wu():
    data = cache.get('folding')
    if data == None:
        return {
            'data': False
        }
    min_time = False
    mid_to_show = False
    for host in data:
        for mid in data[host]['mids']:
            lmid = data[host]['mids'][mid]
            if lmid.has_key( 'next_update' ) == True and lmid['next_update'] != 0 and lmid['next_update'] > datetime.now():
                prog = lmid['progress']
                speed = lmid['next_update'] - lmid['last_update']
                time_rest = ( 100 - prog ) * speed
                if min_time == False or ( time_rest.seconds > 0 and time_rest < min_time ):
                    min_time = time_rest
                    mid_to_show = lmid
    if mid_to_show != False:
        mid_to_show['end'] = mid_to_show['last_update'] + min_time
        return {
            'data': mid_to_show
        }
    else:
        return {
            'data': False
        }

Тег этот находится в файле folding/templatetags/tag_folding.py
Подключить его в шаблоне можно при помощи конструкции

{% load tag_folding }{ folding_closiest_wu %}

Результат можно видеть на главной странице моего блога http://akademic.name/ сразу после последнего поста из juick.