Skip to content

Commit b3e9311

Browse files
vsochbhatiaharsh
authored andcommitted
removing feature bloat: monitor and analyzers (spack#31130)
Signed-off-by: vsoch <[email protected]> Co-authored-by: vsoch <[email protected]>
1 parent f7830c9 commit b3e9311

21 files changed

+5
-2192
lines changed

lib/spack/docs/developer_guide.rst

Lines changed: 0 additions & 268 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,6 @@ with a high level view of Spack's directory structure:
107107
llnl/ <- some general-use libraries
108108
109109
spack/ <- spack module; contains Python code
110-
analyzers/ <- modules to run analysis on installed packages
111110
build_systems/ <- modules for different build systems
112111
cmd/ <- each file in here is a spack subcommand
113112
compilers/ <- compiler description files
@@ -242,22 +241,6 @@ Unit tests
242241
Implements Spack's test suite. Add a module and put its name in
243242
the test suite in ``__init__.py`` to add more unit tests.
244243

245-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
246-
Research and Monitoring Modules
247-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
248-
249-
:mod:`spack.monitor`
250-
Contains :class:`~spack.monitor.SpackMonitorClient`. This is accessed from
251-
the ``spack install`` and ``spack analyze`` commands to send build and
252-
package metadata up to a `Spack Monitor
253-
<https://github.com/spack/spack-monitor>`_ server.
254-
255-
256-
:mod:`spack.analyzers`
257-
A module folder with a :class:`~spack.analyzers.analyzer_base.AnalyzerBase`
258-
that provides base functions to run, save, and (optionally) upload analysis
259-
results to a `Spack Monitor <https://github.com/spack/spack-monitor>`_ server.
260-
261244

262245
^^^^^^^^^^^^^
263246
Other Modules
@@ -301,240 +284,6 @@ Most spack commands look something like this:
301284
The information in Package files is used at all stages in this
302285
process.
303286

304-
Conceptually, packages are overloaded. They contain:
305-
306-
-------------
307-
Stage objects
308-
-------------
309-
310-
311-
.. _writing-analyzers:
312-
313-
-----------------
314-
Writing analyzers
315-
-----------------
316-
317-
To write an analyzer, you should add a new python file to the
318-
analyzers module directory at ``lib/spack/spack/analyzers`` .
319-
Your analyzer should be a subclass of the :class:`AnalyzerBase <spack.analyzers.analyzer_base.AnalyzerBase>`. For example, if you want
320-
to add an analyzer class ``Myanalyzer`` you would write to
321-
``spack/analyzers/myanalyzer.py`` and import and
322-
use the base as follows:
323-
324-
.. code-block:: python
325-
326-
from .analyzer_base import AnalyzerBase
327-
328-
class Myanalyzer(AnalyzerBase):
329-
330-
331-
Note that the class name is your module file name, all lowercase
332-
except for the first capital letter. You can look at other analyzers in
333-
that analyzer directory for examples. The guide here will tell you about the basic functions needed.
334-
335-
^^^^^^^^^^^^^^^^^^^^^^^^^
336-
Analyzer Output Directory
337-
^^^^^^^^^^^^^^^^^^^^^^^^^
338-
339-
By default, when you run ``spack analyze run`` an analyzer output directory will
340-
be created in your spack user directory in your ``$HOME``. The reason we output here
341-
is because the install directory might not always be writable.
342-
343-
.. code-block:: console
344-
345-
~/.spack/
346-
analyzers
347-
348-
Result files will be written here, organized in subfolders in the same structure
349-
as the package, with each analyzer owning it's own subfolder. for example:
350-
351-
352-
.. code-block:: console
353-
354-
$ tree ~/.spack/analyzers/
355-
/home/spackuser/.spack/analyzers/
356-
└── linux-ubuntu20.04-skylake
357-
└── gcc-9.3.0
358-
└── zlib-1.2.11-sl7m27mzkbejtkrajigj3a3m37ygv4u2
359-
├── environment_variables
360-
│   └── spack-analyzer-environment-variables.json
361-
├── install_files
362-
│   └── spack-analyzer-install-files.json
363-
└── libabigail
364-
└── lib
365-
└── spack-analyzer-libabigail-libz.so.1.2.11.xml
366-
367-
368-
Notice that for the libabigail analyzer, since results are generated per object,
369-
we honor the object's folder in case there are equivalently named files in
370-
different folders. The result files are typically written as json so they can be easily read and uploaded in a future interaction with a monitor.
371-
372-
373-
^^^^^^^^^^^^^^^^^
374-
Analyzer Metadata
375-
^^^^^^^^^^^^^^^^^
376-
377-
Your analyzer is required to have the class attributes ``name``, ``outfile``,
378-
and ``description``. These are printed to the user with they use the subcommand
379-
``spack analyze list-analyzers``. Here is an example.
380-
As we mentioned above, note that this analyzer would live in a module named
381-
``libabigail.py`` in the analyzers folder so that the class can be discovered.
382-
383-
384-
.. code-block:: python
385-
386-
class Libabigail(AnalyzerBase):
387-
388-
name = "libabigail"
389-
outfile = "spack-analyzer-libabigail.json"
390-
description = "Application Binary Interface (ABI) features for objects"
391-
392-
393-
This means that the name and output file should be unique for your analyzer.
394-
Note that "all" cannot be the name of an analyzer, as this key is used to indicate
395-
that the user wants to run all analyzers.
396-
397-
.. _analyzer_run_function:
398-
399-
400-
^^^^^^^^^^^^^^^^^^^^^^^^
401-
An analyzer run Function
402-
^^^^^^^^^^^^^^^^^^^^^^^^
403-
404-
The core of an analyzer is its ``run()`` function, which should accept no
405-
arguments. You can assume your analyzer has the package spec of interest at ``self.spec``
406-
and it's up to the run function to generate whatever analysis data you need,
407-
and then return the object with a key as the analyzer name. The result data
408-
should be a list of objects, each with a name, ``analyzer_name``, ``install_file``,
409-
and one of ``value`` or ``binary_value``. The install file should be for a relative
410-
path, and not the absolute path. For example, let's say we extract a metric called
411-
``metric`` for ``bin/wget`` using our analyzer ``thebest-analyzer``.
412-
We might have data that looks like this:
413-
414-
.. code-block:: python
415-
416-
result = {"name": "metric", "analyzer_name": "thebest-analyzer", "value": "1", "install_file": "bin/wget"}
417-
418-
419-
We'd then return it as follows - note that they key is the analyzer name at ``self.name``.
420-
421-
.. code-block:: python
422-
423-
return {self.name: result}
424-
425-
This will save the complete result to the analyzer metadata folder, as described
426-
previously. If you want support for adding a different kind of metadata (e.g.,
427-
not associated with an install file) then the monitor server would need to be updated
428-
to support this first.
429-
430-
431-
^^^^^^^^^^^^^^^^^^^^^^^^^
432-
An analyzer init Function
433-
^^^^^^^^^^^^^^^^^^^^^^^^^
434-
435-
If you don't need any extra dependencies or checks, you can skip defining an analyzer
436-
init function, as the base class will handle it. Typically, it will accept
437-
a spec, and an optional output directory (if the user does not want the default
438-
metadata folder for analyzer results). The analyzer init function should call
439-
it's parent init, and then do any extra checks or validation that are required to
440-
work. For example:
441-
442-
.. code-block:: python
443-
444-
def __init__(self, spec, dirname=None):
445-
super(Myanalyzer, self).__init__(spec, dirname)
446-
447-
# install extra dependencies, do extra preparation and checks here
448-
449-
450-
At the end of the init, you will have available to you:
451-
452-
- **self.spec**: the spec object
453-
- **self.dirname**: an optional directory name the user as provided at init to save
454-
- **self.output_dir**: the analyzer metadata directory, where we save by default
455-
- **self.meta_dir**: the path to the package metadata directory (.spack) if you need it
456-
457-
And can proceed to write your analyzer.
458-
459-
460-
^^^^^^^^^^^^^^^^^^^^^^^
461-
Saving Analyzer Results
462-
^^^^^^^^^^^^^^^^^^^^^^^
463-
464-
The analyzer will have ``save_result`` called, with the result object generated
465-
to save it to the filesystem, and if the user has added the ``--monitor`` flag
466-
to upload it to a monitor server. If your result follows an accepted result
467-
format and you don't need to parse it further, you don't need to add this
468-
function to your class. However, if your result data is large or otherwise
469-
needs additional parsing, you can define it. If you define the function, it
470-
is useful to know about the ``output_dir`` property, which you can join
471-
with your output file relative path of choice:
472-
473-
.. code-block:: python
474-
475-
outfile = os.path.join(self.output_dir, "my-output-file.txt")
476-
477-
478-
The directory will be provided by the ``output_dir`` property but it won't exist,
479-
so you should create it:
480-
481-
482-
.. code::block:: python
483-
484-
# Create the output directory
485-
if not os.path.exists(self._output_dir):
486-
os.makedirs(self._output_dir)
487-
488-
489-
If you are generating results that match to specific files in the package
490-
install directory, you should try to maintain those paths in the case that
491-
there are equivalently named files in different directories that would
492-
overwrite one another. As an example of an analyzer with a custom save,
493-
the Libabigail analyzer saves ``*.xml`` files to the analyzer metadata
494-
folder in ``run()``, as they are either binaries, or as xml (text) would
495-
usually be too big to pass in one request. For this reason, the files
496-
are saved during ``run()`` and the filenames added to the result object,
497-
and then when the result object is passed back into ``save_result()``,
498-
we skip saving to the filesystem, and instead read the file and send
499-
each one (separately) to the monitor:
500-
501-
502-
.. code-block:: python
503-
504-
def save_result(self, result, monitor=None, overwrite=False):
505-
"""ABI results are saved to individual files, so each one needs to be
506-
read and uploaded. Result here should be the lookup generated in run(),
507-
the key is the analyzer name, and each value is the result file.
508-
We currently upload the entire xml as text because libabigail can't
509-
easily read gzipped xml, but this will be updated when it can.
510-
"""
511-
if not monitor:
512-
return
513-
514-
name = self.spec.package.name
515-
516-
for obj, filename in result.get(self.name, {}).items():
517-
518-
# Don't include the prefix
519-
rel_path = obj.replace(self.spec.prefix + os.path.sep, "")
520-
521-
# We've already saved the results to file during run
522-
content = spack.monitor.read_file(filename)
523-
524-
# A result needs an analyzer, value or binary_value, and name
525-
data = {"value": content, "install_file": rel_path, "name": "abidw-xml"}
526-
tty.info("Sending result for %s %s to monitor." % (name, rel_path))
527-
monitor.send_analyze_metadata(self.spec.package, {"libabigail": [data]})
528-
529-
530-
531-
Notice that this function, if you define it, requires a result object (generated by
532-
``run()``, a monitor (if you want to send), and a boolean ``overwrite`` to be used
533-
to check if a result exists first, and not write to it if the result exists and
534-
overwrite is False. Also notice that since we already saved these files to the analyzer metadata folder, we return early if a monitor isn't defined, because this function serves to send results to the monitor. If you haven't saved anything to the analyzer metadata folder
535-
yet, you might want to do that here. You should also use ``tty.info`` to give
536-
the user a message of "Writing result to $DIRNAME."
537-
538287

539288
.. _writing-commands:
540289

@@ -699,23 +448,6 @@ with a hook, and this is the purpose of this particular hook. Akin to
699448
``on_phase_success`` we require the same variables - the package that failed,
700449
the name of the phase, and the log file where we might find errors.
701450

702-
"""""""""""""""""""""""""""""""""
703-
``on_analyzer_save(pkg, result)``
704-
"""""""""""""""""""""""""""""""""
705-
706-
After an analyzer has saved some result for a package, this hook is called,
707-
and it provides the package that we just ran the analysis for, along with
708-
the loaded result. Typically, a result is structured to have the name
709-
of the analyzer as key, and the result object that is defined in detail in
710-
:ref:`analyzer_run_function`.
711-
712-
.. code-block:: python
713-
714-
def on_analyzer_save(pkg, result):
715-
"""given a package and a result...
716-
"""
717-
print('Do something extra with a package analysis result here')
718-
719451

720452
^^^^^^^^^^^^^^^^^^^^^^
721453
Adding a New Hook Type

lib/spack/spack/analyzers/__init__.py

Lines changed: 0 additions & 42 deletions
This file was deleted.

0 commit comments

Comments
 (0)