Diff
Modified: trunk/Tools/ChangeLog (141128 => 141129)
--- trunk/Tools/ChangeLog 2013-01-29 17:58:07 UTC (rev 141128)
+++ trunk/Tools/ChangeLog 2013-01-29 18:07:52 UTC (rev 141129)
@@ -1,3 +1,50 @@
+2013-01-29 Alan Cutter <[email protected]>
+
+ QueueStatusServer needs pages to display historical queue data
+ https://bugs.webkit.org/show_bug.cgi?id=107659
+
+ Reviewed by Eric Seidel.
+
+ Created a /queue-charts/<queue-name> handler to present queue and patch data using Google Chart Tools.
+
+ * QueueStatusServer/app.yaml:
+ * QueueStatusServer/config/charts.py: Copied from Tools/QueueStatusServer/model/queuelog.py.
+ (get_time_unit):
+ * QueueStatusServer/filters/webkit_extras.py:
+ (webkit_linkify):
+ (webkit_bug_id):
+ (webkit_attachment_id):
+ (results_link):
+ (queue_status_link):
+ (queue_charts_link):
+ * QueueStatusServer/handlers/queuecharts.py: Added.
+ (QueueCharts):
+ (QueueCharts.get):
+ (QueueCharts._get_min_med_max):
+ (QueueCharts._get_patch_data):
+ (QueueCharts._get_patch_logs):
+ (QueueCharts._get_queue_data):
+ (QueueCharts._get_queue_logs):
+ (QueueCharts._get_time_unit):
+ (QueueCharts._get_timestamp):
+ (QueueCharts._get_view_range):
+ * QueueStatusServer/handlers/queuestatus.py:
+ (QueueStatus.get):
+ * QueueStatusServer/index.yaml:
+ * QueueStatusServer/main.py:
+ * QueueStatusServer/model/queuelog.py:
+ (QueueLog):
+ (QueueLog.create_key):
+ (QueueLog.get_at):
+ (QueueLog.get_current):
+ (QueueLog.get_or_create):
+ (QueueLog._get_or_create_txn):
+ * QueueStatusServer/stylesheets/charts.css: Added.
+ (.chart):
+ (.choices):
+ * QueueStatusServer/templates/queuecharts.html: Added.
+ * QueueStatusServer/templates/queuestatus.html:
+
2013-01-29 Mario Sanchez Prada <[email protected]>
[GTK] Missing build flags when building with Harfbuzz
Modified: trunk/Tools/QueueStatusServer/app.yaml (141128 => 141129)
--- trunk/Tools/QueueStatusServer/app.yaml 2013-01-29 17:58:07 UTC (rev 141128)
+++ trunk/Tools/QueueStatusServer/app.yaml 2013-01-29 18:07:52 UTC (rev 141129)
@@ -1,5 +1,5 @@
application: webkit-commit-queue
-version: 107775 # Bugzilla bug ID of last major change
+version: 107659 # Bugzilla bug ID of last major change
runtime: python
api_version: 1
Copied: trunk/Tools/QueueStatusServer/config/charts.py (from rev 141128, trunk/Tools/QueueStatusServer/model/queuelog.py) (0 => 141129)
--- trunk/Tools/QueueStatusServer/config/charts.py (rev 0)
+++ trunk/Tools/QueueStatusServer/config/charts.py 2013-01-29 18:07:52 UTC (rev 141129)
@@ -0,0 +1,61 @@
+# Copyright (C) 2013 Google Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+patch_log_limit = 500
+
+# All units are represented numerically as seconds.
+_one_minute_ = 60.0
+_one_hour_ = one_minute * 60.0
+_one_day_ = one_hour * 24.0
+_one_month_ = one_day * 30.0
+
+# How far back to view the history, specified in seconds.
+view_range_choices = [
+ {"name": "1 day", "view_range": one_day},
+ {"name": "1 week", "view_range": one_day * 7},
+ {"name": "1 month", "view_range": one_month},
+]
+
+default_view_range = one_day
+
+_time_units = [
+ #(threshold, time unit, name)
+ (0, one_hour, "hours"),
+ (4 * one_day, one_day, "days"),
+ (3 * one_month, one_month, "months"),
+]
+
+
+def get_time_unit(view_range):
+ current_threshold, current_time_unit, current_name = _time_units[0]
+ for threshold, time_unit, name in _time_units[1:]:
+ if view_range >= threshold:
+ current_time_unit, current_name = time_unit, name
+ else:
+ break
+ return current_time_unit, current_name
Modified: trunk/Tools/QueueStatusServer/filters/webkit_extras.py (141128 => 141129)
--- trunk/Tools/QueueStatusServer/filters/webkit_extras.py 2013-01-29 17:58:07 UTC (rev 141128)
+++ trunk/Tools/QueueStatusServer/filters/webkit_extras.py 2013-01-29 18:07:52 UTC (rev 141129)
@@ -36,6 +36,7 @@
bug_regexp = re.compile(r"bug (?P<bug_id>\d+)")
patch_regexp = re.compile(r"patch (?P<patch_id>\d+)")
+
@register.filter
@stringfilter
def webkit_linkify(value):
@@ -43,17 +44,32 @@
value = patch_regexp.sub(r'<a href="" \g<patch_id></a>', value)
return value
+
@register.filter
@stringfilter
def webkit_bug_id(value):
return '<a href="" % (value, value)
+
@register.filter
@stringfilter
def webkit_attachment_id(value):
return '<a href="" % (value, value)
+
@register.filter
@stringfilter
def results_link(status_id):
return '<a href="" % status_id
+
+
[email protected]
+@stringfilter
+def queue_status_link(queue_name, text):
+ return '<a href="" % (queue_name, text)
+
+
[email protected]
+@stringfilter
+def queue_charts_link(queue_name, text):
+ return '<a href="" % (queue_name, text)
Added: trunk/Tools/QueueStatusServer/handlers/queuecharts.py (0 => 141129)
--- trunk/Tools/QueueStatusServer/handlers/queuecharts.py (rev 0)
+++ trunk/Tools/QueueStatusServer/handlers/queuecharts.py 2013-01-29 18:07:52 UTC (rev 141129)
@@ -0,0 +1,151 @@
+# Copyright (C) 2013 Google Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import calendar
+from datetime import datetime
+import itertools
+from time import time
+
+from google.appengine.ext import webapp
+from google.appengine.ext.webapp import template
+
+from config import logging, charts
+from model.patchlog import PatchLog
+from model.queues import Queue
+from model.queuelog import QueueLog
+
+
+class QueueCharts(webapp.RequestHandler):
+ def get(self, queue_name):
+ queue_name = queue_name.lower()
+ if not Queue.queue_with_name(queue_name):
+ self.error(404)
+ return
+
+ timestamp = self._get_timestamp()
+ view_range = self._get_view_range()
+ time_unit, time_unit_name = charts.get_time_unit(view_range)
+
+ all_queue_names = map(Queue.name, Queue.all())
+
+ template_values = {
+ "all_queue_names": all_queue_names,
+ "patch_data": self._get_patch_data(queue_name, timestamp, view_range),
+ "queue_data": self._get_queue_data(queue_name, timestamp, view_range),
+ "queue_name": queue_name,
+ "seconds_ago_min": 0,
+ "seconds_ago_max": view_range,
+ "time_unit_name": time_unit_name,
+ "time_unit": time_unit,
+ "timestamp": timestamp,
+ "view_range": view_range,
+ "view_range_choices": charts.view_range_choices,
+ }
+ self.response.out.write(template.render("templates/queuecharts.html", template_values))
+
+ @classmethod
+ def _get_min_med_max(cls, values, defaults=(0, 0, 0)):
+ if not values:
+ return defaults
+ length = len(values)
+ sorted_values = sorted(values)
+ return sorted_values[0], sorted_values[length / 2], sorted_values[length - 1]
+
+ def _get_patch_data(self, queue_name, timestamp, view_range):
+ patch_logs = self._get_patch_logs(queue_name, timestamp, view_range)
+ patch_data = []
+ for patch_log in patch_logs:
+ if patch_log.process_duration and patch_log.wait_duration:
+ patch_log_timestamp = calendar.timegm(patch_log.date.utctimetuple())
+ patch_data.append({
+ "attachment_id": patch_log.attachment_id,
+ "seconds_ago": timestamp - patch_log_timestamp,
+ "process_duration": patch_log.process_duration / charts.one_minute,
+ "retry_count": patch_log.retry_count,
+ "status_update_count": patch_log.status_update_count,
+ "wait_duration": patch_log.wait_duration / charts.one_minute,
+ })
+ return patch_data
+
+ def _get_patch_logs(self, queue_name, timestamp, view_range):
+ patch_log_query = PatchLog.all()
+ patch_log_query = patch_log_query.filter("queue_name =", queue_name)
+ patch_log_query = patch_log_query.filter("date >=", datetime.utcfromtimestamp(timestamp - view_range))
+ patch_log_query = patch_log_query.filter("date <=", datetime.utcfromtimestamp(timestamp))
+ patch_log_query = patch_log_query.order("date")
+ return patch_log_query.run(limit=charts.patch_log_limit)
+
+ def _get_queue_data(self, queue_name, timestamp, view_range):
+ queue_logs = self._get_queue_logs(queue_name, timestamp, view_range)
+ queue_data = []
+ for queue_log in queue_logs:
+ queue_log_timestamp = calendar.timegm(queue_log.date.utctimetuple())
+ p_min, p_med, p_max = self._get_min_med_max(queue_log.patch_process_durations)
+ w_min, w_med, w_max = self._get_min_med_max(queue_log.patch_wait_durations)
+ queue_data.append({
+ "bots_seen": len(queue_log.bot_ids_seen),
+ "seconds_ago": timestamp - queue_log_timestamp,
+ "patch_processing_min": p_min,
+ "patch_processing_med": p_med,
+ "patch_processing_max": p_max,
+ "patch_retry_count": queue_log.patch_retry_count,
+ "patch_waiting_min": w_min,
+ "patch_waiting_med": w_med,
+ "patch_waiting_max": w_max,
+ "patches_completed": len(queue_log.patch_process_durations),
+ "patches_waiting": queue_log.max_patches_waiting,
+ "status_update_count": queue_log.status_update_count,
+ })
+ return queue_data
+
+ def _get_queue_logs(self, queue_name, timestamp, view_range):
+ queue_logs = []
+ current_timestamp = timestamp - view_range
+ while current_timestamp <= timestamp:
+ queue_logs.append(QueueLog.get_at(queue_name, logging.queue_log_duration, current_timestamp))
+ current_timestamp += logging.queue_log_duration
+ return queue_logs
+
+ @classmethod
+ def _get_time_unit(cls, view_range):
+ if view_range > charts.one_day * 2:
+ return
+
+ def _get_timestamp(self):
+ timestamp = self.request.get("timestamp")
+ try:
+ return int(timestamp)
+ except ValueError:
+ return int(time())
+
+ def _get_view_range(self):
+ view_range = self.request.get("view_range")
+ try:
+ return int(view_range)
+ except ValueError:
+ return charts.default_view_range
Modified: trunk/Tools/QueueStatusServer/handlers/queuestatus.py (141128 => 141129)
--- trunk/Tools/QueueStatusServer/handlers/queuestatus.py 2013-01-29 17:58:07 UTC (rev 141128)
+++ trunk/Tools/QueueStatusServer/handlers/queuestatus.py 2013-01-29 18:07:52 UTC (rev 141129)
@@ -104,6 +104,7 @@
statuses = self._fetch_statuses(queue, bot_id)
template_values = {
+ "queue_name": queue_name,
"page_title": self._page_title(queue, bot_id),
"work_item_rows": self._rows_for_work_items(queue),
"status_groups": self._build_status_groups(statuses),
Modified: trunk/Tools/QueueStatusServer/index.yaml (141128 => 141129)
--- trunk/Tools/QueueStatusServer/index.yaml 2013-01-29 17:58:07 UTC (rev 141128)
+++ trunk/Tools/QueueStatusServer/index.yaml 2013-01-29 18:07:52 UTC (rev 141129)
@@ -10,6 +10,11 @@
# automatically uploaded to the admin console when you next deploy
# your application using appcfg.py.
+- kind: PatchLog
+ properties:
+ - name: queue_name
+ - name: date
+
- kind: QueueStatus
properties:
- name: active_patch_id
Modified: trunk/Tools/QueueStatusServer/main.py (141128 => 141129)
--- trunk/Tools/QueueStatusServer/main.py 2013-01-29 17:58:07 UTC (rev 141128)
+++ trunk/Tools/QueueStatusServer/main.py 2013-01-29 18:07:52 UTC (rev 141129)
@@ -39,6 +39,7 @@
from handlers.nextpatch import NextPatch
from handlers.patch import Patch
from handlers.patchstatus import PatchStatus
+from handlers.queuecharts import QueueCharts
from handlers.queuestatus import QueueStatus
from handlers.recentstatus import QueuesOverview
from handlers.releasepatch import ReleasePatch
@@ -63,6 +64,7 @@
(r'/results/(.*)', ShowResults),
(r'/status-bubble/(.*)', StatusBubble),
(r'/svn-revision/(.*)', SVNRevision),
+ (r'/queue-charts/(.*)', QueueCharts),
(r'/queue-status/(.*)/bots/(.*)', QueueStatus),
(r'/queue-status/(.*)', QueueStatus),
(r'/next-patch/(.*)', NextPatch),
Modified: trunk/Tools/QueueStatusServer/model/queuelog.py (141128 => 141129)
--- trunk/Tools/QueueStatusServer/model/queuelog.py 2013-01-29 17:58:07 UTC (rev 141128)
+++ trunk/Tools/QueueStatusServer/model/queuelog.py 2013-01-29 18:07:52 UTC (rev 141129)
@@ -44,14 +44,29 @@
patch_retry_count = db.IntegerProperty(default=0)
status_update_count = db.IntegerProperty(default=0)
+ @staticmethod
+ def create_key(queue_name, duration, timestamp):
+ return "%s-%s-%s" % (queue_name, duration, timestamp)
+
@classmethod
- def get_current(cls, queue_name, duration):
- timestamp_now = time()
- timestamp = int(timestamp_now / duration) * duration
+ def get_at(cls, queue_name, duration, timestamp):
+ timestamp = int(timestamp / duration) * duration
date = datetime.utcfromtimestamp(timestamp)
key = cls.create_key(queue_name, duration, timestamp)
- return cls.get_or_insert(key, date=date, duration=duration, queue_name=queue_name)
+ return cls.get_or_create(key, date=date, duration=duration, queue_name=queue_name)
- @staticmethod
- def create_key(queue_name, duration, timestamp):
- return "%s-%s-%s" % (queue_name, duration, timestamp)
+ @classmethod
+ def get_current(cls, queue_name, duration):
+ return cls.get_at(queue_name, duration, time())
+
+ # This is to prevent page requests from generating lots of rows in the database.
+ @classmethod
+ def get_or_create(cls, key_name, **kwargs):
+ return db.run_in_transaction(cls._get_or_create_txn, key_name, **kwargs)
+
+ @classmethod
+ def _get_or_create_txn(cls, key_name, **kwargs):
+ entity = cls.get_by_key_name(key_name, parent=kwargs.get('parent'))
+ if entity is None:
+ entity = cls(key_name=key_name, **kwargs)
+ return entity
Added: trunk/Tools/QueueStatusServer/stylesheets/charts.css (0 => 141129)
--- trunk/Tools/QueueStatusServer/stylesheets/charts.css (rev 0)
+++ trunk/Tools/QueueStatusServer/stylesheets/charts.css 2013-01-29 18:07:52 UTC (rev 141129)
@@ -0,0 +1,8 @@
+.chart {
+ margin-bottom: 40px;
+ height: 200px;
+}
+
+.choices {
+ font-size: 0.75em;
+}
\ No newline at end of file
Added: trunk/Tools/QueueStatusServer/templates/queuecharts.html (0 => 141129)
--- trunk/Tools/QueueStatusServer/templates/queuecharts.html (rev 0)
+++ trunk/Tools/QueueStatusServer/templates/queuecharts.html 2013-01-29 18:07:52 UTC (rev 141129)
@@ -0,0 +1,284 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>{{ queue_name }} Charts</title>
+ <link type="text/css" rel="stylesheet" href="" />
+ <link type="text/css" rel="stylesheet" href="" />
+ <script type="text/_javascript_" src=""
+ <script type="text/_javascript_">
+ google.load('visualization', '1.0', {'packages':['corechart']});
+ google.setOnLoadCallback(function () {
+
+ function secondsToString(seconds) {
+ var _oneSecond_ = 1;
+ var _oneMinute_ = 60;
+ var _oneHour_ = oneMinute * 60;
+ var _oneDay_ = oneHour * 24;
+ var _oneYear_ = oneDay * 365.25;
+ var unitArray = [
+ [oneYear, "year"],
+ [oneDay, "day"],
+ [oneHour, "hour"],
+ [oneMinute, "minute"],
+ [oneSecond, "second"],
+ ];
+ var result = "";
+ for (var i = 0; i < unitArray.length; i++) {
+ var unit = unitArray[i][0];
+ if (seconds >= unit) {
+ if (result !== "") {
+ result += " ";
+ }
+ var name = unitArray[i][1];
+ var number = Math.floor(seconds/unit);
+ result += number + " " + name + (number > 1 ? "s" : "");
+ seconds %= unit;
+ }
+ }
+ if (result === "") {
+ return "0 seconds";
+ }
+ return result;
+ }
+
+ var data, chart, options;
+
+ var timeString = new Date({{ timestamp }} * 1000).toString();
+ var timestampDiv = document.getElementById("timestamp");
+ timestampDiv.innerHTML = "Viewing from " + timeString;
+
+ options = {
+ legend: {position: "top"},
+ hAxis: {
+ title: "{{ time_unit_name|capfirst }} Ago",
+ direction: -1,
+ viewWindow: {
+ min: {{ seconds_ago_min }} / {{ time_unit }},
+ max: {{ seconds_ago_max }} / {{ time_unit }},
+ },
+ gridlines: {
+ count: 9,
+ },
+ },
+ vAxis: {
+ viewWindow: {min: 0 },
+ },
+ lineWidth: 3,
+ };
+
+ // CHART 1
+ options.colors = ["green", "red", "orange"];
+ data = "" google.visualization.DataTable();
+ data.addColumn("number", "<time>");
+ data.addColumn("number", "Patches Completed");
+ data.addColumn({type: "string", role: "tooltip"});
+ data.addColumn("number", "Patches Waiting");
+ data.addColumn({type: "string", role: "tooltip"});
+ data.addColumn("number", "Bots (visible to server)");
+ data.addColumn({type: "string", role: "tooltip"});
+ data.addRows([
+ {% for queue_datum in queue_data %}
+ [
+ {{ queue_datum.seconds_ago }} / {{ time_unit }},
+ {{ queue_datum.patches_completed }},
+ "Patches Completed: " + {{ queue_datum.patches_completed }} + "\n" + secondsToString({{ queue_datum.seconds_ago }}) + " ago",
+ {{ queue_datum.patches_waiting }},
+ "Patches Waiting: " + {{ queue_datum.patches_waiting }} + "\n" + secondsToString({{ queue_datum.seconds_ago }}) + " ago",
+ {{ queue_datum.bots_seen }},
+ "Bots (visible to server): " + {{ queue_datum.bots_seen }} + "\n" + secondsToString({{ queue_datum.seconds_ago }}) + " ago",
+ ],
+ {% endfor %}
+ ]);
+ chart = new google.visualization.LineChart(document.getElementById('chart1'));
+ chart.draw(data, options);
+
+ // CHART 2
+ options.colors = ["blue", "purple"];
+ data = "" google.visualization.DataTable();
+ data.addColumn("number", "<time>");
+ data.addColumn("number", "Status Updates");
+ data.addColumn({type: "string", role: "tooltip"});
+ data.addColumn("number", "Patch Retries");
+ data.addColumn({type: "string", role: "tooltip"});
+ data.addRows([
+ {% for queue_datum in queue_data %}
+ [
+ {{ queue_datum.seconds_ago }} / {{ time_unit }},
+ {{ queue_datum.status_update_count }},
+ "Status Updates: " + {{ queue_datum.status_update_count }} + "\n" + secondsToString({{ queue_datum.seconds_ago }}) + " ago",
+ {{ queue_datum.patch_retry_count }},
+ "Patch Retries: " + {{ queue_datum.patch_retry_count }} + "\n" + secondsToString({{ queue_datum.seconds_ago }}) + " ago",
+ ],
+ {% endfor %}
+ ]);
+ chart = new google.visualization.LineChart(document.getElementById('chart2'));
+ chart.draw(data, options);
+
+ // CHART 3
+ options.colors = ["brown"];
+ options.vAxis.title = "Minutes";
+ data = "" google.visualization.DataTable();
+ data.addColumn("number", "<time>");
+ data.addColumn("number", "Patch Processing Times");
+ data.addColumn({type: "string", role: "tooltip"});
+ data.addColumn({type: "number", role: "interval"});
+ data.addColumn({type: "number", role: "interval"});
+ data.addRows([
+ {% for queue_datum in queue_data %}
+ [
+ {{ queue_datum.seconds_ago }} / {{ time_unit }},
+ {{ queue_datum.patch_processing_med }} / 60,
+ "Patch Processing Times\nMax: " + secondsToString({{ queue_datum.patch_processing_max }}) + "\nMedian: " + secondsToString({{ queue_datum.patch_processing_med }}) + "\nMin: " + secondsToString({{ queue_datum.patch_processing_min }}) + "\n" + secondsToString({{ queue_datum.seconds_ago }}) + " ago",
+ {{ queue_datum.patch_processing_min }} / 60,
+ {{ queue_datum.patch_processing_max }} / 60,
+ ],
+ {% endfor %}
+ ]);
+ chart = new google.visualization.LineChart(document.getElementById('chart3'));
+ chart.draw(data, options);
+
+ // CHART 4
+ options.colors = ["red"];
+ data = "" google.visualization.DataTable();
+ data.addColumn("number", "<time>");
+ data.addColumn("number", "Patch Waiting Times");
+ data.addColumn({type: "string", role: "tooltip"});
+ data.addColumn({type: "number", role: "interval"});
+ data.addColumn({type: "number", role: "interval"});
+ data.addRows([
+ {% for queue_datum in queue_data %}
+ [
+ {{ queue_datum.seconds_ago }} / {{ time_unit }},
+ {{ queue_datum.patch_waiting_med }} / 60,
+ "Patch Waiting Times\nMax: " + secondsToString({{ queue_datum.patch_waiting_max }}) + "\nMedian: " + secondsToString({{ queue_datum.patch_waiting_med }}) + "\nMin: " + secondsToString({{ queue_datum.patch_waiting_min }}) + "\n" + secondsToString({{ queue_datum.seconds_ago }}) + " ago",
+ {{ queue_datum.patch_waiting_min }} / 60,
+ {{ queue_datum.patch_waiting_max }} / 60,
+ ],
+ {% endfor %}
+ ]);
+ chart = new google.visualization.LineChart(document.getElementById('chart4'));
+ chart.draw(data, options);
+
+ function postPatchLink (selection) {
+ if (selection.length > 0 && selection[0].row !== undefined) {
+ var attachmentIdArray = [{% for patch_datum in patch_data %}{{ patch_datum.attachment_id }}, {% endfor %}];
+ var attachmentId = attachmentIdArray[selection[0].row];
+ var aTag = document.getElementById("selectedPatch");
+ aTag.innerHTML = aTag.href = "" + window.location.host + "/patch/" + attachmentId;
+ }
+ }
+
+ // CHART 5
+ options.colors = ["brown", "red"];
+ options.hAxis.title = "{{ time_unit_name|capfirst }} Ago";
+ options.hAxis.viewWindow.min = {{ seconds_ago_min }} / {{ time_unit }};
+ options.hAxis.viewWindow.max = {{ seconds_ago_max }} / {{ time_unit }};
+ delete options.lineWidth;
+ data = "" google.visualization.DataTable();
+ data.addColumn("number", "<time>");
+ data.addColumn("number", "Process Duration");
+ data.addColumn({type: "string", role: "tooltip"});
+ data.addColumn("number", "Wait Duration");
+ data.addColumn({type: "string", role: "tooltip"});
+ data.addRows([
+ {% for patch_datum in patch_data %}
+ [
+ {{ patch_datum.seconds_ago }} / {{ time_unit }},
+ {{ patch_datum.process_duration }},
+ "Patch {{ patch_datum.attachment_id }}\n" + secondsToString({{ patch_datum.seconds_ago }}) + " ago",
+ {{ patch_datum.wait_duration }},
+ "Patch {{ patch_datum.attachment_id }}\n" + secondsToString({{ patch_datum.seconds_ago }}) + " ago",
+ ],
+ {% endfor %}
+ ]);
+ chart = new google.visualization.ScatterChart(document.getElementById('chart5'));
+ var chart5 = chart;
+ google.visualization.events.addListener(chart, "select", function () {postPatchLink(chart5.getSelection());});
+ chart.draw(data, options);
+
+ // CHART 6
+ options.colors = ["blue", "purple"];
+ delete options.vAxis.title;
+ data = "" google.visualization.DataTable();
+ data.addColumn("number", "<time>");
+ data.addColumn("number", "Status Updates");
+ data.addColumn({type: "string", role: "tooltip"});
+ data.addColumn("number", "Retries");
+ data.addColumn({type: "string", role: "tooltip"});
+ data.addRows([
+ {% for patch_datum in patch_data %}
+ [
+ {{ patch_datum.seconds_ago }} / {{ time_unit }},
+ {{ patch_datum.status_update_count }},
+ "Patch {{ patch_datum.attachment_id }}\n" + secondsToString({{ patch_datum.seconds_ago }}) + " ago",
+ {{ patch_datum.retry_count }},
+ "Patch {{ patch_datum.attachment_id }}\n" + secondsToString({{ patch_datum.seconds_ago }}) + " ago",
+ ],
+ {% endfor %}
+ ]);
+ chart = new google.visualization.ScatterChart(document.getElementById('chart6'));
+ var chart6 = chart;
+ google.visualization.events.addListener(chart, "select", function () {postPatchLink(chart6.getSelection());});
+ chart.draw(data, options);
+ });
+
+ function setURLParameter (parameterName, newValue) {
+ var split;
+ split = window.location.href.split("?");
+ var url, parameterArray;
+ url = ""
+ if (split.length > 1) {
+ parameterArray = split[1].split("&");
+ } else {
+ parameterArray = [];
+ }
+ var setParameter = false;
+ for (var i = 0; i < parameterArray.length; i++) {
+ var currentParameterName = decodeURIComponent(parameterArray[i].split("=")[0]);
+ if (currentParameterName === parameterName) {
+ parameterArray[i] = encodeURIComponent(parameterName) + "=" + encodeURIComponent(newValue);
+ setParameter = true;
+ break;
+ }
+ }
+ if (!setParameter) {
+ parameterArray.push(encodeURIComponent(parameterName) + "=" + encodeURIComponent(newValue));
+ }console.log(parameterArray);
+ window.location.href = "" + "?" + parameterArray.join("&");
+ }
+ </script>
+ </head>
+
+ <body>
+ <div class="choices">
+ {% for single_queue_name in all_queue_names %}
+ {% if single_queue_name == queue_name %}
+ {{ queue_name }}
+ {% else %}
+ {{ single_queue_name|force_escape|queue_charts_link:single_queue_name|safe }}
+ {% endif %}
+ {% if not forloop.last %} | {% endif %}
+ {% endfor %}
+ </div>
+ <h1>{{ queue_name }} Charts</h1>
+ <div>[{{ queue_name|force_escape|queue_status_link:"status"|safe }}]</div>
+ <div id="timestamp"></div>
+ <div class="choices">Viewing range:
+ {% for view_range_choice in view_range_choices %}
+ {% if view_range_choice.view_range == view_range %}
+ {{ view_range_choice.name }}
+ {% else %}
+ <a href="" {{ view_range_choice.view_range }})">{{ view_range_choice.name }}</a>
+ {% endif %}
+ {% if not forloop.last %} | {% endif %}
+ {% endfor %}
+ </div>
+ <div class="chart" id="chart1"></div>
+ <div class="chart" id="chart2"></div>
+ <div class="chart" id="chart3"></div>
+ <div class="chart" id="chart4"></div>
+ <div class="chart" id="chart5"></div>
+ <div class="chart" id="chart6"></div>
+ Selected patch: <a id="selectedPatch">(None)</div>
+ </body>
+</html>
Modified: trunk/Tools/QueueStatusServer/templates/queuestatus.html (141128 => 141129)
--- trunk/Tools/QueueStatusServer/templates/queuestatus.html 2013-01-29 17:58:07 UTC (rev 141128)
+++ trunk/Tools/QueueStatusServer/templates/queuestatus.html 2013-01-29 18:07:52 UTC (rev 141129)
@@ -9,6 +9,7 @@
<h3>Summary</h3>
<div>
+<div>[{{ queue_name|force_escape|queue_charts_link:"charts"|safe }}]</div>
Last Pass: {% if last_pass %}{{ last_pass.date|timesince }} ago{% else %}never{% endif %}
{% if not bot_id and last_pass.bot_id %}
by <a href="" last_pass.bot_id }}</a>