@@ -107,7 +107,6 @@ with a high level view of Spack's directory structure:
107
107
llnl/ <- some general-use libraries
108
108
109
109
spack/ <- spack module; contains Python code
110
- analyzers/ <- modules to run analysis on installed packages
111
110
build_systems/ <- modules for different build systems
112
111
cmd/ <- each file in here is a spack subcommand
113
112
compilers/ <- compiler description files
@@ -242,22 +241,6 @@ Unit tests
242
241
Implements Spack's test suite. Add a module and put its name in
243
242
the test suite in ``__init__.py `` to add more unit tests.
244
243
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
-
261
244
262
245
^^^^^^^^^^^^^
263
246
Other Modules
@@ -301,240 +284,6 @@ Most spack commands look something like this:
301
284
The information in Package files is used at all stages in this
302
285
process.
303
286
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
-
538
287
539
288
.. _writing-commands :
540
289
@@ -699,23 +448,6 @@ with a hook, and this is the purpose of this particular hook. Akin to
699
448
``on_phase_success `` we require the same variables - the package that failed,
700
449
the name of the phase, and the log file where we might find errors.
701
450
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
-
719
451
720
452
^^^^^^^^^^^^^^^^^^^^^^
721
453
Adding a New Hook Type
0 commit comments