Initial version of code coverage tracker / dashboard for WebRTC.

BUG=
TEST=

Review URL: https://webrtc-codereview.appspot.com/350001

git-svn-id: http://webrtc.googlecode.com/svn/trunk@1440 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
phoglund@webrtc.org 2012-01-17 12:21:15 +00:00
parent 2877bdc590
commit bd88f39767
10 changed files with 309 additions and 1048 deletions

1
.gitignore vendored
View File

@ -40,6 +40,7 @@
/third_party/cygwin
/third_party/expat
/third_party/google-gflags/src
/third_party/google-visualization-python
/third_party/jsoncpp
/third_party/libjingle
/third_party/libjpeg

3
DEPS
View File

@ -82,6 +82,9 @@ deps = {
"trunk/third_party/libyuv":
(Var("googlecode_url") % "libyuv") + "/trunk@121",
"trunk/third_party/google-visualization-python":
(Var("googlecode_url") % "google-visualization-python") + "/trunk@15",
}
deps_os = {

2
tools/coverage/OWNERS Normal file
View File

@ -0,0 +1,2 @@
phoglund@webrtc.org
kjellander@webrtc.org

23
tools/coverage/README Normal file
View File

@ -0,0 +1,23 @@
This file describes the coverage tracking script and the coverage dashboard.
ABSTRACT:
The intention of this small tracking system is to track code coverage data
over time. Since code coverage is continuously recomputed on the build bots,
the track_coverage.py script is intended to run on the build bot as a cron job
and extract the data from there. The dashboard doesn't care how often this
script runs, but running each hour should be more than enough.
The track_coverage.py script communicates with the dashboard using plain GET
requests (that, and POST, are basically the only way to get data into a
appengine application such as the dashboard). The dashboard is intented to
run on the Google appengine.
HOW TO RUN LOCALLY:
Follow the following instructions:
http://code.google.com/appengine/docs/python/gettingstartedpython27/devenvironment.html
The dashboard can be started on 127.0.0.1:8080 using the dev_appserver.py script
as described in the above URL (and in the following 'hello world' page).
HOW TO DEPLOY:
Follow the following instructions:
http://code.google.com/appengine/docs/python/gettingstartedpython27/uploading.html

View File

@ -0,0 +1,9 @@
application: dashboard
version: 1
runtime: python27
api_version: 1
threadsafe: true
handlers:
- url: /.*
script: dashboard.app

View File

@ -0,0 +1,101 @@
#!/usr/bin/env python
# Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
#
# Use of this source code is governed by a BSD-style license
# that can be found in the LICENSE file in the root of the source
# tree. An additional intellectual property rights grant can be found
# in the file PATENTS. All contributing project authors may
# be found in the AUTHORS file in the root of the source tree.
"""Implements the coverage tracker dashboard and reporting facilities."""
__author__ = 'phoglund@webrtc.org (Patrik Hoglund)'
import datetime
from google.appengine.ext import db
import webapp2
import gviz_api
class CoverageData(db.Model):
"""This represents one coverage report from the build bot."""
date = db.DateTimeProperty(required=True)
line_coverage = db.FloatProperty(required=True)
function_coverage = db.FloatProperty(required=True)
class ShowDashboard(webapp2.RequestHandler):
"""Shows the dashboard page.
The page is shown by grabbing data we have stored previously
in the App Engine database using the AddCoverageData handler.
"""
def get(self):
page_template_filename = 'templates/dashboard_template.html'
# Load the page HTML template.
try:
template_file = open(page_template_filename)
page_template = template_file.read()
template_file.close()
except IOError as exception:
self.ShowErrorPage('Cannot open page template file: %s<br>Details: %s' %
(page_template_filename, exception))
return
coverage_entries = db.GqlQuery('SELECT * '
'FROM CoverageData '
'ORDER BY date ASC')
data = []
for coverage_entry in coverage_entries:
data.append({'date': coverage_entry.date,
'line_coverage': coverage_entry.line_coverage,
'function_coverage': coverage_entry.function_coverage,
})
description = {
'date': ('datetime', 'Date'),
'line_coverage': ('number', 'Line Coverage'),
'function_coverage': ('number', 'Function Coverage')
}
coverage_data = gviz_api.DataTable(description, data)
coverage_json_data = coverage_data.ToJSon(order_by='date')
# Fill in the template with the data and respond:
self.response.write(page_template % vars())
def ShowErrorPage(self, error_message):
self.response.write('<html><body>%s</body></html>' % error_message)
class AddCoverageData(webapp2.RequestHandler):
"""Used to report coverage data.
It will verify the data, but not the sender. Thus, it should be secured
more properly if accessible from an outside network.
"""
def get(self):
try:
posix_time = int(self.request.get('date'))
parsed_date = datetime.datetime.fromtimestamp(posix_time)
line_coverage = float(self.request.get('line_coverage'))
function_coverage = float(self.request.get('function_coverage'))
except ValueError as exception:
self.ShowErrorPage('Invalid parameter in request. Details: %s' %
exception)
return
item = CoverageData(date=parsed_date,
line_coverage=line_coverage,
function_coverage=function_coverage)
item.put()
def ShowErrorPage(self, error_message):
self.response.write('<html><body>%s</body></html>' % error_message)
app = webapp2.WSGIApplication([('/', ShowDashboard),
('/add_coverage_data', AddCoverageData)],
debug=True)

View File

@ -0,0 +1 @@
../../../third_party/google-visualization-python/gviz_api.py

View File

@ -0,0 +1,47 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<!--
Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
Use of this source code is governed by a BSD-style license
that can be found in the LICENSE file in the root of the source
tree. An additional intellectual property rights grant can be found
in the file PATENTS. All contributing project authors may
be found in the AUTHORS file in the root of the source tree.
Template file to be used to generate the WebRTC dashboard.
-->
<head>
<title>WebRTC Coverage Dashboard</title>
<link href="http://code.google.com/css/codesite.pack.04102009.css"
rel="stylesheet" type="text/css">
<script src="https://www.google.com/jsapi" type="text/javascript"></script>
<script type="text/javascript">
google.load('visualization', '1', {packages:['table', 'corechart']});
google.setOnLoadCallback(drawTable);
function drawTable() {
/* Build data tables and views */
var coverage_data_table =
new google.visualization.DataTable(%(coverage_json_data)s);
/* Display tables and charts */
var coverage_chart = new google.visualization.LineChart(
document.getElementById('table_div_coverage'));
coverage_chart.draw(coverage_data_table, {
colors: ['blue', 'red'],
vAxis: {title: 'Coverage'},
hAxis: {title: 'Date'},
width: 1200, height: 300,
});
}
</script>
</head>
<body>
<h1>WebRTC Dashboard</h1>
<h3>Coverage:</h3>
<div id="table_div_coverage"></div>
</body>
</html>

121
tools/coverage/track_coverage.py Executable file
View File

@ -0,0 +1,121 @@
#!/usr/bin/env python
#-*- coding: utf-8 -*-
# Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
#
# Use of this source code is governed by a BSD-style license
# that can be found in the LICENSE file in the root of the source
# tree. An additional intellectual property rights grant can be found
# in the file PATENTS. All contributing project authors may
# be found in the AUTHORS file in the root of the source tree.
"""This script grabs and reports coverage information.
It grabs coverage information from the latest Linux 32-bit build and
pushes it to the coverage tracker, enabling us to track code coverage
over time. This script is intended to run on the 32-bit Linux slave.
"""
__author__ = 'phoglund@webrtc.org (Patrik Höglund)'
import httplib
import os
import re
import sys
import time
# The build-bot user which runs build bot jobs.
BUILD_BOT_USER = 'phoglund'
# The server to send coverage data to.
# TODO(phoglund): replace with real server once we get it up.
DASHBOARD_SERVER = 'localhost:8080'
class FailedToParseCoverageHtml(Exception):
pass
class FailedToReportToDashboard(Exception):
pass
def _find_latest_32bit_debug_build(www_directory_contents):
# Build directories have the form Linux32bitDebug_<number>. There may be other
# directories in the list though, for instance for other build configurations.
# This sort ensures we will encounter the directory with the highest number
# first.
www_directory_contents.sort(reverse=True)
for entry in www_directory_contents:
match = re.match('Linux32bitDBG_\d+', entry)
if match is not None:
return entry
# Didn't find it
return None
def _grab_coverage_percentage(label, index_html_contents):
"""Extracts coverage from a LCOV coverage report.
Grabs coverage by assuming that the label in the coverage HTML report
is close to the actual number and that the number is followed by a space
and a percentage sign.
"""
match = re.search('<td[^>]*>' + label + '</td>.*?(\d+\.\d) %',
index_html_contents, re.DOTALL)
if match is None:
raise FailedToParseCoverageHtml('Missing coverage at label "%s".' % label)
try:
return float(match.group(1))
except ValueError:
raise FailedToParseCoverageHtml('%s is not a float.' % match.group(1))
def _report_coverage_to_dashboard(now, line_coverage, function_coverage):
request_string = ('/add_coverage_data?'
'date=%d&line_coverage=%f&function_coverage=%f' %
(now, line_coverage, function_coverage))
connection = httplib.HTTPConnection(DASHBOARD_SERVER)
connection.request('GET', request_string)
response = connection.getresponse()
if response.status != 200:
message = ('Error: Failed to report to %s%s: got response %d (%s)' %
(DASHBOARD_SERVER, request_string, response.status,
response.reason))
raise FailedToReportToDashboard(message)
# The response content should be empty on success, so check that:
response_content = response.read()
if response_content:
message = ('Error: Dashboard reported the following error: %s.' %
response_content)
raise FailedToReportToDashboard(message)
def _main():
coverage_www_dir = os.path.join('/home', BUILD_BOT_USER, 'www')
www_dir_contents = os.listdir(coverage_www_dir)
latest_build_directory = _find_latest_32bit_debug_build(www_dir_contents)
if latest_build_directory is None:
print 'Error: Found no 32-bit debug build in directory ' + coverage_www_dir
sys.exit(1)
index_html_path = os.path.join(coverage_www_dir, latest_build_directory,
'index.html')
index_html_file = open(index_html_path)
whole_file = index_html_file.read()
line_coverage = _grab_coverage_percentage('Lines:', whole_file)
function_coverage = _grab_coverage_percentage('Functions:', whole_file)
now = int(time.time())
_report_coverage_to_dashboard(now, line_coverage, function_coverage)
if __name__ == '__main__':
_main()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
../../third_party/google-visualization-python/gviz_api.py