%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2008-2011 The University of Melbourne.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%---------------------------------------------------------------------------%
%
% File: report.m.
% Author: pbone.
%
% This module defines deep profiling reports. Each report is the result of
% querying a Deep.data file. Different queries result in different types
% of reports. Reports can be converted into HTML by display_report.m and
% html_format.m, but some tools may use the data structures constructed here
% directly.
%
%---------------------------------------------------------------------------%

:- module report.
:- interface.

:- import_module coverage.
:- import_module mdbcomp.
:- import_module mdbcomp.goal_path.
:- import_module mdbcomp.program_representation.
:- import_module measurement_units.
:- import_module profile.
% TODO: The data structures in query should be moved into a different module,
% then this module may depend on those data structures but not the rest of
% query.
:- import_module query.
:- import_module var_use_analysis.

:- import_module list.
:- import_module map.
:- import_module maybe.
:- import_module set.
:- import_module unit.

%---------------------------------------------------------------------------%

:- type deep_report
    --->    report_message(
                message_report
            )
    ;       report_menu(
                maybe_error(menu_report)
            )
    ;       report_clique(
                maybe_error(clique_report)
            )
    ;       report_clique_recursion_costs(
                maybe_error(clique_recursion_report)
            )
    ;       report_recursion_types_frequency(
                maybe_error(recursion_types_frequency_report)
            )
    ;       report_program_modules(
                maybe_error(program_modules_report)
            )
    ;       report_module(
                maybe_error(module_report)
            )
    ;       report_module_getter_setters(
                maybe_error(module_getter_setters_report)
            )
    ;       report_module_rep(
                maybe_error(module_rep_report)
            )
    ;       report_top_procs(
                maybe_error(top_procs_report)
            )
    ;       report_proc(
                maybe_error(proc_report)
            )
    ;       report_procrep_coverage(
                maybe_error(procrep_coverage_info)
            )
    ;       report_proc_callers(
                maybe_error(proc_callers_report)
            )
    ;       report_proc_static_dump(
                maybe_error(proc_static_dump_info)
            )
    ;       report_proc_dynamic_dump(
                maybe_error(proc_dynamic_dump_info)
            )
    ;       report_call_site_static_dump(
                maybe_error(call_site_static_dump_info)
            )
    ;       report_call_site_dynamic_dump(
                maybe_error(call_site_dynamic_dump_info)
            )
    ;       report_clique_dump(
                maybe_error(clique_dump_info)
            )
    ;       report_call_site_dynamic_var_use(
                maybe_error(call_site_dynamic_var_use_info)
            ).

:- type message_report
    --->    message_report(
                % A simple message, this may be used in response to the
                % 'shutdown' and similar queries.

                string
            ).

:- type menu_report
    --->    menu_report(
                % Statistics about the deep profiling data. Gives simple
                % information about the size of the program and its runtime.
                % These statistics are displayed on the menu of the mdprof_cgi
                % program.

                menu_program_name           :: string,
                menu_quanta_per_sec         :: int,
                menu_user_quanta            :: int,
                menu_inst_quanta            :: int,
                menu_num_callseqs           :: int,
                menu_num_csd                :: int,
                menu_num_css                :: int,
                menu_num_pd                 :: int,
                menu_num_ps                 :: int,
                menu_num_clique             :: int
            ).

:- type clique_report
    --->    clique_report(
                % The clique the report is for.
                cr_clique_ptr               :: clique_ptr,

                % The ancestor call sites of the clique,
                % - from the innermost ancestor call site (in the parent)
                % - to the outermost ancestor call site (in main/2).
                cr_ancestor_call_sites      :: list(
                                                perf_row_data(ancestor_desc)),

                % Reports for every procedure in the clique. The first
                % procedure in the list is the entry procedure (if the parent
                % clique had a valid CSD.) and that CSD was found in the
                % clique. TODO: we might want to express this within this
                % type.
                cr_clique_procs             :: list(clique_proc_report)
            ).

:- type clique_proc_report
    --->    clique_proc_report(
                % Summary information for the cost of this procedure in this
                % clique.
                cpr_proc_summary            :: perf_row_data(proc_desc),

                % A clique may contain more than one proc_dynamic for the same
                % procedure in one rare circumstance. Consider a clique of
                % two procedures, let's call them p and q. The entry procedure
                % is p. When p calls q and q then calls p, the runtime
                % discovers they are in the same clique. However, after all
                % those mutually recursive calls unwind and we get back to the
                % original call to p, p can call q again. If it does so through
                % a call site through which it has not called q before, then
                % the call port code in q will allocate a new proc_dynamic,
                % since it will not find an existing one it can reuse;
                % q's proc_static won't point to an active proc_dynamic, and
                % the call site will not supply one either.
                %
                % We could modify the code we invoke at call ports to keep a
                % list of the proc_dynamics of the procedures known to be in
                % the current clique, and to reuse the proc_dynamic of the
                % callee in these cases, but that would significantly increase
                % the call port overhead. Instead, we let the callee allocate a
                % second (and maybe third etc) proc_dynamic for a procedure,
                % and let the deep profiler sort it out.
                %
                % Note that the entry procedure of a clique will always have
                % exactly one proc_dynamic.
                cpr_first_proc_dynamic      :: clique_proc_dynamic_report,
                cpr_other_proc_dynamics     :: list(clique_proc_dynamic_report)
            ).

:- type clique_proc_dynamic_report
    --->    clique_proc_dynamic_report(
                % Summary information for the cost of this proc _dynamic
                % in this clique.
                cpdr_proc_summary           :: perf_row_data(proc_desc),

                % Information about the costs of the call sites in this
                % procedure in this clique.
                cpdr_call_sites             :: list(clique_call_site_report)
            ).

:- type clique_call_site_report
    --->    clique_call_site_report(
                % The id of the call site, and the summary performance for
                % that call site.
                ccsr_call_site_summary      :: perf_row_data(call_site_desc),

                ccsr_kind_and_callee        :: call_site_kind_and_callee(
                                                proc_desc),

                % All the callees and their associated performance information.
                % If ccsr_kind_and_callee indicates a normal call, the list
                % will have either zero entries (no calls through this site)
                % or one entry (some calls through this site). If this is
                % some other kind of call site, the list may have any number
                % of entries.
                ccsr_callee_perfs           :: list(perf_row_data(clique_desc))
            ).

:- type clique_recursion_report
    --->    clique_recursion_report(
                % The clique the report is for.
                crr_clique_ptr              :: clique_ptr,

                % The type of recursion in this clique.
                crr_recursion_type          :: recursion_type,

                % The number of procedures in the clique.
                crr_num_procs               :: int
            ).

:- type recursion_type
    --->    rt_not_recursive
    ;       rt_single(
                rts_base                    :: recursion_level_report,
                rts_recursive               :: recursion_level_report,
                rts_avg_max_depth           :: float,

                % These costs are per-call.
                rts_avg_rec_cost            :: float,

                % The cost at any level is Cost = func(Level).
                rts_any_rec_cost            :: (func(int) = float)
            )
    ;       rt_divide_and_conquer(
                rtdsc_base                  :: recursion_level_report,
                rtdsc_recursive             :: recursion_level_report
            )
    ;       rt_mutual_recursion(
                rtml_num_procs              :: int
            )
    ;       rt_other(
                rto_all_levels              :: list(recursion_level_report)
            )
    ;       rt_errors(
                rte_errors                  :: list(string)
            ).

    % Recursion types for which call costs at any level can be calculated.
    %
:- inst recursion_type_known_costs for recursion_type/0
    --->    rt_not_recursive
    ;       rt_single(ground, ground, ground, ground, ground).

    % Recursion types for which call costs at any level can not be calculated.
    %
:- inst recursion_type_unknown_costs for recursion_type/0
    --->    rt_divide_and_conquer(ground, ground)
    ;       rt_mutual_recursion(ground)
    ;       rt_other(ground)
    ;       rt_errors(ground).

:- type recursion_level_report
    --->    recursion_level_report(
                rlr_level                       :: int,
                rlr_calls                       :: int,
                rlr_prob                        :: probability,
                rlr_non_rec_calls_cost          :: float,
                rlr_rec_calls_ex_chld_cost      :: float
            ).

:- type recursion_types_frequency_report
    --->    recursion_types_frequency_report(
                rtfr_histogram                  :: recursion_type_histogram
            ).

:- type recursion_type_histogram ==
    map(recursion_type_simple, recursion_type_freq_data).

:- type recursion_type_simple
    --->    rts_not_recursive
    ;       rts_single
    ;       rts_divide_and_conquer
    ;       rts_mutual_recursion(
                rtsmr_num_procs                 :: int
            )
    ;       rts_other(
                rtso_levels                     :: set(int)
            )
    ;       rts_error(
                rtse_error                      :: string
            )
    ;       rts_total_error_instances.

:- type recursion_type_freq_data
    --->    recursion_type_freq_data(
                rtfd_freq                       :: int,
                rtfd_percent                    :: percent,
                rtfd_maybe_summary              :: maybe(perf_row_data(unit)),
                rtfd_entry_procs                :: recursion_type_proc_map
            ).

:- type recursion_type_proc_map ==
    map(proc_static_ptr, recursion_type_proc_freq_data).

:- type recursion_type_proc_freq_data
    --->    recursion_type_proc_freq_data(
                rtpfd_freq                      :: int,
                rtpfd_percent                   :: percent,
                rtpfd_summary                   :: perf_row_data(proc_desc)
            ).

:- type program_modules_report
    --->    program_modules_report(
                % Summary information about all the modules of the program.
                program_modules             :: list(
                                                perf_row_data(module_active))
            ).

:- type module_report
    --->    module_report(
                % Summary information about all the procedures in one module
                % of the program.
                mr_module_name              :: string,
                mr_have_module_rep          :: maybe_have_module_rep,
                mr_procs                    :: list(perf_row_data(proc_active))
            ).

:- type maybe_have_module_rep
    --->    do_not_have_module_rep
    ;       have_module_rep.

:- type data_struct_name
    --->    data_struct_name(string).

:- type field_name
    --->    field_name(string).

:- type getter_or_setter
    --->    getter
    ;       setter.

:- type gs_field_info(I, S)
    --->    gs_field_both(
                gsf_both_getter             :: I,
                gsf_both_setter             :: I,
                gsf_both_summary            :: S
            )
    ;       gs_field_getter(
                gsf_getter                  :: I
            )
    ;       gs_field_setter(
                gsf_setter                  :: I
            ).

:- type gs_field_map(T) == map(field_name, T).
:- type gs_ds_map(T)    == map(data_struct_name, gs_field_map(T)).

:- type gs_field_info   == gs_field_info(
                                perf_row_data(proc_desc),
                                perf_row_data(unit)
                           ).
:- type gs_field_map    == gs_field_map(gs_field_info).
:- type gs_ds_map       == gs_ds_map(gs_field_info).

:- type module_getter_setters_report
    --->    module_getter_setters_report(
                % Summary information about all the getter/setter procedures
                % in one module of the program.
                mgsr_module_name            :: string,
                mgsr_procs                  :: gs_ds_map
            ).

:- type module_rep_report
    --->    module_rep_report(
                % A representation of one module of the program.
                mrr_module_name             :: string,
                mrr_report                  :: string
            ).

:- type top_procs_report
    --->    top_procs_report(
                % Information about the most expensive procedures. The ordering
                % field defines what measurements are used to select and order
                % the procedures in this report.

                tp_ordering                 :: report_ordering,
                tp_top_procs                :: list(perf_row_data(proc_desc))
            ).

:- type proc_report
    --->    proc_report(
                % The integer is a count of the number of cliques this
                % procedure appears in.
                proc_callers_summary        :: perf_row_data(callers_counts),

                % The proc description is inside the proc_summary field.
                proc_summary                :: perf_row_data(proc_desc),
                proc_call_site_summaries    :: list(call_site_perf)
            ).

    % A count of the number of callers.
    %
:- type callers_counts
    --->    callers_counts(
                cc_static                   :: int,
                cc_dynamic                  :: int
            ).

:- type call_site_perf
    --->    call_site_perf(
                % The csf_summary_perf field contains the description of this
                % call site and the summary of its performance.
                %
                % If the call site can call only one procedure, the csf_kind
                % field tell you the id and the name of that procedure, and
                % the type substitution made in the call to it, if there is one
                % and it is known.
                %
                % Otherwise, if the call site can call more than one procedure,
                % the csf_sub_callees field will have one performance row
                % giving the contribution of each of the called procedures.
                %
                % If csf_kind = normal_call_and_info(_), then csf_sub_callees
                % should be the empty list.
                %
                % The call site description is inside the csf_summary field.

                csf_kind                    :: call_site_kind_and_info(
                                                normal_callee_id),
                csf_summary_perf            :: perf_row_data(call_site_desc),
                csf_sub_callees             :: list(perf_row_data(proc_desc))
            ).

:- type normal_callee_id
    --->    normal_callee_id(
                nci_callee_desc             :: proc_desc,
                nci_type_subst              :: string
            ).

:- type procrep_coverage_info
    --->    procrep_coverage_info(
                prci_proc                   :: proc_static_ptr,
                prci_proc_rep               :: proc_rep(goal_id),
                prci_coverage_array         :: goal_attr_array(coverage_info)
            ).

:- type proc_callers_report
    --->    proc_callers_report(
                % The id of the procedure.
                pc_proc_desc                :: proc_desc,

                % The call sites that call this procedure.
                pc_callers                  :: proc_callers,

                % Which batch of rows to show; transmitted without processing
                % from the request.
                pc_batch_number             :: int,

                % How many callers per batch, also transmitted without
                % processing from the request.
                pc_callers_per_batch        :: int,

                % Whether contour exclusion was applied in computing the
                % pc_callers field.
                pc_contour_exclusion        :: contour_exclusion,

                % If contour exclusion was asked for, this field may contain
                % a warning message about nonfatal problems with the exclusion
                % specification file.
                pc_contour_warn_message     :: maybe(string)
            ).

:- type proc_callers
    --->    proc_caller_call_sites(
                pc_caller_call_sites        :: list(
                                                perf_row_data(call_site_desc))
            )
    ;       proc_caller_procedures(
                pc_caller_procedures        :: list(
                                                perf_row_data(proc_desc))
            )
    ;       proc_caller_modules(
                pc_caller_modules           :: list(
                                                perf_row_data(string))
            )
    ;       proc_caller_cliques(
                pc_caller_cliques           :: list(
                                                perf_row_data(clique_desc))
            ).

:- type proc_static_dump_info
    --->    proc_static_dump_info(
                psdi_psptr                  :: proc_static_ptr,
                psdi_raw_name               :: string,
                psdi_uq_refined_name        :: string,
                psdi_q_refined_name         :: string,
                psdi_filename               :: string,
                psdi_linenumber             :: int,
                % Should we list the call_site_statics and coverage points
                % themselves?
                psdi_num_call_sites         :: int,
                psdi_num_coverage_points    :: int
            ).

:- type proc_dynamic_dump_info
    --->    proc_dynamic_dump_info(
                pddi_pdptr                  :: proc_dynamic_ptr,
                pddi_psptr                  :: proc_static_ptr,
                pddi_ps_raw_name            :: string,
                pddi_ps_module_name         :: string,
                pddi_ps_uq_refined_name     :: string,
                pddi_ps_q_refined_name      :: string,
                pddi_call_sites             :: list(call_site_array_slot),
                pddi_coverage_points        :: maybe(list(coverage_point))
            ).

:- type call_site_static_dump_info
    --->    call_site_static_dump_info(
                cssdi_cssptr                :: call_site_static_ptr,
                cssdi_containing_psptr      :: proc_static_ptr,
                cssdi_slot_number           :: int,
                cssdi_line_number           :: int,
                cssdi_goal_path             :: reverse_goal_path,
                cssdi_callee                :: call_site_kind_and_callee
            ).

:- type call_site_dynamic_dump_info
    --->    call_site_dynamic_dump_info(
                csddi_csdptr                :: call_site_dynamic_ptr,
                csddi_caller_pdptr          :: proc_dynamic_ptr,
                csddi_callee_pdptr          :: proc_dynamic_ptr,
                csddi_own_perf              :: perf_row_data(call_site_desc)
            ).

:- type clique_dump_info
    --->    clique_dump_info(
                cdi_clique_desc             :: clique_desc,
                cdi_caller_csd_ptr          :: call_site_dynamic_ptr,
                cdi_member_pdptrs           :: list(proc_dynamic_ptr)
            ).

:- type call_site_dynamic_var_use_info
    --->    call_site_dynamic_var_use_info(
                csdvui_total_cost           :: float,
                csdvui_var_uses             :: list(var_use_and_name)
            ).

:- type var_use_and_name
    --->    var_use_and_name(
                vun_var_name                :: string,
                vun_use                     :: var_use_info
            ).

    % This type represents information about the performance of the subject.
    % It is intended to be displayed on a browser page or used by a tool as is.
    % It is NOT intended to be subject to further processing, such as adding
    % two perf_row_datas together. Any such arithmetic is much better done
    % on the data structures from which perf_row_datas are derived
    % (own_prof_info and inherit_prof_info in the measurements module).
    %
:- type perf_row_data(T)
    --->    perf_row_data(
                % The item represented by this data row.
                perf_row_subject                :: T,

                % Port counts.
                perf_row_calls                  :: int,
                perf_row_exits                  :: int,
                perf_row_fails                  :: int,
                perf_row_redos                  :: int,
                perf_row_excps                  :: int,

                perf_row_bytes_per_word         :: int,
                perf_row_self                   :: inheritable_perf,
                perf_row_maybe_total            :: maybe(inheritable_perf)
            ).

:- type inheritable_perf
    --->    inheritable_perf(
                % Clock ticks and times. We always use simple integers to
                % represent clock ticks, whereas for time, we use more
                % user-friendly units. When the total time for the program
                % is close to zero, i.e. the number of ticks or quanta is zero
                % for the whole program, then the percentage may be zero
                % for everything (it used to be a NaN for 0/0, but we now
                % check explicitly for division by zero).
                perf_row_ticks             :: int,
                perf_row_time              :: time,
                perf_row_time_percent      :: percent,
                perf_row_time_percall      :: time,

                % Call sequence counts.
                perf_row_callseqs          :: int,
                perf_row_callseqs_percent  :: percent,
                perf_row_callseqs_percall  :: float,

                % Memory allocations.
                perf_row_allocs            :: int,
                perf_row_allocs_percent    :: percent,
                perf_row_allocs_percall    :: float,

                % Memory used. We try to use the most appropriate units
                % for representing each given amount of memory.
                % XXX Memory per call might not be an integer, so we should
                % make sure that the memory type can represent fractions.
                perf_row_mem               :: memory,
                perf_row_mem_percent       :: percent,
                perf_row_mem_percall       :: memory
            ).

    % This type is used to define 'most expensive procedures'. It contains all
    % the parameters that the query is given to select and order the procedures
    % shown in the report. See also the function symbol deep_cmd_top_procs of
    % the cmd type.
    %
:- type report_ordering
    --->    report_ordering(
                display_limit               :: display_limit,
                cost_kind                   :: cost_kind,
                incl_desc                   :: include_descendants,
                scope                       :: measurement_scope
            ).

    % The representation of a module in a report structure.
    %
:- type module_active
    --->    module_active(
                ma_module_name              :: string,
                ma_is_active                :: module_is_active
            ).

:- type module_is_active
    --->    module_is_active
    ;       module_is_not_active.

:- type proc_active
    --->    proc_active(
                pa_proc_desc                :: proc_desc,
                pa_is_active                :: proc_is_active
            ).

:- type proc_is_active
    --->    proc_is_active
    ;       proc_is_not_active.

    % The representation of a procedure in a report structure, including
    % information about its location in Mercury source code.
    %
:- type proc_desc
    --->    proc_desc(
                pdesc_ps_ptr                    :: proc_static_ptr,
                pdesc_file_name                 :: string,
                pdesc_line_number               :: int,
                pdesc_module_name               :: string,
                pdesc_uq_refined_name           :: string,
                pdesc_q_refined_name            :: string
            ).

    % The representation of a call site in a report structure, including
    % information about its location in Mercury source code.
    %
:- type call_site_desc
    --->    call_site_desc(
                csdesc_css_ptr                  :: call_site_static_ptr,
                csdesc_container                :: proc_static_ptr,
                csdesc_file_name                :: string,
                csdesc_line_number              :: int,
                csdesc_caller_module_name       :: string,
                csdesc_caller_uq_refined_name   :: string,
                csdesc_caller_q_refined_name    :: string,
                csdesc_slot_number              :: int,
                csdesc_goal_path                :: reverse_goal_path,
                csdesc_maybe_callee             :: maybe(proc_desc)
            ).

:- type ancestor_desc
    --->    ancestor_desc(
                % The caller and callee cliques.
                ad_caller_clique_ptr            :: clique_ptr,
                ad_callee_clique_ptr            :: clique_ptr,

                % The callee procedure.
                ad_callee_pdesc                 :: proc_desc,

                % The description of the call site. This includes a description
                % of the caller procedure.
                ad_call_site_desc               :: call_site_desc
            ).

    % The description of a clique in a report structure.
    %
:- type clique_desc
    --->    clique_desc(
                cdesc_clique_ptr                :: clique_ptr,
                cdesc_entry_member              :: proc_desc,
                cdesc_other_members             :: list(proc_desc)
            ).

%---------------------------------------------------------------------------%
%
% Predicates to make common actions on these types easier.
%

:- pred proc_label_from_proc_desc(deep::in, proc_desc::in,
    string_proc_label::out) is det.

    % foldl(add_call_site_report_to_map(CallSiteReports, map.init,
    %   CallSitesMap).
    %
    % Build a map from goal paths to call site reports.
    %
:- pred add_call_site_report_to_map(clique_call_site_report::in,
    map(reverse_goal_path, clique_call_site_report)::in,
    map(reverse_goal_path, clique_call_site_report)::out) is det.

%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%

:- implementation.

%---------------------------------------------------------------------------%

proc_label_from_proc_desc(Deep, ProcDesc, ProcLabel) :-
    PSPtr = ProcDesc ^ pdesc_ps_ptr,
    deep_lookup_proc_statics(Deep, PSPtr, ProcStatic),
    ProcLabel = ProcStatic ^ ps_id.

add_call_site_report_to_map(CallSite, !Map) :-
    GoalPath = CallSite ^ ccsr_call_site_summary ^ perf_row_subject
        ^ csdesc_goal_path,
    map.det_insert(GoalPath, CallSite, !Map).

%---------------------------------------------------------------------------%
:- end_module report.
%---------------------------------------------------------------------------%
