No title Revision 393232376262 (Thu Jan 29 2009 at 23:06) - Diff Link to this snippet: https://friendpaste.com/3IfIyRv5EzXEnPyk7S9xqe Embed: manni perldoc borland colorful default murphy trac fruity autumn bw emacs pastie friendly Show line numbers Wrap lines 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243diff --git a/Makefile.am b/Makefile.amindex 41ca5cf..2144f19 100644--- a/Makefile.am+++ b/Makefile.am@@ -70,3 +70,7 @@ distsign: distcheck > $(top_srcdir)/$(distdir).tar.gz.md5 sha1sum $(top_srcdir)/$(distdir).tar.gz \ > $(top_srcdir)/$(distdir).tar.gz.sha++.PHONY: t+t: all+ @erl -pa src/couchdb/ -eval "couch_stats_aggregator:test()" -eval "couch_stats_collector:test()"\ No newline at end of filediff --git a/etc/couchdb/default.ini.tpl.in b/etc/couchdb/default.ini.tpl.inindex 28ab36f..06e8b36 100644--- a/etc/couchdb/default.ini.tpl.in+++ b/etc/couchdb/default.ini.tpl.in@@ -33,6 +33,8 @@ external_manager={couch_external_manager, start_link, []} db_update_notifier={couch_db_update_notifier_sup, start_link, []} query_servers={couch_query_servers, start_link, []} httpd={couch_httpd, start_link, []}+stats_aggregator={couch_stats_aggregator, start, []}+stats_collector={couch_stats_collector, start, []} [httpd_global_handlers] / = {couch_httpd_misc_handlers, handle_welcome_req, <<"Welcome">>}@@ -40,12 +42,14 @@ favicon.ico = {couch_httpd_misc_handlers, handle_favicon_req, "%localdatadir%/ww _utils = {couch_httpd_misc_handlers, handle_utils_dir_req, "%localdatadir%/www"} _all_dbs = {couch_httpd_misc_handlers, handle_all_dbs_req}-_stats = {couch_httpd_misc_handlers, handle_stats_req}+;_stats = {couch_httpd_misc_handlers, handle_stats_req} _active_tasks = {couch_httpd_misc_handlers, handle_task_status_req} _config = {couch_httpd_misc_handlers, handle_config_req} _replicate = {couch_httpd_misc_handlers, handle_replicate_req} _uuids = {couch_httpd_misc_handlers, handle_uuids_req} _restart = {couch_httpd_misc_handlers, handle_restart_req}+_stats = {couch_httpd_stats_handlers, handle_stats_req}+ [httpd_db_handlers] _view = {couch_httpd_view, handle_view_req}diff --git a/etc/couchdb/local_dev.ini b/etc/couchdb/local_dev.iniindex 09123cf..510f5c6 100644--- a/etc/couchdb/local_dev.ini+++ b/etc/couchdb/local_dev.ini@@ -12,7 +12,10 @@ ;bind_address = 127.0.0.1 [log]-level = error+level = debug [update_notification] ;unique notifier name=/full/path/to/exe -with "cmd line arg"++[test]+foo = bardiff --git a/share/www/script/couch.js b/share/www/script/couch.jsindex 0a2698a..7926645 100644--- a/share/www/script/couch.js+++ b/share/www/script/couch.js@@ -295,6 +295,13 @@ CouchDB.request = function(method, uri, options) { return req; } +CouchDB.requestStats = function(module, key, options) {+ options = options || {};+ var stat = CouchDB.request("GET", "/_stats/" + module + "/" + key + + "?" + CouchDB.params(options)).responseText;+ return JSON.parse(stat)[module][key];+}+ CouchDB.uuids_cache = []; CouchDB.newUuids = function(n) {@@ -327,3 +334,13 @@ CouchDB.maybeThrowError = function(req) { throw result; } }++CouchDB.params = function(options) {+ options = options || {};+ var returnArray = [];+ for(var key in options) {+ var value = options[key];+ returnArray.push(key + "=" + value);+ }+ return returnArray.join("&");+}\ No newline at end of filediff --git a/share/www/script/couch_test_runner.js b/share/www/script/couch_test_runner.jsindex 31bd4a4..beede46 100644--- a/share/www/script/couch_test_runner.js+++ b/share/www/script/couch_test_runner.js@@ -152,13 +152,13 @@ function updateTestsFooter() { // display the line that failed. // Example: // T(MyValue==1);-function T(arg1, arg2) {+function T(arg1, arg2, testName) { if (!arg1) { if (currentRow) { if ($("td.details ol", currentRow).length == 0) { $("<ol></ol>").appendTo($("td.details", currentRow)); }- $("<li><b>Assertion failed:</b> <code class='failure'></code></li>")+ $("<li><b>Assertion " + (testName ? "'" + testName + "'" : "") + " failed:</b> <code class='failure'></code></li>") .find("code").text((arg2 != null ? arg2 : arg1).toString()).end() .appendTo($("td.details ol", currentRow)); }@@ -166,6 +166,10 @@ function T(arg1, arg2) { } } +function TEquals(expected, actual, testName) {+ T(equals(expected, actual), "expected '" + expected + "', got '" + actual + "'", testName);+}+ function equals(a,b) { if (a === b) return true; try {diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.jsindex a6c47a7..e1eefd9 100644--- a/share/www/script/couch_tests.js+++ b/share/www/script/couch_tests.js@@ -21,6 +21,425 @@ if (typeof window == 'undefined' || !window) { var tests = { + 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 = parseInt(CouchDB.requestStats("couch_db", "open_databases"));+ db.createDb();+ + var new_open_databases = parseInt(CouchDB.requestStats("couch_db", "open_databases"));+ TEquals(parseInt(open_databases) + 1, parseInt(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 = parseInt(CouchDB.requestStats("couch_db", "open_databases"));+ + db.open("123");+ + var new_open_databases = parseInt(CouchDB.requestStats("couch_db", "open_databases"));+ TEquals(parseInt(open_databases) + 1, parseInt(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 = parseInt(CouchDB.requestStats("couch_db", "open_databases"));+ + db.deleteDb();+ var new_open_databases = parseInt(CouchDB.requestStats("couch_db", "open_databases"));+ TEquals(parseInt(open_databases) - 1, parseInt(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 = parseInt(CouchDB.requestStats("couch_db", "open_databases"));+ TEquals(max, parseInt(open_databases), name);+ })+ },+ 'should return 0 for number of open databases after call to restartServer()': function(name) {+ var db = new CouchDB("test_suite_db");+ db.deleteDb();+ db.createDb();++ restartServer();+ var open_databases = parseInt(CouchDB.requestStats("couch_db", "open_databases"));+ + TEquals(0, parseInt(open_databases), name);+ }+ };+ + var request_count_tests = {+ 'should increase the request count for every request': function(name) {+ var requests = parseInt(CouchDB.requestStats("httpd", "request_count")) + 1;++ CouchDB.request("GET", "/");++ var new_requests = parseInt(CouchDB.requestStats("httpd", "request_count"));+ T(requests >= 0, "requests >= 0", name);+ TEquals(requests + 1, new_requests, name);+ },+ 'should return the average requests/s for the last minute': function(name) {+ restartServer();+ var requests = parseFloat(CouchDB.requestStats("httpd", "average_requests"));+ TEquals(requests, 0.0, name);+ },+ 'should return the average request/s for the last 5 and 15 minutes': function(name) {+ restartServer();+ var requests = parseInt(CouchDB.requestStats("httpd", "average_requests"), {"timeframe":5});+ TEquals(requests, 0, name);++ var requests = parseInt(CouchDB.requestStats("httpd", "average_requests"), {"timeframe":15});+ TEquals(requests, 0, 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 = parseInt(CouchDB.requestStats("httpd", "document_reads"));+ db.open("test");+ var new_reads = parseInt(CouchDB.requestStats("httpd", "document_reads"));++ 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 = parseInt(CouchDB.requestStats("httpd", "document_reads"));+ CouchDB.request("GET", "/");+ var new_reads = parseInt(CouchDB.requestStats("httpd", "document_reads"));++ 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 = parseInt(CouchDB.requestStats("httpd", "document_reads"));+ db.open("test", {"open_revs":"all"});+ var new_reads = parseInt(CouchDB.requestStats("httpd", "document_reads"));++ 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 = parseInt(CouchDB.requestStats("httpd", "view_reads"));+ createAndRequestView(db);+ var new_reads = parseInt(CouchDB.requestStats("httpd", "view_reads"));++ 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 = parseInt(CouchDB.requestStats("httpd", "view_reads"));+ db.open("test");+ var new_reads = parseInt(CouchDB.requestStats("httpd", "view_reads"));++ 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 = parseInt(CouchDB.requestStats("httpd", "view_reads"));+ db.query(function(doc) { emit(doc._id)});+ var new_reads = parseInt(CouchDB.requestStats("httpd", "view_reads"));++ 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 = parseInt(CouchDB.requestStats("httpd", "temporary_view_reads"));+ db.query(function(doc) { emit(doc._id)});+ var new_reads = parseInt(CouchDB.requestStats("httpd", "temporary_view_reads"));++ 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 = parseInt(CouchDB.requestStats("httpd", "view_reads"));+ createAndRequestView(db);+ var new_reads = parseInt(CouchDB.requestStats("httpd", "view_reads"));++ TEquals(reads + 1 , new_reads, name);+ }+ };+ + var http_requests_by_method_tests = {+ 'should count GET requests': function(name) {+ var requests = parseInt(CouchDB.requestStats("httpd", "get_requests"));+ var new_requests = parseInt(CouchDB.requestStats("httpd", "get_requests"));++ TEquals(requests + 1, new_requests, name);+ },+ 'should not count GET requests for POST request': function(name) {+ var requests = parseInt(CouchDB.requestStats("httpd", "get_requests"));+ CouchDB.request("POST", "/");+ var new_requests = parseInt(CouchDB.requestStats("httpd", "get_requests"));++ TEquals(requests + 1, new_requests, name); + },+ 'should count POST requests': function(name) {+ var requests = parseInt(CouchDB.requestStats("httpd", "post_requests"));+ CouchDB.request("POST", "/");+ var new_requests = parseInt(CouchDB.requestStats("httpd", "post_requests"));++ 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 = parseInt(CouchDB.requestStats("httpd", "document_creates"));+ db.save({"a":"1"});+ var new_creates = parseInt(CouchDB.requestStats("httpd", "document_creates"));++ 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 = parseInt(CouchDB.requestStats("httpd", "document_creates"));+ db.save(doc);+ var new_creates = parseInt(CouchDB.requestStats("httpd", "document_creates"));++ 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 = parseInt(CouchDB.requestStats("httpd", "document_updates"));+ db.save(doc);+ var new_updates = parseInt(CouchDB.requestStats("httpd", "document_updates"));++ 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 = parseInt(CouchDB.requestStats("httpd", "document_updates"));+ db.save({"a":"1"});+ var new_updates = parseInt(CouchDB.requestStats("httpd", "document_updates"));++ 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 = parseInt(CouchDB.requestStats("httpd", "document_deletes"));+ db.deleteDoc(doc);+ var new_deletes = parseInt(CouchDB.requestStats("httpd", "document_deletes"));++ 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 = parseInt(CouchDB.requestStats("httpd", "document_copies"));+ CouchDB.request("COPY", "/test_suite_db/test", {+ headers: {"Destination":"copy_of_test"}+ });+ var new_copies = parseInt(CouchDB.requestStats("httpd", "document_copies"));++ 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 = parseInt(CouchDB.requestStats("httpd", "document_moves"));+ CouchDB.request("MOVE", "/test_suite_db/test?rev=" + doc._rev, {+ headers: {"Destination":"move_of_test"}+ });+ var new_moves = parseInt(CouchDB.requestStats("httpd", "document_moves"));++ 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 = parseInt(CouchDB.requestStats("httpd", "bulk_requests"));++ var docs = makeDocs(5);+ db.bulkSave(docs);+ + var new_bulks = parseInt(CouchDB.requestStats("httpd", "bulk_requests"));++ 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 = parseInt(CouchDB.requestStats("httpd", "document_creates"));+ CouchDB.request("POST", "/test_suite_db", {body:'{"a":"1"}'});+ var new_creates = parseInt(CouchDB.requestStats("httpd", "document_creates"));++ 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 = parseInt(CouchDB.requestStats("httpd", "document_creates"));+ 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 = parseInt(CouchDB.requestStats("httpd", "document_creates"));+ 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 = parseInt(CouchDB.requestStats("httpd", "document_updates"));+ 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 = parseInt(CouchDB.requestStats("httpd", "document_updates"));+ 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 = parseInt(CouchDB.requestStats("http_status_codes", "404"));+ CouchDB.request("GET", "/nonexistant_db");+ var new_not_founds = parseInt(CouchDB.requestStats("http_status_codes", "404"));++ TEquals(not_founds + 1, new_not_founds, name);+ },+ 'should not increment respinse code counter for other response code': function(name) {+ var not_founds = parseInt(CouchDB.requestStats("http_status_codes", "404"));+ CouchDB.request("GET", "/");+ var new_not_founds = parseInt(CouchDB.requestStats("http_status_codes", "404"));++ TEquals(not_founds, new_not_founds, 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+ ];+ + 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");+ }++ },+ // Do some basic tests. basics: function(debug) { var result = JSON.parse(CouchDB.request("GET", "/").responseText);@@ -2667,7 +3086,7 @@ var tests = { } }) })- }+ }, }; T(db.save(designDoc).ok);diff --git a/src/couchdb/Makefile.am b/src/couchdb/Makefile.amindex 1ad5d14..769cd1c 100644--- a/src/couchdb/Makefile.am+++ b/src/couchdb/Makefile.am@@ -58,6 +58,7 @@ source_files = \ couch_httpd_show.erl \ couch_httpd_view.erl \ couch_httpd_misc_handlers.erl \+ couch_httpd_stats_handlers.erl \ couch_key_tree.erl \ couch_log.erl \ couch_os_process.erl \@@ -65,6 +66,8 @@ source_files = \ couch_rep.erl \ couch_server.erl \ couch_server_sup.erl \+ couch_stats_aggregator.erl \+ couch_stats_collector.erl \ couch_stream.erl \ couch_task_status.erl \ couch_util.erl \@@ -95,6 +98,7 @@ compiled_files = \ couch_httpd_show.beam \ couch_httpd_view.beam \ couch_httpd_misc_handlers.beam \+ couch_httpd_stats_handlers.beam \ couch_key_tree.beam \ couch_log.beam \ couch_os_process.beam \@@ -102,6 +106,8 @@ compiled_files = \ couch_rep.beam \ couch_server.beam \ couch_server_sup.beam \+ couch_stats_aggregator.beam \+ couch_stats_collector.beam \ couch_stream.beam \ couch_task_status.beam \ couch_util.beam \@@ -150,7 +156,7 @@ couch.app: couch.app.tpl # $(ERL) -noshell -run edoc_run files [\"$<\"] %.beam: %.erl couch_db.hrl- $(ERLC) $<+ $(ERLC) -D$TEST $< install-data-hook: if test -f "$(DESTDIR)/$(couchprivlibdir)/couch_erl_driver"; then \diff --git a/src/couchdb/couch_httpd.erl b/src/couchdb/couch_httpd.erlindex 7c37132..a9a130b 100644--- a/src/couchdb/couch_httpd.erl+++ b/src/couchdb/couch_httpd.erl@@ -126,11 +126,8 @@ handle_request(MochiReq, UrlHandlers, DbUrlHandlers) -> mochiweb_headers:to_list(MochiReq:get(headers)) ]), - Method =+ Method1 = case MochiReq:get(method) of- % alias HEAD to GET as mochiweb takes care of stripping the body- 'HEAD' -> 'GET';- % already an atom Meth when is_atom(Meth) -> Meth; @@ -138,6 +135,15 @@ handle_request(MochiReq, UrlHandlers, DbUrlHandlers) -> % possible (if any module references the atom, then it's existing). Meth -> couch_util:to_existing_atom(Meth) end,+ + increment_method_stats(Method1),+ + % alias HEAD to GET as mochiweb takes care of stripping the body+ Method = case Method1 of+ 'HEAD' -> 'GET';+ Other -> Other+ end,+ HttpReq = #httpd{ mochi_req = MochiReq, method = Method,@@ -166,8 +172,12 @@ handle_request(MochiReq, UrlHandlers, DbUrlHandlers) -> RawUri, Resp:get(code) ]),+ couch_stats_collector:increment({httpd, request_count}), {ok, Resp}. +increment_method_stats(Method) ->+ CounterName = list_to_atom(string:to_lower(atom_to_list(Method)) ++ "_requests"),+ couch_stats_collector:increment({httpd, CounterName}). special_test_authentication_handler(Req) -> case header_value(Req, "WWW-Authenticate") of@@ -319,6 +329,7 @@ basic_username_pw(Req) -> start_chunked_response(#httpd{mochi_req=MochiReq}, Code, Headers) ->+ couch_stats_collector:increment({http_status_codes, Code}), {ok, MochiReq:respond({Code, Headers ++ server_header(), chunked})}. send_chunk(Resp, Data) ->@@ -326,6 +337,7 @@ send_chunk(Resp, Data) -> {ok, Resp}. send_response(#httpd{mochi_req=MochiReq}, Code, Headers, Body) ->+ couch_stats_collector:increment({http_status_codes, Code}), if Code >= 400 -> ?LOG_DEBUG("HTTPd ~p error response:~n ~s", [Code, Body]); true -> okdiff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erlindex 2cb4c40..c66c903 100644--- a/src/couchdb/couch_httpd_db.erl+++ b/src/couchdb/couch_httpd_db.erl@@ -80,6 +80,7 @@ db_req(#httpd{method='POST',path_parts=[_DbName]}=Req, Db) -> Doc = couch_doc:from_json_obj(couch_httpd:json_body(Req)), DocId = couch_util:new_uuid(), {ok, NewRev} = couch_db:update_doc(Db, Doc#doc{id=DocId, revs=[]}, []),+ couch_stats_collector:increment({httpd, document_creates}), send_json(Req, 201, {[ {ok, true}, {id, DocId},@@ -100,6 +101,7 @@ db_req(#httpd{path_parts=[_,<<"_ensure_full_commit">>]}=Req, _Db) -> send_method_not_allowed(Req, "POST"); db_req(#httpd{method='POST',path_parts=[_,<<"_bulk_docs">>]}=Req, Db) ->+ couch_stats_collector:increment({httpd, bulk_requests}), {JsonProps} = couch_httpd:json_body(Req), DocsArray = proplists:get_value(<<"docs">>, JsonProps), case couch_httpd:header_value(Req, "X-Couch-Full-Commit", "false") of@@ -370,6 +372,7 @@ db_doc_req(#httpd{method='DELETE'}=Req, Db, DocId) -> couch_httpd:send_error(Req, 412, <<"missing_rev">>, <<"Document rev/etag must be specified to delete">>); RevToDelete ->+ couch_stats_collector:increment({httpd, document_deletes}), {ok, NewRev} = couch_db:delete_doc(Db, DocId, [RevToDelete]), send_json(Req, 200, {[ {ok, true},@@ -393,7 +396,8 @@ db_doc_req(#httpd{method='GET'}=Req, Db, DocId) -> [] -> [{"Etag", DiskEtag}]; % output etag only when we have no meta _ -> [] end,- send_json(Req, 200, Headers, couch_doc:to_json_obj(Doc, Options)) + couch_stats_collector:increment({httpd, document_reads}),+ send_json(Req, 200, Headers, couch_doc:to_json_obj(Doc, Options)) end); _ -> {ok, Results} = couch_db:open_doc_revs(Db, DocId, Revs, Options),@@ -416,6 +420,7 @@ db_doc_req(#httpd{method='GET'}=Req, Db, DocId) -> end, "", Results), send_chunk(Resp, "]"),+ couch_stats_collector:increment({httpd, document_reads}), end_json_response(Resp) end; @@ -460,8 +465,10 @@ db_doc_req(#httpd{method='PUT'}=Req, Db, DocId) -> end, case extract_header_rev(Req, ExplicitRev) of missing_rev ->+ couch_stats_collector:increment({httpd, document_creates}), Revs = []; Rev ->+ couch_stats_collector:increment({httpd, document_updates}), Revs = [Rev] end, {ok, NewRev} = couch_db:update_doc(Db, Doc#doc{id=DocId, revs=Revs}, Options),@@ -485,6 +492,7 @@ db_doc_req(#httpd{method='COPY'}=Req, Db, SourceDocId) -> % save new doc {ok, NewTargetRev} = couch_db:update_doc(Db, Doc#doc{id=TargetDocId, revs=TargetRev}, []),+ couch_stats_collector:increment({httpd, document_copies}), send_json(Req, 201, [{"Etag", "\"" ++ binary_to_list(NewTargetRev) ++ "\""}], {[ {ok, true},@@ -510,9 +518,9 @@ db_doc_req(#httpd{method='MOVE'}=Req, Db, SourceDocId) -> Doc#doc{id=TargetDocId, revs=TargetRev}, #doc{id=SourceDocId, revs=[SourceRev], deleted=true} ],- {ok, ResultRevs} = couch_db:update_docs(Db, Docs, []),-+ couch_stats_collector:increment({httpd, document_moves}),+ DocResults = lists:zipwith( fun(FDoc, NewRev) -> {[{id, FDoc#doc.id}, {rev, NewRev}]}@@ -602,8 +610,10 @@ db_attachment_req(#httpd{method=Method}=Req, Db, DocId, FileNameParts) Doc = case extract_header_rev(Req, couch_httpd:qs_value(Req, "rev")) of missing_rev -> % make the new doc+ couch_stats_collector:increment({httpd, document_creates}), #doc{id=DocId}; Rev ->+ couch_stats_collector:increment({httpd, document_updates}), case couch_db:open_doc_revs(Db, DocId, [Rev], []) of {ok, [{ok, Doc0}]} -> Doc0#doc{revs=[Rev]}; {ok, [Error]} -> throw(Error)diff --git a/src/couchdb/couch_httpd_stats_handlers.erl b/src/couchdb/couch_httpd_stats_handlers.erlnew file mode 100644index 0000000..f307582--- /dev/null+++ b/src/couchdb/couch_httpd_stats_handlers.erl@@ -0,0 +1,30 @@+% 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.++-module(couch_httpd_stats_handlers).+-include("couch_db.hrl").++-export([handle_stats_req/1]).+-import(couch_httpd,+ [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,+ start_json_response/2,send_chunk/2,end_json_response/1,+ start_chunked_response/3, send_error/4]).++handle_stats_req(#httpd{method='GET', path_parts=PathParts}=Req) ->+ [_Db, Module, Key] = PathParts,+ Options = couch_httpd:qs(Req),++ Count = couch_stats_aggregator:get({Module, Key}, Options),+ Response = {[{Module, {[{Key, Count}]}}]},+ send_json(Req, Response);+handle_stats_req(Req) ->+ send_method_not_allowed(Req, "GET").diff --git a/src/couchdb/couch_httpd_view.erl b/src/couchdb/couch_httpd_view.erlindex a6174a2..da43b6c 100644--- a/src/couchdb/couch_httpd_view.erl+++ b/src/couchdb/couch_httpd_view.erl@@ -28,7 +28,7 @@ design_doc_view(Req, Db, Id, ViewName, Keys) -> reduce = Reduce } = QueryArgs = parse_view_query(Req, Keys), DesignId = <<"_design/", Id/binary>>,- case couch_view:get_map_view(Db, DesignId, ViewName, Stale) of+ Result = case couch_view:get_map_view(Db, DesignId, ViewName, Stale) of {ok, View} -> output_map_view(Req, View, Db, QueryArgs, Keys); {not_found, Reason} ->@@ -45,7 +45,9 @@ design_doc_view(Req, Db, Id, ViewName, Keys) -> _ -> throw({not_found, Reason}) end- end.+ end,+ couch_stats_collector:increment({httpd, view_reads}),+ Result. handle_view_req(#httpd{method='GET',path_parts=[_,_, Id, ViewName]}=Req, Db) -> design_doc_view(Req, Db, Id, ViewName, nil);@@ -60,7 +62,7 @@ handle_view_req(Req, _Db) -> handle_temp_view_req(#httpd{method='POST'}=Req, Db) -> QueryArgs = parse_view_query(Req),-+ couch_stats_collector:increment({httpd, temporary_view_reads}), case couch_httpd:primary_header_value(Req, "content-type") of undefined -> ok; "application/json" -> ok;diff --git a/src/couchdb/couch_server.erl b/src/couchdb/couch_server.erlindex 69cfa36..f4f0e35 100644--- a/src/couchdb/couch_server.erl+++ b/src/couchdb/couch_server.erl@@ -182,7 +182,9 @@ maybe_close_lru_db(#server{dbs_open=NumOpen, max_dbs_open=MaxOpen}=Server) maybe_close_lru_db(#server{dbs_open=NumOpen}=Server) -> % must free up the lru db. case try_close_lru(now()) of- ok -> {ok, Server#server{dbs_open=NumOpen-1}};+ ok -> + couch_stats_collector:decrement({couch_db, open_databases}), + {ok, Server#server{dbs_open=NumOpen - 1}}; Error -> Error end. @@ -235,6 +237,7 @@ handle_call({open, DbName, Options}, _From, Server) -> true = ets:insert(couch_dbs_by_pid, {MainPid, DbName}), true = ets:insert(couch_dbs_by_lru, {LruTime, DbName}), DbsOpen = Server2#server.dbs_open + 1,+ couch_stats_collector:increment({couch_db, open_databases}), {reply, {ok, MainPid}, Server2#server{dbs_open=DbsOpen}}; Error ->@@ -270,6 +273,7 @@ handle_call({create, DbName, Options}, _From, Server) -> true = ets:insert(couch_dbs_by_pid, {MainPid, DbName}), true = ets:insert(couch_dbs_by_lru, {LruTime, DbName}), DbsOpen = Server2#server.dbs_open + 1,+ couch_stats_collector:increment({couch_db, open_databases}), couch_db_update_notifier:notify({created, DbName}), {reply, {ok, MainPid}, Server2#server{dbs_open=DbsOpen}};@@ -299,6 +303,7 @@ handle_call({delete, DbName, _Options}, _From, Server) -> true = ets:delete(couch_dbs_by_name, DbName), true = ets:delete(couch_dbs_by_pid, Pid), true = ets:delete(couch_dbs_by_lru, LruTime),+ couch_stats_collector:decrement({couch_db, open_databases}), Server#server{dbs_open=Server#server.dbs_open - 1} end, case file:delete(FullFilepath) of@@ -328,6 +333,7 @@ handle_info({'EXIT', Pid, _Reason}, #server{dbs_open=DbsOpen}=Server) -> true = ets:delete(couch_dbs_by_pid, Pid), true = ets:delete(couch_dbs_by_name, DbName), true = ets:delete(couch_dbs_by_lru, LruTime),- {noreply, Server#server{dbs_open=DbsOpen-1}};+ couch_stats_collector:decrement({couch_db, open_databases}),+ {noreply, Server#server{dbs_open=DbsOpen - 1}}; handle_info(Info, _Server) -> exit({unknown_message, Info}).diff --git a/src/couchdb/couch_server_sup.erl b/src/couchdb/couch_server_sup.erlindex 627c34a..57a848e 100644--- a/src/couchdb/couch_server_sup.erl+++ b/src/couchdb/couch_server_sup.erl@@ -32,6 +32,7 @@ start_link(IniFiles) -> end. restart_core_server() ->+ catch couch_stats_collector:stop(), % TODO: move to more appropriate place supervisor:terminate_child(couch_primary_services, couch_server), supervisor:restart_child(couch_primary_services, couch_server). diff --git a/src/couchdb/couch_stats_aggregator.erl b/src/couchdb/couch_stats_aggregator.erlnew file mode 100644index 0000000..f1a17ff--- /dev/null+++ b/src/couchdb/couch_stats_aggregator.erl@@ -0,0 +1,189 @@+% 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.++-module(couch_stats_aggregator).++-define(TEST, true).+-ifdef(TEST).+ -include_lib("eunit/include/eunit.hrl").+-endif.++-behaviour(gen_server).++-export([init/1, handle_call/3, handle_cast/2, handle_info/2,+ terminate/2, code_change/3]).+++-export([start/0, stop/0, get/1, get/2, time_passed/1]).++-record(state, {}).++-define(COLLECTOR, couch_stats_collector).++% PUBLIC API++start() ->+ gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).+ +stop() ->+ gen_server:call(?MODULE, stop).++get(Key) ->+ get(Key, []).+get(Key, Options) ->+ gen_server:call(?MODULE, {get, Key, Options}).++time_passed(Time) ->+ gen_server:call(?MODULE, {time_passed, Time}).+++% GEN_SERVER+ +init(_) ->+ ets:new(?MODULE, [named_table, set, protected]),+ lists:map(fun(Time) -> init_counter(Time) end, [1, 5, 15]),+ {ok, #state{}}.++handle_call({get, {Module, Key}, Options}, _, State) ->+ Value = + case a2b(Key) of+ <<"average_",CollectorKey/binary>> ->+ get_average(Module, CollectorKey, Options);+ _ -> + ?COLLECTOR:get({b2a(Module), b2a(Key)})+ end,+ + {reply, integer_to_binary(Value), State};++handle_call(stop, _, State) ->+ {stop, normal, stopped, State};++% update all counters that match `Time` = int()+handle_call({time_passed, Time}, _, State) ->+ lists:foreach(fun(Counter) ->+ {{Module, Key, _}, {_, PreviousCount}} = Counter,+ CurrentCount = ?COLLECTOR:get({Module, get_collector_key(Key)}),+ ets:insert(?MODULE, {{Module, Key, Time}, {PreviousCount, CurrentCount}})+ end, ets:tab2list(?MODULE)),+ {reply, ok, State}.++% PRIVATE API++get_average_counters() ->+ [{httpd, <<"previous_request_count">>}].++get_average(Module, Key, Options) ->+ Time = proplists:get_value("timeframe", Options, 1) * 60, % default to 1 minute, in seconds+ case ets:lookup(?MODULE, {Module, <<"previous_",Key/binary>>, Time}) of+ [] -> 0;+ [{_, {PreviousCounter, CurrentCounter}}] ->+ round((CurrentCounter - PreviousCounter) / Time)+ end.++get_collector_key(Key) ->+ <<"previous_", CollectorKey/binary>> = a2b(Key),+ b2a(CollectorKey).++init_counter(Time) ->+ Seconds = Time * 60,+ lists:map(+ fun({Module, Key}) ->+ start_timer(Seconds, fun() -> ?MODULE:time_passed(Seconds) end),+ ets:insert(?MODULE, {{Module, Key, Time}, {0, 0}})+ end, get_average_counters()).+++start_timer(Time, Fun) ->+ spawn(fun() -> timer(Time * 1000, Fun) end).++timer(Time, Fun) ->+ receive+ cancel -> void+ after+ Time -> Fun(),+ timer(Time, Fun)+ end.++% UTILS++integer_to_binary(Integer) ->+ list_to_binary(integer_to_list(Integer)).++b2a(Binary) when is_atom(Binary)->+ Binary;+b2a(Binary) ->+ list_to_atom(binary_to_list(Binary)).++a2b(Atom) when is_binary(Atom) ->+ Atom;+a2b(Atom) ->+ list_to_binary(atom_to_list(Atom)).++% Unused gen_server behaviour API functions that we need to declare.+ +%% @doc Unused+handle_cast(foo, State) ->+ {noreply, State}.++handle_info(_Info, State) ->+ {noreply, State}.++%% @doc Unused+terminate(_Reason, _State) -> ok.++%% @doc Unused+code_change(_OldVersion, State, _Extra) -> {ok, State}.++% TESTS++test_helper(Fun) ->+ catch ?MODULE:stop(),+ ?MODULE:start(),+ ?COLLECTOR:start(),++ Fun(),++ ?MODULE:stop(),+ ?COLLECTOR:stop().++should_return_value_from_collector_test() ->+ test_helper(fun() ->+ ?assertEqual(<<"0">>, ?MODULE:get({couch_db, open_databases}))+ end).++should_handle_multiple_key_value_pairs_test() ->+ test_helper(fun() ->+ ?COLLECTOR:increment({couch_db, open_databases}),+ ?assertEqual(<<"1">>, ?MODULE:get({couch_db, open_databases})),+ ?assertEqual(<<"0">>, ?MODULE:get({couch_db, request_count}))+ end).++should_return_the_average_over_the_last_minute_test() ->+ test_helper(fun() ->+ lists:map(fun(_) ->+ ?COLLECTOR:increment({httpd, request_count})+ end, lists:seq(1, 200)),++ ?MODULE:time_passed(60), % seconds+ ?assertEqual(<<"3">>, ?MODULE:get({httpd, average_request_count}))+ end).++should_return_the_average_over_the_last_five_minutes_test() ->+ test_helper(fun() ->+ lists:map(fun(_) ->+ ?COLLECTOR:increment({httpd, request_count})+ end, lists:seq(1, 2000)),++ ?MODULE:time_passed(300), % seconds+ Result = ?MODULE:get({httpd, average_request_count}, [{"timeframe", list_to_integer("5")}]),+ ?assertEqual(<<"7">>, Result)+ end).diff --git a/src/couchdb/couch_stats_collector.erl b/src/couchdb/couch_stats_collector.erlnew file mode 100644index 0000000..13fb168--- /dev/null+++ b/src/couchdb/couch_stats_collector.erl@@ -0,0 +1,155 @@+% 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.++-module(couch_stats_collector).++-define(TEST, true).+-ifdef(TEST).+ -include_lib("eunit/include/eunit.hrl").+-endif.++-behaviour(gen_server).++-export([init/1, handle_call/3, handle_cast/2, handle_info/2,+ terminate/2, code_change/3]).+++-export([start/0, stop/0, get/1, increment/1, decrement/1]).++-record(state, {}).+++% PUBLIC API++start() ->+ gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).+ +stop() ->+ gen_server:call(?MODULE, stop).++get(Key) ->+ gen_server:call(?MODULE, {get, Key}).++increment({Module, Key}) when is_integer(Key) ->+ gen_server:call(?MODULE, {increment, {Module, list_to_atom(integer_to_list(Key))}});+increment(Key) ->+ gen_server:call(?MODULE, {increment, Key}).++decrement(Key) ->+ gen_server:call(?MODULE, {decrement, Key}).++% GEN_SERVER+ +init(_) ->+ ets:new(?MODULE, [named_table, set, protected]),+ {ok, #state{}}.++handle_call({get, Key}, _, State) ->+ Result = case ets:lookup(?MODULE, Key) of+ [] -> 0;+ [{_,Result1}] -> Result1+ end,+ {reply, Result, State};+ +handle_call({increment, Key}, _, State) ->+ case catch ets:update_counter(?MODULE, Key, 1) of+ {'EXIT', {badarg, _}} -> ets:insert(?MODULE, {Key, 1});+ _ -> ok+ end,+ {reply, ok, State};+ +handle_call({decrement, Key}, _, State) ->+ case catch ets:update_counter(?MODULE, Key, -1) of+ {'EXIT', {badarg, _}} -> ets:insert(?MODULE, {Key, -1});+ _ -> ok+ end,+ {reply, ok, State};+ +% handle_call(reset, _, State) ->+% ets:insert(?MODULE, {Key, 0}),+% {reply, ok, 0};+ +handle_call(stop, _, State) ->+ {stop, normal, stopped, State}.+++% Unused gen_server behaviour API functions that we need to declare.+ +%% @doc Unused+handle_cast(foo, State) ->+ {noreply, State}.++handle_info(_Info, State) ->+ {noreply, State}.++%% @doc Unused+terminate(_Reason, _State) -> ok.++%% @doc Unused+code_change(_OldVersion, State, _Extra) -> {ok, State}.++% TESTS ++test_helper(Fun) ->+ catch ?MODULE:stop(),+ ?MODULE:start(),++ Fun(),++ ?MODULE:stop().++should_return_value_from_store_test() ->+ test_helper(fun() -> + ?assertEqual(0, ?MODULE:get({couch_db, open_databases}))+ end).++should_increment_value_test() ->+ test_helper(fun() ->+ ?assert(?MODULE:increment({couch_db, open_databases}) =:= ok),+ ?assertEqual(1, ?MODULE:get({couch_db, open_databases}))+ end).++should_decrement_value_test() ->+ test_helper(fun() ->+ ?assert(?MODULE:decrement({couch_db, open_databases}) =:= ok),+ ?assertEqual(-1, ?MODULE:get({couch_db, open_databases}))+ end).++should_increment_and_decrement_value_test() ->+ test_helper(fun() ->+ ?assert(?MODULE:increment({couch_db, open_databases}) =:= ok),+ ?assert(?MODULE:decrement({couch_db, open_databases}) =:= ok),+ ?assertEqual(0, ?MODULE:get({couch_db, open_databases}))+ end).++should_reset_counter_value_test() ->+ test_helper(fun() ->+ ?assert(?MODULE:increment({couch_db, open_databases}) =:= ok),+ ?MODULE:stop(),+ ?MODULE:start(),+ ?assertEqual(0, ?MODULE:get({couch_db, open_databases}))+ end).++should_handle_multiple_key_value_pairs_test() ->+ test_helper(fun() ->+ ?MODULE:increment({couch_db, open_databases}),+ ?assertEqual(1, ?MODULE:get({couch_db, open_databases})),+ ?assertEqual(0, ?MODULE:get({couch_db, request_count}))+ end).++should_restart_module_should_create_new_pid_test() ->+ test_helper(fun() ->+ OldPid = whereis(?MODULE),+ ?MODULE:stop(),+ ?MODULE:start(),+ ?assertNot(whereis(?MODULE) =:= OldPid)+ end).