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

com.igormaznitsa.meta.common.utils.TimeGuard Maven / Gradle / Ivy

Go to download

Powerful multi-pass preprocessor to process directives situated in C-styled commentaries

There is a newer version: 7.1.2
Show newest version
/*
 * Copyright 2015 Igor Maznitsa.
 *
 * 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.igormaznitsa.meta.common.utils;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.ThreadSafe;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import com.igormaznitsa.meta.annotation.MustNotContainNull;
import com.igormaznitsa.meta.common.exceptions.UnexpectedProcessingError;
import com.igormaznitsa.meta.annotation.Weight;
import com.igormaznitsa.meta.common.exceptions.TimeViolationError;
import com.igormaznitsa.meta.common.exceptions.MetaErrorListeners;
import com.igormaznitsa.meta.annotation.Constraint;
import static com.igormaznitsa.meta.common.utils.Assertions.assertNotNull;

/**
 * Allows to detect violations of execution time for code blocks or just measure time for them. It works separately for every Thread through ThreadLocal and check stack depth to be
 * informed about current operation level.
 *
 * @since 1.0
 */
@ThreadSafe
public final class TimeGuard {

  private TimeGuard() {
  }

  /**
   * Interface for any object to be informed about time alerts.
   *
   * @since 1.0
   */
  @ThreadSafe
  @Weight(Weight.Unit.EXTRALIGHT)
  public interface TimeAlertListener extends Serializable {

    /**
     * Process time.
     *
     * @param detectedTimeDelayInMilliseconds detected time delay in milliseconds
     * @param timeData data container contains initial parameters.
     * @since 1.0
     */
    void onTimeAlert(long detectedTimeDelayInMilliseconds, @Nonnull TimeData timeData);
  }

  /**
   * Some variant of "null-device" for time alerts, it does absolutely nothing.
   *
   * @since 1.0
   */
  public static final TimeAlertListener NULL_TIME_ALERT_LISTENER = new TimeAlertListener() {
    private static final long serialVersionUID = -2291183279100986316L;

    @Override
    public void onTimeAlert(final long detectedTimeDelayInMilliseconds, final TimeData timeData) {
    }
  };

  /**
   * Data container for time watching action.
   *
   * @since 1.0
   */
  @ThreadSafe
  @Immutable
  public static final class TimeData implements Serializable {

    private static final long serialVersionUID = -2417415112571257128L;

    /**
     * Contains detected stack depth for creation.
     *
     * @since 1.0
     */
    private final int stackDepth;

    /**
     * Max allowed time delay in milliseconds.
     *
     * @since 1.0
     */
    private final long maxAllowedDelayInMilliseconds;

    /**
     * The Creation time of the data container in milliseconds.
     *
     * @since 1.0
     */
    private final long creationTimeInMilliseconds;

    /**
     * The Alert message to be provided into log or somewhere else, for time points it is ID.
     *
     * @since 1.0
     */
    private final String alertMessage;

    /**
     * Some provided processor to be called for alert.
     *
     * @since 1.0
     */
    private final TimeAlertListener alertListener;

    /**
     * The Constructor
     *
     * @param stackDepth stack depth
     * @param alertMessage alert message for time violation
     * @param maxAllowedDelayInMilliseconds max allowed time gap in milliseconds
     * @param violationListener listener for the violation alert
     * @since 1.0
     */
    @Weight(Weight.Unit.LIGHT)
    public TimeData(@Constraint("X>1") final int stackDepth, @Nonnull final String alertMessage, final long maxAllowedDelayInMilliseconds, @Nullable final TimeAlertListener violationListener) {
      this.stackDepth = stackDepth;
      this.maxAllowedDelayInMilliseconds = maxAllowedDelayInMilliseconds;
      this.creationTimeInMilliseconds = System.currentTimeMillis();
      this.alertMessage = alertMessage;
      this.alertListener = GetUtils.ensureNonNull(violationListener, NULL_TIME_ALERT_LISTENER);
    }

    /**
     * Get alert listener if provided
     *
     * @return the provided alert listener
     * @since 1.0
     */
    @Nonnull
    public TimeAlertListener getAlertListener() {
      return this.alertListener;
    }

    /**
     * Get the alert message. For time points it is ID.
     *
     * @return defined alert message.
     * @since 1.0
     */
    @Nullable
    public String getAlertMessage() {
      return this.alertMessage;
    }

    /**
     * Get the detected stack depth during the container creation.
     *
     * @return the detected stack depth
     * @since 1.0
     */
    public int getDetectedStackDepth() {
      return this.stackDepth;
    }

    /**
     * Get the creation time of the container.
     *
     * @return the creation time in milliseconds
     * @since 1.0
     */
    public long getCreationTimeInMilliseconds() {
      return this.creationTimeInMilliseconds;
    }

    /**
     * Get defined max allowed time delay in milliseconds.
     *
     * @return the max allowed time delay in milliseconds
     * @since 1.0
     */
    public long getMaxAllowedDelayInMilliseconds() {
      return this.maxAllowedDelayInMilliseconds;
    }

    /**
     * Check that the object represents a named time point.
     *
     * @return true if the object represents a time point created for statistics.
     */
    public boolean isTimePoint() {
      return this.maxAllowedDelayInMilliseconds < 0L;
    }
  }

  /**
   * Inside thread local storage of registered processors.
   *
   * @since 1.0
   */
  @MustNotContainNull
  private static final ThreadLocal> REGISTRY = new ThreadLocal>() {
    @Override
    protected List initialValue() {
      return new ArrayList();
    }
  };

  /**
   * Add a time watcher. As target of notification meta error listeners will be used.
   *
   * @param alertMessage message for time violation
   * @param maxAllowedDelayInMilliseconds max allowed delay in milliseconds for executing block
   * @see #check()
   * @see #cancelAll()
   * @see MetaErrorListeners
   * @since 1.0
   */
  @Weight(value = Weight.Unit.VARIABLE, comment = "Depends on the current call stack depth")
  // WARNING! Don't make a call from methods of the class to not break stack depth!
  public static void addGuard(@Nullable final String alertMessage, @Constraint("X>0") final long maxAllowedDelayInMilliseconds) {
    final List list = REGISTRY.get();
    list.add(new TimeData(ThreadUtils.stackDepth(), alertMessage, maxAllowedDelayInMilliseconds, null));
  }

  /**
   * Add a named time point.
   *
   * @param timePointName name for the time point
   * @param listener listener to be notified
   * @see #checkPoint(java.lang.String)
   * @since 1.0
   */
  @Weight(value = Weight.Unit.VARIABLE, comment = "Depends on the current call stack depth")
  // WARNING! Don't make a call from methods of the class to not break stack depth!
  public static void addPoint(@Nonnull final String timePointName, @Nonnull final TimeAlertListener listener) {
    final List list = REGISTRY.get();
    list.add(new TimeData(ThreadUtils.stackDepth(), timePointName, -1L, assertNotNull(listener)));
  }

  /**
   * Check named time point(s). Listener registered for the point will be notified and the point will be removed.
   *
   * @param timePointName the name of time point
   * @since 1.0
   */
  @Weight(value = Weight.Unit.VARIABLE, comment = "Depends on the current call stack depth")
  public static void checkPoint(@Nonnull final String timePointName) {
    final long time = System.currentTimeMillis();
    final int stackDepth = ThreadUtils.stackDepth();

    final List list = REGISTRY.get();
    final Iterator iterator = list.iterator();

    boolean detected = false;

    while (iterator.hasNext()) {
      final TimeData timeWatchItem = iterator.next();

      if (timeWatchItem.isTimePoint() && timeWatchItem.getDetectedStackDepth() >= stackDepth && timePointName.equals(timeWatchItem.getAlertMessage())) {
        detected |= true;
        final long detectedDelay = time - timeWatchItem.getCreationTimeInMilliseconds();
        try {
          timeWatchItem.getAlertListener().onTimeAlert(detectedDelay, timeWatchItem);
        } catch (Exception ex) {
          final UnexpectedProcessingError error = new UnexpectedProcessingError("Error during time point processing", ex);
          MetaErrorListeners.fireError(error.getMessage(), error);
        } finally {
          iterator.remove();
        }
      }
    }
    if (!detected) {
      throw new IllegalStateException("Can't find time point [" + timePointName + ']');
    }
  }

  /**
   * Process all time points for the current stack level.
   *
   * @since 1.0
   */
  @Weight(value = Weight.Unit.VARIABLE, comment = "Depends on the current call stack depth")
  public static void checkPoints() {
    final long time = System.currentTimeMillis();
    final int stackDepth = ThreadUtils.stackDepth();

    final List list = REGISTRY.get();
    final Iterator iterator = list.iterator();

    while (iterator.hasNext()) {
      final TimeData timeWatchItem = iterator.next();

      if (timeWatchItem.isTimePoint() && timeWatchItem.getDetectedStackDepth() >= stackDepth) {
        final long detectedDelay = time - timeWatchItem.getCreationTimeInMilliseconds();
        try {
          timeWatchItem.getAlertListener().onTimeAlert(detectedDelay, timeWatchItem);
        } catch (Exception ex) {
          final UnexpectedProcessingError error = new UnexpectedProcessingError("Error during time point processing", ex);
          MetaErrorListeners.fireError(error.getMessage(), error);
        } finally {
          iterator.remove();
        }
      }
    }
  }

  /**
   * Add a time watcher and provide processor of time violation.
   *
   * @param alertMessage message for time violation
   * @param maxAllowedDelayInMilliseconds max allowed delay in milliseconds for executing block
   * @param timeAlertListener alert listener to be notified, if it is null then the global one will get notification
   * @see #check()
   * @see #cancelAll()
   * @since 1.0
   */
  // WARNING! Don't make a call from methods of the class to not break stack depth!
  @Weight(value = Weight.Unit.VARIABLE, comment = "Depends on the current call stack depth")
  public static void addGuard(@Nullable
      final String alertMessage,
      @Constraint("X>0")
      final long maxAllowedDelayInMilliseconds,
      @Nullable
      final TimeAlertListener timeAlertListener
  ) {
    final List list = REGISTRY.get();
    list.add(new TimeData(ThreadUtils.stackDepth(), alertMessage, maxAllowedDelayInMilliseconds, timeAlertListener));
  }

  /**
   * Cancel all time watchers and time points globally for the current thread.
   *
   * @see #cancel()
   * @since 1.0
   */
  @Weight(Weight.Unit.NORMAL)
  public static void cancelAll() {
    final List list = REGISTRY.get();
    list.clear();
    REGISTRY.remove();
  }

  /**
   * Cancel all time watchers and time points for the current stack level.
   *
   * @see #cancelAll()
   * @since 1.0
   */
  @Weight(value = Weight.Unit.VARIABLE, comment = "Depends on the current call stack depth")
  public static void cancel() {
    final int stackDepth = ThreadUtils.stackDepth();

    final List list = REGISTRY.get();
    final Iterator iterator = list.iterator();

    while (iterator.hasNext()) {
      final TimeData timeWatchItem = iterator.next();
      if (timeWatchItem.getDetectedStackDepth() >= stackDepth) {
        iterator.remove();
      }
    }
    if (list.isEmpty()) {
      REGISTRY.remove();
    }
  }

  /**
   * Check all registered time watchers for time bound violations.
   *
   * @see #addGuard(java.lang.String, long)
   * @see #addGuard(java.lang.String, long, com.igormaznitsa.meta.common.utils.TimeGuard.TimeAlertListener)
   * @since 1.0
   */
  @Weight(value = Weight.Unit.VARIABLE, comment = "Depends on the current call stack depth")
  public static void check() {
    final long time = System.currentTimeMillis();

    final int stackDepth = ThreadUtils.stackDepth();

    final List list = REGISTRY.get();
    final Iterator iterator = list.iterator();

    while (iterator.hasNext()) {
      final TimeData timeWatchItem = iterator.next();
      if (timeWatchItem.getDetectedStackDepth() >= stackDepth) {
        final boolean timePoint = timeWatchItem.isTimePoint();
        try {
          final long detectedDelay = time - timeWatchItem.getCreationTimeInMilliseconds();
          if (timePoint) {
            try {
              timeWatchItem.getAlertListener().onTimeAlert(detectedDelay, timeWatchItem);
            } catch (Exception ex) {
              final UnexpectedProcessingError error = new UnexpectedProcessingError("Error during time point processing", ex);
              MetaErrorListeners.fireError(error.getMessage(), error);
            }
          } else if (detectedDelay > timeWatchItem.getMaxAllowedDelayInMilliseconds()) {
            final TimeAlertListener processor = timeWatchItem.getAlertListener();
            if (processor == NULL_TIME_ALERT_LISTENER) {
              MetaErrorListeners.fireError("Detected time violation without defined time alert listener", new TimeViolationError(detectedDelay, timeWatchItem));
            } else {
              try {
                processor.onTimeAlert(detectedDelay, timeWatchItem);
              } catch (Exception ex) {
                final UnexpectedProcessingError error = new UnexpectedProcessingError("Error during time alert processing", ex);
                MetaErrorListeners.fireError(error.getMessage(), error);
              }
            }
          }
        } finally {
          iterator.remove();
        }
      }
    }
    if (list.isEmpty()) {
      REGISTRY.remove();
    }
  }

  /**
   * Check that the thread local for the current thread contains time points or watchers.
   *
   * @return true if the thread local storage is empty, false otherwise
   */
  @Weight(value = Weight.Unit.NORMAL, comment = "May create list in thread local storage")
  public static boolean isEmpty() {
    final boolean result = REGISTRY.get().isEmpty();
    if (result) {
      REGISTRY.remove();
    }
    return result;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy