Exemplo n.º 1
0
  def deleteMetricByName(self, metricName):
    """ Delete both metric and corresponding model (if any)

    NOTE: this method is specific to HTM Metrics

    :param metricName: name of the HTM metric
    :type metricName: string

    :raises htmengine.exceptions.ObjectNotFoundError: if the requested metric
      doesn't exist
    """

    with self.connectionFactory() as conn:
      metricObj = repository.retryOnTransientErrors(
        repository.getCustomMetricByName)(conn,
                                          metricName,
                                          fields=[schema.metric.c.uid,
                                                  schema.metric.c.status])

    if metricObj.status != MetricStatus.UNMONITORED:
      self.unmonitorMetric(metricObj.uid)

    # Delete the metric from the database
    with self.connectionFactory() as conn:
      repository.retryOnTransientErrors(repository.deleteMetric)(
        conn,
        metricObj.uid)

    self._log.info("HTM Metric deleted: metric=%s; name=%s",
                   metricObj.uid, metricName)
Exemplo n.º 2
0
    def deleteMetricByName(self, metricName):
        """ Delete both metric and corresponding model (if any)

    NOTE: this method is specific to HTM Metrics

    :param metricName: name of the HTM metric
    :type metricName: string

    :raises htmengine.exceptions.ObjectNotFoundError: if the requested metric
      doesn't exist
    """

        with self.connectionFactory() as conn:
            metricObj = repository.retryOnTransientErrors(
                repository.getCustomMetricByName)(
                    conn,
                    metricName,
                    fields=[schema.metric.c.uid, schema.metric.c.status])

        if metricObj.status != MetricStatus.UNMONITORED:
            self.unmonitorMetric(metricObj.uid)

        # Delete the metric from the database
        with self.connectionFactory() as conn:
            repository.retryOnTransientErrors(repository.deleteMetric)(
                conn, metricObj.uid)

        self._log.info("HTM Metric deleted: metric=%s; name=%s", metricObj.uid,
                       metricName)
Exemplo n.º 3
0
  def importModel(self, spec):
    """ Import a model

    :param spec: datasource-specific value created by `exportModel`
    :type spec: dict

    ::

        {
          "datasource": "custom",

          "metricSpec": {
            "metric": "prod.web.14.memory",
            "unit": "Count" # optional
          },

          # Optional model params
          "modelParams": {
            "min": min-value,  # optional
            "max": max-value  # optional
          },

          "data": [[value, "2014-07-17 01:36:48"], ...]  # optional
        }

    :returns: datasource-specific unique metric identifier
    """

    try:
      metricId = self.createMetric(spec["metricSpec"]["metric"])
    except app_exceptions.MetricAlreadyExists as e:
      metricId = e.uid

    with self.connectionFactory() as conn:
      metricObj = repository.retryOnTransientErrors(repository.getMetric)(
        conn, metricId)
      if metricObj.status != MetricStatus.UNMONITORED:
        self._log.info("importModel: metric is already monitored: %r",
                       metricObj)
        return metricId

      # Add data
      data = spec.get("data")
      if data:
        if repository.getMetricDataCount(conn, metricId) == 0:
          repository.retryOnTransientErrors(repository.addMetricData)(conn,
                                                                      metricId,
                                                                      data)

    # Start monitoring
    modelSpec = spec.copy()
    modelSpec.pop("data", None)
    return self.monitorMetric(modelSpec)
Exemplo n.º 4
0
    def importModel(self, spec):
        """ Import a model

    :param spec: datasource-specific value created by `exportModel`
    :type spec: dict

    ::

        {
          "datasource": "custom",

          "metricSpec": {
            "metric": "prod.web.14.memory",
            "unit": "Count" # optional
          },

          # Optional model params
          "modelParams": {
            "min": min-value,  # optional
            "max": max-value  # optional
          },

          "data": [[value, "2014-07-17 01:36:48"], ...]  # optional
        }

    :returns: datasource-specific unique metric identifier
    """

        try:
            metricId = self.createMetric(spec["metricSpec"]["metric"])
        except app_exceptions.MetricAlreadyExists as e:
            metricId = e.uid

        with self.connectionFactory() as conn:
            metricObj = repository.retryOnTransientErrors(
                repository.getMetric)(conn, metricId)
            if metricObj.status != MetricStatus.UNMONITORED:
                self._log.info("importModel: metric is already monitored: %r",
                               metricObj)
                return metricId

            # Add data
            data = spec.get("data")
            if data:
                if repository.getMetricDataCount(conn, metricId) == 0:
                    repository.retryOnTransientErrors(
                        repository.addMetricData)(conn, metricId, data)

        # Start monitoring
        modelSpec = spec.copy()
        modelSpec.pop("data", None)
        return self.monitorMetric(modelSpec)
Exemplo n.º 5
0
    def exportModel(self, metricId):
        """ Export the given model

    :param metricId: datasource-specific unique metric identifier

    :returns: model-export specification for HTM model
    :rtype: dict

    ::

        {
          "datasource": "custom",

          "metricSpec": {
            "metric": "prod.web.14.memory",
            "unit": "Count" # optional
          },

          # Optional model params
          "modelParams": {
            "min": min-value,  # optional
            "max": max-value  # optional
          },

          "data": [[value, datetime.datetime], ...]  # optional
        }

    :raises htmengine.exceptions.ObjectNotFoundError: if metric with the
      referenced metric uid doesn't exist
    """
        with self.connectionFactory() as conn:
            metricObj = repository.retryOnTransientErrors(
                repository.getMetric)(conn, metricId)

            if metricObj.datasource != self._DATASOURCE:
                raise TypeError("exportModel: not an HTM metric=%r" %
                                (metricObj, ))
            data = repository.getMetricData(
                conn,
                metricId,
                fields=[
                    schema.metric_data.c.metric_value,
                    schema.metric_data.c.timestamp
                ],
                fromTimestamp=datetime.datetime.utcnow() -
                datetime.timedelta(days=14))

        modelSpec = htmengine.utils.jsonDecode(metricObj.parameters)
        modelSpec["data"] = list(data)

        return modelSpec
Exemplo n.º 6
0
  def exportModel(self, metricId):
    """ Export the given model

    :param metricId: datasource-specific unique metric identifier

    :returns: model-export specification for HTM model
    :rtype: dict

    ::

        {
          "datasource": "custom",

          "metricSpec": {
            "metric": "prod.web.14.memory",
            "unit": "Count" # optional
          },

          # Optional model params
          "modelParams": {
            "min": min-value,  # optional
            "max": max-value  # optional
          },

          "data": [[value, datetime.datetime], ...]  # optional
        }

    :raises htmengine.exceptions.ObjectNotFoundError: if metric with the
      referenced metric uid doesn't exist
    """
    with self.connectionFactory() as conn:
      metricObj = repository.retryOnTransientErrors(repository.getMetric)(
        conn, metricId)

      if metricObj.datasource != self._DATASOURCE:
        raise TypeError("exportModel: not an HTM metric=%r"
                        % (metricObj,))
      data = repository.getMetricData(
        conn,
        metricId,
        fields=[schema.metric_data.c.metric_value,
                schema.metric_data.c.timestamp],
        fromTimestamp=datetime.datetime.utcnow() - datetime.timedelta(days=14))

    modelSpec = htmengine.utils.jsonDecode(metricObj.parameters)
    modelSpec["data"] = list(data)

    return modelSpec
Exemplo n.º 7
0
    def _getMetricStatistics(self, metricId):
        """ Get metric data statistics

    :param metricId: unique identifier of the metric row

    :returns: a dictionary with the metric's statistics
    :rtype: dict; {"min": <min-value>, "max": <max-value>}

    :raises htmengine.exceptions.ObjectNotFoundError: if metric with the
      referenced metric uid doesn't exist

    :raises htmengine.exceptions.MetricStatisticsNotReadyError:
    """
        with self.connectionFactory() as conn:
            stats = repository.retryOnTransientErrors(
                repository.getMetricStats)(conn, metricId)

        minVal = stats["min"]
        maxVal = stats["max"]

        # Handle an edge case which should almost never happen
        # (borrowed from legacy custom adapter logic)
        if maxVal <= minVal:
            self._log.warn(
                "Max encoder value (%g) is not greater than min (%g).",
                maxVal if maxVal is not None else float("nan"),
                minVal if minVal is not None else float("nan"))
            maxVal = minVal + 1

        # Add a 20% buffer on both ends of the range
        # (borrowed from legacy custom adapter)
        buff = (maxVal - minVal) * 0.2
        minVal -= buff
        maxVal += buff

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

        return {"max": maxVal, "min": minVal}
Exemplo n.º 8
0
  def _getMetricStatistics(self, metricId):
    """ Get metric data statistics

    :param metricId: unique identifier of the metric row

    :returns: a dictionary with the metric's statistics
    :rtype: dict; {"min": <min-value>, "max": <max-value>}

    :raises htmengine.exceptions.ObjectNotFoundError: if metric with the
      referenced metric uid doesn't exist

    :raises htmengine.exceptions.MetricStatisticsNotReadyError:
    """
    with self.connectionFactory() as conn:
      stats = repository.retryOnTransientErrors(repository.getMetricStats)(
        conn, metricId)

    minVal = stats["min"]
    maxVal = stats["max"]

    # Handle an edge case which should almost never happen
    # (borrowed from legacy custom adapter logic)
    if maxVal <= minVal:
      self._log.warn("Max encoder value (%g) is not greater than min (%g).",
                     maxVal if maxVal is not None else float("nan"),
                     minVal if minVal is not None else float("nan"))
      maxVal = minVal + 1

    # Add a 20% buffer on both ends of the range
    # (borrowed from legacy custom adapter)
    buff = (maxVal - minVal) * 0.2
    minVal -= buff
    maxVal += buff

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

    return {"max": maxVal,
            "min": minVal}
Exemplo n.º 9
0
  def monitorMetric(self, modelSpec):
    """ Start monitoring a metric; perform model creation logic specific to
    custom metrics.

    Start the model if possible: this will happen if modelParams includes both
    "min" and "max" or there is enough data to estimate them. Alternatively,
    users may pass in a full set of model parameters.

    :param modelSpec: model specification for HTM model; per
        ``model_spec_schema.json`` with the ``metricSpec`` property per
        ``custom_metric_spec_schema.json``
    :type modelSpec: dict

    ::
      1st variant: `uid` is the unique id of an existing metric;
        # TODO: it would be preferable to punt on this variant, and refer
        #  to custom metric by name in modelSpec for consistency with
        # import/export. Web GUI uses metric name; some tests use this variant,
        # though.
        {
          "datasource": "custom",

          "metricSpec": {
            "uid": "4a833e2294494b4fbc5004e03bad45b6",
            "unit": "Count",  # optional
            "resource": "prod.web1",  # optional
            "userInfo": {"symbol": "<TICKER_SYMBOL>"} # optional
          },

          # Optional model params
          "modelParams": {
            "min": min-value,  # optional
            "max": max-value  # optional
          }
        }

    ::

      2nd variant: `metric` is the unique name of the metric; a new custom
      metric row will be created with this name, if it doesn't exit
        {
          "datasource": "custom",

          "metricSpec": {
            "metric": "prod.web.14.memory",
            "unit": "Count",  # optional
            "resource": "prod.web1",  # optional
            "userInfo": {"symbol": "<TICKER_SYMBOL>"} # optional
          },

          # Optional model params
          "modelParams": {
            "min": min-value,  # optional
            "max": max-value  # optional
          }
        }

    :returns: datasource-specific unique model identifier

    :raises ValueError: if finds something invalid in arg

    :raises TypeError: if metric with the referenced uid is not a custom metric

    :raises htmengine.exceptions.ObjectNotFoundError: if referenced metric
      doesn't exist

    :raises htmengine.exceptions.MetricNotSupportedError: if requested metric
      isn't supported

    :raises htmengine.exceptions.MetricAlreadyMonitored: if the metric is
      already being monitored
    """
    metricSpec = modelSpec["metricSpec"]
    with self.connectionFactory() as conn:
      if "uid" in metricSpec:
        # Via metric ID
        metricId = metricSpec["uid"]
        # Convert modelSpec to canonical form
        modelSpec = copy.deepcopy(modelSpec)
        modelSpec["metricSpec"].pop("uid")
        modelSpec["metricSpec"]["metric"] = (
          repository.retryOnTransientErrors(repository.getMetric)(
            conn, metricId).name)
      elif "metric" in metricSpec:
        # Via metric name
        try:
          # Create the metric, if needed
          metricId = repository.retryOnTransientErrors(self._createMetric)(
            conn, metricSpec["metric"])
        except app_exceptions.MetricAlreadyExists as e:
          # It already existed
          metricId = e.uid
      else:
        raise ValueError(
          "Neither uid nor metric name present in metricSpec; modelSpec=%r"
          % (modelSpec,))

    if "completeModelParams" in modelSpec:
      # Attempt to build swarm params from complete model params if present
      swarmParams = (
        scalar_metric_utils.generateSwarmParamsFromCompleteModelParams(
          modelSpec))
    else:
      # Generate swarm params from specified metric min and max or estimate them
      modelParams = modelSpec.get("modelParams", dict())
      minVal = modelParams.get("min")
      maxVal = modelParams.get("max")
      minResolution = modelParams.get("minResolution")
      enableClassifier = modelParams.get("enableClassifier", False)
      if (minVal is None) != (maxVal is None):
        raise ValueError(
          "min and max params must both be None or non-None; metric=%s; "
          "modelSpec=%r" % (metricId, modelSpec,))

      # Start monitoring
      if minVal is None or maxVal is None:
        minVal = maxVal = None

        with self.connectionFactory() as conn:
          numDataRows = repository.retryOnTransientErrors(
            repository.getMetricDataCount)(conn, metricId)

        if numDataRows >= scalar_metric_utils.MODEL_CREATION_RECORD_THRESHOLD:
          try:
            stats = self._getMetricStatistics(metricId)
            self._log.info("monitorMetric: trigger numDataRows=%d, stats=%s",
                           numDataRows, stats)
            minVal = stats["min"]
            maxVal = stats["max"]
          except app_exceptions.MetricStatisticsNotReadyError:
            pass

      stats = {"min": minVal, "max": maxVal, "minResolution": minResolution}
      self._log.debug("monitorMetric: metric=%s, stats=%r", metricId, stats)

      swarmParams = scalar_metric_utils.generateSwarmParams(stats,
                                                            enableClassifier)

    self._startMonitoringWithRetries(metricId, modelSpec, swarmParams)

    return metricId
Exemplo n.º 10
0
    def monitorMetric(self, modelSpec):
        """ Start monitoring a metric; perform model creation logic specific to
    custom metrics.

    Start the model if possible: this will happen if modelParams includes both
    "min" and "max" or there is enough data to estimate them.

    :param modelSpec: model specification for HTM model; per
        ``model_spec_schema.json`` with the ``metricSpec`` property per
        ``custom_metric_spec_schema.json``
    :type modelSpec: dict

    ::
      1st variant: `uid` is the unique id of an existing metric;
        # TODO: it would be preferable to punt on this variant, and refer
        #  to custom metric by name in modelSpec for consistency with
        # import/export. Web GUI uses metric name; some tests use this variant,
        # though.
        {
          "datasource": "custom",

          "metricSpec": {
            "uid": "4a833e2294494b4fbc5004e03bad45b6",
            "unit": "Count",  # optional
            "resource": "prod.web1",  # optional
            "userInfo": {"symbol": "<TICKER_SYMBOL>"} # optional
          },

          # Optional model params
          "modelParams": {
            "min": min-value,  # optional
            "max": max-value  # optional
          }
        }

    ::

      2nd variant: `metric` is the unique name of the metric; a new custom
      metric row will be created with this name, if it doesn't exit
        {
          "datasource": "custom",

          "metricSpec": {
            "metric": "prod.web.14.memory",
            "unit": "Count",  # optional
            "resource": "prod.web1",  # optional
            "userInfo": {"symbol": "<TICKER_SYMBOL>"} # optional
          },

          # Optional model params
          "modelParams": {
            "min": min-value,  # optional
            "max": max-value  # optional
          }
        }

    :returns: datasource-specific unique model identifier

    :raises ValueError: if finds something invalid in arg

    :raises TypeError: if metric with the referenced uid is not a custom metric

    :raises htmengine.exceptions.ObjectNotFoundError: if referenced metric
      doesn't exist

    :raises htmengine.exceptions.MetricNotSupportedError: if requested metric
      isn't supported

    :raises htmengine.exceptions.MetricAlreadyMonitored: if the metric is already
      being monitored
    """
        metricSpec = modelSpec["metricSpec"]

        with self.connectionFactory() as conn:
            if "uid" in metricSpec:
                # Via metric ID
                metricId = metricSpec["uid"]
                # Convert modelSpec to canonical form
                modelSpec = copy.deepcopy(modelSpec)
                modelSpec["metricSpec"].pop("uid")
                modelSpec["metricSpec"]["metric"] = (
                    repository.retryOnTransientErrors(repository.getMetric)(
                        conn, metricId).name)
            elif "metric" in metricSpec:
                # Via metric name
                try:
                    # Crete the metric, if needed
                    metricId = repository.retryOnTransientErrors(
                        self._createMetric)(conn, metricSpec["metric"])
                except app_exceptions.MetricAlreadyExists as e:
                    # It already existed
                    metricId = e.uid
            else:
                raise ValueError(
                    "Neither uid nor metric name present in metricSpec; modelSpec=%r"
                    % (modelSpec, ))

            modelParams = modelSpec.get("modelParams", dict())
            minVal = modelParams.get("min")
            maxVal = modelParams.get("max")
            minResolution = modelParams.get("minResolution")
            if (minVal is None) != (maxVal is None):
                raise ValueError(
                    "min and max params must both be None or non-None; metric=%s; "
                    "modelSpec=%r" % (
                        metricId,
                        modelSpec,
                    ))

        # Start monitoring
        if minVal is None or maxVal is None:
            minVal = maxVal = None

            with self.connectionFactory() as conn:
                numDataRows = repository.retryOnTransientErrors(
                    repository.getMetricDataCount)(conn, metricId)

            if numDataRows >= scalar_metric_utils.MODEL_CREATION_RECORD_THRESHOLD:
                try:
                    stats = self._getMetricStatistics(metricId)
                    self._log.info(
                        "monitorMetric: trigger numDataRows=%d, stats=%s",
                        numDataRows, stats)
                    minVal = stats["min"]
                    maxVal = stats["max"]
                except app_exceptions.MetricStatisticsNotReadyError:
                    pass

        stats = {"min": minVal, "max": maxVal, "minResolution": minResolution}
        self._log.debug("monitorMetric: metric=%s, stats=%r", metricId, stats)

        swarmParams = scalar_metric_utils.generateSwarmParams(stats)

        self._startMonitoringWithRetries(metricId, modelSpec, swarmParams)

        return metricId