/*

   Derby - Class org.apache.derby.impl.store.access.btree.D_BTreeController

   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to you 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.

 */

package org.apache.derby.impl.store.access.btree;

import org.apache.derby.iapi.services.diag.DiagnosticableGeneric;
import org.apache.derby.iapi.services.diag.DiagnosticUtil;
import org.apache.derby.shared.common.sanity.SanityManager;
import org.apache.derby.shared.common.error.StandardException;
import org.apache.derby.iapi.store.raw.Page;

import java.util.Properties;

/**

  A BTreeDiag class is a "helper" class for the rest of the btree generic
  code.  It is separated into a separate class so that it can be compiled
  out if necessary (or loaded at runtime if necessary).

  <p>
  more info.
**/
class LevelInfo
{
    public int num_pages          = 0; // number of pages in heap.
    public int num_overflow_pgs   = 0; // number of overflow pages heap.
    public int num_entries        = 0; // number recs on page
    public int num_deleted        = 0; // number of recs on page marked deleted.
    public long max_pageno        = 0; // biggest page number allocated
    public long num_free_bytes    = 0; // number of free bytes on the pages.
    public long num_res_bytes     = 0; // number of reserved bytes on the pages.
    public long num_overflow_rows = 0; // number of over flow rows on page.
    public long num_rowsize_bytes = 0; // number of bytes in rows.
    public long num_slottab_bytes = 0; // number of bytes in slot table.
    public long min_rowsize_bytes = Long.MAX_VALUE; // length of shortest row.
    public long max_rowsize_bytes = Long.MIN_VALUE; // length of longest row.
}

public class D_BTreeController extends DiagnosticableGeneric
{
    /* Private/Protected methods of This class: */
    private static void diag_page(
    OpenBTree   open_btree,
    ControlRow  control_row, 
    Properties  prop,
    LevelInfo   level_info[])
        throws StandardException
    {
        LevelInfo li    = level_info[control_row.getLevel()];
        Page      page  = control_row.page;



        li.num_pages++;
        li.num_entries += (page.recordCount() - 1);
        li.num_deleted += (page.recordCount() - page.nonDeletedRecordCount());
        li.max_pageno  = Math.max(li.max_pageno, page.getPageNumber());

        DiagnosticUtil.findDiagnostic(page).diag_detail(prop);


        DiagnosticUtil.findDiagnostic(page).diag_detail(prop);

        // number of free bytes on page.
        int free_bytes = 
            Integer.parseInt(prop.getProperty(Page.DIAG_BYTES_FREE));

        li.num_free_bytes += free_bytes;

        // number of bytes reserved on page.
        int res_bytes = 
            Integer.parseInt(prop.getProperty(Page.DIAG_BYTES_RESERVED));

        li.num_res_bytes += res_bytes;

        // overflow rows.
        int overflow = 
            Integer.parseInt(prop.getProperty(Page.DIAG_NUMOVERFLOWED));

        li.num_overflow_rows += overflow;

        // size of rows.
        int rowsize = 
            Integer.parseInt(prop.getProperty(Page.DIAG_ROWSIZE));

        li.num_rowsize_bytes += rowsize;

        // size of slot table.
        int slottable_size = 
            Integer.parseInt(prop.getProperty(Page.DIAG_SLOTTABLE_SIZE));

        li.num_slottab_bytes += slottable_size;

        // minimum row size.
        int min_rowsize = 
            Integer.parseInt(prop.getProperty(Page.DIAG_MINROWSIZE));

        li.min_rowsize_bytes = Math.min(li.min_rowsize_bytes, min_rowsize);

        // maximum row size.
        int max_rowsize = 
            Integer.parseInt(prop.getProperty(Page.DIAG_MAXROWSIZE));

        li.max_rowsize_bytes = Math.max(li.max_rowsize_bytes, max_rowsize);
    }

    private static void diag_level(
    OpenBTree   open_btree,
    ControlRow  control_row, 
    Properties  prop,
    LevelInfo   level_info[])
        throws StandardException
    {
        ControlRow      child = null;

        diag_page(open_btree, control_row, prop, level_info);

        try
        {
            child = control_row.getLeftChild(open_btree);

            if (child != null)
            {
                // this is a branch page.
                if (SanityManager.DEBUG)
                    SanityManager.ASSERT(
                        control_row instanceof BranchControlRow);

                BranchControlRow branch = (BranchControlRow) control_row;

                diag_level(open_btree, child, prop, level_info);
                child.release();
                child = null;

                int numslots = branch.page.recordCount();
                for (int slot = 1; slot < numslots; slot++)
                {
                    child = branch.getChildPageAtSlot(open_btree, slot);
                    diag_level(open_btree, child, prop, level_info);
                    child.release();
                    child = null;
                }
            }
        }
        finally
        {
            if (child != null)
                child.release();
        }

        return;
    }

    private static String out_summary(
    String  hdr,
    long    value,
    double  ratio,
    String  ratio_desc)
    {
        String double_str = "" + ratio;

        String short_str = double_str.substring(
            0, Math.min(double_str.lastIndexOf(".") + 3, double_str.length()));

        return(
            "\t" + hdr + value + ".\t(" + short_str + 
            " " + ratio_desc + ").\n");
    }

    private static String diag_onelevel(
    Properties  prop,
    LevelInfo   li)
    {
        String ret_string   = "";

        ret_string += 
            "Btree conglom has:\n" + 
            "\t" + prop.getProperty(Page.DIAG_PAGE_SIZE) + " bytes per page\n" +
            "\t" + li.num_pages           + " total used pages (" +
                (Integer.parseInt(prop.getProperty(Page.DIAG_PAGE_SIZE)) * 
                     li.num_pages) + 
                " bytes)\n"            +
            "\tmaximum page number   = " + li.max_pageno + ".\n"         +
            "\treserved space %      = " + prop.getProperty(Page.DIAG_RESERVED_SPACE) + "%.\n"         +
            "\tminimum record size   = " + prop.getProperty(Page.DIAG_MINIMUM_REC_SIZE) + ".\n"         +
            "\tpage overhead bytes   = " + prop.getProperty(Page.DIAG_PAGEOVERHEAD) + " bytes per page.\n"         +
            "\tminimum record length = " + li.min_rowsize_bytes + ".\n" +
            "\tmaximum record length = " + li.max_rowsize_bytes + ".\n" +
            "\t# of bytes in rows    = " + li.num_rowsize_bytes + "." +
                "\t(" + 
                (li.num_entries == 0 ? 
                     0 : (li.num_rowsize_bytes / li.num_entries)) + 
                " bytes/row).\n"                                  +
            out_summary(
                "# of reserved bytes   = ", 
                li.num_res_bytes,
                (li.num_res_bytes / li.num_pages),
                "reserved bytes/page") +
            out_summary(
                "# of free bytes       = ",
                li.num_free_bytes,
                (li.num_free_bytes / li.num_pages),
                "free bytes/page")  +
            out_summary(
                "# of slot table bytes = ",
                li.num_slottab_bytes,
                (li.num_slottab_bytes / li.num_pages),
                "slot table bytes/page")  +
            out_summary(
                "# of reserved+free+row+slot bytes = ",
                (li.num_rowsize_bytes +
                 li.num_res_bytes     +
                 li.num_free_bytes     +
                 li.num_slottab_bytes),
                ((li.num_rowsize_bytes +
                  li.num_res_bytes     +
                  li.num_free_bytes     +
                  li.num_slottab_bytes) / li.num_pages),
                "summed bytes/page")  +
            out_summary(
                "# of total records    = ",
                li.num_entries,
                (((double) li.num_entries) / li.num_pages),
                "records/page") +
            out_summary(
                "# of overflow records = ",
                li.num_overflow_rows,
                (((double) li.num_overflow_rows) / li.num_pages),
                "overflow records/page") +
            out_summary(
                "# of deleted records  = ",
                li.num_deleted,
                (((double) li.num_deleted) / li.num_pages),
                "deleted records/page"); 

        return(ret_string);
    }
            

    private static String diag_tabulate(
    Properties  prop,
    LevelInfo   level_info[])
    {
        String ret_string   = "";
        LevelInfo   total   = new LevelInfo();

        // first tabulate totals for all levels
        
        for (int level = 0; level < level_info.length; level++) 
        {
            LevelInfo li = level_info[level];

            total.num_pages         += li.num_pages; 
            total.num_overflow_pgs  += li.num_overflow_pgs; 
            total.num_entries       += li.num_entries; 
            total.num_deleted       += li.num_deleted; 
            total.max_pageno        = Math.max(total.max_pageno, li.max_pageno);
            total.num_free_bytes    += li.num_free_bytes; 
            total.num_res_bytes     += li.num_res_bytes; 
            total.num_overflow_rows += li.num_overflow_rows; 
            total.num_rowsize_bytes += li.num_rowsize_bytes; 
            total.num_slottab_bytes += li.num_slottab_bytes; 
            total.min_rowsize_bytes = 
                Math.min(total.min_rowsize_bytes, li.min_rowsize_bytes);
            total.max_rowsize_bytes = 
                Math.max(total.max_rowsize_bytes, li.max_rowsize_bytes);
        }

        ret_string +=
            "Btree conglom has:\n" + 
            "\t" + prop.getProperty(Page.DIAG_PAGE_SIZE) + " bytes per page\n" +
            "\t" + total.num_pages           + " total used pages (" +
                (Integer.parseInt(prop.getProperty(Page.DIAG_PAGE_SIZE)) * 
                     total.num_pages) + 
                " bytes)\n"            +
            "\tmaximum page number   = " + total.max_pageno + ".\n"         +
            "\treserved space %      = " + prop.getProperty(Page.DIAG_RESERVED_SPACE) + "%.\n"         +
            "\tminimum record size   = " + prop.getProperty(Page.DIAG_MINIMUM_REC_SIZE) + ".\n"         +
            "\tpage overhead bytes   = " + prop.getProperty(Page.DIAG_PAGEOVERHEAD) + " bytes per page.\n";

        // Format Totals:
        ret_string += diag_onelevel(prop, total);

        // Format Totals by level:

        // Totals by level:
        for (int level = 0; level < level_info.length; level++) 
        {
            LevelInfo   li = level_info[level];

            ret_string += "level[" + level + "] stats:\n";

            ret_string += diag_onelevel(prop, li);
        }

        return(ret_string);
    }

    private static String olddiag_tabulate(
    Properties  prop,
    LevelInfo   level_info[])
    {
        String ret_string   = "";
        long   total_pages  = 0;
        long   total_res    = 0;

        for (int level = 0; level < level_info.length; level++) 
        {
            total_pages += level_info[level].num_pages;
        }


        // Totals:
        ret_string += 
            "Btree conglom has:\n" + 
            "\t" + prop.getProperty(Page.DIAG_PAGE_SIZE) + " bytes per page\n" +
            "\t" + total_pages               + " total pages ("                +
                (Integer.parseInt(prop.getProperty(Page.DIAG_PAGE_SIZE)) * 
                     total_pages) + " bytes)\n"                                +
            "\t" + level_info.length         + " total levels\n"               +
            "\t" + level_info[0].num_entries + " total user records\n";

        // Totals by level:
        for (int level = 0; level < level_info.length; level++) 
        {
            LevelInfo   li = level_info[level];

            ret_string += "level[" + level + "] stats:\n";

            ret_string += 
                "\t# of pages           = " + li.num_pages      + ".\n" +
                "\t# of entries         = " + li.num_entries    + ".  " +
                "(" + (li.num_entries / li.num_pages) + " entries/page).\n" +
                "\t# of deleted entries = " + li.num_deleted    + ".  " +
                "(" + (li.num_deleted / li.num_pages) + " deleted/page).\n" +
                "\t# of free bytes      = " + li.num_res_bytes + ".  " +
                "(" + (li.num_res_bytes / li.num_pages) + " reserved bytes/page).\n" +
                "\t# of free bytes      = " + li.num_free_bytes + ".  " +
                "(" + (li.num_free_bytes / li.num_pages) + " free bytes/page).\n" +
                "\t# of slot table bytes= " + li.num_slottab_bytes + ".  " +
                "(" + (li.num_slottab_bytes / li.num_pages) + " slot table bytes/page).\n";
        }

        return(ret_string);
    }

	/*
	** Methods of Diagnosticable
	*/
    public void init(Object obj)
    {
        if (SanityManager.DEBUG)
            SanityManager.ASSERT(obj instanceof BTreeController);

        super.init(obj);
    }

    /**
     * Default implementation of diagnostic on the object.
     * <p>
     * This routine returns a string with whatever diagnostic information
     * you would like to provide about this object.
     * <p>
     * This routine returns a summary table of information about pages in
     * each level of the btree.  It tells the height of the tree, the 
     * average free and reserved bytes per level, and the page size.
     * <p>
     *
	 * @return A string with diagnostic information about the object.
     *
     * @exception StandardException  Standard Derby exception policy
     **/
    public String diag()
        throws StandardException
    {
        OpenBTree   open_btree  = (OpenBTree) this.diag_object;
        ControlRow  root        = null;
        int         tree_height;
        LevelInfo   level_info[] = null;
        String      diag_info    = "";

        
        try
        {
            tree_height = open_btree.getHeight();
            root = ControlRow.get(open_btree, BTree.ROOTPAGEID);

            // Allocate a LevelInfo array with one entry per level of the tree.
            level_info = new LevelInfo[tree_height];
            for (int level = 0; level < level_info.length; level++) 
                level_info[level] = new LevelInfo();

            // ask page to provide diag info:
            Properties prop = new Properties();
            prop.put(Page.DIAG_PAGE_SIZE,        "");
            prop.put(Page.DIAG_BYTES_FREE,       "");
            prop.put(Page.DIAG_BYTES_RESERVED,   "");
            prop.put(Page.DIAG_RESERVED_SPACE,   "");
            prop.put(Page.DIAG_MINIMUM_REC_SIZE, "");
            prop.put(Page.DIAG_NUMOVERFLOWED,    "");
            prop.put(Page.DIAG_ROWSIZE,          "");
            prop.put(Page.DIAG_MINROWSIZE,       "");
            prop.put(Page.DIAG_MAXROWSIZE,       "");
            prop.put(Page.DIAG_PAGEOVERHEAD,     "");
            prop.put(Page.DIAG_SLOTTABLE_SIZE,   "");

            diag_level(open_btree, root, prop, level_info);


            diag_info = diag_tabulate(prop, level_info);
        }
        finally
        {
            if (root != null)
                root.release();
        }

        return(diag_info);
    }
}
