All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.metamx.metrics.JvmMonitor Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2012 Metamarkets Group Inc.
 *
 * Licensed 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 com.metamx.metrics;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.metamx.emitter.service.ServiceEmitter;
import com.metamx.emitter.service.ServiceMetricEvent;
import java.lang.management.BufferPoolMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryType;
import java.lang.management.MemoryUsage;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.gridkit.lab.jvm.perfdata.JStatData;
import org.gridkit.lab.jvm.perfdata.JStatData.LongCounter;
import org.gridkit.lab.jvm.perfdata.JStatData.StringCounter;
import org.gridkit.lab.jvm.perfdata.JStatData.TickCounter;

public class JvmMonitor extends FeedDefiningMonitor
{
  private final Map dimensions;
  private final long pid;

  private final GcCounters gcCounters = new GcCounters();

  public JvmMonitor()
  {
    this(ImmutableMap.of());
  }

  public JvmMonitor(Map dimensions)
  {
    this(dimensions, DEFAULT_METRICS_FEED);
  }

  public JvmMonitor(Map dimensions, String feed)
  {
    this(dimensions, feed, JvmPidDiscoverer.instance());
  }

  public JvmMonitor(Map dimensions, String feed, PidDiscoverer pidDiscoverer)
  {
    super(feed);
    Preconditions.checkNotNull(dimensions);
    this.dimensions = ImmutableMap.copyOf(dimensions);
    this.pid = Preconditions.checkNotNull(pidDiscoverer).getPid();
  }

  @Override
  public boolean doMonitor(ServiceEmitter emitter)
  {
    emitJvmMemMetrics(emitter);
    emitDirectMemMetrics(emitter);
    emitGcMetrics(emitter);

    return true;
  }

  // These metrics are going to be replaced by new jvm/gc/mem/* metrics
  @Deprecated
  private void emitJvmMemMetrics(ServiceEmitter emitter)
  {
    // I have no idea why, but jvm/mem is slightly more than the sum of jvm/pool. Let's just include
    // them both.
    final Map usages = ImmutableMap.of(
        "heap", ManagementFactory.getMemoryMXBean().getHeapMemoryUsage(),
        "nonheap", ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage()
    );
    for (Map.Entry entry : usages.entrySet()) {
      final String kind = entry.getKey();
      final MemoryUsage usage = entry.getValue();
      final ServiceMetricEvent.Builder builder = builder().setDimension("memKind", kind);
      MonitorUtils.addDimensionsToBuilder(builder, dimensions);

      emitter.emit(builder.build("jvm/mem/max", usage.getMax()));
      emitter.emit(builder.build("jvm/mem/committed", usage.getCommitted()));
      emitter.emit(builder.build("jvm/mem/used", usage.getUsed()));
      emitter.emit(builder.build("jvm/mem/init", usage.getInit()));
    }

    // jvm/pool
    for (MemoryPoolMXBean pool : ManagementFactory.getMemoryPoolMXBeans()) {
      final String kind = pool.getType() == MemoryType.HEAP ? "heap" : "nonheap";
      final MemoryUsage usage = pool.getUsage();
      final ServiceMetricEvent.Builder builder = builder()
          .setDimension("poolKind", kind)
          .setDimension("poolName", pool.getName());
      MonitorUtils.addDimensionsToBuilder(builder, dimensions);

      emitter.emit(builder.build("jvm/pool/max", usage.getMax()));
      emitter.emit(builder.build("jvm/pool/committed", usage.getCommitted()));
      emitter.emit(builder.build("jvm/pool/used", usage.getUsed()));
      emitter.emit(builder.build("jvm/pool/init", usage.getInit()));
    }
  }

  private void emitDirectMemMetrics(ServiceEmitter emitter)
  {
    for (BufferPoolMXBean pool : ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class)) {
      final ServiceMetricEvent.Builder builder = builder().setDimension("bufferpoolName", pool.getName());
      MonitorUtils.addDimensionsToBuilder(builder, dimensions);

      emitter.emit(builder.build("jvm/bufferpool/capacity", pool.getTotalCapacity()));
      emitter.emit(builder.build("jvm/bufferpool/used", pool.getMemoryUsed()));
      emitter.emit(builder.build("jvm/bufferpool/count", pool.getCount()));
    }
  }

  private void emitGcMetrics(ServiceEmitter emitter)
  {
    gcCounters.emit(emitter, dimensions);
  }

  /*
   * The following GC-related code is partially based on
   * https://github.com/aragozin/jvm-tools/blob/e0e37692648951440aa1a4ea5046261cb360df70/
   * sjk-core/src/main/java/org/gridkit/jvmtool/PerfCounterGcCpuUsageMonitor.java
   */
  private class GcCounters
  {
    private final List generations = new ArrayList<>();

    GcCounters()
    {
      // connect to itself
      final JStatData jStatData = JStatData.connect(pid);
      final Map> jStatCounters = jStatData.getAllCounters();

      generations.add(new GcGeneration(jStatCounters, 0, "young"));
      generations.add(new GcGeneration(jStatCounters, 1, "old"));
      // Removed in Java 8 but still actual for previous Java versions
      if (jStatCounters.containsKey("sun.gc.generation.2.name")) {
        generations.add(new GcGeneration(jStatCounters, 2, "perm"));
      }
    }

    void emit(ServiceEmitter emitter, Map dimensions)
    {
      for (GcGeneration generation : generations) {
        generation.emit(emitter, dimensions);
      }
    }
  }

  private class GcGeneration
  {
    private final String name;
    private final GcGenerationCollector collector;
    private final List spaces = new ArrayList<>();

    GcGeneration(Map> jStatCounters, long genIndex, String name)
    {
      this.name = name.toLowerCase();

      long spacesCount = ((JStatData.LongCounter) jStatCounters.get(
          String.format("sun.gc.generation.%d.spaces", genIndex)
      )).getLong();
      for (long spaceIndex = 0; spaceIndex < spacesCount; spaceIndex++) {
        spaces.add(new GcGenerationSpace(jStatCounters, genIndex, spaceIndex));
      }

      if (jStatCounters.containsKey(String.format("sun.gc.collector.%d.name", genIndex))) {
        collector = new GcGenerationCollector(jStatCounters, genIndex);
      } else {
        collector = null;
      }
    }

    void emit(ServiceEmitter emitter, Map dimensions)
    {
      ImmutableMap.Builder dimensionsCopyBuilder = ImmutableMap
          .builder()
          .putAll(dimensions)
          .put("gcGen", new String[]{name});

      if (collector != null) {
        dimensionsCopyBuilder.put("gcName", new String[]{collector.name});
      }

      Map dimensionsCopy = dimensionsCopyBuilder.build();

      if (collector != null) {
        collector.emit(emitter, dimensionsCopy);
      }

      for (GcGenerationSpace space : spaces) {
        space.emit(emitter, dimensionsCopy);
      }
    }
  }

  private class GcGenerationCollector
  {
    private final String name;
    private final LongCounter invocationsCounter;
    private final TickCounter cpuCounter;
    private long lastInvocations = 0;
    private long lastCpuNanos = 0;

    GcGenerationCollector(Map> jStatCounters, long genIndex)
    {
      String collectorKeyPrefix = String.format("sun.gc.collector.%d", genIndex);

      String nameKey = String.format("%s.name", collectorKeyPrefix);
      StringCounter nameCounter = (StringCounter) jStatCounters.get(nameKey);
      name = getReadableName(nameCounter.getString());

      invocationsCounter = (LongCounter) jStatCounters.get(String.format("%s.invocations", collectorKeyPrefix));
      cpuCounter = (TickCounter) jStatCounters.get(String.format("%s.time", collectorKeyPrefix));
    }

    void emit(ServiceEmitter emitter, Map dimensions)
    {
      final ServiceMetricEvent.Builder builder = builder();
      MonitorUtils.addDimensionsToBuilder(builder, dimensions);

      long newInvocations = invocationsCounter.getLong();
      emitter.emit(builder.build("jvm/gc/count", newInvocations - lastInvocations));
      lastInvocations = newInvocations;

      long newCpuNanos = cpuCounter.getNanos();
      emitter.emit(builder.build("jvm/gc/cpu", newCpuNanos - lastCpuNanos));
      lastCpuNanos = newCpuNanos;
    }

    private String getReadableName(String name)
    {
      switch (name) {
        // Young gen
        case "Copy":
          return "serial";
        case "PSScavenge":
          return "parallel";
        case "PCopy":
          return "cms";
        case "G1 incremental collections":
          return "g1";

        // Old gen
        case "MCS":
          return "serial";
        case "PSParallelCompact":
          return "parallel";
        case "CMS":
          return "cms";
        case "G1 stop-the-world full collections":
          return "g1";

        default:
          return name;
      }
    }
  }

  private class GcGenerationSpace
  {
    private final String name;

    private final LongCounter maxCounter;
    private final LongCounter capacityCounter;
    private final LongCounter usedCounter;
    private final LongCounter initCounter;

    GcGenerationSpace(Map> jStatCounters, long genIndex, long spaceIndex)
    {
      String spaceKeyPrefix = String.format("sun.gc.generation.%d.space.%d", genIndex, spaceIndex);

      String nameKey = String.format("%s.name", spaceKeyPrefix);
      StringCounter nameCounter = (StringCounter) jStatCounters.get(nameKey);
      name = nameCounter.toString().toLowerCase();

      maxCounter = (LongCounter) jStatCounters.get(String.format("%s.maxCapacity", spaceKeyPrefix));
      capacityCounter = (LongCounter) jStatCounters.get(String.format("%s.capacity", spaceKeyPrefix));
      usedCounter = (LongCounter) jStatCounters.get(String.format("%s.used", spaceKeyPrefix));
      initCounter = (LongCounter) jStatCounters.get(String.format("%s.initCapacity", spaceKeyPrefix));
    }

    void emit(ServiceEmitter emitter, Map dimensions)
    {
      final ServiceMetricEvent.Builder builder = builder();
      MonitorUtils.addDimensionsToBuilder(builder, dimensions);

      builder.setDimension("gcGenSpaceName", name);

      emitter.emit(builder.build("jvm/gc/mem/max", maxCounter.getLong()));
      emitter.emit(builder.build("jvm/gc/mem/capacity", capacityCounter.getLong()));
      emitter.emit(builder.build("jvm/gc/mem/used", usedCounter.getLong()));
      emitter.emit(builder.build("jvm/gc/mem/init", initCounter.getLong()));
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy