Merge pull request #4033 from noahshaw11/implement-view_report-function

Implement index report functions
diff --git a/src/couch/src/couch_debug.erl b/src/couch/src/couch_debug.erl
index 9dc5053..93e11bf 100644
--- a/src/couch/src/couch_debug.erl
+++ b/src/couch/src/couch_debug.erl
@@ -47,7 +47,9 @@
 ]).
 
 -export([
-    print_table/2
+    print_table/2,
+    print_report/1,
+    print_report_with_info_width/2
 ]).
 
 -type throw(_Reason) :: no_return().
@@ -80,6 +82,8 @@
         print_linked_processes,
         memory_info,
         print_table,
+        print_report,
+        print_report_with_info_width,
         restart,
         restart_busy
     ].
@@ -287,15 +291,16 @@
 
         Print cluster of linked processes. This function receives the
         initial Pid to start from. The function doesn't recurse to pids
-        older than initial one. The output would look like similar to:
-        ```
-couch_debug:print_linked_processes(whereis(couch_index_server)).
-name                                         | reductions | message_queue_len |  memory
-couch_index_server[<0.288.0>]                |   478240   |         0         |  109696
-  couch_index:init/1[<0.3520.22>]            |    4899    |         0         |  109456
-    couch_file:init/1[<0.886.22>]            |   11973    |         0         |  67984
-      couch_index:init/1[<0.3520.22>]        |    4899    |         0         |  109456
-        ```
+        older than initial one.
+
+        The output will look like similar to:
+
+            couch_debug:print_linked_processes(whereis(couch_index_server)).
+            name                                         | reductions | message_queue_len |  memory
+            couch_index_server[<0.288.0>]                |   478240   |         0         |  109696
+            couch_index:init/1[<0.3520.22>]              |    4899    |         0         |  109456
+                couch_file:init/1[<0.886.22>]            |   11973    |         0         |  67984
+                couch_index:init/1[<0.3520.22>]          |    4899    |         0         |  109456
 
         ---
     ", []);
@@ -328,6 +333,41 @@
 
         ---
     ", []);
+help(print_report) ->
+    io:format("
+        print_report(Report)
+        --------------------------------
+
+        Print a report in table form.
+          - Report: List of {InfoKey, InfoVal} where each InfoKey is unique
+          (unlike print_table/2).
+
+        The output will look similar to:
+
+            |info           |                                                                                               value
+            |  btree_size   |                                                                                                  51
+            |  def          |                                                                     function(doc){emit(doc.id, 1);}
+            |  id_num       |                                                                                                   0
+            |  options      |
+            |  purge_seq    |                                                                                                   0
+            |  reduce_funs  |
+            |  update_seq   |                                                                                                   3
+
+        ---
+    ", []);
+help(print_report_with_info_width) ->
+    io:format("
+        print_report_with_info_width(Report, Width)
+        --------------------------------
+
+        Print a report in table form. Same as print_report/1 but with a custom
+        width for the InfoKey column.
+          - Report: List of {InfoKey, InfoVal} where each InfoKey is unique
+            (unlike print_table/2).
+          - Width: Width of InfoKey column in TableSpec. Default is 50.
+
+        ---
+    ", []);
 help(print_tree) ->
     io:format("
         print_tree(Tree, TableSpec)
@@ -891,6 +931,26 @@
     ),
     ok.
 
+print_report(Report) ->
+    print_report_with_info_width(Report, 50).
+
+print_report_with_info_width(Report, Width) ->
+    TableSpec = [
+        {Width, left, info},
+        {100, right, value}
+    ],
+    io:format("~s~n", [format(TableSpec)]),
+    lists:map(
+        fun({InfoKey, Value}) ->
+            TableSpec1 = [
+                {Width, left, info},
+                {100, right, InfoKey}
+            ],
+            io:format("~s~n", [table_row(InfoKey, 2, [{InfoKey, Value}], TableSpec1)])
+        end,
+        Report
+    ).
+
 print_tree(Tree, TableSpec) ->
     io:format("~s~n", [format(TableSpec)]),
     map_tree(Tree, fun(_, {Id, Props}, Pos) ->
diff --git a/src/couch_mrview/src/couch_mrview_debug.erl b/src/couch_mrview/src/couch_mrview_debug.erl
index a4203d4..66f5d79 100644
--- a/src/couch_mrview/src/couch_mrview_debug.erl
+++ b/src/couch_mrview/src/couch_mrview_debug.erl
@@ -18,14 +18,30 @@
 ]).
 
 -export([
-    view_signature/2
+    view_signature/2,
+    index_state/1,
+    view_state/1,
+    view_state/2,
+    index_view_state/1,
+    index_view_state/2,
+    index_report/1,
+    view_report/1,
+    view_report/2,
+    index_view_report/1,
+    index_view_report/2
 ]).
 
 -include_lib("couch_mrview/include/couch_mrview.hrl").
 
 help() ->
     [
-        view_signature
+        view_signature,
+        index_state,
+        view_state,
+        index_view_state,
+        index_report,
+        view_report,
+        index_view_report
     ].
 
 -spec help(Function :: atom()) -> ok.
@@ -34,7 +50,223 @@
     io:format("
     view_signature(ShardName, DDocName)
     --------------
+
     Returns a view signature for given ddoc for a given (non clustered) database.
+
+    ---
+    ", []);
+help(index_state) ->
+    io:format("
+    index_state(Pid)
+    --------------
+
+    Pid: Pid of couch_index:init/1, specifically an mrview index.
+
+    Returns a state map for an index that includes the following fields:
+        - collator_versions
+        - compact_running
+        - db_name
+        - idx_name
+        - language
+        - pending_updates
+        - purge_seq
+        - signature
+        - sizes
+        - update_options
+        - update_seq
+        - updater_running
+        - view_file_path
+        - waiting_clients
+        - waiting_commit
+
+    ---
+    ", []);
+help(view_state) ->
+    io:format("
+    view_state(PidOrIdxState)
+    view_state(Pid, ViewName)
+    view_state(IdxState, ViewName)
+    --------------
+
+    PidOrIdxState: Pid of couch_index:init/1, specifically an mrview index, or the state
+    of an mrview index.
+    Pid: Pid of couch_index:init/1, specifically an mrview index.
+    IdxState: State of an mrview index (#mrst{} record).
+    ViewName: Name of the view to be queried or undefined.
+
+    Returns a state map for a ViewName if specified or all views if not that includes the
+    following fields:
+        - btree_size
+        - def
+        - id_num
+        - options
+        - purge_seq
+        - reduce_funs
+        - update_seq
+
+    ---
+    ", []);
+help(index_view_state) ->
+    io:format("
+    index_view_state(Pid)
+    index_view_state(Pid, ViewName)
+    --------------
+
+    Pid: Pid of couch_index:init/1, specifically an mrview index.
+    ViewName: Name of the view to be queried or undefined.
+
+    Returns a state map that includes the index state returned by index_state/1 and the view
+    state returned by view_state/2. Like view_state/2, a ViewName can be specified or not.
+
+    ---
+    ", []);
+help(index_report) ->
+    io:format("
+    index_report(Pid)
+    --------------
+
+    Pid: Pid of couch_index:init/1, specifically an mrview index.
+
+    Prints a report for the index state of an mrview index that includes the following fields:
+        - signature
+        - db_name
+        - idx_name
+        - update_seq
+        - purge_seq
+        - view_file_path
+        - pending_updates
+
+    The output will look similar to:
+
+        |info                 |                                                                                               value
+        |  collator_versions  |                                                                                             153.112
+        |  compact_running    |                                                                                               false
+        |  db_name            |                                                            shards/00000000-ffffffff/dbv1.1658179540
+        |  idx_name           |                                                                                    _design/dbv1ddoc
+        |  language           |                                                                                          javascript
+        |  pending_updates    |                                                                                                   0
+        |  purge_seq          |                                                                                                   0
+        |  signature          |                                                                    a967fb72089e71e870f790f32bcc6a55
+        |  sizes              |                                                          {[{file,4264},{active,163},{external,51}]}
+        |  update_options     |
+        |  update_seq         |                                                                                                   3
+        |  updater_running    |                                                                                               false
+        |  view_file_path     |       .shards/00000000-ffffffff/dbv1.1658179540_design/mrview/a967fb72089e71e870f790f32bcc6a55.view
+        |  waiting_clients    |                                                                                                   0
+        |  waiting_commit     |                                                                                               false
+
+    ---
+    ", []);
+help(view_report) ->
+    io:format("
+    view_report(PidOrIdxState)
+    view_report(Pid, ViewName)
+    view_report(IdxState, ViewName)
+    --------------
+
+    PidOrIdxState: Pid of couch_index:init/1, specifically an mrview index, or the state
+    of an mrview index.
+    Pid: Pid of couch_index:init/1, specifically an mrview index.
+    IdxState: State of an mrview index (#mrst{} record).
+    ViewName: Name of the view to be queried or undefined.
+
+    Prints a report for a ViewName if specified or all views if not that includes the following
+    fields:
+        - id_num
+        - update_seq
+        - purge_seq
+        - reduce_funs
+        - def
+        - btree_size
+        - options
+
+    The output will look similar to:
+
+        dbv1view
+        |info           |                                                                                               value
+        |  btree_size   |                                                                                                  51
+        |  def          |                                                                     function(doc){emit(doc.id, 1);}
+        |  id_num       |                                                                                                   0
+        |  options      |
+        |  purge_seq    |                                                                                                   0
+        |  reduce_funs  |
+        |  update_seq   |                                                                                                   3
+        dbv2view
+        |info           |                                                                                               value
+        |  btree_size   |                                                                                                  50
+        |  def          |                                                                     function(doc){emit(doc.id, 2);}
+        |  id_num       |                                                                                                   1
+        |  options      |
+        |  purge_seq    |                                                                                                   0
+        |  reduce_funs  |                                                                                                _sum
+        |  update_seq   |                                                                                                   3
+
+    ---
+    ", []);
+help(index_view_report) ->
+    io:format("
+    index_view_report(Pid)
+    index_view_report(Pid, ViewName)
+    --------------
+
+    Pid: Pid of couch_index:init/1, specifically an mrview index.
+    ViewName: Name of the view to be queried or undefined.
+
+    Prints a report for the index state and views of an mrview index. The report includes the following
+    fields for an index state:
+        - signature
+        - db_name
+        - idx_name
+        - update_seq
+        - purge_seq
+        - view_file_path
+        - pending_updates
+    The report also includes the following fields for a ViewName if specified or all views if not:
+        - id_num
+        - update_seq
+        - purge_seq
+        - reduce_funs
+        - def
+        - btree_size
+        - options
+
+    The output will look similar to:
+
+        |info                 |                                                                                               value
+        |  collator_versions  |                                                                                             153.112
+        |  compact_running    |                                                                                               false
+        |  db_name            |                                                            shards/00000000-ffffffff/dbv1.1658179540
+        |  idx_name           |                                                                                    _design/dbv1ddoc
+        |  language           |                                                                                          javascript
+        |  pending_updates    |                                                                                                   0
+        |  purge_seq          |                                                                                                   0
+        |  signature          |                                                                    a967fb72089e71e870f790f32bcc6a55
+        |  sizes              |                                                          {[{file,4264},{active,163},{external,51}]}
+        |  update_options     |
+        |  update_seq         |                                                                                                   3
+        |  updater_running    |                                                                                               false
+        |  view_file_path     |       .shards/00000000-ffffffff/dbv1.1658179540_design/mrview/a967fb72089e71e870f790f32bcc6a55.view
+        |  waiting_clients    |                                                                                                   0
+        |  waiting_commit     |                                                                                               false
+        dbv1view
+        |info           |                                                                                               value
+        |  btree_size   |                                                                                                  51
+        |  def          |                                                                     function(doc){emit(doc.id, 1);}
+        |  id_num       |                                                                                                   0
+        |  options      |
+        |  purge_seq    |                                                                                                   0
+        |  reduce_funs  |
+        |  update_seq   |                                                                                                   3
+        dbv2view
+        |info           |                                                                                               value
+        |  btree_size   |                                                                                                  50
+        |  def          |                                                                     function(doc){emit(doc.id, 2);}
+        |  id_num       |                                                                                                   1
+        |  options      |
+        |  purge_seq    |                                                                                                   0
+        |  reduce_funs  |                                                                                                _sum
+        |  update_seq   |                                                                                                   3
+
     ---
     ", []);
 help(Unknown) ->
@@ -48,3 +280,158 @@
     {ok, DDoc} = couch_db:open_doc_int(Db, <<"_design/", DDocName/binary>>, []),
     {ok, IdxState} = couch_mrview_util:ddoc_to_mrst(DDocName, DDoc),
     couch_util:to_hex(IdxState#mrst.sig).
+
+index_state(Pid) when is_pid(Pid) ->
+    {ok, IdxState} = couch_index:get_state(Pid, 0),
+    case IdxState of
+        #mrst{} ->
+            {ok, Info} = couch_index:get_info(Pid),
+            Sig = IdxState#mrst.sig,
+            DbName = IdxState#mrst.db_name,
+            State =
+                Info ++
+                    [
+                        {signature, Sig},
+                        {db_name, DbName},
+                        {idx_name, IdxState#mrst.idx_name},
+                        {view_file_path, couch_mrview_util:index_file(DbName, Sig)}
+                    ],
+            {ok, maps:from_list(State)};
+        _ ->
+            {error, not_mrview_index}
+    end.
+
+view_state(PidOrIdxState) ->
+    view_state(PidOrIdxState, undefined).
+
+view_state(Pid, ViewName) when is_pid(Pid) ->
+    {ok, IdxState} = couch_index:get_state(Pid, 0),
+    view_state(IdxState, ViewName);
+view_state(IdxState, ViewName) ->
+    case IdxState of
+        #mrst{} ->
+            MrViews = lists:foldl(
+                fun(MrView, Acc) ->
+                    {Name, ReduceFuns} =
+                        case MrView#mrview.reduce_funs of
+                            [] ->
+                                {hd(MrView#mrview.map_names), []};
+                            _ ->
+                                % reduce_funs contains tuples of {Name, ReduceFuns}
+                                hd(MrView#mrview.reduce_funs)
+                        end,
+                    View = #{
+                        id_num => MrView#mrview.id_num,
+                        update_seq => MrView#mrview.update_seq,
+                        purge_seq => MrView#mrview.purge_seq,
+                        reduce_funs => ReduceFuns,
+                        def => MrView#mrview.def,
+                        btree_size => couch_btree:size(MrView#mrview.btree),
+                        options => MrView#mrview.options
+                    },
+                    maps:put(Name, View, Acc)
+                end,
+                #{},
+                IdxState#mrst.views
+            ),
+            case ViewName of
+                undefined ->
+                    {ok, MrViews};
+                _ ->
+                    case maps:get(ViewName, MrViews) of
+                        {badkey, Key} ->
+                            io:format("No view named ~p was found.", [Key]),
+                            {error, {badkey, Key}};
+                        Value ->
+                            {ok, #{ViewName => Value}}
+                    end
+            end;
+        _ ->
+            {error, not_mrview_index}
+    end.
+
+index_view_state(Pid) when is_pid(Pid) ->
+    index_view_state(Pid, undefined).
+
+index_view_state(Pid, ViewName) when is_pid(Pid) ->
+    {ok, IdxState} = index_state(Pid),
+    {ok, ViewState} = view_state(Pid, ViewName),
+    {ok, maps:put(views, ViewState, IdxState)}.
+
+index_report(Pid) when is_pid(Pid) ->
+    case index_state(Pid) of
+        {ok, IdxState} ->
+            Sig = maps:get(signature, IdxState),
+            IdxState2 = maps:put(signature, couch_util:to_hex(Sig), IdxState),
+            % Convert collator versions to strings to print pretty
+            IdxState3 = convert_collator_versions_to_strings(IdxState2),
+            IdxState4 = maps:update_with(view_file_path, fun format_view_path/1, IdxState3),
+            couch_debug:print_report_with_info_width(maps:to_list(IdxState4), 21);
+        Error ->
+            Error
+    end.
+
+view_report(PidOrIdxState) ->
+    view_report(PidOrIdxState, undefined).
+
+view_report(Pid, ViewName) when is_pid(Pid) ->
+    {ok, IdxState} = couch_index:get_state(Pid, 0),
+    view_report(IdxState, ViewName);
+view_report(IdxState, ViewName) ->
+    case view_state(IdxState, ViewName) of
+        {ok, ViewState} ->
+            lists:foreach(
+                fun({Name, Info}) ->
+                    io:format("~s~n", [binary_to_list(Name)]),
+                    couch_debug:print_report_with_info_width(maps:to_list(Info), 15)
+                end,
+                maps:to_list(ViewState)
+            );
+        Error ->
+            Error
+    end.
+
+index_view_report(Pid) when is_pid(Pid) ->
+    index_view_report(Pid, undefined).
+index_view_report(Pid, ViewName) when is_pid(Pid) ->
+    case index_view_state(Pid) of
+        {ok, State} ->
+            AllViews = maps:get(views, State),
+            Views =
+                case ViewName of
+                    undefined ->
+                        AllViews;
+                    _ ->
+                        #{ViewName => maps:get(ViewName, AllViews)}
+                end,
+            Sig = maps:get(signature, State),
+            State2 = maps:put(signature, couch_util:to_hex(Sig), State),
+            % Convert collator versions to strings to print pretty
+            State3 = convert_collator_versions_to_strings(State2),
+            State4 = maps:update_with(view_file_path, fun format_view_path/1, State3),
+            couch_debug:print_report_with_info_width(
+                maps:to_list(maps:without([views], State4)), 21
+            ),
+            lists:foreach(
+                fun({Name, Info}) ->
+                    io:format("~s~n", [binary_to_list(Name)]),
+                    couch_debug:print_report_with_info_width(maps:to_list(Info), 15)
+                end,
+                maps:to_list(Views)
+            );
+        Error ->
+            Error
+    end.
+
+convert_collator_versions_to_strings(State) ->
+    CollatorVersions = lists:map(
+        fun(Version) ->
+            binary_to_list(Version)
+        end,
+        maps:get(collator_versions, State)
+    ),
+    maps:put(collator_versions, CollatorVersions, State).
+
+format_view_path(ViewFilePath) ->
+    BaseDir = config:get("couchdb", "view_index_dir"),
+    lists:flatten(string:replace(ViewFilePath, BaseDir ++ "/", "")).