ThreadsCpuCounter.java

/*
** Module   : ThreadsCpuCounter.java
** Abstract : Counter for measuring CPU usage by threads
**
** Copyright (c) 2021, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
** 001 SBI 20211104 Implemented the jmx bean for measuring CPU utilization by threads.
*/ 

/*
** 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.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.function.*;

/** Represents jmx bean for measuring CPU utilization by threads */
public class ThreadsCpuCounter 
extends NanoCounter
implements ThreadsCpuMBean
{
   /** Creates long counters */
   private static final Function<Long, AtomicLong> COUNTER_CREATOR = k -> new AtomicLong(0);
   
   /** The management interface for the JVM thread system */
   private final ThreadMXBean jmxBean = ManagementFactory.getThreadMXBean();
   
   /** Total CPU times */
   private final Map<Long, AtomicLong> cpuTotals = new ConcurrentHashMap<>();
   
   /** Total CPU times in a user mode */
   private final Map<Long, AtomicLong> cpuUserTotals = new ConcurrentHashMap<>();
   
   /** Hold the current thread id for which this measurement is planned */
   private long currentThreadId;
   
   /** Reset counters */
   @Override
   public synchronized void reset()
   {
      currentThreadId = 0;
      cpuTotals.clear();
      cpuUserTotals.clear();
      super.reset();
   }

   /**
    * Converts nanoseconds to milliseconds
    * 
    * @param    ns
    *           The given time interval in nanoseconds
    *           
    * @return   The time in ms
    */
   private static long ms(long ns)
   {
      return TimeUnit.NANOSECONDS.toMillis(ns);
   }

   private StringBuilder addTheadInfo(long tid, StringBuilder buffer)
   {
      if (buffer == null)
      {
         buffer = new StringBuilder();
      }
      ThreadInfo threadInfo = jmxBean.getThreadInfo(tid);
      if (threadInfo != null)
      {
         buffer.append(threadInfo.toString());
      }
      buffer.append("Thread #").append(tid).append(":").append(" ");
      long count = getCount();
      buffer.append("CPU=").append(ms(getThreadElapsedCpuTime(tid))).append("/").append(count).append(" ");
      buffer.append("USER=").append(ms(getThreadElapsedUserTime(tid))).append("/").append(count);
      buffer.append("\n\r");
      
      return buffer;
   }
   /**
    * 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
   {
      StringBuilder buffer = new StringBuilder();
      buffer.append(super.toString(ts, comment));
      buffer.append("\n\r");
      
      if (isEnabled())
      {
         addTheadInfo(currentThreadId, buffer);
         
         for (Long tid : cpuTotals.keySet())
         {
            if (tid != currentThreadId)
            {
               addTheadInfo(tid, buffer);
            }
         }
      }
      
      return buffer.toString();
   }

   /**
    * 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 cpuTotals.computeIfAbsent(tid, COUNTER_CREATOR).get();
   }

   /**
    * 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 cpuUserTotals.computeIfAbsent(tid, COUNTER_CREATOR).get();
   }

   /**
    * 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)
   {
      cpuTotals.computeIfAbsent(tid, COUNTER_CREATOR).addAndGet(value);
   }

   /**
    * 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)
   {
      cpuUserTotals.computeIfAbsent(tid, COUNTER_CREATOR).addAndGet(value);
   }

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

   /**
    * 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()
   {
      if (currentThreadId != 0)
      {
         return getThreadElapsedCpuTime(currentThreadId);
      }
      
      return 0;
   }

   /**
    * 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()
   {
      if (currentThreadId != 0)
      {
         return getThreadElapsedUserTime(currentThreadId);
      }
      
      return 0;
   }

}