Exemple #1
0
  def _getMetricStatisticsTimeSlice(cls, period):
    """ Determine metric statistics collection time range for the maximum range
    appropriate for aggregation of the statistic values of an Autostack

    :param period: Metric period in seconds; must be multiple of 60
    :type period: integer
    :returns: time range for collecting metric statistics adjusted for integral
              number of metric periods.
    :rtype: htm.it.app.runtime.aggregator_utils.TimeRange
    """
    startTime, endTime = cloudwatch_utils.normalizeMetricCollectionTimeRange(
      startTime=None,
      endTime=None,
      period=period)

    return TimeRange(start=startTime, end=endTime)
Exemple #2
0
  def testNormalizeMetricCollectionTimeRangeWithFixedStartAndDefaultEnd(self):

    fakeUtcnow = datetime(2015, 11, 1, 12, 0, 0)

    fixedStartTime = datetime(2015, 11, 1, 9, 0, 0)

    period = 300

    datetimePatch = patch("datetime.datetime",
                          new=Mock(wraps=datetime,
                                   utcnow=Mock(side_effect=[fakeUtcnow])))

    with datetimePatch as datetimeMock:
      rangeStart, rangeEnd = (
        cloudwatch_utils.normalizeMetricCollectionTimeRange(
          startTime=fixedStartTime,
          endTime=None,
          period=period)
      )


    # Fixed start should be honored
    self.assertEqual(rangeStart, fixedStartTime)

    # Default end should be at an integral number of periods, not to exceed
    # the empirically-determine backoff from now and also limited by
    # CLOUDWATCH_MAX_DATA_RECORDS

    self.assertEqual(datetimeMock.utcnow.call_count, 1)

    rangeEndLimit = (
      fakeUtcnow -
      timedelta(seconds=cloudwatch_utils.getMetricCollectionBackoffSeconds(
        period))
    )

    expectedRangeEnd = min(
      (fixedStartTime +
       timedelta(seconds=(rangeEndLimit - fixedStartTime).total_seconds() //
                 period * period)),
      (fixedStartTime +
       timedelta(seconds=cloudwatch_utils.CLOUDWATCH_MAX_DATA_RECORDS * period))
    )

    self.assertEqual(rangeEnd, expectedRangeEnd)
  def getMetricStatistics(self, start, end):
    """ Retrieve metric data statistics for the given time range

    :param start: UTC start time of the metric data range. The start value
      is inclusive: results include datapoints with the time stamp specified. If
      set to None, the implementation will choose the start time automatically
      based on Cloudwatch metric data expiration policy (14 days at the time of
      this writing)
    :type start: datetime.datetime

    :param end: UTC end time of the metric data range. The end value is
      exclusive; results will include datapoints predating the time stamp
      specified. If set to None, will use the current UTC time
    :type start: datetime.datetime

    :returns: a dictionary with the metric's statistics
    :rtype: dict; {"min": <min-value>, "max": <max-value>}
    """
    defaultMinVal = self.MIN
    defaultMaxVal = self.MAX

    start, end = cloudwatch_utils.normalizeMetricCollectionTimeRange(
      startTime=start,
      endTime=end,
      period=self.METRIC_PERIOD)

    period = end - start
    totalSeconds = int(period.total_seconds())

    cloudStats = self._queryCloudWatchMetricStats(period=totalSeconds,
                                                  start=start,
                                                  end=end,
                                                  stats=("Maximum", "Minimum"))
    cloudStats = cloudStats[0] if cloudStats else None
    minVal, maxVal = self._normalizeMinMax(defaultMinVal, defaultMaxVal,
                                           cloudStats)

    self._log.debug("getMetricStatistics for metric %s: minVal=%g, maxVal=%g.",
                    self.METRIC_NAME, minVal, maxVal)

    return {"min": minVal, "max": maxVal}
Exemple #4
0
  def _getMetricCollectionTimeSlice(cls, startTime, period):
    """ Determine metric data collection time range.

    :param startTime: UTC start time of planned metric data collection; may be
                      None when called for the first time, in which case a start
                      time will be calculated and returned in the result (even
                      if there is not enough time for at least one period of
                      metric data collection)
    :type startTime: datetime.datetime
    :param period: Metric period in seconds; must be multiple of 60
    :type period: integer
    :returns: time range for collecting metrics adjusted for integral number of
              periods. If there is not enough time for at least one period,
              then end-time will be set equal to start-time
    :rtype: htm.it.app.runtime.aggregator_utils.TimeRange
    """
    startTime, endTime = cloudwatch_utils.normalizeMetricCollectionTimeRange(
      startTime=startTime,
      endTime=None,
      period=period)

    return TimeRange(start=startTime, end=endTime)
Exemple #5
0
  def testNormalizeMetricCollectionTimeRangeWithDefaultStartAndEnd(self):

    fakeUtcnow = datetime(2015, 11, 1, 12, 0, 0)

    datetimePatch = patch("datetime.datetime",
                          new=Mock(wraps=datetime,
                                   utcnow=Mock(side_effect=[fakeUtcnow])))

    period = 300

    with datetimePatch as datetimeMock:
      rangeStart, rangeEnd = (
        cloudwatch_utils.normalizeMetricCollectionTimeRange(
          startTime=None,
          endTime=None,
          period=period)
      )


    self.assertEqual(datetimeMock.utcnow.call_count, 1)

    self.assertEqual(
      rangeStart,
      fakeUtcnow - cloudwatch_utils.CLOUDWATCH_DATA_MAX_STORAGE_TIMEDELTA)

    # The next verification assumes this relationship
    self.assertLessEqual(
      cloudwatch_utils.CLOUDWATCH_MAX_DATA_RECORDS,
      (cloudwatch_utils.CLOUDWATCH_DATA_MAX_STORAGE_TIMEDELTA.total_seconds() /
       period)
    )

    self.assertEqual(
      rangeEnd,
      rangeStart + timedelta(
        seconds=period * cloudwatch_utils.CLOUDWATCH_MAX_DATA_RECORDS))
  def getMetricData(self, start, end):
    """ Retrieve metric data for up to the given time range

    :param start: UTC start time of the metric data range. The start value
      is inclusive: results include datapoints with the time stamp specified. If
      set to None, the implementation will choose the start time automatically
      based on Cloudwatch metric data expiration policy (14 days at the time of
      this writing)
    :type start: datetime.datetime

    :param end: UTC end time of the metric data range. The end value is
      exclusive; results will include datapoints predating the time stamp
      specified. If set to None, will use the current UTC time as end
    :type end: datetime.datetime

    :returns: A two-tuple (<data-sequence>, <next-start-time>).
      <data-sequence> is a possibly empty sequence of data points sorted by
      timestamp in ascending order. Each data point is a two-tuple of
      (<datetime timestamp>, <value>).
      <next-start-time> is a datetime.datetime object indicating the UTC start
      time to use in next call to this method.
    :rtype: tuple
    """
    period = self.METRIC_PERIOD
    stats = [self.STATISTIC]

    samples = []

    fromDate, toDate = cloudwatch_utils.normalizeMetricCollectionTimeRange(
      startTime=start,
      endTime=end,
      period=period)

    if end is None:
      end = toDate

    if toDate <= fromDate:
      self._log.warning("The requested date range=[%s..%s] is less than "
                        "period=%ss; adapter=%r", start, end, period, self)
    else:
      while fromDate < toDate:
        try:
          rawdata = self._queryCloudWatchMetricStats(
            period=period,
            start=fromDate,
            end=toDate,
            stats=stats)
        except boto.exception.BotoServerError as ex:
          if ex.status == 400 and ex.error_code == "Throttling":
            # TODO: unit-test
            raise htm.it.app.exceptions.MetricThrottleError(repr(ex))
          else:
            raise

        if rawdata:
          # Sort by "Timestamp"
          rawdata.sort(key=lambda row: row["Timestamp"])

          # Format raw data into data points and append to results
          samples.extend((e["Timestamp"], e[self.STATISTIC]) for e in rawdata)

          # Bail out once we get a batch of samples
          break

        # Try the next range
        fromDate, toDate = cloudwatch_utils.normalizeMetricCollectionTimeRange(
          startTime=toDate,
          endTime=end,
          period=period)


    if samples:
      nextCallStartTime = samples[-1][0] + datetime.timedelta(seconds=period)
    else:
      nextCallStartTime = fromDate

    return (samples, nextCallStartTime)