text: # -*- coding: utf-8 -*-
  """Plot performance comparisons between mel, cmds, cmdx and PyMEL"""
  
  import os
  import sys
  import timeit
  import importlib
  from copy import deepcopy
  
  from maya import cmds, mel
  from maya import OpenMaya as om1
  from maya.api import OpenMaya as om2
  from pymel import core as pm
  
  import cmdx
  
  try:
      # Python 3 support
      reload = importlib.reload
  except AttributeError:
      pass
  
  try:
      # Mock irrelevant pygal dependency
      sys.modules["pkg_resources"] = type("Mock", (object,), {
          "iter_entry_points": lambda *args, **kwargs: []
      })()
  
      import pygal
  except ImportError:
      raise ImportError("plot.py requires pygal")
  
  # Results from tests end up here
  data = dict()
  
  # Precisions
  Milliseconds = 0
  Nanoseconds = 1
  
  
  def Test(method,
           task,
           func,
           setup=None,
           teardown=None,
           number=1000,
           repeat=5,
           precision=1):
  
      results = list()
  
      setup = setup or (lambda: None)
      teardown = teardown or (lambda: None)
  
      text = "%s %s: %.1f ms (%.2f {precision}/call)".format(
          precision="μs" if precision else "ms"
      )
  
      for iteration in range(repeat):
          setup()
          results += [timeit.Timer(func).timeit(number)]
          teardown()
  
      print(text % (
          task,
          method,
          10 ** 3 * sum(results),
          10 ** (6 if precision else 3) * min(results) / number
      ))
  
      if task not in data:
          data[task] = {}
  
      # Store for plot
      data[task][method] = {
          "func": func,
          "number": number,
          "results": results,
          "min": sum(results),
          "percall": min(results) / number
      }
  
  
  def New(setup=None):
      cmds.file(new=True, force=True)
      (setup or (lambda: None))()
  
  
  def reload_pymel():
      """PyMEL consists of many submodules
  
      PyMEL does initialisation on import.
      The duration of the initialisation increases
      linearly with the number of plug-ins available
      on import.
  
      """
  
      for mod in sys.modules.copy():
          if mod.startswith("pymel"):
              sys.modules.pop(mod)
  
      import pymel.core
      pymel.core  # avoid linter warning
  
  
  def om1GetAttr():
      """Fastest way of getting an attribute with API 2.0"""
      plug = api2mfn.findPlug("translateX", False)
      return plug.asDouble()
  
  
  def om2GetAttr():
      """Fastest way of getting an attribute with API 2.0"""
      plug = api2mfn.findPlug("translateX", False)
      return plug.asDouble()
  
  
  def om1SetAttr(value):
      """Fastest way of getting an attribute with API 2.0"""
      plug = api2mfn.findPlug("translateX", False)
      return plug.setDouble(value)
  
  
  def om2SetAttr(value):
      """Fastest way of getting an attribute with API 2.0"""
      plug = api2mfn.findPlug("translateX", False)
      return plug.setDouble(value)
  
  
  New()
  
  Test("cmdx", "import", lambda: reload(cmdx), number=100)
  Test("cmds", "import", lambda: reload(cmds), number=100)
  Test("PyMEL", "import", reload_pymel, number=1)
  
  New()
  
  node = cmdx.createNode("transform", name="Node")
  path = node.path()
  pynode = pm.PyNode(path)
  api1node = om1.MFnDagNode().create("transform")
  api2node = om2.MFnDagNode().create("transform")
  api1mfn = om1.MFnDagNode(api1node)
  api2mfn = om2.MFnDagNode(api2node)
  
  node = cmdx.createNode("transform", name="Node")
  path = node.path()
  pynode = pm.PyNode(path)
  api1node = om1.MFnDagNode().create("transform")
  api2node = om2.MFnDagNode().create("transform")
  api1mfn = om1.MFnDagNode(api1node)
  api2mfn = om2.MFnDagNode(api2node)
  
  Test("cmds", "long", lambda: cmds.ls(path, long=True))
  Test("cmdx", "long", lambda: node.path())
  Test("PyMEL", "long", lambda: pm.ls(path, long=True))
  Test("API 1.0", "long", lambda: api2mfn.fullPathName())
  Test("API 2.0", "long", lambda: api2mfn.fullPathName())
  
  Test("mel", "getAttr", lambda: mel.eval("getAttr %s" % (path + ".tx")), number=10000)
  Test("cmds", "getAttr", lambda: cmds.getAttr(path + ".tx"), number=10000)
  Test("cmdx", "getAttr", lambda: cmdx.getAttr(node + ".tx", type=cmdx.Double), number=10000)
  Test("PyMEL", "getAttr", lambda: pynode.tx.get(), number=10000)
  Test("API 1.0", "getAttr", lambda: om1GetAttr(), number=10000)
  Test("API 2.0", "getAttr", lambda: om2GetAttr(), number=10000)
  
  Test("mel", "setAttr", lambda: mel.eval("setAttr %s %s" % (path + ".tx", 5)))
  Test("cmds", "setAttr", lambda: cmds.setAttr(path + ".tx", 5))
  Test("cmdx", "setAttr", lambda: cmdx.setAttr(node + ".tx", 5, type=cmdx.Double))
  Test("PyMEL", "setAttr", lambda: pm.setAttr(pynode + ".tx", 5))
  Test("API 1.0", "setAttr", lambda: om1SetAttr(5))
  Test("API 2.0", "setAttr", lambda: om2SetAttr(5))
  
  Test("cmdx", "node.attr", lambda: node["tx"].read(), number=10000)
  Test("PyMEL", "node.attr", lambda: pynode.tx.get(), number=10000)
  
  Test("cmdx", "node.attr=5", lambda: node["tx"].write(5), number=10000)
  Test("PyMEL", "node.attr=5", lambda: pynode.tx.set(5), number=10000)
  
  Test("mel", "createNode", lambda: mel.eval("createNode \"transform\""), New)
  Test("cmds", "createNode", lambda: cmds.createNode("transform"), New)
  Test("cmdx", "createNode", lambda: cmdx.createNode(cmdx.Transform), New)
  Test("PyMEL", "createNode", lambda: pm.createNode("transform"), New)
  Test("API 1.0", "createNode", lambda: om1.MFnDagNode().create("transform"), New)
  Test("API 2.0", "createNode", lambda: om2.MFnDagNode().create("transform"), New)
  
  New()
  
  root1 = cmdx.createNode("transform")
  root2 = cmdx.createNode("transform")
  child = cmdx.createNode("transform", parent=root1)
  
  def teardown():
      cmds.parent("transform3", "transform1", r=1)
  
  melparent = 'parent -r "transform3" "transform2";'
  Test("mel", "parent", lambda: mel.eval(melparent), number=1, repeat=1000, teardown=teardown)
  Test("cmds", "parent", lambda: cmds.parent("transform3", "transform2", r=1), number=1, repeat=1000, teardown=teardown)
  Test("cmdx", "parent", lambda: cmdx.parent(child, root2), number=1, repeat=1000, teardown=teardown)
  root1, root2, child = pm.ls(map(str, (root1, root2, child)))
  Test("PyMEL", "parent", lambda: pm.parent(child, root2, r=1), number=1, repeat=1000, teardown=teardown)
  
  New()
  
  parent = cmdx.createNode("transform")
  path = parent.path()
  pynode = pm.PyNode(path)
  
  for x in range(100):
      cmdx.createNode("transform", parent=parent)
  
  Test("mel", "listRelatives", lambda: mel.eval('listRelatives -children "transform1"'))
  Test("cmds", "listRelatives", lambda: cmds.listRelatives(path, children=True))
  Test("cmdx", "listRelatives", lambda: cmdx.listRelatives(parent, children=True))
  Test("PyMEL", "listRelatives", lambda: pm.listRelatives(pynode, children=True))
  
  New()
  
  root = cmdx.createNode("transform")
  parent = root
  path = root.path()
  pynode = pm.PyNode(path)
  
  for x in range(100):
      parent = cmdx.createNode("transform", parent=parent)
  
  Test("mel", "allDescendents", lambda: mel.eval('listRelatives -allDescendents "transform1"'))
  Test("cmds", "allDescendents", lambda: cmds.listRelatives(path, allDescendents=True))
  Test("cmdx", "allDescendents", lambda: cmdx.listRelatives(root, allDescendents=True))
  Test("PyMEL", "allDescendents", lambda: pm.listRelatives(pynode, allDescendents=True))
  
  New()
  
  node1 = cmdx.createNode("transform")
  node2 = cmdx.createNode("transform")
  
  
  def teardown():
      cmds.disconnectAttr("transform1.tx", "transform2.tx")
  
  
  melconnect = 'connectAttr "transform1.tx" "transform2.tx"'
  Test("mel", "connectAttr", lambda: mel.eval(melconnect), teardown=teardown, number=1, repeat=1000)
  Test("cmds", "connectAttr", lambda: cmds.connectAttr("transform1.tx", "transform2.tx"), teardown=teardown, number=1, repeat=5000)
  Test("cmdx", "connectAttr", lambda: cmdx.connectAttr(node1["tx"], node2["tx"]), teardown=teardown, number=1, repeat=5000)
  Test("PyMEL", "connectAttr", lambda: pm.connectAttr("transform1.tx", "transform2.tx"), teardown=teardown, number=1, repeat=5000)
  
  New()
  
  def teardown():
      cmds.deleteAttr("transform1.myAttr")
  
  
  node = cmdx.createNode("transform")
  path = node.path()
  
  meladdattr = 'addAttr -ln "myAttr" -at double -dv 0 transform1;'
  Test("mel", "addAttr", lambda: mel.eval(meladdattr), number=1, repeat=1000, teardown=teardown)
  Test("cmds", "addAttr", lambda: cmds.addAttr(path, longName="myAttr", attributeType="double", defaultValue=0), number=1, repeat=1000, teardown=teardown)
  Test("cmdx", "addAttr", lambda: cmdx.addAttr(node, longName="myAttr", attributeType=cmdx.Double, defaultValue=0), number=1, repeat=1000, teardown=teardown)
  Test("PyMEL", "addAttr", lambda: pm.addAttr(path, longName="myAttr", attributeType="double", defaultValue=0), number=1, repeat=1000, teardown=teardown)
  
  Test("cmdx", "node.addAttr", lambda: node.addAttr(cmdx.Double("myAttr")), number=1, repeat=1000, teardown=teardown)
  
  #
  # Render performance characteristics as bar charts
  #
  # |___
  # |___|_______
  # |___________|
  # |______|___
  # |__________|____
  # |_______________|________
  
  # Mock irrelevant pygal dependency
  sys.modules["pkg_resources"] = type("Mock", (object,), {
      "iter_entry_points": lambda *args, **kwargs: []
  })()
  
  
  def stacked(data, dirname):
      data = deepcopy(data)
      tasks = sorted(data.keys())
  
      # Use a fixed order of methods in the plot
      methods = ("mel", "cmds", "PyMEL", "cmdx")
  
      # [group1 result, group2 result, ... of MEL]
      # [group1 result, group2 result, ... of cmds]
      # ...
  
      cols = list()
      for method in methods:
          col = list()
          for task in tasks:
              col += [data[task].get(method, {}).get("min", 0)]
          cols.append(col)
  
      # Normalise along Y-axis
      rows = zip(*cols)
  
      for index, row in enumerate(rows[:]):
          rows[index] = [100.0 * col / sum(row) for col in row]
  
      cols = zip(*rows)
  
      line_chart = pygal.StackedBar()
      line_chart.title = "cmdx performance plot (in %)"
      line_chart.x_labels = tasks
  
      for method, col in enumerate(cols):
          line_chart.add(methods[method], col)
  
      fname = os.path.join(dirname, "stacked.svg")
      line_chart.render_to_file(fname)
  
  
  def horizontal(data, dirname):
      data = deepcopy(data)
      order = ("PyMEL", "mel", "cmds", "cmdx")
  
      for task, methods in data.items():
          chart = pygal.HorizontalBar()
          chart.title = task + u" (μs)"
          for method in order:
              values = methods.get(method, {})
              if not values:
                  continue
              chart.add(method, 10 ** 6 * values.get("percall", 0))
  
          fname = os.path.join(
              dirname, r"%s.svg" % task
          )
  
          chart.render_to_file(fname)
  
  
  def average(x, y, data):
      data = deepcopy(data)
  
      times_faster = list()
      print("|         | Times       | Task")
      print("|:--------|:------------|:------------")
      for task, methods in data.items():
          try:
              a = methods[x]["percall"]
              b = methods[y]["percall"]
          except KeyError:
              continue
  
          faster = a / float(b)
          print("| cmdx is | %.1fx faster | %s" % (faster, task))
          times_faster.append(faster)
  
      average = sum(times_faster) / len(times_faster)
      return round(average, 2)
  
  
  # Draw plots
  dirname = os.path.join(os.path.dirname(cmdx.__file__), "plots")
  stacked(data, dirname)
  horizontal(data, dirname)
  avg = average("PyMEL", "cmdx", data)
  print("- cmdx is on average %.2fx faster than PyMEL" % avg)
  avg = average("cmds", "cmdx", data)
  print("- cmdx is on average %.2fx faster than cmds" % avg)