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},


Reply via email to