/**
   * Return a host report based on the current state (similar to <code>gstat -a</code>).
   *
   * <p>Note: The report will not be accurate immediately as the {@link GangliaService} needs to
   * build up a model of the current state of the monitored hosts.
   *
   * @param hosts The hosts for which host reports will be returned.
   * @param reportOn The metrics to be reported for each host. The {@link IHostReport#getMetrics()}
   *     is an ordered map and will reflect the metrics in the order in which they are requested
   *     here.
   * @param comparator The comparator used to order the {@link IHostReport}s (optional).
   * @return The {@link IHostReport}s for each specified host ordered by the given {@link
   *     Comparator}.
   */
  public IHostReport[] getHostReport(
      final String[] hosts, final String[] reportOn, final Comparator<IHostReport> comparator) {

    if (reportOn == null || reportOn.length == 0) throw new IllegalArgumentException();

    //		if (comparator == null)
    //			throw new IllegalArgumentException();

    final IHostReport[] a = new IHostReport[hosts.length];

    for (int i = 0; i < a.length; i++) {

      final String hostName = hosts[i];

      // Note: This map preserves the insert order of the metrics.
      final Map<String, IGangliaMetricMessage> m =
          new LinkedHashMap<String, IGangliaMetricMessage>();

      for (String metricName : reportOn) {

        final TimestampMetricValue tmv = gangliaState.getMetric(hostName, metricName);

        if (tmv == null) {
          // No score for that metric for that host.
          continue;
        }

        final Object value = tmv.getValue();

        if (value == null) {
          // Should never happen.
          continue;
        }

        final IGangliaMetricMessage metricValue =
            metricFactory.newMetricMessage(hostName, tmv.getMetadata(), false /* spoof */, value);

        if (log.isDebugEnabled())
          log.debug(
              "host="
                  + hostName
                  + ", metric="
                  + metricName
                  + ", value="
                  + value
                  + ", record="
                  + metricValue);

        // Mock up a metric record.
        m.put(metricName /* metricName */, metricValue);
      }

      a[i] = new HostReport(hostName, m);
    }

    // Sort
    if (comparator != null) {

      Arrays.sort(a, comparator);
    }

    return a;
  }
  /**
   * {@inheritDoc}
   *
   * <p>This routine is typically invoked by {@link IGangliaMetricsCollector}s. However, you can
   * also simply invoke it directly.
   *
   * <p>Note: In order to get a nice metadata declaration for the record, the application should
   * also register an {@link IGangliaMetadataFactory}.
   *
   * @param metricName The name of the metric.
   * @param value The metric value.
   * @see #getMetadataFactory()
   */
  @Override
  public void setMetric(final String metricName, final Object value) {

    final GangliaSender sender = this.gangliaSender;

    if (sender != null) {

      try {

        if (metricName == null) {
          log.warn("Metric was emitted with no name.");
          return;
        }

        if (value == null) {
          log.warn("Metric was emitted with a null value: metricName=" + metricName);
          return;
        }

        IGangliaMetadataMessage decl;
        {

          // Look for a pre-declared metric.
          decl = gangliaState.getMetadata(metricName);

          if (decl == null) {

            // Obtain declaration.
            decl = gangliaState.getMetadataFactory().newDecl(hostName, metricName, value);

            if (decl == null) {

              log.error("Could not declare: " + metricName);
              return;
            }

            // Atomically declare/resolve.
            decl = gangliaState.putIfAbsent(decl);
          }
        }

        /*
         * Lookup the current value for the metric.
         *
         * Note: At this point we are guaranteed that a metadata
         * declaration exists so the return value must be non-null.
         */
        final TimestampMetricValue tmv =
            gangliaState.getMetric(getHostName(), decl.getMetricName());

        // Return must be non-null since declaration exists.
        assert tmv != null;

        // Should be the same declaration reference.
        assert decl == tmv.getMetadata();

        if (tmv.getTimestamp() == 0L) {

          /*
           * Send metadata record.
           *
           * TODO Since the captured metadata record might have
           * originated on another host, we should build a new
           * metadata record either now (so we send out the record
           * with our host name) or when we capture the record (as
           * part of obtaining a richer metadata record object).
           */

          sendMessage(tmv.getMetadata());
        }

        // Update the current metric value.
        if (tmv.setValue(value)) {

          /*
           * Send the metric record.
           *
           * Note: Either the metric value has never been transmitted,
           * or it has changed significantly, or TMax might expire if
           * we do not retransmit it now.
           */

          // update the timestamp when sending out the metric value.
          tmv.update();

          final IGangliaMetricMessage msg =
              metricFactory.newMetricMessage(getHostName(), decl, false /* spoof */, value);

          sendMessage(msg);
        }

      } catch (Throwable e) {

        log.warn(e, e);
      }
    }
  }