Marcoil has uploaded a new change for review.
https://gerrit.wikimedia.org/r/78398
Change subject: WIP - Record performance numbers during RT testing
......................................................................
WIP - Record performance numbers during RT testing
Add a perfstats table to keep different performance metrics for each
page and commit. They are kept as key, value pairs so that it'll be easy to
add new stats in the future. The stats are passed from to the server
embedded in the test results, using a new tag.
There's also a very basic interface to view the stats at /perfstats.
Bug: 46659
Change-Id: If73731d53cf360b6a25b3d7bb2bc5c30a2a4e4b2
---
M js/tests/roundtrip-test.js
M js/tests/server/server.js
M js/tests/server/sql/create_everything.mysql
3 files changed, 193 insertions(+), 5 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Parsoid
refs/changes/98/78398/1
diff --git a/js/tests/roundtrip-test.js b/js/tests/roundtrip-test.js
index 6426152..8fde078 100755
--- a/js/tests/roundtrip-test.js
+++ b/js/tests/roundtrip-test.js
@@ -59,6 +59,11 @@
var prefix = ( env && env.wiki && env.wiki.iwp ) || '';
var title = ( env && env.page && env.page.name ) || '';
+ // Finish the total time now?
+ if ( env.profile && env.profile.time ) {
+ env.profile.time.total = new Date() - env.profile.time.total;
+ }
+
var output = '<testsuite name="Roundtrip article ' + Util.encodeXml(
prefix + ':' + title ) + '">';
if ( err ) {
@@ -92,6 +97,21 @@
output += '</testcase>\n';
}
+ }
+
+ // Output the profiling data
+ if ( env.profile ) {
+ output += '<perfstats>\n';
+ for ( var type in env.profile ) {
+ for ( var prop in env.profile[ type ] ) {
+ output += '<perfstat type="' + Util.encodeXml(
type ) + ':';
+ output += Util.encodeXml( prop );
+ output += '">';
+ output += Util.encodeXml( env.profile[ type ][
prop ].toString() );
+ output += '</perfstat>\n';
+ }
+ }
+ output += '</perfstats>\n';
}
output += '</testsuite>\n';
@@ -366,7 +386,10 @@
var out, diff, offsetPairs;
try {
+ env.profile.time.serialize = new Date();
out = new WikitextSerializer( { env: env }
).serializeDOM(document.body);
+ env.profile.time.serialize = new Date() -
env.profile.time.serialize;
+ env.profile.size.serializeddom = out.length;
diff = jsDiff.diffLines( out, env.page.src );
offsetPairs = Util.convertDiffToOffsetPairs( diff );
@@ -392,6 +415,8 @@
return;
}
+ env.profile = { time: { total: new Date() }, size: {} };
+
var target = env.resolveTitle( env.normalizeTitle(
env.page.name ), '' );
var tpr = new TemplateRequest( env, target, null );
@@ -400,7 +425,9 @@
cb( err, env, [] );
} else {
env.setPageSrcInfo( src_and_metadata );
+ env.profile.time.parse = new Date();
Util.parse( env, function ( src, err, out ) {
+ env.profile.time.parse = new Date() -
env.profile.time.parse;
if ( err ) {
cb( err, env, [] );
} else {
diff --git a/js/tests/server/server.js b/js/tests/server/server.js
index 8411bb0..d41fcec 100755
--- a/js/tests/server/server.js
+++ b/js/tests/server/server.js
@@ -176,6 +176,12 @@
'skips = VALUES( skips ), fails = VALUES( fails ), ' +
'errors = VALUES( errors ), score = VALUES( score )';
+var dbInsertPerfStatsStart =
+ 'INSERT INTO perfstats ' +
+ '( page_id, commit_hash, type, value ) VALUES ';
+var dbInsertPerfStatsEnd =
+ ' ON DUPLICATE KEY UPDATE value = VALUES( value )';
+
var dbUpdatePageLatestResults =
'UPDATE pages ' +
'SET latest_stat = ?, latest_score = ?, latest_result = ?, ' +
@@ -219,7 +225,15 @@
'JOIN stats AS s2 ON s2.page_id = pages.id ' +
'WHERE s1.commit_hash = (SELECT hash FROM commits ORDER BY timestamp
DESC LIMIT 1 ) ' +
'AND s2.commit_hash = (SELECT hash FROM commits ORDER BY timestamp DESC
LIMIT 1 OFFSET 1 ) ' +
- 'AND s1.score < s2.score ) as numfixes ' +
+ 'AND s1.score < s2.score ) as numfixes, ' +
+ '(SELECT AVG(perfstats.value) ' +
+ 'FROM perfstats JOIN pages ON perfstats.page_id = pages.id
WHERE ' +
+ 'perfstats.commit_hash = (SELECT hash FROM commits
ORDER BY timestamp DESC LIMIT 1) ' +
+ 'AND perfstats.type = \'time:total\') AS avgtotaltime,
' +
+ '(SELECT AVG(perfstats.value) ' +
+ 'FROM perfstats JOIN pages ON perfstats.page_id = pages.id
WHERE ' +
+ 'perfstats.commit_hash = (SELECT hash FROM commits
ORDER BY timestamp DESC LIMIT 1) ' +
+ 'AND perfstats.type = \'size:serializeddom\') AS
avgxhtmlsize ' +
'FROM pages JOIN stats on pages.latest_stat = stats.id';
@@ -264,7 +278,17 @@
'WHERE s1.commit_hash = (SELECT hash FROM commits ORDER BY timestamp
DESC LIMIT 1 ) ' +
'AND s2.commit_hash = (SELECT hash FROM commits ORDER BY timestamp DESC
LIMIT 1 OFFSET 1 ) ' +
'AND pages.prefix = ? ' +
- 'AND s1.score < s2.score ) as numfixes ' +
+ 'AND s1.score < s2.score ) as numfixes, ' +
+ '(SELECT AVG(perfstats.value) ' +
+ 'FROM perfstats JOIN pages ON perfstats.page_id = pages.id
WHERE ' +
+ 'perfstats.commit_hash = (SELECT hash FROM commits
ORDER BY timestamp DESC LIMIT 1) ' +
+ 'AND perfstats.type = \'time:total\' ' +
+ 'AND pages.prefix = ?) AS avgtotaltime, ' +
+ '(SELECT AVG(perfstats.value) ' +
+ 'FROM perfstats JOIN pages ON perfstats.page_id = pages.id
WHERE ' +
+ 'perfstats.commit_hash = (SELECT hash FROM commits
ORDER BY timestamp DESC LIMIT 1) ' +
+ 'AND perfstats.type = \'size:serializeddom\' ' +
+ 'AND pages.prefix = ?) AS avgxhtmlsize ' +
'FROM pages JOIN stats on pages.latest_stat = stats.id WHERE
pages.prefix = ?';
@@ -371,6 +395,18 @@
'JOIN pages ON pages.id = results.page_id ' +
'WHERE pages.prefix = ?';
+var dbPerfStatsTypes =
+ 'SELECT DISTINCT type FROM perfstats';
+
+var dbPagePerfStatsStart =
+ 'SELECT prefix, title, ';
+
+var dbPagePerfStatsEnd =
+ ' FROM pages JOIN perfstats ON pages.id = perfstats.page_id ' +
+ 'WHERE perfstats.commit_hash = ' +
+ '(SELECT hash FROM commits ORDER BY timestamp LIMIT 1) ' +
+ 'GROUP BY pages.id';
+
var transUpdateCB = function( title, prefix, hash, type, res, trans,
success_cb, err, result ) {
if ( err ) {
trans.rollback();
@@ -453,6 +489,38 @@
return errorCount*1000000+failCount*1000+skipCount;
};
+var parsePerfStats = function( text ) {
+ var regexp = /<perfstat[\s]+type="([\w\:]+)"[\s]*>([\d]+)/g;
+ var perfstats = [];
+ for ( var match = regexp.exec( text ); match !== null; match =
regexp.exec( text ) ) {
+ perfstats.push( { type: match[ 1 ], value: match[ 2 ] } );
+ }
+ console.log( "perfstats: ", perfstats );
+ return perfstats;
+};
+
+var insertPerfStats = function( db, pageId, commitHash, perfstats, cb ) {
+ // If empty, just go along
+ if ( !perfstats || perfstats.length === 0 ) {
+ if ( cb ) {
+ return cb( null, null );
+ }
+ }
+ // Build the query to insert all the results in one go:
+ var dbQuery = dbInsertPerfStatsStart;
+ for ( var i = 0; i < perfstats.length; i++ ) {
+ if ( i !== 0 ) {
+ dbQuery += ", ";
+ }
+ dbQuery += "( " + pageId.toString() + ", '" + commitHash + "',
'" +
+ perfstats[i].type + "', " + perfstats[i].value + ' )';
+ }
+ dbQuery += dbInsertPerfStatsEnd;
+
+ // Make the query using the db arg, which could be a transaction
+ db.query( dbQuery, null, cb );
+};
+
var receiveResults = function ( req, res ) {
var title = decodeURIComponent( req.params[0] ),
result = req.body.results,
@@ -461,6 +529,7 @@
errorCount = result.match( /<error/g );
var prefix = req.params[1];
var commitHash = req.body.commit;
+ var perfstats = parsePerfStats( result );
skipCount = skipCount ? skipCount.length : 0;
failCount = failCount ? failCount.length : 0;
@@ -514,8 +583,13 @@
trans.commit( function() {
console.log( '<- ', prefix + ':' + title, ':', skipCount, failCount,
errorCount, commitHash.substr(0,7) );
+
+
// Insert the performance stats, ignoring errors for now
+
insertPerfStats( db, page.id, commitHash, perfstats, null );
+
+
// Maybe the perfstats aren't committed yet, but it shouldn't be a
problem
res.send('', 200);
-
});
+
} );
} ) );
} ) );
} ) );
@@ -535,7 +609,25 @@
'<li><a href="/failsDistr">Histogram of failures</a></li>\n' +
'<li><a href="/skipsDistr">Histogram of skips</a></li>\n' +
'<li><a href="/commits">List of all tested commits</a></li>\n' +
+ '<li><a href="/perfstats">Performance stats of last
commit</a></li>\n' +
'</ul>';
+};
+
+var displayPerfStat = function( type, value ) {
+ var text;
+ if ( type.match( /^time/ ) ) {
+ // Show time in seconds
+ value = Math.round( (value / 1000) * 100 ) / 100;
+ text = value.toString() + "s";
+ } else if ( type.match( /^size/ ) ) {
+ // Show sizes in KiB
+ value = Math.round( value / 1024 );
+ text = value.toString() + "KiB";
+ } else {
+ // Other values go as they are
+ text = value.toString();
+ }
+ return text;
};
var statsWebInterface = function ( req, res ) {
@@ -558,8 +650,8 @@
// Switch the query object based on the prefix
if ( prefix !== null ) {
query = dbPerWikiStatsQuery;
- queryParams = [ prefix, prefix, prefix, prefix,
- prefix, prefix, prefix, prefix ];
+ queryParams = [ prefix, prefix, prefix, prefix, prefix,
+ prefix, prefix, prefix, prefix, prefix ];
} else {
query = dbStatsQuery;
queryParams = null;
@@ -632,6 +724,8 @@
displayRow(res, "Fails", row[0].avgfails);
displayRow(res, "Skips", row[0].avgskips);
displayRow(res, "Score", row[0].avgscore);
+ displayRow(res, "Time", displayPerfStat( 'time:total',
row[0].avgtotaltime) );
+ displayRow(res, "XHTML size", displayPerfStat(
'size:serializeddom', row[0].avgxhtmlsize) );
res.write( '</tbody></table></p>' );
res.write( indexLinkList() );
@@ -975,6 +1069,60 @@
} );
};
+var GET_perfStats = function( req, res ) {
+ // As MySQL doesn't support PIVOT, we need to get all the perfstats
types
+ // first so we can get then as columns afterwards
+ db.query( dbPerfStatsTypes, null, function( err, types ) {
+ if ( err ) {
+ res.send( err.toString(), 500 );
+ } else if ( !types || types.length === 0 ) {
+ res.send( "No performance stats found", 404);
+ } else {
+ // Create the query to retrieve the stats per page
+ var dbQuery = dbPagePerfStatsStart;
+ for( var t = 0; t < types.length; t++ ) {
+ if ( t !== 0 ) {
+ dbQuery += ", ";
+ }
+ dbQuery += "SUM( IF( TYPE='" + types[ t ].type +
+ "', value, NULL ) ) AS '" + types[ t
].type + "'";
+ }
+ dbQuery += dbPagePerfStatsEnd;
+ db.query( dbQuery, null, function( err, rows ) {
+ if ( err ) {
+ res.send( err.toString(), 500 );
+ } else {
+ res.setHeader(
'Content-Type', 'text/html; charset=UTF-8' );
+ res.status( 200 );
+ res.write('<html>');
+ res.write('<head><style
type="text/css">');
+ res.write('th {
padding: 0 10px }');
+ res.write('td {
text-align: center; }');
+ res.write('td.title {
text-align: left; }');
+
res.write('</style></head>');
+ res.write('<body>');
+ res.write('<table>');
+
res.write('<tr><th>Title</th>');
+ for ( var t = 0; t <
types.length; t++ ) {
+
res.write('<th>' + types[ t ].type + '</th>');
+ }
+ res.write('</tr>');
+ for ( var i = 0; i <
rows.length; i++ ) {
+ var r = rows[ i
];
+ res.write(
'<tr><td class = "title">' + r.prefix + ':' + r.title + '</td>' );
+ for ( var j =
0; j < types.length; j++ ) {
+ var
type = types[ j ].type;
+
res.write( '<td>' + displayPerfStat( type, r[ type ] ) + '</td>' );
+ }
+ res.write(
'</tr>' );
+ }
+ res.end(
'</table></body></html>' );
+ }
+ } );
+ }
+ } );
+};
+
// Make an app
var app = express.createServer();
@@ -1018,6 +1166,9 @@
// Distribution of fails
app.get( /^\/skipsDistr$/, GET_skipsDistr );
+// Performance stats
+app.get( /^\/perfstats$/, GET_perfStats );
+
// List of all commits
app.use( '/commits', GET_commits );
diff --git a/js/tests/server/sql/create_everything.mysql
b/js/tests/server/sql/create_everything.mysql
index 204aebe..5fbf6fd 100644
--- a/js/tests/server/sql/create_everything.mysql
+++ b/js/tests/server/sql/create_everything.mysql
@@ -39,3 +39,13 @@
score INTEGER NOT NULL DEFAULT 0
);
CREATE UNIQUE INDEX stats_page_commit_idx ON stats ( page_id, commit_hash );
+
+CREATE TABLE perfstats (
+ id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ page_id INTEGER NOT NULL,
+ commit_hash CHAR( 40 ) NOT NULL,
+ type TEXT NOT NULL,
+ value INTEGER NOT NULL
+);
+CREATE UNIQUE INDEX perfstats_page_commit_type_idx ON perfstats (
+ page_id, commit_hash, type( 40 ) );
--
To view, visit https://gerrit.wikimedia.org/r/78398
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: If73731d53cf360b6a25b3d7bb2bc5c30a2a4e4b2
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Parsoid
Gerrit-Branch: rt_testing
Gerrit-Owner: Marcoil <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits