| #!/usr/bin/env escript |
| %% -*- erlang -*- |
| |
| % 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. |
| |
| -record(user_ctx, { |
| name = null, |
| roles = [], |
| handler |
| }). |
| |
| test_db_name() -> |
| <<"couch_test_compaction_daemon">>. |
| |
| main(_) -> |
| test_util:init_code_path(), |
| |
| etap:plan(10), |
| case (catch test()) of |
| ok -> |
| etap:end_tests(); |
| Other -> |
| etap:diag(io_lib:format("Test died abnormally: ~p", [Other])), |
| etap:bail(Other) |
| end, |
| ok. |
| |
| test() -> |
| couch_server_sup:start_link(test_util:config_files()), |
| timer:sleep(1000), |
| put(addr, couch_config:get("httpd", "bind_address", "127.0.0.1")), |
| put(port, integer_to_list(mochiweb_socket_server:get(couch_httpd, port))), |
| |
| disable_compact_daemon(), |
| |
| delete_db(), |
| {ok, Db} = create_db(), |
| |
| add_design_doc(Db), |
| couch_db:close(Db), |
| populate(70, 70, 200 * 1024), |
| |
| {_, DbFileSize} = get_db_frag(), |
| {_, ViewFileSize} = get_view_frag(), |
| |
| % enable automatic compaction |
| ok = couch_config:set("compaction_daemon", "check_interval", "3", false), |
| ok = couch_config:set("compaction_daemon", "min_file_size", "100000", false), |
| ok = couch_config:set( |
| "compactions", |
| binary_to_list(test_db_name()), |
| "[{db_fragmentation, \"70%\"}, {view_fragmentation, \"70%\"}]", |
| false), |
| |
| ok = timer:sleep(4000), % something >= check_interval |
| wait_compaction_finished(), |
| |
| {DbFrag2, DbFileSize2} = get_db_frag(), |
| {ViewFrag2, ViewFileSize2} = get_view_frag(), |
| |
| etap:is(true, (DbFrag2 < 70), "Database fragmentation is < 70% after compaction"), |
| etap:is(true, (ViewFrag2 < 70), "View fragmentation is < 70% after compaction"), |
| etap:is(true, (DbFileSize2 < DbFileSize), "Database file size decreased"), |
| etap:is(true, (ViewFileSize2 < ViewFileSize), "View file size decreased"), |
| |
| disable_compact_daemon(), |
| ok = timer:sleep(6000), % 2 times check_interval |
| etap:is(couch_db:is_idle(Db), true, "Database is idle"), |
| populate(70, 70, 200 * 1024), |
| {_, DbFileSize3} = get_db_frag(), |
| {_, ViewFileSize3} = get_view_frag(), |
| |
| % enable automatic compaction |
| ok = couch_config:set( |
| "compactions", |
| "_default", |
| "[{db_fragmentation, \"70%\"}, {view_fragmentation, \"70%\"}]", |
| false), |
| |
| ok = timer:sleep(4000), % something >= check_interval |
| wait_compaction_finished(), |
| |
| {DbFrag4, DbFileSize4} = get_db_frag(), |
| {ViewFrag4, ViewFileSize4} = get_view_frag(), |
| |
| etap:is(true, (DbFrag4 < 70), "Database fragmentation is < 70% after compaction"), |
| etap:is(true, (ViewFrag4 < 70), "View fragmentation is < 70% after compaction"), |
| etap:is(true, (DbFileSize4 < DbFileSize3), "Database file size decreased again"), |
| etap:is(true, (ViewFileSize4 < ViewFileSize3), "View file size decreased again"), |
| |
| ok = timer:sleep(6000), % 2 times check_interval |
| etap:is(couch_db:is_idle(Db), true, "Database is idle"), |
| |
| delete_db(), |
| couch_server_sup:stop(), |
| ok. |
| |
| disable_compact_daemon() -> |
| Configs = couch_config:get("compactions"), |
| lists:foreach( |
| fun({DbName, _}) -> |
| ok = couch_config:delete("compactions", DbName, false) |
| end, |
| Configs). |
| |
| admin_user_ctx() -> |
| {user_ctx, #user_ctx{roles = [<<"_admin">>]}}. |
| |
| create_db() -> |
| {ok, _} = couch_db:create(test_db_name(), [admin_user_ctx()]). |
| |
| delete_db() -> |
| couch_server:delete(test_db_name(), [admin_user_ctx()]). |
| |
| add_design_doc(Db) -> |
| DDoc = couch_doc:from_json_obj({[ |
| {<<"_id">>, <<"_design/foo">>}, |
| {<<"language">>, <<"javascript">>}, |
| {<<"views">>, {[ |
| {<<"foo">>, {[ |
| {<<"map">>, <<"function(doc) { emit(doc._id, doc); }">>} |
| ]}}, |
| {<<"foo2">>, {[ |
| {<<"map">>, <<"function(doc) { emit(doc._id, doc); }">>} |
| ]}}, |
| {<<"foo3">>, {[ |
| {<<"map">>, <<"function(doc) { emit(doc._id, doc); }">>} |
| ]}} |
| ]}} |
| ]}), |
| {ok, _} = couch_db:update_docs(Db, [DDoc]), |
| {ok, _} = couch_db:ensure_full_commit(Db), |
| ok. |
| |
| populate(DbFrag, ViewFrag, MinFileSize) -> |
| {CurDbFrag, DbFileSize} = get_db_frag(), |
| {CurViewFrag, ViewFileSize} = get_view_frag(), |
| populate( |
| DbFrag, ViewFrag, MinFileSize, CurDbFrag, CurViewFrag, |
| lists:min([DbFileSize, ViewFileSize])). |
| |
| populate(DbFrag, ViewFrag, MinFileSize, CurDbFrag, CurViewFrag, FileSize) |
| when CurDbFrag >= DbFrag, CurViewFrag >= ViewFrag, FileSize >= MinFileSize -> |
| ok; |
| populate(DbFrag, ViewFrag, MinFileSize, _, _, _) -> |
| update(), |
| {CurDbFrag, DbFileSize} = get_db_frag(), |
| {CurViewFrag, ViewFileSize} = get_view_frag(), |
| populate( |
| DbFrag, ViewFrag, MinFileSize, CurDbFrag, CurViewFrag, |
| lists:min([DbFileSize, ViewFileSize])). |
| |
| update() -> |
| {ok, Db} = couch_db:open_int(test_db_name(), []), |
| lists:foreach(fun(_) -> |
| Doc = couch_doc:from_json_obj({[{<<"_id">>, couch_uuids:new()}]}), |
| {ok, _} = couch_db:update_docs(Db, [Doc]), |
| query_view() |
| end, lists:seq(1, 100)), |
| couch_db:close(Db). |
| |
| db_url() -> |
| "http://" ++ get(addr) ++ ":" ++ get(port) ++ "/" ++ |
| binary_to_list(test_db_name()). |
| |
| query_view() -> |
| {ok, Code, _Headers, _Body} = test_util:request( |
| db_url() ++ "/_design/foo/_view/foo", [], get), |
| case Code of |
| 200 -> |
| ok; |
| _ -> |
| etap:bail("error querying view") |
| end. |
| |
| get_db_frag() -> |
| {ok, Db} = couch_db:open_int(test_db_name(), []), |
| {ok, Info} = couch_db:get_db_info(Db), |
| couch_db:close(Db), |
| FileSize = couch_util:get_value(disk_size, Info), |
| DataSize = couch_util:get_value(data_size, Info), |
| {round((FileSize - DataSize) / FileSize * 100), FileSize}. |
| |
| get_view_frag() -> |
| {ok, Db} = couch_db:open_int(test_db_name(), []), |
| {ok, Info} = couch_mrview:get_info(Db, <<"_design/foo">>), |
| couch_db:close(Db), |
| FileSize = couch_util:get_value(disk_size, Info), |
| DataSize = couch_util:get_value(data_size, Info), |
| {round((FileSize - DataSize) / FileSize * 100), FileSize}. |
| |
| |
| wait_compaction_finished() -> |
| Parent = self(), |
| Loop = spawn_link(fun() -> wait_loop(Parent) end), |
| receive |
| {done, Loop} -> |
| etap:diag("Database and view compaction have finished") |
| after 60000 -> |
| etap:bail("Compaction not triggered") |
| end. |
| |
| wait_loop(Parent) -> |
| {ok, Db} = couch_db:open_int(test_db_name(), []), |
| {ok, DbInfo} = couch_db:get_db_info(Db), |
| {ok, ViewInfo} = couch_mrview:get_info(Db, <<"_design/foo">>), |
| couch_db:close(Db), |
| case (couch_util:get_value(compact_running, ViewInfo) =:= true) orelse |
| (couch_util:get_value(compact_running, DbInfo) =:= true) of |
| false -> |
| Parent ! {done, self()}; |
| true -> |
| ok = timer:sleep(500), |
| wait_loop(Parent) |
| end. |