code: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You 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 org.apache.skywalking.oap.server.core.analysis.metrics;
  
  import lombok.EqualsAndHashCode;
  import lombok.Getter;
  import lombok.Setter;
  import org.apache.skywalking.oap.server.core.analysis.TimeBucket;
  import org.apache.skywalking.oap.server.core.remote.data.StreamData;
  import org.apache.skywalking.oap.server.core.storage.StorageData;
  import org.apache.skywalking.oap.server.core.storage.StorageID;
  import org.apache.skywalking.oap.server.core.storage.annotation.Column;
  
  /**
   * Metrics represents the statistic data, which analysis by OAL script or hard code. It has the lifecycle controlled by
   * TTL(time to live).
   */
  @EqualsAndHashCode(of = {
      "timeBucket"
  })
  public abstract class Metrics extends StreamData implements StorageData {
      public static final String ENTITY_ID = "entity_id";
      public static final String ID = "id";
  
      /**
       * Time attribute
       */
      @Getter
      @Setter
      @Column(name = TIME_BUCKET)
      private long timeBucket;
  
      /**
       * The last update timestamp of the cache.
       * The `update` means it is combined with the new metrics. This update doesn't mean the database level update ultimately.
       */
      @Getter
      private long lastUpdateTimestamp = 0L;
  
      /**
       * Merge the given metrics instance, these two must be the same metrics type.
       *
       * @param metrics to be merged
       * @return {@code true} if the combined metrics should be continuously processed. {@code false} means it should be
       * abandoned, and the implementation needs to keep the data unaltered in this case.
       */
      public abstract boolean combine(Metrics metrics);
  
      /**
       * Calculate the metrics final value when required.
       */
      public abstract void calculate();
  
      /**
       * Downsampling the metrics to hour precision.
       *
       * @return the metrics in hour precision in the clone mode.
       */
      public abstract Metrics toHour();
  
      /**
       * Downsampling the metrics to day precision.
       *
       * @return the metrics in day precision in the clone mode.
       */
      public abstract Metrics toDay();
  
      /**
       * Set the last update timestamp
       *
       * @param timestamp last update timestamp
       */
      public void setLastUpdateTimestamp(long timestamp) {
          lastUpdateTimestamp = timestamp;
      }
  
      /**
       * @param timestamp        of current time
       * @param expiredThreshold represents the duration between last update time and the time point removing from cache.
       * @return true means this metrics should be removed from cache.
       */
      public boolean isExpired(long timestamp, long expiredThreshold) {
          return timestamp - lastUpdateTimestamp > expiredThreshold;
      }
  
      public long toTimeBucketInHour() {
          if (isMinuteBucket()) {
              return timeBucket / 100;
          } else {
              throw new IllegalStateException("Current time bucket is not in minute dimensionality");
          }
      }
  
      public long toTimeBucketInDay() {
          if (isMinuteBucket()) {
              return timeBucket / 10000;
          } else if (isHourBucket()) {
              return timeBucket / 100;
          } else {
              throw new IllegalStateException("Current time bucket is not in minute dimensionality");
          }
      }
  
      /**
       * Always get the duration for this time bucket in minute.
       */
      protected long getDurationInMinute() {
          if (isMinuteBucket()) {
              return 1;
          } else if (isHourBucket()) {
              return 60;
          } else if (isDayBucket()) {
              return 24 * 60;
          }
          throw new IllegalStateException("Time bucket (" + timeBucket + ") can't be recognized.");
      }
  
      private boolean isMinuteBucket() {
          return TimeBucket.isMinuteBucket(timeBucket);
      }
  
      private boolean isHourBucket() {
          return TimeBucket.isHourBucket(timeBucket);
      }
  
      private boolean isDayBucket() {
          return TimeBucket.isDayBucket(timeBucket);
      }
  
      private volatile StorageID id;
  
      @Override
      public StorageID id() {
          if (id == null) {
              id = id0();
          }
          return id;
      }
  
      protected abstract StorageID id0();
  }
  