Author: jan Date: Wed Feb 25 16:39:55 2009 New Revision: 747852 URL: http://svn.apache.org/viewvc?rev=747852&view=rev Log: add js test suite for stats, enable access for a previously internal metric
Added: couchdb/trunk/share/www/script/test/stats.js Modified: couchdb/trunk/share/Makefile.am couchdb/trunk/share/www/script/couch.js couchdb/trunk/share/www/script/couch_tests.js couchdb/trunk/src/couchdb/couch_httpd_stats_handlers.erl couchdb/trunk/src/couchdb/couch_server.erl couchdb/trunk/src/couchdb/couch_stats_aggregator.erl Modified: couchdb/trunk/share/Makefile.am URL: http://svn.apache.org/viewvc/couchdb/trunk/share/Makefile.am?rev=747852&r1=747851&r2=747852&view=diff ============================================================================== --- couchdb/trunk/share/Makefile.am (original) +++ couchdb/trunk/share/Makefile.am Wed Feb 25 16:39:55 2009 @@ -112,4 +112,5 @@ www/script/test/purge.js \ www/script/test/config.js \ www/script/test/security_validation.js \ + www/script/test/stats.js \ www/style/layout.css Modified: couchdb/trunk/share/www/script/couch.js URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/couch.js?rev=747852&r1=747851&r2=747852&view=diff ============================================================================== --- couchdb/trunk/share/www/script/couch.js [utf-8] (original) +++ couchdb/trunk/share/www/script/couch.js [utf-8] Wed Feb 25 16:39:55 2009 @@ -312,20 +312,13 @@ return req; } -CouchDB.requestStats = function(module, key, aggregate, options) { - var options, optionsOrLast = Array.prototype.pop.apply(arguments); - if (typeof optionsOrLast == "string") { - options = null; - Array.prototype.push.apply(arguments, [optionsOrLast]); - } else { - options = optionsOrLast; +CouchDB.requestStats = function(module, key, test) { + var query_arg = ""; + if(test !== null) { + query_arg = "?flush=true"; } - var request_options = {}; - request_options.headers = {"Content-Type": "application/json"}; - - var stat = CouchDB.request("GET", "/_stats/" + Array.prototype.join.apply(arguments,["/"]) + (options ? - ("?" + CouchDB.params(options)) : ""), request_options).responseText; + var stat = CouchDB.request("GET", "/_stats/" + module + "/" + key + query_arg).responseText; return JSON.parse(stat)[module][key]; } Modified: couchdb/trunk/share/www/script/couch_tests.js URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/couch_tests.js?rev=747852&r1=747851&r2=747852&view=diff ============================================================================== --- couchdb/trunk/share/www/script/couch_tests.js [utf-8] (original) +++ couchdb/trunk/share/www/script/couch_tests.js [utf-8] Wed Feb 25 16:39:55 2009 @@ -66,6 +66,7 @@ loadTest("purge.js"); loadTest("config.js"); loadTest("security_validation.js"); +loadTest("stats.js"); function makeDocs(start, end, templateDoc) { var templateDocSrc = templateDoc ? JSON.stringify(templateDoc) : "{}" Added: couchdb/trunk/share/www/script/test/stats.js URL: http://svn.apache.org/viewvc/couchdb/trunk/share/www/script/test/stats.js?rev=747852&view=auto ============================================================================== --- couchdb/trunk/share/www/script/test/stats.js (added) +++ couchdb/trunk/share/www/script/test/stats.js Wed Feb 25 16:39:55 2009 @@ -0,0 +1,463 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +couchTests.stats = function(debug) { + if (debug) debugger; + + var open_databases_tests = { + 'should increment the number of open databases when creating a db': function(name) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + var open_databases = requestStatsTest("couchdb", "open_databases").current; + db.createDb(); + + var new_open_databases = requestStatsTest("couchdb", "open_databases").current; + TEquals(open_databases + 1, new_open_databases, name); + }, + 'should increment the number of open databases when opening a db': function(name) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + + restartServer(); + + var open_databases = requestStatsTest("couchdb", "open_databases").current; + + db.open("123"); + + var new_open_databases = requestStatsTest("couchdb", "open_databases").current; + TEquals(open_databases + 1, new_open_databases, name); + }, + 'should decrement the number of open databases when deleting': function(name) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + var open_databases = requestStatsTest("couchdb", "open_databases").current; + + db.deleteDb(); + var new_open_databases = requestStatsTest("couchdb", "open_databases").current; + TEquals(open_databases - 1, new_open_databases, name); + }, + 'should keep the same number of open databases when reaching the max_dbs_open limit': function(name) { + restartServer(); + var max = 5; + run_on_modified_server( + [{section: "couchdb", + key: "max_dbs_open", + value: max.toString()}], + + function () { + for(var i=0; i<max+1; i++) { + var db = new CouchDB("test_suite_db" + i); + db.deleteDb(); + db.createDb(); + } + + var open_databases = requestStatsTest("couchdb", "open_databases").max; + T(max >= open_databases, name); + + // not needed for the test but cleanup is nice + for(var i=0; i<max+1; i++) { + var db = new CouchDB("test_suite_db" + i); + db.deleteDb(); + } + }) + }, + }; + + var request_count_tests = { + 'should increase the request count for every request': function(name) { + var requests = requestStatsTest("httpd", "requests").current + 1; + + CouchDB.request("GET", "/"); + + var new_requests = requestStatsTest("httpd", "requests").current; + + TEquals(requests + 1, new_requests, name); + } + }; + + var document_read_count_tests = { + 'should increase read document counter when a document is read': function(name) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + db.save({"_id":"test"}); + + var reads = requestStatsTest("httpd", "document_reads").current; + db.open("test"); + var new_reads = requestStatsTest("httpd", "document_reads").current; + + TEquals(reads + 1 , new_reads, name); + }, + 'should not increase read document counter when a non-document is read': function(name) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + db.save({"_id":"test"}); + + var reads = requestStatsTest("httpd", "document_reads").current; + CouchDB.request("GET", "/"); + var new_reads = requestStatsTest("httpd", "document_reads").current; + + TEquals(reads, new_reads, name); + }, + 'should increase read document counter when a document\'s revisions are read': function(name) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + db.save({"_id":"test"}); + + var reads = requestStatsTest("httpd", "document_reads").current; + db.open("test", {"open_revs":"all"}); + var new_reads = requestStatsTest("httpd", "document_reads").current; + + TEquals(reads + 1 , new_reads, name); + } + }; + + var view_read_count_tests = { + 'should increase the permanent view read counter': function(name) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + + var reads = requestStatsTest("httpd", "view_reads").current; + createAndRequestView(db); + var new_reads = requestStatsTest("httpd", "view_reads").current; + + TEquals(reads + 1 , new_reads, name); + }, + 'should not increase the permanent view read counter when a document is read': function(name) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + db.save({"_id":"test"}); + + var reads = requestStatsTest("httpd", "view_reads").current; + db.open("test"); + var new_reads = requestStatsTest("httpd", "view_reads").current; + + TEquals(reads, new_reads, name); + }, + 'should not increase the permanent view read counter when a temporary view is read': function(name) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + + var reads = requestStatsTest("httpd", "view_reads").current; + db.query(function(doc) { emit(doc._id)}); + var new_reads = requestStatsTest("httpd", "view_reads").current; + + TEquals(reads, new_reads, name); + }, + 'should increase the temporary view read counter': function(name) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + + var reads = requestStatsTest("httpd", "temporary_view_reads").current; + db.query(function(doc) { emit(doc._id)}); + var new_reads = requestStatsTest("httpd", "temporary_view_reads").current; + + TEquals(reads + 1, new_reads, name); + }, + 'should increase the temporary view read counter when querying a permanent view': function(name) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + + var reads = requestStatsTest("httpd", "view_reads").current; + createAndRequestView(db); + var new_reads = requestStatsTest("httpd", "view_reads").current; + + TEquals(reads + 1 , new_reads, name); + } + }; + + var http_requests_by_method_tests = { + 'should count GET requests': function(name) { + var requests = requestStatsTest("httpd", "get_requests").current; + var new_requests = requestStatsTest("httpd", "get_requests").current; + + TEquals(requests + 1, new_requests, name); + }, + 'should not count GET requests for POST request': function(name) { + var requests = requestStatsTest("httpd", "get_requests").current; + CouchDB.request("POST", "/"); + var new_requests = requestStatsTest("httpd", "get_requests").current; + + TEquals(requests + 1, new_requests, name); + }, + 'should count POST requests': function(name) { + var requests = requestStatsTest("httpd", "post_requests").current; + CouchDB.request("POST", "/"); + var new_requests = requestStatsTest("httpd", "post_requests").current; + + TEquals(requests + 1, new_requests, name); + } + }; + + var document_write_count_tests = { + 'should increment counter for document creates': function(name) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + + var creates = requestStatsTest("httpd", "document_creates").current; + db.save({"a":"1"}); + var new_creates = requestStatsTest("httpd", "document_creates").current; + + TEquals(creates + 1, new_creates, name); + }, + 'should not increment counter for document creates when updating a doc': function(name) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + + var doc = {"_id":"test"}; + db.save(doc); + + var creates = requestStatsTest("httpd", "document_creates").current; + db.save(doc); + var new_creates = requestStatsTest("httpd", "document_creates").current; + + TEquals(creates, new_creates, name); + }, + 'should increment counter for document updates': function(name) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + + var doc = {"_id":"test"}; + db.save(doc); + + var updates = requestStatsTest("httpd", "document_updates").current; + db.save(doc); + var new_updates = requestStatsTest("httpd", "document_updates").current; + + TEquals(updates + 1, new_updates, name); + }, + 'should not increment counter for document updates when creating a document': function(name) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + + var updates = requestStatsTest("httpd", "document_updates").current; + db.save({"a":"1"}); + var new_updates = requestStatsTest("httpd", "document_updates").current; + + TEquals(updates, new_updates, name); + }, + 'should increment counter for document deletes': function(name) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + + var doc = {"_id":"test"}; + db.save(doc); + + var deletes = requestStatsTest("httpd", "document_deletes").current; + db.deleteDoc(doc); + var new_deletes = requestStatsTest("httpd", "document_deletes").current; + + TEquals(deletes + 1, new_deletes, name); + }, + 'should increment the copy counter': function(name) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + + var doc = {"_id":"test"}; + db.save(doc); + + var copies = requestStatsTest("httpd", "document_copies").current; + CouchDB.request("COPY", "/test_suite_db/test", { + headers: {"Destination":"copy_of_test"} + }); + var new_copies = requestStatsTest("httpd", "document_copies").current; + + TEquals(copies + 1, new_copies, name); + }, + 'should increment the move counter': function(name) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + + var doc = {"_id":"test"}; + db.save(doc); + + var moves = requestStatsTest("httpd", "document_moves").current; + CouchDB.request("MOVE", "/test_suite_db/test?rev=" + doc._rev, { + headers: {"Destination":"move_of_test"} + }); + var new_moves = requestStatsTest("httpd", "document_moves").current; + + TEquals(moves + 1, new_moves, name); + }, + 'should increase the bulk doc counter': function(name) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + + var bulks = requestStatsTest("httpd", "bulk_requests").current; + + var docs = makeDocs(5); + db.bulkSave(docs); + + var new_bulks = requestStatsTest("httpd", "bulk_requests").current; + + TEquals(bulks + 1, new_bulks, name); + }, + 'should increment counter for document creates using POST': function(name) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + + var creates = requestStatsTest("httpd", "document_creates").current; + CouchDB.request("POST", "/test_suite_db", {body:'{"a":"1"}'}); + var new_creates = requestStatsTest("httpd", "document_creates").current; + + TEquals(creates + 1, new_creates, name); + }, + 'should increment document create counter when adding attachment': function(name) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + + var creates = requestStatsTest("httpd", "document_creates").current; + CouchDB.request("PUT", "/test_suite_db/bin_doc2/foo2.txt", { + body:"This is no base64 encoded text", + headers:{"Content-Type": "text/plain;charset=utf-8"} + }); + var new_creates = requestStatsTest("httpd", "document_creates").current; + TEquals(creates + 1, new_creates, name); + }, + 'should increment document update counter when adding attachment to existing doc': function(name) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + + var doc = {_id:"test"}; + db.save(doc); + + var updates = requestStatsTest("httpd", "document_updates").current; + CouchDB.request("PUT", "/test_suite_db/test/foo2.txt?rev=" + doc._rev, { + body:"This is no base64 encoded text", + headers:{"Content-Type": "text/plain;charset=utf-8"} + }); + var new_updates = requestStatsTest("httpd", "document_updates").current; + TEquals(updates + 1, new_updates, name); + } + + }; + var response_codes_tests = { + 'should increment the response code counter': function(name) { + var db = new CouchDB("nonexistant_db"); + db.deleteDb(); + + var not_founds = requestStatsTest("http_status_codes", "404").current; + CouchDB.request("GET", "/nonexistant_db"); + var new_not_founds = requestStatsTest("http_status_codes", "404").current; + + TEquals(not_founds + 1, new_not_founds, name); + }, + 'should not increment respinse code counter for other response code': function(name) { + var not_founds = requestStatsTest("http_status_codes", "404").current; + CouchDB.request("GET", "/"); + var new_not_founds = requestStatsTest("http_status_codes", "404").current; + + TEquals(not_founds, new_not_founds, name); + } + }; + + var aggregation_tests = { + 'should return the mean': function(name) { + CouchDB.request("GET", "/"); + + var mean = requestStatsTest("httpd", "requests").mean; + + T(mean >= 0, name); + }, + 'should return the maximum': function(name) { + CouchDB.request("GET", "/"); + + var maximum = requestStatsTest("httpd", "requests").max; + + T(maximum >= 0, name); + }, + 'should return the minimum': function(name) { + CouchDB.request("GET", "/"); + + var minimum = requestStatsTest("httpd", "requests", "min").min; + + T(minimum >= 0, name); + }, + 'should return the stddev': function(name) { + CouchDB.request("GET", "/"); + + var stddev = requestStatsTest("httpd", "stddev_requests").current; + + T(stddev >= 0, name); + } + }; + + var summary_tests = { + 'should show a summary of all counters with aggregated values': function(name) { + var options = {}; + options.headers = {"Accept": "application/json"}; + var summary = JSON.parse(CouchDB.request("GET", "/_stats", options).responseText); + var aggregates = ["mean", "min", "max", "stddev", + "current", "resolution"]; + + for(var i in aggregates) { + T(summary.httpd.requests[aggregates[i]] >= 0, aggregates[i] + " >= 0", name); + } + } + }; + + var tests = [ + open_databases_tests, + request_count_tests, + document_read_count_tests, + view_read_count_tests, + http_requests_by_method_tests, + document_write_count_tests, + response_codes_tests, + aggregation_tests, + summary_tests + ]; + + for(var testGroup in tests) { + for(var test in tests[testGroup]) { + tests[testGroup][test](test); + } + }; + + function createAndRequestView(db) { + var designDoc = { + _id:"_design/test", // turn off couch.js id escaping? + language: "javascript", + views: { + all_docs_twice: {map: "function(doc) { emit(doc.integer, null); emit(doc.integer, null) }"}, + } + }; + db.save(designDoc); + + db.view("test/all_docs_twice"); + } + + function requestStatsTest(module, key) { + return CouchDB.requestStats(module, key, true); + } +} + \ No newline at end of file Modified: couchdb/trunk/src/couchdb/couch_httpd_stats_handlers.erl URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_httpd_stats_handlers.erl?rev=747852&r1=747851&r2=747852&view=diff ============================================================================== --- couchdb/trunk/src/couchdb/couch_httpd_stats_handlers.erl (original) +++ couchdb/trunk/src/couchdb/couch_httpd_stats_handlers.erl Wed Feb 25 16:39:55 2009 @@ -22,12 +22,27 @@ -define(b2a(V), list_to_atom(binary_to_list(V))). +-record(stats_query_args, { + range='0', + flush=false +}). + handle_stats_req(#httpd{method='GET', path_parts=[_]}=Req) -> send_json(Req, couch_stats_aggregator:all()); handle_stats_req(#httpd{method='GET', path_parts=[_Stats, Module, Key]}=Req) -> - Time = parse_stats_query(Req), - Stats = couch_stats_aggregator:get_json({?b2a(Module), ?b2a(Key)}, Time), + #stats_query_args{ + range=Range, + flush=Flush + } = parse_stats_query(Req), + + case Flush of + true -> + couch_stats_aggregator:time_passed(); + _ -> ok + end, + + Stats = couch_stats_aggregator:get_json({?b2a(Module), ?b2a(Key)}, Range), Response = {[{Module, {[{Key, Stats}]}}]}, send_json(Req, Response); @@ -35,7 +50,13 @@ send_method_not_allowed(Req, "GET"). parse_stats_query(Req) -> - case couch_httpd:qs(Req) of - [{"range", Time}] -> list_to_atom(Time); - _ -> '0' - end. + lists:foldl(fun({Key,Value}, Args) -> + case {Key, Value} of + {"range", Range} -> + Args#stats_query_args{range=list_to_atom(Range)}; + {"flush", "true"} -> + Args#stats_query_args{flush=true}; + _Else -> % unknown key value pair, ignore. + Args + end + end, #stats_query_args{}, couch_httpd:qs(Req)). Modified: couchdb/trunk/src/couchdb/couch_server.erl URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_server.erl?rev=747852&r1=747851&r2=747852&view=diff ============================================================================== --- couchdb/trunk/src/couchdb/couch_server.erl (original) +++ couchdb/trunk/src/couchdb/couch_server.erl Wed Feb 25 16:39:55 2009 @@ -183,7 +183,7 @@ % must free up the lru db. case try_close_lru(now()) of ok -> - couch_stats_collector:decrement({couchdb, open_databases}), + couch_stats_collector:decrement({couchdb, open_databases}), {ok, Server#server{dbs_open=NumOpen - 1}}; Error -> Error end. Modified: couchdb/trunk/src/couchdb/couch_stats_aggregator.erl URL: http://svn.apache.org/viewvc/couchdb/trunk/src/couchdb/couch_stats_aggregator.erl?rev=747852&r1=747851&r2=747852&view=diff ============================================================================== --- couchdb/trunk/src/couchdb/couch_stats_aggregator.erl (original) +++ couchdb/trunk/src/couchdb/couch_stats_aggregator.erl Wed Feb 25 16:39:55 2009 @@ -241,10 +241,10 @@ end. -aggregate_to_json_term(#aggregates{min=Min,max=Max,mean=Mean,stddev=Stddev,count=Count}) -> +aggregate_to_json_term(#aggregates{min=Min,max=Max,mean=Mean,stddev=Stddev,count=Count,last=Last}) -> {[ % current is redundant, but reads nicer in JSON - {current, Count}, + {current, Last}, {count, Count}, {mean, Mean}, {min, Min},