package com.etsy.statsd.profiler.profilers;

import com.google.common.collect.ImmutableMap;

import com.etsy.statsd.profiler.Arguments;
import com.etsy.statsd.profiler.Profiler;
import com.etsy.statsd.profiler.reporter.Reporter;

import java.lang.management.ManagementFactory;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;

/**
 * This profiler retrieves CPU values for the JVM and System from the "OperatingSystem" JMX Bean.
 * <p>
 * This profiler relies on a JMX bean that might not be available in all JVM implementations.
 * We know for sure it's available in Sun/Oracle's JRE 7+, but there are no guarantees it
 * will remain there for the foreseeable future.
 *
 * @see <a href="http://stackoverflow.com/questions/3044841/cpu-usage-mbean-on-sun-jvm">StackOverflow post</a>
 *
 * @author Alejandro Rivera
 */
public class CPULoadProfiler extends Profiler {

  public static final long PERIOD = 10;
  private static final Map<String, String> ATTRIBUTES_MAP = ImmutableMap.of("ProcessCpuLoad", "cpu.jvm",
                                                                            "SystemCpuLoad",  "cpu.system");

  private AttributeList list;

  public CPULoadProfiler(Reporter reporter, Arguments arguments) {
    super(reporter, arguments);
    try {
      MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
      ObjectName os = ObjectName.getInstance("java.lang:type=OperatingSystem");
      list = mbs.getAttributes(os, ATTRIBUTES_MAP.keySet().toArray(new String[ATTRIBUTES_MAP.size()]));
    } catch (InstanceNotFoundException | ReflectionException | MalformedObjectNameException e) {
      list = null;
    }

  }

  /**
   * Profile memory usage and GC statistics
   */
  @Override
  public void profile() {
    recordStats();
  }

  @Override
  public void flushData() {
    recordStats();
  }

  @Override
  public long getPeriod() {
    return PERIOD;
  }

  @Override
  public TimeUnit getTimeUnit() {
    return TimeUnit.SECONDS;
  }

  @Override
  protected void handleArguments(Arguments arguments) { /* No arguments needed */ }

  /**
   * Records all memory statistics
   */
  private void recordStats() {
    if (list == null) {
      return;
    }

    Attribute att;
    Double value;
    String metric;
    for (Object o : list) {
      att = (Attribute) o;
      value = (Double) att.getValue();

      if (value == null || value == -1.0) {
        continue;
      }

      metric = ATTRIBUTES_MAP.get(att.getName());
      if (metric == null) {
        continue;
      }

      value = ((int) (value * 1000)) / 10.0d; // 0-100 with 1-decimal precision
      recordGaugeValue(metric, value);
    }
  }
}