protected void sendVMMetrics(long epoch) {
    newEvent()
        .time(epoch)
        .service(service("jvm", "memory", "heap-usage"))
        .metric(vm.heapUsage())
        .send();
    newEvent()
        .time(epoch)
        .service(service("jvm", "memory", "non-heap-usage"))
        .metric(vm.nonHeapUsage())
        .send();
    for (Entry<String, Double> pool : vm.memoryPoolUsage().entrySet()) {
      newEvent()
          .time(epoch)
          .service(service("jvm", "memory", "pool-usage", pool.getKey()))
          .metric(pool.getValue())
          .send();
    }
    newEvent()
        .time(epoch)
        .service(service("jvm", "thread", "daemon-count"))
        .metric(vm.daemonThreadCount())
        .send();
    newEvent()
        .time(epoch)
        .service(service("jvm", "thread", "count"))
        .metric(vm.threadCount())
        .send();
    newEvent().time(epoch).service(service("jvm", "uptime")).metric(vm.uptime()).send();
    newEvent()
        .time(epoch)
        .service(service("jvm", "fd-usage"))
        .metric(vm.fileDescriptorUsage())
        .send();

    for (Entry<Thread.State, Double> entry : vm.threadStatePercentages().entrySet()) {
      newEvent()
          .time(epoch)
          .service(service("jvm", "thread", "state", entry.getKey().toString().toLowerCase()))
          .metric(entry.getValue())
          .send();
    }

    for (Entry<String, VirtualMachineMetrics.GarbageCollectorStats> entry :
        vm.garbageCollectors().entrySet()) {
      newEvent()
          .time(epoch)
          .service(service("jvm", "gc", entry.getKey(), "time"))
          .metric(entry.getValue().getTime(TimeUnit.MILLISECONDS))
          .send();
      newEvent()
          .time(epoch)
          .service(service("jvm", "gc", entry.getKey(), "runs"))
          .metric(entry.getValue().getRuns())
          .send();
    }
  }