# FwdServerJMX.java

    /*
    ** Module   : FwdServerJMX.java
    ** Abstract : FWD JMX instrumentation.
    **
    ** Copyright (c) 2024-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
    ** 001 HC  20240222 Created initial version.
    ** 002 SP  20240731 Added JMX_DEBUG flag.
    ** 003 HC  20250205 Removed the disabled SPREADSHEET caching logic.
    ** 004 ICP 20241114 Added QueryCounter JMX.
    */

    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.jmx;

    import java.io.*;
    import java.lang.management.*;
    import java.util.*;
    import java.util.function.Function;
    import java.util.logging.*;
    import javax.management.*;

    import com.goldencode.p2j.util.logging.*;
    import com.goldencode.util.*;

    /** FWD JMX instrumentation */
    public class FwdServerJMX
    extends FwdJMX
    {
       /** When {@code true}, enable usage of some MBeans */
       public static final boolean JMX_DEBUG = System.getProperty("fwd_jmx_debug") != null;

       /** Logger. */
       private static final CentralLogger LOG = CentralLogger.get(FwdServerJMX.class.getName());

       /**
        * Default constructor
        */
       private FwdServerJMX()
       {
          super("FWD Server");
       }

       /**
        * Register MBeans
        */
       public void registerMBeans()
       {
          try
          {
             MBeanServer server = ManagementFactory.getPlatformMBeanServer();
             for (TimeStat ts: TimeStat.values())
             {
                server.registerMBean(ts.getCounter(),
                                     new ObjectName("com.goldencode.p2j:name=" + ts.name()));
             }
             for (Counter c: Counter.values())
             {
                server.registerMBean(c.getCounter(),
                                     new ObjectName("com.goldencode.p2j:name=" + c.name()));
             }
             for (MapCounter c: MapCounter.values())
             {
                server.registerMBean(c.getCounter(), new ObjectName("com.goldencode.p2j:name=" + c.name()));
             }
             for (ThreadsCpuTimers ts: ThreadsCpuTimers.values())
             {
                server.registerMBean(ts.getCounter(), new ObjectName("com.goldencode.p2j:name=" + ts.name()));
             }
             for (QueryProfiler ts: QueryProfiler.values())
             {
                server.registerMBean(ts.getProfiler(), new ObjectName("com.goldencode.p2j:name=" + ts.name()));
             }
             for (QueryCounterEnum qc: QueryCounterEnum.values())
             {
                server.registerMBean(qc.getQueryCounter(), new ObjectName("com.goldencode.p2j:name=" + qc.name()));
             }
             server.registerMBean(this, new ObjectName("com.goldencode.p2j:name=All"));
          }
          catch (Exception e)
          {
             LOG.log(Level.SEVERE, "MBean registration failed", e);
          }
       }

       /**
        * Initialize singleton
        */
       public static void init()
       {
          Holder.INSTANCE.registerMBeans();
       }

       /**
        * Register MBean
        *
        * @param mbean
        *        MBean to be registered
        * @param name
        *        MBean name
        */
       public static void register(Object mbean, String name)
       {
          Holder.INSTANCE.registerMBean(mbean, name);
       }

       /**
        * Instance holder (Lazy initialization)
        */
       private static class Holder
       {
          static final FwdServerJMX INSTANCE = new FwdServerJMX();
       }

       /**
        * Time counters
        */
       public static enum TimeStat
       implements OptionalCounter
       {
          /** ServerStat.widgetAttrs (de)serialization */
          ServerStateS11n,
          /** Message.payload (de)serialization */
          MessagePayloadS11n,
          /** Protocol.objToByteArray */
          objToByteArray,
          /** SSL send time */
          sslSend,
          /** SSL wrap time */
          sslWrap,
          /** SSL wrap time */
          sslUnwrap,

          /** Instrumentation for <code>ContextLocal.get</code>. */
          ContextLocalGet,

          /** Finer instrumentation for preparing the SymbolResolver in {@code DynamicQueryHelper.parse()}. */
          OrmDynQueryProcess_SymRes,
          /** Finer instrumentation for preparing the SchemaDictionary in {@code DynamicQueryHelper.parse()}. */
          OrmDynQueryProcess_SchemaDict,
          /** Finer instrumentation for preparing the lexer and parser in {@code DynamicQueryHelper.parse()}. */
          OrmDynQueryProcess_Parse,
          /** Finer instrumentation for running ONLY the parser in {@code DynamicQueryHelper.parse()}. */
          OrmDynQueryProcess_Parse2,
          /** Instrumentation for setting up for TRPL processing in {@code DynamicQueryHelper.parse()}. */
          OrmDynQueryProcess_Setup,
          /** Instrumentation for running ONLY the annotations TRPL in {@code DynamicQueryHelper.parse()}. */
          OrmDynQueryProcess_Annotation,
          /** Instrumentation for running ONLY the base_structure TRPL in {@code DynamicQueryHelper.parse()}. */
          OrmDynQueryProcess_Base,
          /** Instrumentation for running ONLY the core_conversion TRPL in {@code DynamicQueryHelper.parse()}. */
          OrmDynQueryProcess_Core,
          /** Instrumentation for running ONLY the postprocessing TRPL in {@code DynamicQueryHelper.parse()}. */
          OrmDynQueryProcess_Post,
          /** Instrumentation for optimizing the result JAST by interning the {@code String}-s in
           *  {@code DynamicQueryHelper.parse()}. They proved to have a low impact on performance. */
          OrmDynQueryProcess_Intern,

          /** Instrumentation for <code>DynamicQueryHelper.parseFindQuery</code>. */
          OrmDynParseFind,
          /** Instrumentation for <code>DynamicQueryHelper.parseQuery</code>. */
          OrmDynParseQuery,
          /** Instrumentation for <code>SQLQuery.hydrateRecord</code>. */
          OrmHydrateRecord,
          /** Instrumentation for <code>Query.createSqlQuery</code>*/
          OrmFqlParse,
          /** Instrumentation for <code>UnclosablePreparedStatement</code> execute methods. */
          OrmTempTableQuery,
          /** Instrumentation for <code>RuntimeJastInterpreter.interpret</code>. */
          OrmDynQueryInterpret,
          /** Instrumentation for <code>HQLHelper.obtain</code>. */
          OrmHqlHelperParse,
          /** Instrumentation for <code>PreselectQuery.assembleHQL</code>. */
          OrmPreselectQueryAssemble,
          /** Instrumentation for <code>TempTableBuilder.tempTablePrepareImpl</code>. */
          OrmCreateDynamicTable,
          /** Instrumentation for <code>TemporaryBuffer.createDynamicBufferForTempTable</code>. */
          OrmCreateDynamicBuffer,
          /** Instrumentation for <code>TemporaryBuffer.define</code>. */
          OrmBufferDefineTemp,
          /** Instrumentation for <code>RecordBuffer.define</code>. */
          OrmBufferDefinePerm,
          /** Instrumentation for <code>BufferImpl.resourceDelete</code>. */
          OrmBufferDelete,
          /** Instrumentation for <code>TableMapper.mapTemporaryTable</code>. */
          OrmTableMapperMap,

          /** Instrumentation for <code>TemporaryBuffer.associate</code>. */
          OrmTempTableParam,
          /** Instrumentation for <code>TemporaryBuffer.createDynamicTable</code>. */
          OrmDynamicTableParam,
          /** Instrumentation for <code>DataSet.associate</code>. */
          OrmDataSetParam,
          /** Instrumentation for <code>DataSet.createDynamicDataSet</code>. */
          OrmDynamicDataSetParam,

          /** Instrumentation for <code>OutputTableCopier.finished</code> */
          OutputTableCopier,
          /** Instrumentation for <code>OutputTableHandleCopier.finished</code> */
          OutputTableHandleCopier,
          /** Instrumentation for <code>OutputDataSetCopier.finished</code> */
          OutputDataSetCopier,
          /** Instrumentation for <code>OutputDataSetHandleCopier.finished</code> */
          OutputDataSetHandleCopier,

          /** Instrumentation for <code>TransactionManager.processCommit</code>. */
          TMCommit,
          /** Instrumentation for <code>TransactionManager.processRollback</code>. */
          TMRollback,
          /** Instrumentation for <code>TransactionManager.processValidate</code>. */
          TMValidate,

          /** Instrumentation for <code>LegacyJavaAppserver.addTableMetaData</code>. */
          AppserverAddTableMetaData,
          /** Instrumentation for <code>LegacyJavaAppserver.getJavaParameter</code>. */
          ApperverGetJavaParameter,
          /** Instrumentation for <code>LegacyJavaAppserver.getOutputParameter</code>. */
          AppserverGetOutputParameter;

          /** Counter instance */
          private final NanoCounter counter = new NanoCounter();

          /**
           * Register enable/disable setter
           * @param agent
           *        enable/disable setter
           */
          @Override
          public void register(BooleanConsumer agent)
          {
             counter.register(agent);
          }

          /**
           * Update counter
           * @param value
           *        new value
           */
          @Override
          public void update(long value)
          {
             counter.update(value);
          }

          /**
           * Return the string representation of the counter
           *
           * @param    ts
           *           The measurement time stamp
           * @param    comment
           *           The comment
           *
           * @return   The string representation of the counter
           *
           * @throws   IOException
           *           If IO exception occurs
           */
          @Override
          public String toString(String ts, String comment) throws IOException
          {
             return counter.toString();
          }

           /**
           * Return counter instance
           * @return counter instance
           */
          public NanoCounter getCounter()
          {
             return counter;
          }
       }

       /**
        * Defines the threads cpu counters.
        */
       public static enum ThreadsCpuTimers
       implements ThreadsCpuMBean
       {
          /** This counter is still unused */
          ThreadsCpuUtilization
          ;

          /** Counter instance */
          private final ThreadsCpuCounter counter = new ThreadsCpuCounter();

          /**
           * Register enable/disable setter.
           *
           * @param   agent
           *          enable/disable setter
           */
          @Override
          public void register(BooleanConsumer agent)
          {
             counter.register(agent);
          }

          /**
           * Update counter.
           *
           * @param    value
           *           new value
           */
          @Override
          public void update(long value)
          {
             counter.update(value);
          }

          /**
           * Return the string representation of the counter
           *
           * @param    ts
           *           The measurement time stamp
           * @param    comment
           *           The comment
           *
           * @return   The string representation of the counter
           *
           * @throws   IOException
           *           If IO exception occurs
           */
          @Override
          public String toString(String ts, String comment) throws IOException
          {
             return counter.toString(ts, comment);
          }

           /**
           * Return counter instance.
           *
           * @return   The counter instance
           */
          public ThreadsCpuMBean getCounter()
          {
             return counter;
          }

          /**
           * Gets the elapsed CPU time for the execution thread.
           *
           * @return   The total CPU time in nanoseconds spend by the execution thread.
           */
          @Override
          public long getThreadElapsedCpuTime()
          {
             return counter.getThreadElapsedCpuTime();
          }

          /**
           * Gets the elapsed CPU time for the given thread.
           *
           * @param    tid
           *           The given thread id
           *
           * @return   The total CPU time in nanoseconds spend by the given thread.
           */
          @Override
          public long getThreadElapsedCpuTime(long tid)
          {
             return counter.getThreadElapsedCpuTime(tid);
          }

          /**
           * Updates the elapsed CPU time spent the given thread by adding the given value.
           *
           * @param    tid
           *           The given thread id
           * @param    value
           *           The given value
           */
          @Override
          public void updateThreadElapsedCpuTime(long tid, long value)
          {
             counter.updateThreadElapsedCpuTime(tid, value);
          }

          /**
           * Gets the elapsed CPU time for the execution thread in a user mode.
           *
           * @return   The CPU time in nanoseconds spent by the execution thread in a user mode.
           */
          @Override
          public long getThreadElapsedUserTime()
          {
             return counter.getThreadElapsedUserTime();
          }

          /**
           * Gets the elapsed CPU time for the given thread in a user mode.
           *
           * @param    tid
           *           The given thread id
           *
           * @return   The CPU time in nanoseconds spent by the given thread in a user mode.
           */
          @Override
          public long getThreadElapsedUserTime(long tid)
          {
             return counter.getThreadElapsedUserTime(tid);
          }

          /**
           * Updates the elapsed CPU time spent the given thread in a user mode by adding the given value.
           *
           * @param    tid
           *           The given thread id
           * @param    value
           *           The given value
           */
         @Override
          public void updateThreadElapsedUserTime(long tid, long value)
          {
             counter.updateThreadElapsedUserTime(tid, value);
          }

         /**
          * Sets the execution thread.
          *
          * @param    tid
          *           The thread id
          */
          @Override
          public void setCurrentThread(long tid)
          {
             counter.setCurrentThread(tid);
          }
       }

       /**
        * Long values' counter
        */
       public static enum Counter
       implements OptionalCounter
       {
          /** Incomning network traffic */
          NetworkReads,
          /** Outgoing network traffic */
          NetworkWrites,

          /** Widget attributes flush count */
          WidgetAttrFlushes;

          /** Counter instance */
          private final SimpleLongCounter counter = new SimpleLongCounter();

          /**
           * Register enable/disable setter
           * @param agent
           *        enable/disable setter
           */
          @Override
          public void register(BooleanConsumer agent)
          {
             counter.register(agent);
          }

          /**
           * Update counter
           * @param value
           *        new value
           */
          @Override
          public void update(long value)
          {
             counter.update(value);
          }

          /**
           * Return the string representation of the counter
           *
           * @param ts
           *        The measurement time stamp
           * @param comment
           *        comment
           * @return string representation of the counter
           * @throws IOException
           *         If IO exception occurs
           */
          public String toString(String ts, String comment) throws IOException
          {
             return counter.toString();
          }

          /**
           * Return counter instance
           * @return counter instance
           */
         public SimpleLongCounter getCounter()
          {
             return counter;
          }
       }

       /**
        * String-long map counter
        */
       public static enum MapCounter
       implements OptionalMapCounter
       {
          /** Widget attribute enqueue count */
          WidgetAttrEnqueues,

          /** Task counter for each legacy service worker thread. */
          LegacyServiceWorkerTaskCounter,

          /** Spreadsheet command invocation nano times */
          SpreadsheetCmdNanos;

          /** Counter instance */
          private final SimpleMapCounter counter = new SimpleMapCounter();

          /**
           * Register enable/disable setter
           * @param agent
           *        enable/disable setter
           */
          @Override
          public void register(BooleanConsumer agent)
          {
             counter.register(agent);
          }

          /**
           * Update counter
           *
           * @param valueName
           *        value name to update
           * @param value
           *        new value
           */
          @Override
          public void update(String valueName, long value)
          {
             counter.update(valueName, value);
          }

          /**
           * Return the string representation of the counter
           *
           * @param    ts
           *           The measurement time stamp
           * @param    comment
           *           The comment
           *
           * @return   The string representation of the counter
           *
           * @throws   IOException
           *           If IO exception occurs
           */
          public String toString(String ts, String comment) throws IOException
          {
             return counter.toString();
          }

          /**
           * Return counter instance
           * @return counter instance
           */
          public SimpleMapCounter getCounter()
          {
             return counter;
          }
       }

       /**
        * Profiler which is able to count number of cache misses and hits of each executed query
        */
       public static enum QueryProfiler
       implements OptionalQueryProfiler
       {
          /** Query profiler */
          QueryProfiler;

          /** Profiler instance */
          private final SimpleQueryProfiler profiler = new SimpleQueryProfiler();

          /**
           * Register enable/disable setter
           * @param agent
           *        enable/disable setter
           */
          @Override
          public void register(BooleanConsumer agent)
          {
             profiler.register(agent);
          }

          /**
           * Retrieve the profiler back-end of this profiler.
           *
           * @return   The simple query profiler
           */
          public SimpleQueryProfiler getProfiler()
          {
             return profiler;
          }

          /**
           * Update the number of rows retrieved from a result set
           *
           * @param query
           *        the query for which the update is done
           * @param value
           *        new value
           */
          @Override
          public void updateRowsCount(String query, long value)
          {
             profiler.updateRowsCount(query, value);

          }

          /**
           * Update the number of cache hits of a specified query
           *
           * @param query
           *        the query for which the update is done
           * @param value
           *        the number of cache hits
           */
          @Override
          public void updateCacheHits(String query, long value)
          {
             profiler.updateCacheHits(query, value);

          }

          /**
           * Update the number of cache misses of a specified query
           *
           * @param query
           *        the query for which the update is done
           * @param value
           *        the number of cache misses
           */
          @Override
          public void updateCacheMisses(String query, long value)
          {
             profiler.updateCacheMisses(query, value);

          }

          /**
           * Update the number of nanoseconds spent in hydrate of a specified query
           *
           * @param query
           *        the query for which the update is done
           * @param value
           *        the number of nanoseconds spent in hydrate
           */
          @Override
          public void updateHydrateTotalTime(String query, long value)
          {
             profiler.updateHydrateTotalTime(query, value);
          }

          /**
           * Return the string representation of a profiled query
           *
           * @param    ts
           *           The measurement time stamp
           * @param    comment
           *           The comment
           *
           * @return   The string representation of a profiled query
           */
          public String toString(String ts, String comment)
          {
             return profiler.toString();
          }
       }

       /**
        * Counter for executed queries
        */
       public static enum QueryCounterEnum
       implements QueryCounter
       {
          /** Query counter */
          QueryCounter;

          /** Counter instance */
          private final SimpleQueryCounter queryCounter = new SimpleQueryCounter();

          /**
           * Retrieve the counter back-end of this counter.
           *
           * @return   The simple query counter
           */
          public SimpleQueryCounter getQueryCounter()
          {
             return queryCounter;
          }

          /**
           * Update the query counting configuration for a specific database.
           *
           * @param   databaseName
           *          The database to update the configuration for.
           * @param   config
           *          The new configuration object.
           */
          @Override
          public void updateConfiguration(String databaseName, QueryCountConfiguration config)
          {
             queryCounter.updateConfiguration(databaseName, config);
          }

          /**
           * Updates the count for a specific query executed on the specified database.
           *
           * @param   databaseName
           *          The name of the database on which the query was executed.
           * @param   sqlInfo
           *          The object containing the sql query informations.
           */
          @Override
          public void updateCount(String databaseName, QueryInfo sqlInfo)
          {
             queryCounter.updateCount(databaseName, sqlInfo);
          }

          /**
           * Bootstrap method to register all databases with the query counter.
           *
           * @param   databases
           *          The list of database names to register.
           */
          @Override
          public void bootstrap(List<String> databases)
          {
             queryCounter.bootstrap(databases);
          }

          /**
           * Updates the supplier for initial configuration.
           *
           * @param   supplier
           *          The function to call to get the initial configuration.
           */
          public void updateSupplier(Function<String, QueryCountConfiguration> supplier)
          {
             queryCounter.updateSupplier(supplier);
          }
       }

       /**
        * Print all counters' values to the file.
        *
        * @param   file
        *          The output file name (will be appended if exists).
        *
        * @throws  IOException
        *          If IO exception occurs
        */
       @Override
       public synchronized void dumpAll(String file) throws IOException
       {
          String annotation = getAnnotation();

          for(TimeStat ts: TimeStat.values())
          {
             ts.getCounter().print(annotation + ":" + ts.name(), file);
          }
          for(Counter c: Counter.values())
          {
             c.getCounter().print(annotation + ":" + c.name(), file);
          }
          for(MapCounter c: MapCounter.values())
          {
             c.getCounter().print(annotation + ":" + c.name(), file);
          }
          for(QueryProfiler p: QueryProfiler.values())
          {
             p.getProfiler().print(file);
          }
       }

       /** Reset all counters */
       @Override
       public synchronized void resetAll()
       {
          Arrays.stream(TimeStat.values()).map(TimeStat::getCounter).forEach(NanoCounter::reset);
          Arrays.stream(Counter.values()).map(Counter::getCounter).forEach(SimpleLongCounter::reset);
          Arrays.stream(MapCounter.values()).map(MapCounter::getCounter).forEach(SimpleLongCounter::reset);
          Arrays.stream(QueryProfiler.values()).map(QueryProfiler::getProfiler).forEach(SimpleQueryProfiler::reset);
       }

       /**
        * Enable/disable all counters
        * @param val
        *        new value of the "enable" flag
        */
       @Override
       public synchronized void enableAll(boolean val)
       {
          Arrays.stream(TimeStat.values()).map(TimeStat::getCounter).forEach(c -> c.enable(val));
          Arrays.stream(Counter.values()).map(Counter::getCounter).forEach(c -> c.enable(val));
          Arrays.stream(MapCounter.values()).map(MapCounter::getCounter).forEach(c -> c.enable(val));
          Arrays.stream(QueryProfiler.values()).map(QueryProfiler::getProfiler).forEach(c -> c.enable(val));
       }
    }