summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/docs/monitoring.rst16
-rw-r--r--lib/spack/spack/cmd/install.py1
-rw-r--r--lib/spack/spack/monitor.py115
-rw-r--r--lib/spack/spack/paths.py2
-rw-r--r--lib/spack/spack/test/monitor.py42
5 files changed, 154 insertions, 22 deletions
diff --git a/lib/spack/docs/monitoring.rst b/lib/spack/docs/monitoring.rst
index b1d491c563..41a7a4a2d2 100644
--- a/lib/spack/docs/monitoring.rst
+++ b/lib/spack/docs/monitoring.rst
@@ -102,3 +102,19 @@ more tags to your build, you can do:
# Add two tags, "pizza" and "pasta"
$ spack install --monitor --monitor-tags pizza,pasta hdf5
+
+------------------
+Monitoring Offline
+------------------
+
+In the case that you want to save monitor results to your filesystem
+and then upload them later (perhaps you are in an environment where you don't
+have credentials or it isn't safe to use them) you can use the ``--monitor-save-local``
+flag.
+
+.. code-block:: console
+
+ $ spack install --monitor --monitor-save-local hdf5
+
+This will save results in a subfolder, "monitor" in your designated spack
+reports folder, which defaults to ``$HOME/.spack/reports/monitor``.
diff --git a/lib/spack/spack/cmd/install.py b/lib/spack/spack/cmd/install.py
index 587f039656..5a0b47215a 100644
--- a/lib/spack/spack/cmd/install.py
+++ b/lib/spack/spack/cmd/install.py
@@ -306,6 +306,7 @@ environment variables:
prefix=args.monitor_prefix,
disable_auth=args.monitor_disable_auth,
tags=args.monitor_tags,
+ save_local=args.monitor_save_local,
)
reporter = spack.report.collect_info(
diff --git a/lib/spack/spack/monitor.py b/lib/spack/spack/monitor.py
index 5d6246ebf1..a6ca3be316 100644
--- a/lib/spack/spack/monitor.py
+++ b/lib/spack/spack/monitor.py
@@ -7,6 +7,8 @@
https://github.com/spack/spack-monitor/blob/main/script/spackmoncli.py
"""
+from datetime import datetime
+import hashlib
import base64
import os
import re
@@ -18,11 +20,13 @@ except ImportError:
from urllib2 import urlopen, Request, URLError # type: ignore # novm
import spack
+import spack.config
import spack.hash_types as ht
import spack.main
import spack.store
import spack.util.spack_json as sjson
import spack.util.spack_yaml as syaml
+import spack.util.path
import llnl.util.tty as tty
from copy import deepcopy
@@ -31,7 +35,8 @@ from copy import deepcopy
cli = None
-def get_client(host, prefix="ms1", disable_auth=False, allow_fail=False, tags=None):
+def get_client(host, prefix="ms1", disable_auth=False, allow_fail=False, tags=None,
+ save_local=False):
"""
Get a monitor client for a particular host and prefix.
@@ -47,26 +52,25 @@ def get_client(host, prefix="ms1", disable_auth=False, allow_fail=False, tags=No
"""
global cli
cli = SpackMonitorClient(host=host, prefix=prefix, allow_fail=allow_fail,
- tags=tags)
+ tags=tags, save_local=save_local)
# If we don't disable auth, environment credentials are required
- if not disable_auth:
+ if not disable_auth and not save_local:
cli.require_auth()
- # We will exit early if the monitoring service is not running
- info = cli.service_info()
-
- # If we allow failure, the response will be done
- if info:
- tty.debug("%s v.%s has status %s" % (
- info['id'],
- info['version'],
- info['status'])
- )
- return cli
-
- else:
- tty.debug("spack-monitor server not found, continuing as allow_fail is True.")
+ # We will exit early if the monitoring service is not running, but
+ # only if we aren't doing a local save
+ if not save_local:
+ info = cli.service_info()
+
+ # If we allow failure, the response will be done
+ if info:
+ tty.debug("%s v.%s has status %s" % (
+ info['id'],
+ info['version'],
+ info['status'])
+ )
+ return cli
def get_monitor_group(subparser):
@@ -83,6 +87,9 @@ def get_monitor_group(subparser):
'--monitor', action='store_true', dest='use_monitor', default=False,
help="interact with a montor server during builds.")
monitor_group.add_argument(
+ '--monitor-save-local', action='store_true', dest='monitor_save_local',
+ default=False, help="save monitor results to .spack instead of server.")
+ monitor_group.add_argument(
'--monitor-no-auth', action='store_true', dest='monitor_disable_auth',
default=False, help="the monitoring server does not require auth.")
monitor_group.add_argument(
@@ -110,7 +117,8 @@ class SpackMonitorClient:
to the client on init.
"""
- def __init__(self, host=None, prefix="ms1", allow_fail=False, tags=None):
+ def __init__(self, host=None, prefix="ms1", allow_fail=False, tags=None,
+ save_local=False):
self.host = host or "http://127.0.0.1"
self.baseurl = "%s/%s" % (self.host, prefix.strip("/"))
self.token = os.environ.get("SPACKMON_TOKEN")
@@ -120,9 +128,34 @@ class SpackMonitorClient:
self.spack_version = spack.main.get_version()
self.capture_build_environment()
self.tags = tags
+ self.save_local = save_local
# We keey lookup of build_id by full_hash
self.build_ids = {}
+ self.setup_save()
+
+ def setup_save(self):
+ """Given a local save "save_local" ensure the output directory exists.
+ """
+ if not self.save_local:
+ return
+
+ save_dir = spack.util.path.canonicalize_path(
+ spack.config.get('config:monitor_dir', '~/.spack/reports/monitor'))
+
+ # Name based on timestamp
+ now = datetime.now().strftime('%Y-%m-%d-%H-%M-%S-%s')
+ self.save_dir = os.path.join(save_dir, now)
+ if not os.path.exists(self.save_dir):
+ os.makedirs(self.save_dir)
+
+ def save(self, obj, filename):
+ """
+ Save a monitor json result to the save directory.
+ """
+ filename = os.path.join(self.save_dir, filename)
+ write_json(obj, filename)
+ return {"message": "Build saved locally to %s" % filename}
def load_build_environment(self, spec):
"""
@@ -174,7 +207,7 @@ class SpackMonitorClient:
The token and username must not be unset
"""
- if not self.token or not self.username:
+ if not self.save_local and (not self.token or not self.username):
tty.die("You are required to export SPACKMON_TOKEN and SPACKMON_USER")
def set_header(self, name, value):
@@ -346,8 +379,14 @@ class SpackMonitorClient:
spec.concretize()
as_dict = {"spec": spec.to_dict(hash=ht.full_hash),
"spack_version": self.spack_version}
- response = self.do_request("specs/new/", data=sjson.dump(as_dict))
- configs[spec.package.name] = response.get('data', {})
+
+ if self.save_local:
+ filename = "spec-%s-%s-config.json" % (spec.name, spec.version)
+ self.save(sjson.dump(as_dict), filename)
+ else:
+ response = self.do_request("specs/new/", data=sjson.dump(as_dict))
+ configs[spec.package.name] = response.get('data', {})
+
return configs
def new_build(self, spec):
@@ -384,6 +423,27 @@ class SpackMonitorClient:
spec_file = os.path.join(meta_dir, "spec.yaml")
data['spec'] = syaml.load(read_file(spec_file))
+ if self.save_local:
+ return self.get_local_build_id(data, full_hash, return_response)
+ return self.get_server_build_id(data, full_hash, return_response)
+
+ def get_local_build_id(self, data, full_hash, return_response):
+ """
+ Generate a local build id based on hashing the expected data
+ """
+ hasher = hashlib.md5()
+ hasher.update(str(data).encode('utf-8'))
+ bid = hasher.hexdigest()
+ filename = "build-metadata-%s.json" % full_hash
+ response = self.save(sjson.dump(data), filename)
+ if return_response:
+ return response
+ return bid
+
+ def get_server_build_id(self, data, full_hash, return_response=False):
+ """
+ Retrieve a build id from the spack monitor server
+ """
response = self.do_request("builds/new/", data=sjson.dump(data))
# Add the build id to the lookup
@@ -403,6 +463,10 @@ class SpackMonitorClient:
successful install. This endpoint can take a general status to update.
"""
data = {"build_id": self.get_build_id(spec), "status": status}
+ if self.save_local:
+ filename = "build-%s-status.json" % data['build_id']
+ return self.save(sjson.dump(data), filename)
+
return self.do_request("builds/update/", data=sjson.dump(data))
def fail_task(self, spec):
@@ -444,6 +508,10 @@ class SpackMonitorClient:
"output": read_file(phase_output_file),
"phase_name": phase_name})
+ if self.save_local:
+ filename = "build-%s-phase-%s.json" % (data['build_id'], phase_name)
+ return self.save(sjson.dump(data), filename)
+
return self.do_request("builds/phases/update/", data=sjson.dump(data))
def upload_specfile(self, filename):
@@ -459,6 +527,11 @@ class SpackMonitorClient:
# We load as json just to validate it
spec = read_json(filename)
data = {"spec": spec, "spack_verison": self.spack_version}
+
+ if self.save_local:
+ filename = "spec-%s-%s.json" % (spec.name, spec.version)
+ return self.save(sjson.dump(data), filename)
+
return self.do_request("specs/new/", data=sjson.dump(data))
diff --git a/lib/spack/spack/paths.py b/lib/spack/spack/paths.py
index b0ff031dd1..c45a5a6b0f 100644
--- a/lib/spack/spack/paths.py
+++ b/lib/spack/spack/paths.py
@@ -54,7 +54,7 @@ user_config_path = os.path.expanduser('~/.spack')
user_bootstrap_path = os.path.join(user_config_path, 'bootstrap')
user_bootstrap_store = os.path.join(user_bootstrap_path, 'store')
reports_path = os.path.join(user_config_path, "reports")
-
+monitor_path = os.path.join(reports_path, "monitor")
opt_path = os.path.join(prefix, "opt")
etc_path = os.path.join(prefix, "etc")
diff --git a/lib/spack/spack/test/monitor.py b/lib/spack/spack/test/monitor.py
new file mode 100644
index 0000000000..e8b466ab1a
--- /dev/null
+++ b/lib/spack/spack/test/monitor.py
@@ -0,0 +1,42 @@
+# Copyright 2013-2021 Lawrence Livermore National Security, LLC and other
+# Spack Project Developers. See the top-level COPYRIGHT file for details.
+#
+# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+
+import spack.config
+import spack.spec
+from spack.main import SpackCommand
+import pytest
+import os
+
+install = SpackCommand('install')
+
+
+@pytest.fixture(scope='session')
+def test_install_monitor_save_local(install_mockery_mutable_config,
+ mock_fetch, tmpdir_factory):
+ """
+ Mock installing and saving monitor results to file.
+ """
+ reports_dir = tmpdir_factory.mktemp('reports')
+ spack.config.set('config:monitor_dir', str(reports_dir))
+ out = install('--monitor', '--monitor-save-local', 'dttop')
+ assert "Successfully installed dttop" in out
+
+ # The reports directory should not be empty (timestamped folders)
+ assert os.listdir(str(reports_dir))
+
+ # Get the spec name
+ spec = spack.spec.Spec("dttop")
+ spec.concretize()
+ full_hash = spec.full_hash()
+
+ # Ensure we have monitor results saved
+ for dirname in os.listdir(str(reports_dir)):
+ dated_dir = os.path.join(str(reports_dir), dirname)
+ build_metadata = "build-metadata-%s.json" % full_hash
+ assert build_metadata in os.listdir(dated_dir)
+ spec_file = "spec-dttop-%s-config.json" % spec.version
+ assert spec_file in os.listdir(dated_dir)
+
+ spack.config.set('config:monitor_dir', "~/.spack/reports/monitor")