socorro r19 - in trunk: . webapp webapp/Socorro.egg-info webapp/socorro/config webapp/socorro/controllers webapp/socorro/lib webapp/socorro/models webapp/socorro/public/css webapp/socorro/templates webapp/socorro/templates/includes webapp/socorro/templates/report webapp/socorro/templates/topcrasher
- From: fherrera svn gnome org
- To: svn-commits-list gnome org
- Subject: socorro r19 - in trunk: . webapp webapp/Socorro.egg-info webapp/socorro/config webapp/socorro/controllers webapp/socorro/lib webapp/socorro/models webapp/socorro/public/css webapp/socorro/templates webapp/socorro/templates/includes webapp/socorro/templates/report webapp/socorro/templates/topcrasher
- Date: Fri, 29 Aug 2008 15:36:44 +0000 (UTC)
Author: fherrera
Date: Fri Aug 29 15:36:44 2008
New Revision: 19
URL: http://svn.gnome.org/viewvc/socorro?rev=19&view=rev
Log:
2008-08-29 Fernando Herrera <fherrera novell com>
* webapp/Socorro.egg-info/PKG-INFO:
* webapp/Socorro.egg-info/SOURCES.txt:
* webapp/Socorro.egg-info/requires.txt:
* webapp/setup.py:
* webapp/socorro/config/routing.py:
* webapp/socorro/controllers/report.py:
* webapp/socorro/controllers/topcrasher.py:
* webapp/socorro/lib/base.py:
* webapp/socorro/lib/collect.py:
* webapp/socorro/lib/helpers.py:
* webapp/socorro/lib/monitor.py:
* webapp/socorro/lib/platforms.py:
* webapp/socorro/lib/processor.py:
* webapp/socorro/lib/queryparams.py:
* webapp/socorro/models/__init__.py:
* webapp/socorro/public/css/layout.css:
* webapp/socorro/public/css/style.css:
* webapp/socorro/templates/includes/common.html:
* webapp/socorro/templates/includes/site.html:
* webapp/socorro/templates/query_form.html:
* webapp/socorro/templates/report/index.html:
* webapp/socorro/templates/report/list.html:
* webapp/socorro/templates/topcrasher/bybranch.html:
* webapp/socorro/templates/topcrasher/byversion.html:
* webapp/socorro/templates/topcrasher/index.html: Update to lastest
socorro sources from 2008-08-29 from http://socorro.googlecode.com/svn
revision 472
Modified:
trunk/ChangeLog
trunk/webapp/Socorro.egg-info/PKG-INFO
trunk/webapp/Socorro.egg-info/SOURCES.txt
trunk/webapp/Socorro.egg-info/requires.txt
trunk/webapp/setup.py
trunk/webapp/socorro/config/routing.py
trunk/webapp/socorro/controllers/report.py
trunk/webapp/socorro/controllers/topcrasher.py
trunk/webapp/socorro/lib/base.py
trunk/webapp/socorro/lib/collect.py
trunk/webapp/socorro/lib/helpers.py
trunk/webapp/socorro/lib/monitor.py
trunk/webapp/socorro/lib/platforms.py
trunk/webapp/socorro/lib/processor.py
trunk/webapp/socorro/lib/queryparams.py
trunk/webapp/socorro/models/__init__.py
trunk/webapp/socorro/public/css/layout.css
trunk/webapp/socorro/public/css/style.css
trunk/webapp/socorro/templates/includes/common.html
trunk/webapp/socorro/templates/includes/site.html
trunk/webapp/socorro/templates/query_form.html
trunk/webapp/socorro/templates/report/index.html
trunk/webapp/socorro/templates/report/list.html
trunk/webapp/socorro/templates/topcrasher/bybranch.html
trunk/webapp/socorro/templates/topcrasher/byversion.html
trunk/webapp/socorro/templates/topcrasher/index.html
Modified: trunk/webapp/Socorro.egg-info/PKG-INFO
==============================================================================
--- trunk/webapp/Socorro.egg-info/PKG-INFO (original)
+++ trunk/webapp/Socorro.egg-info/PKG-INFO Fri Aug 29 15:36:44 2008
@@ -1,6 +1,6 @@
Metadata-Version: 1.0
Name: Socorro
-Version: 0.1adev-r2
+Version: 0.1adev-r352
Summary: Breakpad Server
Home-page: http://code.google.com/p/socorro/
Author: Robert Sayre
Modified: trunk/webapp/Socorro.egg-info/SOURCES.txt
==============================================================================
--- trunk/webapp/Socorro.egg-info/SOURCES.txt (original)
+++ trunk/webapp/Socorro.egg-info/SOURCES.txt Fri Aug 29 15:36:44 2008
@@ -24,6 +24,7 @@
socorro/controllers/error.py
socorro/controllers/query.py
socorro/controllers/report.py
+socorro/controllers/status.py
socorro/controllers/symbols.py
socorro/controllers/topcrasher.py
socorro/docs/index.txt
@@ -49,15 +50,82 @@
socorro/lib/simplejson/jsonfilter.py
socorro/lib/simplejson/scanner.py
socorro/models/__init__.py
+socorro/public/calendar.png
socorro/public/favicon.ico
socorro/public/footer_bg.png
socorro/public/header_bg.png
+socorro/public/loading.png
+socorro/public/css/datePicker.css
socorro/public/css/layout.css
socorro/public/css/style.css
+socorro/public/css/flora/flora.accordion.css
+socorro/public/css/flora/flora.all.css
+socorro/public/css/flora/flora.calendar.css
+socorro/public/css/flora/flora.css
+socorro/public/css/flora/flora.datepicker.css
+socorro/public/css/flora/flora.dialog.css
+socorro/public/css/flora/flora.menu.css
+socorro/public/css/flora/flora.resizable.css
+socorro/public/css/flora/flora.slider.css
+socorro/public/css/flora/flora.tablesorter.css
+socorro/public/css/flora/flora.tabs.css
+socorro/public/css/flora/flora.toasterLite.css
+socorro/public/css/flora/flora.toolbar.css
+socorro/public/css/flora/i/accordion-left-act.png
+socorro/public/css/flora/i/accordion-left-over.png
+socorro/public/css/flora/i/accordion-left.png
+socorro/public/css/flora/i/accordion-middle-act.png
+socorro/public/css/flora/i/accordion-middle-over.png
+socorro/public/css/flora/i/accordion-middle.png
+socorro/public/css/flora/i/accordion-right-act.png
+socorro/public/css/flora/i/accordion-right-over.png
+socorro/public/css/flora/i/accordion-right.png
+socorro/public/css/flora/i/asc.gif
+socorro/public/css/flora/i/bg.gif
+socorro/public/css/flora/i/desc.gif
+socorro/public/css/flora/i/dialog-e.gif
+socorro/public/css/flora/i/dialog-n.gif
+socorro/public/css/flora/i/dialog-ne.gif
+socorro/public/css/flora/i/dialog-nw.gif
+socorro/public/css/flora/i/dialog-s.gif
+socorro/public/css/flora/i/dialog-se.gif
+socorro/public/css/flora/i/dialog-sw.gif
+socorro/public/css/flora/i/dialog-title.gif
+socorro/public/css/flora/i/dialog-titlebar-close-hover.png
+socorro/public/css/flora/i/dialog-titlebar-close.png
+socorro/public/css/flora/i/dialog-w.gif
+socorro/public/css/flora/i/menu-submenu.gif
+socorro/public/css/flora/i/resizable-e.gif
+socorro/public/css/flora/i/resizable-n.gif
+socorro/public/css/flora/i/resizable-ne.gif
+socorro/public/css/flora/i/resizable-nw.gif
+socorro/public/css/flora/i/resizable-s.gif
+socorro/public/css/flora/i/resizable-se.gif
+socorro/public/css/flora/i/resizable-sw.gif
+socorro/public/css/flora/i/resizable-w.gif
+socorro/public/css/flora/i/shadow.png
+socorro/public/css/flora/i/slider-bg-1.png
+socorro/public/css/flora/i/slider-bg-2.png
+socorro/public/css/flora/i/slider-handle.gif
+socorro/public/css/flora/i/tabs.png
+socorro/public/css/flora/i/toaster-e.gif
+socorro/public/css/flora/i/toaster-n.gif
+socorro/public/css/flora/i/toaster-ne.gif
+socorro/public/css/flora/i/toaster-nw.gif
+socorro/public/css/flora/i/toaster-s.gif
+socorro/public/css/flora/i/toaster-se.gif
+socorro/public/css/flora/i/toaster-sw.gif
+socorro/public/css/flora/i/toaster-w.gif
+socorro/public/css/flora/i/toolbars-bot-bg.gif
socorro/public/js/MochiKit/MochiKit.js
socorro/public/js/PlotKit/PlotKit_Packed.js
socorro/public/js/PlotKit/excanvas.js
socorro/public/js/PlotKit/iecanvas.htc
+socorro/public/js/jquery/date.js
+socorro/public/js/jquery/jquery-1.2.1.js
+socorro/public/js/jquery/plugins/ui/jquery.datePicker.js
+socorro/public/js/jquery/plugins/ui/jquery.tablesorter.min.js
+socorro/public/js/jquery/plugins/ui/ui.tabs.js
socorro/templates/__init__.py
socorro/templates/admin_index.html
socorro/templates/branch_maintenance.html
@@ -69,6 +137,9 @@
socorro/templates/report/__init__.py
socorro/templates/report/index.html
socorro/templates/report/list.html
+socorro/templates/report/pending.html
+socorro/templates/status/__init__.py
+socorro/templates/status/index.html
socorro/templates/topcrasher/__init__.py
socorro/templates/topcrasher/bybranch.html
socorro/templates/topcrasher/byversion.html
Modified: trunk/webapp/Socorro.egg-info/requires.txt
==============================================================================
--- trunk/webapp/Socorro.egg-info/requires.txt (original)
+++ trunk/webapp/Socorro.egg-info/requires.txt Fri Aug 29 15:36:44 2008
@@ -1,5 +1,5 @@
Pylons==0.9.5
-SQLAlchemy==0.3.7
+SQLAlchemy>=0.3,<0.4
Genshi>=0.3.6
-Authkit>=0.3.0pre5
+Authkit==0.3.0pre5
PasteScript>=1.3.6
\ No newline at end of file
Modified: trunk/webapp/setup.py
==============================================================================
--- trunk/webapp/setup.py (original)
+++ trunk/webapp/setup.py Fri Aug 29 15:36:44 2008
@@ -7,8 +7,8 @@
author="Robert Sayre",
author_email="sayrer gmail com",
url="http://code.google.com/p/socorro/",
- install_requires=["Pylons==0.9.5", "SQLAlchemy==0.3.7", "Genshi>=0.3.6",
- "Authkit>=0.3.0pre5", "PasteScript>=1.3.6"],
+ install_requires=["Pylons==0.9.5", "SQLAlchemy>=0.3,<0.4", "Genshi>=0.3.6",
+ "Authkit==0.3.0pre5", "PasteScript>=1.3.6"],
packages=find_packages(),
include_package_data=True,
test_suite = 'nose.collector',
Modified: trunk/webapp/socorro/config/routing.py
==============================================================================
--- trunk/webapp/socorro/config/routing.py (original)
+++ trunk/webapp/socorro/config/routing.py Fri Aug 29 15:36:44 2008
@@ -12,6 +12,9 @@
# Set default route to the list of reports. We can change this later if we
# create a dashboard or better entry page.
map.connect('', controller='query', action='query')
+
+ # Route to server status page.
+ map.connect('status', controller='status', action='index')
# Routes to topcrasher reports. :product and :version follow 'by....'
# because I thought it was correct to have parameters after the controller
@@ -20,6 +23,11 @@
controller='topcrasher', action='byversion',
requirements=dict(product='[a-zA-Z.]+',
version='[0-9a-zA-Z.]+'))
+ map.connect('topcrasher/byversion/:product/:version/:buildId',
+ controller='topcrasher', action='byversion',
+ requirements=dict(product='[a-zA-Z.]+',
+ version='[0-9a-zA-Z.]+',
+ buildId='[0-9]+'))
map.connect('topcrasher/bybranch/:branch',
controller='topcrasher', action='bybranch',
requirements=dict(branch='[0-9a-zA-Z.]+'))
Modified: trunk/webapp/socorro/controllers/report.py
==============================================================================
--- trunk/webapp/socorro/controllers/report.py (original)
+++ trunk/webapp/socorro/controllers/report.py Fri Aug 29 15:36:44 2008
@@ -1,6 +1,6 @@
from socorro.lib.base import *
from socorro.lib.processor import Processor
-from socorro.models import Report
+from socorro.models import Report, Job, PriorityJob
from socorro.lib.queryparams import BySignatureLimit, getReportsForParams
from socorro.lib.http_cache import responseForKey
import socorro.lib.collect as collect
@@ -16,15 +16,51 @@
class ReportController(BaseController):
def index(self, id):
c.report = Report.by_id(id)
+
+ # If we get no id, 404 -- id can't be blank.
+ if id is None:
+ abort(404, 'Page Not Found')
+
+ # If we don't have a report entry, see if it's in the queue.
+ # If it's there, flag it for priority so the user can see it in ~10 seconds.
+ #
+ # This should not be cached, so we explicitly send no-cache response
+ # headers.
if c.report is None:
- abort(404, 'Not found')
- if c.report['build']:
- resp = responseForKey(c.report['uuid'], expires=(60 * 60))
+ # The pending page adds ?p=1 to redirects. Check this to avoid inserting
+ # duplicate priorityjob entries.
+ try:
+ request.params['p']
+ except(KeyError):
+ c.priority = PriorityJob.add(id)
+
+ h.redirect_to(action='pending', id=id)
+
+ # If we have the report entry, show it as usual.
else:
- resp = responseForKey(c.report['uuid'])
- resp.write(render('report/index'))
- return resp
+ if c.report['build']:
+ resp = responseForKey(c.report['uuid'], expires=(60 * 60))
+ else:
+ resp = responseForKey(c.report['uuid'])
+
+ resp.write(render('report/index'))
+ return resp
+
+ def pending(self, id):
+
+ # If we get no id, 404 -- id can't be blank.
+ if id is None:
+ abort(404, 'Page Not Found')
+
+ # Check to see if the report already got processed at some point and redirect to it if so.
+ c.report = Report.by_id(id)
+ if c.report is not None:
+ h.redirect_to(action='index', id=id)
+
+ c.job = Job.by_uuid(id)
+
+ return render_response('report/pending')
def find(self):
# This method should not touch the database!
@@ -44,7 +80,6 @@
c.params.setFromParams(request.params)
key = "reportlist_%s" % request.environ["QUERY_STRING"]
(c.reports, c.builds, ts) = getReportsForParams(c.params, key)
-
resp = responseForKey("%s%s" % (ts,key))
resp.write(render('report/list'))
return resp
Modified: trunk/webapp/socorro/controllers/topcrasher.py
==============================================================================
--- trunk/webapp/socorro/controllers/topcrasher.py (original)
+++ trunk/webapp/socorro/controllers/topcrasher.py Fri Aug 29 15:36:44 2008
@@ -1,7 +1,7 @@
from socorro.models import Report, reports_table, Branch, branches_table
from socorro.models import getCachedBranchData
from socorro.lib.base import BaseController
-from socorro.lib.queryparams import BaseLimit, getCrashesForParams
+from socorro.lib.queryparams import BaseLimit, getTopCrashes
from socorro.lib.http_cache import responseForKey
from pylons.database import create_engine
from pylons import c, session, request
@@ -24,17 +24,23 @@
user can choose which report they want to see.
"""
(c.products, c.branches, c.product_versions) = getCachedBranchData()
- resp = render_response('topcrasher/index')
+ etag = "%s%s" % (len(c.branches), len(c.product_versions))
+ resp = responseForKey(etag)
+ resp.write(render('topcrasher/index'))
return resp
- def byversion(self, product, version):
+ def byversion(self, product, version, buildId=None):
"""
The purpose of this action is to generate topcrasher reports based on
product and version.
"""
- c.params = BaseLimit(versions=[(product, version)], range=(2, 'weeks'))
- (c.tc, ts) = getCrashesForParams(c.params,"v_%s%s" % (product, version))
- etag = "%s%s%s" % (product, version, ts)
+ if buildId:
+ (c.tc, ts, c.last_updated) = getTopCrashes(product, version, "v_%s%s%s" % (product, version, buildId), buildId=buildId)
+ etag = "%s%s%s%s" % (product, version, buildId, ts)
+ else:
+ (c.tc, ts, c.last_updated) = getTopCrashes(product, version, "v_%s%s" % (product, version))
+ etag = "%s%s%s" % (product, version, ts)
+
resp = responseForKey(etag)
resp.write(render('topcrasher/byversion'))
return resp
@@ -44,6 +50,7 @@
The purpose of this action is to generate topcrasher reports based on
branch.
"""
+
c.params = BaseLimit(branches=[branch], range=(2, 'weeks'))
(c.tc, ts) = getCrashesForParams(c.params, "branch_" + branch)
etag = "%s%s" % (branch, ts)
Modified: trunk/webapp/socorro/lib/base.py
==============================================================================
--- trunk/webapp/socorro/lib/base.py (original)
+++ trunk/webapp/socorro/lib/base.py Fri Aug 29 15:36:44 2008
@@ -27,7 +27,9 @@
kwargs['echo'] = echo
database.get_engines()['%s|%s' % (uri, str(kwargs))] = \
database.create_engine(uri, echo=echo,
- pool_recycle=config.processorConnTimeout)
+ pool_recycle=config.processorConnTimeout,
+ convert_unicode=True,
+ encoding='utf-8')
return WSGIController.__call__(self, environ, start_response)
# Include the '_' function in the public names
Modified: trunk/webapp/socorro/lib/collect.py
==============================================================================
--- trunk/webapp/socorro/lib/collect.py (original)
+++ trunk/webapp/socorro/lib/collect.py Fri Aug 29 15:36:44 2008
@@ -36,20 +36,20 @@
def backOffMessage():
pass
-def makeDumpDir(base):
+def makeDumpDir(dumpDir):
"""Create a directory to hold a group of dumps, and set permissions"""
- tmpPath = tempfile.mkdtemp(dir=base, prefix=config.dumpDirPrefix)
- os.chmod(tmpPath, config.dirPermissions)
+ os.makedirs(dumpDir)
+ os.chmod(dumpDir, config.dirPermissions)
if config.dumpGID is not None:
- os.chown(tmpPath, -1, config.dumpGID)
- return tmpPath
+ os.chown(dumpDir, -1, config.dumpGID)
+ return dumpDir
def findLastModifiedDirInPath(path):
names = os.listdir(path)
breakpadDirs = [os.path.join(path, entry) for entry
in names if entry.startswith(config.dumpDirPrefix)]
-
+
# This could happen if some other process or person has removed things
# from our dated paths
if len(breakpadDirs) == 0:
@@ -79,23 +79,23 @@
"""Return a directory path to hold dump data, creating if necessary"""
# First make an hourly directory if necessary
utc = datetime.utcnow()
+ baseminute = "%02u" % (5 * int(utc.minute/5))
dateString = "%04u-%02u-%02u-%02u" % (utc.year, utc.month, utc.day, utc.hour)
datePath = os.path.join(config.storageRoot, str(utc.year), str(utc.month),
- str(utc.day), str(utc.hour))
+ str(utc.day), str(utc.hour), config.dumpDirPrefix + baseminute)
# if it's not there yet, create the date directory and its first
# dump directory
if not os.path.exists(datePath):
- makedirs(datePath)
return (makeDumpDir(datePath), dateString)
# return the last-modified dir if it has less than dumpCount entries,
# otherwise make a new one
- latestDir = findLastModifiedDirInPath(datePath)
- if len(os.listdir(latestDir)) >= config.dumpDirCount:
- return (makeDumpDir(datePath), dateString)
-
- return (latestDir, dateString)
+ #latestDir = findLastModifiedDirInPath(datePath)
+ #if len(os.listdir(latestDir)) >= config.dumpDirCount:
+ # return (makeDumpDir(datePath), dateString)
+
+ return (datePath, dateString)
def openFileForDumpID(dumpID, dumpDir, suffix, mode):
filename = os.path.join(dumpDir, dumpID + suffix)
@@ -113,9 +113,9 @@
(dirPath, dateString) = getParentPathForDump()
dumpID = str(uuid.uuid1())
outfile = openFileForDumpID(dumpID, dirPath, config.dumpFileSuffix, 'wb')
-
+
# XXXsayrer need to peek at the first couple bytes for a sanity check
- # breakpad leading bytes: 0x504d444d
+ # breakpad leading bytes: 0x504d444d
#
try:
while 1:
@@ -128,6 +128,39 @@
return (dumpID, dirPath, dateString)
+def doCreateSymbolicLink(targetPathname, linkPathname):
+ """ Create a symbolic link called 'linkPathname' linked to 'targetPathname'"""
+ os.symlink(targetPathname, linkPathname)
+ os.chmod(linkPathname, config.dirPermissions)
+ if config.dumpGID is not None:
+ os.chown(linkPathname, -1, config.dumpGID)
+
+def createSymbolicLinkForIndex(id, path, suffix):
+ """ For each json file stored, we're going to save a symbolic link to that file in a directory of the form:
+ {config.storageRoot}/index/{hostname}/{jsonfile}.symlink We can access this structure faster than
+ the distributed structure where the actual json and dump files live.
+ """
+ # create path for index link
+ indexLinkPath = os.path.join(config.storageRoot, "index", os.uname()[1])
+ # create relative path for the link target
+ targetRelativePathName = os.path.join("../..", path[len(config.storageRoot) + 1:], "%s%s" % (id, suffix))
+ try:
+ # create symbolic link
+ symbolicLinkPathname = os.path.join(indexLinkPath, "%s%s" % (id, ".symlink"))
+ doCreateSymbolicLink(targetRelativePathName, symbolicLinkPathname)
+ except OSError:
+ # {hostname} directory does not exist
+ try:
+ mkdir(indexLinkPath)
+ except OSError:
+ # "index" directory probably doesn't exist - create it
+ mkdir(os.path.join(config.storageRoot, "index"))
+ # retry creation of {hostname} directory
+ mkdir(indexLinkPath)
+ # retry creation of symbolic link
+ doCreateSymbolicLink(targetRelativePathName, symbolicLinkPathname)
+
+
def storeJSON(dumpID, dumpDir, form):
names = [name for name in form.keys() if name != config.dumpField]
fields = {}
@@ -139,11 +172,16 @@
fields["timestamp"] = time()
outfile = openFileForDumpID(dumpID, dumpDir, config.jsonFileSuffix, 'w')
+
try:
simplejson.dump(fields, outfile)
finally:
outfile.close()
+ # create index symbolic link for this json file
+ createSymbolicLinkForIndex(dumpID, dumpDir, config.jsonFileSuffix)
+
+
def makeResponseForClient(dumpID, dateString):
response = "CrashID=%s%s\n" % (config.dumpIDPrefix, dumpID)
if config.reporterURL is not None:
Modified: trunk/webapp/socorro/lib/helpers.py
==============================================================================
--- trunk/webapp/socorro/lib/helpers.py (original)
+++ trunk/webapp/socorro/lib/helpers.py Fri Aug 29 15:36:44 2008
@@ -25,4 +25,7 @@
Return a row class (1|2) based on passed value. For use in alternating table
row colors.
"""
- return 'row'+str(int(i)%2+1)
+ if int(i)%2+1 == 1:
+ return 'odd'
+ else:
+ return 'even'
Modified: trunk/webapp/socorro/lib/monitor.py
==============================================================================
--- trunk/webapp/socorro/lib/monitor.py (original)
+++ trunk/webapp/socorro/lib/monitor.py Fri Aug 29 15:36:44 2008
@@ -71,20 +71,26 @@
print "beat to the punch for uuid " + dumpID
# This is ok, someone beat us to it
return
-
+
if report is not None:
didProcess = False
try:
sys.stdout.flush()
print "runProcessor for %s" % dumpID
- try:
- processor = Processor(config.processorMinidump,
- config.processorSymbols)
- processor.process(dir, dumpID, report)
- didProcess = True
- except:
- print "Error in processor:"
- print_exception()
+
+ if report is not False:
+ """Only try to process if the report isn't missing vital information.
+ report will be False if createReport can't validate the report JSON."""
+
+ try:
+ processor = Processor(config.processorMinidump,
+ config.processorSymbols)
+ processor.process(dir, dumpID, report)
+ didProcess = True
+ except:
+ print "Error in processor:"
+ print_exception()
+
finally:
dumppath = os.path.join(dir, dumpID + config.dumpFileSuffix)
Modified: trunk/webapp/socorro/lib/platforms.py
==============================================================================
--- trunk/webapp/socorro/lib/platforms.py (original)
+++ trunk/webapp/socorro/lib/platforms.py Fri Aug 29 15:36:44 2008
@@ -46,7 +46,8 @@
platformList = PlatformList([Platform('windows', 'Windows', 'Windows NT', '#99F'),
Platform('mac', 'Mac OS X', 'Mac OS X', '#3C3'),
- Platform('linux', 'Linux', 'Linux', '#C90')])
+ Platform('linux', 'Linux', 'Linux', '#C90'),
+ Platform('solaris', 'Solaris', 'Solaris', '#A91')])
def count_platforms():
return [platform.count() for platform in platformList]
Modified: trunk/webapp/socorro/lib/processor.py
==============================================================================
--- trunk/webapp/socorro/lib/processor.py (original)
+++ trunk/webapp/socorro/lib/processor.py Fri Aug 29 15:36:44 2008
@@ -13,7 +13,10 @@
sys.stdout.flush()
ZERO = timedelta(0)
+
buildDatePattern = re.compile('^(\\d{4})(\\d{2})(\\d{2})(\\d{2})')
+buildPattern = re.compile('^\\d{10}')
+timePattern = re.compile('^\\d+.?\\d+$')
class UTC(tzinfo):
def utcoffset(self, dt):
@@ -37,43 +40,83 @@
try:
json = simplejson.load(jsonFile)
+ if not isValidReport(json):
+ return False
+
crash_time = None
- report_date = datetime.now()
install_age = None
+ uptime = 0
+ report_date = datetime.now()
- if 'CrashTime' in json and 'InstallTime' in json:
- crash_time = int(json['CrashTime'])
- report_date = datetime.fromtimestamp(crash_time, utctz)
- install_age = crash_time - int(json["InstallTime"])
- elif 'timestamp' in json:
- report_date = datetime.fromtimestamp(json["timestamp"], utctz)
+ if 'CrashTime' in json \
+ and timePattern.match(str(json['CrashTime'])) \
+ and 'InstallTime' in json \
+ and timePattern.match(str(json['InstallTime'])):
+
+ try:
+ crash_time = int(json['CrashTime'])
+ report_date = datetime.fromtimestamp(crash_time, utctz)
+ install_age = crash_time - int(json['InstallTime'])
+
+ if 'StartupTime' in json \
+ and timePattern.match(str(json['StartupTime'])) \
+ and crash_time >= int(json['StartupTime']):
+ uptime = crash_time - int(json['StartupTime'])
+ except (ValueError):
+ pass
+ elif 'timestamp' in json and timePattern.match(str(json['timestamp'])):
+ try:
+ report_date = datetime.fromtimestamp(json['timestamp'], utctz)
+ except (ValueError):
+ pass
build_date = None
try:
- #(y, m, d, h) = map(int, buildDatePattern.match(json["BuildID"]).groups())
- build_date = datetime(2007, 9, 29, 22)
- except (AttributeError, ValueError):
+ (y, m, d, h) = map(int,
+ buildDatePattern.match(str(json['BuildID'])).groups())
+ build_date = datetime(y, m, d, h)
+ except (AttributeError, ValueError, KeyError):
pass
last_crash = None
- if 'SecondsSinceLastCrash' in json:
- last_crash = int(json["SecondsSinceLastCrash"])
+ if 'SecondsSinceLastCrash' in json and timePattern.match(str(json['SecondsSinceLastCrash'])):
+ last_crash = int(json['SecondsSinceLastCrash'])
return model.Report.create(uuid=id,
date=report_date,
- product=json['product'],
- version=json['version'],
- build=json['gnome_version'],
+ product=json.get('ProductName', None),
+ version=json.get('Version', None),
+ build=json.get('BuildID', None),
url=json.get('URL', None),
install_age=install_age,
last_crash=last_crash,
+ uptime=uptime,
email=json.get('Email', None),
- os_version=json['os_version'],
+ os_version=json['os_version'],
build_date=build_date,
- user_id=json.get('UserID', None))
+ user_id=json.get('UserID', None),
+ comments=json.get('Comments', None))
finally:
jsonFile.close()
+def isValidReport(json):
+ """Given a json dict passed from simplejson, we need to verify that required
+ fields exist. If they don't, we should throw away the dump and continue.
+ Method returns a boolean value -- true if valid, false if not."""
+
+ valid = True
+
+ if 'BuildID' not in json or not buildPattern.match(json['BuildID']):
+ valid = False
+
+ if 'ProductName' not in json:
+ valid = False
+
+ if 'Version' not in json:
+ valid = False
+
+ return valid
+
def fixupSourcePath(path):
"""Given a full path of a file in a Mozilla source tree,
strip off anything prior to mozilla/, and convert
Modified: trunk/webapp/socorro/lib/queryparams.py
==============================================================================
--- trunk/webapp/socorro/lib/queryparams.py (original)
+++ trunk/webapp/socorro/lib/queryparams.py Fri Aug 29 15:36:44 2008
@@ -1,8 +1,8 @@
-from socorro.models import reports_table as reports, branches_table as branches, frames_table as frames
+from socorro.models import reports_table as reports, branches_table as branches, frames_table as frames, top_crashers_table as topcrashers
import formencode
import sqlalchemy
from pylons.database import create_engine
-from sqlalchemy import sql, func, select, types
+from sqlalchemy import sql, func, select, types, and_, desc
from sqlalchemy.databases.postgres import PGInterval
import re
from socorro.lib.platforms import count_platforms, platformList
@@ -47,7 +47,7 @@
return platformList[str(value)]
class BaseLimit(object):
- """A base class which validates date/branch/product/version conditions for
+ """A base class which validates date/branch/product/version/buildid conditions for
multiple searches."""
datetime_validator = formencode.validators.Regex(
@@ -60,7 +60,7 @@
stringlist_validator = ListValidator(formencode.validators.String(strip=True))
platform_validator = ListValidator(PlatformValidator())
query_validator = formencode.validators.OneOf(['signature', 'stack'])
- type_validator = formencode.validators.OneOf(['exact', 'contains'])
+ type_validator = formencode.validators.OneOf(['exact', 'startswith', 'contains'])
signature_validator = formencode.validators.String(strip=True, if_empty=None)
@staticmethod
@@ -70,7 +70,7 @@
return (num, unit)
def __init__(self, date=None, range=None,
- products=None, branches=None, versions=None, platforms=None,
+ products=None, branches=None, versions=None, buildid=None, platforms=None,
query=None, query_search=None, query_type=None):
self.date = date
self.range = range # _range is a tuple (number, interval)
@@ -81,6 +81,7 @@
self.query = query
self.query_search = query_search
self.query_type = query_type
+ self.buildid = buildid
def setFromParams(self, params):
"""Set the values of this object from a request.params instance."""
@@ -101,7 +102,8 @@
for platforms in params.getall('platform'):
self.platforms.extend(self.platform_validator.to_python(platforms))
-
+
+ self.query = params.get('buildid', None)
self.query = self.signature_validator.to_python(params.get('query', None))
self.query_search = self.query_validator.to_python(params.get('query_search', None))
self.query_type = self.type_validator.to_python(params.get('query_type', None))
@@ -185,8 +187,11 @@
def filterByQuery(self, q):
if self.query is not None:
- if self.getQueryType() == 'contains':
- pattern = '%' + self.query.replace('%', '%%') + '%'
+ if self.getQueryType() == 'contains' or \
+ self.getQueryType() == 'startswith':
+ pattern = self.query.replace('%', '%%') + '%'
+ if self.getQueryType() == 'contains':
+ pattern = '%' + pattern
if self.getQuerySearch() == 'signature':
q = q.filter(reports.c.signature.like(pattern))
else:
@@ -218,13 +223,23 @@
def query_reports(self):
selects = [reports.c.date,
+ reports.c.date_processed,
+ reports.c.uptime,
+ reports.c.comments,
reports.c.uuid,
reports.c.product,
reports.c.version,
reports.c.build,
reports.c.signature,
reports.c.url,
- reports.c.os_name]
+ reports.c.os_name,
+ reports.c.os_version,
+ reports.c.cpu_name,
+ reports.c.cpu_info,
+ reports.c.address,
+ reports.c.reason,
+ reports.c.last_crash,
+ reports.c.install_age]
s = select(selects,
order_by=sql.desc(reports.c.date),
limit=500,
@@ -289,6 +304,8 @@
limit=100,
engine=create_engine())
s.append_whereclause(reports.c.signature != None)
+ if self.buildid:
+ s.append_whereclause(reports.c.build == self.buildid)
def FilterToAppend(clause):
s.append_whereclause(clause)
@@ -309,7 +326,8 @@
if self.query is not None:
sigtype = {'exact': 'is exactly',
- 'contains': 'contains'}[self.getQueryType()]
+ 'contains': 'contains',
+ 'startswith': 'starts with'}[self.getQueryType()]
sigquery = {'signature': 'the crash signature',
'stack': 'one of the top 10 stack frames'}[self.getQuerySearch()]
@@ -347,6 +365,33 @@
q = q.filter(reports.c.signature == self.signature)
return q
+def getTopCrashes(product, version, key, buildId=None):
+ """
+ Get a list of top crashes for a specific key.
+ Returns a tuple of the topcrashers, current timestamp, and the last updated timestamp from the topcrashers table.
+ """
+ def getTopCrashers():
+ db = create_engine()
+ if buildId:
+ last_updated_sql = db.execute("SELECT last_updated FROM topcrashers WHERE product='%s' AND version='%s' AND build='%s' ORDER BY last_updated DESC LIMIT 1" % (product, version, buildId))
+ for each in last_updated_sql:
+ last_updated = each[0]
+
+ tc = [r for r in topcrashers.select(engine=create_engine(), whereclause=and_(topcrashers.c.product == product, topcrashers.c.version == version, topcrashers.c.build == buildId, topcrashers.c.last_updated == last_updated)).execute()]
+ ts = time.time()
+ else:
+ last_updated_sql = db.execute("SELECT last_updated FROM topcrashers WHERE product='%s' AND version='%s' ORDER BY last_updated DESC LIMIT 1" % (product, version))
+ for each in last_updated_sql:
+ last_updated = each[0]
+
+ tc = [r for r in topcrashers.select(engine=create_engine(), whereclause=and_(topcrashers.c.product == product, topcrashers.c.version == version, topcrashers.c.last_updated == last_updated), order_by=desc(topcrashers.c.total)).execute()]
+ ts = time.time()
+
+ return (tc, ts, last_updated)
+
+ tccache = pylons.cache.get_cache('tc_data')
+ return tccache.get_value(key, createfunc=getTopCrashers,
+ type="memory", expiretime=60)
### XXXcombine the two functions below
def getCrashesForParams(params, key):
"""
Modified: trunk/webapp/socorro/models/__init__.py
==============================================================================
--- trunk/webapp/socorro/models/__init__.py (original)
+++ trunk/webapp/socorro/models/__init__.py Fri Aug 29 15:36:44 2008
@@ -2,6 +2,7 @@
from datetime import datetime
from socorro.lib import config, EmptyFilter
from cStringIO import StringIO
+from sqlalchemy.exceptions import SQLError
import sys
import os
import re
@@ -11,7 +12,7 @@
class TruncatingString(types.TypeDecorator):
"""
Truncating string type.
-
+
By default, SQLAlchemy will throw an error if a string that is too long
is inserted into the database. We subclass the default String type to
automatically truncate to the correct length.
@@ -24,7 +25,9 @@
return value[:self.length]
def convert_result_value(self, value, engine):
- return value
+ if value is None:
+ return None
+ return value.decode('utf-8')
"""
Define database structure.
@@ -44,24 +47,31 @@
default=text("nextval('seq_reports_id')"),
primary_key=True),
Column('date', DateTime(timezone=True)),
- Column('uuid', String(50), index=True, unique=True, nullable=False),
- Column('product', String(30)),
- Column('version', String(16)),
- Column('build', String(30)),
+ Column('date_processed', DateTime(), nullable=False, default=func.now()),
+ Column('uuid', Unicode(50), index=True, unique=True, nullable=False),
+ Column('product', Unicode(30)),
+ Column('version', Unicode(16)),
+ Column('build', Unicode(30)),
Column('signature', TruncatingString(255), index=True),
Column('url', TruncatingString(255), index=True),
Column('install_age', Integer),
Column('last_crash', Integer),
+ Column('uptime', Integer),
Column('comments', TruncatingString(500)),
Column('cpu_name', TruncatingString(100)),
Column('cpu_info', TruncatingString(100)),
Column('reason', TruncatingString(255)),
- Column('address', String(20)),
+ Column('address', Unicode(20)),
Column('os_name', TruncatingString(100)),
Column('os_version', TruncatingString(100)),
Column('email', TruncatingString(100)),
Column('build_date', DateTime()),
- Column('user_id', String(50))
+ Column('user_id', Unicode(50)),
+ Column('starteddatetime', DateTime()),
+ Column('completeddatetime', DateTime()),
+ Column('success', Boolean),
+ Column('message', TEXT(convert_unicode=True)),
+ Column('truncated', Boolean)
)
def upgrade_reports(dbc):
@@ -84,10 +94,11 @@
print " Updated %s rows." % cursor.rowcount
else:
print "ok"
+
print " Checking for reports.user_id...",
cursor.execute("""SELECT 1 FROM pg_attribute
WHERE attrelid = 'reports'::regclass
- AND attname = 'user_id'""");
+ AND attname = 'user_id'""")
if cursor.rowcount == 0:
print "adding"
cursor.execute('ALTER TABLE reports ADD user_id character(50)')
@@ -105,19 +116,106 @@
else:
print "ok"
+ print " Checking for reports.date_processed...",
+ cursor.execute("""SELECT 1 FROM pg_attribute
+ WHERE attrelid = 'reports'::regclass
+ AND attname = 'date_processed'""")
+ if cursor.rowcount == 0:
+ print "setting"
+ cursor.execute("""ALTER TABLE reports
+ ADD date_processed timestamp without time zone
+ NOT NULL default NOW()""")
+ else:
+ print "ok"
+
+ print " Checking for reports.comments...",
+ cursor.execute("""SELECT 1 FROM pg_attribute
+ WHERE attrelid = 'reports'::regclass
+ AND attname = 'comments'""")
+ if cursor.rowcount == 0:
+ print "setting"
+ cursor.execute("""ALTER TABLE reports ADD comments text NULL""")
+ else:
+ print "ok"
+
+ print " Checking for reports.uptime...",
+ cursor.execute("""SELECT 1 FROM pg_attribute
+ WHERE attrelid = 'reports'::regclass
+ AND attname = 'uptime'""")
+ if cursor.rowcount == 0:
+ print "setting"
+ cursor.execute("""ALTER TABLE reports ADD uptime integer NOT NULL default 0""")
+ else:
+ print "ok"
+
+ print " Checking for reports.success...",
+ cursor.execute("""SELECT 1 FROM pg_attribute
+ WHERE attrelid = 'reports'::regclass
+ AND attname = 'success'""")
+ if cursor.rowcount == 0:
+ print "setting"
+ cursor.execute("""ALTER TABLE reports
+ ADD success boolean NULL""")
+ else:
+ print "ok"
+
+ print " Checking for reports.starteddatetime...",
+ cursor.execute("""SELECT 1 FROM pg_attribute
+ WHERE attrelid = 'reports'::regclass
+ AND attname = 'starteddatetime'""")
+ if cursor.rowcount == 0:
+ print "setting"
+ cursor.execute("""ALTER TABLE reports
+ ADD starteddatetime timestamp without time zone NULL""")
+ else:
+ print "ok"
+
+ print " Checking for reports.completeddatetime...",
+ cursor.execute("""SELECT 1 FROM pg_attribute
+ WHERE attrelid = 'reports'::regclass
+ AND attname = 'completeddatetime'""")
+ if cursor.rowcount == 0:
+ print "setting"
+ cursor.execute("""ALTER TABLE reports
+ ADD completeddatetime timestamp without time zone NULL""")
+ else:
+ print "ok"
+
+ print " Checking for reports.message...",
+ cursor.execute("""SELECT 1 FROM pg_attribute
+ WHERE attrelid = 'reports'::regclass
+ AND attname = 'message'""")
+ if cursor.rowcount == 0:
+ print "setting"
+ cursor.execute("""ALTER TABLE reports
+ ADD message text NULL""")
+ else:
+ print "ok"
+
+ print " Checking for reports.truncated...",
+ cursor.execute("""SELECT 1 FROM pg_attribute
+ WHERE attrelid = 'reports'::regclass
+ AND attname = 'truncated'""")
+ if cursor.rowcount == 0:
+ print "setting"
+ cursor.execute("""ALTER TABLE reports
+ ADD truncated boolean NULL""")
+ else:
+ print "ok"
+
frames_table = Table('frames', meta,
- Column('report_id', Integer, ForeignKey('reports.id'), primary_key=True),
+ Column('report_id', Integer, ForeignKey('reports.id', ondelete='CASCADE'), primary_key=True),
Column('frame_num', Integer, nullable=False, primary_key=True, autoincrement=False),
- Column('signature', TruncatingString(255))
+ Column('signature', TruncatingString(255)),
)
modules_table = Table('modules', meta,
- Column('report_id', Integer, ForeignKey('reports.id'), primary_key=True),
+ Column('report_id', Integer, ForeignKey('reports.id', ondelete='CASCADE'), primary_key=True),
Column('module_key', Integer, primary_key=True, autoincrement=False),
Column('filename', TruncatingString(40), nullable=False),
- Column('debug_id', String(40)),
+ Column('debug_id', Unicode(40)),
Column('module_version', TruncatingString(15)),
- Column('debug_filename', TruncatingString(40)),
+ Column('debug_filename', TruncatingString(40))
)
def upgrade_modules(dbc):
@@ -137,14 +235,17 @@
{'length': modules_table.c.debug_id.type.length})
cur.close()
-extensions_table = Table('extensions', meta, Column('report_id', Integer, ForeignKey('reports.id'), primary_key=True), Column('extension_key', Integer, primary_key=True, autoincrement=False),
- Column('extension_id', String(100), nullable=False),
- Column('extension_version', String(16))
+extensions_table = Table('extensions', meta,
+ Column('report_id', Integer, ForeignKey('reports.id', ondelete='CASCADE'), primary_key=True),
+ Column('extension_key', Integer, primary_key=True, autoincrement=False),
+ Column('extension_id', Unicode(100), nullable=False),
+ Column('extension_version', Unicode(16))
)
dumps_table = Table('dumps', meta,
- Column('report_id', Integer, ForeignKey('reports.id'), primary_key=True),
- Column('data', TEXT())
+ Column('report_id', Integer, ForeignKey('reports.id', ondelete='CASCADE'), primary_key=True),
+ Column('truncated', Boolean, default=False),
+ Column('data', TEXT(convert_unicode=True))
)
branches_table = Table('branches', meta,
@@ -153,6 +254,57 @@
Column('branch', String(24), nullable=False)
)
+jobs_id_sequence = Sequence('jobs_id_seq', meta)
+
+priorityjobs_table = Table('priorityjobs', meta,
+ Column('uuid', Unicode(50), primary_key=True, nullable=False)
+)
+
+processors_id_sequence = Sequence('processors_id_seq', meta)
+
+processors_table = Table('processors', meta,
+ Column('id', Integer, processors_id_sequence,
+ default=text("nextval('processors_id_seq')"),
+ primary_key=True),
+ Column('name', String(255), nullable=False),
+ Column('startdatetime', DateTime(), nullable=False),
+ Column('lastseendatetime', DateTime())
+)
+
+jobs_table = Table('jobs', meta,
+ Column('id', Integer, jobs_id_sequence,
+ default=text("nextval('jobs_id_seq')"),
+ primary_key=True),
+ Column('pathname', String(1024), nullable=False),
+ Column('uuid', Unicode(50), index=True, unique=True, nullable=False),
+ Column('owner', Integer, ForeignKey('processors.id')),
+ Column('priority', Integer, default=0),
+ Column('queueddatetime', DateTime()),
+ Column('starteddatetime', DateTime()),
+ Column('completeddatetime', DateTime()),
+ Column('success', Boolean),
+ Column('message', TEXT(convert_unicode=True))
+)
+
+top_crashers_table = Table('topcrashers', meta,
+ Column('id', Integer, primary_key=True),
+ Column('signature', String(255), nullable=False),
+ Column('version', String(30), nullable=False),
+ Column('product', String(30), nullable=False),
+ Column('build', String(30), nullable=False),
+ Column('total', Integer),
+ Column('win', Integer),
+ Column('mac', Integer),
+ Column('linux', Integer),
+ Column('rank', Integer),
+ Column('last_rank', Integer),
+ Column('trend', String(30)),
+ Column('uptime', Float(2)),
+ Column('users', Integer),
+ Column('last_updated', DateTime())
+)
+
+
lock_function_definition = """
declare
begin
@@ -323,7 +475,7 @@
cmd := subst('CREATE TABLE $$ (
CHECK(report_id >= $$),
PRIMARY KEY(report_id, frame_num),
- FOREIGN KEY(report_id) REFERENCES $$ (id)
+ FOREIGN KEY(report_id) REFERENCES $$ (id) ON DELETE CASCADE
) INHERITS (frames)',
ARRAY[ quote_ident(objname),
quote_literal(start_id),
@@ -334,7 +486,7 @@
cmd := subst('CREATE TABLE $$ (
CHECK(report_id >= $$),
PRIMARY KEY(report_id, module_key),
- FOREIGN KEY(report_id) REFERENCES $$ (id)
+ FOREIGN KEY(report_id) REFERENCES $$ (id) ON DELETE CASCADE
) INHERITS (modules)',
ARRAY[ quote_ident(objname),
quote_literal(start_id),
@@ -345,7 +497,7 @@
cmd := subst('CREATE TABLE $$ (
CHECK(report_id >= $$),
PRIMARY KEY(report_id, extension_key),
- FOREIGN KEY(report_id) REFERENCES $$ (id)
+ FOREIGN KEY(report_id) REFERENCES $$ (id) ON DELETE CASCADE
) INHERITS (extensions)',
ARRAY[ quote_ident(objname),
quote_literal(start_id),
@@ -356,7 +508,7 @@
cmd := subst('CREATE TABLE $$ (
CHECK(report_id >= $$),
PRIMARY KEY(report_id),
- FOREIGN KEY(report_id) REFERENCES $$ (id)
+ FOREIGN KEY(report_id) REFERENCES $$ (id) ON DELETE CASCADE
) INHERITS (dumps)',
ARRAY[ quote_ident(objname),
quote_literal(start_id),
@@ -417,6 +569,7 @@
upgrade_reports(dbc)
upgrade_modules(dbc)
+
def ensure_partitions(dbc):
print "Checking for database partitions...",
cur = dbc.cursor()
@@ -445,6 +598,7 @@
fixupSpace = re.compile(r' (?=[\*&,])')
fixupComma = re.compile(r'(?<=,)(?! )')
+stripArgs = re.compile(r'\(.*\)')
filename_re = re.compile('[/\\\\]([^/\\\\]+)$')
@@ -476,14 +630,16 @@
"""
if localEngine:
return localEngine
-
+
from pylons.database import create_engine
return create_engine(pool_recycle=config.processorConnTimeout)
class Frame(dict):
- def __init__(self, module_name, function, source, source_line, instruction):
+ def __init__(self, module_name, frame_num, function, source, source_line, instruction):
self['module_name'] = module_name
+ self['frame_num'] = frame_num
self['signature'] = make_signature(module_name, function, source, source_line, instruction)
+ self['short_signature'] = stripArgs.sub('', self['signature'])
self['function'] = function
self['source'] = source
self['source_line'] = source_line
@@ -500,8 +656,8 @@
if root in config.vcsMappings[type]:
self['source_link'] = config.vcsMappings[type][root] % \
{'file': source_file,
- 'revision': revision,
- 'line': source_line}
+ 'revision': revision,
+ 'line': source_line}
else:
self['source_filename'] = os.path.split(source)[1]
@@ -553,6 +709,7 @@
self._read_header(lines)
self._read_stackframes(lines)
+ # Daddy wants some rollback for when things go bad.
def flush(self):
signature = None
@@ -560,9 +717,9 @@
self.crashed_thread <= len(self.threads) and
len(self.threads[self.crashed_thread]) > 0):
signature = self.threads[self.crashed_thread][0]['signature']
- print "Calculating signature: %s" % signature
+ print "Calculating signature for report %s: %s" % (self['uuid'], signature)
else:
- print "Something didn't work for signatures"
+ print "Failed to create signature for report %s:" % self['uuid']
updatevalues = {'signature': signature,
'cpu_name': self['cpu_name'],
@@ -582,14 +739,15 @@
r = reports_table.update(whereclause=reports_table.c.id==self['id']). \
compile(engine=getEngine(), parameters=updatevalues).execute(updatevalues)
- module_data = [{'report_id': self['id'],
- 'module_key': i,
- 'filename': self.modules[i].filename,
- 'debug_id': self.modules[i].debug_id,
- 'module_version': self.modules[i].module_version,
- 'debug_filename': self.modules[i].debug_filename}
- for i in xrange(0, len(self.modules))]
- r = modules_table.insert().compile(engine=getEngine()).execute(*module_data)
+ if self.modules is not None and len(self.modules) > 1:
+ module_data = [{'report_id': self['id'],
+ 'module_key': i,
+ 'filename': self.modules[i].filename,
+ 'debug_id': self.modules[i].debug_id,
+ 'module_version': self.modules[i].module_version,
+ 'debug_filename': self.modules[i].debug_filename}
+ for i in xrange(0, len(self.modules))]
+ r = modules_table.insert().compile(engine=getEngine()).execute(*module_data)
if (self.crashed_thread is not None and
self.crashed_thread <= len(self.threads)):
@@ -613,7 +771,7 @@
# empty line separates header data from thread data
if line == '':
return
-
+
values = map(EmptyFilter, line.split("|"))
if values[0] == 'OS':
self['os_name'] = values[1]
@@ -633,14 +791,19 @@
debug_id=values[4],
module_version=values[2],
debug_filename=values[3]))
-
+
def _read_stackframes(self, lines):
for line in lines:
(thread_num, frame_num, module_name, function, source, source_line, instruction) = map(EmptyFilter, line.split("|"))
thread_num = int(thread_num)
while thread_num >= len(self.threads):
self.threads.append([])
+
+ if module_name is None:
+ continue
+
self.threads[thread_num].append(Frame(module_name=module_name,
+ frame_num=frame_num,
function=function,
source=source,
source_line=source_line,
@@ -651,7 +814,24 @@
self.product = product
self.version = version
self.branch = branch
-
+
+ def flush(self):
+ branch_data = [{'product': self.product,
+ 'version': self.version,
+ 'branch': self.branch}]
+ r = branches_table.insert().compile(engine=getEngine()).execute(*branch_data)
+ return
+
+ @staticmethod
+ def getAll():
+ """
+ Just returns everything in branches table.
+ """
+ return select([branches_table],
+ distinct=True,
+ order_by=[branches_table.c.branch],
+ engine=getEngine()).execute()
+
@staticmethod
def getBranches():
"""
@@ -667,9 +847,9 @@
"""
Return a list of distinct [product, branch] sorted by product name and branch.
"""
- return select([branches_table.c.product, branches_table.c.branch],
+ return select([branches_table.c.product, branches_table.c.branch],
distinct=True,
- order_by=[branches_table.c.product,
+ order_by=[branches_table.c.product,
branches_table.c.branch],engine=getEngine()).execute()
@staticmethod
@@ -677,7 +857,7 @@
"""
Return a list of distinct [product] sorted by product.
"""
- return select([branches_table.c.product],
+ return select([branches_table.c.product],
distinct=True,
order_by=branches_table.c.product,engine=getEngine()).execute()
@@ -710,7 +890,7 @@
type="memory", expiretime=360)
class Module(object):
- def __init__(self, filename, debug_id,
+ def __init__(self, filename, debug_id,
module_version, debug_filename):
self.filename = filename
self.debug_id = debug_id
@@ -724,6 +904,29 @@
self.extension_id = extension_id
self.extension_version = extension_version
+class Job(object):
+ """
+ For accessing and updating the reports queue.
+ """
+
+ @staticmethod
+ def by_uuid(uuid):
+ """ Get queue information for a pending uuid. """
+ return select([jobs_table], limit=1, whereclause=jobs_table.c.uuid==uuid,
+ engine=getEngine()).execute().fetchone()
+
+class PriorityJob(object):
+ @staticmethod
+ def add(uuid):
+ """ Insert passed UUID into the priorityjobs table. """
+ vals = [{'uuid': uuid}]
+ try:
+ priorityjobs_table.insert().compile(engine=getEngine()).execute(*vals)
+ except(SQLError):
+ pass
+
+
+
#
# Check whether we're running outside Pylons
#
@@ -737,6 +940,6 @@
from sqlalchemy.ext.sessioncontext import SessionContext
localEngine = create_engine(config.processorDatabaseURI,
strategy="threadlocal",
- poolclass=pool.QueuePool,
+ poolclass=pool.QueuePool,
pool_recycle=config.processorConnTimeout,
pool_size=1)
Modified: trunk/webapp/socorro/public/css/layout.css
==============================================================================
--- trunk/webapp/socorro/public/css/layout.css (original)
+++ trunk/webapp/socorro/public/css/layout.css Fri Aug 29 15:36:44 2008
@@ -430,6 +430,45 @@
/* page content */
+#menu {
+}
+
+#quickfind {
+ display: inline;
+ float: right;
+ font-size: smaller;
+}
+
+#report-header {
+ float: left;
+}
+
+#report-header-details {
+ float: right;
+ font-size: smaller;
+}
+
+#report-header-details span {
+ font-weight: bold;
+}
+
+#report-index {
+ clear: both;
+}
+
+
+.hidden {
+ display: none;
+}
+
+h1.loading {
+ background: transparent url(../loading.png) no-repeat left;
+ padding-left: 25px;
+}
+
+
+
+
div#content {
clear: both;
padding: 1em;
Modified: trunk/webapp/socorro/public/css/style.css
==============================================================================
--- trunk/webapp/socorro/public/css/style.css (original)
+++ trunk/webapp/socorro/public/css/style.css Fri Aug 29 15:36:44 2008
@@ -57,7 +57,7 @@
font-weight:normal;
}
.list th {
- background: #ccf;
+ background: #dfd;
border: 1px solid #000;
font-weight: bold;
padding: 2px;
@@ -70,13 +70,13 @@
.list th a:hover {
background-color: #fff;
}
-.row1 {
+.even {
background-color: #eee;
}
-.row2 {
+.odd {
background-color: #ddd;
}
-.row1:hover, .row2:hover {
+.even:hover, .odd:hover {
background-color: #fff;
}
@@ -84,6 +84,26 @@
text-align: right;
}
+div.code {
+ background-color: #eee;
+ border: 1px solid #ccc;
+ font-family: monospace;
+ width: 640px;
+ height: 480px;
+ white-space: pre;
+ overflow: auto;
+}
+
+th a.expand {
+ display: inline;
+ font-size: smaller;
+}
+
+dt {
+ font-weight: bold;
+}
+
+
/* styling page content */
h1 {
Modified: trunk/webapp/socorro/templates/includes/common.html
==============================================================================
--- trunk/webapp/socorro/templates/includes/common.html (original)
+++ trunk/webapp/socorro/templates/includes/common.html Fri Aug 29 15:36:44 2008
@@ -4,7 +4,7 @@
from socorro.lib.platforms import platformList
?>
-<table py:def="list_by_signature(signatures, params)" class="list">
+<table py:def="list_by_signature(signatures, params)" id="signatureList" class="tablesorter">
<thead>
<tr>
<th>Rank</th>
@@ -34,42 +34,86 @@
</tbody>
</table>
-<table xmlns:py="http://genshi.edgewall.org/" py:def="list_reports(reports)" class="list">
+<table py:def="list_topcrashers(signatures)" id="signatureList" class="tablesorter">
<thead>
<tr>
- <th rowspan="2">Date</th>
- <th colspan="2">Product/Version/Build/OS</th>
+ <th>Rank</th>
+ <th>Signature</th>
+ <th>Build ID</th>
+ <th>#</th>
+ <th>Win</th>
+ <th>Lin</th>
+ <th>Mac</th>
</tr>
+ </thead>
+ <tbody>
+ <?python row=1 ?>
+ <tr py:for="signature in signatures" class="${h.get_row_class(row)}">
+ <td>${str(row)}</td>
+ <td><a href="${h.url_for('/report/list', signature=signature.signature, range_unit='weeks', version=c.product + ':' + c.version, range_value='2')}" title="View reports with this signature.">${signature.signature}</a></td>
+ <td>${signature.build}</td>
+ <td>${signature.total}</td>
+ <td>${signature.win}</td>
+ <td>${signature.linux}</td>
+ <td>${signature.mac}</td>
+ <?python row+=1 ?>
+ </tr>
+ </tbody>
+</table>
+
+<table xmlns:py="http://genshi.edgewall.org/" py:def="list_reports(reports)" class="tablesorter" id="reportsList">
+ <thead>
<tr>
- <th>Signature</th>
- <th>URL</th>
+ <th>Date</th>
+ <th>Product</th>
+ <th>Version</th>
+ <th>Build</th>
+ <th>OS</th>
+ <th>CPU</th>
+ <th>Reason</th>
+ <th>Address</th>
+ <th>Uptime</th>
+ <th>Comments</th>
</tr>
</thead>
<tbody>
- <?python row = 0 ?>
<py:for each="report in reports">
- <tr class="${h.get_row_class(row)}">
- <td class="report-date" rowspan="2">
+ <tr>
+ <td class="report-date">
<a href="${h.url_for(id=report.uuid, controller='report', action='index')}">
- ${report.date.strftime("%Y-%m-%d")} ${report.date.strftime("%H:%M")}
+ ${report.date_processed.strftime("%Y-%m-%d")} ${report.date_processed.strftime("%H:%M")}
</a>
- </td>
- <td class="report-product" colspan="2">
- ${report.product} ${report.version}: ${report.build} -
- ${report.os_name}
- </td>
+ </td> <td>${report.product}</td>
+ <td>${report.version}</td>
+ <td>${report.build}</td>
+ <td>${report.os_name} ${report.os_version}</td>
+ <td>${report.cpu_name}</td>
+ <td>${report.reason}</td>
+ <td>${report.address}</td>
+ <td>${report.uptime}</td>
+ <td>${report.comments}</td>
</tr>
- <tr class="${'row'+str(row%2+1)}">
- <td class="report-signature">
- <code>
- ${report.signature}
- </code>
- </td>
- <td class="report-url">
- ${report.url}
+ </py:for>
+ </tbody>
+</table>
+
+<table xmlns:py="http://genshi.edgewall.org/" py:def="list_comments(reports)" class="tablesorter" id="commentsList">
+ <thead>
+ <tr>
+ <th>Date</th>
+ <th>Comments</th>
+ </tr>
+ </thead>
+ <tbody>
+ <py:for each="report in reports">
+ <tr>
+ <td class="report-date">
+ <a href="${h.url_for(id=report.uuid, controller='report', action='index')}">
+ ${report.date_processed.strftime("%Y-%m-%d")} ${report.date_processed.strftime("%H:%M")}
+ </a>
</td>
+ <td>${report.comments}</td>
</tr>
- <?python row+=1 ?>
</py:for>
</tbody>
</table>
Modified: trunk/webapp/socorro/templates/includes/site.html
==============================================================================
--- trunk/webapp/socorro/templates/includes/site.html (original)
+++ trunk/webapp/socorro/templates/includes/site.html Fri Aug 29 15:36:44 2008
@@ -45,19 +45,22 @@
Crash Reporter
</h1>
<div id="control">
- <form id="crash-search" action="${h.url_for(controller='report', action='find')}"
- py:if="not request.params.get('do_query', None)">
- <input type="text" size="24" name="id" id="search-crash-id"/>
- <input type="submit" id="search-crash-submit" value="Go to Report" />
- </form>
+ <form id="quickfind" action="${h.url_for(controller='report', action='find')}">
+ <input type="text" size="24" name="id" id="crash-id"/>
+ <input type="submit" value="Go to Report" />
+ </form>
</div>
<div id="tabs">
<ul id="portal-globalnav">
<li id="portaltab-root">
<a href="/"><span>Home</span></a>
</li>
- <li><a href="${ h.url_for(controller='query', action='query', id=None) }"><span>Find a report</span></a></li>
- <li><a href="${ h.url_for(controller='topcrasher', action='index') }"><span>Top Crashers</span></a></li>
+ <div id="menu">
+ <ul>
+ <li>${ h.link_to('Find a Report', h.url_for(controller='query', action='query', id=None)) }</li>
+ <li>${ h.link_to('Top Crashers', h.url_for(controller='topcrasher', action='index')) }</li>
+ </ul>
+ </div>
</ul>
</div> <!-- end of #tabs -->
</div> <!-- end of #header -->
Modified: trunk/webapp/socorro/templates/query_form.html
==============================================================================
--- trunk/webapp/socorro/templates/query_form.html (original)
+++ trunk/webapp/socorro/templates/query_form.html Fri Aug 29 15:36:44 2008
@@ -11,17 +11,28 @@
<xi:include href="includes/common.html" />
<head>
<title>Crash Reports</title>
+ ${ h.stylesheet_link_tag("/css/datePicker.css") }
+ ${ h.stylesheet_link_tag('/css/flora/flora.tablesorter.css') }
+ ${ h.javascript_include_tag("/js/jquery/jquery-1.2.1.js") }
+ ${ h.javascript_include_tag("/js/jquery/date.js") }
+ ${ h.javascript_include_tag("/js/jquery/plugins/ui/jquery.datePicker.js") }
+ ${ h.javascript_include_tag('/js/jquery/plugins/ui/jquery.tablesorter.min.js') }
+ <script type="text/javascript">
+ <![CDATA[
+ $(function()
+ {
+ Date.format='yyyy-mm-dd';
+ $('.date-pick').datePicker();
+ $('.date-pick').dpSetStartDate('2007-01-01');
+ $('#signatureList').tablesorter();
+ });
+ ]]>
+ </script>
</head>
<body>
<div id="content">
- <form id="quickfind" action="${h.url_for(controller='report', action='find')}"
- py:if="not request.params.get('do_query', None)">
- <input type="submit" value="Go to Report" />
- <input type="text" size="24" name="id" id="crash-id"/>
- </form>
-
- <h1>Search Crash Reports</h1>
+ <h1 class="first">Search Crash Reports</h1>
<form name="query" method="GET" action="${h.url_for(action='query')}">
<input type="hidden" name="do_query" value="1" />
@@ -70,12 +81,13 @@
Stack Signature
</option>
<option value="stack" selected="${c.params.getQuerySearch() == 'stack' and 'selected' or ''}">
- Top 10 Stack Frames
+ One of the top 10 Stack Frames
</option>
</select>
<select name="query_type">
<option py:for="(value, desc) in (('contains', 'contains'),
- ('exact', 'is exactly'))"
+ ('exact', 'is exactly'),
+ ('startswith', 'starts with'))"
value="${value}"
selected="${c.params.getQueryType() == value and 'selected' or ''}">
${desc}
@@ -85,8 +97,8 @@
</p>
<p>
- <label for="date">Occuring before date (default: now, format yyyy-mm-dd HH:MM:SS): </label>
- <input type="text" name="date" size="20" value="${c.params.date}" />
+ <label for="date">Occurring before date (default: now, format yyyy-mm-dd HH:MM:SS): </label>
+ <input type="text" name="date" size="20" class="date-pick" value="${c.params.date}" />
<br />
<label for="range_value">Date range: </label>
<input type="text" name="range_value" value="${c.params.getRange()[0]}" size="2" /><select name="range_unit">
Modified: trunk/webapp/socorro/templates/report/index.html
==============================================================================
--- trunk/webapp/socorro/templates/report/index.html (original)
+++ trunk/webapp/socorro/templates/report/index.html Fri Aug 29 15:36:44 2008
@@ -4,63 +4,100 @@
xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="../includes/site.html" />
<head>
- <title>Breakpad Crash Report: ${c.report.uuid}</title>
- ${ h.javascript_include_tag(builtins=True) }
+ <title>[@ ${c.report.signature} ] - ${c.report.product} ${c.report.version} Crash Report - Report ID: ${c.report.uuid}</title>
+ ${ h.javascript_include_tag("/js/jquery/jquery-1.2.1.js") }
+ ${ h.javascript_include_tag("/js/jquery/plugins/ui/ui.tabs.js") }
+ ${ h.stylesheet_link_tag("/css/flora/flora.all.css") }
+ <script type="text/javascript">
+ $(document).ready(function(){
+ $('#report-index > ul').tabs();
+ $('#showallthreads').removeClass('hidden').click(function(){
+ $('#allthreads').toggle(400);
+ return false;
+ });
+ $('.signature-column').append(' <a class="expand" href="#">[Expand]</a>');
+ $('.expand').click(function(){
+ // swap cell title into cell text for each cell in this column
+ $("td:nth-child(3)", $(this).parents('tbody')).each(function(){
+ $(this).text($(this).attr('title')).removeAttr('title');
+ });
+ $(this).remove();
+ return false;
+ });
+ });
+ </script>
</head>
<body>
<div id="content">
- <p id="report-nav">${h.link_to('Record','#record')} ${h.link_to('Frames','#frames')}
- ${h.link_to('Modules','#modules')}</p>
- <h1 class="first" id="record">${c.report.product} ${c.report.version} Crash Report</h1>
- <table class="list record">
- <tr class="row1">
- <th>UUID</th><td>${c.report.uuid}</td>
- </tr>
- <tr class="row2">
- <th>Time</th><td>${c.report.date}</td>
- </tr>
- <tr class="row1">
- <th>Build ID</th><td>${c.report.build}</td>
- </tr>
- <tr class="row2">
- <th>OS</th><td>${c.report.os_name}</td>
- </tr>
- <tr class="row1">
- <th>OS Version</th><td>${c.report.os_version}</td>
- </tr>
- <tr class="row2">
- <th>CPU</th><td>${c.report.cpu_name}</td>
- </tr>
- <tr class="row1">
- <th>CPU Info</th><td>${c.report.cpu_info}</td>
- </tr>
- <tr class="row2">
- <th>Crash Reason</th><td>${c.report.reason}</td>
- </tr>
- <tr class="row1">
- <th>Crash Address</th><td>${c.report.address}</td>
- </tr>
- <tr class="row2" py:if="c.report.url is not None">
- <?python
- url = c.report.url
- if len(url) > 100:
- url = url[:100] + "…"
- ?>
- <th>URL</th><td>${h.link_to(url, url=c.report.url)}</td>
- </tr>
- </table>
- <py:if test="c.report['dump'] is not None and len(c.report.threads) > 0">
- <h2 id="frames">Stack Traces</h2>
- <table class="list" py:def="stack_trace(frames)">
+ <h1 id="report-header" class="first">${c.report.product} ${c.report.version} Crash Report [@ ${c.report.signature} ]</h1>
+ <div id="report-header-details">ID: <span>${c.report.uuid}</span><br/> Signature: <span>${c.report.signature}</span></div>
+ <div id="report-index" class="flora">
+ <ul>
+ <li><a href="#details"><span>Details</span></a></li>
+ <li><a href="#frames"><span>Frames</span></a></li>
+ <li><a href="#modules"><span>Modules</span></a></li>
+ <li><a href="#rawdump"><span>Raw Dump</span></a></li>
+ </ul>
+ <div id="details">
+ <table class="list record">
+ <tr class="odd">
+ <th>Signature</th><td>${c.report.signature}</td>
+ </tr>
+ <tr class="even">
+ <th>UUID</th><td>${c.report.uuid}</td>
+ </tr>
+ <tr class="odd">
+ <th>Time</th><td>${c.report.date}</td>
+ </tr>
+ <tr class="even">
+ <th>Uptime</th><td>${c.report.uptime}</td>
+ </tr>
+ <tr class="odd">
+ <th>Product</th><td>${c.report.product}</td>
+ </tr>
+ <tr class="even">
+ <th>Version</th><td>${c.report.version}</td>
+ </tr>
+ <tr class="odd">
+ <th>Build ID</th><td>${c.report.build}</td>
+ </tr>
+ <tr class="even">
+ <th>OS</th><td>${c.report.os_name}</td>
+ </tr>
+ <tr class="odd">
+ <th>OS Version</th><td>${c.report.os_version}</td>
+ </tr>
+ <tr class="even">
+ <th>CPU</th><td>${c.report.cpu_name}</td>
+ </tr>
+ <tr class="odd">
+ <th>CPU Info</th><td>${c.report.cpu_info}</td>
+ </tr>
+ <tr class="even">
+ <th>Crash Reason</th><td>${c.report.reason}</td>
+ </tr>
+ <tr class="odd">
+ <th>Crash Address</th><td>${c.report.address}</td>
+ </tr>
+ <tr class="even">
+ <th>Comments</th><td>${c.report.comments}</td>
+ </tr>
+ </table>
+ </div>
+ <div id="frames">
+ <py:if test="c.report['dump'] is not None and len(c.report.threads) > 0">
+ <table class="list" py:def="stack_trace(frames)">
<tr>
- <th>frame</th>
- <th>signature</th>
- <th>source</th>
+ <th>Frame</th>
+ <th>Module</th>
+ <th class="signature-column">Signature</th>
+ <th>Source</th>
</tr>
- <?python row = 0 ?>
+ <?python row = 1 ?>
<tr py:for="frame in frames" class="${h.get_row_class(row)}">
<td>${frame.frame_num}</td>
- <td>${frame.signature}</td>
+ <td>${frame.module_name}</td>
+ <td title="${frame.signature}">${frame.short_signature}</td>
<td py:choose="frame.source_link is not None">
<py:when test="True">
${h.link_to(frame.source_info, url=frame.source_link)}
@@ -69,47 +106,48 @@
</td>
<?python row += 1 ?>
</tr>
- </table>
- <h3>Stack of Crashing Thread</h3>
- ${stack_trace(c.report.threads[c.report.crashed_thread])}
- <!-- this just gives me tons of errors in the JS Console -->
- <!-- h.parallel_effects(h.visual_effect('toggle_appear', 'allthreads'),h.visual_effect('toggle_appear', 'link_toggle') -->
- <p id="link_toggle">${h.link_to('Click to view other threads', url='#allthreads', onclick=h.visual_effect('toggle_appear', 'allthreads') + ' return false;')}</p>
- <div id="allthreads">
- <py:for each="i in range(len(c.report.threads))">
- <py:if test="i != c.report.crashed_thread">
- <h3>Stack of Thread ${i}</h3>
- ${stack_trace(c.report.threads[i])}
- </py:if>
- </py:for>
- </div>
- <script>document.getElementById("allthreads").style.display="none";</script>
- </py:if>
- <py:if test="c.report['dump'] is not None and len(c.report.modules) > 0">
- <h2 id="modules">Modules</h2>
- <table class="list">
- <tr>
- <th>filename</th>
- <th>Version</th>
- <th>Debug Identifier</th>
- <th>Debug Filename</th>
- </tr>
- <?python row = 0 ?>
- <tr py:for="module in c.report.modules" class="${h.get_row_class(row)}">
- <td>${module.filename}</td>
- <td>${module.module_version}</td>
- <td>${module.debug_id}</td>
- <td>${module.debug_filename}</td>
- <?python row += 1 ?>
- </tr>
</table>
- </py:if>
- <py:if test="c.report['dump'] is not None">
- <p>${h.link_to('Click to view raw dump.', url='#rawdump', onclick=h.visual_effect('toggle_appear', 'rawdump') + ' return false;')}</p>
- <pre id="rawdump">${c.report['dump']}</pre>
- <script>document.getElementById("rawdump").style.display="none";</script>
- </py:if>
+ <h2>Crashing Thread</h2>
+ ${stack_trace(c.report.threads[c.report.crashed_thread])}
+ <p id="showallthreads" class="hidden">${h.link_to('Show/hide other threads', url='#allthreads')}</p>
+ <div id="allthreads">
+ <py:for each="i in range(len(c.report.threads))">
+ <py:if test="i != c.report.crashed_thread">
+ <h2>Thread ${i}</h2>
+ ${stack_trace(c.report.threads[i])}
+ </py:if>
+ </py:for>
+ </div>
+ <script>document.getElementById("allthreads").style.display="none";</script>
+ </py:if>
+ </div>
+ <div id="modules">
+ <py:if test="c.report['dump'] is not None and len(c.report.modules) > 0">
+ <table class="list">
+ <tr>
+ <th>Filename</th>
+ <th>Version</th>
+ <th>Debug Identifier</th>
+ <th>Debug Filename</th>
+ </tr>
+ <?python row = 1 ?>
+ <tr py:for="module in c.report.modules" class="${h.get_row_class(row)}">
+ <td>${module.filename}</td>
+ <td>${module.module_version}</td>
+ <td>${module.debug_id}</td>
+ <td>${module.debug_filename}</td>
+ <?python row += 1 ?>
+ </tr>
+ </table>
+ </py:if>
+ </div>
+ <div id="rawdump">
+ <py:if test="c.report['dump'] is not None">
+ <div class="code">${c.report['dump']}</div>
+ </py:if>
+ </div>
</div>
- <!-- end content -->
+ </div>
+ <!-- end content -->
</body>
</html>
Modified: trunk/webapp/socorro/templates/report/list.html
==============================================================================
--- trunk/webapp/socorro/templates/report/list.html (original)
+++ trunk/webapp/socorro/templates/report/list.html Fri Aug 29 15:36:44 2008
@@ -1,5 +1,3 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
- "http://www.w3.org/TR/html4/strict.dtd">
<?python
from calendar import timegm
@@ -12,25 +10,41 @@
<xi:include href="../includes/site.html" />
<head>
<title>Crash Reports in ${c.params.signature}</title>
-
- <script type="text/javascript" src="${h.url_for('/js/MochiKit/MochiKit.js')}"></script>
- <script type="text/javascript" src="${h.url_for('/js/PlotKit/excanvas.js')}"></script>
- <script type="text/javascript" src="${h.url_for('/js/PlotKit/PlotKit_Packed.js')}"></script>
+ ${ h.javascript_include_tag('/js/MochiKit/MochiKit.js') }
+ ${ h.javascript_include_tag('/js/PlotKit/excanvas.js') }
+ ${ h.javascript_include_tag('/js/PlotKit/PlotKit_Packed.js') }
+ ${ h.javascript_include_tag('/js/jquery/jquery-1.2.1.js') }
+ ${ h.javascript_include_tag('/js/jquery/plugins/ui/ui.tabs.js') }
+ ${ h.javascript_include_tag('/js/jquery/plugins/ui/jquery.tablesorter.min.js') }
+ ${ h.stylesheet_link_tag('/css/flora/flora.all.css') }
+
+ <script type="text/javascript">
+ <![CDATA[
+ $(document).ready(function() {
+ $('#buildid-table').tablesorter();
+ $('#reportsList').tablesorter({sortList:[[8,1]]});
+ $('#report-list > ul').tabs();
+ });
+ ]]>
+ </script>
<style type="text/css">
#buildid-outer {
display: none;
}
#buildid-div {
+ float: left;
height: 160px;
width: 500px;
margin-top: 0.5em;
margin-bottom: 0.5em;
margin-left: auto;
- margin-right: auto;
+ margin-right: 1.5em;
}
#buildid-labels {
- float: left;
+ }
+ .clear {
+ clear: both;
}
</style>
@@ -39,54 +53,58 @@
<body>
<div id="content">
- <h1 class="first">Crash Reports in ${c.params.signature}</h1>
-
- <p>${str(c.params)}</p>
-
-
- <div id="buildid-outer">
- <ul id="buildid-labels">
- <li py:for="platform in platformList"
- py:if="len(c.params.platforms) == 0 or platform in c.params.platforms"
- style="color: ${platform.color()}">
- ${platform.name()}
- </li>
- </ul>
-
- <div id="buildid-div">
- <canvas id="buildid-graph" width="500" height="160"></canvas>
+ <h1 class="first">Crash Reports in ${c.params.signature}</h1>
+ <p>${str(c.params)}</p>
+ <div id="report-list">
+ <ul>
+ <li><a href="#graph"><span>Graph</span></a></li>
+ <li><a href="#table"><span>Table</span></a></li>
+ <li><a href="#reports"><span>Reports</span></a></li>
+ </ul>
+ <div id="graph">
+ <div id="buildid-outer"> <div id="buildid-div"> <canvas id="buildid-graph" width="500" height="160"></canvas>
+ </div>
+ <ul id="buildid-labels">
+ <li py:for="platform in platformList"
+ py:if="len(c.params.platforms) == 0 or platform in c.params.platforms"
+ style="color: ${platform.color()}">
+ ${platform.name()}
+ </li>
+ </ul>
+ </div>
+ <div class="clear"></div>
+ </div>
+ <div id="table">
+ <table id="buildid-table" class="tablesorter">
+ <thead>
+ <tr>
+ <th>Build ID</th>
+ <th py:if="len(c.params.platforms) != 1">Crashes</th>
+ <th py:for="platform in platformList"
+ py:if="len(c.params.platforms) == 0 or platform in c.params.platforms">
+ ${platform.name()[:3]}
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr py:for="build in c.builds">
+ <td class="human-buildid">${build.build_date.strftime('%Y%m%d%H')}</td>
+ <td class="crash-count" py:if="len(c.params.platforms) != 1">
+ ${build.count} - ${"%.3f%%" % (build.frequency * 100)}
+ </td>
+ <td py:for="platform in platformList"
+ py:if="len(c.params.platforms) == 0 or platform in c.params.platforms">
+ ${getattr(build, 'count_%s' % platform.id())} -
+ ${"%.3f%%" % (getattr(build, 'frequency_%s' % platform.id()) * 100)}
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <div id="reports">
+ ${list_reports(c.reports)}
+ </div>
</div>
- </div>
-
- ${list_reports(c.reports)}
-
- <table id="buildid-table" class="list">
- <thead>
- <tr>
- <th>Build ID</th>
- <th py:if="len(c.params.platforms) != 1">Crashes</th>
- <th py:for="platform in platformList"
- py:if="len(c.params.platforms) == 0 or platform in c.params.platforms">
- ${platform.name()[:3]}
- </th>
- </tr>
- </thead>
- <tbody>
- <?python row=1?>
- <tr py:for="build in c.builds" class="${h.get_row_class(row)}">
- <td class="human-buildid">${build.build_date.strftime('%Y%m%d%H')}</td>
- <td class="crash-count" py:if="len(c.params.platforms) != 1">
- ${build.count} - ${"%.3f%%" % (build.frequency * 100)}
- </td>
- <td py:for="platform in platformList"
- py:if="len(c.params.platforms) == 0 or platform in c.params.platforms">
- ${getattr(build, 'count_%s' % platform.id())} -
- ${"%.3f%%" % (getattr(build, 'frequency_%s' % platform.id()) * 100)}
- </td>
- <?python row+=1 ?>
- </tr>
- </tbody>
- </table>
</div>
@@ -190,16 +208,18 @@
];
colors.pop();
- var chart = new CanvasRenderer($('buildid-graph'), layout,
+ var chart = new CanvasRenderer(MochiKit.DOM.getElement('buildid-graph'), layout,
{IECanvasHTC: '${h.url_for('/js/PlotKit/iecanvas.htc')}',
colorScheme: colors,
shouldStroke: true,
strokeColor: null,
- strokeWidth: 2});
+ strokeWidth: 2,
+ shouldFill: false,
+ axisLabelWidth: 75});
chart.render();
- $('buildid-outer').style.display = 'block';
+ MochiKit.DOM.getElement('buildid-outer').style.display = 'block';
}
</script>
Modified: trunk/webapp/socorro/templates/topcrasher/bybranch.html
==============================================================================
--- trunk/webapp/socorro/templates/topcrasher/bybranch.html (original)
+++ trunk/webapp/socorro/templates/topcrasher/bybranch.html Fri Aug 29 15:36:44 2008
@@ -6,11 +6,23 @@
<xi:include href="../includes/common.html" />
<head>
<title>Top Crashers for the ${c.branch} Branch</title>
+ ${ h.javascript_include_tag('/js/jquery/jquery-1.2.1.js') }
+ ${ h.javascript_include_tag('/js/jquery/plugins/ui/jquery.tablesorter.min.js') }
+ ${ h.stylesheet_link_tag('/css/flora/flora.tablesorter.css') }
+ <script type="text/javascript">
+ <![CDATA[
+ $(document).ready(function()
+ {
+ $('#signatureList').tablesorter();
+ }
+ );
+ ]]>
+ </script>
</head>
<body>
-<div id="content" py:choose="c.tc.rowcount">
+<div id="content" py:choose="len(c.tc)">
<h1 class="first">Top Crashers for the ${c.branch} Branch</h1>
<p py:when="0">No results were found.</p>
<div py:otherwise="">Below are the top 100 crashers over the last 2 weeks.
Modified: trunk/webapp/socorro/templates/topcrasher/byversion.html
==============================================================================
--- trunk/webapp/socorro/templates/topcrasher/byversion.html (original)
+++ trunk/webapp/socorro/templates/topcrasher/byversion.html Fri Aug 29 15:36:44 2008
@@ -5,16 +5,28 @@
<xi:include href="../includes/site.html" />
<xi:include href="../includes/common.html" />
<head>
- <title>Top Crashers for ${c.product} ${c.version}</title>
+ <title>Top Crashers for ${c.product} ${c.version} ${c.params.buildid}</title>
+ ${ h.javascript_include_tag('/js/jquery/jquery-1.2.1.js') }
+ ${ h.javascript_include_tag('/js/jquery/plugins/ui/jquery.tablesorter.min.js') }
+ ${ h.stylesheet_link_tag('/css/flora/flora.tablesorter.css') }
+ <script type="text/javascript">
+ <![CDATA[
+ $(document).ready(function()
+ {
+ $("#signatureList").tablesorter();
+ }
+ );
+ ]]>
+ </script>
</head>
<body>
-<div id="content" py:choose="c.tc.rowcount">
- <h1 class="first">Top Crashers for ${c.product} ${c.version}</h1>
+<div id="content" py:choose="len(c.tc)">
+ <h1 class="first">Top Crashers for ${c.product} ${c.version} ${c.params.buildid}</h1>
<p py:when="0">No results were found.</p>
- <div py:otherwise="">Below are the top 100 crashers over the last 2 weeks.
- ${list_by_signature(c.tc, c.params)}
+ <div py:otherwise="">Below are the top 100 crashers as of ${c.last_updated}
+ ${list_topcrashers(c.tc)}
</div>
</div>
<!-- end content -->
Modified: trunk/webapp/socorro/templates/topcrasher/index.html
==============================================================================
--- trunk/webapp/socorro/templates/topcrasher/index.html (original)
+++ trunk/webapp/socorro/templates/topcrasher/index.html Fri Aug 29 15:36:44 2008
@@ -10,16 +10,6 @@
<body>
<div id="content">
-
- <h1 class="first">Top Crashers by Branch</h1>
- <ul>
- <li py:for="row in c.branches">
- <a href="${ h.url_for('/'+'/'.join(['topcrasher',
- 'bybranch',
- row.branch])) }">${row.branch}</a>
- </li>
- </ul>
-
<h1>Top Crashers by Version</h1>
<ul>
<li py:for="row in c.product_versions">
@@ -32,6 +22,5 @@
</div>
<!-- end content -->
-
</body>
</html>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]