protected <T> void metricChanged(BaseMetricImpl<T> metric) {
    long lastTime = metric.changeTime;

    // NOTE: changeTime is only accurate for metrics that are enabled, and
    // to guarantee that it always corresponds to the value, updates to
    // enabled metrics are synchronized.
    metric.changeTime = timebase + System.nanoTime();
    metric.valueChanged = true;
    metricsDataChanged = true;

    int level;
    if ((lastTime == 0) || (metric.changeTime <= lastTime)) {
      level = 0;
    } else {
      // Longer duration -> higher level.
      double r = random.nextDouble();
      if (r == 0) {
        level = NLEVELS - 1;
      } else {
        level =
            Math.min(
                NLEVELS - 1,
                (int) (Math.log((metric.changeTime - lastTime) / r) / METRIC_LEVEL_DIVISOR));
      }
    }
    MetricLevel<T> metricLevel = metric.levels.get(level);
    MetricLevelValues values = metricLevel.values.peekLast();
    if ((values == null) || values.isFull()) {
      if (logger.isTraceEnabled()) {
        logger.trace(
            "New level {} entry at {} for {}", new Object[] {level, metric.changeTime, metric});
      }
      values = new MetricLevelValues(metric.changeTime, metric.encodeValue());
      metricLevel.values.addLast(values);
    } else {
      if (logger.isTraceEnabled()) {
        logger.trace(
            "Adding to level {} entry at {} for {}",
            new Object[] {level, metric.changeTime, metric});
      }
      values.append(metric.encodeValue(metricLevel));
    }
    metricLevel.lastValue = metric.getObject();
    metricLevel.lastTime = metric.changeTime;
  }