Metric Plugins¶

Understand can display tables and charts built from metric values. Additional metrics can be provided to Understand by writing a metric plugin. A sample plugin is below.

The parameter metric passed to test_* and value is an Metric object. A metrics plugin can provide multiple metrics. Use the id method to find the requested metric id. The database is also available.

Important: If the plugin needs to know what other metrics are available for an entity, architecture, or database, it must use the metric parameter’s list method. It is not safe to call any other methods that list metrics from inside a metric plugin such as ent.metrics, arch.metrics, db.metrics, or Metric.list. Those metric list functions may call back into the metrics plugin leading to infinite recursion. It is safe to retrieve metric values using the normal metric methods ( ent.metric, arch.metric, or db.metric).

The sample below maps old 6.3 metric ids to the new metric ids.

# A sample metrics plugin.
#
# This plugin maps ids from Understand 6.3 to the new 6.4 ids.

import understand

# For this plugin, define the map from old id to new id
metDict = {
  "AltCountLineBlank" :"CountLineBlankWithInactive",
  "AltCountLineCode" :"CountLineCodeWithInactive",
  "AltCountLineComment" :"CountLineCommentWithInactive",
  "CountLineBlank_Html" :"CountLineBlankHtml",
  "CountLineBlank_Javascript" :"CountLineBlankJavascript",
  "CountLineBlank_Php" :"CountLineBlankPhp",
  "CountLineCode_Javascript" :"CountLineCodeJavascript",
  "CountLineCode_Php" :"CountLineCodePhp",
  "CountLineComment_Html" :"CountLineCommentHtml",
  "CountLineComment_Javascript" :"CountLineCommentJavascript",
  "CountLineComment_Php" :"CountLineCommentPhp",
  "CountLine_Html" :"CountLineHtml",
  "CountLine_Javascript" :"CountLineJavascript",
  "CountLine_Php" :"CountLinePhp",
  "AltAvgLineBlank" :"AvgCountLineBlankWithInactive",
  "AltAvgLineCode" :"AvgCountLineCodeWithInactive",
  "AltAvgLineComment" :"AvgCountLineCommentWithInactive",
  "AvgLine" :"AvgCountLine",
  "AvgLineBlank" :"AvgCountLineBlank",
  "AvgLineCode" :"AvgCountLineCode",
  "AvgLineComment" :"AvgCountLineComment",
  "CountDeclProgunit" : "CountDeclProgUnit",
  "CountStmtDecl_Javascript" :"CountStmtDeclJavascript",
  "CountStmtDecl_Php" :"CountStmtDeclPhp",
  "CountStmtExe_Javascript" :"CountStmtExeJavascript",
  "CountStmtExe_Php" :"CountStmtExePhp",
}

def ids():
  """
  Required, a list of metric ids that this script provides

  For example, CountLineCode is a metric id.
  """
  return list(metDict.keys())

def name(id):
  """
  Required, the name of the metric given by id.

  For example, CountLineCode -> "Source Lines of Code"
  """
  # This sample does not provide new names for these metrics.
  return id;

def description(id):
  """
  Required, the description of the metric given by id

  For example, CountLineCode -> "Number of lines containing source code"
  """
  # It is safe to call understand.Metric.description from a plugin, as long
  # as the id passed to understand.Metric.description is not an id provided
  # by this plugin.
  return understand.Metric.description(metDict.get(id))

def is_integer(id):
   """
   Optional, return True if the metric value is an integer.

   If this function it not implemented, it is assumed false, meaning the
   value should be represented as a double/float.
   """
   # All the renamed metrics were integer metrics
   return True

# One of the following three test functions should return True.
def test_entity(metric, ent):
  """
  Optional, return True if metric can be calculated for the given entity.
  """
  # Important: It is NOT safe to call ent.metrics() here to check for the
  # existence of the new metric id. That method will include plugin metrics
  # and lead to infinite recursion. Instead, use the metric object to retrieve
  # only built-in metrics.
  return metDict.get(metric.id()) in metric.list(ent)

def test_architecture(metric, arch):
  """
  Optional, return True if metric can be calculated for the given architecture.
  """
  # Important: It is NOT safe to call arch.metrics() here to check for the
  # existence of the new metric id. That method will include plugin metrics
  # and lead to infinite recursion. Instead, use the metric object to retrieve
  # only built-in metrics.
  return metDict.get(metric.id()) in metric.list(arch)

def test_global(metric, db):
  """
  Optional, return True if metric can be calculated for the given database.
  """
  # Important: It is NOT safe to call db.metrics() here to check for the
  # existence of the new metric id. That method will include plugin metrics
  # and lead to infinite recursion. Instead, use the metric object to retrieve
  # only built-in metrics.
  return metDict.get(metric.id()) in metric.list(db)

def test_available(metric,entkindstr):
  """
  Optional, return True if the metric is potentially available.

  This is used when there isn't a specific target for the metric, like lists
  of metrics available for export, or for a treemap.

  Use metric.db() to retrieve the database. If the metric is language specific,
  the code might look like this:
    return "Ada" in metric.db().language()

  entkindstr may be empty. If it is empty, return True as long as the metric
  is available for an entity, architecture, or the project as a whole.

  If entkindstr is not empty, return True only if the metric is available for
  entities matching the provided kind string. Kind checks are performed like
  this:
    my_kinds = set(understand.Kind.list_entity(myMetricKindString)
    test_kinds = set(understand.Kind.list_entity(entkindstr)
    return len(my_kinds.intersection(test_kinds)) > 0
  """
  # Important: It is NOT safe to call understand.Metric.list() here to check for
  # the existence of the new metric id. That method will include plugin metrics
  # and lead to infinite recursion. Instead, use the metric object to retrieve
  # only built-in metrics.
  return metDict.get(metric.id()) in metric.list(entkindstr)

def value(metric, target):
  """
  Required, return the metric value for the target. The target may be
  an entity, architecture, or database depending on which test functions
  returned True.
  """
  id = metDict.get(metric.id())
  # It is safe to get the value of any metric defined outside this plugin
  # using ent.metric(), db.metric(), and arch.metric(). Because the target
  # will have the metric method no matter what kind it is, this line works:
  return float(target.metric([id])[id])

  # But, if different targets need to be treated differently, use isinstance
  # to determine the target:
  #   if isinstance(target, understand.Db):
  #     pass
  #   elif isinstance(target, understand.Arch):
  #     pass
  #   else: # must be entity
  #     pass