with Transport_Name;
with Context;
--
with Calendar;
with Debug_Tools;
with Compilation;
with Direct_Io;
with Disk_Daemon;
with Io;
with Log;
with Profile;
with Scheduler;
with System_Utilities;
with Table_Formatter;
with Time_Utilities;

package body Performance is

-------------------------------------------------------------------------------
    -- Constants

    The_Unit_To_Demote_And_Promote : constant String :=
       Context & ".the_null_procedure";
    File_Wait : constant Duration := 10.0;

-------------------------------------------------------------------------------
    -- Renaming and Instantiation

    package Dd renames Disk_Daemon;
    package Su renames System_Utilities;
    package Tu renames Time_Utilities;

    package Dio is new Direct_Io (Performance_Record);

    function "+" (X, Y : Dio.Count) return Dio.Count renames Dio."+";
    function "=" (X, Y : Tu.Time) return Boolean renames Tu."=";

-------------------------------------------------------------------------------

    function Time_Stamp (Of_Entry : Performance_Record)
                        return Time_Utilities.Time is
    begin
        return Of_Entry.The_Time;
    end Time_Stamp;
    function Disk_Q_Load (Of_Entry : Performance_Record) return Natural is
    begin
        return Of_Entry.The_Disk_Wait_Load;
    end Disk_Q_Load;
    function Run_Q_Load (Of_Entry : Performance_Record) return Natural is
    begin
        return Of_Entry.The_Run_Queue_Load;
    end Run_Q_Load;
    function Job_Cpu_Time (Of_Entry : Performance_Record) return Duration is
    begin
        return Of_Entry.The_Demote_Promote_Cpu;
    end Job_Cpu_Time;
    function Job_Elapsed_Time (Of_Entry : Performance_Record) return Duration is
    begin
        return Of_Entry.The_Demote_Promote_Elapsed;
    end Job_Elapsed_Time;
    function Available_Disk_Space
                (Of_Entry : Performance_Record) return Natural is
    begin
        return Of_Entry.The_Unused_Disk_Space;
    end Available_Disk_Space;
    function Gc_Running (Of_Entry : Performance_Record) return Boolean is
    begin
        return Of_Entry.The_Gc_Running;
    end Gc_Running;
    function Active_Sessions (Of_Entry : Performance_Record) return Natural is
    begin
        return Of_Entry.The_Active_Session_Count;
    end Active_Sessions;

    function Within_Time_Span
                (The_Time, The_Start, The_End : Tu.Time) return Boolean is
        use Calendar;
        T_Time : Calendar.Time := Tu.Convert_Time (The_Time);
        T_Start : Calendar.Time := Tu.Convert_Time (The_Start);
        T_End : Calendar.Time := Tu.Convert_Time (The_End);
    begin
        return T_Time >= T_Start and T_Time <= T_End;
    end Within_Time_Span;

    function Get_Entries (Starting_At : Time_Utilities.Time;
                          Ending_At : Time_Utilities.Time;
                          From_File : String) return Iterator is
        The_Current_Record : Performance_Record;
        The_Datafile : Dio.File_Type;
        File_Index : Dio.Count;
        Result : Iterator := Nil;  
        The_Start_Time : Time_Utilities.Time := Starting_At;  
        The_End_Time : Time_Utilities.Time := Ending_At;
    begin
        loop
            Open_Datafile:
                begin
                    Dio.Open (The_Datafile, Dio.In_File, From_File);
                    exit;
                exception
                    when Dio.Use_Error =>
                        delay File_Wait;
                end Open_Datafile;
        end loop;
        File_Index := Dio.Size (The_Datafile);
        for I in reverse 1 .. File_Index loop
            Dio.Read (The_Datafile, The_Current_Record, I);
            if Within_Time_Span (The_Current_Record.The_Time,
                                 The_Start_Time, The_End_Time) then
                Result := Make (The_Current_Record, Result);
            end if;  
        end loop;
        Dio.Close (The_Datafile);
        return Result;
    exception
        when Dio.Name_Error =>
            Log.Put_Line ("Unable to locate data file '" & From_File & "'",
                          Profile.Error_Msg);

    end Get_Entries;

    function Value (Iter : Iterator) return Performance_Record is
    begin
        return First (Iter);
    end Value;
    function Done (Iter : Iterator) return Boolean is
    begin
        return Length (Iter) = 0;
    end Done;
    procedure Next (Iter : in out Iterator) is
    begin
        Iter := Rest (Iter);
    end Next;


-------------------------------------------------------------------------------
    -- Returns the 15 minutes Disk Wait Load

    function Disk_Wait_Load return Scheduler.Load_Factor is
        The_Last_Sample,  
        The_Last_Minute,  
        The_Last_5_Minutes,  
        The_Last_15_Minutes : Scheduler.Load_Factor;
    begin
        Scheduler.Get_Disk_Wait_Load (The_Last_Sample, The_Last_Minute,
                                      The_Last_5_Minutes, The_Last_15_Minutes);
        return The_Last_15_Minutes;
    end Disk_Wait_Load;

-------------------------------------------------------------------------------
    -- Returns the 15 minutes Run Queue Load

    function Run_Queue_Load return Scheduler.Load_Factor is
        The_Last_Sample,  
        The_Last_Minute,  
        The_Last_5_Minutes,  
        The_Last_15_Minutes : Scheduler.Load_Factor;
    begin
        Scheduler.Get_Run_Queue_Load (The_Last_Sample, The_Last_Minute,
                                      The_Last_5_Minutes, The_Last_15_Minutes);
        return The_Last_15_Minutes;
    end Run_Queue_Load;

-------------------------------------------------------------------------------
    -- Returns the time to demote and promote a null procedure

    procedure Demote_Promote (Cpu_Time : out Duration;
                              Elapsed_Time : out Duration) is

        The_Result_Cpu : Duration := Su.Cpu;
        The_Result_Elapsed : Duration := Su.Elapsed;

    begin
        Compilation.Demote (Unit => The_Unit_To_Demote_And_Promote,
                            Response => "<QUIET>");
        Compilation.Make (Unit => The_Unit_To_Demote_And_Promote,
                          Response => "<QUIET>");
        The_Result_Cpu := Su.Cpu - The_Result_Cpu;
        The_Result_Elapsed := Su.Elapsed - The_Result_Elapsed;
        Cpu_Time := The_Result_Cpu;
        Elapsed_Time := The_Result_Elapsed;
    end Demote_Promote;

-------------------------------------------------------------------------------
    -- Returns the unused disk space in number of 1kB pages

    function Unused_Disk_Space return Natural is

        The_Space_Unused : Natural := 0;

    begin
        for Volume in Dd.Volume_Number loop
            if Dd.Exists (Volume) then
                The_Space_Unused := The_Space_Unused +
                                       Dd.Unused_Capacity (Volume);
            end if;
        end loop;
        return The_Space_Unused;
    end Unused_Disk_Space;

-------------------------------------------------------------------------------
    -- returns the number of active sessions

    function Session_Count return Natural is
        Iter : Su.Session_Iterator;
        Session_Count : Natural := 0;
    begin
        Su.Init (Iter);
        while not Su.Done (Iter) loop
            Session_Count := Session_Count + 1;
            Su.Next (Iter);
        end loop;
        return Session_Count;
    end Session_Count;

-------------------------------------------------------------------------------
-- Monitor program

    procedure Monitor (Interval : Duration := 15 * 60.0;
                       Data_File : String := "performance_data") is

        The_Datafilename : constant String := Context & "." & Data_File;
        The_Datafile : Dio.File_Type;
        Dp_Cpu_Time : Duration;
        Dp_Elapsed_Time : Duration;

    begin
        loop
            Demote_Promote (Dp_Cpu_Time, Dp_Elapsed_Time);
            Open_Datafile:
                loop
                    begin
                        Dio.Open (The_Datafile, Dio.Out_File, The_Datafilename);
                        exit Open_Datafile;
                    exception
                        when Dio.Use_Error =>
                            delay File_Wait;
                        when Dio.Name_Error =>
                            Dio.Create (The_Datafile, Dio.Out_File,
                                        The_Datafilename);
                            Dio.Close (The_Datafile);
                    end;
                end loop Open_Datafile;
            Dio.Write (The_Datafile,
                       Performance_Record'
                          (Tu.Get_Time, Disk_Wait_Load, Run_Queue_Load,
                           Dp_Cpu_Time, Dp_Elapsed_Time, Unused_Disk_Space,
                           Dd.Garbage_Collector_Is_Running, Session_Count),
                       Dio.Size (The_Datafile) + 1);
            Dio.Close (The_Datafile);
            delay Interval;
        end loop;
    end Monitor;

-------------------------------------------------------------------------------
    -- Report programs


    function Image (Load_Factor : Scheduler.Load_Factor) return String is
        type Q_Load_Factor is digits 3;
        package Fio is new Io.Float_Io (Q_Load_Factor);
        The_Image : String (1 .. 5);
        F : Q_Load_Factor := Q_Load_Factor (Load_Factor) / 100.0;
    begin
        Fio.Put (The_Image, F, 2, 0);
        return The_Image;
    end Image;

    procedure Put_Header (Report_Type : String; To_File : Io.File_Type) is
    begin
        Io.Put_Line (To_File, Time_Utilities.Image (Time_Utilities.Get_Time));
        Io.Put_Line (To_File, Report_Type & " generated for " &
                                 Transport_Name.Local_Host_Name ("tcp/ip"));
    end Put_Header;
    procedure Report (Start_Date : String := "";
                      End_Date : String := "";
                      Output : String := "";
                      Data_File : String := "performance_data") is

        package Tf is new Table_Formatter (6, "/");
        The_Datafilename : constant String := Context & "." & Data_File;
        The_Datafile : Dio.File_Type;
        The_Start_Time,  
        The_End_Time : Tu.Time;
        The_Output_File : Io.File_Type;
        The_Current_Record,  
        The_Previous_Record : Performance_Record :=
           Performance_Record'(The_Time => Tu.Nil,
                               The_Disk_Wait_Load => 0,
                               The_Run_Queue_Load => 0,
                               The_Demote_Promote_Cpu => 0.0,
                               The_Demote_Promote_Elapsed => 0.0,
                               The_Unused_Disk_Space => 0,
                               The_Gc_Running => False,
                               The_Active_Session_Count => 0);
        Iter : Iterator;

    begin
        if Start_Date = "" then
            The_Start_Time := Tu.Value ("60/01/11 16:15:00");
        else
            The_Start_Time := Tu.Value (Start_Date);
        end if;
        if End_Date = "" then
            The_End_Time := Tu.Get_Time;
        else
            The_End_Time := Tu.Value (End_Date);
        end if;
        Iter := Get_Entries (The_Start_Time, The_End_Time, The_Datafilename);
        Tf.Header ("Time", Tf.Right);
        Tf.Header ("DWL / RQL", Tf.Right);
        Tf.Header ("Disk", Tf.Right);
        Tf.Header ("DP: Cpu / Elap", Tf.Right);
        Tf.Header ("Users", Tf.Right);
        Tf.Header ("GC", Tf.Centered);
        The_Previous_Record.The_Unused_Disk_Space := 0;
        while not Done (Iter) loop
            The_Current_Record := Value (Iter);
            if Time_Stamp (The_Previous_Record) /= Tu.Nil then
                Tf.Item (Tu.Image (Time_Stamp (The_Current_Record),
                                   Tu.Year_Month_Day, Tu.Military));
                Tf.Subitem (Image (Disk_Q_Load (The_Current_Record)));
                Tf.Subitem (Image (Run_Q_Load (The_Current_Record)));
                Tf.Last_Subitem;
                declare
                    The_Consumed_Space : Integer :=
                       Available_Disk_Space (The_Previous_Record) -
                          Available_Disk_Space (The_Current_Record);
                begin
                    if The_Consumed_Space < 0 then
                        Tf.Item ("0");
                    else
                        Tf.Item (Integer'Image (The_Consumed_Space));
                    end if;
                end;
                Tf.Subitem (Tu.Image (Tu.Convert
                                         (Job_Cpu_Time (The_Current_Record))));

                Tf.Subitem (Tu.Image (Tu.Convert (Job_Elapsed_Time
                                                     (The_Current_Record))));  
                Tf.Last_Subitem;
                Tf.Item (Natural'Image (Active_Sessions (The_Current_Record)));
                if Gc_Running (The_Current_Record) then
                    Tf.Item ("*");
                else
                    Tf.Item ("");
                end if;
            end if;
            The_Previous_Record := The_Current_Record;
            Next (Iter);
        end loop;

        if Output /= "" then
            Io.Create (The_Output_File, Io.Out_File, Output);
            Put_Header ("Detailed Performance Report", The_Output_File);
            Io.New_Line (The_Output_File);
            Tf.Display (The_Output_File);
            Io.Close (The_Output_File);
        else
            Put_Header ("Detailed Performance Report", Io.Current_Output);
            Io.New_Line (Io.Current_Output);
            Tf.Display (Io.Current_Output);
        end if;
    exception
        when others =>
            Log.Put_Line (Debug_Tools.Get_Exception_Name (True, True),
                          Profile.Exception_Msg);
    end Report;

    procedure Weekly_Report (Week_Of : String := ">>MONDAY'S DATE<<";
                             Start_Time : String := "9:00:00";
                             End_Time : String := "17:00:00";
                             Output : String := "";
                             Weekend_Too : Boolean := False;
                             Data_File : String := "performance_data") is
        use Time_Utilities;  
        The_Datafilename : constant String := Context & "." & Data_File;
        The_Datafile : Dio.File_Type;
        Monday, The_Start_Time,  
        The_End_Time : Tu.Time;
        The_Output_File : Io.File_Type;
        The_Current_Record : Performance_Record;  
        package Tf is new Table_Formatter (7);
        Iter : Iterator;
    begin
        Monday := Tu.Value (Week_Of);
        if Tu.Day_Of_Week (Monday) /= Tu.Weekday'First then
            Io.Put_Line (Week_Of & " is not a Monday.");
            return;
        end if;
        The_Start_Time := Tu.Value (Week_Of & " " & Start_Time);
        The_End_Time := Tu.Value (Week_Of & " " & End_Time);
        Tf.Header ("Day", Tf.Left);
        Tf.Header ("Disk Start", Tf.Right);
        Tf.Header ("Disk End", Tf.Right);
        Tf.Header ("RunQ Avg", Tf.Right);
        Tf.Header ("PD Avg", Tf.Right);  
        Tf.Header ("Users", Tf.Right);
        Tf.Header ("Samples", Tf.Right);
        declare
            use Calendar;  
            Disk_Start : Natural := 0;
            Disk_End : Natural := 0;
            Runq : Scheduler.Load_Factor := 0;  
            Pd : Duration := 0.0;  
            Users : Natural := 0;
            Start_Time : Tu.Time;
            End_Time : Tu.Time;
            Item_Count : Natural := 0;
        begin
            for Day in Tu.Weekday loop
                if not Weekend_Too then
                    exit when Day = 6;
                end if;

                Iter := Get_Entries (Starting_At => The_Start_Time,
                                     Ending_At => The_End_Time,
                                     From_File => The_Datafilename);
                Item_Count := 0;
                Runq := 0;  
                Pd := 0.0;  
                Users := 0;
                while not Done (Iter) loop
                    The_Current_Record := Value (Iter);
                    if Item_Count = 0 then
                        Disk_Start := Available_Disk_Space (The_Current_Record);
                        Start_Time := Time_Stamp (The_Current_Record);
                    end if;
                    Item_Count := Item_Count + 1;
                    Runq := Runq + Run_Q_Load (The_Current_Record);
                    Users := Users + Active_Sessions (The_Current_Record);
                    Disk_End := Available_Disk_Space (The_Current_Record);
                    End_Time := Time_Stamp (The_Current_Record);
                    Pd := Pd + Job_Elapsed_Time (The_Current_Record);
                    Next (Iter);
                end loop;
                Tf.Item (Tu.Image (Day));

                if Item_Count /= 0 then
                    Runq := Runq / Item_Count;
                    Pd := Pd / Item_Count;
                    Users := Users / Item_Count;
                    Tf.Item (Natural'Image (Disk_Start));
                    Tf.Item (Natural'Image (Disk_End));
                    Tf.Item (Image (Runq));
                    Tf.Item (Tu.Image (Pd));
                    Tf.Item (Natural'Image (Users));
                    Tf.Item (Natural'Image (Item_Count));
                else
                    Tf.Item ("NA");
                    Tf.Item ("NA");
                    Tf.Item ("NA");
                    Tf.Item ("NA");
                    Tf.Item ("NA");
                    Tf.Item (Natural'Image (Item_Count));
                end if;
                The_Start_Time := Time_Utilities.Convert_Time
                                     (Time_Utilities.Convert_Time
                                         (The_Start_Time) + (60.0 * 60 * 24));
                The_End_Time := Time_Utilities.Convert_Time
                                   (Time_Utilities.Convert_Time (The_End_Time) +
                                    (60.0 * 60 * 24));


            end loop;
            if Output /= "" then
                Io.Create (The_Output_File, Io.Out_File, Output);
                Put_Header ("Weekly Machine Report", The_Output_File);
                Io.Put_Line (The_Output_File,
                             "For the week starting on " &
                                Time_Utilities.Image
                                   (Monday,
                                    Date_Style => Time_Utilities.Expanded,
                                    Contents => Time_Utilities.Date_Only));
                Io.New_Line (The_Output_File);
                Tf.Display (The_Output_File);
                Io.Close (The_Output_File);
            else
                Put_Header ("Weekly Machine Report", Io.Current_Output);
                Io.Put_Line (Io.Current_Output,
                             "For the week starting on " &
                                Time_Utilities.Image
                                   (Monday,
                                    Date_Style => Time_Utilities.Expanded,
                                    Contents => Time_Utilities.Date_Only));
                Io.New_Line (Io.Current_Output);
                Tf.Display (Io.Current_Output);
            end if;
        exception
            when others =>
                Log.Put_Line (Debug_Tools.Get_Exception_Name (True, True),
                              Profile.Exception_Msg);

        end;
    end Weekly_Report;
end Performance;