Permalink
Browse files

Split images, refactoring.

  • Loading branch information...
1 parent b78ebb4 commit 05c48fe82b680780fe588d7e8ceb3e6281e59bba @robertwb robertwb committed Feb 10, 2012
Showing with 114 additions and 58 deletions.
  1. +1 −0 .hgignore
  2. +34 −41 patchbot.py
  3. +56 −16 serve.py
  4. +1 −1 templates/ticket_list.html
  5. +22 −0 util.py
View
@@ -3,3 +3,4 @@ syntax: glob
*.log
*.pyc
*~
+images/_cache/*
View
@@ -7,7 +7,7 @@
from http_post_file import post_multipart
from trac import scrape, pull_from_trac
-from util import now_str as datetime, parse_datetime, prune_pending, do_or_die, get_base, compare_version
+from util import now_str as datetime, parse_datetime, prune_pending, do_or_die, get_base, compare_version, current_reports
def filter_on_authors(tickets, authors):
if authors is not None:
@@ -16,15 +16,6 @@ def filter_on_authors(tickets, authors):
if authors is None or set(ticket['authors']).issubset(authors):
yield ticket
-def current_reports(ticket, base=None):
- if 'reports' not in ticket:
- return []
- return filter(lambda report: (ticket['patches'] == report['patches'] and
- ticket['spkgs'] == report['spkgs'] and
- ticket['depends_on'] == (report.get('deps') or []) and
- (base is None or base == report['base'])),
- ticket['reports'])
-
def contains_any(key, values):
clauses = [{'key': value} for value in values]
return {'$or': clauses}
@@ -320,6 +311,28 @@ def machine_data():
return [dist_name, dist_version, arch, release, node]
return [system, arch, release, node]
+def parse_time_of_day(s):
+ def parse_interval(ss):
+ ss = ss.strip()
+ if '-' in ss:
+ start, end = ss.split('-')
+ return float(start), float(end)
+ else:
+ return float(ss), float(ss) + 1
+ return [parse_interval(ss) for ss in s.split(',')]
+
+def check_time_of_day(hours):
+ from datetime import datetime
+ now = datetime.now()
+ hour = now.hour + now.minute / 60.
+ for start, end in parse_time_of_day(hours):
+ if start < end:
+ if start <= hour <= end:
+ return True
+ elif hour <= end or start <= hour:
+ return True
+ return False
+
def get_conf(path, server, **overrides):
if path is None:
unicode_conf = {}
@@ -366,31 +379,11 @@ def locate_plugin(name):
conf["plugins"] = [(name, locate_plugin(name)) for name in conf["plugins"]]
return conf
-def parse_time_of_day(s):
- def parse_interval(ss):
- ss = ss.strip()
- if '-' in ss:
- start, end = ss.split('-')
- return float(start), float(end)
- else:
- return float(ss), float(ss) + 1
- return [parse_interval(ss) for ss in s.split(',')]
-
-def check_time_of_day(hours):
- from datetime import datetime
- now = datetime.now()
- hour = now.hour + now.minute / 60.
- for start, end in parse_time_of_day(hours):
- if start < end:
- if start <= hour <= end:
- return True
- elif hour <= end or start <= hour:
- return True
- return False
-
-def main():
+def main(args):
global conf
+ # Most configuration is done in the config file, which is reread between
+ # each ticket for live configuration of the patchbot.
parser = OptionParser()
parser.add_option("--config", dest="config")
parser.add_option("--sage-root", dest="sage_root", default=os.environ.get('SAGE_ROOT'))
@@ -399,11 +392,8 @@ def main():
parser.add_option("--ticket", dest="ticket", default=None)
parser.add_option("--list", dest="list", default=False)
parser.add_option("--skip-base", dest="skip_base", default=False)
- (options, args) = parser.parse_args()
+ (options, args) = parser.parse_args(args)
- if options.sage_root == os.environ.get('SAGE_ROOT'):
- print "WARNING: Do not use this copy of sage while the patchbot is running."
-
conf_path = options.config and os.path.abspath(options.config)
if options.ticket:
tickets = [int(t) for t in options.ticket.split(',')]
@@ -420,6 +410,9 @@ def main():
print
sys.exit(0)
+ if options.sage_root == os.environ.get('SAGE_ROOT'):
+ print "WARNING: Do not use this copy of sage while the patchbot is running."
+
if not options.skip_base:
clean = lookup_ticket(options.server, 0)
def good(report):
@@ -450,8 +443,8 @@ def good(report):
if __name__ == '__main__':
# allow this script to serve as a single entry point for bots and the server
- if sys.argv[1] == '--serve':
- del sys.argv[1]
+ args = list(sys.argv)
+ if args[1] == '--serve':
+ del args[1]
from serve import main
- main()
-
+ main(args)
View
@@ -1,4 +1,4 @@
-import sys, bz2, json, traceback, re, collections
+import os, sys, bz2, json, traceback, re, collections
from cStringIO import StringIO
from optparse import OptionParser
from flask import Flask, render_template, make_response, request, Response
@@ -8,8 +8,7 @@
import db
from db import tickets, logs
-from patchbot import current_reports
-from util import now_str
+from util import now_str, current_reports
app = Flask(__name__)
@@ -81,7 +80,7 @@ def ticket_list():
summary = dict((key, 0) for key in status_order)
def preprocess(all):
for ticket in all:
- ticket['report_count'], ticket['report_status'] = get_ticket_status(ticket, base)
+ ticket['report_count'], ticket['report_status'], ticket['report_status_composite'] = get_ticket_status(ticket, base)
if 'reports' in ticket:
ticket['pending'] = len([r for r in ticket['reports'] if r['status'] == 'Pending'])
summary[ticket['report_status']] += 1
@@ -177,16 +176,18 @@ def preprocess_reports(all):
if 'time' in item:
item['log'] = log_name(info['id'], item)
yield item
- return render_template("ticket.html", reports=preprocess_reports(info['reports']), ticket=ticket, info=format_info(info), status=get_ticket_status(info, base=base)[1])
+ return render_template("ticket.html", reports=preprocess_reports(info['reports']), ticket=ticket, info=format_info(info), status=get_ticket_status(info, base=base)[2])
+# The fact that this image is in the trac template lets the patchbot know
+# when a page gets updated.
@app.route("/ticket/<int:ticket>/status.png")
def render_ticket_status(ticket):
try:
info = trac.scrape(ticket, db=db)
except:
info = tickets.find_one({'id': ticket})
- status = get_ticket_status(info, base=base)[1]
- response = make_response(open('images/%s-blob.png' % status_colors[status]).read())
+ status = get_ticket_status(info, base=base)[2]
+ response = make_response(create_status_image(status))
response.headers['Content-type'] = 'image/png'
response.headers['Cache-Control'] = 'no-cache'
return response
@@ -303,11 +304,45 @@ def get_log(log):
@app.route("/blob/<status>")
def status_image(status):
- response = make_response(open('images/%s-blob.png' % status_colors[status]).read())
+ response = make_response(create_status_image(status))
response.headers['Content-type'] = 'image/png'
response.headers['Cache-Control'] = 'max-age=3600'
return response
+def create_status_image(status):
+ if ',' in status:
+ status_list = status.split(',')
+ if len(set(status_list)) == 1:
+ status = status_list[0]
+ else:
+ try:
+ from PIL import Image
+ import numpy
+ path = 'images/_cache/' + ','.join(status_list) + '-blob.png'
+ if not os.path.exists(path):
+ composite = numpy.asarray(Image.open(status_image(status_list[0]))).copy()
+ height, width, _ = composite.shape
+ for ix, status in enumerate(reversed(status_list)):
+ slice = numpy.asarray(Image.open(status_image(status)))
+ start = ix * width / len(status_list)
+ end = (ix + 1) * width / len(status_list)
+ composite[:,start:end,:] = slice[:,start:end,:]
+ if not os.path.exists('images/_cache'):
+ os.mkdir('images/_cache')
+ Image.fromarray(composite, 'RGBA').save(path)
+ return open(path).read()
+ except ImportError:
+ print "here"
+ status = min_status(status_list)
+ return open(status_image(status)).read()
+
+def status_image(status):
+ return 'images/%s-blob.png' % status_colors[status]
+
+def min_status(status_list):
+ index = min(status_order.index(status) for status in status_list)
+ return status_order[index]
+
@app.route("/robots.txt")
def robots():
return """
@@ -326,25 +361,30 @@ def robots():
def get_ticket_status(ticket, base=None):
all = current_reports(ticket, base=base)
if len(all):
- index = min(status_order.index(report['status']) for report in all)
- return len(all), status_order[index]
+ status_list = [report['status'] for report in all]
+ if len(set(status_list)) == 1:
+ composite = single = status_list[0]
+ else:
+ composite = ','.join(status_list)
+ single = min_status(status_list)
+ return len(all), single, composite
elif ticket['spkgs']:
- return 0, 'Spkg'
+ return 0, 'Spkg', 'Spkg'
elif not ticket['patches']:
- return 0, 'NoPatch'
+ return 0, 'NoPatch', 'NoPatch'
else:
- return 0, 'New'
+ return 0, 'New', 'New'
-def main():
+def main(args):
parser = OptionParser()
parser.add_option("-b", "--base", dest="base")
parser.add_option("-p", "--port", dest="port")
parser.add_option("--debug", dest="debug", default=True)
- (options, args) = parser.parse_args()
+ (options, args) = parser.parse_args(args)
global global_base, base
global_base = base = options.base
app.run(debug=options.debug, host="0.0.0.0", port=int(options.port))
if __name__ == '__main__':
- main()
+ main(sys.argv)
@@ -24,7 +24,7 @@
<tr>
<td>{{'*' if ticket.pending else ''}}</td>
<td>{{ticket.report_count}}</td>
-<td><img src='/blob/{{ticket.report_status}}' alt='{{ticket.report_status}}' title='{{ticket.report_status}}' height=16></td>
+<td><img src='/blob/{{ticket.report_status_composite}}' alt='{{ticket.report_status_composite}}' title='{{ticket.report_status_composite}}' height=16></td>
<td align='right'>
<a href="/ticket/{{ticket.id}}/">{{ticket.id}}</a>
</td>
View
22 util.py
@@ -25,6 +25,28 @@ def prune_pending(ticket, machine=None, timeout=6*60*60):
reports.remove(report)
return reports
+def current_reports(ticket, base=None, unique=False):
+ if 'reports' not in ticket:
+ return []
+ if unique:
+ seen = set()
+ def first(x):
+ if x in seen:
+ return False
+ else:
+ seen.add(x)
+ return True
+ else:
+ first = lambda x: True
+ reports = list(ticket['reports'])
+ reports.sort(lambda a, b: cmp(b['time'], a['time']))
+ return filter(lambda report: (ticket['patches'] == report['patches'] and
+ ticket['spkgs'] == report['spkgs'] and
+ ticket['depends_on'] == (report.get('deps') or []) and
+ (not base or base == report['base'])) and
+ first('/'.join(report['machine'])),
+ reports)
+
def do_or_die(cmd):
print cmd
res = os.system(cmd)

0 comments on commit 05c48fe

Please sign in to comment.