Files
gem5/src/python/gem5/resources/resource.py
Bobby R. Bruce c718e94753 stdlib: Add comment to ShadowResource (#645)
This comment explains that this solution is a hack the solution created
by https://github.com/gem5/gem5/issues/644 should eventually replace it.
2023-12-03 13:38:59 -08:00

1266 lines
44 KiB
Python

# Copyright (c) 2021-2023 The Regents of the University of California
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met: redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer;
# redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution;
# neither the name of the copyright holders nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import json
import os
from abc import ABCMeta
from functools import partial
from pathlib import Path
from typing import (
Any,
Dict,
Generator,
List,
Optional,
Set,
Tuple,
Type,
Union,
)
from m5.util import (
fatal,
warn,
)
from _m5 import core
from ..isas import (
ISA,
get_isa_from_str,
)
from .client import get_resource_json_obj
from .downloader import get_resource
from .looppoint import (
LooppointCsvLoader,
LooppointJsonLoader,
)
"""
Resources are items needed to run a simulation, such as a disk image, kernel,
or binary. The gem5 project provides pre-built resources, with sources, at
<resources.gem5.org>. Here we provide the AbstractResource class and its
various implementations which are designed to encapsulate a resource for use
in the gem5 Standard Library.
These classes may be contructed directly. E.g.:
.. code-block:: python
binary = BinaryResource(local_path="/path/to/binary")
or obtained via the gem5-resources infrastructure with the ``obtain_resource``
function:
.. code-block:: python
binary = obtain_resource("resource name here")
"""
class AbstractResource:
"""
An abstract class which all Resource classes inherit from.
"""
__metaclass__ = ABCMeta
def __init__(
self,
id: Optional[str] = None,
resource_version: Optional[str] = None,
local_path: Optional[str] = None,
description: Optional[str] = None,
source: Optional[str] = None,
downloader: Optional[partial] = None,
):
"""
:param local_path: The path on the host system where this resource is
located.
:param description: Description describing this resource. Not a
required parameter. By default is ``None``.
:param source: The source (as in "source code") for this resource. This
string should navigate users to where the source for this
resource may be found. Not a required parameter. By default
is ``None``.
:param resource_version: Version of the resource itself.
:param downloader: A partial function which is used to download the
resource. If set, this is called if the resource is not present at the
specified `local_path`.
"""
self._id = id
self._local_path = local_path
self._description = description
self._source = source
self._version = resource_version
self._downloader = downloader
def get_id(self) -> str:
"""Returns the ID of the resource."""
return self._id
def get_category_name(cls) -> str:
raise NotImplementedError
def __str__(self):
message = (
f"{self.get_category_name()}({self._id}, {self._version})\n"
"For more information, please visit "
f"https://resources.gem5.org/resources/{self._id}?"
f"version={self._version}"
)
return message
def get_resource_version(self) -> str:
"""Returns the version of the resource."""
return self._version
def get_local_path(self) -> Optional[str]:
"""Returns the local path of the resource.
If specified the `downloader` partial function is called to download
the resource if it is not present or up-to-date at the specified
`local_path`.
"""
if self._downloader:
self._downloader()
if self._local_path and not os.path.exists(self._local_path):
raise Exception(
f"Local path specified for resource, '{self._local_path}', "
"does not exist."
)
return self._local_path
def get_description(self) -> Optional[str]:
"""Returns description associated with this resource."""
return self._description
def get_source(self) -> Optional[str]:
"""Returns information as to where the source for this resource may be
found.
"""
return self._source
class FileResource(AbstractResource):
"""A resource consisting of a single file."""
def __init__(
self,
local_path: str,
id: Optional[str] = None,
resource_version: Optional[str] = None,
description: Optional[str] = None,
source: Optional[str] = None,
downloader: Optional[partial] = None,
**kwargs,
):
super().__init__(
local_path=local_path,
id=id,
description=description,
source=source,
resource_version=resource_version,
downloader=downloader,
)
def get_category_name(cls) -> str:
return "FileResource"
def get_local_path(self) -> Optional[str]:
# Here we override get_local_path to ensure the file exists.
file_path = super().get_local_path()
if not file_path:
raise Exception("FileResource path not specified.")
if not os.path.isfile(file_path):
raise Exception(
f"FileResource path specified, '{file_path}', is not a file."
)
return file_path
class DirectoryResource(AbstractResource):
"""A resource consisting of a directory."""
def __init__(
self,
local_path: str,
id: Optional[str] = None,
resource_version: Optional[str] = None,
description: Optional[str] = None,
source: Optional[str] = None,
downloader: Optional[partial] = None,
**kwargs,
):
super().__init__(
local_path=local_path,
id=id,
description=description,
source=source,
resource_version=resource_version,
downloader=downloader,
)
def get_category_name(cls) -> str:
return "DirectoryResource"
def get_local_path(self) -> Optional[str]:
# Here we override get_local_path to ensure the directory exists.
dir_path = super().get_local_path()
if not dir_path:
raise Exception("DirectoryResource path not specified.")
if not os.path.isdir(dir_path):
raise Exception(
f"DirectoryResource path specified, {dir_path}, is not a "
"directory."
)
return dir_path
class DiskImageResource(FileResource):
"""A Disk Image resource."""
def __init__(
self,
local_path: str,
id: Optional[str] = None,
resource_version: Optional[str] = None,
description: Optional[str] = None,
source: Optional[str] = None,
downloader: Optional[partial] = None,
root_partition: Optional[str] = None,
**kwargs,
):
super().__init__(
local_path=local_path,
id=id,
description=description,
source=source,
resource_version=resource_version,
downloader=downloader,
)
self._root_partition = root_partition
def get_root_partition(self) -> Optional[str]:
"""Returns, if applicable, the Root Partition of the disk image."""
return self._root_partition
def get_category_name(cls) -> str:
return "DiskImageResource"
class BinaryResource(FileResource):
"""A binary resource."""
def __init__(
self,
local_path: str,
id: Optional[str] = None,
resource_version: Optional[str] = None,
description: Optional[str] = None,
source: Optional[str] = None,
downloader: Optional[partial] = None,
architecture: Optional[Union[ISA, str]] = None,
**kwargs,
):
super().__init__(
local_path=local_path,
id=id,
description=description,
source=source,
resource_version=resource_version,
downloader=downloader,
)
self._architecture = None
if architecture:
if isinstance(architecture, str):
self._architecture = get_isa_from_str(architecture)
elif isinstance(architecture, ISA):
self._architecture = architecture
def get_category_name(cls) -> str:
return "BinaryResource"
def get_architecture(self) -> Optional[ISA]:
"""Returns the ISA this binary is compiled to."""
return self._architecture
class BootloaderResource(BinaryResource):
"""A bootloader resource."""
def __init__(
self,
local_path: str,
id: Optional[str] = None,
resource_version: Optional[str] = None,
description: Optional[str] = None,
source: Optional[str] = None,
downloader: Optional[partial] = None,
architecture: Optional[Union[ISA, str]] = None,
**kwargs,
):
super().__init__(
local_path=local_path,
id=id,
description=description,
architecture=architecture,
source=source,
resource_version=resource_version,
downloader=downloader,
)
def get_category_name(cls) -> str:
return "BootloaderResource"
class GitResource(DirectoryResource):
"""A git resource."""
def __init__(
self,
local_path: str,
id: Optional[str] = None,
resource_version: Optional[str] = None,
description: Optional[str] = None,
source: Optional[str] = None,
downloader: Optional[partial] = None,
**kwargs,
):
super().__init__(
local_path=local_path,
id=id,
description=description,
source=source,
resource_version=resource_version,
downloader=downloader,
)
def get_category_name(cls) -> str:
return "GitResource"
class KernelResource(BinaryResource):
"""A kernel resource."""
def __init__(
self,
local_path: str,
id: Optional[str] = None,
resource_version: Optional[str] = None,
description: Optional[str] = None,
source: Optional[str] = None,
downloader: Optional[partial] = None,
architecture: Optional[Union[ISA, str]] = None,
**kwargs,
):
super().__init__(
local_path=local_path,
id=id,
description=description,
source=source,
architecture=architecture,
resource_version=resource_version,
downloader=downloader,
)
def get_category_name(cls) -> str:
return "KernelResource"
class CheckpointResource(DirectoryResource):
"""A checkpoint resource. The following directory structure is expected:
<local_path>:
- board.physmem.store0.pmem
- m5.cpt
"""
def __init__(
self,
local_path: str,
id: Optional[str] = None,
resource_version: Optional[str] = None,
description: Optional[str] = None,
source: Optional[str] = None,
downloader: Optional[partial] = None,
**kwargs,
):
super().__init__(
local_path=local_path,
id=id,
description=description,
source=source,
resource_version=resource_version,
downloader=downloader,
)
def get_category_name(cls) -> str:
return "CheckpointResource"
class SimpointResource(AbstractResource):
"""A SimPoint resource. This resource stores all information required to
perform a SimPoint creation and restore. It contains the SimPoint, the
SimPoint interval, the weight for each SimPoint, the full warmup length,
and the warmup length for each SimPoint.
"""
def __init__(
self,
resource_version: Optional[str] = None,
simpoint_interval: int = None,
simpoint_list: List[int] = None,
weight_list: List[float] = None,
warmup_interval: int = 0,
id: Optional[str] = None,
workload_name: Optional[str] = None,
description: Optional[str] = None,
source: Optional[str] = None,
downloader: Optional[partial] = None,
local_path: Optional[str] = None,
**kwargs,
):
"""
:param simpoint_interval: The SimPoint interval.
:param simpoint_list: The SimPoint list.
:param weight_list: The weight list.
:param warmup_interval: The warmup interval. Default to zero (a value
of zero means effectively not set).
:param workload_name: SimPoints are typically associated with a
particular workload due to their dependency on
chosen input parameters.
This field helps backtrack to that resource if
required. This should relate to a workload "name"
field in the ``resource.json`` file.
"""
super().__init__(
local_path=local_path,
id=id,
description=description,
source=source,
resource_version=resource_version,
downloader=downloader,
)
self._weight_list = weight_list
self._simpoint_list = simpoint_list
self._simpoint_interval = simpoint_interval
self._warmup_interval = warmup_interval
self._workload_name = workload_name
self._simpoint_start_insts = list(
inst * simpoint_interval for inst in self.get_simpoint_list()
)
if self._warmup_interval != 0:
self._warmup_list = self._set_warmup_list()
else:
self._warmup_list = [0] * len(self.get_simpoint_start_insts)
def get_simpoint_list(self) -> List[int]:
"""Returns the a list containing all the SimPoints for the workload."""
return self._simpoint_list
def get_simpoint_start_insts(self) -> List[int]:
"""Returns a lst containing all the SimPoint starting instrunction
points for the workload. This was calculated by multiplying the
SimPoint with the SimPoint interval when it was generated."""
return self._simpoint_start_insts
def get_warmup_interval(self) -> int:
"""Returns the instruction length of the warmup interval."""
return self._warmup_interval
def get_weight_list(self) -> List[float]:
"""Returns the list that contains the weight for each SimPoint. The
order of the weights matches that of the list returned by
``get_simpoint_list()``. I.e. ``get_weight_list()[3]`` is the weight for
SimPoint ``get_simpoint_list()[3]``."""
return self._weight_list
def get_simpoint_interval(self) -> int:
"""Returns the SimPoint interval value."""
return self._simpoint_interval
def get_warmup_list(self) -> List[int]:
"""Returns the a list containing the warmup length for each SimPoint.
Each warmup length in this list corresponds to the SimPoint at the same
index in ``get_simpoint_list()``. I.e., ``get_warmup_list()[4]`` is the
warmup length for SimPoint ``get_simpoint_list()[4]``."""
return self._warmup_list
def get_workload_name(self) -> Optional[str]:
"""Return the workload name this SimPoint is associated with."""
return self._workload_name
def _set_warmup_list(self) -> List[int]:
"""
This function uses the ``warmup_interval``, fits it into the
``simpoint_start_insts``, and outputs a list of warmup instruction lengths
for each SimPoint.
The warmup instruction length is calculated using the starting
instruction of a SimPoint to minus the ``warmup_interval`` and the ending
instruction of the last SimPoint. If it is less than 0, then the warmup
instruction length is the gap between the starting instruction of a
SimPoint and the ending instruction of the last SimPoint.
"""
warmup_list = []
for index, start_inst in enumerate(self.get_simpoint_start_insts()):
warmup_inst = start_inst - self.get_warmup_interval()
if warmup_inst < 0:
warmup_inst = start_inst
else:
warmup_inst = self.get_warmup_interval()
warmup_list.append(warmup_inst)
# change the starting instruction of a SimPoint to include the
# warmup instruction length
self._simpoint_start_insts[index] = start_inst - warmup_inst
return warmup_list
def get_category_name(cls) -> str:
return "SimpointResource"
class LooppointCsvResource(FileResource, LooppointCsvLoader):
"""This LoopPoint resource used to create a LoopPoint resource from a
pinpoints CSV file."""
def __init__(
self,
local_path: str,
id: Optional[str] = None,
resource_version: Optional[str] = None,
description: Optional[str] = None,
source: Optional[str] = None,
downloader: Optional[partial] = None,
**kwargs,
):
FileResource.__init__(
self,
local_path=local_path,
id=id,
description=description,
source=source,
resource_version=resource_version,
downloader=downloader,
)
LooppointCsvLoader.__init__(self, pinpoints_file=Path(local_path))
def get_category_name(cls) -> str:
return "LooppointCsvResource"
class LooppointJsonResource(FileResource, LooppointJsonLoader):
def __init__(
self,
local_path: str,
id: Optional[str] = None,
resource_version: Optional[str] = None,
region_id: Optional[Union[str, int]] = None,
description: Optional[str] = None,
source: Optional[str] = None,
downloader: Optional[partial] = None,
**kwargs,
):
FileResource.__init__(
self,
local_path=local_path,
id=id,
description=description,
source=source,
resource_version=resource_version,
downloader=downloader,
)
LooppointJsonLoader.__init__(
self, looppoint_file=local_path, region_id=region_id
)
def get_category_name(cls) -> str:
return "LooppointJsonResource"
class SimpointDirectoryResource(SimpointResource):
"""A SimPoint diretory resource. This SimPoint Resource assumes the
existance of a directory containing a SimPoint file and a weight file."""
def __init__(
self,
local_path: str,
simpoint_file: str,
weight_file: str,
simpoint_interval: int,
warmup_interval: int,
id: Optional[str] = None,
resource_version: Optional[str] = None,
workload_name: Optional[str] = None,
description: Optional[str] = None,
source: Optional[str] = None,
downloader: Optional[partial] = None,
**kwargs,
):
"""
:param simpoint_file: The SimPoint file. This file is a list of
SimPoints, each on its own line. It should map
1-to-1 to the weights file.
:param weight_file: The SimPoint weights file. This file is a list of
weights, each on its own line.
"""
self._simpoint_file = simpoint_file
self._weight_file = weight_file
# This is a little hack. The functions `get_simpoint_file` and
# `get_weight_file` use the local path, so we set it here despite it
# also being set in the `AbstractResource` constructor. This isn't
# elegant but does not harm.
self._local_path = local_path
(
simpoint_list,
weight_list,
) = self._get_weights_and_simpoints_from_file()
super().__init__(
simpoint_interval=simpoint_interval,
simpoint_list=simpoint_list,
weight_list=weight_list,
warmup_interval=warmup_interval,
workload_name=workload_name,
local_path=local_path,
id=id,
description=description,
source=source,
downloader=downloader,
resource_version=resource_version,
)
def get_simpoint_file(self) -> Path:
"""Return the SimPoint File path."""
return Path(Path(self._local_path) / self._simpoint_file)
def get_weight_file(self) -> Path:
"""Returns the Weight File path."""
return Path(Path(self._local_path) / self._weight_file)
def _get_weights_and_simpoints_from_file(
self,
) -> Tuple[List[int], List[int]]:
"""This is a helper function to extract the weights and SimPoints from
the files.
"""
simpoint_weight_pair = []
with open(self.get_simpoint_file()) as simpoint_file, open(
self.get_weight_file()
) as weight_file:
while True:
line = simpoint_file.readline()
if not line:
break
interval = int(line.split(" ", 1)[0])
line = weight_file.readline()
if not line:
fatal("not engough weights")
weight = float(line.split(" ", 1)[0])
simpoint_weight_pair.append((interval, weight))
simpoint_weight_pair.sort(key=lambda obj: obj[0])
# use simpoint to sort
weight_list = []
simpoint_list = []
for simpoint, weight in simpoint_weight_pair:
simpoint_list.append(simpoint)
weight_list.append(weight)
return simpoint_list, weight_list
def get_category_name(cls) -> str:
return "SimpointDirectoryResource"
class SuiteResource(AbstractResource):
"""
A suite resource. This resource is used to specify a suite of workloads to
run on a board. It contains a list of workloads to run, along with their
IDs and versions.
Each workload in a suite is used to create a `WorkloadResource` object.
These objects are stored in a list and can be iterated over.
"""
def __init__(
self,
workloads: Dict["WorkloadResource", Set[str]] = {},
resource_version: Optional[str] = None,
description: Optional[str] = None,
source: Optional[str] = None,
id: Optional[str] = None,
**kwargs,
) -> None:
"""
:param workloads: A list of ``WorkloadResource`` objects
created from the ``_workloads`` parameter.
:param local_path: The path on the host system where this resource is
located.
:param description: Description describing this resource. Not a
required parameter. By default is ``None``.
:param source: The source (as in "source code") for this resource
on gem5-resources. Not a required parameter. By default
is ``None``.
:param resource_version: Version of the resource itself.
"""
self._workloads = workloads
self._description = description
self._source = source
self._resource_version = resource_version
super().__init__(
id=id,
description=description,
source=source,
resource_version=resource_version,
)
def __iter__(self) -> Generator["WorkloadResource", None, None]:
"""
Returns a generator that iterates over the workloads in the suite.
:yields: A generator that iterates over the workloads in the suite.
"""
yield from self._workloads.keys()
def __len__(self):
"""
Returns the number of workloads in the suite.
:returns: The number of workloads in the suite.
"""
return len(self._workloads)
def get_category_name(cls) -> str:
return "SuiteResource"
def with_input_group(self, input_group: str) -> "SuiteResource":
"""
:param input_group: The input group to filter the workloads by.
:returns: A new SuiteResource object with only the workloads that use
the specified input group.
"""
if input_group not in self.get_input_groups():
raise Exception(
f"Input group {input_group} not found in Suite.\n"
f"Available input groups are {self.get_input_groups()}"
)
filtered_workloads = {}
for workload, input_groups in self._workloads.items():
if input_group in input_groups:
filtered_workloads[workload] = input_groups
return SuiteResource(
local_path=self._local_path,
resource_version=self._resource_version,
description=self._description,
source=self._source,
workloads=filtered_workloads,
)
def get_input_groups(self) -> Set[str]:
"""
:returns: A set of all input groups used by the workloads in a suite.
"""
return {
input_group
for input_groups in self._workloads.values()
for input_group in input_groups
}
class ShadowResource(AbstractResource):
"""A special resource class which delays the `obtain_resource` call. It is,
in a sense, half constructed. Only when a function or attribute is called
which is is neither `get_id` or `get_resource_version` does this class
fully construct itself by calling the `obtain_resource_call` partial
function.
**Note:** This class is a hack. The ideal solution to this would be to
enable the bundled obtaining of resources in the gem5 Standard Library.
Use of the class is discouraged and should not be depended on. Issue
https://github.com/gem5/gem5/issues/644 is tracking the implementation of
an alternative.
"""
def __init__(
self,
id: str,
resource_version: str,
obtain_resource_call: partial,
):
super().__init__(
id=id,
resource_version=resource_version,
)
self._workload: Optional[AbstractResource] = None
self._obtain_resource_call = obtain_resource_call
def __getattr__(self, attr):
"""if getting the id or resource version, we keep the object in the
"shdow state" where the `obtain_resource` function has not been called.
When more information is needed by calling another attribute, we call
the `obtain_resource` function and store the result in the `_workload`.
"""
if attr in {"get_id", "get_resource_version"}:
return getattr(super(), attr)
if not self._workload:
self._workload = self._obtain_resource_call()
return getattr(self._workload, attr)
class WorkloadResource(AbstractResource):
"""A workload resource. This resource is used to specify a workload to run
on a board. It contains the function to call and the parameters to pass to
that function.
"""
def __init__(
self,
function: str = None,
id: Optional[str] = None,
resource_version: Optional[str] = None,
description: Optional[str] = None,
source: Optional[str] = None,
local_path: Optional[str] = None,
parameters: Optional[Dict[str, Any]] = {},
**kwargs,
):
"""
:param function: The function to call on the board.
:param parameters: The parameters to pass to the function.
"""
super().__init__(
local_path=local_path,
id=id,
description=description,
source=source,
resource_version=resource_version,
)
self._id = id
self._func = function
self._params = parameters
def get_id(self) -> str:
"""Returns the ID of the workload."""
return self._id
def get_function_str(self) -> str:
"""
Returns the name of the workload function to be run.
This function is called via the AbstractBoard's ``set_workload``
function. The parameters from the ``get_parameters`` function are passed
to this function.
"""
return self._func
def get_parameters(self) -> Dict[str, Any]:
"""
Returns a dictionary mapping the workload parameters to their values.
These parameters are passed to the function specified by
``get_function_str`` via the AbstractBoard's ``set_workload`` function.
"""
return self._params
def set_parameter(self, parameter: str, value: Any) -> None:
"""
Used to set or override a workload parameter
:param parameter: The parameter of the function to set.
:param value: The value to set to the parameter.
"""
self._params[parameter] = value
def get_category_name(cls) -> str:
return "WorkloadResource"
def obtain_resource(
resource_id: str,
resource_directory: Optional[str] = None,
download_md5_mismatch: bool = True,
resource_version: Optional[str] = None,
clients: Optional[List] = None,
gem5_version=core.gem5Version,
to_path: Optional[str] = None,
quiet: bool = False,
) -> AbstractResource:
"""
This function primarily serves as a factory for resources. It will return
the correct AbstractResource implementation based on the resource
requested.
:param resource_name: The name of the gem5 resource as it appears under the
"id" field in the ``resource.json`` file.
:param resource_directory: The location of the directory in which the
resource is to be stored. If this parameter is not
set, it will set to the environment variable
``GEM5_RESOURCE_DIR``. If the environment is not set
it will default to ``~/.cache/gem5`` if available,
otherwise the CWD. *Note*: This argument is ignored
if the ``to_path`` parameter is specified.
:param download_md5_mismatch: If the resource is present, but does not have
the correct md5 value, the resource will be
deleted and re-downloaded if this value is ``True``.
Otherwise an exception will be thrown. ``True`` by
default.
:param resource_version: Version of the resource itself.
Not a required parameter. ``None`` by default.
:param clients: A list of clients to search for the resource. If this
parameter is not set, it will default search all clients.
:param gem5_version: The gem5 version to use to filter incompatible
resource versions. By default set to the current gem5
version. If `None`, this filtering is not performed.
:param to_path: The path to which the resource is to be downloaded. If
``None``, the resource will be downloaded to the resource directory
with the file/directory name equal to the ID of the resource.
**Note**: Usage of this parameter will override the
``resource_directory`` parameter.
:param quiet: If ``True``, suppress output. ``False`` by default.
"""
# Obtain the resource object entry for this resource
resource_json = get_resource_json_obj(
resource_id,
resource_version=resource_version,
clients=clients,
gem5_version=gem5_version,
)
# This is is used to store the partial function which is used to download
# the resource when the `get_local_path` function is called.
downloader: Optional[partial] = None
# If the "url" field is specified, the resoruce must be downloaded.
if "url" in resource_json and resource_json["url"]:
# If the `to_path` parameter is set, we use that as the path to which
# the resource is to be downloaded. Otherwise, default to the
# `resource_directory` parameter plus the resource ID.
if not to_path:
# If the `resource_directory` parameter is not set via this
# function, we heck the "GEM5_RESOURCE_DIR" environment variable.
# If this too is not set we call `_get_default_resource_dir()` to
# determine where the resource directory is, or should be, located.
if resource_directory == None:
resource_directory = os.getenv(
"GEM5_RESOURCE_DIR", _get_default_resource_dir()
)
# Small checks here to ensure the resource directory is valid.
if os.path.exists(resource_directory):
if not os.path.isdir(resource_directory):
raise Exception(
"gem5 resource directory, "
"'{}', exists but is not a directory".format(
resource_directory
)
)
# This is the path to which the resource is to be stored.
to_path = os.path.join(resource_directory, resource_id)
assert to_path
# Here we ensure the directory in which the resource is to be stored
# is created.
#
# `exist_ok=True` here as, occasionally, if multiple instance of gem5
# are started simultaneously, a race condition can exist to create the
# resource directory. Without `exit_ok=True`, threads which lose this
# race will thrown a `FileExistsError` exception. `exit_ok=True`
# ensures no exception is thrown.
try:
Path(to_path).parent.mkdir(parents=True, exist_ok=True)
except Exception as e:
fatal(
f"Recursive creation of the directory "
f"'{Path(to_path).parent.absolute}' failed. \n"
f"Perhaps the path specified, '{to_path}', is incorrect?\n"
f"Failed with Exception:\n{e}"
)
# Download the resource if it does not already exist.
downloader = partial(
get_resource,
resource_name=resource_id,
to_path=to_path,
download_md5_mismatch=download_md5_mismatch,
resource_version=resource_version,
clients=clients,
gem5_version=gem5_version,
quiet=quiet,
)
# Obtain the type from the JSON. From this we will determine what subclass
# of `AbstractResource` we are to create and return.
resources_category = resource_json["category"]
if resources_category == "resource":
# This is a stop-gap measure to ensure to work with older versions of
# the "resource.json" file. These should be replaced with their
# respective specializations ASAP and this case removed.
if "root_partition" in resource_json:
# In this case we should return a DiskImageResource.
root_partition = resource_json["root_partition"]
return DiskImageResource(
local_path=to_path,
root_partition=root_partition,
downloader=downloader,
**resource_json,
)
return CustomResource(local_path=to_path, downloader=downloader)
assert resources_category in _get_resource_json_type_map
resource_class = _get_resource_json_type_map[resources_category]
if resources_category == "suite":
workloads = resource_json["workloads"]
workloads_obj = {}
for workload in workloads:
workloads_obj[
ShadowResource(
id=workload["id"],
resource_version=workload["resource_version"],
obtain_resource_call=partial(
obtain_resource,
workload["id"],
resource_version=workload["resource_version"],
resource_directory=resource_directory,
clients=clients,
gem5_version=gem5_version,
),
)
] = set(workload["input_group"])
resource_json["workloads"] = workloads_obj
if resources_category == "workload":
# This parses the "resources" and "additional_params" fields of the
# workload resource into a dictionary of AbstractResource objects and
# strings respectively.
params = {}
if "resources" in resource_json:
for key in resource_json["resources"].keys():
assert isinstance(key, str)
value = resource_json["resources"][key]
assert isinstance(value, dict)
params[key] = obtain_resource(
value["id"],
resource_version=value["resource_version"],
resource_directory=resource_directory,
clients=clients,
gem5_version=gem5_version,
)
if "additional_params" in resource_json:
for key in resource_json["additional_params"].keys():
assert isinstance(key, str)
value = resource_json["additional_params"][key]
assert isinstance(value, str)
params[key] = value
resource_json["parameters"] = params
# Once we know what AbstractResource subclass we are using, we create it.
# The fields in the JSON object are assumed to map like-for-like to the
# subclass contructor, so we can pass the resource_json map directly.
return resource_class(
local_path=to_path, downloader=downloader, **resource_json
)
def _get_default_resource_dir() -> str:
"""
Obtain the default gem5 resources directory on the host system. This
function will iterate through sensible targets until it finds one that
works on the host system.
:returns: The default gem5 resources directory.
"""
test_list = [
# First try `~/.cache/gem5`.
os.path.join(Path.home(), ".cache", "gem5"),
# Last resort, just put things in the cwd.
os.path.join(Path.cwd(), "resources"),
]
for path in test_list:
if os.path.exists(path): # If the path already exists...
if os.path.isdir(path): # Check to see the path is a directory.
return path # If so, the path is valid and can be used.
else: # If the path does not exist, try to create it.
try:
os.makedirs(path, exist_ok=False)
return path
except OSError:
continue # If the path cannot be created, then try another.
raise Exception("Cannot find a valid location to download resources")
# The following classes exist to preserve backwards functionality between the
# API for obtaining resources in v21.1.0 and prior.
class CustomResource(AbstractResource):
"""
A custom gem5 resource. This can be used to encapsulate a resource provided
by a gem5 user as opposed to one available within the gem5 resources
repository.
.. warning::
This class is deprecated and will be removed in future releases of gem5.
Please use the correct AbstractResource subclass instead.
"""
def __init__(self, local_path: str, metadata: Dict = {}):
"""
:param local_path: The path of the resource on the host system.
:param metadata: Add metadata for the custom resource. **Warning:**
As of v22.1.1, this parameter is not used.
"""
warn(
"The `CustomResource` class is deprecated. Please use an "
"`AbstractResource` subclass instead."
)
if bool(metadata): # Empty dicts cast to False
warn(
"the `metadata` parameter was set via the `CustomResource` "
"constructor. This parameter is not used."
)
super().__init__(local_path=local_path)
class CustomDiskImageResource(DiskImageResource):
"""
A custom disk image gem5 resource. It can be used to specify a custom,
local disk image.
.. warning::
This class is deprecated and will be removed in future releases of gem5.
Please use the DiskImageResource class instead. This class is merely
a wrapper for it.
"""
def __init__(
self,
local_path: str,
resource_version: Optional[str] = None,
root_partition: Optional[str] = None,
metadata: Dict = {},
):
"""
:param local_path: The path of the disk image on the host system.
:param root_partition: The root disk partition to use.
:param metadata: Metadata for the resource. **Warning:** As of "
"v22.1.1, this parameter is not used.
:param resource_version: Version of the resource itself.
"""
warn(
"The `CustomDiskImageResource` class is deprecated. Please use "
"`DiskImageResource` instead."
)
if bool(metadata): # Empty dicts cast to False
warn(
"the `metadata` parameter was set via the "
"`CustomDiskImageResource` constructor. This parameter is not "
"used."
)
super().__init__(
local_path=local_path,
root_partition=root_partition,
resource_version=resource_version,
)
def Resource(
resource_id: str,
resource_directory: Optional[str] = None,
download_md5_mismatch: bool = True,
resource_version: Optional[str] = None,
clients: Optional[List[str]] = None,
) -> AbstractResource:
"""
This function was created to maintain backwards compatibility for v21.1.0
and prior releases of gem5 where `Resource` was a class.
In the interests of gem5-resource specialization, the ``Resource`` class
has been dropped. Instead users are advised to use the ``obtain_resource``
function which will return the correct AbstractResource implementation.
This function (disguised as a class) wraps this function.
"""
warn(
"`Resource` has been deprecated. Please use the `obtain_resource` "
"function instead."
)
return obtain_resource(
resource_id=resource_id,
resource_directory=resource_directory,
download_md5_mismatch=download_md5_mismatch,
resource_version=resource_version,
clients=clients,
)
_get_resource_json_type_map = {
"disk-image": DiskImageResource,
"binary": BinaryResource,
"kernel": KernelResource,
"checkpoint": CheckpointResource,
"git": GitResource,
"bootloader": BootloaderResource,
"file": FileResource,
"directory": DirectoryResource,
"simpoint": SimpointResource,
"simpoint-directory": SimpointDirectoryResource,
"resource": Resource,
"looppoint-pinpoint-csv": LooppointCsvResource,
"looppoint-json": LooppointJsonResource,
"suite": SuiteResource,
"workload": WorkloadResource,
}