aboutsummaryrefslogtreecommitdiff
path: root/doc/source/developer_information/developer_guide/writing_plugins.rst
diff options
context:
space:
mode:
Diffstat (limited to 'doc/source/developer_information/developer_guide/writing_plugins.rst')
-rw-r--r--doc/source/developer_information/developer_guide/writing_plugins.rst863
1 files changed, 863 insertions, 0 deletions
diff --git a/doc/source/developer_information/developer_guide/writing_plugins.rst b/doc/source/developer_information/developer_guide/writing_plugins.rst
new file mode 100644
index 00000000..f44f84db
--- /dev/null
+++ b/doc/source/developer_information/developer_guide/writing_plugins.rst
@@ -0,0 +1,863 @@
+.. _writing-plugins:
+
+
+Writing Plugins
+================
+
+Workload Automation offers several plugin points (or plugin types). The most
+interesting of these are
+
+:workloads: These are the tasks that get executed and measured on the device. These
+ can be benchmarks, high-level use cases, or pretty much anything else.
+:targets: These are interfaces to the physical devices (development boards or end-user
+ devices, such as smartphones) that use cases run on. Typically each model of a
+ physical device would require its own interface class (though some functionality
+ may be reused by subclassing from an existing base).
+:instruments: Instruments allow collecting additional data from workload execution (e.g.
+ system traces). Instruments are not specific to a particular workload. Instruments
+ can hook into any stage of workload execution.
+:output processors: These are used to format the results of workload execution once they have been
+ collected. Depending on the callback used, these will run either after each
+ iteration and/or at the end of the run, after all of the results have been
+ collected.
+
+You can create a plugin by subclassing the appropriate base class, defining
+appropriate methods and attributes, and putting the .py file containing the
+class into the "plugins" subdirectory under ``~/.workload_automation`` (or
+equivalent) where it will be automatically picked up by WA.
+
+
+Plugin Basics
+--------------
+
+This sub-section covers things common to implementing plugins of all types. It
+is recommended you familiarize yourself with the information here before
+proceeding onto guidance for specific plugin types.
+
+.. _context:
+
+The Context
+^^^^^^^^^^^
+
+The majority of methods in plugins accept a context argument. This is an
+instance of :class:`wa.framework.execution.ExecutionContext`. It contains
+information about the current state of execution of WA and keeps track of things
+like which workload is currently running.
+
+Notable methods of the context are:
+
+:context.get_resource(resource, strict=True):
+ This method should be used to retrieve a resource using the resource getters rather than using the ResourceResolver directly as this method additionally record any found resources hash in the output metadata.
+
+:context.add_artifact(name, host_file_path, kind, description=None, classifier=None):
+ Plugins can add :ref:`artifacts <artifact>` of various kinds to the run
+ output directory for WA and associate them with a description and/or
+ :ref:`classifier <classifiers>`.
+
+:context.add_metric(name, value, units=None, lower_is_better=False, classifiers=None):
+ This method should be used to add :ref:`metrics <metrics>` that have been
+ generated from a workload, this will allow WA to process the results
+ accordingly depending on which output processors are enabled.
+
+Notable attributes of the context are:
+
+:context.workload:
+ :class:`wa.framework.workload` object that is currently being executed.
+
+:context.tm:
+ This is the target manager that can be used to access various information
+ about the target including initialization parameters.
+
+:context.current_job:
+ This is an instance of :class:`wa.framework.job.Job` and contains all
+ the information relevant to the workload job currently being executed.
+
+:context.current_job.spec:
+ The current workload specification being executed. This is an
+ instance of :class:`wa.framework.configuration.core.JobSpec`
+ and defines the workload and the parameters under which it is
+ being executed.
+
+:context.current_job.current_iteration:
+ The current iteration of the spec that is being executed. Note that this
+ is the iteration for that spec, i.e. the number of times that spec has
+ been run, *not* the total number of all iterations have been executed so
+ far.
+
+:context.job_output:
+ This is the output object for the current iteration which
+ is an instance of :class:`wa.framework.output.JobOutput`. It contains
+ the status of the iteration as well as the metrics and artifacts
+ generated by the workload.
+
+
+In addition to these, context also defines a few useful paths (see below).
+
+
+Paths
+^^^^^
+
+You should avoid using hard-coded absolute paths in your plugins whenever
+possible, as they make your code too dependent on a particular environment and
+may mean having to make adjustments when moving to new (host and/or device)
+platforms. To help avoid hard-coded absolute paths, WA defines a number of
+standard locations. You should strive to define your paths relative
+to one of these.
+
+On the host
+~~~~~~~~~~~
+
+Host paths are available through the context object, which is passed to most
+plugin methods.
+
+context.run_output_directory
+ This is the top-level output directory for all WA results (by default,
+ this will be "wa_output" in the directory in which WA was invoked.
+
+context.output_directory
+ This is the output directory for the current iteration. This will an
+ iteration-specific subdirectory under the main results location. If
+ there is no current iteration (e.g. when processing overall run results)
+ this will point to the same location as ``root_output_directory``.
+
+
+Additionally, the global ``wa.settings`` object exposes on other location:
+
+settings.dependency_directory
+ this is the root directory for all plugin dependencies (e.g. media
+ files, assets etc) that are not included within the plugin itself.
+
+As per Python best practice, it is recommended that methods and values in
+``os.path`` standard library module are used for host path manipulation.
+
+On the target
+~~~~~~~~~~~~~
+
+Workloads and instruments have a ``target`` attribute, which is an interface to
+the target used by WA. It defines the following location:
+
+target.working_directory
+ This is the directory for all WA-related files on the target. All files
+ deployed to the target should be pushed to somewhere under this location
+ (the only exception being executables installed with ``target.install``
+ method).
+
+Since there could be a mismatch between path notation used by the host and the
+target, the ``os.path`` modules should *not* be used for on-target path
+manipulation. Instead target has an equipment module exposed through
+``target.path`` attribute. This has all the same attributes and behaves the
+same way as ``os.path``, but is guaranteed to produce valid paths for the target,
+irrespective of the host's path notation. For example:
+
+.. code:: python
+
+ result_file = self.target.path.join(self.target.working_directory, "result.txt")
+ self.command = "{} -a -b -c {}".format(target_binary, result_file)
+
+.. note:: Output processors, unlike workloads and instruments, do not have their
+ own target attribute as they are designed to be able to be ran offline.
+
+.. _resource-resolution:
+
+Dynamic Resource Resolution
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The idea is to decouple resource identification from resource discovery.
+Workloads/instruments/devices/etc state *what* resources they need, and not
+*where* to look for them -- this instead is left to the resource resolver that
+is part of the execution context. The actual discovery of resources is
+performed by resource getters that are registered with the resolver.
+
+A resource type is defined by a subclass of
+:class:`wa.framework.resource.Resource`. An instance of this class describes a
+resource that is to be obtained. At minimum, a ``Resource`` instance has an
+owner (which is typically the object that is looking for the resource), but
+specific resource types may define other parameters that describe an instance of
+that resource (such as file names, URLs, etc).
+
+An object looking for a resource invokes a resource resolver with an instance of
+``Resource`` describing the resource it is after. The resolver goes through the
+getters registered for that resource type in priority order attempting to obtain
+the resource; once the resource is obtained, it is returned to the calling
+object. If none of the registered getters could find the resource,
+``NotFoundError`` is raised (or ``None`` is returned instead, if invoked with
+``strict=False``).
+
+The most common kind of object looking for resources is a ``Workload``, and the
+``Workload`` class defines
+:py:meth:`wa.framework.workload.Workload.init_resources` method, which may be
+overridden by subclasses to perform resource resolution. For example, a workload
+looking for an executable file would do so like this::
+
+ from wa import Workload
+ from wa.import Executable
+
+ class MyBenchmark(Workload):
+
+ # ...
+
+ def init_resources(self, resolver):
+ resource = Executable(self, self.target.abi, 'my_benchmark')
+ host_exe = resolver.get(resource)
+
+ # ...
+
+
+Currently available resource types are defined in :py:mod:`wa.framework.resources`.
+
+.. _deploying-executables:
+
+Deploying executables to a target
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Some targets may have certain restrictions on where executable binaries may be
+placed and how they should be invoked. To ensure your plugin works with as
+wide a range of targets as possible, you should use WA APIs for deploying and
+invoking executables on a target, as outlined below.
+
+As with other resources, host-side paths to the executable binary to be deployed
+should be obtained via the :ref:`resource resolver <resource-resolution>`. A
+special resource type, ``Executable`` is used to identify a binary to be
+deployed. This is similar to the regular ``File`` resource, however it takes an
+additional parameter that specifies the ABI for which the executable was
+compiled for.
+
+In order for the binary to be obtained in this way, it must be stored in one of
+the locations scanned by the resource resolver in a directory structure
+``<root>/bin/<abi>/<binary>`` (where ``root`` is the base resource location to
+be searched, e.g. ``~/.workload_automation/dependencies/<plugin name>``, and
+``<abi>`` is the ABI for which the executable has been compiled, as returned by
+``self.target.abi``).
+
+Once the path to the host-side binary has been obtained, it may be deployed
+using one of two methods from a
+`Target <http://devlib.readthedocs.io/en/latest/target.html>`_ instance --
+``install`` or ``install_if_needed``. The latter will check a version of that
+binary has been previously deployed by WA and will not try to re-install.
+
+.. code:: python
+
+ from wa import Executable
+
+ host_binary = context.resolver.get(Executable(self, self.target.abi, 'some_binary'))
+ target_binary = self.target.install_if_needed(host_binary)
+
+
+.. note:: Please also note that the check is done based solely on the binary name.
+ For more information please see the devlib
+ `documentation <http://devlib.readthedocs.io/en/latest/target.html#Target.install_if_needed>`_.
+
+Both of the above methods will return the path to the installed binary on the
+target. The executable should be invoked *only* via that path; do **not** assume
+that it will be in ``PATH`` on the target (or that the executable with the same
+name in ``PATH`` is the version deployed by WA.
+
+For more information on how to implement this, please see the
+:ref:`how to guide <deploying-executables-example>`.
+
+
+Deploying assets
+-----------------
+WA provides a generic mechanism for deploying assets during workload initialization.
+WA will automatically try to retrieve and deploy each asset to the target's working directory
+that is contained in a workloads ``deployable_assets`` attribute stored as a list.
+
+If the parameter ``cleanup_assets`` is set then any asset deployed will be removed
+again and the end of the run.
+
+If the workload requires a custom deployment mechanism the ``deploy_assets``
+method can be overridden for that particular workload, in which case, either
+additional assets should have their on target paths added to the workload's
+``deployed_assests`` attribute or the corresponding ``remove_assets`` method
+should also be implemented.
+
+.. _plugin-parmeters:
+
+Parameters
+----------
+
+All plugins can be parametrized. Parameters are specified using
+``parameters`` class attribute. This should be a list of
+:class:`wa.framework.plugin.Parameter` instances. The following attributes can be
+specified on parameter creation:
+
+:name:
+ This is the only mandatory argument. The name will be used to create a
+ corresponding attribute in the plugin instance, so it must be a valid
+ Python identifier.
+
+:kind:
+ This is the type of the value of the parameter. This must be an
+ callable. Normally this should be a standard Python type, e.g. ``int``
+ or ``float``, or one the types defined in :mod:`wa.utils.types`.
+ If not explicitly specified, this will default to ``str``.
+
+ .. note:: Irrespective of the ``kind`` specified, ``None`` is always a
+ valid value for a parameter. If you don't want to allow
+ ``None``, then set ``mandatory`` (see below) to ``True``.
+
+:allowed_values:
+ A list of the only allowed values for this parameter.
+
+ .. note:: For composite types, such as ``list_of_strings`` or
+ ``list_of_ints`` in :mod:`wa.utils.types`, each element of
+ the value will be checked against ``allowed_values`` rather
+ than the composite value itself.
+
+:default:
+ The default value to be used for this parameter if one has not been
+ specified by the user. Defaults to ``None``.
+
+:mandatory:
+ A ``bool`` indicating whether this parameter is mandatory. Setting this
+ to ``True`` will make ``None`` an illegal value for the parameter.
+ Defaults to ``False``.
+
+ .. note:: Specifying a ``default`` will mean that this parameter will,
+ effectively, be ignored (unless the user sets the param to ``None``).
+
+ .. note:: Mandatory parameters are *bad*. If at all possible, you should
+ strive to provide a sensible ``default`` or to make do without
+ the parameter. Only when the param is absolutely necessary,
+ and there really is no sensible default that could be given
+ (e.g. something like login credentials), should you consider
+ making it mandatory.
+
+:constraint:
+ This is an additional constraint to be enforced on the parameter beyond
+ its type or fixed allowed values set. This should be a predicate (a function
+ that takes a single argument -- the user-supplied value -- and returns
+ a ``bool`` indicating whether the constraint has been satisfied).
+
+:override:
+ A parameter name must be unique not only within an plugin but also
+ with that plugin's class hierarchy. If you try to declare a parameter
+ with the same name as already exists, you will get an error. If you do
+ want to override a parameter from further up in the inheritance
+ hierarchy, you can indicate that by setting ``override`` attribute to
+ ``True``.
+
+ When overriding, you do not need to specify every other attribute of the
+ parameter, just the ones you what to override. Values for the rest will
+ be taken from the parameter in the base class.
+
+
+Validation and cross-parameter constraints
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+A plugin will get validated at some point after construction. When exactly
+this occurs depends on the plugin type, but it *will* be validated before it
+is used.
+
+You can implement ``validate`` method in your plugin (that takes no arguments
+beyond the ``self``) to perform any additional *internal* validation in your
+plugin. By "internal", I mean that you cannot make assumptions about the
+surrounding environment (e.g. that the device has been initialized).
+
+The contract for ``validate`` method is that it should raise an exception
+(either ``wa.framework.exception.ConfigError`` or plugin-specific exception type -- see
+further on this page) if some validation condition has not, and cannot, been met.
+If the method returns without raising an exception, then the plugin is in a
+valid internal state.
+
+Note that ``validate`` can be used not only to verify, but also to impose a
+valid internal state. In particular, this where cross-parameter constraints can
+be resolved. If the ``default`` or ``allowed_values`` of one parameter depend on
+another parameter, there is no way to express that declaratively when specifying
+the parameters. In that case the dependent attribute should be left unspecified
+on creation and should instead be set inside ``validate``.
+
+Logging
+-------
+
+Every plugin class has it's own logger that you can access through
+``self.logger`` inside the plugin's methods. Generally, a :class:`Target` will
+log everything it is doing, so you shouldn't need to add much additional logging
+for device actions. However you might what to log additional information, e.g.
+what settings your plugin is using, what it is doing on the host, etc.
+(Operations on the host will not normally be logged, so your plugin should
+definitely log what it is doing on the host). One situation in particular where
+you should add logging is before doing something that might take a significant
+amount of time, such as downloading a file.
+
+
+Documenting
+-----------
+
+All plugins and their parameter should be documented. For plugins
+themselves, this is done through ``description`` class attribute. The convention
+for an plugin description is that the first paragraph should be a short
+summary description of what the plugin does and why one would want to use it
+(among other things, this will get extracted and used by ``wa list`` command).
+Subsequent paragraphs (separated by blank lines) can then provide a more
+detailed description, including any limitations and setup instructions.
+
+For parameters, the description is passed as an argument on creation. Please
+note that if ``default``, ``allowed_values``, or ``constraint``, are set in the
+parameter, they do not need to be explicitly mentioned in the description (wa
+documentation utilities will automatically pull those). If the ``default`` is set
+in ``validate`` or additional cross-parameter constraints exist, this *should*
+be documented in the parameter description.
+
+Both plugins and their parameters should be documented using reStructureText
+markup (standard markup for Python documentation). See:
+
+http://docutils.sourceforge.net/rst.html
+
+Aside from that, it is up to you how you document your plugin. You should try
+to provide enough information so that someone unfamiliar with your plugin is
+able to use it, e.g. you should document all settings and parameters your
+plugin expects (including what the valid values are).
+
+
+Error Notification
+------------------
+
+When you detect an error condition, you should raise an appropriate exception to
+notify the user. The exception would typically be :class:`ConfigError` or
+(depending the type of the plugin)
+:class:`WorkloadError`/:class:`DeviceError`/:class:`InstrumentError`/:class:`OutputProcessorError`.
+All these errors are defined in :mod:`wa.framework.exception` module.
+
+A :class:`ConfigError` should be raised where there is a problem in configuration
+specified by the user (either through the agenda or config files). These errors
+are meant to be resolvable by simple adjustments to the configuration (and the
+error message should suggest what adjustments need to be made. For all other
+errors, such as missing dependencies, mis-configured environment, problems
+performing operations, etc., the plugin type-specific exceptions should be
+used.
+
+If the plugin itself is capable of recovering from the error and carrying
+on, it may make more sense to log an ERROR or WARNING level message using the
+plugin's logger and to continue operation.
+
+.. _instrument-reference:
+
+Adding an Instrument
+---------------------
+Instruments can be used to collect additional measurements during workload
+execution (e.g. collect power readings). An instrument can hook into almost any
+stage of workload execution. Any new instrument should be a subclass of
+Instrument and it must have a name. When a new instrument is added to Workload
+Automation, the methods of the new instrument will be found automatically and
+hooked up to the supported signals. Once a signal is broadcasted, the
+corresponding registered method is invoked.
+
+Each method in ``Instrument`` must take two arguments, which are ``self`` and
+``context``. Supported methods and their corresponding signals can be found in
+the :ref:`Signals Documentation <instruments_method_map>`. To make
+implementations easier and common, the basic steps to add new instrument is
+similar to the steps to add new workload and an example can be found in the
+:ref:`How To <adding-an-instrument-example>` section.
+
+.. _instrument-api:
+
+To implement your own instrument the relevant methods of the interface shown
+below should be implemented:
+
+ :name:
+
+ The name of the instrument, this must be unique to WA.
+
+ :description:
+
+ A description of what the instrument can be used for.
+
+ :parameters:
+
+ A list of additional :class:`Parameters` the instrument can take.
+
+ :initialize(context):
+
+ This method will only be called once during the workload run
+ therefore operations that only need to be performed initially should
+ be performed here for example pushing the files to the target device,
+ installing them.
+
+ :setup(context):
+
+ This method is invoked after the workload is setup. All the
+ necessary setup should go inside this method. Setup, includes
+ operations like clearing logs, additional configuration etc.
+
+ :start(context):
+
+ It is invoked just before the workload start execution. Here is
+ where instrument measurement start being registered/taken.
+
+ :stop(context):
+
+ It is invoked just after the workload execution stops and where
+ the measurements should stop being taken/registered.
+
+ :update_output(context):
+
+ It is invoked after the workload updated its result.
+ update_result is where the taken measures are added to the result so it
+ can be processed by Workload Automation.
+
+ :teardown(context):
+
+ It is invoked after the workload is torn down. It is a good place
+ to clean any logs generated by the instrument.
+
+ :finalize(context):
+
+ This method is the complement to the initialize method and will also
+ only be called once so should be used to deleting/uninstalling files
+ pushed to the device.
+
+
+This is similar to a ``Workload``, except all methods are optional. In addition to
+the workload-like methods, instruments can define a number of other methods that
+will get invoked at various points during run execution. The most useful of
+which is perhaps ``initialize`` that gets invoked after the device has been
+initialised for the first time, and can be used to perform one-time setup (e.g.
+copying files to the device -- there is no point in doing that for each
+iteration). The full list of available methods can be found in
+:ref:`Signals Documentation <instruments_method_map>`.
+
+.. _prioritization:
+
+Prioritization
+^^^^^^^^^^^^^^
+
+Callbacks (e.g. ``setup()`` methods) for all instruments get executed at the
+same point during workload execution, one after another. The order in which the
+callbacks get invoked should be considered arbitrary and should not be relied
+on (e.g. you cannot expect that just because instrument A is listed before
+instrument B in the config, instrument A's callbacks will run first).
+
+In some cases (e.g. in ``start()`` and ``stop()`` methods), it is important to
+ensure that a particular instrument's callbacks run a closely as possible to the
+workload's invocations in order to maintain accuracy of readings; or,
+conversely, that a callback is executed after the others, because it takes a
+long time and may throw off the accuracy of other instruments. You can do
+this by using decorators on the appropriate methods. The available decorators are:
+``very_slow``, ``slow``, ``normal``, ``fast``, ``very_fast``, with ``very_fast``
+running closest to the workload invocation and ``very_slow`` running furtherest
+away. For example::
+
+ from wa import very_fast
+ # ..
+
+ class PreciseInstrument(Instrument)
+
+ # ...
+ @very_fast
+ def start(self, context):
+ pass
+
+ @very_fast
+ def stop(self, context):
+ pass
+
+ # ...
+
+``PreciseInstrument`` will be started after all other instruments (i.e.
+*just* before the workload runs), and it will stopped before all other
+instruments (i.e. *just* after the workload runs).
+
+If more than one active instrument has specified fast (or slow) callbacks, then
+their execution order with respect to each other is not guaranteed. In general,
+having a lot of instruments enabled is going to negatively affect the
+readings. The best way to ensure accuracy of measurements is to minimize the
+number of active instruments (perhaps doing several identical runs with
+different instruments enabled).
+
+Example
+^^^^^^^
+
+Below is a simple instrument that measures the execution time of a workload::
+
+ class ExecutionTimeInstrument(Instrument):
+ """
+ Measure how long it took to execute the run() methods of a Workload.
+
+ """
+
+ name = 'execution_time'
+
+ def initialize(self, context):
+ self.start_time = None
+ self.end_time = None
+
+ @very_fast
+ def start(self, context):
+ self.start_time = time.time()
+
+ @very_fast
+ def stop(self, context):
+ self.end_time = time.time()
+
+ def update_output(self, context):
+ execution_time = self.end_time - self.start_time
+ context.add_metric('execution_time', execution_time, 'seconds')
+
+
+.. include:: developer_information/developer_reference/instrument_method_map.rst
+
+.. _adding-an-output-processor:
+
+Adding an Output processor
+----------------------------
+
+A output processor is responsible for processing the results. This may
+involve formatting and writing them to a file, uploading them to a database,
+generating plots, etc. WA comes with a few output processors that output
+results in a few common formats (such as csv or JSON).
+
+You can add your own output processors by creating a Python file in
+``~/.workload_automation/plugins`` with a class that derives from
+:class:`wa.OutputProcessor <wa.framework.processor.OutputProcessor>`, and should
+implement the relevant methods from the following interface:
+
+ :name:
+
+ The name of the output processor, this must be unique to WA.
+
+ :description:
+
+ A description of what the output processor can be used for.
+
+ :parameters:
+
+ A list of additional :class:`Parameters` the output processor can take.
+
+ :initialize():
+
+ This method will only be called once during the workload run
+ therefore operations that only need to be performed initially should
+ be performed here.
+
+ :process_job_output(output, target_info, run_ouput):
+
+ This method should be used to perform the processing of the
+ output from an individual job output. This is where any
+ additional artifacts should be generated if applicable.
+
+ :export_job_output(output, target_info, run_ouput):
+
+ This method should be used to perform the exportation of the
+ existing data collected/generated for an individual job. E.g.
+ uploading them to a database etc.
+
+ :process_run_output(output, target_info):
+
+ This method should be used to perform the processing of the
+ output from the run as a whole. This is where any
+ additional artifacts should be generated if applicable.
+
+ :export_run_output(output, target_info):
+
+ This method should be used to perform the exportation of the
+ existing data collected/generated for the run as a whole. E.g.
+ uploading them to a database etc.
+
+ :finalize():
+
+ This method is the complement to the initialize method and will also
+ only be called once.
+
+
+The method names should be fairly self-explanatory. The difference between
+"process" and "export" methods is that export methods will be invoked after
+process methods for all output processors have been generated. Process methods
+may generate additional artifacts (metrics, files, etc.), while export methods
+should not -- they should only handle existing results (upload them to a
+database, archive on a filer, etc).
+
+The output object passed to job methods is an instance of
+:class:`wa.framework.output.JobOutput`, the output object passed to run methods
+is an instance of :class:`wa.RunOutput <wa.framework.output.RunOutput>`.
+
+
+Adding a Resource Getter
+------------------------
+
+A resource getter is a plugin that is designed to retrieve a resource
+(binaries, APK files or additional workload assets). Resource getters are invoked in
+priority order until one returns the desired resource.
+
+If you want WA to look for resources somewhere it doesn't by default (e.g. you
+have a repository of APK files), you can implement a getter for the resource and
+register it with a higher priority than the standard WA getters, so that it gets
+invoked first.
+
+Instances of a resource getter should implement the following interface::
+
+ class ResourceGetter(Plugin):
+
+ name = None
+
+ def register(self, resolver):
+ raise NotImplementedError()
+
+The getter should define a name for itself (as with all plugins), in addition it
+should implement the ``register`` method. This involves registering a method
+with the resolver that should used to be called when trying to retrieve a resource
+(typically ``get``) along with it's priority (see `Getter Prioritization`_
+below. That method should return an instance of the resource that
+has been discovered (what "instance" means depends on the resource, e.g. it
+could be a file path), or ``None`` if this getter was unable to discover
+that resource.
+
+Getter Prioritization
+^^^^^^^^^^^^^^^^^^^^^
+
+A priority is an integer with higher numeric values indicating a higher
+priority. The following standard priority aliases are defined for getters:
+
+
+ :preferred: Take this resource in favour of the environment resource.
+ :local: Found somewhere under ~/.workload_automation/ or equivalent, or
+ from environment variables, external configuration files, etc.
+ These will override resource supplied with the package.
+ :lan: Resource will be retrieved from a locally mounted remote location
+ (such as samba share)
+ :remote: Resource will be downloaded from a remote location (such as an HTTP
+ server)
+ :package: Resource provided with the package.
+
+These priorities are defined as class members of
+:class:`wa.framework.resource.SourcePriority`, e.g. ``SourcePriority.preferred``.
+
+Most getters in WA will be registered with either ``local`` or
+``package`` priorities. So if you want your getter to override the default, it
+should typically be registered as ``preferred``.
+
+You don't have to stick to standard priority levels (though you should, unless
+there is a good reason). Any integer is a valid priority. The standard priorities
+range from 0 to 40 in increments of 10.
+
+Example
+^^^^^^^
+
+The following is an implementation of a getter that searches for files in the
+users dependencies directory, typically
+``~/.workload_automation/dependencies/<workload_name>`` It uses the
+``get_from_location`` method to filter the available files in the provided
+directory appropriately::
+
+ import sys
+
+ from wa import settings,
+ from wa.framework.resource import ResourceGetter, SourcePriority
+ from wa.framework.getters import get_from_location
+
+ class UserDirectory(ResourceGetter):
+
+ name = 'user'
+
+ def register(self, resolver):
+ resolver.register(self.get, SourcePriority.local)
+
+ def get(self, resource):
+ basepath = settings.dependencies_directory
+ directory = _d(os.path.join(basepath, resource.owner.name))
+ return get_from_location(directory, resource)
+
+.. _adding_a_target:
+
+Adding a Target
+---------------
+
+In WA3, a 'target' consists of a platform and a devlib target. The
+implementations of the targets are located in ``devlib``. WA3 will instantiate a
+devlib target passing relevant parameters parsed from the configuration. For
+more information about devlib targets please see `the documentation
+<http://devlib.readthedocs.io/en/latest/target.html>`_.
+
+The currently available platforms are:
+ :generic: The 'standard' platform implementation of the target, this should
+ work for the majority of use cases.
+ :juno: A platform implementation specifically for the juno.
+ :tc2: A platform implementation specifically for the tc2.
+ :gem5: A platform implementation to interact with a gem5 simulation.
+
+The currently available targets from devlib are:
+ :linux: A device running a Linux based OS.
+ :android: A device running Android OS.
+ :local: Used to run locally on a linux based host.
+ :chromeos: A device running ChromeOS, supporting an android container if available.
+
+For an example of adding you own customized version of an existing devlib target,
+please see the how to section :ref:`Adding a Custom Target <adding-custom-target-example>`.
+
+
+Other Plugin Types
+---------------------
+
+In addition to plugin types covered above, there are few other, more
+specialized ones. They will not be covered in as much detail. Most of them
+expose relatively simple interfaces with only a couple of methods and it is
+expected that if the need arises to extend them, the API-level documentation
+that accompanies them, in addition to what has been outlined here, should
+provide enough guidance.
+
+:commands: This allows extending WA with additional sub-commands (to supplement
+ exiting ones outlined in the :ref:`invocation` section).
+:modules: Modules are "plugins for plugins". They can be loaded by other
+ plugins to expand their functionality (for example, a flashing
+ module maybe loaded by a device in order to support flashing).
+
+
+Packaging Your Plugins
+----------------------
+
+If your have written a bunch of plugins, and you want to make it easy to
+deploy them to new systems and/or to update them on existing systems, you can
+wrap them in a Python package. You can use ``wa create package`` command to
+generate appropriate boiler plate. This will create a ``setup.py`` and a
+directory for your package that you can place your plugins into.
+
+For example, if you have a workload inside ``my_workload.py`` and a result
+processor in ``my_result_processor.py``, and you want to package them as
+``my_wa_exts`` package, first run the create command ::
+
+ wa create package my_wa_exts
+
+This will create a ``my_wa_exts`` directory which contains a
+``my_wa_exts/setup.py`` and a subdirectory ``my_wa_exts/my_wa_exts`` which is
+the package directory for your plugins (you can rename the top-level
+``my_wa_exts`` directory to anything you like -- it's just a "container" for the
+setup.py and the package directory). Once you have that, you can then copy your
+plugins into the package directory, creating
+``my_wa_exts/my_wa_exts/my_workload.py`` and
+``my_wa_exts/my_wa_exts/my_result_processor.py``. If you have a lot of
+plugins, you might want to organize them into subpackages, but only the
+top-level package directory is created by default, and it is OK to have
+everything in there.
+
+.. note:: When discovering plugins through this mechanism, WA traverses the
+ Python module/submodule tree, not the directory structure, therefore,
+ if you are going to create subdirectories under the top level directory
+ created for you, it is important that your make sure they are valid
+ Python packages; i.e. each subdirectory must contain a __init__.py
+ (even if blank) in order for the code in that directory and its
+ subdirectories to be discoverable.
+
+At this stage, you may want to edit ``params`` structure near the bottom of
+the ``setup.py`` to add correct author, license and contact information (see
+"Writing the Setup Script" section in standard Python documentation for
+details). You may also want to add a README and/or a COPYING file at the same
+level as the setup.py. Once you have the contents of your package sorted,
+you can generate the package by running ::
+
+ cd my_wa_exts
+ python setup.py sdist
+
+This will generate ``my_wa_exts/dist/my_wa_exts-0.0.1.tar.gz`` package which
+can then be deployed on the target system with standard Python package
+management tools, e.g. ::
+
+ sudo pip install my_wa_exts-0.0.1.tar.gz
+
+As part of the installation process, the setup.py in the package, will write the
+package's name into ``~/.workoad_automation/packages``. This will tell WA that
+the package contains plugin and it will load them next time it runs.
+
+.. note:: There are no uninstall hooks in ``setuputils``, so if you ever
+ uninstall your WA plugins package, you will have to manually remove
+ it from ``~/.workload_automation/packages`` otherwise WA will complain
+ about a missing package next time you try to run it.